0. Start with my nonsense
There are some differences between "Lighting" in Unity and painting in Photoshop, at least in my mind. However, I'm glad to accept these differences, and I think it's the charm of learning art and technology at the same time!
Today I'll talk something about illumination model.
1. Per-vertex or Per-pixel?
An image of 1920x1080 contains 2073600 pixels, while it's almost impossible to find a model with so many vertices.
Per-vertex lighting can save resources, but it's too discrete and linear. When you meet something like pow(lightRange, gloss), it will be distorted. Per-pixel lighting is more like painting in Photoshop, accordingly it needs more resources.
Their codes are very similar, in this passage I'll use per-pixel lighting.
2. What do we need to light a 3D objects?
At this stage, we only consider simple Ambient, Diffuse and Specular reflection. "Simple" means there is only a single capsule, no occlusion, no chamfer, no metallic and so on.
i. Diffuse
To get how diffuse contributes to color, you need to get:
_LightColor0 —— by #include "Lighting.cginc"
_Diffuse —— by Properties
dot(worldNormal, worldLightDir)
ii. Specular reflection
To get how specular reflection contributes to color, you need to get:
_LightColor0 —— by #include "Lighting.cginc"
_Specular, _Gloss —— by Properties
dot(reflectDir, viewDir)
Let's Coding!
Phong illumination model
Start with a simple framework:
Shader "Phong"{
Properties{
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader{
Pass{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
float4 vert(){
}
float4 frag(){
}
ENDCG
}
}
Fallback "Specular"
}
How the struct works?
a2v gets every vertex and normal in model space.
v2f gives clip space the vertex, as it saves every position and normal in world space. For normal transformations, use inverse transpose matrix.
* If it's per-vertex lighting, you need to calculate COLOR in vert() and save COLOR in v2f instead.
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1; //No more affine transformations
};
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
How fragment shader works?
Use worldNormal and worldLightDir to calculate the light and shade value of Diffuse.
Use reflect(). When inputting worldLightDir, you need to reverse it, as it is originally used to describe the direction of objects to light. Then use reflectDir and viewDir to calculate Specular.
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// Phong
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
Blinn-Phong illumination model
The way to calculate Specular Reflection has changed.
// Blinn-Phong
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
FINAL CODE (Phong)
Shader "Unlit/SpecularVertexLevel"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
Outside the capsule
In this passage, you set the _Diffuse color as pure color. But if we add a texture, the Diffuse color will be more colorful!