A software-rasterizer in C++, that I wrote to get a basic understanding of the OpenGL graphics pipeline.
l.t.r. "Sad toaster" by tasha.lime, "Coatlicue" by aptass, "Gameboy Cartridge" by Karolina D, "Demon Skull" by Auxiar Molkhun
- C++ implementation
- generic vertex and fragment attributes
- programmable vertex and fragment shader (functors)
- generic framebuffer targets (for "offscreen" rendering)
- small math library (2D, 3D, 4D vectors and 2x2, 3x3, 4x4 matrices)
- .obj and .mat loading
- GLFW/OpenGL viewer (uploads framebuffer each frame)
- Rasterizer
- perspective-correct attribute interpolation
- z-buffering
- texture sampler filter (nearest, linear)
- texture sampler wrapping (repeat, edge)
- face culling
- custom framebuffer
- line rendering (wireframe rendering)
- mip map generation
- mip map level computation
- anisotropic filtering
- cubemap
- Examples
- minimal examples
- colored triangle
- index buffer
- texture and sampler
- custom framebuffer
- glfw/gl viewer
- model loading
- Blinn-Phong illumination
- Cel Shading with depth buffer edge detection
- Normal Mapping
- Shadow Mapping
- Screen Space Ambient Occlusion
- Physically-based rendering + offline pre-integration
- minimal examples
Similar to traditional Graphic APIs we define the input and output of the shader stages, i.e. the data passing through the rasterization pipeline.
All members from Varying
contained in the VARYING(...)
macro are interpolated per fragment by the rasterizer.
Note, that it is mandatory to have a Vec4 position
interpolated attribute.
/* vertex data -> input to draw call (via Buffer) */
struct Vertex
{
Vec3 pos;
Vec3 color;
};
/*
* Output of vertex stage, Input to fragment stage
* -> position is mandatory
* -> members to interpolate are declared by VARYING macro (member need scalar multiplication, and addition)
*/
struct Varying
{
Vec4 position;
Vec3 color;
VARYING(position, color);
};
/* uniform struct accessable from both "shaders" */
struct Uniforms {};
Vertex and fragment shaders are written as function objects which need to be set accordingly with onVertex
and onFragment
(currently no default shader).
Program<Vertex, Varying, Uniforms> program;
program.onVertex([](const Uniforms& uniform, const Vertex& in, Varying& out)
{
out.position = Vec4(in.pos, 1.0f);
out.color = in.color;
});
program.onFragment([](const Uniforms& uniform, const Varying& in, Vec4& out)
{
out = Vec4(in.color, 1.0f);
});
Mesh data is provided to the renderer with a Buffer
object.
Buffer<Vertex> buffer;
buffer.primitive = ePrimitive::TRIANGLES;
buffer.vertices = { { {-0.5, -0.5, 0.5}, {1.0, 0.0, 0.0} },
{ { 0.5, -0.5, 0.5}, {0.0, 1.0, 0.0} },
{ { 0.0, 0.5, 0.5}, {0.0, 0.0, 1.0} } };
An instance of Renderer
contains a default framebuffer with a color and depth target.
After clearing the framebuffer, a draw call can be submitted with the previously defined shader program and vertex buffer.
/* rasterizer with framebuffer size */
Renderer rasterizer(480, 480);
/* clear framebuffer */
rasterizer.framebuffer().clear(Vec4(0, 0, 0, 1));
/* submit draw call */
rasterizer.draw(program, buffer);
/* save framebuffer as .png */
rasterizer.framebuffer().color().save("00_triangle.png");
Model Loading and Texture Mapping ("Sad toaster" Link by tasha.lime.)
Blinn-Phong Illumination with diffuse, normal and ambient occlusion map ("Demon Skull" Link by Auxiar Molkhun)
Physically-based rendering with pre-integrated irradiance and radiance maps ("Bilora Bella 46 Camera" Link by Martijn Vaes licensed under CC-BY-4.0)
Cel-Shading with post-process edge detection on the depth buffer ("Bird" Link by Václav Pleticha licensed under CC-BY-4.0)
Shadow Mapping (based on "low poly house" Link by cofitelle licensed under CC-BY-4.0 CC-BY-4.0)
Screen-space ambient occlussion ("Pokemon FireRed - Player's Room" Link by Wesai licensed under CC-BY-4.0)
Tiny Renderer
Scratchpixel Lesson
Rasterator
SRPBR
Stack Overflow Perspective Interpolation
learnopengl Tutorials
IBL Blogpost by Bruno Opsenica