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

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.

0

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.

0

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:

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.

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.

0

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.

(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.