使用现代OpenGL渲染纹理

3
我正在使用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");

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


哦,确保你的着色器成功编译。你发布的那些应该无法编译。内置变量如 gl_ProjectionMatrix 在核心配置文件中不可用。 - Reto Koradi
@RetoKoradi 嗯,如果我从glFragColor中移除texColor变量,那么(无纹理的)多边形会以我设置的颜色渲染,但一旦我添加了texColor变量,就完全没有任何渲染。至于着色器,它们编译没有问题,仍然可以工作,我认为内置函数根本不起作用。我移除了内置函数(和texColor变量一段时间,以便多边形能够渲染),一切都正常工作,所以我将只移除它们,因为它们没有任何作用。 - Gyo
1
嗯,你在插入任何数据之前调用glGenerateMipmap有点奇怪,但这应该不是问题,因为你实际上并没有使用mipmaps。你检查过SOIL_load_image返回的非空指针以及imagewimagew的值是否正确吗? - PeterT
@PeterT 这个奇怪吗?就像我说的,我不太确定自己在做什么,因为通常教程没有解释何时调用函数以及为什么要这样调用。嗯,我不太确定如何检查它是否返回 nullptr,但我相信我可以弄清楚,不过我已经检查了 imagew 和 imageh,它们是调用 SOIL_load_image 后图像文件的正确值,所以我可以肯定它至少加载了图像,至少我认为是这样。 - Gyo
@Gyo:PXSprite::render中的xywidthheight的值范围是多少?我怀疑你可能在使用像素坐标,而你的顶点着色器只会传递(除了y之外),这意味着你的精灵位置必须是剪辑空间坐标(并且y可能有一些奇怪的情况)。 - datenwolf
显示剩余3条评论
2个回答

1
最好简化您的问题以追踪问题。例如,您可以分配一个width*height*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);

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

GL_TEXTURE0 是默认的活动纹理单元,而 0 是统一变量的默认值。因此,虽然在建立适用于多个纹理的模式时进行这些调用是很好的,但省略这些部分不会影响简单示例的运行。 - Reto Koradi
嗯...我刚刚检查了一下,规范中确实非常清楚地说明采样器默认使用第一个纹理单元。 我唯一能看到可能有问题的另一件事是在上传纹理数据之前生成MIP贴图。这可能会从未初始化的数据创建纹理,而采样器可能正在使用这些纹理? - Patrik H
这可能听起来很愚蠢,但我不确定如何使用UV坐标给片段上色。我尝试了所有的方法,但没有任何变化。我觉得问题出在绘制上,因为我知道图像加载是正确的,但无论是着色器中的代码还是顶点数组的代码中都有些问题。对于这个问题我感到非常无知,真是抱歉。 - Gyo
你可以这样做:glFragColor = vec4(outTexCoord, 0.0, 1.0)。实际上,现在我看了一下那段代码,你能从你的片段着色器中删除 out vec4 glTexColor; 这行吗?我觉得这可能会阻止 glFragColor 绑定到第一个输出。 - Patrik H
我尝试将glFragColor更改为那个,但没有任何变化,我还删除了glTexColor,但窗口仍然是黑色的。难道我真的太笨了,无法让它正常工作吗? - Gyo
顺便说一句,谢谢你的回答。虽然它没有解决问题,因为我忘记绑定VAO,但它确实帮助我更好地理解了着色器和绘制纹理的过程,所以还是要感谢你! - Gyo

1
我注意到这段代码中存在一些问题。部分已经在其他答案/评论中提到,部分尚未提及。

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()调用之前发生,因为显然必须启用属性以进行绘制。

与其仅将它们移动到绘制调用之前,还不如将它们放置在属性设置代码中,沿着glVertexAttribPointer()调用。已在VAO中跟踪启用/禁用状态,因此无需每次进行这些调用。只需在设置期间设置所有此状态,然后在绘制调用之前简单地绑定VAO即可再次设置所有必要状态。

不必要的glVertexAttribPointer()调用

这个调用在render()方法中是多余的,但并不会造成任何损害:

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

再次强调,此状态在VAO中跟踪,因此在设置过程中进行一次调用就足够了,一旦解决了上述问题。


非常感谢!我觉得主要问题是我忘记了绑定调用,我真的很傻错过了那个,所以非常感谢你指出来。我也按照你建议做了其他事情!现在我可以渲染纹理了,但是我不能再绘制多边形了。我之前是用VAOs来绘制它们的,但是现在 glFragColor = texColor*outColor; 而不是 glFragColor = outColor; 它们就不会绘制了,或者至少我看不到它们了。我猜想为了绘制具有自己颜色的多边形,我需要创建一个单独的片段着色器,但如果我错了,请告诉我。再次感谢你! - Gyo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接