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

本文关键字:网格 优化 绘制 模型 3D 一个 | 更新日期: 2023-09-27 17:55:22

我们正在为学校制作一款在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();
            }
        }
    }
}

因此,如果在Blender中,我只制作了一个网格,那么它必须执行更少的循环,我不确定这是否会使其绘制得更快。我需要将绘图性能提高很多的想法吗?该游戏是分屏的,最多可以有 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);
    }
} 

关于垃圾回收器的问题

所以通过设置像效果这样的矢量。方向光0.漫反射颜色使用此方法...

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()函数,它不会将所有垃圾添加到堆栈中,以便收集器几乎可以立即拾取它吗?我意识到它为收藏家增加了一些额外的工作,但它不应该增加那么多,对吗?

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

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

例如:

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

这真是太漂亮了。你在那里分配了一个 Vector3。还有另外 2 个。但是,嘿,你处于双重循环中!前effect.AmbientLightColor去哪儿了?好吧,它会被垃圾收集。这是每帧的大量分配和垃圾回收。

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

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

如果在实际上不需要分配新对象的所有位置执行此操作,则会看到性能的显着提高。

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

我完全同意@Heandel的回答。 除了分配之外,这里还有一些事情:

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

Model Object = levelModels[part.modelName]; : 这是在每帧对每个部分的字符串进行哈希查找吗? 为什么不在水平加载时只执行此操作一次,并将模型参考与零件一起存储,而不仅仅是名称?

您为每个部分拨打 3 次电话来MathHelper.ToRadians。 为什么不一开始就以弧度存储旋转呢?

占用

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

mesh.Draw();
}
}

它应该是

}
mesh.Draw();
}