Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decrease and increase the size of an image on an object - ThreeJS

Tags:

three.js

I'm making use of the THREE.ImageUtils.loadTexture function to load an image and then paste it onto each of the objects in my scene like so:

_.forEach(bags, (bag) => {
  if(bag['children'][0] && bag['children'][0].material) {
    let material = new THREE.MeshPhongMaterial({map: tex});
    bag['children'][0].material = material;
  }
});

I've looked into tex.scale.x,tex.scale.y & tex.offset.x, tex.offset.y but these don't seem to be what I need. Here's what the object looks like at the moment

enter image description here

but I'm after stricter positioning on each side. Something like this (pseudocode) which would place the top-left corner on that required side at that position.

sideA.image.setSize(x,y);

Does anyone have any examples of this?

EDIT

Thanks for the feedback so far guys. @Martin I've setup a basic PlaneGeometry shape to test this out. So this shapes face is basic two triangles attached to each other; I've attached an image of what the uv settings that you posted do:

enter image description here

So from what you said I can't extend one of the triangles as that would break the geometry used so I need to manipulate both the triangles to move the image around properly. Correct?

like image 468
Katana24 Avatar asked Jan 05 '17 16:01

Katana24


1 Answers

Unfortunately, there is no easy way to do this in three.js.

In order to get proper control over the texturing of non-trivial meshes, you will probably need to do some UV-unwrapping of the model first. This should™ normally be done by the person providing the meshes, because it is way easier to do it in tools like blender, 3dstudio, cinema4d and the likes.

However, I assume that you don't have that person available for some reason and need to do this in javascript. It's more fun this way. Let's further assume, that we just want to map the image to the front- and backside of the bag, the inside and edges will not be textured.

In three.js and most other 3D-applications, the exact mapping of the texture happens by providing texture-coordinates (aka UV-coordinates) for each vertex for every face. These UV-coordinates (ranged from 0 to 1) specify the position in the texture that belongs to the vertex. The point (0, 0) is the bottom-left corner of the image and (1, 1) the top-right corner, independent of the image's aspect-ratio.

So, for a simple quad made from two triangles this would be something like this:

const geometry = new THREE.Geometry();
const vertices = [
   // the four vertices ABCD
   [0,0,0], [0,1,0], [1,0,0], [1,1,0]
].map(v => new THREE.Vector3(...v));

// setup two triangles ADB and ACD:
//
// B---D
// | / |
// A---C

const normal = new THREE.Vector3(0,0,1);
const faces = [
  new THREE.Face3(0, 3, 1, normal),
  new THREE.Face3(0, 2, 3, normal)
];

geometry.vertices = vertices;
geometry.faces = faces;

So, this is just the geometry itself, but it's important to know how it was built for the next step - the UV-coordinates:

const uvs = geometry.faceVertexUvs[0];
const vertexUvs = [
  [0,0], [0,1], [1,0], [1,1]
].map(p => new THREE.Vector2(...p));

// we need to set the UV-coordinates for both faces separately, using 
// the same scheme as before:
uvs[0] = [vertexUvs[0], vertexUvs[3], vertexUvs[1]];
uvs[1] = [vertexUvs[0], vertexUvs[2], vertexUvs[3]];

And that's it basically. This is roughly what THREE.PlaneGeometry does. Some more points about that:

  • three.js is able to handle two different sets of uv-coordinates, that's why there is the geometry.faceVertexUvs[0].
  • the indices within that array correspond to the indices in the faces-array, so uvs[0] is the first face and uvs[1] the second.
  • each of those is again an array of length 3 corresponding to the vertices of that face.

Now in the case of your bags it should be possible to find out which two faces are the front-facing and back-facing part. You will need to find them and edit their UV-coordinates to match the positions in the image you want to see (I don't know how exactly those models look, so I can't help you much further here). All the other UV-coordinates could be set to some safe value, like (0, 0), then everything that is not the front- or back-side will have the color of the bottom-left pixel of your texture.

If you want to texture them as well, you should really consider doing the texture-map in proper 3d-editing software.

EDIT (2017-01-15)

Looking at the image with the angular-logo, the top-left triangle is flipped. This is because three.js is using a different face- or vertex-order than I did in my example.

It helps a lot to understand these things if you just play around with it in the browser-console or debugger (this is what i just did). According to that, the plane-geometry from three.js has the following structure:

// A---B
// | / |
// C---D

vertices = [A, B, C, D]
faces = [Face3(A, C, B), Face3(C, B, D)]

Notable is here that, for whatever reason, the first face is counterclockwise and the second face in clockwise-order (i believe that is kind of bad practice and triangles should always be counterclockwise). Have a look at the faceVertexUVs for the geometry to see how it should look like for this case (just type new THREE.PlaneGeometry(1,1).faceVertexUvs[0] into the browser-console)

Now for the other point, scaling the textures. One thing you could try is to use UV-values greater than 1 for the vertices. This would cause the texture to be drawn smaller. However, this will only work properly if

  • the texture wrap-mode is clamp-to-edge (texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;)
  • all textures have a 1px wide border in a neutral color

Otherwise (and this is the way I would recommend) you should use a canvas as texture and prepare the image before actually using it. I made a simple example here: http://codepen.io/usefulthink/pen/GrjmrM?editors=0010

like image 50
Martin Schuhfuß Avatar answered Sep 27 '22 16:09

Martin Schuhfuß