在 ThreeJS 中将立体投影映射到球体内部

ste*_*esu 5 javascript 3d textures mesh three.js

当谈到 3D 动画时,有很多不熟悉的术语和概念(也许附加到这个问题的第二个问题:有哪些好书可以熟悉这些概念?)。我不知道“UV”是什么(在 3D 渲染的上下文中),也不熟悉用于将图像上的像素映射到网格上的点的工具。

我有一个 360 度相机生成的以下图像(它实际上是 HTMLvideo元素的输出):

360度全景

我希望该图像的中心是球体的“顶部”,并且该图像中圆的任何半径都是沿着球体从上到下的弧。

这是我的起点(直接从 Three.JS 文档复制代码行):

var video = document.getElementById( "texture-video" );

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

var texture = new THREE.VideoTexture( video );
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;

var material = new THREE.MeshBasicMaterial( { map: texture } );

var geometry = new THREE.SphereGeometry(0.5, 100, 100);
var mesh = new THREE.Mesh( geometry, material );

scene.add( mesh );

camera.position.z = 1

function animate()
{
    mesh.rotation.y += 0.01;
    requestAnimationFrame( animate );
    renderer.render( scene, camera );
}
animate();
Run Code Online (Sandbox Code Playgroud)

这会产生以下结果:

映射到球体

有几个问题:

  • 纹理旋转 90 度
  • 地面扭曲了,虽然如果旋转固定的话这可能会被修复?
  • 更新:根据对所生成的球体的进一步调查,它实际上并未旋转 90 度。相反,图像的顶部是球体的顶部,图像的底部是球体的底部。这导致图像的左右边缘变成我看到的扭曲的“侧向地面”
  • 这是在球体的外侧。我想将其投影到球体内部(并将相机放置在球体内部)

目前,如果我将相机放置在球体内,我会得到纯黑色。我不认为这是照明问题,因为 Three.JS 文档说 aMeshBasicMaterial不需要照明。我认为问题可能是所有球体面的法线都指向外,我需要反转它们。我不确定如何做到这一点 - 但我很确定这是可能的,因为我认为这就是天空盒的工作原理。

做了一些研究,我很确定我需要修改“UV”来解决这个问题,我只是不知道这意味着什么,或者到底意味着什么......

ste*_*esu 6

工作示例

我分叉了 @manthrax 的 CodeSandbox.io 解决方案并用我自己的解决方案更新了它:

https://codesandbox.io/s/4w1njkrv9

解决方案

因此,在花了一天时间研究 UV 映射以了解其含义及其工作原理之后,我能够坐下来并制定一些三角函数,将球体上的点映射到立体图像上的点。基本上可以归结为以下几点:

  1. 使用 Y 坐标的反余弦确定立体图像上极坐标的大小
  2. 使用 X 和 Z 坐标的反正切来确定极坐标在立体图像上的角度
  3. 用于x = Rcos(theta), y = Rsin(theta)计算立体图像上的直角坐标

如果时间允许,我可能会在 Illustrator 中快速绘制一个图像或其他东西来解释数学,但它是标准三角学

在此之后我更进一步,因为我使用的相机只有 240 度垂直视角 - 这导致图像稍微扭曲(尤其是在地面附近)。通过从 360 度减去垂直视角并除以二,您可以得到一个与垂直方向的角度,在该角度内不应发生映射。由于球体沿 Y 轴定向,因此该角度映射到特定的 Y 坐标 - 在该坐标上方有数据,在该坐标下方没有数据。

  1. 计算这个“最小Y值”
  2. 对于球体上的所有点:
    • 如果该点高于最小 Y 值,则对其进行线性缩放,以便第一个此类值被计为“0”,并且出于映射目的,球体的顶部仍被计为“1”
    • 如果该点低于最小 Y 值,则不返回任何内容

奇怪的警告

由于某种原因,我编写的代码将图像颠倒了。我不知道是我搞砸了我的三角学还是我对 UV 贴图的理解搞砸了。不管怎样,这个问题可以通过在映射后将球体翻转 180 度来轻松解决

另外,我不知道如何在 UV 贴图中“不返回任何内容”,因此我将低于最小 Y 值的所有点映射到图像的角(黑色)

在 240 度视角下,球体底部没有图像数据的空间足够大(在我的显示器上),当我直视前方时,我可以看到黑色圆圈。我不喜欢它的视觉外观,所以我将垂直 FOV 设置为 270。这会导致地面周围出现轻微变形,但没有使用 360 度时那么严重。

代码

这是我为更新 UV 贴图而编写的代码:

// Enter the vertical FOV for the camera here
var vFov = 270; // = 240;

var material = new THREE.MeshBasicMaterial( { map: texture, side: THREE.BackSide } );
var geometry = new THREE.SphereGeometry(0.5, 200, 200);

function updateUVs()
{
    var maxY = Math.cos(Math.PI * (360 - vFov) / 180 / 2);
    var faceVertexUvs = geometry.faceVertexUvs[0];
    // The sphere consists of many FACES
    for ( var i = 0; i < faceVertexUvs.length; i++ )
    {
        // For each face...
        var uvs = faceVertexUvs[i];
        var face = geometry.faces[i];
        // A face is a triangle (three vertices)
        for ( var j = 0; j < 3; j ++ )
        {
            // For each vertex...
            // x, y, and z refer to the point on the sphere in 3d space where this vertex resides
            var x = face.vertexNormals[j].x;
            var y = face.vertexNormals[j].y;
            var z = face.vertexNormals[j].z;

            // Because our stereograph goes from 0 to 1 but our vertical field of view cuts off our Y early
            var scaledY = (((y + 1) / (maxY + 1)) * 2) - 1;

            // uvs[j].x, uvs[j].y refer to a point on the 2d texture
            if (y < maxY)
            {
                var radius = Math.acos(1 - ((scaledY / 2) + 0.5)) / Math.PI;
                var angle = Math.atan2(x, z);

                uvs[j].x = (radius * Math.cos(angle)) + 0.5;
                uvs[j].y = (radius * Math.sin(angle)) + 0.5;
            } else {
                uvs[j].x = 0;
                uvs[j].y = 0;
            }
        }
    }
    // For whatever reason my UV mapping turned everything upside down
    // Rather than fix my math, I just replaced "minY" with "maxY" and
    // rotated the sphere 180 degrees
    geometry.rotateZ(Math.PI);
    geometry.uvsNeedUpdate = true;
}
updateUVs();

var mesh = new THREE.Mesh( geometry, material );
Run Code Online (Sandbox Code Playgroud)

结果

现在,如果您将此网格添加到场景中,一切看起来都很完美:

在此输入图像描述 在此输入图像描述 在此输入图像描述 在此输入图像描述

一件事我仍然不明白

在球体底部的“洞”周围有一个彩色环。它看起来几乎就像天空的镜子。我不知道它为什么存在或它是如何到达那里的。有人可以在评论中阐明这一点吗?