GLSL shader for glossy specular reflections on an cubemapped surface

linello picture linello · Aug 3, 2012 · Viewed 18k times · Source

I wrote a shader for environmental cubemapping

*Vertex shader *

varying vec3 Normal;
varying vec3 EyeDir;
uniform samplerCube cubeMap;

void main()
{
        gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex;
        Normal = gl_NormalMatrix * gl_Normal;
        EyeDir = vec3(gl_ModelViewMatrix * gl_Vertex);
}

*Fragment shader *

varying vec3 Normal;
varying vec3 EyeDir;

uniform samplerCube cubeMap;

 void main(void)
 {
    vec3 reflectedDirection = normalize(reflect(EyeDir, normalize(Normal)));
    reflectedDirection.y = -reflectedDirection.y;
    vec4 fragColor = textureCube(cubeMap, reflectedDirection);
    gl_FragColor = fragColor;
}

This is the classical result: http://braintrekking.files.wordpress.com/2012/07/glsl_cubemapreflection.png?w=604&h=466 Now I want to add some specular white highlight in order to obtain a more glossy effect, like motherpearl. How is it possible to add this kind of highlight? Like the one in this image Should I sum a specular component to gl_FragColor? A first attempt is to compute specular reflection in vertex shader

vec3 s = normalize(vec3(gl_LightSource[0].position - EyeDir));
vec3 v = normalize(EyeDir);
vec3 r = reflect( s, Normal );
vec3 ambient = vec3(gl_LightSource[0].ambient*gl_FrontMaterial.ambient);

float sDotN = max( dot(s,Normal), 0.0 );
vec3 diffuse = vec3(gl_LightSource[0].diffuse * gl_FrontMaterial.diffuse * sDotN);
vec3 spec = vec3(0.0);
if( sDotN > 0.0 )
    spec = gl_LightSource[0].specular * gl_FrontMaterial.specular * pow( max( dot(r,v), 2.0 ), gl_FrontMaterial.shininess );

LightIntensity = 0*ambient + 0*diffuse +  spec;

and to multiply it to gl_FragColor but the effect I obtain is not convincing.

Someone has idea how to do it?

Answer

Tara picture Tara · Jan 3, 2013

Here's an example of how you could do it:

Mother-of-Pearl-Effect OFF:
enter image description here
Mother-of-Pearl-Effect ON:
enter image description here

Vertex shader:

uniform vec3 fvEyePosition;

varying vec3 ViewDirection;
varying vec3 Normal;

void main( void )
{
   gl_Position = ftransform();
   vec4 fvObjectPosition = gl_ModelViewMatrix * gl_Vertex;

   ViewDirection  = fvEyePosition - fvObjectPosition.xyz;
   Normal         = gl_NormalMatrix * gl_Normal;
}

Fragment shader:

uniform samplerCube cubeMap;

varying vec3 ViewDirection;
varying vec3 Normal;

const float mother_pearl_brightness = 1.5;

#define MOTHER_PEARL

void main( void )
{
   vec3  fvNormal         = normalize(Normal);
   vec3  fvViewDirection  = normalize(ViewDirection);
   vec3  fvReflection     = normalize(reflect(fvViewDirection, fvNormal)); 

#ifdef MOTHER_PEARL
   float view_dot_normal = max(dot(fvNormal, fvViewDirection), 0.0);
   float view_dot_normal_inverse = 1.0 - view_dot_normal;

   gl_FragColor = textureCube(cubeMap, fvReflection) * view_dot_normal;
   gl_FragColor.r += mother_pearl_brightness * textureCube(cubeMap, fvReflection + vec3(0.1, 0.0, 0.0) * view_dot_normal_inverse) * (1.0 - view_dot_normal);
   gl_FragColor.g += mother_pearl_brightness * textureCube(cubeMap, fvReflection + vec3(0.0, 0.1, 0.0) * view_dot_normal_inverse) * (1.0 - view_dot_normal);
   gl_FragColor.b += mother_pearl_brightness * textureCube(cubeMap, fvReflection + vec3(0.0, 0.0, 0.1) * view_dot_normal_inverse) * (1.0 - view_dot_normal);
#else
   gl_FragColor = textureCube(cubeMap, fvReflection);
#endif
}

Of course the way the R, G and B components are being calculated is not very correct, but I'm posting this code to show you the way, not the solution.


EDIT:

Here's the promised "proper" version of the iridescent shader:

Vertex shader:

varying vec3 v_view_direction;
varying vec3 v_normal;
varying vec2 v_texture_coordinate;

void main(void)
{
   gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
   v_texture_coordinate = gl_MultiTexCoord0.xy;
   v_view_direction = -gl_ModelViewMatrix[3].xyz;
   v_normal = gl_NormalMatrix * gl_Normal;
}

Fragment shader:

uniform samplerCube texture_reflection;
uniform sampler2D texture_iridescence;
uniform sampler2D texture_noise;

varying vec3 v_view_direction;
varying vec3 v_normal;
varying vec2 v_texture_coordinate;

const float noise_strength = 0.5;

void main(void)
{
   vec3 n_normal = normalize(v_normal);
   vec3 n_wiew_direction = normalize(v_view_direction);
   vec3 n_reflection = normalize(reflect(n_wiew_direction, n_normal)); 

   vec3 noise_vector = (texture2D(texture_noise, v_texture_coordinate).xyz - vec3(0.5)) * noise_strength;

   float inverse_dot_view = 1.0 - max(dot(normalize(n_normal + noise_vector), n_wiew_direction), 0.0);
   vec3 lookup_table_color = texture2D(texture_iridescence, vec2(inverse_dot_view, 0.0)).rgb;

   gl_FragColor.rgb = textureCube(texture_reflection, n_reflection).rgb * lookup_table_color * 2.5;
   gl_FragColor.a = 1.0;
}

Results

No iridescent effect:
enter image description here

Iridescent effect (lookup texture 1):
enter image description here

Iridescent effect (lookup texture 2):
enter image description here

Iridescence lookup texture 2: enter image description here

Noise texture:
enter image description here

Remarks:
The iridescence lookup texture could also be a 1D texture, which would be much more memory efficient.
Also, the way the noise vector is calculated is actually nonsense. The right solution would be to use bump mapping. But hey, it works! :D