兰伯特(Lambert)光照模型总结

兰伯特光照模型是经验模型,主要用来模拟粗糙物体表面的光照现象,即漫反射。
漫反射特点
1:反射强度与观察者的角度没有关系
2:反射强度与光线的入射角度有关系
漫反射光照符合兰伯特定律(Lambert’s law):反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比,夹角越大,受到的光线照射量越少,当夹角大于90度,光线照射物体背面,此时认为光照强度为0。

在这里插入图片描述
漫反射公式:漫反射 = 光源颜色 x 模型基础颜色 x(法线 · 光源方向),这里使用max函数防止结果为负值,另外可以乘以一个系数来控制最终结果。这里法线和光源方向点乘表示两者之间的夹角,夹角越大点乘值越小
在这里插入图片描述

漫反射,兰伯特逐顶点光照

Shader "MyCustom/Diffuse_Lambert_Vertex"
{
    
    
    Properties
    {
    
    
        //材质的漫反射颜色
        _Color("Base Color", color) = (1.0, 1.0, 1.0, 1.0)
        //漫反射系数
        _kD("kD", Range(0, 1)) = 1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            //引入Unity内置的一些变量
            #include "UnityCG.cginc"

            //UnityCG.cginc中定义了结构体appdata_base,appdata_tan,appdata_full用于顶点着色器输入
            //这里自定义appdata类型,只包含需要的数据
            struct appdata
            {
    
    
                float4 vertex : POSITION; //顶点位置
                float3 normal : NORMAL;   //法线
            };

            //顶点着色器输出
            struct v2f
            {
    
    
                //SV_POSITION是裁剪空间中的顶点坐标,它是DirectX 10中引入的系统数值语义,在大多数平台上,它和POSITION语义是等价的,
                //但在某些平台(例如PS4)上必须使用SV_POSITION来修饰顶点着色器的输出,否则无法让Shader正常工作
                //SV_POSITION一旦被作为顶点着色器的输出语义,那么顶点位置就被固定了,后续不能再被改变它的空间位置
                float4 vertex : SV_POSITION;
                float4 col : COLOR;
            };

            //为了使用Properties语义块中声明的属性,需要定义一个和该属性类型相匹配的变量
            //由于颜色属性的范围在0到1之间,因此我们可以使用fixed精度的变量来存储它
            float4 _Color;
            float _kD;
            //表示这个变量的初始值来自于外部的其他环境
            uniform float4 _LightColor0;

            v2f vert (appdata v)
            {
    
    
                v2f o;

                //Unity内置 模型 * 世界 * 投影矩阵 UNITY_MATRIX_MVP,把顶点位置从模型空间转换到裁剪空间中
                o.vertex = UnityObjectToClipPos(v.vertex);

                //在计算法线和光线之间的点积时,只有两者处于同一坐标系下,它们的点积才有意义,
                //所以需要把顶点的法线从模型空间转到世界空间,unity_WorldToObject是4*4
                float3 worldNormal = normalize(mul(float4(v.normal, 0.0), unity_WorldToObject).xyz);
                //也可以这么写
                // float3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

                //获取光源方向(们假设场景中只有一个光源且该光源的类型是平行光)
                float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

                //当法线和光线之间夹角大于90时,光线是在照射物体的背面,此时光照强度为0
                float lambert = max(dot(worldNormal, worldLight), 0.0);
                //也可以使用saturate函数把参数截取到[0, 1]的范围内,防止负值
                // float lambert = saturate(dot(worldNormal, worldLight));
                
                float3 diffuse = _kD * lambert * _Color.rgb * _LightColor0.rgb;

                //环境光
                float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                float3 finalColor = diffuse + ambient;
                o.col = float4(finalColor, 1.0);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                return i.col;
            }
            ENDCG
        }
    }
}

漫反射,兰伯特逐像素光照

Shader "MyCustom/Diffuse_Lambert_Pixel"
{
    
    
    Properties
    {
    
    
        _Color("Base Color", color) = (1.0, 1.0, 1.0, 1.0)
        _kD("kD", Range(0, 1)) = 1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                //顶点着色器中计算世界空间下法线,传递给片元着色器
                float3 worldNormal : TEXCOORD0;
            };

            float4 _Color;
            float _kD;
            uniform float4 _LightColor0;

            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                return o;
            }

            //将光照的计算转移到片元着色器中
            fixed4 frag (v2f i) : SV_Target
            {
    
    
                float3 worldNormal = i.worldNormal;
                float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

                float lambert = max(dot(worldNormal, worldLight), 0.0);
                float3 diffuse = _kD * lambert * _Color.rgb * _LightColor0.rgb;
                float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                float3 finalColor = diffuse + ambient;

                return float4(finalColor, 1.0);
            }
            ENDCG
        }
    }
}

半兰伯特逐像素光照

Shader "MyCustom/HalfLambert"
{
    
    
    Properties
    {
    
    
        _Color ("Base Color", color) = (1.0, 1.0, 1.0, 1.0)
        _kD ("kD", Range(0, 1)) = 1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }
        LOD 100

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
            };

            float4 _Color;
            float _kD;
            uniform float4 _LightColor0;

            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                float3 worldNormal = i.worldNormal;
                float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                
                // 广义上的半兰伯特 half lambert = light * diffuse * (α* (n * l) + β)
                // half lambert = light * diffuse * (0.5 * (n * l) + 0.5)
                float halfLambert = 0.5 * dot(worldNormal, worldLight) + 0.5;
                
                float3 diffuse = _kD * halfLambert * _Color.rgb * _LightColor0.rgb;
                float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                float3 finalColor = diffuse + ambient;

                return float4(finalColor, 1.0);
            }
            ENDCG
        }
    }
}

实际对比

在这里插入图片描述
逐顶点光照:计算量较小,明暗过渡的边缘有锯齿,不够自然。而且,由于逐顶点光照会在渲染图元内部对顶点颜色进行插值,这会导致渲染图元内部的颜色总是暗于顶点处的最高颜色值,这在某些情况下会产生明显的棱角现象

逐像素光照:计算量较大,光照效果更加平滑,有一个问题仍然存在。在被光面,模型的外观通常是全黑的,没有任何明暗变化,由公式 max(dot(n, l), 0) 可以看出,被光面颜色都是0,这会使模型的背光区域看起来就像一个平面一样,失去了模型细节表现。为此,有一种改善技术被提出来,这就是半兰伯特光照模型

半兰伯特光照:为了解决兰伯特光照不够亮的问题,n * l 的值域是 [-1, 1], 0.5 * dot(n * l) + 0.5 处理后值域变成了 [0, 1]

参考《Unity Shader入门精要》

猜你喜欢

转载自blog.csdn.net/sinat_34014668/article/details/125592172