The Visible Object: Geometry, Materials, Textures, and Light

In Three.js, a visible mesh is not one indivisible thing. It is a transform-bearing object connected to reusable vertex data, appearance rules, image resources, and scene lighting. This chapter separates those responsibilities and then follows them into physically based materials and color management. The goal is practical: choose resources that fit the scene, reuse them without confusing ownership, and distinguish a lighting problem from a color-space or resolution problem.

A mesh is a relationship between state and resources

The object that appears in the scene is a Mesh. It has a position, orientation, and scale because it is an Object3D, but it also points to a geometry and a material. The geometry says what shape can be drawn. The material says how that shape should be shaded. The mesh says where this combination belongs in the scene.

That split is why three cubes can share one BoxGeometry. The geometry stores the vertices and other attributes for the box. Each mesh stores a different transform. If all three cubes should have the same appearance, they can share a material as well. If they should have different colors, the geometry can still be shared while each mesh points to a different material. The object-level state and the resource-level state answer different questions.

BufferGeometry is the data structure behind that shape. It can hold a position attribute, normals, colors, UV coordinates, morph attributes, and custom attributes. An index can let several triangles refer to the same vertex data instead of repeating it. UV coordinates connect points on a surface to positions in a texture. These are not just names for modeling concepts; they are the data that Three.js prepares for the renderer and, eventually, the GPU.

A geometry can also be divided into groups. Groups assign ranges of vertices or indices to material slots, allowing one geometry to be drawn with an array of materials. The convenience has a direct consequence: those groups are rendered in separate draw calls. A material array is therefore a useful representation of different surfaces, not a free way to avoid the cost of multiple material states.

The same boundary appears when geometry changes. A geometry’s attributes are buffers, and bounds may be computed for culling or raycasting. If an application changes vertex positions, it may need to update the relevant GPU data and recompute bounds. If the change is structural rather than a normal data update, creating a fresh geometry may be the safer path. Three.js provides the operations, but the application still knows whether the data is static, dynamic, shared, or obsolete.

The attributes also explain why a single visible shape can have several kinds of data. Positions locate vertices. Normals describe how surfaces face the light. UV coordinates map surface points into a texture. Vertex colors can vary appearance without creating a separate material for every object. Morph attributes provide alternate vertex data that can be blended for expressions or other controlled shape changes. An index connects triangles to shared vertices. None of these attributes is the surface appearance by itself; the material decides which inputs the shader uses and how it interprets them.

A cube with a different image on each face illustrates the boundary. The geometry can provide groups for the faces, while an array of materials supplies six texture references. That is easy to describe, but it creates multiple material states and draw work.