Легкий рендеринг приводит к низкому FPS

В настоящее время я разрабатываю класс для своей игры XNA, которая отображает свет на изображении. В то время я сделал источник для рисования моей карты освещения, однако FPS в моем источнике очень низкий. Я знаю, что он сильно уменьшается при циклическом просмотре каждого пикселя, однако я не знаю другого способа получить и установить каждый пиксель моей текстуры в XNA, кроме как с помощью оператора «For»?

Источник тока:

 public struct Light
    {
        public int Range;
        public int Intensity;
        public Color LightColor;
        public Vector2 LightLocation;

        public Light(int _Range, int _Intensity, Color _LightColor, Vector2 _LightLocation)
        {
            Range = _Range;
            Intensity = _Intensity;
            LightLocation = _LightLocation;
            LightColor = _LightColor;
        }
    }
    public class RenderClass
    {
        [System.Runtime.InteropServices.DllImport("User32.dll")]
        public static extern bool MessageBox(IntPtr h, string S, string C, int a);

        public static Texture2D RenderImage(Light[] LightLocations, Texture2D ScreenImage, Viewport v, bool ShadowBack = false)
        {
            Texture2D[] Images = new Texture2D[LightLocations.Count()];
            int curCount = 0;

            /*LOOP THROUGHT EACH LIGHT*/
            foreach (Light LightLocation in LightLocations)
            {
                /*VARIABLES*/
                Color LightColor = LightLocation.LightColor;
                int Range = LightLocation.Range;
                int Intensity = LightLocation.Intensity;

                /*GET COLORS*/
                int Width = v.Width;
                int Height = v.Height;
                Color[] Data = new Color[Width * Height];
                ScreenImage.GetData<Color>(Data);

                /*VARIABLES TO SET COLOR*/
                Color[] SetColorData = new Color[Width * Height];

                /*CIRCEL*/
                int Radius = 15 / 2; // Define range to middle [Radius]
                int Area = (int)Math.PI * (Radius * Radius);

                for (int X = 0; X < Width; X++)
                {
                    for (int Y = 0; Y < Height; Y++)
                    {
                        int Destination = X + Y * Width;

                        #region Light
                        /*GET COLOR*/
                        Color nColor = Data[Destination];

                        /*CREATE NEW COLOR*/
                        Vector2 MiddlePos = new Vector2(LightLocation.LightLocation.X + Radius, LightLocation.LightLocation.Y + Radius);
                        Vector2 CurrentLocation = new Vector2(X, Y);

                        float Distance;
                        Distance = Vector2.Distance(MiddlePos, CurrentLocation);
                        Distance *= 100;
                        Distance /= MathHelper.Clamp(Range, 0, 100);

                        Vector3 newColors = nColor.ToVector3();

                        nColor = new Color(
                            newColors.X,
                            newColors.Y,
                            newColors.Z, 
                            Distance / 100);


                        /*SET COLOR*/
                        SetColorData[Destination] = nColor; // Add to array
                        #endregion
                        #region Shadow
                        #endregion
                    }
                }


                ScreenImage.SetData<Color>(SetColorData);
                Images[curCount] = ScreenImage;
                curCount++;
            }

            return Images[0]; // Temporarily returning the first image of the array.
        }
    }

Как видите, это медленный и плохой метод. Поэтому мне было интересно, есть ли лучший способ получить и установить каждый пиксель?

Заранее спасибо, dotTutorials! знак равно


person Rasmus Søborg    schedule 27.05.2012    source источник
comment
В чем смысл делегата OppositeColor?   -  person Chris Sinclair    schedule 27.05.2012
comment
Пользовался ранее. Мой плохой, я довольно грязный в этом источнике, атм. Сейчас исправлю =)   -  person Rasmus Søborg    schedule 27.05.2012
comment
Возможно, вам следует почистить его; удалите часть ненужного создания объектов (например, Vector2 MiddlePos/CurrentLocation только для расчета расстояния) и, возможно, оцените время обработки. Я не знаю, есть ли более эффективный метод для достижения того же общего эффекта, к которому вы стремитесь, но если вы просматриваете каждый пиксель в отрендеренном представлении, каждый кадр, то этот внутренний цикл необходимо оптимизировать как насколько это возможно.   -  person Chris Sinclair    schedule 27.05.2012
comment
Вы также можете переместить Clamp(Range, 0, 100) из цикла, и, поскольку вы умножаете Distance на 100, а затем делите на 100, вы можете удалить эти три шага. РЕДАКТИРОВАТЬ: Кроме того, вы можете удалить создание MiddlePos из цикла. В любом случае, сначала оптимизируйте свой цикл, тогда вы сможете найти узкое место.   -  person Chris Sinclair    schedule 27.05.2012
comment
Вместо того, чтобы настраивать каждый пиксель на ЦП, вы должны рисовать круги с помощью SpriteBatch на графическом процессоре. Используйте подходящую текстуру и масштаб. Уверен, вы сможете добиться желаемого результата. Сначала переключите rendertarget на целевую текстуру.   -  person Nico Schertler    schedule 27.05.2012
comment
Правда, однако, я предпочел бы сделать это. Kinda Enlight область вокруг моего света   -  person Rasmus Søborg    schedule 28.05.2012


Ответы (1)


Я думаю, что эту работу лучше всего выполнять в пиксельных шейдерах.

Вы можете создать файл эффекта, который работает с одним источником света за раз.
XNA использует DX9, поэтому вы будете ограничены 128 постоянными регистрами, которые, я думаю, вы можете использовать для сжатия до трех источников света.

Таким образом, вы устанавливаете карту освещения в качестве цели рендеринга, перебираете все источники света, устанавливаете постоянные данные для вашего эффекта, визуализируете четырехугольник размером с цель рендеринга и в своем пиксельном шейдере вычисляете уравнение освещения.

По сути примерно так:

    // In LoadContent
RenderTarget2D lightmapRT = new RenderTarget2D(graphics.GraphicsDevice,
                                                 128,
                                                 128,
                                                 false, //No mip-mapping
                                                 SurfaceFormat.Color,
                                                 DepthFormat.Depth24);

// We now render to the lightmap in Render method
graphics.GraphicsDevice.SetRenderTarget(lightmapRT);

// Lightmap is black by default
graphics.GraphicsDevice.Clear(Color.Black);

// Use the sprite batch to draw quads with custom shader
spriteBatch.Begin(0, BlendState.Opaque, null, null, null, lightmapFx);

foreach (var light in lights)
{
    // Pass the light parameters to the shader
    lightmapFx.Parameters["Viewport"].SetValue(new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height));
    lightmapFx.Parameters["Range"].SetValue(light.Range);
    lightmapFx.Parameters["Intensity"].SetValue(light.Intensity);
    lightmapFx.Parameters["LightColor"].SetValue(light.LightColor);
    lightmapFx.Parameters["LightLocation"].SetValue(light.LightLocation);

    // Render quad
    spriteBatch.Draw(...);
}
spriteBatch.End();

И файл FX будет выглядеть примерно так:

float Range;
float Intensity;
float3 LightColor;
float2 LightLocation;
float2 Viewport;

struct VStoPS
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

VStoPS VS(in float4 color    : COLOR0,
          in float2 texCoord : TEXCOORD0,
          in float4 position : POSITION0)
{
    VStoPS vsout = (VStoPS)0;

    // Half pixel offset for correct texel centering.
    vsout.Position.xy -= 0.5;

    // Viewport adjustment.
    vsout.Position.xy = position.xy / Viewport;
    vsout.Position.xy *= float2(2, -2);
    vsout.Position.xy -= float2(1, -1);

    // Pass texcoords as is
    vsout.TexCoord = texCoord;

    return vsout;
}

float4 PS(VStoPS psin)
{
    // Do calculations here
    // Here I just set it to white
    return float4(1.0f, 1.0f, 1.0f, 1.0f);
}

technique Main
{
    pass p0
    {
        VertexShader = compile vs_3_0 VS();
        PixelShader = compile ps_3_0 PS();
    }
}

Обратите внимание, что этот код не проверен и, вероятно, полон ошибок. Я оставляю это на ваше усмотрение, чтобы выяснить, что нужно сделать в пиксельном шейдере.

person Julien Lebot    schedule 28.05.2012