by Pat Wilson
Torque3D includes a standard set of material features which meet the basic needs of most visual effects. Most shader features in Torque3D are controlled by parameters that are set on a per-material basis; each shape instance which shares a material uses the same parameters to render. Some visual effects require shapes, which share the same material, to use different values for each instance of the shape. This document will provide the programmer with an overview of the render-instance system in Torque3D, an explanation of how to add per-instance data to the TSStatic object, and an example implementation for getting data from game-play scripts into a shader constant.
This document builds on the example introduced in the document: Extending the Torque3D Material System
Rendering a Mesh in Torque3D: Starting at DrawPrimitive
The first step to implementing this feature is understanding how a mesh gets drawn in Torque3D. We will work backwards, starting with the ‘DrawPrimitive’ call, working all the way back to the game object which it represents. This overview will touch on the RenderBin system, the TSMesh code, game objects inheriting from ShapeBase as well as TSStatic, and explain how to pass data between these discrete systems.
The final call issued to render almost every mesh object in Torque3D is located in ‘RenderMeshMgr::render()’. RenderMeshMgr contains a list of MeshRenderInst, each of which describes a mesh instance which needs to be rendered. As you can see, from the code to the right, this structure contains the basic information needed to render a mesh instance (abridged version, see ‘Engine/source/renderInstance/renderPassManager’); the vertex buffer, primitive buffer (index buffer + geometry information), transforms, and material.
For each of these instances, the RenderMeshMgr will set vertex and primitive information, pass the relevant, per-instance data to the BaseMatInstance, and then issue a DrawPrimitive call.
The transforms are a perfect example of per-instance data, so we will trace their path. Each MeshRenderInst contains the transforms that it needs to draw properly, and these get assigned to shader constants prior to render in ‘RenderMeshMgr::render()’ with the following call:
This code passes the per-instance transforms through some methods into ‘ProcessedShaderMaterial::setTransforms’. Here, the transforms get combined, manipulated and set as shader constants. Where do these transforms get assigned? For that we go to the next step in our backtracking of a mesh render.
The TSMesh class encapsulates a huge amount of functionality, most of which is way out of the scope of this document. We’ll start with a look at TSMesh::innerRender (Heavily abridged, see ‘Engine/source/ts/tsMesh.cpp’).
This is where the MeshRenderInst gets allocated, and later added to the active RenderPassManager for the SceneState. This, in turn, provides it to the RenderMeshMgr, which adds it to its list of instances because it has a compatible type, ‘RenderPassManager::RIT_Mesh’. The variable I would like to draw attention to is ‘rdata’ which has been passed in as a parameter to ‘TSMesh::innerRender’. This parameter gets passed through a chain of methods. Working backwards: TSMesh::innerRender(), is called by TSMesh::render(), which is called by TSShapeInstance::render(), which is called by ShapeBase::render() as well as TSStatic::prepRenderImage(). I will be skipping over TSShapeInstance, because it is outside the scope of this document, and does not modify the data member in which we are interested.
ShapeBase and TSStatic
The next step in this chain are the game object base-classes ShapeBase, and TSStatic. These classes each have a TSShapeInstance member variable which gets a call to ‘render()‘, and is passed an instance of TSRenderState** which contains the data we are following, among other things.
Each instance of TSStatic has several data members, which are exposed to the scripting language in ‘TSStatic::initPersistFields()’. We will be modifying TSStatic simply because it makes for shorter, cleaner example code, but the concepts introduced are applicable to ShapeBase, GameBase, or wherever your game-object needs take you.
Adding Per-Instance Data to TSStatic
Now that we have skimmed over the path for data to get from a game-object to a draw primitive call, we are going to use the material feature that we created in the "preceding document" and change how the data gets assigned to the shader constant. Previously, the value for the ‘multConst’ parameter was assigned from a parameter specified in the Material definition in script. Once this part of the document is complete, the value will be assigned from a parameter which can differ for any TSStatic in the scene. To do this, we will start by adding the script-accessible data field on TSStatic and follow the data path forward, all the way back to RenderMeshMgr.
The first task is to remove the ‘mMultConstant’ data member that we added to Material and put it in TSStatic. Remember to initialize the data member in the constructor (don’t initialize to -1.0 alpha any more), and expose it as a field in ‘TSStatic::initPersistFields()’. Remove the code that was added in the section ‘Exposing Data to the Shader Feature’ in the "preceding document", as well.
Now we must make sure that this member is updated over the network, by modifying ‘packUpdate()/unpackUpdate()’. Please note that use of update masks, and packing should be considered for a real feature implementation, however this delves slightly deeper into Torque3D networking, and is out of the scope of this document. For now we will simply write the full color value on each call to ‘packUpdate()’.
With this code in place, lets turn attention to ‘TSStatic::prepRenderImage()’. In this method, a TSRenderState instance, called ‘rdata’ is created and passed to the render() method of TSShapeInstance. Modify the TSRenderState structure in ‘Engine/source/ts/tsRenderState.h’ to include the additional data member.
Next add a call to the new method in ‘TSStatic::prepRenderImage()’.
This will cause the value to be passed down the chain we traced earlier all the way into ‘TSMesh::innerRender()’ and that is our next stop. This is where data gets assigned to a MeshRenderInst and then passed to the active RenderPassManager. First add a member to MeshRenderInst in ‘Engine/source/renderInstance/renderPassManager.h’ for the data we are passing around:
Then add an assignment in ‘TSMesh::innerRender()’ near the following code:
The next step is to provide a path for communication between RenderMeshMgr and ProcessedShaderMaterial. We can do that many ways, including making methods, but for the ease of this example, modify the SceneGraphData structure in ‘Engine/source/materials/sceneData.h’.
Next add the following line to ‘RenderBinManager::setupSGData()’ in ‘Engine/source/renderInstance/renderBinManager.cpp’.
RenderMeshMgr calls ‘RenderBinManager::setupSGData()’ during its render loop, which will assign the value into the SceneGraphData structure. In order to get it into the shader constants, I am going to introduce a different method for setting shader constants than I demonstrated previously. Modify the ConstantMultFeature class to include the following:
This method for assignment of shader constants allows you to encapsulate the handle acquisition and value assignment within the shader feature which requires those constants.
One final task remains, to enable MFT_ConstantMult during ‘ProcessedShaderMaterial::_determineFeatures()’. To do this, we will add a boolean field on Material. I have provided the code snippets, for a step-by-step, see the "preceding document".
Recall that we modified the Material definition to include a color value for ‘multConstant’. Change that to simply indicate ‘true’.
Next, fire up the Torque3D World Editor, and add some shapes that use that material. If you inspect the object you created, you will see that the field we exposed on TSStatic is visible in the inspector window. You can use the inspector to modify the color value using the color selector widget, and you should see your shape changing colors. If you add multiple copies of the shape, each copy stores, and draws, its own value for the color multiply.
This document should provide the programmer with the tools needed to implement shader effects which have parameters that vary on a per-shape basis. Combining the techniques learned in this document with other powerful constructs in Torque3D will allow you to create the visual effects that you need for your game.