Back
Featured image of post Shader入门精要-简单光照模型

Shader入门精要-简单光照模型

介绍 Lambert Phong 等光照模型

技术美术——基础光照模型

Lambert

Lambert 光照模型的光照计算简单粗暴,是光源方向点成法线方向,其值在 [-1, 1] 之间,基本计算公式如下:

C = (c * m) * max(0 , n * l )

// 法线方向点乘光源方向
float NdotL = max(0.0,dot(normalDirection,lightDirection));
float LambertDiffuse = NdotL * SurfaceColor;
float3 finalColor = LambertDiffuse * LightColor;

Half Lambert

Lambert 光照模型是一个简单方便的光照计算模型,但是,有一个问题存在。在光照无法照射的区域,模型外观变成了全黑,没有任何明暗的变化,使得模型背光区看起来像一个平面,失去了模型细节表现。可以通过添加环境光来得到非全黑的效果,但即使这样仍然无法解决背光面明暗一样的缺点。为了解决这个问题,有人在 Lambert 光照模型的基础上进行改良,这就是 Half Lambert 光照模型,通过乘以一个系数 a 再加上一个系数 b 的方法,将 Lambert 光照模型的值 [-1, 1] 重新映射至 [-a + b, a + b],绝大多数情况下 a b 的值都取 0.5 ,这样 Half Lambert 光照模型的值 [0, 1],在一定程度上改善了 Lambert 光照模型所带来的问题:

// 
float NdotL = max(0.0,dot(normalDirection,lightDirection) * 0.5 + 0.5);
float HalfLambertDiffuse = NdotL * SurfaceColor;
float3 finalColor = HalfLambertDiffuse * LightColor;

Phong Lighting

高光计算部分,基本计算公式:

Cspecular = (Clight * M specular)Max(0, V * r)

需要四个参数参与计算,入射光的颜色和强度 Clight,材质的高光反射系数 Mspecular,视线方向 v 以及反射方向 r。

// 视线方向,摄像机坐标 - 顶点坐标得到视线方向,向量从顶点指向摄像机,就是视线方向 
float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
// 光线反射方向,有光源方向和顶点的法线方向计算得到
float3 lightReflectDirection = reflect( -lightDirection, normalDirection );
// Lambert 光照模型
float NdotL = max(0, dot( normalDirection, lightDirection ));
float RdotV = max(0, dot( lightReflectDirection, viewDirection ));
// 计算具体的高光,是一个指数函数,再乘以光照强度、高光颜色
float3 specularity = pow(RdotV, _SpecularGloss/4) *_SpecularPower *_SpecularColor.rgb ;

float3 lightingModel = NdotL * diffuseColor + specularity;

Blinn-Phong

高光计算部分,基本公式:

Cspecular = (Clight * M specular)Max(0, n * h)

不同于 Phong 模型,这里不使用视线方向 v 和反射方向 r,取而代之的是引入新的矢量 h,通过视角方向 v 和光照方向 i 相加归一化处理后得到。

// 视线方向,摄像机坐标 - 顶点坐标得到视线方向,向量从顶点指向摄像机,就是视线方向 
float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
// 半角方向,根据经验取得的模型,让视线方向+光源方向作为光反射的方向
float3 halfDirection = normalize(viewDirection+lightDirection); 
float NdotL = max(0, dot( normalDirection, lightDirection ));
float NdotV = max(0, dot( normalDirection, halfDirection ));
// 计算具体的高光,是一个指数函数,再乘以光照强度、高光颜色
float3 specularity = pow(NdotV ,_SpecularGloss)*_SpecularPower
 *_SpecularColor.rgb ;

float3 lightingModel = NdotL * diffuseColor + specularity;

总结

在实际应用中,我们还是大量使用 Half LambertBlinn-Phong 光照模型,需要注意的是,这两个模型都是 经验 模型,我们从物理的角度看,这两个模型都是不正确的,但是,在实际的渲染中,这两个模型的结果更让人满意。最后给出适用的代码,可以根据需要自行注释掉关于光照部分计算的代码来观察不同光照模型之间的区别

四种不同光照模型的结果
四种不同光照模型的结果

Shader "URP/HalfLambertBlinnPhong"
{
    Properties
    {
        _Diffuse ("Diffuse", Color) = (1,1,1,1)
        _Specular("Specular",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(1.0,200))=10
    }

    SubShader
    {
        Tags { "RenderPipeline"="UniversalRenderPipeline" "RenderType"="Opaque" }

        Pass
        {
            Tags { "LightMode"="UniversalForward"}

            HLSLPROGRAM
            // UnityCG.cginc 文件被代替,使用 HLSL 文件,这里引用文件
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"

            #pragma vertex vert
            #pragma fragment frag

            CBUFFER_START(UnityPerMaterial) // 兼容 SRP 批处理,将所有材质属性声明在一个 UnityPerMaterial 的 CBUFFER 块中   
            float4 _Diffuse;
            float4 _Specular;
            float _Gloss;
            CBUFFER_END

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 lightmapUV : TEXCOORD1;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1; 
            };
            v2f vert(a2v v)
            {
                v2f o;
                ZERO_INITIALIZE(v2f, o);   // 初始化变量
                o.pos=TransformObjectToHClip(v.vertex.xyz);           // 顶点坐标从模型空间转到裁剪空间
                o.worldNormal = TransformObjectToWorldDir(v.normal);  // 法线从模型空间转到世界空间
                o.worldPos = TransformObjectToWorldDir(v.vertex.xyz); // 顶点从对象空间转到世界空间
                return o;
            }

            half4 frag(v2f i):SV_Target
            {
                Light mainLight = GetMainLight();  // 获取主光源
                half3 worldNormal = normalize(i.worldNormal); // 顶点法线方向归一化
                half3 worldLightDir = normalize(TransformObjectToWorldDir(mainLight.direction)); // 主光源方向归一化
                half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos); // 计算视角方向并且归一化
                half3 lightReflectDir = reflect( -worldLightDir, worldNormal );   // 计算 phong 模型中高光部分
                half3 halfDir = normalize(worldLightDir + viewDir); // 计算半角方向且归一化

                half3 NdotL = max(0.0, dot(worldNormal, worldLightDir));   // Lambert
                half3 RdotV = max(0.0, dot(worldNormal, lightReflectDir)); // Phong
                half3 NdotV = max(0.0, dot(worldNormal, halfDir));         // Blinn-Phong

                /// lambert + Phong
                //half3 diffuse = mainLight.color.rgb * _Diffuse.rgb * NdotL;
                //half3 specular = mainLight.color.rgb * _Specular.rgb * pow(RdotV, _Gloss);

                /// Lambert + Blinn-Phong
                //half3 diffuse = mainLight.color.rgb * _Diffuse.rgb * NdotL;
                //half3 specular = mainLight.color.rgb * _Specular.rgb * pow(NdotV, _Gloss);

                /// Half Lambert + Phong
                //half3 diffuse = mainLight.color.rgb * _Diffuse.rgb * pow(NdotL * 0.5 + 0.5, 2.0);
                //half3 specular = mainLight.color.rgb * _Specular.rgb * pow(RdotV, _Gloss);

                /// Half Lambert + Blinn-Phong
                half3 diffuse = mainLight.color.rgb * _Diffuse.rgb * pow(NdotL * 0.5 + 0.5, 2.0);
                half3 specular = mainLight.color.rgb * _Specular.rgb * pow(NdotV, _Gloss);
                
                // 计算环境光
                half3 ambient = _GlossyEnvironmentColor;

                half4 col= half4(ambient + diffuse + specular, 1.0);
                return col;
            }
            ENDHLSL
        }
    }
    FallBack "Packages/com.unity.render-pipelines.universal/FallbackError"
}
Shader "SRP/HalfLambertBlinnPhong"
{
    Properties
    {
        _Diffuse("Diffuse Color", Color) = (1,1,1,1)
        _SpcularColor("Spcular Color", Color) = (1,1,1,1)
        _SpcularStrength("Spcular Strenght", Range(0.0, 100)) = 10
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            float4 _Diffuse;
            float4 _SpcularColor;
            float _SpcularStrength;

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 worldNormal : NORMAL;
                float3 worldPos : TEXCOORD0;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);  // 顶点坐标从模型空间转到裁剪空间
                o.worldNormal = UnityObjectToWorldDir(v.normal); // 将法线从模型空间转到世界空间
                o.worldPos = UnityObjectToWorldDir(v.vertex);    // 将顶点从模型空间转到世界空间
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                half3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 获取环境光
                half3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz); // 主光源的方向
                half3 worldNormal = normalize(i.worldNormal);            // 法线方向
                half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos); // 视角方向
                half3 lightReflectDir = reflect(-worldLightDir, worldNormal);     // 主光源光线反射方向
                half3 halfDir = normalize(viewDir + worldLightDir);

                half3 NdotL = max(0.0, dot(worldNormal, worldLightDir));   // lambert
                half3 RdotV = max(0.0, dot(worldNormal, lightReflectDir)); // phong
                half3 NdotV = max(0.0, dot(worldNormal, halfDir));         // blinn phong

                // lambert + phong
                //half3 diffuse = _LightColor0.rgb * _Diffuse * NdotL;
                //half3 specular = _LightColor0.rgb * _SpcularColor.rgb * pow(RdotV, _SpcularStrength);
                // Lambert + blinn phong
                //half3 diffuse = _LightColor0.rgb * _Diffuse * NdotL;
                //half3 specular = _LightColor0.rgb * _SpcularColor.rgb * pow(NdotV, _SpcularStrength);
                // half lambert + phong
                //half3 diffuse = _LightColor0.rgb * _Diffuse * pow(NdotL * 0.5 + 0.5, 2.0);
                //half3 specular = _LightColor0.rgb * _SpcularColor.rgb * pow(RdotV, _SpcularStrength);
                // half lambert + blinn phong
                half3 diffuse = _LightColor0.rgb * _Diffuse * pow(NdotL * 0.5 + 0.5, 2.0);
                half3 specular = _LightColor0.rgb * _SpcularColor.rgb * pow(NdotV, _SpcularStrength);

                return half4(diffuse + specular + ambient, 1);
            }
            ENDCG
        }
    }
}

相关链接 Lighting Models In Unity - Jordan Stevens