Thank you so much @Apostolique ! I managed to get it working after some more digging over the past 2 days. The shader wasn’t working, and I had such a massive headache debugging the shader. Turns out, the problem was not the shader but the custom vertex struct.
But basically, for those that can't get custom vertex shader input working, make sure your color is offsetting 4 bytes NOT 4 floats!!!
Here is a working vertex that passes an extra Vector2 to the vertex shader:
[StructLayout(LayoutKind.Sequential, Pack = 1)]// <-- that's for alignment I think
[DataContract] // <-- you don't actually need the data contract, but MonoGame has it so I put it here.
public struct CustomVertex : IVertexType
{
[DataMember]
public Vector3 Position;
[DataMember]
public Color Color;
[DataMember]
public Vector2 TextureCoordinate;
[DataMember]
public Vector2 FuncIndex;
public static readonly VertexDeclaration VertexDeclaration;
VertexDeclaration IVertexType.VertexDeclaration => VertexDeclaration;
public CustomVertex(Vector3 position, Color color, Vector2 textureCoordinate, int funcInd)
{
this.Position = position;
this.Color = color;
this.TextureCoordinate = textureCoordinate;
this.FuncIndex = new Vector2((float)funcInd, 0);
}
public override readonly int GetHashCode()
{
return HashCode.Combine(Position, Color, TextureCoordinate, FuncIndex);
}
public override readonly string ToString()
{
return
"{{Position:" + Position +
" Color:" + Color +
" TextureCoordinate:" + TextureCoordinate +
" FuncInd:" + FuncIndex +
"}}";
}
public static bool operator ==(CustomVertex left, CustomVertex right)
{
return
left.Position == right.Position &&
left.Color == right.Color &&
left.TextureCoordinate == right.TextureCoordinate &&
left.FuncIndex == right.FuncIndex;
}
public static bool operator !=(CustomVertex left, CustomVertex right)
{
return !(left == right);
}
public override readonly bool Equals(object? obj)
{
if (obj == null)
return false;
if (obj.GetType() != base.GetType())
return false;
return this == ((CustomVertex)obj);
}
// Statis constructor sets VertexDeclaration, which is just
// information of the layout of the vertex shader input struct.
static CustomVertex()
{
int offset = 0;
var elements = new VertexElement[] {
new(OffsetInline(ref offset, VEOffset.FLOAT3), VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
// -------PAY ATTENTION TO THE FOLLOWING LINE'S OFFSET VALUE
new(OffsetInline(ref offset, VEOffset.COLOR_RGBA), VertexElementFormat.Color, VertexElementUsage.Color, 0),
new(OffsetInline(ref offset, VEOffset.FLOAT2), VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
new(OffsetInline(ref offset, VEOffset.FLOAT2), VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 1),
};
VertexDeclaration = new VertexDeclaration(elements);
}
private static int OffsetInline(ref int value, int offset)
{
int old = value;
value += offset;
return old;
}
}
Offset table class, just a class full of constants:
/// <summary>
/// Vertex Element Offset table.
/// </summary>
public static class VEOffset
{
public const int FLOAT = sizeof(float);
public const int FLOAT2 = FLOAT * 2;
public const int FLOAT3 = FLOAT * 3;
public const int FLOAT4 = FLOAT * 4;
public const int COLOR_RGBA = sizeof(byte) * 4; //<-- MUST be byte NOT float
}
If you look carefully at the static constructor, I passed in the size of 4 bytes for the color, not 4 floats. I think the reason behind this is 4 floats would take too much space, and colors don’t need to be that precise.
AND in the vertex shader, make sure you do something like this for the vertex shader input. I found out (I think) that TEXCOORD means actual floats, whereas COLOR is converting bytes into floats. So if you need to pass in c# floats, make sure your semantic is TEXCOORD and not COLOR:
struct VertexShaderInput
{
float4 Position : POSITION0;
float4 Color : COLOR0;
float2 uv : TEXCOORD0;
float2 Func : TEXCOORD1;
};
This is now working, but if I’m wrong about anything please let me know
!