前言
Unity游戏开发过程中,无论是UI还是3D物体之间是什么因素决定了它的渲染顺序的了,本章是为了深入学习理解渲染顺序相关知识而写。
Note:
- 本章节本非深入渲染管线学习,而是理解Unity渲染管线里的一些渲染顺序相关的决定因素设计。
渲染顺序相关概念
一个物体的渲染无论3D还是2D都需要经过摄像机的照射,然后通过Shader经历渲染管线处理后最终显示在屏幕上。
在正确理解渲染顺序之前,让我们先了解一些相关概念知识:
- Camera(OpaqueSortMode,TransparencySortMode,Depth)
- RenderQueue
- SortingLayer
- SortingOrder
- ZTest & ZWrite
Camera
OpaqueSortMode
Opaque object sorting mode of a Camera. Opaque objects are sorted by various criteria (sorting layers, shader queues, materials, distance, lightmaps etc.) to maximize both the CPU efficiency (reduce number of state changes and improve draw call batching), and to maximize GPU efficiency (many GPUs prefer rough front-to-back rendering order for faster rejection of invisible surfaces).
简单的理解OpaqueSortMode是设置摄像机对不透明物体的排序顺序的策略标准。
OpaqueSortMode.FrontToBack(根据距离排序,先绘制近的后绘制远的结合ZTest实现正确渲染显示)
OpaqueSortMode.NoDistanceSort(不根据距离排序)
TransparencySortMode
Transparent object sorting mode. By default, perspective Cameras sort objects based on distance from Camera position to the object center; and orthographic Cameras sort based on distance along the view direction.
简单的理解TransparencySortMode是设置摄像机对透明物体的排序顺序的策略标准。
TransparencySortMode.Perspective(透视投影–判定物体中心与摄像机距离)
TransparencySortMode.Orthographic(正交投影–判定物体与摄像机方向距离)
TransparencySortMode.CustomAxis(轴判定–判定与指定轴距离)
比如纯2D游戏,可能就希望使用正交投影方式。
Depth
Camera’s depth in the camera rendering order. Cameras with lower depth are rendered before cameras with higher depth. Use this to control the order in which cameras are drawn if you have multiple cameras and some of them don’t cover the full screen.
简单的理解Camera.depth是用于控制多个摄像机之间的渲染顺序,值越低越先渲染。
常规的场景摄像机和UI摄像机采用不同摄像机,为了确保UI摄像机在场景摄像机之上,所以一般把UI摄像机的depth设置的更小。
RenderQueue
Determine in which order objects are renderered. This way for example transparent objects are rendered after opaque objects.
从上面的介绍可以看出,RenderQueue决定物体的渲染顺序(e.g. 透明物体在不透明物体之后渲染)
让我们看下不同的RenderQueue的详细介绍:

RenderQueue越大显示层级越高
Note:
- 不透明物体RenderQueue<= 2500,透明物体RenderQueue>2500
SortingLayer
SortingLayer allows you to set the render order of multiple sprites easily. There is always a default SortingLayer named “Default” which all sprites are added to initially.
从上面介绍可以看出,SortingLayer也是决定物体显示顺序优先级,SortingLayer越大显示层级越高,至于它和RenderQueu之间的优先级后续会讲到。
SortingOrder
Renderer’s order within a sorting layer. You can group GameObjects into layers in their SpriteRenderercomponent. This is called the SortingLayer. The sorting order decides what priority each GameObject has to the Renderer within each Sorting Layer.
从上面介绍可以看出,SortingOrder是在SortingLayer之后的一个显示优先级设置,(SortingLayer相同前提下)值越大显示层级越高。
ZTest & ZWrite
ZWrite(深度写入)
Sets whether the depth buffer contents are updated during rendering. Normally, ZWrite is enabled for opaque objects and disabled for semi-transparent ones. Disabling ZWrite can lead to incorrect depth ordering. In this case, you need to sort geometry on the CPU.
从上面的介绍可以看出ZWrite是决定渲染时是否写入深度信息。通常情况情况不透明物体的ZWrite是打开的,半透明物体ZWrite是关闭的。
那么什么是深度信息了?
深度其实就是该像素点在3d世界中距离摄像机的距离。离摄像机越远,则深度值(Z值)越大。
这里需要了解两个渲染管线里的概念:
- Z-Buffer(深度缓冲区)(用于记录当前渲染过程中渲染记录的深度信息)
- Color-Buff(颜色缓冲区)(用于记录当前渲染过程中渲染记录的颜色信息)
Note:
- 深度信息写入的大前提是通过ZTest(深度测试)
ZTest(深度测试)
Sets the conditions under which geometry passes or fails depth testing. Depth testing allows GPUs that have “Early-Z” functionality to reject geometry early in the pipeline, and also ensures correct ordering of the geometry.
从上面的介绍可以看出ZTest是设置深度测试判定策略。深度测试可以实现“Early-Z”从而实现提前扔掉不必要的参与渲染的数据,同时ZTest是确保渲染顺序的关键。
ZWrite结合ZTest的逻辑流程如下:
- 当ZTest开启时,以深度缓冲为判断基准,来判断是否满足通过条件,如果不满足则直接丢弃,如果满足执行2
- 更新颜色缓存,同时判断是否可以更新深度缓冲,(也就是 ZWrite 是On还是 Off),如果Off,不更新深度缓冲(之后如果有同位置的颜色要深度测试,用的还是之前深度缓冲里对应颜色的深度),如果满足执行3**
- 更新深度缓冲
ZTest与ZWrite的所有组合关系如下
- 深度测试通过,深度写入开启:更新颜色缓冲区,更新深度缓冲区;
- 深度测试通过,深度写入关闭:更新颜色缓冲区,不更新深度缓冲区;
- 深度测试失败,深度写入开启:不更新颜色缓冲区,不更新深度缓冲区;
- 深度测试失败,深度写入关闭:不更新颜色缓冲区,不更新深度缓冲区。
了解了ZWrite和ZTest概念后,这里就会好奇,ZWrite和ZTest和RenderQueue都是用于决定渲染顺序的,那他们之间的优先级是怎样的了?详细测试见后面实战。
Note:
- 只有通过ZTest才会把像素信息写入Color-Buffer(颜色缓冲区)
Alpha Blend(透明度混合)
透明度混合使用當前片元的透明度作為混合因子,與已經存儲在顏色緩衝中的顏色進行混合,得到新的顏色,不過透明度混合必須要關閉深度寫入(Zwrite) 需要特別注意的是,透明度混合只會關閉深度寫入不會關閉深度測試(ZTest),這意味著當透明度混合渲染一個片元時,還是會比較他的深度與深度緩存中的深度值,如果深度值距離相機更遠就不會進行混合操作。
正是因为透明度混合只关闭ZWrite不关闭ZTest,所以不透明物体依然可以通过ZTest进行对透明物体的遮挡。
实战
学习了影响渲染顺序相关的概念后,我们知道了影响物体渲染顺序的东西很多,特别是材质的Shader里的RenderQueue,但Unity有一些数据默认是没有显示在Inspector面板上的,为了方便快速查看或修改渲染相关数据,这里我通过扩展Inspector的方式让相关数据显示出来。
Inspecotr扩展
Camera Inspector扩展
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
| using UnityEditor; using UnityEngine; using UnityEngine.Rendering;
[CanEditMultipleObjects] [CustomEditor(typeof(Camera))] public class CameraInspector : CameraEditor {
private Camera mTarget;
public override void OnInspectorGUI() { base.OnInspectorGUI(); if(mTarget == null) { mTarget = (Camera)target; } if (mTarget != null) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("opaqueSortMode", GUILayout.Width(170f)); EditorGUI.BeginChangeCheck(); var newOpaqueSortMode = (OpaqueSortMode)EditorGUILayout.EnumPopup(mTarget.opaqueSortMode); if(EditorGUI.EndChangeCheck()) { mTarget.opaqueSortMode = newOpaqueSortMode; } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("transparencySortMode", GUILayout.Width(170f)); EditorGUI.BeginChangeCheck(); var newTransparencySortMode = (TransparencySortMode)EditorGUILayout.EnumPopup(mTarget.transparencySortMode); if(EditorGUI.EndChangeCheck()) { mTarget.transparencySortMode = newTransparencySortMode; } EditorGUILayout.EndHorizontal(); } } }
|

可以看到通过扩展,我把Camera.opaqueSortMode和Camera.transparencySortMode都在Inspecotr面板显示出来了。
MeshRenderer Inspector扩展
MeshRendererInspector.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 98 99 100 101 102 103
| using System; using System.Linq; using UnityEditor; using UnityEngine;
[CustomEditor(typeof(MeshRenderer))] public class MeshRendererInspector : Editor { private SerializedProperty mSortingOrderProperty;
private SerializedProperty mMaterialsProperty;
private MeshRenderer mTarget;
private string[] mSortingLayersNameArray;
private int mSortingLayerIndex;
private void OnEnable() { mTarget = (MeshRenderer)target; mSortingLayersNameArray = SortingLayer.layers.Select(x => x.name).ToArray(); mSortingOrderProperty = serializedObject.FindProperty("m_SortingOrder"); mMaterialsProperty = serializedObject.FindProperty("m_Materials"); UpdateSortingLayerIndex(); }
public override void OnInspectorGUI() { base.OnInspectorGUI(); serializedObject.Update(); EditorGUI.BeginChangeCheck(); var selectedIndex = EditorGUILayout.Popup("SortingLayerName", mSortingLayerIndex, mSortingLayersNameArray); if(EditorGUI.EndChangeCheck()) { mTarget.sortingLayerName = SortingLayer.layers[selectedIndex].name; UpdateSortingLayerIndex(); } if(mSortingOrderProperty != null) { EditorGUILayout.PropertyField(mSortingOrderProperty); } if(mMaterialsProperty != null) { for(int i = 0; i < mMaterialsProperty.arraySize; i++) { var material = mMaterialsProperty.GetArrayElementAtIndex(i); if(material != null && material.objectReferenceValue != null) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField($"Materials[{i}]", GUILayout.Width(170f)); EditorGUILayout.BeginVertical(); var mat = (Material)material.objectReferenceValue; EditorGUILayout.ObjectField(mat, EditorType.MATERIAL_TYPE, false); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("RenderQueue", GUILayout.Width(80f)); EditorGUILayout.IntField(mat.renderQueue); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); } } } serializedObject.ApplyModifiedProperties(); }
private void UpdateSortingLayerIndex() { mSortingLayerIndex = Array.FindIndex(mSortingLayersNameArray, FindSortingLayerNameIndex); }
private bool FindSortingLayerNameIndex(string sortingName) { return mTarget.sortingLayerName == sortingName; } }
|

可以看到通过扩展,我把MeshRenderer的SortingLayerName和Sorting Order以及材质的RenderQueue值都显示出来了。
SpriteRenderer Inspector扩展
SpriteRendererInspector.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
| using System; using System.Reflection; using UnityEditor; using UnityEngine;
[CustomEditor(typeof(SpriteRenderer))] public class SpriteRendererInspector : Editor { private SerializedProperty mMaterialsProperty;
private Type mSpriteRendererEditorType;
private Editor mSpriteRendererEditor;
private int mSortingLayerIndex;
private void OnEnable() { mMaterialsProperty = serializedObject.FindProperty("m_Materials"); var assembly = Assembly.GetAssembly(typeof(Editor)); mSpriteRendererEditorType = assembly.GetType("UnityEditor.SpriteRendererEditor", true); mSpriteRendererEditor = Editor.CreateEditor(target, mSpriteRendererEditorType); }
public override void OnInspectorGUI() { if(mSpriteRendererEditor != null) { mSpriteRendererEditor.OnInspectorGUI(); } serializedObject.Update(); if(mMaterialsProperty != null) { for(int i = 0; i < mMaterialsProperty.arraySize; i++) { var material = mMaterialsProperty.GetArrayElementAtIndex(i); if(material != null && material.objectReferenceValue != null) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField($"Materials[{i}]", GUILayout.Width(170f)); EditorGUILayout.BeginVertical(); var mat = (Material)material.objectReferenceValue; EditorGUILayout.ObjectField(mat, EditorType.MATERIAL_TYPE, false); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("RenderQueue", GUILayout.Width(80f)); EditorGUILayout.IntField(mat.renderQueue); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); } } } serializedObject.ApplyModifiedProperties(); } }
|

可以看到通过扩展我把SpriteRenderer材质相关的RenderQueue值显示出来了。
SkinnedMeshRenderer Inspector扩展
SKinnedMeshRendererInspector.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 98 99 100 101 102 103
| using System; using System.Linq; using UnityEditor; using UnityEngine;
[CustomEditor(typeof(SkinnedMeshRenderer))] public class SkinnedMeshRendererInspector : Editor { private SerializedProperty mSortingOrderProperty;
private SerializedProperty mMaterialsProperty;
private SkinnedMeshRenderer mTarget;
private string[] mSortingLayersNameArray;
private int mSortingLayerIndex;
private void OnEnable() { mTarget = (SkinnedMeshRenderer)target; mSortingLayersNameArray = SortingLayer.layers.Select(x => x.name).ToArray(); mSortingOrderProperty = serializedObject.FindProperty("m_SortingOrder"); mMaterialsProperty = serializedObject.FindProperty("m_Materials"); UpdateSortingLayerIndex(); }
public override void OnInspectorGUI() { base.OnInspectorGUI(); serializedObject.Update(); EditorGUI.BeginChangeCheck(); var selectedIndex = EditorGUILayout.Popup("SortingLayerName", mSortingLayerIndex, mSortingLayersNameArray); if(EditorGUI.EndChangeCheck()) { mTarget.sortingLayerName = SortingLayer.layers[selectedIndex].name; UpdateSortingLayerIndex(); } if(mSortingOrderProperty != null) { EditorGUILayout.PropertyField(mSortingOrderProperty); } if(mMaterialsProperty != null) { for(int i = 0; i < mMaterialsProperty.arraySize; i++) { var material = mMaterialsProperty.GetArrayElementAtIndex(i); if(material != null && material.objectReferenceValue != null) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField($"Materials[{i}]", GUILayout.Width(170f)); EditorGUILayout.BeginVertical(); var mat = (Material)material.objectReferenceValue; EditorGUILayout.ObjectField(mat, EditorType.MATERIAL_TYPE, false); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("RenderQueue", GUILayout.Width(80f)); EditorGUILayout.IntField(mat.renderQueue); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); } } } serializedObject.ApplyModifiedProperties(); }
private void UpdateSortingLayerIndex() { mSortingLayerIndex = Array.FindIndex(mSortingLayersNameArray, FindSortingLayerNameIndex); }
private bool FindSortingLayerNameIndex(string sortingName) { return mTarget.sortingLayerName == sortingName; } }
|

可以看到通过扩展我把SkinnedMeshRenderer的SortingLayerName和Sorting Order以及材质设置的RenderQueue值都显示出来了。
Camera Depth
摄像机深度在所有影响渲染排序的因素里排首位
游戏开发过程中我们的UI往往在3D场景之上,为了做不同的渲染合批,我们一般会设置2个摄像机,一个作为3D主摄像机,一个作为UI摄像机。


可以看到为了使UI摄像机渲染结果再3D主摄像机之上,我们把UI摄像机的Depth设置值>3D主摄像机

可以看到UI摄像机照射的结果显示在了3D主摄像机之上,所以可以得出Camera.Depth值越大越晚渲染。
(ZWrite & ZTest) VS RenderQueue
前面提到过ZWrite&ZTest和RenderQueue都是影响渲染顺序的关键。那么他们之间优先级又是怎样得了?
这里直接说结论:
如果开启了ZTest和ZWrite,可不管物件绘制順序 (rendering order),使得里摄像机越近的物件,永远都绘制在其他离摄像机越远的物件之前 (ZTest LEqual)。
首先我们创建一个ZWrite On和ZTest LEqual的Shader:
ZWriteZTestOnSurfaceShader.shader
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Shader "Custom/ZWriteZTestOnSurfaceShader" { ****** SubShader { Tags { "RenderType"="Opaque" } LOD 200 ZWrite On ZTest LEqual ****** } }
|
从上面可以看到我们指定了ZWrite On和ZTest LEqual表示开始深度写入,且深度比较标准是距离越近或相等的情况通过深度测试。同时我们将默认的RenderQueue设置成Opaque,用于不透明物体的渲染。




从上面可以看到我们创建了一个ZWrite On ZTest LEqual且RenderQueue分别为2010(红色)和2000(蓝色)的材质球,分别用于两个Cube渲染显示。
可以看到即使红色小球的RenderQueue值更高,但因为开启了ZWrite On和ZTest LEqual,红色Cube离摄像机更远依然渲染在蓝色Cube背后。
接下来让我们看一下两个Cube距离摄像机位置一致时,RenderQueue不一样,两个Cube是怎么显示的?



从上面可以看到,距离摄像机相同距离的前提下,红色Cube拥有更高的RenderQueue(2010)显示在了蓝色Cube RenderQueue(2000)之上。
结论:
- 开启ZWrite和ZTest的情况下,深度优先于RenderQueue,深度一样则比较RenderQueue
Sorting Layer VS RenderQueue
那么Sorting Layer和RenderQueue在渲染顺序里谁的优先级更高了?
不透明物体 > 透明物体
同为不透明物体或透明物体时,Sorting Layer > RenderQueue
在测试之前让我们首先添加几个3D Sorting Layer:

接下来让我们实战测试:
红色方块设置:

蓝色方块设置:

绿色方块设置:

Game渲染结果:

从上面的测试可以看到:
- 同时开启ZWrite和ZTest,当物体都是不透明物体时Sorting Layer设置优先于RenderQueue
- 同时开启ZWrite和ZTest,一个为不透明物体一个为透明物体时,透明物体在不透明物体之后渲染,此时RenderQueue的影响优先与Sorting Layer
Note:
- RenderQueue 2500是不透明物体(<=2500)和透明物体(>2500)的分界线
Sorting Order VS RenderQueue
那么Sorting Order和RenderQueue在渲染顺序里谁的优先级更高了?
在前面我们了解到RenderQueue会影响物体处于透明或不透明渲染队列里,所以
不透明物体 > 透明物体
同为不透明物体或透明物体时,Sorting Order> RenderQueue
这里我就不一一截图单个物体材质设置了,通过材质球名字就能看出我的测试单元设置:

ZOnSortingOrder10GreenMaterial2000 VS ZOnSortingOder5RedMaterial2005 VS ZOnSortingOrder1BlueMaterial2009

从上面测试结果可以看看出:
- 同为不透明物体状态下,Sorting Order > RenderQueue,而不是Sorting Order+RenderQueue的值决定的
ZOnSortingOrder6BlueMaterial2100 VS ZOnSortingOrder7GreenMaterial2000

从上面测试结果可以看看出:
- 不透明物体 > 透明物体依然适用
学习总结
渲染顺序优先级:
大的优先级如下:
Camera.depth > Material type((RenderQueue不透明 > RenderQueue透明) ) > SortingLayer> SortingOrder > RenderQueue
其他规则:
如果开启了ZTest和ZWrite,可不管物件绘制順序 (rendering order),使得里摄像机越近的物件,永远都绘制在其他离摄像机越远的物件之前 (ZTest LEqual)。
同为不透明物体或透明物体时,Sorting Layer > RenderQueue
Github
Reference
Unity rendering order 整理筆記
Unity 渲染顺序详解
Unity渲染顺序(Queue,ZWrite,ZTest)
[UnityShader]渲染队列、ZWrite和ZTest
[Shader筆記]透明物體(Z-Test、Z-Write、Z-Buffer、Alpha Blending)