【unity shader 入门精要】CH6-2 Unity中的基础光照-实践部分
发布日期:2021-05-07 01:50:11 浏览次数:35 分类:精选文章

本文共 10934 字,大约阅读时间需要 36 分钟。

文章目录

在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:

在这里插入图片描述

在这里插入图片描述

上一篇:【unity shader 入门精要】CH8 透明效果
下一篇:【unity shader 入门精要】CH7 基础纹理

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2025年04月06日 06时12分02秒