Add ktx
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Copyright 2020 The Khronos Group Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
libktx.js
|
||||
libktx.wasm
|
||||
libktx_read.js
|
||||
libktx_read.wasm
|
||||
msc_basis_transcoder.js
|
||||
msc_basis_transcoder.wasm
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2020 Mark Callow
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JS Binding Tests & Demos</title>
|
||||
<link rel="shortcut icon" href="ktx_favicon.ico" type="image/x-icon" />
|
||||
<style>
|
||||
body {
|
||||
max-width: 500px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<h1>KTX2 with Basis WebGL tests</h1>
|
||||
<p>
|
||||
Example webpages to test the libktx Javascript wrappers and WASM
|
||||
support for transcoding Basis Universal compressed textures. All
|
||||
tests require WebAssembly and WebGL support.
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="libktx-webgl">libktx and WebGL</a>: tests the
|
||||
functionality of the read-write Javascript binding for libktx,
|
||||
which has been compiled to Javascript using Emscripten. Among other
|
||||
things, it reads a .png file, creates a KTX2 texture, encodes it to
|
||||
the universal Basis-LZ format, transcodes it and renders it using
|
||||
WebGL.</li>
|
||||
<li><a href="libktx-read-webgl">Read only libktx and WebGL</a>: tests
|
||||
the read-only JS binding to libktx and its embedded Basisu transcoder,
|
||||
which are compiled to .wasm. It reads a KTX v2 file containing a Basis
|
||||
Universal compressed texture and renders it using WebGL.</li>
|
||||
<li><a href="llt-three/">transcodeImage with Pure JS KTX2 loader for three.js</a>:
|
||||
transcodeImage is a BasisU transcoder method independent of .basis or any container
|
||||
format written in C++ and compiled to .wasm.</li>
|
||||
</ul>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,491 @@
|
||||
/**
|
||||
* @author Don McCurdy / https://www.donmccurdy.com
|
||||
* @author Austin Eng / https://github.com/austinEng
|
||||
* @author Shrek Shao / https://github.com/shrekshao
|
||||
* @author Mark Callow / https:://github.com/MarkCallow
|
||||
*/
|
||||
|
||||
/**
|
||||
* Loader for Texture Payloads in .ktx files including Basis Universal textures.
|
||||
*
|
||||
* Basis Universal is a "supercompressed" GPU texture and texture video
|
||||
* compression system that outputs a highly compressed intermediate file format
|
||||
* (.basis) that can be quickly transcoded to a wide variety of GPU texture
|
||||
* compression formats.
|
||||
*
|
||||
* This loader parallelizes the transcoding process across a configurable number
|
||||
* of web workers, before transferring the transcoded compressed texture back
|
||||
* to the main thread.
|
||||
*/
|
||||
THREE.KTXTextureLoader = function ( manager ) {
|
||||
|
||||
THREE.Loader.call( this, manager );
|
||||
|
||||
this.useAlpha = true;
|
||||
this.transcoderPath = '';
|
||||
this.transcoderBinary = null;
|
||||
this.transcoderPending = null;
|
||||
|
||||
this.workerLimit = 4;
|
||||
this.workerPool = [];
|
||||
this.workerNextTaskID = 1;
|
||||
this.workerSourceURL = '';
|
||||
this.workerConfig = {
|
||||
format: null,
|
||||
useAlpha: true,
|
||||
astcSupported: false,
|
||||
etcSupported: false,
|
||||
dxtSupported: false,
|
||||
pvrtcSupported: false,
|
||||
};
|
||||
};
|
||||
|
||||
THREE.KTXTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
|
||||
|
||||
constructor: THREE.KTXTextureLoader,
|
||||
|
||||
setTranscoderPath: function ( path ) {
|
||||
|
||||
this.transcoderPath = path;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
setWorkerLimit: function ( workerLimit ) {
|
||||
|
||||
this.workerLimit = workerLimit;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
detectSupport: function ( renderer ) {
|
||||
|
||||
var config = this.workerConfig;
|
||||
|
||||
config.astcSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_astc' );
|
||||
config.etcSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_etc1' );
|
||||
config.dxtSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_s3tc' );
|
||||
config.pvrtcSupported = !! renderer.extensions.get( 'WEBGL_compressed_texture_pvrtc' )
|
||||
|| !! renderer.extensions.get( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );
|
||||
|
||||
if (config.astcSupported) {
|
||||
|
||||
config.format = THREE.KTXTextureLoader.BASIS_FORMAT.cTFASTC_4x4;
|
||||
|
||||
} else if ( config.dxtSupported ) {
|
||||
|
||||
config.format = this.useAlpha ? THREE.KTXTextureLoader.BASIS_FORMAT.cTFBC3 : THREE.KTXTextureLoader.BASIS_FORMAT.cTFBC1;
|
||||
|
||||
} else if ( config.pvrtcSupported ) {
|
||||
|
||||
config.format = this.useAlpha ? THREE.KTXTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA : THREE.KTXTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB;
|
||||
|
||||
} else if ( config.etcSupported ) {
|
||||
|
||||
config.format = THREE.KTXTextureLoader.BASIS_FORMAT.cTFETC1;
|
||||
|
||||
} else {
|
||||
|
||||
throw new Error( 'THREE.KTXTextureLoader: No suitable compressed texture format found.' );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
load: function ( url, onLoad, onProgress, onError ) {
|
||||
|
||||
var loader = new THREE.FileLoader( this.manager );
|
||||
|
||||
loader.setResponseType( 'arraybuffer' );
|
||||
|
||||
loader.load( url, ( buffer ) => {
|
||||
|
||||
this._createTexture( buffer )
|
||||
.then( onLoad )
|
||||
.catch( onError );
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} buffer
|
||||
* @return {Promise<THREE.CompressedTexture>}
|
||||
*/
|
||||
_createTexture: function ( buffer ) {
|
||||
|
||||
var worker;
|
||||
var taskID;
|
||||
|
||||
var texturePending = this._getWorker()
|
||||
.then( ( _worker ) => {
|
||||
|
||||
worker = _worker;
|
||||
taskID = this.workerNextTaskID ++;
|
||||
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
|
||||
worker._callbacks[ taskID ] = { resolve, reject };
|
||||
worker._taskCosts[ taskID ] = buffer.byteLength;
|
||||
worker._taskLoad += worker._taskCosts[ taskID ];
|
||||
|
||||
worker.postMessage( { type: 'transcode', id: taskID, buffer }, [ buffer ] );
|
||||
|
||||
} );
|
||||
|
||||
} )
|
||||
.then( ( message ) => {
|
||||
|
||||
var config = this.workerConfig;
|
||||
|
||||
var { width, height, hasAlpha, mipmaps } = message;
|
||||
|
||||
var texture;
|
||||
|
||||
switch(config.format) {
|
||||
case THREE.KTXTextureLoader.BASIS_FORMAT.cTFASTC_4x4:
|
||||
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.KTXTextureLoader.COMPRESSED_RGBA_ASTC_4x4_KHR );
|
||||
break;
|
||||
case THREE.KTXTextureLoader.BASIS_FORMAT.cTFBC1:
|
||||
case THREE.KTXTextureLoader.BASIS_FORMAT.cTFBC3:
|
||||
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.KTXTextureLoader.DXT_FORMAT_MAP[ config.format ], THREE.UnsignedByteType );
|
||||
break;
|
||||
case THREE.KTXTextureLoader.BASIS_FORMAT.cTFETC1:
|
||||
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_ETC1_Format );
|
||||
break;
|
||||
case THREE.KTXTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB:
|
||||
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_PVRTC_4BPPV1_Format );
|
||||
break;
|
||||
case THREE.KTXTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA:
|
||||
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGBA_PVRTC_4BPPV1_Format );
|
||||
break;
|
||||
default:
|
||||
throw new Error( 'THREE.KTXTextureLoader: No supported format available.' );
|
||||
}
|
||||
|
||||
texture.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter;
|
||||
texture.magFilter = THREE.LinearFilter;
|
||||
texture.generateMipmaps = false;
|
||||
texture.needsUpdate = true;
|
||||
|
||||
return texture;
|
||||
|
||||
} );
|
||||
|
||||
texturePending
|
||||
.finally( () => {
|
||||
|
||||
if ( worker && taskID ) {
|
||||
|
||||
worker._taskLoad -= worker._taskCosts[ taskID ];
|
||||
delete worker._callbacks[ taskID ];
|
||||
delete worker._taskCosts[ taskID ];
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
return texturePending;
|
||||
|
||||
},
|
||||
|
||||
_initTranscoder: function () {
|
||||
|
||||
if ( ! this.transcoderBinary ) {
|
||||
|
||||
// Load transcoder wrapper.
|
||||
var jsLoader = new THREE.FileLoader( this.manager );
|
||||
jsLoader.setPath( this.transcoderPath );
|
||||
var jsContent = new Promise( ( resolve, reject ) => {
|
||||
|
||||
jsLoader.load( 'libktx.js', resolve, undefined, reject );
|
||||
|
||||
} );
|
||||
|
||||
// Load transcoder WASM binary.
|
||||
var binaryLoader = new THREE.FileLoader( this.manager );
|
||||
binaryLoader.setPath( this.transcoderPath );
|
||||
binaryLoader.setResponseType( 'arraybuffer' );
|
||||
var binaryContent = new Promise( ( resolve, reject ) => {
|
||||
|
||||
binaryLoader.load( 'libktx.wasm', resolve, undefined, reject );
|
||||
|
||||
} );
|
||||
|
||||
this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
|
||||
.then( ( [ jsContent, binaryContent ] ) => {
|
||||
|
||||
var fn = THREE.KTXTextureLoader.BasisWorker.toString();
|
||||
|
||||
var body = [
|
||||
'/* basis_transcoder.js */',
|
||||
jsContent,
|
||||
'/* worker */',
|
||||
fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
|
||||
].join( '\n' );
|
||||
|
||||
this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
|
||||
this.transcoderBinary = binaryContent;
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
return this.transcoderPending;
|
||||
|
||||
},
|
||||
|
||||
_getWorker: function () {
|
||||
|
||||
return this._initTranscoder().then( () => {
|
||||
|
||||
if ( this.workerPool.length < this.workerLimit ) {
|
||||
|
||||
var worker = new Worker( this.workerSourceURL );
|
||||
|
||||
worker._callbacks = {};
|
||||
worker._taskCosts = {};
|
||||
worker._taskLoad = 0;
|
||||
|
||||
this.workerConfig.useAlpha = this.useAlpha;
|
||||
worker.postMessage( {
|
||||
type: 'init',
|
||||
config: this.workerConfig,
|
||||
transcoderBinary: this.transcoderBinary,
|
||||
} );
|
||||
|
||||
worker.onmessage = function ( e ) {
|
||||
|
||||
var message = e.data;
|
||||
|
||||
switch ( message.type ) {
|
||||
|
||||
case 'transcode':
|
||||
worker._callbacks[ message.id ].resolve( message );
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
worker._callbacks[ message.id ].reject( message );
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error( 'THREE.KTXTextureLoader: Unexpected message, "' + message.type + '"' );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.workerPool.push( worker );
|
||||
|
||||
} else {
|
||||
|
||||
this.workerPool.sort( function ( a, b ) {
|
||||
|
||||
return a._taskLoad > b._taskLoad ? - 1 : 1;
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
return this.workerPool[ this.workerPool.length - 1 ];
|
||||
|
||||
} );
|
||||
|
||||
},
|
||||
|
||||
dispose: function () {
|
||||
|
||||
for ( var i = 0; i < this.workerPool.length; i ++ ) {
|
||||
|
||||
this.workerPool[ i ].terminate();
|
||||
|
||||
}
|
||||
|
||||
this.workerPool.length = 0;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
/* CONSTANTS */
|
||||
|
||||
THREE.KTXTextureLoader.BASIS_FORMAT = {
|
||||
cTFETC1: 0,
|
||||
cTFETC2: 1,
|
||||
cTFBC1: 2,
|
||||
cTFBC3: 3,
|
||||
cTFBC4: 4,
|
||||
cTFBC5: 5,
|
||||
cTFBC7_M6_OPAQUE_ONLY: 6,
|
||||
cTFBC7_M5: 7,
|
||||
cTFPVRTC1_4_RGB: 8,
|
||||
cTFPVRTC1_4_RGBA: 9,
|
||||
cTFASTC_4x4: 10,
|
||||
cTFATC_RGB: 11,
|
||||
cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
|
||||
cTFRGBA32: 13,
|
||||
cTFRGB565: 14,
|
||||
cTFBGR565: 15,
|
||||
cTFRGBA4444: 16,
|
||||
};
|
||||
|
||||
// DXT formats, from:
|
||||
// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
|
||||
THREE.KTXTextureLoader.DXT_FORMAT = {
|
||||
COMPRESSED_RGB_S3TC_DXT1_EXT: 0x83F0,
|
||||
COMPRESSED_RGBA_S3TC_DXT1_EXT: 0x83F1,
|
||||
COMPRESSED_RGBA_S3TC_DXT3_EXT: 0x83F2,
|
||||
COMPRESSED_RGBA_S3TC_DXT5_EXT: 0x83F3,
|
||||
};
|
||||
THREE.KTXTextureLoader.DXT_FORMAT_MAP = {};
|
||||
THREE.KTXTextureLoader.DXT_FORMAT_MAP[ THREE.KTXTextureLoader.BASIS_FORMAT.cTFBC1 ] =
|
||||
THREE.KTXTextureLoader.DXT_FORMAT.COMPRESSED_RGB_S3TC_DXT1_EXT;
|
||||
THREE.KTXTextureLoader.DXT_FORMAT_MAP[ THREE.KTXTextureLoader.BASIS_FORMAT.cTFBC3 ] =
|
||||
THREE.KTXTextureLoader.DXT_FORMAT.COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||
|
||||
// ASTC formats, from:
|
||||
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
|
||||
THREE.KTXTextureLoader.COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
|
||||
|
||||
/* WEB WORKER */
|
||||
|
||||
THREE.KTXTextureLoader.BasisWorker = function () {
|
||||
|
||||
var config;
|
||||
var transcoderPending;
|
||||
var _BasisFile;
|
||||
|
||||
onmessage = function ( e ) {
|
||||
|
||||
var message = e.data;
|
||||
|
||||
switch ( message.type ) {
|
||||
|
||||
case 'init':
|
||||
config = message.config;
|
||||
init( message.transcoderBinary );
|
||||
break;
|
||||
|
||||
case 'transcode':
|
||||
transcoderPending.then( () => {
|
||||
|
||||
try {
|
||||
|
||||
var { width, height, hasAlpha, mipmaps } = transcode( message.buffer );
|
||||
|
||||
var buffers = [];
|
||||
|
||||
for ( var i = 0; i < mipmaps.length; ++ i ) {
|
||||
|
||||
buffers.push( mipmaps[ i ].data.buffer );
|
||||
|
||||
}
|
||||
|
||||
self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps }, buffers );
|
||||
|
||||
} catch ( error ) {
|
||||
|
||||
console.error( error );
|
||||
|
||||
self.postMessage( { type: 'error', id: message.id, error: error.message } );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function init( wasmBinary ) {
|
||||
|
||||
var m;
|
||||
transcoderPending = new Promise( ( resolve ) => {
|
||||
|
||||
m = { wasmBinary, onRuntimeInitialized: resolve };
|
||||
BASIS(m);
|
||||
|
||||
} ).then( () => {
|
||||
|
||||
var { BasisFile, initializeBasis } = m;
|
||||
|
||||
_BasisFile = BasisFile;
|
||||
|
||||
initializeBasis();
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
function transcode( buffer ) {
|
||||
|
||||
var basisFile = new _BasisFile( new Uint8Array( buffer ) );
|
||||
|
||||
var width = basisFile.getImageWidth( 0, 0 );
|
||||
var height = basisFile.getImageHeight( 0, 0 );
|
||||
var levels = basisFile.getNumLevels( 0 );
|
||||
var hasAlpha = basisFile.getHasAlpha();
|
||||
|
||||
function cleanup() {
|
||||
|
||||
basisFile.close();
|
||||
basisFile.delete();
|
||||
|
||||
}
|
||||
|
||||
if ( ! width || ! height || ! levels ) {
|
||||
|
||||
cleanup();
|
||||
throw new Error( 'THREE.KTXTextureLoader: Invalid .basis file' );
|
||||
|
||||
}
|
||||
|
||||
if ( ! basisFile.startTranscoding() ) {
|
||||
|
||||
cleanup();
|
||||
throw new Error( 'THREE.KTXTextureLoader: .startTranscoding failed' );
|
||||
|
||||
}
|
||||
|
||||
var mipmaps = [];
|
||||
|
||||
for ( var mip = 0; mip < levels; mip ++ ) {
|
||||
|
||||
var mipWidth = basisFile.getImageWidth( 0, mip );
|
||||
var mipHeight = basisFile.getImageHeight( 0, mip );
|
||||
var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, config.format ) );
|
||||
|
||||
var status = basisFile.transcodeImage(
|
||||
dst,
|
||||
0,
|
||||
mip,
|
||||
config.format,
|
||||
config.useAlpha,
|
||||
0
|
||||
);
|
||||
|
||||
if ( ! status ) {
|
||||
|
||||
cleanup();
|
||||
throw new Error( 'THREE.KTXTextureLoader: .transcodeImage failed.' );
|
||||
|
||||
}
|
||||
|
||||
mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } );
|
||||
|
||||
}
|
||||
|
||||
cleanup();
|
||||
|
||||
return { width, height, hasAlpha, mipmaps };
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
## Not functional. Under Development.
|
||||
|
||||
## Credits
|
||||
|
||||
Based on BasisU gltf sample.
|
||||
|
||||
* Contributors:
|
||||
* [Don McCurdy](https://www.donmccurdy.com)
|
||||
* [Austin Eng](https://github.com/austinEng)
|
||||
* [Shrek Shao](https://github.com/shrekshao)
|
||||
* [Mark Callow](https://github.com/MarkCallow)
|
||||
* Made with [three.js](https://threejs.org/).
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
{
|
||||
"accessors": [
|
||||
{
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"type": "VEC3",
|
||||
"count": 55381,
|
||||
"min": [
|
||||
-527.3590087890625,
|
||||
-104.39354705810547,
|
||||
-495.3590087890625
|
||||
],
|
||||
"max": [
|
||||
599.3590087890625,
|
||||
-33.72303009033203,
|
||||
561.3590087890625
|
||||
],
|
||||
"bufferView": 0,
|
||||
"name": "mesh-0-0_Accessor_POSITION"
|
||||
},
|
||||
{
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"type": "VEC2",
|
||||
"count": 55381,
|
||||
"min": [
|
||||
0.0000017028407910402166,
|
||||
0.00000289904824057885
|
||||
],
|
||||
"max": [
|
||||
0.9993386268615723,
|
||||
0.9999556541442871
|
||||
],
|
||||
"bufferView": 1,
|
||||
"name": "mesh-0-0_Accessor_TEXCOORD_0"
|
||||
},
|
||||
{
|
||||
"byteOffset": 0,
|
||||
"componentType": 5123,
|
||||
"type": "SCALAR",
|
||||
"count": 151158,
|
||||
"bufferView": 4,
|
||||
"name": "mesh-0-0_Accessor_indices"
|
||||
},
|
||||
{
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"type": "VEC3",
|
||||
"count": 30806,
|
||||
"min": [
|
||||
-527.3590087890625,
|
||||
-105.48443603515625,
|
||||
-495.3590087890625
|
||||
],
|
||||
"max": [
|
||||
599.3590087890625,
|
||||
-26.287290573120117,
|
||||
561.3590087890625
|
||||
],
|
||||
"bufferView": 2,
|
||||
"name": "mesh-1-0_Accessor_POSITION"
|
||||
},
|
||||
{
|
||||
"byteOffset": 0,
|
||||
"componentType": 5126,
|
||||
"type": "VEC2",
|
||||
"count": 30806,
|
||||
"min": [
|
||||
0.00000964943137660157,
|
||||
0.000003775880941248033
|
||||
],
|
||||
"max": [
|
||||
0.9999449253082275,
|
||||
0.9999914169311523
|
||||
],
|
||||
"bufferView": 3,
|
||||
"name": "mesh-1-0_Accessor_TEXCOORD_0"
|
||||
},
|
||||
{
|
||||
"byteOffset": 302316,
|
||||
"componentType": 5123,
|
||||
"type": "SCALAR",
|
||||
"count": 58476,
|
||||
"bufferView": 4,
|
||||
"name": "mesh-1-0_Accessor_indices"
|
||||
}
|
||||
],
|
||||
"asset": {
|
||||
"generator": "obj2gltf",
|
||||
"version": "2.0"
|
||||
},
|
||||
"buffers": [
|
||||
{
|
||||
"name": "mesh-0-0_Buffer_POSITION",
|
||||
"byteLength": 2143008,
|
||||
"uri": "mesh-0-0_Buffer_POSITION.bin"
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 664572,
|
||||
"byteOffset": 0,
|
||||
"target": 34962,
|
||||
"name": "bufferView_0",
|
||||
"byteStride": 12
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 443048,
|
||||
"byteOffset": 664572,
|
||||
"target": 34962,
|
||||
"name": "bufferView_0",
|
||||
"byteStride": 8
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 369672,
|
||||
"byteOffset": 1107620,
|
||||
"target": 34962,
|
||||
"name": "bufferView_0",
|
||||
"byteStride": 12
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 246448,
|
||||
"byteOffset": 1477292,
|
||||
"target": 34962,
|
||||
"name": "bufferView_0",
|
||||
"byteStride": 8
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 419268,
|
||||
"byteOffset": 1723740,
|
||||
"target": 34963,
|
||||
"name": "bufferView_1"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"name": "Texture",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorTexture": {
|
||||
"index": 0,
|
||||
"texCoord": 0
|
||||
},
|
||||
"metallicFactor": 0,
|
||||
"roughnessFactor": 1,
|
||||
"baseColorFactor": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
"emissiveTexture": {
|
||||
"index": 0,
|
||||
"texCoord": 0
|
||||
},
|
||||
"alphaMode": "OPAQUE",
|
||||
"doubleSided": false,
|
||||
"emissiveFactor": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"POSITION": 0,
|
||||
"TEXCOORD_0": 1
|
||||
},
|
||||
"indices": 2,
|
||||
"material": 0,
|
||||
"mode": 4
|
||||
}
|
||||
],
|
||||
"name": "mesh-split_1"
|
||||
},
|
||||
{
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"POSITION": 3,
|
||||
"TEXCOORD_0": 4
|
||||
},
|
||||
"indices": 5,
|
||||
"material": 0,
|
||||
"mode": 4
|
||||
}
|
||||
],
|
||||
"name": "mesh-split_2"
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"children": [
|
||||
1
|
||||
],
|
||||
"name": "rootNode_0",
|
||||
"mesh": 0
|
||||
},
|
||||
{
|
||||
"mesh": 1
|
||||
}
|
||||
],
|
||||
"samplers": [
|
||||
{
|
||||
"magFilter": 9729,
|
||||
"minFilter": 9729,
|
||||
"wrapS": 33071,
|
||||
"wrapT": 33071,
|
||||
"name": "sampler_0"
|
||||
}
|
||||
],
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"nodes": [
|
||||
0
|
||||
],
|
||||
"name": "scene"
|
||||
}
|
||||
],
|
||||
"textures": [
|
||||
{
|
||||
"sampler": 0,
|
||||
"name": "textureAtlas",
|
||||
"extensions": {
|
||||
"GOOGLE_texture_basis": {
|
||||
"source": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"images": [
|
||||
{
|
||||
"name": "textureAtlasImage",
|
||||
"mimeType": "image/basis",
|
||||
"uri": "textureAtlasImage.basis"
|
||||
}
|
||||
],
|
||||
"extensionsUsed": [
|
||||
"GOOGLE_texture_basis"
|
||||
],
|
||||
"extensionsRequired": [
|
||||
"GOOGLE_texture_basis"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"accessors" : [
|
||||
{
|
||||
"bufferView" : 0,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5123,
|
||||
"count" : 36,
|
||||
"max" : [
|
||||
35
|
||||
],
|
||||
"min" : [
|
||||
0
|
||||
],
|
||||
"type" : "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView" : 1,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 36,
|
||||
"max" : [
|
||||
1.000000,
|
||||
1.000000,
|
||||
1.000001
|
||||
],
|
||||
"min" : [
|
||||
-1.000000,
|
||||
-1.000000,
|
||||
-1.000000
|
||||
],
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 2,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 36,
|
||||
"max" : [
|
||||
1.000000,
|
||||
1.000000,
|
||||
1.000000
|
||||
],
|
||||
"min" : [
|
||||
-1.000000,
|
||||
-1.000000,
|
||||
-1.000000
|
||||
],
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 3,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 36,
|
||||
"max" : [
|
||||
1.000000,
|
||||
-0.000000,
|
||||
-0.000000,
|
||||
1.000000
|
||||
],
|
||||
"min" : [
|
||||
0.000000,
|
||||
-0.000000,
|
||||
-1.000000,
|
||||
-1.000000
|
||||
],
|
||||
"type" : "VEC4"
|
||||
},
|
||||
{
|
||||
"bufferView" : 4,
|
||||
"byteOffset" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 36,
|
||||
"max" : [
|
||||
1.000000,
|
||||
1.000000
|
||||
],
|
||||
"min" : [
|
||||
-1.000000,
|
||||
-1.000000
|
||||
],
|
||||
"type" : "VEC2"
|
||||
}
|
||||
],
|
||||
"asset" : {
|
||||
"generator" : "VKTS glTF 2.0 exporter",
|
||||
"version" : "2.0"
|
||||
},
|
||||
"bufferViews" : [
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 72,
|
||||
"byteOffset" : 0,
|
||||
"target" : 34963
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 432,
|
||||
"byteOffset" : 72,
|
||||
"target" : 34962
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 432,
|
||||
"byteOffset" : 504,
|
||||
"target" : 34962
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 576,
|
||||
"byteOffset" : 936,
|
||||
"target" : 34962
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 288,
|
||||
"byteOffset" : 1512,
|
||||
"target" : 34962
|
||||
}
|
||||
],
|
||||
"buffers" : [
|
||||
{
|
||||
"byteLength" : 1800,
|
||||
"uri" : "Cube.bin"
|
||||
}
|
||||
],
|
||||
"images" : [
|
||||
{
|
||||
"uri" : "Cube_BaseColor.png"
|
||||
},
|
||||
{
|
||||
"uri" : "Cube_MetallicRoughness.png"
|
||||
}
|
||||
],
|
||||
"materials" : [
|
||||
{
|
||||
"name" : "Cube",
|
||||
"pbrMetallicRoughness" : {
|
||||
"baseColorTexture" : {
|
||||
"index" : 0
|
||||
},
|
||||
"metallicRoughnessTexture" : {
|
||||
"index" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes" : [
|
||||
{
|
||||
"name" : "Cube",
|
||||
"primitives" : [
|
||||
{
|
||||
"attributes" : {
|
||||
"NORMAL" : 2,
|
||||
"POSITION" : 1,
|
||||
"TANGENT" : 3,
|
||||
"TEXCOORD_0" : 4
|
||||
},
|
||||
"indices" : 0,
|
||||
"material" : 0,
|
||||
"mode" : 4
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes" : [
|
||||
{
|
||||
"mesh" : 0,
|
||||
"name" : "Cube"
|
||||
}
|
||||
],
|
||||
"samplers" : [
|
||||
{}
|
||||
],
|
||||
"scene" : 0,
|
||||
"scenes" : [
|
||||
{
|
||||
"nodes" : [
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures" : [
|
||||
{
|
||||
"sampler" : 0,
|
||||
"source" : 0
|
||||
},
|
||||
{
|
||||
"sampler" : 0,
|
||||
"source" : 1
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 871 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 319 B |
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<head>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@v0.108.0"></script>
|
||||
<script src="GLTFLoader.js"></script>
|
||||
<script src="KTXTextureLoader.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@v0.104.0/examples/js/controls/OrbitControls.js"></script>
|
||||
<!-- <script src="../transcoder/build/basis_transcoder.js"></script> -->
|
||||
<style>
|
||||
html, body { width:100%; height:100%; margin:0; padding:0; }
|
||||
canvas { display:block; }
|
||||
#panel { position: absolute; top: 10px; left: 10px; color: white; background-color:rgba(0.3, 0.3, 0.3, 0.3); padding: 0.5em; max-width: 400px;}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="panel">
|
||||
<strong>Transcoding a KTX2 file with a BasisU PayLoad glTF Demo</strong>
|
||||
<div id="log"></div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
const renderer = new THREE.WebGLRenderer( { antialias: true } );
|
||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
||||
renderer.gammaOutput = true;
|
||||
renderer.gammaFactor = 2.2;
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color( 0xf0f0f0 );
|
||||
|
||||
const light = new THREE.AmbientLight();
|
||||
scene.add( light );
|
||||
|
||||
const light2 = new THREE.PointLight();
|
||||
scene.add( light2 );
|
||||
|
||||
const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
|
||||
camera.position.set( 8, 6, 5 );
|
||||
camera.lookAt( new THREE.Vector3( 0, -2, 0 ) );
|
||||
|
||||
const controls = new THREE.OrbitControls( camera, renderer.domElement );
|
||||
controls.autoRotate = true;
|
||||
|
||||
// Create BasisTextureLoader and detect supported target formats.
|
||||
|
||||
const ktxLoader = new THREE.KTXTextureLoader();
|
||||
//basisLoader.setTranscoderPath( '../transcoder/build/' );
|
||||
ktxLoader.setTranscoderPath( '..' );
|
||||
//ktxLoader.useAlpha = false;
|
||||
ktxLoader.detectSupport( renderer );
|
||||
|
||||
let formatName = 'Unknown format';
|
||||
switch(basisLoader.workerConfig.format)
|
||||
{
|
||||
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4:
|
||||
formatName = 'ASTC';
|
||||
break;
|
||||
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1:
|
||||
formatName = 'BC1';
|
||||
break;
|
||||
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3:
|
||||
formatName = 'BC3';
|
||||
break;
|
||||
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB:
|
||||
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA:
|
||||
formatName = 'PVRTC';
|
||||
break;
|
||||
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1:
|
||||
formatName = 'ETC1';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
log(`Transcode to: <strong>${formatName}</strong>`);
|
||||
|
||||
// Register KTXTextureLoader for .ktx and .ktx2 extension.
|
||||
|
||||
//THREE.Loader.Handlers.add( /\.basis$/, basisLoader );
|
||||
THREE.Loader.Handlers.add( /\.ktx$/, ktxTexture );
|
||||
THREE.Loader.Handlers.add( /\.ktx2$/, ktxTexture );
|
||||
|
||||
// Create GLTFLoader, load model, and render.
|
||||
|
||||
const loader = new THREE.GLTFLoader();
|
||||
|
||||
loader.load( 'assets/Cube.gltf', ( gltf ) => {
|
||||
|
||||
const model = gltf.scene;
|
||||
model.scale.set( 0.01, 0.01, 0.01 );
|
||||
|
||||
scene.add( model );
|
||||
|
||||
document.body.appendChild( renderer.domElement );
|
||||
|
||||
animate();
|
||||
|
||||
}, undefined, ( e ) => console.error( e ) );
|
||||
|
||||
// Main render loop.
|
||||
|
||||
function animate() {
|
||||
|
||||
requestAnimationFrame( animate );
|
||||
controls.update();
|
||||
renderer.render( scene, camera );
|
||||
|
||||
}
|
||||
|
||||
// Support viewport resizing.
|
||||
|
||||
window.addEventListener( 'resize', () => {
|
||||
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
||||
|
||||
}, false );
|
||||
|
||||
function log(s) {
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = s;
|
||||
document.getElementById('log').appendChild(div);
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 563 KiB |
@@ -0,0 +1,48 @@
|
||||
<!doctype html>
|
||||
<!-- Copyright 2024 Mark Callow.
|
||||
SPDX-License-Identifier: Apache-2.0 -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>libktx.js Demo with WebGL</title>
|
||||
<link rel="stylesheet" href="../webgl.css" type="text/css">
|
||||
<link rel="shortcut icon" href="../ktx_favicon.ico" type="image/x-icon" />
|
||||
<style>
|
||||
body {
|
||||
/* Set margins to center the page. */
|
||||
margin: 2em auto 2em auto;
|
||||
max-width: 55em;
|
||||
}
|
||||
/* Center the canvas too. */
|
||||
canvas {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
#panel {
|
||||
color: white;
|
||||
background-color:rgba(0.3, 0.3, 0.3, 0.3);
|
||||
padding: 0.5em;
|
||||
max-width: 55em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="panel">
|
||||
<h2>libktx Read-only Javascript Binding Test</h2>
|
||||
<p>This test uses the read-only Javascript binding for libktx,
|
||||
which has been compiled to Javascript using Emscripten. It uses a
|
||||
KTX2 file with a Basis Universal-compressed payload. It has
|
||||
transcoded the images to <b id='format'>FORMAT</b>.
|
||||
</p>
|
||||
</div>
|
||||
<p></p>
|
||||
|
||||
<canvas id="glcanvas" width="640" height="480"></canvas>
|
||||
</body>
|
||||
|
||||
<script src="../gl-matrix.js"></script>
|
||||
<script src="../libktx_read.js"></script>
|
||||
<script src="libktx-read-test.js"></script>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,733 @@
|
||||
/*
|
||||
* Copyright 2015-2020 MDN Contributors
|
||||
* Copyright 2024 Mark Callow
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
This code originated from sample 7 in the MDN WebGL examples
|
||||
at https://github.com/mdn/webgl-examples which is licensed
|
||||
under Creative Commons Zero v1.0 Universal. Modifications
|
||||
made here are also licensed under CC0v1.
|
||||
*/
|
||||
|
||||
var cubeRotation = 0.0;
|
||||
var gl;
|
||||
var texture;
|
||||
|
||||
var astcSupported = false;
|
||||
var etcSupported = false;
|
||||
var dxtSupported = false;
|
||||
var pvrtcSupported = false;
|
||||
|
||||
//
|
||||
// Start here
|
||||
//
|
||||
const canvas = document.querySelector('#glcanvas');
|
||||
gl = canvas.getContext('webgl2');
|
||||
|
||||
// If we don't have a GL context, give up now
|
||||
if (!gl) {
|
||||
alert('Unable to initialize WebGL. Your browser or machine may not support it.');
|
||||
} else {
|
||||
createKtxReadModule({preinitializedWebGLContext: gl}).then(instance => {
|
||||
window.ktx = instance;
|
||||
// Make existing WebGL context current for Emscripten OpenGL.
|
||||
ktx.GL.makeContextCurrent(
|
||||
ktx.GL.createContext(document.getElementById("glcanvas"),
|
||||
{ majorVersion: 2.0 })
|
||||
);
|
||||
main()
|
||||
});
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
texture = loadTexture(gl, 'ktx_app_basis.ktx2');
|
||||
|
||||
astcSupported = !!gl.getExtension('WEBGL_compressed_texture_astc');
|
||||
etcSupported = !!gl.getExtension('WEBGL_compressed_texture_etc1');
|
||||
dxtSupported = !!gl.getExtension('WEBGL_compressed_texture_s3tc');
|
||||
pvrtcSupported = !!(gl.getExtension('WEBGL_compressed_texture_pvrtc')) || !!(gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc'));
|
||||
|
||||
// Vertex shader program
|
||||
|
||||
const vsSource = `
|
||||
attribute vec4 aVertexPosition;
|
||||
attribute vec3 aVertexNormal;
|
||||
attribute vec3 aTextureCoord;
|
||||
|
||||
uniform mat4 uNormalMatrix;
|
||||
uniform mat4 uModelViewMatrix;
|
||||
uniform mat4 uProjectionMatrix;
|
||||
uniform mat3 uUVMatrix;
|
||||
|
||||
varying highp vec2 vTextureCoord;
|
||||
varying highp vec3 vLighting;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
|
||||
//vTextureCoord.x = aTextureCoord.x;
|
||||
// Invert Y coordinate to account for PNG top-left origin.
|
||||
//vTextureCoord.y = aTextureCoord.y * -1.0 + 1.0;
|
||||
vTextureCoord = vec2(uUVMatrix * aTextureCoord);
|
||||
|
||||
// Apply lighting effect
|
||||
|
||||
highp vec3 ambientLight = vec3(0.3, 0.3, 0.3);
|
||||
highp vec3 directionalLightColor = vec3(1, 1, 1);
|
||||
highp vec3 directionalVector = normalize(vec3(0.85, 0.8, 0.75));
|
||||
|
||||
highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0);
|
||||
|
||||
highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0);
|
||||
vLighting = ambientLight + (directionalLightColor * directional);
|
||||
}
|
||||
`;
|
||||
|
||||
// Fragment shader program
|
||||
|
||||
const fsSource = `
|
||||
varying highp vec2 vTextureCoord;
|
||||
varying highp vec3 vLighting;
|
||||
|
||||
uniform sampler2D uSampler;
|
||||
|
||||
highp vec3 srgb_encode(highp vec3 color) {
|
||||
highp float r = color.r < 0.0031308 ? 12.92 * color.r : 1.055 * pow(color.r, 1.0/2.4) - 0.055;
|
||||
highp float g = color.g < 0.0031308 ? 12.92 * color.g : 1.055 * pow(color.g, 1.0/2.4) - 0.055;
|
||||
highp float b = color.b < 0.0031308 ? 12.92 * color.b : 1.055 * pow(color.b, 1.0/2.4) - 0.055;
|
||||
return vec3(r, g, b);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
highp vec3 vertexColor = vec3(0.9, 0.9, 0.9);
|
||||
highp vec4 texelColor = texture2D(uSampler, vTextureCoord);
|
||||
highp vec4 fragcolor;
|
||||
// DECAL
|
||||
fragcolor.rgb = vertexColor.rgb * (1.0 - texelColor.a) + texelColor.rgb * texelColor.a;
|
||||
fragcolor.a = texelColor.a;
|
||||
fragcolor.rgb *= vLighting;
|
||||
fragcolor.rgb = srgb_encode(fragcolor.rgb);
|
||||
gl_FragColor = fragcolor;
|
||||
}
|
||||
`;
|
||||
|
||||
// Initialize a shader program; this is where all the lighting
|
||||
// for the vertices and so forth is established.
|
||||
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
|
||||
|
||||
// Collect all the info needed to use the shader program.
|
||||
// Look up which attributes our shader program is using
|
||||
// for aVertexPosition, aVertexNormal, aTextureCoord,
|
||||
// and look up uniform locations.
|
||||
const programInfo = {
|
||||
program: shaderProgram,
|
||||
attribLocations: {
|
||||
vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
|
||||
vertexNormal: gl.getAttribLocation(shaderProgram, 'aVertexNormal'),
|
||||
textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'),
|
||||
},
|
||||
uniformLocations: {
|
||||
projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
|
||||
modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
|
||||
normalMatrix: gl.getUniformLocation(shaderProgram, 'uNormalMatrix'),
|
||||
uvMatrix: gl.getUniformLocation(shaderProgram, 'uUVMatrix'),
|
||||
uSampler: gl.getUniformLocation(shaderProgram, 'uSampler'),
|
||||
},
|
||||
};
|
||||
|
||||
// Here's where we call the routine that builds all the
|
||||
// objects we'll be drawing.
|
||||
const buffers = initBuffers(gl);
|
||||
|
||||
var then = 0;
|
||||
|
||||
// Draw the scene repeatedly
|
||||
function render(now) {
|
||||
now *= 0.001; // convert to seconds
|
||||
const deltaTime = now - then;
|
||||
then = now;
|
||||
|
||||
drawScene(gl, programInfo, buffers, texture, deltaTime);
|
||||
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
|
||||
//
|
||||
// initBuffers
|
||||
//
|
||||
// Initialize the buffers we'll need. For this demo, we just
|
||||
// have one object -- a simple three-dimensional cube.
|
||||
//
|
||||
function initBuffers(gl) {
|
||||
|
||||
// Create a buffer for the cube's vertex positions.
|
||||
|
||||
const positionBuffer = gl.createBuffer();
|
||||
|
||||
// Select the positionBuffer as the one to apply buffer
|
||||
// operations to from here out.
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
|
||||
// Now create an array of positions for the cube.
|
||||
|
||||
const positions = [
|
||||
// Front face
|
||||
-1.0, -1.0, 1.0,
|
||||
1.0, -1.0, 1.0,
|
||||
1.0, 1.0, 1.0,
|
||||
-1.0, 1.0, 1.0,
|
||||
|
||||
// Back face
|
||||
-1.0, -1.0, -1.0,
|
||||
-1.0, 1.0, -1.0,
|
||||
1.0, 1.0, -1.0,
|
||||
1.0, -1.0, -1.0,
|
||||
|
||||
// Top face
|
||||
-1.0, 1.0, -1.0,
|
||||
-1.0, 1.0, 1.0,
|
||||
1.0, 1.0, 1.0,
|
||||
1.0, 1.0, -1.0,
|
||||
|
||||
// Bottom face
|
||||
-1.0, -1.0, -1.0,
|
||||
1.0, -1.0, -1.0,
|
||||
1.0, -1.0, 1.0,
|
||||
-1.0, -1.0, 1.0,
|
||||
|
||||
// Right face
|
||||
1.0, -1.0, -1.0,
|
||||
1.0, 1.0, -1.0,
|
||||
1.0, 1.0, 1.0,
|
||||
1.0, -1.0, 1.0,
|
||||
|
||||
// Left face
|
||||
-1.0, -1.0, -1.0,
|
||||
-1.0, -1.0, 1.0,
|
||||
-1.0, 1.0, 1.0,
|
||||
-1.0, 1.0, -1.0,
|
||||
];
|
||||
|
||||
// Now pass the list of positions into WebGL to build the
|
||||
// shape. We do this by creating a Float32Array from the
|
||||
// JavaScript array, then use it to fill the current buffer.
|
||||
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
||||
|
||||
// Set up the normals for the vertices, so that we can compute lighting.
|
||||
|
||||
const normalBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
|
||||
|
||||
const vertexNormals = [
|
||||
// Front
|
||||
0.0, 0.0, 1.0,
|
||||
0.0, 0.0, 1.0,
|
||||
0.0, 0.0, 1.0,
|
||||
0.0, 0.0, 1.0,
|
||||
|
||||
// Back
|
||||
0.0, 0.0, -1.0,
|
||||
0.0, 0.0, -1.0,
|
||||
0.0, 0.0, -1.0,
|
||||
0.0, 0.0, -1.0,
|
||||
|
||||
// Top
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
|
||||
// Bottom
|
||||
0.0, -1.0, 0.0,
|
||||
0.0, -1.0, 0.0,
|
||||
0.0, -1.0, 0.0,
|
||||
0.0, -1.0, 0.0,
|
||||
|
||||
// Right
|
||||
1.0, 0.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
|
||||
// Left
|
||||
-1.0, 0.0, 0.0,
|
||||
-1.0, 0.0, 0.0,
|
||||
-1.0, 0.0, 0.0,
|
||||
-1.0, 0.0, 0.0
|
||||
];
|
||||
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexNormals),
|
||||
gl.STATIC_DRAW);
|
||||
|
||||
// Now set up the texture coordinates for the faces.
|
||||
|
||||
const textureCoordBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
|
||||
|
||||
const textureCoordinates = [
|
||||
// Front
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0,
|
||||
// Back
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0,
|
||||
// Top
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0,
|
||||
// Bottom
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0,
|
||||
// Right
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0,
|
||||
// Left
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0,
|
||||
];
|
||||
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates),
|
||||
gl.STATIC_DRAW);
|
||||
|
||||
// Build the element array buffer; this specifies the indices
|
||||
// into the vertex arrays for each face's vertices.
|
||||
|
||||
const indexBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||
|
||||
// This array defines each face as two triangles, using the
|
||||
// indices into the vertex array to specify each triangle's
|
||||
// position.
|
||||
|
||||
const indices = [
|
||||
0, 1, 2, 0, 2, 3, // front
|
||||
4, 5, 6, 4, 6, 7, // back
|
||||
8, 9, 10, 8, 10, 11, // top
|
||||
12, 13, 14, 12, 14, 15, // bottom
|
||||
16, 17, 18, 16, 18, 19, // right
|
||||
20, 21, 22, 20, 22, 23, // left
|
||||
];
|
||||
|
||||
// Now send the element array to GL
|
||||
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
|
||||
new Uint16Array(indices), gl.STATIC_DRAW);
|
||||
|
||||
return {
|
||||
position: positionBuffer,
|
||||
normal: normalBuffer,
|
||||
textureCoord: textureCoordBuffer,
|
||||
indices: indexBuffer,
|
||||
};
|
||||
}
|
||||
|
||||
function elem(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
// Upload content of a ktxTexture to WebGL.
|
||||
//
|
||||
// Returns the created WebGL texture object and matching texture target.
|
||||
//
|
||||
// needs Emscripten's OpenGL ES emulation.
|
||||
function uploadTextureToGl(gl, ktexture) {
|
||||
const { transcode_fmt } = ktx;
|
||||
var formatString;
|
||||
|
||||
if (ktexture.needsTranscoding) {
|
||||
var format;
|
||||
if (astcSupported) {
|
||||
formatString = 'ASTC';
|
||||
format = transcode_fmt.ASTC_4x4_RGBA;
|
||||
} else if (dxtSupported) {
|
||||
formatString = ktexture.numComponents == 4 ? 'BC3' : 'BC1';
|
||||
format = transcode_fmt.BC1_OR_3;
|
||||
} else if (pvrtcSupported) {
|
||||
formatString = 'PVRTC1';
|
||||
format = transcode_fmt.PVRTC1_4_RGBA;
|
||||
} else if (etcSupported) {
|
||||
formatString = 'ETC';
|
||||
format = transcode_fmt.ETC;
|
||||
} else {
|
||||
formatString = 'RGBA4444';
|
||||
format = transcode_fmt.RGBA4444;
|
||||
}
|
||||
if (ktexture.transcodeBasis(format, 0) != ktx.error_code.SUCCESS) {
|
||||
alert('Texture transcode failed. See console for details.');
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const result = ktexture.glUpload();
|
||||
if (result.error != gl.NO_ERROR) {
|
||||
alert('WebGL error when uploading texture, code = '
|
||||
+ result.error.toString(16));
|
||||
return undefined;
|
||||
}
|
||||
if (result.object === undefined) {
|
||||
alert('Texture upload failed. See console for details.');
|
||||
return undefined;
|
||||
}
|
||||
if (result.target != gl.TEXTURE_2D) {
|
||||
alert('Loaded texture is not a TEXTURE2D.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
target: result.target,
|
||||
object: result.object,
|
||||
format: formatString,
|
||||
uvMatrix: null
|
||||
}
|
||||
}
|
||||
|
||||
function createPlaceholderTexture(gl, color)
|
||||
{
|
||||
// // Must create texture via Emscripten so it knows of it.
|
||||
// var texName;
|
||||
// ktx.GL._glGenTextures(1, texName);
|
||||
// texture = ktx.GL.textures[texName];
|
||||
// Since it doesn't seem possible to get the above to work
|
||||
// use a placeholder WebGLTexture object for a temporary
|
||||
// image.
|
||||
const placeholder = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, placeholder);
|
||||
|
||||
const level = 0;
|
||||
const internalFormat = gl.RGBA;
|
||||
const width = 1;
|
||||
const height = 1;
|
||||
const border = 0;
|
||||
const srcFormat = gl.RGBA;
|
||||
const srcType = gl.UNSIGNED_BYTE;
|
||||
const pixel = new Uint8Array(color);
|
||||
|
||||
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
|
||||
width, height, border, srcFormat, srcType,
|
||||
pixel);
|
||||
return {
|
||||
target: gl.TEXTURE_2D,
|
||||
object: placeholder,
|
||||
format: "",
|
||||
uvMatrix: mat3.create()
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Sets the uvMatrix for a texture.
|
||||
//
|
||||
// The WebGL texture object is expected to have been created from the
|
||||
// content of the ktxTexture object. The matrix is adjusted according
|
||||
// to the orientation in the ktxTexture object.
|
||||
//
|
||||
function setUVMatrix(texture, inMatrix, ktexture) {
|
||||
texture.uvMatrix = inMatrix;
|
||||
if (ktexture.orientation.x == ktx.OrientationX.LEFT) {
|
||||
mat3.translate(texture.uvMatrix, texture.uvMatrix, [1.0, 0.0]);
|
||||
mat3.scale(texture.uvMatrix, texture.uvMatrix, [-1.0, 1.0]);
|
||||
}
|
||||
if (ktexture.orientation.y == ktx.OrientationY.DOWN) {
|
||||
mat3.translate(texture.uvMatrix, texture.uvMatrix, [0.0, 1.0]);
|
||||
mat3.scale(texture.uvMatrix, texture.uvMatrix, [1.0, -1.0]);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Binds a texture and sets suitable texture parameters.
|
||||
//
|
||||
// The WebGL texture object is expected to have been created from the
|
||||
// content of the ktxTexture object.
|
||||
//
|
||||
function setTexParameters(texture, ktexture) {
|
||||
gl.bindTexture(texture.target, texture.object);
|
||||
|
||||
if (ktexture.numLevels > 1 || ktexture.generateMipmaps) {
|
||||
// Enable bilinear mipmapping.
|
||||
gl.texParameteri(texture.target,
|
||||
gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
|
||||
} else {
|
||||
gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
}
|
||||
gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
|
||||
gl.bindTexture(texture.target, null);
|
||||
}
|
||||
|
||||
function loadTexture(gl, url)
|
||||
{
|
||||
// Because images have to be downloaded over the internet
|
||||
// they might take a moment until they are ready. Until
|
||||
// then temporarily fill the texture with a single pixel image
|
||||
// so we can use it immediately. When the image has finished
|
||||
// downloading we'll update texture to the new contents
|
||||
|
||||
const placeholder = createPlaceholderTexture(gl, [0, 0, 255, 255]);
|
||||
gl.bindTexture(placeholder.target, placeholder.object);
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onload = function(){
|
||||
var ktxdata = new Uint8Array(this.response);
|
||||
ktexture = new ktx.texture(ktxdata);
|
||||
const tex = uploadTextureToGl(gl, ktexture);
|
||||
setUVMatrix(tex, mat3.create(), ktexture);
|
||||
setTexParameters(tex, ktexture);
|
||||
gl.bindTexture(tex.target, tex.object);
|
||||
gl.deleteTexture(texture.object);
|
||||
texture = tex;
|
||||
elem('format').innerText = tex.format;
|
||||
ktexture.delete();
|
||||
};
|
||||
|
||||
//xhr.onprogress = runProgress;
|
||||
//xhr.onloadstart = openProgress;
|
||||
xhr.send();
|
||||
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
function isPowerOf2(value) {
|
||||
return (value & (value - 1)) == 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Draw the scene.
|
||||
//
|
||||
function drawScene(gl, programInfo, buffers, texture, deltaTime) {
|
||||
gl.enable(gl.CULL_FACE);
|
||||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.enable(gl.DEPTH_TEST); // Enable depth testing
|
||||
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
|
||||
|
||||
// In case the source image has translucent parts ...
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque
|
||||
gl.clearDepth(1.0); // Clear everything
|
||||
|
||||
// Clear the canvas before we start drawing on it.
|
||||
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
|
||||
// Create a perspective matrix, a special matrix that is
|
||||
// used to simulate the distortion of perspective in a camera.
|
||||
// Our field of view is 45 degrees, with a width/height
|
||||
// ratio that matches the display size of the canvas
|
||||
// and we only want to see objects between 0.1 units
|
||||
// and 100 units away from the camera.
|
||||
|
||||
const fieldOfView = 45 * Math.PI / 180; // in radians
|
||||
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
|
||||
const zNear = 0.1;
|
||||
const zFar = 100.0;
|
||||
const projectionMatrix = mat4.create();
|
||||
|
||||
// note: glmatrix.js always has the first argument
|
||||
// as the destination to receive the result.
|
||||
mat4.perspective(projectionMatrix,
|
||||
fieldOfView,
|
||||
aspect,
|
||||
zNear,
|
||||
zFar);
|
||||
|
||||
// Set the drawing position to the "identity" point, which is
|
||||
// the center of the scene.
|
||||
const modelViewMatrix = mat4.create();
|
||||
|
||||
// Now move the drawing position a bit to where we want to
|
||||
// start drawing the square.
|
||||
|
||||
mat4.translate(modelViewMatrix, // destination matrix
|
||||
modelViewMatrix, // matrix to translate
|
||||
[-0.0, 0.0, -6.0]); // amount to translate
|
||||
mat4.rotate(modelViewMatrix, // destination matrix
|
||||
modelViewMatrix, // matrix to rotate
|
||||
cubeRotation, // amount to rotate in radians
|
||||
[0, 0, 1]); // axis to rotate around (Z)
|
||||
mat4.rotate(modelViewMatrix, // destination matrix
|
||||
modelViewMatrix, // matrix to rotate
|
||||
cubeRotation * .7,// amount to rotate in radians
|
||||
[0, 1, 0]); // axis to rotate around (X)
|
||||
|
||||
const normalMatrix = mat4.create();
|
||||
mat4.invert(normalMatrix, modelViewMatrix);
|
||||
mat4.transpose(normalMatrix, normalMatrix);
|
||||
|
||||
// Tell WebGL how to pull out the positions from the position
|
||||
// buffer into the vertexPosition attribute
|
||||
{
|
||||
const numComponents = 3;
|
||||
const type = gl.FLOAT;
|
||||
const normalize = false;
|
||||
const stride = 0;
|
||||
const offset = 0;
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
|
||||
gl.vertexAttribPointer(
|
||||
programInfo.attribLocations.vertexPosition,
|
||||
numComponents,
|
||||
type,
|
||||
normalize,
|
||||
stride,
|
||||
offset);
|
||||
gl.enableVertexAttribArray(
|
||||
programInfo.attribLocations.vertexPosition);
|
||||
}
|
||||
|
||||
// Tell WebGL how to pull out the texture coordinates from
|
||||
// the texture coordinate buffer into the textureCoord attribute.
|
||||
{
|
||||
const numComponents = 2;
|
||||
const type = gl.FLOAT;
|
||||
const normalize = false;
|
||||
const stride = 0;
|
||||
const offset = 0;
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.textureCoord);
|
||||
gl.vertexAttribPointer(
|
||||
programInfo.attribLocations.textureCoord,
|
||||
numComponents,
|
||||
type,
|
||||
normalize,
|
||||
stride,
|
||||
offset);
|
||||
gl.enableVertexAttribArray(
|
||||
programInfo.attribLocations.textureCoord);
|
||||
}
|
||||
|
||||
// Tell WebGL how to pull out the normals from
|
||||
// the normal buffer into the vertexNormal attribute.
|
||||
{
|
||||
const numComponents = 3;
|
||||
const type = gl.FLOAT;
|
||||
const normalize = false;
|
||||
const stride = 0;
|
||||
const offset = 0;
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normal);
|
||||
gl.vertexAttribPointer(
|
||||
programInfo.attribLocations.vertexNormal,
|
||||
numComponents,
|
||||
type,
|
||||
normalize,
|
||||
stride,
|
||||
offset);
|
||||
gl.enableVertexAttribArray(
|
||||
programInfo.attribLocations.vertexNormal);
|
||||
}
|
||||
|
||||
// Tell WebGL which indices to use to index the vertices
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);
|
||||
|
||||
// Tell WebGL to use our program when drawing
|
||||
|
||||
gl.useProgram(programInfo.program);
|
||||
|
||||
// Set the shader uniforms
|
||||
|
||||
gl.uniformMatrix4fv(
|
||||
programInfo.uniformLocations.projectionMatrix,
|
||||
false,
|
||||
projectionMatrix);
|
||||
gl.uniformMatrix4fv(
|
||||
programInfo.uniformLocations.modelViewMatrix,
|
||||
false,
|
||||
modelViewMatrix);
|
||||
gl.uniformMatrix4fv(
|
||||
programInfo.uniformLocations.normalMatrix,
|
||||
false,
|
||||
normalMatrix);
|
||||
gl.uniformMatrix3fv(
|
||||
programInfo.uniformLocations.uvMatrix,
|
||||
false,
|
||||
texture.uvMatrix);
|
||||
|
||||
// Specify the texture to map onto the faces.
|
||||
|
||||
// Tell WebGL we want to affect texture unit 0
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
|
||||
// Bind the texture to texture unit 0
|
||||
gl.bindTexture(texture.target, texture.object);
|
||||
|
||||
// Tell the shader we bound the texture to texture unit 0
|
||||
gl.uniform1i(programInfo.uniformLocations.uSampler, 0);
|
||||
|
||||
{
|
||||
const vertexCount = 36;
|
||||
const type = gl.UNSIGNED_SHORT;
|
||||
const offset = 0;
|
||||
gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
|
||||
}
|
||||
|
||||
// Update the rotation for the next draw
|
||||
|
||||
cubeRotation += deltaTime;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize a shader program, so WebGL knows how to draw our data
|
||||
//
|
||||
function initShaderProgram(gl, vsSource, fsSource) {
|
||||
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
|
||||
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
||||
|
||||
// Create the shader program
|
||||
|
||||
const shaderProgram = gl.createProgram();
|
||||
gl.attachShader(shaderProgram, vertexShader);
|
||||
gl.attachShader(shaderProgram, fragmentShader);
|
||||
gl.linkProgram(shaderProgram);
|
||||
|
||||
// If creating the shader program failed, alert
|
||||
|
||||
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
||||
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
|
||||
return null;
|
||||
}
|
||||
|
||||
return shaderProgram;
|
||||
}
|
||||
|
||||
//
|
||||
// creates a shader of the given type, uploads the source and
|
||||
// compiles it.
|
||||
//
|
||||
function loadShader(gl, type, source) {
|
||||
const shader = gl.createShader(type);
|
||||
|
||||
// Send the source to the shader object
|
||||
|
||||
gl.shaderSource(shader, source);
|
||||
|
||||
// Compile the shader program
|
||||
|
||||
gl.compileShader(shader);
|
||||
|
||||
// See if it compiled successfully
|
||||
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
|
||||
gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
<!doctype html>
|
||||
<!-- Copyright 2024 Mark Callow.
|
||||
SPDX-License-Identifier: Apache-2.0 -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>libktx.js Demo with WebGL</title>
|
||||
<link rel="stylesheet" href="../webgl.css" type="text/css">
|
||||
<link rel="shortcut icon" href="../ktx_favicon.ico" type="image/x-icon" />
|
||||
<style>
|
||||
body {
|
||||
/* Set margins to center the page. */
|
||||
margin: 2em auto 2em auto;
|
||||
max-width: 65em;
|
||||
}
|
||||
/* Center the canvas container too. */
|
||||
#container {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
position: relative;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
#glcanvas {
|
||||
border: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
z-index: -1;
|
||||
}
|
||||
#glcontent {
|
||||
position: relative;
|
||||
margin: 10px;
|
||||
}
|
||||
#panel {
|
||||
color: white;
|
||||
background-color:rgba(0.3, 0.3, 0.3, 0.3);
|
||||
padding: 0.5em;
|
||||
max-width: 65em;
|
||||
}
|
||||
.item {
|
||||
display: inline-block;
|
||||
margin: 1em;
|
||||
padding: 1em;
|
||||
background: transparent;
|
||||
vertical-align: top;
|
||||
}
|
||||
.label {
|
||||
margin-top: 0.5em;
|
||||
width: 256px;
|
||||
}
|
||||
/*
|
||||
* The size of an image is changed when it is added to the div
|
||||
* styled by this. So the size needs to be a multiple of 4 for
|
||||
* compatibility with various compressed texture formats in WebGL.
|
||||
*/
|
||||
.view {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
/* An img inside a .view should fill the view. */
|
||||
.view img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.pass {
|
||||
color: green;
|
||||
}
|
||||
.fail {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="panel">
|
||||
<h2>libktx Read-write Javascript Binding Test</h2>
|
||||
<p>This tests the functionality of the read-write Javascript
|
||||
binding for libktx, which has been compiled to Javascript using
|
||||
Emscripten. Though loosely referred to as "write" the added
|
||||
functionalty tested here is more about creating ktxTexture objects
|
||||
from scratch than writing them out as files.
|
||||
</p>
|
||||
<p>Click the <i>Run Tests</i> button to run the tests.</p>
|
||||
<p>The test reads a .png file then performs various operations
|
||||
using the wrapper API including creating an uncompressed
|
||||
ktxTexture2 object. The operations are shown in the list below.
|
||||
</p>
|
||||
</div>
|
||||
<p id="test_ktx">
|
||||
<input type="button" value="Run Tests" id='runtests' disabled='true'
|
||||
onclick="runTests('ktx_app.png')">
|
||||
</p>
|
||||
<p>Create uncompressed ktxTexture2: <b id='create_result'></b></p>
|
||||
<p>Copy input image to new texture: <b id='copy_image_result'></b></p>
|
||||
<p>Check oetf and primaries are set correctly: <b id='colorspace_set_result'></b></p>
|
||||
<p>Write and read metadata: <b id='metadata_result'></b></p>
|
||||
<p>Get image from new texture: <b id='get_image_result'></b></p>
|
||||
<p>Create copy of original texture: <b id='create_copy_result'></b></p>
|
||||
<p>Compress ktxTexture2 with Basis: <b id='compress_basis_result'></b></p>
|
||||
<p>Write KTX file to memory: <b id='write_to_memory_result'></b></p>
|
||||
<p>Read KTX file from memory: <b id='read_from_memory_result'></b></p>
|
||||
<p>ASTC inputSwizzle set: <b id ='swizzle_set_result'></b></p>
|
||||
<p>Compress ktxTexture2 with ASTC: <b id='compress_astc_result'></b></p>
|
||||
<div id="container">
|
||||
<!-- <canvas id="glcanvas" width="1280" height="480"></canvas> -->
|
||||
<canvas id="glcanvas"></canvas>
|
||||
<div id="glcontent"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="../gl-matrix.js"></script>
|
||||
<script src="../libktx.js"></script>
|
||||
<script src="libktx-test.js"></script>
|
||||
<script type="text/javascript">
|
||||
function createElem(type, parent, className) {
|
||||
const elem = document.createElement(type);
|
||||
parent.appendChild(elem);
|
||||
if (className) {
|
||||
elem.className = className;
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
function randArrayElement(array) {
|
||||
return array[Math.random() * array.length | 0];
|
||||
}
|
||||
|
||||
function rand(min, max) {
|
||||
if (max === undefined) {
|
||||
max = min;
|
||||
min = 0;
|
||||
}
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
// Make 3 elements over the canvas.
|
||||
const contentElem = document.querySelector('#glcontent');
|
||||
const items = [];
|
||||
const numItems = 6;
|
||||
const origImageItem = 0, uncompTextureItem = 1;
|
||||
const copyTextureItem = 2, basisCompTextureItem = 3;
|
||||
const writeReadTextureItem = 4, astcCompTextureItem = 5;
|
||||
const labelText = [
|
||||
'Input image',
|
||||
'Uncompressed ktxTexture2',
|
||||
'Copy of uncompressed ktxTexture2',
|
||||
'Compressed to ETC1S/Basis-LZ',
|
||||
'Written and Read .ktx2 file with uncompressed texture',
|
||||
'ASTC compressed texture'
|
||||
];
|
||||
|
||||
for (let i = 0; i < numItems; ++i) {
|
||||
const outerElem = createElem('div', contentElem, 'item');
|
||||
const viewElem = createElem('div', outerElem, 'view');
|
||||
const labelElem = createElem('div', outerElem, 'label');
|
||||
//labelElem.textContent = `Item ${i + 1}`;
|
||||
labelElem.textContent = labelText[i];
|
||||
const bgColor = [rand(1), rand(1), rand(1), 1];
|
||||
var placeholder = {};
|
||||
switch (i) {
|
||||
case 0:
|
||||
placeholder.target = null;
|
||||
placeholder.object = null;
|
||||
placeholder.uvMatrix = null;
|
||||
break;
|
||||
default:
|
||||
const texColor = [rand(255), rand(255), rand(255), 255];
|
||||
placeholder = createPlaceholderTexture(gl, texColor);
|
||||
break;
|
||||
}
|
||||
items.push({
|
||||
color: bgColor,
|
||||
texture: placeholder,
|
||||
element: viewElem,
|
||||
label: labelElem
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,635 @@
|
||||
/**
|
||||
* @author donmccurdy / https://www.donmccurdy.com
|
||||
* @author MarkCallow / https://github.com/MarkCallow
|
||||
*
|
||||
* References:
|
||||
* - KTX: http://github.khronos.org/KTX-Specification/
|
||||
* - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
|
||||
*/
|
||||
|
||||
import {
|
||||
CompressedTexture,
|
||||
CompressedTextureLoader,
|
||||
FileLoader,
|
||||
LinearEncoding,
|
||||
LinearFilter,
|
||||
LinearMipmapLinearFilter,
|
||||
RGB_ETC1_Format,
|
||||
RGB_ETC2_Format,
|
||||
RGBA_ETC2_EAC_Format,
|
||||
RGB_PVRTC_4BPPV1_Format,
|
||||
RGB_S3TC_DXT1_Format,
|
||||
RGBA_ASTC_4x4_Format,
|
||||
RGBA_PVRTC_4BPPV1_Format,
|
||||
RGBA_S3TC_DXT5_Format,
|
||||
sRGBEncoding,
|
||||
UnsignedByteType,
|
||||
} from 'https://unpkg.com/three@0.114.0/build/three.module.js';
|
||||
|
||||
|
||||
const DFD = {
|
||||
modelUastc: 166,
|
||||
modelEtc1s: 163,
|
||||
channelUastc: {
|
||||
rgb: 0,
|
||||
rgba: 3,
|
||||
rrr: 4,
|
||||
rrrg: 5
|
||||
},
|
||||
channelEtc1s: {
|
||||
rgb: 0,
|
||||
rrr: 3,
|
||||
ggg: 4,
|
||||
aaa: 15,
|
||||
}
|
||||
};
|
||||
|
||||
class KTX2Loader extends CompressedTextureLoader {
|
||||
|
||||
constructor ( manager ) {
|
||||
|
||||
super( manager );
|
||||
|
||||
this.basisModule = null;
|
||||
|
||||
this.transcoderConfig = {};
|
||||
|
||||
}
|
||||
|
||||
detectSupport ( renderer ) {
|
||||
|
||||
this.transcoderConfig = {
|
||||
astcSupported: !! renderer.extensions.get( 'WEBGL_compressed_texture_astc' ),
|
||||
etc1Supported: !! renderer.extensions.get( 'WEBGL_compressed_texture_etc1' ),
|
||||
etc2Supported: !! renderer.extensions.get( 'WEBGL_compressed_texture_etc' ),
|
||||
dxtSupported: !! renderer.extensions.get( 'WEBGL_compressed_texture_s3tc' ),
|
||||
pvrtcSupported: !! renderer.extensions.get( 'WEBGL_compressed_texture_pvrtc' )
|
||||
|| !! renderer.extensions.get( 'WEBKIT_WEBGL_compressed_texture_pvrtc' )
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
init () {
|
||||
|
||||
var scope = this;
|
||||
|
||||
// The Emscripten wrapper returns a fake Promise, which can cause
|
||||
// infinite recursion when mixed with native Promises. Wrap the module
|
||||
// initialization to return a native Promise.
|
||||
return new Promise( function ( resolve ) {
|
||||
|
||||
MSC_TRANSCODER().then( function ( basisModule ) {
|
||||
|
||||
scope.basisModule = basisModule;
|
||||
|
||||
basisModule.initTranscoders();
|
||||
|
||||
resolve();
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
load ( url, onLoad, onProgress, onError ) {
|
||||
|
||||
var scope = this;
|
||||
|
||||
var texture = new CompressedTexture();
|
||||
|
||||
var bufferPending = new Promise( function (resolve, reject ) {
|
||||
|
||||
new FileLoader( scope.manager )
|
||||
.setPath( scope.path )
|
||||
.setResponseType( 'arraybuffer' )
|
||||
.load( url, resolve, onProgress, reject );
|
||||
|
||||
} );
|
||||
|
||||
Promise.all( [ bufferPending, this.init() ] ).then( function ( [ buffer ] ) {
|
||||
scope.parse( buffer, function ( _texture ) {
|
||||
|
||||
texture.copy( _texture );
|
||||
texture.needsUpdate = true;
|
||||
|
||||
if ( onLoad ) onLoad( texture );
|
||||
|
||||
}, onError );
|
||||
|
||||
} );
|
||||
|
||||
return texture;
|
||||
|
||||
}
|
||||
|
||||
parse ( buffer, onLoad, onError ) {
|
||||
|
||||
var BasisLzEtc1sImageTranscoder = this.basisModule.BasisLzEtc1sImageTranscoder;
|
||||
var UastcImageTranscoder = this.basisModule.UastcImageTranscoder;
|
||||
var TextureFormat = this.basisModule.TextureFormat;
|
||||
|
||||
var ktx = new KTX2Container( buffer );
|
||||
|
||||
// TODO(donmccurdy): Should test if texture is transcodable before attempting
|
||||
// any transcoding. If supercompressionScheme is KTX_SS_BASIS_LZ and dfd
|
||||
// colorModel is ETC1S (163) or if dfd colorModel is UASTCF (166)
|
||||
// then texture must be transcoded.
|
||||
|
||||
var texFormat = ktx.dfd.colorModel == DFD.modelUastc ? TextureFormat.UASTC4x4
|
||||
: TextureFormat.ETC1S;
|
||||
// TODO(donmccurdy): Handle all channelIds (i.e. the R & R+G cases),
|
||||
// choosing appropriate transcode target formats or providing queries
|
||||
// for applications so they know what to do with the content.
|
||||
var hasAlpha = false;
|
||||
if (texFormat == TextureFormat.UASTC4x4) {
|
||||
var transcoder = new UastcImageTranscoder();
|
||||
if ( (ktx.dfd.samples[0].channelId & 0xf) === DFD.channelUastc.rgba )
|
||||
hasAlpha = true;
|
||||
} else {
|
||||
var transcoder = new BasisLzEtc1sImageTranscoder();
|
||||
if ( ktx.dfd.numSamples == 2 && (ktx.dfd.samples[1].channelId & 0xf) === DFD.channelEtc1s.aaa ) {
|
||||
hasAlpha = true;
|
||||
}
|
||||
}
|
||||
ktx.initMipmaps( texFormat, hasAlpha, transcoder, this.basisModule, this.transcoderConfig )
|
||||
.then( function () {
|
||||
|
||||
var texture = new CompressedTexture(
|
||||
ktx.mipmaps,
|
||||
ktx.getWidth(),
|
||||
ktx.getHeight(),
|
||||
ktx.transcodedFormat,
|
||||
UnsignedByteType
|
||||
);
|
||||
|
||||
texture.encoding = ktx.getEncoding();
|
||||
texture.premultiplyAlpha = ktx.getPremultiplyAlpha();
|
||||
texture.minFilter = ktx.mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter;
|
||||
texture.magFilter = LinearFilter;
|
||||
|
||||
onLoad( texture );
|
||||
|
||||
} )
|
||||
.catch( onError );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class KTX2Container {
|
||||
|
||||
constructor ( arrayBuffer ) {
|
||||
|
||||
this.arrayBuffer = arrayBuffer;
|
||||
|
||||
this.mipmaps = null;
|
||||
this.transcodedFormat = null;
|
||||
|
||||
// Confirm this is a KTX 2.0 file, based on the identifier in the first 12 bytes.
|
||||
var idByteLength = 12;
|
||||
var id = new Uint8Array( this.arrayBuffer, 0, idByteLength );
|
||||
if ( id[ 0 ] !== 0xAB || // '´'
|
||||
id[ 1 ] !== 0x4B || // 'K'
|
||||
id[ 2 ] !== 0x54 || // 'T'
|
||||
id[ 3 ] !== 0x58 || // 'X'
|
||||
id[ 4 ] !== 0x20 || // ' '
|
||||
id[ 5 ] !== 0x32 || // '2'
|
||||
id[ 6 ] !== 0x30 || // '0'
|
||||
id[ 7 ] !== 0xBB || // 'ª'
|
||||
id[ 8 ] !== 0x0D || // '\r'
|
||||
id[ 9 ] !== 0x0A || // '\n'
|
||||
id[ 10 ] !== 0x1A || // '\x1A'
|
||||
id[ 11 ] !== 0x0A // '\n'
|
||||
) {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: Missing KTX 2.0 identifier.' );
|
||||
|
||||
}
|
||||
|
||||
// TODO(donmccurdy): If we need to support BE, derive this from typeSize.
|
||||
var littleEndian = true;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// Header.
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
var headerByteLength = 17 * Uint32Array.BYTES_PER_ELEMENT;
|
||||
var headerReader = new KTX2BufferReader( this.arrayBuffer, idByteLength, headerByteLength, littleEndian );
|
||||
|
||||
this.header = {
|
||||
|
||||
vkFormat: headerReader.nextUint32(),
|
||||
typeSize: headerReader.nextUint32(),
|
||||
pixelWidth: headerReader.nextUint32(),
|
||||
pixelHeight: headerReader.nextUint32(),
|
||||
pixelDepth: headerReader.nextUint32(),
|
||||
arrayElementCount: headerReader.nextUint32(),
|
||||
faceCount: headerReader.nextUint32(),
|
||||
levelCount: headerReader.nextUint32(),
|
||||
|
||||
supercompressionScheme: headerReader.nextUint32(),
|
||||
|
||||
dfdByteOffset: headerReader.nextUint32(),
|
||||
dfdByteLength: headerReader.nextUint32(),
|
||||
kvdByteOffset: headerReader.nextUint32(),
|
||||
kvdByteLength: headerReader.nextUint32(),
|
||||
sgdByteOffset: headerReader.nextUint64(),
|
||||
sgdByteLength: headerReader.nextUint64(),
|
||||
|
||||
};
|
||||
|
||||
if ( this.header.pixelDepth > 0 ) {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: Only 2D textures are currently supported.' );
|
||||
|
||||
}
|
||||
|
||||
if ( this.header.arrayElementCount > 1 ) {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: Array textures are not currently supported.' );
|
||||
|
||||
}
|
||||
|
||||
if ( this.header.faceCount > 1 ) {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: Cube textures are not currently supported.' );
|
||||
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// Level index
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
var levelByteLength = this.header.levelCount * 3 * 8;
|
||||
var levelReader = new KTX2BufferReader( this.arrayBuffer, idByteLength + headerByteLength, levelByteLength, littleEndian );
|
||||
|
||||
this.levels = [];
|
||||
|
||||
for ( var i = 0; i < this.header.levelCount; i ++ ) {
|
||||
|
||||
this.levels.push( {
|
||||
byteOffset: levelReader.nextUint64(),
|
||||
byteLength: levelReader.nextUint64(),
|
||||
uncompressedByteLength: levelReader.nextUint64()
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// Data Format Descriptor (DFD)
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
var dfdReader = new KTX2BufferReader(
|
||||
this.arrayBuffer,
|
||||
this.header.dfdByteOffset,
|
||||
this.header.dfdByteLength,
|
||||
littleEndian
|
||||
);
|
||||
|
||||
const sampleStart = 6;
|
||||
const sampleWords = 4;
|
||||
this.dfd = {
|
||||
|
||||
vendorId: dfdReader.skip( 4 /* totalSize */ ).nextUint16(),
|
||||
versionNumber: dfdReader.skip( 2 /* descriptorType */ ).nextUint16(),
|
||||
descriptorBlockSize: dfdReader.nextUint16(),
|
||||
colorModel: dfdReader.nextUint8(),
|
||||
colorPrimaries: dfdReader.nextUint8(),
|
||||
transferFunction: dfdReader.nextUint8(),
|
||||
flags: dfdReader.nextUint8(),
|
||||
texelBlockDimension: {
|
||||
x: dfdReader.nextUint8() + 1,
|
||||
y: dfdReader.nextUint8() + 1,
|
||||
z: dfdReader.nextUint8() + 1,
|
||||
w: dfdReader.nextUint8() + 1,
|
||||
},
|
||||
/*
|
||||
texelBlockDimension0: dfdReader.nextUint8(),
|
||||
texelBlockDimension1: dfdReader.nextUint8(),
|
||||
texelBlockDimension2: dfdReader.nextUint8(),
|
||||
*/
|
||||
bytesPlane0: dfdReader.nextUint8(),
|
||||
numSamples: 0,
|
||||
samples: [],
|
||||
};
|
||||
|
||||
dfdReader.skip( 7 /* bytesPlane[1-7] */ );
|
||||
this.dfd.numSamples = (this.dfd.descriptorBlockSize/4 - sampleStart) / sampleWords;
|
||||
for (i = 0; i < this.dfd.numSamples; i++) {
|
||||
this.dfd.samples[i] = {
|
||||
channelId: dfdReader.skip( 3 /* bitOffset + bitLength */ ).nextUint8(),
|
||||
// ... remainder not implemented.
|
||||
};
|
||||
dfdReader.skip( 12 /* samplePosition[0-3], lower, upper */ );
|
||||
}
|
||||
|
||||
if ( this.header.vkFormat !== 0x00 /* VK_FORMAT_UNDEFINED */ &&
|
||||
!( this.header.supercompressionScheme === 1 /* BasisLZ */ ||
|
||||
this.dfd.colorModel === DFD.modelUastc ) ) {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: Only Basis Universal supercompression is currently supported.' );
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// Key/Value Data (KVD)
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
// Not implemented.
|
||||
this.kvd = {};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// Supercompression Global Data (SGD)
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
if (this.header.sgdByteLength > 0) {
|
||||
var sgdReader = new KTX2BufferReader(
|
||||
this.arrayBuffer,
|
||||
this.header.sgdByteOffset,
|
||||
this.header.sgdByteLength,
|
||||
littleEndian
|
||||
);
|
||||
|
||||
this.sgd = {
|
||||
|
||||
endpointCount: sgdReader.nextUint16(),
|
||||
selectorCount: sgdReader.nextUint16(),
|
||||
endpointsByteLength: sgdReader.nextUint32(),
|
||||
selectorsByteLength: sgdReader.nextUint32(),
|
||||
tablesByteLength: sgdReader.nextUint32(),
|
||||
extendedByteLength: sgdReader.nextUint32(),
|
||||
|
||||
imageDescs: [],
|
||||
|
||||
endpointsData: null,
|
||||
selectorsData: null,
|
||||
tablesData: null,
|
||||
extendedData: null,
|
||||
|
||||
};
|
||||
|
||||
for ( var i = 0; i < this.header.levelCount; i ++ ) {
|
||||
|
||||
this.sgd.imageDescs.push( {
|
||||
|
||||
imageFlags: sgdReader.nextUint32(),
|
||||
rgbSliceByteOffset: sgdReader.nextUint32(),
|
||||
rgbSliceByteLength: sgdReader.nextUint32(),
|
||||
alphaSliceByteOffset: sgdReader.nextUint32(),
|
||||
alphaSliceByteLength: sgdReader.nextUint32(),
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
var endpointsByteOffset = this.header.sgdByteOffset + sgdReader.offset;
|
||||
var selectorsByteOffset = endpointsByteOffset + this.sgd.endpointsByteLength;
|
||||
var tablesByteOffset = selectorsByteOffset + this.sgd.selectorsByteLength;
|
||||
var extendedByteOffset = tablesByteOffset + this.sgd.tablesByteLength;
|
||||
|
||||
this.sgd.endpointsData = new Uint8Array( this.arrayBuffer, endpointsByteOffset, this.sgd.endpointsByteLength );
|
||||
this.sgd.selectorsData = new Uint8Array( this.arrayBuffer, selectorsByteOffset, this.sgd.selectorsByteLength );
|
||||
this.sgd.tablesData = new Uint8Array( this.arrayBuffer, tablesByteOffset, this.sgd.tablesByteLength );
|
||||
this.sgd.extendedData = new Uint8Array( this.arrayBuffer, extendedByteOffset, this.sgd.extendedByteLength );
|
||||
} else {
|
||||
this.sgd = {};
|
||||
}
|
||||
}
|
||||
|
||||
initMipmaps ( texFormat, hasAlpha, transcoder, basisModule, config ) {
|
||||
|
||||
var TranscodeTarget = basisModule.TranscodeTarget;
|
||||
var TextureFormat = basisModule.TextureFormat;
|
||||
var ImageInfo = basisModule.ImageInfo;
|
||||
|
||||
var scope = this;
|
||||
|
||||
var mipmaps = [];
|
||||
var width = this.getWidth();
|
||||
var height = this.getHeight();
|
||||
|
||||
var isVideo = false;
|
||||
|
||||
// For both ETC1S and UASTC4x4 formats.
|
||||
if ( texFormat == TextureFormat.ETC1S ) {
|
||||
var numEndpoints = this.sgd.endpointCount;
|
||||
var numSelectors = this.sgd.selectorCount;
|
||||
|
||||
var endpoints = this.sgd.endpointsData;
|
||||
var selectors = this.sgd.selectorsData;
|
||||
var tables = this.sgd.tablesData;
|
||||
|
||||
transcoder.decodePalettes( numEndpoints, endpoints, numSelectors, selectors );
|
||||
transcoder.decodeTables( tables );
|
||||
}
|
||||
var targetFormat;
|
||||
|
||||
if ( config.astcSupported ) {
|
||||
|
||||
targetFormat = TranscodeTarget.ASTC_4x4_RGBA;
|
||||
this.transcodedFormat = RGBA_ASTC_4x4_Format;
|
||||
|
||||
} else if ( config.dxtSupported ) {
|
||||
|
||||
targetFormat = hasAlpha ? TranscodeTarget.BC3_RGBA : TranscodeTarget.BC1_RGB;
|
||||
this.transcodedFormat = hasAlpha ? RGBA_S3TC_DXT5_Format : RGB_S3TC_DXT1_Format;
|
||||
|
||||
} else if ( config.pvrtcSupported ) {
|
||||
|
||||
targetFormat = hasAlpha ? TranscodeTarget.PVRTC1_4_RGBA : TranscodeTarget.PVRTC1_4_RGB;
|
||||
this.transcodedFormat = hasAlpha ? RGBA_PVRTC_4BPPV1_Format : RGB_PVRTC_4BPPV1_Format;
|
||||
|
||||
} else if ( config.etc2Supported ) {
|
||||
|
||||
targetFormat = hasAlpha ? TranscodeTarget.ETC2_RGBA : TranscodeTarget.ETC1_RGB /* subset of ETC2 */;
|
||||
this.transcodedFormat = hasAlpha ? RGBA_ETC2_EAC_Format : RGB_ETC2_Format;
|
||||
|
||||
} else if ( config.etc1Supported ) {
|
||||
|
||||
targetFormat = TranscodeTarget.ETC1_RGB;
|
||||
this.transcodedFormat = RGB_ETC1_Format;
|
||||
|
||||
} else {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: No suitable compressed texture format found.' );
|
||||
|
||||
}
|
||||
if ( !basisModule.isFormatSupported( targetFormat, texFormat ) ) {
|
||||
throw new Error( 'THREE.KTX2Loader: Selected texture format not supported by current msc_basis_transcoder.js build.' );
|
||||
}
|
||||
|
||||
var imageDescIndex = 0;
|
||||
|
||||
for ( var level = 0; level < this.header.levelCount; level ++ ) {
|
||||
|
||||
var levelWidth = width / Math.pow( 2, level );
|
||||
var levelHeight = height / Math.pow( 2, level );
|
||||
|
||||
var numImagesInLevel = 1; // TODO(donmccurdy): Support cubemaps, arrays and 3D.
|
||||
var imageOffsetInLevel = 0;
|
||||
|
||||
var imageInfo = new ImageInfo(texFormat, levelWidth, levelHeight, level);
|
||||
var levelImageByteLength = imageInfo.numBlocksX * imageInfo.numBlocksY * this.dfd.bytesPlane0;
|
||||
|
||||
for ( var imageIndex = 0; imageIndex < numImagesInLevel; imageIndex ++ ) {
|
||||
|
||||
var result;
|
||||
if ( texFormat == TextureFormat.UASTC4x4 ) {
|
||||
imageInfo.flags = 0;
|
||||
imageInfo.rgbByteOffset = 0;
|
||||
imageInfo.rgbByteLength = levelImageByteLength;
|
||||
imageInfo.alphaByteOffset = 0;
|
||||
imageInfo.alphaByteLength = 0;
|
||||
result = transcoder.transcodeImage(targetFormat,
|
||||
new Uint8Array( this.arrayBuffer, this.levels[ level ].byteOffset + imageOffsetInLevel, levelImageByteLength),
|
||||
imageInfo,
|
||||
0,
|
||||
hasAlpha,
|
||||
isVideo
|
||||
);
|
||||
} else {
|
||||
var imageDesc = this.sgd.imageDescs[ imageDescIndex++ ];
|
||||
|
||||
imageInfo.flags = imageDesc.imageFlags;
|
||||
imageInfo.rgbByteOffset = 0;
|
||||
imageInfo.rgbByteLength = imageDesc.rgbSliceByteLength;
|
||||
imageInfo.alphaByteOffset = imageDesc.alphaSliceByteOffset > 0 ? imageDesc.rgbSliceByteLength : 0;
|
||||
imageInfo.alphaByteLength = imageDesc.alphaSliceByteLength;
|
||||
result = transcoder.transcodeImage(targetFormat,
|
||||
new Uint8Array( this.arrayBuffer, this.levels[ level ].byteOffset + imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength + imageDesc.alphaSliceByteLength ),
|
||||
imageInfo,
|
||||
0,
|
||||
isVideo
|
||||
);
|
||||
}
|
||||
|
||||
if ( result.transcodedImage === undefined ) {
|
||||
|
||||
throw new Error( 'THREE.KTX2Loader: Unable to transcode image.' );
|
||||
|
||||
}
|
||||
|
||||
// Transcoded image is written in memory allocated by WASM. We could avoid copying
|
||||
// the image by waiting until the image is uploaded to the GPU, then calling
|
||||
// delete(). However, (1) we don't know if the user will later need to re-upload it
|
||||
// e.g. after calling texture.clone(), and (2) this code will eventually be in a
|
||||
// Web Worker, and transferring WASM's memory seems like a very bad idea.
|
||||
var levelData = result.transcodedImage.get_typed_memory_view().slice();
|
||||
result.transcodedImage.delete();
|
||||
|
||||
mipmaps.push( { data: levelData, width: levelWidth, height: levelHeight } );
|
||||
imageOffsetInLevel += levelImageByteLength;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new Promise( function ( resolve, reject ) {
|
||||
|
||||
scope.mipmaps = mipmaps;
|
||||
|
||||
resolve();
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
getWidth () { return this.header.pixelWidth; }
|
||||
|
||||
getHeight () { return this.header.pixelHeight; }
|
||||
|
||||
getEncoding () {
|
||||
|
||||
return this.dfd.transferFunction === 2 /* KHR_DF_TRANSFER_SRGB */
|
||||
? sRGBEncoding
|
||||
: LinearEncoding;
|
||||
|
||||
}
|
||||
|
||||
getPremultiplyAlpha () {
|
||||
|
||||
return !! ( this.dfd.flags & 1 /* KHR_DF_FLAG_ALPHA_PREMULTIPLIED */ );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class KTX2BufferReader {
|
||||
|
||||
constructor ( arrayBuffer, byteOffset, byteLength, littleEndian ) {
|
||||
|
||||
this.dataView = new DataView( arrayBuffer, byteOffset, byteLength );
|
||||
this.littleEndian = littleEndian;
|
||||
this.offset = 0;
|
||||
|
||||
}
|
||||
|
||||
nextUint8 () {
|
||||
|
||||
var value = this.dataView.getUint8( this.offset, this.littleEndian );
|
||||
|
||||
this.offset += 1;
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
nextUint16 () {
|
||||
|
||||
var value = this.dataView.getUint16( this.offset, this.littleEndian );
|
||||
|
||||
this.offset += 2;
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
nextUint32 () {
|
||||
|
||||
var value = this.dataView.getUint32( this.offset, this.littleEndian );
|
||||
|
||||
this.offset += 4;
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
nextUint64 () {
|
||||
|
||||
// https://stackoverflow.com/questions/53103695/
|
||||
var left = this.dataView.getUint32( this.offset, this.littleEndian );
|
||||
var right = this.dataView.getUint32( this.offset + 4, this.littleEndian );
|
||||
var value = this.littleEndian ? left + ( 2 ** 32 * right ) : ( 2 ** 32 * left ) + right;
|
||||
|
||||
if ( ! Number.isSafeInteger( value ) ) {
|
||||
|
||||
console.warn( 'THREE.KTX2Loader: ' + value + ' exceeds MAX_SAFE_INTEGER. Precision may be lost.' );
|
||||
|
||||
}
|
||||
|
||||
this.offset += 8;
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
skip ( bytes ) {
|
||||
|
||||
this.offset += bytes;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { KTX2Loader };
|
||||
@@ -0,0 +1,191 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>msc_transcoder_test with three.js</title>
|
||||
<link rel="shortcut icon" href="../ktx_favicon.ico" type="image/x-icon" />
|
||||
</head>
|
||||
<script src="../msc_basis_transcoder.js"></script>
|
||||
<!--
|
||||
<script defer src="https://unpkg.com/es-module-shims@0.4.6/dist/es-module-shims.js"></script>
|
||||
-->
|
||||
<!-- Use an import map shim to ensure KTX2Loader and the demo itself
|
||||
use the same CDN-hosted copy of the threejs library. -->
|
||||
<!--
|
||||
<script type="importmap-shim">
|
||||
{
|
||||
"imports": {
|
||||
"https://raw.githack.com/donmccurdy/three.js/feat-ktx2loader/build/three.module.js": "https://unpkg.com/three@0.114.0/build/three.module.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-->
|
||||
<style>
|
||||
html, body { width:100%; height:100%; margin:0; padding:0; }
|
||||
canvas { display:block; }
|
||||
h2 { margin-top: 0; }
|
||||
#panel {
|
||||
position: absolute;
|
||||
color: white;
|
||||
background-color:rgba(0.3, 0.3, 0.3, 0.3);
|
||||
padding: 0.5em;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
max-width: 50em;
|
||||
}
|
||||
</style>
|
||||
<div id="panel">
|
||||
<h2>msc_basis_transcoder test</h2>
|
||||
<p>
|
||||
Loads and parses KTX2 files with a plain JS loader, transcodes the
|
||||
Basis Universal payload with <code>msc_basis_transcoder</code>,
|
||||
and renders the result with three.js. The first KTX2 file contains
|
||||
BasisLZ/ETC1S format and the second UASTC format. These textures
|
||||
have been transcoded to <b id='format'>FORMAT</b>.
|
||||
</p>
|
||||
<div id="log"></div>
|
||||
</div>
|
||||
<!-- <script type="module-shim"> -->
|
||||
<script type="module">
|
||||
import * as THREE from 'https://unpkg.com/three@0.114.0/build/three.module.js';
|
||||
import { OrbitControls } from 'https://unpkg.com/three@0.114.0/examples/jsm/controls/OrbitControls.js';
|
||||
/*
|
||||
import { KTX2Loader } from 'https://raw.githack.com/donmccurdy/three.js/feat-ktx2loader/examples/jsm/loaders/KTX2Loader.js';
|
||||
*/
|
||||
import { KTX2Loader } from '/llt-three/KTX2Loader.js';
|
||||
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
|
||||
const renderer = new THREE.WebGLRenderer( { antialias: true } );
|
||||
renderer.setSize( width, height );
|
||||
renderer.outputEncoding = THREE.sRGBEncoding;
|
||||
document.body.appendChild( renderer.domElement );
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color( 0xF0F0F0 );
|
||||
|
||||
const camera = new THREE.PerspectiveCamera( 60, width / height, 0.1, 100 );
|
||||
camera.position.set( 2, 1.5, 1 );
|
||||
camera.lookAt( scene.position );
|
||||
scene.add(camera);
|
||||
|
||||
const controls = new OrbitControls( camera, renderer.domElement );
|
||||
controls.autoRotate = true;
|
||||
|
||||
// FIXME: compressed textures don't support? KTX2 compressed textures do.
|
||||
// PlaneBufferGeometry UVs assume flipY=true, which compressed textures don't support.
|
||||
const geometry = flipY( new THREE.PlaneBufferGeometry() );
|
||||
const materialEtc1s = new THREE.MeshBasicMaterial( {
|
||||
color: 0xFFFFFF,
|
||||
side: THREE.DoubleSide,
|
||||
defines: {DECAL: ""}
|
||||
} );
|
||||
/* This isn't working. No time for further investigation.
|
||||
const myShader = [
|
||||
'diffuseColor *= texelColor;',
|
||||
'#ifndef DECAL',
|
||||
' diffuseColor *= texelColor;',
|
||||
'#else',
|
||||
' diffuseColor = vec4( mix( diffuse, texelColor.rgb, texelColor.a ), opacity );',
|
||||
'#endif',
|
||||
].join( '\n' );
|
||||
materialEtc1s.onBeforeCompile = function ( shader ) {
|
||||
shader.fragmentShader = shader.fragmentShader.replace(
|
||||
myShader
|
||||
);
|
||||
};
|
||||
*/
|
||||
|
||||
const materialUastc = new THREE.MeshBasicMaterial( {
|
||||
alphaTest: 1.0,
|
||||
color: 0xFFFFFF,
|
||||
side: THREE.DoubleSide
|
||||
} );
|
||||
var meshEtc1s = new THREE.Mesh(geometry, materialEtc1s);
|
||||
meshEtc1s.position.z = -0.5;
|
||||
|
||||
var meshUastc = new THREE.Mesh(geometry, materialUastc);
|
||||
meshUastc.position.z = +0.5;
|
||||
|
||||
scene.add(meshEtc1s, meshUastc);
|
||||
|
||||
const formats = [
|
||||
'RGBA_ASTC_4x4_Format',
|
||||
'RGB_S3TC_DXT1_Format',
|
||||
'RGBA_S3TC_DXT5_Format',
|
||||
'RGB_PVRTC_4BPPV1_Format',
|
||||
'RGBA_PVRTC_4BPPV1_Format',
|
||||
'RGB_ETC1_Format',
|
||||
'RGB_ETC2_Format',
|
||||
'RGBA_ETC2_EAC_Format',
|
||||
];
|
||||
|
||||
const formatToString = ( format ) => formats.find( ( name ) => THREE[ name ] === format );
|
||||
|
||||
animate();
|
||||
|
||||
var loader = new KTX2Loader().detectSupport( renderer )
|
||||
|
||||
loader.load( '../libktx-webgl/ktx_app_basis.ktx2', ( texture ) => {
|
||||
|
||||
materialEtc1s.map = texture;
|
||||
elem('format').innerText = formatToString( materialEtc1s.map.format );
|
||||
|
||||
materialEtc1s.needsUpdate = true;
|
||||
|
||||
}, ( p ) => console.log( `...${p}` ), ( e ) => console.error( e ) );
|
||||
|
||||
loader.load( '../libktx-webgl/ktx_document_uastc_rdo5.ktx2', ( texture ) => {
|
||||
|
||||
materialUastc.map = texture;
|
||||
//elem('format').innerText = formatToString( materialEtc1s.map.format );
|
||||
|
||||
materialUastc.needsUpdate = true;
|
||||
|
||||
}, ( p ) => console.log( `...${p}` ), ( e ) => console.error( e ) );
|
||||
|
||||
function elem(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
|
||||
requestAnimationFrame( animate );
|
||||
|
||||
meshUastc.needsUpdate = true;
|
||||
controls.update();
|
||||
|
||||
renderer.render( scene, camera );
|
||||
|
||||
}
|
||||
|
||||
window.addEventListener( 'resize', () => {
|
||||
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
|
||||
camera.aspect = width / height;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize( width, height );
|
||||
|
||||
}, false );
|
||||
|
||||
// FIXME: read the orientation metadata and only do this if it indicates
|
||||
// Y orientation is up.
|
||||
/** Correct UVs to be compatible with `flipY=false` textures. */
|
||||
function flipY ( geometry ) {
|
||||
|
||||
var uv = geometry.attributes.uv;
|
||||
|
||||
for ( var i = 0; i < uv.count; i ++ ) {
|
||||
|
||||
uv.setY( i, 1 - uv.getY( i ) );
|
||||
|
||||
}
|
||||
|
||||
return geometry;
|
||||
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2020 Mark Callow
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
canvas {
|
||||
border: 2px solid black;
|
||||
background-color: black;
|
||||
}
|
||||
video {
|
||||
display: none;
|
||||
}
|
||||
Reference in New Issue
Block a user