Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explain to me like I am 5: OpenGL 4.x Rendering Pipeline

I have spent the last couple of weeks in my off-time looking at openGL. And while I do not have a problem following some of the older NeHe examples, from everything I have read, OpenGL4 is a totally different process. And I have access to the red book and the super bible, but the former is still offering legacy opengl calls where as the latter uses their own library. Neither is especially helpful in understanding how to put together code in a project. For example, my current understanding is that glu and glut are legacy and shouldn't be used for opengl 4.

I can generate vertices very easily for a hypothetical model space. I have an extremely hard time understanding how a model ends up showing up on my screen. About 95% of my attempts end up with a black blank screen.

Thanks in advance.

Here's some code:

# primatives.py
from collections import Iterable
from functools import reduce
import operator

import numpy as np

from exc import UnimplementedMethod

class Primative(object):
    SIZE = 1  # number of pixels on a default grid

    def __init__(self, point=None, *args, **kwargs):
        self.point = point if isinstance(point, Iterable) else [0, 0, 0]
        self.point = np.array(self.point, dtype=np.float32)
        scaler = [self.SIZE/2]*len(self.point)
        self.point = (self.point * scaler).tolist()

    @property
    def active(self):
        attr = "__active__"
        if not hasattr(self, attr):
            setattr(self, attr, False)
        return getattr(self, attr)

    @active.setter
    def active(self, value):
        attr = "__active__"
        if value in [True, False]:
            setattr(self, attr, value)
        return getattr(self, attr)

    @property
    def vertices(self):
        """Returns a simple list of calculated vertices"""
        clsname = self.__class__.__name__
        raise UnimplementedMethod(clsname)

    @property
    def dimension(self):
        return len(self.point)

    @property
    def scaler(self):
        attr = "__scaler__"
        if not hasattr(self, attr):
            size = self.SIZE / 2
            setattr(self, attr, [size]*self.dimension)
        return getattr(self, attr)

    @scaler.setter
    def scaler(self, *values):
        attr = "__scaler__"
        values = values[0] if len(values) == 1 else values
        if len(values) == 1 and len(values) != self.point:
            if isinstance(values, [int, float]):
                setattr(self, attr, [values]*self.dimension)
            elif isinstance(values, Iterable):
                data = [(v, i)
                        for v, i in zip(values, xrange(self.dimension))]
                value = [v for v, i in data]
                if len(value) != self.dimension:
                    raise ValueError
                setattr(self, attr, value)

    @property
    def translation(self):
        attr = "__transalation__"
        if not hasattr(self, attr):
            size = self.SIZE / 2
            setattr(self, attr, [size]*self.dimension)
        return getattr(self, attr)

    @translation.setter
    def transalation(self, *values):
        attr = "__transalation__"
        values = values[0] if len(values) == 1 else values
        if isinstance(values, (int, float)):
            setattr(self, attr, [values]*self.dimension)
        elif isinstance(values, Iterable):
            data = [(v, i)
                    for v, i in zip(values, xrange(self.dimension))]
            value = [v for v, i in data]
            if len(value) != self.dimension:
                raise ValueError
            setattr(self, attr, value)

    @property
    def rotation(self):
        """
        Rotation in radians
        """
        attr = "__rotation__"
        if not hasattr(self, attr):
            setattr(self, attr, [0]*self.dimension)
        return getattr(self, attr)

    @rotation.setter
    def rotation(self, *values):
        """
        Rotation in radians
        """
        attr = "__rotation__"
        values = values[0] if len(values) == 1 else values
        if isinstance(values, (int, float)):
            setattr(self, attr, [values]*self.dimension)
        elif isinstance(values, Iterable):
            data = [(v, i)
                    for v, i in zip(values, xrange(self.dimension))]
            value = [v for v, i in data]
            if len(value) != self.dimension:
                raise ValueError
            setattr(self, attr, value)

    @property
    def volume(self):
        clsname = self.__class__.__name__
        raise UnimplementedMethod(clsname)


class Cube(Primative):

    #       G           H
    #        * --------- *
    #       /|          /|
    #    C / |       D / |
    #     * --------- *  |
    #     |  * -------|- *
    #     | / E       | / F
    #     |/          |/
    #     * --------- *
    #      A           B

    @property
    def center_of_mass(self):
        """
        Uses density to calculate center of mass
        """
        return self.point

    @property
    def material(self):
        clsname = self.__class__.__name__
        raise UnimplementedMethod(clsname)

    @material.setter
    def material(self, value):
        clsname = self.__class__.__name__
        raise UnimplementedMethod(clsname)

    @property
    def mass(self):
        return self.material.density * self.volume

    @property
    def volume(self):
        func = operator.mul
        return reduce(func, self.scaler, 1)

    @property
    def normals(self):
        """
        computes the vertex normals
        """
        norm = []
        if len(self.point) == 1:
            norm = [
            # counter clockwise
            # x    (left hand rule)
            (-1),           # A
            (1)             # B
            ]
        elif len(self.point) == 2:
            norm = [  
            # counter clockwise
            # x, y    (left hand rule)
            (-1, -1),       # A
            (1, -1),        # B
            (1, 1),         # C
            (-1, 1)         # D
            ]
        elif len(self.point) == 3:
            norm = [
            # counter clockwise
            # x, y, z    (left hand rule)
            (-1, -1, 1),    # A 0
            (1, -1, 1),     # B 1
            (1, 1, 1),      # D 2
            (-1, 1, 1),     # C 3
            (-1, -1, -1),   # E 4
            (1, -1, -1),    # F 5
            (1, 1, -1),     # H 6
            (-1, 1, -1),    # G 7
            ]
        return norm

    @property
    def indices(self):
        indices = []
        if len(self.point) == 2:
            indices = [
                [[1, 0, 3], [2, 3, 1]],   # BAC CDB front
            ]
        elif len(self.point) == 3:
            indices = [
                [[1, 0, 3], [2, 3, 1]],  # BAC CDB front
                [[5, 1, 2], [2, 6, 5]],  # FBD DHF right
                [[4, 5, 6], [6, 7, 4]],  # EFH HGE back
                [[5, 4, 0], [0, 1, 5]],  # FEA ABF bottom
                [[0, 4, 7], [7, 3, 0]],  # AEG GCA left
                [[2, 3, 7], [7, 6, 2]],  # DCG GHD top
            ]
        return indices

    @property
    def nodes(self):
        normals = np.array(self.normals, dtype=np.float32)
        scaler = np.array(self.scaler, dtype=np.float32)
        nodes = normals * scaler
        return nodes.tolist()

    @property
    def vertices(self):
        verts = (n for node in self.nodes for n in node)
        return verts

And one more:

# Voxel.py
from collections import Iterable
from time import time

import numpy as np
import pyglet
from pyglet.gl import *

from primatives import Cube
import materials


class Voxel(Cube):
    """
    Standard Voxel
    """

    def __init__(self, point=None, material=None):
        super(Voxel, self).__init__(point=point)
        if isinstance(material, materials.Material):
            self.material = material
        else:
            self.material = materials.stone

    def __str__(self):
        point = ", ".join(str(p) for p in self.point)
        material = self.material.name
        desc = "<Voxel [%s] (%s)>" % (material, point)
        return desc

    def __repr__(self):
        point = ", ".join(str(p) for p in self.point)
        material = self.material.name
        desc = "<Voxel %s(%s)>" % (material, point)
        return desc

    @property
    def material(self):
        attr = "__material__"
        if not hasattr(self, attr):
            setattr(self, attr, materials.ether)
        return getattr(self, attr)

    @material.setter
    def material(self, value):
        attr = "__material__"
        if value in materials.valid_materials:
            setattr(self, attr, value)
        return getattr(self, attr)


class Chunk(Cube):
    """
    A Chunk contains a specified number of Voxels.  Chunks are an
    optimization to manage voxels which do not change often.
    """
    NUMBER = 16
    NUMBER_OF_VOXELS_X = NUMBER
    NUMBER_OF_VOXELS_Y = NUMBER
    NUMBER_OF_VOXELS_Z = NUMBER

    def __init__(self, point=None):
        point = (0, 0, 0) if point is None else point
        super(Chunk, self).__init__(point=point)
        self.batch = pyglet.graphics.Batch()
        points = []
        x_scale = self.NUMBER_OF_VOXELS_X / 2
        y_scale = self.NUMBER_OF_VOXELS_Y / 2
        z_scale = self.NUMBER_OF_VOXELS_Z / 2
        self.rebuild_mesh = True
        if len(point) == 1:
            points = ((x,) for x in xrange(-x_scale, x_scale))
        elif len(point) == 2:
            points = ((x, y) 
                      for x in xrange(-x_scale, x_scale) 
                      for y in xrange(-y_scale, y_scale))
        elif len(point) == 3:
            points = ((x, y, z) 
                      for x in xrange(-x_scale, x_scale) 
                      for y in xrange(-y_scale, y_scale)
                      for z in xrange(-z_scale, z_scale))
        t = time()
        self.voxels = dict((point, Voxel(point)) for point in points)
        self.active_voxels = dict((p, v) 
                                  for p, v in self.voxels.iteritems()
                                  if v.active)
        self.inactive_voxels = dict((p, v) 
                                    for p, v in self.voxels.iteritems()
                                    if not v.active)
        print 'Setup Time: %s' % (time() - t)

    @property
    def material(self):
        return ether

    @material.setter
    def material(self, value):
        if value in materials.valid_materials:
            for voxel in self.voxels:
                if voxel.material != value:
                    voxel.material = value
                    self.rebuild_mesh = True

    @property
    def mesh(self):
        """
        Returns the verticies as defined by the Chunk's Voxels
        """
        attr = "__mesh__"
        if self.rebuild_mesh == True:
            self.mesh_vert_count = 0
            vertices = []
            t = time()
            for point, voxel in self.active_voxels.iteritems():
                if voxel.active is True:
                    vertices.extend(voxel.vertices)
                    num_verts_in_voxel = len(voxel.normals)
                    self.mesh_vert_count += num_verts_in_voxel
            print "Mesh Generation Time: %s" % time() - t
            vertices = tuple(vertices)
            setattr(self, attr, vertices)
            voxel_count = len(self.active_voxels)
            voxel_mesh = self.mesh
            count = self.mesh_vert_count
            group = None
            data = ('v3f/static', vertices)
            self.batch.add(count, self.mode, group, data)
        return getattr(self, attr)

    @property
    def center_of_mass(self):
        """
        Uses density to calculate center of mass.  This is probably only
        useful if the chunk represents an object.
        """
        center = self.point
        points = []
        for point, voxel in self.active_voxels.iteritems():
            mass = voxel.mass
            if mass > 0:
                point = [p*mass for p in point]
            points.append(point)
        points = np.array(points)
        means = []
        if points.any():
            for idx, val in enumerate(self.point):
                means.append(np.mean(points[:, idx]))
        if means:
            center = means
        return center

    def add(self, voxel):
        added = False
        point = None
        if isinstance(voxel, Voxel):
            point = voxel.point
        elif isinstance(voxel, Iterable):
            point = voxel
        if point in self.inactive_voxels.iterkeys():
            last = self.voxels[point]
            self.voxels[point] = voxel if isinstance(voxel, Voxel) else last
            self.voxels[point].active = True
            self.active_voxels[point] = self.voxels[point]
            self.inactive_voxels.pop(point)
            added = True
            self.rebuild_mesh = True
        return added

    def remove(self, voxel):
        removed = False
        point = None
        if isinstance(voxel, Voxel):
            point = voxel.point
        elif isinstance(voxel, Iterable):
            point = voxel
        if point in self.active_voxels.iterkeys():
            last = self.voxels[point]
            self.voxels[point] = voxel if isinstance(voxel, Voxel) else last
            self.voxels[point].active = False
            self.inactive_voxels[point] = self.voxels[point]
            self.active_voxels.pop(point)
            removed = True
            self.rebuild_mesh = True
        return removed

    def render(self):
        voxels = len(self.active_voxels)
        self.batch.draw()
        return voxels

if __name__ == "__main__":
    import pyglet
    from pyglet.gl import *

    class Window(pyglet.window.Window):

        def __init__(self, *args, **kwargs):
            super(Window, self).__init__(*args, **kwargs)
            vox_cnt = self.setup_scene()
            print 'Added: %s voxels' % (vox_cnt)

        def run(self):
            """wrapper to start the gui loop"""
            pyglet.app.run()

        def setup_scene(self):
            self.chunk = Chunk()
            cnt = 0
            t = time()
            for x in xrange(self.chunk.NUMBER_OF_VOXELS_X):
                for y in xrange(self.chunk.NUMBER_OF_VOXELS_Y):
                        self.chunk.add((x, y))
                        cnt += 1
            print "Setup Scene Time: %s" % (time() - t)
            return cnt

        def render_scene(self):
            y = h = self.height
            x = w = self.width
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()
            # glEnable(GL_DEPTH_TEST)
            # glDepthFunc(GL_LESS)
            t = time()
            voxels_drawn = self.chunk.render()
            print 'Render Time: %s' % (time() - t)
            print 'Points Rendered %s' % voxels_drawn
            # array_len = len(self.vertex_data)
            # glDrawArrays(GL_TRIANGLES, 0, array_len)

        def on_draw(self, *args, **kwargs):
            self.render_scene()

    w = Window()
    w.run()
like image 231
Brian Bruggeman Avatar asked Nov 01 '22 06:11

Brian Bruggeman


2 Answers

There are examples in the source distributions, if you download them (link to page).

The one you want to see is in <top-dir>/examples/opengl.py -- for a torus. If you make the following modifications you will have a cube.

# line 91:
cube.draw()  # previously torus.draw()

# line 178: replace the line with the below (GL_TRIANGLES for GL_QUADS)
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT, indices)

# line 187:
cube = Cube(0.8)  # previously torus = Torus(1, 0.3, 50, 30)


# replace lines 125 through 166 with:
class Cube(object):
    Vertices =(0.,0.,0., 1.,0.,0., 0.,0.,1., 1.,0.,1.,
               0.,1.,0., 1.,1.,0., 0.,1.,1., 1.,1.,1.)

    def __init__(self, scale):
        # Create the vertex and normal arrays.
        indices = [0,1,3,2, 1,5,7,3, 5,4,6,7,
                   0,2,6,4, 0,4,5,1, 2,3,7,6]
        normals = [  0.0, -1.0,  0.0,
                     1.0,  0.0,  0.0,
                     0.0,  1.0,  0.0,
                    -1.0,  0.0,  0.0,
                     0.0,  0.0,  -1.0,
                     0.0,  0.0,   1.0]
        vertices = [scale * v for v in Cube.Vertices]
        vertices = (GLfloat * len(vertices))(*vertices)
        normals = (GLfloat * len(normals))(*normals)
like image 172
slushy Avatar answered Nov 13 '22 19:11

slushy


I don't know about python's class wrappers and I did not do any graphics programming for quite some time. But I know that you should search for qualified answers about real workings and internals in the community of hardware guys who either create the VHDL GPU code or write low level drivers or so. They KNOW for sure how it works and some FAQ explanation should be available already in their community.

Based on that assumption this is what some Googling gave me to start with:

  1. OpenGL 4.4 API Reference Card - page 7 - (available among top level resources on http://www.opengl.org) shows some simple picture (for 5 years old?) with the rendering pipeline split into

    • Blue blocks indicate various buffers that feed or get fed by the OpenGL pipeline
    • Green blocks indicate fixed function stages
    • Yellow blocks indicate programmable stages
  2. Jarrred Walton's - Return of the DirectX vs. OpenGL Debates points to a 130-page slideshow How OpenGL Can Unlock 15x Performance Gains | NVIDIA Blog. Both articles fall into categories AMD,Intel,NVIDIA,Game Developer Converence

I have dome some simple OpenGL using C, C++, Delphi long ago and my recommendation is to get rid of the python mapping at first altogether. Look for suitable class library with good community with some good support only afterwards you know what you are looking for.

The above are IMHO the waters to start fishing in

like image 31
xmojmr Avatar answered Nov 13 '22 20:11

xmojmr