笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题。
【Unity Shader】(三) ------ 漫反射和高光反射的实现 【Unity Shader】(四) ------ 纹理之法线纹理、单张纹理及遮罩纹理的实现 【Unity Shader】(五) ------ 透明效果之半透明效果的实现及原理 【Unity Shader】(六) ------ 复杂的光照(上) 【Unity Shader】(七) ------ 复杂的光照(下) 【Unity Shader】(八) ------ 高级纹理之立方体纹理及光线反射、折射的实现
目录
前言
一. 渲染路径
1.1 前向渲染
1.1.1 工作原理
1.1.2 逐像素光源数目
1.1.3 光源处理方式
1.1.4 SH 处理
1.1.5 前向渲染的 Pass 块
1.1.6 对前向渲染的总结
1.2 延迟渲染路径
1.2.1 工作原理
G-Buffer pass
1.2.2 光源处理方式
1.2.3 延迟渲染的缺点
1.2.4 延迟渲染的 Pass 块
1.2.5 对延迟渲染总结
二. Unity 中的光源
2.1 平行光
2.2 点光源
2.3 聚光灯
2.4 光源类型的影响
三 .实践
3.1 准备工作
3.2 定义 Base Pass
3.3 定义 Additional Pass
3.4 完整代码
四 . 总结
前言
本文探讨的是场景中存在多种光源时的渲染情况,在本文之前,我曾经介绍过基础的光照模型原理,而本文也会基于这些基础来进行介绍,如果读者对光照模型不太了解,可以翻看我之前的文章 【Unity Shader】(三) ------ 漫反射和高光反射的实现。本文也可以看作是对这一篇章的拓展和补充。由于完整的光照计算内容较多,所以分为两个部分介绍,本文介绍第一部分,下一篇介绍第二部分,最后会给出综合所有光照计算的完整 shader。
一. 渲染路径
渲染路径决定了光照在 shader 中是如何应用的,所以在计算光源时,需要在每个 Pass 块内指定它的渲染路径,Unity 才会为我们提供正确的光照信息。
我们可以在Unity中的摄像机的看到它支持的渲染路径
- Forward (前向渲染)
- Deferred (延迟渲染)
- Legacy Vertex Lit (遗留的顶点照明渲染)
- Legacy Deferred (遗留的延迟渲染)
下图是四种渲染路径的部分属性对比,如果想了解更详细的信息,读者可以自行去 Unity 官方文档查看。
需要注意的是:如果当前显卡不支持所设置的渲染路径,则 Unity 会自动使用更低一级的渲染路径。本文注重介绍前两种渲染路径。
我们需要在 shader 的每一个 Pass中指定它所使用的渲染路径,如:
Pass{ Tags {"LightMode" = "ForwardBase"} //其它代码 }
其中 ForwardBase 就是渲染路径其中一种,下表是部分 LightMode 标签支持的渲染路径设置选项
标签名 描述 Always 不管哪种渲染路径,该Pass总会渲染,但不计算光照 ForwardBase 用于前向渲染。计算环境光、平行光、逐顶点/SH 和 LightMaps ForwardAdd 用于前向渲染。计算额外的逐像素光源,一个 Pass 对应一个光源 Deferred 用于延迟渲染。渲染 G 缓冲 (G-buffer) ShadowCaster 把物体的深度信息渲染到阴影映射纹理或深度纹理中 PrepassBase 用于遗留的延迟渲染。渲染法线和高光反射的指数部分 PrepassFinal 用于遗留的延迟渲染。通过合并纹理、光照和自发光来渲染得到最后的颜色 Vertex、VertexLMRGBM 和 VertexLM 用于遗留的顶点照明渲染 我们同样可以在官方文档中查到其相关说明
具体解释读者可以自行查看。
只有正确地指定渲染路径,一些光照变量才会被正确地赋值,我们的计算结果才会正确。下面我们开始介绍这些渲染路径
1.1 前向渲染
1.1.1 工作原理
前向渲染路径是我们最常用的一种渲染路径。在进行一次完整的前向渲染时,要计算颜色缓冲区和深度缓冲区中的信息,遍历每一个片元,进行深度测试,如果没有通过深度测试,则说明该片元不可见,则舍弃;如果通过深度测试,则计算光照,更新帧缓冲。我在之前的【Unity Shader】(五) ------ 透明效果之半透明效果的实现及原理 中介绍了深度测试的原理,如果读者对深度测试等知识点不太熟悉的话,可以翻看一下,本文就不再赘述了。
假定一个物体在 N 个逐像素光源的影响范围内,上文已经提到,一个 Pass 块计算一个逐像素光源,那么在帧缓冲中把这些 Pass 块计算得到的光照结果混合起来就是最终的颜色值了。可以想象得到的是,如果场景中存在 N 个逐像素光源 和 M 个物体,那么就需要 N * M 个 Pass,那么很显然,随着 N 或 M 的逐渐增大,性能会逐渐下降。所以 Unity 会限制每个物体的逐像素光照项目。
1.1.2 逐像素光源数目
读者可以查看和设置项目中的逐像素光源数目,Edit -> Project Setting - > Quality,在 Rendering 中的 Pixel Light Count
一般默认为 4 ,表示一个物体可以接收除最亮的平行光外的 4 个逐像素光照。
1.1.3 光源处理方式
在 Unity 中,渲染一个物体时,会先计算哪些光源照亮了它,以及如何照亮(处理方式)。在前向渲染中,处理方式有3种:逐顶点处理,逐像素处理和球谐函数(Spherical Harmonics,SH)处理,而如何处理则由光源的类型和渲染模式决定。光源类型是指该光源是平行光还是点光源等,而渲染模式则是指该光源是否是重要的(Important)。
Type 表示光源类型,平行光,点光源,聚光灯之类;Render Mode 表示渲染模式,有 Auto , Important , Not Important 三类,而当你创建一个光源时,默认为 Auto ,这表示 Unity 会自主帮我们判断这个光源的处理方式。而如果手动设置为 Important 则表示该光源是 “重要的” ,要进行逐像素处理。而 Not Important 则表示不重要,进行逐顶点或 SH 处理。
在前向渲染中,渲染一个物体的时候,Unity 会根据场景中各个光源的设置和影响程度来进行排序。我们可以在官方文档中看到这一段的阐述
In Forward Rendering, some number of brightest lights that affect each object are rendered in fully per-pixel lit mode. Then, up to 4 point lights are calculated per-vertex. The other lights are computed as Spherical Harmonics (SH), which is much faster but is only an approximation.
即排序之后,一定数目的光源会进行逐像素处理,然后最多 4 个光源进行逐顶点处理。而其它的光源则会进行 SH 处理。SH 处理虽然很快,但是得到的值是一个粗略的近似值。我们同样引用官方的样例来解释这段话
假定存在光源 A-H,它们的颜色和光照强度一样,并且渲染模式为 Auto, 图中圆圈为物体,且在这 8 个光源的影响范围内。那么,Unity 会进行以下分组。
其中影响程度最大的 4 个光源 A - D 进行逐像素处理(因为距离相比其他光源更近),然后最多 4 个光源进行逐顶点处理(D - G),最后,剩下部分进行 SH 处理(G - H)。而我们可以注意到,这个顺序中,有部分重叠了,如 D 进行了逐像素处理,又进行了逐顶点处理,对此,官方做出了如下解释:
Note that light groups overlap; for example last per-pixel light blends into per-vertex lit mode so there are less “light popping” as objects and lights move around.
以 D 为例,既进行了逐像素处理,又进行了逐顶点处理然后进行混合,是为了减少当光源四处移动时产生的 “光弹出”(emmmm,这也许不是一个好的翻译,请原谅我英文水平的不足)。
我们来总结一下 Unity 的判断规则:
- 最亮的平行光总按逐像素处理
- 设置为 Important 的光源按逐像素处理
- 设置为 Not Important 的光源按逐顶点或者SH处理
- 按以上规则得到的逐像素光源数目少于 Quality 中设置的数目时,会有更多的光源以逐像素的方式渲染
1.1.4 SH 处理
前文数处提到光源处理方式,前两种我在 【Unity Shader】(三) ------ 漫反射和高光反射的实现 中已经介绍过了,现在我们来看一下这个 SH 处理。
官方原话:
Spherical Harmonics lights are very fast to render. They have a tiny cost on the CPU, and are actually free for the GPU to apply (that is, base pass always computes SH lighting; but due to the way SH lights work, the cost is exactly the same no matter how many SH lights are there).
大意为:SH 处理渲染是非常快的,对于CPU的消耗微小,可以自由应用GPU;因此,Base Pass (前向渲染中有两种 Pass ,分别为 Base Pass 和 Additional Pass) 块中通常计算 SH 处理的光源,而且由于 SH 处理的工作方式,不管 Base Pass 块中有多少个 SH 处理的光源,它的消耗基本都是一样的。
同样需要注意的是:
- SH 处理用于顶点而非像素,意味着不支持 Cookie 和 法线纹理
- SH 是非常低频的,这意味着无法实现清晰的灯光转换。也只影响漫反射光照(镜面高光的频率太低)。
- 当 SH 光源靠近一些表面时,看起来会得到 “错误的结果”。
总的来说,SH 处理对于小的,动态的物体来说,已经足够好了。
1.1.5 前向渲染的 Pass 块
前面我们说到前向前向渲染有两种 Pass :Base Pass , Additional Pass。
官方对此的解释为
我简单地做一个总结,大概如下
需要注意的是如果 Base Pass 中使用了 OnlyDirectional 的 PassFlags,那么这个 Base Pass 块只会计算主平行光,环境光、光照探针和光照纹理,而 SH 和 逐顶点光源则不包含在内。
总结一下:
- Base Pass 一般会定义一个,当然也可以定义多个,如需要双面渲染的情况。一个 Base Pass 仅会执行一次,所以环境光和自发光会放在 Base Pass 里面计算,因为我们只希望他们只计算一次。如果放在 Additional Pass 就有可能计算多次,然后叠加,得到错误的结果。
- Additional Pass 中,我们开启了混合 Blend one one,因为一个 Additional Pass 会被每个逐像素光源(除平行光)执行一次,我们希望它能够与上一次的光照结果在帧缓存中进行叠加,然后最终得到有多个光照的渲染效果。如果不开启混合,那么 Additional Pass 就会覆盖上一次的渲染效果,那么最终效果看起来就像是只受单个光源的影响。
- 除了定义正确的 LightMode,我们也使用了编译指令。编译指令会保证 Unity 能够为相应的 Pass 生成所需要的 shader 变种,这些变种会处理不同条件下的渲染逻辑。简单的说,使用了编译指令之后,我们才可以在 Pass 中使用正确的光照变量。读者可以自行去官方文档查看。
1.1.6 对前向渲染的总结
前向渲染是一种传统的渲染方式,工作原理也是比较简单的。通过上面的解释,我们也能够知道,前向渲染依赖于场景的复杂度,当场景中存在大量的实时光照时,程序的性能会快速下降。因为在多个光源中,每执行一个 Pass 都需要重新渲染 一遍,所以许多的计算实际上是重复了。为了解决这个问题,我们可以使用延迟渲染路径
1.2 延迟渲染路径
1.2.1 工作原理
延迟渲染是一张更古老的渲染方法,相比于前向渲染,延迟渲染会利用额外的缓冲区,统称为 G (Geometry)缓冲(G - buffer)。
我们来看一下官方对于 G 缓冲的定义:
G-Buffer pass
The g-buffer pass renders each GameObject once. Diffuse and specular colors, surface smoothness, world space normal, and emission+ambient+reflections+lightmaps are rendered into g-buffer textures. The g-buffer textures are setup as global shader properties for later access by shaders (CameraGBufferTexture0 .. CameraGBufferTexture3 names).
大意为:G-buffer Pass 渲染每个GameObject一次。把物体漫反射和镜面反射颜色,表面平滑度,世界空间法线和自发光+环境光+反射+光照贴图渲染为G缓冲区纹理。G-buffer纹理被设置为全局着色器属性,以供稍后访问着色器。
由此我们可以得知延迟渲染的工作原理为:包含两个 Pass,第一个 Pass 对每个片元进行深度测试,如果通过则把这些信息存储到 G 缓冲中;第二个 Pass 则利用 G 缓冲中各个片元信息进行光照计算,更新帧缓冲。
1.2.2 光源处理方式
在前向渲染中,光源的处理方法取决于多条规则,而在延迟渲染中,每个光源都可以按逐像素来处理,而且它的效率与光源数目无关。也就是延迟渲染不依赖于场景复杂度,而与屏幕空间大小有关。所以在这种情况下,点光源和聚光灯的消耗就会变得十分 “廉价”。
所以,如果场景中实时光照数目较多且因此造成性能下降时,可以考虑使用延迟渲染。
1.2.3 延迟渲染的缺点
- 不支持真正的抗锯齿功能
- 无法处理半透明物体
- 显卡需要支持 MRT 、Shader Mode 3.0 以上、深度渲染纹理。
- 在移动端,需要硬件支持 OpenGL ES 3.0 以上
- 需要注意的是:如果摄像机的 Projection 设置为了 Orthographi(正交),那么摄像机会回退到前向渲染。因为延迟渲染不支持正交投影。
1.2.4 延迟渲染的 Pass 块
(1)第一个 Pass 用来渲染 G 缓冲,把物体的漫反射颜色、高光反射颜色、平滑度等信息存储到 G 缓冲中,对于每个物体来说,这个 Pass 只执行一次。
(2)第二个 Pass 利用上一个 Pass 中的信息进行真正的光照计算,并存储到帧缓冲中。而默认 G 缓冲的渲染纹理:
- RT0,ARGB32格式:漫反射颜色(RGB),遮罩(A)
- RT1,ARGB32格式:镜面反射颜色(RGB),粗糙度(A)
- RT2,ARGB2101010格式:世界空间标准(RGB),未使用(A)
- RT3,ARGB2101010(非HDR)或ARGBHalf(HDR)格式:Emission + lighting + lightmaps + reflection probes
- 深度+模板缓冲区
需要十分十分十分(说 3 遍)注意的是:读者可能在别的地方看过不同的布局,因为不同版本的 Unity,其内容可能会有所不同,上面列出的 Unity 2018.2 的,读者如果不是使用 2018.2 的话,可以到官方文档查看自己所用版本的默认布局。
还有一点值得注意的是:在第二个 Pass 中计算光照时,默认仅可以使用 Unity 内置的 Standard 光照模型
1.2.5 对延迟渲染总结
延迟渲染适用于场景中存在较多实时光源且存在性能忧患的情况,延迟渲染不支持摄像机的正交投影,使用延迟渲染需要考虑硬件支持。
二. Unity 中的光源
一直以来,我们在渲染的路上都在和光照打交道,所以我们也应该熟悉一下 Unity 中的光源。
Unity 中的光源有 4 种:平行光、点光源、聚光灯和面光源(仅在烘焙时使用),我们来简单学习一下前3种光源。
2.1 平行光
平行光是最简单的光源:
- 照亮范围没有限制,位置不唯一,几何属性只有方向,相当于 “太阳” 或 “月亮”。
- 到场景中任何一点的方向都是一样的,光照强度不会随着距离而改变,即没有光照衰减的概念
2.2 点光源
点光源:
- 点光源位于空间中的一个点,并在所有方向上均匀地发出光,有空间限制
- 撞击表面的光的方向是从接触点返回到光对象的中心的线,强度随着距光的距离而减小,在指定范围内达到零
- 光强度与距离光源的距离的平方成反比。这被称为“平方反比定律”,类似于光在现实世界中的表现
2.3 聚光灯
聚光灯是这 3 种光源中最复杂的一种。
- 由空间中的一块锥形区域定义,有空间范围限制
- 光照衰减随物体逐渐远离点光源而逐渐减小,顶点处光照强度最强。边界处强度为 0
2.4 光源类型的影响
我们常用的光源属性有:光源位置、方向(光源到某一点的方向)、颜色、强度和衰减(到某一点的衰减,除了平行光,均与该点到光源的距离有关)。
三 .实践
前面说了这么多理论知识,现在我们结合起来,来编写你们最想看到的代码吧。我们来实现一个前向渲染的 shader。在这个shader 中,我们采用 Blinn - Phong 模型,对于光照的计算,我在之前的文章已经介绍过了,所以这里只是贴出一些值得注意的地方,完整的代码在这一节的最后。
3.1 准备工作
创建场景,去掉天空盒子,保留平行光和摄像机;同时,为了做明显的对比,创建一个点光源,并把颜色调为红色或蓝色这种显眼的颜色
创建Capsule,Materiral,shader 命名为 ForwardRendering。编辑 shader
3.2 定义 Base Pass
I. 定义 Properties 块,因为只计算光照,所以比较简单
Properties { _Diffuse ("Diffuse", Color) = (1, 1, 1, 1) _Specular ("Specular", Color) = (1, 1, 1, 1) _Gloss ("Gloss", Range(8.0, 256)) = 20 }
II. 先定义第一个 Pass : Base Pass
Pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag #include "Lighting.cginc"
把 LightMode 定义为 ForwardBase,并添加编译指令 #pragma multi_compile_fwdbase
III. 输入输出结构体
struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; };
IV. 顶点着色器,进行简单的空间转换
v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; }
V. 片元着色器,对最亮的平行光,环境光,自发光等做计算。但此处我们不考虑自发光
fixed4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); fixed3 halfDir = normalize(worldLightDir + viewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); fixed atten = 1.0; return fixed4(ambient + (diffuse + specular) * atten, 1.0); }
在这里 atten 表示光照衰减值,因为是平行光,所以固定为 1
至此 Base Pass 块基本定义完成。下面我们来定义 Additional Pass
3.3 定义 Additional Pass
I. 定义标签,同时注意一定要开启混合
Pass { Tags { "LightMode"="ForwardAdd" } Blend One One CGPROGRAM #pragma multi_compile_fwdadd #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" #include "AutoLight.cginc"
II. Additional Pass 中对光源的处理方式与 Base Pass 中基本一致,去掉了环境光,自发光,逐顶点光照,SH 光照部分,然后添加对不同光源类型的支持。因为在这里处理的光源可能是平行光,点光源和聚光灯中任意一种。所以我们需要对其进行判断,然后计算五个影响要素:位置,方向,颜色,强度和衰减
III. 对光源进行判断,得到光源方向,记得归一化
fixed4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); #ifdef USING_DIRECTIONAL_LIGHT fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); #else fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz); #endif
IV. 常规的光照计算
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); fixed3 halfDir = normalize(worldLightDir + viewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
V. 最后,处理不同光源的衰减
#ifdef USING_DIRECTIONAL_LIGHT fixed atten = 1.0; #else #if defined (POINT) float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz; fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; #elif defined (SPOT) float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)); fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; #else fixed atten = 1.0; #endif #endif return fixed4((diffuse + specular) * atten, 1.0);
可以看到的是,点光源和聚光灯求衰减比较复杂,要通过复杂的数学表达式计算得到。因为这种计算操作较大,所以 Unity 选择使用一张纹理作为查找表(LUT),我们首先得到光源空间下的坐标,然后用这个坐标对衰减纹理进行采样得到衰减值。
最后,保存两部分的代码,查看效果,可以看到,平行光和点光源都照亮了物体
3.4 完整代码
Shader "Unity/Custom/01--Forward Rendering" { Properties { _Diffuse ("Diffuse", Color) = (1, 1, 1, 1) _Specular ("Specular", Color) = (1, 1, 1, 1) _Gloss ("Gloss", Range(8.0, 256)) = 20 } SubShader { Tags { "RenderType"="Opaque" } Pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma multi_compile_fwdbase #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; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); fixed3 halfDir = normalize(worldLightDir + viewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); fixed atten = 1.0; return fixed4(ambient + (diffuse + specular) * atten, 1.0); } ENDCG } Pass { Tags { "LightMode"="ForwardAdd" } Blend One One CGPROGRAM #pragma multi_compile_fwdadd #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" #include "AutoLight.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; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); #ifdef USING_DIRECTIONAL_LIGHT fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); #else fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz); #endif fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); fixed3 halfDir = normalize(worldLightDir + viewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); #ifdef USING_DIRECTIONAL_LIGHT fixed atten = 1.0; #else #if defined (POINT) float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz; fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; #elif defined (SPOT) float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)); fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; #else fixed atten = 1.0; #endif #endif return fixed4((diffuse + specular) * atten, 1.0); } ENDCG } } FallBack "Specular" }
四 . 总结
本文主要解释了光照在渲染中的关系和处理、渲染路径及其工作原理以及光源类型,影响属性等,可能你会感慨其复杂性,但不得不说,在学习 shader 的路上,这注定是不可避免且极其重要的一点。在第3节我们也实现了一个可以初步处理多光源的shader,不过即便如此,它还是欠缺的。我们会在下一篇章中介绍关于光照衰减和阴影的知识,届时我们便可以实现一个真正的完整的光照 shader 。
本文可能比较啰嗦,毕竟理论知识较多,笔者的文笔也一般般,但仍然希望本文能够对你的学习有所帮助。
路漫漫其修远兮,我也想躺着玩手机~~~~~