为什么 OpenGL 中的 3D 投影可以正常工作,但会留下痕迹?

Why is 3D projection in OpenGL working, but leaving a trail behind?

我刚刚从 Wikipedia 中为 3D 投影做了一些数学运算,因为我注意到它们很简单,不需要库。它确实有效,但是立方体在移动时会留下痕迹。请注意,立方体实际上并没有移动,我实际上是在改变相机位置,这让立方体看起来像是在移动。

没有必要指出我正在做的 100 个不好的做法,我知道,这只是一个快速测试。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <glad/glad.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_opengl.h>
#include <math.h>
#include "utils.h"
#include "keys.h"

char p = 1;

typedef struct Vec3 {
float x;
float y;
float z;
} Vec3;

void Mat_iden(float *m, Uint32 s) {
Uint32 i = 0;
Uint32 unt = s + 1;
while (i < s) {
    m[unt * i] = 1;
    i++;
}
}

float one[3][3];
float two[3][3];
float three[3][3];

int main() {
SDL_Init(SDL_INIT_VIDEO);
IMG_Init(IMG_INIT_PNG);

SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);

SDL_Window *w = SDL_CreateWindow("Snapdoop", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 500, 500, SDL_WINDOW_OPENGL);
SDL_GLContext c = SDL_GL_CreateContext(w);

gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress);

Mat_iden(one[0], 3);
Mat_iden(two[0], 3);
Mat_iden(three[0], 3);

Shader s[2];

s[0] = Shade("/home/shambhav/eclipse-workspace/Snadoop/src/vs.glsl");
s[1] = Shade("/home/shambhav/eclipse-workspace/Snadoop/src/fs.glsl");
Shade_comp(&s[0], GL_VERTEX_SHADER);
Shade_comp(&s[1], GL_FRAGMENT_SHADER);
Program sp;
Prog_attach(&sp, s, 2);
printf("VS: %s\n", s[0].info);
printf("FS: %s\n", s[1].info);
printf("SP: %s\n", sp.info);
glDeleteShader(s[0].c);
glDeleteShader(s[1].c);

float v[48] = {
        //Front
        0.25, 0.25, 0.25, 1.0, 1.0, 0.0,
        -0.25, 0.25, 0.25, 1.0, 0.0, 0.0,
        -0.25, -0.25, 0.25, 0.0, 1.0, 1.0,
        0.25, -0.25, 0.25, 0.0, 1.0, 0.0,
        //Back
        0.25, 0.25, -0.25, 0.0, 0.0, 1.0,
        -0.25, 0.25, -0.25, 1.0, 0.0, 1.0,
        -0.25, -0.25, -0.25, 1.0, 1.0, 1.0,
        0.25, -0.25, -0.25, 0.0, 0.0, 0.0
};
unsigned int i[36] = {
        //Front
        0, 1, 2,
        2, 3, 0,
        //Right
        0, 3, 7,
        7, 4, 0,
        //Left
        1, 2, 6,
        6, 5, 2,
        //Back
        4, 5, 6,
        6, 7, 4,
        //Up
        0, 1, 5,
        5, 4, 0,
        //Down
        3, 7, 2,
        2, 6, 7
};

GLuint VAO, VBO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);

glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(v), v, GL_STATIC_DRAW);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(i), i, GL_STATIC_DRAW);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void *)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void *)(sizeof(float) * 3));
glEnableVertexAttribArray(1);

Vec3 cam = {1.0, 1.0, 1.0};
Vec3 theta = {0, 0, 0};

Key k = (const Key){ 0 };
printf("%d\n", k.alpha[9]);

SDL_Event e;
while (p) {
    while (SDL_PollEvent(&e)) {
        switch (e.type) {
        case SDL_QUIT:
            p = 0;
            break;

        case SDL_KEYDOWN:
            *key(&k, e.key.keysym.sym) = 1;
            break;

        case SDL_KEYUP:
            *key(&k, e.key.keysym.sym) = 0;
            break;
        }
    }

    if (*key(&k, SDLK_RIGHT)) {
        cam.x += 0.01;
    }
    if (*key(&k, SDLK_LEFT)) {
        cam.x -= 0.01;
    }
    if (*key(&k, SDLK_UP)) {
        cam.y += 0.01;
    }
    if (*key(&k, SDLK_DOWN)) {
        cam.y -= 0.01;
    }

    if (*key(&k, 'w')) {
        theta.y += 0.01;
    }
    if (*key(&k, 's')) {
        theta.y -= 0.01;
    }
    if (*key(&k, 'a')) {
        theta.x -= 0.01;
    }
    if  (*key(&k, 'd')) {
        theta.x += 0.01;
    }
    if (*key(&k, 'z')) {
        theta.z -= 0.01;
    }
    if (*key(&k, 'x')) {
        theta.z += 0.01;
    }
    if (*key(&k, 'n')) {
        cam.z += 0.01;
    }
    if (*key(&k, 'm')) {
        cam.z -= 0.01;
    }

    one[1][1] = cos(theta.x);
    one[1][2] = sin(theta.x);
    one[2][1] = -sin(theta.x);
    one[2][2] = cos(theta.x);

    two[0][0] = cos(theta.y);
    two[0][2] = -sin(theta.y);
    two[2][0] = sin(theta.y);
    two[2][2] = cos(theta.y);

    three[0][0] = cos(theta.z);
    three[0][1] = sin(theta.z);
    three[1][0] = -sin(theta.z);
    three[1][1] = cos(theta.z);

    glUseProgram(sp.p);
    glUniformMatrix3fv(2, 1, GL_FALSE, one[0]);
    glUniformMatrix3fv(3, 1, GL_FALSE, two[0]);
    glUniformMatrix3fv(4, 1, GL_FALSE, three[0]);
    glUniform3f(5, cam.x, cam.y, cam.z);
    glClear(GL_DEPTH_BUFFER_BIT);

    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
    SDL_GL_SwapWindow(w);
}

glDeleteProgram(sp.p);
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);

SDL_GL_DeleteContext(c);
SDL_DestroyWindow(w);
SDL_Quit();
return 0;
}

顶点着色器(vs.glsl):

#version 450 core

layout (location = 0) in vec3 pos;
layout (location = 1) in vec3 tcol;
layout (location = 2) uniform mat3 x;
layout (location = 3) uniform mat3 y;
layout (location = 4) uniform mat3 z;
layout (location = 5) uniform vec3 c;

out vec3 col;

void main() {
vec3 d = x * y * z * (pos - c);
gl_Position.x = d.x / d.z;
gl_Position.y = d.y / d.z;
gl_Position.z = 0.0;
gl_Position.w = 1.0;
col = tcol;
}

片段着色器:

#version 450 core

out vec4 color;

in vec3 col;

void main() {
color = vec4(col, 1.0);
}

我认为 keys.h 和 utils.h 不应该出现在这里,因为它们与 OpenGL 无关。这是一个最小可重现示例,因为仅需要额外的部分(keys.h 和 utils.h)分别用于管理关键数据和加载着色器。

我的代码中的某些键可能被颠倒了,这只是所有方面的错误代码...对此表示抱歉。

这是我移动立方体(准确地说是相机视角)后拍摄的图像。需要注意的一件重要事情是,除了轨迹之外,它似乎工作得很好。

您还需要清除颜色缓冲区:

glClear(GL_DEPTH_BUFFER_BIT);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glClear 清除指定的缓冲区。缓冲区用位掩码指定。 GL_COLOR_BUFFER_BIT表示清除当前启用的彩色写入缓冲区。

简短的回答是您需要更改:

glClear(GL_DEPTH_BUFFER_BIT);

...到...

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

更多详情

评论要求详细说明,我再详细说明一下。

当您说 glClear(GL_DEPTH_BUFFER_BIT) 时,它会清除 Z 缓冲区(深度缓冲区)中的像素值。当你说 glClear(GL_COLOR_BUFFER_BIT) 时,它会清除颜色缓冲区中像素的 RGBA 通道(将它们设置为 glClearColor)。如果你说 glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT) 它会同时清除 Z-Buffer 和颜色缓冲区。 这就是您想要做的。您希望每一帧都以全新的黑色背景开始,并在其上方绘制该帧的内容。

把它想象成将每个像素设置为黑色并将深度值设置为零。实际上,它会将颜色缓冲区设置为 glClearColor 指定的颜色,并将深度值设置为 glClearDepth.

指定的值

在您的评论中,您说您认为它“清除了功能”。 glClear 不是这样做的。 如果你想完全启用或禁用写入深度缓冲区,你可以使用glDepthMask来实现。此函数使您可以完全禁用对深度缓冲区的写入,同时可能仍将颜色值写入颜色缓冲区。有一个名为 glColorMask 的类似函数,可让您 select 您还想写入颜色缓冲区的哪些通道(红色、绿色、蓝色、and/or alpha)。通过这种方式,您可能会做一些有趣的事情,比如只渲染绿色,或者甚至做一个特殊的效果,在渲染过程中你只渲染深度值和 not 颜色值(可能在准备敲除要在后续通道中应用的特殊效果。)glClear,相反,实际上是在颜色缓冲区或深度缓冲区的像素中设置值。

在您发布的代码中,您只是在做 glClear(GL_DEPTH_BUFFER_BIT),这只是清除深度缓冲区,而不是颜色缓冲区。这基本上会在您绘制的最后一帧 canvas 上留下所有绘画,因此前几帧的剩余图像在屏幕上仍然可见。您应该清除两个缓冲区。

Because you only draw your colorful square each frame, you draw a new square over top of whatever was in the buffer from last time. If you're double-buffering (common in full-screen graphics modes, but not windowed graphics modes), you may find that you're drawing over top of a frame from two-frames-ago, which may produce a strobing/flashing marquee effect.

glClear 的参数称为 位掩码 。它使用掩码的每一位作为复选框来 select 是否应清除特定类型的缓冲区。指定 GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT 将在逻辑上 这些位一起创建一个数字,同时设置两个位 - 这就像选中两个复选框,说,“是的,请清除深度缓冲区,并且是的,还要清除颜色缓冲区”。

最多可以有四种不同的缓冲区,而不仅仅是颜色和深度。四个掩码字段是 GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BITGL_ACCUM_BUFFER_BITGL_STENCIL_BUFFER_BIT。其中每一个都是一个位字段值,一个具有单个二进制位集的数字,可以像 4 个单独的复选框一样逻辑或运算在一起。在您的应用程序中,您的渲染目标可能没有累加器缓冲区或模板缓冲区。一些渲染目标甚至不使用深度缓冲区。这完全取决于您最初是如何创建渲染缓冲区的。在您的情况下,您似乎有一个带有颜色和深度的缓冲区。因此,当需要清除缓冲区并准备渲染帧时,您需要确保选中两个框,有效地询问您的颜色和深度分量要清除的缓冲区。通过将 GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT 作为参数传递给 glClear.

来实现

此处位域的使用堪称典范,glClear 实际上在维基百科页面上用于 Mask_(computing) - Uses of bitmasks 来解释如何使用位掩码!