通过制作一个网格来优化绘制3d模型

时间:2011-05-09 04:48:35

标签: c# optimization 3d xna

我们正在为在Xbox 360上运行的学校制作3D游戏。我们有一个巨大的水平,包含大约100个部分,每个部分由许多网格组成,其中包含许多顶点。我们有一个自定义着色器,通过对您周围的级别进行着色,可以发出类似ping的效果。我们还有一个3D迷你地图,因为游戏是在太空中,你可以在任何方向定向。因此,当我们绘制水平时,我们必须每帧绘制4次,一个绘制在主视图端口,一个绘制主视图端口中的ping,一个绘制迷你地图中的水平,一个绘制迷你地图中的ping。它在快速PC上以每秒60帧的速度运行,但在xbox上仅为20帧。我们已经关闭了我们背后隐藏的部分你无法看到它并帮助了一些但我们仍然需要它更快。

以下是主视图端口上没有ping的级别的主要图...

//draw the main level in a regular way
foreach (LevelObject part in levelData.LevelParts)
{
    partBounds.Center = part.position;
    if (viewFrustum.Intersects(partBounds))
    {
        //Rotate X
        Matrix worldMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(part.Xrotate));
        //Rotate Z
        worldMatrix *= Matrix.CreateRotationZ(MathHelper.ToRadians(part.Zrotate));
        //Rotate Y
        worldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(part.Yrotate));

        worldMatrix *= Matrix.CreateWorld(part.position, Vector3.Forward, Vector3.Up);
        Model Object = levelModels[part.modelName];

        //set in the gamers viewport
        foreach (ModelMesh mesh in Object.Meshes)
        {
            foreach (BasicEffect effect in mesh.Effects)
            {
                //effect.EnableDefaultLighting();
                effect.LightingEnabled = true;
                effect.AmbientLightColor = new Vector3(0.09f, 0.15f, 0.215f);
                effect.DirectionalLight0.DiffuseColor = new Vector3(0.3f, 0.3f, 0.3f);
                effect.DirectionalLight0.Direction = viewMatrix.Forward;
                effect.DirectionalLight0.Enabled = true;
                effect.DirectionalLight1.DiffuseColor = new Vector3(0.05f, 0.085f, 0.25f);
                effect.DirectionalLight1.Direction = viewMatrix.Down;
                effect.DirectionalLight1.Enabled = true;

                effect.PreferPerPixelLighting = true;
                effect.World = worldMatrix;
                effect.View = viewMatrix;
                effect.Projection = projectionMatrix;
                mesh.Draw();
            }
        }
    }
}

因此,如果在混合器中我将水平部分只做了一个网格,则必须做更少的循环,我不确定是否会使它更快地绘制。我需要多少想法来提高绘图性能?这个游戏是分屏,最多可以有4个玩家,这会使等级提高4倍。

这是完整的绘制功能

public override void Draw(GameTime gameTime)
{
    /* NORMAL VIEW */

    //set viewport for everyone
    for (int i = 0; i < SignedInGamer.SignedInGamers.Count; i++)
    {
        GraphicsDevice.Viewport = Camera.gameScreenViewPorts[SignedInGamer.SignedInGamers[i]];
        Matrix viewMatrix = Camera.viewMatrix[SignedInGamer.SignedInGamers[i]];
        Matrix projectionMatrix = Camera.projectionMatrix[SignedInGamer.SignedInGamers[i]];

        GraphicsDevice.RasterizerState = RasterizerState.CullNone;
        GraphicsDevice.BlendState = BlendState.Opaque;

        //view frustrum object for culling
        BoundingFrustum viewFrustum = new BoundingFrustum(viewMatrix * projectionMatrix);
        BoundingSphere partBounds = new BoundingSphere();
        partBounds.Radius = levelData.scale;

        //draw the main level in a regular way
        foreach (LevelObject part in levelData.LevelParts)
        {
            partBounds.Center = part.position;
            if (viewFrustum.Intersects(partBounds))
            {
                //Rotate X
                Matrix worldMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(part.Xrotate));
                //Rotate Z
                worldMatrix *= Matrix.CreateRotationZ(MathHelper.ToRadians(part.Zrotate));
                //Rotate Y
                worldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(part.Yrotate));

                worldMatrix *= Matrix.CreateWorld(part.position, Vector3.Forward, Vector3.Up);
                Model Object = levelModels[part.modelName];

                //set in the gamers viewport
                foreach (ModelMesh mesh in Object.Meshes)
                {
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        //effect.EnableDefaultLighting();
                        effect.LightingEnabled = true;
                        effect.AmbientLightColor = new Vector3(0.09f, 0.15f, 0.215f);
                        effect.DirectionalLight0.DiffuseColor = new Vector3(0.3f, 0.3f, 0.3f);
                        effect.DirectionalLight0.Direction = viewMatrix.Forward;
                        effect.DirectionalLight0.Enabled = true;
                        effect.DirectionalLight1.DiffuseColor = new Vector3(0.05f, 0.085f, 0.25f);
                        effect.DirectionalLight1.Direction = viewMatrix.Down;
                        effect.DirectionalLight1.Enabled = true;

                        effect.PreferPerPixelLighting = true;
                        effect.World = worldMatrix;
                        effect.View = viewMatrix;
                        effect.Projection = projectionMatrix;
                        mesh.Draw();
                    }
                }
            }
        }

        /* PING VIEW */

        List<Vector3> pingPos = new List<Vector3>();
        List<Vector4> pingColor = new List<Vector4>();
        List<float> pingRange = new List<float>();

        for (int a = 0; a < Game.Components.Count; a++)
        {
            if (Game.Components[a] is Ship)
            {
                pingPos.Add(((Ship)Game.Components[a]).worldMatrix.Translation);
                pingColor.Add(new Vector4(
                    ((Ship)Game.Components[a]).playerColor.R,
                    ((Ship)Game.Components[a]).playerColor.G,
                    ((Ship)Game.Components[a]).playerColor.B,
                    1));
                pingRange.Add(((Ship)Game.Components[a]).pingRange * (levelData.scale * 2.0f));
            }
        }


        if (pingPos.Count() > 0)
        {

            //apply ping lighting stuffs here
            pingTest.Parameters["PingPos"].SetValue(pingPos.ToArray());
            pingTest.Parameters["PingPosCount"].SetValue(pingPos.Count());
            pingTest.Parameters["PingColor"].SetValue(pingColor.ToArray());
            pingTest.Parameters["PingColorCount"].SetValue(pingColor.Count());
            pingTest.Parameters["PingRange"].SetValue(pingRange.ToArray());
            pingTest.Parameters["PingRangeCount"].SetValue(pingRange.Count());
        }

        foreach (LevelObject part in levelData.LevelParts)
        {
            partBounds.Center = part.position;
            if (viewFrustum.Intersects(partBounds))
            {
                //Rotate X
                Matrix worldMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(part.Xrotate));
                //Rotate Z
                worldMatrix *= Matrix.CreateRotationZ(MathHelper.ToRadians(part.Zrotate));
                //Rotate Y
                worldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(part.Yrotate));

                worldMatrix *= Matrix.CreateWorld(part.position, Vector3.Forward, Vector3.Up);
                Model Object = levelModels[part.modelName];

                foreach (ModelMesh mesh in Object.Meshes)
                {
                    //ok, this is going to be kind of weird, and there's got to be a cleaner
                    //or better way to do this.
                    List<Effect> backup = new List<Effect>();

                    foreach (ModelMeshPart meshpart in mesh.MeshParts)
                    {
                        backup.Add(meshpart.Effect);
                        meshpart.Effect = pingTest;
                        meshpart.Effect.Parameters["World"].SetValue(worldMatrix * mesh.ParentBone.Transform);
                        meshpart.Effect.Parameters["View"].SetValue(viewMatrix);
                        meshpart.Effect.Parameters["Projection"].SetValue(projectionMatrix);

                        //Matrix worldInverseTransposeMatrix = Matrix.Transpose(Matrix.Invert(mesh.ParentBone.Transform * world));
                        //pingTest.Parameters["WorldInverseTranspose"].SetValue(worldInverseTransposeMatrix);
                    }

                    mesh.Draw();

                    //reset the basic effect crap
                    foreach (ModelMeshPart meshpart in mesh.MeshParts)
                    {
                        meshpart.Effect = backup.First();
                        backup.RemoveAt(0);
                    }
                    //
                }
            }
        }

        //Undo the weird things this shader does
        GraphicsDevice.BlendState = BlendState.Opaque;
        GraphicsDevice.DepthStencilState = DepthStencilState.Default;

        /* MINIMAP VIEW */
        GraphicsDevice.Viewport = Camera.mapViewports[SignedInGamer.SignedInGamers[i]];
        viewMatrix = Camera.mapViewMatrix[SignedInGamer.SignedInGamers[i]];
        projectionMatrix = Camera.mapProjectionMatrix[SignedInGamer.SignedInGamers[i]];

        GraphicsDevice.RasterizerState = RasterizerState.CullNone;
        GraphicsDevice.BlendState = BlendState.Opaque;

        //view frustum for the map
        BoundingFrustum mapFrustum = new BoundingFrustum(viewMatrix * projectionMatrix);

        //draw the main level in a regular way
        foreach (LevelObject part in levelData.LevelParts)
        {
            partBounds.Center = part.position;
            if (mapFrustum.Intersects(partBounds))
            {
                //Rotate X
                Matrix worldMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(part.Xrotate));
                //Rotate Z
                worldMatrix *= Matrix.CreateRotationZ(MathHelper.ToRadians(part.Zrotate));
                //Rotate Y
                worldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(part.Yrotate));

                worldMatrix *= Matrix.CreateWorld(part.position, Vector3.Forward, Vector3.Up);
                Model Object = levelModels[part.modelName];

                //set in the gamers viewport
                foreach (ModelMesh mesh in Object.Meshes)
                {
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        //effect.EnableDefaultLighting();
                        effect.LightingEnabled = true;

                        if (match(part.position / 15.0f))
                            effect.AmbientLightColor = new Vector3(1.0f, 0.30f, 0.43f);
                        else
                            effect.AmbientLightColor = new Vector3(0.18f, 0.30f, 0.43f);
                        //effect.DirectionalLight0.DiffuseColor = new Vector3(0.6f, 0.6f, 0.6f);
                        effect.DirectionalLight0.DiffuseColor = new Vector3(0.6f, 0.6f, 0.6f);
                        effect.DirectionalLight0.Direction = viewMatrix.Forward;
                        effect.DirectionalLight0.Enabled = true;
                        //effect.DirectionalLight1.DiffuseColor = new Vector3(0.1f, 0.17f, 0.5f);
                        effect.DirectionalLight1.DiffuseColor = new Vector3(0.1f, 0.17f, 0.5f);
                        effect.DirectionalLight1.Direction = viewMatrix.Down;
                        effect.DirectionalLight1.Enabled = true;

                        effect.PreferPerPixelLighting = true;
                        effect.World = worldMatrix;
                        effect.View = viewMatrix;
                        effect.Projection = projectionMatrix;
                        mesh.Draw();
                    }

                }
            }
        }

        base.Draw(gameTime);
        return;

        if (pingPos.Count() > 0)
        {
            //apply ping lighting stuffs here
            pingTest.Parameters["PingPos"].SetValue(pingPos.ToArray());
            pingTest.Parameters["PingPosCount"].SetValue(pingPos.Count());
            pingTest.Parameters["PingColor"].SetValue(pingColor.ToArray());
            pingTest.Parameters["PingColorCount"].SetValue(pingColor.Count());
            pingTest.Parameters["PingRange"].SetValue(pingRange.ToArray());
            pingTest.Parameters["PingRangeCount"].SetValue(pingRange.Count());
        }

            foreach (LevelObject part in levelData.LevelParts)
            {
                partBounds.Center = part.position;
                if (mapFrustum.Intersects(partBounds))
                {
                    //Rotate X
                    Matrix worldMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(part.Xrotate));
                    //Rotate Z
                    worldMatrix *= Matrix.CreateRotationZ(MathHelper.ToRadians(part.Zrotate));
                    //Rotate Y
                    worldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(part.Yrotate));

                    worldMatrix *= Matrix.CreateWorld(part.position, Vector3.Forward, Vector3.Up);
                    Model Object = levelModels[part.modelName];

                    foreach (ModelMesh mesh in Object.Meshes)
                    {
                        //ok, this is going to be kind of weird, and there's got to be a cleaner
                        //or better way to do this.
                        List<Effect> backup = new List<Effect>();

                        foreach (ModelMeshPart meshpart in mesh.MeshParts)
                        {
                            backup.Add(meshpart.Effect);
                            meshpart.Effect = pingTest;
                            meshpart.Effect.Parameters["World"].SetValue(worldMatrix * mesh.ParentBone.Transform);
                            meshpart.Effect.Parameters["View"].SetValue(viewMatrix);
                            meshpart.Effect.Parameters["Projection"].SetValue(projectionMatrix);

                            //Matrix worldInverseTransposeMatrix = Matrix.Transpose(Matrix.Invert(mesh.ParentBone.Transform * world));
                            //pingTest.Parameters["WorldInverseTranspose"].SetValue(worldInverseTransposeMatrix);



                        }

                        mesh.Draw();

                        //reset the basic effect crap
                        foreach (ModelMeshPart meshpart in mesh.MeshParts)
                        {
                            meshpart.Effect = backup.First();
                            backup.RemoveAt(0);
                        }
                        //*/
                    }
                }
            }

        //}
        //Undo the weird things this shader does
        GraphicsDevice.BlendState = BlendState.Opaque;
        GraphicsDevice.DepthStencilState = DepthStencilState.Default;

        base.Draw(gameTime);
    }

} 

关于垃圾收集器的问题

所以通过使用这种方法设置像effect.DirectionalLight0.DiffuseColor这样的矢量...

effect.DirectionalLight0.DiffuseColor.X = 0.3f;
effect.DirectionalLight0.DiffuseColor.Y = 0.3f;
effect.DirectionalLight0.DiffuseColor.Z = 0.3f;

而不是这种方法......

effect.DirectionalLight0.DiffuseColor = new Vector3(.3, .3, .3);

我为垃圾收集器分配更少的内存来获取,但是一旦调用了draw()函数,它就不会将所有垃圾添加到堆栈中,这样收集器几乎可以立即拾取它?我意识到它为收集器增加了一些额外的工作,但它不应该添加那么多吗?

3 个答案:

答案 0 :(得分:2)

速度问题不一定来自你的想法。视频游戏中C#的严重问题在于非常很容易做错事,因为它们看起来很漂亮

例如:

effect.AmbientLightColor = new Vector3(0.09f, 0.15f, 0.215f);

那令人难以置信的漂亮。你在那里分配一个Vector3。还有2个人。 但是,嘿,你在双foreach循环! 之前的effect.AmbientLightColor去了哪里?好吧,它会收集垃圾。这是每帧的分配和垃圾收集的批次

相反,你应该使用更长的东西,但更多更有效:

effect.AmbientLightColor.X = 0.09f;
effect.AmbientLightColor.Y = 0.15f;
effect.AmbientLightColor.Z = 0.215f;

如果您在实际上不需要分配新对象的任何地方执行此操作,您将看到相当的性能提升。

黄金法则总是避免分配。如果您提供了更多代码,我可以尝试提供更多帮助,但我认为这应该有所帮助。

答案 1 :(得分:1)

我完全同意@ Heandel的回答。除了分配之外,还有以下几点:

内循环内部实际变量是什么?似乎只有2个方向向量和3个矩阵调用的分配正在改变。因此,当效果最初添加到网格时(可能在加载时),而不是每个渲染帧一次,可以仅调用12行中的7行。

Model Object = levelModels[part.modelName];:这是每帧每个部分的字符串哈希查找吗?为什么不在级别加载时只执行一次,并将模型引用与部件一起存储,而不仅仅是名称?

你为每个部分拨打MathHelper.ToRadians 3次电话。为什么不直接以弧度存储旋转?

答案 2 :(得分:1)

消耗超过1/2帧速率的一件事是mesh.Draw(); 是在错误的循环中,你有

mesh.Draw();
}
}

应该是

}
mesh.Draw();
}