Skip to content

WebGPU: Sobol random number generator#761

Open
TheBlek wants to merge 13 commits into
gkjohnson:webgpu-pathtracerfrom
TheBlek:webgpu-sobol
Open

WebGPU: Sobol random number generator#761
TheBlek wants to merge 13 commits into
gkjohnson:webgpu-pathtracerfrom
TheBlek:webgpu-sobol

Conversation

@TheBlek
Copy link
Copy Markdown

@TheBlek TheBlek commented Apr 29, 2026

@gkjohnson

This PR makes a lot of changes in order to support selecting random number generator in API:

  • Implements sobol numbers by porting webgl implementation in two version: with and without texture lookup; From my cursory performance testing it's not conclusive whether texture lookup makes sizable difference, curious to know how it work on your computer
  • Create and manage function nodes for random numbers in WebGPUPathtracer. That means passing them down to everything that needs them: PathtracerBVHComputeData, material and backend in general.
  • Current material is stored and handled inside WebGPUPathtracer now. This allows handling random numbers for a material by default and leaving setRandomFunctions on PathtracerBackend unspecified, so derivations don't have to call super before doing their stuff. Not sure about this decision, but it felt easier to think about the system with material handled here. Let me know what you think.
  • In order to make random number nodes hot-swappable I had to resort to a lot of proxyFn nodes which feels a little messy. Perhaps there could be one place that creates proxy nodes and then distributes them? Not sure how this should be handled

Here's some image comparisons between pcg and sobol random numbers. It looks like diffuse surfaces are not much by more uniform random numbers, unlike specular which has less noise (see clearcoat image).

PCG Sobol
khronos-ClearCoatCarPaint sobol-khronos-ClearCoatCarPaint
pcg-khronos-MetalRoughSpheres khronos-MetalRoughSpheres
pcg-khronos-Lantern khronos-Lantern

@TheBlek
Copy link
Copy Markdown
Author

TheBlek commented May 7, 2026

@gkjohnson Let me know if you want this PR divided or just want to take a different direction for implementing different random number generators.

@gkjohnson
Copy link
Copy Markdown
Owner

Here's a quick comparison of the random functions on the khronos-AnimatedCube w/ 100 samples. It's subtle but I just wanted to make sure there was a perceptible difference. I somewhat expected a bit more, though?

PCG Sobol
image image
image image

From my cursory performance testing it's not conclusive whether texture lookup makes sizable difference, curious to know how it work on your computer

I'm not seeing any difference between the performance of PCG, SOBOL, or SOBOL_TEXTURE. I expect the performance won't be dominated all that much by the random function implementation.

I'm thinking we can just keep the non-texture version of the SOBOL randomness unless there's a good reason to do otherwise?

Create and manage function nodes for random numbers in WebGPUPathtracer. That means passing them down to everything that needs them: PathtracerBVHComputeData, material and backend in general.

In order to make random number nodes hot-swappable I had to resort to a lot of proxyFn nodes which feels a little messy. Perhaps there could be one place that creates proxy nodes and then distributes them? Not sure how this should be handled

I'll think through this, as well - I agree it's not ideal to have to string the randomness functions down through to every subfunction.

for ( var i = 0; i < INTEGRATION_SAMPLES; i++ ) {

let wh = ggxDirection( wo, vec2( alpha ), pcgRand2() );
let wh = ${ ggxDirectionFunc }( wo, vec2( alpha ), ${ pcgFunctions.vec2f }() );
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is always using "pcg" randomness. Is this intended?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, kind of. I think it does not really matter which random function we use here since there are many samples taken.

Comment thread src/webgpu/WaveFrontPathTracer.js Outdated
Comment on lines +260 to +276
enqueueRaysKernel.seed ++;
enqueueRaysKernel.seed = this.seed;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the need for this change? We should still need to store a different seed per ray because they may each be at a different point in the path.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, if we're being proper, seed should be different for each ray. Fixed that.

Comment on lines +4 to +9
// Alpha test should be the last index as it is summed with triangle index
export const RNG_INDEX_RAY_JITTER = 0;
export const RNG_INDEX_ENVIRONMENT_SAMPLE = 1;
export const RNG_INDEX_SCATTER_TYPE = 2;
export const RNG_INDEX_SCATTER_DIRECTION = 3;
export const RNG_INDEX_ALPHA_TEST = 4;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fully understanding what this is for?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are unique numbers for each sample of random numbers that are used for sobol number generation. They appear in webgl pathtracer as plain numbers:

state.isShadowRay = scatterRec.specularPdf < rand( 4 );

For better structure and to ensure there are no duplicates, I decided to define those constants here.

rayQueue[ index ].pixel = indexUV;
rayQueue[ index ].throughputColor = vec3f( 1.0 );
rayQueue[ index ].currentBounce = 0;
rayQueue[ index ].pcgStateS0 = ${ getPcgSeed }();
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain how this is working, now? Is this replaced by RNG_INDEX_RAY_JITTER?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, it just initializes each time without carrying it over. Will add a little fix to use current bounce in initialization. Although, if we are to keep only one random number generation algorithm, it should not matter as sobol numbers' state can be properly initialized on each stage properly.

@gkjohnson
Copy link
Copy Markdown
Owner

Create and manage function nodes for random numbers in WebGPUPathtracer. That means passing them down to everything that needs them: PathtracerBVHComputeData, material and backend in general.

In order to make random number nodes hot-swappable I had to resort to a lot of proxyFn nodes which feels a little messy. Perhaps there could be one place that creates proxy nodes and then distributes them? Not sure how this should be handled

I'll think through this, as well - I agree it's not ideal to have to string the randomness functions down through to every subfunction.

Having thought through this a bit more - I'm thinking we don't really need a user-settable randomness function (at least for now). We can keep the PCG functions around but if we're seeing that Sobol is better than we should just export and use that everywhere. Then for dev we can change the randomness functions around in that source file if we have to. I don't think the toggle features is worth the implementation complexity atm.

@TheBlek
Copy link
Copy Markdown
Author

TheBlek commented May 30, 2026

Then for dev we can change the randomness functions around in that source file if we have to.

I understand not exposing the toggle to the users, but even implementing new random numbers and changing them in every place is tedious and make comparisons harder.

I simplified the implementation a bit by extracting proxyFn nodes into a single object that is passed around. Let me know if complexity is still an issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants