L0V3
L0V3

Reputation: 11

Monogame/XNA - 3D - lighting and own shaders don´t work with command GraphicsDevice.DrawUserPrimitives<...>(...)

Here is whole code:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace _3D_01
{
    public class Game1 : Game
    {
        private GraphicsDeviceManager graphics;
        private BasicEffect effect;

        private VertexPositionColor[] vertices; // the triangle

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            vertices = new VertexPositionColor[3];
            vertices[0] = new VertexPositionColor(new Vector3(0, 0, 0), Color.Red);
            vertices[1] = new VertexPositionColor(new Vector3(10, 20, 0), Color.Green);
            vertices[2] = new VertexPositionColor(new Vector3(20, 0, 0), Color.Blue);

            base.Initialize();
        }

        protected override void LoadContent()
        {
            effect = new BasicEffect(GraphicsDevice);
            effect.VertexColorEnabled = true;
            effect.View = Matrix.CreateLookAt(new Vector3(0, 0, 40), new Vector3(0, 0, 0), new Vector3(0, 1, -1));
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(1.047f, GraphicsDevice.DisplayMode.AspectRatio, 1, 1000);
            effect.World = Matrix.CreateTranslation(new Vector3(0, 0, 0));
            //effect.EnableDefaultLighting();

            // LIGHT:
            effect.LightingEnabled = true;
            effect.DirectionalLight0.DiffuseColor = new Vector3(0.7f, 0.3f, 0.7f);
            effect.DirectionalLight0.SpecularColor = new Vector3(0.3f, 0.7f, 0.3f);
            effect.DirectionalLight0.Direction = new Vector3(0, 0.5f, -1);
            effect.DirectionalLight0.Enabled = true;

            effect.CurrentTechnique.Passes[0].Apply();
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleStrip, vertices, 0, 1); // drawing the triangle

            base.Draw(gameTime);
        }
    }
}

In the code, I created an array of vertices with three vertices that form a triangle. In the LoadContent() method is setting of shader BasicEffect. In the Draw() method is drawing of the triangle by command:

GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleStrip, vertices, 0, 1); // drawing the triangle

If I comment out this piece of code in the LoadContent() method

//effect.EnableDefaultLighting();

// LIGHT:
effect.LightingEnabled = true;
effect.DirectionalLight0.DiffuseColor = new Vector3(0.7f, 0.3f, 0.7f);
effect.DirectionalLight0.SpecularColor = new Vector3(0.3f, 0.7f, 0.3f);
effect.DirectionalLight0.Direction = new Vector3(0, 0.5f, -1);
effect.DirectionalLight0.Enabled = true;

, everything works as it should (a triangle with red, green and blue vertex is drawn). However, if I leave this piece of code that sets the lighting in the BasicEffect shader uncommented, and/or leave the effect.EnableDefaultLighting(); command uncommented, the triangle is drawn black. The lighting does not work.

In short, it seems to me that when using commands to set lighting:

effect.EnableDefaultLighting();
// or
effect.LightingEnabled = true;

the drawing command:

GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleStrip, vertices, 0, 1); // drawing the triangle

stops working correctly and the triangle is drawn black, the lighting does not work.

I don´t know what I am doing wrong.

I tried to draw an fbx model instead of drawing a triangle. The lighting and lighting settings worked for that. However, other commands are used for this and I don´t want to use fbx model:

private void DrawModel(Model model, Matrix world, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();

                    effect.LightingEnabled = true; // Turn on the lighting subsystem.

                    effect.DirectionalLight0.DiffuseColor = new Vector3(1, 1, 1); // a reddish light
                    effect.DirectionalLight0.Direction = new Vector3(1, 1, -0.1f);  // coming along the x-axis
                    effect.DirectionalLight0.SpecularColor = new Vector3(0, 1, 0); // with green highlights

                    effect.AmbientLightColor = new Vector3(0.2f, 0.2f, 0.2f); // Add some overall ambient light.
                    effect.EmissiveColor = new Vector3(0.2f, 0.2f, 0.2f); // Sets some strange emmissive lighting.  This just looks weird.

                    effect.World = world;
                    effect.View = view;
                    effect.Projection = projection;
                }

                mesh.Draw();
            }
        }

The above method was called in the Draw() method:

DrawModel(model, world, view, projection);

The declaration and definition of parameters looks like this:

private Model model;
private Matrix world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
private Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 50), new Vector3(0, 0, 0), Vector3.UnitY);
private Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(60), 800f / 480f, 0.1f, 100f);

If I added a drawing of that triangle to the Draw() method again, it was drawn in black again, but the fbx model was drawn correctly.

The reason why I don't want to use fbx model (the way it works) is that I need to change the coordinates of the model's vertices while the game is running (I need to change the model's shape, not just move and rotate whole model), so I want to have the model assembled directly from vertices and triangles which I can change in code, simply I want to have an array of vertices and an array of indexes defining the triangles and thus be able to change the coordinates of the vertices. Then draw all the triangles by the command:

GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleStrip, vertices, 0, 1); // drawing the triangle

, unfortunately the lighting does not work when using this way.

I don´t know what I am doing wrong.

The same problem occurs when I try to use my own shader.

Upvotes: 1

Views: 661

Answers (1)

Albur Godwin
Albur Godwin

Reputation: 21

I had the very same problem as you have, and just found out today what went wrong: the 3D geometry information needs explicit normal vectors.
(You can have a hint about this by only setting AmbientColor: you should get the colour you ask for, which is logical, because ambient colour does not use normal vectors for its calculations.)

I solved this problem using the VertexPositionNormalTexture class to define the vertices, and its property Normal to define the normal vectors. Note that leaving the Texture property uninitialized causes no problem: my untextured cubes work well without it.

As an example, here is the portion of my code that takes care of the vertex buffer and index buffer.

// vertex buffer for a cube
// (the coordinates details are specific to my game, nothing special)
cubeVertices = new VertexPositionNormalTexture[8];
float demi_taille_cube = taille_cube / 2;
cubeVertices[0].Position = new Vector3(-demi_taille_cube, 0, demi_taille_cube); // coin inférieur gauche avant
cubeVertices[1].Position = new Vector3(-demi_taille_cube, taille_cube, demi_taille_cube); // coin supérieur gauche avant
cubeVertices[2].Position = new Vector3(demi_taille_cube, 0, demi_taille_cube); // coin inférieur droit avant
cubeVertices[3].Position = new Vector3(demi_taille_cube, taille_cube, demi_taille_cube); // coin supérieur droit avant
cubeVertices[4].Position = new Vector3(-demi_taille_cube, 0, -demi_taille_cube); // coin inférieur gauche arrière
cubeVertices[5].Position = new Vector3(-demi_taille_cube, taille_cube, -demi_taille_cube); // coin supérieur gauche arrière
cubeVertices[6].Position = new Vector3(demi_taille_cube, 0, -demi_taille_cube); // coin inférieur droit arrière
cubeVertices[7].Position = new Vector3(demi_taille_cube, taille_cube, -demi_taille_cube); // coin supérieur droit arrière


/* Computing the normal vectors: I have decided to align them with
the straight line that goes from the center to the vertex
(here, it may be a better idea to use an unindexed vertex buffer
and choose the normals orthogonal to their associated triangle;
see the discussion at the end). */
/* Also normalizing the normal vectors; in my case, apparently
not necessary since everything is symmetric/homogeneous, but surely
a good idea in general, just in case, especially when using shaders
that may not take care of it. */

Vector3 translation_y = new Vector3(0, -demi_taille_cube, 0);
for (int i = 0; i < 8; i++)
{
        cubeVertices[i].Normal = cubeVertices[i].Position + translation_y;
        cubeVertices[i].Normal.Normalize();
}

// index buffer; by the way, giving correct clockwise order for face
// rendering, so that standard backface culling can be enabled
short[] indices = new short[36];
indices[0] = 0; indices[1] = 1; indices[2] = 2;
indices[3] = 1; indices[4] = 3; indices[5] = 2;
indices[6] = 0; indices[7] = 2; indices[8] = 4;
indices[9] = 2; indices[10] = 6; indices[11] = 4;
indices[12] = 1; indices[13] = 5; indices[14] = 3;
indices[15] = 5; indices[16] = 7; indices[17] = 3;
indices[18] = 0; indices[19] = 4; indices[20] = 1;
indices[21] = 4; indices[22] = 5; indices[23] = 1;
indices[24] = 2; indices[25] = 3; indices[26] = 6;
indices[27] = 3; indices[28] = 7; indices[29] = 6;
indices[30] = 4; indices[31] = 6; indices[32] = 5;
indices[33] = 6; indices[34] = 7; indices[35] = 5;

Unfortunately, there does not seem to be a VertexPositionNormalColor class, which could be a problem since you are using VertexPositionColor; I guess you could circumvent this problem by using DiffuseColor. Even before solving the ‘normal’ problem, I was using it to colour my cubes. (Which I incidentally realized means that MonoGame’s diffuse lighting effect does not use normals, while R. B. Whitaker’s HLSL tutorial on diffuse lighting does. This may be of interest to you, since I see through your code and comments that you are using Sir R. B. Whitaker’s (fine) tutorials. ;))

As a final complement, I also stumbled upon the following information during my research, which is logical: as for colours, you may have not to use vertex indices should you want several different normals at one given vertex.

  1. You use indexed rendering, there are only 8 vertices in your cube and they are shared among its faces. That means even if you had one normal for each vertex, they would be shared as well. This can be fine in some cases (smooth surfaces with averaged normals), but since cubes usually have sharp edges this is not what you want. You want 24 vertices, each with their own normal. That is, have six quads (one for each cube face) of 4 vertices each. There just isn't much benefit from rendering a simple cube using indices.

PS: I have tested defining the normals as orthogonal to their faces, and it does look way more natural. In your case, you can compute the normal to the face (ie the triangle) by computing the cross product of two side-vectors (being careful of the multiplication order, or else, you will end up with the opposite direction), for instance using the static method Vector3.Cross, and then assign it to the three vertices’ normals.
Here is the detailed code, just to clearly show which points are used and in which order.

Vector3 first_vector = new Vector3(20, 0, 0) - new Vector3(0, 0, 0);
Vector3 second_vector = new Vector3(10, 20, 0) - new Vector3(0, 0, 0);
Vector3 normal_vector = Vector3.Cross(first_vector, second_vector);
vertices[0].Normal = normal_vector;
vertices[1].Normal = normal_vector;
vertices[2].Normal = normal_vector;

Upvotes: 0

Related Questions