Skip to content

Commit

Permalink
WebGPURenderer: support using 3d textures in shaders with texture3d()…
Browse files Browse the repository at this point in the history
… and add VolumeNodeMaterial() and examples. (#28418)

* support 3d textures

* remove unused import

* whitespace fixes

* export texture3D

* use new tsl

* replacing to updateValue()

---------

Co-authored-by: aardgoose <[email protected]>
  • Loading branch information
aardgoose and aardgoose authored May 21, 2024
1 parent 989e89f commit b38ef16
Show file tree
Hide file tree
Showing 17 changed files with 642 additions and 12 deletions.
4 changes: 3 additions & 1 deletion examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,9 @@
"webgpu_storage_buffer",
"webgpu_mesh_batch",
"webgpu_instancing_morph",
"webgpu_texturegrad"
"webgpu_texturegrad",
"webgpu_volume_cloud",
"webgpu_volume_perlin"
],
"webaudio": [
"webaudio_orientation",
Expand Down
1 change: 1 addition & 0 deletions examples/jsm/nodes/Nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export { default as StorageBufferNode, storage, storageObject } from './accessor
export * from './accessors/TangentNode.js';
export { default as TextureNode, texture, textureLoad, /*textureLevel,*/ sampler } from './accessors/TextureNode.js';
export { default as TextureStoreNode, textureStore } from './accessors/TextureStoreNode.js';
export { default as Texture3DNode } from './accessors/Texture3DNode.js';
export { default as UVNode, uv } from './accessors/UVNode.js';
export { default as UserDataNode, userData } from './accessors/UserDataNode.js';

Expand Down
6 changes: 6 additions & 0 deletions examples/jsm/nodes/accessors/ReferenceNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ class ReferenceNode extends Node {

getNodeType( builder ) {

if ( this.node === null ) {

this.updateValue();

}

return this.node.getNodeType( builder );

}
Expand Down
100 changes: 100 additions & 0 deletions examples/jsm/nodes/accessors/Texture3DNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import TextureNode from './TextureNode.js';
import { addNodeClass } from '../core/Node.js';
import { nodeProxy, vec3, tslFn, If } from '../shadernode/ShaderNode.js';

const normal = tslFn( ( { texture, uv } ) => {

const epsilon = 0.0001;

const ret = vec3().temp();

If( uv.x.lessThan( epsilon ), () => {

ret.assign( vec3( 1, 0, 0 ) );

} ).elseif( uv.y.lessThan( epsilon ), () => {

ret.assign( vec3( 0, 1, 0 ) );

} ).elseif( uv.z.lessThan( epsilon ), () => {

ret.assign( vec3( 0, 0, 1 ) );

} ).elseif( uv.x.greaterThan( 1 - epsilon ), () => {

ret.assign( vec3( - 1, 0, 0 ) );

} ).elseif( uv.y.greaterThan( 1 - epsilon ), () => {

ret.assign( vec3( 0, - 1, 0 ) );

} ).elseif( uv.z.greaterThan( 1 - epsilon ), () => {

ret.assign( vec3( 0, 0, - 1 ) );

} ).else( () => {

const step = 0.01;

const x = texture.uv( uv.add( vec3( - step, 0.0, 0.0 ) ) ).r.sub( texture.uv( uv.add( vec3( step, 0.0, 0.0 ) ) ).r );
const y = texture.uv( uv.add( vec3( 0.0, - step, 0.0 ) ) ).r.sub( texture.uv( uv.add( vec3( 0.0, step, 0.0 ) ) ).r );
const z = texture.uv( uv.add( vec3( 0.0, 0.0, - step ) ) ).r.sub( texture.uv( uv.add( vec3( 0.0, 0.0, step ) ) ).r );

ret.assign( vec3( x, y, z ) );

} );

return ret.normalize();

} );


class Texture3DNode extends TextureNode {

constructor( value, uvNode = null, levelNode = null ) {

super( value, uvNode, levelNode );

this.isTexture3DNode = true;

}

getInputType( /*builder*/ ) {

return 'texture3D';

}

getDefaultUV() {

return vec3( 0.5, 0.5, 0.5 );

}

setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for 3d TextureNode

setupUV( builder, uvNode ) {

return uvNode;

}

generateUV( builder, uvNode ) {

return uvNode.build( builder, 'vec3' );

}

normal( uvNode ) {

return normal( { texture: this, uv: uvNode } );

}

}

export default Texture3DNode;

export const texture3D = nodeProxy( Texture3DNode );

addNodeClass( 'Texture3DNode', Texture3DNode );
4 changes: 2 additions & 2 deletions examples/jsm/nodes/core/NodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ class NodeBuilder {

isReference( type ) {

return type === 'void' || type === 'property' || type === 'sampler' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture';
return type === 'void' || type === 'property' || type === 'sampler' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D';

}

Expand Down Expand Up @@ -529,7 +529,7 @@ class NodeBuilder {
getVectorType( type ) {

if ( type === 'color' ) return 'vec3';
if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' ) return 'vec4';
if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) return 'vec4';

return type;

Expand Down
1 change: 1 addition & 0 deletions examples/jsm/nodes/materials/Materials.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export { default as MeshMatcapNodeMaterial } from './MeshMatcapNodeMaterial.js';
export { default as PointsNodeMaterial } from './PointsNodeMaterial.js';
export { default as SpriteNodeMaterial } from './SpriteNodeMaterial.js';
export { default as ShadowNodeMaterial } from './ShadowNodeMaterial.js';
export { default as VolumeNodeMaterial } from './VolumeNodeMaterial.js';
106 changes: 106 additions & 0 deletions examples/jsm/nodes/materials/VolumeNodeMaterial.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js';
import { varying } from '../core/VaryingNode.js';
import { property } from '../core/PropertyNode.js';
import { materialReference } from '../accessors/MaterialReferenceNode.js';
import { modelWorldMatrixInverse } from '../accessors/ModelNode.js';
import { cameraPosition } from '../accessors/CameraNode.js';
import { positionGeometry } from '../accessors/PositionNode.js';
import { tslFn, vec2, vec3, vec4 } from '../shadernode/ShaderNode.js';
import { min, max } from '../math/MathNode.js';
import { loop, Break } from '../utils/LoopNode.js';
import { texture3D } from '../accessors/Texture3DNode.js';

class VolumeNodeMaterial extends NodeMaterial {

constructor( params = {} ) {

super();

this.normals = false;
this.lights = false;
this.isVolumeNodeMaterial = true;
this.testNode = null;

this.setValues( params );

}

setup( builder ) {

const map = texture3D( this.map, null, 0 );

const hitBox = tslFn( ( { orig, dir } ) => {

const box_min = vec3( - 0.5 );
const box_max = vec3( 0.5 );

const inv_dir = dir.reciprocal();

const tmin_tmp = box_min.sub( orig ).mul( inv_dir );
const tmax_tmp = box_max.sub( orig ).mul( inv_dir );

const tmin = min( tmin_tmp, tmax_tmp );
const tmax = max( tmin_tmp, tmax_tmp );

const t0 = max( tmin.x, max( tmin.y, tmin.z ) );
const t1 = min( tmax.x, min( tmax.y, tmax.z ) );

return vec2( t0, t1 );

} );

this.fragmentNode = tslFn( () => {

const vOrigin = varying( vec3( modelWorldMatrixInverse.mul( vec4( cameraPosition, 1.0 ) ) ) );
const vDirection = varying( positionGeometry.sub( vOrigin ) );

const rayDir = vDirection.normalize();
const bounds = property( 'vec2', 'bounds' ).assign( hitBox( { orig: vOrigin, dir: rayDir } ) );

bounds.x.greaterThan( bounds.y ).discard();

bounds.assign( vec2( max( bounds.x, 0.0 ), bounds.y ) );

const p = property( 'vec3', 'p' ).assign( vOrigin.add( bounds.x.mul( rayDir ) ) );
const inc = property( 'vec3', 'inc' ).assign( vec3( rayDir.abs().reciprocal() ) );
const delta = property( 'float', 'delta' ).assign( min( inc.x, min( inc.y, inc.z ) ) );

delta.divAssign( materialReference( 'steps', 'float' ) );

const ac = property( 'vec4', 'ac' ).assign( vec4( materialReference( 'base', 'color' ), 0.0 ) );

loop( { type: 'float', start: bounds.x, end: bounds.y, update: '+= delta' }, () => {

const d = property( 'float', 'd' ).assign( map.uv( p.add( 0.5 ) ).r );

if ( this.testNode !== null ) {

this.testNode( { map: map, mapValue: d, probe: p, finalColor: ac } ).append();

} else {

// default to show surface of mesh
ac.a.assign( 1 );
Break();

}

p.addAssign( rayDir.mul( delta ) );

} );

ac.a.equal( 0 ).discard();

return vec4( ac );

} )();

super.setup( builder );

}

}

export default VolumeNodeMaterial;

addNodeMaterial( 'VolumeNodeMaterial', VolumeNodeMaterial );
14 changes: 13 additions & 1 deletion examples/jsm/renderers/common/nodes/NodeSampledTexture.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,16 @@ class NodeSampledCubeTexture extends NodeSampledTexture {

}

export { NodeSampledTexture, NodeSampledCubeTexture };
class NodeSampledTexture3D extends NodeSampledTexture {

constructor( name, textureNode ) {

super( name, textureNode );

this.isSampledTexture3D = true;

}

}

export { NodeSampledTexture, NodeSampledCubeTexture, NodeSampledTexture3D };
12 changes: 11 additions & 1 deletion examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MathNode, GLSLNodeParser, NodeBuilder, UniformNode, vectorComponents }
import NodeUniformBuffer from '../../common/nodes/NodeUniformBuffer.js';
import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js';

import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js';
import { NodeSampledTexture, NodeSampledCubeTexture, NodeSampledTexture3D } from '../../common/nodes/NodeSampledTexture.js';

import { RedFormat, RGFormat, IntType, DataTexture, RGBFormat, RGBAFormat, FloatType } from 'three';

Expand All @@ -27,6 +27,7 @@ const supports = {
const defaultPrecisions = `
precision highp float;
precision highp int;
precision highp sampler3D;
precision mediump sampler2DArray;
precision lowp sampler2DShadow;
`;
Expand Down Expand Up @@ -322,6 +323,10 @@ ${ flowData.code }

snippet = `samplerCube ${ uniform.name };`;

} else if ( uniform.type === 'texture3D' ) {

snippet = `sampler3D ${ uniform.name };`;

} else if ( uniform.type === 'buffer' ) {

const bufferNode = uniform.node;
Expand Down Expand Up @@ -759,6 +764,11 @@ void main() {

this.bindings[ shaderStage ].push( uniformGPU );

} else if ( type === 'texture3D' ) {

uniformGPU = new NodeSampledTexture3D( uniformNode.name, uniformNode.node );
this.bindings[ shaderStage ].push( uniformGPU );

} else if ( type === 'buffer' ) {

node.name = `NodeBuffer_${ node.id }`;
Expand Down
14 changes: 14 additions & 0 deletions examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ class WebGLTextureUtils {

glTextureType = gl.TEXTURE_2D_ARRAY;

} else if ( texture.isData3DTexture === true ) {

glTextureType = gl.TEXTURE_3D;

} else {

glTextureType = gl.TEXTURE_2D;
Expand Down Expand Up @@ -286,6 +290,10 @@ class WebGLTextureUtils {

gl.texStorage3D( gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth );

} else if ( texture.isData3DTexture ) {

gl.texStorage3D( gl.TEXTURE_3D, levels, glInternalFormat, width, height, depth );

} else if ( ! texture.isVideoTexture ) {

gl.texStorage2D( glTextureType, levels, glInternalFormat, width, height );
Expand Down Expand Up @@ -429,6 +437,12 @@ class WebGLTextureUtils {

gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data );

} else if ( texture.isData3DTexture ) {

const image = options.image;

gl.texSubImage3D( gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data );

} else if ( texture.isVideoTexture ) {

texture.update();
Expand Down
Loading

0 comments on commit b38ef16

Please sign in to comment.