0
votes

I'm using InstanceGeometry to render thousands of base geometries (boxes) in a scene. It's efficient and uses only 1 material/texture, but repeats the image texture for every instance.

I'm trying to figure out how to have the texture spread out over x number of instances. Say for example there's 8 box instances, I'd like to 1/8 of the texture to appear on every box.

I think the transformUV function on THREE.Texture is what I'd want to use, but I'm not sure how to use it in this context. OR, would the texture mapping happen in the Shader itself?

UPDATE

My own code is pretty involved and uses the built-in three.js materials adapted for instances, so let's just use one of the three.js examples as a starting point: https://github.com/mrdoob/three.js/blob/master/examples/webgl_buffergeometry_instancing_dynamic.html

also pasted in brief below..

Vertex shader:

precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec3 offset;
attribute vec2 uv;
attribute vec4 orientation;
varying vec2 vUv;

// http://www.geeks3d.com/20141201/how-to-rotate-a-vertex-by-a-quaternion-in-glsl/
vec3 applyQuaternionToVector( vec4 q, vec3 v ){
    return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );
}

void main() {
    vec3 vPosition = applyQuaternionToVector( orientation, position );
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4( offset + vPosition, 1.0 );
}

Fragment shader

precision highp float;
uniform sampler2D map;
varying vec2 vUv;

void main() {
    gl_FragColor = texture2D( map, vUv );
}

JS:

var instances = 50;
var bufferGeometry = new THREE.BoxBufferGeometry( 2, 2, 2 );

var geometry = new THREE.InstancedBufferGeometry();
geometry.index = bufferGeometry.index;
geometry.attributes.position = bufferGeometry.attributes.position;
geometry.attributes.uv = bufferGeometry.attributes.uv;

// per instance data
var offsets = [];
var orientations = [];
var vector = new THREE.Vector4();
var x, y, z, w;

for ( var i = 0; i < instances; i ++ ) {

    // offsets
    x = Math.random() * 100 - 50;
    y = Math.random() * 100 - 50;
    z = Math.random() * 100 - 50;

    vector.set( x, y, z, 0 ).normalize();
    vector.multiplyScalar( 5 );

    offsets.push( x + vector.x, y + vector.y, z + vector.z );

    // orientations
    x = Math.random() * 2 - 1;
    y = Math.random() * 2 - 1;
    z = Math.random() * 2 - 1;
    w = Math.random() * 2 - 1;

    vector.set( x, y, z, w ).normalize();
    orientations.push( vector.x, vector.y, vector.z, vector.w );
}

offsetAttribute = new THREE.InstancedBufferAttribute( new Float32Array( offsets ), 3 );
orientationAttribute = new THREE.InstancedBufferAttribute( new Float32Array( orientations ), 4 ).setDynamic( true );

geometry.addAttribute( 'offset', offsetAttribute );
geometry.addAttribute( 'orientation', orientationAttribute );

// material
var material = new THREE.ShaderMaterial( {
    uniforms: {
        map: { value: new THREE.TextureLoader().load( 'textures/crate.gif' ) }          },
    vertexShader: document.getElementById( 'vertexShader' ).textContent,
    fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );

mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
1
Could you share the code?prisoner849
@prisoner849 added a starting point. .. starting point assumes mapping would take place in the shader code since the image texture is applied as a uniformRay Sweeten
I think you'll need a new attribute named something like instanceOffsetUV. You can use that in the vertex shader to shift the UVs.Don McCurdy

1 Answers

3
votes

You're going to have to create an additional custom attribute that holds the offset of UVs, just like you're creating an attribute that holds the x, y, z offsets, but with u, v.

First, you add it in JavaScript:

var uvOffsets = [];
var u, v;
for ( var i = 0; i < instances; i ++ ) {
    //... inside the loop
    u = Math.random(); // I'm assigning random, but you can do the math...
    v = Math.random(); // ... to make it discrete 1/8th amounts
    uvOffsets.push(u, v);
}

// Add new attribute to BufferGeometry
var uvOffsetAttribute = new THREE.InstancedBufferAttribute( new Float32Array( uvOffsets ), 2 );
geometry.addAttribute( 'uvOffset', uvOffsetAttribute );

Then, in your Vertex shader:

// [...]
attribute vec2 uv;
attribute vec2 uvOffset;
varying vec2 vUv;

void main() {
    vec3 vPosition = applyQuaternionToVector( orientation, position );

    // Divide uvs by 8, and add assigned offsets
    vUv = (uv / 8.0) + uvOffset;

    gl_Position = projectionMatrix * modelViewMatrix * vec4( offset + vPosition, 1.0 );
}

Finally, in your frag shader:

precision highp float;
uniform sampler2D map;
uniform vec2 uvOffset;
varying vec2 vUv; // <- these UVs have been transformed by vertex shader.

void main() {
    gl_FragColor = texture2D( map, vUv ); // <- Transformation is applied to texture
}