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;
};
"""
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))