Browse Source

KTX2Loader: Support ETC2, BCn, and ASTC 4x4 sRGB (#31155)

* KTX2Loader: Support .setPath()

* KTX2Loader: Add compressed formats

* Examples: Organize KTX2 example in sections

* update examples, fix missing formats

* clean up

* clean up

* clean up

* remove unused textures

* remove ktx2 example from E2E :(
Don McCurdy 9 months ago
parent
commit
a5a4983fd3
29 changed files with 278 additions and 131 deletions
  1. 66 26
      examples/jsm/loaders/KTX2Loader.js
  2. BIN
      examples/screenshots/webgl_loader_texture_ktx2.jpg
  3. BIN
      examples/textures/compressed/2d_astc_6x6.ktx2
  4. BIN
      examples/textures/compressed/2d_etc1s.ktx2
  5. BIN
      examples/textures/compressed/2d_rgba16_linear.ktx2
  6. BIN
      examples/textures/compressed/2d_rgba16_uastc_hdr_linear.ktx2
  7. BIN
      examples/textures/compressed/2d_rgba32_linear.ktx2
  8. BIN
      examples/textures/compressed/2d_rgba8.ktx2
  9. BIN
      examples/textures/compressed/2d_rgba8_displayp3.ktx2
  10. BIN
      examples/textures/compressed/2d_rgba8_linear.ktx2
  11. BIN
      examples/textures/compressed/2d_uastc.ktx2
  12. BIN
      examples/textures/compressed/sample_etc1s.ktx2
  13. BIN
      examples/textures/compressed/sample_uastc.ktx2
  14. BIN
      examples/textures/compressed/sample_uastc_zstd.ktx2
  15. BIN
      examples/textures/ktx2/2d_astc4x4.ktx2
  16. BIN
      examples/textures/ktx2/2d_bc1.ktx2
  17. BIN
      examples/textures/ktx2/2d_bc3.ktx2
  18. BIN
      examples/textures/ktx2/2d_bc5.ktx2
  19. BIN
      examples/textures/ktx2/2d_bc7.ktx2
  20. BIN
      examples/textures/ktx2/2d_etc1.ktx2
  21. BIN
      examples/textures/ktx2/2d_etc1s.ktx2
  22. BIN
      examples/textures/ktx2/2d_etc2.ktx2
  23. BIN
      examples/textures/ktx2/2d_rgba16_linear.ktx2
  24. BIN
      examples/textures/ktx2/2d_rgba32_linear.ktx2
  25. BIN
      examples/textures/ktx2/2d_rgba8.ktx2
  26. BIN
      examples/textures/ktx2/2d_rgba8_linear.ktx2
  27. BIN
      examples/textures/ktx2/2d_uastc.ktx2
  28. 211 105
      examples/webgl_loader_texture_ktx2.html
  29. 1 0
      test/e2e/puppeteer.js

+ 66 - 26
examples/jsm/loaders/KTX2Loader.js

@@ -1,60 +1,76 @@
 import {
-	CompressedTexture,
 	CompressedArrayTexture,
 	CompressedCubeTexture,
+	CompressedTexture,
 	Data3DTexture,
 	DataTexture,
 	FileLoader,
 	FloatType,
 	HalfFloatType,
-	NoColorSpace,
 	LinearFilter,
 	LinearMipmapLinearFilter,
 	LinearSRGBColorSpace,
 	Loader,
-	RedFormat,
-	RGB_BPTC_UNSIGNED_Format,
-	RGB_ETC1_Format,
-	RGB_ETC2_Format,
-	RGB_PVRTC_4BPPV1_Format,
+	NoColorSpace,
+	RGBAFormat,
 	RGBA_ASTC_4x4_Format,
 	RGBA_ASTC_6x6_Format,
 	RGBA_BPTC_Format,
+	RGBA_S3TC_DXT3_Format,
 	RGBA_ETC2_EAC_Format,
 	RGBA_PVRTC_4BPPV1_Format,
-	RGBA_S3TC_DXT5_Format,
 	RGBA_S3TC_DXT1_Format,
-	RGBAFormat,
+	RGBA_S3TC_DXT5_Format,
+	RGB_BPTC_UNSIGNED_Format,
+	RGB_ETC1_Format,
+	RGB_ETC2_Format,
+	RGB_PVRTC_4BPPV1_Format,
+	RGB_S3TC_DXT1_Format,
 	RGFormat,
+	RedFormat,
 	SRGBColorSpace,
-	UnsignedByteType,
+	UnsignedByteType
 } from 'three';
 import { WorkerPool } from '../utils/WorkerPool.js';
 import {
 	read,
 	KHR_DF_FLAG_ALPHA_PREMULTIPLIED,
+	KHR_DF_PRIMARIES_BT709,
+	KHR_DF_PRIMARIES_DISPLAYP3,
+	KHR_DF_PRIMARIES_UNSPECIFIED,
 	KHR_DF_TRANSFER_SRGB,
 	KHR_SUPERCOMPRESSION_NONE,
 	KHR_SUPERCOMPRESSION_ZSTD,
-	VK_FORMAT_UNDEFINED,
-	VK_FORMAT_R16_SFLOAT,
-	VK_FORMAT_R16G16_SFLOAT,
+	VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT,
+	VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
+	VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
+	VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
+	VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
+	VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
+	VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
+	VK_FORMAT_BC1_RGB_SRGB_BLOCK,
+	VK_FORMAT_BC1_RGB_UNORM_BLOCK,
+	VK_FORMAT_BC3_SRGB_BLOCK,
+	VK_FORMAT_BC3_UNORM_BLOCK,
+	VK_FORMAT_BC5_SNORM_BLOCK,
+	VK_FORMAT_BC5_UNORM_BLOCK,
+	VK_FORMAT_BC7_SRGB_BLOCK,
+	VK_FORMAT_BC7_UNORM_BLOCK,
+	VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK,
+	VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK,
 	VK_FORMAT_R16G16B16A16_SFLOAT,
-	VK_FORMAT_R32_SFLOAT,
-	VK_FORMAT_R32G32_SFLOAT,
+	VK_FORMAT_R16G16_SFLOAT,
+	VK_FORMAT_R16_SFLOAT,
 	VK_FORMAT_R32G32B32A32_SFLOAT,
-	VK_FORMAT_R8_SRGB,
-	VK_FORMAT_R8_UNORM,
-	VK_FORMAT_R8G8_SRGB,
-	VK_FORMAT_R8G8_UNORM,
+	VK_FORMAT_R32G32_SFLOAT,
+	VK_FORMAT_R32_SFLOAT,
 	VK_FORMAT_R8G8B8A8_SRGB,
 	VK_FORMAT_R8G8B8A8_UNORM,
-	VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT,
-	VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
-	VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
-	KHR_DF_PRIMARIES_UNSPECIFIED,
-	KHR_DF_PRIMARIES_BT709,
-	KHR_DF_PRIMARIES_DISPLAYP3
+	VK_FORMAT_R8G8_SRGB,
+	VK_FORMAT_R8G8_UNORM,
+	VK_FORMAT_R8_SRGB,
+	VK_FORMAT_R8_UNORM,
+	VK_FORMAT_UNDEFINED
 } from '../libs/ktx-parse.module.js';
 import { ZSTDDecoder } from '../libs/zstddec.module.js';
 import { DisplayP3ColorSpace, LinearDisplayP3ColorSpace } from '../math/ColorSpaces.js';
@@ -311,8 +327,10 @@ class KTX2Loader extends Loader {
 
 		const loader = new FileLoader( this.manager );
 
-		loader.setResponseType( 'arraybuffer' );
+		loader.setPath( this.path );
+		loader.setCrossOrigin( this.crossOrigin );
 		loader.setWithCredentials( this.withCredentials );
+		loader.setResponseType( 'arraybuffer' );
 
 		loader.load( url, ( buffer ) => {
 
@@ -919,10 +937,29 @@ const FORMAT_MAP = {
 	[ VK_FORMAT_R8_SRGB ]: RedFormat,
 	[ VK_FORMAT_R8_UNORM ]: RedFormat,
 
+	[ VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK ]: RGB_ETC2_Format,
+	[ VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK ]: RGBA_ETC2_EAC_Format,
+
 	[ VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT ]: RGBA_ASTC_4x4_Format,
+	[ VK_FORMAT_ASTC_4x4_SRGB_BLOCK ]: RGBA_ASTC_4x4_Format,
+	[ VK_FORMAT_ASTC_4x4_UNORM_BLOCK ]: RGBA_ASTC_4x4_Format,
 	[ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: RGBA_ASTC_6x6_Format,
 	[ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: RGBA_ASTC_6x6_Format,
 
+	[ VK_FORMAT_BC1_RGBA_UNORM_BLOCK ]: RGBA_S3TC_DXT1_Format,
+	[ VK_FORMAT_BC1_RGBA_SRGB_BLOCK ]: RGBA_S3TC_DXT1_Format,
+	[ VK_FORMAT_BC1_RGB_UNORM_BLOCK ]: RGB_S3TC_DXT1_Format,
+	[ VK_FORMAT_BC1_RGB_SRGB_BLOCK ]: RGB_S3TC_DXT1_Format,
+
+	[ VK_FORMAT_BC3_SRGB_BLOCK ]: RGBA_S3TC_DXT3_Format,
+	[ VK_FORMAT_BC3_UNORM_BLOCK ]: RGBA_S3TC_DXT3_Format,
+
+	[ VK_FORMAT_BC5_SNORM_BLOCK ]: RGBA_S3TC_DXT5_Format,
+	[ VK_FORMAT_BC5_UNORM_BLOCK ]: RGBA_S3TC_DXT5_Format,
+
+	[ VK_FORMAT_BC7_SRGB_BLOCK ]: RGBA_BPTC_Format,
+	[ VK_FORMAT_BC7_UNORM_BLOCK ]: RGBA_BPTC_Format,
+
 };
 
 const TYPE_MAP = {
@@ -942,6 +979,9 @@ const TYPE_MAP = {
 	[ VK_FORMAT_R8_SRGB ]: UnsignedByteType,
 	[ VK_FORMAT_R8_UNORM ]: UnsignedByteType,
 
+	[ VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK ]: UnsignedByteType,
+	[ VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK ]: UnsignedByteType,
+
 	[ VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT ]: HalfFloatType,
 	[ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: UnsignedByteType,
 	[ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: UnsignedByteType,

BIN
examples/screenshots/webgl_loader_texture_ktx2.jpg


BIN
examples/textures/compressed/2d_astc_6x6.ktx2


BIN
examples/textures/compressed/2d_etc1s.ktx2


BIN
examples/textures/compressed/2d_rgba16_linear.ktx2


BIN
examples/textures/compressed/2d_rgba16_uastc_hdr_linear.ktx2


BIN
examples/textures/compressed/2d_rgba32_linear.ktx2


BIN
examples/textures/compressed/2d_rgba8.ktx2


BIN
examples/textures/compressed/2d_rgba8_displayp3.ktx2


BIN
examples/textures/compressed/2d_rgba8_linear.ktx2


BIN
examples/textures/compressed/2d_uastc.ktx2


BIN
examples/textures/compressed/sample_etc1s.ktx2


BIN
examples/textures/compressed/sample_uastc.ktx2


BIN
examples/textures/compressed/sample_uastc_zstd.ktx2


BIN
examples/textures/ktx2/2d_astc4x4.ktx2


BIN
examples/textures/ktx2/2d_bc1.ktx2


BIN
examples/textures/ktx2/2d_bc3.ktx2


BIN
examples/textures/ktx2/2d_bc5.ktx2


BIN
examples/textures/ktx2/2d_bc7.ktx2


BIN
examples/textures/ktx2/2d_etc1.ktx2


BIN
examples/textures/ktx2/2d_etc1s.ktx2


BIN
examples/textures/ktx2/2d_etc2.ktx2


BIN
examples/textures/ktx2/2d_rgba16_linear.ktx2


BIN
examples/textures/ktx2/2d_rgba32_linear.ktx2


BIN
examples/textures/ktx2/2d_rgba8.ktx2


BIN
examples/textures/ktx2/2d_rgba8_linear.ktx2


BIN
examples/textures/ktx2/2d_uastc.ktx2


+ 211 - 105
examples/webgl_loader_texture_ktx2.html

@@ -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>

+ 1 - 0
test/e2e/puppeteer.js

@@ -74,6 +74,7 @@ const exceptionList = [
 	'webgl_interactive_lines',
 	'webgl_loader_collada_kinematics',
 	'webgl_loader_ldraw',
+	'webgl_loader_texture_ktx2',
 	'webgl_loader_pdb',
 	'webgl_modifier_simplifier',
 	'webgl_multiple_canvases_circle',

粤ICP备19079148号