THREE.js genera coordinate UV

Sto lavorando all’importazione di un modello in una scena utilizzando il loader OBJ THREE.js.

So che sono in grado di importare la geometria bene, perché quando assegno un MeshNormalMaterial ad esso, si mostra alla grande. Tuttavia, se uso qualcosa che richiede le coordinate UV, mi dà l’errore:

[.WebGLRenderingContext]GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 1 

So che questo è dovuto al fatto che l’OBJ caricato non ha coordinate UV, ma mi chiedevo se esistesse un modo per generare le coordinate della trama necessarie. Ho provato

 material.needsUpdate = true; geometry.uvsNeedUpdate = true; geometry.buffersNeedUpdate = true; 

… ma senza risultati.

C’è un modo per generare automagicamente trame UV usando three.js, o devo assegnare le coordinate da solo?

A mia conoscenza non esiste un modo automatico per calcolare l’UV.

Devi calcolare te stesso. Calcolare un UV per un piano è abbastanza semplice, questo sito spiega come: calcolare le coordinate della trama

Per una forma complessa, non so come. Forse potresti rilevare la superficie planare.

MODIFICARE

Ecco un codice di esempio per una superficie planare (x, y, z) dove z = 0 :

 geometry.computeBoundingBox(); var max = geometry.boundingBox.max, min = geometry.boundingBox.min; var offset = new THREE.Vector2(0 - min.x, 0 - min.y); var range = new THREE.Vector2(max.x - min.x, max.y - min.y); var faces = geometry.faces; geometry.faceVertexUvs[0] = []; for (var i = 0; i < faces.length ; i++) { var v1 = geometry.vertices[faces[i].a], v2 = geometry.vertices[faces[i].b], v3 = geometry.vertices[faces[i].c]; geometry.faceVertexUvs[0].push([ new THREE.Vector2((v1.x + offset.x)/range.x ,(v1.y + offset.y)/range.y), new THREE.Vector2((v2.x + offset.x)/range.x ,(v2.y + offset.y)/range.y), new THREE.Vector2((v3.x + offset.x)/range.x ,(v3.y + offset.y)/range.y) ]); } geometry.uvsNeedUpdate = true; 

Le altre risposte qui sono state di grande aiuto, ma non si adattavano perfettamente alle mie esigenze per applicare una texture ripetitiva a tutti i lati di una forma con superfici prevalentemente piatte. Il problema è che usando solo i componenti xey come u e v si ottengono trame strane su superfici verticali.

La mia soluzione qui sotto utilizza le normali di superficie per scegliere quali due componenti (x, y e z) devono essere mappati su u e v. È ancora piuttosto grezzo ma funziona piuttosto bene.

 function assignUVs(geometry) { geometry.faceVertexUvs[0] = []; geometry.faces.forEach(function(face) { var components = ['x', 'y', 'z'].sort(function(a, b) { return Math.abs(face.normal[a]) > Math.abs(face.normal[b]); }); var v1 = geometry.vertices[face.a]; var v2 = geometry.vertices[face.b]; var v3 = geometry.vertices[face.c]; geometry.faceVertexUvs[0].push([ new THREE.Vector2(v1[components[0]], v1[components[1]]), new THREE.Vector2(v2[components[0]], v2[components[1]]), new THREE.Vector2(v3[components[0]], v3[components[1]]) ]); }); geometry.uvsNeedUpdate = true; } 

Questa funzione non normalizza gli UV alla dimensione dell’object. Funziona meglio quando si applica la stessa texture a oggetti di dimensioni diverse nella stessa scena. Tuttavia, a seconda delle dimensioni del tuo sistema di coordinate del mondo, probabilmente dovrai ridimensionare e ripetere la trama:

 texture.repeat.set(0.1, 0.1); texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping; 

Le risposte qui sono brillanti e mi hanno aiutato molto. Solo una cosa: se stai aggiornando i vertici, non riassegnare gli uv, ma impostarli, come in (scope è la mia geometria):

 scope.updateUVs = (copy=true) => { scope.computeBoundingBox(); var max = scope.boundingBox.max; var min = scope.boundingBox.min; var offset = new THREE.Vector2(0 - min.x, 0 - min.y); var range = new THREE.Vector2(max.x - min.x, max.y - min.y); if (!copy) { scope.faceVertexUvs[0] = []; } var faces = scope.faces; for (i = 0; i < scope.faces.length ; i++) { var v1 = scope.vertices[faces[i].a]; var v2 = scope.vertices[faces[i].b]; var v3 = scope.vertices[faces[i].c]; var uv0 = new THREE.Vector2( ( v1.x + offset.x ) / range.x , ( v1.y + offset.y ) / range.y ); var uv1 = new THREE.Vector2( ( v2.x + offset.x ) / range.x , ( v2.y + offset.y ) / range.y ); var uv2 = new THREE.Vector2( ( v3.x + offset.x ) / range.x , ( v3.y + offset.y ) / range.y ); if (copy) { var uvs =scope.faceVertexUvs[0][i]; uvs[0].copy(uv0); uvs[1].copy(uv1); uvs[2].copy(uv2); } else { scope.faceVertexUvs[0].push([uv0, uv1, uv2]); } } scope.uvsNeedUpdate = true; } 

Questa è una versione generale che funziona per la mapping sferica (imbardata, coordinate del passo), vedi l’esempio qui , (guarda la funzione loadSuzanne ):

 function assignUVs(geometry) { geometry.faceVertexUvs[0] = []; geometry.faces.forEach(function(face) { var uvs = []; var ids = [ 'a', 'b', 'c']; for( var i = 0; i < ids.length; i++ ) { var vertex = geometry.vertices[ face[ ids[ i ] ] ].clone(); var n = vertex.normalize(); var yaw = .5 - Math.atan( nz, - nx ) / ( 2.0 * Math.PI ); var pitch = .5 - Math.asin( ny ) / Math.PI; var u = yaw, v = pitch; uvs.push( new THREE.Vector2( u, v ) ); } geometry.faceVertexUvs[ 0 ].push( uvs ); }); geometry.uvsNeedUpdate = true; }