使用现代 OpenGL 渲染纹理

Posted

技术标签:

【中文标题】使用现代 OpenGL 渲染纹理【英文标题】:Rendering Textures with Modern OpenGL 【发布时间】:2016-07-28 05:54:38 【问题描述】:

所以我目前正在使用带有 OpenGL (glew) 的 SDL2 制作游戏,并使用 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 这样的内置插件在核心配置文件中不可用。 @RetoKoradi 好吧,如果我从 glFragColor 中删除 texColor 变量,(未纹理化的)多边形会呈现为我设置的颜色,但是一旦我添加了 texColor 变量,就什么也不会呈现。关于着色器,它们不会编译失败,它仍然可以工作,我认为内置插件根本没有做任何事情。我删除了内置插件(以及一秒钟的 texColor 变量以使多边形渲染)并且一切正常,所以我将删除它们,因为它们什么都不做。 在插入任何数据之前调用glGenerateMipmap 很奇怪,但这应该不是问题,因为您实际上并没有使用mipmap。检查SOIL_load_image 是否返回非空指针,imagewimagew 的值是否正确? @PeterT 奇怪吗?就像我说的那样,我不完全确定自己在做什么,因为我通常发现教程没有解释什么时候调用事物以及为什么我当时调用它们。好吧,我不完全确定如何检查它是否返回 nullptr,我想我确定我可以弄清楚,但我检查了 imagew 和 imageh,它们是调用 @ 后图像文件的正确值987654331@,所以我可以肯定地假设它正在正确加载图像,至少我认为。 【参考方案1】:

我在这段代码中发现了一些问题。部分已经在其他答案/cmets 中提到,部分没有。

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() 调用之后。它根据当前纹理内容生成mipmap,这意味着您需要先指定纹理数据。

此错误不会立即对您当前的代码造成损害,因为您实际上并未使用 mipmap。但是,如果您曾经将 GL_TEXTURE_MIN_FILTER 值设置为使用 mipmapping,这将很重要。

glEnableVertexAttribArray() 放错地方了

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

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

不必要的glVertexAttribPointer()调用

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

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

同样,这个状态在 VAO 中被跟踪,所以在设置过程中进行一次调用就足够了,一旦你解决了上面列出的问题。

【讨论】:

非常感谢!我认为主要问题是我忘记了绑定调用,而我错过了这一点很愚蠢,非常感谢您指出这一点。我也做了你建议的其他事情!我现在可以渲染纹理,但不能再绘制多边形了。我正在用 VAO 绘制它们,但现在 glFragColor = texColor*outColor; 而不是 glFragColor = outColor; 它们不绘制,或者至少我再也看不到它们了。我假设要使用自己的颜色绘制多边形,我需要创建一个单独的片段着色器,但如果我错了,请告诉我。但再次,非常感谢你!【参考方案2】:

最好简化您的问题以跟踪问题。例如,您可以分配一个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

【讨论】:

GL_TEXTURE0 是默认的活动纹理单元,0 是统一变量的默认值。因此,尽管进行这些调用以建立仍然适用于多个纹理的模式是件好事,但省略这些部分不应该阻止一个简单的示例运行。 hmm...我刚刚检查过,在规范中确实很清楚采样器默认为第一个纹理单元。我认为唯一有问题的另一件事是在上传纹理数据之前生成 MIP。这实际上可能会从未初始化的数据中创建纹理,而这正是采样器可能正在使用的? 这听起来可能很愚蠢,但我不确定如何使用 uv 坐标为片段着色。我尝试了所有这些事情,但没有任何改变。我觉得问题出在绘图中,因为我知道图像正在正确加载,但是我的代码中的着色器或顶点数组的代码中有问题。很抱歉对此一无所知。 实际上,您可以执行glFragColor = vec4(outTexCoord, 0.0, 1.0) 之类的操作,现在我查看了该代码,您可以从片段着色器中删除out vec4 glTexColor; 行吗?我认为这可能会阻止 glFragColor 绑定到第一个输出。 我尝试将 glFragColor 更改为此,但没有任何改变,我删除了glTexColor,但窗口仍然是黑色的。我只是因为无法使其正常工作而愚蠢吗?

以上是关于使用现代 OpenGL 渲染纹理的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL渲染纹理全白

现代 OpenGL,在对象上获得白色纹理

OpenGL渲染白色纹理错误

OpenGL不使用纹理渲染

使用 VAO/VBO 进行 OpenGL 模型/纹理渲染

无法使用 OpenGL 渲染 FBX 导入的纹理模型