Three.JS对部分球体进行UV映射

5
之前的问题中,我想将一个立体投影映射到球体上,以便直播虚拟现实事件。最终,我学到了很多有关UV映射的知识,并成功地构建了一个非常精确的映射,效果非常出色。然而,这种映射方法有一个缺点:球体底部必须映射到“空白”,因此我将其映射到纹理的一个角落。
为了改进这个解决方案,我尝试根据Three.JS文档中的SphereGeometry,仅创建一个部分球体,调整theta长度。
虽然我得到了正确的球形形状,但我注意到UV映射有些奇怪。
var fov = 270;
var geometry = new THREE.SphereGeometry(
    1, // Radius
    50, // Horizontal segments
    50, // Vertical segments
    0, // Phi start
    2 * Math.PI, // Phi length
    Math.acos((360 - fov) * Math.PI / 180 / 2), // Theta start
    Math.PI - Math.acos((360 - fov) * Math.PI / 180 / 2) // Theta length
);
var minX, minY, minZ, maxX, maxY, maxZ;
var faceVertexUvs = geometry.faceVertexUvs[0];
for ( var i = 0; i < faceVertexUvs.length; i++ ) {
    var face = geometry.faces[i];
    for ( var j = 0; j < 3; j ++ ) {
        var x = face.vertexNormals[j].x;
        var y = face.vertexNormals[j].y;
        var z = face.vertexNormals[j].z;

        // Capture the upper and lower bounds for all points
        if (!minX || x < minX) minX = x;
        if (!maxX || x > maxX) maxX = x;
        if (!minY || y < minY) minY = y;
        if (!maxY || y > maxX) maxY = y;
        if (!minZ || z < minZ) minZ = z;
        if (!maxZ || z > maxZ) maxZ = z;
    }
}
console.log("minX: " + minX + ", maxX: " + maxX);
console.log("minY: " + minY + ", maxY: " + maxY);
console.log("minZ: " + minZ + ", maxZ: " + maxZ);

我之前学习UV映射时发现,无论网格的实际大小如何,“x”、“y”和“z”值的范围都从“-1”到“1”。如果球体的半径为1或100,则“x”值为1表示“球体极右”,“y”值为1表示“球体顶部”。但是,当我有一个像这样的不完整的球体(使用“thetaStart”和“thetaLength”来在球体顶部切一个洞),突然“maxY”等于约0.7854。
为什么这个网格以0.7854结束,而不是从-1到1缩放?我希望通过改变球体的形状来简化我的UV映射逻辑(删除我链接的先前问题中的“scaledY”项),但是改变球体的形状似乎对UV映射没有任何影响。
有没有办法告诉Three.JS,这个部分球体是完整的形状,其坐标应从-1到1范围内?
1个回答

3

我觉得您在描述和代码片段中将法线和UV混淆了。您在上面的这些行中读取了法线的值:

    var x = face.vertexNormals[j].x;
    var y = face.vertexNormals[j].y;
    var z = face.vertexNormals[j].z;

使用SphereGeometry属性时,最小值和最大值y法线与预期结果匹配:enter image description here 你想做的是查看最小值和最大值的UV值。我已更新代码以获取UV值:
var fov = 270;
var geometry:THREE.SphereGeometry = new THREE.SphereGeometry(
    2, // Radius
    50, // Horizontal segments
    50, // Vertical segments
    0, // Phi start
    2 * Math.PI, // Phi length
    Math.acos((360 - fov) * Math.PI / 180 / 2), // Theta start
    Math.PI - Math.acos((360 - fov) * Math.PI / 180 / 2) // Theta length
);
var minX, minY, minZ, maxX, maxY, maxZ;
var faceVertexUvs = geometry.faceVertexUvs[0];

for ( var i = 0; i < faceVertexUvs.length; i++ ) {
    var vertUV = faceVertexUvs[i];
    for ( var j = 0; j < 3; j ++ ) {
        var x = vertUV[j].x;
        var y = vertUV[j].y;

        // Capture the upper and lower bounds for all points
        if (!minX || x < minX) minX = x;
        if (!maxX || x > maxX) maxX = x;
        if (!minY || y < minY) minY = y;
        if (!maxY || y > maxX) maxY = y;
    }
}
console.log("minX: " + minX + ", maxX: " + maxX);
console.log("minY: " + minY + ", maxY: " + maxY);

当您运行此代码时,将会看到该范围始终为[0, 1],而不是[-1, 1]。另外请注意,UV没有Z值,只有X和Y值,因为它们用于映射二维图像。
法线: - x: (-1, 1) - y: (-1, 1) - z: (-1, 1)
UV: - x: [0, 1] - y: [0, 1]
您可以在此代码中查看ThreeJS如何构建球体的UV,具体在第90、94和111行。

我可能很困惑,因为我对UV映射的概念还比较新。我知道法线指的是三维空间中多边形上的点,而UV指的是二维纹理上的点。我也知道UV范围在[0,1]之间。然而,我认为法线始终在[-1,1]之间。当我制作一个半径为100的球体时,法线仍然在[-1,1]范围内,这表明它与网格的物理大小无关。因此,我认为1表示“网格的顶部”,而不管其形状或大小如何。 - stevendesu
1
创建球体时,法线在X、Y、Z空间中垂直于面。但由于你的球体没有“北极点”,所以你不会遇到指向正上方的法线。这就是为什么你发现你的最大y值是0.7854的原因。 - M -
这很有道理,但也引发了一些新问题,关于如何为具有两个面朝向相同方向的网格创建UV映射(例如甜甜圈,内外环都有面朝向所有基本方向)。感谢您的解释。 - stevendesu
这个例子很好地说明了ThreeJS如何通过将这张图片映射到其标准几何体上(包括甜甜圈,也称为环面)来定义其UV。现在,要映射自己的纹理取决于你使用的软件。 - M -

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接