《Unity Shader》9.4.2 不透明物体的阴影
需要注意的是,上面的代码里我们只更改了Base Pass中的代码,使其可以得到阴影效果,而没有对Additional Pass进行任何更改。(3)在场景中创建一个正方体、两个平面,并把第2步中的材质赋给正方体,但不改变两个平面的材质(默认情况下,它们会使用内置的Standard材质)。需要注意的是,这个宏的参数需要是下一个可用的插值寄存器的索引值,在上面的例子中就是2。为了让正方体可以接收阴影,我
(1)在Unity中新建一个场景。在本书资源中,该场景名为Scene_9_4_2。在Unity 5.2中,默认情况下场景将包含一个摄像机和一个平行光,并且使用了内置的天空盒子。在Window →Lighting → Skybox中去掉场景中的天空盒子。
command+N 创建scene

打开 window-rendering-lighting,点击 environment,点击 skybox material

(2)新建一个材质。在本书资源中,该材质名为ShadowMat。我们把9.2节中的Chapter9-ForwardRendering赋给它。



(3)在场景中创建一个正方体、两个平面,并把第2步中的材质赋给正方体,但不改变两个平面的材质(默认情况下,它们会使用内置的Standard材质)。




调整一下大概这样
自己平移、旋转一下平面或者正方体或者照相机,对照 Game 显示的效果调整到可以。

(4)保存场景。
为了让场景中可以产生阴影,我们首先需要让平行光可以收集阴影信息。这需要在光源的Light组件中开启阴影,如图9.15所示。
在本例中,我们选择了软阴影(Soft Shadows)。
点击 directional light,查看 inspector 面板

我们把正方体和两个平面的Cast Shadows和Receive Shadows都设为开启状态




Cast Shadows设置为Two Sided来允许对物体的所有面都计算阴影信息



为了让正方体可以接收阴影,我们首先新建一个Unity Shader,在本书资源中,它的名称为Chapter9-Shadow


删除Chapter9-Shadow中的代码,把Chapter9-ForwardRendering的代码复制给它。
我们需要对代码进行一些更改。
(1)首先,我们在Base Pass中包含进一个新的内置文件:
(2)我们在顶点着色器的输出结构体v2f中添加了一个内置宏SHADOW_COORDS:
这个宏的作用很简单,就是声明一个用于对阴影纹理采样的坐标。需要注意的是,这个宏的参数需要是下一个可用的插值寄存器的索引值,在上面的例子中就是2。
(3)然后,我们在顶点着色器返回之前添加另一个内置宏TRANSFER_SHADOW:
这个宏用于在顶点着色器中计算上一步中声明的阴影纹理坐标。
(4)接着,我们在片元着色器中计算阴影值,这同样使用了一个内置宏SHADOW_ATTENUATION:
SHADOW_COORDS、TRANSFER_SHADOW和SHADOW_ATTENUATION是计算阴影时的“三剑客”。这些内置宏帮助我们在必要时计算光源的阴影。
在前向渲染中,宏SHADOW_COORDS实际上就是声明了一个名为_ShadowCoord的阴影纹理坐标变量。而TRANSFER_SHADOW的实现会根据平台不同而有所差异。如果当前平台可以使用屏幕空间的阴影映射技术(通过判断是否定义了UNITY_NO_SCREENSPACE_SHADOWS来得到), TRANSFER_SHADOW会调用内置的ComputeScreenPos函数来计算_ShadowCoord;如果该平台不支持屏幕空间的阴影映射技术,就会使用传统的阴影映射技术,TRANSFER_SHADOW会把顶点坐标从模型空间变换到光源空间后存储到_ShadowCoord中。然后,SHADOW_ATTENUATION负责使用_ShadowCoord对相关的纹理进行采样,得到阴影信息。
(5)在完成了上面的所有操作后,我们只需要把阴影值shadow和漫反射以及高光反射颜色相乘即可。
保存文件,返回Unity我们可以发现,现在正方体也可以接收来自右侧平面的阴影了。需要注意的是,上面的代码里我们只更改了Base Pass中的代码,使其可以得到阴影效果,而没有对Additional Pass进行任何更改。大体上,Additional Pass的阴影处理和Base Pass是一样的。我们将在9.4.4节看到如何处理这些阴影。

Shader "Custom/Chapter9-Shadow"{
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"} //标签告诉Unity这个着色器用于渲染不透明物体
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase //保证使用光照衰减等光照变量可以被正确赋值
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc" //在Base Pass中包含进一个新的内置文件,计算阴影时所用的宏都是在这个文件中声明的
//这些宏中会使用上下文变量来进行相关计算,我们需要保证:a2f结构体中的顶点坐标变量名必须是vertex,顶点着色器的输出结构体v2f必须命名为v,且v2f中的顶点位置变量必须命名为pos
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;
SHADOW_COORDS(2)//添加了一个内置宏SHADOW_COORDS,声明了一个名为_ShadowCoord的阴影纹理坐标变量
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o); // Pass shadow coordinates to pixel shader,在顶点着色器中计算上一步中声明的阴影纹理坐标
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;
fixed shadow = SHADOW_ATTENUATION(i);// Use shadow coordinates to sample shadow map,使用_ShadowCoord对相关的纹理进行采样,得到阴影信息。
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}
ENDCG
}
Pass {
// Pass for other pixel lights
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; //将世界坐标转换到光源坐标系,lightCoord 光源坐标系
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; // 通过查询衰减纹理获取衰减值,使用光源空间坐标的距离平方作为UV
#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; // 计算聚光灯衰减:检查是否在锥形范围内 × 查询Cookie纹理 × 查询衰减纹理
#else
fixed atten = 1.0;
#endif
#endif
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}

这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐










所有评论(0)