缘起
有时候我们需要传一组数据给我们的 Shader,以便实现一些特殊的效果。但 Unity 官方的 ShaderLab 貌似不提供这样的功能,我们能定义的属性只有有限的几种:纹理、数值等。
其实在底层,不管是 OpenGL 还是 Direct3D,其 Shader 都是支持传递任意数量数组数据的。Unity 虽然对 Shader 开发进行了一些简化封装,却并没有屏蔽这样的功能。而且在其 5.4+ 版本中,还通过增加的 API 提供了有限的引擎支持。
步骤
Shader
- 变量声明
uniform float4 _Points[100]; // 数组变量
uniform float _Points_Num; // 数组长度变量
- 在 Surface Shaders 或 Vertex and Fragment Shaders 中使用
// 遍历
for (int j=0; j<_Points_Num; j++)
{
float4 p4 = _Points[j]; // 索引取值
// 自定义处理
}
Script
- 通过 Material 给 Shader 传递真正的数组数据
var points = new Vector4[60];
for (var i = 0; i < 60; i++)
{
points[i] = new Vector4(some.x, some.y, some.z, some.w);
}
var render = GetComponentInChildren<MeshRenderer>();
foreach (var material in render.materials)
{
material.SetInt("_Points_Num",points.Length);
material.SetVectorArray("_Points",points);
}
- 通过 Shader 进行全局赋值
Shader.SetGlobalInt("_Points_Num",points.Length);
Shader.SetGlobalVectorArray("_Points",points);
限制
-
只支持几种固定类型的数组
- float
- Matrix
- Vector4
所以,如果你想要传递 int、Vector3 之类的数据,需要选择一个相近的类型,比如 int => float,Vector3 => Vector4。
还可以把相关的数据整合到一起,比如用于表示坐标的 Vector3 和半径的 float 整合到一个 Vector4 中,这样也不会浪费空间。
-
只可以通过脚本传值,不像纹理之类的有编辑器支持
注意
-
在 Shader 底层,也仅支持固定长度的数组,所以一般要声明一个长度足够大的数组,再附带长度变量,以标识有效的数据长度。
-
可能是出于效率的原因,第一次传进去的数组长度决定了 Shader 真正用到的数组长度
比如第一次传进去了长度为 1 的数组,以后即使再传更长的数组,Shader 也只使用 1 个数组元素
所以第一次要传一个足够长度的数组进行初始化
网友评论