在shader中,光照是一个非常基础并且重要的知识点,下面我们就来学习一下shader中的基础光照。
在现实中的光照是极其复杂的,而且会受到诸多因素的影响,这是我们有限的计算能力所无法模拟的。因此我们在渲染中基本上都是使用一个简化的模型来模拟真实的光照环境。来达到我们想要的效果,看起来差不多就行。
计算机图形学第一定律:如果它看起来是对的,那么它就是对的。
——————《3D数学基础:图形与游戏开发》
标准光照模型
光照模型的主要结构由4个分量组成:
- 自发光(emissive): 一个表面本身发射的光。
- 环境光(ambient): 模拟其他所有的间接光照。
- 漫反射(diffuse): 用于描述当光线从光源照射到模型表面时,该表面会向每个方向散射的辐射量。
- 镜面高光(specular): 用于描述当光线从光源照射到模型表面时,该表面会完全镜面反射方向散射的辐射量。
1.自发光(emissive)
它的计算很简单,就是直接使用了该材质的自发光颜色
通常在实时渲染中,自发光的表面往往并不会照亮周围的表面,也就是说,这个物体并不会被当成一个光源。
在片元着色器中直接返回一个颜色值就ok
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
如图:
2.环境光(ambient)
光通常都不是来自同一个光源,而是来自于我们周围分散的很多光源,由其他物体反射而来的光,为了模拟这种间接光照,我们使用了一个常亮:环境光。
在Unity中可以通过 window => Rendering => Lighting Setting 修改环境光颜色。
shader中可以通过内置变量 UNITY_LIGHTMODEL_AMBIENT 获取环境光。
fixed4 frag (v2f i) : SV_Target
{
//环境光
fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT.rgba;
return _Color+ambient;
}
我们可以看到该物体会随着环境光的改变而改变。
3.漫反射(diffuse)
漫反射(diffuse) 是光线照射在物体粗糙的表面会无序地向四周反射的现象。是投射在粗糙表面上的光向各个方向反射的现象。当一束平行的入射光线射到粗糙的表面时,表面会把光线向着四面八方反射,所以入射线虽然互相平行,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射,
我们可以通过下图观察到,反射光的强度取决于在表面法向量和入射光的光线之间的角度的余弦值。
所以我们首先需要计算出法向量N和入射光方向L的角度的余弦值。
我们可以通过他们的点乘来计算,公式如下:
把向量归一化处理后,|L| 和 |N| 都是1,所有可以简化为:
所以得出最终计算 漫反射的公式:
- :入射光颜色
- :材质颜色
- max函数:防止点乘结果为负数
- :入射方向
- :当前点的法向量
演示:
Unity Shader代码如下:
PS:这里是在片元着色器里计算的,也可以在顶点着色器里计算。
如果想要效果好就在片元着色器里计算。
如果想要性能好就在顶点着色器里计算。
Shader "lcl/baseLighting/002_Diffuse_fargment" {
//在片元着色器计算漫反射
SubShader {
Pass{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
struct a2v {
float4 vertex : POSITION;
float3 normal: NORMAL;
};
struct v2f{
float4 position:SV_POSITION;
float3 worldNormalDir:COLOR0;
};
v2f vert(a2v v){
v2f f;
f.position = UnityObjectToClipPos(v.vertex);
f.worldNormalDir = mul(v.normal,(float3x3) unity_WorldToObject);
return f;
};
fixed4 frag(v2f f):SV_TARGET{
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
//法向量
fixed3 normalDir = normalize(f.worldNormalDir);
//光照方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//漫反射计算
fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir,lightDir),0);
fixed3 resultColor = diffuse+ambient;
return fixed4(resultColor,1);
};
ENDCG
}
}
FallBack "VertexLit"
}
以上用到的光照模型是 兰伯特光照模型(Lambert)
其实如果我们仔细观察会发现背光的地方非常暗,完全就看不见该球体的形状了,这样的话效果就不太理想了,如图:
有时候我们需要在背光的地方也能有一点光线,此时我们就可以用 半兰伯特光照模型 了,该技术是Valve公式在开发游戏《半条命》时提出的,由于该技术是在原兰伯特光照模型的基础上修改的,所以被称为半兰伯特光照模型
半兰伯特光照模型:
其实就是把 的结果范围从 [-1,1] 映射到 [0,1]的范围内。这样的话在背光面也会有明暗变化,不会完全黑掉。
我们通过下图可以清楚的看到他们的取值区间:
效果演示:
关键代码如下:
fixed4 frag(v2f f):SV_TARGET{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 normalDir = normalize(f.worldNormalDir);
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//半兰伯特漫反射 值范围0-1
fixed3 halfLambert = dot(normalDir,lightDir)*0.5+0.5;
fixed3 diffuse = _LightColor0.rgb * halfLambert;
fixed3 resultColor = diffuse+ambient;
return fixed4(resultColor,1);
};
结束!下节继续探索高光反射(Specular), Phone和BlinnPnone光照模型