GLSL Programming/Unity/Translucent Surfaces

Leaves lit from both sides: note that the missing specular reflection results in a more saturated green of the backlit leaves.

This tutorial covers translucent surfaces.

It is one of several tutorials about lighting that go beyond the Phong reflection model. However, it is based on per-pixel lighting with the Phong reflection model as described in Section “Smooth Specular Highlights”. If you haven't read that tutorial yet, you should read it first.

The Phong reflection model doesn't take translucency into account, i.e. the possibility that light is transmitted through a material. This tutorial is about translucent surfaces, i.e. surfaces that allow light to transmit from one face to the other, e.g. paper, clothes, plastic films, or leaves.

For translucent illumination, the vector V to the viewer and the vector L to the light source are on opposite sides.

Contents

We will distinguish between two kinds of light transmission: diffuse translucency and forward-scattered translucency, which correspond to the diffuse and specular terms in the Phong reflection model. Diffuse translucency is a diffuse transmission of light analogously to the diffuse reflection term in the Phong reflection model (see Section “Diffuse Reflection”): it only depends on the dot product of the surface normal vector and the direction to the light source — except that we use the negative surface normal vector since the light source is on the backside, thus the equation for the diffuse translucent illumination is:

Some translucent surfaces (e.g. plastic films) are almost transparent and allow light to shine through the surface almost directly but with some forward scattering; i.e., one can see light sources through the surface but the image is somewhat blurred. This is similar to the specular term of the Phong reflection model (see Section “Specular Highlights” for the equation) except that we replace the reflected light direction R by the negative light direction -L and the exponent nshininess{\displaystyle n_{\text{shininess}}} corresponds now to the sharpness of the forward-scattered light:

The following implementation is based on Section “Smooth Specular Highlights”, which presents per-pixel lighting with the Phong reflection model. The implementation allows for rendering backfaces and flips the surface normal vector in this case. A more elaborated version could also use different colors for the frontface and the backface (see Section “Two-Sided Smooth Surfaces”). In addition to the terms of the Phong reflection model, we also compute illumination by diffuse translucency and forward-scattered translucency. Here is the part that is specific for the fragment shader:

#ifdefFRAGMENTvoidmain(){vec3normalDirection=normalize(varyingNormalDirection);if(!gl_FrontFacing)// do we look at the backface?{normalDirection=-normalDirection;// flip normal}vec3viewDirection=normalize(_WorldSpaceCameraPos-vec3(position));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-position);floatdistance=length(vertexToLightSource);attenuation=1.0/distance;// linear attenuation lightDirection=normalize(vertexToLightSource);}// Computation of the Phong reflection model: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);}// Computation of the translucent illumination:vec3diffuseTranslucency=attenuation*vec3(_LightColor0)*vec3(_DiffuseTranslucentColor)*max(0.0,dot(lightDirection,-normalDirection));vec3forwardTranslucency;if(dot(normalDirection,lightDirection)>0.0)// light source on the wrong side?{forwardTranslucency=vec3(0.0,0.0,0.0);// no forward-scattered translucency}else// light source on the right side{forwardTranslucency=attenuation*vec3(_LightColor0)*vec3(_ForwardTranslucentColor)*pow(max(0.0,dot(-lightDirection,viewDirection)),_Sharpness);}// Computation of the complete illumination:gl_FragColor=vec4(ambientLighting+diffuseReflection+specularReflection+diffuseTranslucency+forwardTranslucency,1.0);}#endif

The complete shader code defines the shader properties for the material constants and adds another pass for additional light sources with additive blending but without the ambient lighting:

Shader"GLSLtranslucentsurfaces"{Properties{_Color("DiffuseMaterialColor",Color)=(1,1,1,1)_SpecColor("SpecularMaterialColor",Color)=(1,1,1,1)_Shininess("Shininess",Float)=10_DiffuseTranslucentColor("DiffuseTranslucentColor",Color)=(1,1,1,1)_ForwardTranslucentColor("ForwardTranslucentColor",Color)=(1,1,1,1)_Sharpness("Sharpness",Float)=10}SubShader{Pass{Tags{"LightMode"="ForwardBase"}// pass for ambient light and first light sourceCullOff// show frontfaces and backfacesGLSLPROGRAM// User-specified propertiesuniformvec4_Color;uniformvec4_SpecColor;uniformfloat_Shininess;uniformvec4_DiffuseTranslucentColor;uniformvec4_ForwardTranslucentColor;uniformfloat_Sharpness;// 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")varyingvec4position;// position of the vertex (and fragment) in world space varyingvec3varyingNormalDirection;// surface normal vector in world space#ifdefVERTEXvoidmain(){mat4modelMatrix=_Object2World;mat4modelMatrixInverse=_World2Object;// unity_Scale.w // is unnecessary because we normalize vectorsposition=modelMatrix*gl_Vertex;varyingNormalDirection=normalize(vec3(vec4(gl_Normal,0.0)*modelMatrixInverse));gl_Position=gl_ModelViewProjectionMatrix*gl_Vertex;}#endif#ifdefFRAGMENTvoidmain(){vec3normalDirection=normalize(varyingNormalDirection);if(!gl_FrontFacing)// do we look at the backface?{normalDirection=-normalDirection;// flip normal}vec3viewDirection=normalize(_WorldSpaceCameraPos-vec3(position));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-position);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);}vec3diffuseTranslucency=attenuation*vec3(_LightColor0)*vec3(_DiffuseTranslucentColor)*max(0.0,dot(lightDirection,-normalDirection));vec3forwardTranslucency;if(dot(normalDirection,lightDirection)>0.0)// light source on the wrong side?{forwardTranslucency=vec3(0.0,0.0,0.0);// no forward-scattered translucency}else// light source on the right side{forwardTranslucency=attenuation*vec3(_LightColor0)*vec3(_ForwardTranslucentColor)*pow(max(0.0,dot(-lightDirection,viewDirection)),_Sharpness);}gl_FragColor=vec4(ambientLighting+diffuseReflection+specularReflection+diffuseTranslucency+forwardTranslucency,1.0);}#endifENDGLSL}Pass{Tags{"LightMode"="ForwardAdd"}// pass for additional light sourcesCullOffBlendOneOne// additive blending GLSLPROGRAM// User-specified propertiesuniformvec4_Color;uniformvec4_SpecColor;uniformfloat_Shininess;uniformvec4_DiffuseTranslucentColor;uniformvec4_ForwardTranslucentColor;uniformfloat_Sharpness;// 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")varyingvec4position;// position of the vertex (and fragment) in world space varyingvec3varyingNormalDirection;// surface normal vector in world space#ifdefVERTEXvoidmain(){mat4modelMatrix=_Object2World;mat4modelMatrixInverse=_World2Object;// unity_Scale.w // is unnecessary because we normalize vectorsposition=modelMatrix*gl_Vertex;varyingNormalDirection=normalize(vec3(vec4(gl_Normal,0.0)*modelMatrixInverse));gl_Position=gl_ModelViewProjectionMatrix*gl_Vertex;}#endif#ifdefFRAGMENTvoidmain(){vec3normalDirection=normalize(varyingNormalDirection);if(!gl_FrontFacing)// do we look at the backface?{normalDirection=-normalDirection;// flip normal}vec3viewDirection=normalize(_WorldSpaceCameraPos-vec3(position));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-position);floatdistance=length(vertexToLightSource);attenuation=1.0/distance;// linear attenuation lightDirection=normalize(vertexToLightSource);}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);}vec3diffuseTranslucency=attenuation*vec3(_LightColor0)*vec3(_DiffuseTranslucentColor)*max(0.0,dot(lightDirection,-normalDirection));vec3forwardTranslucency;if(dot(normalDirection,lightDirection)>0.0)// light source on the wrong side?{forwardTranslucency=vec3(0.0,0.0,0.0);// no forward-scattered translucency}else// light source on the right side{forwardTranslucency=attenuation*vec3(_LightColor0)*vec3(_ForwardTranslucentColor)*pow(max(0.0,dot(-lightDirection,viewDirection)),_Sharpness);}gl_FragColor=vec4(diffuseReflection+specularReflection+diffuseTranslucency+forwardTranslucency,1.0);}#endifENDGLSL}}// The definition of a fallback shader should be commented out // during development:// Fallback "Specular"}