Search code examples
pythonopenglglsltexture-mappingpyopengl

How to add texture to a triangle?


I'm trying to add woody texture to the triangle. Code works fine for only triangle. But raises error while I try to add texture. I think the problem is in the GLSL or in creating EBO/VBO (not sure). The whole screen remains black.

Here is the whole code. What am I doing wrong here?

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL import shaders

import glfw
import numpy as np
from PIL import Image

VERTEX_SHADER = """
#version 330
    layout (location = 0) in vec4 position;

    in vec2 InTexCoords;
    out vec2 OutTexCoords;
    
    void main(){
    gl_Position = position;
    OutTexCoords = InTexCoords;
    }
"""

FRAGMENT_SHADER = """
#version 330
    out vec4 FragColor;
    uniform vec4 triangleColor;
    
    in vec2 OutTexCoords;
    uniform sampler2D sampleTex;

    void main() {
    FragColor = texture(sampleTex,OutTexCoords);
    }
"""
shaderProgram = None

def initialize():
    global VERTEXT_SHADER
    global FRAGMENT_SHADER
    global shaderProgram

    #compiling shaders
    vertexshader = shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER)
    fragmentshader = shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)

    #creating shaderProgram
    shaderProgram = shaders.compileProgram(vertexshader, fragmentshader)

    #vertex and indices data
                #triangle          #texture
    vertices = [-0.5, -0.5, 0.0,    0.0,0.0,
                 0.5, -0.5, 0.0,    1.0,0.0,
                 0.0, 0.5, 0.0,     0.5,1.0]
    
    indices = [0,1,2]

    vertices = np.array(vertices, dtype=np.float32)
    indices = np.array(vertices, dtype=np.float32)

    #add vertices to buffer
    VBO = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, VBO)
    glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)

    #add indices to buffer
    EBO = glGenBuffers(1)
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO)
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)
  
    position = 0
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
    glEnableVertexAttribArray(position)

    texCoords = 1
    glBindAttribLocation( shaderProgram, texCoords, 'InTexCoords')
    glVertexAttribPointer(texCoords,2, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))
    glEnableVertexAttribArray(texCoords)

    #creating texture
    texture = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, texture)
    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)

    image = Image.open("wood.jpg")
    img_data = np.array(list(image.getdata()), np.uint8)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
    
    
def render(window):
    global shaderProgram
  
    glClearColor(0, 0, 0, 1)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
  
    glUseProgram(shaderProgram)

    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, None)

     
    glUseProgram(0)
    glfw.swap_buffers(window)

def main():
    glfw.init()
    window = glfw.create_window(640, 640,"EXAMPLE PROGRAM",None,None)
    glfw.make_context_current(window)
    initialize()

    while not glfw.window_should_close(window):
        glfw.poll_events()
        render(window)
            
    glfw.terminate()


if __name__ == '__main__':
    main()

I was trying to follow this tutorial learnopengl. But the tutorial is in C++. Besides, I had slightly different approach. I'm not adding color codes in vertices. But I don't think it is the problem in the way of adding the texture.


Solution

  • The stride argument of glVertexAttribPointer specifies the byte offset between consecutive generic vertex attributes. Your attributes consist of vertex coordinates with 3 components and texture coordinates with 2 components. Hence your stride argument has to 20 (5 * 4 bytes) rather than 24:

    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))

    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(0))
    

    glVertexAttribPointer(texCoords,2, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))

    glVertexAttribPointer(texCoords,2, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(12))
    

    The data type of the indices has to be integral. The type in the draw call (glDrawElements(..., ..., GL_UNSIGNED_INT, ...)) has to match this type. Use uint32 rather than float (and vertices -> indices):

    indices = np.array(vertices, dtype=np.float32)

    indices = np.array(indices, dtype=np.uint32)
    

    Associating a generic vertex attribute index with a named attribute variable (glBindAttribLocation) has to be done, before the program is linked (before glLinkProgram).
    I recommend to set the attribute index by a Layout Qualifier:

    layout (location = 0) in vec4 position;
    layout (location = 1) in vec2 InTexCoords;