光源的着色器
我们在《渲染13:延迟渲染》中添加了对延迟渲染路径的支持。我们所要做的只是填补G缓冲区。光源在后面的过程中会在后面渲染。那个教程简要地介绍了Unity如何添加这些光源。 这一次,我们将自己渲染这些光源。
为了测试光源,我将使用一个简单的场景,其环境强度设置为零。它用延迟模式下的高动态光照渲染摄像机进行渲染。
测试场景,有方向光源和没有方向光源的效果对比。
场景中的所有对象都使用我们自己的着色器渲染到G缓冲区。但是,这些光源是用Unity的默认延迟着色器渲染的,它被命名为Hidden / Internal-DefferedShader。你可以通过“编辑/项目设置/图形”转到图形设置并将“延迟”着色器模式切换为“自定义着色器”来进行验证。
默认的延迟光源着色器。
使用一个自定义的着色器
每个延迟光源使用单独的渲染通道进行渲染,修改图像的颜色。实际上,它们是图像效果,就像我们之前教程中的延迟渲染下雾的着色器一样。 我们从一个简单的着色器开始,用黑色覆盖一切。
Shader"Custom/DeferredShading"{
Properties {
}
SubShader {
Pass {
Cull Off
ZTest Always
ZWrite Off
CGPROGRAM
#pragma target 3.0
#pragma vertex VertexProgram
#pragma fragment FragmentProgram
#pragma exclude_renderers nomrt
#include "UnityCG.cginc"
structVertexData {
float4 vertex : POSITION;
};
structInterpolators {
float4 pos : SV_POSITION;
};
Interpolators VertexProgram (VertexData v) {
Interpolators i;
i.pos = UnityObjectToClipPos(v.vertex);
returni;
}
float4 FragmentProgram (Interpolators i) : SV_Target {
return0;
}
ENDCG
}
}
}
指示Unity在渲染延迟光源的时候使用此着色器。
使用我们的自定义着色器。
第二个渲染通道
切换到我们的着色器后,Unity抱怨说它没有足够的渲染通道。显然,需要第二个渲染通道。我们只是复制我们已经拥有的渲染通道,看看会发生什么。
Pass {
…
}
Pass {
…
}
Unity现在接受我们的着色器并使用它来渲染方向光源。 结果,一切都变黑了。 唯一的例外是天空。模板缓冲区用作掩码以避免在那里渲染,因为方向光源不影响背景。
自定义着色器,照亮和不照亮的效果对比。
但是第二个渲染通道呢? 请记住,当高动态光照渲染被禁用的时候,光源数据被用对数编码。最后一个渲染通道需要、反转这个编码。这是第二个渲染通道的用处。 因此,如果你禁用了摄像机的高动态光照渲染,我们的着色器的第二个渲染通道还是会被使用,一次。
避开对天空的影响
当以低动态光照渲染模式渲染时,你可能会看到天空变黑。 这可能发生在场景视图或游戏视图中。如果天空变黑,那么执行转换的渲染通道没有正确地使用模板缓冲区作为掩码。要解决此问题,请明确设置第二个渲染通道的模板。当我们处理的是非背景部分的片段的时候,我们只应该进行渲染。 通过_StencilNonBackground提供了适当的模板值。
Pass {
Cull Off
ZTest Always
ZWrite Off
Stencil {
Ref [_StencilNonBackground]
ReadMask [_StencilNonBackground]
CompBack Equal
CompFront Equal
}
…
}
我们可以调试模板缓冲区吗?
不幸的是,帧调试器不会显示有关模板缓冲区的任何信息,也不显示其内容以及通道如何使用。 也许这些信息将在以后的版本中添加。
转换颜色
为了使第二个渲染通道工作,我们必须转换光源缓冲区中的数据。像我们的雾着色器一样,使用UV坐标来绘制全屏的四边形,我们可以使用它来采样缓冲区。
structVertexData {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
structInterpolators {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
Interpolators VertexProgram (VertexData v) {
Interpolators i;
i.pos = UnityObjectToClipPos(v.vertex);
i.uv = v.uv;
returni;
}
光源缓冲区本身通过_LightBuffer变量提供给着色器。
sampler2D _LightBuffer;
…
float4 FragmentProgram (Interpolators i) : SV_Target {
returntex2D(_LightBuffer, i.uv);
}
当没有被照亮的时候,原始的低动态光照渲染数据。
使用公式2-C对低动态光照渲染的颜色进行对数编码。 为了解码,我们必须使用公式-log2 C。
1
return-log2(tex2D(_LightBuffer, i.uv));
当没有被照亮的时候的低动态光照渲染的图像。
现在我们知道它可以正常工作,再次启用高动态光照渲染。
项目文件下载地址:unitypackage。
方向光源
第一个渲染通道会负责渲染光源,所以这将是相当复杂的。让我们为它创建一个名为MyDeferredShading.cginc的导入文件。 将渲染通道中的所有代码复制到此文件。
#if !defined(MY_DEFERRED_SHADING)
#define MY_DEFERRED_SHADING
#include "UnityCG.cginc"
…
#endif
然后在第一个渲染通道导入MyDeferredShading。
Pass {
Cull Off
ZTest Always
ZWrite Off
CGPROGRAM
#pragma vertex VertexProgram
#pragma fragment FragmentProgram
#pragma exclude_renderers nomrt
#include "MyDeferredShading.cginc"
ENDCG
}
因为我们应该添加光照到图像,我们必须确保我们不会移除已经渲染的内容。 我们可以通过更改混合模式来组合完整的源颜色和目标颜色。
1
2
3
4Blend One One
Cull Off
ZTest Always
ZWrite Off
我们需要所有可能的光照配置的着色器变体。multi_compile_lightpasscompiler指令创建我们需要的所有关键字组合。唯一的例外是高动态光照渲染模式。 我们必须为此添加一个单独的多编译指令。
#pragma exclude_renderers nomrt
#pragma multi_compile_lightpass
#pragma multi_compile _ UNITY_HDR_