使用深度缓冲区时将值重绘到屏幕

Values being redrawn to screen when using depth buffer

我已经使用大小为 640 * 480 的 std::vector 实现了深度缓冲区。我可以很好地写入和读取缓冲区,但我注意到缓冲区似乎是沿着左右边缘复制的.缓冲区是逐行写入的,从左到右然后向下一行。

我很确定问题与深度缓冲区有关,因为禁用从缓冲区读取修复了伪像并显示缓冲区仍在正确写入。

我使用 SDL 作为图形库,而不是 OpenGL。

这个缓冲区应该只在中间显示一个梯形。左右多余的位应该不会出现

是什么导致了这些伪像?或者,我能知道一些更好地调试它的方法吗?

要复制的最少代码(据我所知):

#include "SDL.h"
#include <vector> 
#include <algorithm>
#include <iostream>

struct vec3d {
    float x = 0;
    float y = 0;
    float z = 0;
};

struct tri3d {
    vec3d p1;
    vec3d p2;
    vec3d p3;
};

struct vector2d {
    float x;
    float y;
};

float vect_dot_vect(vector2d a, vector2d b) {
    return(a.x * b.x + a.y * b.y);
}

int draw_tri(SDL_Renderer* renderer, std::vector<float>& buffer_out, tri3d triangle, int half_x_width, int half_y_width, int depth_test) { // depthmap is a linear array. Buffer out is pointing to the first value
    tri3d scaled_tri = triangle;

    // Find bounding box of tri
    int x = (int)std::min(std::min(floor(scaled_tri.p1.x), floor(scaled_tri.p2.x)), floor(scaled_tri.p3.x));
    int y = (int)std::min(std::min(floor(scaled_tri.p1.y), floor(scaled_tri.p2.y)), floor(scaled_tri.p3.y));
    int wx = (int)std::max(std::max(ceil(scaled_tri.p1.x), ceil(scaled_tri.p2.x)), ceil(scaled_tri.p3.x)) - x;
    int wy = (int)std::max(std::max(ceil(scaled_tri.p1.y), ceil(scaled_tri.p2.y)), ceil(scaled_tri.p3.y)) - y;

    // Find edge vectors
    vector2d ac;
    ac.x = scaled_tri.p3.x - scaled_tri.p1.x;
    ac.y = scaled_tri.p3.y - scaled_tri.p1.y;
    vector2d ab;
    ab.x = scaled_tri.p2.x - scaled_tri.p1.x;
    ab.y = scaled_tri.p2.y - scaled_tri.p1.y;

    float cc = vect_dot_vect(ac, ac);
    float cb = vect_dot_vect(ac, ab);
    float cp;
    float bb = vect_dot_vect(ab, ab);
    float bp;
    float invDenom = 1 / (cc * bb - pow(cb, 2));
    float u;
    float v;
    float w;

    float x_dif = x - scaled_tri.p1.x;
    float y_dif = y - scaled_tri.p1.y;

    int full_y_width = half_y_width * 2;
    float twoarea = (ab.x * ac.y - ab.y * ac.x);
    float barycentric_depth_weights[3] = { scaled_tri.p1.z, scaled_tri.p2.z, scaled_tri.p3.z };

    float depth_map_value;

    for (size_t i = wy; i != 0; i--) {
        for (size_t q = wx; q != 0; q--) {
            vector2d ap;
            ap.x = q + x_dif;
            ap.y = i + y_dif;
            cp = vect_dot_vect(ac, ap);
            bp = vect_dot_vect(ab, ap);

            // Find barycentric coords
            u = (bb * cp - cb * bp) * invDenom;
            v = (cc * bp - cb * cp) * invDenom;
            w = abs(1 - u - v);
            depth_map_value = (w * barycentric_depth_weights[0] + v * barycentric_depth_weights[1] + u * barycentric_depth_weights[2]);

            // Test if in tri
            if (u >= 0 && v >= 0 && u + v < 1) {

                // Test depth buffer
                if (buffer_out[(y + i) * full_y_width + x + q] < (0.0625 + depth_map_value)) {
                    buffer_out[(y + i) * full_y_width + x + q] = depth_map_value;
                }
            }
        }
    }
    return 0;
}

SDL_Window* win_make_window(int display_width, int display_height, SDL_WindowFlags flags) {
    // Returns an SDL window given screen size and flags
    SDL_Window* window = NULL;
    window = SDL_CreateWindow("Minimum code", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, display_width, display_height, flags);
    if (window == NULL) {
        printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
    }
    return window;
}

int draw_buffer(SDL_Renderer* renderer, std::vector<float>& buffer, int half_screen_x, int half_screen_y) {
    // Iterate over every pixel and draw
    int depth_map_value;
    int screen_y = 2 * half_screen_y;
    for (size_t i = 0; i < screen_y; i++) {
        for (size_t q = 0; q < half_screen_x * 2; q++) {
            depth_map_value = buffer.at(screen_y * i + q) * 100;
            SDL_SetRenderDrawColor(renderer, depth_map_value, depth_map_value, depth_map_value, 255);
            SDL_RenderDrawPoint(renderer, (int)q, (int)i);
        }
    }
    return 0;
}

int main(int argc, char* argv[]) {

    const int half_screen_size[2] = { 320, 240 };   // Half size of screen. Needed it elsewhere
    const SDL_WindowFlags flags = SDL_WINDOW_SHOWN;

    // SDL startup boilerplate
    SDL_Window* window = NULL;
    SDL_Surface* screenSurface = NULL;
    SDL_Renderer* renderer = NULL;

    // The tris, already projected
    tri3d tri1;
    tri1.p1 = { 577.173828, 453.201538, 1.37657264 };
    tri1.p2 = { 108.381744, 399.609772, 1.03054810 };
    tri1.p3 = { 547.989380,70.1635742,1.20407486 };

    tri3d tri2;
    tri2.p1 = { 108.381744, 399.609772, 1.03054810 };
    tri2.p2 = { 131.230850, 108.719635, 0.930727124 };
    tri2.p3 = { 547.989380, 70.1635742, 1.20407486 };

    //Create depth buffer
    std::vector<float> depth_buffer = {0};
    depth_buffer.resize(4 * static_cast<__int64>(half_screen_size[0]) * static_cast<__int64>(half_screen_size[1]));

    // Catch startup errors
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0) printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); // Catch startup errors

    else {
        SDL_Event event_handle;
        window = win_make_window(half_screen_size[0] * 2, half_screen_size[1] * 2, flags);
        screenSurface = SDL_GetWindowSurface(window);
        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

        // Draw tris to screen. No pixels actually drawn for debug purposes, only modifies depth buffer
        draw_tri(renderer, depth_buffer, tri1, half_screen_size[0], half_screen_size[1], 1);
        draw_tri(renderer, depth_buffer, tri2, half_screen_size[0], half_screen_size[1], 1);

        // Draw the buffer to screen
        draw_buffer(renderer, depth_buffer, half_screen_size[0], half_screen_size[1]);

        SDL_RenderPresent(renderer);
    }
    // Close everything else
    std::cin.get();
    SDL_DestroyWindow(window);
    SDL_DestroyRenderer(renderer);
    SDL_Quit();

    return 0;
}

这是一个学校项目,因此,我不能使用 SDL 提供的功能,除了绘制到屏幕和处理 windows。

我修改了代码以在计算深度缓冲区时绘制深度缓冲区,并注意到当从左到右按列绘制时,最左边的工件不再是渲染器。通过更改渲染区域的范围,似乎写入深度缓冲区上的一个点也会写入另一个点。还不知道该怎么做。

不知道“半”尺寸有什么问题,因为您似乎到处都使用全尺寸,但您的数组索引是错误的。当用 [width, height] 迭代矩形时,正确的索引代码是例如:

for(int y = 0; y != height; ++y) {
    for(int x = 0; x != width; ++x) {
        int pixel = pixel_array[y*width+x];       // not y*height!
    }
}

更正您在这两个地方索引深度数组:

draw_tri 中,buffer_out[(y + i) * full_y_width + x + q] - 应该是 full_x_width,你还没有,

draw_bufferdepth_map_value = buffer.at(screen_y * i + q) * 100; 中 - 应该是 screen_x