methods for generating UVs are completely open ended.
It’s a stylistic/function choice based on the needs of the model.
Sometimes you want a texture mapped completely to each face. Sometimes you want them mapped to the whole model like a box unwrap..
Sometimes for more organic forms you need something far more complex, like a true topologically aware unwrap, and there are entire phd’s based on different techniques for these kinds of advanced unwraps.
If you’re only talking about the actual mechanism for adding UVs to a model without them…
You can do something like (pseudocode):
let uvArray = new Float32Array( geometry.attributes.position.count * 2 )
//do your unwrapping algorithm here on uvArray based on the vertices in the position attribute
geometry.setAttribute("uv",new THREE.BufferAttribute( uvArray, 2 ));
Here is a “box uv unwrap” I wrote a while ago that does a simple box unwrap, which is usable for a lot of cases where you just need Something.
import *as THREE from "three"
export function boxUnwrap(geometry) {
const position = geometry.attributes.position;
const normalAttr = geometry.attributes.normal;
if (!position || !normalAttr) return;
const uvArray = new Float32Array(position.count * 2);
const normal = new THREE.Vector3();
const pos = new THREE.Vector3();
for (let i = 0; i < position.count; i++) {
pos.fromBufferAttribute(position, i);
normal.fromBufferAttribute(normalAttr, i).set(Math.abs(normal.x), Math.abs(normal.y), Math.abs(normal.z));
let u = 0, v = 0;
if (normal.x >= normal.y && normal.x >= normal.z) {
u = pos.y; v = pos.z; // X projection (YZ plane)
} else if (normal.y >= normal.x && normal.y >= normal.z) {
u = pos.x; v = pos.z; // Y projection (XZ plane)
} else {
u = pos.x; v = pos.y; // Z projection (XY plane)
}
uvArray[i * 2] = (u - geometry.boundingBox.min.x) / geometry.boundingBox.getSize(new THREE.Vector3()).x;
uvArray[i * 2 + 1] = (v - geometry.boundingBox.min.y) / geometry.boundingBox.getSize(new THREE.Vector3()).y;
}
geometry.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2));
}