- 小光,小光,小光,小光!
- 本文所总结视频为或许是小光从油管搬运到B站的视频:传送门
- 本文主旨补充Unity客户端编程Shader知识点。
- 我创建了一个游戏制作交流群:637959304 进群密码:(CSGO的拆包密码)欢迎各位大佬一起学习交流,不限于任何平台(U3D、UE、COCO2dx、GamesMaker等),以及欢迎编程,美术,音乐等游戏相关的任何人员一起进群学习交流。
高级光照
法线贴图
- 计算步骤:
1、采样法线贴图并转换成向量,让顶点着色器传递法线、副法线和切线向量
2、将采样得到的法向信息从切线空间转换到世界空间 - 法线贴图(normal mapping)的一个像素不代表颜色,而是代表一个法向量(左图)。在RGB通道中,RGB分别代表XYZ三个向量分量。(右图)
- 法线映射。就是用一张图片中定义的法线替代模型上定义的法线
- 切线空间法向量:在一般的图像中,0到255的红色值,对应了X轴向的-90度到90度而绿色对应Y轴,蓝色对应Z轴。三轴合一即为法向量。
- 向量转换:切线空间法向量->物体空间->世界坐标系。转换完成后才可以计算光照向量的影响。
//需要在a2v的输入结构体以及v2f输出结构体中定义binormal以及tangent(都为float3类型)来进行向量转换
texture normalMap : NormalMap
<
string name = "default_normal .dds";
string UIName ="Normal Map”;
string TextureType ="2D";
>
sampler2D normalMapSampler = sampler_state
{
Texture = <normalMap>;
MinFilter = linear;
MagFilter = linear;
MipFilter = Anisotropic;
};
v2f v(a2v In,uniform float4 lightPosition)
{
v2f out;
Out.worldNormal = mul(In.normal,WorldInverseTranspose).xyz;
Out.WorldTangent = mul(In.tangent,WorldInverseTranspose).xyz;
Out.WorldBinormal = mul(In.Binormal,WorldInverseTranspose).xyz;
float3 worldSpacePos = mul(In.position,World);
Out.lightVec = lightPosition - worldSpacePos;
Out.texCoord.xy = In.texCoord;
Out.position = mul(In.position,WorldViewProjection);
return Out;
}
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float4 ColorTexture = tex2D(diffuseMapSampler,In.texCoord.xy);
float3 normal = tex2D(normalMapSampler,In.texCoord).xyz * 2.0 - 1.0;
float3 Nn = In.worldNormal;
float3 Tn = In.worldTangent;
float3 Bn = In.worldBinormal;
float3 N = (Nn * Normal.z) + (normal.x * Bn + normal.y * -Tn);
N = normalize(N);
float3 L = normalize(In.lightVec.xyz);
float4 Ambient = AmbientColor * ColorTexture;
float4 diffuselight = saturate(dot(N,L) ) * lightColor;//saturate控制输出结果在0-1之间
float4 Diffuse = DiffuseColor * ColorTexture * diffuselight;
return Diffuse + Ambient;
}
基本光照模型
- 环境光(Ambient Lighting):环境光不宜设置过高,否则会遮盖漫反射和高光的效果。
- 漫反射(Diffuse Lighting):从光源发射的有向光
- 高光(Specular Lighting):光线发生镜面反射后的反射光线
- 上述三种光照合在一起成为Blinn光照模型。
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float4 Ambient = AmbientColor * ColorTexture;
}
- 漫反射光
- 预处理:1、把向量转换到同个向量空间中 2、每一个向量都已经单位化(方便计算夹角)
//光照向量,其余部分与法线贴图代码相同,下述只补充新增代码
struct v2f
{
float3 lightVec : TEXCOORD1;
}
v2f v(a2v In,uniform float4 lightPosition)
{
Out.lightVec = lightPosition - worldSpacePos;
}
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float3 L = normalize(In.lightVec.xyz);
float diffuselight = saturate(dot(N,L));
float4 Diffuse = diffuselight * DiffuseColor * ColorTexture;
}
- 高光
- 镜面性(Specularity):借助光线来源与摄像机位置模拟光线在表面上发射的近似效果。
- 计算步骤:
1、引入观察者坐标到像素着色器中
2、再顶点着色器中计算出观察向量
3、计算出一个折中向量(该向量直接应用于光照会导致高光覆盖范围过大,如下图所示)
4、引用光泽度(gloss)变量控制高光范围=>pow(NdotH,gloss); NdotH自乘次数越多,光照范围越收束。

float Glossiness
<
string UIWidget = "slider";
float UIMin = 1.0;
float UIMax = 512.0;
float UIStep = 0.1;
string UIName = "Glossiness";
> = 20.0;
struct v2f
{
float3 eyeVec : TEXCOORD2;
}
v2f v(a2v In,uniform float4 lightPosition)
{
Out.eyeVec = ViewInverse[3] - worldSpacePos;//VI矩阵第四行是摄像机世界坐标
}
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float4 SpecularTexture = tex2D(SpecularMapSampler,In.texCoord.xy);//高光贴图采样
float3 V = normalize(In.eyeVec.xyz);
//位于光照向量和观察向量中间
float3 H = normalize(L + V);
float NdotH = saturate(dot(N,H));
float gloss = Glossiness * SpecularTexture.a;
float SpecPower = pow(NdotH,gloss);
float4 Specular = float4(SpecPower,SpecPower,SpecPower,1);
// = SpecPower * SpecularColor * SpecularTexture;
return (Ambient + Diffuse +Specular);
}
高级光照
光照衰减(Ligt Attenuation)
- 光照原理物体,照射到物体上的光线就越少。光照衰减只会影响漫反射和高光。
- 光照衰减公式:
1、线性光照衰减(Linear Light Decay):光源与物体的距离和物体亮度成反比(取距离倒数)
2、改进版:成平方反比。(更接近于真实光照)缺点是衰减速率过快,需要反复调试参数。
float decayscale
<
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 100000.0;
float UIstep = 1.0;
String UIName ="Light Decay scale";
>=40.0:
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float d = length(In.lightVec.xyz);//获取光源到物体的长度,length为计算向量长度的内置函数
//float atttenuation = (1/d) * decayScale;//线性衰减,decayScale为自定义系数,decayScale=1时几乎没有光照效果
float atttenuation = (1/d*d) * decayScale;
float light = (Diffuse +Specular)* lightColor * attenuation;
return (Ambient + light);
}
方向光(Directional Light)
- 方向光只影响光照方向,无论光源物体相对距离如何,亮度不会进行改变(例如阳光),因此方向光也没有光照衰减。
//v2f v中不再需要Out.lightVec的计算,也不需要传入lightPosition,用方向光代替
float4 lightDir : Direction
<
string UIName = "Light";
string Object = "TargetLight";
string Space = "World";
int refID = 0;
> = (100.0f,100.0f,100.0f,0.0f);
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float3 L = normalize(lightDir);
}
聚光灯
//需要lightPos以及lightDir
//加入ConeAngle变量控制聚光角度,根据高光中的知识点可以得出数值越小范围越大。
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float3 L = normalize(lightDir);//改变光线效果为方向光,之前填入的是In.LightVec.xyz
float SpotCone = pow(saturate(dot(L,normalize(light1Dir))),ConeAngle);//点积结果如果为负数,则大于90度的角也可以产生光照效果,不过去掉saturate可以模仿警报灯的那种双向光照效果
return (Ambient + Diffuse + Specular) * lightColor * SpotCone;
}
全局光照
- 全局光照包含两部分:环境光遮蔽(Ambient Occlusion),分散卷积立方贴图(Diffusely Convolved Cubemaps)
- 需要以不重叠且坐标合法的方式展开UV

- 制作环境光贴图步骤请参考原视频2.8部分,制作完成后用环境光代替漫反射贴图即可。
//定义新的LightMap
texture LightMap : lightMap
<
string name = "default_bump_normal.dds";
string UIName = "Normal Map";
string TextureType = "2D";
//同时要在diffuseMap和specularMap设置如下参数,把UV1提供给漫反射、高光、发现三个贴图,UV2给全局光照
int Texcoord = 1;
int MapChannel = 2;
}
v2f v(a2v In,uniform float4 lightPosition)
{
Out.textCoord1.xy = In.texCoord1;
}
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float4 LightmapTexture = tex2D(LightMapSampler,In.texCoord1.xy);//需要定义LightMapSampler采样器
float4 Ambient = AmbientColor * ColorTexture * LightmapTexture;
}
- 环境立方贴图(Cube Mapping):计算从环境四处发射来的光线
texture CubeMap : EnvMap
<
string name = "default_bump_normal.dds";
string UIName = "Ambient Cube";
string TextureType = "Cube";
}
samplerCUBE CubeMapSampler = sampler_state
{
Texture = <CubeMap>;
MinFilter = linear;
MagFilter = Linear;
MapFilter = Anisotropic;
}
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float3 worldNorm = N;//使用该向量对立方贴图进行采样
float4 env = texCUBE(CubeMapSampler,worldNorm);//取出世界法线与立方贴图焦点的颜色
float4 Ambient = AmbientColor * ColorTexture * LightmapTexture * env;
}
- 制作环境立方贴图:对应视频2.10部分
- 方式:1、基于真实环境光线的立方贴图 2、基于虚拟环境的立方贴图
- 方式1步骤:1、制作光照探针图(HDR图像,高亮度会显示暗部细节,反之显示更多亮部细节)2、将探针图转换为经纬图 3、经纬图转换为分散卷积图(每个像素不仅携带自己方向上的颜色信息,还要计算180度球体范围内的所有像素信息然后进行平均运算,即每一个像素都代表了半球范围内的所有信息) 4、把分散卷积图转换为立方环境贴图(十字形)
立方环境贴图- 方式2步骤:在软件(3DS max等)中用摄像机截取六个方向的图片,然后重复方式1的步骤
光照模型
- 光照模型:一套数学公式,基于我们选定的参数来决定每个像素的渲染颜色
- Phong光照模型(高光):基于Blinn模型的简化版。区别为,Phong计算高光的方式与Blinn模型不同。Blinn模型中不计算高光的折中向量,而直接计算反射结果,以此来代替HDR贴图的高消耗。

float4 f(v2f In,uniform float4 lightColor) : COLOR
{
//通过光照向量和表面法向量计算反射后的光照向量
float3 R = reflect(L,N);//利用reflect函数计算反射向量
float RdotV = saturate(dot(R,V));//反射向量和观察向量的点积
float gloss = Glossiness * SpecularTexture.a;
float SpecPower = pow(RdotV,gloss);
float4 Specular = float4(SpecPower,SpecPower,SpecPower,1);
return (Ambient + Diffuse +Specular) *lightColor;
}
- Phong模型高光效果:适合塑料表面,光照比较紧凑
光泽度=4
光泽度=0.01
- Cook/Torrence光照模型(高光):为模拟金属表面(拉丝金属、铝制品)而设计,应对计算不光滑表面
- 菲涅尔效应:传送门
不光滑表面光线示意图
其中一些光照不会反射出去
float CookTorrance(float NdotL,float NdotV,float NdotH,float VdotH,float Roughness1,float Roughness2)
{
Roughness1 *= 3.0f;
float G1 = ( 2.0f*NdotH *Ndotv)/VdotH;
float G2 = (2.0f*NdotH*NdotL)/VdotH;
float G = min( 1.0f ,max ( 0.0f, min(G1,G2)));//阴影和遮罩强度
float F = Roughness2 + (1.0f - Roughness2 ) * pow( 1.0f - Ndotv,5.0f );//计算模型边缘光照
//模拟粗糙度,Roughness1计算表面粗糙度,Roughness2用于计算菲涅尔现象
float R_2 = Roughness1 *Roughness1;
float NDotH_2 = NdotH * NdotH;
float A = 1.0f / (4.0f * R_2 * NDotH_2 * NDotH_2);
float B = exp( -(1.0f - NDotH_2 ) / (R_2 * NDotH_2);
float R = A * B;
return ((G*F*R) / (NdotL * NdotV));
}
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float NdotL =dot(N,L);//此处不进行saturate操作,以供Cook/Torrence模型使用
float diffuselight = saturate(NdotL);//对于saturate操作另行保存
float H = normalize(L+V);
float NdotH = dout(N,H);
float NdotV = dot(N,V);
float VdotH = dot(V,H);
////Roughness1,Roughness2为自定义的参数
float 4 Specular = CookTorrance(NdotL,NdotV,NdotH,VdotH,Roughness1,Roughness2) * SpecularColor * SpecularTexture;
return lightColor * diffuselight * ((ColorTexture * DiffuseColor) + Specular) + Ambient;
}
- Oren-Nayer光照模型(漫反射):可以替换Blinn模型的漫反射光照计算部分。适用于皮肤,衣服,黏土等表面,来模拟粗糙表面。
- 缺点:计算耗时长。
粗糙度为0时等价于Blinn模型
中等粗糙度
高粗糙度
float orenNayar(float3 N,float3 V,float NdotL,float NdotV,float Roughness1)
{
//根据传入三个向量,将光照摊平在表面上
float theta_r = acos(NdotV);
float theta_i = acos(NdotL);
float cos_phi_diff = saturate(dot(normalize(V - N * NdotV),normalize(L - N * NdotL)));
float alpha = max(theta_i,theta_r);
float beta = min(theta_i,theta_r);
float A = 1.0 - 0.5 * Roughness1 / (Roughness1 + 0.33);
float B = 0.45 * Roughness1 / (Roughness1 + 0.09);
return saturate(NdotL) * (A + B * sin(alpha) * tan(beta));
}
float4 f(v2f In,uniform float4 lightColor) : COLOR
{
float4 Diffuse = orenNayar(N,L,NdotL,NdotV,Roughness1) * ColorTexture * DiffuseColor;
return (Diffuse + Ambient) * lightColor;
}