1. icon for News
  2. icon for TCHOW

News

Monday, August 12, 2013

Rktcr's Mineral Pipeline

In Rktcr, your goal is to come up with a path that collects fourteen gems (at least for teams with large-size worlds; smaller worlds have fewer). Until recently, these gems had clever names (and mineral associations), but all drew the same in game. Since gem styles was an item on my release to do list, I spent some time over the last two days making each gem unique.

In this post, I'll talk about how I made these gems. The content pipeline isn't very long (blender + a python script), but it certainly served its purpose adequately.

It's also worth saying up front that I included in Rktcr several minerals not cut into gemstones. Dialog still refers to these as gems. The pedantic among you may abhor this.

Modeling: Blender

All the gems in Rktcr start in Blender -- a 3D modelling program I've been using for a long time. One of the great things about Blender is the integrated python scripting. The following script scrapes all objects named "Gem:*" out of a .blend file and deposits triangle lists into Rktcr's distribution directory:

#!/usr/bin/env python

import bpy
import struct
import math

for obj in bpy.data.objects:
    if obj.name[0:4] != 'Gem:':
        continue
    gem = obj.name[4:]
    print('Doing ' + gem)
    blob = open('../dist/gems/' + gem + '.blob', 'wb')

    mesh = obj.data
    count = 0
    for poly in mesh.polygons:
        count += (len(poly.vertices) - 2) * 3
 
    for poly in mesh.polygons:
        norm = poly.normal
        a = []
        a_norm = []
        b = []
        b_norm = []
        for vi in poly.vertices:
            vert = mesh.vertices[vi].co * 0.1
            if poly.use_smooth:
                norm = mesh.vertices[vi].normal
            if len(a) == 0:
                a = vert
                a_norm = norm
            else:
                if len(b) != 0:
                    blob.write(struct.pack('fff',*a_norm))
                    blob.write(struct.pack('fff',*a))
                    blob.write(struct.pack('fff',*b_norm))
                    blob.write(struct.pack('fff',*b))
                    blob.write(struct.pack('fff',*norm))
                    blob.write(struct.pack('fff',*vert))
                    b = vert
                    b_norm = norm
        blob.close()
quit()

Loading

Why write out binary blobs instead of (say) using .obj export? One big reason is simplicity. This is the code I use to import the models:

Vector3f vert, norm;
while (
    file->read(reinterpret_cast< char * >(&norm), sizeof(norm)) && 
    file->read(reinterpret_cast< char * >(&vert), sizeof(vert))) {
    verts.push_back(vert);
    norms.push_back(norm);
}

Of course, it would be even simpler to slurp the file directly into (GPU?) memory, but I decided to defer that because it would require monkeying a bit with Rktcr's file handling layer.

Rendering

Minerals rendered as diffuse-only shaded models would be pretty boring. After casting about for a one-size-fits-all rendering approach, and being unsatisfied with everything I could come up with, I ended up crafting seven different display styles.

Diffuse

Even though diffuse rendering is rather boring, it is used in-engine for rendering "ghost" gems -- gems the player has already picked up on this path.

Clear

Clear gems are rendered in two layers -- a back shell and a front shell. Both shells are rendered using a base color and an additional highlight looked up from a lighting texture using a pseudo-reflection vector. Lighting on the back shell is tinted to the gem color, while lighting on the front shell is simply added.

Metallic

Metallic gems use one layer rendered with a Phong material. For being such a simple shading model, it works quite well (especially when coupled with the right geometry).

Dusty Metallic

The dusty metallic model is metallic with a "dust" texture used to modulate the specular power and alter the diffuse color. I found that using Blender's rendering of the gem with only ambient occlusion was a good starting point for dust textures.

Refractive

The refractive style is used for a few different minerals that occur in not-so-pristine crystals. It renders the gem with a dust texture (like "dusty metallic") and highlights from texture-based vertex lighting (like the "clear" style). Additionally, the inside of the gem is rendered by looking up in an "inside" texture using a pseudo-refraction vector.

Unfortunately, for all its sophistication, I'm not very happy with the outputs of this style just yet. It can be very tricky balancing the dust, lighting, and refraction effects to achieve a nice look.

Tourmaline

The "tourmaline" style started out as a single-purpose rendering engine for -- you guessed it -- tourmaline; however, I ended up using it for one other mineral as well.

This style renders in a single pass, generating front and back reflections using two different psuedo-reflection vectors. Like a "clear"-style gem, the front reflection is added and the back reflection is modulated. However, unlike a "clear"-style gem, the tint color is set using a texture.

Opal

The opal gets its own rendering engine to deal with the milky outer shell and the reflective inner fire. The gem is drawn in three passes -- the shell is drawn with low opacity, the fire, then the shell again.

The fire is made to shimmers by using a pseudo-reflection vector to look up vertex colors in a texture.

The shell uses a modified diffuse lighting model to fake subsurface scattering.

The Finished Minerals

Above, you see all fourteen gems (plus the goal gem for challenge levels and a placeholder gem used in the editor) rendered by the game; I'm not entirely happy with all of them yet (e.g. obsidian probably needs a second light to have good contrast with the background). However, this is probably quite close to the set of gems that I'll be shipping August 21st.