说来其实也不算什么疑难杂症,原因后面再叙,只是最近遇到个代码问题确实令我犯了好一阵疑难,在此简单一记,算作总结吧~
起因颇为简单,平时工作时无意瞟了一眼NGUI的UILabel实现,其中有段实现字体阴影的代码,原理来说其实挺清晰的,就是将原先用以渲染文本的顶点数据原样拷贝一份,然后做些位置偏移和颜色改变即可,代码大概是这副样子~
// generate original vertex data
NGUIText.Print(text, verts, uvs, cols);
// apply an effect if one was requested
if (effectStyle != Effect.None)
{
// apply shadow vertex data
ApplyShadow(verts, uvs, cols, offset, end, pos.x, -pos.y);
}
浮光掠影的看了一下,突然想到一个问题:既然ApplyShadow整体复制了一份文本顶点数据,那么最终生成的顶点数据大概就是:
v1, v2, v3, ... , c1, c2, c3, ...
(其中v1, v2, v3, ... 为原始顶点数据、c1, c2, c3, ... 为复制后的顶点数据)
那么问题来了,既然这些顶点是在同一个drawcall中进行绘制的,那么为何c1, c2, c3, ...的绘制结果总是在v1, v2, v3, ... 之后呢?如果不能保证这个绘制顺序,那么自然也不能正确的实现文本阴影的渲染~
依着这个疑问,首先瞅了下相关的NGUI Shader实现,大概来看还是相对简单的,代码大抵是这样:
// shader header
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
}
Cull Off
Lighting Off
ZWrite Off
Offset -1, -1
Fog { Mode Off }
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
half4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : POSITION;
half4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
sampler2D _MainTex;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.texcoord = v.texcoord;
o.color = v.color;
return o;
}
half4 frag (v2f i) : COLOR
{
half4 col = i.color;
col.a *= tex2D(_MainTex, i.texcoord).a;
return col;
}
ENDCG
}
}
// other SubShaders
简单上张图~
值得注意的就是Shader关闭了ZWrite,这将导致所有的顶点都会被渲染至framebuffer,如果我们尝试打开下ZWrite,Z-fighting便如期而至了~
但是目前虽然确定了所有顶点都会被渲染,但是各个顶点间的渲染顺序还不能确定,普通渲染一般都会打开ZWrite,那么这时的渲染顺序其实并不重要,因为最终的绘制顺序会由ZBuffer来保证,可惜这里的ZWrite被关闭了~
继续Google了一阵,觉得还是请教下熟悉这块的同事效率更高,于是问了下引擎组的同事,几次询问讨论下来,确定了渲染顺序的问题:
即顶点的渲染顺序与其提交至GPU的顺序是一致的(至少“看上去”是的,GPU可以在保证渲染结果正确性的基础上进行优化),所以如果那上面的例子来说的话:
v1, v2, v3, ... , 会首先绘制,然后绘制c1, c2, c3, ...
WTF,如果是这样的话,文本的阴影就会一直显示在文本之上!但是同事的一个简单示例又确实说明了这种渲染顺序的正确性,没办法,事实证明还有猫腻的地方,得再细查一下~
重新回头深入看了一下之前那个想当然的ApplyShadow,终于发现了蹊跷:
/// <summary>
/// Apply a shadow effect to the buffer.
/// </summary>
public void ApplyShadow (BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols, int start, int end, float x, float y)
{
// implementation details ...
for (int i = start; i < end; ++i)
{
// add copy buffer data
verts.Add(verts.buffer[i]);
uvs.Add(uvs.buffer[i]);
cols.Add(cols.buffer[i]);
// update verts.buffer[i]
Vector3 v = verts.buffer[i];
v.x += x;
v.y += y;
verts.buffer[i] = v;
// implementation details ...
// update cols.buffer[i]
}
}
原来该方法是将阴影顶点数据加到了原顶点数据之前!还是按照之前的示例来说,正确的顶点数据应该是:
c1, c2, c3, ..., v1, v2, v3, ...
(其中v1, v2, v3, ... 为原始顶点数据、c1, c2, c3, ... 为复制后的顶点数据)
看到此,终算是全身通畅了,SO上也有相关的解答(好像还提到了例外的情况,不是十分熟悉),Spec也大概了浏览了一下,不过暂时没有定位到十分相关的说明,有 了解的童鞋可以告之一下~
就这样吧~