Book Image

3D Graphics Rendering Cookbook

By : Sergey Kosarevsky, Viktor Latypov
4 (2)
Book Image

3D Graphics Rendering Cookbook

4 (2)
By: Sergey Kosarevsky, Viktor Latypov

Overview of this book

OpenGL is a popular cross-language, cross-platform application programming interface (API) used for rendering 2D and 3D graphics, while Vulkan is a low-overhead, cross-platform 3D graphics API that targets high-performance applications. 3D Graphics Rendering Cookbook helps you learn about modern graphics rendering algorithms and techniques using C++ programming along with OpenGL and Vulkan APIs. The book begins by setting up a development environment and takes you through the steps involved in building a 3D rendering engine with the help of basic, yet self-contained, recipes. Each recipe will enable you to incrementally add features to your codebase and show you how to integrate different 3D rendering techniques and algorithms into one large project. You'll also get to grips with core techniques such as physically based rendering, image-based rendering, and CPU/GPU geometry culling, to name a few. As you advance, you'll explore common techniques and solutions that will help you to work with large datasets for 2D and 3D rendering. Finally, you'll discover how to apply optimization techniques to build performant and feature-rich graphics applications. By the end of this 3D rendering book, you'll have gained an improved understanding of best practices used in modern graphics APIs and be able to create fast and versatile 3D rendering frameworks.
Table of Contents (12 chapters)

Using the Assimp library

Open Asset Import Library, which can be shortened to Assimp, is a portable open source C++ library that can be used to load various popular 3D model formats in a uniform manner.

Getting ready

We will use Assimp version 5.0 for this recipe. Here is the Bootstrap JSON snippet that you can use to download it:

{
  "name": "assimp",
  "source": {
    "type": "git",
    "url": "https://github.com/assimp/assimp.git",
    "revision": "a9f82dbe0b8a658003f93c7b5108ee4521458a18"
  }
}

Before we can link to Assimp, let's disable the unnecessary functionality in CMakeLists.txt. We will only be using the .obj and .gltf 3D format importers throughout this book:

set(ASSIMP_NO_EXPORT ON CACHE BOOL "")
set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "")
set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "")
set(ASSIMP_INSTALL_PDB OFF CACHE BOOL "")
set(  ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE BOOL "")
set(ASSIMP_BUILD_OBJ_IMPORTER ON CACHE BOOL "")
set(ASSIMP_BUILD_GLTF_IMPORTER ON CACHE BOOL "")

The full source code can be found in Chapter2/07_Assimp.

How to do it...

Let's load a 3D model from a .glft2 file via Assimp. The simplest code to do this will look like this:

  1. First, we request the library to convert any geometric primitives it might encounter into triangles:
    const aiScene* scene = aiImportFile(  "data/rubber_duck/scene.gltf",  aiProcess_Triangulate);
  2. Additionally, we do some basic error checking, as follows:
    if ( !scene || !scene->HasMeshes() ) {
      printf("Unable to load file\n");
      exit( 255 );
    }
  3. Now we can convert the loaded 3D scene into a data format that we can use to upload the model into OpenGL. For this recipe, we will only use vertex positions in vec3 format without indices:
    std::vector<vec3> positions;
    const aiMesh* mesh = scene->mMeshes[0];
    for (unsigned int i = 0; i != mesh->mNumFaces; i++) {
      const aiFace& face = mesh->mFaces[i];
      const unsigned int idx[3] = { face.mIndices[0],    face.mIndices[1], face.mIndices[2] };
  4. To keep this example as simple as possible, we can flatten all of the indices and store only the vertex positions. Swap the y and z coordinates to orient the model:
      for (int j = 0; j != 3; j++) {
         const aiVector3D v = mesh->mVertices[idx[j]];
         positions.push_back( vec3(v.x, v.z, v.y) );
      }
    }
  5. Now we can deallocate the scene pointer with aiReleaseImport(scene) and upload the content of positions[] into an OpenGL buffer:
    GLuint VAO;
    glCreateVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    GLuint meshData;
    glCreateBuffers(1, &meshData);
    glNamedBufferStorage(meshData,  sizeof(vec3) * positions.size(),  positions.data(), 0);
    glVertexArrayVertexBuffer(  VAO, 0, meshData, 0, sizeof(vec3) );
    glEnableVertexArrayAttrib(VAO, 0 );
    glVertexArrayAttribFormat(  VAO, 0, 3, GL_FLOAT, GL_FALSE, 0);
    glVertexArrayAttribBinding(VAO, 0, 0);
  6. Save the number of vertices to be used by glDrawArrays() in the main loop and render the 3D model:
    const int numVertices =  static_cast<int>(positions.size());

Here, we use the same two-pass technique from the Doing math with GLM recipe to render a wireframe 3D model on top of a solid image:

while ( !glfwWindowShouldClose(window) ) {
  ...
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  glDrawArrays(GL_TRIANGLES, 0, numVertices);
  ...
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  glDrawArrays(GL_TRIANGLES, 0, numVertices);
  glfwSwapBuffers(window);
  glfwPollEvents(); 
}

The output graphics should look similar to the following screenshot:

Figure 2.6 – A wireframe rubber duck

Figure 2.6 – A wireframe rubber duck