Unity中如何向Shader传数组

项目开发笔记(十三)

最近使用Shader的时候需要给Shader传递参数,后面发现参数过多(30+个颜色值和10+个纹理),Shader在PC上运行得好好的,但是到某些移动设备上就尿崩,突然发现:

  1. 为什么shader无法往其传递数组呢?
  2. 为什么使用了多个纹理后(例如三星T560),在设备上无法运行正常?

Unity允许通过Material往Shader里传递int,Texture,Color, Vector4等参数,而且不同的移动设备可能支持不同的 Shader Model(SM 1 2 3 4 5),不同的SM版本会限制纹理指令和算术指令的数量使用。参考:High-Level_Shading_Languag。经过测试设备三星T560只能传入8个纹理指令,再增加就会工作不正常。我们不能改变设备显卡对SM的支持,只能通过减少纹理指令和算术指令的方式来兼容它。顺带把如何传入数组的问题也解决了。那怎么做呢?

0x00. 首先怎么往Shader传递参数?

举个例子 倘若你在Shader中定义了如下变量:

//-------------------------------------------------------------------------
int _nCount = 0;
// 容差
float _fTolerance;
// 纹理平铺数量
int _nUVTileCount;
sampler2D _MainTex;
//-------------------------------------------------------------------------

则可通过Material来传输给它:

// m_sShader 为Shader变量
// m_txtMain 为Texture变量
Material m = null;
if (null != m_sShader)
{
    m = new Material(m_sShader);
    m.SetInt("_nCount", 6);
    m.SetFloat("_fTolerance", 0.4f);
    m.SetInt("_nUVTileCount", 3);
    m.SetTexture("_MainTex", m_txtMain);
}

0x01. 怎么给Shader传递N个参数呢?

(!!警告!!这种方式会造成多次纹理采样)

当然可以使用0x00的方式一个一个传,但是在没有严格的性能把控的情况下,我们可以把N个参数写入纹理中,然后传输纹理对象给Shader。举个例子,下面的代码我通过一个texture对象把10个颜色值(或者说是 10×4 个浮点数指)传给了Shader。

Texture2D colorTxt = new Texture2D(10, 10);
Color[] colors = new Color[10]{
    Color.black, Color.white,
    Color.yellow , Color.gray ,
    Color.green , Color.grey ,
    Color.blue,Color.red,
    Color.magenta,Color.cyan};

for (int j = 0; j != 10; j++)
{
    for (int i = 0; i != 10; ++i)
    {
        colorTxt.SetPixel(i, j, colors[j]);
    }
}
colorTxt.Apply();
// m_sShader 为Shader变量
Material m = new Material(m_sShader);
m.SetTexture("_ColorTxt", colorTxt);

在Shader中使用的时候可以如下:

// 获取第0个参数
fixed4 argv000 = tex2D(_TagTex002, ((0.5,0.05)))
// 获取第1个参数
fixed4 argv001 = tex2D(_TagTex002, ((0.5,0.15)))
// ....

上图纹理放大后如下图:

通过这种方式就可以把无数个数值参数传递给了Shader,注意在几个或者少于10个参数的情况下不推荐这种方式。

0x02. 怎么给Shader传递更多的纹理对象?

拿SMT560来说,如果我定义超过8个纹理对象并传给Shader的时候 Shader就会显示不正常。那么怎么解决这个问题呢?
答案就在0x01,简单来说就是合并纹理。把传入的纹理们 几个合并成一个,或者如果传输的都是小纹理 则可考虑全部纹理合并成一个。然后使用的时候再根据UV值换算。这样就解决了文章开头的两个小问题。

PS:如各位朋友有更好的方案欢迎留言告知,感激不尽

参考文献

  1. High-Level_Shading_Languag
  2. 安卓中的Shader限制讨论

《Unity中如何向Shader传数组》有2个想法

  1. 纹理其实就是数组。
    最大纹理数量和纹理填充率都是受GPU硬件限制的,这个我怀疑和GPU AGP总线通道数量有关。不过用8个纹理,真的太恐怖了,我不可想象你用的是多复杂的shader。
    0x01的方式并不推荐,看上文中你的多次纹理采样,我心里就微微发寒。我要是BOSS,一定要你命三千。

发表评论

邮箱地址不会被公开。 必填项已用*标注