
【unity shader 入门精要】CH6-2 Unity中的基础光照-实践部分
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():防止点积结果为负值
与原兰伯特模型相比,半兰伯特光照模型没有使用max操作来防止 n \boldsymbol{n} n 和 I \boldsymbol{I} I 的点积为负值,而是对其结果进行了一个α倍的缩放再加上一个β大小的偏移。 绝大多数情况下,α和β的值均为0.5:
通过这样的方式,我们可以把 n ⋅ I \boldsymbol{n} · \boldsymbol{I} n⋅I 的结果范围从[-1, 1]映射到[0, 1]范围内。对于模型的背光面,在原兰伯特光照模型中点积结果将映射到0处;而在半兰伯特模型中,背光面也可以有明暗变化,不同的点积结果会映射到不同的值上。
反射方向 r \boldsymbol{r} r 的计算:
使用逐顶点的方法得到的高光效果有比较大的问题,可以看出高光部分明显不平滑。这主要是因为,高光反射部分的计算是非线性的,而在顶点着色器中计算光照再进行插值的过程是线性的,破坏了原计算的非线性关系,就会出现较大的视觉问题。因此,我们就需要使用逐像素的方法来计算高光反射。
Blinn模型计算高光反射:
我们只需要修改片元着色器中对高光反射部分的计算代码:
可以看出,Blinn-Phong光照模型的高光反射部分看起来更大、更亮一些。
发布日期:2021-05-07 01:50:11
浏览次数:35
分类:精选文章
本文共 10934 字,大约阅读时间需要 36 分钟。
文章目录
在Unity Shader中实现漫反射光照模型
基本光照模型中漫反射部分的计算:


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)光照模型。半兰伯特模型
广义的半兰伯特光照模型公式:


需要注意的是,半兰伯特是没有任何物理依据的,它仅仅是一个视觉加强技术。
// 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中实现高光反射光照模型



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-Phong光照模型。
需要再次提醒的是,这两种光照模型都是经验模型,也就是说,我们不应该认为Blinn-Phong模型是对“正确的”Phong模型的近似。
实际上,在一些情况下, Blinn-Phong模型更符合实验结果。
使用Unity内置的函数
Unity帮我们处理了不同种类光源的情况。计算光源方向的3个函数:WorldSpaceLightDir、UnityWorldSpaceLightDir和ObjSpace LightDir仅可用于前向渲染。这是因为只有在前向渲染时,这3个函数里使用的内置变量_WorldSpaceLightPos0等才会被正确赋值。
使用内置函数改写Unity Shader
使用这些内置函数来改写使用Blinn-Phong光照模型的Unity Shader:
发表评论
最新留言
路过按个爪印,很不错,赞一个!
[***.219.124.196]2025年04月06日 06时12分02秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
阿里钉钉面试题
2019-03-06
华为社招笔试
2019-03-06
MFC的Dlg和App什么区别?应用程序类与对话框类
2019-03-06
C\C++下获取系统进程或线程ID(转)
2019-03-06
VS环境变量(转)
2019-03-06
C++中找资源或者函数的方法
2019-03-06
一些留给自己的思考题(只求回过头来能够有所获)
2019-03-06
SQL函数返回表的写法
2019-03-06
delete对象时会自动调用类的析构函数
2019-03-06
C++ 子类对象直接赋值给父类对象可行,反过来不行
2019-03-06
WMWare下安装centOS7,并使用xshell进行连接记录.
2019-03-06
linux下同一个动态库名为何辣么多的.so文件
2019-03-06
SQL联表的方式(逗号, Left Join, Right Join)
2019-03-06
牛客网输入输出举例
2019-03-06
字符串初始化时的注意点
2019-03-06
dll路径加载顺序
2019-03-06
悬垂指针和野指针的区别
2019-03-06
软考相关试题
2019-03-06
顺序表的操作
2019-03-06
常量表达式
2019-03-06