|
《Unity Shader入门精要》源代码
9.1 Unity 的渲染路径
渲染路径决定了光照是如何应用到 Unity Shader 中的,Unity 会根据我们渲染的不同的渲染路径为我们计算对应点的光照信息。
可以在 Edit -> Project Setting -> Graphics -> Render Path 调整默认的渲染路径,也可以直接在相机的 Camera 组件中直接调节它。
完成上面设置后,我们就可以在 Pass 中使用标签来指定该 Pass 的渲染路径,例如:
Pass{
Tags {"LightMode"="ForwardBase"}
...
}
表 9.1 给出了 Pass 的 LightMode 标签支持的渲染路径的设置选项:
Image
设置渲染路径相当于和 Unity 底层做了一次沟通,Unity 会根据我们设置的渲染路径,计算出对应的光照属性,并给对应的内置变量赋值。
9.1.1 前向渲染路径
1. 前项渲染的原理
没进行一次完整的前项渲染,我们会渲染该对象的图元,以及两个缓冲区信息:颜色缓冲区、深度缓冲区。
以及一个物体在多个逐像素光源的影响区域就要进行执行多个 Pass,并将每个 Pass 计算的结果混合。假如,场景中有 N 个物体,每个物体受 M 个光源影响,那渲染整个场景需要 N * M 个 Pass。
在 Unity 中前向渲染路径的处理方式有 3 种:逐顶点处理、逐像素处理、球谐函数(Spherical Harmonics,SH)处理。决定光源使用哪种处理方式取决于它的类型和渲染模式。它的类型指的是平行光、聚光等,渲染模式指的是是否为重要的(Important),如果我们把一个光照的模式设为 Important,就意味着我们告诉 Unity 这个光源很重要需要逐像素处理。可以在官员的 Light 组件 设置这些。
在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。其中,一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。Unity使用的判断规则如下:
- 场景中最亮的平行光总是按逐像素处理的。
- 渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理。
- 渲染模式被设置成Important的光源,会按逐像素处理。
- 如果根据以上规则得到的逐像素光源数量小于 Quality Seting 中的逐像素光源数量(Pixel Light Count)。
两种 Pass 进行的标签和渲染设置以及常规光照计算:
Image
图 9.4 的几点说明:
- 在前项渲染设置中,除了设置 Pass 标签,还使用了 #pragma multi_compile_fwdbase 这样的编译命令。该命令可以为相应类型的 Pass 生成所有需要的 Shader 变种,这些变种会处理不同条件下的渲染逻辑,例如是否使用光照贴图、是否开启阴影等。也就是说,只有分别给 Base Pass 和 Addition Pass 使用这两条编译指令,我们才能在相关 Pass 中得到正确的光照变量,例如光照衰减值
- Base Pass 旁边给出了 其支持的一些光照特性。
- Base Pass 中渲染的平行光默认是支持阴影的(如果开启了光源的阴影功能),而 Additional Pass中渲染的光源在默认情况下是没有阴影效果的,即便我们在它的Light组件中设置了有阴影的 Shadow Type。但我们可以在 Additional Pass 中使用 #pragma multi_compile.fwdadd_fullshadows 代替pragma multi_compile_fwdadd 编译指令,为点光源和聚光灯开启阴影效果,但这需要Unity 在内部使用更多的 Shader变种。
- 环境光和自发光也是在 Base Pass 中计算的。这是因为,对于一个物体来说,环境光和自发光我们只希望计算一次即可,而如果我们在Additional Pass中计算这两种光照,就会造成叠加多次环境光和自发光,这不是我们想要的。
- 在 Additional Pass 的渲染设置中,我们还需要开启和设置了混合模式。这是因为,我们希望每个Additional Pass 可以与上一次的光照结果在帧缓存中进行叠加,从而得到最终的有多个光照的渲染效果。如果我们没有开启和设置混合模式,那么Additional Pass的渲染结果台覆盖掉之前的渲染结果,看起来就好像该物体只受该光源的影响。通常情况下,我们选择的混合模式是 Blend One One。
- 对于前向渲染来说,一个Unity Shader 通常会定义一个Base Pass(Base Pass 也可以宛义多次,例如需要双面渲染等情况)以及一个Additional Pass。一个Base Pass仅会执行次(定义了多个Base Pass的情况除外),而一个Additional Pass会根据影响该物体的其能逐像素光源的数目被多次调用,即每个逐像素光源会执行一次Additional Pass。
3. 内置的光照变量和函数
Image
Image
9.1.2 顶点照明渲染路径
该渲染路径是前项渲染路径中的一个子集,它只能使用逐顶点的方法来计算光照,而不能使用逐像素才能得到的效果,例如阴影、法线映射等,这是因为如果选择此渲染路径,Unity 只会填充那些顶点相关变量。对应的它对硬件配置要求最低,但效果也最差。
Image
Image
9.1.3 延迟渲染路径
当场景中包含大量光源实时光源时,前项渲染的性能会急剧下降。每个物体可能需要执行多个 Pass 。没执行一次 Pass 我们需要从新渲染一次物体,其中很多计算是重复的。
延迟渲染,除了有前面渲染中使用颜色缓冲、深度缓冲、还有 G 缓冲(G-buffer),G 是 Geometry 的缩写。该缓冲区存储了我们所有关心的表面的其他信息,例如该表面的法线、位置、用于光照计算的材质属性等。
1. 延迟渲染的原理
延迟渲染主要包含两个 Pass。第一个 Pass 不进行任何光照计算,而仅仅计算哪些片元是可见的,并将这些片元的相关信息记录到 G 缓冲区中。第二个 Pass 利用 G 缓冲区中的信息进行光照计算。
通常情况下延迟渲染使用的 Pass 数目就是两个,与场景光源数目无关。
2. Unity 中的延迟渲染
Unity 中有两种延迟渲染,一种是遗留的延迟渲染路径,即较久的版本,另一种是 Unity5.x 使用的延迟渲染路径,该路径需要一定的硬件支持。如果场景中有大量光源,最好使用延迟渲染路径。
延迟渲染的缺点:
- 不支持真正的抗锯齿(anti-aliasing)功能。
- 不能处理半透明物体。
- 对显卡有一定要求。如果要使用延迟渲染的话,显卡必须支持 MRT(Multiple Render Targets)、Shader Mode 3.0及以上、深度渲染纹理以及双面的模板缓冲。
当使用延迟渲染时,Unity 为我们提供了两个 Pass:
(1)第一个Pass用于渲染G缓冲。在这个Pass中,我们会把物体的漫反射颜色、高光反射酷、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G 缓冲区中。对于每个物体来说,这个Pass仅会执行一次。
(2)(2)第二个 Pass用于计算真正的光照模型。这个Pass会使用上一个Pass 中国要计算终的光照颜色,再存储到帧缓冲中。
默认的 G缓冲区包含了以下几个渲染纹理(Render Texture,RT)。(注意,不同Unity 版本的渲染纹理存储内容会有所不同》
- RTO:格式是ARGB32,RGB通道用于存储漫反射颜色,A 通道没有被使用。
- RT1:格式是ARGB32,RGB通道用于存储高光反射颜色,A通道用于存储高光反射的指数部分。。 RT2:格式是ARGB2101010,RGB通道用于存储法线,A通道没有被使照。
- RT3:格式是 ARGB32(非 HDR)或ARGBHalf(HDR),用于存储自发光 + lightmap +反射探针(reflection probes).
- 深度领冲和模板缓冲,在每一个 Pass 中计算光照时,默认情况下仅可以使用Unity 内置的 Standard 光照模型。如果我们想使用员他的光照模型,就需要替换掉原有的 Intemal-DeferredShading.sbader 文件。更详细的信息可以访问官网(https://docs.unity3d.com/Manual/RenderTech-DeferredShading.html)
3. 可访问的内置变量
这些变量都可以在 UnityDeferredLibary.cginc 中找到它们的声明。
Image
9.2 Unity 的光源类型
Unity 中一共有 4 种光源,平行光、点光源、聚光灯、面光源。
9.2.1 光源类型
每种光源的属性不同。最常用的光源属性有光源的位置、方向、颜色、强度、衰减。
1. 平行光
平行光相当于太阳,它的几何属性只有方向。
2. 点光源
点光源照亮的空间是球形的,有光源半径、方向属性由光源位置减去某点位置得到、光源的颜色和强度、以及衰减
3. 聚光灯和点光源基本一致,不过照亮空间是锥形区域,衰减更难计算,因为还要判断在不在照明区域。
9.2.2 在前向渲染中处理不同的光源类型
本节将介绍如何在Unity Shader 中访问光源的属性(位置、方向、颜色、强度以及衰减)。
一、实践
场景搭建:需要在场景中添加一个 胶囊(capsule)和 3 个点光源,并给不同的点光源修改不同的光照颜色。
本节将使用 Blinn-Phong 光照模型,在这里只给出关键代码,省略了与之前章节重复的代码。
(1)首先定义第一个 Pass -- Base Pass,并且为其设置渲染路劲标签:
{
//Base Pass: 计算环境光和第一个逐像素光照(平行光)
Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma multi_compile_fwdbase 指令可以保证我们在 Shader 中使用的光照衰减等变量可以被正确赋值
(2)在 Base Pass 中计算环境光
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
环境光与自发光只计算一遍,因此在后面的 Addition Pass 就不会再计算这个部分了
(3)在 Base Pass 中处理场景中最重要的平行光。如果场景中包含多个平行光,Unity 会选择最亮的那个进行逐像素处理;如果没有任何平行光,那 Base Pass 会被当成全黑光源处理。光源的 5 个属性:位置、方向、颜色、强度、衰减。我们可以直接用 _WorldSpaceLightPos 来得到平行光方向,用 _LightColor0 来获取它的颜色和强度的乘积,而平行光没有衰减,所以衰减设为 1.0 即可。
//漫反射光
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
......
//高光
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);
//平行光的衰减值总是 1(无衰减)
fixed atten = 1.0;
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
(4) 给场景中的其他逐像素光源定义 Additional Pass。首先设置 Pass 的渲染路径标签:
Pass
{
Tags{"LightMode" = "ForwardAdd"}
Blend One One
CGPROGRAM
#pragma multi_compile_fwdadd
#pragma multi_compile_fwdadd 指令可以保证 Addition Pass 访问到正确的光照变量。 Blend 指令开启混合模式,否则Additional Pass 的结果会直接覆盖之前的结果,而我们想要的则是它们共同的结果。除了 Blend One One,常见的混合系数还有 Blend SrcAlpha One。
(5)通常来说,Additional Pass 的光照处理和 Base Pass 的光照处理差不多,不过要去掉环境光、自发光、逐顶点光照、SH 光照的部分,并添加一些对不同光源类型的支持。 同时由于 Addition Pass 处理的光源类型可能是 平行光、点光源 或是 聚光灯,所以我们需要根据光源类型分别计算他们的光照属性:
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
#endif
如果是平行光,那光源方向可直接由 _WorldSpaceLightPos0.xyz 得到,否则需要用这个位置减去世界空间下的顶点位置。所以需要先根据是否定义了 USING_DIRECTIONAL_LIGHT 来判断是否是平行光。
(6)计算不同光源的衰减
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
Unity 使用一个纹理作为查找表(Lookup Table,LUT)代替复杂的计算来得到光源的衰减。首先得到光源空间下的坐标,然后对衰减纹理进行采样。unity_WorldToLight 是对应的变换矩阵,定义在 AutoLight.cginc 库中。
结果:
Image
首先在 Base Pass 进行 逐顶点、SH 光照,以及逐像素的平行光,并根据光源的重要性,逐个调用 Addition Pass。光源的重要性光源强度、距离、颜色有关。
9.3 Unity 的光照衰减
Unity 使用 查找表 来计算光照衰减。
好处:
- 不依赖数学公式的复杂性,比起数学公式计算,能提升一定性能,而且大部分的情况得到的结果也都是良好的。
坏处:
- 需要预处理得到采样纹理,而且纹理的大小也会影响精度。
- 不直观,同时也不方便,因此一旦将数据存储到查找表中,我们就无法使用其他数学公式来计算衰减。
9.3. 1 用于光照衰减的纹理
Unity 在内部使用一张名为 _LightTexture0 的纹理来计算光照衰减。如果我们对该光源使用了 cookie,那衰减查找纹理是 _LightTextureB0。通常情况下我们只关心对角线上的纹理颜色值。(0,0)是与光源重合的值,(1,1)是最远点的值。
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
将顶点位置从 世界空间 变到 光源空间后,并使用距离的平方来对纹理进行采样。
9.3.2 使用数学公式计算衰减
尽管纹理采样的方法更快,但是有时我们希望用公式计算光源的衰减:例如:
float distance = length(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
atten = 1.0/distance; //线性衰减
当时由于 Unity 没有提供光源的范围,聚光灯的朝向,张开角度等信息,导致得到的结果不尽人意。
9.4 Unity 的阴影
9.4.1 阴影是如何产生的
注:该部分直接复制的原文
阴影区域的产生是因为光线被其他不透明物体遮挡而无法到达这些区域。
在实时渲染中,我们最常使用的是一种名为 Shadow Map 的技术。由于 阴影就是你看得到,而光源看不到的地方,所以这种技术简单来说就是,它会首先把摄像机的位置放在与光源重合的位置上,然后记录深度信息,如果一个顶点的深度大于记录的深度那就为阴影。
Shadow Map(该文章8.6节)
在前向渲染路径中,如果场景中最重要的平行光开启了阴影,Unity会该源算阴影映射纹理(shadowmap)。这张阴影映射纹理本质上也是一张深度图,它记录了从该光源的位置出发、能看到的场景中距离它最近的表面位置(深度信息)。
那么,在计算阴影映射纹理时,我们如何判定距离它最近的表面位置呢?一种方法是,先把摄像机放置到光源的位置上,然后按正常的渲染流程,即调用 Base Pass 和 Additional Pass 来更新深度信息,得到阴影映射纹理。但这种方法会对性能造成一定的浪费,因为我们实际上仅仅需要深度信息而已,而 Base Pass 和 Additional Pass 中往往涉及很多复杂的光照模型计算。
因此,Uniy选择使用一个额外的Pass 来专门更新光源的阴影映射纹理,这个 Pass 就是 LightMode 标签被设置为 ShadowCaster 的Pass。这个 Pass 的渲染目标不是帧缓存,而是 阴影映射纹理(或深度纹理)。Unity首先把摄像机放置到光源的位置上,然后调用该Pass,通过对顶点变换后得到光源空间下的位置,并据此来输出深度信息到阴影映射纹理中。因此,当开启了光源的阴影效果后,底层渲染引擎首先会在当前渲染物体的 Unity Shader 中找到 LightMode 为 ShadowCaster 的 Pass,如果没有,它就会在 Fallback 指定的Unity Shader 中继续寻找,如果仍然没有找到,该物体就无法向其他物体投射阴影(但它仍然可以接收来自其他物体的阴影)。当找到了一个 LightMode 为 ShadowCaster 的 Pass 后,Unity 会使用该 Pass 来更新光源的阴影映射纹理。
在传统的阴影映射纹理的实现中,我们会在正常渲染的Pass 中把顶点位置变换到光源空间下,以得到它在光源空间中的三维位置信息。然后,我们使用xy分量对阴影映射纹理进行采样,得到阴影映射纹理中该位置的深度信息。如果该深度值小于该顶点的深度值(通常由z分量得到),那么说明该点位于阴影中。但在 Unity 5 中,Unity 使用了不同于这种传统的阴影采样技术,即屏幕空间的阴影映射技术(Screenspace Shadow Map)。屏幕空间的阴影映射 原本是延迟渲染中产生阴影的方法。需要注意的是,并不是所有的平台Unity 都会使用这种技术。这是因为,屏幕空间的阴影映射需要显卡支持 MRT,而有些移动平台不支持这种特性。
当使用了屏幕空间的阴影映射技术时,Unity 首先会通过调用 LightMode 为 ShadowCaster 的 Pass 来得到可投射阴影的光源的阴影映射纹理以及摄像机的深度纹理。然后,根据光源的阴影映射纹理和摄像机的深度纹理来得到屏幕空间的阴影图。如果摄像机的深度图中记录的表面深度大于转换到阴影映射纹理中的深度值,就说明该表面虽然是可见的,但是却处于该光源的阴影中。通过这样的方式,阴影图就包含了屏幕空间中所有有阴影的区域。如果我们想要一个物体接收来自其他物体的阴影,只需要在 Shader 中对阴影图进行采样。由于阴影图是屏幕空间下的,因此,我们首先需要把表面坐标从模型空间变换到屏幕空间中,然后使用这个坐标对阴影图进行采样即可。
总结一下,一个物体接收来自其他物体的阴影,以及它向其他物体投射阴影是两个过程。
- 如果我们想要一个物体接收来自其他物体的阴影,就必须在 Shader中对阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果和最后的光照结果相乘来产生阴影效果。
- 如果我们想要一个物体向其他物体投射阴影,就必须把该物体加入到光源的阴影映射纹理的计算中,从而让其他物体在对阴影映射纹理采样时可以得到该物体的相关信息。在 Unity 中这个过程是通过为物体执行 LightMode 为 Shadow Caster 中的 Pass 来实现的。如果使用了屏幕空间的投影和映射技术,Unity 还会使用 这个 Pass 产生的摄像机的深度纹理。
9.4.2 不透明物体的阴影
首先在光源的 Light 组件 -> Shadow Type 开启阴影,这样平行光就可以收集阴影信息。本节代码基于上面 ForwardRender 进行修改。
1. 让物体投射阴影
在物体的 Mesh Renderer 组件中的 Cast Shadows 和 Receive Shadowss 来设置是否投射和接收阴影。其中 Cast Shadows 设为开启(On)时,它就会被加入到光源的阴影纹理计算中,而 Receive Shadows 则保障一些内置变量被正确赋值。
Image
在前面一节中虽然我们没有使用 LightMode 为 ShaderCaster 的 Pass,如上图,它仍然投射出了阴影。这是因为 Fallback 语义:
Fallback "Specular"
虽然 Specular 中并没有定义这样的 Pass,但是 Specular 的 Fallback 又回调了 VertexLit,而 VertexLit 中定义了这样的 Pass:
// Pass to render object as a shadow caster
Pass{
Name "ShadowCaster"
Tags { "LightMode"= "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f{
V2F_SHADOW_CASTER;
};
v2f vert( appdata_base v)
{
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET (o)
return o;
}
float4 frag( v2f i):SV_Target
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
我们也可以在自己的 Shadow 中加上这样的 Pass,自定义可以让我们更好的控制阴影,但这个 Pass 功能是通用的,所有直接 Fallback 更方便。在之前的 也有 Fallback 使用内置的 Diffuse, Diffuse 也一样,它回调了 VertexLit。
观察上图可以发现,右侧的平面并没有投射出阴影,这是因为背面被剔除了,所以它不会被加入到阴影纹理的计算中,可以将它的 Cast Shadows 设置为 Two Sided
Image
2. 让物体接收阴影
本节代码基于上面 ForwardRender 进行修改。
(1)在 Base Pass 中包含进一个新的内置文件
#include "AutoLight.cginc"
(2)在顶点着色器中的输出结构体 v2f 中添加内置宏 SHADOW_COORDS
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float4 pos : SV_POSITION; //注意这里最好用 pos 命名
SHADOW_COORDS(2)
该宏的作用是声明一个用于对阴影纹理采样的坐标。这个宏的参数需要下一个可用的插值寄存器的索引值,这里的插值寄存器指的是 TEXCOORDn,因为0、1以及被使用过了,所以只能使用 TEXCOORD2
(3)在顶点着色器返回前添加内置宏 TRANSFER_SHADOW
v2f vert(a2v v)
{
v2f o;
...
//将阴影纹理坐标发送给 片元着色器
TRANSFER_SHADOW(o);
return o;
}
(4)在片元着色器中计算阴影值,使用 SHADOW_ATTENUATION:
//使用阴影坐标采样阴影纹理
fixed shadow = SHADOW_ATTENUATION(i);
上述宏在 AutoLight.cginc 中的定义如下:
Image
如上图红色方框的地方,是定义的宏,而红色下划线的地方时它们使用的上下文变量,所以 在 vert 的输入结构体中 顶点坐标变量名必须是 vertex,在输出结构体中一定得命名为 pos。其他需要注意的是 TRANSFER_SHADOW, 它有两种定义,使用哪种定义得看支不支持 屏幕空间的阴影映射。在最后的地方可以看到当关闭阴影映射时, SHADOW_COORDS 和 TRANSFER_SHADOW 没任何作用,而 SHADOW_ATTENUATION 会直接等于数值 1。
(5)最后将阴影值与漫反射光以及高光相乘即可。
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
结果:
Image
打开帧调试器 :该过程大致可分为 4 个部分:UpdateDepthTexture,更新摄像机深度纹理;RenderShadowmap,渲染得到的平行光的阴影映射纹理;CollectShadow,根据深度纹理和阴影映射纹理得到屏幕空间的阴影图;最后渲染结果。
Image
9.4.3 统一管理光照衰减和阴影
实际上,光照衰减和阴影对物体的最终的渲染结果的影响本质是一样的,都是把光照衰减因子和阴影值与光照结果相乘。而 Unity 提供了 UNITY_LGIHT_ATTENUATION 来帮我们同时计算这两个信息。
该代码基于 AlphaTest 进行修改(1)包含头文件
#include "Lighting.cginc"
#include "AutoLight.cginc"
(2)在 v2f 中添加内置宏 SHADOW_COORDS
struct v2f
{
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float4 pos : SV_POSITION;
SHADOW_COORDS(2)
};
(3) 使用内置宏 TRANSFER_SHADOW 计算阴影纹理坐标,然后将其传递给片元着色器
v2f vert(a2v v)
{
v2f o;
...
TRANSFER_SHADOW(o);
return o;
}
(4) 在片元着色器中 使用 UNITY_LIGHT_ATTENUATION 计算阴影和光照衰减
fixed4 frag(v2f i) : SV_Target
{
...
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4((diffuse + specular) * atten, 1.0);
}
(5) 修改 Fallback
Fallback "VertexLit"
结果:
Image
注意正方体的 Mesh Renderer 组件的 Caster Shadows 属性需要调成 Two Sided
同时 Unity 所有默认的 透明度混合 Shader 都没有包含阴影投射的 Pass,这意味着半透明物体不参与 深度图和阴影映射纹理 的计算。如果我们想要 透明度混合物体投射阴影,需要修改对应的代码,并且需要 将 Fallback 从 Transparent/VertexLit 修改位 VertexLit。
完整代码:在 Assets/Shaders/Common 文件夹下,分别是BumpedDiffuse、BumpedSpecular,都包含了法线纹理,多光源、光照衰减、阴影。它们的区别是,一个采用 Phong 模型,一个采用 Blinn-Phong 模型。
本文使用 Zhihu On VSCode 创作并发布
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|