THREE.CubeTexture using THREE.CompressedTexture

I was trying to create THREE.CubeTexture using 6 different THREE.CompressedTextures and realized that the CubeTexture does not support CompressedTextures. I opened this issue but have not received any feedback: https://github.com/mrdoob/three.js/issues/16584

Does anyone have any opinion on this? I’ll try to do a PR about this issue but want to make sure if this is an actual bug, so was hoping for some feedback.

Before doing a PR, can you please demonstrate the issue with simple live demo? For example you could refactor the following demo so it uses compressed textures:

https://threejs.org/examples/webgl_materials_cubemap.html

When looking at WebGLTextures, setTextureCube() does handle compressed textures.

@Mugen87

Thanks for the suggestion. I compressed the 6 textures used in the example to KTX format using S3TC compression, tested them with a KTXLoader and mapped them to a cube to make sure they are valid textures. Then I changed only this line:

.jpg to .ktx and this is the result:

This makes perfect sense to me as the CubeTextureLoader does not perform a file format check and internally uses an ImageLoader which seem to work well on Web friendly formats such as PNG and JPEG but not with KTX. IMO the CubeTextureLoader should accept an optional loader variable to be able to load compressed textures.

@Mugen87

And this is what happens when I load these 6 KTX textures using a KTXLoader and create the CubeTexture like: new THREE.CubeTexture(textureImagesAry):

As specified in the error mesage, texImage2D is called instead of compressedTexImage2D.

The problem might be here: https://github.com/mrdoob/three.js/blob/b50a9d6c88494088911ab600f77109b8161e3dd2/src/renderers/webgl/WebGLTextures.js#L373

As the texture variable here is a THREE.CubeTexture, therefore .isCompressedTexture property is undefined / false.

Is it possible for you to share your code as a github repo? I want to have a closer look at this issue by myself :innocent:

In any event, your findings are already very interesting!

@Mugen87

Here’s the repo: https://github.com/oguzeroglu/threejs_compressed_cubemap_bug

Clone the repo and start a webserver (I use “python -m SimpleHTTPServer”)

There are 3 HTML files:
withJPG.html -> This is the normal, working version with JPG textures
withKTX.html -> This is almost the same as withJPG.html, instead KTX textures are used to prove that THREE.CubeTextureLoader does not go well with compressed textures
withCompressedTextures.html -> This uses a KTXLoader to load 6 different KTX textures, constructs directly an instance of THREE.CubeTexture without using a CubeTextureLoader. I did this to demonstrate some possible logical problems inside WebglTextures class.

1 Like

CubeTextureLoader is definitely not designed for compressed textures and I think it’s no good to add support for it. Otherwise it would rely on too many loaders from the examples.

So let’s assume you create a CubeTexture with compressed textures by yourself (like in withCompressedTextures.html). I can confirm that in this case there are several issues in WebGLTextures. For example:

var isCompressed = ( texture && texture.isCompressedTexture );

should be (similar to isDataTexture):

var isCompressed = ( texture.image[ 0 ] && texture.image[ 0 ].isCompressedTexture );

However, even if I add this code, I get the following WebGL error in Firefox:

Error: WebGL warning: texImage2D: Desired upload requires more data than is available: (511 rows plus 512 pixels needed, 85 rows plus 170 pixels available)
Error: WebGL warning: generateMipmap: The texture’s base level must be complete.

Um, the KTX textures do actually work, right?

Yes I had the same problem, it was complaining about the buffer size and yes I can confirm that they are actually valid textures. It is not supposed to call textImage2D anyway. That’s the problem. It’s supposed to call compressedTexImage2D. So the error message you posted is probably caused by Compressed Texture + textImage2D

But the compressed textures have RGBFormat format. compressedTexImage2D() is only called for certain texture compression formats (like e.g. RGB_S3TC_DXT1_Format or RGB_ETC1_Format).

Ah, this might be the case as the KTX files are compressed with S3TC, I think they should be used with compressedTextImage2D. I’ll investigate if this line causes the problem:

After applying the mentioned fix to WebGLTextures, I can load your textures with this app-level code:

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - materials - cube reflection / refraction [Walt]</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>
	<body>

		<div id="container"></div>
		<div id="info">
			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - cube mapping demo.<br />
			Texture by <a href="http://www.humus.name/index.php?page=Textures" target="_blank" rel="noopener">Humus</a>, Walt Disney head by <a href="http://davidoreilly.com/post/18087489343/disneyhead" target="_blank" rel="noopener">David OReilly</a>
		</div>

		<script src="./three.js"></script>

		<script src="./OrbitControls.js"></script>

		<script src="./OBJLoader.js"></script>
		<script src="./KTXLoader.js"></script>

		<script src="./WebGL.js"></script>
		<script src="./stats.min.js"></script>

		<script>

			if ( WEBGL.isWebGLAvailable() === false ) {

				document.body.appendChild( WEBGL.getWebGLErrorMessage() );

			}

			var container, stats;

			var camera, scene, renderer;

			var pointLight;

			function onTextureLoaded(textureData){
				console.log(textureData);
				images.push(textureData);
				ctr ++;
				if (ctr == 6){
					reflectionCube = new THREE.CubeTexture(images);
					refractionCube = new THREE.CubeTexture(images);

					reflectionCube.format = reflectionCube.images[ 0 ].format;
					refractionCube.format = refractionCube.images[ 0 ].format;

					reflectionCube.generateMipmaps = false;
					refractionCube.generateMipmaps = false;

					reflectionCube.minFilter = THREE.LinearFilter;
					refractionCube.minFilter = THREE.LinearFilter;

					reflectionCube.needsUpdate = true;
					refractionCube.needsUpdate = true;

					init();
					animate();
				}
			}

			var path = 'SwedishRoyalCastle/';
			var format = '.ktx';
			var urls = [
				path + 'px' + format, path + 'nx' + format,
				path + 'py' + format, path + 'ny' + format,
				path + 'pz' + format, path + 'nz' + format
			];
			var ctr = 0;
			var images = [];
			var loader = new THREE.KTXLoader();
			var reflectionCube, refractionCube;
			for (var i = 0; i<urls.length; i++){
				loader.load(urls[i], onTextureLoaded);
			}


			function init() {

				container = document.createElement( 'div' );
				document.body.appendChild( container );

				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 5000 );
				camera.position.z = 2000;

				refractionCube.mapping = THREE.CubeRefractionMapping;

				scene = new THREE.Scene();
				scene.background = reflectionCube;

				//lights
				var ambient = new THREE.AmbientLight( 0xffffff );
				scene.add( ambient );

				pointLight = new THREE.PointLight( 0xffffff, 2 );
				scene.add( pointLight );

				//materials
				var cubeMaterial3 = new THREE.MeshLambertMaterial( { color: 0xff6600, envMap: reflectionCube, combine: THREE.MixOperation, reflectivity: 0.3 } );
				var cubeMaterial2 = new THREE.MeshLambertMaterial( { color: 0xffee00, envMap: refractionCube, refractionRatio: 0.95 } );
				var cubeMaterial1 = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: reflectionCube } );

				//models
				var objLoader = new THREE.OBJLoader();

				objLoader.setPath( 'walt/' );
				objLoader.load( 'WaltHead.obj', function ( object ) {

					var head = object.children[ 0 ];

					head.scale.multiplyScalar( 15 );
					head.position.y = - 500;
					head.material = cubeMaterial1;

					var head2 = head.clone();
					head2.position.x = - 900;
					head2.material = cubeMaterial2;

					var head3 = head.clone();
					head3.position.x = 900;
					head3.material = cubeMaterial3;

					scene.add( head, head2, head3 );

				} );

				//renderer
				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				container.appendChild( renderer.domElement );

				//controls
				var controls = new THREE.OrbitControls( camera, renderer.domElement );
				controls.enableZoom = false;
				controls.enablePan = false;
				controls.minPolarAngle = Math.PI / 4;
				controls.maxPolarAngle = Math.PI / 1.5;

				//stats
				stats = new Stats();
				container.appendChild( stats.dom );

				window.addEventListener( 'resize', onWindowResize, false );

			}

			function onWindowResize() {

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}

			function animate() {

				requestAnimationFrame( animate );
				render();

			}

			function render() {

				renderer.render( scene, camera );
				stats.update();

			}

		</script>

	</body>
</html>

As you can see, it’s necessary to apply some configurations to CubeTexture in onTextureLoaded(). The code looks similar to what happens in HDRCubeTextureLoader.

1 Like

Do you want to make a PR with the fix for WebGLTextures? I think this is something that needs to be corrected in the library.

1 Like

Yep, I’m on it :slight_smile:

1 Like