Unity3D - Shader - 实现漫反射光照模型

计算公式

基本光照模型中漫反射部分的计算公式:

  • Cdiffuse=(Clightmdiffuse)max(0,n^l^)
  • Clight = 入射光线的颜色和强度
  • mdiffuse = 材质的漫反射系数
  • n^ = 表面法线
  • l^ = 光源方向

为防止点积结果为负值,需要使用max操作,保证结果在(0,1)内。

逐顶点实现

以下是漫反射部分计算的Shader(逐顶点实现):

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
    Properties {
        // 用于控制材质的漫反射颜色 初始值为白色
        _Diffuse ("Diffuse", Color) = (1,1,1,1)
    }

    SubShader {
        Pass {
            // 指明该Pass的光照模式
            // 只有定义了正确的LightMode才能在Unity中得到一些内置光照变量
            // 例如_LightColor()
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM

            // 告诉Unity顶点着色器与片元着色器的名字
            #pragma vertex vert
            #pragma fragment frag

            // Unity内置文件
            #include "Lighting.cginc"

            // 为了使用Properties语义块中声明的属性,需要定义
            // 一个和该属性类型相匹配的变量
            // 材质的漫反射属性
            fixed4 _Diffuse;

            // 顶点着色器的输入结构体
            struct a2v {
                // 顶点位置
                float4 vertex : POSITION;
                // 顶点法线
                float3 normal : NORMAL;
            };

            // 顶点着色器的输出结构体
            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;
            };

            // 顶点着色器
            // 实现一个逐顶点的漫反射光照
            v2f vert(a2v v) {
                v2f o;

                // Transform the vertex from object space to projection space
                // mul(UNITY_MATRIX_MVP, v.vertex)
                // 把顶点位置从模型空间转换到裁剪空间
                o.pos = UnityObjectToClipPos(v.vertex);

                // Get ambient term
                // 通过Unity内置变量得到环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                // Transform the normal frame object space to world space
                // _World2Object = unity_WorldToObject
                // v.normal是模型空间下的,需要把法线转换到世界空间中。
                // 需要使用原变换矩阵的逆转置矩阵来变换法线就可以得到正确的世界空间结果
                // 模型空间到世界空间的变换矩阵的逆矩阵 = _WorldToObject
                // 调换mul函数中的位置得到和转置矩阵相同的乘法
                // mul代表的矩阵乘法,且是右乘
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

                // Get the light direction in world space
                // 规范化光源方向
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);

                // Compute diffuse term
                // 计算漫反射:漫反射 = (入射光线的颜色和强度 * 材质的漫反射颜色) * max(表面法线 * 光源方向)
                // 材质漫反射颜色 = _Diffuse.rgb
                // 光源颜色和强度信息 = _LightColor0.rgb
                // 光源方向 = _WorldSpaceLightPos0.xyz
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

                // 环境光添加到输出颜色上
                o.color = ambient + diffuse;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                return fixed4(i.color, 1.0);
            }

            ENDCG
        }
    }
    Fallback "Diffuse"
}

逐顶点光照可能会出现一些锯齿,出现一些视觉问题。

逐像素实现

逐像素实现可以得到更加平滑的光照效果。

逐像素实现:

Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
    Properties {
        _Diffuse ("Diffuse", Color) = (1,1,1,1)
    }

    SubShader {
        Pass {
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Diffuse;

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

            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
            };

            // 顶点着色器
            // 实现一个逐顶点的漫反射光照
            v2f vert(a2v v) {
                v2f o;

                // Transform the vertex from object space to projection space
                // mul(UNITY_MATRIX_MVP, v.vertex)
                o.pos = UnityObjectToClipPos(v.vertex);

                // Transform the normal fram object space to world space
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {

                // Get ambient term
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                // Get the normal in the world space
                fixed3 worldNormal = normalize(i.worldNormal);

                // Get the light direction in world space
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                // compute diffuse term
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                fixed3 color = ambient + diffuse;

                return fixed4(color, 1.0);
            }

            ENDCG
        }
    }
    Fallback "Diffuse"
}

此外还有个问题,在光无法达到的区域,模型外观通常是全黑的,没有任何明暗变化,这会是背光区域就像平面一样,失去立体。
为了解决这个问题,半兰伯特光照模型被提出。

半兰伯特(Half Lambert)光照明模型

半兰伯特光照模型仅仅是一个视觉效果,没有任何物理依据。

公式为:

cdiffuse=(clightmdiffuse)(0.5(n^l^)+0.5)

通过如上方式,可以把 n^ l^ 的计算结果从[-1, 1]映射到[0,1]范围内,也就是说对于模型的背光面,原兰伯特光照模型中点积的结果都是同一个值,即0;而在半兰伯特模型中,背光面也可以有明暗变化,不同的点积结果会映射到不同的值上。


Shader "Unity Shaders Book/Chapter 6/HalfLambert" {
    Properties {
        _Diffuse ("Diffuse", Color) = (1,1,1,1)
    }

    SubShader {
        Pass {
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Diffuse;

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

            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
            };

            // 顶点着色器
            // 实现一个逐顶点的漫反射光照
            v2f vert(a2v v) {
                v2f o;

                // Transform the vertex from object space to projection space
                // mul(UNITY_MATRIX_MVP, v.vertex)
                o.pos = UnityObjectToClipPos(v.vertex);

                // Transform the normal fram object space to world space
                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {

                // Get ambient term
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                // Get the normal in the world space
                fixed3 worldNormal = normalize(i.worldNormal);

                // Get the light direction in world space
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                // compute diffuse term
                fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;

                fixed3 color = ambient + diffuse;

                return fixed4(color, 1.0);
            }

            ENDCG
        }
    }
    Fallback "Diffuse"
}

使用Unity内置函数计算相关:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/Chapter6-BlinnPhongUseBuildFunction" {
    Properties {
        _Diffuse("Diffuse", Color) = (1,1,1,1)

        // 用于控制材质的高光反射颜色
        _Specular ("Specular", Color) = (1,1,1,1)

        // 用于控制高光区域的大小
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }
    SubShader {
        Pass {
            // 指明光照模式
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

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

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

            v2f vert(a2v v) {
                v2f o;

                // Transform the vertex from object space to projection space
                // 把顶点位置从模型空间转换到裁剪空间
                o.pos = UnityObjectToClipPos(v.vertex);

                // Transform the normal fram object space to world space
                // v.normal是模型空间下的,需要把法线转换到世界空间中。
                // 需要使用原变换矩阵的逆转置矩阵来变换法线就可以得到正确的世界空间结果
                // 模型空间到世界空间的变换矩阵的逆矩阵 = _WorldToObject
                // 调换mul函数中的位置得到和转置矩阵相同的乘法
                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                // Transform the vertex from object space to world space
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                // Get ambient term
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(i.worldNormal);

                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                // Compute diffuse term
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                // Get the view direction in world space
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

                // Get the half direction in world space
                fixed3 halfDir = normalize(worldLightDir + viewDir);

                // Compute specular term
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);

                return fixed4(ambient + diffuse + specular, 1.0);
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

你可能感兴趣的:(Unity3D,Unity3D教程学习)