使用现代OpenGL渲染纹理

Rendering Textures with Modern OpenGL

本文关键字:纹理 OpenGL      更新时间:2023-10-16

所以我目前正在制作一个游戏,使用SDL2和OpenGL(glew),并使用SOIL加载图像,我正在使用现代OpenGL技术和顶点阵列对象和着色器等等。我目前只想在窗口中渲染一个纹理,但似乎做不到。我查阅了许多教程和解决方案,但似乎不明白该如何正确处理。我不确定是着色器出了问题,还是我的代码本身出了问题。我会在下面发布所有必要的代码,如果需要,我很乐意提供。欢迎任何答案和解决方案。对于未来的上下文,为了方便起见,我有一个类用于存储VAO的数据。代码:

以下是加载纹理的代码:

 void PXSprite::loadSprite() {
     glGenTextures(1, &textureID);
     glBindTexture(GL_TEXTURE_2D, textureID);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
     glGenerateMipmap(GL_TEXTURE_2D);
     int imagew, imageh;
     //The path is a class variable, and I haven't received any errors from this function, so I can only assume it's getting the texture file correctly.
     unsigned char* image = SOIL_load_image(path.c_str(), &imagew, &imageh, 0, SOIL_LOAD_RGB);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imagew, imageh, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
     SOIL_free_image_data(image);
     glBindTexture(GL_TEXTURE_2D, 0);
     //Don't worry about this code. It's just to keep the buffer object data.
     //It works properly when rendering polygons.
     spriteVAO.clear();
     spriteVAO.addColor(PXColor::WHITE());
     spriteVAO.addColor(PXColor::WHITE());
     spriteVAO.addColor(PXColor::WHITE());
     spriteVAO.addColor(PXColor::WHITE());
     spriteVAO.addTextureCoordinate(0, 0);
     spriteVAO.addTextureCoordinate(1, 0);
     spriteVAO.addTextureCoordinate(1, 1);
     spriteVAO.addTextureCoordinate(0, 1);
     glGenVertexArrays(1, &spriteVAO.vaoID);
     glGenBuffers(1, &spriteVAO.posVBOid);
     glBindBuffer(GL_ARRAY_BUFFER, spriteVAO.posVBOid);
     glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*12, nullptr, GL_STATIC_DRAW);
     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
     glGenBuffers(1, &spriteVAO.colVBOid);
     glBindBuffer(GL_ARRAY_BUFFER, spriteVAO.colVBOid);
     glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 16, &spriteVAO.colors[0], GL_STATIC_DRAW);
     glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, 0);
     glGenBuffers(1, &spriteVAO.texVBOid);
     glBindBuffer(GL_ARRAY_BUFFER, spriteVAO.texVBOid);
     glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, &spriteVAO.texCoords[0], GL_STATIC_DRAW);
     glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
     glBindTexture(GL_TEXTURE_2D, 0);
}

这是我渲染纹理的代码:

void PXSprite::render(int x, int y) {
    spriteVAO.clear(PXVertexArrayObject::positionAttributeIndex);
    spriteVAO.addPosition(x, y);
    spriteVAO.addPosition(x+width, y);
    spriteVAO.addPosition(x, y+height);
    spriteVAO.addPosition(x+width, y+height);
    glBindTexture(GL_TEXTURE_2D, textureID);
    glBindVertexArray(spriteVAO.vaoID);
    glBindBuffer(GL_ARRAY_BUFFER, spriteVAO.posVBOid);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat)*12, &spriteVAO.positions[0]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);
    glBindTexture(GL_TEXTURE_2D, 0);
}

这是我的顶点着色器:

#version 330 core
in vec3 in_Position;
in vec4 in_Color;
in vec2 in_TexCoord;
out vec4 outPosition;
out vec4 outColor;
out vec2 outTexCoord;
void main() {
gl_Position = vec4(in_Position.x, in_Position.y*-1.0, in_Position.z, 1.0);
outTexCoord = in_TexCoord;
outColor = in_Color;
}

这是我的碎片着色器:

#version 330 core
in vec2 outTexCoord;
in vec4 outColor;
out vec4 glFragColor;
out vec4 glTexColor;
uniform sampler2D pxsampler;
void main() {
    vec4 texColor = texture(pxsampler, outTexCoord);
    //This outputs the color of polygons if I don't multiply outColor by texColor, but once I add texColor, no colors show up at all.
    glFragColor = texColor*outColor;
}

最后,这里有一些代码可以参考我在加载着色器时正确使用的VAO属性指针:

glBindAttribLocation(ProgramID, 0, "in_Position");
glBindAttribLocation(ProgramID, 1, "in_Color");
glBindAttribLocation(ProgramID, 2, "in_TexCoord");

欢迎任何帮助或建议。如果需要任何额外的代码,我会添加它。如果这个问题看起来多余,我很抱歉,但我找不到任何东西来帮助我。如果有人能向我解释渲染纹理的代码是如何工作的,那就太好了,因为从我读过的教程中,他们通常不会解释什么做什么,这样我就知道如何再次做,所以我不清楚我到底在做什么。再次感谢您的帮助。谢谢

我在这段代码中发现了一些问题。部分已在其他答复/评论中提及,部分未提及。

VAO绑定

主要问题是,在设置顶点属性时,VAO未绑定。请参阅以下代码序列:

glGenVertexArrays(1, &spriteVAO.vaoID);
glGenBuffers(1, &spriteVAO.posVBOid);
glBindBuffer(GL_ARRAY_BUFFER, spriteVAO.posVBOid);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*12, nullptr, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

在这里,您创建一个VAO(或者更准确地说,VAO的id),但不绑定它。glVertexAttribPointer()调用设置的状态存储在当前绑定的VAO中。这个调用实际上应该在核心配置文件上下文中给您一个GL_INVALID_OPERATION错误,因为在进行调用时需要绑定VAO

要解决此问题,请在创建id:后绑定VAO

glGenVertexArrays(1, &spriteVAO.vaoID);
glBindVertexArray(spriteVAO.vaoID);
...

glGenerateMipmap()位置错误

正如注释中所指出的,此调用属于glTexImage2D()调用之后的。它根据当前纹理内容生成mipmaps,这意味着您需要首先指定纹理数据。

这个错误不会对您当前的代码造成直接的危害,因为您实际上并没有使用mipmaps。但是,如果您将GL_TEXTURE_MIN_FILTER值设置为使用mipmapping,这将很重要。

glEnableVertexAttribArray()位置错误

这需要在glDrawArray()调用之前发生,因为显然必须为绘图启用属性。

与其只在draw调用之前移动它们,不如将它们与glVertexAttribPointer()调用一起放置在属性设置代码中。启用/禁用状态在VAO中进行跟踪,因此无需每次都进行这些调用。只需在设置过程中设置一次所有这些状态,然后在draw调用之前简单地绑定VAO,即可再次设置所有必要的状态。

不必要的glVertexAttribPointer()调用

无害,但render()方法中的此调用是多余的:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);

同样,此状态在VAO中被跟踪,因此一旦您解决了上面列出的问题,在设置期间进行一次此调用就足够了。

最好简化问题以跟踪问题。例如,您可以不加载实际的图像,而是分配一个width*heigth*3缓冲区,并用127填充它,以获得灰色图像(或粉色图像,使其更明显)。试着用uv坐标为碎片着色,而不是使用采样器来查看这些值是否设置正确。

我认为您应该在绘制调用之前启用顶点属性数组:

glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

此外,在将纹理绑定到纹理单元之前,您应该指定要使用的单元,而不是依赖默认值:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID);

并确保您将采样器绑定到纹理单元:

glUniform1i(glGetUniformLocation(ProgramID, "pxsampler"), 0);

对于采样器统一,我们直接设置索引,而不是GL_TEXTURE*值。因此GL_TEXTURE0在设置统一时需要一个索引0,而GL_TEXTURE0