three.js texture across InstanceGeometry

让人想犯罪 __ 提交于 2021-02-08 10:28:58

问题


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:


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
}


来源:https://stackoverflow.com/questions/52709226/three-js-texture-across-instancegeometry

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!