-
-
Notifications
You must be signed in to change notification settings - Fork 35.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
support all texture formats and types in `DataTextureArray.setLayerUp…
…date` and `CompressedTextureArray.setLayerUpdate` (#28654) * create TextureUtils * docs * use tabs instead of spaces * fix lint * use a byte view * remove typo * ensure the unit8array adheres to the byteLength of the input data source * add example * update screenshot * fix indentation * convert more spaces to tabs * generate screenshot * generate screenshot * test increase debounce and render in interval instead of loop * cleanup example * preserve parity between type and typedarray constructor * remove dependency on setInterval * update screenshot --------- Co-authored-by: Renaud Rohlinger <renaud.rohlinger@gmail.com>
- Loading branch information
1 parent
01dc3d9
commit 9c00c1c
Showing
7 changed files
with
376 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<base href="../../../" /> | ||
<script src="page.js"></script> | ||
<link type="text/css" rel="stylesheet" href="page.css" /> | ||
</head> | ||
<body> | ||
<h1>[name]</h1> | ||
|
||
<p class="desc">A class containing utility functions for textures.</p> | ||
|
||
<h2>Methods</h2> | ||
|
||
<h3>[method:Number getByteLength]( [param:Number width], [param:Number height], [param:Number format], [param:Number type] )</h3> | ||
<p> | ||
Given the width, height, format, and type of a texture. Determines how | ||
many bytes must be used to represent the texture. | ||
</p> | ||
|
||
<h2>Source</h2> | ||
|
||
<p> | ||
[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] | ||
</p> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<title>three.js webgl - texture array layer update</title> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> | ||
<link type="text/css" rel="stylesheet" href="main.css"> | ||
</head> | ||
<script id="vs" type="x-shader/x-vertex"> | ||
uniform vec2 size; | ||
attribute uint instancedIndex; | ||
flat out uint diffuseIndex; | ||
out vec2 vUv; | ||
|
||
void main() { | ||
|
||
vec3 translation = vec3(0, float(instancedIndex) * size.y - size.y, 0); | ||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position + translation, 1.0 ); | ||
|
||
diffuseIndex = instancedIndex; | ||
|
||
// Convert position.xy to 1.0-0.0 | ||
|
||
vUv.xy = position.xy / size + 0.5; | ||
vUv.y = 1.0 - vUv.y; // original data is upside down | ||
|
||
} | ||
</script> | ||
|
||
<script id="fs" type="x-shader/x-fragment"> | ||
precision highp float; | ||
precision highp int; | ||
precision highp sampler2DArray; | ||
|
||
uniform sampler2DArray diffuse; | ||
in vec2 vUv; | ||
flat in uint diffuseIndex; | ||
|
||
out vec4 outColor; | ||
|
||
void main() { | ||
|
||
outColor = texture( diffuse, vec3( vUv, diffuseIndex ) ); | ||
|
||
} | ||
</script> | ||
<body> | ||
<div id="info"> | ||
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - 2D Compressed Texture Array Layer Updates<br /> | ||
Loop from the movie Spirited away | ||
by the <a href="https://www.ghibli.jp/" target="_blank" rel="noopener">Studio Ghibli</a><br /> | ||
</div> | ||
|
||
<script type="importmap"> | ||
{ | ||
"imports": { | ||
"three": "../build/three.module.js", | ||
"three/addons/": "./jsm/" | ||
} | ||
} | ||
</script> | ||
|
||
<script type="module"> | ||
|
||
import * as THREE from 'three'; | ||
|
||
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; | ||
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'; | ||
|
||
let camera, scene, mesh, renderer; | ||
|
||
const planeWidth = 20; | ||
const planeHeight = 10; | ||
|
||
init(); | ||
|
||
async function init() { | ||
|
||
const container = document.createElement( 'div' ); | ||
document.body.appendChild( container ); | ||
|
||
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 2000 ); | ||
camera.position.z = 70; | ||
|
||
scene = new THREE.Scene(); | ||
|
||
// Configure the renderer. | ||
|
||
renderer = new THREE.WebGLRenderer( { antialias: true } ); | ||
renderer.setPixelRatio( window.devicePixelRatio ); | ||
renderer.setSize( window.innerWidth, window.innerHeight ); | ||
container.appendChild( renderer.domElement ); | ||
|
||
// Configure the KTX2 loader. | ||
|
||
const ktx2Loader = new KTX2Loader(); | ||
ktx2Loader.setTranscoderPath( 'jsm/libs/basis/' ); | ||
ktx2Loader.detectSupport( renderer ); | ||
|
||
// Load several KTX2 textures which will later be used to modify | ||
// specific texture array layers. | ||
|
||
const spiritedaway = await ktx2Loader.loadAsync( 'textures/spiritedaway.ktx2' ); | ||
|
||
// Create a texture array for rendering. | ||
|
||
const layerByteLength = THREE.TextureUtils.getByteLength( | ||
spiritedaway.image.width, | ||
spiritedaway.image.height, | ||
spiritedaway.format, | ||
spiritedaway.type, | ||
); | ||
|
||
const textureArray = new THREE.CompressedArrayTexture( [ | ||
{ | ||
data: new Uint8Array( layerByteLength * 3 ), | ||
width: spiritedaway.image.width, | ||
height: spiritedaway.image.height, | ||
} | ||
], spiritedaway.image.width, spiritedaway.image.height, 3, spiritedaway.format, spiritedaway.type ); | ||
|
||
// Setup the GUI | ||
|
||
const formData = { | ||
srcLayer: 0, | ||
destLayer: 0, | ||
transfer() { | ||
const layerElementLength = layerByteLength / spiritedaway.mipmaps[ 0 ].data.BYTES_PER_ELEMENT; | ||
textureArray.mipmaps[ 0 ].data.set( | ||
spiritedaway.mipmaps[ 0 ].data.subarray( | ||
layerElementLength * ( formData.srcLayer % spiritedaway.image.depth ), | ||
layerElementLength * ( ( formData.srcLayer % spiritedaway.image.depth ) + 1 ), | ||
), | ||
layerByteLength * formData.destLayer, | ||
); | ||
textureArray.addLayerUpdate( formData.destLayer ); | ||
textureArray.needsUpdate = true; | ||
|
||
renderer.render( scene, camera ); | ||
}, | ||
}; | ||
|
||
const gui = new GUI(); | ||
gui.add(formData, 'srcLayer', 0, spiritedaway.image.depth - 1, 1); | ||
gui.add(formData, 'destLayer', 0, textureArray.image.depth - 1, 1); | ||
gui.add(formData, 'transfer'); | ||
|
||
/// Setup the scene. | ||
|
||
const material = new THREE.ShaderMaterial( { | ||
uniforms: { | ||
diffuse: { value: textureArray }, | ||
size: { value: new THREE.Vector2( planeWidth, planeHeight ) } | ||
}, | ||
vertexShader: document.getElementById( 'vs' ).textContent.trim(), | ||
fragmentShader: document.getElementById( 'fs' ).textContent.trim(), | ||
glslVersion: THREE.GLSL3 | ||
} ); | ||
|
||
const geometry = new THREE.InstancedBufferGeometry(); | ||
geometry.copy( new THREE.PlaneGeometry( planeWidth, planeHeight ) ); | ||
geometry.instanceCount = 3; | ||
|
||
const instancedIndexAttribute = new THREE.InstancedBufferAttribute( | ||
new Uint16Array( [ 0, 1, 2 ] ), 1, false, 1 | ||
); | ||
instancedIndexAttribute.gpuType = THREE.IntType; | ||
geometry.setAttribute( 'instancedIndex', instancedIndexAttribute ); | ||
|
||
mesh = new THREE.InstancedMesh( geometry, material, 3 ); | ||
|
||
scene.add( mesh ); | ||
|
||
window.addEventListener( 'resize', onWindowResize ); | ||
|
||
} | ||
|
||
function onWindowResize() { | ||
|
||
renderer.setSize( window.innerWidth, window.innerHeight ); | ||
|
||
camera.aspect = window.innerWidth / window.innerHeight; | ||
camera.updateProjectionMatrix(); | ||
|
||
renderer.render( scene, camera ); | ||
|
||
} | ||
|
||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import { AlphaFormat, LuminanceFormat, LuminanceAlphaFormat, RedFormat, RedIntegerFormat, RGFormat, RGIntegerFormat, RGBFormat, RGBAFormat, RGBAIntegerFormat, RGB_S3TC_DXT1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGB_PVRTC_2BPPV1_Format, RGBA_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_BPTC_Format, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, RED_GREEN_RGTC2_Format, SIGNED_RED_GREEN_RGTC2_Format, UnsignedByteType, ByteType, UnsignedShortType, ShortType, HalfFloatType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedIntType, IntType, FloatType, UnsignedInt5999Type } from '../constants.js'; | ||
|
||
/** | ||
* Given the width, height, format, and type of a texture. Determines how many | ||
* bytes must be used to represent the texture. | ||
*/ | ||
function getByteLength( width, height, format, type ) { | ||
|
||
const typeByteLength = getTextureTypeByteLength( type ); | ||
|
||
switch ( format ) { | ||
|
||
// https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml | ||
case AlphaFormat: | ||
return width * height; | ||
case LuminanceFormat: | ||
return width * height; | ||
case LuminanceAlphaFormat: | ||
return width * height * 2; | ||
case RedFormat: | ||
return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; | ||
case RedIntegerFormat: | ||
return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; | ||
case RGFormat: | ||
return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; | ||
case RGIntegerFormat: | ||
return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; | ||
case RGBFormat: | ||
return ( ( width * height * 3 ) / typeByteLength.components ) * typeByteLength.byteLength; | ||
case RGBAFormat: | ||
return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; | ||
case RGBAIntegerFormat: | ||
return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; | ||
|
||
// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc_srgb/ | ||
case RGB_S3TC_DXT1_Format: | ||
case RGBA_S3TC_DXT1_Format: | ||
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; | ||
case RGBA_S3TC_DXT3_Format: | ||
case RGBA_S3TC_DXT5_Format: | ||
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; | ||
|
||
// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_pvrtc/ | ||
case RGB_PVRTC_2BPPV1_Format: | ||
case RGBA_PVRTC_2BPPV1_Format: | ||
return ( Math.max( width, 16 ) * Math.max( height, 8 ) ) / 4; | ||
case RGB_PVRTC_4BPPV1_Format: | ||
case RGBA_PVRTC_4BPPV1_Format: | ||
return ( Math.max( width, 8 ) * Math.max( height, 8 ) ) / 2; | ||
|
||
// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_etc/ | ||
case RGB_ETC1_Format: | ||
case RGB_ETC2_Format: | ||
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; | ||
case RGBA_ETC2_EAC_Format: | ||
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; | ||
|
||
// https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_astc/ | ||
case RGBA_ASTC_4x4_Format: | ||
return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; | ||
case RGBA_ASTC_5x4_Format: | ||
return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 3 ) / 4 ) * 16; | ||
case RGBA_ASTC_5x5_Format: | ||
return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 4 ) / 5 ) * 16; | ||
case RGBA_ASTC_6x5_Format: | ||
return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 4 ) / 5 ) * 16; | ||
case RGBA_ASTC_6x6_Format: | ||
return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 5 ) / 6 ) * 16; | ||
case RGBA_ASTC_8x5_Format: | ||
return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 4 ) / 5 ) * 16; | ||
case RGBA_ASTC_8x6_Format: | ||
return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 5 ) / 6 ) * 16; | ||
case RGBA_ASTC_8x8_Format: | ||
return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 7 ) / 8 ) * 16; | ||
case RGBA_ASTC_10x5_Format: | ||
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 4 ) / 5 ) * 16; | ||
case RGBA_ASTC_10x6_Format: | ||
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 5 ) / 6 ) * 16; | ||
case RGBA_ASTC_10x8_Format: | ||
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 7 ) / 8 ) * 16; | ||
case RGBA_ASTC_10x10_Format: | ||
return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 9 ) / 10 ) * 16; | ||
case RGBA_ASTC_12x10_Format: | ||
return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 9 ) / 10 ) * 16; | ||
case RGBA_ASTC_12x12_Format: | ||
return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 11 ) / 12 ) * 16; | ||
|
||
// https://registry.khronos.org/webgl/extensions/EXT_texture_compression_bptc/ | ||
case RGBA_BPTC_Format: | ||
case RGB_BPTC_SIGNED_Format: | ||
case RGB_BPTC_UNSIGNED_Format: | ||
return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; | ||
|
||
// https://registry.khronos.org/webgl/extensions/EXT_texture_compression_rgtc/ | ||
case RED_RGTC1_Format: | ||
case SIGNED_RED_RGTC1_Format: | ||
return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 8; | ||
case RED_GREEN_RGTC2_Format: | ||
case SIGNED_RED_GREEN_RGTC2_Format: | ||
return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; | ||
|
||
} | ||
|
||
throw new Error( | ||
`Unable to determine texture byte length for ${format} format.`, | ||
); | ||
|
||
} | ||
|
||
function getTextureTypeByteLength( type ) { | ||
|
||
switch ( type ) { | ||
|
||
case UnsignedByteType: | ||
case ByteType: | ||
return { byteLength: 1, components: 1 }; | ||
case UnsignedShortType: | ||
case ShortType: | ||
case HalfFloatType: | ||
return { byteLength: 2, components: 1 }; | ||
case UnsignedShort4444Type: | ||
case UnsignedShort5551Type: | ||
return { byteLength: 2, components: 4 }; | ||
case UnsignedIntType: | ||
case IntType: | ||
case FloatType: | ||
return { byteLength: 4, components: 1 }; | ||
case UnsignedInt5999Type: | ||
return { byteLength: 4, components: 3 }; | ||
|
||
} | ||
|
||
throw new Error( `Unknown texture type ${type}.` ); | ||
|
||
} | ||
|
||
const TextureUtils = { | ||
getByteLength, | ||
}; | ||
|
||
export { getByteLength, TextureUtils }; |
Oops, something went wrong.