Accueil / npm / three@0.186.1 / examples / jsm / helpers / LightProbeGridHelper.js

LightProbeGridHelper.js

import {
	InstancedBufferAttribute,
	InstancedMesh,
	Matrix4,
	NodeMaterial,
	SphereGeometry,
	Vector3
} from 'three/webgpu';
import { array, attribute, Fn, getShIrradianceAt, normalWorld, texture3D, uniform, vec3, vec4 } from 'three/tsl';

/**
 * Visualizes a {@link LightProbeGrid} by rendering a sphere at each probe
 * position, shaded with the probe's L2 spherical harmonics. Uses a single
 * `InstancedMesh` draw call for all probes.
 *
 * This helper can only be used with {@link WebGPURenderer}.
 * When using {@link WebGLRenderer}, import from `LightProbeGridHelperWebGL.js`.
 *
 * ```js
 * const helper = new LightProbeGridHelper( probes );
 * scene.add( helper );
 * ```
 *
 * @private
 * @augments InstancedMesh
 * @three_import import { LightProbeGridHelper } from 'three/addons/helpers/LightProbeGridHelper.js';
 */
class LightProbeGridHelper extends InstancedMesh {

	/**
	 * Constructs a new irradiance probe grid helper.
	 *
	 * @param {LightProbeGrid} probes - The probe grid to visualize.
	 * @param {number} [sphereSize=0.12] - The radius of each probe sphere.
	 */
	constructor( probes, sphereSize = 0.12 ) {

		const geometry = new SphereGeometry( sphereSize, 16, 16 );
		const material = new NodeMaterial();

		const res = probes.resolution;
		const count = res.x * res.y * res.z;

		super( geometry, material, count );

		/**
		 * The probe grid to visualize.
		 *
		 * @type {LightProbeGrid}
		 */
		this.probes = probes;

		this.type = 'LightProbeGridHelper';

		// Atlas and resolution are swappable uniforms, so the shading node builds once.

		this._atlas = texture3D( probes.texture );
		this._resolution = uniform( new Vector3() );

		const nz = this._resolution.z;
		const paddedSlices = nz.add( 2.0 );
		const atlasDepth = paddedSlices.mul( 7.0 );

		material.fragmentNode = Fn( () => {

			const uvw = attribute( 'instanceUVW', 'vec3' );
			const uvZBase = uvw.z.mul( nz ).add( 1.0 );

			const slice = ( t ) => this._atlas.sample( vec3( uvw.xy, uvZBase.add( paddedSlices.mul( t ) ).div( atlasDepth ) ) );

			const s0 = slice( 0 ), s1 = slice( 1 ), s2 = slice( 2 ), s3 = slice( 3 );
			const s4 = slice( 4 ), s5 = slice( 5 ), s6 = slice( 6 );

			const sh = array( [
				s0.xyz,
				vec3( s0.w, s1.xy ),
				vec3( s1.zw, s2.x ),
				s2.yzw,
				s3.xyz,
				vec3( s3.w, s4.xy ),
				vec3( s4.zw, s5.x ),
				s5.yzw,
				s6.xyz
			] );

			return vec4( getShIrradianceAt( normalWorld, sh ).max( vec3( 0.0 ) ), 1.0 );

		} )();

		this.update();

	}

	/**
	 * Rebuilds instance matrices and UVW attributes from the current probe grid,
	 * and rebinds the shading node to its atlas. Call this after changing
	 * `probes` or after re-baking.
	 */
	update() {

		const probes = this.probes;
		const res = probes.resolution;
		const count = res.x * res.y * res.z;

		// Resize instance matrix buffer if needed.

		if ( this.instanceMatrix.count !== count ) {

			this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 );

		}

		this.count = count;

		const uvwArray = new Float32Array( count * 3 );
		const matrix = new Matrix4();
		const probePos = new Vector3();

		let i = 0;

		for ( let iz = 0; iz < res.z; iz ++ ) {

			for ( let iy = 0; iy < res.y; iy ++ ) {

				for ( let ix = 0; ix < res.x; ix ++ ) {

					// Remap to texel centers (must match LightProbeGridNode).
					uvwArray[ i * 3 ] = ( ix + 0.5 ) / res.x;
					uvwArray[ i * 3 + 1 ] = ( iy + 0.5 ) / res.y;
					uvwArray[ i * 3 + 2 ] = ( iz + 0.5 ) / res.z;

					probes.getProbePosition( ix, iy, iz, probePos );
					matrix.makeTranslation( probePos.x, probePos.y, probePos.z );
					this.setMatrixAt( i, matrix );

					i ++;

				}

			}

		}

		this.instanceMatrix.needsUpdate = true;

		this.geometry.setAttribute( 'instanceUVW', new InstancedBufferAttribute( uvwArray, 3 ) );

		this._atlas.value = probes.texture;
		this._resolution.value.copy( res );

	}

	/**
	 * Frees the GPU-related resources allocated by this instance. Call this
	 * method whenever this instance is no longer used in your app.
	 */
	dispose() {

		this.geometry.dispose();
		this.material.dispose();

	}

}

export { LightProbeGridHelper };

Exemple