@@ -91,6 +91,12 @@ const CHANNEL_R = 0u;
9191const CHANNEL_G = 1u ;
9292const CHANNEL_B = 2u ;
9393
94+ const DEGREES_TO_RADIANS = PI / 180f ;
95+ const TERRESTRIAL_SOLAR_RADIUS = 0 .255f * DEGREES_TO_RADIANS ;
96+
97+ const SOLAR_COS_THETA_MAX = cos (TERRESTRIAL_SOLAR_RADIUS );
98+ const SOLAR_INV_PDF = 2f * PI * (1f - SOLAR_COS_THETA_MAX );
99+
94100struct RenderParams {
95101 frameData : FrameData ,
96102 camera : Camera ,
@@ -186,12 +192,26 @@ fn rayColor(primaryRay: Ray, rngState: ptr<function, u32>) -> vec3f {
186192 var radiance = vec3 (0f );
187193 var throughput = vec3 (1f );
188194
195+ var bounce = 1u ;
189196 let numBounces = renderParams . samplingState . numBounces ;
190- for (var bounces = 0u ; bounces < numBounces ; bounces += 1u ) {
191- var intersection : Intersection ;
192- if rayIntersectBvh (ray , T_MAX , & intersection ) {
193- let p = intersection . p ;
194- let scatter = evalImplicitLambertian (intersection , rngState );
197+ loop {
198+ var hit : Intersection ;
199+ if rayIntersectBvh (ray , T_MAX , & hit ) {
200+ let albedo = evalTexture (hit . textureDescriptorIdx , hit . uv );
201+ let p = hit . p ;
202+
203+ let lightDirection = sampleSolarDiskDirection (SOLAR_COS_THETA_MAX , skyState . sunDirection , rngState );
204+ let lightIntensity = vec3f (1000000f , 1000000f , 1000000f ); // TODO: replace with more plausible solar intensity
205+ let brdf = albedo * FRAC_1_PI ;
206+ let reflectance = brdf * dot (hit . n , lightDirection );
207+ let lightVisibility = shadowRay (Ray (p , lightDirection ), T_MAX );
208+ radiance += throughput * lightIntensity * reflectance * lightVisibility * SOLAR_INV_PDF ;
209+
210+ if bounce == numBounces {
211+ break ;
212+ }
213+
214+ let scatter = evalImplicitLambertian (hit . n , albedo , rngState );
195215 ray = Ray (p , scatter . wi );
196216 throughput *= scatter . throughput ;
197217 } else {
@@ -211,6 +231,8 @@ fn rayColor(primaryRay: Ray, rngState: ptr<function, u32>) -> vec3f {
211231
212232 break ;
213233 }
234+
235+ bounce += 1u ;
214236 }
215237
216238 return radiance ;
@@ -276,18 +298,27 @@ fn acesFilmic(x: vec3f) -> vec3f {
276298 return saturate ((x * (a * x + b )) / (x * (c * x + d ) + e ));
277299}
278300
279- fn evalImplicitLambertian (hit : Intersection , rngState : ptr <function , u32 >) -> Scatter {
301+ @must_use
302+ fn sampleSolarDiskDirection (cosThetaMax : f32 , direction : vec3f , state : ptr <function , u32 >) -> vec3f {
303+ let v = rngNextInCone (state , cosThetaMax );
304+ let onb = pixarOnb (direction );
305+ return onb * v ;
306+ }
307+
308+ @must_use
309+ fn evalImplicitLambertian (n : vec3f , albedo : vec3f , rngState : ptr <function , u32 >) -> Scatter {
280310 let v = rngNextInCosineWeightedHemisphere (rngState );
281- let onb = pixarOnb (hit . n );
311+ let onb = pixarOnb (n );
282312 let wi = onb * v ;
283313
284- let textureDesc = textureDescriptors [hit . textureDescriptorIdx ];
285- let uv = hit . uv ;
286- let albedo = textureLookup (textureDesc , uv );
287-
288314 return Scatter (wi , albedo );
289315}
290316
317+ fn evalTexture (textureDescriptorIdx : u32 , uv : vec2f ) -> vec3f {
318+ let textureDesc = textureDescriptors [textureDescriptorIdx ];
319+ return textureLookup (textureDesc , uv );
320+ }
321+
291322fn pixarOnb (n : vec3f ) -> mat3x3f {
292323 // https://www.jcgt.org/published/0006/01/01/paper-lowres.pdf
293324 let s = select (- 1f , 1f , n . z >= 0f );
@@ -299,6 +330,55 @@ fn pixarOnb(n: vec3f) -> mat3x3f {
299330 return mat3x3 (u , v , n );
300331}
301332
333+ // Returns 1.0 if no forward intersections, 0.0 otherwise.
334+ @must_use
335+ fn shadowRay (ray : Ray , rayTMax : f32 ) -> f32 {
336+ let intersector = rayAabbIntersector (ray );
337+ var toVisitOffset = 0u ;
338+ var currentNodeIdx = 0u ;
339+ var nodesToVisit : array <u32 , 32u >;
340+
341+ loop {
342+ let node : BvhNode = bvhNodes [currentNodeIdx ];
343+
344+ if rayIntersectAabb (intersector , node . aabb , rayTMax ) {
345+ if node . triangleCount > 0u {
346+ for (var idx = 0u ; idx < node . triangleCount ; idx = idx + 1u ) {
347+ let triangle : Positions = positionAttributes [node . trianglesOffset + idx ];
348+ // TODO: trihit not actually used. A different code path could be used?
349+ var trihit : TriangleHit ;
350+ if rayIntersectTriangle (ray , triangle , rayTMax , & trihit ) {
351+ return 0f ;
352+ }
353+ }
354+ if toVisitOffset == 0u {
355+ break ;
356+ }
357+ toVisitOffset -= 1u ;
358+ currentNodeIdx = nodesToVisit [toVisitOffset ];
359+ } else {
360+ // Is intersector.invDir[node.splitAxis] < 0f? If so, visit second child first.
361+ if intersector . dirNeg [node . splitAxis ] == 1u {
362+ nodesToVisit [toVisitOffset ] = currentNodeIdx + 1u ;
363+ currentNodeIdx = node . secondChildOffset ;
364+ } else {
365+ nodesToVisit [toVisitOffset ] = node . secondChildOffset ;
366+ currentNodeIdx = currentNodeIdx + 1u ;
367+ }
368+ toVisitOffset += 1u ;
369+ }
370+ } else {
371+ if toVisitOffset == 0u {
372+ break ;
373+ }
374+ toVisitOffset -= 1u ;
375+ currentNodeIdx = nodesToVisit [toVisitOffset ];
376+ }
377+ }
378+
379+ return 1f ;
380+ }
381+
302382fn rayIntersectBvh (ray : Ray , rayTMax : f32 , hit : ptr <function , Intersection >) -> bool {
303383 let intersector = rayAabbIntersector (ray );
304384 var toVisitOffset = 0u ;
@@ -489,6 +569,22 @@ fn textureLookup(desc: TextureDescriptor, uv: vec2f) -> vec3f {
489569 return vec3f (f32 (rgba & 0xffu ), f32 ((rgba >> 8u ) & 0xffu ), f32 ((rgba >> 16u ) & 0xffu )) / 255f ;
490570}
491571
572+ @must_use
573+ fn rngNextInCone (state : ptr <function , u32 >, cosThetaMax : f32 ) -> vec3f {
574+ let u1 = rngNextFloat (state );
575+ let u2 = rngNextFloat (state );
576+
577+ let cosTheta = 1f - u1 * (1f - cosThetaMax );
578+ let sinTheta = sqrt (1f - cosTheta * cosTheta );
579+ let phi = 2f * PI * u2 ;
580+
581+ let x = cos (phi ) * sinTheta ;
582+ let y = sin (phi ) * sinTheta ;
583+ let z = cosTheta ;
584+
585+ return vec3 (x , y , z );
586+ }
587+
492588@must_use
493589fn rngNextInCosineWeightedHemisphere (state : ptr <function , u32 >) -> vec3f {
494590 let u1 = rngNextFloat (state );
0 commit comments