GLSL Programming/Unity/Specular Highlights at Silhouettes: Difference between revisions

From testwiki
Jump to navigation Jump to search
imported>DannyS712
m Update syntaxhighlight tags - remove use of deprecated <source> tags
ย 
(No difference)

Latest revision as of 07:44, 16 April 2020

Template:TOC right

Photo of pedestrians in Lisbon. Note the bright silhouettes due to the backlight.

This tutorial covers the Fresnel factor for specular highlights.

It is one of several tutorials about lighting that go beyond the Phong reflection model. However, it is based on lighting with the Phong reflection model as described in Template:GLSL Programming Unity SectionRef (for per-vertex lighting) and Template:GLSL Programming Unity SectionRef (for per-pixel lighting). If you haven't read those tutorials yet, you should read them first.

Many materials (e.g. matte paper) show strong specular reflection when light grazes the surface; i.e., when backlight is reflected from the opposite direction to the viewer as in the photo Template:Hide in printTemplate:Only in print. The Fresnel factor explains this strong reflection for some materials. Of course, there are also other reasons for bright silhouettes, e.g. translucent hair or fabrics (see Template:GLSL Programming Unity SectionRef).

Interestingly, the effect is often hardly visible because it is most likely when the background of the silhouette is very bright. In this case, however, a bright silhouette will just blend into the background and thus become hardly noticeable.

In addition to most of the vectors used by the Phong reflection model, we require the normalized halfway vector H, which is the direction exactly between the direction to the viewer V and the direction to the light source L.

Schlick's Approximation of the Fresnel Factor

The Fresnel factor Fλ describes the specular reflectance of nonconducting materials for unpolarized light of wavelength λ. Schlick's approximation is:

Fλ=fλ+(1fλ)(1๐‡๐•)5

where V is the normalized direction to the viewer and H is the normalized halfway vector: H = (V + L) / |V + L| with L being the normalized direction to the light source. fλ is the reflectance for HยทV = 1, i.e. when the direction to the light source, the direction to the viewer, and the halfway vector are all identical. On the other hand, Fλ becomes 1 for HยทV = 0, i.e. when the halfway vector is orthogonal to the direction to the viewer, which means that the direction to the light source is opposite to the direction to the viewer (i.e. the case of a grazing light reflection). In fact, Fλ is independent of the wavelength in this case and the material behaves just like a perfect mirror.

Using the built-in GLSL function mix(x,y,w) = x*(1-w) + y*w we can rewrite Schlick's approximation as:

Fλ=fλ+(1fλ)(1๐‡๐•)5   =fλ(1(1๐‡๐•)5)+(1๐‡๐•)5   =mix(fλ,1,(1๐‡๐•)5)

which might be slightly more efficient, at least on some GPUs. We will take the dependency on the wavelength into account by allowing for different values of fλ for each color component; i.e. we consider it an RGB vector. In fact, we identify it with the constant material color kspecular from Template:GLSL Programming Unity SectionRef. In other words, the Fresnel factor adds a dependency of the material color kspecular on the angle between the direction to the viewer and the halfway vector. Thus, we replace the constant material color kspecular with Schlick's approximation (using fλ=kspecular) in any calculation of the specular reflection.

For example, our equation for the specular term in the Phong reflection model was (see Template:GLSL Programming Unity SectionRef):

Ispecular=Iincomingkspecularmax(0,๐‘๐•)nshininess

Replacing kspecular by Schlick's approximation for the Fresnel factor with fλ=kspecular yields:

Ispecular=Iincomingmix(kspecular,1,(1๐‡๐•)5)max(0,๐‘๐•)nshininess

Implementation

The implementation is based on the shader code from Template:GLSL Programming Unity SectionRef. It just computes the halfway vector and includes the approximation of the Fresnel factor:

            vec3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               vec3 halfwayDirection = 
                  normalize(lightDirection + viewDirection);
               float w = pow(1.0 - max(0.0, 
                  dot(halfwayDirection, viewDirection)), 5.0);
               specularReflection = attenuation * vec3(_LightColor0) 
                  * mix(vec3(_SpecColor), vec3(1.0), w)
                  * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

Complete Shader Code

Putting the code snippet from above in the complete shader from Template:GLSL Programming Unity SectionRef results in this shader:

Shader "GLSL Fresnel highlights" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
      _SpecColor ("Specular Material Color", Color) = (1,1,1,1) 
      _Shininess ("Shininess", Float) = 10
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
            // pass for ambient light and first light source

         GLSLPROGRAM

         // User-specified properties
         uniform vec4 _Color; 
         uniform vec4 _SpecColor; 
         uniform float _Shininess;

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform vec3 _WorldSpaceCameraPos; 
            // camera position in world space
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
         uniform vec4 _WorldSpaceLightPos0; 
            // direction to or position of light source
         uniform vec4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
         
         varying vec4 position; 
            // position of the vertex (and fragment) in world space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in world space

         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            position = modelMatrix * gl_Vertex;
            varyingNormalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);

            vec3 viewDirection = 
               normalize(_WorldSpaceCameraPos - vec3(position));
            vec3 lightDirection;
            float attenuation;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(vec3(_WorldSpaceLightPos0));
            } 
            else // point or spot light
            {
               vec3 vertexToLightSource = 
                  vec3(_WorldSpaceLightPos0 - position);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
            
            vec3 ambientLighting = 
               vec3(gl_LightModel.ambient) * vec3(_Color);

            vec3 diffuseReflection = 
               attenuation * vec3(_LightColor0) * vec3(_Color) 
               * max(0.0, dot(normalDirection, lightDirection));
            
            vec3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               vec3 halfwayDirection = 
                  normalize(lightDirection + viewDirection);
               float w = pow(1.0 - max(0.0, 
                  dot(halfwayDirection, viewDirection)), 5.0);
               specularReflection = attenuation * vec3(_LightColor0) 
                  * mix(vec3(_SpecColor), vec3(1.0), w) 
                  * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

            gl_FragColor = vec4(ambientLighting 
               + diffuseReflection + specularReflection, 1.0);
         }
         
         #endif

         ENDGLSL
      }

      Pass {	
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources
         Blend One One // additive blending 

         GLSLPROGRAM

         // User-specified properties
         uniform vec4 _Color; 
         uniform vec4 _SpecColor; 
         uniform float _Shininess;

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform vec3 _WorldSpaceCameraPos; 
            // camera position in world space
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
         uniform vec4 _WorldSpaceLightPos0; 
            // direction to or position of light source
         uniform vec4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
         
         varying vec4 position; 
            // position of the vertex (and fragment) in world space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in world space

         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            position = modelMatrix * gl_Vertex;
            varyingNormalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);

            vec3 viewDirection = 
               normalize(_WorldSpaceCameraPos - vec3(position));
            vec3 lightDirection;
            float attenuation;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(vec3(_WorldSpaceLightPos0));
            } 
            else // point or spot light
            {
               vec3 vertexToLightSource = 
                  vec3(_WorldSpaceLightPos0 - position);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
            
            vec3 diffuseReflection = 
               attenuation * vec3(_LightColor0) * vec3(_Color) 
               * max(0.0, dot(normalDirection, lightDirection));
            
            vec3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               vec3 halfwayDirection = 
                  normalize(lightDirection + viewDirection);
               float w = pow(1.0 - max(0.0, 
                  dot(halfwayDirection, viewDirection)), 5.0);
               specularReflection = attenuation * vec3(_LightColor0) 
                  * mix(vec3(_SpecColor), vec3(1.0), w)
                  * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

            gl_FragColor = 
               vec4(diffuseReflection + specularReflection, 1.0);
         }
         
         #endif

         ENDGLSL
      }
   } 
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Specular"
}

Artistic Control

A useful modification of the implementation above is to replace the power 5.0 by a user-specified shader property. This would give CG artists the option to exaggerate or attenuate the effect of the Fresnel factor depending on their artistic needs.

Consequences for Semitransparent Surfaces

Apart from influencing specular highlights, a Fresnel factor should also influence the opacity α of semitransparent surfaces. In fact, the Fresnel factor describes how a surface becomes more reflective for grazing light rays, which implies that less light is absorbed, refracted, or transmitted, i.e. the transparency T decreases and therefore the opacity α=1T increases. To this end, a Fresnel factor could be computed with the surface normal vector N instead of the halfway vector H and the opacity of a semitransparent surface could increase from a user-specified value α0 (for viewing in the direction of the surface normal) to 1 (independently of the wavelength) with

αFresnel=α0+(1α0)(1๐๐•)5.

In Template:GLSL Programming Unity SectionRef the opacity was considered to result from an attenuation of light as it passes through a layer of semitransparent material. This opacity should be combined with the opacity due to increased reflectivity in the following way: the total opacity αtotal is 1 minus the total transparency Ttotal which is the product of the transparency due to attenuation Tattenuation (which is 1 minus αattenuation) and the transparency due to the Fresnel factor TFresnel (which is 1 minus αFresnel), i.e.:

αtotal=1Ttotal   =1TattenuationTFresnel   =1(1αattenuation)(1αFresnel)

αFresnel is the opacity as computed above while αattenuation is the opacity as computed in Template:GLSL Programming Unity SectionRef. For the view direction parallel to the surface normal vector, αtotal and α0 could be specified by the user. Then the equation fixes αattenuation for the normal direction and, in fact, it fixes all constants and therefore αtotal can be computed for all view directions. Note that neither the diffuse reflection nor the specular reflection should be multiplied with the opacity αtotal since the specular reflection is already multiplied with the Fresnel factor and the diffuse reflection should only be multiplied with the opacity due to attenuation αattenuation.

Summary

Congratulations, you finished one of the somewhat advanced tutorials! We have seen:

  • What the Fresnel factor is.
  • What Schlick's approximation to the Fresnel factor is.
  • How to implement Schlick's approximation for specular highlights.
  • How to add more artistic control to the implementation.
  • How to use the Fresnel factor for semitransparent surfaces.

Further Reading

If you still want to know more

  • about lighting with the Phong reflection model, you should read Template:GLSL Programming Unity SectionRef.
  • about per-pixel lighting (i.e. Phong shading), you should read Template:GLSL Programming Unity SectionRef.
  • about Schlick's approximation, you should read his article โ€œAn inexpensive BRDF model for physically-based renderingโ€ by Christophe Schlick, Computer Graphics Forum, 13(3):233โ€”246, 1994. or you could read Section 14.1 of the book โ€œOpenGL Shading Languageโ€ (3rd edition) by Randi Rost and others, published 2009 by Addison-Wesley, or Section 5 in the Lighting chapter of the book โ€œProgramming Vertex, Geometry, and Pixel Shadersโ€ (2nd edition, 2008) by Wolfgang Engel, Jack Hoxley, Ralf Kornmann, Niko Suni, and Jason Zink (which is available online.)


Template:GLSL Programming BottomNav