三.js 生成UV坐标
Posted
技术标签:
【中文标题】三.js 生成UV坐标【英文标题】:THREE.js generate UV coordinate 【发布时间】:2014-01-13 12:11:53 【问题描述】:我正在使用 THREE.js OBJ 加载器将模型导入场景。
我知道我能够很好地导入几何图形,因为当我为它分配一个 MeshNormalMaterial 时,它显示得很好。但是,如果我使用任何需要 UV 坐标的东西,它会给我错误:
[.WebGLRenderingContext]GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 1
我知道这是因为加载的 OBJ 没有 UV 坐标,但我想知道是否有任何方法可以生成所需的纹理坐标。我试过了
material.needsUpdate = true;
geometry.uvsNeedUpdate = true;
geometry.buffersNeedUpdate = true;
...但无济于事。
有没有办法使用three.js自动生成UV纹理,还是我必须自己分配坐标?
【问题讨论】:
【参考方案1】:据我所知,没有自动计算 UV 的方法。
你必须自己计算。计算飞机的 UV 非常简单,这个网站解释了如何:calculating texture coordinates
对于一个复杂的形状,我不知道怎么做。也许你可以检测平面。
编辑
这里是平面(x, y, z)
的示例代码,其中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;
【讨论】:
为了帮助那些可能寻找不存在的解决方案的人......对于“复杂”、不规则的形状,唯一(理智的)方法是使用 Blender 等 3D 软件,手动将纹理放在建模并导出文件。 不幸的是,自 three.js r.125 以来,此语法不起作用。有谁知道如何将其转换为当前版本的 three.js ?【参考方案2】:这里的其他答案很有帮助,但不太符合我将重复图案纹理应用于具有大部分平坦表面的形状的所有侧面的要求。问题是仅使用 x 和 y 分量作为 u 和 v 会导致垂直表面上出现奇怪的拉伸纹理。
我下面的解决方案使用表面法线来选择要映射到 u 和 v 的两个分量(x、y 和 z)。它仍然很粗糙,但效果很好。
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;
此函数不会将 UV 标准化为对象的大小。这在将相同纹理应用于同一场景中不同大小的对象时效果更好。但是,根据您的世界坐标系的大小,您可能还需要缩放和重复纹理:
texture.repeat.set(0.1, 0.1);
texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping;
【讨论】:
这正是我所需要的。谢谢! 哇,谢谢!我需要很长时间才能做到这一点。 感谢您为我节省了大量时间!【参考方案3】:这里的答案很精彩,对我帮助很大。 只有一件事:如果您正在更新顶点,请不要重新分配 uv,而是设置它们,如(范围是我的几何):
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;
【讨论】:
你忘了声明i
【参考方案4】:
Box UV 映射可能是任何类型的 three.js 配置器中最有用的东西,- https://jsfiddle.net/mmalex/pcjbysn1/
该解决方案适用于索引和非索引缓冲区几何图形。
使用示例:
//build some mesh
var bufferGeometry = new THREE.BufferGeometry().fromGeometry(new THREE.DodecahedronGeometry(2.5, 0));
let material = new THREE.MeshPhongMaterial(
color: 0x10f0f0,
map: new THREE.TextureLoader().load('http://mbnsay.com/rayys/images/1K_UV_checker.jpg')
);
//find out the dimensions, to let texture size 100% fit without stretching
bufferGeometry.computeBoundingBox();
let bboxSize = bufferGeometry.boundingBox.getSize();
let uvMapSize = Math.min(bboxSize.x, bboxSize.y, bboxSize.z);
//calculate UV coordinates, if uv attribute is not present, it will be added
applyBoxUV(bufferGeometry, new THREE.Matrix4().getInverse(cube.matrix), uvMapSize);
//let three.js know
bufferGeometry.attributes.uv.needsUpdate = true;
示例基于applyBoxUV
的如下实现
function _applyBoxUV(geom, transformMatrix, bbox, bbox_max_size)
let coords = [];
coords.length = 2 * geom.attributes.position.array.length / 3;
// geom.removeAttribute('uv');
if (geom.attributes.uv === undefined)
geom.addAttribute('uv', new THREE.Float32BufferAttribute(coords, 2));
//maps 3 verts of 1 face on the better side of the cube
//side of the cube can be XY, XZ or YZ
let makeUVs = function(v0, v1, v2)
//pre-rotate the model so that cube sides match world axis
v0.applyMatrix4(transformMatrix);
v1.applyMatrix4(transformMatrix);
v2.applyMatrix4(transformMatrix);
//get normal of the face, to know into which cube side it maps better
let n = new THREE.Vector3();
n.crossVectors(v1.clone().sub(v0), v1.clone().sub(v2)).normalize();
n.x = Math.abs(n.x);
n.y = Math.abs(n.y);
n.z = Math.abs(n.z);
let uv0 = new THREE.Vector2();
let uv1 = new THREE.Vector2();
let uv2 = new THREE.Vector2();
// xz mapping
if (n.y > n.x && n.y > n.z)
uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
uv0.y = (bbox.max.z - v0.z) / bbox_max_size;
uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
uv1.y = (bbox.max.z - v1.z) / bbox_max_size;
uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
uv2.y = (bbox.max.z - v2.z) / bbox_max_size;
else
if (n.x > n.y && n.x > n.z)
uv0.x = (v0.z - bbox.min.z) / bbox_max_size;
uv0.y = (v0.y - bbox.min.y) / bbox_max_size;
uv1.x = (v1.z - bbox.min.z) / bbox_max_size;
uv1.y = (v1.y - bbox.min.y) / bbox_max_size;
uv2.x = (v2.z - bbox.min.z) / bbox_max_size;
uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
else
if (n.z > n.y && n.z > n.x)
uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
uv0.y = (v0.y - bbox.min.y) / bbox_max_size;
uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
uv1.y = (v1.y - bbox.min.y) / bbox_max_size;
uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
return
uv0: uv0,
uv1: uv1,
uv2: uv2
;
;
if (geom.index) // is it indexed buffer geometry?
for (let vi = 0; vi < geom.index.array.length; vi += 3)
let idx0 = geom.index.array[vi];
let idx1 = geom.index.array[vi + 1];
let idx2 = geom.index.array[vi + 2];
let vx0 = geom.attributes.position.array[3 * idx0];
let vy0 = geom.attributes.position.array[3 * idx0 + 1];
let vz0 = geom.attributes.position.array[3 * idx0 + 2];
let vx1 = geom.attributes.position.array[3 * idx1];
let vy1 = geom.attributes.position.array[3 * idx1 + 1];
let vz1 = geom.attributes.position.array[3 * idx1 + 2];
let vx2 = geom.attributes.position.array[3 * idx2];
let vy2 = geom.attributes.position.array[3 * idx2 + 1];
let vz2 = geom.attributes.position.array[3 * idx2 + 2];
let v0 = new THREE.Vector3(vx0, vy0, vz0);
let v1 = new THREE.Vector3(vx1, vy1, vz1);
let v2 = new THREE.Vector3(vx2, vy2, vz2);
let uvs = makeUVs(v0, v1, v2, coords);
coords[2 * idx0] = uvs.uv0.x;
coords[2 * idx0 + 1] = uvs.uv0.y;
coords[2 * idx1] = uvs.uv1.x;
coords[2 * idx1 + 1] = uvs.uv1.y;
coords[2 * idx2] = uvs.uv2.x;
coords[2 * idx2 + 1] = uvs.uv2.y;
else
for (let vi = 0; vi < geom.attributes.position.array.length; vi += 9)
let vx0 = geom.attributes.position.array[vi];
let vy0 = geom.attributes.position.array[vi + 1];
let vz0 = geom.attributes.position.array[vi + 2];
let vx1 = geom.attributes.position.array[vi + 3];
let vy1 = geom.attributes.position.array[vi + 4];
let vz1 = geom.attributes.position.array[vi + 5];
let vx2 = geom.attributes.position.array[vi + 6];
let vy2 = geom.attributes.position.array[vi + 7];
let vz2 = geom.attributes.position.array[vi + 8];
let v0 = new THREE.Vector3(vx0, vy0, vz0);
let v1 = new THREE.Vector3(vx1, vy1, vz1);
let v2 = new THREE.Vector3(vx2, vy2, vz2);
let uvs = makeUVs(v0, v1, v2, coords);
let idx0 = vi / 3;
let idx1 = idx0 + 1;
let idx2 = idx0 + 2;
coords[2 * idx0] = uvs.uv0.x;
coords[2 * idx0 + 1] = uvs.uv0.y;
coords[2 * idx1] = uvs.uv1.x;
coords[2 * idx1 + 1] = uvs.uv1.y;
coords[2 * idx2] = uvs.uv2.x;
coords[2 * idx2 + 1] = uvs.uv2.y;
geom.attributes.uv.array = new Float32Array(coords);
function applyBoxUV(bufferGeometry, transformMatrix, boxSize)
if (transformMatrix === undefined)
transformMatrix = new THREE.Matrix4();
if (boxSize === undefined)
let geom = bufferGeometry;
geom.computeBoundingBox();
let bbox = geom.boundingBox;
let bbox_size_x = bbox.max.x - bbox.min.x;
let bbox_size_z = bbox.max.z - bbox.min.z;
let bbox_size_y = bbox.max.y - bbox.min.y;
boxSize = Math.max(bbox_size_x, bbox_size_y, bbox_size_z);
let uvBbox = new THREE.Box3(new THREE.Vector3(-boxSize / 2, -boxSize / 2, -boxSize / 2), new THREE.Vector3(boxSize / 2, boxSize / 2, boxSize / 2));
_applyBoxUV(bufferGeometry, transformMatrix, uvBbox, boxSize);
【讨论】:
【参考方案5】:这是适用于球面映射(偏航、俯仰坐标)的通用版本,参见示例 here,(查看 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( n.z, - n.x ) / ( 2.0 * Math.PI );
var pitch = .5 - Math.asin( n.y ) / Math.PI;
var u = yaw,
v = pitch;
uvs.push( new THREE.Vector2( u, v ) );
geometry.faceVertexUvs[ 0 ].push( uvs );
);
geometry.uvsNeedUpdate = true;
【讨论】:
我尝试了上述球体几何的解决方案。虽然它确实产生了一些结果,但它不能正常工作。所以我修复了它 - 你可以在这里看到它的实际效果:codepen.io/knee-cola/pen/XMVBwQ atan 需要一个参数,您应该使用 atan2 以获得正确的结果。以上是关于三.js 生成UV坐标的主要内容,如果未能解决你的问题,请参考以下文章