Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enable mrt on array texture and texture3d render targets #30151

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@
"webgl_multiple_rendertargets",
"webgl_multisampled_renderbuffers",
"webgl_rendertarget_texture2darray",
"webgl_rendertarget_texture3D_mrt",
"webgl_shadowmap_csm",
"webgl_shadowmap_pcss",
"webgl_shadowmap_progressive",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/tags.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"webgl_materials_modified": [ "onBeforeCompile" ],
"webgl_raycaster_bvh": [ "external", "query", "bounds", "tree", "accelerate", "performance", "community", "extension", "plugin", "library", "three-mesh-bvh" ],
"webgl_renderer_pathtracer": [ "external", "raytracing", "pathtracing", "library", "plugin", "extension", "community", "three-gpu-pathtracer", "three-mesh-bvh" ],
"webgl_rendertarget_texture3D_mrt": [ "mrt", "renderTarget"],
"webgpu_compute_particles_snow": [ "external", "webgpu", "stats-gl" ],
"webgl_shadowmap_csm": [ "cascade" ],
"webgl_shadowmap_pcss": [ "soft" ],
Expand Down
322 changes: 322 additions & 0 deletions examples/webgl_rendertarget_texture3D_mrt.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - 3D texture framebuffer attachment with MRT</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>

<script id="vertex-postprocess" type="x-shader/x-vertex">
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

in vec3 position;
in vec2 uv;
out vec2 vUv;

void main()
{
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

</script>

<!--
Fragment shader processing an input 3d texture array and writing the output
into a framebuffer. The framebuffer should have a 3d texture bound
as color attachment.
-->
<script id="fragment-postprocess" type="x-shader/x-fragment">

precision highp sampler3D;
precision mediump float;

in vec2 vUv;

uniform sampler3D uTexture;
uniform float uDepth;
uniform float uIntensity;

#pragma unroll_loop_start
for(int i=0; i< 4; i++) { layout(location = UNROLLED_LOOP_INDEX) out float output_UNROLLED_LOOP_INDEX; }
#pragma unroll_loop_end

void main()
{
float v = 1.618 * abs(uDepth - 0.5);

float voxel = texture(uTexture, vec3( vUv, v )).r;
output_0 = voxel * uIntensity;

voxel = texture(uTexture, vec3( 1. - vUv.x, vUv.y, v + 0.1 )).r;
output_1 = voxel * uIntensity;

voxel = texture(uTexture, vec3( vUv, v + 0.2 )).r;
output_2 = voxel * uIntensity;

voxel = texture(uTexture, vec3( 1. - vUv.x, vUv.y, v + 0.3 )).r;
output_3 = voxel * uIntensity;

}

</script>

<script id="vs" type="x-shader/x-vertex">
uniform vec2 size;
out vec2 vUv;

void main() {

gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

// Convert position.xy to 1.0-0.0

vUv.xy = position.xy / size + 0.5;
vUv.y = 1.0 - vUv.y; // original data is upside down

}
</script>

<script id="fs" type="x-shader/x-fragment">
precision highp float;
precision highp int;
precision highp sampler3D;

uniform sampler3D diffuse;
in vec2 vUv;
uniform int depth;

void main() {

int layer = vUv.x > 0.5 ? 1 : 0;
layer += vUv.y > 0.5 ? 2 : 0;

vec2 uv;

switch(layer) {
case 0: uv = vec2(vUv.x * 2., vUv.y * 2.); break;
case 1: uv = vec2(vUv.x * 2. - 1., vUv.y * 2.); break;
case 2: uv = vec2(vUv.x * 2., vUv.y * 2. - 1.); break;
case 3: uv = vec2(vUv.x * 2. - 1., vUv.y * 2. - 1.); break;
}

// For texture arrays, z is indexed with the actual slice index, 0 1 2 etc
// but for 3d textures the z coordinate needs to be normalized instead

vec4 color = texture( diffuse, vec3( uv, float(layer) / 4. ) );

// lighten a bit
gl_FragColor = vec4( color.rrr * 1.5, 1.0 );
}
</script>
<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">
three.js
</a>
- 3D texture framebuffer MRT color attachments
<br />

Scanned head data by
<a href="https://www.codeproject.com/Articles/352270/Getting-started-with-Volume-Rendering" target="_blank" rel="noopener">Divine Augustine</a><br />
licensed under
<a href="https://www.codeproject.com/info/cpol10.aspx" target="_blank" rel="noopener">CPOL</a>
</div>

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

<script type="module">

import * as THREE from 'three';

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

const DIMENSIONS = {
width: 256,
height: 256,
depth: 109
};

const params = {
intensity: 1
};

/** Post-processing objects */

const postProcessScene = new THREE.Scene();
const postProcessCamera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );

const renderTarget = new THREE.WebGL3DRenderTarget( DIMENSIONS.width, DIMENSIONS.height, 4 );
renderTarget.texture.format = THREE.RedFormat;

const postProcessMaterial = new THREE.RawShaderMaterial( {
uniforms: {
uTexture: { value: null },
uDepth: { value: 0 },
uIntensity: { value: 1.0 }
},
vertexShader: document.getElementById( 'vertex-postprocess' ).textContent.trim(),
fragmentShader: document.getElementById( 'fragment-postprocess' ).textContent.trim(),
glslVersion: THREE.GLSL3
} );

const depthStep = 0.003;

let camera, scene, mesh, renderer, stats;

const planeWidth = 50;
const planeHeight = 50;

init();

function init() {

const container = document.createElement( 'div' );
document.body.appendChild( container );

camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 2000 );
camera.position.z = 70;

scene = new THREE.Scene();

/** Post-processing scene */

const planeGeometry = new THREE.PlaneGeometry( 2, 2 );
const screenQuad = new THREE.Mesh( planeGeometry, postProcessMaterial );
postProcessScene.add( screenQuad );

// 3D Texture is available on WebGL 2.0

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

stats = new Stats();
container.appendChild( stats.dom );

window.addEventListener( 'resize', onWindowResize );

const gui = new GUI();

gui.add( params, 'intensity', 0, 1 ).step( 0.01 ).onChange( value => postProcessMaterial.uniforms.uIntensity.value = value );
gui.open();

// width 256, height 256, depth 109, 8-bit, zip archived raw data

new THREE.FileLoader()
.setResponseType( 'arraybuffer' )
.load( 'textures/3d/head256x256x109.zip', function ( data ) {

const zip = unzipSync( new Uint8Array( data ) );
const array = new Uint8Array( zip[ 'head256x256x109' ].buffer );

const texture = new THREE.Data3DTexture( array, DIMENSIONS.width, DIMENSIONS.height, DIMENSIONS.depth );
texture.format = THREE.RedFormat;
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;
texture.unpackAlignment = 1;
texture.needsUpdate = true;

const material = new THREE.ShaderMaterial( {
uniforms: {
diffuse: { value: renderTarget.texture },
size: { value: new THREE.Vector2( planeWidth, planeHeight ) }
},
vertexShader: document.getElementById( 'vs' ).textContent.trim(),
fragmentShader: document.getElementById( 'fs' ).textContent.trim()
} );

const geometry = new THREE.PlaneGeometry( planeWidth, planeHeight );

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

scene.add( mesh );

postProcessMaterial.uniforms.uTexture.value = texture;

renderer.setAnimationLoop( animate );

} );

}

function onWindowResize() {

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

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

}

function animate() {

let value = postProcessMaterial.uniforms.uDepth.value;

value += depthStep;

postProcessMaterial.uniforms.uDepth.value = value % 1;

render();

stats.update();

}

/**
* Renders the 2D array into the render target `renderTarget`.
*/
function renderTo3DTexture() {

renderer.setRenderTarget( renderTarget );

const gl = renderer.getContext();
const drawBuffersArray = [ gl.COLOR_ATTACHMENT0 ];
const glTexture = renderer.properties.get( renderTarget.texture ).__webglTexture;

for ( let i = 1; i < 4; i ++ ) {

gl.framebufferTextureLayer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, glTexture, 0, i );

drawBuffersArray.push( gl.COLOR_ATTACHMENT0 + i );

}

// set draw buffers state for MRT
gl.drawBuffers( drawBuffersArray );

renderer.render( postProcessScene, postProcessCamera );

// restore draw buffers state
gl.drawBuffers( [ gl.COLOR_ATTACHMENT0 ] );

renderer.setRenderTarget( null );

}

function render() {

// Step 1 - Render the input Data3DTexture into the 4 slices of the render target
renderTo3DTexture();

// Step 2 - Renders the scene containing the plane with a material
// sampling the 3D render target texture.
renderer.render( scene, camera );

}

</script>
</body>
</html>
2 changes: 2 additions & 0 deletions src/renderers/WebGL3DRenderTarget.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class WebGL3DRenderTarget extends WebGLRenderTarget {

this.texture = new Data3DTexture( null, width, height, depth );

this.textures = this.textures.map( () => this.texture );

this.texture.isRenderTargetTexture = true;

}
Expand Down
2 changes: 2 additions & 0 deletions src/renderers/WebGLArrayRenderTarget.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class WebGLArrayRenderTarget extends WebGLRenderTarget {

this.texture = new DataArrayTexture( null, width, height, depth );

this.textures = this.textures.map( () => this.texture );

this.texture.isRenderTargetTexture = true;

}
Expand Down
10 changes: 8 additions & 2 deletions src/renderers/WebGLRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2403,9 +2403,15 @@ class WebGLRenderer {

} else if ( isRenderTarget3D ) {

const layer = activeCubeFace || 0;

const textureProperties = properties.get( renderTarget.texture );
const layer = activeCubeFace;
_gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel, layer );

for ( let i = 0; i < renderTarget.textures.length; i ++ ) {

_gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, textureProperties.__webglTexture, activeMipmapLevel || 0, layer + i );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't layer + i be layer here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without the "+ i" it would attach the same layer of the texture to all COLOR_ATTACHMENTs. This way it will attach layer on ATTACHMENT0, layer + 1 on ATTACHMENT1 etc

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's not so intuitive to implicitly offset the bound layer for each MRT-attached 3d texture. I think this should be set to layer as Renaud has suggested under the assumption that each MRT texture is different.

And if there's a strong use case that requires binding different layers of the same 3d texture with MRT then that should be discussed in a separate issue so each layer is user-configurable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gkjohnson this was the only way that it would work with no changes/additions to the three api. We could have explicit assignment of layers to attachments by also allowing an array of indices as the layer argument. I can do the change but is this something that would be acceptable for the WebGLRenderer @Mugen87 ?


}

} else if ( renderTarget !== null && activeMipmapLevel !== 0 ) {

Expand Down
Loading
Loading