3

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;
};
"""
Kasim B
  • 41
  • 5

2 Answers2

1

I think you need an offset 8 as the last argument to the second glVertexAttribPointer, the one that defines the texture coordinates.

You are using an interleaved array with vertex coords followed by tex coords, right? So the first attrib pointer defines vertex coords as 2 floating point numbers, 16 bytes (4 x float) apart, starting at byte 0 of the buffer. The second attrib pointer defines the texture coords as 2 floating point numbers, 16 bytes apart, also starting at byte 0 of the buffer. So the vertex coords are being re-used as texture coords.

Hugh Fisher
  • 2,321
  • 13
  • 8
1

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))
Rabbid76
  • 202,892
  • 27
  • 131
  • 174