Choose the right type
CGP often offers several close types for the same idea: five ways to store a transform, four containers, seven drawables, five camera controllers. Each page of the reference describes one type; this page compares them, need by need. For each need, the first line of the table is the usual choice of the course.
Transform an object
| Need | Type | Limits |
|---|---|---|
| Place a shape in the scene | affine, the type of mesh_drawable::model |
Not invertible (per-axis scaling) |
| Place an element of a hierarchy | affine_rts, the type of hierarchy_mesh_drawable_node::transform_local |
Uniform scaling only |
| Move a rigid object (no scaling) | affine_rt |
No scaling |
| Rotate only | rotation_transform |
No translation |
| Describe a place: a camera, a joint, a local basis | frame |
Same data as affine_rt, read as axes |
| Send a transform to a shader, or a projection | mat4 |
Hard to read and to animate |
Rules of thumb:
- Fill the fields, not a matrix.
model.translation,model.rotation,model.scalingandmodel.scaling_xyzare what you change indisplay_frame(); themat4is built bydrawwhen it sends the uniform. Amat4written by hand is rarely needed outside a shader. - Need the inverse?
affinehas none, because ofscaling_xyz. Keep the transforms you invert (a camera, a joint) inaffine_rts,affine_rt,frameorrotation_transform, which all haveinverse. - Animate a rotation with
rotation_transform::lerprather than by interpolating angles: it follows the shortest path between the two orientations.
// a shape turning on itself while it moves (affine: the model of a mesh_drawable) shape.model.translation = {std::cos(t), std::sin(t), 0}; shape.model.rotation = rotation_transform::from_axis_angle({0,0,1}, t); shape.model.scaling_xyz = {1, 1, 2}; // twice as tall along its own z axis
What each type stores, what T * p computes and the order of products:
Rotations, frames and affine transforms.
Store data
| Need | Type | Limits |
|---|---|---|
| A point, a direction, a color | vec3 (also vec2, vec4) |
Size fixed at compile time |
| A list of values whose size changes: positions, velocities, particles | numarray |
1D only |
| Values indexed by (i, j): a height field, the vertices of a grid surface | grid_2D |
Fixed shape once resized |
| Values indexed by (i, j, k): a scalar field for marching cubes | grid_3D |
Memory grows as N^3 |
| An image read from or written to a file | image_structure |
Bytes (unsigned char), not floats |
Rules of thumb:
vec3is anumarray_stack<float,3>: no allocation, and the functions of vecN (norm,dot,normalize...) work on any size. Use it for one value, andnumarray<vec3>for many:mesh::positionis anumarray<vec3>.- A
grid_2Dis anumarrayplus adimension: element (i, j) is at offseti + dimension.x * jofdata(index_to_offset). Prefer it to anumarraywith hand-written offsets, which is where indexing errors come from. - To compute on an image, convert it:
image_structureholds 8-bit channels, so agrid_2D<vec3>of floats is easier to process. Both can become a texture (initialize_texture_2d_on_gpuaccepts either).
// heights on a 100 x 100 grid, then the matching vertex positions grid_2D<float> height; height.resize(100, 100); numarray<vec3> position; for (int j = 0; j < 100; ++j) for (int i = 0; i < 100; ++i) position.push_back({i / 99.0f, j / 99.0f, height(i, j)});
Display a shape
| Need | Type | Limits |
|---|---|---|
| A surface: a primitive, an OBJ file, a mesh built by hand | mesh_drawable, from a mesh |
Number of vertices and triangles fixed after initialize_data_on_gpu |
| Triangles listed three points at a time, with one normal per face | triangles_drawable |
No shared vertices |
| A polyline or a set of segments | curve_drawable |
Fixed number of points |
| A curve that grows while the program runs (sketching) | curve_drawable_dynamic_extend |
Points are only added |
| The trail of a moving point | trajectory_drawable |
Keeps the last N positions |
| An articulated object: a character, a robot arm | hierarchy_mesh_drawable |
Built from several mesh_drawable |
| A background around the camera | skybox_drawable |
Cube map only |
| Many copies of one shape | mesh_drawable with per-instance buffers |
Needs a custom shader |
Rules of thumb:
- CPU, then GPU. Build or modify a
mesh(on the CPU), then copy it once withinitialize_data_on_gpu. At each frame, change onlymodelandmaterial; to move the vertices themselves, update the buffers (vbo_position.update). See From CPU to GPU. mesh_drawableortriangles_drawable? In amesh, triangles can share a vertex, andnormal_updateaverages the normals of the triangles around it: the surface looks smooth.triangles_drawabletakes every triangle on its own and, without normals, computes one normal per face: the facets stay visible.- One object, many copies. For a few copies, draw the same
mesh_drawableseveral times with a differentmodel. For thousands, use instancing: Draw many instances.
// one drawable, drawn three times at three places for (int k = 0; k < 3; ++k) { sphere.model.translation = {2.0f * k, 0, 0}; draw(sphere, environment); }
Move the camera
The camera of the scene template is split into three parts: a model (where the camera is), a controller (how the mouse and the keyboard move the model), and a projection (how the 3D scene becomes an image).
| Need | Controller (model) | Limits |
|---|---|---|
| Look at an object from all sides, keep the horizon level | camera_controller_orbit_euler (camera_orbit_euler), default of the template |
Keeps an up axis |
| Turn freely around an object | camera_controller_orbit (camera_orbit) |
The horizon tilts |
| Walk in a scene | camera_controller_first_person_euler (camera_first_person_euler) |
Keeps an up axis |
| Look in any direction from a point | camera_controller_first_person (camera_first_person) |
The horizon tilts |
| Fly like a plane | camera_controller_fly_mode (camera_first_person) |
Always moves forward |
| Need | Projection |
|---|---|
| Perspective view (the default) | camera_projection_perspective |
| Parallel view, no perspective: a 2D scene, a technical drawing | camera_projection_orthographic |
| A projection matrix for a shader only (the light of a shadow map) | the functions projection_perspective and projection_orthographic, which return a mat4 |
Rules of thumb:
- Changing the controller is a change of type of
camera_controlinsrc/scene.hpp, plus its initialization (look_at, rotation axis): see Switching controller. - The view matrix always comes from
camera_control.camera().matrix_view(), and the projection matrix fromcamera_projection.matrix(): the shaders do not depend on the controller. See Camera and matrices.
See also
- Glossary: short definitions of the terms used on this page.
- Create basic shapes: the primitives that return a
mesh. - Hierarchies of shapes: local and global transforms of an articulated object.