我在使用GPU实例化的时候,发现在安卓7.0及以下搭载高通处理器的手机上存在一个难以理解的问题。这似乎与内存布局有关,但仅在上述手机类型上发生。让我们逐步重现此问题。首先,有一个用于 GPU Instancing 的着色器
Shader "Unlit/UniformTest"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#pragma multi_compile _ NO_SPRITE_RENDERER
#include "UnityCG.cginc"
#ifdef UNITY_INSTANCING_ENABLED
UNITY_INSTANCING_BUFFER_START(CustomDataPerDraw)
UNITY_DEFINE_INSTANCED_PROP(float4, _RenderDataArray)
UNITY_DEFINE_INSTANCED_PROP(float4,_MainTexOffsetArray)
UNITY_INSTANCING_BUFFER_END(CustomDataPerDraw)
#define _RenderData UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _RenderDataArray)
#define _MainTexOffset UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _MainTexOffsetArray)
#endif
#ifndef UNITY_INSTANCING_ENABLED
float4 _RenderData;
float4 _MainTexOffset;
#endif
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
sampler2D _MainTex;
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
#ifdef UNITY_INSTANCING_ENABLED
float4 renderData = _RenderData;
v.vertex.xy += renderData.zw;
o.uv = v.uv * _MainTexOffset.xy + _MainTexOffset.zw;
#else
o.uv = v.uv;
#endif
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
fixed4 col = tex2D(_MainTex, i.uv);
#ifdef UNITY_INSTANCING_ENABLED
float4 renderData = _RenderData;
col.a *= renderData.x;
#endif
return col;
}
ENDCG
}
}
}
还有一个程序可以使用这个着色器一次绘制 5 个四边形网格:
public class DrawTest : MonoBehaviour
{
public Shader shader;
public Texture2D tex;
private Mesh m_mesh;
private Material m_mat;
private MaterialPropertyBlock m_block;
private Matrix4x4[] m_renderArr;
void Start()
{
m_mat = new Material(shader);
m_mat.enableInstancing = true;
m_mat.SetTexture("_MainTex", tex);
m_block = new MaterialPropertyBlock();
m_block.SetVectorArray("_RenderDataArray",
new Vector4[] {
new Vector4(0.5f, 0f, 0f, 0f),
new Vector4(0.3f, 0f, 0f, 0f),
new Vector4(0.6f, 0f, 0f, 0f),
new Vector4(0.8f, 0f, 0f, 0f),
new Vector4(1f, 0f, 0f, 0f)
});
m_block.SetVectorArray("_MainTexOffsetArray",
new Vector4[] {
new Vector4(1f, 1f, 0.2f, 0f),
new Vector4(1f, 1f, 0.4f, 0f),
new Vector4(1f, 1f, 0.6f, 0f),
new Vector4(1f, 1f, 0.8f, 0f),
new Vector4(1f, 1f, 1f, 0f)
});
m_renderArr = new Matrix4x4[5];
m_renderArr[0] = Matrix4x4.Translate(new Vector3(1.5f, 2.5f, -2f));
m_renderArr[1] = Matrix4x4.Translate(new Vector3(-1.5f, 2.5f, -2f));
m_renderArr[2] = Matrix4x4.Translate(new Vector3(1.5f, -1f, -2f));
m_renderArr[3] = Matrix4x4.Translate(new Vector3(-1.5f, -1f, -2f));
m_renderArr[4] = Matrix4x4.Translate(new Vector3(0f, 0.75f, -2f));
var quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
m_mesh = quad.GetComponent<MeshFilter>().sharedMesh;
Destroy(quad);
}
void Update()
{
Graphics.DrawMeshInstanced(m_mesh, 0, m_mat, m_renderArr, m_renderArr.Length, m_block);
}
private float delta;
private float sliderValue = 0.5f;
private void OnGUI()
{
GUILayout.Space(300f);
var val = GUILayout.HorizontalSlider(sliderValue, 0f, 1f, GUILayout.Width(200f), GUILayout.Height(50f));
delta = val - sliderValue;
if (!Mathf.Approximately(0f, delta))
{
Transform camTrans = Camera.main.transform;
camTrans.position += camTrans.forward * delta * 10f;
delta = 0f;
}
sliderValue = val;
}
private void OnDestroy()
{
Destroy(m_mat);
}
}
如果我们更改着色器代码,将 _MainTexOffsetArray 移动到另一个统一缓冲区:
#ifdef UNITY_INSTANCING_ENABLED
UNITY_INSTANCING_BUFFER_START(CustomDataPerDraw)
UNITY_DEFINE_INSTANCED_PROP(float4, _RenderDataArray)
UNITY_INSTANCING_BUFFER_END(CustomDataPerDraw)
UNITY_INSTANCING_BUFFER_START(AnotherDataPerDraw)
UNITY_DEFINE_INSTANCED_PROP(float4,_MainTexOffsetArray)
UNITY_INSTANCING_BUFFER_END(AnotherDataPerDraw)
#define _RenderData UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _RenderDataArray)
#define _MainTexOffset UNITY_ACCESS_INSTANCED_PROP(AnotherDataPerDraw, _MainTexOffsetArray)
#endif
如果我们恢复着色器代码并在 _RenderDataArray 和 _MainTexOffsetArray 之间添加另一个属性:
#ifdef UNITY_INSTANCING_ENABLED
UNITY_INSTANCING_BUFFER_START(CustomDataPerDraw)
UNITY_DEFINE_INSTANCED_PROP(float4, _RenderDataArray)
UNITY_DEFINE_INSTANCED_PROP(float2,_FlipArray)
UNITY_DEFINE_INSTANCED_PROP(float4,_MainTexOffsetArray)
UNITY_INSTANCING_BUFFER_END(CustomDataPerDraw)
#define _RenderData UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _RenderDataArray)
#define _MainTexOffset UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _MainTexOffsetArray)
#define _Flip UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw,_FlipArray)
#endif
如果我们让fragment shader不直接使用uniform,而是使用顶点插值,那么一切都OK。我这里就不展示普通图片了。
Shader "Unlit/UniformTest"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#pragma multi_compile _ NO_SPRITE_RENDERER
#include "UnityCG.cginc"
#ifdef UNITY_INSTANCING_ENABLED
UNITY_INSTANCING_BUFFER_START(CustomDataPerDraw)
UNITY_DEFINE_INSTANCED_PROP(float4, _RenderDataArray)
UNITY_DEFINE_INSTANCED_PROP(float2,_FlipArray)
UNITY_DEFINE_INSTANCED_PROP(float4,_MainTexOffsetArray)
UNITY_INSTANCING_BUFFER_END(CustomDataPerDraw)
#define _RenderData UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _RenderDataArray)
#define _MainTexOffset UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _MainTexOffsetArray)
#define _Flip UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw,_FlipArray)
#endif
#ifndef UNITY_INSTANCING_ENABLED
float4 _RenderData;
float4 _MainTexOffset;
#endif
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float2 uv : TEXCOORD0;
// Fragment shader does not directly use uniform variable, but uses vertex interpolation
float alpha : SD_ALPHA;
float4 vertex : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
sampler2D _MainTex;
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
#ifdef UNITY_INSTANCING_ENABLED
float4 renderData = _RenderData;
v.vertex.xy += renderData.zw;
o.uv = v.uv * _MainTexOffset.xy + _MainTexOffset.zw;
o.vertex = UnityObjectToClipPos(v.vertex);
// Fragment shader does not directly use uniform variable, but uses vertex interpolation
o.alpha = renderData.x;
#else
o.uv = v.uv;
o.alpha = 1.0;
#endif
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
fixed4 col = tex2D(_MainTex, i.uv);
#ifdef UNITY_INSTANCING_ENABLED
// Fragment shader does not directly use uniform variable, but uses vertex interpolation
col.a *= i.alpha;
#endif
return col;
}
ENDCG
}
}
}
同样,在我们的多次测试中,只有搭载高通处理器的安卓7.0及以下版本的手机才会出现这个问题;使用RenderDoc查看更多细节,统一数据没有传入片段Shader。我不确定是什么原因。是因为同一着色器程序中的顶点着色器和片段着色器在Android 7.0中不能同时使用统一数据还是这是一个错误?