|
@@ -42,8 +42,7 @@
|
|
|
|
|
|
<body>
|
|
|
<div id="info">
|
|
|
- <a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> -
|
|
|
- Float volume render test (mip / isosurface)
|
|
|
+ <a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - Float volume render test (mip / isosurface)
|
|
|
</div>
|
|
|
<div id="inset"></div>
|
|
|
|
|
@@ -61,18 +60,16 @@
|
|
|
|
|
|
<script>
|
|
|
|
|
|
- if ( ! Detector.webgl ) { Detector.addGetWebGLMessage(); }
|
|
|
+ if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
|
|
|
|
|
|
var container,
|
|
|
renderer,
|
|
|
scene,
|
|
|
camera,
|
|
|
controls,
|
|
|
- volmaterial,
|
|
|
+ material,
|
|
|
volconfig,
|
|
|
- cmtextures,
|
|
|
- gui,
|
|
|
- drawrequested;
|
|
|
+ cmtextures;
|
|
|
|
|
|
init();
|
|
|
|
|
@@ -84,28 +81,28 @@
|
|
|
var canvas = document.createElement( 'canvas' );
|
|
|
var context = canvas.getContext( 'webgl2' );
|
|
|
renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );
|
|
|
-
|
|
|
- // Attach renderer to DOM
|
|
|
- container = document.createElement( 'div' );
|
|
|
- document.body.appendChild( container );
|
|
|
- container.appendChild( renderer.domElement );
|
|
|
+ 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)
|
|
|
- camera = new THREE.OrthographicCamera()
|
|
|
- camera.position.z = 1000;
|
|
|
- camera.near = 0.1;
|
|
|
- camera.far = 5000;
|
|
|
- camera.up = new THREE.Vector3( 0, 0, 1 ); // In our data, z is the up/down axis !!
|
|
|
- scene.add( camera );
|
|
|
+ var h = 512; // frustum height
|
|
|
+ var aspect = window.innerWidth / window.innerHeight;
|
|
|
+ camera = new THREE.OrthographicCamera( - h * aspect / 2, h * aspect / 2, h / 2, - h / 2, 1, 1000 );
|
|
|
+ camera.position.set( 0, 0, 128 );
|
|
|
+ camera.up.set( 0, 0, 1 ); // In our data, z is up
|
|
|
|
|
|
// Create controls
|
|
|
controls = new THREE.OrbitControls( camera, renderer.domElement );
|
|
|
- controls.addEventListener( 'change', animate );
|
|
|
+ controls.addEventListener( 'change', render );
|
|
|
controls.target.set( 64, 64, 128 );
|
|
|
- controls.enableDamping = false;
|
|
|
+ controls.minZoom = 0.5;
|
|
|
+ controls.maxZoom = 4;
|
|
|
controls.update();
|
|
|
|
|
|
- // Lighting is backed into the shader a.t.m.
|
|
|
+ // scene.add( new THREE.AxesHelper( 128 ) );
|
|
|
+
|
|
|
+ // Lighting is baked into the shader a.t.m.
|
|
|
// var dirLight = new THREE.DirectionalLight( 0xffffff );
|
|
|
|
|
|
// The gui for interaction
|
|
@@ -117,14 +114,9 @@
|
|
|
gui.add( volconfig, 'renderstyle', { mip: 'mip', iso: 'iso' } ).onChange( updateUniforms );
|
|
|
gui.add( volconfig, 'isothreshold', 0, 1, 0.01 ).onChange( updateUniforms );
|
|
|
|
|
|
- // Keep track of window size (and initialize now)
|
|
|
- window.addEventListener( 'resize', onWindowResize, false );
|
|
|
- onWindowResize();
|
|
|
-
|
|
|
// Load the data ...
|
|
|
- var loader = new THREE.NRRDLoader();
|
|
|
- loader.load( "models/nrrd/stent.nrrd", function ( volume ) {
|
|
|
- window.volume = volume;
|
|
|
+ new THREE.NRRDLoader().load( "models/nrrd/stent.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 RedFormat and FloatType.
|
|
|
// Also see https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
|
|
@@ -138,74 +130,72 @@
|
|
|
|
|
|
// Colormap textures
|
|
|
cmtextures = {
|
|
|
- viridis: new THREE.TextureLoader().load( 'textures/cm_viridis.png' ),
|
|
|
- gray: new THREE.TextureLoader().load( 'textures/cm_gray.png' )
|
|
|
+ viridis: new THREE.TextureLoader().load( 'textures/cm_viridis.png', render ),
|
|
|
+ gray: new THREE.TextureLoader().load( 'textures/cm_gray.png', render )
|
|
|
};
|
|
|
|
|
|
- // Material (shaders) to render the volume using raycasting
|
|
|
- volmaterial = new THREE.ShaderMaterial( THREE.VolumeRenderShader1 );
|
|
|
- volmaterial.side = THREE.BackSide; // The volume shader uses the backface as its "reference point"
|
|
|
+ // Material
|
|
|
+ var shader = THREE.VolumeRenderShader1;
|
|
|
+
|
|
|
+ var 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 ];
|
|
|
|
|
|
- // Apply standard volume material uniform info
|
|
|
- volmaterial.uniforms.u_data.value = texture;
|
|
|
- volmaterial.uniforms.u_size.value = [ volume.xLength, volume.yLength, volume.zLength ];
|
|
|
+ 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"
|
|
|
+ } );
|
|
|
|
|
|
- // Geometry for the volume
|
|
|
- var volgeometry = new THREE.BoxGeometry( volume.xLength, volume.yLength, volume.zLength );
|
|
|
- volgeometry = volgeometry.translate( volume.xLength / 2 - 0.5, volume.yLength / 2 - 0.5, volume.zLength / 2 - 0.5 );
|
|
|
- var volcube = new THREE.Mesh( volgeometry, volmaterial );
|
|
|
- scene.add( volcube );
|
|
|
+ // Mesh
|
|
|
+ var 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 );
|
|
|
|
|
|
- // Apply uniforms now, this will call animate(), but apparently that's not enough sometimes
|
|
|
- this.updateUniforms();
|
|
|
- window.setTimeout(animate, 100);
|
|
|
- window.setTimeout(animate, 1000);
|
|
|
+ var mesh = new THREE.Mesh( geometry, material );
|
|
|
+ scene.add( mesh );
|
|
|
|
|
|
} );
|
|
|
|
|
|
+ window.addEventListener( 'resize', onWindowResize, false );
|
|
|
+
|
|
|
}
|
|
|
|
|
|
function updateUniforms() {
|
|
|
|
|
|
- volmaterial.uniforms.u_clim.value = [ volconfig.clim1, volconfig.clim2 ];
|
|
|
- volmaterial.uniforms.u_renderstyle.value = volconfig.renderstyle == 'mip' ? 0 : 1; // 0: MIP, 1: ISO
|
|
|
- volmaterial.uniforms.u_renderthreshold.value = volconfig.isothreshold; // For ISO renderstyle
|
|
|
- volmaterial.uniforms.u_cmdata.value = cmtextures[volconfig.colormap];
|
|
|
+ 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 ];
|
|
|
|
|
|
- volmaterial.needsUpdate = true;
|
|
|
- animate();
|
|
|
+ render();
|
|
|
|
|
|
}
|
|
|
|
|
|
function onWindowResize() {
|
|
|
|
|
|
- camera.right = window.innerWidth / 5.0;
|
|
|
- camera.left = -camera.right;
|
|
|
- camera.top = window.innerHeight / 5.0;
|
|
|
- camera.bottom = -camera.top;
|
|
|
- camera.updateProjectionMatrix();
|
|
|
-
|
|
|
- renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
|
|
- if (controls.handleResize) { controls.handleResize(); }
|
|
|
+ var aspect = window.innerWidth / window.innerHeight;
|
|
|
|
|
|
- animate();
|
|
|
- }
|
|
|
+ var frustumHeight = camera.top - camera.bottom;
|
|
|
|
|
|
- function animate() {
|
|
|
+ camera.left = - frustumHeight * aspect / 2;
|
|
|
+ camera.right = frustumHeight * aspect / 2;
|
|
|
+
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
|
|
|
- // throttled animate function
|
|
|
- if ( !drawrequested ) {
|
|
|
- drawrequested = true;
|
|
|
- window.requestAnimationFrame( _animate );
|
|
|
- }
|
|
|
+ render();
|
|
|
|
|
|
}
|
|
|
|
|
|
- function _animate() {
|
|
|
+ function render() {
|
|
|
|
|
|
- drawrequested = false;
|
|
|
renderer.render( scene, camera );
|
|
|
|
|
|
}
|