I have an exercise to draw a simple cube that teacher asks me to fill in where:
"YOUR CODE HERE ...".
I have done it but when coming to see the result, it rendered something weird (Figure 1 ). I have run that code on my friend's laptop and it gave a really good cube (Figure 2).
There are 3 files in used:
from libs.buffer import *
import glfw
class Cube(object):
def __init__(self, vert_shader, frag_shader):
self.vertices = np.array([
# YOUR CODE HERE to specify vertices' coordinates
[-0.5, -0.5, -0.5], # A 0
[0.5, -0.5, -0.5], # B 1
[0.5, -0.5, 0.5], # C 2
[-0.5, -0.5, 0.5], # D 3
[-0.5, 0.5, -0.5], # E 4
[0.5, 0.5, -0.5], # F 5
[0.5, 0.5, 0.5], # G 6
[-0.5, 0.5, 0.5] # H 7
], dtype=np.float32)
self.indices = np.array([
# YOUR CODE HERE to specify indices
4, 7, 5, 6, # Top
6, 0,
0, 4, 1, 5, 2, 6, 3, 7, 0, 4, # Sides
5, 1,
0, 3, 1, 2 # Bottom
])
# YOUR CODE HERE to compute vertices' normals using the coordinates
normals = np.random.normal(0, 1, (self.vertices.shape[0], 3)).astype(np.float32)
normals[:, 2] = np.abs(normals[:, 2])
self.normals = normals / np.linalg.norm(normals, axis=1, keepdims=True)
# colors: RGB format
self.colors = np.array([
# YOUR CODE HERE to specify vertices' color
[0.0, 0.0, 0.0], # black
[1.0, 0.0, 0.0], # red
[1.0, 1.0, 0.0], # yellow
[0.0, 1.0, 0.0], # green
[0.0, 0.0, 1.0], # blue
[1.0, 0.0, 1.0], # magenta
[1.0, 1.0, 1.0], # white
[0.0, 1.0, 1.0] # cyan
], dtype=np.float32)
self.vao = VAO()
self.shader = Shader(vert_shader, frag_shader)
self.uma = UManager(self.shader)
#
"""
Create object -> call setup -> call draw
"""
def setup(self):
# setup VAO for drawing cylinder's side
self.vao.add_vbo(0, self.vertices, ncomponents=3, stride=0, offset=None)
self.vao.add_vbo(1, self.colors, ncomponents=3, stride=0, offset=None)
# setup EBO for drawing cylinder's side, bottom and top
self.vao.add_ebo(self.indices)
return self
def draw(self, projection, view, model):
GL.glUseProgram(self.shader.render_idx)
modelview = view
self.uma.upload_uniform_matrix4fv(projection, 'projection', True)
self.uma.upload_uniform_matrix4fv(modelview, 'modelview', True)
self.vao.activate()
GL.glDrawElements(GL.GL_TRIANGLE_STRIP, self.indices.shape[0], GL.GL_UNSIGNED_INT, None)
def key_handler(self, key):
if key == glfw.KEY_1:
self.selected_texture = 1
if key == glfw.KEY_2:
self.selected_texture = 2
import OpenGL.GL as GL
import cv2
class VAO(object):
def __init__(self):
self.vao = GL.glGenVertexArrays(1)
GL.glBindVertexArray(self.vao)
GL.glBindVertexArray(0)
self.vbo = {}
self.ebo = None
def add_vbo(self, location, data, ncomponents=3, dtype=GL.GL_FLOAT, normalized=False, stride=0, offset=None):
self.activate()
buffer_idx = GL.glGenBuffers(1)
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, buffer_idx)
GL.glBufferData(GL.GL_ARRAY_BUFFER, data, GL.GL_STATIC_DRAW)
# location = GL.glGetAttribLocation(self.shader.render_idx, name)
GL.glVertexAttribPointer(location, ncomponents, dtype, normalized, stride, offset)
GL.glEnableVertexAttribArray(location)
self.vbo[location] = buffer_idx
self.deactivate()
def add_ebo(self, indices):
self.activate()
self.ebo = GL.glGenBuffers(1)
GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, self.ebo)
GL.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, indices, GL.GL_STATIC_DRAW)
self.deactivate()
def __del__(self):
GL.glDeleteVertexArrays(1, [self.vao])
GL.glDeleteBuffers(1, list(self.vbo.values()))
if self.ebo is not None:
GL.glDeleteBuffers(1, [self.ebo])
def activate(self):
GL.glBindVertexArray(self.vao) # activated
def deactivate(self):
GL.glBindVertexArray(0) # activated
class UManager(object):
def __init__(self, shader):
self.shader = shader
self.textures = {}
@staticmethod
def load_texture(filename):
texture = cv2.cvtColor(cv2.imread(filename, 1), cv2.COLOR_BGR2RGB)
return texture
def _get_texture_loc(self):
if not bool(self.textures):
return 0
else:
locs = list(self.textures.keys())
locs.sort(reverse=True)
ret_id = locs[0] + 1
return ret_id
"""
* first call to setup_texture: activate GL.GL_TEXTURE0
> use GL.glUniform1i to associate the activated texture to the texture in shading program (see fragment shader)
* second call to setup_texture: activate GL.GL_TEXTURE1
> use GL.glUniform1i to associate the activated texture to the texture in shading program (see fragment shader)
* second call to setup_texture: activate GL.GL_TEXTURE2
> use GL.glUniform1i to associate the activated texture to the texture in shading program (see fragment shader)
and so on
"""
def setup_texture(self, sampler_name, image_file):
rgb_image = UManager.load_texture(image_file)
GL.glUseProgram(self.shader.render_idx) # must call before calling to GL.glUniform1i
texture_idx = GL.glGenTextures(1)
binding_loc = self._get_texture_loc()
self.textures[binding_loc] = {}
self.textures[binding_loc]["id"] = texture_idx
self.textures[binding_loc]["name"] = sampler_name
GL.glActiveTexture(GL.GL_TEXTURE0 + binding_loc) # activate texture GL.GL_TEXTURE0, GL.GL_TEXTURE1, ...
GL.glBindTexture(GL.GL_TEXTURE_2D, texture_idx)
GL.glUniform1i(GL.glGetUniformLocation(self.shader.render_idx, sampler_name), binding_loc)
GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB,
rgb_image.shape[1], rgb_image.shape[0], 0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, rgb_image)
GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
def upload_uniform_matrix4fv(self, matrix, name, transpose=True):
GL.glUseProgram(self.shader.render_idx)
location = GL.glGetUniformLocation(self.shader.render_idx, name)
GL.glUniformMatrix4fv(location, 1, transpose, matrix)
def upload_uniform_matrix3fv(self, matrix, name, transpose=False):
GL.glUseProgram(self.shader.render_idx)
location = GL.glGetUniformLocation(self.shader.render_idx, name)
GL.glUniformMatrix3fv(location, 1, transpose, matrix)
def upload_uniform_vector4fv(self, vector, name):
GL.glUseProgram(self.shader.render_idx)
location = GL.glGetUniformLocation(self.shader.render_idx, name)
GL.glUniform4fv(location, 1, vector)
def upload_uniform_vector3fv(self, vector, name):
GL.glUseProgram(self.shader.render_idx)
location = GL.glGetUniformLocation(self.shader.render_idx, name)
GL.glUniform3fv(location, 1, vector)
def upload_uniform_scalar1f(self, scalar, name):
GL.glUseProgram(self.shader.render_idx)
location = GL.glGetUniformLocation(self.shader.render_idx, name)
GL.glUniform1f(location, scalar)
def upload_uniform_scalar1i(self, scalar, name):
GL.glUseProgram(self.shader.render_idx)
location = GL.glGetUniformLocation(self.shader.render_idx, name)
GL.glUniform1i(location, scalar)
import OpenGL.GL as GL # standard Python OpenGL wrapper
import glfw # lean windows system wrapper for OpenGL
import numpy as np # all matrix manipulations & OpenGL args
from itertools import cycle # cyclic iterator to easily toggle polygon rendering modes
from libs.transform import Trackball
from cube import *
# ------------ Viewer class & windows management ------------------------------
class Viewer:
""" GLFW viewer windows, with classic initialization & graphics loop """
def __init__(self, width=800, height=800):
self.fill_modes = cycle([GL.GL_LINE, GL.GL_POINT, GL.GL_FILL])
# version hints: create GL windows with >= OpenGL 3.3 and core profile
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL.GL_TRUE)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.window_hint(glfw.RESIZABLE, False)
glfw.window_hint(glfw.DEPTH_BITS, 16)
glfw.window_hint(glfw.DOUBLEBUFFER, True)
self.win = glfw.create_window(width, height, 'Viewer', None, None)
# make win's OpenGL context current; no OpenGL calls can happen before
glfw.make_context_current(self.win)
# initialize trackball
self.trackball = Trackball()
self.mouse = (0, 0)
# register event handlers
glfw.set_key_callback(self.win, self.on_key)
glfw.set_cursor_pos_callback(self.win, self.on_mouse_move)
glfw.set_scroll_callback(self.win, self.on_scroll)
# useful message to check OpenGL renderer characteristics
print('OpenGL', GL.glGetString(GL.GL_VERSION).decode() + ', GLSL',
GL.glGetString(GL.GL_SHADING_LANGUAGE_VERSION).decode() +
', Renderer', GL.glGetString(GL.GL_RENDERER).decode())
# initialize GL by setting viewport and default render characteristics
GL.glClearColor(0.5, 0.5, 0.5, 0.1)
#GL.glEnable(GL.GL_CULL_FACE) # enable backface culling (Exercise 1)
#GL.glFrontFace(GL.GL_CCW) # GL_CCW: default
GL.glEnable(GL.GL_DEPTH_TEST) # enable depth test (Exercise 1)
GL.glDepthFunc(GL.GL_LESS) # GL_LESS: default
# initially empty list of object to draw
self.drawables = []
def run(self):
""" Main render loop for this OpenGL windows """
while not glfw.window_should_close(self.win):
# clear draw buffer
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
win_size = glfw.get_window_size(self.win)
view = self.trackball.view_matrix()
projection = self.trackball.projection_matrix(win_size)
# draw our scene objects
for drawable in self.drawables:
drawable.draw(projection, view, None)
# flush render commands, and swap draw buffers
glfw.swap_buffers(self.win)
# Poll for and process events
glfw.poll_events()
def add(self, *drawables):
""" add objects to draw in this windows """
self.drawables.extend(drawables)
def on_key(self, _win, key, _scancode, action, _mods):
""" 'Q' or 'Escape' quits """
if action == glfw.PRESS or action == glfw.REPEAT:
if key == glfw.KEY_ESCAPE or key == glfw.KEY_Q:
glfw.set_window_should_close(self.win, True)
if key == glfw.KEY_W:
GL.glPolygonMode(GL.GL_FRONT_AND_BACK, next(self.fill_modes))
for drawable in self.drawables:
if hasattr(drawable, 'key_handler'):
drawable.key_handler(key)
def on_mouse_move(self, win, xpos, ypos):
""" Rotate on left-click & drag, pan on right-click & drag """
old = self.mouse
self.mouse = (xpos, glfw.get_window_size(win)[1] - ypos)
if glfw.get_mouse_button(win, glfw.MOUSE_BUTTON_LEFT):
self.trackball.drag(old, self.mouse, glfw.get_window_size(win))
if glfw.get_mouse_button(win, glfw.MOUSE_BUTTON_RIGHT):
self.trackball.pan(old, self.mouse)
def on_scroll(self, win, _deltax, deltay):
""" Scroll controls the camera distance to trackball center """
self.trackball.zoom(deltay, glfw.get_window_size(win)[1])
# -------------- main program and scene setup --------------------------------
def main():
""" create windows, add shaders & scene objects, then run rendering loop """
viewer = Viewer()
# place instances of our basic objects
model = Cube("./gouraud.vert", "./gouraud.frag").setup()
viewer.add(model)
# start rendering loop
viewer.run()
if __name__ == '__main__':
glfw.init() # initialize windows system glfw
main() # main function keeps variables locally scoped
glfw.terminate() # destroy all glfw windows and GL contexts
Hope someone can help me solve this conflict.
P/s: I use MacOS whereas my friend uses Window.
The problem is the data type of index array in cube.py. We need to specify a specific type in order to run smoothly through various OSs.
For example: self.indices = np.array([...], dtype=np.uint32)
.