1

I'm trying to put a texture on my object (which is a 3D cube) but I'm not sure how to do it because I'm not getting the correct result. Also, I'm using SDL for this.

This is where I initialize everything:

void OpenGLWindow::initGL()
{
    .
    .
    .
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    shader = loadShaderProgram("simple.vert", "simple.frag");
    glUseProgram(shader);

    // ambient
    glUniform3f(glGetUniformLocation(shader, "objectColor"), 1.0f, 0.5f, 0.31f);
    glUniform3f(glGetUniformLocation(shader, "lightColor"), 1.0f, 1.0f, 1.0f);
    glUniform3fv(glGetUniformLocation(shader, "lightPos"), 1, &lightPos[0]);
    glUniform3f(glGetUniformLocation(shader, "viewPos"), 0.0f, 0.0f, 3.0f);

    int width, height, nrChannels;
    unsigned char *data = stbi_load("container.png", &width, &height, &nrChannels, 0);

    if (data) {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }

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

    stbi_image_free(data);

    // Set our viewing and projection matrices, since these do not change over time
    glm::mat4 projectionMat = glm::perspective(glm::radians(90.0f), 4.0f/3.0f, 0.1f, 10.0f);
    int projectionMatrixLoc = glGetUniformLocation(shader, "projectionMatrix");
    glUniformMatrix4fv(projectionMatrixLoc, 1, false, &projectionMat[0][0]);

    glm::vec3 eyeLoc(0.0f, 0.0f, 2.0f);
    glm::vec3 targetLoc(0.0f, 0.0f, 0.0f);
    glm::vec3 upDir(0.0f, 1.0f, 0.0f);
    glm::mat4 viewingMat = glm::lookAt(eyeLoc, targetLoc, upDir);
    int viewingMatrixLoc = glGetUniformLocation(shader, "viewingMatrix");
    glUniformMatrix4fv(viewingMatrixLoc, 1, false, &viewingMat[0][0]);

    // Load the model that we want to use and buffer the vertex attributes
    //geometry.loadFromOBJFile("sphere.obj");
    geometry.loadFromOBJFile("cube.obj");

    GLuint vertexbuffer;
    glGenBuffers(1, &vertexbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glBufferData(GL_ARRAY_BUFFER, 3*geometry.vertexCount()*sizeof(float), geometry.vertexData(), GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);

    GLuint texturebuffer;
    glGenBuffers(1, &texturebuffer);
    glBindBuffer(GL_ARRAY_BUFFER, texturebuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(geometry.textureCoordData()) * sizeof(glm::vec3), geometry.textureCoordData(), GL_STATIC_DRAW);
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, texturebuffer);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);

    glPrintError("Setup complete", true);
}

I'm reading an .obj file which has the coordinates of the vertices, normals and textures already using a class which stores each coordinates in an appropriate vector;

for e.g. std::vector vertices; std::vector textureCoords; std::vector normals;

How can I send the values inside the texture's vector to the shader?

This is my vertex shader:

in vec3 position;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoord;

uniform mat4 projectionMatrix;
uniform mat4 viewingMatrix;
uniform mat4 modelMatrix;

void main()
{
    vec4 transformedPosition = projectionMatrix * viewingMatrix * modelMatrix * vec4(position, 1.0f);
    gl_Position = transformedPosition;
    FragPos = vec3(modelMatrix * vec4(position, 1.0));
    Normal = mat3(transpose(inverse(modelMatrix))) * position;
}

Fragment shader:

out vec4 outColor;
in vec3 Normal;
in vec3 FragPos;

uniform vec3 lightPos;
uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 viewPos;

void main()
{
    float ambientStrength = 0.06;
    vec3 ambient = ambientStrength * lightColor;

    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    float specularStrength = 0.1;
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor;
    vec3 result = (ambient + diffuse + specular) * objectColor;
    outColor = vec4(result, 1.0);
}

What should I do to put the texture on the object (3D cube)?

This is what I'm getting so far:

enter image description here

The texture doesn't appear on all faces and when zooming in or out, some red, green or blue lines appear on the surface.

pk123
  • 101
  • 8
  • 1
    *"I'm not getting the correct result"* - You have to be more specific. What exactly is the issue? How do you read the *.obj* file? See [How do I sort the texture positions based on the texture indices given in a Wavefront (.obj) file?](https://stackoverflow.com/questions/51708275/how-do-i-sort-the-texture-positions-based-on-the-texture-indices-given-in-a-wave/51711080#51711080) and [Rendering meshes with multiple indices](https://stackoverflow.com/questions/11148567/rendering-meshes-with-multiple-indices/11148568#11148568) – Rabbid76 Aug 18 '18 at 13:50
  • I cannot get the texture on the object. I'm using a class which handles that, which I believe is the same as the method in the first link you posted. I have a vector which has all the texture coordinates. I think I should send that to the shader but I'm not sure how to. – pk123 Aug 18 '18 at 14:19

1 Answers1

1

Since you have 3 types of vertex coordinates you need 3 Vertex Shader inputs:

in vec3 a_position;
in vec2 a_uv;
in vec3 a_normal;

For each of these attribute you have define and enable an array of generic vertex attribute data by glVertexAttribPointer respectively glEnableVertexAttribArray. The index of the vertex attribute (which is the first parameter to glEnableVertexAttribArray and glVertexAttribPointer), can be get by glGetAttribLocation.

e.g.

GLint uv_inx = glGetAttribLocation(shader, "a_uv");
glEnableVertexAttribArray(uv_inx);
glBindBuffer(GL_ARRAY_BUFFER, texturebuffer);
glVertexAttribPointer(uv_inx, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);

But the modern way would be to define the vertex attribute indices in shader by useing Layout Qualifiers, which makes the glGetAttribLocation calls superfluous:

layout (location = 0) in vec3 a_position;
layout (location = 1) in vec2 a_uv;
layout (location = 2) in vec3 a_normal;

You have to pass the attributes from the vertex shader to the fragment shader. The final vertex shader and input to the fragment shader may look like this:

Vertex shader:

#version 400

layout (location = 0) in vec3 a_position;
layout (location = 1) in vec2 a_uv;
layout (location = 2) in vec3 a_normal;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoord;

uniform mat4 projectionMatrix;
uniform mat4 viewingMatrix;
uniform mat4 modelMatrix;

void main()
{
    vec4 transformedPosition = projectionMatrix * viewingMatrix * modelMatrix * vec4(a_position, 1.0f);
    gl_Position = transformedPosition;
    FragPos     = vec3(modelMatrix * vec4(a_position, 1.0));
    Normal      = mat3(transpose(inverse(modelMatrix))) * a_normal;
    TexCoord    = a_uv;
}

Fragment shader:

in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoord;

To load and look up the texture in the fragment shader, you need a texture sampler uniform. See Sampler. To the sampler uniform you have to assign the index of the texture unit - see glActiveTexture. In your case this is 0 for GL_TEXTURE0 (which is default).

Texture sampler uniform in the fragment shader:

uniform sampler2D u_texture;

Setting texture unit 0 to the texture sampler uniform, in c++ code:

GLint tex_loc = glGetUniformLocation(shader, "u_texture");
glUniform1i(tex_loc, 0); // 0 because of GL_TEXTURE0

Since GLSL version 4.2, this can done in shader code by a Binding point qualifier:

layout (binding = 0) uniform sampler2D u_texture;

The fragment shader may look like this:

#version 420

out vec4 outColor;

in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoord;

.....

layout (binding = 0) uniform sampler2D u_texture;

void main()
{
    vec4 textureColor = texture(u_texture, TexCoord);

    ....
    vec3 result = (ambient + diffuse + specular) * textureColor.rgb;
    outColor = vec4(result, 1.0);
}

Of course you can "modulate" the object color and the texture corot by multiplying them:

vec3 result = (ambient + diffuse + specular) * objectColor.rgb * textureColor.rgb;
Rabbid76
  • 142,694
  • 23
  • 71
  • 112
  • Sorry for replying a bit late, I was away. So I just added everything in your answer but I'm getting this error: `Shader load error: error: linking with uncompiled shadererror: linking with uncompiled shader` And I didn't add/remove anything more from any of my files. – pk123 Aug 18 '18 at 17:55
  • Ok, I think the issue is that I'm using `#version 330 core` and you put `#version 420 core` and `#version 400 core` in the fragment and vertex shaders respectively. Is there a way to go around that? – pk123 Aug 18 '18 at 17:59
  • I reverted back to `#version 330 core` and I'm simply using `uniform sampler2D u_texture;` instead of `layout (binding = 0) uniform sampler2D u_texture;`. That shouldn't really be an issue right? Now everything compiles but my object's color changes to blue but the texture doesn't show up on the object. Am I missing something? I will update my `initGL()` where I initialize everything in my question above. – pk123 Aug 18 '18 at 18:10
  • @pk123 As I mentiond in the answer, `layout (binding = 0)` is available since GLSL version 4.20. Since you use version 3.30, you have to delete `layout (binding = 0)`. Use only `uniform sampler2D u_texture;`. Since your texture is bound to texture unit 0, this will work. I changed the answer to clarify this. – Rabbid76 Aug 18 '18 at 18:16
  • yes its compiling without errors now. But the texturing is not correct. I've edited my question above. Please have a look if you can. – pk123 Aug 18 '18 at 18:21
  • @pk123 This brings us back to the first comment, which I posted to your question above. See [How do I sort the texture positions based on the texture indices given in a Wavefront (.obj) file?](https://stackoverflow.com/questions/51708275/how-do-i-sort-the-texture-positions-based-on-the-texture-indices-given-in-a-wave/51711080#51711080) – Rabbid76 Aug 18 '18 at 18:23
  • Thanks. But I doubt I will have time to do this now, especially for someone at my level. But thanks anyway :) – pk123 Aug 18 '18 at 18:46