How to
Display a mesh
-
1/ Create a persistent variable (e.g. a variable of the class
scene_structure
) of typemesh_drawable
File
scene.hpp
scene_structure { // ... mesh_drawable my_shape_drawable; }
-
2/ Create a mesh at initialization stage with the expected structure. Then initialize the
mesh_drawable
with the data from themesh
.
File
scene.cpp
, function initialize()
void scene_structure::initialize() { // ... // Create a mesh mesh my_shape = mesh_primitive_sphere(); // Initialize the mesh_drawable using the created mesh my_shape_drawable.initialize_data_on_gpu(my_shape); // Note: // - 'my_shape' is a temporary variable, it will be removed from the memory at the end of the initialize() function // - 'my_shape_drawable' is a variable of the class, it is persistent and can be used in the other methods of the class. }
-
3/ Call the
draw()
function on themesh_drawable
in the animation loop
File
scene.cpp
, function display_frame()
void scene_structure::display_frame() { // ... // Create a mesh draw(my_shape_drawable, environment); }
Apply a transformation to a mesh
Possibility 1: On the GPU via uniform parameter
The first possibility is to use the default uniform parameters[mesh_drawable].model
associated to the mesh_drawable
structure to apply a transformation at the drawing stage.
Example:
my_shape_drawable.model.translation = {1,0,0}; my_shape_drawable.model.rotation = rotation_axis_angle({0,1,0}, Pi/4.0f); my_shape_drawable.model.scale = 2.0f;
[mesh_drawable].model
is a structure of type affine
allowing to set a value for a translation (vec3), a rotation (rotation_transform), and a homogeneous scaling (float).
Principle
my_shape_drawable.model
is a simple variable storing a value for translation/rotation/scaling. It is conceptually similar to writing in a \(4 \times 4\) matrix in a more intuitive way.
When the function draw(my_shape_drawable, ...)
is called, the parameter [mesh_drawable].model
is used to generate a mat4
transformation which is sent as uniform parameter in the shader. Common shaders receives this parameters as uniform mat4 model;
and apply the model matrix to all vertex positions.
Common use in the default shaders:
uniform mat4 model; // ... void main(){ // The position of the vertex in the world space vec4 position = model * vec4(vertex_position, 1.0); // ... }
Concept and Usage
Applying a transformation usingmy_shape_drawable.model
is extremely efficient.
-
- Changing the value of the
model
variable consists in writing a value in a variable. No loop over the vertices is performed. - - All the computation of the transformation is performed by the GPU at the drawing stage.
-
- Beware that the line
my_shape_drawable.model.translation = xxx;
does not apply an actual process of translation on the vertices, it simply write a value in a variable. The actual translation is performed by the shader when calling thedraw()
function using the current value of the translation. -
- Every
mesh_drawable
are associated to a model variable. By default the translation is set to zero, saling to 1, and rotation to the identity. - - If you re-write on the variable along two consecutive lines, only the last value will be used. If you want to apply a combination of transformation, you need to express it on the variable itself.
my_shape_drawable.model.translation = {1,0,0}; my_shape_drawable.model.translation = {0,0,1}; // rewrite on a variable draw(my_shape_drawable, environment);
my_shape_drawable.model.translation = vec3{1,0,0} + vec3{0,0,1}; // Or alternatively my_shape_drawable.model.translation = {1,0,0}; my_shape_drawable.model.translation += {0,0,1}; // add a value to the variable draw(my_shape_drawable, environment);
my_shape_drawable.model.rotation = rotation_1 * rotation_2; my_shape_drawable.model.scaling = scale_1 + scale_2; draw(my_shape_drawable, environment);
Advanced:
For advanced use requiering more degrees of freedom than standard translation/rotation/scaling, a generalmat4
structure is also provided as [mesh_drawable].supplementary_model_matrix
.
A mat4
structure allows more general transformation than a affine_rts
, but is less intuitive to use.
The final transformation sent to the shader is the matrix resulting from the product:
mat4 M = hierarchy_transform_model.matrix() * supplementary_model_matrix * model.matrix() // model: the default way to apply a transformation on a mesh_drawable // supplementary_model_matrix: the generic matrix allowing additional degrees of freedom. // hierarchy_transform_model: matrix dedicated to handle hierarchical transforms.
Possibility 2: On the CPU, writing new positions
The second possibility is to directly modify the vertices of themesh
structure, on the CPU memory, before converting it to a mesh_drawable
.
Example in the initialize()
function:
for(int k=0; k<my_mesh.position.size(); ++k) { my_mesh.position[k] = //... any value or transformation } my_mesh_drawable.initialize_data_on_gpu(camel_mesh);
Concept and Usage
This method is usefull when the transformation is not a simple translation/rotation/scaling, but a more complex process that cannot be expressed as a combination of the standard transformation. Although very generic, this approach is more costly than the use of themodel
variable. It requires to loop over all the vertices of the mesh and to perform the transformation on the CPU. This approach can be used at the initialization stage, but should be not be applied in the animation loop ([mesh_drawable].initialize_data_on_gpu
should not be called in the animation loop).
Deform vertices of a mesh
Compute the normals
On a mesh structure
Themesh
structure stores the set per-vertex normal in the variable [mesh].normal
as numarray
.
mesh
provides the function
[mesh].normal_update();
Update on a mesh_drawable
When the normals of amesh_drawable
need to be updated during the animation loop, the function [mesh_drawable].camel.vbo_normal.update([numarray])
can be called.
Example:
void scene_structure::display_frame() { // Update some normals // my_mesh.normal = ... // Then update the normals of the mesh_drawable my_mesh_drawable.vbo_normal.update(my_mesh.normal); }
Set a texture
Load a 2D texture from a file in "assets/my_texture.jpg", and assign it to a mesh_drawable:[mesh_drawable].texture.load_and_initialize_texture_2d_on_gpu(project::path+"assets/my_texture.jpg");
Add an additional texture
Load an additional texture to be used in the shader under the uniform name "texture_2":[mesh_drawable].supplementary_texture["texture_2"].load_and_initialize_texture_2d_on_gpu(project::path+"assets/your_texture.jpg");
Use instancing
Calling the functiondraw([mesh_drawable], environment, [int] N_instance);
mesh_drawable
.
Remark:
This call is conceptually similar to the following code, but is much more efficient in avoiding the sequential draw calls.
Additional links
for(int gl_InstanceID=0; gl_InstanceID<N_instance; ++gl_InstanceID) draw([mesh_drawable], environment);
- - See examples Instancing Basic, Instancing Procedural
- - OpenGL technical description
- - Tutorial on instancing with OpenGL
Controlling instancing
The different instances can be controled within the shader via the built-in GLSL variablegl_InstanceID
.
Additional per-instance data can be transfert from the C++ program to the GPU using the supplementary VBO data:
[mesh_drawable].initialize_supplementary_data_on_gpu([numarray<vec2/3/4>] data, /*location*/ 4, /*divisor*/ 1);
// Standard inputs // layout (location = 0) in vec3 vertex_position; // layout (location = 1) in vec3 vertex_normal; // layout (location = 2) in vec3 vertex_color; // layout (location = 3) in vec2 vertex_uv; // Dedicated input for instanced data layout (location = 4) in vec2/3/4 instanced_data;
-
- The
numarray
data can store any type of value used in the shader. The size of the array must be, at least,N_instance
. -
- The second parameter
location
is the index identifier of the corresponding variable.
-
- The location starts typically at 4 (after the standard position, normal, color, uv), and continue with 5, 6, etc.
-
- The third parameter
divisor
should be 1 when the entries of the numarray are spread to all instances.
-
- - divisor = 0: is used when the array entries varies for each vertex (default value).
- - divisor = 1: is used when the array entries varies for each isntance.
Activate transparency
Semi-transparency
Use the following code structure to activate color blending for semi-transparent objetcs:void scene_structure::display_frame() { /** 1) Display first all non transparent objects: **/ // draw(my_shapes, ...); // ... // /** 2) Display at the end semi-transparent objects **/ // Color blending activation glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Depth buffer writing de-activation glDepthMask(false); // Optional: Set the global transparency value on the shape (applies on top of its color and texture) // transparent_shape.material.alpha = 0.5f; //0:fully transparent, 1:fully opaque // Draw transparent shape draw(transparent_shape, environment); // Re-activate standard drawing parameters glDepthMask(true); glDisable(GL_BLEND); }
Impostor effect
Impostor/Billboards with pure opaque and pure transparent textures can be handled in the shader using thediscard
call.
See the example Transparent Billboards
Load a mesh from a file
A simple defaultobj
file loader is proposed within CGP with the following syntax
Example in the case of a mesh in 'assets/my_mesh.obj'
mesh my_mesh = mesh_load_file_obj(project::path + "assets/my_mesh.obj");
-
- The loader only reads the
.obj
file, not the.mtl
. Materials and textures need to be set manually. - - Multiple objects are merged into a single mesh.
Advanced loader
For the cases where multiple objects parts with different textures are in the same file, an advanced loader is also available using the following syntax: Example with a file inassets/directory/filename.obj
and its associated .mtl
file:
std::vector<mesh_obj_advanced_loader::shape_element_node> struct_shape = mesh_load_file_obj_advanced(project::path + "assets/directory/", "filename.obj"); std::vector<cgp::mesh_drawable> shapes_drawable = mesh_obj_advanced_loader::convert_to_mesh_drawable(struct_shape);
20_format_parser/mesh_loader/obj_advanced/obj_advanced.hpp
Note: The advanced loader uses the external library tinyobjloader
See example Advanced OBJ Loader