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

Posted

技术标签:

【中文标题】为啥 OpenGL 中的 3D 投影可以工作,但会留下痕迹?【英文标题】:Why is 3D projection in OpenGL working, but leaving a trail behind?为什么 OpenGL 中的 3D 投影可以工作,但会留下痕迹? 【发布时间】:2021-04-29 13:20:51 【问题描述】:

我刚刚从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)。

我的代码中的某些键可能被颠倒了,这在所有方面都是糟糕的代码......抱歉。

这是我在移动立方体后拍摄的图像(或者准确地说是相机透视图)。需要注意的一件主要事情是,它似乎在跟踪之外运行良好。

【问题讨论】:

捂脸?我看不到GL_COLOR_BUFFER_BITglClear 您只清除了深度缓冲区。您可以将它们放在一起glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @Wyck 执行 glClear(ANYTHING) 是否会从屏幕上清除该内容? documentation for glClear 是否不清楚它的作用? ;) @Wyck 不,不清楚,不知道为什么我没有正确理解这一点,但我不是。我不知道为什么当我习惯阅读 Wikipedia 上的枯燥数学时,我不理解一个简单的文档。我以为 glClear(GL_DEPTH_BUFFER_BIT) 会从当前帧中清除深度功能,现在我认为正好相反。 【参考方案1】:

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

glClear(GL_DEPTH_BUFFER_BIT);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

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

【讨论】:

打败我——我只是在写这个。 能否详细说明它的作用? @ShambhavGautam glClear 清除指定的缓冲区。缓冲区用位掩码指定。 GL_COLOR_BUFFER_BIT 表示清除当前启用颜色写入的缓冲区【参考方案2】:

简短的回答是你需要改变:

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 缓冲区和颜色缓冲区。 这就是你想要做的。你想用一个新的黑色背景开始每一帧,然后在它上面绘制你的内容。

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

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

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

因为您每帧只绘制彩色方块,所以您在上次缓冲区中的任何内容之上绘制了一个新方块。如果您使用双缓冲(在全屏图形模式中很常见,但在窗口图形模式中不常见),您可能会发现您正在从两帧前的帧顶部绘制,这可能会产生频闪/闪烁选框效果。

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

最多可以有四种不同类型的缓冲区,而不仅仅是颜色和深度。四个掩码字段是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 来解释如何使用位掩码!

【讨论】:

以上是关于为啥 OpenGL 中的 3D 投影可以工作,但会留下痕迹?的主要内容,如果未能解决你的问题,请参考以下文章

如何在opengl中计算给定3D点及其2D屏幕位置的投影/模型视图矩阵

如何使用 OpenGL 正确投影和扭曲 3d 点

将 OpenGL 绘制到离屏位图

OPENGL若干重要基础概念

使用 OpenGL,如何正确使用 gluOrtho2D 和默认投影?

OpenGL - 003坐标系简单理解