一篇个人Shader笔记,主要是通过分析Shader代码来总结几个光照模型。
0. 标准光照模型
标准模型主要是分为四个部分,自发光 + 高光反射 + 漫反射 + 环境光 根据不同的光照模型,计算不同的光线进行相加
逐顶点和逐像素
模型光照计算主要分为两种,逐像素和逐顶点 ,即Grouraud Shading 和 Phone Shading
逐顶点
在每个顶点上计算光照,在渲染图元内部进行线性插值,最后输出成像素颜色 缺点:顶点计算光照内部进行插值,图元内部总会比顶点处最高颜色值更暗,而且会出现棱角
不过这也告诉我们,光照计算在顶点着色器
逐像素
以每个像素为基础,得到他的法线, (可以是顶点法线插值,可以是从法线纹理中采样),进行光照计算 因此,光照计算在片元着色器
1.漫反射模型
Lambert漫反射模型的基础,主要是满足Lambert定律:即反射光线的强度与表面法线和光源方向之间的夹角的余弦值成正比
写成公式就是: Cdiffuse = ( Clight · mdiffuse )max (0, n · l) Clight :光源颜色 mdiffuse:材质漫反射颜色 n : 表面法线 l : 指向光源单位矢量
1.1 逐顶点漫反射
mdiffuse材质的漫反射颜色的定义,颜色可以在unity中自取
Properties
{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
}
分别是a2v和v2f,即application to vertex shader 和 vertex shader 到 fragment shader 的定义。
在顶点着色器中计算光照,因此需要在a2v中访问到顶点和法线;要将顶点着色器中的光照颜色传递给片元着色器,因此,v2f中需要将顶点转换裁剪空间和储存color值
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
这就是顶点着色器中具体的计算了。Clight和mdiffuse一个是直接得到一个是之前定义了,需要解决的只有n · l 。需要把这两个值都先转换到世界空间进行点乘。
世界坐标下的法线:unity内置的 unity_WorldToObject 可以从世界坐标转换到局部坐标,那么运用矩阵的逆运算,得到法线的世界坐标。
其他的带入公式
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb *saturate(dot(worldNormal,worldLight));
o.color = ambient + diffuse;
return o;
}
片元着色器中直接输出就好了
fixed4 frag(v2f i) : SV_TARGET
{
return fixed4(i.color, 1.0);
}
1.2 逐像素漫反射
主要是两个着色器中计算的内容不一样。
在顶点着色器中,不需要计算光照,只需要把世界空间下的顶点和法线传递给片元着色器即可。
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
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 color = ambient + diffuse;
return fixed4(color, 1.0);
}
2. 高光反射模型
高光反射模型分Phone 和 Blinn Phone 两种 前者模型方式是,高光反射与视角方向和反射方向的点乘值相关, 后者认为是法线方向与视角方向的半程向量与法线的点乘值相关。
Phone高光模型: Cspecular = (Clight · mspecular) max(0, v, r)mgloss Clight : 入射光线的颜色与强度 mspecular: 材质的高光反射系数 v: 视角方向 r : 反射方向 mgloss: 光泽度,反光度
Blinn Phone高光模型: Cspecular = (Clight · mspecular) max(0, n, h)mgloss n : 法线,h 是半程向量 其他大致是一样的
2.1 逐顶点Phone高光反射
漫反射颜色,高光反射系数和光泽度都是自定义的如下
Properties
{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
_Specular("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
在顶点着色器计算光照,主要是计算高光,所以环境光我们直接获取。漫反射的计算方式不多写了,高光带入公式一顿计算,加上环境光返回
reflectDir是直接通过内置函数,写入光线入射方向和顶点法线方向得到出射方向 viewDir 通过相机的世界位置 - 顶点的世界位置得到视野向量
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
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 - mul(unity_ObjectToWorld, v.vertex).xyz);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
o.color = ambient + diffuse + specular;
return o;
最后再片元着色器中直接将颜色输出就好,与上面的一致。
2.2 逐像素Phone高光反射
显然是在片元着色器中计算光照,思路一致。在顶点着色器中,只需要计算出顶点的世界坐标和法线的世界下的位置
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);
}
2.3 逐像素Blinn Phone 高光模型
大致一样的,主要是片元计算光照,多一个半程向量的计算并使用
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(reflectDir, viewDir)), _Gloss);
|