【unity shader 入门精要】CH6-2 Unity中的基础光照-实践部分

在Unity Shader中实现漫反射光照模型

基本光照模型中漫反射部分的计算:
在这里插入图片描述
c l i g h t c_{light} clight:入射光线的颜色和强度
m d i f f u s e m_{diffuse} mdiffuse:材质的漫反射系数
n \boldsymbol{n} n:法线方向
I \boldsymbol{I} I指向光源的单位矢量。
在这里插入图片描述
max():防止点积结果为负值

CG中的saturate函数:
参数:x,用于操作的标量或矢量,可以是float、float2、float3等类型。
描述:把x截取在[0, 1]范围内,如果x是一个矢量,那么会对它的每一个分量进行这样的操作。

逐顶点的漫反射光照

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

Shader "Unlit/6-2Shader" {
    Properties {
        // 得到并控制材质的漫反射颜色
        _Diffuse ("Diffuse", Color) = (0.2, 0.4, 0.5, 0.6)
    }

    SubShader {
        Pass {
            Tags {
                // 指明该Pass的光照模式
                "LightMode" = "ForwardBase"
            }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"   // 使用unity的一些内置变量

            fixed4 _Diffuse;    // 使用Properties语义块中声明的属性,要先定义

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL; // 把模型顶点的法线信息存储到normal变量中
            };

            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;   // 把在顶点着色器中计算得到的光照颜色传递给片元着色器
            };

            // 顶点着色器
            v2f vert (a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                // Transform the normal from object space to world space
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

                fixed3 worldLight = normalize(_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"
}

逐像素光照

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

Shader "Unlit/DiffusePixelLevel" {
    Properties {
        // 得到并控制材质的漫反射颜色
        _Diffuse ("Diffuse", Color) = (0.3, 0.6, 0.5, 0.6)
    }

    SubShader {
        Pass {
            Tags {
                // 指明该Pass的光照模式
                "LightMode" = "ForwardBase"
            }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"   // 使用unity的一些内置变量

            fixed4 _Diffuse;    // 使用Properties语义块中声明的属性,要先定义

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL; // 把模型顶点的法线信息存储到normal变量中
            };

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

            // 顶点着色器
            v2f vert (a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                return o;
            }

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

                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

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

                fixed3 color = ambient + diffuse;

                return fixed4 (color, 1.0);
            }

            ENDCG
        }
    }
    Fallback "Diffuse"

}

逐像素光照可以得到更加平滑的光照效果。
但是,即便使用了逐像素漫反射光照,在光照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面一样,失去了模型细节表现。
实际上我们可以通过添加环境光来得到非全黑的效果,但即便这样仍然无法解决背光面明暗一样的缺点。
为此,有一种改善技术被提出来,这就是半兰伯特(Half Lambert)光照模型。

半兰伯特模型

广义的半兰伯特光照模型公式:
在这里插入图片描述
与原兰伯特模型相比,半兰伯特光照模型没有使用max操作来防止 n \boldsymbol{n} n I \boldsymbol{I} I 的点积为负值,而是对其结果进行了一个α倍的缩放再加上一个β大小的偏移。
绝大多数情况下,α和β的值均为0.5:
在这里插入图片描述
通过这样的方式,我们可以把 n ⋅ I \boldsymbol{n} · \boldsymbol{I} nI 的结果范围从[-1, 1]映射到[0, 1]范围内。对于模型的背光面,在兰伯特光照模型中点积结果将映射到0处;而在兰伯特模型中,背光面也可以有明暗变化,不同的点积结果会映射到不同的值上。

需要注意的是,半兰伯特是没有任何物理依据的,它仅仅是一个视觉加强技术。

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

Shader "Unlit/HalfLambert" {
    Properties {
        // 得到并控制材质的漫反射颜色
        _Diffuse ("Diffuse", Color) = (0.3, 0.6, 0.5, 0.6)
    }

    SubShader {
        Pass {
            Tags {
                // 指明该Pass的光照模式
                "LightMode" = "ForwardBase"
            }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"   // 使用unity的一些内置变量

            fixed4 _Diffuse;    // 使用Properties语义块中声明的属性,要先定义

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL; // 把模型顶点的法线信息存储到normal变量中
            };

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

            // 顶点着色器
            v2f vert (a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

                return o;
            }

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

                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                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 Shader中实现高光反射光照模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
反射方向 r \boldsymbol{r} r 的计算:
在这里插入图片描述


CG提供了计算反射方向的函数reflect:
函数reflect(i, n)
参数
I \boldsymbol{I} I ,入射方向
n \boldsymbol{n} n ,法线方向
描述:当给定入射方向i和法线方向n时,reflect函数可以返回反射方向

在这里插入图片描述

逐顶点光照

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

Shader "Unlit/SpecularVertexLevel" {
    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"

            // 由于颜色属性的范围在0到1之间,因此对于_Diffuse和_Specular属性我们可以使用fixed精度的变量来存储它
            // 而_Gloss的范围很大,因此我们使用float精度来存储。
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

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

            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR;
            };

            v2f vert (a2v v) {
                v2f o;

                o.pos = UnityObjectToClipPos(v.vertex);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));

                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

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

                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));

                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

                o.color = ambient + diffuse + specular;

                return o;
            }

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

            ENDCG
        }
    }
    Fallback "Specular"
}

在这里插入图片描述
在这里插入图片描述
使用逐顶点的方法得到的高光效果有比较大的问题,可以看出高光部分明显不平滑。这主要是因为,高光反射部分的计算是非线性的,而在顶点着色器中计算光照再进行插值的过程是线性的,破坏了原计算的非线性关系,就会出现较大的视觉问题。因此,我们就需要使用逐像素的方法来计算高光反射。

逐像素光照

在这里插入图片描述
在这里插入图片描述

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

Shader "Unlit/SpecularPixelLevel" {
    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;
                float4 normal : NORMAL;
            };

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

            // 顶点着色器只需要计算世界空间下的法线方向和顶点坐标,并把它们传递给片元着色器即可
            v2f vert (a2v v) {
                v2f o;

                o.pos = UnityObjectToClipPos(v.vertex);

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

                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

                return o;
            }

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

                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));

                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);

                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

                return fixed4(ambient + diffuse + specular, 1.0);
            }

            ENDCG
        }
    }
    Fallback "Specular"
}

Blinn-Phong光照模型

Blinn模型没有使用反射方向,而是引入一个新的矢量 h \boldsymbol{h} h ,它是通过对视角方向 v \boldsymbol{v} v 和光照方向 I \boldsymbol{I} I 相加后再归一化得到的:
在这里插入图片描述
Blinn模型计算高光反射:
在这里插入图片描述
我们只需要修改片元着色器中对高光反射部分的计算代码:
在这里插入图片描述
在这里插入图片描述
可以看出,Blinn-Phong光照模型的高光反射部分看起来更大、更亮一些。

在实际渲染中,绝大多数情况我们都会选择Blinn-Phong光照模型。

需要再次提醒的是,这两种光照模型都是经验模型,也就是说,我们不应该认为Blinn-Phong模型是对“正确的”Phong模型的近似。

实际上,在一些情况下, Blinn-Phong模型更符合实验结果。

使用Unity内置的函数

在这里插入图片描述
需要注意的是,这些函数都没有保证得到的方向矢量是单位矢量,因此,我们需要在使用前把它们归一化

Unity帮我们处理了不同种类光源的情况。计算光源方向的3个函数:WorldSpaceLightDir、UnityWorldSpaceLightDir和ObjSpace LightDir仅可用于前向渲染。这是因为只有在前向渲染时,这3个函数里使用的内置变量_WorldSpaceLightPos0等才会被正确赋值。

使用内置函数改写Unity Shader

使用这些内置函数来改写使用Blinn-Phong光照模型的Unity Shader:

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值