Game Development Community

Custom shaders on Custom Model

by Alex Stittle · in Torque X 3D · 02/08/2009 (6:30 pm) · 12 replies

Anyone out there know how to put custom shaders to work on a custom imported DTS model?

Can I leave it to the artist to do the work and the DTS importer will automatically set it up? Or should I do it code?

I am looking through John K's book and it has information regarding how to do this for simple shapes, but unfortunately I can't find any information on how to add custom shaders to DTS models.




#1
02/14/2009 (9:05 pm)
Looked into this a bit and discovered the XNARenderComponent, but unfortunately it has trouble reading my DTS model. I didn't dig too deep, but I think the XNARenderComponent doesn't read DTS model properly. Which is too bad, cause I am pretty sure I could use custom FX/Shaders with it :(

Also, I tried out the GenericMaterial on the TSRenderComponent.Materials but it complains about class casting. Come to think of it, I never tried manually casting it. Maybe that will work, if so, I'll post here.

Otherwise, anyone have insider info?
#2
02/16/2009 (11:24 am)
Hi Alex, I'm working on an article that will go in to much more of a step-by-step approach. But for now (to not waste your time) here are some key pointers.

1. XNARenderComponent is only for loading and rendering .X and .FBX models, it will not work to load a .DTS model. It will also have no impact on model-specific shaders specified in your modeling tool. If you want to show a DTS model in your scene, you must use the T3DTSRenderComponent.

2. A shader is not applied to a 3D model or a 2D sprite. It is applied to a material, or the the entire rendered screen. To render to the screen, you need to use the PostProcessor class. To render to a material, you need to use the GenericMaterial class.

If you can you provide some more details about what your DTS shader will do, I can probably give some more guidance.

John K.
www.envygames.com
#3
02/16/2009 (1:01 pm)
Hey John, thanks for the pointers, and the most excellent news that you are working on an article :)

For now, all I'd like to do is something simple. Convert the colours of a DTS model into only shades of red for example. However, I'd like to do this with the GenericMaterial class because eventually I want to write more complicated shaders (there is a tutorial on shadows I'm reading) and would like to have the know-how to apply any general shader / fx file.

Point number 2 is what is causing me problems I think. If I create a GenericMaterial with a custom effect (which I've failed to do so far, keep getting null pointers) then how would I assign the new instance of the material to replace the materials already on the DTS model? Or does Torque automatically overwrite the instance of the material and register itself when you create a GenericMaterial?
#4
02/16/2009 (4:13 pm)
The new material (with your new shader) is filename-mapped. So, when your DTS uses a specific texture file, that file will be associated with the new material and shader. Since this will apply the shader effect to your entire model, you will need to done one or both of the following:

1. Breakup your DTS model's textures into separate texture files.
2. Have the shader use information within the texture, such as the alpha channel.

www.envygames.com/content/wp-content/uploads/2009/01/airspace_preview_1_450.jpgIn my Airspace game, above, the jet's animated exhaust flame is part of the airplane's DTS model. The glow shader only glows the exhaust because it is mapped to flame.png and not f16.png. This goes to Point 1 about breaking up the textures.

John K.
www.envygames.com
#5
02/16/2009 (10:43 pm)
Ok, I think I understand how the Textures and Shaders are handled. Well, not completely, cause I still can't get it to work :( but I think I have the basic concepts down.

I experimented with the custom shader that comes with your book, and also with the boombot DTS that comes with Torque. I used the following code to create the GenericMaterial and bind a Texture to it.

GenericMaterial genMaterial = new GarageGames.Torque.Materials.GenericMaterial();
            genMaterial.TextureBinds = new List<GenericMaterialTextureBind>();
            genMaterial.FloatBinds = new List<GenericMaterialFloatBind>();
            
            GenericMaterialTextureBind genBind = new GenericMaterialTextureBind();
            genBind.BindType = BindType.Name;
            genBind.BindAddress = "baseTexture";
            genBind.TextureFilename = "data/shapes/boombot/orange_ID1";
            
            genMaterial.TextureBinds.Add(genBind);            
            genMaterial.EffectFilename = "data/effects/MyShader.xnb";

            MaterialManager.Add(genMaterial.Name, genMaterial);

However, the boombot is unaffected. The MaterialManager does register the material (I did a lookup to verify) but when I load the boombot with the T3DTSRenderComponent and look through the Materials[] array to find the Material that goes with orange_ID1. In the private variables I see that the _renderMatrial is null. Which leads me to believe the shader was never actually applied to the model. That, and the boombot looks exactly the same as always :P

Anyways, I don't want to consume your precious time with pointless questions if you are writing an article to answer them all anyways.

Just thought I'd let you know what's going on in case it's an easy oversight.

Btw, the f16 model looks pretty sweet!
#6
02/19/2009 (8:28 pm)
Alright, I've got my custom shader now being called, but there is only one problem... the ViewProjectionMatrix being sent in doesn't transform my point correctly! D'oh! I was so close too...

So, new question. How and which matrix should I be binding in my Shader? Currently, I am using:

float4x4 ViewProjectionMatrix;

Shader Code is Here (from http://www.riemers.net/eng/Tutorials/DirectX/Csharp/Series3/Vertex_Shader.php)
struct VertexToPixel
{
    float4 Position     : POSITION;
    float4 Color        : COLOR0;
};

float4x4 ViewProjectionMatrix;

VertexToPixel SimplestVertexShader( float4 inPos : POSITION)
{
    VertexToPixel Output = (VertexToPixel)0;
    
    //Output.Position = mul(inPos, ViewProjectionMatrix);    

    Output.Position = inPos;
    
    Output.Color = float4(1,1,0,1);    
    
    return Output;    
}

technique Simplest
{
    pass Pass0
    {        
        VertexShader = compile vs_1_1 SimplestVertexShader();
        PixelShader = NULL;
    }
}

Currently, I am setting Output.Position = inPos and it shows up on the screen, but not in the right position at all. Using Output.Position = mul(inPos, ViewProjectionMatrix) results in the matrix not being rendered (somewhere offscreen I suppose).
#7
02/25/2009 (7:30 pm)
Super-Effective! I figured out a way to add custom .fx shaders, woooooo.

Today I discovered the TDN and found this article which opened my eyes to binding custom variables to my shader, and viola! Like magic my shaders were in action.

Here's how I adapted the article to make the Boombot saturate:

The first thing I did was borrow the code for the Saturation.fx effect that I wanted to apply to the Boombot. I modified it slightly by adding 3 variables, World, View, and Projection. The reason I added these was because the worldViewProjection matrix that it had as it's default was not accurately transforming the coordinates of the input vertex and gave me the effect of seeing nothing. You can see I manually do the transform in the SaturationVS function.

Note: Full Credit goes to William Clark for this shader.

//// SaturationEffect.fx ////
float4x4 View; 
float4x4 Projection;
float4x4 World;

texture baseTexture;

float opacity = 1.0;
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;
};

VSOutput SaturationVS(VSInput input)
{
	VSOutput output;
	
    output.position = mul(input.position, World);        
    output.position = mul(output.position, View);
    output.position = mul(output.position, Projection);                     	
		
	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();
    }
}


Now that the shader was ready, I needed to attach it to a material. To do so, I created a SaturationMaterial class (just like in the article). However, my version of the class extended GenericMaterial as opposed to SimpleMaterial. Inside this class, I setup the parameters for the shader in the _SetupObjectParameters so that the transform will work how I want it to in the Effect file. I also randomly set the Saturation parameter to give the Boombot a sort of funkytown glow effect.

Note that in the constructor I point this material to use the saturation effect file created above.
#8
02/25/2009 (7:32 pm)
//// SaturationMaterial.cs ////
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using System;
using System.Collections.Generic;

using GarageGames.Torque.Materials;
using GarageGames.Torque.SceneGraph;
using GarageGames.Torque.T3D;
using GarageGames.Torque.RenderManager;

namespace StarterGame3D
{
    // A specialized material that saturates or desaturates the object's texture.
    public class SaturationMaterial : GenericMaterial
    {
        //======================================================
        #region Constructors
        
        public SaturationMaterial()
        {
            TextureBinds = new List<GenericMaterialTextureBind>();
            FloatBinds = new List<GenericMaterialFloatBind>();
            EffectFilename = @"dataeffectsSaturation.xnb";        
        }
        #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)
        {                              
            EffectManager.SetParameter(_saturationParameter, new Random().Next(10));
            EffectManager.SetParameter(_viewParameter, srs.View);
            EffectManager.SetParameter(_projectionParameter, srs.Projection);
            EffectManager.SetParameter(_worldParameter, renderInstance.ObjectTransform);                        
        }        


        // 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.
            _saturationParameter = EffectManager.GetParameter(Effect, "saturation");
            _projectionParameter = EffectManager.GetParameter(Effect, "Projection");
            _worldParameter = EffectManager.GetParameter(Effect, "World");
            _viewParameter = EffectManager.GetParameter(Effect, "View");         
        }

        // 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.            
            _saturationParameter = null;
            _viewParameter = null;
            _worldParameter = null;
            _projectionParameter = null;

            base._ClearParameters();
        }
        #endregion

        //======================================================
        #region Private, protected, internal fields
        // We'll add our parameters here shortly.

        // The parameter in our effect to which saturation can be uploaded.
        private EffectParameter _saturationParameter;
        private EffectParameter _projectionParameter;
        private EffectParameter _viewParameter;
        private EffectParameter _worldParameter;        

        #endregion
    }
}
#9
02/25/2009 (7:33 pm)
Now all that was left to do was override the Material that the Boombot was originally using. To do this, I loaded in the boombot (orange version) and overwrote one of the Materials it uses to test it. The name of the material is deliberately set to be the same as the Material that the Boombot registers to the MaterialManager when it loads. This is how the override is done, when the new SaturationMaterial is registered, it will replace the old one.

Also, I bound the texture that the shader uses here as well with a GenericBind.

// Create the Material we want to use and name it so it overrides the current one in the MaterialManager
            SaturationMaterial satMaterial = new SaturationMaterial();
            satMaterial.Name = @"datashapesboombotorange_ID1";
            
            // Bind which texture we want to use in the shader
            GenericMaterialTextureBind genBind = new GenericMaterialTextureBind();
            genBind.BindType = BindType.Name;
            genBind.BindAddress = "baseTexture";    // Name of the Texture in the shader
            genBind.TextureFilename = @"datashapesboombotorange_ID1";
            satMaterial.TextureBinds.Add(genBind);

            MaterialManager.Add(satMaterial.Name, satMaterial);

And it worked! So I'm pleased, even if this isn't the most effective way of doing things.

PS: Sorry for the uber long triple posting but I thought this might be useful to other people
#10
03/09/2009 (6:20 am)
Alex,

that was really helpful. I was already waiting for John K's article, but your post made me understand A LOT about Torque's GenericMaterial.

keep on the good work!
Cheers. C:
#11
03/23/2009 (10:15 am)
This is great Alex! I typed up a 7 page outline for working with shaders, but was sidetracked with more engine work. I'll have to circle back and finish it off after GDC wraps up.

John K.
www.envygames.com
#12
05/18/2009 (4:50 am)
Will this work with T3D? or are the engines completly different?