@@ -43,6 +43,7 @@ struct Uniforms {
4343@group (3 ) @binding (2 ) var < storage , read > vertexAttributes : array <VertexAttributes >;
4444@group (3 ) @binding (3 ) var < storage , read > textureDescriptors : array <TextureDescriptor >;
4545@group (3 ) @binding (4 ) var < storage , read > textures : array <u32 >;
46+ @group (3 ) @binding (5 ) var < storage , read > blueNoise : BlueNoise ;
4647
4748struct Aabb {
4849 min : vec3f ,
@@ -86,6 +87,12 @@ struct Ray {
8687 direction : vec3f
8788}
8889
90+ struct BlueNoise {
91+ width : u32 ,
92+ height : u32 ,
93+ data : array <vec2f >,
94+ }
95+
8996const CHANNEL_R = 0u ;
9097const CHANNEL_G = 1u ;
9198const CHANNEL_B = 2u ;
@@ -109,7 +116,6 @@ fn fsMain(in: VertexOutput) -> @location(0) vec4f {
109116
110117 let uv = in . texCoord ;
111118 let textureIdx = vec2u (floor (uv * uniforms . framebufferSize ));
112- var rng = initRng (textureIdx , vec2u (uniforms . framebufferSize ), uniforms . frameCount );
113119 let depthSample = textureLoad (gbufferDepth , textureIdx , 0 );
114120 if depthSample == 0 .0 {
115121 let world = worldFromUv (uv , depthSample );
@@ -124,11 +130,12 @@ fn fsMain(in: VertexOutput) -> @location(0) vec4f {
124130 skyRadiance (theta , gamma , CHANNEL_B )
125131 );
126132 } else {
133+ let coord = vec2u (uv * uniforms . framebufferSize );
127134 let position = worldFromUv (uv , depthSample );
128135 let encodedNormal = textureLoad (gbufferNormal , textureIdx , 0 ). rgb ;
129136 let decodedNormal = 2f * encodedNormal - vec3 (1f );
130137 let albedo = textureLoad (gbufferAlbedo , textureIdx , 0 ). rgb ;
131- color = surfaceColor (& rng , offsetPosition (position , decodedNormal ), decodedNormal , albedo );
138+ color = surfaceColor (coord , offsetPosition (position , decodedNormal ), decodedNormal , albedo );
132139 }
133140
134141 return vec4 (acesFilmic (uniforms . exposure * color ), 1 .0 );
@@ -145,17 +152,18 @@ fn worldFromUv(uv: vec2f, depthSample: f32) -> vec3f {
145152const NUM_BOUNCES = 2 ;
146153
147154@must_use
148- fn surfaceColor (rng : ptr < function , u32 > , primaryPos : vec3f , primaryNormal : vec3f , primaryAlbedo : vec3f ) -> vec3f {
155+ fn surfaceColor (coord : vec2u , primaryPos : vec3f , primaryNormal : vec3f , primaryAlbedo : vec3f ) -> vec3f {
149156 var position = primaryPos ;
150157 var normal = primaryNormal ;
151158 var albedo = primaryAlbedo ;
152159 var radiance = vec3 (0f );
153160 var throughput = vec3 (1f );
161+ let blueNoise = animatedBlueNoise (coord , uniforms . frameCount , 512u );
154162
155- radiance += throughput * lightSample (rng , position , normal , albedo );
163+ radiance += throughput * lightSample (blueNoise , position , normal , albedo );
156164
157165 for (var bounce = 1 ; bounce < NUM_BOUNCES ; bounce += 1 ) {
158- let wi = evalImplicitLambertian (normal , rng );
166+ let wi = evalImplicitLambertian (blueNoise , normal );
159167 let ray = Ray (position , wi );
160168 throughput *= albedo ;
161169
@@ -183,15 +191,15 @@ fn surfaceColor(rng: ptr<function, u32>, primaryPos: vec3f, primaryNormal: vec3f
183191 break ;
184192 }
185193
186- radiance += throughput * lightSample (rng , position , normal , albedo );
194+ radiance += throughput * lightSample (blueNoise , position , normal , albedo );
187195 }
188196
189197 return radiance ;
190198}
191199
192200@must_use
193- fn lightSample (rng : ptr < function , u32 > , position : vec3f , normal : vec3f , albedo : vec3f ) -> vec3f {
194- let lightDirection = sampleSolarDiskDirection (SOLAR_COS_THETA_MAX , skyState . sunDirection , rng );
201+ fn lightSample (u : vec2f , position : vec3f , normal : vec3f , albedo : vec3f ) -> vec3f {
202+ let lightDirection = sampleSolarDiskDirection (u , SOLAR_COS_THETA_MAX , skyState . sunDirection );
195203 let lightIntensity = vec3 (
196204 skyState . solarRadiances [CHANNEL_R ],
197205 skyState . solarRadiances [CHANNEL_G ],
@@ -250,15 +258,15 @@ fn acesFilmic(x: vec3f) -> vec3f {
250258}
251259
252260@must_use
253- fn sampleSolarDiskDirection (cosThetaMax : f32 , direction : vec3f , state : ptr < function , u32 > ) -> vec3f {
254- let v = rngNextInCone ( state , cosThetaMax );
261+ fn sampleSolarDiskDirection (u : vec2f , cosThetaMax : f32 , direction : vec3f ) -> vec3f {
262+ let v = directionInCone ( u , cosThetaMax );
255263 let onb = pixarOnb (direction );
256264 return onb * v ;
257265}
258266
259267@must_use
260- fn evalImplicitLambertian (n : vec3f , rngState : ptr < function , u32 > ) -> vec3f {
261- let v = rngNextInCosineWeightedHemisphere ( rngState );
268+ fn evalImplicitLambertian (u : vec2f , n : vec3f ) -> vec3f {
269+ let v = directionInCosineWeightedHemisphere ( u );
262270 let onb = pixarOnb (n );
263271 return onb * v ;
264272}
@@ -534,14 +542,12 @@ fn offsetPosition(p: vec3f, n: vec3f) -> vec3f {
534542 );
535543}
536544
545+ // `u` is a random number in [0, 1].
537546@must_use
538- fn rngNextInCone (state : ptr <function , u32 >, cosThetaMax : f32 ) -> vec3f {
539- let u1 = rngNextFloat (state );
540- let u2 = rngNextFloat (state );
541-
542- let cosTheta = 1f - u1 * (1f - cosThetaMax );
547+ fn directionInCone (u : vec2f , cosThetaMax : f32 ) -> vec3f {
548+ let cosTheta = 1f - u . x * (1f - cosThetaMax );
543549 let sinTheta = sqrt (1f - cosTheta * cosTheta );
544- let phi = 2f * PI * u2 ;
550+ let phi = 2f * PI * u . y ;
545551
546552 let x = cos (phi ) * sinTheta ;
547553 let y = sin (phi ) * sinTheta ;
@@ -550,49 +556,31 @@ fn rngNextInCone(state: ptr<function, u32>, cosThetaMax: f32) -> vec3f {
550556 return vec3 (x , y , z );
551557}
552558
559+ // `u` is a random number in [0, 1].
553560@must_use
554- fn rngNextInCosineWeightedHemisphere (state : ptr <function , u32 >) -> vec3f {
555- let u1 = rngNextFloat (state );
556- let u2 = rngNextFloat (state );
557-
558- let phi = 2f * PI * u2 ;
559- let sinTheta = sqrt (1f - u1 );
561+ fn directionInCosineWeightedHemisphere (u : vec2f ) -> vec3f {
562+ let phi = 2f * PI * u . y ;
563+ let sinTheta = sqrt (1f - u . x );
560564
561565 let x = cos (phi ) * sinTheta ;
562566 let y = sin (phi ) * sinTheta ;
563- let z = sqrt (u1 );
567+ let z = sqrt (u . x );
564568
565569 return vec3 (x , y , z );
566570}
567571
568572@must_use
569- fn initRng (pixel : vec2u , resolution : vec2u , frame : u32 ) -> u32 {
570- // Adapted from https://github.com/boksajak/referencePT
571- let seed = dot (pixel , vec2u (1u , resolution . x )) ^ jenkinsHash (frame );
572- return jenkinsHash (seed );
573- }
574-
575- @must_use
576- fn jenkinsHash (input : u32 ) -> u32 {
577- var x = input ;
578- x += x << 10u ;
579- x ^= x >> 6u ;
580- x += x << 3u ;
581- x ^= x >> 11u ;
582- x += x << 15u ;
583- return x ;
584- }
585-
586- fn rngNextFloat (state : ptr <function , u32 >) -> f32 {
587- rngNextInt (state );
588- return f32 (*state ) / f32 (0xffffffffu );
589- }
590-
591- fn rngNextInt (state : ptr <function , u32 >) {
592- // PCG random number generator
593- // Based on https://www.shadertoy.com/view/XlGcRh
594-
595- let oldState = *state + 747796405u + 2891336453u ;
596- let word = ((oldState >> ((oldState >> 28u ) + 4u )) ^ oldState ) * 277803737u ;
597- *state = (word >> 22u ) ^ word ;
573+ fn animatedBlueNoise (coord : vec2u , frameIdx : u32 , totalSampleCount : u32 ) -> vec2f {
574+ let idx = (coord . y % blueNoise . height ) * blueNoise . width + (coord . x % blueNoise . width );
575+ let blueNoise = blueNoise . data [idx ];
576+ // 2-dimensional golden ratio additive recurrence sequence
577+ // https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
578+ let n = frameIdx % totalSampleCount ;
579+ let a1 = 0 .7548776662466927f ;
580+ let a2 = 0 .5698402909980532f ;
581+ let r2Seq = fract (vec2 (
582+ a1 * f32 (n ),
583+ a2 * f32 (n )
584+ ));
585+ return fract (blueNoise + r2Seq );
598586}
0 commit comments