NGUI UIPanel绘制原理学习

本文深入探讨了NGUI底层绘制机制,包括UIDrawCall的使用、材质球动态实例化、UV偏移与缩放,以及如何通过shader实现UIPanel内部物体的软边裁剪。详细解析了Panel的面片结构与遮罩效果背后的shader秘密。

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

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 };
        }
    }
void UpdateMaterials()

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里得到屏幕位置再进行计算,达到遮罩效果。时间有限就不继续深究了。总之大概来龙去脉就是这样。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值