LightProbeGrid: Add toJSON / fromJSON serialization - #33658
Conversation
Adds toJSON() / static fromJSON() so a baked grid can be saved to a JSON file and reconstructed (texture and all) without a renderer or a re-bake. Coefficients are stored as fixed-point integers (value * 4096, rounded), which gzips well and stays human-readable — Sponza's 10x7x7 grid is ~17 KB raw, ~6.7 KB gzipped. The Sponza example gains Save / Load buttons, and a new webgl_lightprobes_sponza_baked example loads a pre-baked grid from examples/probes/sponza-lightprobes-bake.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| sun.setFromSphericalCoords( 1, phi, theta ); | ||
| sky.material.uniforms[ 'sunPosition' ].value.copy( sun ); | ||
|
|
||
| const probes = LightProbeGrid.fromJSON( probesJSON ); |
There was a problem hiding this comment.
The JSON is currently saved in a standalone file.
Are you planning to integrate this in the JSON Scene/Object Format? Since probe grids are part of the scene graph, one might expect scene.toJSON() also serializes LightProbeGrid. Granted the serialized probe grid JSON isn't small but we also embed texture images as Base64 into the Scene/Object JSON.
There was a problem hiding this comment.
Yeah, I think eventually it will make sense to have this as part of scene graph serialization, but I'm not 100% sure about the data format and didn't want to commit to core yet.
| { | ||
|
|
||
| const batchPixels = new Float32Array( 9 * totalProbes * 4 ); | ||
| renderer.readRenderTargetPixels( batchTarget, 0, 0, 9, totalProbes, batchPixels ); |
There was a problem hiding this comment.
With this new code, bake() now always performs the readback even if the app never serializes the grid.
Would it be possible to defer this operation and only do this in toJSON()?
There was a problem hiding this comment.
Oh, I thought it was related to the bounce code added in #33657... I'll investigate.
|
|
||
| const texture = new Data3DTexture( atlas, nx, ny, atlasDepth ); | ||
| texture.format = RGBAFormat; | ||
| texture.type = HalfFloatType; |
There was a problem hiding this comment.
Shouldn't this be FloatType instead?
There was a problem hiding this comment.
I tried to store the data in a compact way. We tried a few options and Float16 values scaled by 4096 seemed to be the most compact for JSON without really losing quality.
Feel free to do a pass on it and see if FloatType works too. You'll need to compare the file size of the resulting JSON.
|
|
||
| if ( this.coefficients === null ) { | ||
|
|
||
| throw new Error( 'THREE.LightProbeGrid: toJSON() requires a baked grid. Call bake() first.' ); |
There was a problem hiding this comment.
Since you have already documented the requirements for using toJSON(), do we still need this additional if statement?
I'm okay with keeping it, it's just that usually we don't validate user input like that.
There was a problem hiding this comment.
Ah yeah. Claude was probably overly cautious here.
|
@mrdoob If you don't find the time to finish this PR, are you okay if I continue? |
Related issue: #33125
Description
Adds
toJSON()/static fromJSON()toLightProbeGridso a baked grid can be saved to a JSON file and reconstructed without a renderer or a re-bake.Coefficients are stored as fixed-point integers (
value * 4096, rounded), which gzips well and stays human-readable.webgl_lightprobes_sponza
The Sponza example has new light settings and gains Load bake / Save bake buttons.
https://raw.githack.com/mrdoob/three.js/34309a2353ef3d667939d37baf74e60e5a7e2109/examples/webgl_lightprobes_sponza.html
webgl_lightprobes_sponza_baked
I've also added a new example that loads a pre-baked 32×16×16 grid (~504 KB VRAM) from
examples/probes/sponza-lightprobes-bake.json:Screen.Recording.2026-05-27.at.15.33.27.mov
https://raw.githack.com/mrdoob/three.js/34309a2353ef3d667939d37baf74e60e5a7e2109/examples/webgl_lightprobes_sponza_baked.html