如何在金属着色器中获取随机数?
我在金属着色语言规范中搜索“随机”,但什么也没找到。
所以我正在为另一个项目开发随机数生成器,并希望将其打包成一个简洁的框架一段时间。
你的问题促使我这么做。如果您不介意无耻的插件,here是一个非常简单的框架,它将根据您提供的(最多)三个种子在金属着色器中为您生成随机数。该代码基于以下研究论文,该论文描述了如何在并行处理器上创建随机数以进行蒙特卡罗模拟。它还具有
2^121
的(理论)周期,因此它应该适合在 GPU 上完成的最合理的计算。
您只需在着色器中调用一个初始化程序,然后调用
rand()
,如下所示:
// Initialize a random number generator, seeds 2 and 3 are optional
Loki rng = Loki(seed1, seed2, seed3);
// get a random float [0,1)
float random_float = rng.rand();
我还在存储库中包含了一个示例项目,以便您可以了解它是如何使用的。
看起来没有内置的。MetalShaderShowcase/AAPLWoodShader.metal的示例代码定义了自己的简单
rand
函数。
// Generate a random float in the range [0.0f, 1.0f] using x, y, and z (based on the xor128 algorithm)
float rand(int x, int y, int z)
{
int seed = x + y * 57 + z * 241;
seed= (seed<< 13) ^ seed;
return (( 1.0 - ( (seed * (seed * seed * 15731 + 789221) + 1376312589) & 2147483647) / 1073741824.0f) + 1.0f) / 2.0f;
}
请看一下
[pcg-random]
,非常简单快捷,更重要的是速度快。修改 Metal 的 C 代码非常容易。 https://www.pcg-random.org/
typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t;
void pcg32_srandom_r(thread pcg32_random_t* rng, uint64_t initstate, uint64_t initseq)
{
rng->state = 0U;
rng->inc = (initseq << 1u) | 1u;
pcg32_random_r(rng);
rng->state += initstate;
pcg32_random_r(rng);
}
uint32_t pcg32_random_r(thread pcg32_random_t* rng)
{
uint64_t oldstate = rng->state;
rng->state = oldstate * 6364136223846793005ULL + rng->inc;
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
uint32_t rot = oldstate >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}
如何使用?
float randomF(thread pcg32_random_t* rng)
{
//return pcg32_random_r(rng)/float(UINT_MAX);
return ldexp(float(pcg32_random_r(rng)), -32);
}
pcg32_random_t rng;
pcg32_srandom_r(&rng, pos_grid.x*int_time, pos_grid.y*int_time);
auto randomFloat = randomF(&rng);
除了在 GPU 上计算随机数之外,您还可以在 CPU 上计算一堆随机数,然后使用统一 /
MTLBuffer
将它们传递到着色器中。
基于乔的回答。我需要记住所有随机生成的数字,因此我需要通过 MTLComputeCommandEncoder 缓冲区将种子传递给金属着色器。如果有人感兴趣的话,这是代码。
获取随机 [0, 1] CPU 编号(我认为这是最快的方法):
CGFloat(drand48())
设置(在 AppDelegate 内)CPU 随机种子:
static func getRandomGeneratorSeed() -> Int
{
Int(arc4random_uniform(UInt32(Int32.max)))
}
static func setRandomGeneratorSeed()
{
let seed = Helper.getRandomGeneratorSeed()
srand48(seed)
}
我有为每个金属着色器(我的效果/滤镜)调用的通用函数,所以我总是得到在 CPU 上计算的随机种子。
static func metalizationEffectSetParams1(
computeEncoder: MTLComputeCommandEncoder,
device: MTLDevice
)
{
var seed = Float(Helper.getRandomGeneratorSeed())
let seedBuffer = device.makeBuffer(length: MemoryLayout<Float>.size, options: [])
let bufferPointer = seedBuffer?.contents()
memcpy(bufferPointer, &seed, MemoryLayout<Float>.size)
computeEncoder.setBuffer(seedBuffer, offset: 0, index: 0)
}
和金属着色器:
#import "Loki/loki_header.metal"
#import "MetalHelper_header.metal"
#include <metal_stdlib>
using namespace metal;
struct RandomSeed
{
float seed;
};
// Lines 2
kernel void metalizationShader3(texture2d<float, access::read> inTexture [[texture(0)]],
texture2d<float, access::write> outTexture [[texture(1)]],
uint2 gid [[thread_position_in_grid]],
constant RandomSeed &rndStuff [[buffer(0)]]
) {
if (gid.x >= outTexture.get_width() || gid.y >= outTexture.get_height()) {
return;
}
float4 color = inTexture.read(gid);
float3 rndFloat3 = MetalHelper::randomFloat3(rndStuff.seed + gid.x - gid.y);
float gray = dot(color.rgb, rndFloat3);
outTexture.write(float4(gray, gray, gray, color.a), gid);
}
还有我的 MetalHelper.metal:
#import "Loki/loki_header.metal"
#include "MetalHelper_header.metal"
#include <metal_stdlib>
using namespace metal;
thread float MetalHelper::random01(const unsigned seed) {
return Loki(seed).rand();
}
thread float3 MetalHelper::randomFloat3(const unsigned seed)
{
return float3(MetalHelper::random01(seed), MetalHelper::random01(seed), MetalHelper::random01(seed));
}
thread float MetalHelper::randomOneOrMinusOne(const unsigned seed)
{
float r = MetalHelper::random01(seed);
return r < 0.5 ? 1 : -1;
}