总览
使用延迟着色时,可以影响GameObject的灯光数量没有限制。所有光源均为逐像素光源进行评估,这意味着它们都可以与法线贴图等正确交互。此外,所有光源都可以包含Cookie和阴影。
优点:照明的处理开销与光照射的像素数量成正比。这取决于场景中的灯光大小,无论它照亮了多少个GameObject。因此,可以通过保持较小的灯光来提高性能。延迟阴影还具有高度一致且可预测的行为。每种光的效果都是按像素计算的,因此没有照明计算会分解成大三角形(PS:我理解为场景中的三角网格不会新增?(待测试))。
缺点:不支持抗锯齿;不能处理半透明的GameObject(使用正向渲染来渲染半透明物体);Mesh Renderer 的“接收阴影”标志也不受支持,并且仅以有限的方式支持剔除蒙版。您最多只能使用四个剔除面罩(culling mask)。也就是说,您的剔除层蒙版必须至少包含所有层减去四个任意层,因此必须设置32层中的28层。否则,您将获得图形伪像(graphical artefacts)。
要求
它需要显卡支持Multiple Render Targets(MRT),Shader Model 3.0(或更高版本)并支持深度渲染纹理的图形卡。2006年以后生产的大多数PC图形卡都支持延迟着色,从GeForce 8xxx,Radeon X2400和Intel G45开始。
在移动设备上,不支持延迟着色,主要原因是移动设备大多数不支持MRT(某些确实支持MRT的GPU,但仅支持非常有限的位数)。移动设备:所有 OpenGL ES 3.0 以上都支持延迟渲染
注意:使用正交投影时,不支持延迟渲染。如果相机的投影模式设置为“正交”,则相机将退回到正向渲染(Forward Rendering)。
性能考量
延迟阴影中实时灯光的渲染开销与灯光照亮的像素数量成比例,并且与场景复杂度无关(PS:指与三角网格无关,是多是少都不会影响性能)。因此,小的点光源或点光源的渲染成本非常低廉,如果它们被场景游戏对象完全或部分遮挡,那么它们甚至会更便宜。
当然,带有阴影的灯比没有阴影的灯贵得多。在延迟着色中,仍然需要为每个阴影投射光渲染一次或多次阴影投射GameObject。此外,应用阴影的照明着色器比禁用阴影时使用的着色器具有更高的渲染开销。
实施细节
不支持延迟着色的对象将在延迟着色完成后使用前向渲染路径进行渲染。
下面列出了几何缓冲区(g缓冲区)中渲染目标(RT0-RT4)的默认布局。数据类型放置在每个渲染目标的各个通道中。使用的通道显示在括号中。
- RT0,ARGB32格式:漫反射色(RGB),遮挡(A)。
- RT1,ARGB32格式:镜面反射色(RGB),粗糙度(A)。
- RT2,ARGB2101010格式:世界空间法线(RGB),未使用(A)。
- RT3,ARGB2101010(非HDR)或ARGBHalf(HDR)格式:环境光+光照+光照贴图+反射探针缓冲区。
- 深度+模板缓冲区。
因此,默认的g缓冲区布局为160位/像素(非HDR)或192位/像素(HDR)。
160位/像素代表一个像素点是通过5个32位/像素的RT来生成的,HDR情况是因为RGBA通道会每一个通道占用了16位而不是8位,所以多出了32位。(下方同理) HDR是ARGBHalf格式(每一个通道16位)
如果将Shadowmask or Distance Shadowmask 模式用于混合照明,则使用第五个目标:
- RT4,ARGB32格式:光遮挡值(RGBA)。
因此,g缓冲区布局为192位/像素(非HDR)或224位/像素(HDR)。
如果硬件不支持五个并发渲染目标,则使用阴影遮罩(Shadowmask)的对象将回退到正向渲染路径。当照相机不使用HDR时,对 Emission+lighting buffer (RT3) 进行了对数编码(logarithmically encoded),以提供比通常使用ARGB32纹理可能提供的更大的动态范围。
请注意,当相机使用HDR渲染时,不会为(RT3)创建单独的渲染目标;而是将Camera渲染到的渲染目标(即传递到图像效果的渲染目标)用作RT3。
G-Buffer pass
g缓冲区传递将每个GameObject渲染一次。漫反射和镜面反射的颜色,表面平滑度,世界空间法线以及漫反射+环境光+反射+光照贴图将渲染为g缓冲区纹理。将g缓冲区纹理设置为全局着色器属性,以供以后由着色器(CameraGBufferTexture0 .. CameraGBufferTexture3名称)访问。
Lighting pass
光照过程基于g缓冲区和深度来计算光照。光照是在屏幕空间中计算的,因此处理所需的时间与场景的复杂性无关。将照明添加到 emission buffer (或Lighting Buffer)。
未穿过相机近平面的点光源和点光源被渲染为3D形状,并启用了Z缓冲区针对场景的测试。这使得部分或完全遮挡的点光源和聚光灯渲染起来非常便宜。穿过近平面的方向灯和点/点光源渲染为全屏四边形。
如果光源启用了阴影,则还将在此过程中渲染并应用阴影。请注意,阴影并不是为了“自由”而来。需要渲染阴影投射器,并且必须应用更复杂的灯光着色器。
唯一可用的照明模型是“标准”。如果需要不同的模型,则可以通过将内置着色器的Internal-DeferredShading.shader文件的修改后的版本放置到“资产”文件夹中名为“资源”的文件夹中来修改照明传递着色器。然后转到Edit->Project Settings->Graphics window窗口。将“Deferred”下拉列表更改为“Custom Shader”。然后更改显示在您使用的着色器上的“着色器”选项。
以上内容过一下场子,大概了解下什么是延迟渲染路径即可。
一、实战
1、GBuffer(RT0、RT1、RT2、RT3)四个渲染目标纹理的生成Shader
Shader "Unlit/DeferredPathShaderTest"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Diffuse("Diffuse", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(8.0, 256)) = 8.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Blend One One
Tags{"LightMode" = "Deferred"}
CGPROGRAM
#pragma target 3.0 //延迟渲染必须在target 3.0及以上Shader Model版本
#pragma vertex vert
#pragma fragment frag
#pragma exclude_renderers nomrt //指排除nomrt平台(不支持Multiple Render Target的平台)不进行渲染
#pragma multi_compile __ UNITY_HDR_ON //区分是否开启HDR
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct DeferredOutput
{
float4 gBuffer0 : SV_TARGET0;//RT0 ARGB32 反射率Albedo 遮挡值
float4 gBuffer1 : SV_TARGET1;//RT1 ARBG32 高光反射颜色 高光反射指数部分
float4 gBuffer2 : SV_TARGET2;//RT2 ARBG2101010 世界空间法线 A通道没被使用
float4 gBuffer3 : SV_TARGET3;//RT3 ARGB2101010/ARGBHalf(每个通道16位) 存储自发光 lightmap 反射探针 深度缓冲和模板缓冲
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
DeferredOutput frag(v2f i) {
//一个简易的延迟渲染GBuffer计算
DeferredOutput o;
fixed3 color = tex2D(_MainTex, i.uv).rgb * _Diffuse.rgb;
//RT0 反射率rgb 遮罩值a
o.gBuffer0.rgb = color;
o.gBuffer0.rgb = 1;
//RT1 高光反射值rgb 高光指数部分a
o.gBuffer1.rgb = _Specular.rgb;
o.gBuffer1.a = _Gloss / 256.0;
//RT2 世界空间法线[0,1]范围rgb Alpha部分未使用
o.gBuffer2.rgb = float4(i.worldNormal * 0.5 + 0.5, 1);//[-1,1]转为[0,1]存储在RGB上
//没有开启HDR时
#if !defined(UNITY_HDR_ON)
color.rgb = exp2(-color.rgb); //进行颜色编码 在lightpass部分会进行解码
//exp2函数:计算2的x次方 x指参数
#endif
//RT3 自发光+lightmap+反射探针缓存
o.gBuffer3 = float4(color, 1);
return o;
}
ENDCG
}
}
}
2、自定义LightPass的Shader(可选,因Unity自带有LightPass )
Shader "Unlit/Deffred"
{
SubShader
{
Pass
{
ZWrite Off
//LDR Blend DstColor zero HDR : BLend One One
Blend[_SrcBlend][_DstBlend]
CGPROGRAM
#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_lightpass
#pragma exclude_renderers nomrt
#pragma multi_compile __ UNITY_HDR_ON
#include "UnityCG.cginc"
#include "UnityDeferredLibrary.cginc"
#include "UnityGBuffer.cginc"
sampler2D _CameraGBufferTexture0;
sampler2D _CameraGBufferTexture1;
sampler2D _CameraGBufferTexture2;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
//struct v2f
//{
// float4 pos: SV_POSITION;
// float4 uv:TEXCOORD;
// float3 ray : TEXCOORD1;
//};
unity_v2f_deferred vert(a2v v)
{
unity_v2f_deferred o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = ComputeScreenPos(o.pos);
o.ray = UnityObjectToViewPos(v.vertex) * float3(-1,-1,1);
o.ray = lerp(o.ray, v.normal,_LightAsQuad);
return o;
}
#ifdef UNITY_HDR_ON
half4
#else
fixed4
#endif
frag(unity_v2f_deferred i) : SV_Target
{
float3 worldPos;
float2 uv;
half3 lightDir;
float atten;
float fadeDist;
UnityDeferredCalculateLightParams(i,worldPos,uv,lightDir, atten,fadeDist);
//float2 uv = i.uv.xy / i.uv.w; //得到uv坐标
通过深度和方向重新构建世界坐标
//float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,uv);
//depth = Linear01Depth(depth);
_ProjectionParams.z 远平面 _ProjectionParams.z/i.ray.z 得到适当的距离
//i.ray = i.ray *(_ProjectionParams.z/i.ray.z);
//float4 viewPos = float4(i.ray * depth, 1);
//float3 worldPos = mul(unity_CameraToWorld,viewPos).xyz;
//float fadeDist = UnityComputeShadowFadeDistance(worldPos,viewPos.z);
对不同的光进行衰减计算 包括阴影计算
//#if defined(SPOT)
// float3 toLight = _LightPos.xyz - worldPos;
// half3 lightDir = normalize(toLight);
// float4 uvCookie = mul(unity_WorldToLight,float4(worldPos,1));
// float atten = tex2Dbias(_LightTexture0, float4(uvCookie.xy/uvCookie.w,0,-8)).w;
// atten *= uvCookie< 0;
// atten *= tex2D(_LightTextureB0, dot(toLight,toLight)*_LightPos.w).r;
// atten *= UnityDeferredComputeShadow(worldPos,fadeDist,uv);
//#elif defined(DIRECTIONAL) || defined(DIRECTIONAL_COOKIE)
// half3 lightDir = - _LightDir.xyz;
// float atten = 1.0;
// atten *= UnityDeferredComputeShadow(worldPos,fadeDist,uv);
// #if defined(DIRECTIONAL_COOKIE)
// float4 uvCookie = mul(unity_WorldToLight,float4(worldPos,1));
// atten *= tex2Dbias(_LightTexture0, float4(uvCookie.xy,0,-8)).w;//
// #endif
//#elif defined(POINT) || defined(POINT_COOKIE)
// float3 toLight = _LightPos.xyz - worldPos;
// half3 lightDir = normalize(toLight);
// float atten = tex2D(_LightTextureB0, dot(toLight,toLight)*_LightPos.w).r;
// atten *= UnityDeferredComputeShadow(worldPos,fadeDist,uv);
// #if defined(POINT_COOKIE)
// float4 uvCookie = mul(unity_WorldToLight,float4(worldPos,1));
// atten *= texCUBEbias(_LightTexture0, float4(uvCookie.xyz,-8)).w;//
// #endif
//#else
// half3 lightDir = 0;
// float atten = 0;
//#endif
half3 lightColor = _LightColor.rgb * atten;
half4 gbuffer0 = tex2D(_CameraGBufferTexture0, uv);
half4 gbuffer1 = tex2D(_CameraGBufferTexture1, uv);
half4 gbuffer2 = tex2D(_CameraGBufferTexture2, uv);
half3 diffuseColor = gbuffer0.rgb;
half3 specularColor = gbuffer1.rgb;
float gloss = gbuffer1.a * 50;
float3 worldNormal = normalize(gbuffer2.xyz * 2 - 1);
fixed3 viewDir = normalize(_WorldSpaceCameraPos - worldPos);
fixed3 halfDir = normalize(lightDir + viewDir);
half3 diffuse = lightColor * diffuseColor * max(0,dot(worldNormal,lightDir));
half3 specular = lightColor * specularColor * pow(max(0,dot(worldNormal,halfDir)),gloss);
half4 color = float4(diffuse + specular,1);
#ifdef UNITY_HDR_ON
return color;
#else
return exp2(-color);
#endif
}
ENDCG
}
//转码pass,主要是对于LDR转码
Pass
{
ZTest Always
Cull Off
ZWrite Off
Stencil
{
ref[_StencilNonBackground]
readMask[_StencilNonBackground]
compback equal
compfront equal
}
CGPROGRAM
#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
#pragma exclude_renderers nomrt
#include "UnityCG.cginc"
sampler2D _LightBuffer;
struct v2f
{
float4 vertex:SV_POSITION;
float2 texcoord:TEXCOORD0;
};
v2f vert(float4 vertex:POSITION, float2 texcoord : TEXCOORD0)
{
v2f o;
o.vertex = UnityObjectToClipPos(vertex);
o.texcoord = texcoord.xy;
#ifdef UNITY_SINGLE_PASS_STEREO
o.texcoord = TransformStereoScreenSpaceTex(o.texcoord,1.0);
#endif
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return -log2(tex2D(_LightBuffer,i.texcoord));
}
ENDCG
}
}
}
总结
简单来说,延迟渲染分为2个Pass,一个是GBuffer Pass ,为第二个LightPass进行准备4张渲染纹理。
RT0: rgb存储反射率,a存储遮罩值
RT1:rgb存储高光反射值,a存储高光反射指数
RT2:rgb存储世界空间法线,a不使用
RT3:存储lightmap 自发光 环境反射 反射探针
当计算出上方所有RT之后,进入到LightPass,使用上方RT对屏幕上的像素进行渲染,决定最终颜色。
参考文章
https://blog.youkuaiyun.com/qq_38275140/article/details/86360563