Per-object Shader Parameters
by Matt Jolly · in Torque Game Engine Advanced · 03/25/2009 (10:23 pm) · 9 replies
I've been working at this for a few days now, and with the help of Tim on IRC I've made some progress--I'm able to pass two colors and the fade value of a ShapeBase object to the shader and have it render correctly. However, this applies to *all* objects with that model, so all players are rendered in the same color at the same alpha transparency. I need to set it up so that each player is rendered in their own color and transparency.
At the moment I'm setting the shader constants in TSMesh::render, as this resource suggests. It isn't very effective though.
Doing some searching I've found posts saying that I should be setting the shader values in RenderMeshMgr::render, so that the parameters are set to that specific object's color/fade values just before the object is rendered. The only problem is I can't access those values in the object being rendered. I would need a pointer to, at the very least, a SceneObject. Is there a way to obtain that for the current mesh being rendered by RenderMeshMgr? Or am I going about this completely the wrong way?
I'm at a loss, and I've pretty much reached the limits of my knowledge of shaders (though I've learned a lot in the past few days), so any help would be appreciated.
At the moment I'm setting the shader constants in TSMesh::render, as this resource suggests. It isn't very effective though.
Doing some searching I've found posts saying that I should be setting the shader values in RenderMeshMgr::render, so that the parameters are set to that specific object's color/fade values just before the object is rendered. The only problem is I can't access those values in the object being rendered. I would need a pointer to, at the very least, a SceneObject. Is there a way to obtain that for the current mesh being rendered by RenderMeshMgr? Or am I going about this completely the wrong way?
I'm at a loss, and I've pretty much reached the limits of my knowledge of shaders (though I've learned a lot in the past few days), so any help would be appreciated.
#2
You can do something like the following:
Where paramName is a string containing the name of your shader's parameter preceded by a "$". i.e. if your shader param is timeData, then paramName should be "$timeData". And paramValue is the value you want to set the shader const to.
The set method of MaterialParameters will take most common types of shader params as the value. i.e. if you want to pass a float then just pass a F32 value, if you want to pass a float2 then pass in a Point2F, if you want to pass a float4 then pass in a Point4F, if you want to pass a float4x4 then pass a MatrixF, etc. Arrays are a little trickier, you'll want to use an AlignedArray for those.
You can do that in RenderMeshMgr::render by casting ri->matInst to a MatInstance first.
03/26/2009 (12:39 am)
BTW, after looking at that resource, I should point out that there's a somewhat more shader-friendly way to set shader params in 1.8.1, by using the name of the shader param rather than it's register index.You can do something like the following:
MaterialParameters* matParams = mMatInst->getMaterialParameters();
MaterialParameterHandle* handle = mMatInst->getMaterialParameterHandle(paramName);
if (handle)
{
matParams->set(handle, paramValue);
}Where paramName is a string containing the name of your shader's parameter preceded by a "$". i.e. if your shader param is timeData, then paramName should be "$timeData". And paramValue is the value you want to set the shader const to.
The set method of MaterialParameters will take most common types of shader params as the value. i.e. if you want to pass a float then just pass a F32 value, if you want to pass a float2 then pass in a Point2F, if you want to pass a float4 then pass in a Point4F, if you want to pass a float4x4 then pass a MatrixF, etc. Arrays are a little trickier, you'll want to use an AlignedArray for those.
You can do that in RenderMeshMgr::render by casting ri->matInst to a MatInstance first.
#3
However, it still appears to be "batching". If I add an AIPlayer with a different color, my player will be colored with his color until I walk far enough away so that the AIPlayer is no longer on the screen (and no longer rendered). This is while using the method in the resource I linked in my first post, where I store the color to the sgData struct which I later access in processedCustomMaterial::setupPass. I've also tried setting it like accumTime is set, using MaterialManager to set and read the values. For instance, in RenderMeshMgr::render, I call
With this method, the player is rendered in black (so the values aren't being sent to the shader), unless I rotate and zoom the camera a certain way--then it begins to work (which is odd). The instancing issue is still present though, so when I add an AI unit my player's color is set to theirs.
I've tried sticking setPrimaryColor almost everywhere in RenderMeshMgr::render, but I can never get two objects with different colors to render independently. One object always renders in the other objects color.
03/26/2009 (1:47 pm)
Alright, I added a SceneObject reference to MeshRenderInst, and I'm getting the correct values for the object's color in RenderMeshMgr::render.However, it still appears to be "batching". If I add an AIPlayer with a different color, my player will be colored with his color until I walk far enough away so that the AIPlayer is no longer on the screen (and no longer rendered). This is while using the method in the resource I linked in my first post, where I store the color to the sgData struct which I later access in processedCustomMaterial::setupPass. I've also tried setting it like accumTime is set, using MaterialManager to set and read the values. For instance, in RenderMeshMgr::render, I call
MaterialManager::get()->setPrimaryColor(ri->mObject->mPrimaryColor)which I then access in processedCustomMaterial::setupPass:
if (rpd->shaderHandles.mObjMainColSC)
shaderConsts->set(rpd->shaderHandles.mObjMainColSC, MaterialManager::get()->getPrimaryColor());(Hackish, I know)With this method, the player is rendered in black (so the values aren't being sent to the shader), unless I rotate and zoom the camera a certain way--then it begins to work (which is odd). The instancing issue is still present though, so when I add an AI unit my player's color is set to theirs.
I've tried sticking setPrimaryColor almost everywhere in RenderMeshMgr::render, but I can never get two objects with different colors to render independently. One object always renders in the other objects color.
#4
What I would do is set the constant values in the inner loop of RenderMeshMgr::render, right before the drawPrimitive call.
03/26/2009 (2:31 pm)
You're not going to be able set the constants in setupPass. The reason is setupPass is only called once for each material instance, to minimize state changes, and your players are all sharing a material instance. So whatever color the first one in the batch is using will be used by all of them.What I would do is set the constant values in the inner loop of RenderMeshMgr::render, right before the drawPrimitive call.
#5
I've added:
Now the texture I've applied the shader to isn't rendering. The other textures on the model are, but not the texture I need to color. I've tried debugging the program, and the colors look to be set correctly, so I dunno.
Also, I took out the code in the shader that set the textures alpha to mFadeVal, and it still doesn't render.
EDIT: I removed the following from the texture's CustomMaterial definition:
Trouble is, I kind of need translucency support. >.<
03/26/2009 (3:14 pm)
I appreciate your help Gerald.I've added:
if(passRI->mObject)
{
Con::errorf("Setting shader constants for obj %d",passRI->mObject->getId());
MaterialParameters* matParams = mat->getMaterialParameters();
MaterialParameterHandle* handle = mat->getMaterialParameterHandle(ShaderGenVars::primaryObjCol);
if (handle)
{
matParams->set(handle, passRI->mObject->mPrimaryColor);
}
handle = mat->getMaterialParameterHandle(ShaderGenVars::secondaryObjCol);
if (handle)
{
matParams->set(handle, passRI->mObject->mSecondaryColor);
}
handle = mat->getMaterialParameterHandle(ShaderGenVars::objFade);
if (handle)
{
matParams->set(handle, passRI->mObject->mFadeVal);
}
}aboveGFX->drawPrimitive( passRI->primBuffIndex );in RenderMeshMgr::render, and took out the code I had in processedCustomMaterial::setupPass.
Now the texture I've applied the shader to isn't rendering. The other textures on the model are, but not the texture I need to color. I've tried debugging the program, and the colors look to be set correctly, so I dunno.
Also, I took out the code in the shader that set the textures alpha to mFadeVal, and it still doesn't render.
EDIT: I removed the following from the texture's CustomMaterial definition:
translucent = true; translucentBlendOp = LerpAlpha; translucentZWrite = true; blendOp = LerpAlpha;and now it's working!
Trouble is, I kind of need translucency support. >.<
#6
Ah, and for the love of God, add NULL checks for all those mObject calls.
03/26/2009 (3:24 pm)
RenderMeshMgr only draws opaque meshes. Translucent meshes are handled by RenderTranslucentMgr, so you need to add your changes there as well.Ah, and for the love of God, add NULL checks for all those mObject calls.
#7
Edit: @Manoel, if(passRI->mObject) is checking for NULL, no need to be redundant :p
03/26/2009 (3:27 pm)
Ahh, it didn't sink in that you were using translucency. In that case you'll also need to do this in RenderTranslucentMgr, since translucent meshes are rendered from there so that they're rendered after everything else.Edit: @Manoel, if(passRI->mObject) is checking for NULL, no need to be redundant :p
#8
I can't thank you enough Gerald; you've saved me days, maybe weeks, of pulling out my hair trying to get this shader working properly, and for that I am grateful.
And thanks to you too Manoel!
03/26/2009 (3:39 pm)
Ah, that would make sense. I really need to go through and learn more about the graphics system in Torque, it's not something I fiddle with often. I prefer gameplay/AI.I can't thank you enough Gerald; you've saved me days, maybe weeks, of pulling out my hair trying to get this shader working properly, and for that I am grateful.
And thanks to you too Manoel!
#9
03/26/2009 (3:45 pm)
No problem, Mike. It wasn't too long ago that I was fighting my way through the same types of things. It's pretty daunting at first but once you see how everything fits together you'll be saying 'Aha! But this is so easy!'
Torque Owner Gerald Fishel
Development Ninja
You're on the right track. To further steer you in the right direction, what you're probably going to want to do is put the pointer to your object in the MeshRenderInst object created in TSMesh::render.
There are a couple of ways you can do it. You can add it as the second "key" in the render instance, i.e.:
Or you can add a new field to MeshRenderInst to hold the pointer.
Then you can access your SceneObject from the MeshRenderInst in RenderMeshMgr::render to set the shader values.
TSMesh::smObject is a static member variable holding a pointer to the scene object that is currently generating the render instances in TSMesh::render. Most objects, like ShapeBase, will set that pointer using TSMesh::setObject before having TSMesh generate the render instances.
That should get you started, let me know if you need any more info.
Cheers