1
votes

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;
}
1
I can't see your sampler object ( glBindSampler, glGenSamplers, etc.). Did I miss it in the source?Robinson
Try adding glGenerateMipmap(GL_TEXTURE_2D); after glTexImage2dsamgak
Apparently samplers are a new feature that I am not currently using, but I will look into them. I'm not using mipmaps either as I didn't think I needed them, but it sounds like they're probably a good idea too.Dan

1 Answers

4
votes

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