这篇文章将为大家详细讲解有关怎么利用Stencil来优化局部后处理特效,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
创新互联是网站建设技术企业,为成都企业提供专业的网站建设、成都做网站,网站设计,网站制作,网站改版等技术服务。拥有10多年丰富建站经验和众多成功案例,为您定制适合企业的网站。10多年品质,值得信赖!
只是例子就不要再和我说什么十字片,广告牌叠加了。这个效果的实现就是用纯色在RT上重绘原模型给一次高斯模糊后叠回原图。
加了Stencil后计算量下降了一半(为方便对比,我砍掉了后处理流程最后的原图Blit流程和人物绘制部分,仅显示了downsample和高斯模糊的量)
原理:
Stencil和ZTest就如同两兄弟,非常相似,但前者却常常被忽略。大家都知道,在拥有Early-Z(准确的说是Early-ZS,也就是Z+Stencil)机制的当代GPU里,我们可以通过由前向后绘制,通过ZTest让被遮挡的物体免于绘制,这是一个最基本的fillrate优化手段。
Stencil本身是一个比较+改写特定Buff的技术,和ZTest的比较深度+改写深度其实是一样的机制,只是拥有更多的变化。所以它也可以通过先改写Buff,然后让后面的物体在读取这个Buff的时候检测不通过,从而直接跳过像素计算阶段。
局部物体使用后处理特效很不合算,就是因为即使只有局部需要计算,GPU也必须计算整个屏幕。如果先用Stencil在屏幕上绘制一个标记区域,就可以将后面的计算限定在小范围内。
用的是一个简单的法线延伸OutLine,扩大了棒子的体积。并在绘制过的地方标记Stencil为1
Pass { CULL OFF ZTest OFF COLORMASK 0 Stencil { Ref 1 Comp NotEqual Pass Replace ReadMask 1 WriteMask 1 } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; }; float _Outline; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); float3 norm = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal); float2 offset = TransformViewToProjection(norm.xy); o.vertex.xy += normalize(offset) * o.vertex.z * _Outline; UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { return 1; } ENDCG }
然后再在之后的后处理流程里,给需要的Pass加上
Stencil { Ref 1 Comp Equal Pass Keep ReadMask 1 WriteMask 1 }
即可。
不过……
1. Stencil只能通过绘制实体来写入,不能在多张Buff间复制。所以一旦经过downsample就会丢失,而downsample是后处理中非常常见的。
2. 由于这个原因,RT绘制的顺序也非常重要,只有作为绘制目标的RT才有获得Stencil检测的机会。
3.Stencil在其他RT中的利用,只能通过Graphics.SetRenderTarget(source.colorBuffer, stencil.depthBuffer)来完成,指向一张RT的颜色Buffer,却同时指向另一张RT的depthBuff,且这两张RT的分辨率必须完全一样。
具体代码(普通的Blit是没法用的,会强制切换RenderTarget):
private void DepthBlit(RenderTexture source, RenderTexture destination, Material mat, int pass, RenderTexture depth) { if (depth == null) { Graphics.Blit(source, destination, mat, pass); return; } Graphics.SetRenderTarget(destination.colorBuffer, depth.depthBuffer); GL.PushMatrix(); GL.LoadOrtho(); mat.mainTexture = source; mat.SetPass(pass); GL.Begin(GL.QUADS); GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(0.0f, 1.0f, 0.1f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, 0.1f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 0.0f, 0.1f); GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(0.0f, 0.0f, 0.1f); GL.End(); GL.PopMatrix(); }
纹理大小限制是最麻烦的,其它都还好。实际用法就是,保留保存着Stencil的那张RT,然后在需要的时候挂接在目标RT上。
绘制标记是有代价的,但也可以在绘制正常画面时顺带绘制,这样代价就小很多。
在可以用到的时候记得使用即可。
好吧,我知道你们可能会问Bloom本身具体是怎么做的。Bloom本身Unity内置后处理有一个,但是是全屏的。想做到局部,确实只能单开一张RT来绘制要泛光的物体。
但这就涉及到保留深度缓冲的问题,否则那个物体就必须显示在最前面而无法被障碍物遮挡。因为同时还要切换RT绘制,唯一的办法就是刚才的Graphics.SetRenderTarget(source.colorBuffer, stencil.depthBuffer),绘制新RT,同时使用以前的深度缓冲区。
我为了图简单,是直接在后处理阶段里切换RenderTarget,然后用Graphics.DrawMeshNow来绘制要泛光的物体的,这会导致Mesh无法Batch。想要Batch,必须让摄像机来绘制这些物体,应该是必须用到CommandBuffer这些东西。
关于怎么利用Stencil来优化局部后处理特效就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。