在 GLSL 着色器中使用多个纹理时纹理被覆盖

Texture gets over written when using multiple textures in GLSL shader

我正在努力将多个纹理发送到单个着色器,但遇到一个奇怪的问题,即着色器中的两个采样器似乎都获得了相同的纹理数据。我知道还有很多其他的多纹理问题和答案(这里有一些我已经读过很多遍了 1, 2, )但是一些错误正在躲避我和我开始失去理智了。我相当有信心我已正确设置所有内容,但显然仍然存在一些问题。

所以,目前我有形状、Material、纹理和着色器 classes。我的形状 class 是执行实际绘制的父级。它有一个 material 成员,其中有一个着色器和一个纹理数组。 material class 抽签看起来像这样:

void Shape::Draw(GLenum mode, glm::mat4& model, glm::mat4& view, glm::mat4& proj)
{
    m_Material.Enable();
    m_Material.UpdateTransform(model, view, proj);
    glBindVertexArray(m_VAO);
    glDrawElements(mode, m_NumVerts, GL_UNSIGNED_INT, 0);
    m_Material.Disable();
}

这是我的全部 material class:

#include "pch.h"
#include "Material.h"

Material::Material() :
    m_LightService(LightService::GetInstance())
{
    OGLR_CORE_INFO("CREATING MATERIAL");
}

void Material::SetShader(std::string fileName)
{
    m_Shader.SetShaderFileName(fileName);
}

void Material::Enable() {
    m_Shader.Bind();
    for (const auto text : m_Textures) {
        text->Enable();
    }

    UploadUniforms();
}

void Material::Disable() {
    m_Shader.Unbind();
    for (const auto text : m_Textures) {
        text->Disable();
    }
}

void Material::AddTexture(std::string fileName, std::string typeName) {
    m_Textures.push_back(std::make_shared<Texture>(fileName, m_Shader.ShaderId(), typeName, m_Textures.size()));
}

void Material::UpdateTransform(glm::mat4& model, glm::mat4& view, glm::mat4& proj) {
    m_Shader.UploadUniformMat4("u_Projection", proj);
    m_Shader.UploadUniformMat4("u_View", view);
    m_Shader.UploadUniformMat4("u_Model", model);
}

void Material::UploadUniforms() {
    if (m_Shader.isLoaded()) {
        auto ambient = m_LightService->GetAmbientLight();
        m_Shader.UploadUniformFloat3("uAmbientLight", ambient.strength * ambient.color);
    }
}

void Material::SetMaterialData(std::shared_ptr<MaterialData> matData) {
    AddTexture(matData->ambient_texname, "t_Ambient"); // Wall
    AddTexture(matData->diffuse_texname, "t_Diffuse"); // Farm
}

可以看到,当material从我们正在渲染的.obj的.mtl文件中接收到material数据时,我们在Material::SetMaterialData函数中添加了两个新纹理对象到纹理列表。我们传入要加载的文件名和 glsl 统一采样器的字符串标识符。

启用 material 后,我们将启用着色器和每个纹理对象。

这是我的纹理 class。

#include "pch.h"
#include "Texture.h"
#include <stb_image.h>

Texture::Texture(std::string fileName, uint32_t programId, std::string uniformId, uint16_t unitId)
{
    m_FileName = ASSET_FOLDER + fileName;
    unsigned char* texData = stbi_load(m_FileName.c_str(), &m_Width, &m_Height, &m_NrChannels, 0);

    m_ProgramId = programId;
    
    glUniform1i(glGetUniformLocation(programId, uniformId.c_str()), unitId);
    glGenTextures(1, &m_TextureId);

    m_TextureUnit = GL_TEXTURE0 + unitId;
    glActiveTexture(m_TextureUnit);
    glBindTexture(GL_TEXTURE_2D, m_TextureId);

    if (texData)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_Width, m_Height, 0, GL_RGB, GL_UNSIGNED_BYTE, texData);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        OGLR_CORE_ERROR("Failed to load texture");
        throw std::runtime_error("Failed to load texture: "+ m_FileName);
    }

    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_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    stbi_image_free(texData);
    Disable();
}

void Texture::Enable()
{
    glActiveTexture(m_TextureUnit); // activate the texture unit first before binding texture
    glBindTexture(GL_TEXTURE_2D, m_TextureId);
}

void Texture::Disable()
{
    glBindTexture(GL_TEXTURE_2D, 0);
}

所以,我做的第一件事是从着色器中获取采样器制服的 ID,并将该样本绑定到我正在寻找的纹理单元。然后我们生成该纹理,激活相同的单元并将我生成的纹理绑定到它。我猜这是我在这里的某个地方犯了错误,但我似乎无法弄清楚如何。

这是我目前使用的着色器。

// vertex
#version 330 core

layout (location = 0) in vec3 a_Position;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoord;

out vec3 outNormal;
out vec2 TexCoord;

uniform mat4 u_Projection;
uniform mat4 u_View;
uniform mat4 u_Model;

void main() {
    vec4 worldPosition = u_Model * vec4(a_Position,1.0);
    gl_Position = u_Projection * u_View * worldPosition;
    outNormal = aNormal;
    TexCoord = aTexCoord;
}

// fragment
#version 330 core

out vec4 color;

in vec3 outNormal;
in vec2 TexCoord;

uniform sampler2D t_Ambient;
uniform sampler2D t_Diffuse;

void main() {
    if (TexCoord.x > 0.50)
    {
        //color = vec4(TexCoord.x, TexCoord.y, 0.0, 1.0);
        color = texture(t_Diffuse, TexCoord);
    }
    else
    {
        color = texture(t_Ambient, TexCoord);
    }
}

我希望三角形的每一半都有不同的纹理,但出于某种原因,两个采样器似乎都获得了相同的纹理。如果我在片段着色器中使用该颜色而不是纹理,我会得到一半的纹理和一半的颜色,所以它......至少有效......

我注意到的另一件我认为很奇怪的事情是,渲染的纹理似乎总是我添加的第一个纹理。如果我翻转 Material::SetMaterialData 中 AddTexture 调用的顺序,则会出现另一个纹理。也许有人可以向我解释为什么这很明显,但我本以为如果我以某种方式搞砸了我的纹理绑定,那将是第二个覆盖第一个但是嘿¯_(ツ)_/¯ 我是准备接受这方面的教育。

编辑

我很抱歉,但显然不清楚着色器是否已正确绑定。

在 Shape::Draw 函数的开头我们调用 m_Material.Enable();

开头调用 m_Shader.Bind(); 依次调用 glUseProgram(m_ProgramId);

这发生在任何纹理创建流程之前,因此在我们设置制服之前正确绑定了着色器。

如有任何混淆,我们深表歉意。

glUniform1i 仅为当前启用的着色器绑定制服:

glUniform operates on the program object that was made part of current state by calling glUseProgram.

好像你没有在 glUniform1i(glGetUniformLocation(programId, uniformId.c_str()), unitId); 之前调用 glUseProgram(如果没有 SetMaterialData 的调用者代码,我不能肯定地说)并且制服实际上没有绑定到着色器的 unitId

所以试试这个:

glUseProgram(programId);
glUniform1i(glGetUniformLocation(programId, uniformId.c_str()), unitId);
glGenTextures(1, &m_TextureId);