home << prev next >> contents  

NVIDIA Cg 1.2 Shaders

NVIDIA's Cg 1.2 release introduced interfaces to the Cg programming language, which correspond to mental ray's shader graph and Phenomenon design, and enable mental ray to construct a composite Cg shader that corresponds exactly, subshader by subshader, to the software shader graph. Cg versions prior to 1.2 are not supported by mental ray.

In almost all cases, it is sufficient to write only the fragment shader in Cg. If no vertex shader is supplied, mental ray supplies a built-in default vertex shader. All illumination calculations are performed in the fragment shader.

Also, Cg shaders do not normally depend on a shader support library the way OpenGL shaders do. No C/C++ code is required to use any of the standard Cg shaders shipped with mental ray, so no support libraries are supplied. Only the Cg files, named according to the conventions described above, such as myphong_f.cg, must be available on the hardware search path (defined with -hardware_path). This makes it very simple to make a third-party Cg shader available to mental ray. Each shader must be in a separate file, or pair of files if a vertex shader is required as well.

Shader Parameters

Hardware shaders read their parameters from a set of registers on the graphics board called uniform variables, so mental ray needs to know how to copy the shader parameters to the uniform variables before the shader is started, and the Cg compiler needs to have a matching definition. Here is the Cg syntax for defining shader parameters:

    interface miiBoolean {
        bool eval(vert2frag param);

    interface miiInteger {
        int eval(vert2frag param);

    interface miiScalar {
        float eval(vert2frag param);

    interface miiVector {
        float3 eval(vert2frag param);

    interface miiColor {
        float4 eval(vert2frag p);

    interface miiColorTexture {
        float4 eval(vert2frag param, float3 uv);

    interface miiMatrix {
        float4x4 eval(vert2frag param);

    interface miiLight {
        misLightOut eval(vert2frag param);

This is the complete list of available types. For example, the mental ray data type miBoolean is declared to the Cg compiler as miiBoolean, implemented as the Cg bool type. The eval function tells the Cg compiler that the parameter might be attached to another shader, which must also be compiled and downloaded to the graphics board. (Attaching shader outputs to shader parameters is how both mental ray and Cg implement shader graphs.)

Cg shader parameters are declared in the shader code, right in the function body. For example, if the software shader declaration is defined in .mi syntax as

    declare shader
        color "myshader" (
            color         "param1",
            scalar        "param2",
            vector        "param3",
            color texture "param4"
    end declare

then it is sufficient to declare these four parameters in the Cg shader body, using the mii* data types listed above, using the same names. Note that you can omit parameters from the declaration. For example, here is a possible implementation of the shader in Cg:

    struct myshader : miiColor
        miiColor        param1;
        miiScalar       param2;
        miiVector       param3;
        miiColorTexture param4;

        float4 eval(vert2frag p) {
            float4 c = param1.eval(p);
            float  s = param2.eval(p);
            float3 v = param3.eval(p);
            float4 tex = param4.eval(p, float3(u,v,w));
            return float4(1,1,1,1);

The Cg compiler will be run automatically by mental ray at render time. It will collect all required shaders by following the eval links, and create a single binary shader ready for downloading to the graphics board. Users only need to supply the Cg source code and leave compilation and downloading to mental ray. For more information on the Cg language, see NVIDIA's Cg 1.2 compiler reference manual.

Implicit Shader Parameters

mental ray implicitly supplies several extra parameters for light and material shaders. They are automatically downloaded to the fragment shader when matching names are found in the Cg declaration.

usage name type description
material miLocal2Global CG_FLOAT4x4 Matrix for transforming local coordinates found in vert2frag.position to world space.
light light_origin CG_FLOAT3 Position of the light in camera space
light_dir CG_FLOAT3 Direction to the light in camera space
light_spread CG_FLOAT Spread angle for spot lights
light_type CG_INT Type of the light from miLight_type
light_face CG_INT Illuminate which face of objects: 0=both, 1=front side only, 2=backside only
camera cam_aspect CG_FLOAT Aspect ratio (height*aspect = width)
cam_aperture CG_FLOAT Aperture
cam_focal CG_FLOAT Focal (atan(focal/aperture) = field of view)
cam_clip CG_FLOAT2 Clip plane (x=near, y=far)
cam_size CG_FLOAT2 Resolution of the window
cam_ortho CG_BOOL Is camera orthographic?
cam_transfo CG_FLOAT4x4 Camera transformation
shadowmap shdmap_on CG_BOOL Tell if the shadowmap option is on for the light
shdmap_tex[] samplerRECT Number of shadowmap textures, 1 for spot and directional lights, 6 for point lights
shdmap_transfo CG_FLOAT4x4 Matrix for transforming vert2frag.cPosition to light space
shdmap_window CG_FLOAT2 The transformation to apply to UV coordinates for perspective and parallel views
shdmap_size CG_FLOAT2 The resolution of the shadowmap
shdmap_bias CG_FLOAT The bias to apply to the shadowmap

Shader C/C++ Support Functions

In case a Cg shader is not sufficient, and extra work is needed, a C/C++ support function can be supplied in a separate DSO/DLL. Those functions are mostly useful when the information we want to pass to the shader is not part of the shader declaration. For example, a noise function could compute a lattice data structure and pass it automatically to the shader. The C/C++ function name must begin with the prefix micg_. For example, here is a C++ support function for the Cg shader nvo_img_intensity:

    extern "C" DLLEXPORT miBoolean micg_nvo_img_intensity(
        CGparameter         *result,
        miCg_render         *render,
        miHW_shader         *shader)
        static bool binit = true;

        if (binit) {
            binit = false;

        CGparameter sParam = 0;
        CGcontext   ctx  = render->context_get();

        // Create an instance of the shader
        CGtype cgtype = cgGetNamedUserType(render->program_get());

        sParam = cgCreateParameter(ctx, cgtype);
        *result = sParam;

        // Set automatically all known parameters from the
        // shader declaration
        int i, nb_param = shader->nbparam_get();
        for (i=0; i < nb_param; i++) {
            render->param_eval(sParam, shader->param_get(i));

        // Set the random texture
        CGparameter prnd = cgGetNamedStructParameter(sParam, "random_txt");
        cgGLSetTextureParameter(prnd, random_map);
        prnd = cgGetNamedStructParameter(sParam, "random_size");
        cgGLSetParameter1f(prnd, RES);

        return miTRUE;

This support function installs a Cg shader including parameters, and also loads a texture.


Here is a Cg 1.2 implementation of the mib_illum_phong base material shader:

    struct mib_illum_phong : miiColor
        miiColor   ambience, ambient, diffuse, specular;
        miiScalar  exponent;
        miiInteger mode;
        miiLight   lights[];

        float4 eval(vert2frag p)
            float4 result;
            float4 lambience = ambience.eval( p);
            float4 lambient  = ambient.eval( p);
            float4 ldiffuse  = diffuse.eval( p);
            float4 lspecular = specular.eval( p);
            float  lexponent = exponent.eval(p);
            float3 vdir      = normalize(p.cPosition);

            result = lambience * lambient;

            // Material calculation for each light
            for (int i=0; i < lights.length; i++) {
                misLightOut light = lights[i].eval(p);

                // Lambert's cosine law
                if (light.dot_nl > 0) {
                    result += light.dot_nl * ldiffuse * light.color;

                    // Phong's cosine power
                    float s = mi_phong_specular(lexponent, light.dir, vdir,
                    result += s * lspecular * light.color;
            return result;

Light Shaders work the same way as material shader, but instead of implementing miiColor they must implement the miiLight interface:

    struct mib_light_spot : miiLight {
        miiColor    color;
        miiBoolean  shadow;
        miiScalar   factor;
        miiBoolean  atten;
        miiScalar   start;
        miiScalar   stop;
        miiScalar   cone;

        // Information from the light instance
        float3      origin;             // position of the light
        float3      ldir;               // direction of the light
        float       spread;             // cos angle of the spread

        samplerRECT shdmap_tex[1];      // light shadowmap
        float4x4    shdmap_transfo;     // From eye to local light space
        float2      shdmap_window;      // size of the window
        float2      shdmap_size;        // size of the shadowmap
        float       shdmap_bias;

        // Evaluation of the light
        misLightOut eval(vert2frag p) {
            misLightOut ret_val;
            float4 lcolor = color.eval(p);

            // same for each light?
            ret_val.dir = normalize(origin - p.cPosition); // vector to light
            ret_val.dot_nl = dot(p.cNormal, ret_val.dir);

            // shadow
            if (shadow.eval(p)) {
                float lfactor = factor.eval(p);

                // Shadow coordinates are created form the eye-space
                // position, then compare with the shadowmap
                float4 lPosition = mul(shdmap_transfo, float4(p.cPosition,1));
                float2 shadowUV  = (lPosition.xy/(-lPosition.z * shdmap_window)
                                                        + 0.5) * shdmap_size;
                float4 dist = length(lPosition.xyz);

                float4 pdepth;
                shadowUV -= 0.5;
                pdepth.x = f1texRECT(shdmap_tex[0], float2(shadowUV.x,
                pdepth.y = f1texRECT(shdmap_tex[0], float2(shadowUV.x,
                pdepth.z = f1texRECT(shdmap_tex[0], float2(shadowUV.x+1,
                pdepth.w = f1texRECT(shdmap_tex[0], float2(shadowUV.x+1,

                pdepth = pdepth == float4(0,0,0,0) ? dist*2 : pdepth;
                float4 shd = dist - shdmap_bias - pdepth;
                float map_z = dot(shd < float4(0,0,0,0) ? float4(1,1,1,1) :
                                            float4(0,0,0,0), float4(1,1,1,1));

                lcolor.rgb *= lerp(lfactor,1, map_z/4);

            // cone
            float d = dot(ret_val.dir, ldir);
            if (d <= 0) {
                lcolor.rgb = 0;
            if (d < spread) {
                lcolor.rgb = 0;
            float lcone = cone.eval(p);
            if (d < lcone) {
                lcolor.rgb *= 1 - (d - lcone) / (spread - lcone);
                lcolor = saturate(lcolor);

            // dist attenuation
            bool latten = atten.eval(p);
            if (latten) {
                float dist  = length(origin - p.cPosition);
                float lstop = stop.eval(p);
                if (dist >= lstop) {
                    lcolor.rgb = 0;
                } else {
                    float lstart = start.eval(p);
                    float t = 1 - (dist - lstart) / (lstop - lstart);
                    lcolor.rgb *= t;
            ret_val.color = lcolor;
            return ret_val;
home << prev next >> contents  

Copyright © 1986-2007 by mental images GmbH