Who can tell me how to render Data3DTexture RGB in three.js

I am study render 3Dtexture with a unity demo ===>Unity - Manual: 3D textures, the unity demo render result is


by my result is

I seems three.js data3dTexture only read Red Pixel
And here is my code :slight_smile:

Blockquote

three.js webgl2 - volume
<body>
	<div id="info">
		<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl2 - volume
	</div>

	<script type="importmap">
		{
			"imports": {
				"three": "../build/three.module.js",
				"three/addons/": "./jsm/"
			}
		}
	</script>

	<script type="module">
		import * as THREE from 'three';
		import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

		import { GUI } from 'three/addons/libs/lil-gui.module.min.js';


		let renderer, scene, camera;
		let mesh;

		init();
		animate();

		function init() {

			renderer = new THREE.WebGLRenderer();
			renderer.setPixelRatio( window.devicePixelRatio );
			renderer.setSize( window.innerWidth, window.innerHeight );
			document.body.appendChild( renderer.domElement );

			scene = new THREE.Scene();

			camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
			camera.position.set( 0, 0, 2 );

			new OrbitControls( camera, renderer.domElement );

			// Texture

	

			let i = 0;
			const size = 32;
			const data = new Uint8Array( size * size * size );
			let inverseResolution = 1.0 / (size - 1.0);

			for (let z = 0; z < size; z++)
			{
				let zOffset = z * size * size;
				for (let y = 0; y < size; y++)
				{
					let yOffset = y * size;
					for (let x = 0; x < size; x++)
					{
						const index = x + yOffset + zOffset;
						data[index] = (x * inverseResolution)*128+128;
						data[index+1] = (y * inverseResolution)*128+128;
						data[index+2] = (z * inverseResolution)*128+128;    
					}
				}
			}   


			const texture = new THREE.Data3DTexture( data, size, size, size );
			texture.format = THREE.RedFormat;
			// texture.type = THREE.Float;
			texture.minFilter = THREE.LinearFilter;
			texture.magFilter = THREE.LinearFilter;
			texture.unpackAlignment = 4;
			texture.needsUpdate = true;

			// Material

			const vertexShader = /* glsl */`
				in vec3 position;

				uniform mat4 modelMatrix;
				uniform mat4 modelViewMatrix;
				uniform mat4 projectionMatrix;
				uniform vec3 cameraPos;

				out vec3 vOrigin;
				out vec3 vDirection;

				void main() {
					vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );

					vOrigin = vec3( inverse( modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
					vDirection = position - vOrigin;

					gl_Position = projectionMatrix * mvPosition;
				}
			`;

			const fragmentShader = /* glsl */`
				precision highp float;
				precision highp sampler3D;

				uniform mat4 modelViewMatrix;
				uniform mat4 projectionMatrix;

				in vec3 vOrigin;
				in vec3 vDirection;

				out vec4 color;

				uniform sampler3D map;

				uniform float threshold;
				uniform float steps;

				vec2 hitBox( vec3 orig, vec3 dir ) {
					const vec3 box_min = vec3( - 0.5 );
					const vec3 box_max = vec3( 0.5 );
					vec3 inv_dir = 1.0 / dir;
					vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
					vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
					vec3 tmin = min( tmin_tmp, tmax_tmp );
					vec3 tmax = max( tmin_tmp, tmax_tmp );
					float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
					float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
					return vec2( t0, t1 );
				}

				float sample1( vec3 p ) {
					return texture( map, p ).r;
				}

				
				vec4 sample2( vec3 p ) {
					return texture( map, p );
				}

				#define epsilon .0001

				vec3 normal( vec3 coord ) {
					if ( coord.x < epsilon ) return vec3( 1.0, 0.0, 0.0 );
					if ( coord.y < epsilon ) return vec3( 0.0, 1.0, 0.0 );
					if ( coord.z < epsilon ) return vec3( 0.0, 0.0, 1.0 );
					if ( coord.x > 1.0 - epsilon ) return vec3( - 1.0, 0.0, 0.0 );
					if ( coord.y > 1.0 - epsilon ) return vec3( 0.0, - 1.0, 0.0 );
					if ( coord.z > 1.0 - epsilon ) return vec3( 0.0, 0.0, - 1.0 );

					float step = 0.01;
					float x = sample1( coord + vec3( - step, 0.0, 0.0 ) ) - sample1( coord + vec3( step, 0.0, 0.0 ) );
					float y = sample1( coord + vec3( 0.0, - step, 0.0 ) ) - sample1( coord + vec3( 0.0, step, 0.0 ) );
					float z = sample1( coord + vec3( 0.0, 0.0, - step ) ) - sample1( coord + vec3( 0.0, 0.0, step ) );

					return normalize( vec3( x, y, z ) );
				}

				vec4 BlendUnder(vec4 color, vec4 newColor)
				{
					color.rgb += (1.0 - color.a) * newColor.a * newColor.rgb;
					color.a += (1.0 - color.a) * newColor.a;
					return color;
				}


				void main(){

					vec3 rayDir = normalize( vDirection );
					vec2 bounds = hitBox( vOrigin, rayDir );

					if ( bounds.x > bounds.y ) discard;

					bounds.x = max( bounds.x, 0.0 );

					vec3 p = vOrigin + bounds.x * rayDir;
					vec3 inc = 1.0 / abs( rayDir );
					float delta = min( inc.x, min( inc.y, inc.z ) );
					delta /= steps;

					for ( float t = bounds.x; t < bounds.y; t += delta ) {

						vec4 samplerColor = sample2( p + 0.5 );
						samplerColor.a *= .02;
						color = BlendUnder(color, samplerColor);

						p += rayDir * delta;

					}


					if ( color.a == 0.0 ) discard;

				}
			`;

			const geometry = new THREE.BoxGeometry( 1, 1, 1 );
			const material = new THREE.RawShaderMaterial( {
				glslVersion: THREE.GLSL3,
				uniforms: {
					map: { value: texture },
					cameraPos: { value: new THREE.Vector3() },
					threshold: { value: 0. },
					steps: { value: 200 }
				},
				vertexShader,
				fragmentShader,
				side: THREE.BackSide,
				transparent:true
			} );

			mesh = new THREE.Mesh( geometry, material );
			scene.add( mesh );

			//

			const parameters = { threshold: 0., steps: 200 };

			function update() {

				material.uniforms.threshold.value = parameters.threshold;
				material.uniforms.steps.value = parameters.steps;

			}

			const gui = new GUI();
			gui.add( parameters, 'steps', 0, 300, 1 ).onChange( update );

			window.addEventListener( 'resize', onWindowResize );

		}

		function onWindowResize() {

			camera.aspect = window.innerWidth / window.innerHeight;
			camera.updateProjectionMatrix();

			renderer.setSize( window.innerWidth, window.innerHeight );

		}

		function animate() {

			requestAnimationFrame( animate );

			mesh.material.uniforms.cameraPos.value.copy( camera.position );

			renderer.render( scene, camera );

		}

	</script>

</body>

Without the possibility to debug and test the code, it is hard to guess. I can see at least two things, that might require additional attention:

  1. When you calculate index, are you sure it steps by 3: e.g. its values are 0, 3, 6, … n, n+3,… etc?
const index = x + yOffset + zOffset;
data[index] = ...
data[index+1] = ...
data[index+2] = ... 
  1. I did not find where you set the gl_FragColor in your fragment shader.

I use glsl3.0, it not have gl_FragColor, it use “out vec4 color”…, the code edit from three.js example “three.js webgl2 - volume”

1 Like

You also have this:

texture.format = THREE.RedFormat;

Could it be the reason? According to the docs RedFormat discards the green and blue components and reads just the red component.

yes, I think it is reason, but i if dont set it , it will render all black… I must set it :cold_sweat: :cold_sweat: :cold_sweat:

And I find almost All three.js docs Data3DTexture Demo All set It “RedFormat”, I dont know why…

Got it working. Will share a link here, after I make some other tests.

Thank you very much!:face_holding_back_tears: :face_holding_back_tears:

1 Like

The changes are marked below. Generally, I switched to floating point:

Here is a live demo, you can check the full source code there:

https://codepen.io/boytchev/full/XWQYmmb

image

3 Likes

Thank you again ,And I find change your code at


The resut more like unity demo:

2 Likes

@PavelBoytchev Its wrong ,I find it not your code reason, the reason is I use Unity demo shader code…

const box = new Mesh(new BoxGeometry(1, 1, 1), new ShaderMaterial({
            side: 2,
            uniforms: {
                cameraPos: {
                    value: this.camera.position
                },
                worldToObject: {
                    value: new Matrix4
                },
                t3d: {
                    value: t3d
                }
            },
            transparent: true,
            vertexShader: `
                uniform vec3 cameraPos;
                varying vec3 objectVertex;
                varying vec3 vectorToSurface;
                void main(){
                    objectVertex = position;
                    vec4 tpos =  vec4(position,1.0);
                    vec3 worldPos =( modelMatrix * tpos).xyz;
                    vectorToSurface = worldPos - cameraPos;
                    gl_Position = projectionMatrix * modelViewMatrix  * tpos;
                }
            `,
            fragmentShader: `
                precision highp float;
                precision mediump sampler3D;
                #define MAX_STEP_COUNT 128

                // Allowed floating point inaccuracy
                #define EPSILON 0.001f
                varying vec3 objectVertex;
                varying vec3 vectorToSurface;
                uniform mat4 worldToObject;
                uniform sampler3D t3d;

                vec4 BlendUnder(vec4 color, vec4 newColor)
                {
                    color.rgb += (1.0 - color.a) * newColor.a * newColor.rgb;
                    color.a += (1.0 - color.a) * newColor.a;
                    return color;
                }

                void main(){
                    vec3 rayOrigin = objectVertex;
                    vec3 rayDirection = (worldToObject * vec4(normalize(vectorToSurface), 1.0)).xyz;
                    vec4 color = vec4(.0);
                    vec3 samplePosition = rayOrigin;
                    float _Alpha = .02;
                    float _StepSize = .01;
                    for (int i = 0; i < MAX_STEP_COUNT; i++)
                    {
                        // Accumulate color only within unit cube bounds
                        if(max(abs(samplePosition.x), max(abs(samplePosition.y), abs(samplePosition.z))) < 0.5 + EPSILON)
                        {
                            vec4 sampledColor = texture(t3d, samplePosition + vec3(0.5, 0.5, 0.5));
                            sampledColor.a *= _Alpha;
                            color = BlendUnder(color, sampledColor);
                            samplePosition += rayDirection * _StepSize;
                        }
                    }
                    gl_FragColor = color;
           
                }
            `
        }));


       //when set scale 100, the result also have problem,maybe is the worldToObject uniform not correct...
        box.scale.set(100,100,100);


        box.onBeforeRender = () => {
            box.material.uniforms.worldToObject.value = box.matrixWorld.clone().invert().transpose();
        }
        this.scene.add(box);

should be RGBAFormat

Demos set it to red format because they are usually just visualizing density fields or CT scans… so they only need one channel of data.

Colored volumetric data is more rare, but works the same. just more channels per sample.