Mesh effect field

Greetings everyone. I was wondering at which stage in the monogame pipeline a certain value is assigned to “Effect” field in the “ModelMesh” type.

That is, Type “Model” contains collection of meshes, whereas each individual “ModelMesh” contains “Effect” field. I tried searching across every file on Monogame Github but I could not find where it actually happens.

I assumed it is supposed to be done at ModelProcessor.cs, but the only thing I see there is “mehsparts” having their material field assigned. Namely:

partContent.Material = geometry.Material;

MaterialProcessor doesn’t really do anything related to this as well - it processes the material and returns it, which in its turn gets assgined to respective fields in meshparts.

I need this in order to write an extension for monogame pipeline and be able to process models which contain normal maps. If a single model contains 10 meshes which have different effects it would be atrociously hard and ineffective to do it by hand.

I thought the problem is pretty straightforward and I tried to follow this tutorial from MSDN https://blogs.msdn.microsoft.com/shawnhar/2006/12/07/rendering-a-model-with-a-custom-effect/
, but it is woefully outdated and, obviously, things are done in Monogame differently nowadays, and simply referring to EffectMaterialConent invokes nasty exceptions. Which is why I decided to dig a bit deeper and see what happens under the hood.

And regarding nasty exceptions, this is what we get:

Could not find ContentTypeReader Type. Please ensure the name of the Assembly that contains the Type matches the assembly in the full type name: Microsoft.Xna.Framework.Content.ReflectiveReader1[[Microsoft.Xna.Framework.Content.Pipeline.Graphics.EffectMaterialContent, MonoGame.Framework.Content.Pipeline, Version=3.6.0.199, Culture=neutral, PublicKeyToken=null]] (Microsoft.Xna.Framework.Content.ReflectiveReader1[[Microsoft.Xna.Framework.Content.Pipeline.Graphics.EffectMaterialContent, MonoGame.Framework.Content.Pipeline]])

I hope knowledgeable people here will understand what I mean and I didn’t do it utterly hazy.
I would greatly appreciate if anyone could help with it.

Cheers everyone.

Hey there. After hours and hours we managed to hook up a custom effect to a model. Obviously, it turned out to be a few lines of code, but a lot of reading on github.

Here is how it goes. We basically left model processor unchanged and jumped right into material pocessor. But make sure to simply write a dummy modelprocessor even if it doesn’t do anything, since we have to invoke our custom material processor in the convert function (so override that). We precompiled our custom effect file and load it into compiledeffect field of the effectmaterialcontent:

 if (input.Textures.ContainsKey(NormalMappingModelProcessor.NormalMapKey))
            {
             EffectMaterialContent effectMaterial = new EffectMaterialContent();

            string directory = Path.GetDirectoryName(input.Identity.SourceFilename);
            string effectSrc = Path.Combine(directory, "bin\\Windows\\Normal.xnb");

            effectMaterial.CompiledEffect = new ExternalReference<CompiledEffectContent>(effectSrc, input.Identity);

 // for some odd reason write methods of contenwriter enforce that the format of the filepath in          
 //the external reference
 // is to be written with forward slash, rather than backward slash; whereas it seems that all
 // path operations I perform with standard string methods are inserting backward slash, which
 // is why I had to replace slashes below in the filename
effectMaterial.CompiledEffect.Filename = effectSrc.Replace("\\","/"); 

    // Build all textures
    foreach (string key in input.Textures.Keys)
    {
        ExternalReference<TextureContent> builtTexture =     BuildTexture(input.Textures[key].Filename, input.Textures[key], context, key);
            effectMaterial.Textures.Add(key,builtTexture);
        }
// do similar external reference file name slash replacement for textures
string textureName = (Path.Combine(directory, "bin\\Windows\\normalSand_0.xnb")).Replace("\\","/"); 
effectMaterial.Textures["Bump"].Filename = textureName;

    // return effectMaterialContent
    return effectMaterial;
}

Ofcourse, you can always compile the effect file during the runtime (see the way it’s done in materialprocessor.cs on Monogame github page).

First observations: we don’t set the effect field of the effectmaterialcontent to a *.fx at all -> don’t get xnb reference problem -> profit :).

Second observation: it will compile nicely, however, that’s where the tricky part comes. You will get an exception saying that there is no respective content reader that can handle effectmaterial content.

Solution to tricky part:
After the processing business is completed, the pipeline will call modelwriter In the respective *.cs file on github you can see that the code eventually gets down to the line output.wirte(material), something like this. We made a conjecture that since there is no specific writer for the effectmaterialcontent it must simply invoke a superclass writer. But, as mentioned earlier, there is no reader assigned to it, which is why an exception will be generated when Content.Load function is called.

So, what do we need? First, we need to write a specific writer, in which we will specify the fields to output in a file, plus which reader should handle this type. It goes as follows:

First, override write function:

protected override void Write(ContentWriter output, EffectMaterialContent value) { output.WriteExternalReference(value.CompiledEffect); Dictionary<string, object> dict = new Dictionary<string, object>(); foreach(KeyValuePair<string,ExternalReference<TextureContent>> item in value.Textures){ dict.Add(item.Key,item.Value); } output.WriteObject<Dictionary<string,object>>(dict); }

Then, associate a reader with the type. In our case we simply used Monogame EffectMaterialReader, which actually reads out everything we need:

public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
    var type = typeof(ContentReader);
    var readerType = type.Namespace + ".EffectMaterialReader, " + type.Assembly.FullName;
    // Console.WriteLine(readerType);
    return readerType;
}

And voila. I hope the code is self explanatory, and it will help out people struggling wit the same problem. Finaly, I just realized that even though EffectMaterial (which is a derived class from Effect) is properly assigned to a mesh which has a specific texture key on them (in our case, we assigned special effect for the normal maps), it doesn’t really do anything special at the moment. That is, you will probably have to write a specific Effect class yourself (similar to BasicEffect), to set textures and all the necessary parameters. Furthermore, that means you will have to write your own reader as well.

Great work :slightly_smiling:
So all effects would require to be built into an *.xnb before using them in the pipeline ?
Thats sounds like a regression compared to XNA :confused:

All this code whereas with xna pipeline, 1 line to load effect and voila.

Edit: CompiledEffect seems to be put into OpaqueData whereas in XNA when we loaded the externalreference with *fx, it was put into
myeffectMaterial.effect.
Currently it is:
myeffectMaterial.OpaqueData > key: CompiledEffect, value: Microsoft.Xna.Framework.Content.Pipeline.ExternalReference`1[Microsoft.Xna.Framework.Content.Pipeline.Processors.CompiledEffectContent]
Maybe it’s here the problem lies.

So no one has an “xna” way to assign an effect onto a model at build time in a modelprocessor, instead of a hack, requiring to add/maintain so many lines to maybe work ?

Is it simpler/possible to do this job at loadtime instead of buildtime ? Longer startup but faster coding, if i must wait for someone fixing this i don’t know when i’ll start porting my engine from XNA 4 to monogame.

Do all these methods have to be written in my contentprocessor class or in a modified version of MGCB ?
And another question, why, when building the model to whom the effect.xnb will be assigned, is again passed into

public ExternalReference BuildAsset<TInput,TOutput>(
ExternalReference sourceAsset,
string processorName
)
{
return BuildAsset<TInput, TOutput>(sourceAsset, processorName, null, null, null);
}

I can’t figure why it is rebuilding another time an already xnb file.
Or is this a generic name to import data onto the mesh in the processor ?

So, (I’m using the sourcecode of monogame and its tools to find where the problem lies),
as it already is an effect built into an xnb, i used the debugger to manually set the importerName to “EffectImporter” into:

public PipelineBuildEvent BuildContent(string sourceFilepath, string outputFilepath = null, string importerName = null, string processorName = null, OpaqueDataDictionary processorParameters = null)
{
sourceFilepath = PathHelper.Normalize(sourceFilepath);
ResolveOutputFilepath(sourceFilepath, ref outputFilepath);
//TODO: Change importerName to “EffectImporter”, it will trigger a “Bad token CppNet.Token” exception (didn’t test if xnb is not something different from an effect file)
ResolveImporterAndProcessor(sourceFilepath, ref importerName, ref processorName);

And then it triggers “Bad token CppNet.Token” exception, same one as @Sizaar has bumped into: Custom model processor