I am working on sending multiple textures to a single shader and am having a weird issue where both samplers in the shader seem to get the same texture data. I know there are a lot of other multiple texture questions and answers out there (Here are a few I've read multiple times already 1, 2, 3) but some bug is eluding me and I'm starting to lose my marbles. I am fairly confident I have everything setup correctly but obviously there is still some issue.
So, currently I have Shape, Material, Texture, and Shader classes. My shape class is the parent that performs the actual draw. It has a material member which has a shader and an array of textures. The material class draw looks like this:
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();
}
Here is my whole 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
}
As you can see, when the material receives the material data from the .mtl file of the .obj we are rendering in the Material::SetMaterialData function, we are adding two new texture objects to the list of textures. We are passing in the filename to be loaded and the string identifier of the glsl uniform sampler.
When the material is enabled, we are enabling the shader and each of the texture objects.
Here is the wip of my Texture 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);
}
So, the first thing I do is grab the ID of the sampler uniform from the shader and bind that sample to the texture unit I'm looking for. We then generate that texture, activate the same unit and bind my generated texture to it. I'm guessing that it is somewhere in here that I have blundered but I can't seem to figure out how.
Here are my shaders as they currently stand.
// 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);
}
}
I am expecting each half of my triangle to have different textures but for some reason both samplers seem to get the same texture. If I use that color in the frag shader instead of the texture I get half texture and half the color so it... that at least works...
The other thing that I noticed that I thought was weird was that the texture that gets rendered seems to always be the first one I add. If I flip the order of the AddTexture calls in Material::SetMaterialData the other texture appears. Maybe someone can explain to my why that would be obvious but I would have expected that if I had somehow goofed the binding of my textures that it would be the second one overwriting the first but hey ¯_(ツ)_/¯ I'm ready to be educated on that one.
Edit
I apologize but apparently it was not clear that the shader is being properly bound.
At the beginning of the Shape::Draw function we are calling m_Material.Enable();
The beginning of which calls m_Shader.Bind();
which in turn calls glUseProgram(m_ProgramId);
This occurs before any of the texture creation flow so the shader is properly bound before we are setting the uniforms.
Apologies for any confusion.
Material::AddTexture
! – Rabbid76