Keywords: main

Next: 3.21 Contours Up: 3. Using and Writing Previous: 3.19 Displacement Shaders

# 3.20 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);
        }

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

            obj = mi_api_object_begin(NULL);
            obj->visible = obj->shadow = obj->trace = miTRUE;
            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 flags visible, shadow and trace set to `miTRUE`, a 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 object3.x. 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;

        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));
                obj->visible = obj->trace = obj->shadow = miTRUE;
                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 %sN", oname);
            mi_api_incremental(miTRUE);
            obj = mi_api_object_begin(mi_mem_strdup(oname));
            obj->visible = obj->shadow = obj->trace = miTRUE;
            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);

Next: 3.21 Contours Up: 3. Using and Writing Previous: 3.19 Displacement Shaders