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:
MDrawContext
provided and binds the appropriate parameters to the light parameters on a given HLSL shader.MPxShaderOverride
has simple code which demonstrates how to query light information.MPxDrawOverride
shows how the lighting information can be used to set up fixed function lighting using a simpler interface which is not parameter based.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; iMHWRender::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; pMHWRender::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 );
}
}
}
}
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; iactivatePass( 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; iactivatePass( context, i );
MHWRender::MPxShaderOverride::drawGeometry(context);
}
}
}
}
The dx11Shader example uses custom shaders.
Note: You must first call
MRenderer::needEvaluateAllLights()
in order to request on demand or unlimited light 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:
MDrawContext::numberOfActiveLights()
MDrawContext::getLightInformation()
MDrawContext::getLightParameterInformation()
Note: You must first call
MRenderer::needEvaluateAllLights()
in order to request on demand or unlimited light information.
MRenderer::setLightRequiresShadows()
can be used to either queue or remove from queue such a request. This method does not force a given shadow map to be computed, nor does it force any refresh or new render to occur. It triggers a check for the need for a shadow map update. MRenderer::setLightsAndShadowsDirty()
(see Change Management) can be used to force computation, if necessary.MPxShaderOverride
), then updates occur in the next frame (refresh). If called from a render override operation, then the next scene render triggers an update. The scene render is either the next MSceneRender
called from within an override, or the next scene render called by the renderer internally when there is no render override.Example code using this interface can be found in the dx11Shader (MPxShaderOverride
) and viewRenderOverrideShadows (MRenderOverride
) plug-ins. The dx11Shader plug-in has code that illustrates invoking an additional refresh on light binding changes.
Generally, lighting information is only updated during a scene render (MSceneRender
). If a shadow request is performed from within a user operation (MUserRenderOperation
) in a custom render override, then only the last available lighting information is available. If a user operation requires lighting information, the new virtual method MUserRenderOperation::requiresLightData()
can be overridden and set to return true.
MSceneRender::preSceneRender()
and MSceneRender::postSceneRender()
. They are called before and after either: shadow map update, or the rendering of the scene. At this time, the current draw context is available, and hence lighting/shadowing information (MLightParameterInformation
).viewRenderOverrideShadows
render override plug-in. This plug-in performs a user operation to queue shadow requests and then uses them in a custom scene render. The pre-scene render method is overridden to extract the requested shadow maps for use during scene rendering by a custom shader. Light and shadow information beyond the light limit can be accessed.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; iMFloatPointArray 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.