diff --git a/examples/jsm/helpers/TextureHelper.js b/examples/jsm/helpers/TextureHelper.js new file mode 100644 index 00000000000000..0e9e3e3c591908 --- /dev/null +++ b/examples/jsm/helpers/TextureHelper.js @@ -0,0 +1,122 @@ +import { + Mesh, + ShaderMaterial, + BoxGeometry +} from 'three'; + +class TextureHelper extends Mesh { + + constructor( texture, width = 1, height = 1, depth = 1 ) { + + const material = new ShaderMaterial( { + + type: 'TextureHelperMaterial', + + uniforms: { + + map: { value: texture }, + + }, + + vertexShader: [ + + 'varying vec3 vUvw;', + + 'void main() {', + + ' vUvw = vec3( uv, 0.0 );', // TODO: Populate 'w' for cube, 3D, and array textures. + + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + + '}', + + ].join( '\n' ), + + fragmentShader: [ + + 'precision highp float;', + + 'precision highp sampler2DArray;', + + 'precision highp sampler3D;', + + 'uniform {samplerType} map;', + + 'varying vec3 vUvw;', + + 'vec4 textureHelper( in sampler2D map ) { return texture( map, vUvw.xy ); }', + + 'vec4 textureHelper( in sampler2DArray map ) { return texture( map, vUvw ); }', + + 'vec4 textureHelper( in sampler3D map ) { return texture( map, vUvw ); }', + + 'vec4 textureHelper( in samplerCube map ) { return texture( map, vUvw ); }', + + 'void main() {', + + ' gl_FragColor = linearToOutputTexel( textureHelper( map ) );', + + '}' + + ].join( '\n' ).replace( '{samplerType}', getSamplerType( texture ) ) + + } ); + + // TODO: Display as stack of images. + const geometry = new BoxGeometry( width, height, depth ); + if ( texture.flipY === false ) flipY( geometry ); + + super( geometry, material ); + + this.texture = texture; + this.type = 'TextureHelper'; + + } + + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + +function getSamplerType( texture ) { + + if ( texture.isCubeTexture ) { + + return 'samplerCube'; + + } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + + return 'sampler2DArray'; + + } else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) { + + return 'sampler3D'; + + } else { + + return 'sampler2D'; + + } + +} + +/** Correct UVs to be compatible with `flipY=false` textures. */ +function flipY( geometry ) { + + const uv = geometry.attributes.uv; + + for ( let i = 0; i < uv.count; i ++ ) { + + uv.setY( i, 1 - uv.getY( i ) ); + + } + + return geometry; + +} + +export { TextureHelper }; diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index 6ac2ef96774ced..9d1eaf6f2f9bd5 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -867,7 +867,7 @@ async function createRawTexture( container ) { if ( container.faceCount === 6 ) { - texture = new DataCubeTexture( mipmaps[ 0 ].data ); + texture = new DataCubeTexture( mipmaps ); } else if ( container.layerCount > 1 ) { diff --git a/examples/textures/compressed/3d_etc1s.ktx2 b/examples/textures/compressed/3d_etc1s.ktx2 new file mode 100644 index 00000000000000..3de94fc45c6f24 Binary files /dev/null and b/examples/textures/compressed/3d_etc1s.ktx2 differ diff --git a/examples/textures/compressed/3d_rgba16_linear.ktx2 b/examples/textures/compressed/3d_rgba16_linear.ktx2 new file mode 100644 index 00000000000000..ea98cdd7817660 Binary files /dev/null and b/examples/textures/compressed/3d_rgba16_linear.ktx2 differ diff --git a/examples/textures/compressed/3d_rgba32_linear.ktx2 b/examples/textures/compressed/3d_rgba32_linear.ktx2 new file mode 100644 index 00000000000000..6b0cd6d63d3ce1 Binary files /dev/null and b/examples/textures/compressed/3d_rgba32_linear.ktx2 differ diff --git a/examples/textures/compressed/3d_rgba32_lut.ktx2 b/examples/textures/compressed/3d_rgba32_lut.ktx2 new file mode 100644 index 00000000000000..a9e82826e80af5 Binary files /dev/null and b/examples/textures/compressed/3d_rgba32_lut.ktx2 differ diff --git a/examples/textures/compressed/3d_rgba8.ktx2 b/examples/textures/compressed/3d_rgba8.ktx2 new file mode 100644 index 00000000000000..5ec1d3bed7b892 Binary files /dev/null and b/examples/textures/compressed/3d_rgba8.ktx2 differ diff --git a/examples/textures/compressed/3d_rgba8_linear.ktx2 b/examples/textures/compressed/3d_rgba8_linear.ktx2 new file mode 100644 index 00000000000000..e674eac8c1660a Binary files /dev/null and b/examples/textures/compressed/3d_rgba8_linear.ktx2 differ diff --git a/examples/textures/compressed/3d_uastc.ktx2 b/examples/textures/compressed/3d_uastc.ktx2 new file mode 100644 index 00000000000000..f05db3c2090628 Binary files /dev/null and b/examples/textures/compressed/3d_uastc.ktx2 differ diff --git a/examples/webgl_loader_texture_ktx2.html b/examples/webgl_loader_texture_ktx2.html index a8569b1c4574be..6531ccdaa21904 100644 --- a/examples/webgl_loader_texture_ktx2.html +++ b/examples/webgl_loader_texture_ktx2.html @@ -33,9 +33,10 @@ import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; + import { TextureHelper } from 'three/addons/helpers/TextureHelper.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; - let camera, scene, renderer, controls, loader, plane, cube, material; + let camera, scene, renderer, controls, loader, helper; const SAMPLES = { // 2D @@ -48,6 +49,15 @@ '2D / RGBA32 Linear': '2d_rgba32_linear.ktx2', '2D / ASTC 6x6 (mobile)': '2d_astc_6x6.ktx2', + // 3D + '3D / BasisU ETC1S': '3d_etc1s.ktx2', + '3D / BasisU UASTC': '3d_uastc.ktx2', + '3D / RGBA8 sRGB': '3d_rgba8.ktx2', + '3D / RGBA8 Linear': '3d_rgba8_linear.ktx2', + '3D / RGBA16 Linear': '3d_rgba16_linear.ktx2', + '3D / RGBA32 Linear': '3d_rgba32_linear.ktx2', + '3D / RGBA32 LUT': '3d_rgba32_lut.ktx2', + // Cube 'Cube / RGBA8 sRGB': 'cubemap_rgba8.ktx2', 'Cube / RGBA8 Linear': 'cubemap_rgba8_linear.ktx2', @@ -104,7 +114,6 @@ window.addEventListener( 'resize', onWindowResize ); scene = new THREE.Scene(); - scene.background = new THREE.Color( 0x202020 ); camera = new THREE.PerspectiveCamera( 60, width / height, 0.1, 100 ); camera.position.set( 0, 0, 2.5 ); @@ -113,20 +122,6 @@ controls = new OrbitControls( camera, renderer.domElement ); - // Default UVs assume flipY=true, which compressed textures don't support. - const planeGeometry = flipY( new THREE.PlaneGeometry() ); - const cubeGeometry = flipY( new THREE.BoxGeometry() ); - material = new THREE.MeshBasicMaterial( { - color: 0xFFFFFF, - side: THREE.DoubleSide, - transparent: true, - } ); - plane = new THREE.Mesh( planeGeometry, material ); - cube = new THREE.Mesh( cubeGeometry, material ); - cube.visible = false; - scene.add( plane ); - scene.add( cube ); - loader = new KTX2Loader() .setTranscoderPath( 'jsm/libs/basis/' ) .detectSupport( renderer ); @@ -169,19 +164,15 @@ texture.minFilter = THREE.NearestMipmapNearestFilter; texture.needsUpdate = true; - if ( path.startsWith( 'cube' ) ) { + if ( helper ) { - texture.mapping = THREE.CubeUVReflectionMapping; + scene.remove( helper ); + helper.dispose(); } - scene.background = null; - - material.map = texture; - material.needsUpdate = true; - - plane.visible = path.startsWith( '2d' ); - cube.visible = path.startsWith( 'cube' ) || path.startsWith( 'array' ); + helper = new TextureHelper( texture ); + scene.add( helper ); console.info( `class: ${ texture.constructor.name }` ); console.info( `format: ${ FORMAT_LABELS[ texture.format ] }` ); @@ -196,22 +187,6 @@ // NOTE: Call `loader.dispose()` when finished loading textures. - - } - - /** Correct UVs to be compatible with `flipY=false` textures. */ - function flipY( geometry ) { - - const uv = geometry.attributes.uv; - - for ( let i = 0; i < uv.count; i ++ ) { - - uv.setY( i, 1 - uv.getY( i ) ); - - } - - return geometry; - }