Three.js: UVNode to handle more than 2 uv layers

Created on 2 Aug 2019  路  6Comments  路  Source: mrdoob/three.js

Description of the problem

Currently only two uv layers are supported by UVNode(). Using an index other than 0 (uv) or 1 (uv2) will prevent the fragment shader from compiling and will throw an error in the console (undefined value returned by NodeBuilder).

This limitation is acceptable for the standard material system but it would really make sense if the node system could handle any number of layers.

/ping @sunag

Three.js version
  • [x] Dev
  • [x] r107
Browser
  • [x] All of them
OS
  • [x] All of them
Enhancement

Most helpful comment

Yeah, I will include this in this weekend 馃憤

All 6 comments

Yeah, I will include this in this weekend 馃憤

@sunag I was wondering if you could give me a few tips to make it work as I am at the point in my project where I need the feature.

I started by modifying the generate function from UVNode to get ride of vertexDict and fragmentDict:

UVNode.prototype.generate = function ( builder, output ) {

    builder.requires.uv[ this.index ] = true;

    var uvIndex = this.index > 0 ? this.index + 1 : '';
    var result = builder.isShader( 'vertex' ) ? 'uv' + uvIndex : 'vUv' + uvIndex;

    return builder.format( result, this.getType( builder ), output );

};

and then in build function of NodeBuilder I replaced the two if this.requires.uv statements by:

for ( var i = 0; i < this.requires.uv.length; i++ ) {

    if ( this.requires.uv[ i ] ) {

        var uvIndex = i > 0 ? i + 1 : '';

        this.addVaryCode( 'varying vec2 vUv' + uvIndex + ';' );

        if ( i > 0 ) {

            this.addVertexParsCode( 'attribute vec2 uv' + uvIndex + ';' );

        }

        this.addVertexFinalCode( 'vUv' + uvIndex + ' = uv' + uvIndex +';' );

    }

}

Now it doesn't return any errors but it doesn't work either. Do you know what other part of the code I shall update. If I am successful, I will create a PR for it.

Thanks.

I printed this.vertexShader and this.fragmentShader from NodeMaterial build function to check the output. For example if I use UVNode(2) instead of UVNode(1) with my code above, uv2 and vUv2 are accurately replaced by uv3 and vUv3 in both vertex and fragment shaders but for index 2, the texture is not applied (all uv coordinates defaulted to 0,0)

Ok, I have it figured out. The code I wrote above works fine. The issue is with BufferGeometry.fromGeometry() which only converts a maximum of 2 uv layers.

For multiple reasons, I still use Geometry and the automatic conversion process loses all layers other than the first two. To avoid modifying the core version of three.js I manually create geometry._bufferGeometry at load time using a custom function:

function geometryToBufferGeometry(geometry) {
    let bufferGeometry = new THREE.BufferGeometry();
    bufferGeometry.fromGeometry(geometry);

    // Look for extra uv layers
    let uvs = {};
    let faceVertexUvs = geometry.faceVertexUvs;
    for (let i  = 2; i < faceVertexUvs.length; i++) {
        if (faceVertexUvs[i] && faceVertexUvs[i].length > 0) uvs[i] = [];
    }

    // Build uv arrays
    let faces = geometry.faces;
    for (let i = 0; i < faces.length; i++) {
        for (let uvLayer in uvs) {
            let vertexUvs = faceVertexUvs[uvLayer][i];

            if (vertexUvs !== undefined) {
                uvs[uvLayer].push(vertexUvs[0], vertexUvs[1], vertexUvs[2]);
            } else {
                console.warn('Undefined vertexUv' + uvLayer + ' ', i);
                uvs[uvLayer].push(new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2());
            }
        }
    }

    // Convert to BufferAttribute
    for (let uvLayer in uvs) {
        let attribute = new Float32Array(uvs[uvLayer].length * 2);
        bufferGeometry.addAttribute('uv' + (parseInt(uvLayer) + 1) , new THREE.BufferAttribute(attribute, 2).copyVector2sArray(uvs[uvLayer]));
    }

    return bufferGeometry;
}

It would make much more sense to update BufferGeometry.fromDirectGeometry() and DirectGeometry.fromGeometry() directly in the core. I cannot see a reason why only two layers and not all shall be converted.

If you think that this is a good idea, I can create a PR to modify the following files in order to support any number of uv layers:

  • DirectGeometry.js
  • BufferGeometry.js
  • UVNode
  • NodeBuilder
Was this page helpful?
0 / 5 - 0 ratings

Related issues

clawconduce picture clawconduce  路  3Comments

danieljack picture danieljack  路  3Comments

akshaysrin picture akshaysrin  路  3Comments

boyravikumar picture boyravikumar  路  3Comments

filharvey picture filharvey  路  3Comments