GLSL Programming/Unity/Specular Highlights

This tutorial covers per-vertex lighting (also known as Gouraud shading) using the Phong reflection model.

It extends the shader code in Section “Diffuse Reflection” by two additional terms: ambient lighting and specular reflection. Together, the three terms constitute the Phong reflection model. If you haven't read Section “Diffuse Reflection”, this would be a very good opportunity to read it.

Contents

Consider the painting by Caravaggio to the left. While large parts of the white shirt are in shadows, no part of it is completely black. Apparently there is always some light being reflected from walls and other objects to illuminate everything in the scene — at least to a certain degree. In the Phong reflection model, this effect is taken into account by ambient lighting, which depends on a general ambient light intensity Iambient light{\displaystyle I_{\text{ambient light}}} and the material color kdiffuse{\displaystyle k_{\text{diffuse}}} for diffuse reflection. In an equation for the intensity of ambient lighting Iambient{\displaystyle I_{\text{ambient}}}:

Analogously to the equation for diffuse reflection in Section “Diffuse Reflection”, this equation can also be interpreted as a vector equation for the red, green, and blue components of light.

In Unity, the ambient light is specified by choosing Edit > Render Settings(In Unity5 by choosing Window > Lighting) from the main menu. In a GLSL shader in Unity, this color is always available as gl_LightModel.ambient, which is one of the pre-defined uniforms of the OpenGL compatibility profile mentioned in Section “Shading in World Space”.

The computation of the specular reflection requires the surface normal vector N, the direction to the light source L, the reflected direction to the light source R, and the direction to the viewer V.

If you have a closer look at Caravaggio's painting, you will see several specular highlights: on the nose, on the hair, on the lips, on the lute, on the violin, on the bow, on the fruits, etc. The Phong reflection model includes a specular reflection term that can simulate such highlights on shiny surfaces; it even includes a parameter nshininess{\displaystyle n_{\text{shininess}}} to specify a shininess of the material. The shininess specifies how small the highlights are: the shinier, the smaller the highlights.

A perfectly shiny surface will reflect light from the light source only in the geometrically reflected direction R. For less than perfectly shiny surfaces, light is reflected to directions around R: the smaller the shininess, the wider the spreading. Mathematically, the normalized reflected direction R is defined by:

for a normalized surface normal vector N and a normalized direction to the light source L. In GLSL, the function vec3 reflect(vec3 I, vec3 N) (or vec4 reflect(vec4 I, vec4 N)) computes the same reflected vector but for the direction I from the light source to the point on the surface. Thus, we have to negate our direction L to use this function.

The specular reflection term computes the specular reflection in the direction of the viewer V. As discussed above, the intensity should be large if V is close to R, where “closeness” is parametrized by the shininess nshininess{\displaystyle n_{\text{shininess}}}. In the Phong reflection model, the cosine of the angle between R and V to the nshininess{\displaystyle n_{\text{shininess}}}-th power is used to generate highlights of different shininess. Similarly to the case of the diffuse reflection, we should clamp negative cosines to 0. Furthermore, the specular term requires a material color kspecular{\displaystyle k_{\text{specular}}} for the specular reflection, which is usually just white such that all highlights have the color of the incoming light Iincoming{\displaystyle I_{\text{incoming}}}. For example, all highlights in Caravaggio's painting are white. The specular term of the Phong reflection model is then:

The shader code for the ambient lighting is straightforward with a component-wise vector-vector product:

vec3ambientLighting=vec3(gl_LightModel.ambient)*vec3(_Color);

For the implementation of the specular reflection, we require the direction to the viewer in world space, which we can compute as the difference between the camera position and the vertex position (both in world space). The camera position in world space is provided by Unity in the uniform _WorldSpaceCameraPos; the vertex position can be transformed to world space as discussed in Section “Diffuse Reflection”. The equation of the specular term in world space could then be implemented like this:

vec3viewDirection=normalize(vec3(vec4(_WorldSpaceCameraPos,1.0)-modelMatrix*gl_Vertex));vec3specularReflection;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{specularReflection=attenuation*vec3(_LightColor0)*vec3(_SpecColor)*pow(max(0.0,dot(reflect(-lightDirection,normalDirection),viewDirection)),_Shininess);}

This code snippet uses the same variables as the shader code in Section “Diffuse Reflection” and additionally the user-specified properties _SpecColor and _Shininess. (The names were specifically chosen such that the fallback shader can access them; see the discussion in Section “Diffuse Reflection”.) pow(a, b) computes ab{\displaystyle a^{b}}.

If the ambient lighting is added to the first pass (we only need it once) and the specular reflection is added to both passes of the full shader of Section “Diffuse Reflection”, it looks like this:

Shader"GLSLper-vertexlighting"{Properties{_Color("DiffuseMaterialColor",Color)=(1,1,1,1)_SpecColor("SpecularMaterialColor",Color)=(1,1,1,1)_Shininess("Shininess",Float)=10}SubShader{Pass{Tags{"LightMode"="ForwardBase"}// pass for ambient light and first light sourceGLSLPROGRAM// User-specified propertiesuniformvec4_Color;uniformvec4_SpecColor;uniformfloat_Shininess;// The following built-in uniforms (except _LightColor0) // are also defined in "UnityCG.glslinc", // i.e. one could #include "UnityCG.glslinc" uniformvec3_WorldSpaceCameraPos;// camera position in world spaceuniformmat4_Object2World;// model matrixuniformmat4_World2Object;// inverse model matrixuniformvec4_WorldSpaceLightPos0;// direction to or position of light sourceuniformvec4_LightColor0;// color of light source (from "Lighting.cginc")varyingvec4color;// the Phong lighting computed in the vertex shader#ifdefVERTEXvoidmain(){mat4modelMatrix=_Object2World;mat4modelMatrixInverse=_World2Object;// unity_Scale.w // is unnecessary because we normalize vectorsvec3normalDirection=normalize(vec3(vec4(gl_Normal,0.0)*modelMatrixInverse));vec3viewDirection=normalize(vec3(vec4(_WorldSpaceCameraPos,1.0)-modelMatrix*gl_Vertex));vec3lightDirection;floatattenuation;if(0.0==_WorldSpaceLightPos0.w)// directional light?{attenuation=1.0;// no attenuationlightDirection=normalize(vec3(_WorldSpaceLightPos0));}else// point or spot light{vec3vertexToLightSource=vec3(_WorldSpaceLightPos0-modelMatrix*gl_Vertex);floatdistance=length(vertexToLightSource);attenuation=1.0/distance;// linear attenuation lightDirection=normalize(vertexToLightSource);}vec3ambientLighting=vec3(gl_LightModel.ambient)*vec3(_Color);vec3diffuseReflection=attenuation*vec3(_LightColor0)*vec3(_Color)*max(0.0,dot(normalDirection,lightDirection));vec3specularReflection;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{specularReflection=attenuation*vec3(_LightColor0)*vec3(_SpecColor)*pow(max(0.0,dot(reflect(-lightDirection,normalDirection),viewDirection)),_Shininess);}color=vec4(ambientLighting+diffuseReflection+specularReflection,1.0);gl_Position=gl_ModelViewProjectionMatrix*gl_Vertex;}#endif#ifdefFRAGMENTvoidmain(){gl_FragColor=color;}#endifENDGLSL}Pass{Tags{"LightMode"="ForwardAdd"}// pass for additional light sourcesBlendOneOne// additive blending GLSLPROGRAM// User-specified propertiesuniformvec4_Color;uniformvec4_SpecColor;uniformfloat_Shininess;// The following built-in uniforms (except _LightColor0) // are also defined in "UnityCG.glslinc", // i.e. one could #include "UnityCG.glslinc" uniformvec3_WorldSpaceCameraPos;// camera position in world spaceuniformmat4_Object2World;// model matrixuniformmat4_World2Object;// inverse model matrixuniformvec4_WorldSpaceLightPos0;// direction to or position of light sourceuniformvec4_LightColor0;// color of light source (from "Lighting.cginc")varyingvec4color;// the diffuse lighting computed in the vertex shader#ifdefVERTEXvoidmain(){mat4modelMatrix=_Object2World;mat4modelMatrixInverse=_World2Object;// unity_Scale.w // is unnecessary because we normalize vectorsvec3normalDirection=normalize(vec3(vec4(gl_Normal,0.0)*modelMatrixInverse));vec3viewDirection=normalize(vec3(vec4(_WorldSpaceCameraPos,1.0)-modelMatrix*gl_Vertex));vec3lightDirection;floatattenuation;if(0.0==_WorldSpaceLightPos0.w)// directional light?{attenuation=1.0;// no attenuationlightDirection=normalize(vec3(_WorldSpaceLightPos0));}else// point or spot light{vec3vertexToLightSource=vec3(_WorldSpaceLightPos0-modelMatrix*gl_Vertex);floatdistance=length(vertexToLightSource);attenuation=1.0/distance;// linear attenuation lightDirection=normalize(vertexToLightSource);}// vec3 ambientLighting = // vec3(gl_LightModel.ambient) * vec3(_Color);vec3diffuseReflection=attenuation*vec3(_LightColor0)*vec3(_Color)*max(0.0,dot(normalDirection,lightDirection));vec3specularReflection;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{specularReflection=attenuation*vec3(_LightColor0)*vec3(_SpecColor)*pow(max(0.0,dot(reflect(-lightDirection,normalDirection),viewDirection)),_Shininess);}color=vec4(diffuseReflection+specularReflection,1.0);// no ambient lighting in this passgl_Position=gl_ModelViewProjectionMatrix*gl_Vertex;}#endif#ifdefFRAGMENTvoidmain(){gl_FragColor=color;}#endifENDGLSL}}// The definition of a fallback shader should be commented out // during development:// Fallback "Specular"}