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

时间:2018-04-18 13:14:10

标签: javascript 3d three.js textures mesh

谈到3D动画时,我不熟悉一些 很多 的术语和概念(可能是附加到此的第二个问题)一:熟悉这些概念有哪些好书?)。我不知道" UV"是(在3D渲染的背景下),我不熟悉将图像上的像素映射到网格上的点的工具。

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

360-degree Panorama

我希望这张图片的中心位于"顶部"球体的角度,以及该图像中圆圈的任何半径都是从上到下沿球体的弧线。

这是我的起点(直接从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();

这会产生以下结果:

Mapped to a sphere

有一些问题:

  • 纹理旋转90度
  • 地面是扭曲的,但如果旋转固定可能会修复?
  • 更新:在对正在生产的球体进行进一步调查后,它实际上没有旋转90度。相反,图像的顶部是球体的顶部,图像的底部是球体的底部。这会导致图像的左右边缘变成扭曲的侧面地面"我看到了
  • 这是在球体的外部上。我想把它投射到球体的里面(并将相机放在球体内)

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

做一些研究我很确定我需要修改" UV"来解决这个问题,我只是不知道这甚至意味着什么? / p>

3 个答案:

答案 0 :(得分:2)

工作示例

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

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

解决方案

因此,在花了一天时间研究紫外线贴图以了解其意义及其工作原理后,我能够坐下来划出一些触发来将球体上的点映射到我的立体图像上。它基本上归结为以下几点:

  1. 使用Y坐标的反余弦来确定立体图像上极坐标的大小
  2. 使用X和Z坐标的反正切来确定立体图像上极坐标的角度
  3. 使用x = Rcos(theta), y = Rcos(theta)计算立体图像上的直角坐标
  4. 如果时间允许,我可以在Illustrator中绘制快速图像或者用来解释数学的东西,但它是标准的三角法

    在此之后我更进了一步,因为我使用的相机只有240度的垂直视角 - 这导致图像稍微扭曲(特别是靠近地面)。通过从360减去垂直视角并除以2,您可以从垂直方向获得一个角度,在该角度内不应进行任何映射。因为球体是沿着Y轴定向的,所以这个角度映射到一个特定的Y坐标 - 在该坐标之上有数据,低于该坐标不存在。

    1. 计算此"最小Y值"
    2. 对于球体上的所有点:
      • 如果该点高于最小Y值,则将其线性缩放,使第一个这样的值计为" 0"并且球体的顶部仍被计为" 1"用于制图目的
      • 如果该点低于最小Y值,则不返回任何内容
    3. 奇怪的警告

      出于某种原因,我写的代码将图像翻转过来。我不知道我是否搞砸了我的三角学,或者我是否搞砸了我对UV贴图的理解。无论如何,通过在映射

      之后将球体翻转180度来简单地修复这个问题

      同样,我也不知道如何"什么也不返回"在UV地图中,所以我将最小Y值以下的所有点映射到图像的角落(黑色)

      在240度视角下,球体底部没有图像数据的空间足够大(在我的显示器上),我可以看到直视前方的黑色圆圈。我不喜欢它的视觉外观,所以我插入270用于垂直FOV。这导致了地面的轻微扭曲,但没有使用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 );
      

      结果

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

      enter image description here enter image description here enter image description here enter image description here

      我仍然不理解

      的一件事

      在&#34;洞周围&#34;在球体底部有一个多色环。它几乎看起来像天空的镜子。我不知道为什么会存在或者它是如何存在的。任何人都可以在评论中阐明这一点吗?

答案 1 :(得分:1)

在接近极端展开紫外线的大约10分钟内,我就可以得到它。

您可以修改polarUnwrap函数以尝试获得更好的映射....

https://codesandbox.io/s/8nx75lkn28

enter image description here

您可以使用

替换TextureLoader()。loadTexture()
//assuming you have created a HTML video element with id="video"
var video = document.getElementById( 'video' );

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

将您的视频投放到那里......

更多信息:

https://threejs.org/docs/#api/textures/VideoTexture

这也可能对您有用:

https://community.theta360.guide/t/displaying-thetas-dual-fisheye-video-with-three-js/1160

答案 2 :(得分:0)

我认为,修改UV会非常困难,因此立体投影图像会很合适。球体的UV设置为适合具有等长矩形投影的纹理。

要将图像从立体图转换为equirectangular,您可能需要使用全景工具,例如PTGuiHugin。或者您可以使用Photoshop(应用滤镜&gt;扭曲&gt;极坐标&gt;极坐标到矩形)。

Equirectangular projection 图像的Equirectangular投影(使用Photoshop),调整为2:1宽高比(纹理不需要)

如果您希望纹理位于球体内(或法线翻转),则可以将材质设置为git diff <commit-hash1> <commit-hash2> <commit-hash3> | grep <my-search-text>

THREE.BackSide

也许,你必须水平翻转纹理:How to flip a Three.js texture horizontally