Skip to content

CPU Renderer using rasterization to display 3D models

Notifications You must be signed in to change notification settings

remisansfamine/rasterizer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rasterization rendering

CPU Rendering library written in C++ 17 for C/C++ with a scene loader (using stb, TinyObjLoader, GLFW and ImGui) and a mathematics library.

/!\ CPU Software renderers are not performant nor efficient, it is not recommended to use them on a serious project. Use it at your own risk. /!\

Global summary

  1. Renderer
  2. Scene
  3. Global

Renderer

Description: CPU Renderer using rasterization to display 3D models in a color buffer (Use GLFW and ImGui)

Renderer table of Contents

  1. Features
  2. Usage
  3. How does it work ?
  4. Exemples
  5. References
  6. Know bugs

Features

  • Draw triangles on the input color buffer using input vertices
  • Triangle wireframe
  • Triangle rasterization
  • Depth test using the input depth buffer
  • Triangle homogeneous clipping
  • Texture support (+ bilinear filtering)
  • Material support (ambient, diffuse, specular and emission)
  • Lighting support using Gouraud and Phong models (ambient, diffuse, specular and attenuation)
  • Blending support (+ texture with transparence and cutout)
  • Gamma correction
  • Post-process effect (Box blur, Gaussian blur, Light bloom, MSAA)

Usage

Initialization

rdrImpl* rdrInit(float* colorBuffer, float* depthBuffer, int width, int height)

Set rendering parameters

void rdrSetUniformFloatV(rdrImpl* renderer, rdrUniformType type, float* value)
void rdrSetUniformBool(rdrImpl* renderer, rdrUniformType type, bool value)

void rdrSetModel(rdrImpl* renderer, float* modelMatrix)
void rdrSetView(rdrImpl* renderer, float* viewMatrix)
void rdrSetProjection(rdrImpl* renderer, float* projectionMatrix)

void rdrSetTexture(rdrImpl* renderer, float* colors32Bits, int width, int height)
void rdrSetUniformMaterial(rdrImpl* renderer, rdrMaterial* material)
void rdrSetUniformLight(rdrImpl* renderer, int index, rdrLight* light)

Call post-process effects

void rdrFinish(rdrImpl* renderer)

Shutdown

void rdrShutdown(rdrImpl* renderer)

How does it work ?

Summary

  1. Vertex shader
  2. Clipping
  3. Normalized Device Coordinates
  4. Screen coordinates
  5. Rasterization
  6. Pixel shader
  7. Blending
  8. Post-process

Vertex shader

First of all, the renderer takes the input vertices and applies the vertex shader to them. The vertex shader computes the clip coordinates (which are homogeneous) with the Model-View-Projection matrix (it also applies the Model matrix to the normals and the local coordinates). It saves the vertex informations (like colors and UVs) in a varying (for each vertex) for further operations and to calculate lighting.

Outcodes and outpoints computing - Clipping

After that the pipeline calls two functions to check if the current triangle needs to be clipped, and how to clip it. If it should be clipped, it adds new triangles to rasterize with new varyings (for each vertex).

Normalized Device Coordinates

Then the renderer divides the homogeneous coordinates by their w component to get NDC coordinates (which are between -1 and 1). With these coordinates the renderer can ignore some faces (like back faces) using the normal of the triangle's face.

Screen coordinates

NDC coordinates are then remapped with the viewport definition, and the varyings are interpolated with the weights of clipped coordinates. With these remapped coordinates and the interpolated varyings the main function can finally rasterize the clipped triangles (or draw them as lines with the Wireframe mode).

Rasterization

At the start of this step the bounding box of the current triangle is calculated. For each of its pixel, his weight is computed to check if it is in the triangle or not (If MSAA is enabled, this step uses the samples of the pixel instead of using the pixel centroid). After passing this test, the depth test should be passed, it checks if there is already a pixel drawn in the color buffer at his position and if his depth is greater than its own. Then the perspective correction is occurred to avoid PS1 graphics-like and get correct weights to interpolate varyings.

Pixel shader

After getting the interpolated varying, the fragment shader is called. The fragment shader calculates the pixel color using the differents values of the inputs. The lighting can be calculated here (If the Phong model is enabled, else it is calculated during Vertex shader). If the current triangle is textured, the fragment shader gets the appropriate color using the UVs (It can also filter the texture using bilinear interpolation). The fragment shader can also discard pixels depending on its settings.

Blending

After getting the fragment color, the blending can be applied with the old color of the pixel to have a transparency effect. Then the alpha test should be passed to write in the depth buffer (to avoid non-transparent faces due to the rendering order of the vertices). These two steps are applied to each sample of the current pixel if the MSAA is enabled. After these steps the buffers (color buffer or MSAA color buffer) can be filled with the blended color.

Post-process

The final step is to apply effects on the frame buffer after getting all pixels (or of the samples) color, by traversing all the pixels of the frame buffer. If the MSAA is enabled, no pixel has a color, this color is obtained by calculating the average color of all samples of the current pixel. Then other effects can be applied like Box blur, Gaussian blur or Bloom. These effects are applied by obtaining the average of pixels around the current one with some factors. At the very end, the frame buffer is traversed once more to apply the gamma correction.

Exemples

Diffuse

Colored diffuse using Phong shading.

Specular

Lighting using Gouraud and Phong shading: diffuse, specular, emissive and attenuation.

Blending

Depth test and blending using a transparent texture (this scene uses Gouraud shading with a red light, a blue and another green and a gray ambient visible on the window).

Face culling

Configurable face culling and perspective correction.

Bilinear Nearest

10x10 Texture with a bilinear filter - The same texture without any filter.

Post-process

Post-processing effects: MSAA, Box blur and Gaussian blur.

References

Viewport:

Rasterization:

Perspective correction:

Face culling:

Lighting:

Blending:

Clipping:

Bilinear filtering:

Post-process effects:

MSAA:

Known bugs

  • Sometimes lines may appear on some models, for example if the camera is in (0;0;0) and it does not move.
  • On small resolutions the accuracy of the rasterization is not perfect.
  • Models with incorrect information can cause crashes.
  • MSAA can prevent cutout (alpha test) from working if blending is enabled.
  • Phong shading can be more performant than Gouraud shading.
  • Box blur, Gaussian blur and Light bloom may not work properly.

Scene

Description: Default scene program to load .obj, textures and materials editable via ImGui, and then display them using the rendering software. (Use stb, TinyObjLoader and ImGui)

  1. Features
  2. Usage
  3. Data format

Features

  • Load .obj (support textures and materials), quads and triangles with TinyObjLoader
  • Load textures
  • Load materials
  • Sort models with their transform using
  • Fully editable lights, materials and objects from ImGui window
  • Manage the function calls to the renderer

Usage

Controls

  • WASD/ZQSD/Array keys: Move foward, backward, left and right.
  • Space: Move upward.
  • Left Shit: Move downward.
  • NUMPAD+/NUMPAD-: Change the velocity of the camera.

Initialization

scnImpl* scnCreate()

Update

void scnUpdate(scnImpl* scene, float deltaTime, rdrImpl* renderer)

(To sort models)
void scnSetCameraPosition(scnImpl* scene, float* cameraPos)

Shutdown

void scnDestroy(scnImpl* scene)

Load resources

void loadTriangle(...)
void loadQuad(...)
bool loadObject(...)
int  loadMaterial(...)
int  loadTexture(...)
void loadTriangle(...)

Data format

Mesh

vector of Triangles | List of triangles that all have the same texture and material
int textureIndex    | Index of the current texture
int materialIndex   | Index of the current material

Object

bool enable         | Current state
vector of Mesh      | List of mesh that all have the same transform
3 floats -> x, y, z | Position
3 floats -> x, y, z | Rotation
3 floats -> x, y, z | Scale

Shared informations (Renderer and scene)

Third-party programs and libraries

stb

https://github.com/nothings/stb

TinyObjLoader

https://github.com/tinyobjloader/tinyobjloader

GLFW

https://www.glfw.org/

Glad

https://glad.dav1d.de/

ImGui

https://github.com/ocornut/imgui

msf_gif

https://github.com/notnullnotvoid/msf_gif

Data format

Vertex

3 floats -> x, y, z    | Position
3 floats -> nx, ny, nz | Normal
4 floats -> r, g, b, a | Color
2 floats -> u, v       | Texture coordinates

Light

  bool   enabled                | Current state
4 floats position -> x, y, z, w | Position and light type (w)
4 floats ambient  -> r, g, b, a | Ambient color
4 floats diffuse  -> r, g, b, a | Diffuse color
4 floats specular -> r, g, b, a | Specular colors
3 floats attenuation -> c, l, q | Constant, linear and quadratic attenuations

Material

4 floats ambientColor  -> r, g, b, a | Ambient color
4 floats diffuseColor  -> r, g, b, a | Diffuse color
4 floats specularColor -> r, g, b, a | Specular colors
4 floats emissionColor -> r, g, b, a | Emission colors
  float shininess                    | Shininess exponent

Texture

2 ints -> w, h         | Texture size
4 floats -> r, g, b, a | Texture datas

(Only for the scene)
string -> filepath     | Texture file path