代码&示例图见:zaizai77/Shader-Learn: 实现一些书里讲到的shader
到了这里就开启了书里的中级篇,之后会讲解 Unity 中的渲染路径,如何计算光照衰减和阴影,如何使用高级纹理和动画等一系列进阶内容
Unity 中的渲染路径
在Unity里,渲染路径(Rendering Path)决定了光照是如何应用到Unity Shader中的。我们需要为每个Pass指定它使用的渲染路径
在Unity 5.0版本之前,主要有3种:前向渲染路径(Forward Rendering Path)、延迟渲染路径(Deferred Rendering Path)和顶点照明渲染路径(Vertex Lit Rendering Path)。
设置Rendering path
摄像机组件的Rendering Path中的设置可以覆盖Project Settings中的设置
如果当前的显卡并不支持所选择的渲染路径,Unity会自动使用更低一级的渲染路径。例如,如果一个GPU不支持延迟渲染,那么Unity就会使用前向渲染。
Pass {
Tags { "LightMode" = "ForwardBase" }
前向渲染路径
前向渲染路径的原理
每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲区,一个是深度缓冲区。我们利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区中的颜色值。伪代码描述:
Pass {
for (each primitive in this model) {
for (each fragment covered by this primitive) {
if (failed in depth test) {
// 如果没有通过深度测试,说明该片元是不可见的
discard;
} else {
// 如果该片元可见
// 就迚行光照计算
float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
// 更新帧缓冲
writeFrameBuffer(fragment, color);
}
}
}
}
对于每个逐像素光源,我们都需要进行上面一次完整的渲染流程。如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。假设,场景中有N个物体,每个物体受M个光源的影响,那么要渲染整个场景一共需要N*M个Pass。可以看出,如果有大量逐像素光照,那么需要执行的Pass数目也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照的数目。
Unity中的前向渲染
一个Pass不仅仅可以用来计算逐像素光照,它也可以用来计算逐顶点等其他光照。这取决于光照计算所处流水线阶段以及计算时使用的数学模型。当我们渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式。
在Unity中,前向渲染路径有3种处理光照(即照亮物体)的方式:逐顶点处理、逐像素处理,球谐函数(Spherical Harmonics, SH)处理。而决定一个光源使用哪种处理模式取决于它的类型和渲染模式。光源类型指的是该光源是平行光还是其他类型的光源,而光源的渲染模式指的是该光源是否是重要的(Important)。
在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。
- 场景中最亮的平行光总是按逐像素处理的。
- 渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理。
- 渲染模式被设置成Important的光源,会按逐像素处理。
- 如果根据以上规则得到的逐像素光源数量小于Quality Setting中的逐像素光源数量(Pixel Light Count),会有更多的光源以逐像素的方式进行渲染。
光照会在Unity Shader的Pass中进行计算。前向渲染有两种Pass:
- Base Pass
- Additional Pass
- #pragma multi_compile_fwdbase和#pragma multi_compile_fwdadd,只有分别为Bass Pass和Additional Pass使用这两个编译指令,得到一些正确的光照变量,例如光照衰减值等
- Base Pass旁边的注释给出了Base Pass中支持的一些光照特性。例如在Base Pass中,我们可以访问光照纹理(lightmap)。
- 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。
(要记得东西也太多了吧。。。。)
内置的光照变量和函数
顶点照明渲染路径
顶点照明渲染路径是对硬件配置要求最少、运算性能最高,但同时得到效果最差的一种类型。它仅支持逐顶点的光源计算,不支持逐像素,所以无法得到:阴影、法线映射、高精度的高光反射等效果,它是前向渲染的一个子集。
Unity 中的顶点照明渲染
顶点照明渲染路径通常在一个Pass中就可以完成对物体的渲染,在这个Pass中,我们会计算关心的光源对物体的照明,且是按照逐顶点处理的。
它是Unity中最快速的渲染路径,且具有最广泛的硬件支持(游戏机上不支持这种路径)。Unity 5以后被作为一个遗留的渲染路径,未来的版本中可能会被移除。
可访问的内置变量和函数
在Unity中一个顶点照明的Pass中最多可以访问8个逐顶点光源。
顶点照明渲染路径中可以使用的内置变量:
如果影响该物体的光源数目小于8,则数组中剩余的光源颜色会被设置成黑色。有些变量我们同样可以在前向渲染路径中使用,如:unity_LightColor,但这些变量的数组的维度和数值在不同渲染路径下的值是不同的。
顶点照明渲染路径中可以使用的内置函数:
延迟渲染路径
当场景中包含大量实时光源,使用前向渲染会造成性能急剧下降。每个光源都会执行一个Pass,重复地渲染一个物体会存在很多相同的重复计算。
在这样的环境下,本是一种更古老的渲染方法的延迟渲染,又流行了起来。除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还使用了额外的缓冲区——G缓冲(Geometry Buffer,G-buffer)。G缓冲区存储了我们关心的表面(通常指离相机最近的表面)的其他信息,如:表面的法线、位置、用于光照计算的材质属性等。
延迟渲染的过程大致可以用下面的伪代码来描述:
Pass 1 {
// 第一个Pass不迚行真正的光照计算
// 仅仅把光照计算需要的信息存储到G缓冲中
for (each primitive in this model) {
for (each fragment covered by this primitive) {
if (failed in depth test) {
// 如果没有通过深度测试,说明该片元是不可见的
discard;
} else {
// 如果该片元可见
// 就把需要的信息存储到G缓冲中
writeGBuffer(materialInfo, pos, normal, lightDir, viewDir);
}
}
}
}
Pass 2 {
// 利用G缓冲中的信息迚行真正的光照计算
for (each pixel in the screen) {
if (the pixel is valid) {
// 如果该像素是有效的
// 读取它对应的G缓冲中的信息
readGBuffer(pixel, materialInfo, pos, normal, lightDir, viewDir);
// 根据读取到的信息迚行光照计算
float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
// 更新帧缓冲
writeFrameBuffer(pixel, color);
}
}
}
延迟渲染使用的Pass数目通常是2个,与场景中包含的光源数目没有关系,和屏幕的大小有关。
延迟渲染适合在光源数目众多、使用前向渲染造成性能瓶颈的情况下使用,每个光源都可以按逐像素处理。但它也有缺点:
- 不支持真正的抗锯齿功能;
- 不能处理半透明物体;
- 对显卡有要求,必须支持MRT(Multiple Render Targets)、Shader Mode 3.0及以上、深度渲染纹理以及双面的模板缓冲。
延迟渲染需要两个Pass:
- 第一个Pass用于渲染G缓冲
把物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中对每个物体来说,这个Pass仅执行一次。
- 第二个Pass用于计算真正的光照模型
使用上一个Pass中渲染的数据来计算最终的颜色光照颜色,再存储到帧缓冲中。
这些变量都可以在UnityDeferred Library.cginc文件中找到它们的声明。
更加深入的东西就直接看官方文档吧,太深入了,写了也是白写,记不住
Unity - Manual: Introduction to rendering paths in the Built-In Render Pipeline
↑ 4种渲染路径(前向渲染路径、延迟渲染路径、遗留的延迟渲染路径和顶点照明渲染路径)的详细比较,包括它们的特性比较(是否支持逐像素光照、半透明物体、实时阴影等)、性能比较以及平台支持
Unity 的光源类型
之前的例子中用且仅用了一个平行光光源,之后在本节中,我们将会学习如何在Unity中处理点光源(point light)和聚光灯(spot light)。
Unity一共支持4种光源类型:平行光、点光源、聚光灯和面光源(area light)面光源仅在烘焙时才可发挥作用,所以这里不讨论
光源类型有什么影响
最常使用的光源属性有:光源的位置、方向(到某点的方向)、颜色、强度以及衰减(到某点的衰减,与点到光源的距离有关)。
平行光
它的几何定义是最简单的。平行光可以照亮的范围是没有限制的,它通常是作为太阳这样的角色在场景中出现的,它没有一个唯一的位置,也就是说,它可以放在场景中的任意位置,它的几何属性只有方向,光照强度不会随着距离而发生改变。
点光源
点光源的照亮空间则是有限的,它是由空间中的一个球体定义的。点光源可以表示由一个点发出的、向所有方向延伸的光
点光源是有位置属性的,同时,点光源也是会衰减的,随着物体逐渐远离点光源,它接收到的光照强度也会逐渐减小。点光源球心处的光照强度最强,球体边界处的最弱,值为0。其中间的衰减值可以由一个函数定义。
聚光灯
它的照亮空间同样是有限的,但不再是简单的球体,而是由空间中的一块锥形区域定义的。聚光灯可以用于表示由一个特定位置出发、向特定方向延伸的光。
在前向渲染中处理不同的光源类型
在了解了3种光源的几何定义后,我们来看一下如何在Unity Shader中访问它们的5个属性:位置、方向、颜色、强度以及衰减
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
还使用了#pragma编译指令。#pragma multi_compile_fwdbase指令可以保证我们在Shader中使用光照衰减等光照变量可以被正确赋值。
我们在Base Pass中处理了场景中的最重要的平行光。在这个例子中,场景中只有一个平行光。如果场景中包含了多个平行光,Unity会选择最亮的平行光传递给Base Pass进行逐像素处理,其他平行光会按照逐顶点或在Additional Pass中按逐像素的方式处理。如果场景中没有任何平行光,那么Base Pass会当成全黑的光源处理
我们可以使用__WorldSpaceLightPos0来得到这个平行光的方向(位置对平行光来说没有意义),使用_LightColor0来得到它的颜色和强度(_LightColor0已经是颜色和强度相乘后的结果),由于平行光可以认为是没有衰减的,因此这里我们直接令衰减值为1.0。
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
...
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)),
_Gloss);
// The attenuation of directional light is always 1
fixed atten = 1.0;
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
Additional Pass
Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
与Base Pass不同的是,我们还使用Blend命令开启和设置了混合模式。这是因为,我们希望Additional Pass计算得到的光照结果可以在帧缓存中与之前的光照结果进行叠加。如果没有使用Blend命令的话,Additional Pass会直接覆盖掉之前的光照结果。
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
#endif
在上面的代码中,我们首先判断了当前处理的逐像素光源的类型,这是通过使用#ifdef指令判断是否定义了USING_DIRECTIONAL_LIGHT来得到的。如果当前前向渲染Pass处理的光源类型是平行光,那么Unity的底层渲染引擎就会定义USING_DIRECTIONAL_LIGHT。如果判断得知是平行光的话,光源方向可以直接由_WorldSpaceLightPos0.xyz得到;如果是点光源或聚光灯,那么_WorldSpaceLightPos0.xyz表示的是世界空间下的光源位置,而想要得到光源方向的话,我们就需要用这个位置减去世界空间下的顶点位置。
处理不同光源的衰减
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
float3 lightCoord = mul(_LightMatrix0, float4(i.worldPosition, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
我们可以使用数学表达式来计算给定点相对于点光源和聚光灯的衰减,但这些计算往往涉及开根号、除法等计算量相对较大的操作,因此Unity选择了使用一张纹理作为查找表(Lookup Table, LUT),以在片元着色器中得到光源的衰减。我们首先得到光源空间下的坐标,然后使用该坐标对衰减纹理进行采样得到衰减值
Shader "Custom/Chapter9/ForwardRendering"
{
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 {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#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 {
// Pass for other pixel lights
Tags { "LightMode" = "ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#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
}
}
}
对于场景中的一个物体,如果它不在一个光源的光照范围内,Unity是不会为这个物体调用Pass来处理这个光源的
我们可以通过把光源的Render Mode设为Not Important来告诉Unity,我们不希望把该光源当成逐像素处理。
因为shader 中没有写逐顶点光照处理,所以被设置为Not Important 的点光源不会对胶囊体产生影响
Unity 的光照衰减
上面的示例中使用一张纹理作为查找表来在片元着色器中计算逐像素光照的衰减。这样的好处在于,计算衰减不依赖于数学公式的复杂性,我们只要使用一个参数值去纹理中采样即可。但使用纹理查找来计算衰减也有一些弊端。
- 需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度。
- 不直观,同时也不方便,因此一旦把数据存储到查找表中,我们就无法使用其他数学公式来计算衰减。
用于光照衰减的纹理
Unity在内部使用一张名为_LightTexture0的纹理来计算光源衰减,我们通常只关心_LightTexture0对角线上的纹理颜色值,这些值表明了在光源空间中不同位置的点的衰减值。例如,(0, 0)点表明了与光源位置重合的点的衰减值,而(1, 1)点表明了在光源空间中所关心的距离最远的点的衰减。
为了对_LightTexture0纹理采样得到给定点到该光源的衰减值,我们首先需要得到该点在光源空间中的位置,这是通过_LightMatrix0变换矩阵得到的,_LightMatrix0可以把顶点从世界空间变换到光源空间
float3 lightCoord = mul(_LightMatrix0, float4(i.worldPosition, 1)).xyz;
然后,我们可以使用这个坐标的模的平方对衰减纹理进行采样,得到衰减值:
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
可以发现,在上面的代码中,我们使用了光源空间中顶点距离的平方(通过dot函数来得到)来对纹理采样,之所以没有使用距离值来采样是因为这种方法可以避免开方操作。然后,我们使用宏UNITY_ATTEN_CHANNEL来得到衰减纹理中衰减值所在的分量,以得到最终的衰减值。
Unity 中的阴影
为了让场景看起来更加真实,具有深度信息,我们通常希望光源可以把一些物体的阴影投射在其他物体上
阴影是如何产生的
当一个光源发射的一条光线遇到一个不透明物体时,这条光线就不可以再继续照亮其他物体(这里不考虑光线反射)。因此,这个物体就会向它旁边的物体投射阴影,那些阴影区域的产生是因为光线无法到达这些区域。
在实时渲染中,我们最常使用的是一种名为Shadow Map的技术。这种技术理解起来非常简单,它会首先把摄像机的位置放在与光源重合的位置上,那么场景中该光源的阴影区域就是那些摄像机看不到的地方。而Unity就是使用的这种技术。
在前向渲染路径中,如果场景中最重要的平行光开启了阴影,Unity就会为该光源计算它的阴影映射纹理(shadowmap)。这张阴影映射纹理本质上也是一张深度图,它记录了从该光源的位置出发、能看到的场景中距离它最近的表面位置(深度信息)。
Unity选择使用一个额外的Pass来专门更新光源的阴影映射纹理,这个Pass就是LightMode标签被设置为ShadowCaster的Pass。这个Pass的渲染目标不是帧缓存,而是阴影映射纹理(或深度纹理)。
因此当开启了光源的阴影效果后,底层渲染会优先找当前渲染物体的Unity Shader中LightMode标签为ShadowCaster的Pass,如果没有,会继续在Fallback中找,如果仍然没有找到,该物体就不会向其他物体投射阴影。
- 传统的阴影映射纹理实现过程:
在正常渲染的Pass中把顶点位置变换到光源空间下,得到光源空间中顶点的位置,使用xy分量对阴影映射纹理进行采样得到记录的深度值,如果它小于这个顶点的z分量,则说明这个顶点在阴影中。
- Unity 5及以后使用的屏幕空间的阴影映射技术(Screenspace Shadow Map):
调用LightMode为ShadowCaster的Pass得到光源的阴影映射纹理和相机的深度纹理,如果相机深度纹理记录的深度大于转换到光源阴影映射纹理中的深度值,则说明这个物体表面的该点处于阴影中。通过这样的方式,生成的阴影图包含了屏幕空间中所有有阴影的区域。我们只需要对这个阴影图进行采样就可以计算场景中哪些物体会呈现阴影了。
关于阴影,其实涉及投射和接收两个过程。举个例子,在太阳光下物体A挡住了物体B,在物体B上产生了阴影。那么对于物体A,就是投射阴影的过程,对于物体B就是接收阴影的过程。
光源的阴影映射纹理的生成过程会计算投射物体在光源空间中的位置;
而接收物体的Shader实现中会对光源的阴影映射纹理进行采样,以确认它是否会被挡住并产生阴影。
不透明物体的阴影
Mesh Renderer组件的Cast Shadows和Receive Shadows属性可以控制该物体是否投射/接收阴影
让物体接收阴影
首先,我们在Base Pass中包含进一个新的内置文件:
#include "AutoLight.cginc"。
这是因为,我们下面计算阴影时所用的宏都是在这个文件中声明的。
我们在顶点着色器的输出结构体v2f中添加了一个内置宏SHADOW_COORDS:
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
这个宏的作用很简单,就是声明一个用于对阴影纹理采样的坐标。需要注意的是,这个宏的参数需要是下一个可用的插值寄存器的索引值,在上面的例子中就是2。
然后,我们在顶点着色器返回之前添加另一个内置宏TRANSFER_SHADOW:
v2f vert(a2v v) {
v2f o;
...
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
这个宏用于在顶点着色器中计算上一步中声明的阴影纹理坐标。
接着,我们在片元着色器中计算阴影值,这同样使用了一个内置宏SHADOW_ATTENUATION:
// Use shadow coordinates to sample shadow map
fixed shadow = SHADOW_ATTENUATION(i);