Saltar al contenido

Eliminando las caras ocultas con un buffer-z

agosto 25, 2018
Eliminar caras ocultas Motor Render desde 0 en C++

En este post explicaremos como eliminar las caras ocultas utilizando un buffer en el eje z.

Cuando se proyecta una escena de tres dimensiones en un plano de dos, es necesario determinar qué polígonos son visibles y cuáles no.

El algoritmo del pintor rasteriza los objetos en función de la profundidad, de forma que los mas cercanos cubren a los lejanos.

Lo que haremos será llevar un control de los valores de z de los triángulos que dibujemos, de forma que al terminar el renderizado solo se rastericen los píxeles de las caras más cercanas al punto de vista. La implementación será como la siguiente:

int *zbuffer = new int[width*height];
for (int i=0; i<width*height; i++) {
	zbuffer[i] = std::numeric_limits<int>::min();
}
...
P.z = ??
...
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);
}

Calcular el valor z de un punto a partir de las coordenadas baricéntricas de un triángulo

El valor del eje z en cada uno de los puntos indica, en este caso (cámara frontal), la profundidad (distancia) desde el punto de vista.

Este valor lo calcularemos a partir de las coordenadas baricéntricas respecto al triángulo por cada uno de los valores z de los vértices. Se entiende mejor con la nueva implementación para dibujar los triángulos:

void triangle(Vec3i *pts, int *zbuffer, TGAImage &image, TGAColor color) {
    Vec2f bboxmin( std::numeric_limits<float>::max(),  std::numeric_limits<float>::max());
    Vec2f bboxmax(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
    Vec2f clamp(image.get_width()-1, image.get_height()-1);
    for (int i=0; i<3; i++) {
        bboxmin.x = std::max(0.f,      std::min(bboxmin.x, (float)pts[i].x));
        bboxmax.x = std::min(clamp.x, std::max(bboxmax.x, (float)pts[i].x));
        bboxmin.y = std::max(0.f,      std::min(bboxmin.y, (float)pts[i].y));
        bboxmax.y = std::min(clamp.y, std::max(bboxmax.y, (float)pts[i].y));
    }
    Vec3f P;
    for (P.x=bboxmin.x; P.x<=bboxmax.x; P.x++) {
        for (P.y=bboxmin.y; P.y<=bboxmax.y; P.y++) {
            Vec3f bc_screen  = barycentric(pts, P);
            if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0) continue;
            P.z = (pts[0].z * bc_screen.x) + (pts[1].z * bc_screen.y) + (pts[2].z * bc_screen.z);
            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);
            }
        }
    }
}

Y obtenemos un resultado como el siguiente:

render luz frontal z-buffer

Podemos representar también el buffer para ver la representación de las profundidades que hemos calculado:

TGAImage zbimage(width, height, TGAImage::GRAYSCALE);
for (int i=0; i<width; i++) {
	for (int j=0; j<height; j++) {
		zbimage.set(i, j, TGAColor(zbuffer[i+j*width], 1));
	}
}

zbuffer representacion