I am trying to render multiple objects in pyopengl using pyqt5. After following tutorials I created a 3D mesh which uploads a wavefront obj file and renders it with texture. This worked for me:
class Model:
def __init__(self, file_name, texture_name):
self.object = ObjectLoader()
self.object.load_model(file_name)
//creating and compiling shaders
glUseProgram(shader)
vertex_buffer_object = GLuint(0)
glGenBuffers(1, vertex_buffer_object)
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object)
glBufferData(GL_ARRAY_BUFFER, len(self.object.model) * 4, self.object.c_model, GL_DYNAMIC_DRAW)
glDrawArrays(GL_TRIANGLES, 0, len(self.object.vertex_index))
# vertices
vertex_offset = 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(vertex_offset))
glEnableVertexAttribArray(0)
# textures
texture_offset = len(self.object.vertex_index) * 12
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 2, ctypes.c_void_p(texture_offset))
glEnableVertexAttribArray(1)
# normals
normal_offset = texture_offset + len(self.object.texture_index) * 8
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(normal_offset))
glEnableVertexAttribArray(2)
texture = GLuint(0)
glGenTextures(1, texture)
glBindTexture(GL_TEXTURE_2D, texture)
# texture wrapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
# texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
image = Image.open(texture_name)
flipped_image = image.transpose(Image.FLIP_TOP_BOTTOM)
image_data = image.convert("RGB").tobytes()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data)
// setting the view, projection etc matrix
Since I am working with PyQt5, I have this class were I load the object:
class Obj(QOpenGLWidget):
def __init__(self, parent):
QOpenGLWidget.__init__(self, parent)
def initializeGL(self):
glClearColor(82/255, 95/255, 107/255, 1.0)
glEnable(GL_DEPTH_TEST)
self.model = Model("....obj", "texture.png")
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glDrawArrays(GL_TRIANGLES, 0, len(self.model.object.vertex_index))
self.update()
def resizeGL(self, width, height):
glViewport(0, 0, width, height)
What I am struggling with is creating in the Obj class another Model. I tried creating it in initializeGL and rendering in paintGL with another glDrawArrays call, but nothing happened, or nothing expected... Any idea of what I am doing wrong?
You have to use a Vertex Array Object. A Vertex Array Object stores all of the state needed to supply vertex data.
The constructor of the model only creates all the required OpenGL objects. The shader program is not part of the model. Use the same program for all models:
class Model:
def __init__(self, file_name, texture_name):
self.object = ObjectLoader()
self.object.load_model(file_name)
# create vertex array object
self.vao = glGenVertexArrays(1)
glBindVertexArray(self.vao)
vertex_buffer_object = GLuint(0)
glGenBuffers(1, vertex_buffer_object)
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object)
glBufferData(GL_ARRAY_BUFFER, len(self.object.model) * 4, self.object.c_model, GL_DYNAMIC_DRAW)
glDrawArrays(GL_TRIANGLES, 0, len(self.object.vertex_index))
# vertices
vertex_offset = 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(vertex_offset))
glEnableVertexAttribArray(0)
# textures
texture_offset = len(self.object.vertex_index) * 12
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 2, ctypes.c_void_p(texture_offset))
glEnableVertexAttribArray(1)
# normals
normal_offset = texture_offset + len(self.object.texture_index) * 8
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(normal_offset))
glEnableVertexAttribArray(2)
# create texture
self.texture = GLuint(0)
glGenTextures(1, self.texture)
glBindTexture(GL_TEXTURE_2D, self.texture)
# texture wrapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
# texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
image = Image.open(texture_name)
flipped_image = image.transpose(Image.FLIP_TOP_BOTTOM)
image_data = image.convert("RGB").tobytes()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data)
Add a method to the class which binds the OpenGL objects (VAO and texture) and draws the mesh:
class Model:
# [...]
def draw(self):
// bind texture
glBindTexture(GL_TEXTURE_2D, self.texture)
// create vertex array object
glBindVertexArray(self.vao)
// draw object
glDrawArrays(GL_TRIANGLES, 0, len(self.object.vertex_index))
With this setup you can draw multiple models:
class Obj(QOpenGLWidget):
def __init__(self, parent):
QOpenGLWidget.__init__(self, parent)
def initializeGL(self):
glClearColor(82/255, 95/255, 107/255, 1.0)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
self.model1 = Model("....obj", "texture.png")
self.model2 = Model("....obj", "texture2.png")
# [...]
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# install program
glUseProgram(self.shader)
# setting the view, projection etc matrix
# [...]
self.model1.draw()
self.model2.draw()
# [...]
self.update()
def resizeGL(self, width, height):
glViewport(0, 0, width, height)
The models can be organized in a list, too:
class Obj(QOpenGLWidget):
# [...]
def initializeGL(self):
# [...]
self.models = [
Model("....obj", "texture.png"),
Model("....obj", "texture2.png")
# [...]
]
def paintGL(self):
# [...]
for model in self.models:
model.draw()
# [...]
Related
I'm making a basic voxel rendering class with python using glfw. I'm using glBufferSubData instead of glBufferData, but it won't render. Here is the code that is suspected to be causing the problem:
# imports
import glfw, numpy as np
from OpenGL.GL import *
from ctypes import *
from core.texture_manager import *
glfw.init()
class VBOManager:
def __init__(self, renderer):
self.renderer = renderer
self.run()
def run(self):
for i in self.renderer.to_add[:self.renderer.to_add_count]:
self.renderer.vertices.extend(i[0])
self.renderer.texCoords.extend(i[1])
vertices = np.array(i[0], dtype=np.float32)
texCoords = np.array(i[1], dtype=np.float32)
# use glBufferSubData
glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo)
glBufferData(GL_ARRAY_BUFFER, self.renderer.vertices.nbytes, vertices.nbytes, (c_float * len(vertices))(*vertices))
glFlush()
glVertexPointer(3, GL_FLOAT, 0, None)
glTexCoordPointer(3, GL_FLOAT, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo_1)
glBufferSubData(GL_ARRAY_BUFFER, self.renderer.texCoords.nbytes, texCoords.nbytes, (c_float * len(texCoords))(*texCoords))
glFlush()
self.renderer.to_add.remove(i)
class TerrainRenderer:
def __init__(self, window):
self.window = window
self.vertices = np.array([])
self.texCoords = np.array([])
self.to_add = []
self.to_add_count = 256
self.vbo, self.vbo_1 = glGenBuffers (2)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferData(GL_ARRAY_BUFFER, 12 * 4, None, GL_STATIC_DRAW)
self.vbo_manager = VBOManager(self)
self.texture_manager = TextureAtlas()
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glEnableClientState (GL_VERTEX_ARRAY)
def render(self):
try:
self.vbo_manager.run()
except RuntimeError:
pass
glClear (GL_COLOR_BUFFER_BIT)
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glBindBuffer (GL_ARRAY_BUFFER, self.vbo)
glVertexPointer (3, GL_FLOAT, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glTexCoordPointer(2, GL_FLOAT, 0, None)
glDrawArrays (GL_QUADS, 0, len(self.vertices))
glDisable(GL_TEXTURE_2D)
glDisable(GL_BLEND)
def add(self, posList, texCoords):
self.to_add.append((numpy.array(posList), numpy.array(texCoords)))
When I start the application that uses this code, it shows no logs, warnings or errors, but it also doesn't render the vertices at all. NOTHING is visible on the screen.
glBufferSubData just updates a buffer object's data store but doesn't creates a buffer object's data store. You need to create the buffer object's data store with glBufferData, after that you can update the data with glBufferSubData. Furthermore, The 2nd argument of glBufferSubData specifies the offset into the buffer object's data store where data replacement will begin.
glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
glBufferData(GL_ARRAY_BUFFER, texCoords.nbytes, None, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo_1)
glBufferSubData(GL_ARRAY_BUFFER, 0, texCoords.nbytes, (c_float * len(texCoords))(*texCoords))
So I've been racking my brain with nonstop trial and error. I feel like I keep coming back with questions about the same thing and it is frustrating.
I am following the tutorials and looking at the C++ code and following along with Python trying to replicate the result. I've noticed there are subtle differences through trial and error and I've searched high and low on the internet to see if someone else has experienced my issue. 9/10 times it is because the texture was not bound, however, it my case it is.
https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/4.1.textures/textures.cpp
So I am wondering what I am doing wrong... any help?
import os # For mac... I am using a mac
import glfw # We're using this instead of GLUT as we have more flexibility
import numpy as np # We will use numpy for our arrays
# Using the API wrapper instead of something mugh higher. Keep in mind that the API is a state machine
from OpenGL.GL import *
from OpenGL.arrays import *
from ctypes import c_void_p
from PIL import Image
class HelloWindow():
width = 800
height = 640
title = 'Hello Window'
window = None
shader_program = None
vao = None
vbo = None
texture = None
vertex_gsl = """
#version 410 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 3) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoord;
void main() {
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = vec2(aTexCoord);
}
"""
fragment_gsl = """
#version 410 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D texture1;
void main() {
FragColor = texture(texture1, TexCoord);
}
"""
def __init__(self):
if not glfw.init():
raise TypeError('Unable to initalize glfw')
self.main()
def main(self):
# Set window hints
self.set_window_hints()
# Create the window
self.create_window()
max_vertex_attributes = glGetIntegerv(GL_MAX_VERTEX_ATTRIBS)
print('Maximum number of vertex attributes in a vertex shader is: ' + str(max_vertex_attributes))
# Keep the window open in a loop
self.loop()
def set_window_hints(self):
glfw.window_hint(glfw.SAMPLES, 4)
# Using the core version in Mac OS but can be set to something else
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 1)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
def create_window(self):
self.window = glfw.create_window(self.width, self.height, self.title, None, None)
if not self.window:
raise TypeError('Unable to create the window')
glfw.terminate()
glfw.make_context_current(self.window)
glfw.set_framebuffer_size_callback(self.window, self.frame_buffer_size)
### Let's setup our data
verts = np.array([
# positions # colors # texture coords
[-0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
[-0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
[0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0],
[0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]
], dtype=np.float32) # OpenGL expects 32 bit data. Not 64 bit if you're on a 64 bit machine
indicies = np.array([
[0, 1, 3],
[1, 2, 3]
], dtype=np.uint32)
self.vao = glGenVertexArrays(1)
self.vbo = glGenBuffers(1)
self.ebo = glGenBuffers(1)
glBindVertexArray(self.vao)
glBindBuffer(GL_ARRAY_BUFFER, self.vbo) # Bind the buffer as an array buffer and not an element buffer
glBufferData(GL_ARRAY_BUFFER, verts, GL_STATIC_DRAW)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ebo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicies, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 8 * verts.itemsize, None)
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 3, GL_FLOAT, False, 8 * verts.itemsize, c_void_p(3 * verts.itemsize))
glEnableVertexAttribArray(1)
glVertexAttribPointer(2, 2, GL_FLOAT, False, 8 * verts.itemsize, c_void_p(6 * verts.itemsize))
glEnableVertexAttribArray(2)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
self.setup_shader()
self.texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, self.texture)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
# Set the wrapping texture parameters for x,y equivalents
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
# Set the texture filtering parameters from min to max
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
# Load our image
img = Image.open('../res/container.jpg')
img.transpose(Image.FLIP_TOP_BOTTOM)
img.convert('RGB')
data = np.array(list(img.getdata()), np.uint8)
# Set the texture data
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.width, img.height, 0, GL_RGB, GL_UNSIGNED_BYTE, data)
glGenerateMipmap(GL_TEXTURE_2D)
#img.close()
def loop(self):
while not glfw.window_should_close(self.window):
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT) # We always use this after we clear the color
# Maintain aspect ratio (optional)
glfw.set_window_aspect_ratio(self.window, self.width, self.height)
# Handle input
self.capture_input()
# Bind the texture
glBindTexture(GL_TEXTURE_2D, self.texture)
# Do some rendering
glUseProgram(self.shader_program)
glBindVertexArray(self.vao)
#glEnableVertexAttribArray(0)
#glDrawArrays(GL_TRIANGLES, 0, 3)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
glfw.swap_interval(1)
glfw.swap_buffers(self.window)
glfw.poll_events()
glfw.destroy_window(self.window)
# Add the vao and vbo to an array so they're deleted. We can use this when creating several vaos and vbos
vao_list = np.array([self.vao], dtype=np.uint32)
vbo_list = np.array([self.vbo], dtype=np.uint32)
glDeleteVertexArrays(1, vao_list)
glDeleteBuffers(1, vbo_list)
glfw.terminate()
def capture_input(self):
if glfw.get_key(self.window, glfw.KEY_ESCAPE) == glfw.PRESS: # Get the key pressed and check if it is escape key
glfw.set_window_should_close(self.window, True)
def frame_buffer_size(self, window, width, height):
glViewport(0, 0, width, height)
def setup_shader(self):
vertex_shader = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertex_shader, self.vertex_gsl)
glCompileShader(vertex_shader)
if not glGetShaderiv(vertex_shader, GL_COMPILE_STATUS):
glGetShaderInfoLog(vertex_shader, 512, None)
raise TypeError('vertex_shader did not compile correctly. Check the GSL')
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragment_shader, self.fragment_gsl)
glCompileShader(fragment_shader)
if not glGetShaderiv(fragment_shader, GL_COMPILE_STATUS):
glGetShaderInfoLog(fragment_shader, 512, None)
raise TypeError('fragment_shader did not compile correctly. Check the GSL')
self.shader_program = glCreateProgram()
glAttachShader(self.shader_program, vertex_shader)
glAttachShader(self.shader_program, fragment_shader)
glLinkProgram(self.shader_program)
if not glGetProgramiv(self.shader_program, GL_LINK_STATUS):
glGetProgramInfoLog(self.shader_program, 512, None)
glDeleteShader(vertex_shader)
glDeleteShader(fragment_shader)
if __name__ == '__main__':
hello_window = HelloWindow()
In the program the attribute index specified for the array of texture coordinates is 2:
glVertexAttribPointer(2, 2, GL_FLOAT, False, 8 * verts.itemsize, c_void_p(6 * verts.itemsize))
glEnableVertexAttribArray(2)
But in the vertex shader the attribute index specified for the texture coordinates is 3, by Layout Qualifier:
layout (location = 3) in vec2 aTexCoord;
Use the same attribute index in both cases and your texture will show up.
I tried everything but still I don't get my error. I am trying to put a texture on my sphere object.
"""
Minimal texture on sphere demo
This is demo for showing how to put image
on sphere as texture in PyOpenGL.
"""
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *
from PIL import Image
import numpy
def run_scene():
glutInit()
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
glutCreateWindow("Minimal sphere OpenGL")
lightning()
glutDisplayFunc(draw_sphere)
glMatrixMode(GL_PROJECTION)
gluPerspective(40, 1, 1, 40)
glMatrixMode(GL_MODELVIEW)
gluLookAt(0, 0, 10,
0, 0, 0,
0, 1, 0)
glPushMatrix()
glutMainLoop()
return
def lightning():
glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glEnable(GL_BLEND)
glLightfv(GL_LIGHT0, GL_POSITION, [10, 4, 10, 1])
glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.8, 1, 0.8, 1])
glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1)
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05)
glEnable(GL_LIGHT0)
return
def draw_sphere():
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glPushMatrix()
texture_id = read_texture('mars.png')
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, texture_id)
glEnable(GL_TEXTURE_GEN_S)
glEnable(GL_TEXTURE_GEN_T)
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP)
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP)
glutSolidSphere(1, 50, 50)
glDisable(GL_TEXTURE_2D)
glPopMatrix()
glutSwapBuffers()
return
def read_texture(filename):
img = Image.open(filename)
img_data = numpy.array(list(img.getdata()), numpy.int8)
texture_id = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.size[0], img.size[1], 0,
GL_RGB, GL_UNSIGNED_BYTE, img_data)
return texture_id
if __name__ == '__main__':
run_scene()
This is result of this code, simple green sphere as this:
But this is not my expected result. I want to simply show this texture mars.png on sphere surface:
Help me with this I cannot solve this on my own it seems.
In read_texture() after you generate a texture name you don't bind it. So the subsequent texture related calls are going to the default texture and not your newly created texture.
texture_id = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture_id) # This is what's missing
Also since your image is a PNG image. Then list(img.getdata()) will be a list of tuples containing (R, G, B, A). So when calling glTexImage2D you need to tell that your data is GL_RGBA and not GL_RGB.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.size[0], img.size[1], 0,
GL_RGBA, GL_UNSIGNED_BYTE, img_data)
You can also automate it.
format = GL_RGB if img.mode == "RGB" else GL_RGBA
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.size[0], img.size[1], 0,
format, GL_UNSIGNED_BYTE, img_data)
You can also convert it to not have an alpha channel.
img = img.convert("RGB")
Which of course needs to be done before calling img.getdata().
Now you should see the following:
Also an important note. You're calling read_texture() in draw_sphere(). This means that every time draw_sphere() is called, the image is loaded and a new texture is created. You really don't want to do that. Instead before calling glutMainLoop() call read_texture() and store the result as a global name.
im trying to get multi texturing working and have so far got miltiple textures to load using this function
def loadTexture(name):
img = PIL.Image.open(name) # .jpg, .bmp, etc. also work
img_data = numpy.array(list(img.getdata()), numpy.int8)
id = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
glBindTexture(GL_TEXTURE_2D, id)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.size[0], img.size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
return id
And I can set the texture to use with this code
glBindTexture(GL_TEXTURE_2D, 1)
glEnable(GL_TEXTURE_2D)
My first attempt has been this:
glBindTexture(GL_TEXTURE_2D, 1)
glEnable(GL_TEXTURE_2D)
glBegin(GL_TRIANGLES)
....
glEnd()
glBindTexture(GL_TEXTURE_2D, 3)
glEnable(GL_TEXTURE_2D)
glBegin(GL_TRIANGLES)
....
glEnd()
So i render the polys twice and select a different texture each time, this seems to work in as much as calling glBindTexture(GL_TEXTURE_2D, n) will select the relivant texture and it will render but there is no blending going on per se, i just see the last selected texture in the render. I've tried adding glEnable(GL_BLEND), but that doesn't seem to do anything.
What I would like to do is to add pixels of the two passes together
How would I go about this?
Are you sure that you need multiple passes? Here is an example of plain old OpenGL multitexturing:
import pygame
from pygame.locals import *
import numpy
import numpy.linalg
from OpenGL.GL import *
from OpenGL.GL.shaders import *
RESOLUTION = (800,600)
POSITIONS = numpy.array([[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]], dtype=numpy.float32)
TEXCOORDS = numpy.array([[0.0, 1.0], [1.0, 1.0], [0.0, 0.0], [1.0, 0.0]], dtype=numpy.float32)
from PIL import Image
tex0 = 0
tex1 = 0
def loadTexture(path):
img = Image.open(path)
img = img.convert("RGBA")
img_data = numpy.array(list(img.getdata()), numpy.int8)
texture = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.size[0], img.size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, img_data)
return texture
def init():
global tex0
global tex1
tex0 = loadTexture("texture0.png")
tex1 = loadTexture("texture1.png")
glViewport(0, 0, *RESOLUTION)
aspect = RESOLUTION[0]/float(RESOLUTION[1])
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-aspect, +aspect, -1.0, +1.0, -1.0, +1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClearColor(0.0, 0.0, 0.0, 1.0);
glVertexPointer(2, GL_FLOAT, 0, POSITIONS);
glEnableClientState(GL_VERTEX_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2, GL_FLOAT, 0, TEXCOORDS);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE1);
glTexCoordPointer(2, GL_FLOAT, 0, TEXCOORDS);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
def draw():
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, tex0);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glActiveTexture(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, tex1);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
def main():
pygame.init()
screen = pygame.display.set_mode(RESOLUTION, OPENGL | DOUBLEBUF)
init()
while True:
for event in pygame.event.get():
if event.type == QUIT: return
draw()
pygame.display.flip()
if __name__ == "__main__":
main()
Have a look at Texture Combiners, they may enable the type of effect you are looking for. Of course, the state of the art for this kind of thing nowadays are shaders.
I'm working on a project using PyOpenGL, and I'm currently attempting to get OpenGL to render a kind of splash screen. My decided solution to this is to draw a textured 2D rectangle. Unfortunately, it appears that no matter what I do, nothing is ever drawn, I just get a black screen (So I guess something is drawn, otherwise it would be a transparent window, but it's definitely not what I want). Here is the pertinent code for my class:
class ClassThing:
def __init__(self):
self.Splash = True
glutInit(sys.argv)
def TexFromPNG(self, filename):
img = Image.open(filename)
img_data = numpy.array(list(img.getdata()), numpy.uint8)
texture = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.size[0], img.size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
return texture
def run(self):
glutInitDisplayMode(GLUT_RGBA)
glutInitWindowSize(256,224)
self.window = glutCreateWindow("GL")
glutDisplayFunc(self.draw)
glClearColor(0,0,0,0)
glEnable(GL_TEXTURE_2D)
glEnable(GL_VERTEX_ARRAY)
self.MainTex = glGenTextures(1)
self.SplashTex = self.TexFromPNG("Resources/Splash.png")
glutMainLoop()
def draw(self):
glClear(GL_COLOR_BUFFER_BIT)
glLoadIdentity()
if self.Splash:
glBindTexture(GL_TEXTURE_2D, self.SplashTex)
else:
glBindTexture(GL_TEXTURE_2D, self.MainTex)
glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
Varray = numpy.array([[0,0],[0,256],[224,256],[224,0]],numpy.uint16)
glVertexPointer(2,GL_SHORT,0,Varray)
indices = [0,1,2,3]
glDrawElements(GL_QUADS,1,GL_UNSIGNED_SHORT,indices)
glFlush()
thing = ClassThing()
thing.run()
As I said, just that nets me with a totally black screen. It feels like I may be missing some kind of initialization or enabling, but I don't know what else I would need to enable.
As an aside, apparently glVertexPointer is deprecated, how would I run glDrawElements without it? Is there some kind of vertex generation you can do similar to texture generation or what?
Could it be because you called glGenTextures(1) twice? You assigned it to texture in TexFromPNG and assigned it to self.MainTex in run.
You need to supply texture coordinates as well, otherwise OpenGL doesn't know how to map the texture to your quad — you also could use automatic texture coordinate generation, but in your case just specifying coordinates is simpler. In addition you also need to specify viewport and projection
class ClassThing:
def __init__(self):
self.Splash = True
def TexFromPNG(self, filename):
img = Image.open(filename)
img_data = numpy.array(list(img.getdata()), numpy.uint8)
texture = glGenTextures(1)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
glBindTexture(GL_TEXTURE_2D, texture)
# Texture parameters are part of the texture object, so you need to
# specify them only once for a given texture object.
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.size[0], img.size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
return texture
def run(self):
glutInitDisplayMode(GLUT_RGBA)
glutInitWindowSize(256,224)
self.window = glutCreateWindow("GL")
glutReshapeFunc(self.reshape)
glutDisplayFunc(self.draw)
self.MainTex = glGenTextures(1)
self.SplashTex = self.TexFromPNG("Resources/Splash.png")
glutMainLoop()
def reshape(self, width, height):
self.width = width
self.height = height
glutPostRedisplay();
def draw(self):
glViewport(0, 0, self.width, self.height)
glClearDepth(1) # just for completeness
glClearColor(0,0,0,0)
glClear(GL_COLOR_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, 1, 0, 1, -1, 1)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity()
if self.Splash:
glBindTexture(GL_TEXTURE_2D, self.SplashTex)
else:
glBindTexture(GL_TEXTURE_2D, self.MainTex)
# it's a good idea to enable state right before you need it
# there's no such thing like global state intialization in
# OpenGL
glEnable(GL_TEXTURE_2D)
# vertex arrays must be enabled using glEnableClientState
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
Varray = numpy.array([[0,0],[0,1],[1,1],[1,0]],numpy.float)
glVertexPointer(2,GL_FLOAT,0,Varray)
glTexCoordPointer(2,GL_FLOAT,0,Varray)
indices = [0,1,2,3]
glDrawElements(GL_QUADS,1,GL_UNSIGNED_SHORT,indices)
# This implies a glFinish, which includes a glFlush
glutSwapBuffers()
# GLUT initialization in program global, so initialize it on
# the process level. It might be
glutInit(sys.argv)
thing = ClassThing()
thing.run()