2
votes

I'm trying to make a 3D WP7 game in XNA by drawing models consisting of lines, using DrawUserIndexedPrimitives This is all working fine, but now I want to give every model its own Rotation. Currently I am using a BasicEffect to draw all the models. I can set its rotation, but then all the objects will have that same rotation. Is there any way to set a different rotation for every object? I've currently come up with a couple of solutions that I could use, but I'm not happy with either of them:

  • Create a new BasicEffect for every object, with its own World, then loop over all of the objects to get their basic effect and draw it - I guess all those BasicEffects would create a lot of overhead, and I want to separate the effect from the models.
  • Do the calculation of the points myself (ie calclulate the rotation and pass those values on to the DrawUserIndexedPrimitives method) - This is quite an expensive calculation, dragging down the performance a lot.

Is there any suggested route to take here?

I don't know if DrawUserIndexedPrimitives is the best method to use, I just want to be able to draw lines and give them a rotation. If there's a better way to do this, please tell me :)

My current solution:

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

    foreach (var model in _models)
    {

        foreach (var pass in model.Effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            model.Effect.World = _model.GetWorld();

            var points = model.GetPoints();
            GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.LineList, points, 0, points.Length, model.Lines, 0, model.Lines.Length / 2);

            _models[0].Draw(GraphicsDevice);
        }
    }

    base.Draw(gameTime);
}

model.GetWorld() returns a matrix with the rotation, translation and scale of the object, model.GetPoints() a list with the points and model.Lines is a list with the indices of the connected points

2

2 Answers

2
votes

You generally only need one of any type of effect (such as BasicEffect). The reason is because effects consist of two main things: the code written for the GPU (lighting, etc) and the parameters it exposes (such as Effect.World, the world matrix). There would be not much of a point to compile the GPU code more than once as it will be the same each time, so the only other thing we have to consider is the parameters.

Notice the call to pass.Apply(). What that is actually doing is uploading all the parameters you've set on the effect to the GPU every time before you Draw. What that hopefully should tell you is that you should be able to set the rotation on a single effect right before each draw and call Apply, instead of creating a bunch of effects with individually set rotations. Moreover, setting the Effect.World should actually come before the pass.Apply().

To answer the second bullet point, you are totally correct. The GPU is made to do transformations on points with massive parallelism, so it's best not to try it yourself because it will be slow.

As for DrawUserIndexedPrimitives, I'm no graphics buff, but generally that call is reserved for situations where your vertices are constantly changing (like a soft body blob or cloth), basically where there's no way for you to describe the mesh by just translations, rotations, and scales. The GPU exposes special primitives called a VertexBuffer, whose entire goal is to store a bunch of points in GPU memory (which makes them really quick to access). From there, if you want to move it around, the GPU will transform every point (but take note that all you need to pass it is the translation, rotation, and scale and its good to go). Using DrawUserIndexedPrimitives actually has to send all the points to the GPU every frame, which is slower (by how much, who knows without testing these days).

As luck would have it, the graphics card actually implements a special mode of drawing called 'Wireframe' which, instead of drawing triangles, just draws the edges of the triangles as lines. To set wireframe mode, do this before you call Draw:

this.GraphicsDevice.RasterizerState.FillMode = FillMode.WireFrame;

Now in terms of applying your own rotation, all you actually need to do is multiply the world matrix by a rotation matrix (use something like Matrix.CreateRotationX(yourAngle)).

0
votes

Your current solution is fine, though I would do thw world assign before the pass.Apply()

If you have few models you can pass to the shader and array with the transforms, and add to your vertex structure a index to this array.

This way you could draw all your model in one pass.