-
-
Notifications
You must be signed in to change notification settings - Fork 48
docs: Volumetric Global Illumination (Radiance Cascade) example #1962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
aleksanderkatan
wants to merge
42
commits into
main
Choose a base branch
from
docs/volumetric-gi-example
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 39 commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
cb752b3
Add example base
a37f7c5
Rewrite some functions to tgsl
3111af9
Rewrite more functions
a7f41c8
Add some of shaderToy's textures
752d336
Make shaders compile
afad28d
Fix types
d739ac3
Somewhat working shader
825eedb
Fix the example
4b3ef04
Move pipelineABC to another file
7074842
Move another pipeline
562cc4c
Add a resize observer
0b70082
Remove pipelineD
31bc3cb
Move another pipeline
f507005
Fit to container
f9b2e49
Presentation format
546bc66
Refactor
b851f8b
Add bilinear fix toggle
2ae1967
Fix texture precision loss
86129e2
Remove warns, rename common
bf5eb42
Add luminance postprocessing switch
bad3bc8
Add attribution
1063b86
Rename example
246a65e
Add example test
6ab1ccd
Add a thumbnail
0577257
Move pipelines.ts and root.ts to index.ts :(
a3ad1e8
Add number of cascades slider
6ab27a9
Update sdf system to no longer use pixel units
733427a
Add quality slider
ac3d321
Add support for scenes
330e8e4
Add hearts scene
afa0288
Add dots working in like 5fps
17b6198
Add prerender pipeline
1400ffd
Update quality slider
0899d43
Remove type errors
0adc69c
Rewrite to shellless
b5a060b
Adjust width
f0c478f
Tags, better dots
79e2210
Add mockMathRandom, fix tests
26c75a2
Merge remote-tracking branch 'origin/main' into docs/volumetric-gi-ex…
e040302
Update apps/typegpu-docs/src/examples/rendering/volumetric-radiance-c…
aleksanderkatan f30589d
Update tests
ac95f55
Merge branch 'main' into docs/volumetric-gi-example
aleksanderkatan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
220 changes: 220 additions & 0 deletions
220
apps/typegpu-docs/src/examples/rendering/volumetric-radiance-cascades/castAndMerge.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| // An implementation of "Radiance Cascades: A Novel Approach to Calculating Global Illumination." | ||
| // https://drive.google.com/file/d/1L6v1_7HY2X-LV3Ofb6oyTIxgEaP4LOI6/view | ||
| // | ||
| // This uses Radiance Cascades, or RC, to compute volumetric global illumination in 2D. | ||
| // Some tricks were pulled off with the help of youssef_afella to port with animations. | ||
| // | ||
| // RC is a method to parameterize the radiance function in a way where discrete ray intervals | ||
| // can be interpolated and made a continuous radiance field. RC has useful properties for | ||
| // global illumination where it produces fully converged radiance, or no noise. It can be | ||
| // implemented on top of any method for computing global illumination, in any number of dimensions. | ||
| // | ||
| // The algorithm has no dependency on scene complexity or the number of lights in the scene. | ||
| // Lights can be any size, and there are grid constructions that resolve 1px light sources like HRC. | ||
| // | ||
| // This particular implementation uses a simple grid and interpolator (manual bilinear), which introduces | ||
| // transmittance bias, or "ringing," and I employ some nonphysical tricks, "bilinear fix," to hide them. | ||
| // A separate issue exists with shadows where interpolation averages discontinuities to where low-variance | ||
| // or sharp shadows cannot be resolved. This can be fixed with a better probe construction like HRC. | ||
| // | ||
| // RC can be used for other purposes than just lighting. My favorite examples: | ||
| // - gravity simulator by @Suslik https://shadertoy.com/view/XcB3Ry | ||
| // - diffusion solver by AdrianM https://twitter.com/yaazarai/status/1994896819575558435 | ||
| // | ||
| // Now that I have convinced you that RC is awesome and you must implement it into your game, onto the code: | ||
|
|
||
| import * as d from 'typegpu/data'; | ||
| import * as std from 'typegpu/std'; | ||
|
|
||
| // Behold, the one parameter of Radiance Cascades, BASE_INTERVAL_LENGTH. | ||
| // This is the minimum spatial resolution that the cascades can model at C0. | ||
| const BASE_INTERVAL_LENGTH = 0.3; | ||
|
|
||
| // Here is also a BASE_PROBE_SIZE (in px) to try out a sparser probe grid. | ||
| // I do a 2x2 or 4x4 probe grid for screen-space (3D) with later upscaling. | ||
| const BASE_PROBE_SIZE = 1; | ||
|
|
||
| // https://m4xc.dev/articles/fundamental-rc | ||
| const getIntervalScale = (cascadeIndex: number) => { | ||
| 'use gpu'; | ||
| if (cascadeIndex <= 0) { | ||
| return 0.0; | ||
| } | ||
| return d.f32(1 << d.u32(2 * cascadeIndex)); | ||
| }; | ||
|
|
||
| const getIntervalRange = (cascadeIndex: number) => { | ||
| 'use gpu'; | ||
| return d | ||
| .vec2f( | ||
| getIntervalScale(cascadeIndex), | ||
| getIntervalScale(cascadeIndex + 1), | ||
| ) | ||
| .mul(BASE_INTERVAL_LENGTH); | ||
| }; | ||
|
|
||
| export const coordToWorldPos = (coord: d.v2f, resolution: d.v2f) => { | ||
| 'use gpu'; | ||
| const center = resolution.mul(0.5); | ||
| const relative = coord.sub(center); | ||
| return relative.div(std.min(resolution.x, resolution.y) / 2); | ||
| }; | ||
|
|
||
| const castInterval = ( | ||
| scene: d.texture2d<d.F32>, | ||
| intervalStart: d.v2f, | ||
| intervalEnd: d.v2f, | ||
| cascadeIndex: number, | ||
| ) => { | ||
| 'use gpu'; | ||
| const dir = intervalEnd.sub(intervalStart); | ||
| const steps = 16 << d.u32(cascadeIndex); | ||
| const stepSize = dir.div(d.f32(steps)); | ||
|
|
||
| let radiance = d.vec3f(0); | ||
| let transmittance = d.f32(1.0); | ||
|
|
||
| for (let i = d.u32(0); i < steps; i++) { | ||
| const coord = intervalStart.add(stepSize.mul(d.f32(i))); | ||
| const sceneColor = std.textureLoad(scene, d.vec2i(coord), 0); | ||
| radiance = radiance.add( | ||
| sceneColor.xyz.mul(transmittance).mul(sceneColor.w), | ||
| ); | ||
| transmittance *= 1.0 - sceneColor.w; | ||
| } | ||
|
|
||
| return d.vec4f(radiance, transmittance); | ||
| }; | ||
|
|
||
| const mergeIntervals = (near: d.v4f, far: d.v4f) => { | ||
| 'use gpu'; | ||
| const radiance = near.xyz.add(far.xyz.mul(near.w)); | ||
| return d.vec4f(radiance, near.w * far.w); | ||
| }; | ||
|
|
||
| const getBilinearWeights = (ratio: d.v2f) => { | ||
| 'use gpu'; | ||
| return d.vec4f( | ||
| (1.0 - ratio.x) * (1.0 - ratio.y), | ||
| ratio.x * (1.0 - ratio.y), | ||
| (1.0 - ratio.x) * ratio.y, | ||
| ratio.x * ratio.y, | ||
| ); | ||
| }; | ||
|
|
||
| const getBilinearOffset = (offsetIndex: number) => { | ||
| 'use gpu'; | ||
| const offsets = [d.vec2i(0, 0), d.vec2i(1, 0), d.vec2i(0, 1), d.vec2i(1, 1)]; | ||
| return offsets[offsetIndex]; | ||
| }; | ||
|
|
||
| // sampler2D cascadeTexture | ||
| export const castAndMerge = ( | ||
| scene: d.texture2d<d.F32>, | ||
| texture: d.texture2d<d.F32>, | ||
| cascadeIndex: number, | ||
| fragCoord: d.v2f, | ||
| resolution: d.v2f, | ||
| bilinearFix: number, | ||
| cascadesNumber: number, | ||
| ) => { | ||
| 'use gpu'; | ||
| // Probe parameters for cascade N | ||
| const probeSize = d.i32(BASE_PROBE_SIZE << d.u32(cascadeIndex)); | ||
| const probeCenter = std.floor(fragCoord.div(d.f32(probeSize))).add(0.5); | ||
| const probePosition = probeCenter.mul(d.f32(probeSize)); | ||
|
|
||
| // Interval parameters at cascade N | ||
| const dirCoord = std.mod(d.vec2i(fragCoord), probeSize); | ||
| const dirIndex = dirCoord.x + dirCoord.y * probeSize; | ||
| const dirCount = probeSize * probeSize; | ||
|
|
||
| // Interval direction at cascade N | ||
| const angle = 2.0 * Math.PI * ((d.f32(dirIndex) + 0.5) / d.f32(dirCount)); | ||
| const dir = d.vec2f(std.cos(angle), std.sin(angle)); | ||
|
|
||
| let radiance = d.vec4f(0, 0, 0, 1); | ||
|
|
||
| // Trace radiance interval at cascade N | ||
| const intervalRange = getIntervalRange(cascadeIndex); | ||
| const intervalStart = probePosition.add(dir.mul(intervalRange.x)); | ||
| const intervalEnd = probePosition.add(dir.mul(intervalRange.y)); | ||
| let destInterval = castInterval( | ||
| scene, | ||
| intervalStart, | ||
| intervalEnd, | ||
| cascadeIndex, | ||
| ); | ||
|
|
||
| // Skip merge and only trace on the last cascade (computed back-to-front) | ||
| // This can instead merge with sky radiance or an envmap | ||
| if (cascadeIndex === cascadesNumber - 1) { | ||
| return destInterval; | ||
| } | ||
|
|
||
| // Merge cascade N+1 -> cascade N | ||
| const bilinearProbeSize = d.i32(BASE_PROBE_SIZE << d.u32(cascadeIndex + 1)); | ||
| const bilinearBaseCoord = probePosition.div(d.f32(bilinearProbeSize)).sub( | ||
| 0.5, | ||
| ); | ||
| const ratio = std.fract(bilinearBaseCoord); | ||
| const weights = getBilinearWeights(ratio); | ||
| const baseIndex = d.vec2i(std.floor(bilinearBaseCoord)); | ||
|
|
||
| // Merge with upper 4 probes from cascade N+1 | ||
| // This could be done with hardware interpolation but OES_texture_float_linear support is spotty | ||
| // Ideally, a smaller float buffer format would be used like RGBA16F or RG11FB10F for cascades | ||
| for (let b = d.u32(0); b < 4; b++) { | ||
| // Probe parameters for cascade N+1 | ||
| const baseOffset = getBilinearOffset(b); | ||
| const bilinearIndex = std.clamp( | ||
| baseIndex.add(baseOffset), | ||
| d.vec2i(0), | ||
| d.vec2i(resolution).div(bilinearProbeSize).sub(1), | ||
| ); | ||
| const bilinearPosition = d.vec2f(bilinearIndex).add(0.5).mul( | ||
| d.f32(bilinearProbeSize), | ||
| ); | ||
|
|
||
| // Cast 4 locally interpolated intervals at cascade N -> cascade N+1 (bilinear fix) | ||
| if (bilinearFix === 1) { | ||
| const intervalRange = getIntervalRange(cascadeIndex); | ||
| const intervalStart = probePosition.add(dir.mul(intervalRange.x)); | ||
| const intervalEnd = bilinearPosition.add(dir.mul(intervalRange.y)); | ||
| destInterval = castInterval( | ||
| scene, | ||
| intervalStart, | ||
| intervalEnd, | ||
| cascadeIndex, | ||
| ); | ||
| } | ||
|
|
||
| // Sample and interpolate 4 probe directions | ||
| let bilinearRadiance = d.vec4f(0.0); | ||
| for (let dd = 0; dd < 4; dd++) { | ||
| // Fetch and merge with interval dd at probe b from cascade N+1 | ||
| const baseDirIndex = dirIndex * 4; | ||
| const bilinearDirIndex = baseDirIndex + dd; | ||
| const bilinearDirCoord = d.vec2i( | ||
| bilinearDirIndex % bilinearProbeSize, | ||
| bilinearDirIndex / bilinearProbeSize, | ||
| ); | ||
| const bilinearTexel = bilinearIndex.mul(bilinearProbeSize).add( | ||
| bilinearDirCoord, | ||
| ); | ||
| const bilinearInterval = std.textureLoad( | ||
| texture, | ||
| bilinearTexel, | ||
| 0, | ||
| ); | ||
| bilinearRadiance = bilinearRadiance.add( | ||
| mergeIntervals(destInterval, bilinearInterval).mul(weights[b]), | ||
| ); | ||
| } | ||
|
|
||
| // Average of 4 bilinear samples | ||
| radiance = radiance.add(bilinearRadiance.mul(0.25)); | ||
| } | ||
|
|
||
| return radiance; | ||
| }; | ||
57 changes: 57 additions & 0 deletions
57
apps/typegpu-docs/src/examples/rendering/volumetric-radiance-cascades/image.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import * as d from 'typegpu/data'; | ||
| import * as std from 'typegpu/std'; | ||
|
|
||
| // ACES tonemapping fit for the sRGB color space | ||
| // https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl | ||
| export const tonemapACES = (colorArg: d.v3f): d.v3f => { | ||
| 'use gpu'; | ||
| let color = colorArg.xyz; | ||
|
|
||
| // sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT | ||
| const acesInputMat = d.mat3x3f( | ||
| 0.59719, | ||
| 0.07600, | ||
| 0.02840, | ||
| 0.35458, | ||
| 0.90834, | ||
| 0.13383, | ||
| 0.04823, | ||
| 0.01566, | ||
| 0.83777, | ||
| ); | ||
|
|
||
| // ODT_SAT => XYZ => D60_2_D65 => sRGB | ||
| const acesOutputMat = d.mat3x3f( | ||
| 1.60475, | ||
| -0.10208, | ||
| -0.00327, | ||
| -0.53108, | ||
| 1.10813, | ||
| -0.07276, | ||
| -0.07367, | ||
| -0.00605, | ||
| 1.07602, | ||
| ); | ||
|
|
||
| color = acesInputMat.mul(color); | ||
|
|
||
| // Apply RRT and ODT | ||
| const a = color.mul(color.add(0.0245786)).sub(0.000090537); | ||
| const b = color.mul(color.mul(0.983729).add(0.4329510)).add(0.238081); | ||
| color = a.div(b); | ||
|
|
||
| color = acesOutputMat.mul(color); | ||
|
|
||
| // Clamp to [0, 1] | ||
| return std.clamp(color, d.vec3f(0.0), d.vec3f(1.0)); | ||
| }; | ||
|
|
||
| export const gammaSRGB = (linearSRGB: d.v3f) => { | ||
| 'use gpu'; | ||
| const a = linearSRGB.mul(12.92); | ||
| const b = std.pow(linearSRGB, d.vec3f(1.0 / 2.4)).mul(1.055).sub(0.055); | ||
| const c = std.step(d.vec3f(0.0031308), linearSRGB); | ||
| return std.mix(a, b, c); | ||
| }; | ||
|
|
||
| export const exposure = 1.0; |
11 changes: 11 additions & 0 deletions
11
apps/typegpu-docs/src/examples/rendering/volumetric-radiance-cascades/index.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <canvas data-fit-to-container></canvas> | ||
| <p | ||
| class="absolute px-2 py-1 rounded-xl top-2 mx-auto text-lg text-center text-white bg-black/50" | ||
| > | ||
| Port of "<a | ||
| class="text-purple-300" | ||
| href="https://www.shadertoy.com/view/wfyyDz" | ||
| target="_blank" | ||
| rel="noreferrer" | ||
| >2D Volumetric Radiance Cascades</a>" by codyjbennett | ||
| </p> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.