# Cg Programming/Unity/Two-Sided Surfaces

An algebraic surface that includes an oloid in the center. The rendering uses different colors for the two sides of the surface.

This tutorial covers two-sided per-vertex lighting.

It's part of a series of tutorials about basic lighting in Unity. In this tutorial, we extend Section “Specular Highlights” to render two-sided surfaces. If you haven't read Section “Specular Highlights”, this would be a very good time to read it.

### Two-Sided LightingEdit

As shown by the figure of the algebraic surface, it's sometimes useful to apply different colors to the two sides of a surface. In Section “Cutaways”, we have seen how two passes with front face culling and back face culling can be used to apply different shaders to the two sides of a mesh. We will apply the same strategy here.

As mentioned in Section “Cutaways”, an alternative approach in Cg is to use a fragment input parameter with semantic FACE, VFACE, or SV_IsFrontFacing (depending on the API) to distinguish between the two sides, however, this doesn't seem to work in Unity.

The shader code for two-sided per-vertex lighting is a straightforward extension of the code in Section “Specular Highlights”. It requires two sets of material parameters (front and back) and duplicates all passes — one copy with front-face culling and the other with back-face culling. The shaders of the two copies are identical except that the shader for the back faces uses the negated surface normal vector and the properties for the back material.

Shader "Cg two-sided per-vertex lighting" {
Properties {
_Color ("Front Material Diffuse Color", Color) = (1,1,1,1)
_SpecColor ("Front Material Specular Color", Color) = (1,1,1,1)
_Shininess ("Front Material 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
}
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

#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")

// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
uniform float4 _BackColor;
uniform float4 _BackSpecColor;
uniform float _BackShininess;

struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : COLOR;
};

vertexOutput vert(vertexInput input)
{
vertexOutput output;

float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
// multiplication with unity_Scale.w is unnecessary
// because we normalize transformed vectors

float3 normalDirection = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
float3 viewDirection = normalize(_WorldSpaceCameraPos
- mul(modelMatrix, input.vertex).xyz);
float3 lightDirection;
float attenuation;

if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource = _WorldSpaceLightPos0.xyz
- mul(modelMatrix, input.vertex).xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}

float3 ambientLighting =
UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;

float3 diffuseReflection =
attenuation * _LightColor0.rgb * _Color.rgb
* 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 * _LightColor0.rgb
* _SpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}

output.col = float4(ambientLighting + diffuseReflection
+ specularReflection, 1.0);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
return output;
}

float4 frag(vertexOutput input) : COLOR
{
return input.col;
}

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

#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")

// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
uniform float4 _BackColor;
uniform float4 _BackSpecColor;
uniform float _BackShininess;

struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : COLOR;
};

vertexOutput vert(vertexInput input)
{
vertexOutput output;

float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
// multiplication with unity_Scale.w is unnecessary
// because we normalize transformed vectors

float3 normalDirection = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
float3 viewDirection = normalize(_WorldSpaceCameraPos
- mul(modelMatrix, input.vertex).xyz);
float3 lightDirection;
float attenuation;

if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource = _WorldSpaceLightPos0.xyz
- mul(modelMatrix, input.vertex).xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}

float3 diffuseReflection =
attenuation * _LightColor0.rgb * _Color.rgb
* 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 * _LightColor0.rgb
* _SpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}

output.col = float4(diffuseReflection
+ specularReflection, 1.0);
// no ambient contribution in this pass
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
return output;
}

float4 frag(vertexOutput input) : COLOR
{
return input.col;
}

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

#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")

// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
uniform float4 _BackColor;
uniform float4 _BackSpecColor;
uniform float _BackShininess;

struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : COLOR;
};

vertexOutput vert(vertexInput input)
{
vertexOutput output;

float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
// multiplication with unity_Scale.w is unnecessary
// because we normalize transformed vectors

float3 normalDirection = normalize(
mul(float4(-input.normal, 0.0), modelMatrixInverse).xyz);
float3 viewDirection = normalize(_WorldSpaceCameraPos
- mul(modelMatrix, input.vertex).xyz);
float3 lightDirection;
float attenuation;

if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource = _WorldSpaceLightPos0.xyz
- mul(modelMatrix, input.vertex).xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}

float3 ambientLighting =
UNITY_LIGHTMODEL_AMBIENT.rgb * _BackColor.rgb;

float3 diffuseReflection =
attenuation * _LightColor0.rgb * _BackColor.rgb
* 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 * _LightColor0.rgb
* _BackSpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _BackShininess);
}

output.col = float4(ambientLighting + diffuseReflection
+ specularReflection, 1.0);
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
return output;
}

float4 frag(vertexOutput input) : COLOR
{
return input.col;
}

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

#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")

// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
uniform float4 _BackColor;
uniform float4 _BackSpecColor;
uniform float _BackShininess;

struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : COLOR;
};

vertexOutput vert(vertexInput input)
{
vertexOutput output;

float4x4 modelMatrix = _Object2World;
float4x4 modelMatrixInverse = _World2Object;
// multiplication with unity_Scale.w is unnecessary
// because we normalize transformed vectors

float3 normalDirection = normalize(
mul(float4(-input.normal, 0.0), modelMatrixInverse).xyz);
float3 viewDirection = normalize(_WorldSpaceCameraPos
- mul(modelMatrix, input.vertex).xyz);
float3 lightDirection;
float attenuation;

if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource = _WorldSpaceLightPos0.xyz
- mul(modelMatrix, input.vertex).xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}

float3 diffuseReflection =
attenuation * _LightColor0.rgb * _BackColor.rgb
* 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 * _LightColor0.rgb
* _BackSpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _BackShininess);
}

output.col = float4(diffuseReflection
+ specularReflection, 1.0);
// no ambient contribution in this pass
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
return output;
}

float4 frag(vertexOutput input) : COLOR
{
return input.col;
}

ENDCG
}

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

This code consists of four passes, where the first pair of passes renders the front faces, and the second pair of passes renders the back faces using the negated normal vector and the back material properties. The second pass of each pair is the same as the first apart from the additive blending and the missing ambient color.

### SummaryEdit

Congratulations, you made it to the end of this short tutorial with a long shader. We have seen:

• How to use front-face culling and back-face culling to apply different shaders on the two sides of a mesh.
• How to change the Phong lighting computation for back-facing triangles.