Graphics Programming Virtual Meetup

Discord

Twitter

Tiny Renderer

Lesson 3
Depth Buffer

Tutorial link:
https://github.com/ssloy/tinyrenderer/wiki/Lesson-3-Hidden-faces-removal-(z-buffer)


My Code:
https://github.com/cdgiessen/TinyRenderer

Issue: Overlapping Triangles

  • Triangles are drawn first to last
  • No concept of in front or behind
  • Causes ordering artifacts

Example: Note the mouth

Possible Solution:
Painters Algorithm

  • Sort the triangles in 'back to front' order
  • Draw the back triangles first
  • If there are any overlapping triangles, divide the triangles into smaller segments

Drawbacks

  • Requires processing the triangle list
  • Must subdivide triangles to prevent artifacts
  • Needs to be calculated every time the camera moves

Conclusion: Its no good!

Idea: Keep track of "Depth"

  • Have each coordinate remember how far into the scene it is
  • Only draw a pixel if its depth is closer to the camera than the last pixel at that coordinate

Lets draw a simplified scene to illustrate the technique

Same scene from 'above'

Let a 2d plane intersecting our scene

Draw the 2d version of that scene projected on the plane

// scene "2d mesh"
line(Vec2i(20, 34),   Vec2i(744, 400), scene, red);
line(Vec2i(120, 434), Vec2i(444, 400), scene, green);
line(Vec2i(330, 463), Vec2i(594, 200), scene, blue);

// screen line
line(Vec2i(10, 10), Vec2i(790, 10), scene, white);

Create a "line strip" and draw it
We make the line strip 16 pixels tall so we can more easily see it.

TGAImage render(width, 16, TGAImage::RGB);
std::vector<int> ybuffer(width * 16, std::numeric_limits<int>::min());

rasterize(Vec2i(20, 34),   Vec2i(744, 400), render, red,   ybuffer);
rasterize(Vec2i(120, 434), Vec2i(444, 400), render, green, ybuffer);
rasterize(Vec2i(330, 463), Vec2i(594, 200), render, blue,  ybuffer);
void rasterize(Vec2i p0, Vec2i p1, TGAImage &image, 
    TGAColor color, std::vector<int>& ybuffer) {
    if (p0.x>p1.x)
        std::swap(p0, p1);
    for (int x=p0.x; x<=p1.x; x++) {
        float t = (x-p0.x)/(float)(p1.x-p0.x);
        int y = p0.y*(1.-t) + p1.y*t;
        if (ybuffer[x]<y) {
            ybuffer[x] = y;
            image.set(x, 0, color);
        }
    }
}

1d rasterizer function

  • Draw a line between p0.x and p1.x
  • For each pixel draw, determine the 'y' value - (interpolate p0.y & p1.y)
  • If y < the ybuffer at the current pixel, draw it, else skip the pixel

First triangle - red

depth buffer

Second triangle - green

depth buffer

Third triangle - blue

depth buffer

Compare with top down view

Time to make it 3d!

2d depth buffer - simpler to make a 1d vector of floats and index into it manually

// 2d->1d
int idx = x + y*width;

// 1d->2d
int x = index % width;
int y = index / width;

for 3d depth value, use the barycentric coordinates gotten in the previous lesson

Vec3f bc_screen  = barycentric(pts[0], pts[1], pts[2], P);
if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0) continue;
P.z = 0;
for (int i=0; i<3; i++) P.z += pts[i][2]*bc_screen[i];
if (zbuffer[int(P.x+P.y*width)]<P.z) {
    zbuffer[int(P.x+P.y*width)] = P.z;
    image.set(P.x, P.y, color);
}

Voila!

Textures - left as an exercise to the reader


Texture mapping outline:

  • Load UV data from model
  • Load Texture
  • Get UV coordinate from barycentric function
  • Use UV coord to sample from Texture, combine color with lighting value.

Graphics Programming Virtual Meetup

Tiny Renderer Lesson 3

By Charles Giessen

Tiny Renderer Lesson 3

  • 156