16
\$\begingroup\$

How do you generate a random number in HLSL?

I'm asking because I want to try gpu ray tracing. You need to generate random directions in a pixel shader. So I want randFloat(), where the result is a random number between -1 and +1.

Also, what is the deal with the hlsl noise instruction? The docs say it was removed from HLSL 2.0 and up. Why?

I read about an approach where you fill a texture with random values, then have a texture coordinate in each vertex that has an index into that texture. But that's per vertex, I need an instruction I can call in the pixel shader. Plus this approach requires "reseeding" the per-vertex texcoords if you want different values each frame, and that requires a vertex buffer update each frame (which can be expensive!)

With detail, how can you cheaply generate a random number on the GPU?

\$\endgroup\$
6
  • 1
    \$\begingroup\$ What do you mean by per vertex, that tex coord gets interpolated in pixel shader and its per pixel lookup to that random texture. \$\endgroup\$
    – Kikaimaru
    Jul 20, 2012 at 20:18
  • \$\begingroup\$ What I mean is, if the 2 texture values happen to be too close together, there's going to be this dependency (you will get a "blended" value). Say one vertex has (0.5,0.5) for u,v and the adjacent vertex has (0.51,0.52), and there are like 500 fragments on that segment .. then the outputs will be 2 or 3 actual random values (looked up from texture), but the rest will the interpolated linearly along the face and not truly random. I want to eliminate that possibility and have a rand() function that I can call in the pixel shader \$\endgroup\$
    – bobobobo
    Jul 20, 2012 at 20:40
  • \$\begingroup\$ Why would you say that one vertex has 0.5 and other 0.51, that does not sound random at all, you need to "randomize" these tex coords as well, you can do it in pixel shader if you want, but thats a lot more operations. Those tex coords do not need to be generated from their position so it does not matter how close to each other they are. You can sample random texture once in vertex shader (using position, normal, texcoords * some parameter as texcoords) and this will give you texcoords you pass to pixel shader \$\endgroup\$
    – Kikaimaru
    Jul 20, 2012 at 20:51
  • \$\begingroup\$ @Kikaimaru I mean by bad happenstance, it's possible.. \$\endgroup\$
    – bobobobo
    Jul 20, 2012 at 22:42
  • \$\begingroup\$ Yes in random sequence its possible to have two same values after each other \$\endgroup\$
    – Kikaimaru
    Jul 21, 2012 at 10:12

2 Answers 2

15
\$\begingroup\$

Pseudo random numbers in a pixel shader aren't easy to obtain. A pseudo random number generator on the CPU will have some state which it both reads from and writes to, on every call to the function. You can't do that in a pixel shader.

Here's some options:

  1. Use a compute shader instead of a pixel shader - they support read-write access to a buffer, so you can implement any standard PRNG.

  2. Sample from one or more textures containing random data, based on a parameter like screen space position. You can also do some maths on the position before using it to look up into the texture, which should let you reuse the texture if the maths involves a random-per-draw-call shader constant.

  3. Find some mathematical function of screen space position, that gives results that are 'random enough'.

A quick google search found this page with these functions:

float rand_1_05(in float2 uv)
{
    float2 noise = (frac(sin(dot(uv ,float2(12.9898,78.233)*2.0)) * 43758.5453));
    return abs(noise.x + noise.y) * 0.5;
}

float2 rand_2_10(in float2 uv) {
    float noiseX = (frac(sin(dot(uv, float2(12.9898,78.233) * 2.0)) * 43758.5453));
    float noiseY = sqrt(1 - noiseX * noiseX);
    return float2(noiseX, noiseY);
}

float2 rand_2_0004(in float2 uv)
{
    float noiseX = (frac(sin(dot(uv, float2(12.9898,78.233)      )) * 43758.5453));
    float noiseY = (frac(sin(dot(uv, float2(12.9898,78.233) * 2.0)) * 43758.5453));
    return float2(noiseX, noiseY) * 0.004;
}
\$\endgroup\$
3
  • \$\begingroup\$ Re: compute shaders, you could also store RNG state in threadgroup shared memory. For efficiency you don't want just one RNG state (the contention would be a huge bottleneck as every thread tried to update it), but many - perhaps as many as one per thread, or perhaps one per 4 or 8 threads or some such - as long as the states are small enough to make that feasible (i.e. probably not Mersenne Twister). Are there RNG designs in which several SIMD threads can cooperate to generate several random numbers at once? That would be quite useful here. \$\endgroup\$ Jul 20, 2012 at 23:54
  • 3
    \$\begingroup\$ the external link is broken \$\endgroup\$ Feb 11, 2018 at 9:00
  • \$\begingroup\$ Link's available here: web.archive.org/web/20140224205853/http://… but hasn't much more information \$\endgroup\$ Mar 26, 2020 at 8:43
3
\$\begingroup\$

Here is how I generate pseudo random numbers for my particle effects with a compute shader. Not sure how truely random this code is but it works good enough for my purposes.

The key to giving each GPU kernel it's own random sequence is to feed the compute shader an initial random seed from the CPU. Each kernel then uses this seed to cycle the number generator using it's thread id as a count. From there, each thread should have it's own unique seed to generate unique values.

// Source
// http://www.gamedev.net/topic/592001-random-number-generation-based-on-time-in-hlsl/
// Supposebly from the NVidia Direct3D10 SDK
// Slightly modified for my purposes
#define RANDOM_IA 16807
#define RANDOM_IM 2147483647
#define RANDOM_AM (1.0f/float(RANDOM_IM))
#define RANDOM_IQ 127773u
#define RANDOM_IR 2836
#define RANDOM_MASK 123459876

struct NumberGenerator {
    int seed; // Used to generate values.

    // Returns the current random float.
    float GetCurrentFloat() {
        Cycle();
        return RANDOM_AM * seed;
    }

    // Returns the current random int.
    int GetCurrentInt() {
        Cycle();
        return seed;
    }

    // Generates the next number in the sequence.
    void Cycle() {  
        seed ^= RANDOM_MASK;
        int k = seed / RANDOM_IQ;
        seed = RANDOM_IA * (seed - k * RANDOM_IQ ) - RANDOM_IR * k;

        if (seed < 0 ) 
            seed += RANDOM_IM;

        seed ^= RANDOM_MASK;
    }

    // Cycles the generator based on the input count. Useful for generating a thread unique seed.
    // PERFORMANCE - O(N)
    void Cycle(const uint _count) {
        for (uint i = 0; i < _count; ++i)
            Cycle();
    }

    // Returns a random float within the input range.
    float GetRandomFloat(const float low, const float high) {
        float v = GetCurrentFloat();
        return low * ( 1.0f - v ) + high * v;
    }

    // Sets the seed
    void SetSeed(const uint value) {
        seed = int(value);
        Cycle();
    }
};
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .