13 : width(w), height(h), z_buffer(w * h, 1.0f), normal_buffer(w * h,
Vec3(0.0f)) {}
20void Rasterizer::shade_and_write(
int x,
int y,
float depth,
22 const Vec3 &light_direction,
Image &output) {
26 int idx = y *
width + x;
30 shaded =
shade_pixel(color, normal, light_direction,
38 auto apply_contrast = [&](
float v) {
39 return std::clamp((v - 0.5f) *
contrast + 0.5f, 0.0f, 1.0f);
41 shaded.
r = apply_contrast(shaded.
r);
42 shaded.
g = apply_contrast(shaded.
g);
43 shaded.
b = apply_contrast(shaded.
b);
46 uint8_t r =
static_cast<uint8_t
>(std::clamp(shaded.
r, 0.0f, 1.0f) * 255.0f);
47 uint8_t g =
static_cast<uint8_t
>(std::clamp(shaded.
g, 0.0f, 1.0f) * 255.0f);
48 uint8_t b =
static_cast<uint8_t
>(std::clamp(shaded.
b, 0.0f, 1.0f) * 255.0f);
49 uint8_t a =
static_cast<uint8_t
>(std::clamp(shaded.
a, 0.0f, 1.0f) * 255.0f);
53 fog_fac = std::max(0.0f, std::min(1.0f, fog_fac));
54 r =
static_cast<uint8_t
>(r * (1.0f - fog_fac) +
fog_color.
r * 255.0f * fog_fac);
55 g =
static_cast<uint8_t
>(g * (1.0f - fog_fac) +
fog_color.
g * 255.0f * fog_fac);
56 b =
static_cast<uint8_t
>(b * (1.0f - fog_fac) +
fog_color.
b * 255.0f * fog_fac);
57 a =
static_cast<uint8_t
>(a * (1.0f - fog_fac) +
fog_color.
a * 255.0f * fog_fac);
61 uint8_t dr, dg, db, da;
63 float src_a = a / 255.0f;
64 float inv_a = 1.0f - src_a;
65 r =
static_cast<uint8_t
>(r * src_a + dr * inv_a);
66 g =
static_cast<uint8_t
>(g * src_a + dg * inv_a);
67 b =
static_cast<uint8_t
>(b * src_a + db * inv_a);
68 a =
static_cast<uint8_t
>(a + da * inv_a);
82 bool backface_culling,
84 const Vec3 &light_direction,
86 const Color &wireframe_color,
92 if (backface_culling && area > 0.0f)
95 if (std::abs(area) < 1e-12f)
98 float abs_area = std::abs(area);
105 int x_start = std::max(0,
static_cast<int>(std::floor(min_x)));
106 int x_end = std::min(
width - 1,
static_cast<int>(std::ceil(max_x)));
107 int y_start = std::max(0,
static_cast<int>(std::floor(min_y)));
108 int y_end = std::min(
height - 1,
static_cast<int>(std::ceil(max_y)));
110 float inv_area = 1.0f / area;
112 float wire_thresh = 0.0f;
114 wire_thresh = 1.5f / std::sqrt(abs_area > 1e-9f ? abs_area : 1.0f);
115 if (wire_thresh > 0.5f) wire_thresh = 0.5f;
119#pragma omp parallel for if((y_end - y_start) > 16) schedule(static)
121 for (
int y = y_start; y <= y_end; ++y) {
122 for (
int x = x_start; x <= x_end; ++x) {
123 float px =
static_cast<float>(x) + 0.5f;
124 float py =
static_cast<float>(y) + 0.5f;
130 float w2 = 1.0f - w0 - w1;
132 if (w0 < 0.0f || w1 < 0.0f || w2 < 0.0f)
136 if (w0 >= wire_thresh && w1 >= wire_thresh && w2 >= wire_thresh)
139 int pidx = y *
width + x;
141 static_cast<uint8_t
>(std::clamp(wireframe_color.
r, 0.0f, 1.0f) * 255.0f),
142 static_cast<uint8_t
>(std::clamp(wireframe_color.
g, 0.0f, 1.0f) * 255.0f),
143 static_cast<uint8_t
>(std::clamp(wireframe_color.
b, 0.0f, 1.0f) * 255.0f),
144 static_cast<uint8_t
>(std::clamp(wireframe_color.
a, 0.0f, 1.0f) * 255.0f));
157 if (smooth_shading) {
170 Vec2 interp_uv = smooth_shading
174 base_color =
Color(base_color.
r * tex.
r, base_color.
g * tex.
g,
175 base_color.
b * tex.
b, base_color.
a * tex.
a);
178 shade_and_write(x, y, depth, base_color, interp_normal, light_direction, output);
184 float radius,
const Color &color,
185 const Vec3 &normal,
const Vec3 &light_direction,
187 int cx =
static_cast<int>(std::floor(screen_x));
188 int cy =
static_cast<int>(std::floor(screen_y));
189 int r =
static_cast<int>(std::ceil(radius));
190 float r_sq = radius * radius;
192 for (
int dy = -r; dy <= r; ++dy) {
194 if (py < 0 || py >=
height)
continue;
195 for (
int dx = -r; dx <= r; ++dx) {
197 if (px < 0 || px >=
width)
continue;
198 if (
static_cast<float>(dx*dx + dy*dy) > r_sq)
continue;
199 shade_and_write(px, py, depth, color, normal, light_direction, output);
211 const float depth_bias = 0.05f;
214 const float max_occlusion_distance = 1.5f;
217 const float radius_scale = 10.0f;
221 const float dirs[ns][2] = {
222 { 0.309f, 0.951f}, {-0.809f, 0.588f}, { 1.000f, -0.000f}, { 0.809f, -0.588f},
223 {-0.309f, -0.951f}, { 0.588f, 0.809f}, {-0.588f, 0.809f}, {-1.000f, -0.000f},
227 const float depth_C = 2.0f * z_near * z_far;
228 const float depth_D = z_far + z_near;
229 const float depth_E = z_far - z_near;
232 auto linearize = [&](
float raw_z) {
233 float z_ndc = 2.0f * raw_z - 1.0f;
234 return depth_C / (depth_D - z_ndc * depth_E);
237 for (
int y = 0; y <
height; ++y) {
238 for (
int x = 0; x <
width; ++x) {
239 int idx = y *
width + x;
240 float center_depth_raw =
z_buffer[idx];
243 if (center_depth_raw >= 1.0f)
continue;
246 float center_depth = linearize(center_depth_raw);
249 if (center_normal.x == 0.0f && center_normal.y == 0.0f && center_normal.z == 0.0f)
continue;
251 center_normal = glm::normalize(center_normal);
254 int screen_radius =
static_cast<int>((
ssao_radius * radius_scale) / center_depth);
255 screen_radius = std::max(1, std::min(screen_radius, 100));
257 float occlusion = 0.0f;
258 for (
int s = 0; s < ns; ++s) {
259 int sx = x +
static_cast<int>(dirs[s][0] * screen_radius);
260 int sy = y +
static_cast<int>(dirs[s][1] * screen_radius);
263 if (sx < 0 || sx >=
width || sy < 0 || sy >=
height) {
268 float sample_depth = linearize(sample_depth_raw);
271 float depth_delta = center_depth - sample_depth;
274 if (depth_delta > depth_bias) {
277 float range_falloff = 1.0f - (depth_delta / max_occlusion_distance);
280 if (range_falloff > 0.0f) {
282 float depth_ndc_delta = center_depth_raw - sample_depth_raw;
284 dirs[s][0] * screen_radius,
285 -dirs[s][1] * screen_radius,
287 offset_dir = glm::normalize(offset_dir);
288 float hem = glm::dot(center_normal, offset_dir);
290 occlusion += range_falloff * hem;
297 float ao = 1.0f - (
ssao_intensity * (occlusion /
static_cast<float>(ns)));
298 ao = std::max(0.0f, std::min(1.0f, ao));
304 r =
static_cast<uint8_t
>(r * ao);
305 g =
static_cast<uint8_t
>(g * ao);
306 b =
static_cast<uint8_t
>(b * ao);
Low-level math utilities for the rendering pipeline.
glm::vec2 Vec2
2-component floating-point vector (xy).
glm::vec3 Vec3
3-component floating-point vector (xyz).
Color shade_pixel(const Color &base_color, const Vec3 &normal, const Vec3 &light_direction, const Color &specular_color=Color(0, 0, 0, 0), float shininess=0.0f)
Compute the shaded color of a pixel with a single directional light.
Color shade_pixel_multi(const Color &base_color, const Vec3 &normal, const std::vector< Light > &lights, float ambient, const Color &specular_color=Color(0, 0, 0, 0), float shininess=0.0f)
Compute the shaded color with multiple light sources.
The Rasterizer — the per-pixel rendering engine.
An RGBA color with floating-point components.
float g
Green channel, [0, 1].
float r
Red channel, [0, 1].
float b
Blue channel, [0, 1].
float a
Alpha (opacity) channel, [0, 1]. 1.0 = fully opaque.
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Set a single pixel's RGBA value.
void get_pixel(int x, int y, uint8_t &r, uint8_t &g, uint8_t &b, uint8_t &a) const
Get a single pixel's RGBA value.
Color sample_bilinear(float u, float v) const
Sample the image at texture coordinates (u, v) using bilinear interpolation.
float ssao_radius
SSAO sample radius in pixels (default: 16).
void clear(float clear_depth=1.0f)
Clear the depth and normal buffers.
void rasterize_point(float screen_x, float screen_y, float depth, float radius, const Color &color, const Vec3 &normal, const Vec3 &light_direction, Image &output)
Rasterize a single point (filled circle) into the output image.
float fog_end
Distance where fog is fully opaque.
Color fog_color
The fog color (what distant objects blend into).
bool fog_enabled
Enable depth fog (default: false).
float fog_start
Distance where fog begins.
Rasterizer(int w, int h)
Construct a rasterizer for the given output size.
void apply_ssao(Image &output, float z_near, float z_far)
Apply screen-space ambient occlusion to the output image.
int height
Output image height in pixels.
Image * active_texture
Optional texture image for textured meshes.
float ssao_intensity
SSAO darkening intensity (0.0–1.0, default: 0.8).
float ambient
Ambient light level (0.0–1.0, default: 0.3).
int width
Output image width in pixels.
Color specular_color
Specular highlight color.
std::vector< Light > lights
Light sources for Blinn-Phong shading.
float shininess
Shininess exponent (Phong model).
void rasterize_triangle(const Vec3 &screen_v0, const Color &color0, const Vec3 &normal0, const Vec2 &uv0, const Vec3 &screen_v1, const Color &color1, const Vec3 &normal1, const Vec2 &uv1, const Vec3 &screen_v2, const Color &color2, const Vec3 &normal2, const Vec2 &uv2, bool backface_culling, bool smooth_shading, const Vec3 &light_direction, bool wireframe, const Color &wireframe_color, Image &output)
Rasterize a single triangle into the output image.
bool ssao_enabled
Enable SSAO (default: false).
std::vector< float > z_buffer
Z-buffer (depth buffer), one float per pixel.
bool blend_mode
Enable alpha blending for transparent triangles (default: false).
float contrast
Contrast adjustment (1.0 = no change).
std::vector< Vec3 > normal_buffer
Normal buffer, one Vec3 per pixel.