Procedural Meshes: Generating Gemstones Part 2

Last time I talked about writing a gemstone generator for Unity, in this part I’ll talk about taking that script and making it work in SFML 2 using C++. What makes this challenging is that Unity is a 3D engine with a Mesh class making it easy for procedural geometry, while SFML is a C++ framework made primarily for 2D games. The most SFML gives you is the sf::Vector3f object, which allows you to store 3D [x,y,z] coordinates, which means the rest of it is up to you. As of writing, this is the approach I went to generate 3D gemstones in Gemstone Keeper.

First I’ll talk about the more mathematical process before I get into rendering, since regardless of what rendering process I choose in C++, I need to use 3D vector maths. SFML does not provide any 3D math helper functions (and as far as I’m aware, not even any 2D math helper functions), so an easy and lazy workaround is to use an external library with loads of math functions such as GLU or GLM, however attaching an entire 3D library for a single feature does sound a bit excessive. Instead what I did was write my own 3D math functions, writing only the ones I need:

  • Scale – to make the gemstone bigger or smaller after calculating. I could’ve included an origin point so we could scale from any position, but the origin in this calculation will always be at absolute zero [0,0,0], so it’s not necessary.
    sf::Vector3f Gemstone::scaleVector3f(sf::Vector3f Point, sf::Vector3f Scale)
    {
    Point.x *= Scale.x;
    Point.y *= Scale.y;
    Point.z *= Scale.y;
    return Point;
    }
  • Rotate About Y Axis – This will be important for the vector calculations at each symmetry, both rotation could have been done with matrices, however that would’ve required a lot more work to set up and calculate multiplications without using an external library.
    sf::Vector3f Gemstone::rotateYVector3f(sf::Vector3f Point, float Angle, sf::Vector3f Origin)
    {
    sf::Vector3f dir = Point - Origin;
    sf::Vector3f result;
    result.z = (dir.z * cosf(Angle)) - (dir.x * sinf(Angle));
    result.x = (dir.z * sinf(Angle)) + (dir.x * cosf(Angle));
    result.y = dir.y;
    Point = result + Origin;
    return Point;
    }
  • Reflection – Used for calculating the directional vector reflecting from one vector off another.
    sf::Vector3f Gemstone::reflectVector3f(sf::Vector3f incident, sf::Vector3f normal)
    {
    return incident - 2.0f * dotVector3f(incident, normal) * normal;
    }
  • Cross Product – Used to work out a vector perpendicular to two points, we’ll use this to work out vector normals as unlike Unity, we have to work these out ourselves.
    sf::Vector3f Gemstone::crossVector3f(sf::Vector3f a, sf::Vector3f b)
    {
    sf::Vector3f crossProduct = sf::Vector3f((a.y * b.z) - (a.z * b.y), (a.z * b.x) - (a.x * b.z), (a.x * b.y) - (a.y * b.x));
    float length = sqrtf((crossProduct.x * crossProduct.x) + (crossProduct.y * crossProduct.y) + (crossProduct.z * crossProduct.z));
    return sf::Vector3f(crossProduct.x / length, crossProduct.y / length, crossProduct.z / length);
    }
  • Dot Product – Takes two vectors and multiplies them into a single value, this is used for Reflection but also for the basic lighting using Lambert’s Cosine Rule
    float Gemstone::dotVector3f(sf::Vector3f a, sf::Vector3f b)
    {
    return (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
    }

Probably the most obvious answer for rendering 3D geometry in SFML that virtually any other programmer would give is to use OpenGL: it’s practically made for 3D rendering, it’s probably the most efficient way since it’ll render using the GPU, SFML 2.0 is based on OpenGL for rendering anyways and the framework even provides a header (SFML\OpenGL.hpp) for accessing OpenGL function calls. This was also my initial thought, however looking through the documentation and samples made me give up that idea. If I started working on my SFML framework with OpenGL and 2D+3D rendering in mind, implementing OpenGL wouldn’t have been a challenge.

As such, I decided to to the more bizarre option, convert 3D coordinates into 2D view space using 2D vertexes. This means there is more software than hardware rendering with an orthographic camera (so no perspective, no depth), but what I can use is SFML VertexArrays. The VertexArray class is pretty much the core of SFML’s rendering (sprites, text and shapes in SFML use VertexArrays), and when developing my SFML framework I did talk about how much I love VertexArrays. In the end, I think I did a good job, because this looks 3D:

The way I did it was pretty basic, simply use the [x,y] coordinates and throw away the z coordinate. While this does have limitations (particularly no z-buffering), it does a pretty good job. In order to avoid overlapping polygons from the lack of depth testing, I instead do backface culling by using vertex normals and theoretical 3D camera so that any polygons facing away from the viewer will not be rendered (or in this case, have all values zero).

//vert0, vert1 and vert2 are the three vertices of our polygon
sf::Vector3f vector1 = vert1 - vert0;
sf::Vector3f vector2 = vert2 - vert1;
sf::Vector3f normal = crossVector3f(vector1, vector2);
sf::Vector3f cameraDir = sf::Vector3f(0.0f, 0.0f, -1.0f);
if (dotVector3f(cameraDir, normal) > 0.0f)
{
//Render polygon

I also have to handle lighting in software as well, which is what the reflect and dot product are used for. Lighting in 3D graphics is a whole topic on its own, so to make things easy to explain:

  • Ambient: Base colour of gemstone, stored in the gemstone class.
  • Diffuse: Shading, based on the direction of a light source with the normal of each polygon (or pixel).
  • Specular: Shininess, based on reflected light.

sf::Color diffuse = sf::Color(90, 90, 90, 180);
sf::Color specular = sf::Color(220, 220, 220, 255);
sf::Vector3f lightDir = sf::Vector3f(0.5f, -0.5f, -0.5f);
float diff = dotVector3f(lightDir, normal);
if (diff > 0.0f)
{
sf::Vector3f v = reflectVector3f(-lightDir, normal);
float spec = powf(fmaxf(dotVector3f(v, sf::Vector3f(0, 0, 1)), 0.0f), 0.7f);
specular.r = sf::Uint8(specular.r * spec);
specular.g = sf::Uint8(specular.g * spec);
specular.b = sf::Uint8(specular.b * spec);
diffuse.r = sf::Uint8(diffuse.r * diff);
diffuse.g = sf::Uint8(diffuse.g * diff);
diffuse.b = sf::Uint8(diffuse.b * diff);
}
vertices[i + 0].position = (Size / 2.0f) + sf::Vector2f(vert0.x, vert0.y);
vertices[i + 0].texCoords = sf::Vector2f(
gemTex[i + 0].x * texture.getSize().x,
gemTex[i + 0].y * texture.getSize().y);
vertices[i + 0].color = sf::Color(
(ambient.r + diffuse.r + specular.r) / 3,
(ambient.g + diffuse.g + specular.g) / 3,
(ambient.b + diffuse.b + specular.b) / 3,
ambient.a);

Texturing is not shown in the gallery state, since the gemstones are pretty small, so it’s not going to make a difference. For the results page, I used the ‘0’ drawn in a compressed grid to attempt a refraction pattern. While UV coordinates are fraction based [0, 1], Texture coordinates in SFML Vertex Arrays use the image sizes, which is why in the code above I multiply the texCoords by the size of the texture, if there is no texture, then all the texture coordinates are set to zero so only colour is used.

For now, I’m pretty happy with the results. Not only have I somehow many 3D meshes with lighting in Gemstone Keeper, but without a 3D rendering pipeline. There are some snags with this, one is that this process can only be done with either generating once, but storing two sets of mesh data (one normal and one transformed) or by regenerating the data each time you want to change the data (such as rotating it). As such, I minimise the amount of gemstones being generated by only processing those that would be on camera, even with an added bloom shader I’m able to render several 3D spinning gemstones at once.

Still I think this is an interesting method of achieving 3D rendering, combined with generating procedural gemstones.

Advertisements

One thought on “Procedural Meshes: Generating Gemstones Part 2

  1. Pingback: New Years Update | GAMEPOPPER

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s