以下整理主要摘自《Unity Shader 入门精要》
渲染路径
主要有: 前向渲染路径 和 延迟渲染路径
一般使用前向渲染路径。
延迟渲染适合光源数目多,前向渲染会造成性能瓶颈的情况。不支持抗锯齿,不能处理半透明,对显卡有要求,具体选择哪种看文档
https://docs.unity3d.com/Manual/RenderingPaths.html
前向渲染路径
原理
如果一个物体在多个逐像素光源影响区域,则此物体需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果。假设N个物体,受M个光源影响,整个场景共N*M个Pass,由于数目很大,故会限制每个物体的逐像素光照数目。
unity处理
在Forward Rendering中,有三种处理光照(即照亮物体)的方式:逐顶点处理,逐像素处理,球谐函数(Spherical Harmonics,SH)处理。而决定一个灯光是哪种处理模式取决于它的类型和模式。
一定数目的光源会按逐像素的方式处理,最多有4个光源按照逐定点处理,剩下的按SH。
- 场景中最亮的平行光总是逐像素处理的。这意味着,如果场景里只有一个平行光,是否设置它的模式都无关紧要。
- Render Mode被设置成Not Important的光源,会按逐顶点或者球谐函数处理。经试验,第一点中的平行光不受这点的约束。
- Render Mode被设置成Important的光源,会按逐像素处理。
- 如根据以上规则得到的像素光源数量小于设置中的像素光源数量(Pixel Light Count),为了减少亮度,会有更多的光源以逐像素的方式进行渲染。
那在哪里进行光照处理呢?当然是在Pass里。Forward Rendering有两种Pass:Base Pass,Additional Passes。这两种Pass的图例说明如下:
注意两个Pass的Tags和#pragma的设置为必需。如果需要点光源等也有阴影可以在Additional Pass里替换成:#pragma multi_compile_fwdadd_fullshadows
光源
分为:平行光、点光源、聚光灯、面光源
光源设置
光源属性 : 位置、方向、颜色、强度、衰减
- 平行光
只有方向且没有衰减 - 点光源
一个点发出的,向所有方向延伸的光,可用一个球表示。有衰减有位置。 - 聚光灯
一个点发出的,向特定方向延伸的光,可用一个锥形表示。
在前向渲染中处理各种光源
上文说到有两种Pass,首先是Base Pass: 负责计算环境光和平行光。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
……
fixed4 frag(v2f i) : SV_Target {
……
fixed atten = 1.0;
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
}
解释: 这个LightMode决定了Pass只处理平行光,所以衰减度为1. Tags和#pragma的设置上面图例里提过。
其次是Additional Pass
1 | Pass { |
上面是必须的配置代码,Blend可以变,见上一篇(透明效果提到)将改变光照。后面是计算。1
2
3
4
5
6
7fixed4 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
这里分两块解释,首先是_WorldSpaceLightPos0,想得到光照方向,对于平行光来说是_WorldSpaceLightPos0.xyz,对于非平行光则是光源位置减去面片位置。得到光照方向才能继续如之前的相同计算漫反射和高光反射等。
1 | fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); |
解释2: 这里atten(衰减)的计算分三种,平行光已解释过。点光源&聚光灯:由于计算衰减公式比较麻烦,Unity内置了一张映射表,计算的时候用_LightMatrix0将点转换到光源空间,用该坐标在映射表里采样,最终得到衰减值。使用dot(lightCoord,lightCoord).rr作为坐标的原因是,这样可以用事先平方省下一次开方。效果如下。
阴影
原理
ShadowMap技术:把摄像机与光源重合,看不见的地方即是阴影区,生成一张深度图(后文称为阴影纹理),之后只要是接受阴影的物体,会把顶点变到光源空间下,比对自己的深度和深度图此xy的深度,若是自己的深度更大,则处于阴影中。
Unity获得ShadowMap的方式:调用LightMode为ShadowCaster的Pass。因为此Pass比较通用,所以可以使用Fallback,在Fallback中找到此Pass。
以下是在unity标准shader中Mobile-VertexLit.shader内找到的Pass1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
ZWrite On ZTest LEqual Cull Off
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
}
unity设置
- 光源设置 ShadowType决定此光源阴影模式;
- 物体的Mesh Renderer组件的CastShadows参数和Receive Shadows参数决定是否发射/接受阴影。
接収阴影 using内置宏
前文提到用Fallback可以用unity内置的Pass实现阴影显示,然而也可以调用unity内置宏来编写阴影显示的shader。
后面会用到三个内置宏所以在之前需要加上引用:1
#include "AutoLight.cginc"
顶点输出增加内置宏SHADOW_COORDS,增加用于阴影纹理采样的坐标。1
2
3
4
5
6struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
顶点输出钱使用内置宏TRANSFER_SHADOW计算1
2
3
4
5
6v2f vert(a2v v) {
v2f o;
……
TRANSFER_SHADOW(o);
return o;
}
查看TRANSFER_SHADOW源代码可看到,a2v必须包含vertex且名字必须为v,而且v2f必须包含a.pos,否则会GG。运算结果是存储顶点转换到光源空间后的坐标1
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_World2Shadow[0], mul( _Object2World, v.vertex ) );
这里用到内置宏SHADOW_ATTENUATION()来计算阴影值,算出来以后跟其它颜色相乘。计算原理就是前文说过的对阴影纹理采样。1
2
3
4
5fixed4 frag(v2f i) : SV_Target {
……
fixed shadow = SHADOW_ATTENUATION(i);
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}
统一光照和阴影
无论是光照还是阴影,都用到了了从光源出发的纹理以及在光源空间的坐标等,故统一管理与计算。unity又发了福利 UNITY_LIGHT_ATTENUATION
所以在Base Pass 和Additional Pass 里都这么写就好:
1 | struct v2f { |
如此计算出来的atten就是原来的衰减颜色*阴影颜色了。
透明+多重光照+阴影
……整合好一看代码太长了不想贴,就说下思路……其实就是把光照的两个Pass都加上,分别和之前AlphaTest或者AlphaBend代码合并下,即可。
不行有几行还是得记录下…1
2
3
4
5
6
7
8
9
10ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2; //!!!
SHADOW_COORDS(3) //!!!
};
计算之类的还是跟之前一样啦╮(╯▽╰)╭
两个光源效果图
别人的文章:
Shader中的光照
Unity3D光照前置知识——Rendering Paths(渲染路径)及LightMode(光照模式)译解
Unity3D内建参数文档翻译与解析