最近在研究如何把overdraw统计成准确的数据值,让美术同学能直观的知道自己做的特效、场景、角色到底有多费,再加上好久没有写文章了,自己也有点心得今天就分享给大家并且希望可以后面一起讨论。
首先Unity的overdraw是不准确的,如下图所示,我们放两个不透明的物体,在SceneView切到overdraw菜单,居然产生了overdraw。
不透明物体会写深度,默认是从后往前画的。那么按照上面的例子是不可能产生overdraw的。原因是当切到overdraw模式下unity会强制(无论透明还是不透明)都用另一个overdraw的shader渲染,直接 Blend One One然后关闭深度写入,所以就会出现上面的错误结果。
不透明物体大部分情况下是不会产生overdraw的,除非是自己改了RenderQueue。比如后面(白色)的物体深度设置RenderQueue=1999,前面(红色)的物体深度设置2000,那么此时才会真正产生overdraw。如果两个(白色、红色)RenderQueue一样都等于默认值2000的话是不会产生overdraw的。
在说说透明物体、透明物体一般是不写深度的,而且透明物体遵守“画家算法”是从后向前画的,如果只渲染透明物体那么在切到overdraw页签时unity渲染出来的区域就是准确的。
回到文章考开篇,我想做的是统计特效、场景、角色的渲染,实际应用场景存在透明物体+不透明物体混合排列的情况,所以计算出准确的overdraw就更重要了,于是我想起了SRP可编程渲染管线。
我们首先需要准备2个画overdraw的shader,一个是给不透明物体用的(写深度),另一个是给透明物体用的(关闭写深度)
OverDraw/OverDraw-Queue
1 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
Shader "OverDraw/OverDraw-Queue" { SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Transparent" } Fog { Mode Off } ZTest Always ZTest LEqual Blend One One Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag(v2f i) : SV_Target { return fixed4(0.1, 0.04, 0.02, 0); } ENDCG } } } |
OverDraw/OverDraw-Transparent
1 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
Shader "OverDraw/OverDraw-Transparent" { SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Transparent" } Fog { Mode Off } ZWrite Off ZTest Always ZTest LEqual Blend One One Cull Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag(v2f i) : SV_Target { return fixed4(0.1, 0.04, 0.02, 0); } ENDCG } } } |
代码比较好理解就是 Blend One One 一层叠一层,然后fixed4(0.1, 0.04, 0.02, 0);就是那个看起来暗红色的颜色,如果blend多了就越来越红。
OverDrawRenderPipelineAsset.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using UnityEngine; using UnityEngine.Rendering; [CreateAssetMenu(menuName = "Rendering/OverDraw Render Pipeline")] public class OverDrawRenderPipelineAsset : RenderPipelineAsset { protected override RenderPipeline CreatePipeline() { return new OverDrawRenderPipeline(); } } |
Create->Rendering->OverDraw Render Pipeline 创建一个自定义的渲染配置文件,如下图所示,将新创建的配置文件绑定到SRPSetting中。
OverDrawRenderPipeline.cs
1 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
using UnityEngine; using UnityEngine.Rendering; public class OverDrawRenderPipeline : RenderPipeline { protected override void Render(ScriptableRenderContext context, Camera[] cameras) { foreach (Camera camera in cameras) { Render(context, camera); } } const string BUFF_NAME = "Render Camera"; static CommandBuffer s_CommandBuffer = new CommandBuffer { name = BUFF_NAME }; CullingResults cullingResults; static Material s_OverQueue = new Material(Shader.Find("OverDraw/OverDraw-Queue")); static Material s_OverTransparent = new Material(Shader.Find("OverDraw/OverDraw-Transparent")); //支持的ShaderTagID static ShaderTagId[] s_ShaderTagIds = { new ShaderTagId("SRPDefaultUnlit"), new ShaderTagId("Always"), new ShaderTagId("ForwardBase"), new ShaderTagId("PrepassBase"), new ShaderTagId("Vertex"), new ShaderTagId("VertexLMRGBM"), new ShaderTagId("VertexLM") }; public void Render(ScriptableRenderContext context, Camera camera) { //处理裁切 if (camera.TryGetCullingParameters(out ScriptableCullingParameters p)) { cullingResults = context.Cull(ref p); } else { return; } context.SetupCameraProperties(camera); s_CommandBuffer.ClearRenderTarget(true, true, Color.clear); s_CommandBuffer.BeginSample(BUFF_NAME); context.ExecuteCommandBuffer(s_CommandBuffer); s_CommandBuffer.Clear(); //排序设置-SortingCriteria.CommonOpaque表示默认不透明物体排序 var sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonOpaque}; var drawingSettings = new DrawingSettings(s_ShaderTagIds[0], sortingSettings); var filteringSettings = new FilteringSettings(RenderQueueRange.opaque); for (int i = 1; i < s_ShaderTagIds.Length; i++) { drawingSettings.SetShaderPassName(i, s_ShaderTagIds[i]); } //开始绘制不透明物体,SceneView视图中还用物体自身的Shader if (camera.cameraType != CameraType.SceneView) drawingSettings.overrideMaterial = s_OverQueue;//换shader context.DrawRenderers( cullingResults, ref drawingSettings, ref filteringSettings ); //排序设置-SortingCriteria.CommonTransparent表示默认透明物体排序 sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonTransparent}; drawingSettings = new DrawingSettings(s_ShaderTagIds[0], sortingSettings); filteringSettings = new FilteringSettings(RenderQueueRange.transparent); for (int i = 1; i < s_ShaderTagIds.Length; i++) { drawingSettings.SetShaderPassName(i, s_ShaderTagIds[i]); } //开始绘制透明物体,SceneView视图中还用物体自身的Shader if (camera.cameraType != CameraType.SceneView) drawingSettings.overrideMaterial = s_OverTransparent;//换shader context.DrawRenderers( cullingResults, ref drawingSettings, ref filteringSettings ); s_CommandBuffer.EndSample(BUFF_NAME); context.ExecuteCommandBuffer(s_CommandBuffer); s_CommandBuffer.Clear(); context.Submit(); } } |
现在还有2个问题
1.如何把准确的overdraw统计出来?
我的做法是做了一个全屏的RT(避免太卡还是要乘系数),分别取一张overdraw的图,在取一张不带overdraw的图,在遍历两张图的像素值就可以计算出来填充率了。
2.如果项目已经是lwrp、urp、hdrp、怎么处理呢?
其实也简单,就是两个做一个合并就可以了,也可以在创建一个新的SRPSetting,统计overdraw的时候动态覆盖到setting上面,统计完了在还原回来。
不管怎么说,这两个问题我会在下篇文章里好好讨论,我们下篇见!衷心的希望大家可以一起讨论。
- 本文固定链接: https://www.xuanyusong.com/archives/4669
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
不透明物体是从前往后
不透明物体默认不是从前往后画的吗?
不透明物体会写深度,默认是从后往前画的?????
请问下,有完整工程示例么?