Browse Source

WebGPURenderer: Add support for manual mipmaps for cube maps. (#31639)

Michael Herzog 5 months ago
parent
commit
6403bb9223

+ 1 - 0
examples/files.json

@@ -361,6 +361,7 @@
 		"webgpu_materials_alphahash",
 		"webgpu_materials_arrays",
 		"webgpu_materials_basic",
+		"webgpu_materials_cubemap_mipmaps",
 		"webgpu_materials_displacementmap",
 		"webgpu_materials_envmaps_bpcem",
 		"webgpu_materials_envmaps",

BIN
examples/screenshots/webgpu_materials_cubemap_mipmaps.jpg


+ 176 - 0
examples/webgpu_materials_cubemap_mipmaps.html

@@ -0,0 +1,176 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js WebGPU - 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> - cubemap customized mipmaps demo.<br/>
+			Left: WebGPU generated mipmaps<br/>
+			Right: manual mipmaps<br/>
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.webgpu.js",
+					"three/webgpu": "../build/three.webgpu.js",
+					"three/tsl": "../build/three.tsl.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three/webgpu';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			let container;
+
+			let camera, scene, renderer;
+
+			init();
+
+			// load customized cube texture
+			async function loadCubeTextureWithMipmaps() {
+
+				const path = 'textures/cube/angus/';
+				const format = '.jpg';
+				const mipmaps = [];
+				const maxLevel = 8;
+
+				async function loadCubeTexture( urls ) {
+
+					return new Promise( function ( resolve ) {
+
+						new THREE.CubeTextureLoader().load( urls, function ( cubeTexture ) {
+
+							resolve( cubeTexture );
+
+						} );
+
+
+					} );
+
+				}
+
+				// load mipmaps
+				const pendings = [];
+
+				for ( let level = 0; level <= maxLevel; ++ level ) {
+
+					const urls = [];
+
+					for ( let face = 0; face < 6; ++ face ) {
+
+						urls.push( path + 'cube_m0' + level + '_c0' + face + format );
+
+					}
+
+					const mipmapLevel = level;
+
+					pendings.push( loadCubeTexture( urls ).then( function ( cubeTexture ) {
+
+						mipmaps[ mipmapLevel ] = cubeTexture;
+
+					} ) );
+
+				}
+
+				await Promise.all( pendings );
+
+				const customizedCubeTexture = mipmaps.shift();
+				customizedCubeTexture.mipmaps = mipmaps;
+				customizedCubeTexture.colorSpace = THREE.SRGBColorSpace;
+				customizedCubeTexture.minFilter = THREE.LinearMipMapLinearFilter;
+				customizedCubeTexture.magFilter = THREE.LinearFilter;
+				customizedCubeTexture.generateMipmaps = false;
+				customizedCubeTexture.needsUpdate = true;
+
+				return customizedCubeTexture;
+
+			}
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 10000 );
+				camera.position.z = 500;
+
+				scene = new THREE.Scene();
+
+				loadCubeTextureWithMipmaps().then( function ( cubeTexture ) {
+
+					// model
+					const sphere = new THREE.SphereGeometry( 100, 128, 128 );
+
+					// manual mipmaps
+					let material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: cubeTexture } );
+					material.name = 'manual mipmaps';
+
+					let mesh = new THREE.Mesh( sphere, material );
+					mesh.position.set( 100, 0, 0 );
+					scene.add( mesh );
+
+
+					// auto mipmaps
+					material = material.clone();
+					material.name = 'auto mipmaps';
+
+					const autoCubeTexture = cubeTexture.clone();
+					autoCubeTexture.mipmaps = [];
+					autoCubeTexture.generateMipmaps = true;
+					autoCubeTexture.needsUpdate = true;
+
+					material.envMap = autoCubeTexture;
+
+					mesh = new THREE.Mesh( sphere, material );
+					mesh.position.set( - 100, 0, 0 );
+					scene.add( mesh );
+
+				} );
+
+				//renderer
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				container.appendChild( renderer.domElement );
+
+				//controls
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.minPolarAngle = Math.PI / 4;
+				controls.maxPolarAngle = Math.PI / 1.5;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 6 - 0
src/renderers/common/Textures.js

@@ -242,6 +242,12 @@ class Textures extends DataMap {
 		options.needsMipmaps = this.needsMipmaps( texture );
 		options.levels = options.needsMipmaps ? this.getMipLevels( texture, width, height ) : 1;
 
+		// TODO: Uniformly handle mipmap definitions
+		// Normal textures and compressed cube textures define base level + mips with their mipmap array
+		// Uncompressed cube textures use their mipmap array only for mips (no base level)
+
+		if ( texture.isCubeTexture && texture.mipmaps.length > 0 ) options.levels ++;
+
 		//
 
 		if ( isRenderTarget || texture.isStorageTexture === true ) {

+ 10 - 0
src/renderers/webgl-fallback/utils/WebGLTextureUtils.js

@@ -553,6 +553,7 @@ class WebGLTextureUtils {
 		} else if ( texture.isCubeTexture ) {
 
 			const images = options.images;
+			const mipmaps = texture.mipmaps;
 
 			for ( let i = 0; i < 6; i ++ ) {
 
@@ -560,6 +561,15 @@ class WebGLTextureUtils {
 
 				gl.texSubImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, width, height, glFormat, glType, image );
 
+				for ( let j = 0; j < mipmaps.length; j ++ ) {
+
+					const mipmap = mipmaps[ j ];
+					const image = getImage( mipmap.images[ i ] );
+
+					gl.texSubImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, image.width, image.height, glFormat, glType, image );
+
+				}
+
 			}
 
 		} else if ( texture.isDataArrayTexture || texture.isArrayTexture ) {

+ 27 - 8
src/renderers/webgpu/utils/WebGPUTextureUtils.js

@@ -476,7 +476,7 @@ class WebGPUTextureUtils {
 
 		} else if ( texture.isCubeTexture ) {
 
-			this._copyCubeMapToTexture( options.images, textureData.texture, textureDescriptorGPU, texture.flipY, texture.premultiplyAlpha );
+			this._copyCubeMapToTexture( texture, textureData.texture, textureDescriptorGPU );
 
 		} else {
 
@@ -626,27 +626,46 @@ class WebGPUTextureUtils {
 	 * Uploads cube texture image data to the GPU memory.
 	 *
 	 * @private
-	 * @param {Array} images - The cube image data.
+	 * @param {CubeTexture} texture - The cube texture.
 	 * @param {GPUTexture} textureGPU - The GPU texture.
 	 * @param {Object} textureDescriptorGPU - The GPU texture descriptor.
-	 * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not.
-	 * @param {boolean} premultiplyAlpha - Whether the texture should have its RGB channels premultiplied by the alpha channel or not.
 	 */
-	_copyCubeMapToTexture( images, textureGPU, textureDescriptorGPU, flipY, premultiplyAlpha ) {
+	_copyCubeMapToTexture( texture, textureGPU, textureDescriptorGPU ) {
+
+		const images = texture.images;
+		const mipmaps = texture.mipmaps;
 
 		for ( let i = 0; i < 6; i ++ ) {
 
 			const image = images[ i ];
 
-			const flipIndex = flipY === true ? _flipMap[ i ] : i;
+			const flipIndex = texture.flipY === true ? _flipMap[ i ] : i;
 
 			if ( image.isDataTexture ) {
 
-				this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, flipY );
+				this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY );
 
 			} else {
 
-				this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, flipY, premultiplyAlpha );
+				this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY, texture.premultiplyAlpha );
+
+			}
+
+			for ( let j = 0; j < mipmaps.length; j ++ ) {
+
+				const mipmap = mipmaps[ j ];
+				const image = mipmap.images[ i ];
+
+				if ( image.isDataTexture ) {
+
+					this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY, 0, j + 1 );
+
+				} else {
+
+					this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, texture.flipY, texture.premultiplyAlpha, j + 1 );
+
+				}
+
 
 			}
 

粤ICP备19079148号