Lighting Interfaces

For implementers who are using plug-in effects or are overriding the rendering loop, the API provides access to the lighting parameters/attributes which are supported. This includes basic attributes for each respective light type as well as shadow map access.

The information is provided as part of the draw context (MDrawContext). The context is made available at the lowest possible level of granularity, which is the point just before setting up a hardware shader for rendering. Shader parameters can be updated using lighting parameter data, as required, before invoking the shader.

It is possible to use the light and shadow information for fixed-function shading if desired, as the interface is only a data provider which places no restrictions on usage.

The basic construct which returns light property information is a MLightParameterInformation.

A list of these represents the set of active lights in the scene. To determine which lights are active, the 3d viewport parameters for interactive rendering and render globals settings for batch rendering are considered. The number is also restricted by the number of lights in a scene that are considered to be visible as well as the number of lights that can be supported by the active GPU device.

Ambient lights are considered to be a single global ambient with no directional properties. This is a property of the rendering framework and not this API.

The interface for light information is parameter based, as it is for shader instances. As not all lights share the same properties, different parameters are accessible depending on the light type. Semantics provide meaning behind parameter values.

It is possible to access the shadow maps generated by the rendering framework. These are returned as textures (an MTexture). The matrix used to lookup the texture with the appropriate transformation is provided as one of the parameters. Samplers for textures are returned as sampler descriptions. Shadow maps are returned based on viewport or batch render settings as well as per light settings (for example, if shadow mapping is enabled for the light)

The following diagram shows how an MDrawContext is made available at render time for MPxShaderOverride, MSceneRender and MUserOperation operations as well as for MShaderInstance instances.

Figure 68: Access to lighting information is based on access to a draw context.

Samples:

The following sample code shows how to extract the shadow map parameters for use with OpenGL:

// Get the number of active lights 
unsigned int lightCount = context.numberOfActiveLights();
for (unsigned int i=0; i<lightCount; i++) 
{
    // Get light parameter information for a given light
    MHWRender::MLightParameterInformation *lightParam = 
        context.getLightParameterInformation( i );
    if (lightParam) {
        
        MStringArray params;
        lightParam->parameterList(params);

        // Scan through all the parameters for this light. They may be differ for different 
        // light types.
        for (unsigned int p=0; p<params.length(); p++) {
            MHWRender::MSamplerStateDesc samplerDesc;
            if (ptype == MHWRender::MLightParameterInformation::kTexture2) {
                // OpenGL specific extraction of the GPU handle for a shadow map texture
                void *handle = lightParam->getParameterTextureHandle( pname );
                if (handle)
                    int openGLHandle = *((int *)handle));
                break;
            }
            else if (ptype == MHWRender::MLightParameterInformation::kSampler) {
                // Get a sampler description.
                lightParam->getParameter( pname, samplerDesc );
            }                                         
        }
    }
}

Shadowing Control

The information in MPassContext provides sufficient information to indicate when a plug-in is being invoked for rendering during shadow map creation. The MPxShaderOverride and MPxDrawOverride interfaces can use this information to either perform less or more complex rendering. By default the renderer itself sets up either default state and / or a default shader for use.

For MPxDrawOverride, the draw code can be optimized to avoid executing code which is only required for color pass rendering. For example any blending operations can be disabled.

For MPxShaderOverride, the draw code may choose to use a custom shadow shader if the color pass shader performs any type of deformation or tessellation.

For convenience, an MShaderInstance can be used from with either plug-in interface. If used from within an MPxShaderOverride, then binding and unbinding should occur at key activation and deactivation time to reduce redundant shader changes. If used from within an MPxDrawOverride, then binding and unbinding should occur within the draw call.

The resulting shadow maps are used for internal rendering and are made available for beauty pass drawing for plug-ins.

The following is sample code for setting up custom shadow casting and drawing for an MPxShaderOverride. The “beauty pass” uses the shadow map and associated matrix available from a MLightParameterInformation instance.

class hwPhongShaderOverride : public MHWRender::MPxShaderOverride
{
protected:
    // Color shader
    MHWRender::MShaderInstance *fColorShaderInstance;
    // Shadow shader
    MHWRender::MShaderInstance *fShadowShaderInstance;
    
public:
    void createShaderInstance()
    {
        MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer();
        const MHWRender::MShaderManager* shaderMgr = renderer->getShaderManager();

        // If no shadow shader instance created yet acquire one. Use
        // the stock shadow shader provided.
        if (!fShadowShaderInstance)
        {
            fShadowShaderInstance = 
                shaderMgr->getStockShader( MHWRender::MShaderManager::k3dShadowerShader );
        }

        // If no color shader instance created yet acquire one. For
        // now it's just using an internal shader for convenience but
        // a custom shader could be written here as well.
        if (!fColorShaderInstance)
        {
            fColorShaderInstance = 
                shaderMgr->getStockShader( MHWRender::MShaderManager::k3dBlinnShader );
        }
    }
    
    /* virtual */ void activateKey(MHWRender::MDrawContext& context, const MString& key)
    {
        // Bind color or shadower shader as appropriate
        if (fInColorPass)
            fColorShaderInstance->bind( context );
        else if (fInShadowPass)
        {
            // Update the parameters on the shadow shader. Use the view projection 
            // matrix from the active context
            MMatrix viewProj =                                     
                context.getMatrix(MHWRender::MDrawContext::kViewProjMtx);
            fShadowShaderInstance->setParameter("shadowViewProj", viewProj );
            fShadowShaderInstance->bind( context );
        }
    }
    
    // Example of using MShaderInstace to draw. Terminate
    // the shader instance here.
    /* virtual */ void terminateKey(MHWRender::MDrawContext& context, const MString& key)
    {
        if (fInColorPass)
            fColorShaderInstance->unbind( context );
        else if (fInShadowPass)
            fShadowShaderInstance->unbind( context );
    }
    
    /* virtual */ bool draw(MHWRender::MDrawContext& context,
                       const MHWRender::MRenderItemList& renderItemList) const
    {
        // Draw for color pass with a blend state change
        if (fInColorPass)
        {
            stateMgr->setBlendState(sBlendState);
            unsigned int passCount = fColorShaderInstance->getPassCount( context );
            for (unsigned int i=0; i<passCount; i++)
            {
                fColorShaderInstance->activatePass( context, i );
                MHWRender::MPxShaderOverride::drawGeometry(context);
            }
            stateMgr->setBlendState(pOldBlendState);
        }
        // Draw for shadow pass
        else if (fInShadowPass)
        {
            unsigned int passCount = fShadowShaderInstance->getPassCount( context );
            for (unsigned int i=0; i<passCount; i++)
            {
                fShadowShaderInstance->activatePass( context, i );
                MHWRender::MPxShaderOverride::drawGeometry(context);
            }
        }
    }
}

The dx11Shader example uses custom shaders.

"Unlimited" lighting information access

NOTE:

You must first call MRenderer::needEvaluateAllLights() in order to request on demand or unlimited light information.

See What's New in Maya 2016 Extension 2: Lighting for more information.

Even though the number of lights used for internal shading is limited by the renderer's light limit clamp, the API interfaces for accessing light information allow you to specify whether to ignore this light limit setting.

The following interfaces have the option to specify a LightFilter parameter to by-pass the light limit:

Shadow / lighting updates "on demand"

NOTE:

You must first call MRenderer::needEvaluateAllLights() in order to request on demand or unlimited light information.

See What's New in Maya 2016 Extension 2: Lighting for more information.

Convert light information from world to view space for fixed function lighting

When setting up lights, it is important to note that light information is returned in world space as opposed to view space. The MFrameContext::kViewMtx parameter can be used as the input parameter for the MDrawContext::getMatrix() method to get the world to view transform.

Programmable shaders may use view space. Fixed function OpenGL lighting uses view space. (There is no fixed function DirectX lighting). In the OpenGL case, the world-to-view matrix should be loaded when setting up lighting.

See the gpuCache MPxDrawOverride Developer Kit example (gpuCacheDrawOverride.cpp file) for an example of using lighting interfaces for fixed function lighting.

A sample setup is shown below that is based on the sample code. All lighting states must be set up by the plug-in. By default, fixed-function lighting is not set up when running a programmable pipeline as it serves no purpose.

The first part of the code checks for an OpenGL limit of 8 lights and the sets the world to view matrix. Note that the gGLFT variable is an pointer to an MGLFunctionTable, and the context variable is the MDrawContext passed to the plug-in.

MStatus status;

// Take into account only the 8 lights supported by the basic
// OpenGL profile.
const unsigned int nbLights =
    std::min(context.numberOfActiveLights(&status), 8u);
if (status != MStatus::kSuccess) return false;

if (nbLights > 0) {
    // Lights are specified in world space and needs to be
    // converted to view space.
    const MMatrix worldToView =
        context.getMatrix(MHWRender::MFrameContext::kViewMtx, &status);
    gGLFT->glLoadMatrixd(worldToView.matrix[0]);

The rest of the general setup enables lighting and sets up parameters such as checking for double sided lighting. In this example, ambient and specular are set to zero.

    gGLFT->glEnable(MGL_LIGHTING);
    gGLFT->glColorMaterial(MGL_FRONT_AND_BACK, MGL_AMBIENT_AND_DIFFUSE);
    gGLFT->glEnable(MGL_COLOR_MATERIAL) ;
    gGLFT->glEnable(MGL_NORMALIZE) ;

    {
        const MGLfloat ambient[4]  = { 0.0f, 0.0f, 0.0f, 1.0f };
        const MGLfloat specular[4] = { 0.0f, 0.0f, 0.0f, 1.0f };

        gGLFT->glMaterialfv(MGL_FRONT_AND_BACK, MGL_AMBIENT,  ambient);
        gGLFT->glMaterialfv(MGL_FRONT_AND_BACK, MGL_SPECULAR, specular);

        gGLFT->glLightModelfv(MGL_LIGHT_MODEL_AMBIENT, ambient);

        // Check for two sided lighting in VP2.0.
        if (context.getDisplayStyle() & MHWRender::MFrameContext::kTwoSidedLighting) {
            gGLFT->glLightModeli(MGL_LIGHT_MODEL_TWO_SIDE, 1);
        }
        else {
            gGLFT->glLightModeli(MGL_LIGHT_MODEL_TWO_SIDE, 0);
        }
    }

The final part of the code loops though each light and sets the appropriate per-light information. Here, the simpler MDrawContext::getLightInformation() method is used (since the requirement is for fixed function lighting), although parameter based methods can also be used.

    for (unsigned int i=0; i<nbLights; ++i) 
    {
        MFloatPointArray positions;
        MFloatVector direction;
        float intensity;
        MColor color;
        bool hasDirection;
        bool hasPosition;
        // Use simple interface to get basic lighting information for the
        // current light.
        status = context.getLightInformation(
            i, positions, direction, intensity, color,
            hasDirection, hasPosition);

Once all information has been obtained, the plug-in decides the type of light that needs to be set up. First, it checks for a directional light. Directional lights can have a position and thus can be interpreted as spotlights. Note that some information here is hard-coded but could be extracted from parameter based interfaces.

        // Handle lights which have a direction:
        if (hasDirection) 
        {
            if (hasPosition) 
            {
                // Set up a “spot light” as we have direction and position
                MFloatPoint position;
                position[0] = positions[0][0];
                position[1] = positions[0][1];
                position[2] = positions[0][2];
                const MGLfloat ambient[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
                const MGLfloat diffuse[4] = { intensity * color[0],
                                                intensity * color[1],
                                                intensity * color[2],
                                                1.0f };
                const MGLfloat pos[4] = { position[0],
                                            position[1],
                                            position[2],
                                            1.0f };
                const MGLfloat dir[3] = { direction[0],
                                            direction[1],
                                            direction[2]};


                gGLFT->glLightfv(MGL_LIGHT0+i, MGL_AMBIENT,  ambient);
                gGLFT->glLightfv(MGL_LIGHT0+i, MGL_DIFFUSE,  diffuse);
                gGLFT->glLightfv(MGL_LIGHT0+i, MGL_POSITION, pos);
                gGLFT->glLightfv(MGL_LIGHT0+i, MGL_SPOT_DIRECTION, dir);

                // Just using default value's for spot lights.
                // Could use the parameter interface to get more information.
                gGLFT->glLightf(MGL_LIGHT0+i,  MGL_SPOT_EXPONENT, 0.0);
                gGLFT->glLightf(MGL_LIGHT0+i,  MGL_SPOT_CUTOFF,  20.0);
            }
            else 
            {                
                // Set up a “directional” light which has a 180 degree cone.
                const MGLfloat ambient[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
                const MGLfloat diffuse[4] = { intensity * color[0],
                                                intensity * color[1],
                                                intensity * color[2],
                                                1.0f };
                const MGLfloat pos[4] = { -direction[0],
                                            -direction[1],
                                            -direction[2],
                                            0.0f };

                gGLFT->glLightfv(MGL_LIGHT0+i, MGL_AMBIENT,  ambient);
                gGLFT->glLightfv(MGL_LIGHT0+i, MGL_DIFFUSE,  diffuse);
                gGLFT->glLightfv(MGL_LIGHT0+i, MGL_POSITION, pos);
                gGLFT->glLightf(MGL_LIGHT0+i, MGL_SPOT_CUTOFF, 180.0);
            }
        }

Next, the plug-in checks for a point light.

        // Handle setting up a point light:
        else if (hasPosition) 
        {
            MFloatPoint position;
            position[0] = positions[0][0];
            position[1] = positions[0][1];
            position[2] = positions[0][2];
            const MGLfloat ambient[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
            const MGLfloat diffuse[4] = { intensity * color[0],
                                            intensity * color[1],
                                            intensity * color[2],
                                            1.0f };
            const MGLfloat pos[4] = { position[0],
                                        position[1],
                                        position[2],
                                        1.0f };


            gGLFT->glLightfv(MGL_LIGHT0+i, MGL_AMBIENT,  ambient);
            gGLFT->glLightfv(MGL_LIGHT0+i, MGL_DIFFUSE,  diffuse);
            gGLFT->glLightfv(MGL_LIGHT0+i, MGL_POSITION, pos);
            gGLFT->glLightf(MGL_LIGHT0+i, MGL_SPOT_CUTOFF, 180.0);
        }

The final check is for an ambient term which is interpreted as an “ambient light”:

        // Handle setting up an ambient light for the ambient term:
        else 
        {
            const MGLfloat ambient[4] = { intensity * color[0],
                                            intensity * color[1],
                                            intensity * color[2],
                                            1.0f };
            const MGLfloat diffuse[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
            const MGLfloat pos[4] = { 0.0f, 0.0f, 0.0f, 1.0f };


            gGLFT->glLightfv(MGL_LIGHT0+i, MGL_AMBIENT,  ambient);
            gGLFT->glLightfv(MGL_LIGHT0+i, MGL_DIFFUSE,  diffuse);
            gGLFT->glLightfv(MGL_LIGHT0+i, MGL_POSITION, pos);
            gGLFT->glLightf(MGL_LIGHT0+i, MGL_SPOT_CUTOFF, 180.0);
        }

The current light is then enabled:

        // Enable the light
        gGLFT->glEnable(MGL_LIGHT0+i);
    }
}

The plug-in example also provides corresponding sample code for disabling lights. See the Developer Kit example for more details.