0
votes

I followed the tutorial on https://learnopengl.com/Lighting/Lighting-maps and got some weird errors when using multiple textures (one for specular and one for diffuse lighting). When using only one Texture everything works fine but after using two textures, the cube renders either only black or only the specular texture is applied.

I searched through all similar questions on stackoverflow and read through the OpenGL documentation but still can't figure out why the second texture is not working.

This is the render-loop part of my main.cpp that is creating the cube that is supposed to be rendered with the two textures. The shaders and textures all load and compile fine.

// Load textures
    Texture groundTexture = Texture("GROUND", TEXTURE_DIR+"png/container2.png");
    Texture groundSpecular = Texture("SPECULAR", TEXTURE_DIR+"png/container2_specular.png");

    // Load shaders
    Shader textureShader = Shader("SIMPLE_TEXTURE", SHADER_DIR+"vertex/texture_lighted.vs", SHADER_DIR+"fragment/texture_lighted.fs");
    Shader lampShader = Shader("WHITE", SHADER_DIR+"vertex/basic.vs", SHADER_DIR+"fragment/white.fs");

    // projection matrix
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

    // shader configuration
    // --------------------
    textureShader.use();
    // Get the uniform variables location.
    GLuint diffLocation = glGetUniformLocation(textureShader.ID, "material.diffuse");
    GLuint specLocation  = glGetUniformLocation(textureShader.ID, "material.specular");

Another weird thing is that if I change the 0 and 1 to groundTexture.ID and groundSpecular.ID, at least the specular part renders, even though glUniform1i expects the unit numbers here and not the Texture-IDs.

    // Then bind the uniform samplers to texture units:
    glUseProgram(textureShader.ID);
    glUniform1i(diffLocation, 0);
    glUniform1i(specLocation, 1);


    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // Calc time
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // be sure to activate shader when setting uniforms/drawing objects
        textureShader.use();
        textureShader.setVec3("light.position", lightPos);
        textureShader.setVec3("viewPos", camera.Position);
        // light properties
        textureShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
        textureShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f);
        textureShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
        // material properties
        textureShader.setFloat("material.shininess", 64.0f);

        // view/projection transformations
        glm::mat4 view = camera.getViewMatrix();
        textureShader.setMat4("projection", projection);
        textureShader.setMat4("view", view);

        // world transformation
        glm::mat4 model = glm::mat4(1.0f);
        textureShader.setMat4("model", model);

        // bind texture-maps
        glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0
        glBindTexture(GL_TEXTURE_2D, groundTexture.ID);
        glActiveTexture(GL_TEXTURE0 + 1); // Texture unit 1
        glBindTexture(GL_TEXTURE_2D, groundSpecular.ID);
        glActiveTexture(GL_TEXTURE0 + 0); // Texture unit 0

        // render the cube
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);

For completeness sake, here's also my shader-class and texture-class:

#include "shader.h"
#include <stdlib.h>
#include <string.h>
#include <glad/glad.h>
#include <iostream>
#include <glm/ext.hpp>
#include <fstream>
#include <sstream>

/*
 * Creates a new Shader.
 * name = name of the shader
 * vShader = vertex shader
 * fShader = fragment shader
 */
Shader::Shader(std::string name, std::string vShaderPath, std::string fShaderPath) {
    this->name = name;
    this->vShaderPath = vShaderPath;
    this->fShaderPath = fShaderPath;
    compileAndLink();
}

Shader::Shader(const Shader& s) {
    this->name = s.name;
    this->vShaderPath = s.vShaderPath;
    this->fShaderPath = s.fShaderPath;
    compileAndLink();
}

void Shader::compileAndLink() {

    std::string vShader, fShader;
    std::ifstream v_inFile, f_inFile;
    std::stringstream v_strStream, f_strStream;

    // Read File for vertexShader
    v_inFile.open(vShaderPath);
    if (!v_inFile.is_open()) {
        std::cout << "ERROR LOADING VERTEX SHADER FILE: " << vShaderPath << std::endl;
    } else {
        v_strStream << v_inFile.rdbuf();
        vShader = v_strStream.str();
    }

    // Read File for fragmentShader
    f_inFile.open(fShaderPath);
    if (!f_inFile.is_open()) {
        std::cout << "ERROR LOADING FRAGMENT SHADER FILE: " << fShaderPath << std::endl;
    } else {
        f_strStream << f_inFile.rdbuf();
        fShader = f_strStream.str();
    }

    // Compile and link
    const char* vertex_shader = vShader.c_str();
    const char* fragment_shader = fShader.c_str();

    // vertex shader
    int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertex_shader, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    // fragment shader
    int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragment_shader, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    // store reference in ID
    ID = glCreateProgram();

    // link shaders
    glAttachShader(ID, vertexShader);
    glAttachShader(ID, fragmentShader);
    glLinkProgram(ID);

    // check for linking errors
    glGetProgramiv(ID, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(ID, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }

    // clean up
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

}

/*
 * Tell OpenGL to use this shader-program.
 */
void Shader::use() {
    glUseProgram(ID);
}

and

#include <glad/glad.h>
#include <iostream>
#define STB_IMAGE_IMPLEMENTATION
#include "../image-loader/stb_image.h"
#include "texture.h"

Texture::Texture(std::string name, std::string path) {
    this->name = name;
    this->path = path;
    createTexture();
}

Texture::Texture(const Texture& t) {
    this->name = t.name;
    this->path = t.path;
    createTexture();
}

void Texture::createTexture() {
    unsigned int texture;
    glGenTextures(1, &texture); // generate one texture

    // Load file
    int width, height, nrChannels;
    unsigned char *data = stbi_load(this->path.c_str(), &width, &height, &nrChannels, 0);

    if (data) {
        GLenum format;
        if (nrChannels == 1)
            format = GL_RED;
        else if (nrChannels == 3)
            format = GL_RGB;
        else if (nrChannels == 4)
            format = GL_RGBA;

        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);

        // Set Texture Parameters
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // x
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // y
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        stbi_image_free(data); // clean up

    } else {
        std::cout << "Failed to load texture" << std::endl;
    }

    this->ID = texture;
}

void Texture::use() {
    glBindTexture(GL_TEXTURE_2D, this->ID);
}

I am using OpenGL 4.4, initialized like below and glad to load the OpenGL function pointers.

glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

Thanks in advance.

EDIT: Here are the two shaders (obviously important): texture_lighted.vs (vertex shader)

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0));
    TexCoords = aTexCoords;
    // transpose normal vectors with normal matrix of model matrix
    Normal = mat3(transpose(inverse(model))) * aNormal;
}

And texture_lighted.fs (fragment shader)

#version 330 core
out vec4 FragColor;

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;

uniform vec3 viewPos;
uniform Material material;
uniform Light light;

void main()
{
    // ambient
    vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;

    // diffuse
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(light.position - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;

    // specular
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * spec * texture(material.specular, TexCoords).rgb;

    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);
} 

1

1 Answers

3
votes

After the name (value) of the texture is generated by glGenTexture, the named texture has to be bound to a texturing target by glBindTexture, before the texture image is specified by glTexImage2D.
glTexImage2D defines the texture images for the texture, which is currently bound to the specified target:

glGenTextures(1, &texture); 

// [...]

glBindTexture(GL_TEXTURE_2D, texture); 
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);