Unity中如何将PerRendererData与float数组属性结合使用?
Great question! Combining [PerRendererData] with float arrays in Unity shaders is indeed a bit tricky since you can't directly declare a float array property with that attribute, and the CGPROGRAM block doesn't support [PerRendererData] either. But there are a couple of solid workarounds to achieve this:
1. Simulate an Array with Multiple [PerRendererData] Vector Properties
For small float arrays, you can break the array into individual Vector properties (each marked with [PerRendererData]), then reconstruct the array inside your shader's CGPROGRAM block.
Shader Example:
Shader "Custom/PerRendererArray" { Properties { // Declare individual vector properties for each array element [PerRendererData] _ArrayElem0 ("Array Element 0", Vector) = (0,0,0,0) [PerRendererData] _ArrayElem1 ("Array Element 1", Vector) = (0,0,0,0) [PerRendererData] _ArrayElem2 ("Array Element 2", Vector) = (0,0,0,0) // Add as many as you need (within Unity's property limits) } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" // Declare your float array in CGPROGRAM float _Array[3]; float4 _ArrayElem0; float4 _ArrayElem1; float4 _ArrayElem2; struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; // Reconstruct the array from individual properties _Array[0] = _ArrayElem0.x; _Array[1] = _ArrayElem1.x; _Array[2] = _ArrayElem2.x; // Use the array as needed here... o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // Example: Use the first array element to tint the pixel return fixed4(_Array[0], _Array[1], _Array[2], 1); } ENDCG } } }
C# Setup Example:
using UnityEngine; public class PerRendererArraySetup : MonoBehaviour { void Start() { Renderer renderer = GetComponent<Renderer>(); MaterialPropertyBlock props = new MaterialPropertyBlock(); renderer.GetPropertyBlock(props); // Set each "array element" property for this specific renderer props.SetVector("_ArrayElem0", new Vector4(0.8f, 0, 0, 0)); props.SetVector("_ArrayElem1", new Vector4(0, 0.8f, 0, 0)); props.SetVector("_ArrayElem2", new Vector4(0, 0, 0.8f, 0)); renderer.SetPropertyBlock(props); } }
This works well for small arrays, but gets cumbersome if you need a large number of elements.
2. Use MaterialPropertyBlock with ComputeBuffer (Recommended for Large Arrays)
For larger float arrays, the cleanest approach is to use a ComputeBuffer to hold your array data, then assign it to each renderer individually via MaterialPropertyBlock. This achieves the same "per-renderer" behavior as [PerRendererData] without relying on the attribute directly.
Shader Example:
Shader "Custom/PerRendererComputeBuffer" { Properties { // No need for [PerRendererData] here; we'll handle it via code } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" // Declare a structured buffer for your float array StructuredBuffer<float> _FloatArray; struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // Access array elements by index float r = _FloatArray[0]; float g = _FloatArray[1]; float b = _FloatArray[2]; return fixed4(r, g, b, 1); } ENDCG } } }
C# Setup Example:
using UnityEngine; public class PerRendererBufferSetup : MonoBehaviour { private ComputeBuffer _buffer; void Start() { // Create and populate your float array float[] arrayData = new float[] { 0.2f, 0.6f, 0.9f }; // Create a ComputeBuffer (size = number of elements, stride = size of one float) _buffer = new ComputeBuffer(arrayData.Length, sizeof(float)); _buffer.SetData(arrayData); // Assign the buffer to this renderer's property block Renderer renderer = GetComponent<Renderer>(); MaterialPropertyBlock props = new MaterialPropertyBlock(); renderer.GetPropertyBlock(props); props.SetBuffer("_FloatArray", _buffer); renderer.SetPropertyBlock(props); } // Don't forget to release the buffer to avoid memory leaks void OnDestroy() { if (_buffer != null) { _buffer.Release(); _buffer = null; } } }
This method is far more scalable for large arrays, and each renderer gets its own independent buffer instance—exactly the behavior you'd want from combining [PerRendererData] with a float array.
内容的提问来源于stack exchange,提问作者Jethro

