Checking in new webgl files. Note that the WebAssembly transcoder has gotten bloated with BC7 support, which I'm working on fixing right now. (The wasm went from ~200k to 900k, because of a transcoder macro configuration issue.)

This commit is contained in:
richgel999
2019-09-19 14:34:34 -07:00
parent 6bc7418508
commit 2cb6bdadc1
9 changed files with 2712 additions and 2262 deletions

View File

@@ -8,12 +8,15 @@ Requires WebAssembly and WebGL support.
Renders a single texture, using the transcoder (compiled to WASM with emscripten) to generate one of the following compressed texture formats:
* BC1
* ASTC
* BC1 (no alpha)
* BC3
* ETC1 (no alpha)
* PVRTC
Please note that both Firefox and Chrome support BC1, but due to an implementation issue in the code it doesn't work on Firefox yet. (It doesn't test for the "WEBGL_compressed_texture_s3tc" extension.)
On browsers that don't support BC1, there's a low-quality fallback code path for opaque textures (but no fallback for BC3 yet). Note that the fallback path only converts to 16-bit RGB images at the moment, so the quality isn't as good as it should be.
On browsers that don't support any compressed texture format, there's a low-quality fallback code path for opaque textures. Note that the fallback path only converts to 16-bit RGB images at the moment, so the quality isn't as good as it should be.
![Screenshot showing a basis texture rendered as a 2D image in a webpage.](texture/preview.png)
@@ -23,11 +26,13 @@ On browsers that don't support BC1, there's a low-quality fallback code path for
Renders a glTF 3D model with `.basis` texture files, transcoded into one of the following compressed texture formats:
* DTX (BC1)
* ASTC
* Tested in Chrome on Android, Pixel 3 XL.
* DTX (BC1/BC3)
* Tested in Chrome (Linux and macOS) and Firefox (macOS).
* ETC1
* Tested in Chrome on Android, Pixel 3 XL.
* PVRTC (COMPRESSED_RGB_PVRTC_4BPPV1_IMG)
* PVRTC
* Tested in Chrome and Safari on iOS iPhone 6 Plus.
The glTF model in this demo uses a hypothetical `GOOGLE_texture_basis` extension. That extension is defined for the sake of example only the glTF format will officially embed Basis files within a KTX2 wrapper, through a new

View File

@@ -5,165 +5,487 @@
*/
/**
* An example three.js loader for Basis compressed textures.
* Loader for Basis Universal GPU Texture Codec.
*
* 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.BasisTextureLoader = class BasisTextureLoader {
THREE.BasisTextureLoader = function ( manager ) {
constructor () {
THREE.Loader.call( this, manager );
this.etcSupported = false;
this.dxtSupported = false;
this.pvrtcSupported = false;
this.useAlpha = true;
this.transcoderPath = '';
this.transcoderBinary = null;
this.transcoderPending = null;
this.format = 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,
};
}
};
detectSupport ( renderer ) {
THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
const context = renderer.context;
constructor: THREE.BasisTextureLoader,
this.etcSupported = context.getExtension('WEBGL_compressed_texture_etc1');
this.dxtSupported = context.getExtension('WEBGL_compressed_texture_s3tc');
this.pvrtcSupported = context.getExtension('WEBGL_compressed_texture_pvrtc')
|| context.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');
setTranscoderPath: function ( path ) {
if ( ! this.etcSupported && ! this.dxtSupported && ! this.pvrtcSupported ) {
this.transcoderPath = path;
throw new Error( 'THREE.BasisTextureLoader: No suitable compressed texture format found.' );
return this;
}
},
this.format = this.etcSupported
? BASIS_FORMAT.cTFETC1
: ( this.dxtSupported
? BASIS_FORMAT.cTFBC1
: BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY );
setWorkerLimit: function ( workerLimit ) {
return this;
this.workerLimit = workerLimit;
}
return this;
load ( url, onLoad, onProgress, onError ) {
},
fetch( url )
.then( ( res ) => res.arrayBuffer() )
.then( (arrayBuffer) => this._createTexture( arrayBuffer ) )
.then( onLoad )
.catch( onError );
detectSupport: function ( renderer ) {
}
var config = this.workerConfig;
_createTexture ( arrayBuffer ) {
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' );
const BASIS_FORMAT = THREE.BasisTextureLoader.BASIS_FORMAT;
const DXT_FORMAT_MAP = THREE.BasisTextureLoader.DXT_FORMAT_MAP;
if (config.astcSupported) {
const basisFile = new BasisFile( new Uint8Array( arrayBuffer ) );
config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4;
const width = basisFile.getImageWidth( 0, 0 );
const height = basisFile.getImageHeight( 0, 0 );
const images = basisFile.getNumImages();
const levels = basisFile.getNumLevels( 0 );
} else if ( config.dxtSupported ) {
function cleanup () {
config.format = this.useAlpha ? THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3 : THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1;
basisFile.close();
basisFile.delete();
} else if ( config.pvrtcSupported ) {
}
config.format = this.useAlpha ? THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA : THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB;
if ( ! width || ! height || ! images || ! levels ) {
} else if ( config.etcSupported ) {
cleanup();
throw new Error( 'THREE.BasisTextureLoader: Invalid .basis file' );
config.format = THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1;
}
} else {
if ( ! basisFile.startTranscoding() ) {
throw new Error( 'THREE.BasisTextureLoader: No suitable compressed texture format found.' );
cleanup();
throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' );
}
}
return this;
const dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, 0, this.format ) );
},
const startTime = performance.now();
load: function ( url, onLoad, onProgress, onError ) {
const status = basisFile.transcodeImage(
dst,
0,
0,
this.format,
this.etcSupported ? 0 : ( this.dxtSupported ? 1 : 0 ),
0
);
var loader = new THREE.FileLoader( this.manager );
console.log( `THREE.BasisTextureLoader: Transcode time: ${performance.now() - startTime}ms`, dst );
loader.setResponseType( 'arraybuffer' );
cleanup();
loader.load( url, ( buffer ) => {
if ( ! status ) {
this._createTexture( buffer )
.then( onLoad )
.catch( onError );
throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' );
}, onProgress, onError );
}
},
const mipmaps = [ { data: dst, width, height } ];
/**
* @param {ArrayBuffer} buffer
* @return {Promise<THREE.CompressedTexture>}
*/
_createTexture: function ( buffer ) {
let texture;
var worker;
var taskID;
if ( this.etcSupported ) {
var texturePending = this._getWorker()
.then( ( _worker ) => {
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_ETC1_Format );
worker = _worker;
taskID = this.workerNextTaskID ++;
} else if ( this.dxtSupported ) {
return new Promise( ( resolve, reject ) => {
texture = new THREE.CompressedTexture( mipmaps, width, height, DXT_FORMAT_MAP[ this.format ], THREE.UnsignedByteType );
worker._callbacks[ taskID ] = { resolve, reject };
worker._taskCosts[ taskID ] = buffer.byteLength;
worker._taskLoad += worker._taskCosts[ taskID ];
} else if ( this.pvrtcSupported ) {
worker.postMessage( { type: 'transcode', id: taskID, buffer }, [ buffer ] );
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_PVRTC_4BPPV1_Format );
} );
} else {
} )
.then( ( message ) => {
throw new Error( 'THREE.BasisTextureLoader: No supported format available.' );
var config = this.workerConfig;
}
var { width, height, hasAlpha, mipmaps } = message;
texture.minFilter = THREE.LinearMipMapLinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.encoding = THREE.sRGBEncoding;
texture.generateMipmaps = false;
texture.flipY = false;
texture.needsUpdate = true;
var texture;
return texture;
switch(config.format) {
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFASTC_4x4:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.BasisTextureLoader.COMPRESSED_RGBA_ASTC_4x4_KHR );
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1:
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.BasisTextureLoader.DXT_FORMAT_MAP[ config.format ], THREE.UnsignedByteType );
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFETC1:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_ETC1_Format );
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGB:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGB_PVRTC_4BPPV1_Format );
break;
case THREE.BasisTextureLoader.BASIS_FORMAT.cTFPVRTC1_4_RGBA:
texture = new THREE.CompressedTexture( mipmaps, width, height, THREE.RGBA_PVRTC_4BPPV1_Format );
break;
default:
throw new Error( 'THREE.BasisTextureLoader: No supported format available.' );
}
}
}
texture.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = false;
texture.needsUpdate = true;
const BASIS_FORMAT = {
cTFETC1: 0,
cTFBC1: 1,
cTFBC4: 2,
cTFPVRTC1_4_OPAQUE_ONLY: 3,
cTFBC7_M6_OPAQUE_ONLY: 4,
cTFETC2: 5,
cTFBC3: 6,
cTFBC5: 7,
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( 'basis_transcoder.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( 'basis_transcoder.wasm', resolve, undefined, reject );
} );
this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
.then( ( [ jsContent, binaryContent ] ) => {
var fn = THREE.BasisTextureLoader.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.BasisTextureLoader: 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.BasisTextureLoader.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/
const DXT_FORMAT_MAP = {};
const COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
const COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
const COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
const COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
DXT_FORMAT_MAP[ BASIS_FORMAT.cTFBC1 ] = COMPRESSED_RGB_S3TC_DXT1_EXT;
DXT_FORMAT_MAP[ BASIS_FORMAT.cTFBC3 ] = COMPRESSED_RGBA_S3TC_DXT5_EXT;
THREE.BasisTextureLoader.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.BasisTextureLoader.DXT_FORMAT_MAP = {};
THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC1 ] =
THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGB_S3TC_DXT1_EXT;
THREE.BasisTextureLoader.DXT_FORMAT_MAP[ THREE.BasisTextureLoader.BASIS_FORMAT.cTFBC3 ] =
THREE.BasisTextureLoader.DXT_FORMAT.COMPRESSED_RGBA_S3TC_DXT5_EXT;
THREE.BasisTextureLoader.BASIS_FORMAT = BASIS_FORMAT;
THREE.BasisTextureLoader.DXT_FORMAT_MAP = DXT_FORMAT_MAP;
// ASTC formats, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
THREE.BasisTextureLoader.COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
/* WEB WORKER */
THREE.BasisTextureLoader.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.BasisTextureLoader: Invalid .basis file' );
}
if ( ! basisFile.startTranscoding() ) {
cleanup();
throw new Error( 'THREE.BasisTextureLoader: .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.BasisTextureLoader: .transcodeImage failed.' );
}
mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } );
}
cleanup();
return { width, height, hasAlpha, mipmaps };
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
<!DOCTYPE html>
<head>
<script src="https://cdn.jsdelivr.net/npm/three@v0.104.0"></script>
<script src="https://cdn.jsdelivr.net/npm/three@v0.108.0"></script>
<script src="GLTFLoader.js"></script>
<script src="BasisTextureLoader.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>
<!-- <script src="../transcoder/build/basis_transcoder.js"></script> -->
<style>
html, body { width:100%; height:100%; margin:0; padding:0; }
canvas { display:block; }
@@ -14,90 +14,104 @@
</head>
<body>
<div id="panel">
<strong>Basis Texture Transcoder glTF Demo</strong>
<div id="log"></div>
</div>
<script>
BASIS().then(Module => {
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.gammaOutput = true;
renderer.gammaFactor = 2.2;
const { BasisFile, initializeBasis } = Module;
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
window.BasisFile = BasisFile;
const light = new THREE.AmbientLight();
scene.add( light );
initializeBasis();
const light2 = new THREE.PointLight();
scene.add( light2 );
// Initialize three.js scene and renderer.
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 renderer = new THREE.WebGLRenderer( { antialias: true } );
const controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.autoRotate = true;
// Create BasisTextureLoader and detect supported target formats.
const basisLoader = new THREE.BasisTextureLoader();
basisLoader.setTranscoderPath( '../transcoder/build/' );
basisLoader.useAlpha = false;
basisLoader.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 BasisTextureLoader for .basis extension.
THREE.Loader.Handlers.add( /\.basis$/, basisLoader );
// Create GLTFLoader, load model, and render.
const loader = new THREE.GLTFLoader();
loader.load( 'assets/AgiHqSmall.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 );
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 basisLoader = new THREE.BasisTextureLoader();
basisLoader.detectSupport( renderer );
const formatName = basisLoader.etcSupported
? 'ETC1'
: ( basisLoader.dxtSupported ? 'BC1' : 'PVRTC' );
log(`Transcode to: <strong>${formatName}</strong>`);
// Register BasisTextureLoader for .basis extension.
THREE.Loader.Handlers.add( /\.basis$/, basisLoader );
// Create GLTFLoader, load model, and render.
const loader = new THREE.GLTFLoader();
loader.load( 'assets/AgiHqSmall.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 );
});
}, false );
function log(s) {
@@ -107,11 +121,7 @@
}
</script>
<div id="panel">
<strong>Basis Texture Transcoder glTF Demo</strong>
<div id="log"></div>
</div>
</body>
</html>

View File

@@ -79,6 +79,10 @@ function loadArrayBuffer(uri, callback) {
xhr.send(null);
}
// ASTC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0;
// DXT formats, from:
// http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
@@ -86,15 +90,33 @@ COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
// ETC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/
COMPRESSED_RGB_ETC1_WEBGL = 0x8D64;
// PVRTC format, from:
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
BASIS_FORMAT = {
cTFETC1: 0,
cTFBC1: 1,
cTFBC4: 2,
cTFPVRTC1_4_OPAQUE_ONLY: 3,
cTFBC7_M6_OPAQUE_ONLY: 4,
cTFETC2: 5,
cTFBC3: 6,
cTFBC5: 7,
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,
};
BASIS_FORMAT_NAMES = {};
@@ -106,7 +128,10 @@ DXT_FORMAT_MAP = {};
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC1] = COMPRESSED_RGB_S3TC_DXT1_EXT;
DXT_FORMAT_MAP[BASIS_FORMAT.cTFBC3] = COMPRESSED_RGBA_S3TC_DXT5_EXT;
var dxtSupported = true;
var astcSupported = false;
var etcSupported = false;
var dxtSupported = false;
var pvrtcSupported = false;
var drawMode = 0;
var tex, width, height, images, levels, have_alpha, alignedWidth, alignedHeight, format;
@@ -116,18 +141,7 @@ function redraw()
if (!width)
return;
if (dxtSupported)
{
renderer.drawTexture(tex, alignedWidth, alignedHeight, drawMode);
}
else if (format == BASIS_FORMAT.cTFBC1)
{
renderer.drawTexture(tex, alignedWidth, alignedHeight, drawMode);
}
else
{
log('TODO: Implement BC3->RGBA conversion for browsers that don\'t support BC3');
}
renderer.drawTexture(tex, alignedWidth, alignedHeight, drawMode);
}
function dataLoaded(data)
@@ -154,18 +168,52 @@ function dataLoaded(data)
return;
}
format = BASIS_FORMAT.cTFBC1;
if (has_alpha)
var formatString = 'UNKNOWN';
if (astcSupported)
{
format = BASIS_FORMAT.cTFBC3;
log('Decoding .basis data to BC3');
formatString = 'ASTC';
format = BASIS_FORMAT.cTFASTC_4x4;
}
else if (dxtSupported)
{
if (has_alpha)
{
formatString = 'BC3';
format = BASIS_FORMAT.cTFBC3;
}
else
{
formatString = 'BC1';
format = BASIS_FORMAT.cTFBC1;
}
}
else if (pvrtcSupported)
{
if (has_alpha)
{
formatString = 'PVRTC1_RGBA';
format = BASIS_FORMAT.cTFPVRTC1_4_RGBA;
}
else
{
formatString = 'PVRTC1_RGB';
format = BASIS_FORMAT.cTFPVRTC1_4_RGB;
}
}
else if (etcSupported)
{
formatString = 'ETC1';
format = BASIS_FORMAT.cTFETC1;
}
else
{
log('Decoding .basis data to BC1');
formatString = 'RGB565';
format = BASIS_FORMAT.cTFBC1;
log('Decoding .basis data to BC1');
}
elem('format').innerText = formatString;
if (!basisFile.startTranscoding()) {
console.warn('startTranscoding failed');
basisFile.close();
@@ -202,12 +250,29 @@ function dataLoaded(data)
canvas.width = alignedWidth;
canvas.height = alignedHeight;
if (dxtSupported)
if (format === BASIS_FORMAT.cTFASTC_4x4)
{
tex = renderer.createDxtTexture(dst, alignedWidth, alignedHeight, DXT_FORMAT_MAP[format]);
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGBA_ASTC_4x4_KHR);
}
else if (formatString != 'RGB565' && (format === BASIS_FORMAT.cTFBC3 || format === BASIS_FORMAT.cTFBC1))
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, DXT_FORMAT_MAP[format]);
}
else if (format === BASIS_FORMAT.cTFETC1)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGB_ETC1_WEBGL);
}
else if (format === BASIS_FORMAT.cTFPVRTC1_4_RGB)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGB_PVRTC_4BPPV1_IMG);
}
else if (format === BASIS_FORMAT.cTFPVRTC1_4_RGBA)
{
tex = renderer.createCompressedTexture(dst, alignedWidth, alignedHeight, COMPRESSED_RGBA_PVRTC_4BPPV1_IMG);
}
else
{
log('BC1 to RGB565');
var rgb565Data = dxtToRgb565(new Uint16Array(dst.buffer), 0, alignedWidth, alignedHeight);
tex = renderer.createRgb565Texture(rgb565Data, alignedWidth, alignedHeight);
@@ -230,10 +295,10 @@ function viewAlpha() { drawMode = 2; redraw(); }
<body>
<br>
<div style="font-size: 24pt; font-weight: bold">
.basis->BC1/BC3 transcoder test
.basis->Compressed texture transcoder test
</div>
<br>This demo uses the Basis C++ transcoder (compiled to Javascript using Emscripten) to transcode a .basis file to BC1/BC3.
<br>This demo uses the Basis C++ transcoder (compiled to Javascript using Emscripten) to transcode a .basis file to <b id='format'>FORMAT</b>
<br>Thanks to Evan Parker for providing <a href="https://github.com/toji/webgl-texture-utils">webgl-texture-utils</a> and this test bed.
<br>
<br>
@@ -247,10 +312,8 @@ function viewAlpha() { drawMode = 2; redraw(); }
<input type="button" value="View Alpha" onclick="viewAlpha()"></input>
<div style="position:absolute; left: 525px; top:130px; font-size: 20pt; font-weight: bold; color: red">
<div id="nodxt" style="display: none; width: 768px; font-size: 20pt; font-weight: bold; color: red">
NOTE: Your browser does not support DXT, so using RGB565
for the texture below. To get DXT, try Chrome 19+
(beta channel as of 2012-04-20; graphics card DXT support also required).
<div id="no-compressed-tex" style="display: none; width: 768px; font-size: 20pt; font-weight: bold; color: red">
NOTE: Your browser does not support several compressed texture format, so using RGB565.
</div>
<canvas id='canvas'></canvas>
</div>
@@ -260,9 +323,14 @@ function viewAlpha() { drawMode = 2; redraw(); }
<script>
BASIS({onRuntimeInitialized : () => {
var gl = elem('canvas').getContext('webgl');
// Load the DXT extension, and verify it exists.
if (!gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc') && !gl.getExtension('WEBGL_compressed_texture_s3tc')) {
dxtSupported = false;
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'));
if (!(astcSupported || etcSupported || dxtSupported || pvrtcSupported))
{
elem('nodxt').style.display = 'block';
}

View File

@@ -115,6 +115,27 @@ Renderer.prototype.createDxtTexture = function(dxtData, width, height, format) {
return tex;
};
Renderer.prototype.createCompressedTexture = function(data, width, height, format) {
var gl = this.gl_;
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.compressedTexImage2D(
gl.TEXTURE_2D,
0,
format,
width,
height,
0,
data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
//gl.generateMipmap(gl.TEXTURE_2D)
gl.bindTexture(gl.TEXTURE_2D, null);
return tex;
};
Renderer.prototype.createRgb565Texture = function(rgb565Data, width, height) {
var gl = this.gl_;

View File

@@ -119,7 +119,7 @@ struct basis_file
if (!m_transcoder.get_image_level_desc(m_file.data(), m_file.size(), image_index, level_index, orig_width, orig_height, total_blocks))
return 0;
if (format == cTFPVRTC1_4_OPAQUE_ONLY)
if (format == cTFPVRTC1_4_RGB || format == cTFPVRTC1_4_RGBA)
{
// For PVRTC1, Basis only writes (or requires) total_blocks * bytes_per_block. But GL requires extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
@@ -156,7 +156,7 @@ struct basis_file
uint32_t required_size = total_blocks * bytes_per_block;
if (format == cTFPVRTC1_4_OPAQUE_ONLY)
if (format == cTFPVRTC1_4_RGB || format == cTFPVRTC1_4_RGBA)
{
// For PVRTC1, Basis only writes (or requires) total_blocks * bytes_per_block. But GL requires extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt

File diff suppressed because one or more lines are too long