Ab3d.DXEngine support using custom shaders that are used to render some 3D objects.
When I wanted to answer your question, I have found out that there is no good sample on how to on how to create a custom material that uses custom shader and can have different property values for different models. There are a few sample on how to use custom shaders (for example Customizations/CustomFogShaderEffectSample in the main DXEngine samples project and the ShaderFactory project) but they only use standard material properties or have some global properties (fog settings).
Because good customizability is one of the major features of DXEngine and because support for custom shaders is needed for that, I have decided to write a new sample that will show how to create a custom material that use custom effect with custom shaders.
I have updated the samples in the evaluation and commercial versions. For those how already have that installed, you can download the zip that is attached to this post and add and overwrite the files from zip over the existing sample files.
The proces of using custom shaders is best described in the sample, but you can also follow the following guide:
To use custom shaders, you first need to prepare the custom Effect and custom Material classes.
Custom effect class is created by deriving a new class from Ab3d.DirectX.Effect - effect defines how a material that uses this effect will be rendered. Then you need to override the following methods: OnInitializeResources (create shaders and other resources), OnApplyPerFrameSettings, ApplyMaterial (setup per-frame and per-object resources), Dispose.
To store the custom material properties that are different for each 3D model, you also need to create a custom Material class - a new class that is derived from Ab3d.DirectX.Material - this class can also implement some standard material interfaces (see what is available in the Ab3d.DirectX.Materials namespace). You can also define your own interface and define your custom properties there. This material object is then passed to the ApplyMaterial method in your Effect class - there you can read your custom material properties and set the shader constant buffers accordingly.
To tell DXEngine to use your custom effect to render your custom material you need to set the material's Effect property to an instance of your effect.
The best way to do this is in material's overridden OnInitializeResources method. Here you set the Effect property to an instance of your custom effect. Here you can also register your custom effect. The following code does this:
Code:
/// <summary>
/// Initializes resources.
/// </summary>
/// <param name="dxDevice">Parent DXDevice used to initialize resources</param>
protected override void OnInitializeResources(DXDevice dxDevice)
{
// Set this.Effect to an instance of MyEffect.
// This will tell DXEngine to use MyEffect to render this material.
// An instance of MyEffect is get by using GetEffect method on EffectsManager.
// This will also register MyEffect on EffectsManager if it was not registered before (using Activator to create an instance of MyEffect)
this.Effect = dxDevice.EffectsManager.GetEffect<MyEffect>();
}
As you see from code comments, the GetEffect method is used to get an instance of MyEffect. This also registers and creates an instance of MyEffect on EffectsManager. If you need to have more control over the creation of MyEffect instance, you can use the following code (calling GetEffect with createNewEffectInstanceIfNotFound parameter set to false):
Code:
/// <summary>
/// Initializes resources.
/// </summary>
/// <param name="dxDevice">Parent DXDevice used to initialize resources</param>
protected override void OnInitializeResources(DXDevice dxDevice)
{
// Set this.Effect to an instance of MyEffect.
// This will tell DXEngine to use MyEffect to render this material.
var myEffect = dxDevice.EffectsManager.GetEffect<MyEffect>(createNewEffectInstanceIfNotFound: false);
if (myEffect == null)
{
myEffect = new MyEffect(myEffectSettings);
dxDevice.EffectsManager.RegisterEffect(myEffect);
}
this.Effect = myEffect;
}
Another option is to register your effect in the DXEngine initialization process:
Code:
MainDXViewportView.DXSceneDeviceCreated += delegate(object sender, EventArgs args)
{
var dxScene = MainDXViewportView.DXScene;
if (dxScene == null) // Probably using WPF 3D rendering
return;
// Create a new instance of MyEffect
var myEffect = new MyEffect();
// Register effect with EffectsManager - this will also initialize the effect with calling OnInitializeResources method in the MyEffect class
dxScene.DXDevice.EffectsManager.RegisterEffect(myEffect);
};
Here the DXSceneDeviceCreated event is used to execute the code after the DXDevice is created.
You also need to override the Dispose method where you dispose the Effect:
Code:
protected override void Dispose(bool disposing)
{
if (Effect != null)
{
Effect.Dispose(); // Decrease reference counting in the Effect object
Effect = null;
}
base.Dispose(disposing);
}
Now you can create your 3D objects and assign your custom material to the objects. To do this, you need to assign an instance of your custom material to a WPF material that is used by 3D object - for example:
Code:
var myCustomDXMaterial = new MyCustomDXMaterial()
{
MyColor = Colors.Green,
};
// Create standard WPF material and set the myCustomDXMaterial to be used when the model is rendered in DXEngine.
var myCustomDiffuseMaterial = new DiffuseMaterial();
myCustomDiffuseMaterial.SetUsedDXMaterial(myCustomDXMaterial);
myGeometryModel3D.Material = myCustomDiffuseMaterial;
(I am planning to simplify this in the future so you will not need to create a WPF material and will be able to assign your material directly to the GeometryModel3D).
An even better picture of how shaders are used in DXEngine can be get with purchasing "Effects source code" for $299 - this will give you fill source code of all hlsl files and Effect files used in DXEngine.