NGUI源码解析之 UIDrawCall(一)

文章说明

在阅读 NGUI 源码的时候,希望大家已经非常熟悉这套 UI 了,知道基本组件怎么使用。对于一个陌生的东西,我建议大家不需要先 “求解”,我们可以先学会 “使用”,只有熟练使用了,我们就可以带着问题去看源码,那样,更方便我们理解。
所以如果你阅读过程中有些看不懂,建议看看我只之前关于 untiy 渲染坐标系 ,相关的文章先了解一下 mesh 。

如果文章中有写的不对的地方,欢迎大家指出,谢谢。

源码分析

代码名:“UIDrawCall”,文章中不会将所有源代码粘贴出来,也不会每一句去讲解,只会讲一些重点的内容,其余内容读者可以自己去阅读。

Drawcall 创建

在 “UIDrawCall” 中存储着两个静态变量:

  • mActiveList:表示正在使用中的列表,全局所有的正在使用中的 UIDrawCall 都被保存在这个列表中;
  • mInactiveList:表示未使用的回收的列表,全局所有回收到的 UIDrawCall 都被保存到这个列表中;
	//这里缓存的是全局中被使用的 drawcall
	static BetterList<UIDrawCall> mActiveList = new BetterList<UIDrawCall>();  
	//存储未使用的 drawcall 列表
	static BetterList<UIDrawCall> mInactiveList = new BetterList<UIDrawCall>();

创建一个 UIDrawCall :

  • 先去 mInactiveList 中寻找是否有未使用的,如果有就获取它,将其从 mInactiveList 列表中移除,并放入到 mActiveList 列表中。
  • 如果 mInactiveList 没有多余的可使用的,就去创建一个新的 UIDrawCall ,放入到 mActiveList 列表中。

源码:

	static UIDrawCall Create (string name)
	{
#if SHOW_HIDDEN_OBJECTS && UNITY_EDITOR
		name = (name != null) ? "_UIDrawCall [" + name + "]" : "DrawCall";
#endif
		//检查未使用列表中是否有多余的可使用的 dc
		while (mInactiveList.size > 0)
		{
			UIDrawCall dc = mInactiveList.Pop();

			if (dc != null)
			{
				mActiveList.Add(dc);
				if (name != null) dc.name = name;
				NGUITools.SetActive(dc.gameObject, true);
				return dc;
			}
		}

		//没有就先创建一个对象,然后给这个对象添加上 UIDrawCall 组件,并将这个组件添加到使用列表中
#if UNITY_EDITOR
		// If we're in the editor, create the game object with hide flags set right away
		GameObject go = UnityEditor.EditorUtility.CreateGameObjectWithHideFlags(name,
 #if SHOW_HIDDEN_OBJECTS
		HideFlags.DontSave | HideFlags.NotEditable, typeof(UIDrawCall));
 #else
		HideFlags.HideAndDontSave, typeof(UIDrawCall));
 #endif
		UIDrawCall newDC = go.GetComponent<UIDrawCall>();
#else
		GameObject go = new GameObject(name);
		DontDestroyOnLoad(go);
		UIDrawCall newDC = go.AddComponent<UIDrawCall>();
#endif
		// Create the draw call
		mActiveList.Add(newDC);
		return newDC;
	}

Drawcall 更新调用

1.首先介绍一些缓存信息的变量:

  • public List verts = new List(); 顶点
  • public List norms = new List(); 法线
  • public List tans = new List(); 切线
  • public List uvs = new List(); uv
  • public List uv2 = new List(); uv2
  • public List cols = new List(); 颜色
  • mMaterial:被本次 drawcall 调用的 材质,就是我们外部设置的材质,在 baseMaterial 中设置,当创建一个 UIDrawCall 的时候外部会传入一个材质信息,通过 baseMaterial 将这个材质信息保存到这个变量中。
  • mDynamicMat:动态材质,说明这个材质一直在变化
  • mMesh:mesh
  • mFilter: Mesh Filter 组件
  • mRenderer: Mesh Renderer 组件
	//设置材质
	public Material baseMaterial
	{
		get
		{
			return mMaterial;
		}
		set
		{
			if (mMaterial != value)
			{
				mMaterial = value;
				mRebuildMat = true;
			}
		}
	}

2.主要函数:

UpdateGeometry() :UIDrawCall 的核心方法,通过顶点,uv,颜色,贴图等,设置需要绘制的几何信息,主要通过需要当前更新的 UIDrawCall 对象调用这个函数进行数据更新,将一些变化的几何信息重新设置到对应的 MeshFilter, MeshRenderer 和 材质(Material)中保存起来。这里的设置是全部重新设置,也就是说,只要在我们的使用中有涉及到几何信息变化的操作,都会导致这个几何信息被重新计算设置。

源码:

	public void UpdateGeometry (int widgetCount)
	{
		//表示需要调用的 widget 的数量
		this.widgetCount = widgetCount; 
		int vertexCount = verts.Count; //顶点数量

		// Safety check to ensure we get valid values
		// vertexCount % 4 == 0 说明顶点数量一定是4的倍数
		if (vertexCount > 0 && (vertexCount == uvs.Count && vertexCount == cols.Count) && (vertexCount % 4) == 0)
		{
			if (mColorSpace == ColorSpace.Uninitialized)
				mColorSpace = QualitySettings.activeColorSpace;

			if (mColorSpace == ColorSpace.Linear)
			{
				for (int i = 0; i < vertexCount; ++i)
				{
					var c = cols[i];
					c.r = Mathf.GammaToLinearSpace(c.r);
					c.g = Mathf.GammaToLinearSpace(c.g);
					c.b = Mathf.GammaToLinearSpace(c.b);
					c.a = Mathf.GammaToLinearSpace(c.a);
					cols[i] = c;
				}
			}

			// Cache all components
			//没有就创建一个 meshFilter
			if (mFilter == null) mFilter = gameObject.GetComponent<MeshFilter>();
			if (mFilter == null) mFilter = gameObject.AddComponent<MeshFilter>();

			//顶点个数上限 65000
			if (vertexCount < 65000)
			{
				// Populate the index buffer
				//三角形数组的长度 = (顶点个数 / 2) * 3  ======》 (顶点个数/2) 计算三角形个数,
				int indexCount = (vertexCount >> 1) * 3;
				bool setIndices = (mIndices == null || mIndices.Length != indexCount);

				// Create the mesh
				//如果没有 mesh 创建一个
				if (mMesh == null)
				{
					mMesh = new Mesh();
					mMesh.hideFlags = HideFlags.DontSave;
					mMesh.name = (mMaterial != null) ? "[NGUI] " + mMaterial.name : "[NGUI] Mesh";
					if (dx9BugWorkaround == 0) mMesh.MarkDynamic();
					setIndices = true;
				}
#if !UNITY_FLASH
				// If the buffer length doesn't match, we need to trim all buffers
				//如果缓冲区长度匹配不正确,我们需要修剪所有缓冲区
				bool trim = uvs.Count != vertexCount || cols.Count != vertexCount || uv2.Count != vertexCount || norms.Count != vertexCount || tans.Count != vertexCount;

				// Non-automatic render queues rely on Z position, so it's a good idea to trim everything
				if (!trim && panel != null && panel.renderQueue != UIPanel.RenderQueue.Automatic)
					trim = (mMesh == null || mMesh.vertexCount != verts.Count);

				// NOTE: Apparently there is a bug with Adreno devices:
				// http://www.tasharen.com/forum/index.php?topic=8415.0
 #if !UNITY_ANDROID
				// If the number of vertices in the buffer is less than half of the full buffer, trim it
				if (!trim && (vertexCount << 1) < verts.Count) trim = true;
 #endif
#endif
				//计算三角形数量
				mTriangles = (vertexCount >> 1);

				if (mMesh.vertexCount != vertexCount)
				{
					mMesh.Clear();
					setIndices = true;
				}
#if UNITY_4_7
				var hasUV2 = (uv2 != null && uv2.Count == vertexCount);
				var hasNormals = (norms != null && norms.Count == vertexCount);
				var hasTans = (tans != null && tans.Count == vertexCount);

				if (mTempVerts == null || mTempVerts.Length < vertexCount) mTempVerts = new Vector3[vertexCount];
				if (mTempUV0 == null || mTempUV0.Length < vertexCount) mTempUV0 = new Vector2[vertexCount];
				if (mTempCols == null || mTempCols.Length < vertexCount) mTempCols = new Color[vertexCount];

				if (hasUV2 && (mTempUV2 == null || mTempUV2.Length < vertexCount)) mTempUV2 = new Vector2[vertexCount];
				if (hasNormals && (mTempNormals == null || mTempNormals.Length < vertexCount)) mTempNormals = new Vector3[vertexCount];
				if (hasTans && (mTempTans == null || mTempTans.Length < vertexCount)) mTempTans = new Vector4[vertexCount];

				verts.CopyTo(mTempVerts);
				uvs.CopyTo(mTempUV0);
				cols.CopyTo(mTempCols);

				if (hasNormals) norms.CopyTo(mTempNormals);
				if (hasTans) tans.CopyTo(mTempTans);
				if (hasUV2) for (int i = 0, imax = verts.Count; i < imax; ++i) mTempUV2[i] = uv2[i];

				mMesh.vertices = mTempVerts;
				mMesh.uv = mTempUV0;
				mMesh.colors = mTempCols;
				mMesh.uv2 = hasUV2 ? mTempUV2 : null;
				mMesh.normals = hasNormals ? mTempNormals : null;
				mMesh.tangents = hasTans ? mTempTans : null;
#else
				//将顶点信息, UV信息,颜色信息赋值给网络对象
				mMesh.SetVertices(verts);
				mMesh.SetUVs(0, uvs);
				mMesh.SetColors(cols);

 #if UNITY_5_4 || UNITY_5_5_OR_NEWER
				mMesh.SetUVs(1, (uv2.Count == vertexCount) ? uv2 : null);
				//设置法线信息 给网络对象
				mMesh.SetNormals((norms.Count == vertexCount) ? norms : null);
				//设置切线信息 给网络对象
				mMesh.SetTangents((tans.Count == vertexCount) ? tans : null);
 #else
				if (uv2.Count != vertexCount) uv2.Clear();
				if (norms.Count != vertexCount) norms.Clear();
				if (tans.Count != vertexCount) tans.Clear();

				mMesh.SetUVs(1, uv2);
				mMesh.SetNormals(norms);
				mMesh.SetTangents(tans);
 #endif
#endif
				if (setIndices)
				{
					mIndices = GenerateCachedIndexBuffer(vertexCount, indexCount);
					mMesh.triangles = mIndices;
				}

#if !UNITY_FLASH
				if (trim || !alwaysOnScreen)
#endif
					mMesh.RecalculateBounds();
				//将网络对象 mMesh 赋值给 mfilter
				mFilter.mesh = mMesh;
			}
			else
			{
				mTriangles = 0;
				if (mMesh != null) mMesh.Clear();
				Debug.LogError("Too many vertices on one panel: " + vertexCount);
			}

			//获取 MeshRenderer 组件,如果没有就添加
			if (mRenderer == null) mRenderer = gameObject.GetComponent<MeshRenderer>();

			if (mRenderer == null)
			{
				mRenderer = gameObject.AddComponent<MeshRenderer>();
#if UNITY_EDITOR
				mRenderer.enabled = isActive;
#endif
#if !UNITY_4_7
				if (mShadowMode == ShadowMode.None)
				{
					mRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
					mRenderer.receiveShadows = false;
				}
				else if (mShadowMode == ShadowMode.Receive)
				{
					mRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
					mRenderer.receiveShadows = true;
				}
				else
				{
					mRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
					mRenderer.receiveShadows = true;
				}
#endif
			}

			if (mIsNew)
			{
				mIsNew = false;
				if (onCreateDrawCall != null) onCreateDrawCall(this, mFilter, mRenderer);
			}
			//更新材质 完成一次drawcall
			UpdateMaterials();
		}
		else
		{
			if (mFilter.mesh != null) mFilter.mesh.Clear();
			Debug.LogError("UIWidgets must fill the buffer with 4 vertices per quad. Found " + vertexCount);
		}
		//清除缓存信息,以下这些只是用于临时缓存
		verts.Clear();
		uvs.Clear();
		uv2.Clear();
		cols.Clear();
		norms.Clear();
		tans.Clear();
	}

CreateMaterial():创建一个新材质,主要设置新材质中一些重要的信息,比如 Shader。

这里需要补充一个新知识:

3.12.0 NGUI 默认的以 “Unlit/Transparent Colored” 开头的 shader 有如下六种:

  • 1.Unlit/Transparent Colored
  • 2.Unlit/Transparent Colored (Packed) (TextureClip)
  • 3.Hidden/Unlit/Transparent Colored (TextureClip)
  • 4.Hidden/Unlit/Transparent Colored 1
  • 5.Hidden/Unlit/Transparent Colored 2
  • 6.Hidden/Unlit/Transparent Colored 3

UIPanel 的裁剪主要是通过名字为 “Hidden/Unlit/Transparent Colored N” 的 shader 实现的,N 小于等于3,如果项目中这几种不能满足需求,可以按照这种方式自定义第 4 种或者更多的 shader。

在这里插入图片描述
源码:

	//创建一个新的材质,设置好我们需要调用的 shader  和材质信息
	void CreateMaterial ()
	{
		mTextureClip = false;
		mLegacyShader = false;
		mClipCount = panel.clipCount;  //设置 mClipCount 个数,裁剪的 panel 个数

		//给 shader 赋值一个默认名字
		/*	默认的以 “Unlit/Transparent Colored”开头的shader 有如下几种:
		 *  1.Unlit/Transparent Colored
		 *  2.Unlit/Transparent Colored (Packed) (TextureClip)
		 *  3.Hidden/Unlit/Transparent Colored (TextureClip)
		 *  4.Hidden/Unlit/Transparent Colored 1 
		 *  5.Hidden/Unlit/Transparent Colored 2
		 *  6.Hidden/Unlit/Transparent Colored 3
		 */
		string shaderName = (mShader != null) ? mShader.name :
			((mMaterial != null) ? mMaterial.shader.name : "Unlit/Transparent Colored");

		// Figure out the normal shader's name
		//找出正常着色器的名称, GUI/Text Shader 命名的需要文本替换
		shaderName = shaderName.Replace("GUI/Text Shader", "Unlit/Text");

		//删除 以“ N” 结尾的字符串,N表示数字(0-9)
		if (shaderName.Length > 2)
		{
			if (shaderName[shaderName.Length - 2] == ' ')
			{
				int index = shaderName[shaderName.Length - 1];
				if (index > '0' && index <= '9') shaderName = shaderName.Substring(0, shaderName.Length - 2);
			}
		}
		//删除 以“Hidden/”开头的字符串
		if (shaderName.StartsWith("Hidden/"))
			shaderName = shaderName.Substring(7);

		// Legacy functionality
		const string soft = " (SoftClip)";
		shaderName = shaderName.Replace(soft, "");

		//删除 “ (TextureClip)”
		const string textureClip = " (TextureClip)";
		shaderName = shaderName.Replace(textureClip, "");
		//如果是Mask裁剪,就直接寻找 Hidden/Unlit/Transparent Colored (TextureClip) 这样 或者  Hidden/Unlit/Text (TextureClip)这样的shader
		if (panel != null && panel.clipping == Clipping.TextureMask)
		{
			mTextureClip = true;
			shader = Shader.Find("Hidden/" + shaderName + textureClip);
		}
		//如果有一个或者多个裁剪panel 就设置成Soft Clip的形式
		else if (mClipCount != 0)
		{
			//寻找的Shader格式为  Hidden/Unlit/Transparent Colored 1 或者  Hidden/Unlit/Transparent Colored 2 这样的 ,上述看是3个
			//也就是说目前 NGUI 接受的 mClipCount 最大是3层嵌套
			shader = Shader.Find("Hidden/" + shaderName + " " + mClipCount);
			if (shader == null) shader = Shader.Find(shaderName + " " + mClipCount);

			// Legacy functionality 看备注这个应该是遗留功能
			if (shader == null && mClipCount == 1)
			{
				//一般情况下不会走到这里
				mLegacyShader = true;
				shader = Shader.Find(shaderName + soft);
			}
		}
		else shader = Shader.Find(shaderName);

		// Always fallback to the default shader
		// 如果找不到 就用默认的shader, 一般设置成这个shader,就是达不到你需要的效果了
		if (shader == null) shader = Shader.Find("Unlit/Transparent Colored");

		//判断是否有外部设置材质信息
		if (mMaterial != null)
		{
			mDynamicMat = new Material(mMaterial);
			mDynamicMat.name = "[NGUI] " + mMaterial.name;
			mDynamicMat.hideFlags = (HideFlags.DontSave | HideFlags.NotEditable);
			//这里会把之前的材质的所有属性全部复制到新的材质上
			mDynamicMat.CopyPropertiesFromMaterial(mMaterial);
#if !UNITY_FLASH
			string[] keywords = mMaterial.shaderKeywords;
			for (int i = 0; i < keywords.Length; ++i)
				mDynamicMat.EnableKeyword(keywords[i]);
#endif
			// If there is a valid shader, assign it to the custom material
			if (shader != null)
			{
				mDynamicMat.shader = shader;
			}
			else if (mClipCount != 0)
			{
				Debug.LogError(shaderName + " shader doesn't have a clipped shader version for " + mClipCount + " clip regions");
			}
		}
		else
		{
			mDynamicMat = new Material(shader);
			mDynamicMat.name = "[NGUI] " + shader.name;
			mDynamicMat.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
		}
	}

	//重建材质
	Material RebuildMaterial ()
	{
		// Destroy the old material
		//删除旧的材质
		NGUITools.DestroyImmediate(mDynamicMat);

		// Create a new material
		//创建一个新的材质
		CreateMaterial();
		mDynamicMat.renderQueue = mRenderQueue;

		// Update the renderer
		if (mRenderer != null)
		{
			//需要将材质信息存储在 meshrender 组件 sharedMaterials 中
			mRenderer.sharedMaterials = new Material[] { mDynamicMat };
			mRenderer.sortingLayerName = mSortingLayerName;
			mRenderer.sortingOrder = mSortingOrder;
		}
		return mDynamicMat;
	}

	//更新材质
	void UpdateMaterials ()
	{
		if (panel == null) return;

		// If clipping should be used, we need to find a replacement shader
		//如果没有材质或者使用了裁剪,就需要重建材质
		//判断是否需要重建材质信息
		if (mRebuildMat || mDynamicMat == null || mClipCount != panel.clipCount || mTextureClip != (panel.clipping == Clipping.TextureMask))
		{
			RebuildMaterial();
			mRebuildMat = false;
		}
	}

Drawcall 渲染调用

OnWillRenderObject() :这个函数是用于做一些渲染之前的一些设置的,一般用于设置材质的参数 还有 裁剪信息。
关于这一块我决定重新开一篇细讲,因为涉及到 Shader 中的信息,放在一篇文章中太多啦!

Drawcall 销毁

Destroy():移除一个 UIDrawCall

源码:

	static public void Destroy (UIDrawCall dc)
	{
		if (dc)
		{
			if (dc.onCreateDrawCall != null)
			{
				NGUITools.Destroy(dc.gameObject);
				return;
			}

			dc.onRender = null;

			if (Application.isPlaying)
			{
				if (mActiveList.Remove(dc))
				{
					NGUITools.SetActive(dc.gameObject, false);
					mInactiveList.Add(dc);
					dc.mIsNew = true;
				}
			}
			else
			{
				mActiveList.Remove(dc);
#if SHOW_HIDDEN_OBJECTS && UNITY_EDITOR
				if (UnityEditor.Selection.activeGameObject == dc.gameObject)
					UnityEditor.Selection.activeGameObject = null;
#endif
				NGUITools.DestroyImmediate(dc.gameObject);
			}
		}
	}

思考

谁创建的,什么时候创建?
谁调用的,什么时候调用?
谁移除的,什么时候移除?

任何时候我们都要带着这种不断思考的态度去学习,不着急,我们接下来的文章中,慢慢揭晓答案。

Drawcall 的意义

妈耶,到这里我感觉又要来一篇文章讲一下渲染流程了,好累,学的越多感觉越无知。

这里我简单介绍一下什么是 “drawcall”。CPU 通过调用 drawcall 来告诉 GPU 开始进行一个渲染过程。对于 CPU 来讲,drawcall 就是一个命令,通过这个命令让 GPU 进行渲染工作。那么 NGUI 的 UIDrawcall 是什么,可以理解为,UIDrawcall 是 NGUI 的一种上层封装,处理一些 drawcall 中需要的数据,在这里面 NGUI 可以做一些拓展功能。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值