Browse Source

WebGLRenderer: Fix setRenderTarget()'s activeMipmapLevel (#26347)

* Ability to set the mip levels of a texture as a render target

* Render to texture's mip levels example

* Render to texture's mip levels example: added a screenshot, fixed errors

* Render to texture's mip levels example: added the example to json files

* Render to texture's mip levels example: updated the screenshot

* Update webgl_materials_cubemap_render_to_mipmaps.html

Clean up.

* Update WebGLRenderer.js

Code style.

* Update WebGLRenderer.js

Code style.

* Update WebGLRenderer.js

Clean up.

---------

Co-authored-by: Michael Herzog <[email protected]>
Sergey Makovkin 2 years ago
parent
commit
a3c4192845

+ 1 - 0
examples/files.json

@@ -142,6 +142,7 @@
 		"webgl_materials_cubemap_dynamic",
 		"webgl_materials_cubemap_refraction",
 		"webgl_materials_cubemap_mipmaps",
+		"webgl_materials_cubemap_render_to_mipmaps",
 		"webgl_materials_curvature",
 		"webgl_materials_displacementmap",
 		"webgl_materials_envmaps",

BIN
examples/screenshots/webgl_materials_cubemap_render_to_mipmaps.jpg


+ 1 - 0
examples/tags.json

@@ -40,6 +40,7 @@
 	"webgl_materials_blending_custom": [ "alpha" ],
 	"webgl_materials_channels": [ "normal", "depth", "rgba packing" ],
 	"webgl_materials_cubemap_mipmaps": [ "envmap" ],
+	"webgl_materials_cubemap_render_to_mipmaps": [ "envmap", "renderTarget", "mipmap" ],
 	"webgl_materials_envmaps_hdr": [ "rgbm" ],
 	"webgl_materials_lightmap": [ "shadow" ],
 	"webgl_materials_physical_clearcoat": [ "anisotropy" ],

+ 278 - 0
examples/webgl_materials_cubemap_render_to_mipmaps.html

@@ -0,0 +1,278 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials - cubemap mipmaps</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>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - rendering to cubemap mip levels demo.<br/>
+			Left: original cubemap<br/>
+			Right: generated cubemap<br/>
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<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';
+
+			let container;
+			let camera, scene, renderer;
+
+			const CubemapFilterShader = {
+				uniforms: {
+					cubeTexture: { value: null },
+					mipIndex: { value: 0 },
+				},
+
+				vertexShader: /* glsl */ `
+
+					varying vec3 vWorldDirection;
+
+					#include <common>
+
+					void main() {
+						vWorldDirection = transformDirection(position, modelMatrix);
+						#include <begin_vertex>
+						#include <project_vertex>
+						gl_Position.z = gl_Position.w; // set z to camera.far
+					}
+
+					`,
+
+				fragmentShader: /* glsl */ `
+
+					uniform samplerCube cubeTexture;
+					varying vec3 vWorldDirection;
+
+					uniform float mipIndex;
+
+					#include <common>
+
+					void main() {
+						vec3 cubeCoordinates = normalize(vWorldDirection);
+						
+						// Colorize mip levels
+						vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
+						if (mipIndex == 0.0) color.rgb = vec3(1.0, 1.0, 1.0);
+						else if (mipIndex == 1.0) color.rgb = vec3(0.0, 0.0, 1.0);
+						else if (mipIndex == 2.0) color.rgb = vec3(0.0, 1.0, 1.0);
+						else if (mipIndex == 3.0) color.rgb = vec3(0.0, 1.0, 0.0);
+						else if (mipIndex == 4.0) color.rgb = vec3(1.0, 1.0, 0.0);						
+
+						gl_FragColor = textureCube(cubeTexture, cubeCoordinates, 0.0) * color;
+					}
+
+					`,
+			};
+
+
+			init();
+			animate();
+
+
+			async function loadCubeTexture( urls ) {
+
+				return new Promise( function ( resolve ) {
+
+					new THREE.CubeTextureLoader().load( urls, function ( cubeTexture ) {
+
+						resolve( cubeTexture );
+
+					} );
+
+
+				} );
+
+			}
+
+			function allocateCubemapRenderTarget( cubeMapSize ) {
+
+				const params = {
+					magFilter: THREE.LinearFilter,
+					minFilter: THREE.LinearMipMapLinearFilter,
+					generateMipmaps: false,
+					type: THREE.HalfFloatType,
+					format: THREE.RGBAFormat,
+					colorSpace: THREE.LinearSRGBColorSpace,
+					depthBuffer: false,
+				};
+
+				const rt = new THREE.WebGLCubeRenderTarget( cubeMapSize, params );
+
+				const mipLevels = Math.log( cubeMapSize ) * Math.LOG2E + 1.0;
+				for ( let i = 0; i < mipLevels; i ++ ) rt.texture.mipmaps.push( {} );
+
+				rt.texture.mapping = THREE.CubeReflectionMapping;
+				return rt;
+
+			}
+
+			function renderToCubeTexture( cubeMapRenderTarget, sourceCubeTexture ) {
+
+				const cameras = [];
+
+				for ( let i = 0; i < 6; i ++ ) {
+
+					// negative fov is not an error
+					cameras.push( new THREE.PerspectiveCamera( - 90, 1, 1, 10 ) );
+
+				}
+
+				cameras[ 0 ].up.set( 0, 1, 0 );
+				cameras[ 0 ].lookAt( 1, 0, 0 );
+				cameras[ 1 ].up.set( 0, 1, 0 );
+				cameras[ 1 ].lookAt( - 1, 0, 0 );
+				cameras[ 2 ].up.set( 0, 0, - 1 );
+				cameras[ 2 ].lookAt( 0, 1, 0 );
+				cameras[ 3 ].up.set( 0, 0, 1 );
+				cameras[ 3 ].lookAt( 0, - 1, 0 );
+				cameras[ 4 ].up.set( 0, 1, 0 );
+				cameras[ 4 ].lookAt( 0, 0, 1 );
+				cameras[ 5 ].up.set( 0, 1, 0 );
+				cameras[ 5 ].lookAt( 0, 0, - 1 );
+
+				for ( let i = 0; i < 6; i ++ ) cameras[ i ].updateMatrixWorld();
+
+				const geometry = new THREE.BoxGeometry( 5, 5, 5 );
+
+				const material = new THREE.ShaderMaterial( {
+					name: 'FilterCubemap',
+					uniforms: THREE.UniformsUtils.clone( CubemapFilterShader.uniforms ),
+					vertexShader: CubemapFilterShader.vertexShader,
+					fragmentShader: CubemapFilterShader.fragmentShader,
+					side: THREE.BackSide,
+					blending: THREE.NoBlending,
+				} );
+
+				material.uniforms.cubeTexture.value = sourceCubeTexture;
+
+				const mesh = new THREE.Mesh( geometry, material );
+
+				const currentRenderTarget = renderer.getRenderTarget();
+				const currentXrEnabled = renderer.xr.enabled;
+				renderer.xr.enabled = false;
+
+				for ( let faceIndex = 0; faceIndex < 6; faceIndex ++ ) {
+
+					let mipIndex = 0;
+					let mipSize = cubeMapRenderTarget.width;
+
+					// Render to each texture mip level
+					while ( mipSize >= 1 ) {
+
+						cubeMapRenderTarget.viewport.set( 0, 0, mipSize, mipSize );
+						renderer.setRenderTarget( cubeMapRenderTarget, faceIndex, mipIndex );
+						material.uniforms.mipIndex.value = mipIndex;
+						material.needsUpdate = true;
+						renderer.render( mesh, cameras[ faceIndex ] );
+						mipSize >>= 1;
+						mipIndex ++;
+
+					}
+
+				}
+
+				renderer.setRenderTarget( currentRenderTarget );
+				renderer.xr.enabled = currentXrEnabled;
+
+				mesh.geometry.dispose();
+				mesh.material.dispose();
+
+			}
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				// Create renderer
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 10000 );
+				camera.position.z = 500;
+
+				// Create controls
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.minPolarAngle = Math.PI / 4;
+				controls.maxPolarAngle = Math.PI / 1.5;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				// Load a cube texture
+				const r = 'textures/cube/Park3Med/';
+				const urls = [
+					r + 'px.jpg', r + 'nx.jpg',
+					r + 'py.jpg', r + 'ny.jpg',
+					r + 'pz.jpg', r + 'nz.jpg'
+				];
+
+				loadCubeTexture( urls ).then( ( cubeTexture ) => {
+
+					// Allocate a cube map render target
+					const cubeMapRenderTarget = allocateCubemapRenderTarget( 512 );
+
+					// Render to all the mip levels of cubeMapRenderTarget
+					renderToCubeTexture( cubeMapRenderTarget, cubeTexture );
+
+					// Create geometry
+					const sphere = new THREE.SphereGeometry( 100, 128, 128 );
+					let material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: cubeTexture } );
+
+					let mesh = new THREE.Mesh( sphere, material );
+					mesh.position.set( - 100, 0, 0 );
+					scene.add( mesh );
+
+					material = material.clone();
+					material.envMap = cubeMapRenderTarget.texture;
+
+					mesh = new THREE.Mesh( sphere, material );
+					mesh.position.set( 100, 0, 0 );
+					scene.add( mesh );
+
+				} );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 19 - 2
src/renderers/WebGLRenderer.js

@@ -2119,7 +2119,16 @@ class WebGLRenderer {
 
 				if ( renderTarget.isWebGLCubeRenderTarget ) {
 
-					framebuffer = __webglFramebuffer[ activeCubeFace ];
+					if ( Array.isArray( __webglFramebuffer[ activeCubeFace ] ) ) {
+
+						framebuffer = __webglFramebuffer[ activeCubeFace ][ activeMipmapLevel ];
+
+					} else {
+
+						framebuffer = __webglFramebuffer[ activeCubeFace ];
+
+					}
+
 					isCube = true;
 
 				} else if ( ( capabilities.isWebGL2 && renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) {
@@ -2128,7 +2137,15 @@ class WebGLRenderer {
 
 				} else {
 
-					framebuffer = __webglFramebuffer;
+					if ( Array.isArray( __webglFramebuffer ) ) {
+
+						framebuffer = __webglFramebuffer[ activeMipmapLevel ];
+
+					} else {
+
+						framebuffer = __webglFramebuffer;
+
+					}
 
 				}
 

+ 86 - 12
src/renderers/webgl/WebGLTextures.js

@@ -344,14 +344,32 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			for ( let i = 0; i < 6; i ++ ) {
 
-				_gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );
+				if ( Array.isArray( renderTargetProperties.__webglFramebuffer[ i ] ) ) {
+
+					for ( let level = 0; level < renderTargetProperties.__webglFramebuffer[ i ].length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ][ level ] );
+
+				} else {
+
+					_gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );
+
+				}
+
 				if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );
 
 			}
 
 		} else {
 
-			_gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer );
+			if ( Array.isArray( renderTargetProperties.__webglFramebuffer ) ) {
+
+				for ( let level = 0; level < renderTargetProperties.__webglFramebuffer.length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ level ] );
+
+			} else {
+
+				_gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer );
+
+			}
+
 			if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer );
 			if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer );
 
@@ -1326,7 +1344,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 	// Render targets
 
 	// Setup storage for target texture and bind it to correct framebuffer
-	function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget ) {
+	function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget, level ) {
 
 		const glFormat = utils.convert( texture.format, texture.colorSpace );
 		const glType = utils.convert( texture.type );
@@ -1335,13 +1353,16 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		if ( ! renderTargetProperties.__hasExternalTextures ) {
 
+			const width = Math.max( 1, renderTarget.width >> level );
+			const height = Math.max( 1, renderTarget.height >> level );
+
 			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 );
+				state.texImage3D( textureTarget, level, glInternalFormat, width, height, renderTarget.depth, 0, glFormat, glType, null );
 
 			} else {
 
-				state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+				state.texImage2D( textureTarget, level, glInternalFormat, width, height, 0, glFormat, glType, null );
 
 			}
 
@@ -1355,7 +1376,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		} else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753
 
-			_gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0 );
+			_gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, level );
 
 		}
 
@@ -1576,7 +1597,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		if ( colorTexture !== undefined ) {
 
-			setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D );
+			setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, 0 );
 
 		}
 
@@ -1623,13 +1644,41 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			for ( let i = 0; i < 6; i ++ ) {
 
-				renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();
+				if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) {
+
+					renderTargetProperties.__webglFramebuffer[ i ] = [];
+
+					for ( let level = 0; level < texture.mipmaps.length; level ++ ) {
+
+						renderTargetProperties.__webglFramebuffer[ i ][ level ] = _gl.createFramebuffer();
+
+					}
+
+				} else {
+
+					renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();
+
+				}
 
 			}
 
 		} else {
 
-			renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
+			if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) {
+
+				renderTargetProperties.__webglFramebuffer = [];
+
+				for ( let level = 0; level < texture.mipmaps.length; level ++ ) {
+
+					renderTargetProperties.__webglFramebuffer[ level ] = _gl.createFramebuffer();
+
+				}
+
+			} else {
+
+				renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
+
+			}
 
 			if ( isMultipleRenderTargets ) {
 
@@ -1709,7 +1758,19 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			for ( let i = 0; i < 6; i ++ ) {
 
-				setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i );
+				if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) {
+
+					for ( let level = 0; level < texture.mipmaps.length; level ++ ) {
+
+						setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ][ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level );
+
+					}
+
+				} else {
+
+					setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0 );
+
+				}
 
 			}
 
@@ -1732,7 +1793,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 				state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture );
 				setTextureParameters( _gl.TEXTURE_2D, attachment, supportsMips );
-				setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D );
+				setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, 0 );
 
 				if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) {
 
@@ -1764,7 +1825,20 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			state.bindTexture( glTextureType, textureProperties.__webglTexture );
 			setTextureParameters( glTextureType, texture, supportsMips );
-			setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType );
+
+			if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) {
+
+				for ( let level = 0; level < texture.mipmaps.length; level ++ ) {
+
+					setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, level );
+
+				}
+
+			} else {
+
+				setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, 0 );
+
+			}
 
 			if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {