I am looking for a way to draw several objects with unique textures. I came across this old question about instancedMesh where someone got the multiple instances with different textures but on desktop, textures have weird artifacts. Initially I thought something must be wrong with that demo but everything seems fine to me, I also tried to use mix functions in place of conditionals but textures still have artifacts.
I have been looking for different ways to draw multiple unique geometries so merging geometries isn't an option, but most results I get are for multiple objects with merged geometry. Would be great if someone can offer some guidance.
var camera, scene, renderer, stats;
var mesh;
var amount = parseInt(window.location.search.substr(1)) || 10;
var count = Math.pow(amount, 3);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2(1, 1);
var rotationTheta = 0.1;
var rotationMatrix = new THREE.Matrix4().makeRotationY(rotationTheta);
var instanceMatrix = new THREE.Matrix4();
var matrix = new THREE.Matrix4();
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(amount, amount, amount);
camera.lookAt(0, 0, 0);
scene = new THREE.Scene();
var light = new THREE.HemisphereLight(0xffffff, 0x000088);
light.position.set(-1, 1.5, 1);
scene.add(light);
var light = new THREE.HemisphereLight(0xffffff, 0x880000, 0.5);
light.position.set(-1, -1.5, -1);
scene.add(light);
var geometry = new THREE.BoxBufferGeometry(.5, .5, .5, 1, 1, 1);
var material = [
new THREE.MeshStandardMaterial({
map: new THREE.TextureLoader().load('https://threejs.org/examples/textures/square-outline-textured.png')
}),
new THREE.MeshStandardMaterial({
map: new THREE.TextureLoader().load('https://threejs.org/examples/textures/golfball.jpg')
}),
new THREE.MeshStandardMaterial({
map: new THREE.TextureLoader().load('https://threejs.org/examples/textures/metal.jpg')
}),
new THREE.MeshStandardMaterial({
map: new THREE.TextureLoader().load('https://threejs.org/examples/textures/roughness_map.jpg')
}),
new THREE.MeshStandardMaterial({
map: new THREE.TextureLoader().load('https://threejs.org/examples/textures/tri_pattern.jpg')
}),
new THREE.MeshStandardMaterial({
map: new THREE.TextureLoader().load('https://threejs.org/examples/textures/water.jpg')
}),
];
material.forEach((m, side) => {
if (side != 2) return;
m.onBeforeCompile = (shader) => {
shader.uniforms.textures = {
'type': 'tv',
value: [
new THREE.TextureLoader().load('https://threejs.org/examples/textures/crate.gif'),
new THREE.TextureLoader().load('https://threejs.org/examples/textures/equirectangular.png'),
new THREE.TextureLoader().load('https://threejs.org/examples/textures/colors.png')
]
};
shader.vertexShader = shader.vertexShader.replace(
'#define STANDARD',
`#define STANDARD
varying vec3 vTint;
varying float vTextureIndex;`
).replace(
'#include <common>',
`#include <common>
attribute vec3 tint;
attribute float textureIndex;`
).replace(
'#include <project_vertex>',
`#include <project_vertex>
vTint = tint;
vTextureIndex=textureIndex;`
);
shader.fragmentShader = shader.fragmentShader.replace(
'#define STANDARD',
`#define STANDARD
uniform sampler2D textures[3];
varying vec3 vTint;
varying float vTextureIndex;`
)
.replace(
'#include <fog_fragment>',
`#include <fog_fragment>
int texIdx = int(vTextureIndex);
vec4 col;
if (texIdx == 0) {
col = texture2D(textures[0], vUv );
} else if ( texIdx==1) {
col = texture2D(textures[1], vUv );
} else if ( texIdx==2) {
col = texture2D(textures[2], vUv );
}
gl_FragColor = col;
// gl_FragColor.rgb *= vTint;`
);
}
});
mesh = new THREE.InstancedMesh(geometry, material, count);
var i = 0;
var offset = (amount - 1) / 2;
var transform = new THREE.Object3D();
var textures = [];
for (var x = 0; x < amount; x++) {
for (var y = 0; y < amount; y++) {
for (var z = 0; z < amount; z++) {
transform.position.set(offset - x, offset - y, offset - z);
transform.updateMatrix();
mesh.setMatrixAt(i++, transform.matrix);
textures.push(Math.random() < 0.3 ? 0 : (Math.random() < 0.5 ? 1 : 2));
}
}
}
geometry.setAttribute('textureIndex',
new THREE.InstancedBufferAttribute(new Float32Array(textures), 1));
scene.add(mesh);
renderer = new THREE.WebGLRenderer({
antialias: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
new THREE.OrbitControls(camera, renderer.domElement);
stats = new Stats();
stats.showPanel(1); // 0: fps, 1: ms, 2: mb, 3+: custom
stats.domElement.classList.add("statsDom");
document.body.appendChild(stats.domElement);
window.addEventListener('resize', onWindowResize, false);
document.addEventListener('mousemove', onMouseMove, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
raycaster.setFromCamera(mouse, camera);
var intersection = raycaster.intersectObject(mesh);
// console.log('intersection', intersection.length);
if (intersection.length > 0) {
mesh.getMatrixAt(intersection[0].instanceId, instanceMatrix);
matrix.multiplyMatrices(instanceMatrix, rotationMatrix);
mesh.setMatrixAt(intersection[0].instanceId, matrix);
mesh.instanceMatrix.needsUpdate = true;
}
renderer.render(scene, camera);
stats.update();
}
.statsDom {
position: fixed;
top: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three@0.122.0/build/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.9/dat.gui.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.122.0/examples/js/controls/OrbitControls.min.js"></script>
<div></div>
I believe your issue comes from converting a float
to an int
, and then using that to create branches. This bug shows up only in a few GPUs, not all of them. I got it to work by keeping vTextureIndex
as float
, sampling all 3 textures and multiplying each by 1 if the textureIndex
matches, or multiplying by 0 if the textureIndex
does not match.
I basically replaced these lines:
int texIdx = int(vTextureIndex);
vec4 col;
if (texIdx == 0) {
col = texture2D(textures[0], vUv );
} else if ( texIdx==1) {
col = texture2D(textures[1], vUv );
} else if ( texIdx==2) {
col = texture2D(textures[2], vUv );
}
with this approach:
float x = vTextureIndex;
vec4 col;
col = texture2D(textures[0], vUv ) * step(-0.1, x) * step(x, 0.1);
col += texture2D(textures[1], vUv ) * step(0.9, x) * step(x, 1.1);
col += texture2D(textures[2], vUv ) * step(1.9, x) * step(x, 2.1);
textureIndex
is 0, the first texture is multiplied by 1, the others by 0textureIndex
is 1, the second texture is multiplied by 1, the others by 0textureIndex
is 2, the third texture is multiplied by 1, the others by 0var camera, scene, renderer, stats;
var mesh;
var amount = parseInt( window.location.search.substr( 1 ) ) || 10;
var count = Math.pow( amount, 3 );
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2( 1, 1 );
var rotationTheta = 0.1;
var rotationMatrix = new THREE.Matrix4().makeRotationY( rotationTheta );
var instanceMatrix = new THREE.Matrix4();
var matrix = new THREE.Matrix4();
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( amount, amount, amount );
camera.lookAt( 0, 0, 0 );
scene = new THREE.Scene();
var light = new THREE.HemisphereLight( 0xffffff, 0x666666 );
light.position.set( - 1, 1.5, 1 );
scene.add( light );
var light = new THREE.HemisphereLight( 0xffffff, 0x666666, 0.5 );
light.position.set( - 1, - 1.5, - 1 );
scene.add( light );
var geometry = new THREE.BoxBufferGeometry( .5, .5, .5, 1, 1, 1 );
var material = [
new THREE.MeshStandardMaterial({color: 0xff9900}),
new THREE.MeshStandardMaterial({color: 0xff0099}),
new THREE.MeshStandardMaterial( { map: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/metal.jpg' ) } ),
new THREE.MeshStandardMaterial({color: 0x9900ff}),
new THREE.MeshStandardMaterial({color: 0x0099ff}),
new THREE.MeshStandardMaterial({color: 0x99ff00}),
];
material.forEach((m,side)=>{
if ( side!=2 ) return;
m.onBeforeCompile = ( shader ) => {
shader.uniforms.textures = { 'type': 'tv', value: [
new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/crate.gif' ),
new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/sprite0.png' ),
new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/sprite.png' )
] };
shader.vertexShader = shader.vertexShader.replace(
'#define STANDARD',
`#define STANDARD
varying vec3 vTint;
varying float vTextureIndex;`
).replace(
'#include <common>',
`#include <common>
attribute vec3 tint;
attribute float textureIndex;`
).replace(
'#include <project_vertex>',
`#include <project_vertex>
vTint = tint;
vTextureIndex=textureIndex;`
);
shader.fragmentShader = shader.fragmentShader.replace(
'#define STANDARD',
`#define STANDARD
uniform sampler2D textures[3];
varying vec3 vTint;
varying float vTextureIndex;`
)
.replace(
'#include <fog_fragment>',
`#include <fog_fragment>
float x = vTextureIndex;
vec4 col;
col = texture2D(textures[0], vUv ) * step(-0.1, x) * step(x, 0.1);
col += texture2D(textures[1], vUv ) * step(0.9, x) * step(x, 1.1);
col += texture2D(textures[2], vUv ) * step(1.9, x) * step(x, 2.1);
gl_FragColor = col;
`
)
;
}
});
mesh = new THREE.InstancedMesh( geometry, material, count );
var i = 0;
var offset = ( amount - 1 ) / 2;
var transform = new THREE.Object3D();
var textures = [];
for ( var x = 0; x < amount; x ++ ) {
for ( var y = 0; y < amount; y ++ ) {
for ( var z = 0; z < amount; z ++ ) {
transform.position.set( offset - x, offset - y, offset - z );
transform.updateMatrix();
mesh.setMatrixAt( i ++, transform.matrix );
textures.push(Math.random()<0.3 ? 0 : (Math.random()<0.5 ? 1 : 2));
}
}
}
geometry.setAttribute( 'textureIndex',
new THREE.InstancedBufferAttribute( new Float32Array(textures), 1 ) );
scene.add( mesh );
renderer = new THREE.WebGLRenderer( { antialias: false } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
new THREE.OrbitControls( camera, renderer.domElement );
stats = new Stats();
document.body.appendChild( stats.dom );
window.addEventListener( 'resize', onWindowResize, false );
document.addEventListener( 'mousemove', onMouseMove, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onMouseMove( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
raycaster.setFromCamera( mouse, camera );
var intersection = raycaster.intersectObject( mesh );
// console.log('intersection', intersection.length);
if ( intersection.length > 0 ) {
mesh.getMatrixAt( intersection[ 0 ].instanceId, instanceMatrix );
matrix.multiplyMatrices( instanceMatrix, rotationMatrix );
mesh.setMatrixAt( intersection[ 0 ].instanceId, matrix );
mesh.instanceMatrix.needsUpdate = true;
}
renderer.render( scene, camera );
stats.update();
}
<script src="https://cdn.jsdelivr.net/npm/three@0.140.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.140.0/examples/js/libs/stats.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.140.0/examples/js/controls/OrbitControls.js"></script>