书接上文,在LWRP上Camera.SetReplacementShader是用不了的,而且就算能用算出来的不透明物体overdraw也是不对的。如果你的项目已经使用了LWRP,那么我们可以借助Custom Renderer Data来拓展渲染overdraw。如下图所示,需要自己创建一个自定义RendererData然后绑定到LWRP中。
如下图所示,在自定义RendererData中需要声明两个材质,分别是渲染不透明、半透明物体的overdraw,材质的写法可以参考我的上一篇文章 Unity3D研究院之使用SRP计算准确overdraw(一百零九)
OverDrawRendererData.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 97 |
using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; using UnityEditor.Rendering.LWRP; #endif using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.LWRP; [CreateAssetMenu(fileName = "OverDrawRendererData", menuName = "Renderering/OverDrawRendererData", order = 2)] public class OverDrawRendererData : ScriptableRendererData { [SerializeField] private Material m_OverDrawQuad; [SerializeField] private Material m_OverDrawTransparent; public Material overDrawQuad => m_OverDrawQuad; public Material overDrawTransparent => m_OverDrawTransparent; protected override ScriptableRenderer Create() { return new OverDrawRenderer(this); } } public class OverDrawRenderer : ScriptableRenderer { private OverDrawRenderPass m_OverDrawRenderQuadPass; private OverDrawRenderPass m_OverDrawRenderTransparentPass; public OverDrawRenderer(ScriptableRendererData data) : base(data) { m_OverDrawRenderQuadPass = new OverDrawRenderPass(SortingCriteria.CommonOpaque, RenderQueueRange.opaque,(data as OverDrawRendererData).overDrawQuad); m_OverDrawRenderTransparentPass = new OverDrawRenderPass(SortingCriteria.CommonTransparent, RenderQueueRange.transparent, (data as OverDrawRendererData).overDrawTransparent); } public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData) { //绘制不透明物体overdraw EnqueuePass(m_OverDrawRenderQuadPass); //绘制半透明物体overdraw EnqueuePass(m_OverDrawRenderTransparentPass); } } public class OverDrawRenderPass : ScriptableRenderPass { private SortingCriteria m_Criteria; private Material m_OverDrawMaterial; private FilteringSettings m_FilteringSettings; private RenderStateBlock m_RenderStateBlock; private List<ShaderTagId> m_ShaderTagIds = new List<ShaderTagId>() { new ShaderTagId("SRPDefaultUnlit"), new ShaderTagId("LightweightForward"), }; public OverDrawRenderPass(SortingCriteria criteria, RenderQueueRange renderQueueRange, Material overdrawMaterial) { this.m_Criteria = criteria; this.m_OverDrawMaterial = overdrawMaterial; this.m_FilteringSettings = new FilteringSettings(renderQueueRange); this.m_RenderStateBlock = new RenderStateBlock(RenderStateMask.Nothing); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var drawingSettings = CreateDrawingSettings(m_ShaderTagIds, ref renderingData, this.m_Criteria); if(!renderingData.cameraData.isSceneViewCamera) drawingSettings.overrideMaterial = m_OverDrawMaterial; CommandBuffer cmd = CommandBufferPool.Get("Name"); using(new ProfilingSample(cmd, "Name")) { context.ExecuteCommandBuffer(cmd); cmd.Clear(); context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref m_FilteringSettings, ref m_RenderStateBlock); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } } #if UNITY_EDITOR [CustomEditor(typeof(OverDrawRendererData))] public class OverDrawRendererDataEditor : ScriptableRendererDataEditor { public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(serializedObject.FindProperty("m_OverDrawQuad")); EditorGUILayout.PropertyField(serializedObject.FindProperty("m_OverDrawTransparent")); serializedObject.ApplyModifiedProperties(); base.OnInspectorGUI(); } } #endif |
此时在GameView视图中就可以能看到OverDraw了,接下来就要具体分析了。
1.贴图有效率
贴图有效率: 能看见渲染的像素/实际渲染的面积。
上面这个例子可以看出这个特效实际渲染的面积就是下面overdraw的区域,但由于贴图的透明区域比较大所以大部分的渲染面积都是白白浪费的。贴图有效率只能对单个特效进行统计,对多个particalSystem拼接成的是没有意义的。
最理想的情况下能看见渲染的像素/实际渲染的面积=1
2.像素覆盖率
效率覆盖率:像素渲染的面积/像素渲染覆盖的面积。
像素渲染的面积应该比较好理解,就是每个暗红色的像素面积的总和,像素渲染覆盖的面积,就是假设每个暗红的像素值是1暗红的面积记作1,如果这个像素出现一次overdraw那么这个像素数面积就是1+1,如果多次overdraw这个像素的面积就记1+n,所有的像素面积加在一起就是像素渲染覆盖的面积。
最理想的情况下像素渲染的面积/像素渲染覆盖的面积=1 但是大多数情况下overdraw的次数会很多这个数值会更大
3.全屏覆盖率
全屏覆盖率:像素渲染覆盖的面积/屏幕的面积
屏幕的面积就是长X宽实际的像素数量,全屏覆盖率可以计算出每个特效实际的渲染压力,特效的消耗毕竟还是看渲染面积的。
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 |
void LateUpdate() { //不渲染overdraw ResetOverDrawShader(); m_Camera.targetTexture = m_RT; m_Camera.Render(); RenderTexture.active = m_RT; Texture2D texture2 = new Texture2D(m_RT.width, m_RT.height, TextureFormat.RGBA32, false); texture2.ReadPixels(new Rect(0, 0, m_RT.width, m_RT.height), 0, 0); //texture2就是不渲染overdraw物体的像素信息。 //开始渲染overdraw SetOverDrawShader(); m_Camera.targetTexture = m_RT; m_Camera.Render(); RenderTexture.active = m_RT; Texture2D texture = new Texture2D(m_RT.width, m_RT.height, TextureFormat.RGBA32, false); texture.ReadPixels(new Rect(0, 0, m_RT.width, m_RT.height), 0, 0); //texture就是不渲染overdraw物体的像素信息。 //最后通过遍历texture和texture2的像素值就能计算出来数据 } |
最后还有2个问题
1.为了美术同学方便,我们需要保证GameView视图和SceneView视图中摄像机的朝向是一致的。
1 2 3 4 5 6 7 |
void ResetCamera() { if(SceneView.lastActiveSceneView) SceneView.lastActiveSceneView.AlignViewToObject(script.transform); } |
2.美术同学可能在场景中会放很多参照物,但是我们统计的时候是需要这些参照物了,所以要从SceneView视图中隐藏。图下图所示,进入统计模式时,脚本设置SceneView视图中只显示”TransparentFx”至于为什么选择这个,因为项目一般不会用到这个层,如果自定义一个新层还需要考虑多个项目通用问题。
代码中能设置SceneView和GameView只看TransparentFx层
1 2 3 |
Tools.visibleLayers = m_Camera.cullingMask = LayerMask.GetMask("TransparentFX"); |
overdraw的工具我的计划是统计所有美术资源,包括 场景、角色、特效、争取给每个每个资源统计一个合理的分数,告诉美术同学做的资源渲染上到底有多费~ 同时也欢迎大家一起讨论。
- 本文固定链接: https://www.xuanyusong.com/archives/4674
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
请问ResetOverDrawShader()和SetOverDrawShader()做了什么
overrideMaterial 会导致替换后的物体主贴图等属性丢失链接,这个问题请问你怎么解决?
升级到unity2019.3 URP 后 要添加 new ShaderTagId(“SRPDefaultUnlit”),
你好,有完整工程么?