Search code examples
javaopenglglslshaderlwjgl

Why is my texture being rendered black?


I am writing an OpenGL application using LWJGL 3. I am able to render solid colours just fine, but as soon as I try to introduce a texture sampler all I see is black.

Solid colour vs texture sampling

Specifically, the value returned by texture(texUnit, DataIn.texCoord) in the fragment shader is always vec4(0, 0, 0, 1).

I have even tried manually populating the buffer passed to glTexImage2D with all 255s, but I get the same result. I'm totally stumped at this point!

Initialisation:

private void initGL() {

    // Enable depth buffer
    GL11.glEnable(GL11.GL_DEPTH_TEST);

    // Set background colour
    GL11.glClearColor(0.2f, 0.2f, 0.4f, 0.0f);

    // Set viewport to the whole window
    GL11.glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

    // Enable alpha blending (transparency)
    GL11.glEnable(GL11.GL_BLEND);
    GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

    // Enable back-face culling
    GL11.glEnable(GL11.GL_CULL_FACE);
    GL11.glCullFace(GL11.GL_BACK);

    // Use linear filtering for texture scaling
    GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
            GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
    GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
            GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);

    // Clamp texture co-ordinates between 0 and 1
    GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
            GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
    GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
            GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);

    int errorCode = GL11.glGetError();
    if (errorCode != GL11.GL_NO_ERROR) {
        throw new RuntimeException(
                "OpenGL error " + String.valueOf(errorCode)
                + " during initialisation");
    }
}

Texture loading:

    String filename = "terrain.png";

    // Read image into a ByteBuffer
    IntBuffer w = BufferUtils.createIntBuffer(1);
    IntBuffer h = BufferUtils.createIntBuffer(1);
    IntBuffer comp = BufferUtils.createIntBuffer(1);
    ByteBuffer texelData = 
            STBImage.stbi_load(GFX_DIR + filename, w, h, comp, 4);
    if (texelData == null) {
        throw new RuntimeException("Error loading " + filename + ": " +
                STBImage.stbi_failure_reason());
    }
    int width = w.get();
    int height = h.get();

    // Generate texture ID
    terrainTexId = GL11.glGenTextures();
    // Pass our texture to the shader
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, terrainTexId);
    GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height,
            0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texelData);
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); // Deselect

    int errorCode = GL11.glGetError();
    if (errorCode != GL11.GL_NO_ERROR) {
        throw new RuntimeException(
                "OpenGL error " + String.valueOf(errorCode)
                + " loading texture: " + filename);
    }

Rendering:

    // Clear the screen and depth buffer
    GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

    // Use our shader program
    GL20.glUseProgram(Shaders.programId);

    // Set the projection matrix
    FloatBuffer fb = BufferUtils.createFloatBuffer(16);
    projection.setPerspective(
            camera.getFovY(),
            window.getAspectRatio(), 
            Camera.Z_NEAR,
            Camera.Z_FAR);
    GL20.glUniformMatrix4fv(
            Shaders.projectionLoc, false, projection.get(fb));

    // Set the model-view matrix.
    modelView.setLookAt(
            camera.getPos(),
            camera.getTarget(),
            camera.getUpVector());
    GL20.glUniformMatrix4fv(Shaders.modelViewLoc, false, modelView.get(fb));

    // Bind our texture to texture unit 0
    GL13.glActiveTexture(GL13.GL_TEXTURE0 + 0);
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, terrainTexId);
    // Tell the shader to sample from texture unit 0.
    // This is the default anyway.
    GL20.glUniform1i(Shaders.texUnitLoc, 0);

    // Pass the lighting information to the shader
    fb = BufferUtils.createFloatBuffer(3);
    GL20.glUniform3fv(Shaders.lightAmbientColourLoc,
            lighting.getAmbientColour().get(fb));
    GL20.glUniform1f(Shaders.lightAmbientIntensityLoc,
            lighting.getAmbientIntensity());
    GL20.glUniform3fv(Shaders.lightDiffuseColourLoc,
            lighting.getDiffuseColour().get(fb));
    GL20.glUniform3fv(Shaders.lightDiffuseAngleLoc,
            lighting.getDiffuseVector().get(fb));
    GL20.glUniform1f(Shaders.lightDiffuseIntensityLoc,
            lighting.getDiffuseIntensity());

    // Bind to the VAO that has all the information about the vertices
    GL30.glBindVertexArray(terrainSection.getVaoId());
    GL20.glEnableVertexAttribArray(Shaders.PARAM_VERTEX);
    GL20.glEnableVertexAttribArray(Shaders.PARAM_VERTEX_NORMAL);
    GL20.glEnableVertexAttribArray(Shaders.PARAM_MATERIAL_AMBIENT_COLOUR);
    GL20.glEnableVertexAttribArray(Shaders.PARAM_MATERIAL_DIFFUSE_COLOUR);
    GL20.glEnableVertexAttribArray(Shaders.PARAM_TEXTURE_COORDS);

    // Draw the vertices
    GL11.glDrawArrays(
            GL11.GL_TRIANGLES, 0, TerrainSection.NUM_VERTICES_FOR_BUFFERS);

    // Put everything back to default (deselect)
    GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
    GL20.glDisableVertexAttribArray(Shaders.PARAM_VERTEX);
    GL20.glDisableVertexAttribArray(Shaders.PARAM_VERTEX_NORMAL);
    GL20.glDisableVertexAttribArray(Shaders.PARAM_MATERIAL_AMBIENT_COLOUR);
    GL20.glDisableVertexAttribArray(Shaders.PARAM_MATERIAL_DIFFUSE_COLOUR);
    GL30.glBindVertexArray(0);
    GL20.glUseProgram(0);

Vertex Shader:

#version 330

uniform mat4 projection;
uniform mat4 modelView;
uniform vec3 lightAmbientColour;
uniform float lightAmbientIntensity;
uniform vec3 lightDiffuseAngle;
uniform vec3 lightDiffuseColour;
uniform float lightDiffuseIntensity;

layout(location = 0) in vec3 vertex;
layout(location = 1) in vec3 vertexNormal;
layout(location = 2) in vec3 materialAmbientColour;
layout(location = 3) in vec3 materialDiffuseColour;
layout(location = 4) in vec2 texCoord;

out Data {
    vec4 colour;
    vec2 texCoord;
} DataOut;

void main(void) {
    gl_Position = projection * modelView * vec4(vertex, 1.0);

    vec3 ambientComponent = lightAmbientIntensity * 
            (lightAmbientColour * materialAmbientColour);
    ambientComponent = clamp(ambientComponent, 0.0, 1.0);

    // The dot product gives us a measure of how "aligned" 2 vectors are,
    // between 0 and 1. If the light direction and the vertex normal are
    // well-aligned, the vertex should appear more brightly-lit.
    float dotProduct = dot(lightDiffuseAngle, vertexNormal);
    if (dotProduct < 0){
        dotProduct = 0;
    }
    vec3 diffuseComponent = lightDiffuseIntensity * dotProduct * 
            (lightDiffuseColour * materialDiffuseColour);
    diffuseComponent = clamp(diffuseComponent, 0.0, 1.0);

    vec3 colourResult = max(diffuseComponent, ambientComponent);
    DataOut.colour = vec4(colourResult, 1.0);
    DataOut.texCoord = texCoord;
}

Fragment Shader:

#version 330

uniform sampler2D texUnit;

in Data {
    vec4 colour;
    vec2 texCoord;
} DataIn;

out vec4 fragColour;

void main() {

    if (DataIn.colour.w == 0.0){
        // Discard transparent fragments, so they don't affect the depth buffer
        discard;
    }

    vec4 texColour = texture(texUnit, DataIn.texCoord);
    fragColour = DataIn.colour * texColour;
}

Solution

  • You should:

    • avoid blending for the moment
    • move glTexParameteri into the texture loading/initialization, since it is part of the texture. Or use a sampler
    • call glActiveTexture before glBindTexture in the texture loading
    • check Shaders.texUnitLoc != -1
    • in the rendering avoid either:
      • binding/unbinding the vao and add glVertexAttribPointer
      • calling glEnableVertexAttribArray and glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); since those are part of the vao
    • GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); when the vao is bound, will result into an ebo/ibo unbind in the bound vao
    • if you don't use any indices (glDrawArrays), you don't have to unbind any ebo/ibo
    • since you call all those enableVertexAttribArray you should also call glDisableVertexAttribArray(Shaders.PARAM_TEXTURE_COORDS);
    • define in your shader the same semantic, such as:

      • #define VERTEX 0
      • #define VERTEX_NORMAL 1
      • #define MATERIAL_AMBIENT_COLOUR 2
      • #define MATERIAL_DIFFUSE_COLOUR 3
      • #define TEXTURE_COORDS 4

      and assign them accordingly to the various locations

    • check texture coordinates
    • avoid the discard for the moment
    • write the pure color fragColour = texColour for the moment