2
0
Эх сурвалжийг харах

Merge branch 'feature/layered-framebuffer' of https://github.com/DavidPeicho/three.js into dev

# Conflicts:
#	src/renderers/WebGLRenderTarget.js
Mr.doob 4 жил өмнө
parent
commit
e1e5ce7d11

+ 1 - 0
examples/files.json

@@ -316,6 +316,7 @@
 	"webgl2": [
 		"webgl2_buffergeometry_attributes_integer",
 		"webgl2_materials_texture2darray",
+		"webgl2_texture2darray_rendertarget",
 		"webgl2_materials_texture3d",
 		"webgl2_multisampled_renderbuffers",
 		"webgl2_volume_cloud",

BIN
examples/screenshots/webgl2_texture2darray_rendertarget.jpg


+ 306 - 0
examples/webgl2_texture2darray_rendertarget.html

@@ -0,0 +1,306 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - 2D texture array framebuffer attachment</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">
+
+	out vec2 vUv;
+
+	void main()
+	{
+		vUv = uv;
+		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+	}
+
+	</script>
+
+	<!--
+		Fragment shader processing an input 2d texture array and writing the output
+		into a framebuffer. The framebuffer should have a 2d texture array bound
+		as color attachment.
+	-->
+	<script id="fragment-postprocess" type="x-shader/x-fragment">
+
+	precision highp sampler2DArray;
+	precision mediump float;
+
+	in vec2 vUv;
+
+	uniform sampler2DArray uTexture;
+	uniform int uDepth;
+	uniform float uSampleLeft;
+	uniform float uSampleWidth;
+
+	void main()
+	{
+		float voxel = texture( uTexture, vec3( vUv, uDepth ) ).r;
+		gl_FragColor.r = (voxel - uSampleLeft) / uSampleWidth;
+	}
+
+	</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 sampler2DArray;
+
+	uniform sampler2DArray diffuse;
+	in vec2 vUv;
+	uniform int depth;
+
+	void main() {
+
+		vec4 color = texture( diffuse, vec3( vUv, depth ) );
+
+		// 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>
+			- 2D Texture array framebuffer color attachment
+			<br />
+
+			<p>
+				This example shows how to render to an array of 2D texture.</br>
+				WebGL2 allows to render to specific "layers" in 3D texture and array of textures.</br>
+				You can use the mouse left click to change the intensity of the render target.
+			</p>
+
+			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="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import Stats from './jsm/libs/stats.module.js';
+			import { JSZip } from './jsm/libs/jszip.module.min.js';
+
+			import { WEBGL } from './jsm/WebGL.js';
+
+			if ( WEBGL.isWebGL2Available() === false ) {
+
+				document.body.appendChild( WEBGL.getWebGL2ErrorMessage() );
+
+			}
+
+			const DIMENSIONS = {
+				width: 256,
+				height: 256,
+				depth: 109
+			};
+
+			const App = {
+				mousePrevious: new THREE.Vector2()
+			};
+
+			/** Post-processing objects */
+
+			const postProcessScene = new THREE.Scene();
+			const postProcessCamera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 );
+
+			const renderTargetTexture = new THREE.DataTexture2DArray();
+			renderTargetTexture.format = THREE.RedFormat;
+			renderTargetTexture.type = THREE.UnsignedByteType;
+
+			const renderTarget = new THREE.WebGLRenderTarget();
+			renderTarget.setTexture( renderTargetTexture );
+			renderTarget.setSize( DIMENSIONS.width, DIMENSIONS.height, DIMENSIONS.depth );
+
+			const postProcessMaterial = new THREE.ShaderMaterial( {
+				uniforms: {
+					uTexture: { value: null },
+					uDepth: { value: 55 },
+					uSampleLeft: { value: 0 },
+					uSampleWidth: { value: 1.0 },
+				},
+				vertexShader: document.getElementById( 'vertex-postprocess' ).textContent.trim(),
+				fragmentShader: document.getElementById( 'fragment-postprocess' ).textContent.trim()
+			} );
+
+			var depthStep = 0.4;
+
+			var camera, scene, mesh, renderer, stats;
+
+			var planeWidth = 50;
+			var planeHeight = 50;
+
+			init();
+
+			function init() {
+
+				var 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.PlaneBufferGeometry( 2, 2 );
+				const screenQuad = new THREE.Mesh( planeGeometry, postProcessMaterial );
+				postProcessScene.add( screenQuad );
+
+				// 2D Texture array is available on WebGL 2.0
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				renderer.domElement.addEventListener( 'mousedown', onMouseDown );
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				// width 256, height 256, depth 109, 8-bit, zip archived raw data
+
+				new THREE.FileLoader()
+					.setResponseType( 'arraybuffer' )
+					.load( 'textures/3d/head256x256x109.zip', function ( data ) {
+
+						var zip = new JSZip( data );
+						var array = zip.files[ 'head256x256x109' ].asUint8Array();
+
+						const texture = new THREE.DataTexture2DArray( array, DIMENSIONS.width, DIMENSIONS.height, DIMENSIONS.depth );
+						texture.format = THREE.RedFormat;
+						texture.type = THREE.UnsignedByteType;
+
+						var material = new THREE.ShaderMaterial( {
+							uniforms: {
+								diffuse: { value: renderTarget.texture },
+								depth: { value: 55 },
+								size: { value: new THREE.Vector2( planeWidth, planeHeight ) }
+							},
+							vertexShader: document.getElementById( 'vs' ).textContent.trim(),
+							fragmentShader: document.getElementById( 'fs' ).textContent.trim()
+						} );
+
+						var geometry = new THREE.PlaneBufferGeometry( planeWidth, planeHeight );
+
+						mesh = new THREE.Mesh( geometry, material );
+
+						scene.add( mesh );
+
+						postProcessMaterial.uniforms.uTexture.value = texture;
+
+						animate();
+
+					} );
+
+			}
+
+			function onMouseDown(e) {
+
+				renderer.domElement.addEventListener('mouseup', onMouseUp);
+				renderer.domElement.addEventListener('mousemove', onMouseMove);
+
+				App.mousePrevious.set(e.clientX, e.clientY);
+
+			}
+
+			function onMouseUp() {
+
+				renderer.domElement.removeEventListener('mousemove', onMouseMove);
+				renderer.domElement.removeEventListener('mouseup', onMouseUp);
+
+			}
+
+			function onMouseMove(e) {
+
+				const x = e.clientX;
+				const y = e.clientY;
+				const deltaX = ( x - App.mousePrevious.x ) * 0.001;
+				const deltaY = ( y - App.mousePrevious.y ) * 0.001;
+				postProcessMaterial.uniforms.uSampleLeft.value += deltaX;
+				postProcessMaterial.uniforms.uSampleWidth.value += deltaY;
+
+				App.mousePrevious.set(x, y);
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				var value = mesh.material.uniforms[ "depth" ].value;
+
+				value += depthStep;
+
+				if ( value > 109.0 || value < 0.0 ) {
+
+					if ( value > 1.0 ) value = 109.0 * 2.0 - value;
+					if ( value < 0.0 ) value = - value;
+
+					depthStep = - depthStep;
+
+				}
+
+				mesh.material.uniforms[ "depth" ].value = value;
+
+				render();
+
+			}
+
+			function renderTo2DArray() {
+
+				const layer = Math.floor( mesh.material.uniforms[ "depth" ].value );
+				postProcessMaterial.uniforms.uDepth.value = layer;
+				renderer.setRenderTarget( renderTarget, layer );
+				renderer.render( postProcessScene, postProcessCamera );
+				renderer.setRenderTarget( null );
+
+			}
+
+			function render() {
+
+				renderTo2DArray();
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 19 - 2
src/renderers/WebGLRenderTarget.js

@@ -18,6 +18,7 @@ class WebGLRenderTarget extends EventDispatcher {
 
 		this.width = width;
 		this.height = height;
+		this.depth = 1;
 
 		this.scissor = new Vector4( 0, 0, width, height );
 		this.scissorTest = false;
@@ -31,6 +32,7 @@ class WebGLRenderTarget extends EventDispatcher {
 		this.texture.image = {};
 		this.texture.image.width = width;
 		this.texture.image.height = height;
+		this.texture.image.depth = 1;
 
 		this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false;
 		this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter;
@@ -41,15 +43,29 @@ class WebGLRenderTarget extends EventDispatcher {
 
 	}
 
-	setSize( width, height ) {
+	setTexture( texture ) {
 
-		if ( this.width !== width || this.height !== height ) {
+		texture.image = {
+			width: this.width,
+			height: this.height,
+			depth: this.depth
+		};
+
+		this.texture = texture;
+
+	}
+
+	setSize ( width, height, depth = 1 ) {
+
+		if ( this.width !== width || this.height !== height || this.depth !== depth ) {
 
 			this.width = width;
 			this.height = height;
+			this.depth = depth;
 
 			this.texture.image.width = width;
 			this.texture.image.height = height;
+			this.texture.image.depth = depth;
 
 			this.dispose();
 
@@ -70,6 +86,7 @@ class WebGLRenderTarget extends EventDispatcher {
 
 		this.width = source.width;
 		this.height = source.height;
+		this.depth = source.depth;
 
 		this.viewport.copy( source.viewport );
 

+ 8 - 0
src/renderers/WebGLRenderer.js

@@ -1782,9 +1782,11 @@ function WebGLRenderer( parameters ) {
 
 		let framebuffer = _framebuffer;
 		let isCube = false;
+		let isRenderTarget3D = false;
 
 		if ( renderTarget ) {
 
+			isRenderTarget3D = renderTarget.is3D();
 			const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer;
 
 			if ( renderTarget.isWebGLCubeRenderTarget ) {
@@ -1830,6 +1832,12 @@ function WebGLRenderer( parameters ) {
 			const textureProperties = properties.get( renderTarget.texture );
 			_gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel );
 
+		} else if ( isRenderTarget3D ) {
+
+			const textureProperties = properties.get( renderTarget.texture );
+			const layer = activeCubeFace || 0;
+			_gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel || 0, layer );
+
 		}
 
 	};

+ 47 - 15
src/renderers/webgl/WebGLTextures.js

@@ -835,7 +835,17 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 		const glFormat = utils.convert( renderTarget.texture.format );
 		const glType = utils.convert( renderTarget.texture.type );
 		const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
-		state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+
+		if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) {
+
+			state.texImage3D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.depth, 0, glFormat, glType, null );
+
+		} else {
+
+			state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+
+		}
+
 		_gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
 		_gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( renderTarget.texture ).__webglTexture, 0 );
 		_gl.bindFramebuffer( _gl.FRAMEBUFFER, null );
@@ -1011,8 +1021,10 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 	// Set up GL resources for the render target
 	function setupRenderTarget( renderTarget ) {
 
+		const texture = renderTarget.texture;
+
 		const renderTargetProperties = properties.get( renderTarget );
-		const textureProperties = properties.get( renderTarget.texture );
+		const textureProperties = properties.get( texture );
 
 		renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
 
@@ -1022,13 +1034,14 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
 		const isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true );
+		const isRenderTarget3D = texture.isDataTexture3D || texture.isDataTexture2DArray;
 		const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
 
 		// Handles WebGL2 RGBFormat fallback - #18858
 
-		if ( isWebGL2 && renderTarget.texture.format === RGBFormat && ( renderTarget.texture.type === FloatType || renderTarget.texture.type === HalfFloatType ) ) {
+		if ( isWebGL2 && texture.format === RGBFormat && ( texture.type === FloatType || texture.type === HalfFloatType ) ) {
 
-			renderTarget.texture.format = RGBAFormat;
+			texture.format = RGBAFormat;
 
 			console.warn( 'THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.' );
 
@@ -1059,9 +1072,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 					_gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer );
 
-					const glFormat = utils.convert( renderTarget.texture.format );
-					const glType = utils.convert( renderTarget.texture.type );
-					const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
+					const glFormat = utils.convert( texture.format );
+					const glType = utils.convert( texture.type );
+					const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType );
 					const samples = getRenderTargetSamples( renderTarget );
 					_gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height );
 
@@ -1094,7 +1107,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 		if ( isCube ) {
 
 			state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture );
-			setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, supportsMips );
+			setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips );
 
 			for ( let i = 0; i < 6; i ++ ) {
 
@@ -1102,9 +1115,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			}
 
-			if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+			if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-				generateMipmap( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, renderTarget.width, renderTarget.height );
+				generateMipmap( _gl.TEXTURE_CUBE_MAP, texture, renderTarget.width, renderTarget.height );
 
 			}
 
@@ -1112,13 +1125,32 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		} else {
 
-			state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture );
-			setTextureParameters( _gl.TEXTURE_2D, renderTarget.texture, supportsMips );
-			setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D );
+			let glTextureType = _gl.TEXTURE_2D;
+
+			if ( isRenderTarget3D ) {
+
+				// Render targets containing layers, i.e: Texture 3D and 2d arrays
+
+				if ( isWebGL2 ) {
+
+					const isTexture3D = texture.isDataTexture3D;
+					glTextureType = isTexture3D ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY;
+
+				} else {
+
+					console.warn( 'THREE.DataTexture3D and THREE.DataTexture2DArray only supported with WebGL2.' );
+
+				}
+
+			}
+
+			state.bindTexture( glTextureType, textureProperties.__webglTexture );
+			setTextureParameters( glTextureType, texture, supportsMips );
+			setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, _gl.COLOR_ATTACHMENT0, glTextureType );
 
-			if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+			if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-				generateMipmap( _gl.TEXTURE_2D, renderTarget.texture, renderTarget.width, renderTarget.height );
+				generateMipmap( _gl.TEXTURE_2D, texture, renderTarget.width, renderTarget.height );
 
 			}