光照类型

  1. 平行光:类似于太阳光,平行光没有距离概念,不会衰减,不考虑遮挡,对于空间中的任意点接收到的平行光的方向、强度都是一样的

  2. 点光源:相较于平行光,点光源是有位置概念的,点光源的位置会影响,接收光照的点的方向和强度

  3. 聚光灯:相较于点光源,聚光灯相当于是点光源的一部分,聚光灯多了一个朝向的概念,点光源的范围是球形的,聚光灯的范围是朝着聚光灯朝向的锥形


光照衰减

​ 随着距离的增加,光照的强度会不断衰减,在Unity中点光源我们可以自己计算,但是对于聚光定由于要考虑聚光灯的朝向、张角等信息而Unity在当前版本不能通过Shader获取这些信息(也可能现在提供了,参考入门精要是没有),只能通过脚本进行数据传递,但是Unity提供了光照衰减纹理,可以直接采样获取到衰减值,主要原理是在材质上按照光源空间的不同位置存储,在采样时将要着色的点转到光源空间进行衰减值采样,这样可以大大减少光照衰减的计算量

代码如下:

#if defined(POINT)  //点光源
    float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;//转到光源空间
    fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined(SPOT)  //聚光灯
    float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
    fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else  
    fixed atten = 1.0;
#endif

光照模型

  1. 环境光:用来描述各种简介光的合集,,Unity中可以通过UNITY_LIGHTMODEL_AMBIENT.xyz获取

  2. 漫反射:反映物体随机散射到各个方向的辐射度,没有视线概念,符合兰伯特定律 diffuseCol = lightCol * matDiffuseCol * max(0,normal * lightDir ),反射光线的强度是和表面法线和入射光的反方向的余弦值成正比的,漫反射的计算经常会使用的到半兰伯特模型进行光照的增强,一般为是diffuseCol = lightCol * matDiffuseCol *(0.5 + 0,5 *normal * lightDir)),将两个向量的点积从[-1,1]映射到[0,1]

  3. 高光: 反映物体的光泽感,是一种经验模型,与视线相关,Phong模型的公式为 specularCol = lightCol * matSpecularCol * pow(max(0,viewDirl * reflectLightDir ),matGloss),视线和反射光照越接近越容易高光,物体的反光度越大,越不容易高光即光点越小,reflectLightDir 是可以计算得到的,公式为 2 * (normal * lightDir) * normal - lightDir,在Blinn-Phong模型中,引入了引入了半程向量,是对视线方向和光源方向做平均归一化后的结果,让该向量和法线的夹角计算代替反射向量和视线方向的夹角计算来降低计算量

  4. 自发光:物体自身作为光源直接参与光照计算


向前渲染

渲染路径用于决定光照如何应用到Shader中,向前渲染路径是一种Unity最常用的渲染路径,是对每个光源的光照计算结果的混合。对于光源的光照计算,Unity可以分为逐像素、逐顶点、球谐函数(SH)三种处理方式,逐像素是在片元着色器中对每个像素进行光照计算,逐顶点是在顶点着色器对每个顶点进行光照计算,最终像素结果是顶点计算结果的插值结果,球谐函数是一种数学方式的估算,具体原理还没看,总之是一种计算量结果相对不准确但是计算量最低的方式。Unity会依据以下四条规则判断光源的光照计算方式(参照UnityShader入门精要)

  1. 场景中最亮的平行光总是按逐像素处理
  2. 渲染模式被设置为Not Import的光源会按逐顶点或者SH处理
  3. 渲染模式被设置为Important的光源会按逐像素处理
  4. 根据以上规则得到逐像素光源数量小于设置的逐像素光源数量会有更多的光源以逐像素方式进行渲染

对于光照的计算,都写在了Pass里面,向前渲染分为两种Pass

  1. Base Pass:计算一个逐像素的平行光、所有逐顶点和SH光源,可以实现环境光、平行光阴影、自发光等

    //BasePass关键设置
    Tags { "LightMode" = "ForwardBase"}
    #pragma multi_compile_fwdbase
    
  2. Addtional Pass:计算其他逐像素光源

    1
    2
    3
    4
    //Addtional Pass关键设置
    Tags {"LightMode" = "ForwardAdd"}
    Blend One One
    #pragma multi_compile_fwdadd

光照计算实践

通过传入数据计算光照

不考虑渲染路径,我们直接通过脚本传入光源的各类信息进行光照计算

代码如下

//逐像素
v2f vert(a2v v) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.worldNormal = UnityObjectToWorldNormal(v.normal);
    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    o.worldViewDir = _WorldSpaceCameraPos.xyz - o.worldPos.xyz;
    o.uv = v.texcoord.xy;
    return o;
}
fixed4 frag(v2f i) : SV_Target{
    //平行光
    float colorDirectDiffuse = 0.5 + 0.5 * dot(normalize(-_DirDirect),normalize(i.worldNormal));//没有环境光用半兰伯特模型增强下亮度
    float colorDirectSpecular = pow(saturate(dot(normalize(i.worldNormal), normalize(normalize(-_DirDirect) + normalize(i.worldViewDir)))), _SpecularPow);//Blin-Phong
    //fixed3 reflectDir = reflect(_DirDirect, i.worldNormal);
    //float colorDirectSpecular = pow(saturate(dot(normalize(reflectDir), normalize(i.worldViewDir))), _SpecularPow);//Phong
    fixed4 colorDirect = (colorDirectDiffuse + colorDirectSpecular) * _ColorDirect * _IntentDirect;

    //点光源
    float distance = length(_PosPoint - i.worldPos);
    float attenPoint = 1 / distance;//这边直接采用与距离成反比的衰减计算方式
    float colorPointDiffuse = saturate(dot(normalize(_PosPoint - i.worldPos), normalize(i.worldNormal)));
    float colorPointSpecular =pow(saturate(dot(normalize(i.worldNormal), normalize(normalize(_PosPoint - i.worldPos) + normalize(i.worldViewDir)))), _SpecularPow);
    fixed4 colorPoint = (colorPointDiffuse + colorPointSpecular) * _ColorPoint * _IntentPoint * attenPoint;

    //聚光灯
    fixed4 colorSpot = fixed4(0.0 , 0.0, 0.0, 1.0);
    if (2 * degrees(acos(dot(normalize(i.worldPos - _PosSpot), normalize(_DirSpot)))) <= _AngleSpot) {//相比点光源多了角度判断
        float distance = length(_PosSpot - i.worldPos);
        float attenSpot = 1 / distance;
        float colorSpotDiffuse = saturate(dot(normalize(_PosSpot - i.worldPos), normalize(i.worldNormal)));
        float colorSpotSpecular = pow(saturate(dot(normalize(i.worldNormal), normalize(normalize(_PosSpot - i.worldPos) + normalize(i.worldViewDir)))), _SpecularPow);
        colorSpot = (colorSpotDiffuse + colorSpotSpecular) * _ColorSpot * _IntentSpot * attenSpot;
    }
    //return tex2D(_MainTex, i.uv) * (colorDirect + colorPoint + colorSpot);
    return _Color * (colorDirect + colorPoint + colorSpot);
}

值得注意的是在计算点光源和聚光灯时是不采用半兰伯特模型模型的,因为如果这两种光不需要模拟环境光,使用半兰伯特反而可能产生一些异常效果

在平行光强度0.5 白色,点光源强度1 红色,聚光灯强度1 绿色下的渲染效果:

如果把片元着色器的计算转移到顶点着色器的效果:

可以看到计算量减少了但是光照的计算结果也不够精确


通过向前渲染计算光照

依据向前渲染路径进行光照计算

    Pass{
        Tags { "LightMode" = "ForwardBase"}
        CGPROGRAM
        #pragma vertex vert      
        #pragma fragment frag
        #pragma multi_compile_fwdbase
        #include "Lighting.cginc"  
...
        v2f vert(a2v v) {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
            return o;
        }
        fixed4 frag(v2f i) : SV_Target{
            fixed3 worldNormal = normalize(i.worldNormal);
            fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
            //环境光
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            //漫反射
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (0.5 + dot(worldNormal, worldLightDir));
            //Blin-Phong 高光
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
            fixed3 halfDir = normalize(worldLightDir + viewDir);
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _SpecularPow);

            fixed atten = 1.0;
            fixed4 finalColor = fixed4(ambient + (diffuse + specular) * atten,1);
            return finalColor;
        }
        ENDCG
    }
    Pass {
        Tags {"LightMode" = "ForwardAdd"}
        Blend One One
        CGPROGRAM
        #pragma vertex vert      
        #pragma fragment frag
        #pragma multi_compile_fwdadd
        #include "Lighting.cginc"  
        #include "AutoLight.cginc" 
...
        v2f vert(a2v v) {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
            return o;
        }
        fixed4 frag(v2f i) : SV_Target{
            fixed3 worldNormal = normalize(i.worldNormal);

            #if defined(DIRECTIONAL)  
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
            #else  
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
            #endif  

            //漫反射
            fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
            //Blin-Phong 高光
            fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
            fixed3 halfDir = normalize(worldLightDir + viewDir);
            fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _SpecularPow);

            #if defined(POINT)  
                float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
                fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
            #elif defined(SPOT)  
                float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
                fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
            #else  
                fixed atten = 1.0;
            #endif

            fixed4 finalColor = fixed4((diffuse + specular) * atten,1);
            return finalColor;
        }
        ENDCG
    }

渲染效果:

可以看到效果上和自己去计算的还是有较大差距的,主要原因应该是光照衰减的计算方式不一样