1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 16:37:35 +00:00

LibGL: Fix clipping and interpolate vertex attributes

The previous clipping implementation was problematic especially when
clipping against the near plane. Triangles are now correctly clipped
using homogenous coordinates against all frustum planes.

Texture coordinates and vertex colors are now correctly interpolated.
The earier implementation was just a placeholder.
This commit is contained in:
Stephan Unverwerth 2021-08-16 19:22:08 +02:00 committed by Andreas Kling
parent 39ff1459f8
commit 220ac5eb02
6 changed files with 111 additions and 205 deletions

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com> * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
* Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -8,7 +9,9 @@
#include <AK/ScopeGuard.h> #include <AK/ScopeGuard.h>
#include <LibGL/Clipper.h> #include <LibGL/Clipper.h>
bool GL::Clipper::point_within_clip_plane(const FloatVector4& vertex, ClipPlane plane) namespace GL {
bool Clipper::point_within_clip_plane(const FloatVector4& vertex, ClipPlane plane)
{ {
switch (plane) { switch (plane) {
case ClipPlane::LEFT: case ClipPlane::LEFT:
@ -28,83 +31,53 @@ bool GL::Clipper::point_within_clip_plane(const FloatVector4& vertex, ClipPlane
return false; return false;
} }
// TODO: This needs to interpolate color/UV data as well! GLVertex Clipper::clip_intersection_point(const GLVertex& p1, const GLVertex& p2, ClipPlane plane_index)
FloatVector4 GL::Clipper::clip_intersection_point(const FloatVector4& vec, const FloatVector4& prev_vec, ClipPlane plane_index)
{ {
// See https://www.microsoft.com/en-us/research/wp-content/uploads/1978/01/p245-blinn.pdf
// "Clipping Using Homogenous Coordinates" Blinn/Newell, 1978
// float w1 = p1.position.w();
// This is an implementation of the Cyrus-Beck algorithm to clip lines against a plane float w2 = p2.position.w();
// using the triangle's line segment in parametric form. float x1 = clip_plane_normals[plane_index].dot(p1.position);
// In this case, we find that n . [P(t) - plane] == 0 if the point lies on float x2 = clip_plane_normals[plane_index].dot(p2.position);
// the boundary, which in this case is the clip plane. We then substitute float a = (w1 + x1) / ((w1 + x1) - (w2 + x2));
// P(t)= P1 + (P2-P1)*t (where P2 is a point inside the clipping boundary, and P1 is,
// in this case, the point that lies outside the boundary due to our implementation of Sutherland-Hogdman)
// into P(t) to arrive at the equation:
//
// n · [P1 + ((P2 - P1) * t) - plane] = 0.
// Substitude seg_length = P2 - P1 (length of line segment) and dist = P1 - plane (distance from P1 to plane)
//
// By rearranging, we now end up with
//
// n·[P1 + (seg_length * t) - plane] = 0
// n·(dist) + n·(seg_length * t) = 0
// n·(seg_length*t) = -n·(dist)
// Therefore
// t = (-n·(dist))/(n·seg_length)
//
// NOTE: n is the normal vector to the plane we are clipping against.
//
// Proof of algorithm found here
// http://studymaterial.unipune.ac.in:8080/jspui/bitstream/123456789/4843/1/Cyrus_Beck_Algo.pdf
// And here (slightly more intuitive with a better diagram)
// https://www.csee.umbc.edu/~rheingan/435/pages/res/gen-5.Clipping-single-page-0.html
FloatVector4 seg_length = vec - prev_vec; // P2 - P1 GLVertex out;
FloatVector4 dist = prev_vec - clip_planes[plane_index]; // Distance from "out of bounds" vector to plane out.position = p1.position * (1 - a) + p2.position * a;
out.color = p1.color * (1 - a) + p2.color * a;
float plane_normal_line_segment_dot_product = clip_plane_normals[plane_index].dot(seg_length); out.tex_coord = p1.tex_coord * (1 - a) + p2.tex_coord * a;
float neg_plane_normal_dist_dot_procut = -clip_plane_normals[plane_index].dot(dist); return out;
float t = (plane_normal_line_segment_dot_product / neg_plane_normal_dist_dot_procut);
// P(t) = P1 + (t * (P2 - P1))
FloatVector4 lerped_vec = prev_vec + (seg_length * t);
return lerped_vec;
} }
// FIXME: Getting too close to zNear causes VERY serious artifacting. Should we cull the whole triangle?? void Clipper::clip_triangle_against_frustum(Vector<GLVertex>& input_verts)
void GL::Clipper::clip_triangle_against_frustum(Vector<FloatVector4>& input_verts)
{ {
Vector<FloatVector4, 6> clipped_polygon; list_a = input_verts;
Vector<FloatVector4, 6> input = input_verts; list_b.clear_with_capacity();
Vector<FloatVector4, 6>* current = &clipped_polygon;
Vector<FloatVector4, 6>* output_list = &input;
ScopeGuard guard { [&] { input_verts = *output_list; } };
for (u8 plane = 0; plane < NUMBER_OF_CLIPPING_PLANES; plane++) { auto read_from = &list_a;
swap(current, output_list); auto write_to = &list_b;
clipped_polygon.clear();
if (current->size() == 0) {
return;
}
for (size_t plane = 0; plane < NUMBER_OF_CLIPPING_PLANES; plane++) {
write_to->clear_with_capacity();
// Save me, C++23 // Save me, C++23
for (size_t i = 0; i < current->size(); i++) { for (size_t i = 0; i < read_from->size(); i++) {
const auto& curr_vec = current->at(i); const auto& curr_vec = read_from->at((i + 1) % read_from->size());
const auto& prev_vec = current->at((i - 1) % current->size()); const auto& prev_vec = read_from->at(i);
if (point_within_clip_plane(curr_vec, static_cast<ClipPlane>(plane))) { if (point_within_clip_plane(curr_vec.position, static_cast<ClipPlane>(plane))) {
if (!point_within_clip_plane(prev_vec, static_cast<ClipPlane>(plane))) { if (!point_within_clip_plane(prev_vec.position, static_cast<ClipPlane>(plane))) {
FloatVector4 intersect = clip_intersection_point(prev_vec, curr_vec, static_cast<ClipPlane>(plane)); auto intersect = clip_intersection_point(prev_vec, curr_vec, static_cast<ClipPlane>(plane));
clipped_polygon.append(intersect); write_to->append(intersect);
} }
write_to->append(curr_vec);
clipped_polygon.append(curr_vec); } else if (point_within_clip_plane(prev_vec.position, static_cast<ClipPlane>(plane))) {
} else if (point_within_clip_plane(prev_vec, static_cast<ClipPlane>(plane))) { auto intersect = clip_intersection_point(prev_vec, curr_vec, static_cast<ClipPlane>(plane));
FloatVector4 intersect = clip_intersection_point(prev_vec, curr_vec, static_cast<ClipPlane>(plane)); write_to->append(intersect);
clipped_polygon.append(intersect);
} }
} }
swap(write_to, read_from);
} }
input_verts = *read_from;
}
} }

View file

@ -7,6 +7,7 @@
#pragma once #pragma once
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibGL/GLStruct.h>
#include <LibGfx/Vector4.h> #include <LibGfx/Vector4.h>
namespace GL { namespace GL {
@ -34,26 +35,24 @@ class Clipper final {
}; };
static constexpr FloatVector4 clip_plane_normals[] = { static constexpr FloatVector4 clip_plane_normals[] = {
{ 1, 0, 0, 1 }, // Left Plane { 1, 0, 0, 0 }, // Left Plane
{ -1, 0, 0, 1 }, // Right Plane { -1, 0, 0, 0 }, // Right Plane
{ 0, -1, 0, 1 }, // Top Plane { 0, -1, 0, 0 }, // Top Plane
{ 0, 1, 0, 1 }, // Bottom plane { 0, 1, 0, 0 }, // Bottom plane
{ 0, 0, -1, 1 }, // Near Plane { 0, 0, 1, 0 }, // Near Plane
{ 0, 0, 1, 1 } // Far Plane { 0, 0, -1, 0 } // Far Plane
}; };
public: public:
Clipper() { } Clipper() { }
void clip_triangle_against_frustum(Vector<FloatVector4>& input_vecs); void clip_triangle_against_frustum(Vector<GLVertex>& input_vecs);
const Vector<FloatVector4, MAX_CLIPPED_VERTS>& clipped_verts() const;
private: private:
bool point_within_clip_plane(const FloatVector4& vertex, ClipPlane plane); bool point_within_clip_plane(const FloatVector4& vertex, ClipPlane plane);
FloatVector4 clip_intersection_point(const FloatVector4& vec, const FloatVector4& prev_vec, ClipPlane plane_index); GLVertex clip_intersection_point(const GLVertex& vec, const GLVertex& prev_vec, ClipPlane plane_index);
Vector<GLVertex> list_a;
private: Vector<GLVertex> list_b;
Vector<FloatVector4, MAX_CLIPPED_VERTS> m_clipped_verts;
}; };
} }

View file

@ -7,6 +7,8 @@
#pragma once #pragma once
#include "GL/gl.h" #include "GL/gl.h"
#include <LibGfx/Vector2.h>
#include <LibGfx/Vector4.h>
namespace GL { namespace GL {
@ -15,9 +17,9 @@ struct GLColor {
}; };
struct GLVertex { struct GLVertex {
GLfloat x, y, z, w; FloatVector4 position;
GLfloat r, g, b, a; FloatVector4 color;
GLfloat u, v; FloatVector2 tex_coord;
}; };
struct GLTriangle { struct GLTriangle {

View file

@ -123,6 +123,9 @@ void SoftwareGLContext::gl_end()
// Make sure we had a `glBegin` before this call... // Make sure we had a `glBegin` before this call...
RETURN_WITH_ERROR_IF(!m_in_draw_state, GL_INVALID_OPERATION); RETURN_WITH_ERROR_IF(!m_in_draw_state, GL_INVALID_OPERATION);
triangle_list.clear_with_capacity();
processed_triangles.clear_with_capacity();
// Let's construct some triangles // Let's construct some triangles
if (m_current_draw_mode == GL_TRIANGLES) { if (m_current_draw_mode == GL_TRIANGLES) {
GLTriangle triangle; GLTriangle triangle;
@ -169,29 +172,22 @@ void SoftwareGLContext::gl_end()
triangle_list.append(triangle); triangle_list.append(triangle);
} }
} else { } else {
vertex_list.clear_with_capacity();
RETURN_WITH_ERROR_IF(true, GL_INVALID_ENUM); RETURN_WITH_ERROR_IF(true, GL_INVALID_ENUM);
} }
vertex_list.clear_with_capacity();
auto mvp = m_projection_matrix * m_model_view_matrix;
// Now let's transform each triangle and send that to the GPU // Now let's transform each triangle and send that to the GPU
for (size_t i = 0; i < triangle_list.size(); i++) { for (size_t i = 0; i < triangle_list.size(); i++) {
GLTriangle& triangle = triangle_list.at(i); GLTriangle& triangle = triangle_list.at(i);
GLVertex& vertexa = triangle.vertices[0];
GLVertex& vertexb = triangle.vertices[1];
GLVertex& vertexc = triangle.vertices[2];
FloatVector4 veca({ vertexa.x, vertexa.y, vertexa.z, 1.0f });
FloatVector4 vecb({ vertexb.x, vertexb.y, vertexb.z, 1.0f });
FloatVector4 vecc({ vertexc.x, vertexc.y, vertexc.z, 1.0f });
// First multiply the vertex by the MODELVIEW matrix and then the PROJECTION matrix // First multiply the vertex by the MODELVIEW matrix and then the PROJECTION matrix
veca = m_model_view_matrix * veca; triangle.vertices[0].position = mvp * triangle.vertices[0].position;
veca = m_projection_matrix * veca; triangle.vertices[1].position = mvp * triangle.vertices[1].position;
triangle.vertices[2].position = mvp * triangle.vertices[2].position;
vecb = m_model_view_matrix * vecb;
vecb = m_projection_matrix * vecb;
vecc = m_model_view_matrix * vecc;
vecc = m_projection_matrix * vecc;
// At this point, we're in clip space // At this point, we're in clip space
// Here's where we do the clipping. This is a really crude implementation of the // Here's where we do the clipping. This is a really crude implementation of the
@ -203,87 +199,35 @@ void SoftwareGLContext::gl_end()
// Okay, let's do some face culling first // Okay, let's do some face culling first
Vector<FloatVector4> vecs; m_clipped_vertices.clear_with_capacity();
Vector<GLVertex> verts; m_clipped_vertices.append(triangle.vertices[0]);
m_clipped_vertices.append(triangle.vertices[1]);
m_clipped_vertices.append(triangle.vertices[2]);
m_clipper.clip_triangle_against_frustum(m_clipped_vertices);
vecs.append(veca); if (m_clipped_vertices.size() < 3)
vecs.append(vecb); continue;
vecs.append(vecc);
m_clipper.clip_triangle_against_frustum(vecs);
// TODO: Copy color and UV information too! for (auto& vec : m_clipped_vertices) {
for (size_t vec_idx = 0; vec_idx < vecs.size(); vec_idx++) { // perspective divide
FloatVector4& vec = vecs.at(vec_idx); float w = vec.position.w();
GLVertex vertex; vec.position.set_x(vec.position.x() / w);
vec.position.set_y(vec.position.y() / w);
vec.position.set_z(vec.position.z() / w);
vec.position.set_w(1 / w);
// Perform the perspective divide // to screen space
if (vec.w() != 0.0f) { vec.position.set_x(scr_width / 2 + vec.position.x() * scr_width / 2);
vec.set_x(vec.x() / vec.w()); vec.position.set_y(scr_height / 2 - vec.position.y() * scr_height / 2);
vec.set_y(vec.y() / vec.w());
vec.set_z(vec.z() / vec.w());
vec.set_w(1 / vec.w());
}
vertex.x = vec.x();
vertex.y = vec.y();
vertex.z = vec.z();
vertex.w = vec.w();
// FIXME: This is to suppress any -Wunused errors
vertex.u = 0.0f;
vertex.v = 0.0f;
if (vec_idx == 0) {
vertex.r = vertexa.r;
vertex.g = vertexa.g;
vertex.b = vertexa.b;
vertex.a = vertexa.a;
vertex.u = vertexa.u;
vertex.v = vertexa.v;
} else if (vec_idx == 1) {
vertex.r = vertexb.r;
vertex.g = vertexb.g;
vertex.b = vertexb.b;
vertex.a = vertexb.a;
vertex.u = vertexb.u;
vertex.v = vertexb.v;
} else {
vertex.r = vertexc.r;
vertex.g = vertexc.g;
vertex.b = vertexc.b;
vertex.a = vertexc.a;
vertex.u = vertexc.u;
vertex.v = vertexc.v;
}
vertex.x = (vec.x() + 1.0f) * (scr_width / 2.0f) + 0.0f; // TODO: 0.0f should be something!?
vertex.y = scr_height - ((vec.y() + 1.0f) * (scr_height / 2.0f) + 0.0f);
vertex.z = vec.z();
verts.append(vertex);
} }
if (verts.size() == 0) { GLTriangle tri;
continue; tri.vertices[0] = m_clipped_vertices[0];
} else if (verts.size() == 3) { for (size_t i = 1; i < m_clipped_vertices.size() - 1; i++) {
GLTriangle tri;
tri.vertices[0] = verts.at(0); tri.vertices[1] = m_clipped_vertices[i];
tri.vertices[1] = verts.at(1); tri.vertices[2] = m_clipped_vertices[i + 1];
tri.vertices[2] = verts.at(2);
processed_triangles.append(tri); processed_triangles.append(tri);
} else if (verts.size() == 4) {
GLTriangle tri1;
GLTriangle tri2;
tri1.vertices[0] = verts.at(0);
tri1.vertices[1] = verts.at(1);
tri1.vertices[2] = verts.at(2);
processed_triangles.append(tri1);
tri2.vertices[0] = verts.at(0);
tri2.vertices[1] = verts.at(2);
tri2.vertices[2] = verts.at(3);
processed_triangles.append(tri2);
} }
} }
@ -292,10 +236,10 @@ void SoftwareGLContext::gl_end()
// Let's calculate the (signed) area of the triangle // Let's calculate the (signed) area of the triangle
// https://cp-algorithms.com/geometry/oriented-triangle-area.html // https://cp-algorithms.com/geometry/oriented-triangle-area.html
float dxAB = triangle.vertices[0].x - triangle.vertices[1].x; // A.x - B.x float dxAB = triangle.vertices[0].position.x() - triangle.vertices[1].position.x(); // A.x - B.x
float dxBC = triangle.vertices[1].x - triangle.vertices[2].x; // B.X - C.x float dxBC = triangle.vertices[1].position.x() - triangle.vertices[2].position.x(); // B.X - C.x
float dyAB = triangle.vertices[0].y - triangle.vertices[1].y; float dyAB = triangle.vertices[0].position.y() - triangle.vertices[1].position.y();
float dyBC = triangle.vertices[1].y - triangle.vertices[2].y; float dyBC = triangle.vertices[1].position.y() - triangle.vertices[2].position.y();
float area = (dxAB * dyBC) - (dxBC * dyAB); float area = (dxAB * dyBC) - (dxBC * dyAB);
if (area == 0.0f) if (area == 0.0f)
@ -314,10 +258,6 @@ void SoftwareGLContext::gl_end()
m_rasterizer.submit_triangle(triangle, m_texture_units); m_rasterizer.submit_triangle(triangle, m_texture_units);
} }
triangle_list.clear();
processed_triangles.clear();
vertex_list.clear();
m_in_draw_state = false; m_in_draw_state = false;
} }
@ -546,19 +486,9 @@ void SoftwareGLContext::gl_vertex(GLdouble x, GLdouble y, GLdouble z, GLdouble w
GLVertex vertex; GLVertex vertex;
vertex.x = x; vertex.position = { static_cast<float>(x), static_cast<float>(y), static_cast<float>(z), static_cast<float>(w) };
vertex.y = y; vertex.color = m_current_vertex_color;
vertex.z = z; vertex.tex_coord = { m_current_vertex_tex_coord.x(), m_current_vertex_tex_coord.y() };
vertex.w = w;
vertex.r = m_current_vertex_color.x();
vertex.g = m_current_vertex_color.y();
vertex.b = m_current_vertex_color.z();
vertex.a = m_current_vertex_color.w();
// FIXME: This is to suppress any -Wunused errors
vertex.w = 0.0f;
vertex.u = m_current_vertex_tex_coord.x();
vertex.v = m_current_vertex_tex_coord.y();
vertex_list.append(vertex); vertex_list.append(vertex);
} }
@ -847,7 +777,7 @@ void SoftwareGLContext::gl_delete_lists(GLuint list, GLsizei range)
return; return;
for (auto& entry : m_listings.span().slice(list - 1, range)) for (auto& entry : m_listings.span().slice(list - 1, range))
entry.entries.clear(); entry.entries.clear_with_capacity();
} }
void SoftwareGLContext::gl_end_list() void SoftwareGLContext::gl_end_list()

View file

@ -126,6 +126,7 @@ private:
Vector<GLVertex, 96> vertex_list; Vector<GLVertex, 96> vertex_list;
Vector<GLTriangle, 32> triangle_list; Vector<GLTriangle, 32> triangle_list;
Vector<GLTriangle, 32> processed_triangles; Vector<GLTriangle, 32> processed_triangles;
Vector<GLVertex> m_clipped_vertices;
GLenum m_error = GL_NO_ERROR; GLenum m_error = GL_NO_ERROR;
bool m_in_draw_state = false; bool m_in_draw_state = false;

View file

@ -107,9 +107,9 @@ static void rasterize_triangle(const RasterizerOptions& options, Gfx::Bitmap& re
VERIFY((render_target.height() % RASTERIZER_BLOCK_SIZE) == 0); VERIFY((render_target.height() % RASTERIZER_BLOCK_SIZE) == 0);
// Calculate area of the triangle for later tests // Calculate area of the triangle for later tests
IntVector2 v0 { (int)triangle.vertices[0].x, (int)triangle.vertices[0].y }; IntVector2 v0 { (int)triangle.vertices[0].position.x(), (int)triangle.vertices[0].position.y() };
IntVector2 v1 { (int)triangle.vertices[1].x, (int)triangle.vertices[1].y }; IntVector2 v1 { (int)triangle.vertices[1].position.x(), (int)triangle.vertices[1].position.y() };
IntVector2 v2 { (int)triangle.vertices[2].x, (int)triangle.vertices[2].y }; IntVector2 v2 { (int)triangle.vertices[2].position.x(), (int)triangle.vertices[2].position.y() };
int area = edge_function(v0, v1, v2); int area = edge_function(v0, v1, v2);
if (area == 0) if (area == 0)
@ -259,7 +259,8 @@ static void rasterize_triangle(const RasterizerOptions& options, Gfx::Bitmap& re
continue; continue;
auto barycentric = FloatVector3(coords.x(), coords.y(), coords.z()) * one_over_area; auto barycentric = FloatVector3(coords.x(), coords.y(), coords.z()) * one_over_area;
float z = interpolate(triangle.vertices[0].z, triangle.vertices[1].z, triangle.vertices[2].z, barycentric); float z = interpolate(triangle.vertices[0].position.z(), triangle.vertices[1].position.z(), triangle.vertices[2].position.z(), barycentric);
z = options.depth_min + (options.depth_max - options.depth_min) * (z + 1) / 2; z = options.depth_min + (options.depth_max - options.depth_min) * (z + 1) / 2;
bool pass = false; bool pass = false;
@ -322,26 +323,26 @@ static void rasterize_triangle(const RasterizerOptions& options, Gfx::Bitmap& re
// Perspective correct barycentric coordinates // Perspective correct barycentric coordinates
auto barycentric = FloatVector3(coords.x(), coords.y(), coords.z()) * one_over_area; auto barycentric = FloatVector3(coords.x(), coords.y(), coords.z()) * one_over_area;
float interpolated_reciprocal_w = interpolate(triangle.vertices[0].w, triangle.vertices[1].w, triangle.vertices[2].w, barycentric); float interpolated_reciprocal_w = interpolate(triangle.vertices[0].position.w(), triangle.vertices[1].position.w(), triangle.vertices[2].position.w(), barycentric);
float interpolated_w = 1 / interpolated_reciprocal_w; float interpolated_w = 1 / interpolated_reciprocal_w;
barycentric = barycentric * FloatVector3(triangle.vertices[0].w, triangle.vertices[1].w, triangle.vertices[2].w) * interpolated_w; barycentric = barycentric * FloatVector3(triangle.vertices[0].position.w(), triangle.vertices[1].position.w(), triangle.vertices[2].position.w()) * interpolated_w;
// FIXME: make this more generic. We want to interpolate more than just color and uv // FIXME: make this more generic. We want to interpolate more than just color and uv
FloatVector4 vertex_color; FloatVector4 vertex_color;
if (options.shade_smooth) { if (options.shade_smooth) {
vertex_color = interpolate( vertex_color = interpolate(
FloatVector4(triangle.vertices[0].r, triangle.vertices[0].g, triangle.vertices[0].b, triangle.vertices[0].a), triangle.vertices[0].color,
FloatVector4(triangle.vertices[1].r, triangle.vertices[1].g, triangle.vertices[1].b, triangle.vertices[1].a), triangle.vertices[1].color,
FloatVector4(triangle.vertices[2].r, triangle.vertices[2].g, triangle.vertices[2].b, triangle.vertices[2].a), triangle.vertices[2].color,
barycentric); barycentric);
} else { } else {
vertex_color = { triangle.vertices[0].r, triangle.vertices[0].g, triangle.vertices[0].b, triangle.vertices[0].a }; vertex_color = triangle.vertices[0].color;
} }
auto uv = interpolate( auto uv = interpolate(
FloatVector2(triangle.vertices[0].u, triangle.vertices[0].v), triangle.vertices[0].tex_coord,
FloatVector2(triangle.vertices[1].u, triangle.vertices[1].v), triangle.vertices[1].tex_coord,
FloatVector2(triangle.vertices[2].u, triangle.vertices[2].v), triangle.vertices[2].tex_coord,
barycentric); barycentric);
*pixel = pixel_shader(uv, vertex_color); *pixel = pixel_shader(uv, vertex_color);