Browse Source

KTX2Exporter: Fix metadata, add example (#29541)

Don McCurdy 1 year ago
parent
commit
b13d1b1c34

+ 1 - 0
examples/files.json

@@ -503,6 +503,7 @@
 		"misc_exporter_stl",
 		"misc_exporter_usdz",
 		"misc_exporter_exr",
+		"misc_exporter_ktx2",
 		"misc_lookat"
 	],
 	"css2d": [

+ 35 - 12
examples/jsm/exporters/KTX2Exporter.js

@@ -1,4 +1,5 @@
 import {
+	ColorManagement,
 	FloatType,
 	HalfFloatType,
 	UnsignedByteType,
@@ -10,6 +11,7 @@ import {
 	NoColorSpace,
 	LinearSRGBColorSpace,
 	SRGBColorSpace,
+	SRGBTransfer,
 	DataTexture,
 	REVISION,
 } from 'three';
@@ -43,6 +45,13 @@ import {
 	VK_FORMAT_R8G8B8A8_UNORM,
 } from '../libs/ktx-parse.module.js';
 
+/**
+ * References:
+ * - https://github.khronos.org/KTX-Specification/ktxspec.v2.html
+ * - https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html
+ * - https://github.com/donmccurdy/KTX-Parse
+ */
+
 const VK_FORMAT_MAP = {
 
 	[ RGBAFormat ]: {
@@ -95,14 +104,23 @@ const VK_FORMAT_MAP = {
 
 };
 
-const KHR_DF_CHANNEL_MAP = {
+const KHR_DF_CHANNEL_MAP = [
 
-	0: KHR_DF_CHANNEL_RGBSDA_RED,
-	1: KHR_DF_CHANNEL_RGBSDA_GREEN,
-	2: KHR_DF_CHANNEL_RGBSDA_BLUE,
-	3: KHR_DF_CHANNEL_RGBSDA_ALPHA,
+	KHR_DF_CHANNEL_RGBSDA_RED,
+	KHR_DF_CHANNEL_RGBSDA_GREEN,
+	KHR_DF_CHANNEL_RGBSDA_BLUE,
+	KHR_DF_CHANNEL_RGBSDA_ALPHA,
 
-};
+];
+
+// TODO: sampleLower and sampleUpper may change based on color space.
+const KHR_DF_CHANNEL_SAMPLE_LOWER_UPPER = {
+
+	[ FloatType ]: [ 0xbf800000, 0x3f800000 ],
+	[ HalfFloatType ]: [ 0xbf800000, 0x3f800000 ],
+	[ UnsignedByteType ]: [ 0, 255 ],
+
+}
 
 const ERROR_INPUT = 'THREE.KTX2Exporter: Supported inputs are DataTexture, Data3DTexture, or WebGLRenderer and WebGLRenderTarget.';
 const ERROR_FORMAT = 'THREE.KTX2Exporter: Supported formats are RGBAFormat, RGFormat, or RedFormat.';
@@ -172,7 +190,7 @@ export class KTX2Exporter {
 		basicDesc.colorPrimaries = texture.colorSpace === NoColorSpace
 			? KHR_DF_PRIMARIES_UNSPECIFIED
 			: KHR_DF_PRIMARIES_BT709;
-		basicDesc.transferFunction = texture.colorSpace === SRGBColorSpace
+		basicDesc.transferFunction = ColorManagement.getTransfer( texture.colorSpace ) === SRGBTransfer
 			? KHR_DF_TRANSFER_SRGB
 			: KHR_DF_TRANSFER_LINEAR;
 
@@ -188,7 +206,8 @@ export class KTX2Exporter {
 
 			let channelType = KHR_DF_CHANNEL_MAP[ i ];
 
-			if ( texture.colorSpace === LinearSRGBColorSpace || texture.colorSpace === NoColorSpace ) {
+			// Assign KHR_DF_SAMPLE_DATATYPE_LINEAR if the channel is linear _and_ differs from the transfer function.
+			if ( channelType === KHR_DF_CHANNEL_RGBSDA_ALPHA && basicDesc.transferFunction !== KHR_DF_TRANSFER_LINEAR ) {
 
 				channelType |= KHR_DF_SAMPLE_DATATYPE_LINEAR;
 
@@ -204,11 +223,11 @@ export class KTX2Exporter {
 			basicDesc.samples.push( {
 
 				channelType: channelType,
-				bitOffset: i * array.BYTES_PER_ELEMENT,
+				bitOffset: i * array.BYTES_PER_ELEMENT * 8,
 				bitLength: array.BYTES_PER_ELEMENT * 8 - 1,
 				samplePosition: [ 0, 0, 0, 0 ],
-				sampleLower: texture.type === UnsignedByteType ? 0 : - 1,
-				sampleUpper: texture.type === UnsignedByteType ? 255 : 1,
+				sampleLower: KHR_DF_CHANNEL_SAMPLE_LOWER_UPPER[ texture.type ][ 0 ],
+				sampleUpper: KHR_DF_CHANNEL_SAMPLE_LOWER_UPPER[ texture.type ][ 1 ],
 
 			} );
 
@@ -269,7 +288,11 @@ async function toDataTexture( renderer, rtt ) {
 
 	}
 
-	return new DataTexture( view, rtt.width, rtt.height, rtt.texture.format, rtt.texture.type );
+	const texture = new DataTexture( view, rtt.width, rtt.height, rtt.texture.format, rtt.texture.type );
+
+	texture.colorSpace = rtt.texture.colorSpace;
+
+	return texture;
 
 }
 

+ 203 - 0
examples/misc_exporter_ktx2.html

@@ -0,0 +1,203 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - exporter - ktx2</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="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - exporter - ktx2
+		</div>
+
+		<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';
+			import { KTX2Exporter } from 'three/addons/exporters/KTX2Exporter.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let scene, camera, renderer, exporter, mesh, controls, renderTarget, dataTexture;
+
+			const params = {
+				target: 'pmrem',
+				export: exportFile
+			};
+
+			init();
+
+			function init() {
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.toneMapping = THREE.AgXToneMapping;
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				//
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( 10, 0, 0 );
+
+				scene = new THREE.Scene();
+
+				exporter = new KTX2Exporter();
+				const rgbeloader = new RGBELoader();
+
+				//
+
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+				pmremGenerator.compileEquirectangularShader();
+
+				rgbeloader.load( 'textures/equirectangular/venice_sunset_1k.hdr', function ( texture ) {
+
+					texture.mapping = THREE.EquirectangularReflectionMapping;
+
+					renderTarget = pmremGenerator.fromEquirectangular( texture );
+					scene.background = renderTarget.texture;
+
+				} );
+
+				createDataTexture();
+
+				//
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+				controls.rotateSpeed = - 0.25; // negative, to track mouse pointer
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				const gui = new GUI();
+
+				gui.add( params, 'target' ).options( [ 'pmrem', 'data-texture' ] ).onChange( swapScene );
+				gui.add( params, 'export' ).name( 'Export KTX2' );
+				gui.open();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				controls.update();
+				renderer.render( scene, camera );
+
+			}
+
+			function createDataTexture() {
+
+				const normal = new THREE.Vector3();
+				const coord = new THREE.Vector2();
+				const size = 800, radius = 320, factor = Math.PI * 0.5 / radius;
+				const data = new Float32Array( 4 * size * size );
+
+				for ( let i = 0; i < size; i ++ ) {
+
+					for ( let j = 0; j < size; j ++ ) {
+
+						const idx = i * size * 4 + j * 4;
+						coord.set( j, i ).subScalar( size / 2 );
+
+						if ( coord.length() < radius )
+							normal.set(
+								Math.sin( coord.x * factor ),
+								Math.sin( coord.y * factor ),
+								Math.cos( coord.x * factor )
+							);
+						else
+							normal.set( 0, 0, 1 );
+
+						data[ idx + 0 ] = .5 + .5 * normal.x;
+						data[ idx + 1 ] = .5 + .5 * normal.y;
+						data[ idx + 2 ] = .5 + .5 * normal.z;
+						data[ idx + 3 ] = 1.;
+
+					}
+
+				}
+
+				dataTexture = new THREE.DataTexture( data, size, size, THREE.RGBAFormat, THREE.FloatType );
+				dataTexture.needsUpdate = true;
+
+				const material = new THREE.MeshBasicMaterial( { map: dataTexture } );
+				const quad = new THREE.PlaneGeometry( 50, 50 );
+				mesh = new THREE.Mesh( quad, material );
+				mesh.visible = false;
+
+				scene.add( mesh );
+
+			}
+
+			function swapScene() {
+
+				if ( params.target == 'pmrem' ) {
+
+					camera.position.set( 10, 0, 0 );
+					controls.enabled = true;
+					scene.background = renderTarget.texture;
+					mesh.visible = false;
+					renderer.toneMapping = THREE.AgXToneMapping;
+
+				} else {
+
+					camera.position.set( 0, 0, 70 );
+					controls.enabled = false;
+					scene.background = new THREE.Color( 0, 0, 0 );
+					mesh.visible = true;
+					renderer.toneMapping = THREE.NoToneMapping;
+
+				}
+
+			}
+
+			async function exportFile() {
+
+				let result;
+
+				if ( params.target == 'pmrem' )
+					result = await exporter.parse( renderer, renderTarget );
+				else
+					result = await exporter.parse( dataTexture );
+
+				saveArrayBuffer( result, params.target + '.ktx2' );
+
+			}
+
+			function saveArrayBuffer( buffer, filename ) {
+
+				const blob = new Blob( [ buffer ], { type: 'image/ktx2' } );
+				const link = document.createElement( 'a' );
+
+				link.href = URL.createObjectURL( blob );
+				link.download = filename;
+				link.click();
+
+			}
+
+		</script>
+
+	</body>
+</html>

BIN
examples/screenshots/misc_exporter_ktx2.jpg


粤ICP备19079148号