在 Three.js 中 UV 映射立方体没有正确连接面 [英] UV Mapping a cube in Three.js not joining faces correctly

查看:22
本文介绍了在 Three.js 中 UV 映射立方体没有正确连接面的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已通过 UV 映射成功地将图像纹理应用于立方体(以生成光球查看器).尽管纹理与立方体面完美对齐,但面连接处的线条是可见的细直线.

如果通过 Canvas 拆分纹理图块并通过 MultiMaterial 应用于立方体,则不会发生同样的问题.

下图比较了两种应用纹理的方法的结果(点击查看大图):

可以在

还有两件事情需要注意:

  • 图像的尺寸,需要是 2 的幂(宽度和高度)...否则 WebGLRenderer 会报错
  • 顶部和底部瓷砖放置一个 X 坐标,它将在 UV 坐标中形成一个有几个小数位的有理数,WebGL 计算的起始像素将是一个孔数(即顶部为 4.5*1024,顶部为 6*1024底部)

可以在 CodePen.io

由于瓷砖已经重新排列,我需要修复UV映射功能:

function mapCubeUV_v2(geometry, cubeH) {//将所有顶点转换为极坐标geometry.faceVertexUvs[0] = [];//这会清除对象上可能已经存在的任何 UV 贴图//遍历对象定义的所有面//... 我们需要为它们中的每一个定义一个 UV 贴图geometry.faces.forEach(function(face) {var uvs = [];var ids = ['a', 'b', 'c'],faceSign = face.normal.x+'.'+face.normal.y+'.'+face.normal.z;for( var i = 0; i < ids.length; i++ ) {//使用点访问顶点var vertexIndex = face[ ids[ i ] ],顶点=几何.顶点[顶点索引],瓷砖,uvY, uvX;//图像中的人脸顺序:东、南、西、北、上、下开关(面签){case '-1.0.0'://东uvY = vertex.y;uvX = vertex.z;瓷砖Ix = 0;休息;case '0.0.1'://南uvY = vertex.y;uvX = 顶点.x;tileIx = 1;休息;case '1.0.0'://西uvY = vertex.y;uvX = -vertex.z;瓷砖Ix = 2;休息;case '0.0.-1'://北uvY = vertex.y;uvX = -vertex.x;瓷砖Ix = 3;休息;case '0.1.0'://向上uvY = -vertex.z;uvX = 顶点.x;tileIx = 4.5;//"up" 是 "north" 的 1.5 tile 宽度距离休息;case '0.-1.0'://向下uvY = vertex.z;uvX = 顶点.x;瓷砖Ix = 6;//如果距离上"还有 1.5 个宽度的距离,则下"休息;}//坐标值范围从 [-cubeH/2, +cubeH/2]//这里我们正在修复将范围移动到 [0, +cubeH]uvY = uvY+cubeH/2;uvX = uvX+cubeH/2;//每个 UV 坐标代表小数范围 [0, +1]uvY = uvY/cubeH;uvX = uvX/cubeH;//由于图像包含多个纹理图块(其中 8 个),//[uvX] 必须调整为指向图像的一部分//包含当前图块uvX = (uvX+tileIx)/8;控制台日志(uvX);//if(faceSign!=='1.0.0') {//uvY = uvX = 0;//}uvs.push(new THREE.Vector2(uvX, uvY));}geometry.faceVertexUvs[0].push(uvs);});geometry.uvsNeedUpdate = true;返回(几何);}

I have successfully applied an image texture to a cube via UV mapping (to produce a photo-sphere viewer). Although the texture is perfectly aligned with cube faces, the lines at which faces join are visible as thin straight lines.

The same problem does not happen if texture tiles are split via Canvas and applied to cube via MultiMaterial.

The following image compares results of the two methods of applying a texture (click for larger image):

Live example can be found at CodePen.io

The image used for the texture can be found here

Here's the code which does the UV mapping (it's pretty straightforward):

function mapCubeUV(geometry, cubeH) {

  // converting all vertices into polar coordinates
  geometry.faceVertexUvs[0] = []; // This clears out any UV mapping that may have already existed on the object

  // walking through all the faces defined by the object
  // ... we need to define a UV map for each of them
  geometry.faces.forEach(function(face) {

    var uvs = [];

    var ids = [ 'a', 'b', 'c'],
        faceSign = face.normal.x+'.'+face.normal.y+'.'+face.normal.z;

    for( var i = 0; i < ids.length; i++ ) {

      // using the point to access the vertice
      var vertexIndex = face[ ids[ i ] ],
          vertex = geometry.vertices[ vertexIndex ],
          tileIx,
          uvY, uvX;

      // face order in the image: West, East, Up, Down, South, North
      switch(faceSign) {
        case '1.0.0': // West
          uvY = vertex.y;
          uvX = -vertex.z;
          tileIx = 0;
          break;
        case '-1.0.0': // East
          uvY = vertex.y;
          uvX = vertex.z;
          tileIx = 1;
          break;
        case '0.1.0': // Up
          uvY = -vertex.z;
          uvX = vertex.x;
          tileIx = 2;
          break;
        case '0.-1.0': // Down
          uvY = vertex.z;
          uvX = vertex.x;
          tileIx = 3;
          break;
        case '0.0.1': // South
          uvY = vertex.y;
          uvX = vertex.x;
          tileIx = 4;
          break;
        case '0.0.-1': // North
          uvY = vertex.y;
          uvX = -vertex.x;
          tileIx = 5;
          break;
      }

      // coordinate values range from [-cubeH/2, +cubeH/2]
      // here we're fixing moving the range to [0, +cubeH]
      uvY = uvY+cubeH/2;
      uvX = uvX+cubeH/2;

      // each UV coordinate represents decimal range [0, +1]
      uvY = uvY/cubeH;
      uvX = uvX/cubeH;

      // since the image contains multiple texture tiles (8 of them = 6 with
      // images + 2 dummy, which were added so that the width is a multiple of 2),
      // [uvX] must be adjusted to point to the part of the image
      // containing current tile
      uvX = (uvX+tileIx)/8;

      uvs.push( new THREE.Vector2( uvX, uvY ) );
    }

    geometry.faceVertexUvs[ 0 ].push( uvs );
  });

  geometry.uvsNeedUpdate = true;

  return(geometry);
}

I have double-checked the values produced by the above function and everything looks good - the UV values when multiplied with the image width and height produce the correct values in pixel. Here's the dump:

Face VerticeA   VerticeB   VerticeC 
 0: (    0,1), (    0,0), (0.125,1)
 1: (    0,0), (0.125,0), (0.125,1)
 2: (0.125,1), (0.125,0), ( 0.25,1)
 3: (0.125,0), ( 0.25,0), ( 0.25,1)
 4: ( 0.25,1), ( 0.25,0), (0.375,1)
 5: ( 0.25,0), (0.375,0), (0.375,1)
 6: (0.375,1), (0.375,0), (  0.5,1)
 7: (0.375,0), (  0.5,0), (  0.5,1)
 8: (  0.5,1), (  0.5,0), (0.625,1)
 9: (  0.5,0), (0.625,0), (0.625,1)
10: (0.625,1), (0.625,0), ( 0.75,1)
11: (0.625,0), ( 0.75,0), ( 0.75,1)

Am I doing something wrong or is there some problem with Three.js?

P.S. the test was based on an example found on Three.js website

P.P.S

a very similar question can be found HERE (although it doesn't deal with manually calculating the UV map)

解决方案

After fiddling with the problem for a while and a good night's sleep I discovered that the problem is caused by pixels from adjacent tile, which leak though at the face edges. This doesn't happen the image is cut-up into smaller independent sections (i.e. via canvas), since the pixels from the adjacent tiles are not copied.

The problem can be solved by (re)arranging the tiles in the source image so that they are placed next to a tile they will be next when applied to the cube. That way if pixels do shine through at the edge, it will be the correct ones.

In the image used by the example from the question above the tiles were ordered the following way: West-East-Top-Bottom-South-North. The correct order should be East-South-West-North + Top + Bottom.

There's still the problem with tiles at the edges of this sequence, who's edges are still adjacent to the wrong tile: * North - it's right edge should be connected with East * Top - it's left edge should be connected with East, and right edge with West * Bottom - left edge with West, right edge with East

To fix this we will need to put some space between North, Top and Bottom. Then we can copy-paste a narrow vertical strip from the edges of the East & West tiles and paste them next to edges of North, Top & Bottom. This will prevent wrong pixels shine through.

The following image shows the original image and what the end-result should look like (strips, which are added to a tile are marked by blue letters):

Two more things which need to be taken care of are:

  • the dimensions of the image, which need to be power of two (both width and height) ... otherwise WebGLRenderer will complain
  • the Top and Bottom tiles are placed a X coordinate which will in UV coordinates form a rational number with few decimal places, to that start pixel calculated by WebGL will be a hole number (i.e. 4.5*1024 for Top and 6*1024 for Bottom)

A live example of this solution comparing the two approaches can be found at CodePen.io

Since the tiles have been re-arranged, I needed to fix the UV mapping function:

function mapCubeUV_v2(geometry, cubeH) {

  // converting all vertices into polar coordinates
  geometry.faceVertexUvs[0] = []; // This clears out any UV mapping that may have already existed on the object

  // walking through all the faces defined by the object
  // ... we need to define a UV map for each of them
  geometry.faces.forEach(function(face) {

    var uvs = [];

    var ids = [ 'a', 'b', 'c'],
        faceSign = face.normal.x+'.'+face.normal.y+'.'+face.normal.z;

    for( var i = 0; i < ids.length; i++ ) {

      // using the point to access the vertice
      var vertexIndex = face[ ids[ i ] ],
          vertex = geometry.vertices[ vertexIndex ],
          tileIx,
          uvY, uvX;

      // face order in the image: East, South, West, North, Up, Down
      switch(faceSign) {
        case '-1.0.0': // East
          uvY = vertex.y;
          uvX = vertex.z;
          tileIx = 0;
          break;
        case '0.0.1': // South
          uvY = vertex.y;
          uvX = vertex.x;
          tileIx = 1;
          break;
        case '1.0.0': // West
          uvY = vertex.y;
          uvX = -vertex.z;
          tileIx = 2;
          break;
        case '0.0.-1': // North
          uvY = vertex.y;
          uvX = -vertex.x;
          tileIx = 3;
          break;
        case '0.1.0': // Up
          uvY = -vertex.z;
          uvX = vertex.x;
          tileIx = 4.5; // "up" is 1.5 tile width distance from "north"
          break;
        case '0.-1.0': // Down
          uvY = vertex.z;
          uvX = vertex.x;
          tileIx = 6; // "down" if further 1.5 widths distance from "up"
          break;
                     }

      // coordinate values range from [-cubeH/2, +cubeH/2]
      // here we're fixing moving the range to [0, +cubeH]
      uvY = uvY+cubeH/2;
      uvX = uvX+cubeH/2;

      // each UV coordinate represents decimal range [0, +1]
      uvY = uvY/cubeH;
      uvX = uvX/cubeH;

      // since the image contains multiple texture tiles (8 of them),
      // [uvX] must be adjusted to point to the part of the image
      // containing current tile
      uvX = (uvX+tileIx)/8;
      console.log(uvX);

      // if(faceSign!=='1.0.0') {
      //    uvY = uvX = 0;
      // }

      uvs.push( new THREE.Vector2( uvX, uvY ) );
    }

    geometry.faceVertexUvs[ 0 ].push( uvs );
  });

  geometry.uvsNeedUpdate = true;

  return(geometry);
}

这篇关于在 Three.js 中 UV 映射立方体没有正确连接面的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆