|
现实生活中,一个物体除了接收光照外,它还会生成和接收阴影。在渲染中,如果一个物体没有阴影,那么看起来就像浮空一样,也很假很奇怪。
生成阴影
现实生活中,阴影是光线被阻挡的结果。
光线被阻挡,会生成阴影,那也就是说,当这个位置有物体遮挡(有被写入深度)的时候,物体背后会生成阴影。那么没有写入深度的物体(透明物体)又是怎样生成阴影的呢?
在unity 中,使用的是 ShadowMapping,是一种常用的实时阴影生成方法。
且物体上需要开启阴影的生成与接收
光源无法达到的位置,但是相机能看得到的,那么就认为这个位置是在阴影下。这个位置通过比较,到光源和到摄像机的深度得到结果。
然后,要用一个东西记录这些信息,也就是阴影映射纹理(shadowmap),它从本质上来说,是一张深度图。它记录了,从该光源的位置出发、能看到的场景中距离它最近的表面信息(深度信息)。
不透明物体的阴影
最简单的一种生成阴影办法,则是在shader 中添加 FallBack
Shader
{
......
SubShader {}
// 生成阴影
FallBack "Diffuse"
}它会找到内置的 Diffuse shader 文件,使用里面的生成阴影的方法。具体原理嘛,还不太懂。这样做的好处是可以减少自己写的 shader 中的pass 的数量。
另外一种方法,则是添加一个pass来绘制阴影。直接上代码
Pass
{
// 生成阴影
Tags {"LightMode" = "ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 定义在LightMode=ShadowCaster的Pass中,会自动生成两个宏:
// 1. SHADOWS_DEPTH :用于生成直线光和聚光灯阴影.
// 2. SHADOW_CUBE :用于生成点光源阴影.
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct appdata
{
float4 vertex :POSITION;
half3 normal : NORMAL;
};
struct v2f
{
// 用于声明需要传送到片断的数据.
V2F_SHADOW_CASTER;
};
v2f vert(appdata v)
{
v2f o;
//主要是计算阴影的偏移以解决不正确的Shadow Acne和Peter Panning现象
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
// 内置的定义宏,没有查看到具体的逻辑
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
- 添加"LightMode" = "ShadowCaster"的Pass.
- appdata中声明float4 vertex:POSITION;和half3 normal:NORMAL; 这是生成阴影所需要的语义
- v2f中添加V2F_SHADOW_CASTER; 用于声明需要传送到片断的数据.
- 在顶点着色器中添加TRANSFER_SHADOW_CASTER_NORMALOFFSET(o),主要是计算阴影的偏移以解决不正确的Shadow Acne和Peter Panning现象.
- 在片断着色器中添加 SHADOW_CASTER_FRAGMENT(i)
在 UnityCG.cginc 中,我们能找到相关的注释,但是并没有具体函数的实现
V2F_SHADOW_CASTER
// Declare all data needed for shadow caster pass output (any shadow directions/depths/distances as needed),
// plus clip space position.
#define V2F_SHADOW_CASTER V2F_SHADOW_CASTER_NOPOS UNITY_POSITION(pos)TRANSFER_SHADOW_CASTER_NORMALOFFSET
// Vertex shader part, with support for normal offset shadows. Requires
// position and normal to be present in the vertex input.
#define TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) TRANSFER_SHADOW_CASTER_NOPOS(o,o.pos)SHADOW_CASTER_FRAGMENT
#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
// Rendering into point light (cubemap) shadows
#define V2F_SHADOW_CASTER_NOPOS float3 vec : TEXCOORD0;
#define TRANSFER_SHADOW_CASTER_NOPOS_LEGACY(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
#define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
#define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);
#else
// Rendering into directional or spot light shadows
#define V2F_SHADOW_CASTER_NOPOS
// Let embedding code know that V2F_SHADOW_CASTER_NOPOS is empty; so that it can workaround
// empty structs that could possibly be produced.
#define V2F_SHADOW_CASTER_NOPOS_IS_EMPTY
#define TRANSFER_SHADOW_CASTER_NOPOS_LEGACY(o,opos) \
opos = UnityObjectToClipPos(v.vertex.xyz); \
opos = UnityApplyLinearShadowBias(opos);
#define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
opos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
opos = UnityApplyLinearShadowBias(opos);
#define SHADOW_CASTER_FRAGMENT(i) return 0;
#endif透明物体的阴影
我们知道阴影的生成和物体的深度信息有关系,可以通过 Frame Debug 来查看阴影的信息
Alpha Test (生成/接收阴影) Alpha Blend (不生成/接收阴影)
Alpha Blend 是需要关闭深度写入的,所以一般情况下,它是不会生成阴影的。因为要为这样的半透明物体生成正确的阴影非常的复杂,而且也影响性能。但是,也可以通过一些强制的耍手段令其生成阴影。
在shader内添加
FallBack "VertexLit"
Alpha Test 虽然看起来有生成阴影,但是阴影看起来不是很对。由于一些面完全背向光源,因此这些面的深度信息没有假如到阴影映射纹理的计算中。需要修改物体Mesh Renderer 的 Cast Shadows 属性设置为 Two Sided
接收阴影
- 在v2f中添加UNITY_SHADOW_COORDS(idx),unity会自动声明一个叫_ShadowCoord的float4变量,用作阴影的采样坐标.
- 在顶点着色器中添加TRANSFER_SHADOW(o),用于将上面定义的_ShadowCoord纹理采样坐标变换到相应的屏幕空间纹理坐标,为采样阴影纹理使用.
- 在片断着色器中添加UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos),其中atten即存储了采样后的阴影.
Pass
{
// 采样阴影的时候需要修改渲染路径为 ForwarBase (因为拉近拉远时出现阴影消失的问题)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
// 采样阴影时需要对应的变体开关 (这些其实是涉及调用的宏,这些宏里面有需要用到当前这些变体的状态)
#pragma multi_compile DIRECTIONAL
#pragma multi_compile SHADOWS_SCREEN
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 worldPos : TEXCOORD1;
float4 pos :SV_POSITION; // 为什么要改名pos呢? 因为TRANSFER_SHADOW 进入到实际取的是 .pos 的顶点
// 这里取的参数实际是 TEXCOORD2 unity会自动声明一个叫_ShadowCoord的float4变量,用作阴影的采样坐标.
// 需要引用 AutoLight.cginc
UNITY_SHADOW_COORDS(2)
};
v2f vert(appdata v)
{
v2f o = (v2f)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld,v.vertex);
// 用于将上面定义的_ShadowCoord纹理采样坐标变换到相应的屏幕空间纹理坐标,为采样阴影纹理使用.
TRANSFER_SHADOW(o)
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 其中atten即存储了采样后的阴影.
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
return atten;
}
ENDCG
}
左侧是unity内置的默认shader,右侧是粗糙实现的阴影采样。从图中很明显看到,虽然阴影的位置大致是一样的,但是颜色却很明显不一样。atten 采样得到的是非黑即白的简单阴影数据。
搜索 UNITY_LIGHT_ATTENUATION 相关的阴影采样,在 UnityStandardCore.cginc 能看到比较复杂的计算。阴影采样得到的 atten ,还需要加入到一系列的计算中,最后才得到一个物体的最终颜色,这个颜色包括物体自己以及接收的阴影颜色。
half4 fragForwardBaseInternal (VertexOutputForwardBase i)
{
UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy);
FRAGMENT_SETUP(s)
UNITY_SETUP_INSTANCE_ID(i);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
UnityLight mainLight = MainLight ();
UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);
half occlusion = Occlusion(i.tex.xy);
UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
c.rgb += Emission(i.tex.xy);
UNITY_EXTRACT_FOG_FROM_EYE_VEC(i);
UNITY_APPLY_FOG(_unity_fogCoord, c.rgb);
return OutputForward (c, s.alpha);
}
以上是unity 阴影的简单使用,需要了解阴影生成的原理和细节,需要进一步了解计算机图形学的渲染知识才行。
相关推荐
【GAMES101-现代计算机图形学入门-闫令琪】 【精准空降到 50:41】
【GAMES202-高质量实时渲染】 【精准空降到 04:41】
相关参考: |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|