A tile based cpu rasterizer
- Windows
- Linux
- MacOS
- download zip or clone CPU-Rasterizer
- run 'setup.bat' or type 'premake vs2017/vs2019'
#include "CGL.h"
#include "HelloTriangleShader.hpp"
int main()
{
size_t w = 600;
size_t h = 400;
// initialize window
cglInitWindow("Demo", w, h);
// set viewport
cglSetViewPort(0, 0, w, h);
// resize callback
cglAddResizeEvent([](size_t resized_w, size_t resized_h, void* ud)
{
UNUSED(ud);
cglSetViewPort(0, 0, resized_w, resized_h);
}, nullptr);
HelloTriangleShader shader;
uint32_t shader_id;
cglCreateProgram(&shader, shader_id);
// a triangle
cglVert v1(cglVec4(-0.5f, -0.5f, 0.0f, 1.0f), cglVec3Zero, cglVec2Zero);
cglVert v2(cglVec4(0.5f, -0.5f, 0.0f, 1.0f), cglVec3Zero, cglVec2Zero);
cglVert v3(cglVec4(0.0f, 0.5f, 0.0f, 1.0f), cglVec3Zero, cglVec2Zero);
std::vector<cglVert> vertex_buffer = {v1, v2, v3};
std::vector<size_t> index_buffer = { 0, 1, 2 };
auto vid = cglBindVertexBuffer(vertex_buffer);
auto iid = cglBindIndexBuffer(index_buffer);
// setup shader properties
cglUniformMatrix4fv(shader_id, mat_model_prop, cglMat4Identity);
cglUniformMatrix4fv(shader_id, mat_view_prop, cglMat4Identity);
cglUniformMatrix4fv(shader_id, mat_projection_prop, cglMat4Identity);
cglUniformMatrix4fv(shader_id, mat_vp_prop, cglMat4Identity);
cglUniformMatrix4fv(shader_id, mat_mvp_prop, cglMat4Identity);
cglDisable(cglPipelineFeature::kBlending);
cglDisable(cglPipelineFeature::kFaceCulling);
cglDepthFunc(cglCompareFunc::kAlways);
// set background color
cglSetClearColor(tinymath::kColorBlue);
while (cglIsMainWindowOpen())
{
// clear window buffer
cglClearMainWindow();
// clear buffer
cglClearBuffer(cglFrameContent::kColor | cglFrameContent::kDepth | cglFrameContent::kStencil);
cglUseProgram(shader_id);
cglUseVertexBuffer(vid);
cglUseIndexBuffer(iid);
// draw primitive
cglDrawPrimitive();
// fence primitive tasks
cglFencePrimitives();
// fence pixel tasks
cglFencePixels();
cglSwapBuffer(true);
}
cglCloseMainWindow();
return 0;
}
Shader:
#pragma once
#include "Shader.hpp"
using namespace CpuRasterizor;
using namespace tinymath;
class HelloTriangleShader : public Shader
{
public:
HelloTriangleShader() : Shader("sample_shader") {}
v2f vertex_shader(const a2v& input) const
{
v2f o;
auto opos = vec4f(input.position.x, input.position.y, input.position.z, 1.0f);
auto m = model();
auto vp = vp_matrix();
auto wpos = m * opos;
auto cpos = vp * wpos;
o.position = cpos;
return o;
}
Color fragment_shader(const v2f& input) const
{
UNUSED(input);
// draw a white triangle
return kColorWhite;
}
};
Result:
#include "CGL.h"
#include "HelloTextureShader.hpp"
int main()
{
size_t w = 600;
size_t h = 400;
// initialize window
cglInitWindow("Demo", w, h);
// set viewport
cglSetViewPort(0, 0, w, h);
// resize callback
cglAddResizeEvent([](size_t resized_w, size_t resized_h, void* ud)
{
UNUSED(ud);
cglSetViewPort(0, 0, resized_w, resized_h);
}, nullptr);
HelloTextureShader shader;
uint32_t shader_id;
cglCreateProgram(&shader, shader_id);
// a triangle
cglVert v1(cglVec4(-0.5f, -0.5f, 0.0f, 1.0f), cglVec3Zero, cglVec2(0.0f, 0.0f));
cglVert v2(cglVec4(0.5f, -0.5f, 0.0f, 1.0f), cglVec3Zero, cglVec2(1.0f, 0.0f));
cglVert v3(cglVec4(0.0f, 0.5f, 0.0f, 1.0f), cglVec3Zero, cglVec2(0.5f, 1.0f));
std::vector<cglVert> vertex_buffer = { v1, v2, v3 };
std::vector<size_t> index_buffer = { 0, 1, 2 };
auto vid = cglBindVertexBuffer(vertex_buffer);
auto iid = cglBindIndexBuffer(index_buffer);
// setup shader properties
cglUniformMatrix4fv(shader_id, mat_model_prop, cglMat4Identity);
cglUniformMatrix4fv(shader_id, mat_view_prop, cglMat4Identity);
cglUniformMatrix4fv(shader_id, mat_projection_prop, cglMat4Identity);
cglUniformMatrix4fv(shader_id, mat_vp_prop, cglMat4Identity);
cglUniformMatrix4fv(shader_id, mat_mvp_prop, cglMat4Identity);
cglDisable(cglPipelineFeature::kBlending);
cglDisable(cglPipelineFeature::kFaceCulling);
cglDepthFunc(cglCompareFunc::kAlways);
auto tex = std::make_shared<Texture>(64, 64, cglTextureFormat::kRGB);
// generate a checkerboard texture
for (size_t r = 0; r < 64; ++r)
{
for (size_t c = 0; c < 64; ++c)
{
auto val = ((r & 0x8) == 0) ^ ((c & 0x8) == 0);
tex->write(r, c, { (float)val, (float)val, (float)val, 1.0f});
}
}
// todo: strinify the key
shader.local_properties.set_texture(123, tex);
// set background color
cglSetClearColor(tinymath::kColorBlue);
while (cglIsMainWindowOpen())
{
// clear window buffer
cglClearMainWindow();
// clear buffer
cglClearBuffer(cglFrameContent::kColor | cglFrameContent::kDepth | cglFrameContent::kStencil);
cglUseProgram(shader_id);
cglUseVertexBuffer(vid);
cglUseIndexBuffer(iid);
// draw primitive
cglDrawPrimitive();
// fence primitive tasks
cglFencePrimitives();
// fence pixel tasks
cglFencePixels();
cglSwapBuffer(true);
}
cglCloseMainWindow();
return 0;
}
Shader:
#pragma once
#include "Shader.hpp"
using namespace CpuRasterizor;
using namespace tinymath;
class HelloTextureShader : public Shader
{
public:
HelloTextureShader() : Shader("sample_shader") {}
v2f vertex_shader(const a2v& input) const
{
v2f o;
auto opos = vec4f(input.position.x, input.position.y, input.position.z, 1.0f);
auto m = model();
auto vp = vp_matrix();
auto wpos = m * opos;
auto cpos = vp * wpos;
o.position = cpos;
o.uv = input.uv;
return o;
}
Color fragment_shader(const v2f& input) const
{
UNUSED(input);
// visualize uv
//return input.uv;
// sample texture
Color c;
if (local_properties.has_texture(123))
{
local_properties.get_texture(123)->sample(input.uv.x, input.uv.y, c);
}
return c;
}
};
Result:
UV can be visualized by:
Color fragment_shader(const v2f& input) const
{
return input.uv;
}
Result:
#include "CGL.h"
#include "HelloLightingShader.hpp"
HelloLightingShader shader;
cglVec3 spherical_coord(float theta, float phi)
{
cglVec3 ret;
ret.x = cglCos(theta) * cglCos(phi);
ret.y = cglSin(theta);
ret.z = cglCos(theta) * cglSin(phi);
return cglNormalize(ret);
}
int main()
{
size_t w = 600;
size_t h = 400;
// initialize window
cglInitWindow("Demo", w, h);
// set viewport
cglSetViewPort(0, 0, w, h);
// resize callback
cglAddResizeEvent([](size_t resized_w, size_t resized_h, void* ud)
{
UNUSED(ud);
cglSetViewPort(0, 0, resized_w, resized_h);
}, nullptr);
// setup shader properties
cglVec3 cam_pos = cglVec3(1.5f, 1.5f, 1.5f);
cglVec3 cube_pos = cglVec3Zero;
cglVec3 light_direction;
float l_theta = 0.0f;
float l_phi = 0.0f;
light_direction.x = 0.0f;
light_direction.y = 0.5f;
light_direction.z = 1.0f;
float near = 0.05f;
float far = 100.0f;
cglMat4 model = cglTranslation(cube_pos);
cglMat4 view = cglLookat(cam_pos, cube_pos, cglVec3Up);
cglMat4 proj = cglPerspective(60.0f, (float)w / (float)h, near, far);
uint32_t shader_id;
cglCreateProgram(&shader, shader_id);
cglUniformMatrix4fv(shader_id, mat_model_prop, model);
cglUniformMatrix4fv(shader_id, mat_view_prop, view);
cglUniformMatrix4fv(shader_id, mat_projection_prop, proj);
cglUniformMatrix4fv(shader_id, mat_vp_prop, proj * view);
cglUniformMatrix4fv(shader_id, mat_mvp_prop, proj * view * model);
cglUniform4fv(shader_id, cam_position_prop, cam_pos);
cglUniform4fv(shader_id, albedo_prop, cglVec4(0.5f, 0.0f, 0.0f, 1.0f));
cglUniform4fv(shader_id, light_direction_prop, light_direction);
cglUniform4fv(shader_id, light_color_prop, cglVec4(1.0f, 1.0f, 1.0f, 1.0f));
cglUniform1f(shader_id, light_intensity_prop, 1.0f);
cglDisable(cglPipelineFeature::kBlending);
cglEnable(cglPipelineFeature::kFaceCulling);
cglCullFace(cglFaceCulling::Back);
cglDepthFunc(cglCompareFunc::kLess);
std::vector<Vertex> vert;
std::vector<size_t> ind;
// construct a sphere
float step = TWO_PI / 64.0f;
for (float phi = 0.0f; phi < TWO_PI; phi += step)
{
for (float theta = -HALF_PI; theta < HALF_PI; theta += step)
{
float phi0 = phi;
float phi1 = phi + step;
float theta0 = theta;
float theta1 = theta + step;
auto pos0 = spherical_coord(theta0, phi0);
auto pos1 = spherical_coord(theta1, phi0);
auto pos2 = spherical_coord(theta1, phi1);
auto pos3 = spherical_coord(theta0, phi1);
Vertex v0, v1, v2, v3;
v0.position = pos0;
v0.normal = pos0;
v1.position = pos1;
v1.normal = pos1;
v2.position = pos2;
v2.normal = pos2;
v3.position = pos3;
v3.normal = pos3;
ind.push_back(vert.size());
vert.push_back(v0);
ind.push_back(vert.size());
vert.push_back(v1);
ind.push_back(vert.size());
vert.push_back(v2);
ind.push_back(vert.size());
vert.push_back(v0);
ind.push_back(vert.size());
vert.push_back(v2);
ind.push_back(vert.size());
vert.push_back(v3);
}
}
auto vid = cglBindVertexBuffer(vert);
auto iid = cglBindIndexBuffer(ind);
// set background color
cglSetClearColor(tinymath::kColorBlue);
while (cglIsMainWindowOpen())
{
// clear window buffer
cglClearMainWindow();
// clear buffer
cglClearBuffer(cglFrameContent::kColor | cglFrameContent::kDepth | cglFrameContent::kStencil);
// rotate light
l_theta += 0.01f;
l_phi += 0.01f;
light_direction.x = cglCos(l_theta) * cglCos(l_phi);
light_direction.y = cglSin (l_theta);
light_direction.z = cglCos(l_theta) * cglSin(l_phi);
light_direction = cglNormalize(light_direction);
shader.local_properties.set_float4(light_direction_prop, light_direction);
cglUseProgram(shader_id);
cglUseVertexBuffer(vid);
cglUseIndexBuffer(iid);
// draw primitive
cglDrawPrimitive();
// fence primitive tasks
cglFencePrimitives();
// fence pixel tasks
cglFencePixels();
cglSwapBuffer(true);
}
cglCloseMainWindow();
return 0;
}
Shader:
#pragma once
#include "Shader.hpp"
using namespace CpuRasterizor;
using namespace tinymath;
class HelloLightingShader : public Shader
{
public:
HelloLightingShader() : Shader("sample_shader") {}
v2f vertex_shader(const a2v& input) const
{
v2f o;
auto opos = vec4f(input.position.x, input.position.y, input.position.z, 1.0f);
auto m = model();
auto vp = vp_matrix();
auto wpos = m * opos;
auto v = view();
auto p = projection();
auto cpos = vp * wpos;
o.position = cpos;
o.uv = input.uv;
mat3x3 normal_matrix = mat4x4_to_mat3x3(transpose(inverse(m)));
o.normal = normal_matrix * input.normal;
return o;
}
Color fragment_shader(const v2f& input) const
{
vec4f albedo = local_properties.get_float4(albedo_prop);
vec4f light_color = local_properties.get_float4(light_color_prop);
float intensity = local_properties.get_float(light_intensity_prop);
vec3f normal = input.normal;
vec3f light_dir = local_properties.get_float4(light_direction_prop).xyz;
float ndl = dot(normal, light_dir);
return albedo * light_color * ndl * intensity;
}
};
Result:
Physical based rendering | Physical based rendering | Image based lighting |
---|---|---|
MSAA | Pixel Derivative | Clipping | DebugView |
---|---|---|---|
Stencil test | Depth test | Blending |
---|---|---|
Point sampler | Bilinear sampler |
---|---|
Mipmap |
---|
-
tile based
-
fully parallel of triangle/pixel processing
-
vertex/fragment shader
-
cvv clipping
-
frustum culling
-
back-face culling
-
msaa (pixel frequency and subsample frequency)
-
perspective correct interpolation
-
early-z
-
alpha test
-
stencil test
-
depth test
-
alpha blending
-
texture sampler
-
cubemap sampler
-
pixel derivative
-
mipmap
-
segment drawer
-
viewer
-
input manager
-
model importer
-
resource serialization & deserialization
-
hierarchical transform
-
roaming camera
-
material
-
texture
-
renderer
-
cook-torrance brdf
-
ibl baker
-
ibl
-
metallic workflow
-
gamma/linear workflow
-
simd
-
premake --> cmake
-
cross platform