2
\$\begingroup\$

I am, like many others, currently working on a block-based game in C++ and OpenGl. Recently, I added ambient occlusion support. This ambient occlusion is created by modulating the brightness of a point on the face of a block by an OpenGl varying from each corner. The corner's darkness is proportional to the amount of blocks that intersect that corner. This usually works very well, as can be seen in this screenshot:

Ambient Occlusion on a stone area

It does create some artifacts, however. One good example is seen at the base of a tree trunk:

Atrifacts at the base of a tree trunk

In this example, the size of the pseudo-shadow varies based on the orientation of the triangles around the chunk as described here:

Notice that the ambient occlusion is different for the vertices on the side than it is for the vertices on the top and bottom. To fix this, we just need to pick a consistent orientation for the quads. This can be done by comparing the ambient occlusion terms for each quad and selecting an appropriate orientation.

Their proposed solution, however, is not of the kind that I prefer. It seems more like a trick to cover up the fact that the varyings aren't being interpolated over the whole quad, as opposed to a real solution. As such, I thought that it may work to render with GL_QUADS instead of GL_TRIANGLES, as perhaps that would allow the varyings to vary as I want. Unfortunately, this is not possible in the version of OpenGl that I am targeting (3.3 core). As such, I ask: how can I interpolate the varying values (eg. the brightness) over a quad instead of a triangle? Is there some algorithm that I can use to change the brightness value at each corner of a quad to produce the same result? I have tried searching for this via a search engine, but no useful results came up.

\$\endgroup\$
2
\$\begingroup\$

There is no "the whole quad" for OpenGL to interpolate values across. It doesn't exist, in your current setup, so you're not going to be able to make OpenGL do that, and you'll need to make it happen yourself.

It seems to me that an easy way to approach this would be to make a tiny 2x2 pixel texture for each face of each cube and put the occlusion data for each vertex of the face into those four texture pixels. Then in your fragment shader, sample the face's occlusion data from those tiny textures. Sampling a 2D texture is interpolated between the four texels around the sample point, and since you're doing it inside the fragment shader, you're avoiding the problematic triangle-based interpolation that OpenGL does.

Of course, doing that for each face of each cube would require a huge number of textures, even if you only do it for the exposed faces. If you do want to do this, you'd probably want to bundle all these tiny textures together into a single texture array, just to simplify how many texture objects you need to bind and unbind during rendering, and to simplify sampling.

To me, this still seems like a lot of work for something that seems like a pretty minor graphical anomaly.. but I don't immediately see any obviously better approaches. Up to you whether it's worth the bother to satisfy your demands of not doing something that feels like a "trick"?

\$\endgroup\$
  • \$\begingroup\$ I'd suspect there are many repeating occlusion value patterns too, so you might be able to make an atlas that spans the combinations you need, then just permute the texture coordinates to re-use the same 4 pixels between all faces that need similar shading patterns. \$\endgroup\$ – DMGregory Jul 7 at 10:52
  • \$\begingroup\$ Oo, yeah, I like that idea! Assuming four possible 'occlusion' values per vertex, there are only 256 unique possible 2x2 texures. And a good chunk of those can be eliminated using mirroring or rotation.. In fact, you could do away with the texture entirely, and just encode the four values into a vertex attribute (perhaps the third texture coordinate which would otherwise have been used to specify the layer of the texture array); then you don't even need to do the texture lookup and can just do the interpolation in the fragment shader based upon the texture coordinates. \$\endgroup\$ – Trevor Powell Jul 7 at 14:30
  • \$\begingroup\$ @TrevorPowell I considered adding a vec4 for the brightness at each point of the square and a vec2 for the position on the square to the vertex array attributes, but that is 6 extra floats per point which obviously undesirable. Is there some way to compress this down a bit,and not multiply everything by 4? \$\endgroup\$ – john01dav Jul 7 at 19:15
  • \$\begingroup\$ To add on to this, even Minecraft has this artifact. It would seem like it is not worth it performance wise adding such a case to fix this. Minecraft seems to dampen this effect to hide it. \$\endgroup\$ – Jason Jul 12 at 2:32
  • \$\begingroup\$ Here's another fun thought; Instead of using textures, put your occlusion data for each face into an integer vertex attribute. If you store it as an unsigned byte, that gives two bits each for each vertex on a face. Set that same eight-bit value for each vert on the face and you can do bilinear interpolation in the shader based upon the "texture coordinates", without actually needing a texture. It's still way overkill, probably, and still means that you can't share vertices between faces of a cube, but.. it's a lot less absurd than my texture-based suggestion. \$\endgroup\$ – Trevor Powell Jul 14 at 13:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.