目录索引
译文
我们在实现阴影的过程中使用的技术可以根据我们想要实现的类型而有所不同。我们使用的流程是 Unity 中实现阴影的最佳流程,但具体的实现可能因开发者所选的渲染管线而异,例如,前几节详细介绍的阴影贴图实现流程仅适用于内置渲染管线。
在通用渲染管线(URP)中实现阴影时,我们需要引入包含了坐标运算的“Lighting.hlsl”包。
让我们从配置 URP 的渲染管线开始吧!如下图所示,我们需要确保图形(Graphics)菜单下的可编程渲染管线设置(Scriptable Render Pipeline Settings)中分配了渲染管线资产(Render Pipeline Asset)。
![图片[1]-《Unity着色器圣经》8.0.6 | 通用渲染管线下的阴影映射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/v2-dfb1bbd54e80d49ee3b5193fdfba85e0_1440w-1024x438.jpg)
配置好之后,让我们创建一个名为 USB_shadow_map_URP 的无光照着色器,我们将在这个着色器中实现阴影贴图和阴影投射的功能。我们需要检查程序是否包含了必要的依赖项,否则着色器可能无法编译。
鉴于阴影着色器的性质,USB_shadow_map_URP 着色器是不透明的,并在 URP 中工作,因此需定义如下:
Shader "USB/USB_shadow_map_URP"
{
Properties { … }
SubShader
{
Tags
{
"RenderType"="Opaque"
// 添加渲染管线
"RenderPipeline"="UniversalRenderPipeline"
}
…
}
}
我们已经了解过,阴影是通过两个 pass 产生的:一个 pass 用于阴影投射,另一个 pass 用于阴影贴图。默认情况下,Unity 在新建着色器时只添加了一个 pass,我们也可以将其用于阴影贴图的 pass,并为阴影投射新增一个 pass。
在URP中,有一种光照着色器“Lit”,归属于“Universal Render Pipeline”分类下。
在该着色器中,我们可以找到一个名为“ShadowCaster”的 pass,负责计算阴影投射到其他物体上的坐标。
我们可以直接复制 Lit 着色器里的 ShadowCaster pass 并粘贴到我们的着色器中,也可以在着色器中包含 ShadowCaster pass 的路径,并通过 UsePass 命令直接调用它,如下面的代码所示。
SubShader
{
Tags
{
"RenderType"="Opaque"
// 添加渲染管线
"RenderPipeline"="UniversalRenderPipeline"
}
// 默认的 color Pass
Pass { … }
// 阴影投射 Pass
UsePass "Universal Render Pipeline/Lit/ShadowCaster"
}
当我们要将别的着色器的 pass 的功能包含进我们自定义的着色器时,就会用到 UsePass 命令。比如前文中我们将在 URP 路径中,位于 Lit 着色器中的 ShadowCaster pass 包含进了 USB_shadow_map_URP 以进行阴影投射的计算。
// Unity中内置的着色器
// 和我们之前所写的不同
Shader "Universal Render Pipeline/Lit"
{
Properties { … }
SubShader
{
Pass
{
Name "ShadowCaster"
Tags { "LightMode"="ShadowCaster" }
…
}
}
}
既然我们将阴影投射交给 Lit 的 pass 计算,那么着色器中自带的 pass 就被我们定义为处理阴影贴图的pass了,也就是说,我们必须在其中包含 URP 的 LightMode。
// default color Pass
Pass
{
Tags
{
"LightMode"="UniversalForward"
}
HLSLPROGRAM
...
ENDHLSL
}
UniversalForward 属性和 ForwardBase 类似,区别在于前者在同一个 pass 中评估所有光的贡献。
定义好 LightMode 后,我们必须添加一些依赖项,以帮助我们实现阴影贴图:
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ _MAIN_LIGHT_SHADOW
#include "HLSLSupport.cginc"
#include "Packages/com.unity.render - pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render - pipelines.universal/ShaderLibrary/Lighting.hlsl"
...
ENDHLSL
在 Core.hlsl 和 Lighting.hlsl 依赖项中,有几个函数我们将在计算中使用,其中包括被包含于 ShaderVariablesFunctions.hlsl 子依赖项的 GetVertexPositionInputs(顶点位置输入)函数和被包含于 Shadows.hlsl 子依赖项的 GetShadowCoord(阴影坐标)函数。
因为我们要实现阴影贴图,所以我们可以在顶点输出中创建一个四维向量用于储存 UV 坐标。
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 shadowCoord : TEXCOORD1;
};
我们在第 8.0.4 小节中介绍过,shadowCoord 向量之所以有四个维度,是因为它将存储顶点从 NDC 转换为 UV 坐标的结果。
对于坐标,我们需要使用 GetShadowCoord 函数,该函数要求我们将 VertexPositionInputs 类型的对象作为参数:
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// 你可以在Core.hlsl中找到VertexPositionInputs
// 你可以在ShaderVariablesFunctions.hlsl中找到GetVertexPositionInputs
VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
// 你可以在Shadows.hlsl中找到GetShadowCoord
o.shadowCoord = GetShadowCoord(vertexInput);
return o;
}
此时,shadowCoord 向量已经包含了阴影的坐标,现在我们只需将其作为参数传递给 GetMainLight (float4shadowCoord) 函数即可。GetMainLight 函数可计算光的方向、衰减、阴影衰减和光照颜色,该函数被包含在 Lighting.hlsl 依赖项中。
Light GetMainLight()
{
Light light;
light.direction = _MainLightPosition.xyz;
light.distanceAttenuation = unity_LightData.z;
light.shadowAttenuation = 1.0;
light.color = _MainLightColor.rgb;
return light;
}
Light GetMainLight(float4 shadowCoord)
{
Light light = GetMainLight();
light.shadowAttenuation = MainLightRealtimeShadow(shadowCoord);
return light;
}
GetMainLight 函数最多有三种变体,我们可以在第三种变体中加入 shadowMask 以备不时之需。现在,我们只需创建一个向量用来存储阴影衰减,并将其乘以纹理的 RGB 颜色:
fixed4 frag (v2f i) : SV_Target
{
// 你可以在Lighting.hlsl中找到GetMainLight
Light light = GetMainLight(i.shadowCoord);
float3 shadow = light.shadowAttenuation;
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= shadow;
return col;
}
原文对照
The technique we use for the shadow calculation can vary depending on the type we want to implement. The process we use is the optimal for Unity, however, its implementation may vary depending on the rendering pipeline chosen, e.g., the process for the shadow map implementation that was detailed in the previous sections, works only in Built-in RP.
If we want to generate shadows in Universal RP, we have to use the dependency “Lighting.hlsl”, as this package includes definitions for the coordinate calculations.
We will start by configuring the rendering pipeline in Universal RP. To do this, we must make sure that we have assigned a Render Pipeline Asset in the Scriptable Render Pipeline Settings box, in the Graphics menu.
![图片[1]-《Unity着色器圣经》8.0.6 | 通用渲染管线下的阴影映射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/v2-dfb1bbd54e80d49ee3b5193fdfba85e0_1440w-1024x438.jpg)
Once configured, we will create a new Unlit Shader, which we will call USB_shadow_map_ URP. This shader will be used to implement both a shadow map and the shadow caster function in the rendering engine. Likewise, we will review the necessary dependencies so that the program can compile.
Given its nature, this shader is opaque and works in Universal RP, therefore, the program rendering values need to be defined.
Shader "USB/USB_shadow_map_URP"
{
Properties { … }
SubShader
{
Tags
{
"RenderType"="Opaque"
// add the rendering pipeline
"RenderPipeline"="UniversalRenderPipeline"
}
…
}
}
As we already know, shadows are produced in two passes: one for shadow caster and one for texture (shadow map). By default, Unity adds only one pass which; again, we could use for the shadow map. This way, we have to add an extra pass in our shader for the shadow caster definition.
In Universal RP, there is a shader called “Lit”, which has been included in the category “Universal Render Pipeline”.
Within this shader, we can find a pass called “ShadowCaster” (Name “ShadowCaster”) that is responsible for the calculation of coordinates for the projection of shadows on other objects.
In Unity, we can dynamically include passes using the UsePass command, what does this mean?
On the one hand, we could go to the Lit shader, copy the pass, and paste it into our shader, or we can simply include the route of the pass and make a call directly to its function.
SubShader
{
Tags
{
"RenderType"="Opaque"
// add the rendering pipeline
"RenderPipeline"="UniversalRenderPipeline"
}
// default color Pass
Pass { … }
// shadow caster Pass
UsePass "Universal Render Pipeline/Lit/ShadowCaster"
}
The UsePass command is used when we want to include functionalities of a pass that is in a different shader to the one we are programming. This means that, if we take into consideration the previous example: the ShadowCaster pass located in the Lit shader, in the Universal Render Pipeline path, will contain the shadow caster calculations.
// default shader included in Unity
// different from the one we are programming
Shader "Universal Render Pipeline/Lit"
{
Properties { … }
SubShader
{
Pass
{
Name "ShadowCaster"
Tags { "LightMode"="ShadowCaster" }
…
}
}
}
Since we have included this path, we must define the default pass as the one that will process the shadow map, that is, we have to include the LightMode for Universal RP.
// default color Pass
Pass
{
Tags
{
"LightMode"="UniversalForward"
}
HLSLPROGRAM
...
ENDHLSL
}
The “UniversalForward” property works similarly to “ForwardBase”, the difference is that the former evaluates all light contributions in the same pass.
Once the LightMode is defined, we must add some dependencies that will help us to implement the shadow map.
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ _MAIN_LIGHT_SHADOW
#include "HLSLSupport.cginc"
#include "Packages/com.unity.render - pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render - pipelines.universal/ShaderLibrary/Lighting.hlsl"
...
ENDHLSL
The dependencies “Core.hlsl” and “Lighting.hlsl” have several functions that we will use in the calculation, among them: the GetVertexPositionInputs function, which belongs to a sub-dependence called “ShaderVariablesFunctions.hlsl” and GetShadowCoord that is included in a sub-dependence called “Shadows.hlsl”.
Because we are going to implement a shadow map, we will need a vector that can store its UV coordinates, for this we can create a four-dimensional vector in the vertex output.
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 shadowCoord : TEXCOORD1;
};
As detailed in section 8.0.4, the shadowCoord vector has four dimensions because it will store the result of the vertices’ transformation from NDC to UV coordinates.
For the coordinates, we will have to use the GetShadowCoord function that asks us for a VertexPositionInputs type object as an argument.
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// you can find VertexPositionInputs at Core.hlsl
// you can find GetVertexPositionInputs at
// ShaderVariablesFunctions.hlsl
VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
// you can find GetShadowCoord at Shadows.hlsl
o.shadowCoord = GetShadowCoord(vertexInput);
return o;
}
At this point, the shadowCoord vector already has the coordinates for the shadow generation, now we simply have to pass it as an argument to the GetMainLight (float4shadowCoord) function that has the calculations for light direction, attenuation, shadow attenuation and light color. This function is included in the “Lighting.hlsl” dependency.
Light GetMainLight()
{
Light light;
light.direction = _MainLightPosition.xyz;
light.distanceAttenuation = unity_LightData.z;
light.shadowAttenuation = 1.0;
light.color = _MainLightColor.rgb;
return light;
}
Light GetMainLight(float4 shadowCoord)
{
Light light = GetMainLight();
light.shadowAttenuation = MainLightRealtimeShadow(shadowCoord);
return light;
}
The GetMainLight function has up to three variations, we can include the shadowMask in the third, in case we need it. Now we simply have to create a vector to store the shadow attenuation and multiply it by the texture RGB color.
fixed4 frag (v2f i) : SV_Target
{
// you can find GetMainLight function at Lighting.hlsl
Light light = GetMainLight(i.shadowCoord);
float3 shadow = light.shadowAttenuation;
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= shadow;
return col;
}
暂无评论内容