четверг, 23 июня 2011 г.

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

 

Использование Вершинных Текстур в играх на платформе XNA (перевод)

Catalin Zima

Оглавление
  1. Введение
  2. Представление ландшафта, используя карту высот
  3. Морфинг ландшафта
  4. Системы частиц
  5. Шаги на снегу
  6. Заключение и будущие разработки
Введение
В этом обучающем приложении будет рассмотрено четыре метода, в каждом из которых возможно использование Вершинных Текстур в XNA играх. Мы начнём с краткого описания вершинных текстур, что это такое и как их использовать. Затем мы продолжим, показав реализацию четырёх эффектов (с использованием этих текстур). Они будут начинаться с простейших базовых операций и постепенно возрастать в трудности, затрагивая различные техники, которые могут быть использованы вместе с вершинными текстурами. Постараемся объяснить каждый шаг, почему выбрана именно эта реализация и как различные техники использовать в других задачах, неописанных в данной статье.
Некоторые разделы будут содержать Бонус (дополнение) с разъяснениями как использовать те или иные подходы реализации в других подобных задачах, даже если они не связаны с Vertex Texture Fetching (далее по тексту просто VTF). Эти подразделы будут небольшими и содержать некоторый код; однако значения всех переменных будет объяснено, так что Вы сможете использовать их в любом контексте.
Вершинные Структуры
С момента появления программируемых графических процессоров в их архитектуру было заложено, что вершинный и пиксельный шейдеры имеют значительно разные характеристики. Шейдер Модели 3.0 сделал первые шаги для объединения их функциональности. DirectX 10 сделал последний шаг и объединил набор инструкций между всеми типами шейдеров: пиксельными (Pixel), вершинными (Vertex) и новыми геометрическими (Geometry) шейдерами.
Обычно Вершинные Шейдеры имеют дело с трансформацией вершин и манипулированием свойств вершин таких как позиция, цвет, нормаль и координаты текстуры. Вершины определяют треугольники, которые затем трансформируются в пиксели. На этом шаге пиксельный Шейдер вступает в игру и может использоваться для нанесения текстуры, бамп маппинга, нормальной карты, и для многих остальных эффектов, которые мы все так любим. Для краткого ознакомления с HLSL просмотрите эту статью и статьи по шейдерам на creators.xna.com.
VTF позволяет нам читать информацию из текстуры в вершинный шейдер, почти так же, как это делает пиксельный шейдер. Чтобы использовать функциональность VTF Вам потребуется XBOX 360 (у которого унифицированная шейдерная архитектура) или графическая карта NVIDIA серии GeForce 6 или выше. Для всех владельцев видеоадаптеров от ATI применима другая технология - Render To Vertex Buffer (R2VB), т.е. “отрисовка в вершинный буфер”. Она позволяет достигнуть тех же результатов, но я не знаю, как использовать данную технологию в XNA. (Тут можно прочитать как сэмулировать VTF программно, для пользователей адаптеров ATI).
Вершинные текстуры ведут себя как пиксельные за исключением следующих ограничений:
  • Билинейная (Bilinear) и трилинейная (Trilinear) фильтрация не поддерживаются на аппаратном уровне. Далее будет рассмотрено, как реализовать билинейную фильтрацию в вершинном шейдере.
  • Анизотропная (Anisotropic) фильтрация не поддерживается на аппаратном уровне.
  • Автоматический расчет mipmap уровня не производится и если необходимо, расчет нужно реализовывать самим.
Основной код
Прежде чем начать, пожалуйста, скачайте файл Resources.zip. Он содержит:
· Две карты высот, .dds в формате R32F; каждый пиксель записан в 32 битный float и содержит информацию о высоте.
o Примечание: видеокарты NVIDIA поколения 6 и 7 для VTF поддерживают только R32F и A32R32G32B32F форматы. Возможно GeForce 8 и XBOX 360 могут для VTF использовать и другие форматы, но у меня нет возможности проверить это.
· Несколько текстур взятых с turbosquid.com и других уроков и примеров.
· Camera.cs является компонентом (GameComponent), который реализует игровую камеру. Взят из примера Skinned Model Sample.
o Используйте триггеры или клавиши Z и X для зума камеры.
o Используйте правый стик или клавиши WASD для перемещения камеры.
· Grid.cs класс для воспроизведения (отрисовки) сетки в плоскостях XZ.
· DudeModel.zip будет использован в четвертом разделе.
Эти ресурсы будут использованы в данном уроке, и исходный код будет содержать ссылки на эти файлы. Grid.cs.
Данный класс реализует геометрию сетки и будет использован в разделах “Рисовка карты высот”, “Морфинг ландшафта” и “Шаги на снегу”. Свойство CellSize отвечает за то, на сколько большими будут ячейки сетки, а свойство Dimension контролирует размерность сетки (сколько строк, столбцов). Каждая вершина содержит информацию о позиции, нормали и соответствующей текстурной координате. Таким образом, каждая вершина ссылается (мапится) на пиксель в текстуре, и это будет использовано в вершинном шейдере для манипуляции вершиной. Механизм ассоциации между вершиной и пикселем текстуры является ключевым при использовании текстур вершины. Изначально высота для всех вершин установлена в 0, так что мы имеем идеально гладкую поверхность, в этом можно убедиться, посмотрев на функцию GenerateStructures (строка 46, кода Grid.cs).
    //горизонатльная поверхность размера cellSize * dimension, 
    //  центр на Vector3.Zero
    vert.Position = new Vector3((i - dimension / 2.0f) * cellSize, 
                                0, 
                                (j - dimension  /2.0f) * cellSize);
                                
    // ассоциируем текстуру координат для каждой вершины
    vert.TextureCoordinate = new Vector2((float)i / dimension, 
                                         (float)j / dimension);

I. Отрисовка ландшафта, используя карту высот

Мы начнём с простого примера отрисовки ландшафта, используя карту высот. Карта высот это изображение (оттенки серого), содержащее информацию о высотах. Чем темнее оттенок пикселя, тем ниже данный участок поверхности, соответственно, чем светлее, тем выше. Когда мы используем текстуру, которая содержит данные с плавающей точкой, то принцип остаётся таким же. Каждый пиксель включает переменную типа float: 0,0 – минимальная высота, а 1,0 – максимальная. На самом деле построить ландшафт по карте высот можно и без использования вершинных текстур, просто прочитав карту высот на этапе загрузки и на основе ее данных построить буфер вершин. А так как эта операция производится только один раз (в момент загрузки), а не каждый кадр (в вершинном шейдере), то использование вершинных текстур для решения подобных задач, в плане производительности приложения, несколько нелогично. Однако, это наиболее простейший и понятный пример, который демонстрирует использование VTF.
Итак, давайте начнём с создания нового проекта (для Windows или Xbox360). Добавим в проект файлы Camera.cs и Grid.cs. Затем идем в основной класс созданного проекта (Game1.cs) и добавляем пространство имен VTFTutorial в объявления using.
        using VTFTutorial;
Теперь добавляем две новые переменные в класс Game1: одну для камеры и одну для сетки. Инициализируем их и указываем параметры в конструкторе класса. Камера должна быть добавлена в список компонентов. Так же мы зададим параметры сетки CellSize и Dimension, 4 и 256 соответственно. Вы можете поиграться с параметром CellSize и повыставлять ему различные параметры, просто ради интереса и посмотреть что будет. О том, что произойдет если выставить другие параметры свойству Dimension, мы поговорим чуть позже. Теперь в методе LoadGraphicsContent добавьте вызов grid.LoadGraphicsContent().
    Camera camera;
    Grid grid;
 
    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);
 
        camera = new Camera(this);
        this.Components.Add(camera);
 
        grid = new Grid(this);
        grid.Dimension = 256;
        grid.CellSize = 4;
    }
 
    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        grid.LoadGraphicsContent();
    }
 
У нас есть геометрия, и теперь нам необходимо создать файл эффекта, который нам создаст и отрисует ландшафт. Добавьте в проект новую папку с именем “Shaders”, затем в эту папку добавьте новый текстовой файл (Add -> New Item...) с именем “VTFDisplacement.fx”. Как только это сделаете, откройте его, и мы приступим к написанию кода шейдера на языке HLSL (High Level Shading Language).
Нам понадобиться три параметра матрицы: для матриц мира (world), вида (view) и проекции (projection). Далее, мы должны добавить карту высот с именем displacementMap (карта смещения, так как именно в ней содержится информация о смещении вершин относительно плоскости XZ) и самплер дня нее. Самплер будет использоваться для чтения данных высоты из карты высот в вершинном шейдере. Для всех фильтров с самплере используется значение Point, потому что линейный (Linear) и анизотропный (Anisotropic) не поддерживаются для вершинных текстур. Даже если прописать другое значение для фильтров, никаких сообщений об ошибках выведено не будет, но использоваться все равно будет значение Point.
float4x4 world; // матрица виртуального мира
    float4x4 view;  // матрица вида
    float4x4 proj;  // матрица проекции  
 
    //максимальная высота ландшафта
    float maxHeight = 128;
 
    // эта текстура будет нанесены на карту высот
    texture displacementMap; 
          
    //этот сэмплер будет использован для чтения карты высот
    sampler displacementSampler = sampler_state      
    {
            Texture   = <displacementMap>;
            
            MipFilter = Point;
            MinFilter = Point;
            MagFilter = Point;
            
            AddressU  = Clamp;
            AddressV  = Clamp;
    };
На следующем шаге создадим две структуры вершинного шейдера, одна будет описывать данные, поступающие на вход шейдера, вторая - данные на выходе из него. На вход мы будем подавать местоположение вершины и ее текстурные координаты, на выходе мы будем получать трансформированные вершины в пространстве, а так же будем передавать в пиксельный шейдер еще один «комплект» координат вершины в переменной worldPos для «раскрашивания» ландшафта, основываясь на данных высоты вершин. Переданные текстурные координаты здесь пока не трогаем.
struct VS_INPUT 
    {
        float4 position : POSITION;
        float4 uv       : TEXCOORD0;
    };
    
    struct VS_OUTPUT
    {
        float4 position  : POSITION;
        float4 uv        : TEXCOORD0;
        float4 worldPos  : TEXCOORD1;
    };
Код для вершинного шейдера выглядит так:
    VS_OUTPUT Transform(VS_INPUT In)
    {
        //инициализация выходной структуры
        VS_OUTPUT Out = (VS_OUTPUT)0;
        
        //рассчёт View * Projection matrix
        float4x4 viewProj = mul(view, proj);
        
        //окончательный рассчёт the World * View * Projection matrix
        float4x4 worldViewProj= mul(world, viewProj);
 
        // данная инструкция читает карту высот 
        // в соответствие с текстурными координатами вершин
        
        // Примечание: мы передаём как параметр 0 уровень mipmap в tex2Dlod, 
        // т.к. хотим чтобы изображение использовалось 1 к 1
        float height = tex2Dlod ( displacementSampler, 
                                  float4(In.uv.xy , 0 , 0 ) );
 
        // После считывания данных с высоты текстуры 
        // мы присваиваем это значение у координаты вершин
        // и т.к. значение height лежит в диапазоне от 0 до 1 
        // то перемножаем с переменной Maximum Height (отвечает за  максимальный подъём ландшафта)
        In.position.y = height * maxHeight;
       
        //Передаём в пискельный шейдер координаты вершины в мировом пространстве
        Out.worldPos = mul(In.position, world);
        
        //Рассчитываем финальное положение вершины 
        // для вывода на экран
        Out.position = mul( In.position, worldViewProj);
        
        Out.uv = In.uv;
        
        return Out;
    }
 
Функция tex2Dlod читает данные высот из текстуры, используя текстурные координаты, которые создаются в классе Grid.cs. Перед тем как координаты вершины будут перемножены со всеми матрицами для вывода на экран, мы подменим данные координаты Y. Таким образом, на этом этапе вершины будут выстроены по высоте в соответствии с данными карты высот, и только потом произойдет преобразование вершин при помощи матриц мира, вида и проекции. Все эти действия выполняет графический процессор.
Настало время позаботиться о пиксельном шейдере. Мы просто отрисовываем ландшафт, раскрашивая в оттенки серого в соответствии с высотой: более светлые тона наверху, темные внизу. Техника (technique) шейдера имеет всего один проход (pass), содержащий два наших шейдера. В параметрах компиляции указываем vs_3_0 и ps_3_0, т.к. VTF, как мы отметили ранее, является функциональностью Shader Model 3.0.
float4 PixelShader(in float4 worldPos : TEXCOORD1) : COLOR
    {
        return worldPos.y / maxHeight;
    }
 
    technique GridDraw
    {
        pass P0
        {
            vertexShader = compile vs_3_0 Transform();
            pixelShader  = compile ps_3_0 PixelShader();
        }
    }

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

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