Texture offsets of buffer geometries in threejs with the UV attribute
When working out a custom geometry or playing around with a built in geometry in threejs, there are a number of attributes of interest if the geometry is to be used with a mesh object. When it comes to using THREE.Points or THREE.Line I just need to worry about the position. However when it comes to mesh objects I am also going to want to have a normal attribute that has to do with the direction that points of the position attribute are facing.
Today though I will be getting into the uv attribute that is used to position the textures that are used when skinning a geometry with a material that will make use of such an attribute. It is a way to change the locations of where the texture is to be applied for a given face that will be using the texture, by creating an array of values for each vertex. Each vertex has a horizontal and vertical value for it in the array so this might differ a little in terms of the number of values for each vertex compared to the other attributes. So for example if a geometry is a plane that has a vertex count of 4 then there will be two numbers for each vertex that will result in a length of 8 values when it comes to the uvs for that plane.
The uv attribute of a buffer geometry and what to know first
This is a post on an advanced topic in threejs that has to do with creating, or mutating an array of uv values for an instance of buffer geometry. This is then not a getting started type post with threejs or javaScript in general and I assume that you have logged a fair amount of time working with threejs before hand. In any case in this section I will be going over some things that you should maybe be aware of before hand, or maybe read up on a bit more before reading the rest of this post.
One way or another you are going to need a texture to use with a material
The uv attribute has to do with how a texture is to be applied to a face of a geometry in a mesh. So in order to make use of uvs and to play around with them you are going to need a texture to use as a color map of a material. In this post I am creating textures using canvas elements which is one way to go about making some textures with javaScript code rather than an external image. There are a number of other options though when it comes to having a texture to use though such as data textures, using the texture loader to load external images, or just directly call the texture constructor and pass an image if you have some other way to load images.
There is much more to read about when it comes to buffer geometry in general
The way that I think about it is that learning about the uv attribute of a buffer geometry comes after learning about the normal, and position attributes. Those attributes of a geometry are also very important as they define the position of the points in space, and the directions that they are facing which is something that is used when it comes to light or the use of the normal material. However there is even more to a geometry beyond the position, normal, and uv attributes, when it comes to prototype methods of buffer geometry class instance as well as other properties such as groups and material index values.
Do not confuse uvs with the material index values of groups.
The uvs have to do with how a texture is to be applied to a face when it comes to offsets in the image. When it comes to a box geometry for example the uv attribute is set up by default to use all of the image for all sides. Often this might be what I want to happen but often I might want to do something else. I could edit the uv attribute so that I use only certain parts of a single texture for certain faces of the cube, but another option would be to use more than one image for more than one material. Then it is just a matter of what image I want to use for what face.
For the most part I think it is a good idea to learn a thing or two about how to edit the uv attribute and use just one texture. However I think I should still at least mention here that there is another option that might need to be used in some cases that has to do with setting the material index values of group objects, and creating the groups in the first place. However getting into that here would be to far off topic.
Source code examples are up on Github
The source code examples that I am writing about in this post can also be found in my test threejs repo on github. This is also where I park the source code for my many other posts on threejs as well.
Always check your version numbers
When I first wrote this post I was using r127 of threejs, and the last time I came around to edit all of the demos I them all updated to the standards I set for r146. Also the last time I came around to edit this post a little I started some new r152 demos as well. In time code breaking changes might be made to the library that will result in these examples no longer working until I get around to editing this post yet again.
1 - Basic examples of the uv attribute of buffer geometry
In this section I will be starting out with just some basic examples of the uv attribute of the buffer geometry class in threejs. This will involve examples where I am mutating a uv attribute that is all ready there to begin with such as with built in geometries. However i will also be going over at least one example that involves creating a custom geometry from the ground up as well.
1.1 - Basic uv mutation example using a Plane, and a canvas texture
To get a general idea of what the uvs are for when it comes to textures it might be best to start working with a plane geometry and look at the values of uv attribute of the plane that is created when using the built in geometry constructor. For example if I create a plane geometry that is a size of 1 by 1 and also has a section size of 1 by 1 then that results in a geometry composed of just 4 points, and because there are 2 uv values for each point it will result in just 8 values in the uv attribute. This is then a nice simple starting point when it comes to playing around with these values to gain a sense of what happens when the values are changed.
|
|
The values that I will typically want to use will be just 0, and 1 which is what will happen when I want to use all of the texture for a face. Numbers between 0 and one imply what I am using just some of the image, while numbers above 1 mean that I am using the whole of the image for just part of the face actually.
1.2 - Basic triangle custom geometry example with uv attribute added.
This next example involves adding a uv attribute to a clean new buffer geometry that is made from scratch. Sense this is a basic section of this post this custom geometry will be just three points in space, so in other words a single triangle. The first step is to create a new buffer geometry by calling the main THREE.BufferGeomerty constructor function. After that the returned result will be a clean, new Buffer geometry instance however it will not have any attributes at all, including a position attribute.
So then I will want to start by adding the position attribute which will be the actually position of three points in space. To do this I will want to create a Float32Array of numbers where each three numbers are the x y and z positions of each point in space. Sense I want to make just a single triangle here this will be 9 numbers in the array then. I can then use this array to create a new instance of Buffer Attribute which I can then use to set the position attribute of this new geometry by calling the set attribute method, and giving the string ‘position for the name of the attribute’.
Next I will want to add a normal attribute to this as well, one quick way to do this that will work well more often that not will be to just call the compute vertex normal’s method of the buffer geometry instance.
Now that I have a position and normal attribute I can now add the uv attribute to define what areas of a 2d texture should be drawn to the surface of this triangle. Once again as with my position attribute I will want to create an instance of a Float32Array but this time I will only have two values for each point as these are the 2d image offsets for each point in the triangle. I can then use this array as the first argument for a new instance of buffer attribute and this time I will be making the item size of the attribute 2. This can then be used to set the uv attribute of the buffer geometry by once again calling the set attribute method.
|
|
2 - Minimap for help with UV mapping
I have found that a good way to gain a sense of what is going on with a UV attribute is to have a mini map of sorts that shows what the current state is with the UV attribute of a geometry. That is to have some kind of display that is a scaled down, or up image of the texture that is being used with the geometry. Then use the UV attribute of the geometry to also draw lines over this scaled texture to show what areas of the texture are being used for what triangle. In this section that I am going to be writing about some of these kinds of demos that are great for working out logic that will be used to set or update the UV attribute.
2.1 - Starting with just a Single triangle
One has to start somewhere with this sort of thing so with this demo the goal is to just work out the core idea of what I want to do with just a single triangle. Simple enough, but even when it comes to that this can still prove to be a little involved. On top of having the usual renderer I alsl have a plain old 2d canvas that I will be drawing to with the dome element of the renderer. I will also be drawing this plain old main canvas with yet another 2d canvas that will be the texture that I will be using with the map option of the material of the mesh object. So this texture will be used to draw the current state of this mini map background, and then also be used with the material of the mesh object.
I then have a wide range of helper functions for this demo then. One of which I am using to create the main mini map object, and another to draw the current state of this object. I also have methods that I use to create an array of vector2 objects from the geometry that I give it.
|
|
2.2 - Using a non indexed plane geometry demo
For this next demo I wanted to use this minimap code to help get an idea of what is going on with a simple Plane geometry. I ran into some problems but was able to fix it by making the geometry a non indexed geometry. When it comes to making some kind of stand alone module to help me with the process of displaying what the current state of the uv attribute is I will of course want to address that by updating the code used to draw the points in the mini map so that it will work better with an indexed geometry.
|
|
3 - Set Face method for Box Geometry
This is a demo based on code that I wrote for my mutation of the uv attribute of a Box geometry threejs project example. The goal with that project was to make some code that I can use to quickly set the faces of a box geometry to given cells in a 2d image. So simply put I have this set face method that will take a uv attribute, face index, cell index, and order array, and a cell size as arguments and set the uv attribute with those values. So then I call the set face method, pass the uv and then the face index of the cube in the 0 - 5 range. I then pass a cell index that I want to use in the texture that I am using with the material, the value of which will differ based on the cell size argument that is given. The order array is then the order of the vertex to use when mapping the part of the image to the face.
|
|
4 - Updating uvs at run time in an Animation loop
Just because the array of uvs can be updated at run time that does not mean that doing so is a good idea. I think that generally uvs are something that should be set up once and only once and if I want to do something that involves more than one texture for a face it might be better to think in terms of more than one texture file and material and updating the textures used with materials rather than messing around with uvs.
Still if for some reason I do need to change the state of the uvs over time in a loop I just need to make sure that I keep setting the needs update property of the uv attribute back to true each time.
|
|
Conclusion
The uvs of a geometry are for setting up the texture cornets of a geometry when it comes to what portions of a texture should be used when applying it to the face of geometry. This array of values should not be confused with other values of a geometry such as the groups that are used when setting material index values. That is that another part of creating a custom geometry would involve also creating an array of groups, and each group object would contain a material index value to use for a given set of triangles when using an array of materials for a mesh object that will be used with a buffer geometry.
That is it for now when it comes to the uv attribute, at some point in the future I might be able to get around to doing some editing for this post as I often do for my threejs content. Some additional examples that have to do with creating a geometry from the ground up might be called for, as well as some other ways of explaining what uvs are all about might help.