Skip to content

Commit

Permalink
KTX2Exporter: Fix metadata, add example (#29541)
Browse files Browse the repository at this point in the history
  • Loading branch information
donmccurdy authored Oct 3, 2024
1 parent 2513543 commit b13d1b1
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 12 deletions.
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@
"misc_exporter_stl",
"misc_exporter_usdz",
"misc_exporter_exr",
"misc_exporter_ktx2",
"misc_lookat"
],
"css2d": [
Expand Down
47 changes: 35 additions & 12 deletions examples/jsm/exporters/KTX2Exporter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ColorManagement,
FloatType,
HalfFloatType,
UnsignedByteType,
Expand All @@ -10,6 +11,7 @@ import {
NoColorSpace,
LinearSRGBColorSpace,
SRGBColorSpace,
SRGBTransfer,
DataTexture,
REVISION,
} from 'three';
Expand Down Expand Up @@ -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 ]: {
Expand Down Expand Up @@ -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.';
Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand All @@ -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 ],

} );

Expand Down Expand Up @@ -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;

}

Expand Down
203 changes: 203 additions & 0 deletions examples/misc_exporter_ktx2.html
Original file line number Diff line number Diff line change
@@ -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>
Binary file added examples/screenshots/misc_exporter_ktx2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit b13d1b1

Please sign in to comment.