[工作积累] Tricks with UE4 PerInstanceRandom

时间:2022-03-03 17:16:14

最近在用UE4的Instancing, 发现限制很多。

Unity有instancing的attribute array (uniform/constant buffer),通过InstanceID来访问每个instance的数据,实现每个实例的不同的参数(通常的一种做法)。

然而Unreal没有这样的功能,只有一些instancing的vertex buffer。

shader:

 #if USE_INSTANCING && !USE_INSTANCING_EMULATED
float4 InstanceOrigin : ATTRIBUTE8; // per-instance random in w
half4 InstanceTransform1 : ATTRIBUTE9; // hitproxy.r + 256 * selected in .w
half4 InstanceTransform2 : ATTRIBUTE10; // hitproxy.g in .w
half4 InstanceTransform3 : ATTRIBUTE11; // hitproxy.b in .w
#endif // USE_INSTANCING && !USE_INSTANCING_EMULATED

C++:

 template <class FloatType>
struct FInstanceStream
{
FVector4 InstanceOrigin; // per-instance random in w
FloatType InstanceTransform1[]; // hitproxy.r + 256 * selected in .w
FloatType InstanceTransform2[]; // hitproxy.g in .w
FloatType InstanceTransform3[]; // hitproxy.b in .w
int16 InstanceLightmapAndShadowMapUVBias[];
...
};

在不改动引擎代码的前提下(不熟悉,风险大), 总结了以下workaround:

其中第一种是简单的颜色映射。

后面的几种是为了解决多个参数的方式,因为只有一个随机值,导致随机化不理想。比如一个实例有两个随机颜色,用PerInstanceRandom去查两个颜色表【蓝,黄,绿】和【黑,白,灰】那么第一个颜色是蓝色的时候,第二个颜色一定是黑色,而实际需求的很可能是两个颜色也要随机搭配。

解决方法是把一个随机值映射为多个。前两种比较类似,一个用随机函数,一个用纹理采样。最后一种是hack, 取每一位的数字就不一样。虽然hack,有很多限制,但是目前用起来还算可以。
事实上,后面两种都是第一种的特化,第二种Fn=TextureSample(Texn, x), 第三种Fn=frac(x*10^n),虽然我一开始想到的就是第三种最简单的hack。

  1. Simple senario
    • map to color
      • create an color LUT table (i.e. using photoshop, filter mode need to set as point/nearest if interpolation is not expected)
      • make float2: use PerInstanceRandom as x
      • link to TextureSample node's uv pin
    • map to value
      • PerInstanceRandom * (max-min) + min
    • could be done in blueprint
  2. Multiple variation attributes:(i.e. speed/color/move range/angular speed/etc.)
    • Simply map PerInstanceRandom to multiple attributes works but may not have expected results
    • i.e. instances with 2 different color, using PerInstanceRandom to access 2 color tables, lets say [Blue, Yellow, Cyan] and [Black, White, Grey]
      because using the same PerInstanceRandom as UV, when the first color is "Blue", the second "random" color will always be "Black".
      while the expected result may need be any combinations of the color in the 2 tables, not fixed combinations.
    • Solution: remap PerInstanceRandom to multiple random data
      • 1.multiple custom random function
        • random0 = F0(PerInstanceRandom)
        • random1 = F1(PerInstanceRandom);
        • ...
        • randomn = Fn(PerInstanceRandom);
        • get color using random0;
        • get speed/color2 using random1;
        • ...
        • pros: can map PerInstanceRandom to any count of random numbers
        • cons: custom math/random function need to be written for each attribute variation
      • 2.using random noise texture: similiar as above while using texture sampling
        • random0 = sample(RandomNoiseTexture0, float2( PerInstanceRandom, 0));
        • random1 = sample(RandomNoiseTexture1, float2(PerInstancerandom, 0));
        • ...
        • pros: easy & simple; blueprint friendly; can map PerInstanceRandom to any count of random number
        • cons: one additional random noise texture for each attribute - extra workflow & runtime cost (texture uniform may exceed?)
          - this could be avoid by using a single random texture with multiple rows: randomn = sample(Texture, float2(PerInstanceRandom, (0.5+n)/N));
      • 3.simple hack: most significant digit shift
        • random0 = PerInstanceRandom;
        • random1 = frac(random0*10);
        • random2 = rac(random1*10);
        • ...
        • pros: simple & easy; blueprint friendly
        • cons: precision lost on each iteration; max attribute variations limited to 5-6, due to floating point precision issue. last random number( i.e. random5 ) have lowest resolution: approximately 10 (probably 0.0~0.9 or even less), but still good to be used as index/uv to sampling color table if color count is small.
        • if variation count is small, this method is personally recommended.

note: the last 2 methods can be consider as an speicialization of the first one: 2. Fn=TextureSample(x),  3.Fn=frac(x*10^n)

note2: instance's AbsoluteWorldPosition could be used to generate attribute variations.