I am taking the example code for rendering volumes using NRRD loader from here. I wanted to be able to add clipping planes so I took the following example here to combine the two together. However, it seems that clipping does not work the same way with ShaderMaterial as it did for me with other material types. I was wondering how I could clip my nrrd model in three js:
function init() {
scene = new THREE.Scene();
// Create renderer
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// Create camera (The volume renderer does not work very well with perspective yet)
const h = 512; // frustum height
const aspect = window.innerWidth / window.innerHeight;
camera = new THREE.OrthographicCamera( - h * aspect / 2, h * aspect / 2, h / 2, - h / 2, 1, 1000 );
camera.position.set( - 64, - 64, 128 );
camera.up.set( 0, 0, 1 ); // In our data, z is up
// Create controls
controls = new OrbitControls( camera, renderer.domElement );
controls.update();
controls.addEventListener( 'change', render );
controls.target.set( 64, 64, 128 );
controls.minZoom = 0.5;
controls.maxZoom = 40;
// Display world axis
var axesHelper = new THREE.AxesHelper( 1000 );
scene.add( axesHelper );
// Lighting is baked into the shader a.t.m.
// let dirLight = new DirectionalLight( 0xffffff );
// Light source positioned above the scene
const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444 );
hemiLight.position.set( 0, 100, -100 );
scene.add( hemiLight );
// Light source positioned at front of tote
const dirLight = new THREE.DirectionalLight( 0xffffff );
dirLight.position.set( 0, 40, 50 );
scene.add( dirLight );
// ***** Clipping planes: *****
const localPlane = new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0.8 );
const globalPlane = new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 0.1 );
var planeHelper = new THREE.PlaneHelper(localPlane, 500);
scene.add(planeHelper);
// The gui for interaction
volconfig = { clim1: 0.25, clim2: 0.5, renderstyle: 'mip', isothreshold: 0.15, colormap: 'viridis' };
const gui = new GUI();
gui.add( volconfig, 'clim1', 0, 0.5, 0.01 ).onChange( updateUniforms );
gui.add( volconfig, 'clim2', 0, 0.5, 0.01 ).onChange( updateUniforms );
gui.add( volconfig, 'colormap', { gray: 'gray', viridis: 'viridis' } ).onChange( updateUniforms );
gui.add( volconfig, 'renderstyle', { mip: 'mip', iso: 'iso' } ).onChange( updateUniforms );
gui.add( volconfig, 'isothreshold', 0, 100, 0.01 ).onChange( updateUniforms );
// Load the data ...
new NRRDLoader().load( './test.nrrd', function ( volume ) {
// Texture to hold the volume. We have scalars, so we put our data in the red channel.
// THREEJS will select R32F (33326) based on the THREE.RedFormat and THREE.FloatType.
// Also see https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
// TODO: look the dtype up in the volume metadata
const texture = new THREE.Data3DTexture( volume.data, volume.xLength, volume.yLength, volume.zLength );
texture.format = THREE.RedFormat;
texture.type = THREE.FloatType;
texture.minFilter = texture.magFilter = THREE.LinearFilter;
texture.unpackAlignment = 1;
texture.needsUpdate = true;
// Colormap textures
cmtextures = {
viridis: new THREE.TextureLoader().load( 'textures/cm_viridis.png', render ),
gray: new THREE.TextureLoader().load( 'textures/cm_gray.png', render )
};
// Material
const shader = VolumeRenderShader1;
const uniforms = THREE.UniformsUtils.clone( shader.uniforms );
uniforms[ 'u_data' ].value = texture;
uniforms[ 'u_size' ].value.set( volume.xLength, volume.yLength, volume.zLength );
uniforms[ 'u_clim' ].value.set( volconfig.clim1, volconfig.clim2 );
uniforms[ 'u_renderstyle' ].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
uniforms[ 'u_renderthreshold' ].value = volconfig.isothreshold; // For ISO renderstyle
uniforms[ 'u_cmdata' ].value = cmtextures[ volconfig.colormap ];
material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader,
//side: THREE.BackSide, // The volume shader uses the backface as its "reference point"
side: THREE.DoubleSide,
// ***** Clipping setup (material): *****
clippingPlanes: [ localPlane ],
blending: THREE.AdditiveBlending,
depthWrite: false,
clipShadows: true
} );
// THREE.Mesh
const geometry = new THREE.BoxGeometry( volume.xLength, volume.yLength, volume.zLength );
geometry.translate( volume.xLength / 2 - 0.5, volume.yLength / 2 - 0.5, volume.zLength / 2 - 0.5 );
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
// ***** Clipping setup (renderer): *****
const globalPlanes = [ globalPlane ],
Empty = Object.freeze( [] );
renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
renderer.localClippingEnabled = true;
const gui = new GUI(),
folderLocal = gui.addFolder( 'Local Clipping' ),
propsLocal = {
get 'Enabled'() {
return renderer.localClippingEnabled;
},
set 'Enabled'( v ) {
renderer.localClippingEnabled = v;
},
get 'Plane'() {
return localPlane.constant;
},
set 'Plane'( v ) {
localPlane.constant = v;
}
},
folderGlobal = gui.addFolder( 'Global Clipping' ),
propsGlobal = {
get 'Enabled'() {
return renderer.clippingPlanes !== Empty;
},
set 'Enabled'( v ) {
renderer.clippingPlanes = v ? globalPlanes : Empty;
},
get 'Plane'() {
return globalPlane.constant;
},
set 'Plane'( v ) {
globalPlane.constant = v;
}
};
folderLocal.add( propsLocal, 'Enabled' );
folderLocal.add( propsLocal, 'Plane', 0.3, 80 );
render();
} );
}
function updateUniforms() {
material.uniforms[ 'u_clim' ].value.set( volconfig.clim1, volconfig.clim2 );
material.uniforms[ 'u_renderstyle' ].value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
material.uniforms[ 'u_renderthreshold' ].value = volconfig.isothreshold; // For ISO renderstyle
material.uniforms[ 'u_cmdata' ].value = cmtextures[ volconfig.colormap ];
render();
}
render();
}
function render() {
renderer.render( scene, camera );
}