Trying to load .fbx file with normal map (I think I'm very close)

Dear MonoGame Community,

I successfully implemented a shader that takes a normal map and uses it to create the illusion of bumpiness on a given surface. When I decided to generalize my solution, I realized I needed a way to automatize loading my assets, because it’ll get messy very quickly. After a decent amount of research I could load my maps and shader source with a custom ModelProcessor. Right now I have a CustomModelProcessor and a CustomMaterialProcessor and they pretty much work.

It feels like I miss one last step, In order to use my HLSL code in a clean way, I wish to have a NormalMapEffect, that is derived from the Effect class, and my idea is to override the OnApply() method. This is done basically but I don’t know where to instantiate this, so that my Mesh.MeshParts will have a type of NormalMapEffect (where desired). Right now, the type that I get from my content pipeline into the MeshParts is EffectMaterial.

If you could guide me I’d be very grateful :bow:

Okay I think I solved it, and I’m pretty sure its not optimal, but here is my solution:

I created a CustomEffectProcessor that creates a CustomEffect from EffectContent. This was neccesary because i wanted to save the type information. So that when I read it, I know what class I have to instantiate. Something like this:

The CustomEffect:

    public class CustomEffect : CompiledEffectContent {
        public string Type { get; set; }
        public CustomEffect(byte[] effectCode, string type) : base(effectCode) {
            Type = type;

The processor:

    [ContentProcessor(DisplayName = "Custom Effect Processor")]
    public class CustomEffectProcessor : ContentProcessor<EffectContent, CustomEffect> {

        [DefaultValue(typeof(EShaderType), "Effect")]
        public EShaderType Type { get; set; }

        public override CustomEffect Process(EffectContent input, ContentProcessorContext context) {
            EffectProcessor ep = new EffectProcessor();

            CompiledEffectContent c = ep.Process(input, context);

            return new CustomEffect(c.GetEffectCode(), Type.ToString());

The writer:

    public class CustomEffectWriter : ContentTypeWriter<CustomEffect> {
        public override string GetRuntimeReader(TargetPlatform targetPlatform) {
            return "CustomContentPipelineExtension.CustomEffectReader, CustomContentPipelineExtension";

        protected override void Write(ContentWriter output, CustomEffect value) {
            var code = value.GetEffectCode();

And finally the reader:

    public class CustomEffectReader : ContentTypeReader<Effect> {
        private byte[] scratchBuffer;
        public static GraphicsDevice Device; // monkey patch for ContentReader.GraphicsDevice is internal... Dont forget to initialize this in your Game.Initialize method

        //copied from the MonoGame ContentManager for being internal too
        internal byte[] GetScratchBuffer(int size) {
            size = Math.Max(size, 1024 * 1024);
            if (scratchBuffer == null || scratchBuffer.Length < size)
                scratchBuffer = new byte[size];
            return scratchBuffer;

        protected override Effect Read(ContentReader input, Effect existingInstance) {
            string shaderType = input.ReadString();
            int dataSize = input.ReadInt32();
            byte[] data = GetScratchBuffer(dataSize);
            input.Read(data, 0, dataSize);
            Effect effect;
            if(shaderType == "Effect") {
                effect = new Effect(Device, data, 0, dataSize);
            } else {
                // instantiate other types
            effect.Name = input.AssetName;
            return effect;

The idea was to guide the whole model import through, starting with how the shader is serialized and deserialized, so when the actual Effect is being instantiated I can decide on the type based on the shader. One little catch is that inside the CustomEffectProcessor there is a Type property, you have to set that in the Content Pipeline tool, also you have to keep an enum updated for that, but I think thats much more manageable than manually handling textures and shaders. I did not test it extensively yet, I just wanted to let you guys know that I might worked around my issue.