Browse Source

KTX2Loader: Add support for ASTC 6x6 format (#26586)

* KTX2Loader: Add support for ASTC 6x6 format
* Add various KTX2 samples
* Update KTX2 screenshot
Don McCurdy 1 year ago
parent
commit
530740c9e8

+ 94 - 61
examples/jsm/loaders/KTX2Loader.js

@@ -30,6 +30,7 @@ import {
 	RGB_PVRTC_4BPPV1_Format,
 	RGB_S3TC_DXT1_Format,
 	RGBA_ASTC_4x4_Format,
+	RGBA_ASTC_6x6_Format,
 	RGBA_BPTC_Format,
 	RGBA_ETC2_EAC_Format,
 	RGBA_PVRTC_4BPPV1_Format,
@@ -59,6 +60,8 @@ import {
 	VK_FORMAT_R8G8_UNORM,
 	VK_FORMAT_R8G8B8A8_SRGB,
 	VK_FORMAT_R8G8B8A8_UNORM,
+	VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
+	VK_FORMAT_ASTC_6x6_UNORM_BLOCK
 } from '../libs/ktx-parse.module.js';
 import { ZSTDDecoder } from '../libs/zstddec.module.js';
 
@@ -298,33 +301,7 @@ class KTX2Loader extends Loader {
 
 		if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) {
 
-			const mipmaps = [];
-			const pendings = [];
-
-			for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) {
-
-				pendings.push( createDataTexture( container, levelIndex ).then( function ( dataTexture ) {
-
-					mipmaps[ levelIndex ] = dataTexture;
-
-				} ) );
-
-			}
-
-			await Promise.all( pendings );
-
-			const texture = mipmaps[ 0 ];
-			texture.mipmaps = mipmaps.map( dt => {
-
-				return {
-					data: dt.source.data,
-					width: dt.source.data.width,
-					height: dt.source.data.height,
-					depth: dt.source.data.depth
-				};
-
-			} );
-			return texture;
+			return createRawTexture( container );
 
 		}
 
@@ -730,7 +707,10 @@ KTX2Loader.BasisWorker = function () {
 };
 
 //
-// DataTexture and Data3DTexture parsing.
+// Parsing for non-Basis textures. These textures are may have supercompression
+// like Zstd, but they do not require transcoding.
+
+const UNCOMPRESSED_FORMATS = new Set( [ RGBAFormat, RGFormat, RedFormat ] );
 
 const FORMAT_MAP = {
 
@@ -749,6 +729,9 @@ const FORMAT_MAP = {
 	[ VK_FORMAT_R8_SRGB ]: RedFormat,
 	[ VK_FORMAT_R8_UNORM ]: RedFormat,
 
+	[ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: RGBA_ASTC_6x6_Format,
+	[ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: RGBA_ASTC_6x6_Format,
+
 };
 
 const TYPE_MAP = {
@@ -768,6 +751,9 @@ const TYPE_MAP = {
 	[ VK_FORMAT_R8_SRGB ]: UnsignedByteType,
 	[ VK_FORMAT_R8_UNORM ]: UnsignedByteType,
 
+	[ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: UnsignedByteType,
+	[ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: UnsignedByteType,
+
 };
 
 const COLOR_SPACE_MAP = {
@@ -776,14 +762,13 @@ const COLOR_SPACE_MAP = {
 	[ VK_FORMAT_R8G8_SRGB ]: SRGBColorSpace,
 	[ VK_FORMAT_R8_SRGB ]: SRGBColorSpace,
 
+	[ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: SRGBColorSpace,
+
 };
 
-async function createDataTexture( container, levelIndex = 0 ) {
+async function createRawTexture( container ) {
 
 	const { vkFormat } = container;
-	const pixelWidth = Math.max( 1, container.pixelWidth >> levelIndex );
-	const pixelHeight = Math.max( 1, container.pixelHeight >> levelIndex );
-	const pixelDepth = Math.max( 1, container.pixelDepth >> levelIndex );
 
 	if ( FORMAT_MAP[ vkFormat ] === undefined ) {
 
@@ -791,16 +776,11 @@ async function createDataTexture( container, levelIndex = 0 ) {
 
 	}
 
-	const level = container.levels[ levelIndex ];
-
-	let levelData;
-	let view;
-
-	if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) {
+	//
 
-		levelData = level.levelData;
+	let zstd;
 
-	} else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) {
+	if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) {
 
 		if ( ! _zstd ) {
 
@@ -814,44 +794,97 @@ async function createDataTexture( container, levelIndex = 0 ) {
 
 		}
 
-		levelData = ( await _zstd ).decode( level.levelData, level.uncompressedByteLength );
+		zstd = await _zstd;
 
-	} else {
+	}
+
+	//
 
-		throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' );
+	const mipmaps = [];
 
-	}
 
-	if ( TYPE_MAP[ vkFormat ] === FloatType ) {
+	for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) {
+
+		const levelWidth = Math.max( 1, container.pixelWidth >> levelIndex );
+		const levelHeight = Math.max( 1, container.pixelHeight >> levelIndex );
+		const levelDepth = container.pixelDepth ? Math.max( 1, container.pixelDepth >> levelIndex ) : 0;
+
+		const level = container.levels[ levelIndex ];
+
+		let levelData;
+
+		if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) {
+
+			levelData = level.levelData;
 
-		view = new Float32Array(
+		} else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) {
 
-			levelData.buffer,
-			levelData.byteOffset,
-			levelData.byteLength / Float32Array.BYTES_PER_ELEMENT
+			levelData = zstd.decode( level.levelData, level.uncompressedByteLength );
 
-		);
+		} else {
+
+			throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' );
+
+		}
 
-	} else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) {
+		let data;
 
-		view = new Uint16Array(
+		if ( TYPE_MAP[ vkFormat ] === FloatType ) {
+
+			data = new Float32Array(
+
+				levelData.buffer,
+				levelData.byteOffset,
+				levelData.byteLength / Float32Array.BYTES_PER_ELEMENT
+
+			);
 
-			levelData.buffer,
-			levelData.byteOffset,
-			levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT
+		} else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) {
 
-		);
+			data = new Uint16Array(
+
+				levelData.buffer,
+				levelData.byteOffset,
+				levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT
+
+			);
+
+		} else {
+
+			data = levelData;
+
+		}
+
+		mipmaps.push( {
+
+			data: data,
+			width: levelWidth,
+			height: levelHeight,
+			depth: levelDepth,
+
+		} );
+
+	}
+
+	let texture;
+
+	if ( UNCOMPRESSED_FORMATS.has( FORMAT_MAP[ vkFormat ] ) ) {
+
+		texture = container.pixelDepth === 0
+		? new DataTexture( mipmaps, container.pixelWidth, container.pixelHeight )
+		: new Data3DTexture( mipmaps, container.pixelWidth, container.pixelHeight, container.pixelDepth );
 
 	} else {
 
-		view = levelData;
+		if ( container.pixelDepth > 0 ) throw new Error( 'THREE.KTX2Loader: Unsupported pixelDepth.' );
+
+		texture = new CompressedTexture( mipmaps, container.pixelWidth, container.pixelHeight );
+
+
 
 	}
-	//
 
-	const texture = pixelDepth === 0
-		? new DataTexture( view, pixelWidth, pixelHeight )
-		: new Data3DTexture( view, pixelWidth, pixelHeight, pixelDepth );
+	texture.mipmaps = mipmaps;
 
 	texture.type = TYPE_MAP[ vkFormat ];
 	texture.format = FORMAT_MAP[ vkFormat ];

BIN
examples/screenshots/webgl_loader_texture_ktx2.jpg


BIN
examples/textures/compressed/2d_astc_6x6.ktx2


BIN
examples/textures/compressed/2d_etc1s.ktx2


BIN
examples/textures/compressed/2d_rgba16_linear.ktx2


BIN
examples/textures/compressed/2d_rgba32_linear.ktx2


BIN
examples/textures/compressed/2d_rgba8.ktx2


BIN
examples/textures/compressed/2d_rgba8_linear.ktx2


BIN
examples/textures/compressed/2d_uastc.ktx2


+ 73 - 40
examples/webgl_loader_texture_ktx2.html

@@ -33,8 +33,47 @@
 
 			import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-			let camera, scene, renderer, controls;
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let camera, scene, renderer, controls, loader, material;
+
+			const SAMPLES = {
+				'BasisU ETC1S': '2d_etc1s.ktx2',
+				'BasisU UASTC': '2d_uastc.ktx2',
+				'RGBA8 sRGB': '2d_rgba8.ktx2',
+				'RGBA8 Linear': '2d_rgba8_linear.ktx2',
+				'RGBA16 Linear': '2d_rgba16_linear.ktx2',
+				'RGBA32 Linear': '2d_rgba32_linear.ktx2',
+				'ASTC 6x6 (mobile)': '2d_astc_6x6.ktx2',
+			};
+
+			const FORMAT_LABELS = {
+				[ THREE.RGBAFormat ]: 'RGBA',
+				[ THREE.RGBA_BPTC_Format ]: 'RGBA_BPTC',
+				[ THREE.RGBA_ASTC_4x4_Format ]: 'RGBA_ASTC_4x4',
+				[ THREE.RGB_S3TC_DXT1_Format ]: 'RGB_S3TC_DXT1',
+				[ THREE.RGBA_S3TC_DXT5_Format ]: 'RGBA_S3TC_DXT5',
+				[ THREE.RGB_PVRTC_4BPPV1_Format ]: 'RGB_PVRTC_4BPPV1',
+				[ THREE.RGBA_PVRTC_4BPPV1_Format ]: 'RGBA_PVRTC_4BPPV1',
+				[ THREE.RGB_ETC1_Format ]: 'RGB_ETC1',
+				[ THREE.RGB_ETC2_Format ]: 'RGB_ETC2',
+				[ THREE.RGBA_ETC2_EAC_Format ]: 'RGB_ETC2_EAC',
+			};
+
+			const TYPE_LABELS = {
+				[ THREE.UnsignedByteType ]: 'UnsignedByteType',
+				[ THREE.ByteType ]: 'ByteType',
+				[ THREE.ShortType ]: 'ShortType',
+				[ THREE.UnsignedShortType ]: 'UnsignedShortType',
+				[ THREE.IntType ]: 'IntType',
+				[ THREE.UnsignedIntType ]: 'UnsignedIntType',
+				[ THREE.FloatType ]: 'FloatType',
+				[ THREE.HalfFloatType ]: 'HalfFloatType',
+			};
+
+			const params = {
+				sample: Object.values( SAMPLES )[ 0 ],
+			};
 
 			init().then( animate );
 
@@ -54,61 +93,31 @@
 				scene.background = new THREE.Color( 0x202020 );
 
 				camera = new THREE.PerspectiveCamera( 60, width / height, 0.1, 100 );
-				camera.position.set( 2, 1.5, 1 );
+				camera.position.set( 0, 0, 2.5 );
 				camera.lookAt( scene.position );
 				scene.add( camera );
 
 				controls = new OrbitControls( camera, renderer.domElement );
-				controls.autoRotate = true;
 
 				// PlaneGeometry UVs assume flipY=true, which compressed textures don't support.
 				const geometry = flipY( new THREE.PlaneGeometry() );
-				const material = new THREE.MeshBasicMaterial( {
+				material = new THREE.MeshBasicMaterial( {
 					color: 0xFFFFFF,
-					side: THREE.DoubleSide
+					side: THREE.DoubleSide,
+					transparent: true,
 				} );
 				const mesh = new THREE.Mesh( geometry, material );
 				scene.add( mesh );
 
-				const formatStrings = {
-					[ THREE.RGBAFormat ]: 'RGBA32',
-					[ THREE.RGBA_BPTC_Format ]: 'RGBA_BPTC',
-					[ THREE.RGBA_ASTC_4x4_Format ]: 'RGBA_ASTC_4x4',
-					[ THREE.RGB_S3TC_DXT1_Format ]: 'RGB_S3TC_DXT1',
-					[ THREE.RGBA_S3TC_DXT5_Format ]: 'RGBA_S3TC_DXT5',
-					[ THREE.RGB_PVRTC_4BPPV1_Format ]: 'RGB_PVRTC_4BPPV1',
-					[ THREE.RGBA_PVRTC_4BPPV1_Format ]: 'RGBA_PVRTC_4BPPV1',
-					[ THREE.RGB_ETC1_Format ]: 'RGB_ETC1',
-					[ THREE.RGB_ETC2_Format ]: 'RGB_ETC2',
-					[ THREE.RGBA_ETC2_EAC_Format ]: 'RGB_ETC2_EAC',
-				};
-
-				// Samples: sample_etc1s.ktx2, sample_uastc.ktx2, sample_uastc_zstd.ktx2
-				const loader = new KTX2Loader()
+				loader = new KTX2Loader()
 					.setTranscoderPath( 'jsm/libs/basis/' )
 					.detectSupport( renderer );
 
+				const gui = new GUI();
 
-				try {
+				gui.add( params, 'sample', SAMPLES ).onChange( loadTexture );
 
-					const texture = await loader.loadAsync( './textures/compressed/sample_uastc_zstd.ktx2' );
-
-					console.info( `transcoded to ${formatStrings[ texture.format ]}` );
-
-					material.map = texture;
-					material.transparent = true;
-
-					material.needsUpdate = true;
-
-				} catch ( e ) {
-
-					console.error( e );
-
-				} finally {
-
-					loader.dispose();
-
-				}
+				await loadTexture( params.sample );
 
 
 			}
@@ -134,6 +143,30 @@
 
 			}
 
+			async function loadTexture( path ) {
+
+				try {
+
+					const texture = await loader.loadAsync( `./textures/compressed/${path}` );
+					texture.minFilter = THREE.NearestMipmapNearestFilter;
+
+					material.map = texture;
+					material.needsUpdate = true;
+
+					console.info( `format: ${ FORMAT_LABELS[ texture.format ] }` );
+					console.info( `type: ${ TYPE_LABELS[ texture.type ] }` );
+
+				} catch ( e ) {
+
+					console.error( e );
+
+				}
+
+				// NOTE: Call `loader.dispose()` when finished loading textures.
+
+
+			}
+
 			/** Correct UVs to be compatible with `flipY=false` textures. */
 			function flipY( geometry ) {