1

I am using my own algorithm to cull occluded front-faces that should never be displayed. When I don't cull the faces, I have a perfect set of blocks. When I enable my face culling, I have unexpected results. I have checked my culling code, and I believe it is properly culling the appropriate faces.

For simplicity's sake, I have reduced my output to a plane of 9 boxes. As an example, the box in the middle of the plane need only have a top face and a bottom face. All other faces can be discarded because they simply won't ever be seen anywhere.

I checked each box within the code to determine if they had the correct faces culled, and I believe they do. That makes me think it's a problem with my vertices, indices or normals.

I'm using python's Pyglet, which auto-creates and manages the VAOs and VBOs.

OpenGL settings:

    glEnable(GL_DEPTH_TEST)
    glEnable(GL_CULL_FACE)

OpenGL On Draw:

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

Vertex code:

        v0 = [1,  1,  1]
        v1 = [-1,  1,  1]
        v2 = [-1, -1,  1]
        v3 = [1, -1,  1]
        v4 = [1, -1, -1]
        v5 = [1,  1, -1]
        v6 = [-1,  1, -1]
        v7 = [-1, -1, -1]
        pt = self.point
        s = getattr(self, 'scale', 0.5)
        faces = ((k, v) for k, v in (
            ('front', [v * s + p for vert in [v0, v1, v2, v3]
                       for v, p in zip(vert, pt)]),
            ('right', [v * s + p for vert in [v0, v3, v4, v5]
                       for v, p in zip(vert, pt)]),
            ('top', [v * s + p for vert in [v0, v5, v6, v1]
                     for v, p in zip(vert, pt)]),
            ('left', [v * s + p for vert in [v1, v6, v7, v2]
                      for v, p in zip(vert, pt)]),
            ('bottom', [v * s + p for vert in [v7, v4, v3, v2]
                        for v, p in zip(vert, pt)]),
            ('back', [v * s + p for vert in [v4, v7, v6, v5]
                      for v, p in zip(vert, pt)]))
            if getattr(self, k)
        )

Normals code:

        verts_per_face = 4
        faces = ((k, v) for k, v in (
            ('front', [0, 0, 1] * verts_per_face),
            ('right', [1, 0, 0] * verts_per_face),
            ('top', [0, 1, 0] * verts_per_face),
            ('left', [-1, 0, 0] * verts_per_face),
            ('bottom', [0, -1, 0] * verts_per_face),
            ('back', [0, 0, -1] * verts_per_face))
            if getattr(self, k)
        )

Indices code:

        t0 = [0,  1,  2]
        t1 = [2,  3,  0]
        t2 = [4,  5,  6]
        t3 = [6,  7,  4]
        t4 = [8,  9, 10]
        t5 = [10, 11,  8]
        t6 = [12, 13, 14]
        t7 = [14, 15, 12]
        t8 = [16, 17, 18]
        t9 = [18, 19, 16]
        t10 = [20, 21, 22]
        t11 = [22, 23, 20]
        triangles = ((k, v) for k, v in (
            ('front', [t for triangle in [t0, t1] for t in triangle]),
            ('right', [t for triangle in [t2, t3] for t in triangle]),
            ('top', [t for triangle in [t4, t5] for t in triangle]),
            ('left', [t for triangle in [t6, t7] for t in triangle]),
            ('bottom', [t for triangle in [t8, t9] for t in triangle]),
            ('back', [t for triangle in [t10, t11] for t in triangle]))
            if getattr(self, k)
        )

Culling code:

                for face, neighbor_point in block.neighbors():
                    # import pdb; pdb.set_trace()
                    if neighbor_point in pts:
                        neighbor = self.blocks.get(neighbor_point)
                        if neighbor:
                            setattr(block, face, False)
                        else:
                            setattr(block, face, True)
                    else:
                        setattr(block, face, True)

Example output after culling the front and left faces on one of the boxes:

<Voxel (1,0,1)  # top right corner box in images /w center point = (1, 0, 1)
[f r t l o a]   # front, right, top, left, bottom, back
[ |+|+| |+|+]   # + = face available,  ' ' = face culled
(1, 0, 0) (1, 0, 0) (1, 0, 0) (1, 0, 0)  # right normals
(0, 1, 0) (0, 1, 0) (0, 1, 0) (0, 1, 0)  # top normals
(0, -1, 0) (0, -1, 0) (0, -1, 0) (0, -1, 0)  # bottom normals 
(0, 0, -1) (0, 0, -1) (0, 0, -1) (0, 0, -1)  # back normals
[ 1.50| 0.50| 1.50|  # right verts
  1.50|-0.50| 1.50| 
  1.50|-0.50| 0.50| 
  1.50| 0.50| 0.50| 
  1.50| 0.50| 1.50|  # top verts
  1.50| 0.50| 0.50| 
  0.50| 0.50| 0.50| 
  0.50| 0.50| 1.50| 
  0.50|-0.50| 0.50|  # bottom verts
  1.50|-0.50| 0.50| 
  1.50|-0.50| 1.50| 
  0.50|-0.50| 1.50| 
  1.50|-0.50| 0.50|  # back verts
  0.50|-0.50| 0.50| 
  0.50| 0.50| 0.50| 
  1.50| 0.50| 0.50]>

Culling, No Wireframes!No Culling, No Wireframes Culling, WireframesNo Culling, Wireframes

genpfault
  • 51,148
  • 11
  • 85
  • 139
Brian Bruggeman
  • 5,008
  • 2
  • 36
  • 55
  • I am a bit too lazy to read all of your code. But I think you have a fundamental misunderstanding of what face culling is. The code you post under "culling" does not make any sense. Culling on the gl side GL_CULL_FACE is totally automatic. The easiest way to understand it is that it is 2d. Compute the area of every result 2d triangle. If it is negative it is back facing. You can easily draw this. 3 vertices, draw the triangle 0,1,2 and 2,1,0. Same shape, but different sign for the area. It has nothing to do with neighbors or normals! Just 2d winding. – starmole Apr 27 '14 at 06:26
  • The whole 3d thing works because winding does not change under projection. But it is really a 2d thing. As an exercise figure out how to compute the are of a 2d triangle with 3 vertices that are defined by three indices 0,1,2. Then do the same for indices 2,1,0. – starmole Apr 27 '14 at 06:31
  • I am attempting to remove the front-facing triangles that are never seen. As a simple example, the middle box only needs a top and a bottom face -- all other faces are shared by another box and are never seen. – Brian Bruggeman Apr 27 '14 at 14:16
  • You could do a depth-only pre-pass to eliminate the interior faces in the wireframe. It is not technically going to cull anything, just prevent those faces from being shaded by causing them to fail a depth test the 2nd time. Since it sounds more like you are trying to do this to reduce vertex processing load, I do not think that is what you actually want. But to be honest, in this simple example there would be no benefit to identifying the occluded faces, it would take more processing time than it saves. This holds true even when you scale it up to a larger number of triangles (within reason). – Andon M. Coleman Apr 27 '14 at 15:58
  • Are you targeting particular platforms? Since you're using Python, I figure it's not mobile class GPUs, which partly have very different architectures. Most traditional GPUs do depth testing before the fragment shader. If you can render your geometry front to back, which might be fairly easy with blocks in a grid, the hidden faces will be eliminated before the fragment shader. That might be the best you can do. I doubt that applying logic on the CPU, particularly in Python, will be more efficient than letting the GPU eliminate the invisible faces. – Reto Koradi Apr 27 '14 at 16:46
  • @RetoKoradi: I am not currently targeting platforms. However, if I can write it in python, I can easily translate it to c/c++/objC if I needed to at a later point. The python lets me prototype a design without incurring the cost of working through debugging linking/compiling/library woes. In addition, I'm new to openGL, so this is also a learning experience. – Brian Bruggeman Apr 27 '14 at 17:16
  • @AndonM.Coleman: After reading your comment, I did some testing. With my culling working properly, I can see as much as a 50% drop in the amount of time it takes to render some of my scenes across tens of thousands to hundreds of thousands of boxes. So I think culling the front faces makes a lot of sense. – Brian Bruggeman Apr 28 '14 at 15:25

2 Answers2

0

See here for how to implement a culling algorithm. In your culling code, you are doing something completely irrelevant. To determine whether a face should be culled, you should only have to check it's winding order (assuming you provide all triangles in CW or CCW order in relation to their normals). If your triangles are not ordered, then in order to determine culling, you have to check the z-order of the vertices of the triangle, something which the graphics card does automatically. If you decide to implement this yourself, you would effectively be implementing software rendering (although rendering in triangle fragments, rather than individual pixels).

d3dave
  • 1,361
  • 14
  • 23
  • Actually, it becomes important when I have about 350,000 extra faces later. I am not trying to remove 'back facing' triangles. As you noted, this is already done automagically. However, I am trying to cull the front-facing triangles that will never be seen. I do think it could be a winding problem. But I am not sure why my front facing triangles work fine when all are present, but appear to be wrong when some are not present. – Brian Bruggeman Apr 27 '14 at 14:14
  • @Brian, Ok, I think I understand better now. In that case, I don't know of a way to do what you are asking atm. – d3dave Apr 27 '14 at 15:21
0

I was not calculating the indices properly. The new method should be:

        t0 = [0,  1,  2]
        t1 = [2,  3,  0]
        t2 = [4,  5,  6]
        t3 = [6,  7,  4]
        t4 = [8,  9, 10]
        t5 = [10, 11,  8]
        t6 = [12, 13, 14]
        t7 = [14, 15, 12]
        t8 = [16, 17, 18]
        t9 = [18, 19, 16]
        t10 = [20, 21, 22]
        t11 = [22, 23, 20]
        triangles = ((k, v) for k, v in (
            ('front', [t for triangle in [t0, t1] for t in triangle]),
            ('right', [t for triangle in [t2, t3] for t in triangle]),
            ('top', [t for triangle in [t4, t5] for t in triangle]),
            ('left', [t for triangle in [t6, t7] for t in triangle]),
            ('bottom', [t for triangle in [t8, t9] for t in triangle]),
            ('back', [t for triangle in [t10, t11] for t in triangle]))
        )
        inds = []
        faces = [f for f, v in self.faces if v]
        for triangle, face in zip(triangles, faces):
            f, tdata = triangle
            inds.extend(tdata)
Brian Bruggeman
  • 5,008
  • 2
  • 36
  • 55