Geometry Shaders

Geometry shaders are functions that procedurally create geometric objects. They are different from most of the shaders described here because they do not return a color, but a miTag reference to the object or instance group created by the shader. The geometry shader is called with a miState containing valid fields for camera, options, current shader, and version. The shader parameters are passed as the third argument. The geometry shader is expected to return the tag of the object or instance group it creates.

Here is a simple example for a geometry shader that creates a single triangle. A more robust shader would check the return values of all function calls made here. Geometry shaders use a special set of shader interface functions to create geometry. All these functions begin with mi_api_.

    static void add_vector(miScalar x, miScalar y, miScalar z)
    {
        miVector v;
        v.x = x; v.y = y; v.z = z;
        mi_api_vector_xyz_add(&v);
    }

    static void init_object_flags(miObject * object)
    {
        object->visible     = miTRUE;
        object->shadow      =
        object->reflection  =
        object->refraction  =
        object->finalgather = 0x03;
    }

    miBoolean geotriangle(
        miTag           *result,
        miState         *state,
        miTag           *paras)
    {
        miObject        *obj;

        obj = mi_api_object_begin(NULL);
        init_object_flags(obj);
        mi_api_basis_list_clear();
        mi_api_object_group_begin(0.0);

        add_vector(-1., 0., 0.);
        add_vector( 1., 0., 0.);
        add_vector( 0., 0., 1.);

        mi_api_vertex_add(0);
        mi_api_vertex_add(1);
        mi_api_vertex_add(2);

        mi_api_poly_begin_tag(1, *mi_eval_tag(paras));
        mi_api_poly_index_add(0);
        mi_api_poly_index_add(1);
        mi_api_poly_index_add(2);
        mi_api_poly_end();

        mi_api_object_group_end();
        return(mi_geoshader_add_result(result, mi_api_object_end()));
    }

In this example an unnamed object is created with the visible flag visible set to miTRUE, shadow, reflection, refraction and finalgather cast and receive bits set, caustic mode set to 0, and a label 0. In the mandatory object group a triangle is constructed with API calls. The material of the triangle is assigned from the shader parameters. The object group definition is finalized with the polygonal information argument set to miTRUE.

The following example is more complex. It creates multiple sub-instances, each referencing a placeholder object. The sub-instance transformation matrices are set so that the objects form a ring. Since only placeholders are created, mental ray will not create all their triangles up front but merely watch their bounding boxes (also set by the geometry shader), and when a ray hits the bounding box the placeholder object will be created by calling the installed callback function, makeobject.

This is a very efficient way of creating geometry in a geometry shader. It allows mental ray to store small and simple placeholders in the scene, and generate the objects only when they are needed, and remove them from the cache when they are no longer needed. Instead of blocking large sections of memory, only the geometry actually needed is stored, and if an object is never seen because it is hidden by another or off-screen, it will never be loaded or tessellated.

    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <shader.h>
    #include <geoshader.h>

    static miBoolean makeobject(miTag, void *);

    typedef struct {
        miScalar        radius;
        int             nobjects;
    } Multgeo;

    static void init_object_flags(miObject * object)
    {
        object->visible     = miTRUE;
        object->shadow      =
        object->reflection  =
        object->refraction  =
        object->finalgather = 0x03;
    }

    DLLEXPORT int wreath_version(void) {return(1);}

    DLLEXPORT miBoolean wreath(
        miTag           *result,
        miState         *state,
        Multgeo         *paras)
    {
        int             i, num = *mi_eval_integer(&paras->nobjects);
        miScalar        radius = *mi_eval_scalar (&paras->radius);
        miScalar        angle;
        miInstance      *inst;
        miObject        *obj;
        char            oname[100], iname[100];

        for (i=0; i < num; i++) {
            /* placeholder object */
            sprintf(oname, "wreath%x_obj%d", state->shader, i);
            obj = mi_api_object_begin(mi_mem_strdup(oname));
            init_object_flags(obj);
            obj->bbox_min.x = obj->bbox_min.y = obj->bbox_min.z = -1;
            obj->bbox_max.x = obj->bbox_max.y = obj->bbox_max.z = 1;
            mi_api_object_callback(makeobject, 0);
            mi_api_object_end();

            /* instance */
            sprintf(iname, "wreath%x_inst%d", state->shader, i);
            inst = mi_api_instance_begin(mi_mem_strdup(iname));
            mi_matrix_ident(inst->tf.global_to_local);
            angle = 2 * M_PI * i / num - M_PI;
            inst->tf.global_to_local[12] -= sin(angle) * radius;
            inst->tf.global_to_local[13] += cos(angle) * radius;
            mi_matrix_invert(inst->tf.local_to_global,
                             inst->tf.global_to_local);
            mi_geoshader_add_result(result,
                    mi_api_instance_end(mi_mem_strdup(oname), 0, 0));
        }
        return(miTRUE);
    }

    static void add_vector(miScalar a, miScalar b, miScalar c)
    {
        miVector v; v.x=a; v.y=b; v.z=c;
        mi_api_vector_xyz_add(&v);
    }

    static void add_triangle(int a, int b, int c)
    {
        mi_api_poly_begin_tag(1, 0);
        mi_api_poly_index_add(a);
        mi_api_poly_index_add(b);
        mi_api_poly_index_add(c);
        mi_api_poly_end();
    }

    #define USUB        32
    #define VSUB        16

    static miBoolean makeobject(    /* create the geometry for a placeholder */
        miTag           tag,        /* create this tag (by name) */
        void            *arg)       /* 2nd arg of mi_api_object_callback */
    {
        miVector        v;
        miInteger       i, j, nv=0;
        miScalar        p, r;
        miObject        *obj;
        const char      *oname = mi_api_tag_lookup(tag);

        mi_info("creating object %s\n", oname);
        mi_api_incremental(miTRUE);
        obj = mi_api_object_begin(mi_mem_strdup(oname));
        init_object_flags(obj);
        mi_api_object_group_begin(0.0);
        for (i=1; i <= VSUB; i++) {
            v.z = -cos(i * M_PI / (VSUB+1));
            r   = sqrt(1. - v.z*v.z);
            for (j=0; j < USUB; j++, nv++) {
                p   = j * 2 * M_PI / USUB;
                v.x = r * cos(p);
                v.y = r * sin(p);
                mi_api_vector_xyz_add(&v);              /* point */
                mi_api_vector_xyz_add(&v);              /* normal */
            }
        }
        add_vector(0, 0, -1);                           /* south pole */
        add_vector(0, 0, -1);
        add_vector(0, 0, +1);                           /* north pole */
        add_vector(0, 0, +1);
        for (i=0; i < nv; i++) {
            mi_api_vertex_add(i*2);
            mi_api_vertex_normal_add(i*2+1);
        }
        mi_api_vertex_add(nv*2+0);
        mi_api_vertex_normal_add(nv*2+1);
        mi_api_vertex_add(nv*2+2);
        mi_api_vertex_normal_add(nv*2+3);
        for (j=0; j < USUB; j++)                        /* south pole */
            add_triangle(nv, (j+1)%USUB, j);
        for (j=0; j < USUB; j++)                        /* north pole */
            add_triangle(nv+1, nv-USUB+j, nv-USUB+((j+1)%USUB));
        for (i=0; i < VSUB-1; i++)
            for (j=0; j < USUB; j++) {
                int p1 =     i*USUB + j;
                int p2 =     i*USUB + (j+1)%USUB;
                int p3 = (i+1)*USUB + (j+1)%USUB;
                int p4 = (i+1)*USUB + j;
                add_triangle(p1, p2, p4);
                add_triangle(p2, p3, p4);
            }
        mi_api_object_group_end();
        mi_api_object_end();
        return(miTRUE);
    }

This particular example is contrived because the makeobject shader creates the same sphere every time, so multiple instancing of a single object would have been more efficient. Here is a simple scene that uses this shader:

    verbose on
    link "base.so"
    $include "base.mi"

    link "geosh.so"
    declare shader
        geometry "wreath" (
                scalar          "radius",
                integer         "nobjects")
        version 1
    end declare

    options "opt"
        samples         -1 2
        object space
    end options

    camera "cam"
        frame           1
        output          "rgb" "out.rgb"
        focal           30
        aperture        44
        aspect          1
        resolution      500 500
    end camera

    instance "cam_inst" "cam"
        transform       0.7719  0.3042 -0.5582 0.0
                        0.0000  0.8781  0.4785 0.0
                        0.6357 -0.3693  0.6778 0.0
                        0.0000 -1.0000 -19.000 1.0
    end instance

    light "light1"
        "mib_light_point" ("color" 1 1 1)
        origin          -50 50 0
    end light

    instance "light_inst" "light1" end instance

    material "mtl"
        "mib_illum_phong" (
            "exponent"  50,
            "ambient"   0.5  0.5  0.5,
            "diffuse"   0.7  0.7  0.7,
            "specular"  1.0  1.0  1.0,
            "ambience"  0.3  0.3  0.3,
            "lights"    ["light_inst"]
        )
    end material

    instance "geo_inst0"
        geometry "wreath" ("radius" 10, "nobjects" 36)
        material "mtl"
    end instance

    instance "geo_inst1"
        geometry "wreath" ("radius" 10, "nobjects" 36)
        material "mtl"
        transform       0 0 1 0   0 1 0 0   1 0 0 0   0 0 0 1
    end instance

    instance "geo_inst2"
        geometry "wreath" ("radius" 10, "nobjects" 36)
        material "mtl"
        transform       1 0 0 0   0 0 1 0   0 1 0 0   0 0 0 1
    end instance

    instgroup "rootgrp"
        "cam_inst" "light_inst" "geo_inst0" "geo_inst1" "geo_inst2"
    end instgroup

    render "rootgrp" "cam_inst" "opt"

Here is the resulting image:

Note that the default assumption for the geometry type is traditional geometry like polygons or NURBS. If hair geometry is needed, an extra step is required if setting up a callback function. Here is an example:

     miTag     tag;
     miObject *obj;

     obj = mi_api_object_begin(mi_mem_strdup(obj_name));
     mi_api_object_callback(obj_cb, (void *)&args);
     tag = mi_api_object_end();

     obj = (miObject *)mi_scene_edit(tag);
     obj->geo.placeholder_list.type = miOBJECT_HAIR;
     mi_scene_edit_end(tag);

Note that all scene elements created by a geometry shader are in a special scope that will be deleted later, when rendering has finished and the scene is postprocessed. That means that a geometry shader cannot generally incrementally change scene database elements that it has created in a previous frame.

Copyright © 1986-2009 by mental images GmbH