Cg Programming/Unity/Two-Sided Smooth Surfaces
This tutorial covers two-sided per-pixel lighting (i.e. two-sided Phong shading).
Here we combine the per-pixel lighting discussed in Section “Smooth Specular Highlights” with the two-sided lighting discussed in Section “Two-Sided Surfaces”.
Shader Coder
The required changes to the code of Section “Smooth Specular Highlights” are: new properties for the back material, duplication of the passes with front-face culling for one copy and back-face culling for the other copy, and negation of the surface normal vector for the rendering of the back faces. It's actually quite straightforward. The code looks like this:
Shader "Cg two-sided per-pixel lighting" { Properties { _Color ("Diffuse Material Color", Color) = (1,1,1,1) _SpecColor ("Specular Material Color", Color) = (1,1,1,1) _Shininess ("Shininess", Float) = 10 _BackColor ("Back Material Diffuse Color", Color) = (1,1,1,1) _BackSpecColor ("Back Material Specular Color", Color) = (1,1,1,1) _BackShininess ("Back Material Shininess", Float) = 10 } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } // pass for ambient light and first light source Cull Back // render only front faces CGPROGRAM #pragma vertex vert #pragma fragment frag // User-specified properties uniform float4 _Color; uniform float4 _SpecColor; uniform float _Shininess; uniform float4 _BackColor; uniform float4 _BackSpecColor; uniform float _BackShininess; // The following built-in uniforms (apart from _LightColor0) // are defined in "UnityCG.cginc", which could be #included uniform float4 unity_Scale; // w = 1/scale; see _World2Object uniform float3 _WorldSpaceCameraPos; uniform float4x4 _Object2World; // model matrix uniform float4x4 _World2Object; // inverse model matrix // (all but the bottom-right element have to be scaled // with unity_Scale.w if scaling is important) uniform float4 _WorldSpaceLightPos0; // position or direction of light source uniform float4 _LightColor0; // color of light source (from "Lighting.cginc") struct vertexInput { float4 vertex : POSITION; float3 normal : NORMAL; }; struct vertexOutput { float4 pos : SV_POSITION; float4 posWorld : TEXCOORD0; float3 normalDir : TEXCOORD1; }; vertexOutput vert(vertexInput input) { vertexOutput output; float4x4 modelMatrix = _Object2World; float4x4 modelMatrixInverse = _World2Object; // multiplication with unity_Scale.w is unnecessary // because we normalize transformed vectors output.posWorld = mul(modelMatrix, input.vertex); output.normalDir = normalize(float3( mul(float4(input.normal, 0.0), modelMatrixInverse))); output.pos = mul(UNITY_MATRIX_MVP, input.vertex); return output; } float4 frag(vertexOutput input) : COLOR { float3 normalDirection = normalize(input.normalDir); float3 viewDirection = normalize( _WorldSpaceCameraPos - float3(input.posWorld)); float3 lightDirection; float attenuation; if (0.0 == _WorldSpaceLightPos0.w) // directional light? { attenuation = 1.0; // no attenuation lightDirection = normalize(float3(_WorldSpaceLightPos0)); } else // point or spot light { float3 vertexToLightSource = float3(_WorldSpaceLightPos0 - input.posWorld); float distance = length(vertexToLightSource); attenuation = 1.0 / distance; // linear attenuation lightDirection = normalize(vertexToLightSource); } float3 ambientLighting = float3(UNITY_LIGHTMODEL_AMBIENT) * float3(_Color); float3 diffuseReflection = attenuation * float3(_LightColor0) * float3(_Color) * max(0.0, dot(normalDirection, lightDirection)); float3 specularReflection; if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side? { specularReflection = float3(0.0, 0.0, 0.0); // no specular reflection } else // light source on the right side { specularReflection = attenuation * float3(_LightColor0) * float3(_SpecColor) * pow(max(0.0, dot( reflect(-lightDirection, normalDirection), viewDirection)), _Shininess); } return float4(ambientLighting + diffuseReflection + specularReflection, 1.0); } ENDCG } Pass { Tags { "LightMode" = "ForwardAdd" } // pass for additional light sources Blend One One // additive blending Cull Back // render only front faces CGPROGRAM #pragma vertex vert #pragma fragment frag // User-specified properties uniform float4 _Color; uniform float4 _SpecColor; uniform float _Shininess; uniform float4 _BackColor; uniform float4 _BackSpecColor; uniform float _BackShininess; // The following built-in uniforms (apart from _LightColor0) // are defined in "UnityCG.cginc", which could be #included uniform float4 unity_Scale; // w = 1/scale; see _World2Object uniform float3 _WorldSpaceCameraPos; uniform float4x4 _Object2World; // model matrix uniform float4x4 _World2Object; // inverse model matrix // (all but the bottom-right element have to be scaled // with unity_Scale.w if scaling is important) uniform float4 _WorldSpaceLightPos0; // position or direction of light source uniform float4 _LightColor0; // color of light source (from "Lighting.cginc") struct vertexInput { float4 vertex : POSITION; float3 normal : NORMAL; }; struct vertexOutput { float4 pos : SV_POSITION; float4 posWorld : TEXCOORD0; float3 normalDir : TEXCOORD1; }; vertexOutput vert(vertexInput input) { vertexOutput output; float4x4 modelMatrix = _Object2World; float4x4 modelMatrixInverse = _World2Object; // multiplication with unity_Scale.w is unnecessary // because we normalize transformed vectors output.posWorld = mul(modelMatrix, input.vertex); output.normalDir = normalize(float3( mul(float4(input.normal, 0.0), modelMatrixInverse))); output.pos = mul(UNITY_MATRIX_MVP, input.vertex); return output; } float4 frag(vertexOutput input) : COLOR { float3 normalDirection = normalize(input.normalDir); float3 viewDirection = normalize( _WorldSpaceCameraPos - float3(input.posWorld)); float3 lightDirection; float attenuation; if (0.0 == _WorldSpaceLightPos0.w) // directional light? { attenuation = 1.0; // no attenuation lightDirection = normalize(float3(_WorldSpaceLightPos0)); } else // point or spot light { float3 vertexToLightSource = float3(_WorldSpaceLightPos0 - input.posWorld); float distance = length(vertexToLightSource); attenuation = 1.0 / distance; // linear attenuation lightDirection = normalize(vertexToLightSource); } float3 diffuseReflection = attenuation * float3(_LightColor0) * float3(_Color) * max(0.0, dot(normalDirection, lightDirection)); float3 specularReflection; if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side? { specularReflection = float3(0.0, 0.0, 0.0); // no specular reflection } else // light source on the right side { specularReflection = attenuation * float3(_LightColor0) * float3(_SpecColor) * pow(max(0.0, dot( reflect(-lightDirection, normalDirection), viewDirection)), _Shininess); } return float4(diffuseReflection + specularReflection, 1.0); // no ambient lighting in this pass } ENDCG } Pass { Tags { "LightMode" = "ForwardBase" } // pass for ambient light and first light source Cull Front // render only back faces CGPROGRAM #pragma vertex vert #pragma fragment frag // User-specified properties uniform float4 _Color; uniform float4 _SpecColor; uniform float _Shininess; uniform float4 _BackColor; uniform float4 _BackSpecColor; uniform float _BackShininess; // The following built-in uniforms (apart from _LightColor0) // are defined in "UnityCG.cginc", which could be #included uniform float4 unity_Scale; // w = 1/scale; see _World2Object uniform float3 _WorldSpaceCameraPos; uniform float4x4 _Object2World; // model matrix uniform float4x4 _World2Object; // inverse model matrix // (all but the bottom-right element have to be scaled // with unity_Scale.w if scaling is important) uniform float4 _WorldSpaceLightPos0; // position or direction of light source uniform float4 _LightColor0; // color of light source (from "Lighting.cginc") struct vertexInput { float4 vertex : POSITION; float3 normal : NORMAL; }; struct vertexOutput { float4 pos : SV_POSITION; float4 posWorld : TEXCOORD0; float3 normalDir : TEXCOORD1; }; vertexOutput vert(vertexInput input) { vertexOutput output; float4x4 modelMatrix = _Object2World; float4x4 modelMatrixInverse = _World2Object; // multiplication with unity_Scale.w is unnecessary // because we normalize transformed vectors output.posWorld = mul(modelMatrix, input.vertex); output.normalDir = normalize(float3( mul(float4(-input.normal, 0.0), modelMatrixInverse))); // negate input.normal for the back faces output.pos = mul(UNITY_MATRIX_MVP, input.vertex); return output; } float4 frag(vertexOutput input) : COLOR { float3 normalDirection = normalize(input.normalDir); float3 viewDirection = normalize( _WorldSpaceCameraPos - float3(input.posWorld)); float3 lightDirection; float attenuation; if (0.0 == _WorldSpaceLightPos0.w) // directional light? { attenuation = 1.0; // no attenuation lightDirection = normalize(float3(_WorldSpaceLightPos0)); } else // point or spot light { float3 vertexToLightSource = float3(_WorldSpaceLightPos0 - input.posWorld); float distance = length(vertexToLightSource); attenuation = 1.0 / distance; // linear attenuation lightDirection = normalize(vertexToLightSource); } float3 ambientLighting = float3(UNITY_LIGHTMODEL_AMBIENT) * float3(_BackColor); float3 diffuseReflection = attenuation * float3(_LightColor0) * float3(_BackColor) * max(0.0, dot(normalDirection, lightDirection)); float3 specularReflection; if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side? { specularReflection = float3(0.0, 0.0, 0.0); // no specular reflection } else // light source on the right side { specularReflection = attenuation * float3(_LightColor0) * float3(_BackSpecColor) * pow(max(0.0, dot( reflect(-lightDirection, normalDirection), viewDirection)), _BackShininess); } return float4(ambientLighting + diffuseReflection + specularReflection, 1.0); } ENDCG } Pass { Tags { "LightMode" = "ForwardAdd" } // pass for additional light sources Blend One One // additive blending Cull Front // render only back faces CGPROGRAM #pragma vertex vert #pragma fragment frag // User-specified properties uniform float4 _Color; uniform float4 _SpecColor; uniform float _Shininess; uniform float4 _BackColor; uniform float4 _BackSpecColor; uniform float _BackShininess; // The following built-in uniforms (apart from _LightColor0) // are defined in "UnityCG.cginc", which could be #included uniform float4 unity_Scale; // w = 1/scale; see _World2Object uniform float3 _WorldSpaceCameraPos; uniform float4x4 _Object2World; // model matrix uniform float4x4 _World2Object; // inverse model matrix // (all but the bottom-right element have to be scaled // with unity_Scale.w if scaling is important) uniform float4 _WorldSpaceLightPos0; // position or direction of light source uniform float4 _LightColor0; // color of light source (from "Lighting.cginc") struct vertexInput { float4 vertex : POSITION; float3 normal : NORMAL; }; struct vertexOutput { float4 pos : SV_POSITION; float4 posWorld : TEXCOORD0; float3 normalDir : TEXCOORD1; }; vertexOutput vert(vertexInput input) { vertexOutput output; float4x4 modelMatrix = _Object2World; float4x4 modelMatrixInverse = _World2Object; // multiplication with unity_Scale.w is unnecessary // because we normalize transformed vectors output.posWorld = mul(modelMatrix, input.vertex); output.normalDir = normalize(float3( mul(float4(-input.normal, 0.0), modelMatrixInverse))); // negate input.normal for the back faces output.pos = mul(UNITY_MATRIX_MVP, input.vertex); return output; } float4 frag(vertexOutput input) : COLOR { float3 normalDirection = normalize(input.normalDir); float3 viewDirection = normalize( _WorldSpaceCameraPos - float3(input.posWorld)); float3 lightDirection; float attenuation; if (0.0 == _WorldSpaceLightPos0.w) // directional light? { attenuation = 1.0; // no attenuation lightDirection = normalize(float3(_WorldSpaceLightPos0)); } else // point or spot light { float3 vertexToLightSource = float3(_WorldSpaceLightPos0 - input.posWorld); float distance = length(vertexToLightSource); attenuation = 1.0 / distance; // linear attenuation lightDirection = normalize(vertexToLightSource); } float3 diffuseReflection = attenuation * float3(_LightColor0) * float3(_BackColor) * max(0.0, dot(normalDirection, lightDirection)); float3 specularReflection; if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side? { specularReflection = float3(0.0, 0.0, 0.0); // no specular reflection } else // light source on the right side { specularReflection = attenuation * float3(_LightColor0) * float3(_BackSpecColor) * pow(max(0.0, dot( reflect(-lightDirection, normalDirection), viewDirection)), _BackShininess); } return float4(diffuseReflection + specularReflection, 1.0); // no ambient lighting in this pass } ENDCG } } // The definition of a fallback shader should be commented out // during development: // Fallback "Specular" }
Summary
Congratulations, you have reached the end of this short tutorial. We have seen:
- How two-sided surfaces can be rendered with per-pixel lighting.
Further Reading
If you still want to know more
- about the shader version for single-sided per-pixel lighting, you should read Section “Smooth Specular Highlights”.
- about the shader version for two-sided per-vertex lighting, you should read Section “Two-Sided Surfaces”.
Unless stated otherwise, all example source code on this page is granted to the public domain.
Last modified on 18 August 2012, at 16:53