简体   繁体   中英

WebGL2: How to render via shaders onto a TEXTURE_3D

I've read that WebGL2 gives us access to 3d textures . I'm trying to use this to perform some GPU-side computations and then store the output in a 64x64x64 3D texture. The render flow is

compute shader -> render to 3dTexture -> read shader -> render to screen

This is my simple compute shader, the texture's RGB channels should correspond to the XYZ fragment coordinates.

#version 300 es
precision mediump sampler3D;
precision highp float;
layout(location = 0) out highp vec4 pc_fragColor;

void main() {
    vec3 color = vec3(gl_FragCoord.x / 64.0, gl_FragCoord.y / 64.0, gl_FragDepth);
    pc_fragColor.rgb = color;
    pc_fragColor.a = 1.0;
}

However, this only seems to be rendering to a single "slice" of the 3DTexture, where depth is 0.0. All subsequent depths from 1 to 63 px remain black:

在此处输入图像描述

I've created a working demo below to demonstrate this issue.

 var renderer, target3d, camera; const SIDE = 64; var computeMaterial, computeMesh; var readDataMaterial, readDataMesh, read3dTargetMaterial, read3dTargetMesh; var textField = document.querySelector("#textField"); function init() { // Three.js boilerplate renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(new THREE.Color(0x000000), 1.0); document.body.appendChild(renderer.domElement); camera = new THREE.Camera(); // Create volume material to render to 3dTexture computeMaterial = new THREE.RawShaderMaterial({ vertexShader: SIMPLE_VERTEX, fragmentShader: COMPUTE_FRAGMENT, uniforms: { uZCoord: { value: 0.0 }, }, depthTest: false, }); computeMaterial.type = "VolumeShader"; computeMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), computeMaterial); // Left material, reads Data3DTexture readDataMaterial = new THREE.RawShaderMaterial({ vertexShader: SIMPLE_VERTEX, fragmentShader: READ_FRAGMENT, uniforms: { uZCoord: { value: 0.0 }, tDiffuse: { value: create3dDataTexture() } }, depthTest: false }); readDataMaterial.type = "DebugShader"; readDataMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), readDataMaterial); // Right material, reads 3DRenderTarget texture target3d = new THREE.WebGL3DRenderTarget(SIDE, SIDE, SIDE); target3d.depthBuffer = false; read3dTargetMaterial = readDataMaterial.clone(); read3dTargetMaterial.uniforms.tDiffuse.value = target3d.texture; read3dTargetMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), read3dTargetMaterial); } // Creates 3D texture with RGB gradient along the XYZ axes function create3dDataTexture() { const d = new Uint8Array( SIDE * SIDE * SIDE * 4 ); window.dat = d; let i4 = 0; for ( let z = 0; z < SIDE; z ++ ) { for ( let y = 0; y < SIDE; y ++ ) { for ( let x = 0; x < SIDE; x ++ ) { d[i4 + 0] = (x / SIDE) * 255; d[i4 + 1] = (y / SIDE) * 255; d[i4 + 2] = (z / SIDE) * 255; d[i4 + 3] = 1.0; i4 += 4; } } } const texture = new THREE.Data3DTexture( d, SIDE, SIDE, SIDE ); texture.format = THREE.RGBAFormat; texture.minFilter = THREE.NearestFilter; texture.magFilter = THREE.NearestFilter; texture.unpackAlignment = 1; texture.needsUpdate = true; return texture; } function onResize() { renderer.setSize(window.innerWidth, window.innerHeight); } function animate(t) { // Render volume shader to target3d buffer renderer.setRenderTarget(target3d); renderer.render(computeMesh, camera); // Update z texture coordinate along sine wave renderer.autoClear = false; const sinZCoord = Math.sin(t / 1000); readDataMaterial.uniforms.uZCoord.value = sinZCoord; read3dTargetMaterial.uniforms.uZCoord.value = sinZCoord; textField.innerText = sinZCoord.toFixed(4); // Render data3D texture to screen renderer.setViewport(0, window.innerHeight - SIDE*4, SIDE * 4, SIDE * 4); renderer.setRenderTarget(null); renderer.render(readDataMesh, camera); // Render 3dRenderTarget texture to screen renderer.setViewport(SIDE * 4, window.innerHeight - SIDE*4, SIDE * 4, SIDE * 4); renderer.setRenderTarget(null); renderer.render(read3dTargetMesh, camera); renderer.autoClear = true; requestAnimationFrame(animate); } init(); window.addEventListener("resize", onResize); requestAnimationFrame(animate);
 html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; } #title { position: absolute; top: 0; left: 0; color: white; font-family: sans-serif; } h3 { margin: 2px; }
 <div id="title"> <h3>texDepth</h3><h3 id="textField"></h3> </div> <script src="https://threejs.org/build/three.js"></script> <script> ///////////////////////////////////////////////////////////////////////////////////// // Compute frag shader // It should output an RGB gradient in the XYZ axes to the 3DRenderTarget // But gl_FragCoord.z is always 0.5 and gl_FragDepth is always 0.0 const COMPUTE_FRAGMENT = `#version 300 es precision mediump sampler3D; precision highp float; precision highp int; layout(location = 0) out highp vec4 pc_fragColor; void main() { vec3 color = vec3(gl_FragCoord.x / 64.0, gl_FragCoord.y / 64.0, gl_FragDepth); pc_fragColor.rgb = color; pc_fragColor.a = 1.0; }`; ///////////////////////////////////////////////////////////////////////////////////// // Reader frag shader // Samples the 3D texture along uv.x, uv.y, and uniform Z coordinate const READ_FRAGMENT = `#version 300 es precision mediump sampler3D; precision highp float; precision highp int; layout(location = 0) out highp vec4 pc_fragColor; in vec2 vUv; uniform sampler3D tDiffuse; uniform float uZCoord; void main() { vec3 UV3 = vec3(vUv.x, vUv.y, uZCoord); vec3 diffuse = texture(tDiffuse, UV3).rgb; pc_fragColor.rgb = diffuse; pc_fragColor.a = 1.0; } `; ///////////////////////////////////////////////////////////////////////////////////// // Simple vertex shader, // renders a full-screen quad with UVs without any transformations const SIMPLE_VERTEX = `#version 300 es precision highp float; precision highp int; in vec2 uv; in vec3 position; out vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); }`; ///////////////////////////////////////////////////////////////////////////////////// </script>

  • On the left side, I'm sampling a Data3DTexture that I created via JavaScript. The blue channel smoothly transitions as I move up and down the depth axis, as expected.
  • On the right side I'm sampling the WebGL3DRenderTarget texture rendered in the frag shader I showed above. As you can see, it's only rendering to the texture when the depth coordinate is 0.0. All the other “slices” are black.

在此处输入图像描述 在此处输入图像描述

How can I render my computations to all 64 depth slices? I'm using Three.js for this demo, but I could use any other library like TWGL or vanilla WebGL to achieve the same results.

It doesn't look documented but you can use a second argument to setRenderTarget to set the "layer" of the 3d render target to render to. Here are the changes to make:

  1. When rendering to the render target perform a new render for every layer:
for ( let i = 0; i < SIDE; i ++ ) {
   
  // set the uZCoord color value for the shader
  computeMesh.material.uniforms.uZCoord.value = i / (SIDE - 1);

  // Set the 3d target "layer" to render into before rendering
  renderer.setRenderTarget(target3d, i);
  renderer.render(computeMesh, camera);

} 
  1. Use the "uZCoord" uniform in the compute fragment shader:
    uniform float uZCoord;
    void main() {
        vec3 color = vec3(gl_FragCoord.x / 64.0, gl_FragCoord.y / 64.0, uZCoord);
        pc_fragColor.rgb = color;
        pc_fragColor.a = 1.0;
    }

Other than that I don't believe theres a way to render to the full 3d volume of the target in a single draw call. This three.js example shows how to do this but with render target arrays, as well:

https://threejs.org/examples/?q=array#webgl2_rendertarget_texture2darray

 var renderer, target3d, camera; const SIDE = 64; var computeMaterial, computeMesh; var readDataMaterial, readDataMesh, read3dTargetMaterial, read3dTargetMesh; var textField = document.querySelector("#textField"); function init() { // Three.js boilerplate renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(new THREE.Color(0x000000), 1.0); document.body.appendChild(renderer.domElement); camera = new THREE.Camera(); // Create volume material to render to 3dTexture computeMaterial = new THREE.RawShaderMaterial({ vertexShader: SIMPLE_VERTEX, fragmentShader: COMPUTE_FRAGMENT, uniforms: { uZCoord: { value: 0.0 }, }, depthTest: false, }); computeMaterial.type = "VolumeShader"; computeMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), computeMaterial); // Left material, reads Data3DTexture readDataMaterial = new THREE.RawShaderMaterial({ vertexShader: SIMPLE_VERTEX, fragmentShader: READ_FRAGMENT, uniforms: { uZCoord: { value: 0.0 }, tDiffuse: { value: create3dDataTexture() } }, depthTest: false }); readDataMaterial.type = "DebugShader"; readDataMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), readDataMaterial); // Right material, reads 3DRenderTarget texture target3d = new THREE.WebGL3DRenderTarget(SIDE, SIDE, SIDE); target3d.depthBuffer = false; read3dTargetMaterial = readDataMaterial.clone(); read3dTargetMaterial.uniforms.tDiffuse.value = target3d.texture; read3dTargetMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), read3dTargetMaterial); } // Creates 3D texture with RGB gradient along the XYZ axes function create3dDataTexture() { const d = new Uint8Array( SIDE * SIDE * SIDE * 4 ); window.dat = d; let i4 = 0; for ( let z = 0; z < SIDE; z ++ ) { for ( let y = 0; y < SIDE; y ++ ) { for ( let x = 0; x < SIDE; x ++ ) { d[i4 + 0] = (x / SIDE) * 255; d[i4 + 1] = (y / SIDE) * 255; d[i4 + 2] = (z / SIDE) * 255; d[i4 + 3] = 1.0; i4 += 4; } } } const texture = new THREE.Data3DTexture( d, SIDE, SIDE, SIDE ); texture.format = THREE.RGBAFormat; texture.minFilter = THREE.NearestFilter; texture.magFilter = THREE.NearestFilter; texture.unpackAlignment = 1; texture.needsUpdate = true; return texture; } function onResize() { renderer.setSize(window.innerWidth, window.innerHeight); } function animate(t) { for ( let i = 0; i < SIDE; i ++ ) { // Render volume shader to target3d buffer computeMesh.material.uniforms.uZCoord.value = i / ( SIDE - 1 ); renderer.setRenderTarget(target3d, i); renderer.render(computeMesh, camera); } // Update z texture coordinate along sine wave renderer.autoClear = false; const sinZCoord = Math.sin(t / 1000); readDataMaterial.uniforms.uZCoord.value = sinZCoord; read3dTargetMaterial.uniforms.uZCoord.value = sinZCoord; textField.innerText = sinZCoord.toFixed(4); // Render data3D texture to screen renderer.setViewport(0, window.innerHeight - SIDE*4, SIDE * 4, SIDE * 4); renderer.setRenderTarget(null); renderer.render(readDataMesh, camera); // Render 3dRenderTarget texture to screen renderer.setViewport(SIDE * 4, window.innerHeight - SIDE*4, SIDE * 4, SIDE * 4); renderer.setRenderTarget(null); renderer.render(read3dTargetMesh, camera); renderer.autoClear = true; requestAnimationFrame(animate); } init(); window.addEventListener("resize", onResize); requestAnimationFrame(animate);
 html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; } #title { position: absolute; top: 0; left: 0; color: white; font-family: sans-serif; } h3 { margin: 2px; }
 <div id="title"> <h3>texDepth</h3><h3 id="textField"></h3> </div> <script src="https://threejs.org/build/three.js"></script> <script> ///////////////////////////////////////////////////////////////////////////////////// // Compute frag shader // It should output an RGB gradient in the XYZ axes to the 3DRenderTarget // But gl_FragCoord.z is always 0.5 and gl_FragDepth is always 0.0 const COMPUTE_FRAGMENT = `#version 300 es precision mediump sampler3D; precision highp float; precision highp int; layout(location = 0) out highp vec4 pc_fragColor; uniform float uZCoord; void main() { vec3 color = vec3(gl_FragCoord.x / 64.0, gl_FragCoord.y / 64.0, uZCoord); pc_fragColor.rgb = color; pc_fragColor.a = 1.0; }`; ///////////////////////////////////////////////////////////////////////////////////// // Reader frag shader // Samples the 3D texture along uv.x, uv.y, and uniform Z coordinate const READ_FRAGMENT = `#version 300 es precision mediump sampler3D; precision highp float; precision highp int; layout(location = 0) out highp vec4 pc_fragColor; in vec2 vUv; uniform sampler3D tDiffuse; uniform float uZCoord; void main() { vec3 UV3 = vec3(vUv.x, vUv.y, uZCoord); vec3 diffuse = texture(tDiffuse, UV3).rgb; pc_fragColor.rgb = diffuse; pc_fragColor.a = 1.0; } `; ///////////////////////////////////////////////////////////////////////////////////// // Simple vertex shader, // renders a full-screen quad with UVs without any transformations const SIMPLE_VERTEX = `#version 300 es precision highp float; precision highp int; in vec2 uv; in vec3 position; out vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); }`; ///////////////////////////////////////////////////////////////////////////////////// </script>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM