Search code examples
pythonopenglgraphicstexturespyopengl

Pyopengl textures not rendering properly, object filled solid gray


I am trying to add textures (png image file) to a rectangle which is being rendered. However, instead no matter what image I use, the object just appears gray. I have a feeling that I am missing something to do with tetxure co-ordinates. In each line of the vertex buffer, the first 2 numbers represent x and y co-ordinates whilst the last 2 represent texture co-ordinates.

Most answers online suggest that there is an issue with the image data being passed into the texture, or that is has not been bound properly.

from OpenGL.GL import *
import glfw
import numpy
import sys
from PIL import Image

class Shader:    
    def readshader(self, Title, filepath):
        #reads shader (This works so I have removed it for ease of reading)

    def CreateShader(self, filepath):
        program = glCreateProgram()
        VERT = self.readshader("VERTEX", filepath)
        vertShader = glCreateShader(GL_VERTEX_SHADER)
        self.Compileshader(vertShader, VERT, program, "Vertex")     
        FRAG = self.readshader("FRAGMENT", filepath)
        fragShader = glCreateShader(GL_FRAGMENT_SHADER)
        self.Compileshader(fragShader, FRAG, program, "Fragment")
        glLinkProgram(program)
        glValidateProgram(program)
        glDeleteShader(vertShader)
        glDeleteShader(fragShader)  
        return program

    def Compileshader(self, shader, shaderstring, program, type):
        glShaderSource(shader, shaderstring)
        glCompileShader(shader)
        status = glGetShaderiv(shader, GL_COMPILE_STATUS)
        if not status:
            info = glGetShaderInfoLog(shader)
            print("Error in " + type + " Shader:")
            print(info.decode("utf-8"))
            glDeleteShader(shader)
        else:
            glAttachShader(program, shader)

class Renderer:
    def __init__(self):
        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)
        self.buffer = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.buffer)
        glEnableVertexAttribArray(0)
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, None)
        self.ibo = glGenBuffers(1)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ibo)

    def AttachTexture(self, NewTexture, Width, Height, Uniform, value):
        glEnable(GL_TEXTURE_2D)
        self.Texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self.Texture)

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NewTexture)

        glGenerateMipmap(GL_TEXTURE_2D); ##new

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 16, None) ##new
        glEnableVertexAttribArray(2)  ##new

        glBindTexture(GL_TEXTURE_2D, self.Texture)  #new

        glActiveTexture(GL_TEXTURE0 + value)
        location = glGetUniformLocation(self.program, Uniform)
        glUniform1i(location, value)

    def AttachShader(self, program):
        self.program = program
        glUseProgram(self.program)

    def GetUniformLocation(self, Uniform, r, g, b):
        location = glGetUniformLocation(self.program, Uniform)
        glUniform4f(location, r, g, b, 1.0)

    def ArrayBufferData(self, positions):
        glBufferData(GL_ARRAY_BUFFER, positions, GL_STATIC_DRAW)

    def IndexBufferData(self, indices):
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)

    def Unbind(self):
        glBindVertexArray(0)
        glUseProgram(0)
        glBindBuffer(GL_ARRAY_BUFFER, 0)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)

    def Bind(self):
        glBindVertexArray(self.vao)
        glUseProgram(self.program)
        glBindBuffer(GL_ARRAY_BUFFER, self.buffer)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ibo)

    def DrawElements(self, length):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glDrawElements(GL_TRIANGLES, length, GL_UNSIGNED_INT, None)


def main():
    TextureA = Image.open("Textures\Texture Test 2019.08.12 01.58.png").transpose(Image.FLIP_TOP_BOTTOM)
    Texture = numpy.frombuffer(TextureA.tobytes(), numpy.uint8)
    Width, Height = TextureA.size
    name = "OpenGL Testing"
    if not glfw.init():
        return
    window = glfw.create_window(640, 480, "Hello World", None, None)
    if not window:
        glfw.terminate()
    glfw.make_context_current(window)
    glfw.swap_interval(1)

    NewShader = Shader()
    program = NewShader.CreateShader("Shaders\Complete Shader 2019.08.12 02.41.txt")

    NewBuffer = Renderer()
    NewBuffer.AttachShader(program)
    positions = numpy.array([-0.5, -0.5, 0.0, 0.0\
                             ,0.5, -0.5, 1.0, 0.0\
                             ,0.5,  0.5, 1.0, 1.0\
                            ,-0.5,  0.5, 0.0, 1.0]\
                            ,dtype = 'float32')
    indices = numpy.array([0, 1, 2,\
                           2, 3, 0]\
                          ,dtype = 'int32')

    NewBuffer.ArrayBufferData(positions)
    NewBuffer.IndexBufferData(indices)

    red = 0.0
    increment = 0.05

    while not glfw.window_should_close(window):
        NewBuffer.Bind()
        if red > 1.0:
            increment = -0.05
        elif red < 0.0:
            increment = 0.05
        red += increment
        NewBuffer.GetUniformLocation("u_Color", red, 0.3, 0.8)
        NewBuffer.DrawElements(len(indices))
        NewBuffer.AttachTexture(Texture, Width, Height, "u_Texture", 0)

        glfw.swap_buffers(window)
        glfw.poll_events()

    glfw.terminate()

if __name__ == '__main__': main()

#For reference here is the vertex and fragment shader I am using:

"""
@VERTEX
#version 330 core

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

out vec2 v_TexCoord;

void main()
{
    gl_Position = position;
    v_TexCoord = texCoord;
};
@FRAGMENT
#version 330 core

layout(location = 0) out vec4 color;

in vec2 v_TexCoord;

uniform vec4 u_Color;
uniform sampler2D u_Texture;

void main()
{
    vec4 texColor = texture(u_Texture, v_TexCoord);
    color = texColor;
};
"""

Solution

  • The attribute index of the texture coordinates is 1:

    layout(location = 1) in vec2 texCoord;
    

    So when you specify and enable the array of generic vertex attributes, then the attribute index has to be 1, too. See glVertexAttribPointer respectively glEnableVertexAttribArray.

    When a named buffer object is bound, then the last parameter of glVertexAttribPointer is treated as a byte offset into the buffer object's data store.
    The content of the buffer are to vertex coordinates, followed by 2 texture coordinates:

    x0, y0, u0, v0,   x1, y1, u1, v1,   x2, y2, u2, v2, ...
    

    Each component has a size of 4 (size of float), so the stride is 16 = 4*4.
    The offset of the vertex coordinates is 0 and the offset texture coordinates is 8=2*4.

    Since the type of offset (last) parameter of glVertexAttribPointer is const GLvoid * you've to cast the parameter to ctypes.c_void_p:

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, c_void_p(0))
    

    If the offset is 0, it can be used None instead:

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, None)
    

    Bind the buffer before you the arrays of generic vertex attribute data are specified, set the proper attribute indices and offsets:

    from ctypes import c_void_p
    
    glBindBuffer(GL_ARRAY_BUFFER, self.buffer)
    
    glEnableVertexAttribArray(0)
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, None)
    
    glEnableVertexAttribArray(1)
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 16, c_void_p(8))