Cube
This post is part of a course on geometric modeling at the Summer Liberal Arts Institute for Computer Science held at Carleton College in 2021.
In this exercise, you will break into the third dimension and model a cube. We start on paper, progress to a disappointing rendering, and iterate toward a satisfactory rendering with 3D shading.
Drawing
As always, we start our thinking on paper in order to get ideas out in front of us. Follow these steps to lay out the groundwork for our cube.
- Draw a cube on paper.
- Pretend that the origin (0, 0, 0) is right in the middle of the cube.
- Pretend that the nearest face of the cube is on the z = 1 plane and that the farthest face is on z = -1. In computer graphics, a common convention is to have the z-axis point out of the screen at the viewer. This may feel strange to you.
- Pretend that the right face of the cube is on the x = 1 plane and the left is on x = -1.
- Pretend that the top face of the cube is on the y = 1 plane and the bottom is on y = -1.
- Label each vertex with its xyz-coordinates.
- Label each vertex with the index that you want it to have in the
positions
array. Since there are eight vertices, the indices run from 0 through 7. I recommend that you follow the labeling scheme shown on this reference cube: Then you can use this reference to figure out the triangles later on.
Keep your drawing nearby and modify it as you find ways to improve upon your initial sketch.
Render
Turn your drawing into an interactive rendering by completing the following steps:
- Define a function in
shapes.js
namedgenerateCube
. It will have the same general structure asgenerateSquareDonut
. - In the function, create a
positions
array that contains the xyz-coordinates of the eight vertices of the cube. Order the vertices according to the indices you assigned. - Create a
triangles
array that stitches the array vertices together to form the faces of the cube. Each of the six faces is composed of two triangles, and each triangle is three indices. How many total indices are in the list? - Return the two arrays in a single object.
- Call
generateCube
inrender.js
. - Render the cube as points.
- Switch to rendering the cube as a solid mesh.
In your browser you should see a cube that you can rotate and zoom. Discerning the faces is difficult, because there is no shading. All the pixels of the cube are assigned the color that you set in the material.
Lighting
To properly see 3D geometry, you need to add lighting and use a material that supports lighting. Follow these steps to shade the cube’s pixels based on a light source.
- In
render.js
, change theMeshBasicMaterial
toMeshPhongMaterial
, which is named after Bui Tuong Phong, the inventor of the shading algorithm you’re going to use. - Add a new property to the material to set the shininess of the surface: The higher the shininess value, the more concentrated the specular highlights will be. A mirror, for example, has a very high shininess value.
const material = new THREE.MeshPhongMaterial({ color: 'orange', shininess: 90, });
- Add a light source. THREE.js provides many different kinds of light sources. We start with the one that uses the fewest parameters:
DirectionalLight
. A directional light is used to model light that comes from very far away, much like sunlight. Anchor the light to the camera so that it doesn’t rotate with the scene:Since the light is anchored to the camera, the positioning is relative to the camera. This means that the light is 2 units to the right of the camera, 2 units above, and 1 unit behind.const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(2, 2, 1); camera.add(light); scene.add(camera);
When you reload the scene, you should see a black cube. The shading isn’t working yet.
Normals
Besides adding a light source, you also need to provide information about the orientation of the surface around each vertex. In particular, you need to provide a normal for each vertex. A normal is a direction that is perpendicular to a surface. A normal that aligns with the direction to the light source means the surface is fully illuminated. As the surface tilts away, it receives less light. Follow these steps to add normals to your cube:
- Paste this line in the
head
element inindex.html
:<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/helpers/VertexNormalsHelper.js"></script>
- In
render.js
, add this line of code after you’ve fully configuredgeometry
:geometry.computeVertexNormals();
- When you reload, the cube appears shaded, but the shading doesn’t seem right. Display the normals to understand the problem: Place this code somewhere after you’ve created
const normals = new THREE.VertexNormalsHelper(mesh, 0.5, 0x00ff00, 1); scene.add(normals);
mesh
. See how the normals point out from the corners of the cube. Normals are supposed to be perpendicular to the surface, but these normals aren’t perpendicular. - The issue with the normals is that they are averages of the normals of the faces surrounding each corner. That averaging isn’t appropriate for sharp bends. Instead, you want the faces to be treated separately. Call this method to dissociate the faces after configuring the geometry but before computing the vertex normals: After reloading, you should see three normals at each vertex and they should appear perpendicular to the faces. The lighting should also feel more “normal.”
geometry = geometry.toNonIndexed();