GLSL Programming/Unity/Translucent Surfaces
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.
Diffuse Translucency
editWe 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:
This is the most common illumination for many translucent surfaces, e.g. paper and leaves.
Forward-Scattered Translucency
editSome 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 corresponds now to the sharpness of the forward-scattered light:
Of course, this model of forward-scattered translucency is not accurate at all but it allows us to fake the effect and tweak the parameters.
Implementation
editThe 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:
#ifdef FRAGMENT
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
if (!gl_FrontFacing) // do we look at the backface?
{
normalDirection = -normalDirection; // flip normal
}
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);
}
// Computation of the Phong reflection model:
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
{
specularReflection = attenuation * vec3(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
// Computation of the translucent illumination:
vec3 diffuseTranslucency = attenuation * vec3(_LightColor0)
* vec3(_DiffuseTranslucentColor)
* max(0.0, dot(lightDirection, -normalDirection));
vec3 forwardTranslucency;
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
Complete Shader Code
editThe 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 "GLSL translucent surfaces" {
Properties {
_Color ("Diffuse Material Color", Color) = (1,1,1,1)
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
_Shininess ("Shininess", Float) = 10
_DiffuseTranslucentColor ("Diffuse Translucent Color", Color)
= (1,1,1,1)
_ForwardTranslucentColor ("Forward Translucent Color", Color)
= (1,1,1,1)
_Sharpness ("Sharpness", Float) = 10
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
// pass for ambient light and first light source
Cull Off // show frontfaces and backfaces
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _DiffuseTranslucentColor;
uniform vec4 _ForwardTranslucentColor;
uniform float _Sharpness;
// 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);
if (!gl_FrontFacing) // do we look at the backface?
{
normalDirection = -normalDirection; // flip normal
}
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
{
specularReflection = attenuation * vec3(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
vec3 diffuseTranslucency = attenuation * vec3(_LightColor0)
* vec3(_DiffuseTranslucentColor)
* max(0.0, dot(lightDirection, -normalDirection));
vec3 forwardTranslucency;
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);
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources
Cull Off
Blend One One // additive blending
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform vec4 _SpecColor;
uniform float _Shininess;
uniform vec4 _DiffuseTranslucentColor;
uniform vec4 _ForwardTranslucentColor;
uniform float _Sharpness;
// 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);
if (!gl_FrontFacing) // do we look at the backface?
{
normalDirection = -normalDirection; // flip normal
}
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
{
specularReflection = attenuation * vec3(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
vec3 diffuseTranslucency = attenuation * vec3(_LightColor0)
* vec3(_DiffuseTranslucentColor)
* max(0.0, dot(lightDirection, -normalDirection));
vec3 forwardTranslucency;
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);
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Specular"
}
Summary
editCongratulations! You finished this tutorial on translucent surfaces, which are very common but cannot be modeled by the Phong reflection model. We have covered:
- What translucent surfaces are.
- Which forms of translucency are most common (diffuse translucency and forward-scattered translucency).
- How to implement diffuse and forward-scattered translucency.
Further Reading
editIf you still want to know more
- about the diffuse term of the Phong reflection model, you should read Section “Diffuse Reflection”.
- about the ambient or the specular term of the Phong reflection model, you should read Section “Specular Highlights”.
- about per-pixel lighting with the Phong reflection model, you should read Section “Smooth Specular Highlights”.
- about per-pixel lighting of two-sided surfaces, you should read Section “Two-Sided Smooth Surfaces”.