Instanced textures with DataArrayTexture error

Hello everyone,

I have a project where I need to generate textures on the fly and pass them to instances in a custom material. I would typically do this (not on the web) either by passing a sampler2D array as uniform, or by merging the textures into a master texture, and then use a variable index number to access the desired places. However, I cannot seem to be able to do any of that since to read them in a shader I need to pass a variable sampler index, which as far as I understand is not allowed in GLSL 300es.

I read around and it seems I could use a DataArrayTexture for this purpose instead, using its depth index. However, I’m stuck with an error already trying to run the example provided by three.js.

Capture

Here the code I’m trying. slightly adapted from the original here: three.js examples

const vertex = `
	uniform vec2 size;
	out vec2 vUv;

	void main() {

		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

		// 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
	}
`;

const fragment = `
	precision highp float;
	precision highp int;
	precision highp sampler2DArray;

	uniform sampler2DArray diffuse;
	in vec2 vUv;
	uniform int depth;

	out vec4 outColor;

	void main() {

		vec4 color = texture( diffuse, vec3( vUv, depth ) );

		// lighten a bit
		outColor = vec4( color.rrr * 1.5, 1.0 );

	}
`

import * as THREE from '../../js/three.js-r124/build/three.module.js';

class DataArrayTexture extends THREE.Texture {

  constructor( data = null, width = 1, height = 1, depth = 1 ) {
    super( null );

    this.isDataArrayTexture = true;

    this.image = { data, width, height, depth };

    this.magFilter = THREE.NearestFilter;
    this.minFilter = THREE.NearestFilter;

    this.wrapR = THREE.ClampToEdgeWrapping;

    this.generateMipmaps = false;
    this.flipY = false;
    this.unpackAlignment = 1;
  }

}

let camera, scene, mesh, renderer, stats;

const planeWidth = 50;
const planeHeight = 50;

let depthStep = 0.4;

init();
animate();

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();

	// width 256, height 256, depth 109, 8-bit, zip archived raw data

	new THREE.FileLoader()
		.setResponseType( 'arraybuffer' )
		.load( 'textures/3d/head256x256x109.zip', function ( data ) {

			const zip = fflate.unzipSync( new Uint8Array( data ) );
			const array = new Uint8Array( zip[ 'head256x256x109' ].buffer );

			const texture = new DataArrayTexture( array, 256, 256, 109 );
			texture.format = THREE.RedFormat;
			texture.needsUpdate = true;

			const material = new THREE.ShaderMaterial( {
				uniforms: {
					diffuse: { value: texture },
					depth: { value: 55 },
					size: { value: new THREE.Vector2( planeWidth, planeHeight ) }
				},
				vertexShader: vertex,
				fragmentShader: fragment,
				glslVersion: THREE.GLSL3
			} );

			const geometry = new THREE.PlaneGeometry( planeWidth, planeHeight );

			mesh = new THREE.Mesh( geometry, material );

			scene.add( mesh );

		} );

	// 2D Texture array is available on WebGL 2.0

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

	console.log(renderer.capabilities.isWebGL2);

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

	window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

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

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

}

function animate() {

	requestAnimationFrame( animate );

	if ( mesh ) {

		let value = mesh.material.uniforms[ 'depth' ].value;

		value += depthStep;

		if ( value > 109.0 || value < 0.0 ) {

			if ( value > 1.0 ) value = 109.0 * 2.0 - value;
			if ( value < 0.0 ) value = - value;

			depthStep = - depthStep;

		}

		mesh.material.uniforms[ 'depth' ].value = value;

	}

	render();
	stats.update();
}

function render() {
	renderer.render( scene, camera );
}

I have to use r124 for this project, but I’ve tried up to r135 and I have the same issue.

What am I doing wrong? Many thanks!

1 Like