|
|
@@ -5,13 +5,70 @@
|
|
|
<meta charset="utf-8">
|
|
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
|
|
<link type="text/css" rel="stylesheet" href="main.css">
|
|
|
+ <style>
|
|
|
+ * {
|
|
|
+ box-sizing: border-box;
|
|
|
+ -moz-box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ body {
|
|
|
+ background-color: #fff;
|
|
|
+ color: #444;
|
|
|
+ }
|
|
|
+
|
|
|
+ a {
|
|
|
+ color: #08f;
|
|
|
+ }
|
|
|
+
|
|
|
+ #content {
|
|
|
+ position: absolute;
|
|
|
+ top: 0; width: 100%;
|
|
|
+ z-index: 1;
|
|
|
+ padding: 3em 0 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ section {
|
|
|
+ padding: 1em;
|
|
|
+ }
|
|
|
+
|
|
|
+ #c {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ section .description {
|
|
|
+ max-width: 50em;
|
|
|
+ text-wrap: pretty;
|
|
|
+ }
|
|
|
+
|
|
|
+ .list-item {
|
|
|
+ display: inline-block;
|
|
|
+ margin: 1em;
|
|
|
+ padding: 1em;
|
|
|
+ box-shadow: 1px 2px 4px 0px rgba(0,0,0,0.25);
|
|
|
+ }
|
|
|
+
|
|
|
+ .list-item > div:nth-child(1) {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .list-item > div:nth-child(2) {
|
|
|
+ color: #888;
|
|
|
+ font-family: sans-serif;
|
|
|
+ width: 200px;
|
|
|
+ margin-top: 0.5em;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
</head>
|
|
|
<body>
|
|
|
|
|
|
- <div id="info">
|
|
|
- <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl - KTX2 texture loader<br />
|
|
|
- <a href="http://github.khronos.org/KTX-Specification/" target="_blank" rel="noopener">KTX2</a> with
|
|
|
- <a href="https://github.com/binomialLLC/basis_universal" target="_blank">Basis Universal GPU Texture Codec</a>
|
|
|
+ <canvas id="c"></canvas>
|
|
|
+
|
|
|
+ <div id="content">
|
|
|
+ <div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - KTX2 texture loader - webgl</div>
|
|
|
</div>
|
|
|
|
|
|
<script type="importmap">
|
|
|
@@ -28,146 +85,148 @@
|
|
|
import * as THREE from 'three';
|
|
|
|
|
|
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
|
|
|
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
- import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
|
|
-
|
|
|
- let camera, scene, renderer, controls, loader, material;
|
|
|
-
|
|
|
- const SAMPLES = {
|
|
|
- 'BasisU ETC1S': '2d_etc1s.ktx2',
|
|
|
- 'BasisU UASTC': '2d_uastc.ktx2',
|
|
|
- 'RGBA8 sRGB': '2d_rgba8.ktx2',
|
|
|
- 'RGBA8 Linear': '2d_rgba8_linear.ktx2',
|
|
|
- // 'RGBA8 Display P3': '2d_rgba8_displayp3.ktx2',
|
|
|
- 'RGBA16 Linear': '2d_rgba16_linear.ktx2',
|
|
|
- 'RGBA16 Linear (UASTC HDR)': '2d_rgba16_uastc_hdr_linear.ktx2',
|
|
|
- 'RGBA32 Linear': '2d_rgba32_linear.ktx2',
|
|
|
- 'ASTC 6x6 (mobile)': '2d_astc_6x6.ktx2',
|
|
|
- };
|
|
|
-
|
|
|
- const FORMAT_LABELS = {
|
|
|
- [ THREE.RGBAFormat ]: 'RGBA',
|
|
|
- [ THREE.RGBA_BPTC_Format ]: 'RGBA_BPTC',
|
|
|
- [ THREE.RGB_BPTC_UNSIGNED_Format ]: 'RGB_BPTC_UNSIGNED',
|
|
|
- [ THREE.RGBA_ASTC_4x4_Format ]: 'RGBA_ASTC_4x4',
|
|
|
- [ THREE.RGBA_ASTC_6x6_Format ]: 'RGBA_ASTC_6x6',
|
|
|
- [ THREE.RGB_S3TC_DXT1_Format ]: 'RGB_S3TC_DXT1',
|
|
|
- [ THREE.RGBA_S3TC_DXT5_Format ]: 'RGBA_S3TC_DXT5',
|
|
|
- [ THREE.RGB_PVRTC_4BPPV1_Format ]: 'RGB_PVRTC_4BPPV1',
|
|
|
- [ THREE.RGBA_PVRTC_4BPPV1_Format ]: 'RGBA_PVRTC_4BPPV1',
|
|
|
- [ THREE.RGB_ETC1_Format ]: 'RGB_ETC1',
|
|
|
- [ THREE.RGB_ETC2_Format ]: 'RGB_ETC2',
|
|
|
- [ THREE.RGBA_ETC2_EAC_Format ]: 'RGB_ETC2_EAC',
|
|
|
- };
|
|
|
-
|
|
|
- const TYPE_LABELS = {
|
|
|
- [ THREE.UnsignedByteType ]: 'UnsignedByteType',
|
|
|
- [ THREE.ByteType ]: 'ByteType',
|
|
|
- [ THREE.ShortType ]: 'ShortType',
|
|
|
- [ THREE.UnsignedShortType ]: 'UnsignedShortType',
|
|
|
- [ THREE.IntType ]: 'IntType',
|
|
|
- [ THREE.UnsignedIntType ]: 'UnsignedIntType',
|
|
|
- [ THREE.FloatType ]: 'FloatType',
|
|
|
- [ THREE.HalfFloatType ]: 'HalfFloatType',
|
|
|
- };
|
|
|
-
|
|
|
- const params = {
|
|
|
- sample: Object.values( SAMPLES )[ 0 ],
|
|
|
- };
|
|
|
+
|
|
|
+ let canvas, renderer;
|
|
|
+
|
|
|
+ const scenes = [];
|
|
|
+
|
|
|
+ const sections = [
|
|
|
+ {
|
|
|
+ title: 'Uncompressed',
|
|
|
+ description: 'Uncompressed formats (rgba8, rgba16, rgba32) load as THREE.DataTexture objects.'
|
|
|
+ + ' Lossless, easy to read/write, uncompressed on GPU, optionally compressed over the network.',
|
|
|
+ textures: [
|
|
|
+ { path: '2d_rgba8.ktx2' },
|
|
|
+ { path: '2d_rgba8_linear.ktx2' },
|
|
|
+ { path: '2d_rgba16_linear.ktx2' },
|
|
|
+ { path: '2d_rgba32_linear.ktx2' },
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: 'Compressed',
|
|
|
+ description: 'Compressed formats (ASTC, BCn, ...) load as THREE.CompressedTexture objects,'
|
|
|
+ + ' reducing memory cost. Requires native support on the device GPU: no single compressed'
|
|
|
+ + ' format is supported on every device.',
|
|
|
+ textures: [
|
|
|
+ { path: '2d_astc4x4.ktx2' },
|
|
|
+ { path: '2d_etc1.ktx2' },
|
|
|
+ { path: '2d_etc2.ktx2' },
|
|
|
+ { path: '2d_bc1.ktx2' },
|
|
|
+ { path: '2d_bc3.ktx2' },
|
|
|
+ // { path: '2d_bc5.ktx2' },
|
|
|
+ { path: '2d_bc7.ktx2' },
|
|
|
+ ]
|
|
|
+ },
|
|
|
+
|
|
|
+ {
|
|
|
+ title: 'Universal',
|
|
|
+ description: 'Basis Universal textures are specialized intermediate formats supporting fast'
|
|
|
+ + ' runtime transcoding into other GPU texture compression formats. After transcoding,'
|
|
|
+ + ' universal textures can be used on any device at reduced memory cost.',
|
|
|
+ textures: [
|
|
|
+ { path: '2d_etc1s.ktx2' },
|
|
|
+ { path: '2d_uastc.ktx2' },
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ ]
|
|
|
|
|
|
init();
|
|
|
|
|
|
async function init() {
|
|
|
|
|
|
- const width = window.innerWidth;
|
|
|
- const height = window.innerHeight;
|
|
|
+ canvas = document.getElementById( 'c' );
|
|
|
|
|
|
- renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
|
+ renderer = new THREE.WebGLRenderer( { canvas, antialias: true } );
|
|
|
+ renderer.setClearColor( 0xffffff, 1 );
|
|
|
renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
- renderer.setSize( width, height );
|
|
|
- document.body.appendChild( renderer.domElement );
|
|
|
|
|
|
- window.addEventListener( 'resize', onWindowResize );
|
|
|
+ const loader = new KTX2Loader()
|
|
|
+ .setTranscoderPath( 'jsm/libs/basis/' )
|
|
|
+ .setPath( 'textures/ktx2/' )
|
|
|
+ .detectSupport( renderer );
|
|
|
|
|
|
- scene = new THREE.Scene();
|
|
|
- scene.background = new THREE.Color( 0x202020 );
|
|
|
+ const geometry = flipY( new THREE.PlaneGeometry( 1, 1 ) );
|
|
|
|
|
|
- camera = new THREE.PerspectiveCamera( 60, width / height, 0.1, 100 );
|
|
|
- camera.position.set( 0, 0, 2.5 );
|
|
|
- camera.lookAt( scene.position );
|
|
|
- scene.add( camera );
|
|
|
+ const content = document.getElementById( 'content' );
|
|
|
|
|
|
- controls = new OrbitControls( camera, renderer.domElement );
|
|
|
+ for ( const section of sections ) {
|
|
|
|
|
|
- // PlaneGeometry UVs assume flipY=true, which compressed textures don't support.
|
|
|
- const geometry = flipY( new THREE.PlaneGeometry() );
|
|
|
- material = new THREE.MeshBasicMaterial( {
|
|
|
- color: 0xFFFFFF,
|
|
|
- side: THREE.DoubleSide,
|
|
|
- transparent: true,
|
|
|
- } );
|
|
|
- const mesh = new THREE.Mesh( geometry, material );
|
|
|
- scene.add( mesh );
|
|
|
+ const sectionElement = document.createElement( 'section' );
|
|
|
|
|
|
- loader = new KTX2Loader()
|
|
|
- .setTranscoderPath( 'jsm/libs/basis/' )
|
|
|
- .detectSupport( renderer );
|
|
|
+ const sectionHeader = document.createElement( 'h2' );
|
|
|
+ sectionHeader.textContent = section.title;
|
|
|
+ sectionElement.appendChild( sectionHeader );
|
|
|
|
|
|
- const gui = new GUI();
|
|
|
+ const sectionDescription = document.createElement( 'p' );
|
|
|
+ sectionDescription.className = 'description';
|
|
|
+ sectionDescription.textContent = section.description;
|
|
|
+ sectionElement.appendChild( sectionDescription );
|
|
|
|
|
|
- gui.add( params, 'sample', SAMPLES ).onChange( loadTexture );
|
|
|
+ for ( const { path, supported } of section.textures ) {
|
|
|
|
|
|
- await loadTexture( params.sample );
|
|
|
+ const scene = new THREE.Scene();
|
|
|
|
|
|
- renderer.setAnimationLoop( animate );
|
|
|
+ // make a list item
|
|
|
+ const element = document.createElement( 'div' );
|
|
|
+ element.className = 'list-item';
|
|
|
|
|
|
- }
|
|
|
+ const sceneElement = document.createElement( 'div' );
|
|
|
+ element.appendChild( sceneElement );
|
|
|
|
|
|
- function animate() {
|
|
|
+ const labelElement = document.createElement( 'div' );
|
|
|
+ labelElement.innerText = 'file: ' + path;
|
|
|
+ element.appendChild( labelElement );
|
|
|
|
|
|
- controls.update();
|
|
|
+ // the element that represents the area we want to render the scene
|
|
|
+ scene.userData.element = sceneElement;
|
|
|
+ sectionElement.appendChild( element );
|
|
|
|
|
|
- renderer.render( scene, camera );
|
|
|
+ const camera = new THREE.PerspectiveCamera( 50, 1, 1, 10 );
|
|
|
+ camera.position.z = 2;
|
|
|
+ scene.userData.camera = camera;
|
|
|
|
|
|
- }
|
|
|
+ try {
|
|
|
|
|
|
- function onWindowResize() {
|
|
|
+ const texture = await loader.loadAsync( supported === false ? 'fail_load.ktx2' : path );
|
|
|
+ const mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial({ map: texture }) );
|
|
|
|
|
|
- const width = window.innerWidth;
|
|
|
- const height = window.innerHeight;
|
|
|
+ labelElement.innerText += '\ncolorSpace: ' + texture.colorSpace;
|
|
|
|
|
|
- camera.aspect = width / height;
|
|
|
- camera.updateProjectionMatrix();
|
|
|
- renderer.setSize( width, height );
|
|
|
+ scene.add( mesh );
|
|
|
+ scenes.push( scene );
|
|
|
|
|
|
- }
|
|
|
+ } catch (e) {
|
|
|
|
|
|
- async function loadTexture( path ) {
|
|
|
+ console.error( `Failed to load ${path}`, e );
|
|
|
|
|
|
- try {
|
|
|
+ }
|
|
|
|
|
|
- const texture = await loader.loadAsync( `./textures/compressed/${path}` );
|
|
|
- texture.minFilter = THREE.NearestMipmapNearestFilter;
|
|
|
|
|
|
- material.map = texture;
|
|
|
- material.needsUpdate = true;
|
|
|
+ }
|
|
|
|
|
|
- console.info( `format: ${ FORMAT_LABELS[ texture.format ] }` );
|
|
|
- console.info( `type: ${ TYPE_LABELS[ texture.type ] }` );
|
|
|
- console.info( `colorSpace: ${ texture.colorSpace }` );
|
|
|
+ content.appendChild( sectionElement );
|
|
|
|
|
|
- } catch ( e ) {
|
|
|
+ }
|
|
|
|
|
|
- console.error( e );
|
|
|
+ renderer.setAnimationLoop( animate );
|
|
|
|
|
|
- }
|
|
|
|
|
|
- // NOTE: Call `loader.dispose()` when finished loading textures.
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateSize() {
|
|
|
+
|
|
|
+ const width = canvas.clientWidth;
|
|
|
+ const height = canvas.clientHeight;
|
|
|
+
|
|
|
+ if ( canvas.width !== width || canvas.height !== height ) {
|
|
|
|
|
|
+ renderer.setSize( width, height, false );
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
- /** Correct UVs to be compatible with `flipY=false` textures. */
|
|
|
+ /** Rewrite UVs for `flipY=false` textures. */
|
|
|
function flipY( geometry ) {
|
|
|
|
|
|
const uv = geometry.attributes.uv;
|
|
|
@@ -182,6 +241,53 @@
|
|
|
|
|
|
}
|
|
|
|
|
|
+ function animate() {
|
|
|
+
|
|
|
+ updateSize();
|
|
|
+
|
|
|
+ canvas.style.transform = `translateY(${window.scrollY}px)`;
|
|
|
+
|
|
|
+ renderer.setClearColor( 0xffffff );
|
|
|
+ renderer.setScissorTest( false );
|
|
|
+ renderer.clear();
|
|
|
+
|
|
|
+ renderer.setClearColor( 0xe0e0e0 );
|
|
|
+ renderer.setScissorTest( true );
|
|
|
+
|
|
|
+ scenes.forEach( function ( scene ) {
|
|
|
+
|
|
|
+ // get the element that is a place holder for where we want to
|
|
|
+ // draw the scene
|
|
|
+ const element = scene.userData.element;
|
|
|
+
|
|
|
+ // get its position relative to the page's viewport
|
|
|
+ const rect = element.getBoundingClientRect();
|
|
|
+
|
|
|
+ // check if it's offscreen. If so skip it
|
|
|
+ if ( rect.bottom < 0 || rect.top > renderer.domElement.clientHeight ||
|
|
|
+ rect.right < 0 || rect.left > renderer.domElement.clientWidth ) {
|
|
|
+
|
|
|
+ return; // it's off screen
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // set the viewport
|
|
|
+ const width = rect.right - rect.left;
|
|
|
+ const height = rect.bottom - rect.top;
|
|
|
+ const left = rect.left;
|
|
|
+ const bottom = renderer.domElement.clientHeight - rect.bottom;
|
|
|
+
|
|
|
+ renderer.setViewport( left, bottom, width, height );
|
|
|
+ renderer.setScissor( left, bottom, width, height );
|
|
|
+
|
|
|
+ const camera = scene.userData.camera;
|
|
|
+
|
|
|
+ renderer.render( scene, camera );
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
</script>
|
|
|
|
|
|
</body>
|