You have to use a vertexPosition Normal Texture
if you want all of them add the below i don’t think its included by default in monogame i don’t know why and i don’t know if basic effect will work right with it though i imagine it should.
public struct VertexPositionNormalColorTexture : IVertexType
{
public Vector3 Position;
public Vector3 Normal;
public Color Color;
public Vector2 TextureCoordinate;
public static int SizeInBytes = (3 + 3 + 1 + 2) * sizeof(float);
public static VertexDeclaration VertexDeclaration = new VertexDeclaration
(
new VertexElement(VertexElementByteOffset.PositionStartOffset(), VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
new VertexElement(VertexElementByteOffset.OffsetVector3(), VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
new VertexElement(VertexElementByteOffset.OffsetColor(), VertexElementFormat.Color, VertexElementUsage.Color, 0),
new VertexElement(VertexElementByteOffset.OffsetVector2(), VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
);
VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } }
}
public struct VertexElementByteOffset
{
public static int currentByteSize = 0;
public static int PositionStartOffset() { currentByteSize = 0; var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; }
public static int Offset(float n) { var s = sizeof(float); currentByteSize += s; return currentByteSize - s; }
public static int Offset(Vector2 n) { var s = sizeof(float) * 2; currentByteSize += s; return currentByteSize - s; }
public static int Offset(Color n) { var s = sizeof(int); currentByteSize += s; return currentByteSize - s; }
public static int Offset(Vector3 n) { var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; }
public static int Offset(Vector4 n) { var s = sizeof(float) * 4; currentByteSize += s; return currentByteSize - s; }
public static int OffsetFloat() { var s = sizeof(float); currentByteSize += s; return currentByteSize - s; }
public static int OffsetColor() { var s = sizeof(int); currentByteSize += s; return currentByteSize - s; }
public static int OffsetVector2() { var s = sizeof(float) * 2; currentByteSize += s; return currentByteSize - s; }
public static int OffsetVector3() { var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; }
public static int OffsetVector4() { var s = sizeof(float) * 4; currentByteSize += s; return currentByteSize - s; }
}
With custom vertex data you have to calculate them the normals yourself with a function.
You do that with a cross product on each triangles vertice sides.
For a triangle with vertices A B C
d0 = B - A;
d1 = C - B;
d0.Normalize();
d1.Normalize();
n = cross( d0 , d1);
You can apply that to each vertice for a dirty version.
if you cross n = cross( d1 , d0);
This changes the culling or the direction of the normal it inverts it.
Thats what cull clockwise and counterclockwise is about the gpu use that to not draw entire triangle vertices or pixels to speed things up.
When you don’t use indices or normals the gpu does the calculation itself using a dirty version.
If you are culling them depending on the order you passed in the vertices, if not culling it ignores the sign.
Here is mine its basically how you do it i really should normalize n0 n1 i dunno why i didn’t.
This is done only once when you create them typically you would save the results to file and load it.
/// <summary>
/// This method creates smooth normals from a quad structured vertice mesh array
/// </summary>
private PositionNormalColorUv[] CreateSmoothNormals(PositionNormalColorUv[] vertices, int[] indexs)
{
// Under quads there are two triangles per quad
// such that the triangle associated with normals in a proper indexed vertice list
// will describe per triangle all shared normals for each vertice.
// hence each vertice will be shared by 6 triangles ranging from 0 to 5
// so that for each vertice we must must calculate the surrounding normals to average them.
int tvertmultiplier = 3;
int triangles = (int)(indexs.Length / tvertmultiplier);
// Loop the vertices
for (int currentTestedVerticeIndex = 0; currentTestedVerticeIndex < vertices.Length; currentTestedVerticeIndex++)
{
// Reset are sum and total counts
float total = 0;
Vector3 sum = Vector3.Zero;
// loop thru each triangle
for (int t = 0; t < triangles; t++)
{
int tvstart = t * tvertmultiplier;
int tindex0 = tvstart + 0;
int tindex1 = tvstart + 1;
int tindex2 = tvstart + 2;
var vindex0 = indices[tindex0];
var vindex1 = indices[tindex1];
var vindex2 = indices[tindex2];
// Test each triangle for the currentTestedVertice presence
if (vindex0 == currentTestedVerticeIndex || vindex1 == currentTestedVerticeIndex || vindex2 == currentTestedVerticeIndex)
{
// Obtain a orthagonal direction from the surface of this triangle.
// This is basically a non unit length normal.
// Multiply by some amount 10 here to increase precision and avoid nans.
var n0 = (vertices[vindex1].Position - vertices[vindex0].Position) * 10f;
var n1 = (vertices[vindex2].Position - vertices[vindex1].Position) * 10f;
// Sum the cross product and increment the total
var cnorm = Vector3.Cross(n0, n1);
sum += cnorm;
total += 1;
}
}
// Find the average
if (total > 0)
{
var averagednormal = sum / total;
// Normalize the result
averagednormal.Normalize();
// Assign the normal to the vertice
vertices[currentTestedVerticeIndex].Normal = averagednormal;
if (showSmoothing)
{
Console.WriteLine(" SmoothedNormals: vertices[" + currentTestedVerticeIndex + "].Position: " + vertices[currentTestedVerticeIndex].Position);
Console.WriteLine(" SmoothedNormals: vertices[" + currentTestedVerticeIndex + "].Normal: " + averagednormal);
}
}
}
return vertices;
}
This is with color and textures turned off the color is ambient white passed in.
basically.
The normals when it matches the light direction as shown brighten those vertices that match via the result of the dot product that ranges from
-1 to 0 black. |_
to .7 at a 45 degree light to normal difference |/.
Then to 1 , when they align | |
This is why the normals stick out orthagonally from the surface.
When the result of the dot is 1 we multiply color.White by it and get the full white color 255 * 1.
When the dot is say 0 and you multiply 255 r g b by 0 you get 0 so
Shown below the flat area all has normals that stick out like the green arrow from the surface.
the light is the rotating green arrow basic effect sets it as direction so you can imagine that rotating green arrow pointing in the reverse direction and the dot result between them.
That’s this shader i posted here, with those vertice normals generated by the above function.
The relevant part is here below basic effect does something similar.
Intensity.
saturate just turns negatives results into 0;
the col is just my function it can be whatever
i rip out part of the regular lighting to directly replace it with some ambient color.
//______________________________________________________________
// shader techniques TechniqueB
//______________________________________________________________
VertexShaderOutputB VertexShaderFunctionB(VertexShaderInputB input)
{
VertexShaderOutputB output;
output.Position = mul(input.Position, gworldviewprojection);
output.TexureCoordinateA = input.TexureCoordinateA * input.Color;
float3 normal = normalize(mul(input.Normal, gworld));
output.Normal = normal;
float lightIntensity = dot(normal, lightDir);
lightIntensity = saturate(lightIntensity);
float4 col = input.Color;
float alpha = col.a;
col = ((col *.5 + lightColor *.5) * (lightIntensity * (1 - ambientColor))) + (col * ambientColor);
col.a = alpha;
output.Color = col;
return output;
}
PixelShaderOutputB PixelShaderFunctionB(VertexShaderOutputB input)
{
PixelShaderOutputB output;
float4 A = tex2D(TextureSamplerA, input.TexureCoordinateA) * input.Color;
output.Color = A;
if (displayVertexNormalShading)
{
output.Color = input.Color; // vertex normal shading
}
return output;
}