问题
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)
回答1:
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);
}
来源:https://stackoverflow.com/questions/43401684/uv-mapping-a-cube-in-three-js-not-joining-faces-correctly