NGUI底层绘制都是调用UIDrawCall来完成的,它会动态实例化出材质球,改变UV偏移和缩放(图集显示)。并且全部是面片
UIPanel也是面片,但是内部物体遮罩比较特殊,经过查找发现,影响UIPanel内部物体遮罩的是它的shader。
=====================================
查找过程:
1.首先是在UIPanel中找到mClipRange,然后在Fill中找到似乎和UIDrawCall有关。
void Fill (Material mat) { // Cleanup deleted widgets for (int i = mWidgets.size; i > 0; ) if (mWidgets[--i] == null) mWidgets.RemoveAt(i); // Fill the buffers for the specified material for (int i = 0, imax = mWidgets.size; i < imax; ++i) { UIWidget w = mWidgets.buffer[i]; if (w.visibleFlag == 1 && w.material == mat) { UINode node = GetNode(w.cachedTransform); if (node != null) { if (generateNormals) w.WriteToBuffers(mVerts, mUvs, mCols, mNorms, mTans); else w.WriteToBuffers(mVerts, mUvs, mCols, null, null); } else { Debug.LogError("No transform found for " + NGUITools.GetHierarchy(w.gameObject), this); } } } if (mVerts.size > 0) { // Rebuild the draw call's mesh UIDrawCall dc = GetDrawCall(mat, true); dc.depthPass = depthPass; dc.Set(mVerts, generateNormals ? mNorms : null, generateNormals ? mTans : null, mUvs, mCols); } else { // There is nothing to draw for this material -- eliminate the draw call UIDrawCall dc = GetDrawCall(mat, false); if (dc != null) { mDrawCalls.Remove(dc); NGUITools.DestroyImmediate(dc.gameObject); } } // Cleanup mVerts.Clear(); mNorms.Clear(); mTans.Clear(); mUvs.Clear(); mCols.Clear(); }
2.发现UIDrawCall是其对材质球创建控制的底层。不过没做成单例的形式,而是组合进来,缺点是UI组件的粒度比较大。
3.UpdateMaterials方法里是其对Panel软硬边裁剪的实现。
void UpdateMaterials() { bool useClipping = (mClipping != Clipping.None); // If clipping should be used, create the clipped material if (useClipping) { Shader shader = null; if (mClipping != Clipping.None) { const string alpha = " (AlphaClip)"; const string soft = " (SoftClip)"; // Figure out the normal shader's name string shaderName = mSharedMat.shader.name; shaderName = shaderName.Replace(alpha, ""); shaderName = shaderName.Replace(soft, ""); // Try to find the new shader if (mClipping == Clipping.HardClip || mClipping == Clipping.AlphaClip) shader = Shader.Find(shaderName + alpha); else if (mClipping == Clipping.SoftClip) shader = Shader.Find(shaderName + soft); // If there is a valid shader, assign it to the custom material if (shader == null) mClipping = Clipping.None; } // If we found the shader, create a new material if (shader != null) { if (mClippedMat == null) { mClippedMat = mSharedMat; mClippedMat.hideFlags = HideFlags.DontSave; } mClippedMat.shader = shader; mClippedMat.mainTexture = mSharedMat.mainTexture; } else if (mClippedMat != null) { NGUITools.Destroy(mClippedMat); mClippedMat = null; } } else if (mClippedMat != null) { NGUITools.Destroy(mClippedMat); mClippedMat = null; } // If depth pass should be used, create the depth material if (mDepthPass) { if (mDepthMat == null) { Shader shader = Shader.Find("Unlit/Depth Cutout"); mDepthMat = new Material(shader); mDepthMat.hideFlags = HideFlags.DontSave; } mDepthMat.mainTexture = mSharedMat.mainTexture; } else if (mDepthMat != null) { NGUITools.Destroy(mDepthMat); mDepthMat = null; } // Determine which material should be used Material mat = (mClippedMat != null) ? mClippedMat : mSharedMat; if (mDepthMat != null) { // If we're already using this material, do nothing if (mRen.sharedMaterials != null && mRen.sharedMaterials.Length == 2 && mRen.sharedMaterials[1] == mat) return; // Set the double material mRen.sharedMaterials = new Material[] { mDepthMat, mat }; } else if (mRen.sharedMaterial != mat) { mRen.sharedMaterials = new Material[] { mat }; } }
4.OnWillRenderObject()方法里对材质球的调用十分可疑。
mClippedMat.mainTextureOffset = new Vector2(-mClipRange.x / mClipRange.z, -mClipRange.y / mClipRange.w); mClippedMat.mainTextureScale = new Vector2(1f / mClipRange.z, 1f / mClipRange.w);
5.为了验证想法,把动态实例化的材质球改掉,手动调节UV。在UpdateMaterials ()中
//mClippedMat = new Material(mSharedMat); mClippedMat = mSharedMat;
6.
7.但是UI Panel光是面片还不够,这并不能解释其中的每个物体都能被裁剪的问题。
8.检查了下,不太可能是代码问题。似乎是shader做了手脚。在某个软边裁剪的shader,像素着色器下找到如下内容
half4 frag (v2f IN) : COLOR { // Softness factor float2 factor = (float2(1.0, 1.0) - abs(IN.worldPos)) * _ClipSharpness; // Sample the texture half4 col = tex2D(_MainTex, IN.texcoord) * IN.color; col.a *= clamp( min(factor.x, factor.y), 0.0, 1.0); return col; }
调试一下,输出值改为tex2D
return tex2D(_MainTex, IN.texcoord) * IN.color;
发现不再显示遮罩效果,但调整offset偏移值也无效。
9.继续刨根问底,发现顶点着色器的UV变换没加TRANSFORM_TEX,百度了下似乎不加外部偏移等参数就无效。加上之后,可以进行偏移等操作。
v2f vert (appdata_t v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.color = v.color; //!!!! o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); o.worldPos = TRANSFORM_TEX(v.vertex.xy, _MainTex); return o; }
确实是公用UV,不再有遮罩效果
可能是shader里得到屏幕位置再进行计算,达到遮罩效果。时间有限就不继续深究了。总之大概来龙去脉就是这样。