【Unity Shader学习笔记】Unity光照基础-漫反射光照
本代码只适用于平行光。
1、逐顶点漫反射光照
1.1漫反射光照原理
1.2代码实现
在Properties语义块中声明一个漫反射颜色属性
Properties
{
//漫反射参数,用于调整漫反射效果
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
在SubShader语义块中定义一个Pass语义块。
在Pass的第一行指明光照模式。
Tags{"LightMode" = "ForwardBase"}
在Pass中,使用CGPROGRAM与ENDCG包围Cg代码片,并告诉Unity顶点着色器与片元着色器的名字。
使用#include包含Unity的内置文件。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//包含内置文件
#include "Lighting.cginc"
ENGCG
接着在CGPROGRAM与ENDCG中定义此前在Properties中定义的属性。
fixed4 _Diffuse;
定义输入与输出结构体
struct a2v
{
//模型变换
float4 vertex_position : POSITION;
//法线方向(计算)
float3 normal : NORMAL;
};
struct v2f
{
//输出顶点位置
float4 pos : SV_POSITION;
//存储顶点着色器输出的颜色
fixed3 color_in : COLOR;
};
主要计算都集中在顶点着色器。在顶点着色器中,我们有两个目标:把坐标从模型空间转换至裁剪空间中,以及输出漫反射计算之后的color。
坐标变换很简单。代码如下:
//pos变换
o.pos = UnityObjectToClipPos(v.vertex_position);
计算颜色要复杂一些。根据最上方的公式,我们一共需要四个变量。_Diffuse变量我们已经定义好,光源颜色不需要运算(_LightColor0.rgb可直接得到),接下来需要获取世界坐标下的光源方向、世界坐标下的法线方向。
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//法线原本是模型空间下的,需要变换至世界坐标,因此需要mul()
//光源方向本来就是世界坐标下的,因此不需要mul()
//使用normalize()进行归一化
随后可以进行最重要的漫反射运算
防止法线和光源方向点乘的结果为负值,将其截取到0,这可以防止物体被从后面来的光源照亮。
saturate函数是Cg提供的函数,可以将参数截取在[0,1]的范围内。
dot()函数点乘两个向量。
//漫反射部分计算
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
加上环境光输出
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
return o;
别忘了在shader最后Fallback。FallBack "Diffuse"
最终写成如下代码:
Shader "Practise/DiffuseVertexShader"
{
Properties
{
//漫反射参数,用于调整漫反射效果
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
//包含内置文件
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
struct a2v
{
//模型变换
float4 vertex_position : POSITION;
//法线方向(计算)
float3 normal : NORMAL;
};
struct v2f
{
//输出顶点位置
float4 pos : SV_POSITION;
//存储顶点着色器输出的颜色
fixed3 color_in : COLOR;
};
v2f vert(a2v v)
{
//两个目标:输出变换后的pos坐标、输出计算后的color
v2f o;
//pos变换
o.pos = UnityObjectToClipPos(v.vertex_position);
//color计算
//变量准备
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); //法线原本是模型空间下的,需要变换至世界坐标
//环境光部分
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//漫反射部分计算
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
//整理输出
o.color_in = ambient + diffuse;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return fixed4(i.color_in, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
效果如下:
2、逐像素漫反射光照
逐像素光照中,主要的计算过程需要放到片元着色器中。顶点着色器只需要将变换后的位置坐标、变换后的世界空间中的发现坐标传到片元着色器中即可。
在逐顶点漫反射的基础上修改:
//输入顶点着色器
struct a2v{
float3 pos : POSITION;
float3 normal : NORMAL;
};
//顶点着色器输出
struct v2f{
float4 worldpos : SV_POSITION;
fixed3 worldnormal : TEXCOORD0;
};
TEXCOORD0、TEXCOORD1 等语义用于指示任意高精度数据,如纹理坐标和位置。
把计算挪到片元着色器:
v2f vert(a2v v)
{
// 返回
v2f o;
//变换pos
o.worldpos = UnityObjectToClipPos(v.pos);
//变换法线
o.worldnormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_TARGET
{
fixed3 worldNormal = normalize(i.worldnormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
得到代码:
Shader "Prcatice/DiffusePixelShader"
{
Properties
{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
//输入顶点着色器
struct a2v{
float3 pos : POSITION;
float3 normal : NORMAL;
};
//顶点着色器输出
struct v2f{
float4 worldpos : SV_POSITION;
fixed3 worldnormal : TEXCOORD0;
};
v2f vert(a2v v)
{
// 返回
v2f o;
//变换pos
o.worldpos = UnityObjectToClipPos(v.pos);
//变换法线
o.worldnormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_TARGET
{
fixed3 worldNormal = normalize(i.worldnormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
最终效果:
3、对比
逐顶点漫反射可能会在向光面与背光面的之间形成一些锯齿。
(如下图,左侧为逐像素漫反射,右侧为逐顶点漫反射,右侧阴影明显有菱形锯齿)