HLSL着色器原理:(二)高级光照

  3.2 游戏引擎技术
  • 小光,小光,小光,小光!
  • 本文所总结视频为或许是小光从油管搬运到B站的视频:传送门
  • 本文主旨补充Unity客户端编程Shader知识点。
  • 我创建了一个游戏制作交流群:637959304 进群密码:(CSGO的拆包密码)欢迎各位大佬一起学习交流,不限于任何平台(U3D、UE、COCO2dx、GamesMaker等),以及欢迎编程,美术,音乐等游戏相关的任何人员一起进群学习交流。

高级光照

法线贴图

  • 计算步骤:
    1、采样法线贴图并转换成向量,让顶点着色器传递法线、副法线和切线向量
    2、将采样得到的法向信息从切线空间转换到世界空间
  • 法线贴图(normal mapping)的一个像素不代表颜色,而是代表一个法向量(左图)。在RGB通道中,RGB分别代表XYZ三个向量分量。(右图)
image 49 - HLSL着色器原理:(二)高级光照
image 50 - HLSL着色器原理:(二)高级光照
  • 法线映射。就是用一张图片中定义的法线替代模型上定义的法线
  • 切线空间法向量:在一般的图像中,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光照模型。
image 51 1024x407 - HLSL着色器原理:(二)高级光照
image 52 - HLSL着色器原理:(二)高级光照
  • 环境光
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自乘次数越多,光照范围越收束。
image 53 - HLSL着色器原理:(二)高级光照
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
image 58 - HLSL着色器原理:(二)高级光照
  • 制作环境光贴图步骤请参考原视频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、把分散卷积图转换为立方环境贴图(十字形)
image 59 - HLSL着色器原理:(二)高级光照
探针图
image 60 - HLSL着色器原理:(二)高级光照
经纬图
image 62 - HLSL着色器原理:(二)高级光照
分散卷积图
image 63 - HLSL着色器原理:(二)高级光照
立方环境贴图
  • 方式2步骤:在软件(3DS max等)中用摄像机截取六个方向的图片,然后重复方式1的步骤

光照模型

  • 光照模型:一套数学公式,基于我们选定的参数来决定每个像素的渲染颜色
  • Phong光照模型(高光):基于Blinn模型的简化版。区别为,Phong计算高光的方式与Blinn模型不同。Blinn模型中不计算高光的折中向量,而直接计算反射结果,以此来代替HDR贴图的高消耗。
image 64 - HLSL着色器原理:(二)高级光照
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模型高光效果:适合塑料表面,光照比较紧凑
image 65 1024x687 - HLSL着色器原理:(二)高级光照
光泽度=4
image 66 1024x769 - HLSL着色器原理:(二)高级光照
光泽度=0.01
  • Blinn模型高光效果:适合金属表面
image 67 1024x663 - HLSL着色器原理:(二)高级光照

  • Cook/Torrence光照模型(高光):为模拟金属表面(拉丝金属、铝制品)而设计,应对计算不光滑表面
  • 菲涅尔效应:传送门
image 68 1024x476 - HLSL着色器原理:(二)高级光照
不光滑表面光线示意图
image 69 1024x304 - HLSL着色器原理:(二)高级光照
其中一些光照不会反射出去
  • 数据结构
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;
}

image 70 - HLSL着色器原理:(二)高级光照
高光滑度
image 71 - HLSL着色器原理:(二)高级光照
中等光滑度
image 72 - HLSL着色器原理:(二)高级光照
低光滑度

  • Oren-Nayer光照模型(漫反射):可以替换Blinn模型的漫反射光照计算部分。适用于皮肤,衣服,黏土等表面,来模拟粗糙表面。
  • 缺点:计算耗时长。
image 73 - HLSL着色器原理:(二)高级光照
粗糙度为0时等价于Blinn模型
image 74 - HLSL着色器原理:(二)高级光照
中等粗糙度
image 75 - HLSL着色器原理:(二)高级光照
高粗糙度
  • 数据结构
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;
}

LEAVE A COMMENT