首页 >Unity3D频道 >【Unity3D研究院之游戏开发】 > Unity3D研究院之使用SRP计算准确overdraw(一百零九)
2019
11-16

Unity3D研究院之使用SRP计算准确overdraw(一百零九)

最近在研究如何把overdraw统计成准确的数据值,让美术同学能直观的知道自己做的特效、场景、角色到底有多费,再加上好久没有写文章了,自己也有点心得今天就分享给大家并且希望可以后面一起讨论。

首先Unity的overdraw是不准确的,如下图所示,我们放两个不透明的物体,在SceneView切到overdraw菜单,居然产生了overdraw。

QQ20191116-204502

不透明物体会写深度,默认是从后往前画的。那么按照上面的例子是不可能产生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

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

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

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中。

QQ20191116-212126

OverDrawRenderPipeline.cs

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();

    }
    
} 

OK,最后运行准确的overdraw已经统计完毕了。

QQ20191116-214240

现在还有2个问题

1.如何把准确的overdraw统计出来?

我的做法是做了一个全屏的RT(避免太卡还是要乘系数),分别取一张overdraw的图,在取一张不带overdraw的图,在遍历两张图的像素值就可以计算出来填充率了。

2.如果项目已经是lwrp、urp、hdrp、怎么处理呢?

其实也简单,就是两个做一个合并就可以了,也可以在创建一个新的SRPSetting,统计overdraw的时候动态覆盖到setting上面,统计完了在还原回来。

不管怎么说,这两个问题我会在下篇文章里好好讨论,我们下篇见!衷心的希望大家可以一起讨论。

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

Unity3D研究院之使用SRP计算准确overdraw(一百零九)》有 4 条评论

  1. 大超说:

    不透明物体是从前往后

  2. heigong说:

    不透明物体默认不是从前往后画的吗?

  3. gao说:

    请问下,有完整工程示例么?

留下一个回复

你的email不会被公开。 必填项已用 * 标注。