Indexed Buffer Geometry in threejs
The index property of a buffer geometry instance in threejs is a way to define an array of index values in a position attribute that will be used to draw triangles. Simply put it is a way to reuse points stored in the position attribute so that the over all length of the array in the position attribute is lower than it would otherwise have to be. The main reason why I might want to have a geometry indexed is to save memory when it comes to geometries with a lot of points in them. Also it would help to reduce the amount of overhead it would take to update geometry also a little as it is less points that have to be looped over in order to do so.
However there are also some draw backs with this as well that have to do with the state of the normal attribute the corresponds with the position attribute for example. Also because I am reusing points any kind of effect that has to do with exploding a geometry into a hole bunch of single triangles is not possible as the points are being reused. It is not so hard to convert an index geometry to a non indexed one though, doing so involves just calling the to non indexed method of the buffer geometry class. Things might be a little involved when it comes to the other way around though as it will involve creating a buffer attribute instance and using the set index method.
The Index property of buffer geometry and what to know first
The index property if an advanced topic in threejs that hs to do with how to go about reusing points that are defined I the position attribute of a buffer geometry. It should go without saying then that this is not a good starting point for people that are new to threejs, let alone JavaScript in general. I am the assuming that you know a thing or two about the basics of setting up a threejs project, as well a various other things that have to do with client side web development. I will not be getting into every little detail that you should know before hand at this point, but I do like to use these opening sections to write about a few things you might want to read up more on before continuing to read the rest of this post.
Read More on Buffer geometry in general
The index property if just one little feature of a buffer geometry object in threejs. There is a great deal more to be aware of when it comes to buffer geometry, so you might want to check out my main post on buffer geometry in general.
Source Code is up on Github
The examples that I write about in this post can also be found on my Github. This is also where I park the source code exmaples that I have made for my other blog posts on threejs as well.
Version Numbers Matter
When I first wrote this post I was using r146 pf threejs.
1 - Some Basic examples of Indexed Buffer Geometry in threejs
This first section will be just a few quick basic examples that have to do with the index property of a buffer geometry centered around examples that involve a very simple custom geometry. The index property has to do with reusing points in the position attribute so then these examples will involve a very simple custom geometry that is just two triangles. There is then just understanding the difference between using 6 points with no index, and 4 points with an index if two points are at the same location.
1.1 - The basic Index of an Indexed Geometry
For this example I am creating what might very well be the most basic form of an index geometry that is composed of just two triangles made from 4 points in the position attribute. I start out by making a new blank buffer geometry by calling the THREE.BufferGeometry with the new keyword and no arguments of any kind. The result is then a blank clean buffer geometry returned that I store to a variable. Now I can use the set attribute method of the buffer geometry class to set a position attribute. However first I will need an instance of THREE.BufferAttribute set up with the position data that I want to use to create my two triangles.
To create the position attribute I call the THREE.BufferAttribute constructor and pass a Float32Array that will contain the points in space that I want to use. Each three numbers in the array will be for x,y, and then z values for a single point in space. I want 4 points and 3 axis values for each point so that means the length of the array will be 12. After I pass the array as the first argument I will then pass the number 3 as the second argument as there are 3 values for each item in this buffer attribute. Now that I have the buffer attribute for the position attribute I can now call the set attribute method of that blank geometry, pass the string position as the first argument, and then the buffer attribute for position as the next.
So now I have a geometry with a position attribute, now I will want to add the index. For this it is more or less the same process as with making the custom position attribute. However I will be using a Uint8Array, and this time it is just 1 number per item as this is an array of index values for points in the position attribute that I just set. When I have my index buffer attribute instance I can then pass it as an argument when calling the set index method of the geometry.
One last thing with the geometry is that I will want to call the compute vertex normals method to quickly create a normals attribite for this geometry. The reason why is because I am going to use this geometry with a mesh object that will use the mesh normal material so I will need this.
|
|
1.2 - Indexed and non indexed geometry compared
When it comes to getting started with the index property of buffer geometry there is making a custom geometry that is just two triangles. With a non indexed geometry these two triangles will consist of 6 points in space stored in the position attribute, even if two of the points of each triangles are the same. When it comes to an indexed geometry however only 4 points can be defined in the position attribute, and then an index can be used to define 6 index values of points in the position attribute as a way to draw the triangles.
|
|
With both mesh objects In this example I am using the mesh normal material and if you look closely at the outcome of this you will notice that they both look a littler different. This is because for both geometries I am calling the compute vertex normals method to create the normal attribute of the geometries and with the indexed geometry the state of the normal attribute is not a typically desired outcome. This is because we have four points rather than sit which results in 4 normal vector rather than six, which in turn also effects the face normals sense vector normals are used to find that.
2 - The set index method
One thing that I learned is that it is a good idea to be aware of the limits of typed arrays when making a buffer attribute that is to be used with the set index method of the buffer geometry class. In the threejs docs it is shown that a buffer attribute needs to be passed as the first and only argument when calling the set index method. However the good news is that a plane old JavaScript array can be passed in place of a buffer attribute as well and it may be best to just simply do that actually.
If you do want to pass a buffer attribute when calling the set index method just keep in mind that there are limits to these various data types. The numbers should be integers however it might still be best to go with a float option actually sense some of them have higher max safe integer values than the integer options for typed arrays. This is very true with the Uint8Array typed array that is just a single byte of course, and as such it will limit the usable point count of the geometry to 256.
2.1 - Custom Plane Geometry based on THREE.PlaneGeometry that just passes an array when calling setIndex
If you are not sure what typed array you should use one option is to just pass a plane old javaScript array and let the set index method figure that out for you. I have found that this is something that is being done in the actual threejs source code when it comes to the plane geometry constructor function for example. Speaking of plane geometry the source code of the plane geometry is what this example is based off of with just a few very minor changes to create a helper funciton that creates and mutates a Buffer Geometry, rather than a Class that extends BufferGeometry.
|
|
Notice that when passing a plane javaScript array to the set index method it will choose a Uint16Array, or a Uint32Array depending on the count of points in the position attribute. So if the count of points is below the limit of Uint16Array it will use that else it will go higher to the Uint32Array. The limit of a Uint32Array is 4,294,967,295 which is a whole lot of points, and for the most part I do not think that is something that I will end up going over for any geometry at least in terms of typical, practical use anyway.
Still if for some weird reason I do need the next step up there is no Uint64Array but there is Float64Array that has a max safe integer value of 9,007,199,254,740,991. I would not just use that though, it might be best to use Uint32Array arrays if I want to make things static and explicit, or just pass a plane old javaScript array and be done with this.
3 - Animaiton loop exmaples
As always I like to work out at least one if not more animation loop example for my posts that help to give a better idea of what the subject of the post is all about. For the subject of indexed and none indexed geometry there is a whole lot of potential when it comes to this subject that involves changing the state of a position attribute over time for both and indexed and non indexed geometry to showcase a major difference between they two.
3.1 - Two Box Geometry based Mesh Objects
When using the THREE.BoxGeometry class to create a geometry it will have an index for it set up for me. If I want the box to not be indexed I can just call the to non index method to do so. For this example I create a geometry with the box geometry constructor function, and then another geometry that is just a clone of this. I then class the to non indexed method off of the clone of the box geometry to end up with an indexed and non indexed box geometry that I then use with two mesh objects. I then add both of these mesh objects to a group and loop over the children of the group in a main update method.
When updating the same points in each position attribute the result as one should expect is very different. When it comes to the non index geometry I can move a whole triangle by itself from the rest of the geometry, however of course when it comes to the indexed geometry there are shared points which effect the whole of the geometry.
|
|
3.2 - Two Plane Geometry Objects video1 example
Although the box geometry example shows what the deference is between index and non indexed geometry I have found that this example that involves the use of plane geometry does a better job of showing the difference. When it comes to the mesh that uses the geometry that does not have an index that breaks apart into a whole bunch of triangles, where the indexed geometry just turns into a bunch of peaks and values and starts to look like land.
|
|
Conclusion
In any case the index of a geometry is something to be aware of to say the least. There are situations in which I might want to create one in order to crunch down the size of the position array. Also there are situations in which I might want to create a non indexed geometry and recompute the normal and uv attributes for a geometry while I am at it.