Mr.Yuan Mr.Yuan

苟正其身矣,于从政乎何有?不能正其身,如正人何?

目录
three.js 修改UV映射实现球型贴图 -- BufferGeometry对象
/  

three.js 修改UV映射实现球型贴图 -- BufferGeometry对象

前言

网上有很多直接贴贴图的实现方式. 但现在公司的有个全景的项目不能以这种单纯的方式去实现. 综合下来, 现在的需求要求在一个实实在在的空间模型中实现全景的预览, 场景的切换, 上帝, 个人视角的切换等等.

要实现模型中无缝的全景UV映射全景图 这要先从 缓存几何模型(BufferGeometry) 说起.

缓存几何模型(BufferGeometry)

该类是一个 几何模型(Geometry) 的高效替代,因为它使用缓存(buffer)来保存所有数据,包括顶点位置、面索引、法向量、颜色、UVs以及自定义属性。 这节约了向GPU传递全部这些数据的成本。但同时也使得BufferGeometry要比 几何模型(Geometry) 更难以处理,不是以对象的方式来访问,比如使用Vector3来访问位置数据, 以Color对象来访问颜色数据,你得从相应的attribute缓存中访问原始数据。 这使得BufferGeometry很适合用来存储静态对象,也就是当我们创建完模型实例后不太需要去操作它。

相关属性对象

position

对象

geometry.attributes.position

array: 记录几何模型的顶点信息 x, y, z 的集合.
count: 顶点的数量
itemSize: array数据的分组大小

array 是一维数组数组顺序是 [x1,y1,z1,x2,y2,z2...] 依次顺序排列.

normal

对象

geometry.attributes.normal

保存模型中每个顶点处的面或顶点法向量的x, y, 和 z 分量。

uv

对象

geometry.attributes.uv

保存模型中UV坐标信息. [u,v,u1,v1,u2,v2...] - (itemSize: 2)

顶点顺序

添加了一个球体观察点的顺序有一个发现

var geometry = new THREE.SphereBufferGeometry(50, 4, 4);
var sphere = new THREE.Mesh(geometry, materialLocation);
scene.add(sphere);
console.log(sphere);

position 的数组顶点数据是这样的顺序:

从头往下: y值 r ~ 0 ~ -r
从左住右: x值 r ~ 0 ~ -r
从前往后: z值 r ~ 0 ~ -r

换句话说就是: 它是顺时针螺旋向下的.

3D图形P1.png

开始的点都是 y = r 时的最头上的点. 这个点在array中会出现多次(准确的是应该是水平分割面的数量的次数), 同样的 y = -r 时脚下的点也是如此.

这个会产生一个问题: 上下极点贴图聚合点.

因为这个时候采用一般处理的计算方式时这 4 个点的所有UV值计算出来都会是 (0.5,0.5)

压缩线产生的原因

一个横切面的点数是水平分割面的数量+1, 比如你上面的代码水平分割面的数量是4, 实际的顶点有 5个. 最后一点的的位置与起始点相重合.

所以在计算UV时第一个点与最后一个点计算出来的UV值都会是同一个值. 这也是为什么当你是以计算UV来贴图实现全景时, 会有一条压缩线的原因.

UV

UV 的取值范围为 0 ~ 1.

四个顶点所对应的坐标如下:

01289998846QE3e.gif

解决压缩线的思路

当了解到整个模型的连接起来的地方是两个无限接近的点时, 就有了相应的解决办法:
把假设你的UV起点是0, 则把另外的那个无线接近的点设置为 1 就可解决;

解决方法

  1. 定位每一圈的最后一个点.
  2. 根据UV的起点坐标, 将其设置为终点坐标.

这里只用修改U值. V值还是正常的计算值.

计算UV

通过遍历所有顶点来计算 UV 坐标.

先说下大概流程:

- 遍历模型点, 获取点坐标信息.
- 根据点坐标获取X,Y轴的夹角角度.
- X,Y轴夹角角度映射出UV坐标.
- 筛选每一圈的最后一点个并重置终点值.
- 修改UV.

主要方法:

function calcUvs(geometry) {
    let _g = geometry;
    console.log(_g);

    let _position = geometry.getAttribute("position");
    let uvArray = new Float32Array(2 * _position.count);

    console.log(_position);

    let _uvI = 0;
    let uvP = {};
    for (let i = 0; i < _position.array.length; i += 3) {
        let _x = _position.array[i];
        let _y = _position.array[i + 1];
        let _z = _position.array[i + 2];

        let _r = getRadius(0,0,0,_x,_y,_z);

        let angleXY = getAngleXY(Math.round(_r), _x, _y, _z);
        // console.log(angleXY);
        uvP = getUVPosition(angleXY.angleX||0, angleXY.angleY||0);

        // 每圈的最后一个点 解决 0 1拼接点的压缩线问题.
        if ( (i / 3) % (_g.parameters.widthSegments + 1) == 0 ) {
	    uvP.u = 1;
	}

	uvArray[_uvI] = uvP.u;
	_uvI += 1;
	uvArray[_uvI] = uvP.v;
        _uvI += 1;
    }
    // v.material = material;
    let _uv = _g.getAttribute("uv");
    _uv.needsUpdate = false;
    _uv.setArray(uvArray);
    console.log(_uv);
    // 更新
    _uv.needsUpdate = true;
};
// 获取模型点到可视点的半径
function getRadius(x,y,z,x1,y1,z1) {
    return Math.pow(
        Math.pow(x-x1, 2) + Math.pow(y-y1, 2) + Math.pow(z-z1, 2), 1/2
    );
};
// 根据半径和坐标获取角度
function getAngleXY(r, x, y, z) {
    return {
        angleX: Math.atan2(z, x) + Math.PI,
	angleY: Math.asin(y / r)
    };
};
// 角度转化为 UV 坐标 UV 范围 0 ~ 1
function getUVPosition(angleX, angleY) {
    return {
	u: (angleX / (2 * Math.PI)) % 1,
	v: 1 / 2 + angleY / Math.PI
    }
};

标题:three.js 修改UV映射实现球型贴图 -- BufferGeometry对象
作者:K
地址:https://pala.icu/articles/2019/12/21/1576858372541.html