TorqueX/CustomMaterials
From TDN
|
[edit] Introduction
[edit] Inheriting From SimpleMaterialFirst off, you need to create a new class in its own .cs file. I'm going to be creating a material that saturates/desaturates the texture of the object being drawn with it. Thus, I created SaturationMaterial.cs. For now, it's a pretty simple file:
//// SaturationMaterial.cs ////
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using GarageGames.Torque.Materials;
using GarageGames.Torque.SceneGraph;
using GarageGames.Torque.RenderManager;
namespace MyGame
{
// A specialized material that saturates or desaturates the object's texture.
public class SaturationMaterial : SimpleMaterial
{
//======================================================
#region Constructors
// Initializes a new instance of the SaturationMaterial class.
public SaturationMaterial()
{
// We'll add a bit of code here later to change what's automatically loaded for us.
}
#endregion
//======================================================
#region Private, protected, internal methods
// Sets up this effect and selects a technique to render with.
protected override string _SetupEffect(SceneRenderState srs, MaterialInstanceData materialData)
{
// For now, we'll accept whatever SimpleMaterial would use.
return base._SetupEffect(srs, materialData);
}
// Performs per-object parameter setup.
protected override void _SetupObjectParameters(RenderInstance renderInstance, SceneRenderState srs)
{
base._SetupObjectParameters(renderInstance, srs);
// We'll access some instance data here soon.
}
// Loads the parameters from our effect into this material instance.
protected override void _LoadParameters()
{
base._LoadParameters();
// Here is where we'll find the parameters in our .fx file so we can set them from C# code.
}
// Clears references to parameters from this material.
protected override void _ClearParameters()
{
// Any parameters you found in _LoadParameters are no longer valid when this is called, so you should
// set them to null.
base._ClearParameters();
}
#endregion
//======================================================
#region Private, protected, internal fields
// We'll add our parameters here shortly.
#endregion
}
}
[edit] Using SaturationMaterial on a T2DStaticSprite
[edit] Case 1: TXB generated scene
<SimpleMaterial name="CharacterMaterial" type="GarageGames.Torque.Materials.SimpleMaterial">
<TextureFilename>data/images/Character.png</TextureFilename>
<IsTranslucent>false</IsTranslucent>
<IsAdditive>false</IsAdditive>
</SimpleMaterial>
What you want to do is change the references to SimpleMaterial and GarageGames.Torque.Materials.SimpleMaterial to SaturationMaterial and MyGame.SaturationMaterial (where MyGame was the namespace of the SaturationMaterial class).
<SaturationMaterial name="CharacterMaterial" type="MyGame.SaturationMaterial">
<TextureFilename>data/images/Character.png</TextureFilename>
<IsTranslucent>false</IsTranslucent>
<IsAdditive>false</IsAdditive>
</SaturationMaterial>
The result is that whenever CharacterMaterial is used, it will actually be using SaturationMaterial to render it. Kinda hard to tell at this point, but if you set a breakpoint in any of the functions in SaturationMaterial.cs, you'll see that it is hit. [edit] Case 2: Object created in code
T2DStaticSprite Character = new T2DStaticSprite();
Character.Material = TorqueObjectDatabase.Instance.FindObject<SimpleMaterial>("CharacterMaterial");
// Set the position, add components, etc.
TorqueObjectDatabase.Instance.Register(Character);
The FindObject call is a bit sketchy, but the point is, you create a new Sprite, you set its material to something, and you register it with the game. T2DStaticSprite Character = new T2DStaticSprite(); Character.Material = new SaturationMaterial(); // Set the position, add components, etc. TorqueObjectDatabase.Instance.Register(Character); Just like in Case 1, we're now using SaturationMaterial to render our character, although again, it's hard to tell at this point. [edit] Writing a Custom .fx File
//// SaturationEffect.fx ////
// As a SimpleMaterial, we get worldViewProjection, opacity, and baseTexture passed up to us with
// valid values.
float4x4 worldViewProjection;
float opacity = 1.0;
texture baseTexture;
// We add the saturation parameter.
float saturation = 0.0;
// Set up our sampler to read from baseTexture. NOTE: To do a "retro" pixelated look, change the
// "Linear"s to "Point"s.
sampler2D baseTextureSampler = sampler_state
{
Texture = <baseTexture>;
MipFilter = Linear;
MinFilter = Linear;
MagFilter = Linear;
};
struct VSInput
{
float4 position : POSITION;
float2 texCoord : TEXCOORD0;
};
struct VSOutput
{
float4 position : POSITION;
float2 texCoord : TEXCOORD0;
};
// Our vertex shader doesn't have to do anything surprising.
VSOutput SaturationVS(VSInput input)
{
VSOutput output;
output.position = mul(input.position, worldViewProjection);
output.texCoord = input.texCoord;
return output;
}
// Our pixel shader is where it's at.
float4 SaturationPS(VSOutput input) : COLOR
{
// First get our color out of the texture.
float4 color = tex2D(baseTextureSampler, input.texCoord);
// Then get a gray that's the same luminance (in some sense. Don't get me started about L1 vs. L2 norms etc.)
float gray = (color.r + color.g + color.b) / 3;
// And finally use our saturation value to increase/decrease our saturation.
color.rgb = lerp(gray, color, saturation);
// This lets the VisibilityLevel of our object (which is passed to opacity) show up as fading in/out.
color.a *= opacity;
return color;
}
technique SaturationTechnique
{
pass P0
{
VertexShader = compile vs_2_0 SaturationVS();
PixelShader = compile ps_2_0 SaturationPS();
}
}
That's all well and good, but we're not using this effect yet. To do that, we need to make some changes in SaturationMaterial.cs.
public SaturationMaterial()
{
EffectFilename = "data/effects/SaturationEffect";
}
Second, choose our SaturationTechnique (one .fx file can have several different techniques if you wish). In _SetupEffect, replace the contents of the function with:
// Sets up this effect and selects a technique to render with.
protected override string _SetupEffect(SceneRenderState srs, MaterialInstanceData materialData)
{
// We're gonna ignore the technique SimpleMaterial would choose, but still let it setup.
base._SetupEffect(srs, materialData);
return "SaturationTechnique";
}
If all went well, you should have your character rendering in grayscale (or greyscale, you silly Brits/Canadians). [edit] Passing Parameters to the .fx File
// The parameter in our effect to which saturation can be uploaded.
private EffectParameter _saturationParameter;
// The current saturation value to upload.
private float _saturation = 0;
// Gets or sets our current saturation value.
public float Saturation
{
get { return _saturation; }
set { _saturation = value; }
}
Next, we find our parameters by name from the .fx file, in the _LoadParameters function: // Here is where we'll find the parameters in our .fx file so we can set them from C# code. _saturationParameter = EffectManager.GetParameter(Effect, "saturation"); Now, to be proper, in _ClearParameters, we need to null out our invalid _saturationParameter: // Any parameters you found in _LoadParameters are no longer valid when this is called, so you should // set them to null. _saturationParameter = null; Finally, in _SetupObjectParameters, we pass our saturation value up to our technique: // Upload our current saturation value. EffectManager.SetParameter(_saturationParameter, _saturation); Now, if we do something like... ((SaturationMaterial)Character.Material).Saturation = -1; ...we'll see the material change based upon this parameter, in realtime. [edit] ConclusionHopefully you've got enough information now to write all kinds of crazy custom materials for your games. Let me know if I can help ya with anything. |



