Опубликован: 05.08.2010 | Уровень: специалист | Доступ: платный
Самостоятельная работа 3:

Windows Forms и XNA 3.0

Общие сведения о примитивах

Примитивом называется графический объект - точка, прямая линия, треугольник, которые используются для построения более сложных объектов. В XNA Framework поддерживает шесть типов примитивов, заданных структурой PrimitiveType: список точек PointList, список линий LineList, полоса линий LineStrip, список треугольников TriangleList, полоса треугольников TriangleStrip и веер треугольников TriangleFan.

Примитивы описываются координатами точек вершин в мировой системе координат. В XNA и OpenGL используется правая система мировых координат, а в DirectX - левая. Начало мировой системы координат находится на поверхности экрана в его центре. Для наблюдателя, смотрящего на экран, ось Ox направлена по горизонтали вправо, а ось Oy - по вертикали вверх. Системы отличаются только направлением оси Oz: в правой системе координат она направлена из глубины экрана к пользователю, а в левой - от пользователя в глубину экрана.

Совокупность трехмерных объектов в мировой системе координат называется сценой. Визуализация сцены выполняется при помощи установки камеры, размещение которой также задается относительно мировой системы координат. Положение камеры ассоциируется с положением глаз наблюдателя и с математической точки зрения определяет матрицу вида. Для корректного отображения объектов сцены на экране каждый из них предварительно преобразуется матрицей вида. Обычно камера отодвигается от экрана в положительном направлении оси Oz и несколько приподнимается вверх в положительном направлении оси Oy. Это позволяет установить взгляд наблюдателя на сцену как с балкона театра.

В пространстве имен Microsoft.Xna.Framework.Graphics имеется ряд структур для хранения информации о вершинах примитива. В данном упражнении мы будем использовать структуру VertexPositionColor, инкапсулирующую информацию о координатах и цвете вершины.

Напомним, что любая структура, как и класс, содержит члены-методы и члены-данные и относится к значимому типу (в противоположность к ссылочномы типу). Члены-данные струтуры (как и класса) называются полями. В структурах C# конструктор по умолчанию является предопределенным и его задавать нельзя. Это сделано для того, что объявление структурной переменной одновременно означает и создание экземпляра структуры с неявным вызовом конструктора по умолчанию. Зато параметризованных конструкторов может быть сколько угодно при условии, что каждый из них имеет уникальную в рамках структуры сигнатуру. В этом случае применение оператора new обязательно.

Библиотечная структура VertexPositionColor имеет один параметризованный конструктор вида:

public VertexPositionColor(Microsoft.Xna.Framework.Vector3 position,
      Microsoft.Xna.Framework.Graphics.Color color)
  • position - координаты вершины
  • color - цвет вершины

Информацию обо всех вершинах примитива можно хранить в массиве, например:

VertexPositionColor[] vertices;

Но здесь есть один нюанс. Дело в том, что при визуализации примитивов информация о вершинах напрямую передается в графический процессор видеокарты GPU (Graphics Processor Unit), который не знает, что используется массив структур именно VertexPositionColor. Для разъяснения графическому процессору формата отдельных полей структуры применяются декларации формата вершины. В XNA Framework декларация вершины задается классом VertexDeclaration, конструктор которого имеет вид:

public VertexDeclaration(Microsoft.Xna.Framework.Graphics.GraphicsDevice graphicsDevice,
      Microsoft.Xna.Framework.Graphics.VertexElement[] elements)
  • graphicsDevice - графическое устройство, используемое для работы с вершинами
  • elements - массив элементов c описанием формата

Массив elements содержит информацию о формате данных, с которыми будет работать графическое устройство:

  • Адрес описываемого поля структуры (смещение от начала структуры)
  • Тип поля структуры (скаляр, вектор, упакованный вектор)
  • Информация, содержащаяся в данном поле (координаты вершины, цвет вершины, текстурные координаты и т.п.)
  • Некоторая другая служебная информация

Если для хранения данных применяется структура VertexPositionColor, то описание ее формата хранится в статическом поле только для чтения VertexPositionColor.VertexElements. Пользователю (программисту) остается только передать это поле конструктору класса VertexDeclaration в качестве второго параметра.

Состав элементов сцены описывается один раз при загрузке приложения. Код загрузки описания примитивов можно разместить, например, в обработчике события Load формы. Изменение позиции объектов и их цвет, расчитываемые для каждого кадра визуализации сцены с помощью мировых матриц смещения, поворота и масштабирования, матриц проекций и вида, помещают в блок кода Update(), который выполняется каждый раз в обработчике события Paint.

Визуализация массива примитивов осуществляется с помощью метода класса GraphicsDevice, имеющего следующее описание

public void DrawUserPrimitives<T>(Microsoft.Xna.Framework.Graphics.PrimitiveType primitiveType, T[] vertexData,
    int vertexOffset, int primitiveCount)
  • T - шаблон типа
  • primitiveType - тип примитива, задаваемый с использованием перечисления PrimitiveType
  • vertexData - массив вершин примитива
  • vertexOffset - смещение от начала массива. Данный параметр обычно равен нулю. Ненулевые значения применяется, когда визуализируемый примитив использует примитивы лишь из части массива (например, вершины разных примитивов хранятся в одном большом общем массиве)
  • primitiveCount - количество примитивов, которые нужно выбрать для визуализации

Таким образом, для визуализации примитивов приложение должно выполнить примерно следующие шаги:

В обработчике события Load:

  • Создать экземпляр presentParams класса PresentationParameters и определить в его свойствах настройки будущего графического устройства device
  • Создать само графическое устройство device типа GraphicsDevice и настроить его через свойства экземпляра presentParams
  • Создать и заполнить массив vertices структур типа VertexPositionColor[], где каждый элемент этого массива задает координаты вершины типа Vector3 и ее цвет типа Color
  • Создать декларацию формата вершины - экземпляр decl структуры VertexDeclaration, передав ее конструктору статическое поле-массив VertexPositionColor.VertexElements

В обработчике события Paint:

  • Передать ссылку на объект decl свойству VertexDeclaration объекта device для корректной обработки вершин при отображении сцены на экране
  • Привести размеры заднего буфера в соответствие размерам окна вывода
  • Очисть экран методом device.Clear()
  • Нарисовать набор примитивов вызовом метода device.DrawUserPrimitives()
  • Показать полученное изображение на экране, переключив буферы методом device.Present()

На первый взгляд операцию передачи ссылки на объект decl свойству VertexDeclaration объекта device было бы рациональнее вынести в обработчик события Load. Однако настройки графического устройства теряются при сбросе методом Reset() и нужно восстанавливать информацией из decl и presentParams. И это нужно делать сразу в обработчике события Paint.

Но это еще не все. Дело в том, что все современные видеокарты содержат специализированные GPU - векторные и пиксельные процессоры, используемые для ускорения операций преобразования вершин и закраски примитивов. Так как эти процессоры принимают участие при визуализации любых примитивов, приложение должно запрограммировать их на выполнения требуемых преобразований. Если этого не сделать, то результат вызова метода device.DrawUserPrimitives() будет непредсказуемым. Отсюда возникает необходимость программирования вершинных и пиксельных процессоров графического ускорителя видеокарты.

Введение в HLSL

Для программирования вершинных и пиксельных процессоров GPU служит язык HLSL (High Level Shader Language - язык высокого уровня для программирования шейдеров). Его разработала Microsoft в 2002 году. Программа для вершинного процессора называется вершинным шейдером ( VS ), а для пиксельного процессора - пиксельным шейдером ( PS ) . Классы библиотеки XNA Framework при работе с шейдерами на платформе Windows в значительной степени опирается на функциональность DirectX.

Язык HLSL хотя и тесно связан с архитектурой графического процессора, но является управляемым C -подобным языком программирования для некоторого виртуального процессора, приближенного к некоторому реальному прототипу. Такой подход напоминает применение промежуточного языка IL в .NET. Компиляция шейдера в систему команд физического процессора происходит непосредственно перед загрузкой шейдера в GPU. Это позволяет несколько абстрагироваться от аппаратной части процессора, генерируя при компиляции промежуточный байт-код, и таким образом расширить семейство видеокарт, на которых может работать графическое приложение.

На первой ступени вершины обрабатываются вершинным процессором по программе, называемой вершинным шейдером. На выходе из вершинного процессора получаются так называемые трансформированные (преобразованные) вершины. К вершинам могут быть "привязаны" различные параметры: цвет вершины, текстурные координаты и так далее. Координаты трансформированных вершин задаются в логической системе однородных координат, называемой clip space.

Однородные координаты вершины определяются четырьмя числами: (x, y, z, w). Перевод однородных координат в обычные геометрические осуществляется путем деления первых трех компонентов на четвертый компонент w: (x/w, y/w, z/w). Например, вершине с однородными координатами (1, 2, 3, 4) в трехмерном пространстве соответствует точка с координатами (1/4, 2/4, 3/4). Использование четвертого компонента обусловлено рядом особенностей алгоритмов визуализации трехмерных изображений, используемых в 3D -графике.

При визуализации двухмерных изображений компонент w обычно полагают равным 1. В этом случае нижнему левому углу клиентской области формы соответствует точка с координатами (-1, -1, 0, 1), правому верхнему углу клиентской области - (1, 1, 0, 1), а центру клиентской области - соответственно (0, 0, 0, 1).


На второй ступени графического конвейера видеокарта производит преобразование координат вершины из логической системы координат в оконную. По умолчанию координаты трансформируются таким образом, чтобы растянуть изображение на всю поверхность элемента управления. Managed DirectX через код XNA позволяет программисту задавать координаты вершин в оконных координатах. В этом случае, при вызове метода device.DrawUserPrimitives() вершины сразу поступают на третью стадию графического конвейера, минуя первую и вторую стадии.

На третьей ступени идет сборка примитивов. На этой стадии вершины объединяются в примитивы. Тип примитивов определяется первым параметром метода device.DrawUserPrimitives(). Так при использовании параметра PrimitiveType.TriangleStrip вершины трактуются, как опорные точки полосы треугольников. При этом каждый треугольник из полосы является независимым примитивов и обрабатывается независимо от других треугольников этой полосы.

На четвертой ступени происходит растеризация примитивов – преобразование каждого примитива в набор пикселей экрана. Параметры внутренних пикселей примитива (например, цвет) определяются путем интерполяции соответствующих параметров вершин вдоль поверхности примитива. Благодаря этой интерполяции при закраске треугольника с разноцветными вершинами образуются красивые цветовые переходы - градиенты цвета.

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

Самой крупной логической единицей HLSL является эффект ( Effect ), хранящийся в отдельном текстовом файле с расширением .fx. В принципе, эффект можно считать аналогом материала в 3DS MAX. Каждый эффект состоит из одной или нескольких техник ( technique ). Техника – это способ визуализации материала. Например, эффект визуализации мраморного материала может содержать три техники: технику High для получения изображения наивысшего качества при низкой производительности, Medium для получения изображения среднего качества, и Low – максимальная производительность при низком качестве изображения.

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

Известным инструментом для разработки шейдеров является RenderMonkey компании AMD. Этот продукт можно скачать по адресу (http://developer.amd.com/gpu/rendermonkey/Pages/default.aspx).

Типы данных HLSL

В HLSL все встроенные типы делятся на две большие группы: скалярные и векторные. Скалярные типы данных являются аналогами встроенных типов данных языка C.

Тип Описание
bool Логический тип, который может принимать значения true или false
int 32-х битное целое число
half 16-ти битное число с плавающей точкой
float 32-х битное число с плавающей точкой
double 64-х битное число с плавающей точкой

Задавая тип скаларной переменной, мы просто указываем компилятору свое пожелание. Если текущий ускоритель не поддерживает некоторые типы данных, используемые в программе, то при компиляции шейдера в машинный код они будут заменены ближайшими аналогами. Например, тип double может быть заменен на тип float, half или какой-нибудь иной внутренний тип. Поэтому программист должен стараться избегать жесткой привязки к точности и допустимому диапазону значений используемого типа данных. Особенно это актуально для типа int, так как подавляющее большинство современных ускорителей не поддерживают тип int, в результате чего он эмулируется посредством одного из вещественных типов. Подобная эмуляция приводит к существенному падению производительности шейдера.

Большинство данных, используемых в трехмерной графике, является векторами размерности от 2 до 4 скаларных переменных. Так, координаты точки в трехмерном пространстве задаются трехмерным вектором, цвет пиксела - четырехмерным вектором (три цвета и альфа-канал). Соответственно, все современные GPU являются векторными процессорами, способными одновременно выполнять одну операцию сразу над набором из четырех чисел (четырехмерным вектором).

Для описание векторных данных в HLSL имеются векторные типы размерностью от 2 до 4. Если сказать обобщенно, то векторный тип из N элементов скалярного типа type задается с использованием синтаксиса, отдаленно напоминающего шаблоны из C#:

vector<type, size>
  • type - имя скалярного типа: bool, int, half, float или double
  • size - размерность вектора: 1, 2, 3 или 4

Например, объявление переменной v, являющейся вектором из четырех чисел типа float, выполняется так:

vector<float, 4> v;

Но на практике обычно используется сокращенная запись

float4 v;

Язык HLSL позволяет инициализировать вектор двумя способами. Первый способ - перечислить значения вектора в фигурных скобках, например, присвоение четырехмерному вектору v начального значения выглядит так

float4 v = {0.2, 0.4, 0.6, 0.8};

Другой способ - создать новый вектор с использованием конструктора и присвоить его вектору v:

float4 v = float4(0.2, 0.4, 0.6, 0.8);

Любой вектор имеет множество конструкторов, которые могут принимать в качестве параметров как переменные скалярных типов, так и - векторных. Единственное ограничение: общее количество всех компонентов векторных и скалярных типов должно быть равно N. Подобное многообразие конструкторов дает программисту большую гибкость при инициализации векторов. Например, вот как последовательно можно создать векторы разных размерностей:

float2 a = {0.1, 0.2}; // Создаем двухмерный вектор  
    float2 b = float2(0.3, 0.4); // Создаем еще один двухмерный  
    float3 c = float3(b, 1.0); // Создаем трехмерный вектор   
    float4 d = float4(0.7, c); // Создаем четырехмерный вектор   
    float4 e = float4(a, b); // Создаем еще один четырехмерный вектор
Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?

Иван Циферблат
Иван Циферблат
Россия, Таганрог, 36, 2000