суббота, 25 июня 2011 г.

Четыре метода использования Вершинных Текстур (Vertex Textures) Часть 5


Использование Вершинных Текстур в играх на платформе XNA (перевод)
Catalin Zima
Сколько много получилось полей! Надо немного это всё прокомментировать. Цель визуализации randomTexture будет заполняться случайными значениями, которые будут использоваться при симуляции. Цель визуализации temporaryRT необходима при обновлении координат и скоростей частиц, так как XNA не умеет одновременно писать и читать данные из текстуры. К примеру, когда происходит пересчет координат на основе старых данных, то новое значение записывается во временную цель визуализации, которая впоследствии копируется на место исходных значений. Вершинный буфер содержит все данные о вершинах, которые соответствуют количеству частиц. Если необходимо, мы можем создать несколько вершинных буферов, для нескольких независимых систем частиц, которые будут хранить различные части данных о координатах и скоростях частиц. Обновление будет происходить одновременно, но мы не сможем отрисовать эти системы, используя отсечение по пирамиде камеры. В качестве разминки, можете сами написать данную реализацию. Так же нам необходимы два эффекта (шейдера): первый используется для отрисовки частиц, он просто читает координаты вершин в вершинный шейдер, второй целиком и полностью занимается обновлением координат и скоростей. И, наконец, переменная particleCount отвечает за размер целей визуализации, ее величина должна быть относительно 2 (делиться на 2 без остатка), итого у нас будет particleCount * particleCount частиц.
Так, а сейчас перейдём к функции LoadGraphicsContent, где мы проинициализируем наши переменные:
particleTexture = content.Load("Textures\\flare");
    
    spriteBatch = new SpriteBatch(graphics.GraphicsDevice);


    temporaryRT = new RenderTarget2D(graphics.GraphicsDevice,
                                     particleCount,
                                     particleCount,
                                     1,
                                     SurfaceFormat.Vector4,
                                     MultiSampleType.None,
                                     0);
                                    
    positionRT = new RenderTarget2D(graphics.GraphicsDevice,
                                    particleCount,
                                    particleCount,
                                    1,
                                    SurfaceFormat.Vector4,
                                    MultiSampleType.None,
                                    0);
                                   
    velocityRT = new RenderTarget2D(graphics.GraphicsDevice,
                                    particleCount,
                                    particleCount,
                                    1,
                                    SurfaceFormat.Vector4,
                                    MultiSampleType.None,
                                    0);
                                   
    simulationDepthBuffer = new DepthStencilBuffer(
                                    graphics.GraphicsDevice,
                                    particleCount,
                                    particleCount,
                        graphics.GraphicsDevice.DepthStencilBuffer.Format);
                       
    isPhysicsReset = false;

SurfaceFormat для целей визуализации указан Vector4. На моей видеокарте (GeForce 7600) Vector4 единственный поддерживаемый формат для VTF (HalfVector4 не поддерживается, а Single содержит только одно значение, а нам нужно как минимум 3, чтобы записать информацию о координатах частицы). Если Вы планируете, запуск на XBOX 360, то Вам необходимо использовать HalfVector4, так как Vector4 генерирует ошибку времени исполнения, говоря, что данный формат не поддерживается. Данные физики необходимо сбрасывать при перестройке целей визуализации, так что isPhysicsReset устанавливаем в false.
Далее добавляем код инициализации вершинного буфера частиц:
VertexPositionColor[] vertices =
              new VertexPositionColor[particleCount * particleCount];
             
    Random rand = new Random();

    for (int i = 0; i < particleCount; i++)
    {
        for (int j = 0; j < particleCount; j++)
        {
            VertexPositionColor vert = new VertexPositionColor();

            vert.Color = new Color(150,
                                   150,
                                   (byte)(200 + rand.Next(50)));
                                  
            vert.Position = new Vector3();
           
            vert.Position.X = (float)i / (float)particleCount;
            vert.Position.Y = (float)j / (float)particleCount;
           
            vertices[i * particleCount + j] = vert;
        }
    }

    particlesVB = new VertexBuffer(graphics.GraphicsDevice,
                                   typeof(VertexPositionColor),
                                   particleCount * particleCount,
                                   ResourceUsage.Points);
                                  
    particlesVB.SetData(vertices);


Мы будем использовать технику называемую Point Sprites, таким образом, для одной частицы нужна только одна вершина. Для каждой частицы мы установили случайный цвет, в данном примере я выбрал случайное значение бело-синего цвета. Вместо координат в пространстве для вершин, мы установили текстурные координаты, которые будут заменяться реальными координатами при считывании из текстуры. Как Вы могли заметить мы не устанавливали значение Position.Z, так как данных для этого значения еще нет. Мы вообще не можем установить это значение сразу, если нам понадобится два прохода для получения данных, к примеру, выборка данных из двух различных текстур или при установке сортировки для эффекта мерцания и т.п. Вообще есть много применений где можно использовать данный подход, но в данном уроке мы просто оставляем это значение не заполненным. Вершинный буфер создается с флагом ResourceUsage.Points и данные вершин просто копируются в него.
Далее мы создаем randomTexture и заполняем случайными значениями в диапазоне от -0,5 до 0,5, таким образом, что значения X, Y и Z определяют куб с центром в начале координат. Размер текстуры выбирается произвольный, по Вашему желанию.
randomTexture = new Texture2D(graphics.GraphicsDevice,
                                  128,
                                  128,
                                  1,
                                  ResourceUsage.None,
                                  SurfaceFormat.Vector4);
                                 
    Vector4[] pointsarray = new Vector4[128*128];
   
    for (int i = 0; i < 128*128; i++)
    {
        pointsarray[i] = new Vector4();
       
        pointsarray[i].X = (float)rand.NextDouble() - 0.5f;
        pointsarray[i].Y = (float)rand.NextDouble() - 0.5f;
        pointsarray[i].Z = (float)rand.NextDouble() - 0.5f;
        pointsarray[i].W = (float)rand.NextDouble() - 0.5f;
    }
   
    randomTexture.SetData(pointsarray);

Теперь давайте немного попишем шейдеры. Создайте новый файл с именем Particle.fx в папке Shaders. Нам необходимы стандартные матрицы, затем добавьте текстуры, одну для рисования частиц и еще одну, из которой мы будем читать координаты частиц.
    float4x4 view;
    float4x4 proj;
    float4x4 world;

    float sizeModifier : PARTICLE_SIZE = 3.5f;

    // тестура для отрисовки сцены
    texture textureMap : DiffuseMap; 
    sampler textureSampler = sampler_state
    {
        Texture = ;
       
        AddressU  = CLAMP;
        AddressV  = CLAMP;
       
        MIPFILTER = LINEAR;
        MINFILTER = LINEAR;
        MAGFILTER = LINEAR;
    };

    // текстура для координат, используется в VTF
    texture positionMap;
    sampler positionSampler = sampler_state
    {
        Texture   = ;
       
        MipFilter = None;
        MinFilter = Point;
        MagFilter = Point;
       
        AddressU  = Clamp;
        AddressV  = Clamp;
    };

Переменная sizeModifier отвечает за размер частиц. После того как мы напишем весь код, можете поиграться с этим значением и подобрать размер частиц, который больше по вкусу. Далее добавляем входные и выходные структуры.
    struct VS_INPUT
    {
        float4 vertexData  : POSITION;
        float4 color       : COLOR0;
    };

    struct VS_OUTPUT
    {
        float4 position    : POSITION;
        float4 color       : COLOR0;
        float Size         : PSIZE0;
    };

    struct PS_INPUT
    {
        #ifdef XBOX
            float4 textureCoordinate : SPRITETEXCOORD;
        #else
            float2 textureCoordinate : TEXCOORD0;
        #endif
       
        float4 Color : COLOR0;
    };


Нам необходим блок #ifdef, так как реализация Point Sprites на XBOX 360 немного отличается от реализации Windows. Далее в вершинном шейдере мы рассчитываем worldViewProjection матрицу, извлекаем данные координат частиц и трансформируем их. Мы рассчитываем размер частиц на основе матрицы проекции и высоты экрана, чтобы размер частиц не зависел, от разрешения экрана.
    float screenHeight = 600;

    VS_OUTPUT Transform(VS_INPUT In)
    {
        VS_OUTPUT Out = (VS_OUTPUT)0;
       
        float4x4 worldView      = mul(world, view);
        float4x4 WorldViewProj  = mul(worldView, proj);
       
        // преобразовываем координаты из локальной системы координат
        // в однородную
        float4 realPosition = tex2Dlod ( positionSampler,
                                         float4(In.vertexData.x,
                                                In.vertexData.y,
                                                0,
                                                0));
                                               
        Out.color = In.color;
       
        // нам нужно установить это значение в 1, т.к. значение считанное
        // из текстуры содержит данные о времени жизни частицы,
        // которые в данный момент нас не интересуют
        realPosition.w = 1;
       
        Out.position = mul( realPosition , WorldViewProj);
       
        Out.Size = sizeModifier *
                   proj._m11 /
                   Out.position.w *
                   screenHeight /
                   2;
                  
        return Out;
    }

И, наконец, пиксельный шейдер отрисовывает частицу с определенным цветом. Можно использовать данные о времени жизни частицы для изменения ее цвета, к примеру, затухание через альфа канал или использовать одномерную текстуру, для раскрашивания частицы. Пиксельный шейдер для рисования частицы:
    float4 ApplyTexture(PS_INPUT input) : COLOR
    {
         float2 textureCoordinate;
        
    #ifdef XBOX
        textureCoordinate = abs(input.textureCoordinate.zw);
    #else
        textureCoordinate = input.textureCoordinate.xy;
    #endif
   
         float4 col=tex2D(textureSampler,
                          textureCoordinate) * input.Color;
                         
         return col;
    }

    technique TransformAndTexture
    {
        pass P0
        {
            vertexShader = compile vs_3_0 Transform();
            pixelShader  = compile ps_3_0 ApplyTexture();
        }
    }

Теперь в папке Shaders создадим еще один файл шейдера ParticlePhysics.fx. На этот раз нам нужно четыре текстуры в шейдере. Временная текстура (temporaryMap) нужна при копировании данных из одной цели визуализации в другую. Текстуры positionMap и velocityMap содержат данные координат и скоростей частиц, randomMap содержит случайные данные, которые мы генерировали ранее.
    texture temporaryMap;
    sampler temporarySampler : register(s0)  = sampler_state
    {
        Texture   = ;
       
        MipFilter = None;
        MinFilter = Point;
        MagFilter = Point;
       
        AddressU  = Clamp;
        AddressV  = Clamp;
    };

    texture positionMap;
    sampler positionSampler  = sampler_state
    {
        Texture   = ;
       
        MipFilter = None;
        MinFilter = Point;
        MagFilter = Point;
       
        AddressU  = Clamp;
        AddressV  = Clamp;
    };

    texture velocityMap;
    sampler velocitySampler = sampler_state
    {
        Texture   = ;
       
        MipFilter = None;
        MinFilter = Point;
        MagFilter = Point;
       
        AddressU  = Clamp;
        AddressV  = Clamp;
    };

    texture randomMap;
    sampler randomSampler : register(s0) = sampler_state
    {
        Texture   = ;
       
        MipFilter = None;
        MinFilter = Point;
        MagFilter = Point;
       
        AddressU  = wrap;
        AddressV  = wrap;
    };

Комментариев нет:

Отправить комментарий