Jump to content
  • Advertisement
Sign in to follow this  
george7378

Random numbers in a pixel shader

This topic is 1514 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have a path tracing program which I created a while ago and I thought it would be an awesome project to convert it to work on the GPU using a HLSL pixel shader. However, path tracing requires thousands of random numbers for each pixel of the final image, and there is no random number generator in HLSL. So, I came up with an idea to create my own:

 

1) Create an array of random numbers in the main program, with the resolution of the final path traced image. Send it into the shader as a texture.

 

2) At the top of the pixel shader, create a variable which keeps track of how many random number calls have been made for this pixel. Let's call it nRandomCalls. Every time the path tracer asks for a new random number, increment it by 1.

 

3) Sample the noise texture using the location of the current pixel. This assigns a value to each pixel which is random, but always the same for this particular pixel. Use the obtained number to generate a set of texture coordinates.

 

Now, here is my idea for how to get the random numbers themselves. Every time a new random number is needed...

 

4) Increment nRandomCalls by 1.

 

5) Sample the noise texture again using the texture coordinates from part (3) plus an offset which is determined using nRandomCalls. This produces our final random number.

 

As long as the initial array is random enough, this should act as a good source of noise, right? Is there any reason why this wouldn't work? Thanks smile.png

Edited by george7378

Share this post


Link to post
Share on other sites
Advertisement

It may be faster and easier to use values the pixel shader does have access to, and multiply a few of them together as a seed for a chaotic function.

 

Screen position is a good start, and something else time/framerate dependent should give you enough chaos to do what you need.

 

Or maybe I'm misunderstanding something of your goal?

Edited by StarMire

Share this post


Link to post
Share on other sites

Frame time would be pretty good actually but since all of my rendering is basically happening from scratch within my pixel shader, I can't send anything from the program until the image has finished rendering.

Share this post


Link to post
Share on other sites

Frame time would be pretty good actually but since all of my rendering is basically happening from scratch within my pixel shader, I can't send anything from the program until the image has finished rendering.

You mean to say this isn't interactive, or that you need many thousands of random numbers between frames?

If you can't feed any information into it, then you could consider irrational numbers, perhaps.

Share this post


Link to post
Share on other sites

I have seen shaders that do similar things, e.g. Perlin noise implementations which precompute some functions and pass them in a texture. You just need to make sure the randomness doesn't have any obvious artefacts or cycle. Your choice would give a stable set of random numbers for each pixel, which would be non-ideal if you were just adding noise to the image, but may be good for your purposes because it wouldn't cause pixel twinkle if you were standing still.

 

The question is then:

  1. Is this significantly faster than calculating a pseudo-random function? I have no idea, it would certain make your shader smaller which is nice, although at the cost of many texture accesses.
  2. Is this screen size random texture going to waste too much texture space? Perhaps intelligently using a smaller random texture would be just as good.

Share this post


Link to post
Share on other sites

Don't generate random numbers on the CPU. It's a complete waste of time, even if you update them in feedback mode. The idea is as follows: assign each run of your shader/kernel/whatever a unique integer (say, 32 bit or 64 bit) that you increment each frame or whatever, and then assign each pixel a unique integer as well (perhaps width * 65536 + height or something similar) directly inside the pixel shader in a register. At this point you have a unique integer C per pixel per run, and can run a pseudorandom function (a good choice is a slimmed down block cipher) on the ordered pairs (C, 0), (C, 1), ..., (C, n) to generate as many random numbers as you want. Advantages? Essentially zero memory bandwidth, since the information you need to compute the starting integer is already available in your shader, and hence fully compute bound, which is a good thing since with a GPU path tracer you are almost certainly memory bound. Disadvantages? None, of course. It's probably easier to implement than what you're doing now as well once you wrap your mind around it.

 

In pseudocode:

// CPU-side

uint counter = 0;

while (true)
{
    // do work..

    render(counter);
    ++counter;
}

// GPU-side

uint cpu_counter; // e.g. in cbuffer

struct prng_state
{
    uint a;
    uint b;
    uint c;
};

uint PRF(prng_state state)
{
    // implement your favorite pseudorandom function here
    // (it could be as simple as a bunch of xors and stuff,
    //  but you can use e.g. a simplified block cipher)
}

uint rand(inout prng_state state)
{
    uint retval = PRF(state);
    ++state.c; // counter
    return retval;
}

void ps()
{
    // derive unique counter for this pixel, e.g.:
    uint uid = 65536 * screen_pos.x + screen_pos.y;

    // init pseudorandom number generator for this pixel
    // ==> each pixel gets its own unique state (IMPORTANT)
    prng_state state = prng_state{cpu_counter, uid, 0};
    //                            ^             ^   ^
    //                     unique per frame     |   |------ multiple random numbers per pixel
    //                                          |
    //                                   unique per pixel

    uint one_random_number = rand(state):

    uint another_random_float = rand(state) / float(UINT_MAX);

    /* etc.. can write wrappers for floats/integers/etc. */
}

(this implementation above for instance buys you 2^32 pseudorandom numbers per pixel per frame, for a resolution up to 65536x65536 and 2^32 draw calls - you can simply add more elements to the prng_state struct or switch to longs if you need more, or you can start packing bits if you want to micro-optimize)

 

In short, you can obtain an essentially infinite high quality stream of different pseudorandom numbers for every pixel using a dozen bytes of global memory and two to four GPU registers, depending on the size of the internal state of your chosen pseudorandom function. Also, if you write it properly, it can generalize not only to pixels but to any independent work units, by choosing the right mapping function to assign unique integers to work units. In fact once you understand that the whole framework boils down to supplying a unique ID to every pixel + a counter variable to have multiple random numbers per pixel, it becomes easy to modify it to suit your needs.

 

Now you might ask why you can't just use the free variable state.c as the actual PRNG state to implement e.g. a linear congruential generator or a multiply-with-carry. You could do that if you wanted to, but the advantage of the counter method is that under the assumption that you have a good quality pseudorandom function, you can exactly quantify how many pseudorandom numbers you can obtain per pixel shader invocation, in other words, it can easily be made reliable at no particular additional cost, so it is my preferred option.

 

In any case, the lesson to draw here is to not waste time and memory doing this on the CPU. Just pass unique state to your pixel shader and implement a PRNG there!

 

PS: I too am in the (slow due to real life) process of implementing a brand new GPU ray tracer. I may write a few articles or journal entries on it at a later indeterminate date, and if I do I will be certain to spend some time explaining in detail how I dealt with generating pseudorandom numbers.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!