Единое окно доступа к образовательным ресурсам

Введение в XNA

Голосов: 5

Книга адресована студентам и начинающим разработчикам, которые хотят использовать в своих проектах высокопроизводительную графику. Книга рассчитана на читателей, уже знакомых с основами C# и платформы .NET. Первая глава знакомит читателя с XNA Framework. Вторая глава посвящена визуализации базовых примитивов XNA Framework. В третьей главе рассматриваются более сложные вопросы визуализации: использование полноэкранного режима, плавная анимация примитивов и имитация прозрачности. В четвертой главе весь ранее изученный материал сводится воедино на примере создания полноценного хранителя экрана с дистрибутивом, при необходимости автоматически инсталлирующим на компьютер пользователя XNA Framework. В пятой, заключительной главе затрагивается тема программируемого графического конвейера, но уже на значительно более глубоком уровне; рассматриваются язык HLSL, основы ассемблеро-подобного языка Vertex Shader 1.1 и интегрированная среда разработки FX Composer 2.0, значительно облегчающая разработку и отладку шейдеров. Данная книга входит в состав <a target=_blank href="http://www.microsoft.com/Rus/Msdnaa/Curricula/">"Библиотеки учебных курсов"</a>, формирование которой ведется в рамках программы академического сотрудничества <a target=_blank href="http://www.microsoft.com/rus/msdnaa/">MSDN Academic Alliance (MSDN AA)</a>.

Приведенный ниже текст получен путем автоматического извлечения из оригинального PDF-документа и предназначен для предварительного просмотра.
Изображения (картинки, формулы, графики) отсутствуют.
             MessageBoxIcon.Error);
    closing = true;
    Application.Idle += new EventHandler(Application_Idle);
    return;
}


// Компилируем байт-код промежуточного языка и создаем объект эффекта.
effect = new Effect(device, compiledEffect.GetEffectCode(),
 CompilerOptions.NotCloneable, null);


// Если текущая техника не может выполнена на текущем графическом устройстве.
if (!effect.CurrentTechnique.Validate())
{
// Выводим сообщение об ошибке и завершаем работу приложения
    MessageBox.Show(String.Format("Ошибка        при    валидации    техники   \"{0}\"   эффекта
\"{1}\"\n\r" +
         "Скорее всего, функциональность шейдера превышает возможности GPU",
        effect.CurrentTechnique.Name,          effectFileName),       "Критическая       ошибка",
MessageBoxButtons.OK,
         MessageBoxIcon.Error);
    closing = true;
    Application.Idle += new EventHandler(Application_Idle);
    return;
}

Визуализация объекта, использующего эффект.
Визуализация примитивов, использующих эффект, начинается с вызова метода Effect.Begin:
public void Begin();
Далее приложение должно перебрать все         проходы   (коллекция   passes)   текущей     техники
(CurrentTechnique) и для каждой техники:
14. Вызвать метод Pass текущего эффекта.
15. Визуализировать примитивы с использованием метода GraphicsDevice.DrawUserPrimitives.
16. Вызывать метод End текущего эффекта.
По окончанию визуализации эффекта приложение должно вызвать метод Effect.End. В итоге код
визуализации примитива выглядит следующим образом:
Effect effect;
...
// Фрагмент типового обработчика события Paint
...
// Начинаем визуализацию примитивов с использованием эффекта effect.
effect.Begin();
// Перебираем все проходы визуализации текущей техники
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
// Начинаем визуализацию текущего прохода
    effect.Begin();
// Визуализируем примитивы
    device.DrawUserPrimitives(...);
...
    device.DrawUserPrimitives(...);
// Завершаем проход
    effect.End();
}
// Заканчиваем визуализацию эффекта


effect.End();
Ну что ж, этих знаний вполне достаточно, для того, чтобы попробовать свои силы в визуализации простых
примитивов.
      П р им еч а н ие
      Как известно, оператор foreach, используемый                нами для перебора коллекции проходов
      (effect.CurrentTechnique.Passes), обладает несколько более низкой производительностью по сравнению с
      классическим оператором for. Однако при небольшом количестве итераций эта особенность не является сколь
      либо заметным недостатком. Более подробно эта тема будет рассмотрена в разделе 3.3.4.


2.4. Точки (PrimitiveType.PointList).
Как известно, иногда лучше один раз увидеть, чем сто раз услышать. Эта простая истина как никогда
подходит к XNA Framework с весьма запутанной технологией визуализации примитивов. Поэтому мы
начнѐм изучение материала с разбора приложения, рисующего в центре экрана одну точку цвета морской
волны (листинг 2.7).

Листинг 2.7.

// Пример Examples\Ch02\Ex01

// Стандартные директивы C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

// При обработке исключений, связанных с открытием файла эффекта, нам понадобится
// пространство имен System.IO
using System.IO;

// Включаем в приложение пространства имен XNA Framework
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using XnaGraphics = Microsoft.Xna.Framework.Graphics;

namespace GSP.XNA.Book.Ch02.Ex01
{
    public partial class MainForm : Form
    {
// Устройство XNA Framework
        GraphicsDevice device = null;
// Параметры представления данных на экране
        PresentationParameters presentParams;
// Графический буфер для хранения вершин (то есть координат нашей точки)
        VertexPositionColor[] vertices = null;
// Декларация формата вершины
        VertexDeclaration decl = null;
// Эффект, используемый при визуализации точки
        Effect effect = null;

// Флаг, устанавливаемый в true при подготовке к завершении работы приложения
        bool closing = false;

        public MainForm()
        {
            InitializeComponent();


        }

        private void MainForm_Load(object sender, EventArgs e)
        {
// Стандартная процедура настройки параметров формы и создание графического устройства
            SetStyle(ControlStyles.Opaque | ControlStyles.ResizeRedraw, true);
            MinimumSize = SizeFromClientSize(new Size(1, 1));

            presentParams = new PresentationParameters();
            presentParams.IsFullScreen = false;
            presentParams.BackBufferCount = 1;
            presentParams.SwapEffect = SwapEffect.Discard;
            presentParams.BackBufferWidth = ClientSize.Width;
            presentParams.BackBufferHeight = ClientSize.Height;

           device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,
 this.Handle, CreateOptions.SoftwareVertexProcessing | CreateOptions.SingleThreaded,
 presentParams);

// Создаѐм массив, предназначенный для хранения координат одной точки
            vertices = new VertexPositionColor[1];
// Создаем декларацию формата вершины
            decl = new VertexDeclaration(device, VertexPositionColor.VertexElements);
// Задаѐм координаты точки (вершины) таким образом, чтобы она всегда была в центре экрана.
// Цвет точки устанавливаем в морской волны, но в действительности он не влияет на цвет
// точки, так как используемый эффект игнорирует информацию о цвете вершины
                    vertices[0] = new VertexPositionColor(new Vector3(0.0f, 0.0f, 0.0f),
 XnaGraphics.Color.Aqua);



// Структура для хранения кода откомпилированного эффекта
            CompiledEffect compiledEffect;
            try
            {
// Пытаемся загрузить эффект из файла и откомпилировать его в промежуточный байт-код
                compiledEffect = Effect.CompileEffectFromFile(effectFileName, null, null,
 CompilerOptions.None, TargetPlatform.Windows);
            }
// Если файл с эффектом не был найден
            catch (IOException ex)
            {
// Выводим сообщение об ошибке и завершаем работу приложения
                MessageBox.Show(ex.Message, "Критическая ошибка", MessageBoxButtons.OK,
 MessageBoxIcon.Error);
                closing = true;
                Application.Idle += new EventHandler(Application_Idle);

                return;
            }

// Если эффект не был удачно откомпилирован
            if (!compiledEffect.Success)
            {
// Выводим сообщение об ошибках и предупреждениях из свойства ErrorsAndWarnings и завершаем
// работу приложения
                MessageBox.Show(String.Format("Ошибка при компиляции эффекта: \r\n{0}",
 compiledEffect.ErrorsAndWarnings), "Критическая ошибка", MessageBoxButtons.OK,
 MessageBoxIcon.Error);
                closing = true;


                Application.Idle += new EventHandler(Application_Idle);
                return;
            }

// Создаем эффект на базе скомпилированного байт-кода. Обратите на использование флага
// CompilerOptions.NotCloneable,который позволяет ощутимо сократить объем оперативной
// памяти, используемой эффектом
            effect = new Effect(device, compiledEffect.GetEffectCode(),
 CompilerOptions.NotCloneable, null);

// Выполняем валидацию текущей техники (проверяем, может ли текущая техника выполнится на
// данном GPU)
            if (!effect.CurrentTechnique.Validate())
            {
// Если функциональность текущего GPU недостаточна, выводим сообщение об ошибке
                MessageBox.Show(String.Format("Ошибка при валидации техники \"{0}\" эффекта
 \"{1}\"\n\r" + "Скорее всего, функциональность шейдера превышает возможности GPU",
 effect.CurrentTechnique.Name, effectFileName), "Критическая ошибка", MessageBoxButtons.OK,
 MessageBoxIcon.Error);
                closing = true;
                Application.Idle += new EventHandler(Application_Idle);
                return;
            }
        }

        private void MainForm_Paint(object sender, PaintEventArgs e)
        {
// Если приложение завершает работу из-за проблем в обработчике события Load, выходим из
// обработчика события Paint (эффект effect может быть не корректно инициализирован, поэтому
// попытка визуализации сцены может спровоцировать исключение)
            if (closing)
                return;

            try
            {
// Проверяем, не потеряно ли устройство
                if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.Lost)
                    throw new DeviceLostException();

                if (device.GraphicsDeviceStatus == GraphicsDeviceStatus.NotReset)
                    device.Reset(presentParams);

// Очищаем форму
                device.Clear(XnaGraphics.Color.CornflowerBlue);

// Устанавливаем формат вершины
                    device.VertexDeclaration = decl;

// Начинаем визуализацию эффекта.
                effect.Begin();
// Перебираем все проходы эффекта
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
// Начинаем визуализацию текущего прохода
                    pass.Begin();
// Рисуем точку
                    device.DrawUserPrimitives(PrimitiveType.PointList, vertices, 0,
 vertices.Length);
// Заканчиваем визуализацию прохода


                     pass.End();
                 }

// Оканчиваем визуализацию эффекта
                effect.End();
// Завершаем визуализацию примитивов

// Выводим полученное изображение на экран
                device.Present();
            }
// Обработка потери устройства
            catch (DeviceNotResetException)
            {
                Invalidate();
            }
            catch (DeviceLostException)
            {
            }
        }

// Обработчик события Idle. Завершает работу приложения.
        void Application_Idle(object sender, EventArgs e)
        {
            Close();
        }

// Сброс устройства при изменении размеров окна
        private void MainForm_Resize(object sender, EventArgs e)
        {
            if (WindowState != FormWindowState.Minimized)
            {
                presentParams.BackBufferWidth = ClientSize.Width;
                presentParams.BackBufferHeight = ClientSize.Height;
                device.Reset(presentParams);
            }
        }

// Удаление устройства при завершении программы
        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (device != null)
            {
                device.Dispose();
                device = null;
            }
        }
    }
}


Рассмотрим наиболее интересные фрагменты программы. Вначале мы объявляем массив для хранения
вершин (то есть координат нашей точки) и декларацию вершины, для хранения описания формата
элементов массива:
VertexPositionColor[] vertices = null;
VertexDeclaration decl = null;
Ниже объявляется эффект, который будет использоваться для визуализации точки:
Effect effect = null;


Инициализация всех этих объектов выполняется в обработчике события Load формы. После создания
графического устройства, обработчик события Load создает массив с информацией о единственной вершине
сцены и декларацию формата этой вершины:
vertices = new VertexPositionColor[1];
vertices[0] = new VertexPositionColor(new Vector3(0.0f, 0.0f, 0.0f), XnaGraphics.Color.Aqua);
// Описание формата вершины берется из поля VertexPositionColor
decl = new VertexDeclaration(device, VertexPositionColor.VertexElements);


Далее обработчик события Load выполняет компиляцию fx-файла, после использует полученный байт-код
для создания объекта эффекта:
// Для сокращения объѐма кода из него исключена обработка исключительных ситуаций. В реальных
// приложениях так поступать категорически не рекомендуется, так как это значительно снизит
// “дуракоустойчивость” вашего приложения. Поэтому настоятельно рекомендую ознакомится с
// полной версией кода из листинга 2.7.
CompiledEffect compiledEffect;
// Компилируем fx-файл в байт код
compiledEffect = Effect.CompileEffectFromFile(effectFileName, null, null,
 CompilerOptions.None, TargetPlatform.Windows);
// Используем полученный байт-код для создания объекта эффекта.
effect = new Effect(device, compiledEffect.GetEffectCode(), CompilerOptions.NotCloneable,
null);
При возникновении ошибок при загрузке или компиляции эффекта обработчик не завершает работу
приложения путем вызова метода Close формы, так как, если верить MSDN, это может вызвать утечку
ресурсов. Вместо этого он регистрирует собственный обработчик события Idle, автоматически
вызывающий метод Close. Но здесь есть один подводный камень: метод Idle будет вызван по завершении
обработки всех событий, в том числе Paint. Таким образом, если не принять особых мер, не исключен
вызов метода Idle с не полностью сформированным эффектом, что с большой долей вероятности приведет
к краху приложения. Для борьбы с этим недоразумением в начале обработчика события Paint
осуществляется проверка, не готовится ли приложение к завершению работы: если это так, то обработчик
события Paint не выполняет визуализацию сцены.
Переходим к обработчику события Paint, выполняющего визуализацию изображения. Первым делом
данный обработчик выполняет стандартные проверки потери устройства, после чего очищает экран. Далее
он присваивает свойству VertexDeclaration графического устройства декларацию вершины, созданную в
обработчике события Load:
device.VertexDeclaration = decl;
На первый взгляд эту операцию было бы рациональнее вынести в обработчик события Load. Однако это не
самая лучшая идея, так как информация о параметрах графического устройства теряется при сбросе методом
Reset. Следовательно, такое приложение перестало бы нормально функционировать после первой же
потери устройства.
И, наконец, главная изюминка программы: визуализация точки на экране с использованием эффекта:
effect.Begin();


foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
    pass.Begin();
    device.DrawUserPrimitives(PrimitiveType.PointList, vertices, 0, vertices.Length);
    pass.End();
}


effect.End();
Как видно, смотря на обилие кода, приложение имеет достаточно простую структуру. Как говорится, у
страха глаза велики.
Теперь давайте попробуем создать это приложение в Visual Studio 2005. Для начала запустите Visual Studio
2005,    создайте   проект    нового    приложения    Windows      Forms     и    подключите     сборку
Microsoft.Xna.Framework.dll. В окне Solution Explorer щелкните правой кнопкой мыши на узле
проекта и выберите в контекстном меню команду Add | New Folder, и создайте папку Data, в которой мы


будем хранить различные вспомогательные эффекты (рисунок 2.6). Затем добавьте в папку Data файл
эффекта SimpleEffect.fx (рисунок 2.7), например, при помощи команды контекстного меню Add | New
Item... . Поместите в файл SimpleEffect.fx текст эффекта из листинга 2.6.




Рисунок 2.6. Создание новой папки.




Рисунок 2.7. Файл SimpleEffect.fx.

После этих действий в каталоге проекта появится каталог Data, содержащий файл эффекта
SimpleEffect.fx. Однако подобное расположение файла не совсем удобно, ведь при компиляции Debug-
версии приложения Visual Studio копирует исполняемый exe-файл в подкаталог проекта bin\Debug, а при
компиляции Release версии соответственно в каталог bin\Release. Соответственно, было бы логичным,
если бы файл эффекта размещался вместе с исполняемым файлом приложения, что облегчило бы создание
инсталлятора финальной версии приложения. К счастью, это достаточно легко организовать: просто
выделите в окне Solution Explorer файл SimpleEffect.fx и в окне Properties присвойте свойству
Copy to Output Directory значение Copy if newer (рисунок 2.8). После этого при каждой компиляции
приложения Visual Studio будет автоматически создавать в подкаталоге bin\Debug или bin\Release
подкаталог bin\Debug\Data или bin\Release\Data и копировать в него файл SimpleEffect.fx.


В заключении остаѐтся создать необходимые обработчики сообщений в соответствии с листингом 2.7.
Полную версию приложения можно найти на CD диске с книгой в каталоге Ch02\Ex01.




Рисунок 2.8. Свойства файла SimpleEffect.fx.


2.4.1. Проверка аппаратной поддержки вершинных шейдеров.
Наше приложение, визуализирующее точку в центре экрана, всегда создает графическое устройство с
использованием флага CreateOptions.SoftwareVertexProcessing, то есть вершинные шейдеры всегда
выполняются средствами центрального процессора (CPU). Учитывая, что подавляющее большинство
современных графических процессоров имеют аппаратную поддержку вершинных шейдеров, этот недочет
приводит    к     неоптимальному    использованию     ресурсов   GPU.    Использование     флага
CreateOptions.HardwareVertexProcessing тоже не является хорошей идей, так это сделает
невозможной работу приложения на видеокартах без аппаратных вершинных процессоров (например, Intel
GMA 900 и Intel GMA 950).
Так что же делать? Наиболее красивое решение проблемы – проверка возможностей текущего GPU. Если
текущий GPU имеет аппаратные вершинные процессоры, приложение должно создать устройство с
использованием флага CreateOptions.HardwareVertexProcessing, в противном случае –
CreateOptions.SoftwareVertexProcessing.
Таким образом, нам необходимо научиться анализировать возможности текущего GPU. В XNA Framework
информация обо всех возможностях графического устройства инкапсулируются в классе
GraphicsDeviceCapabilities, каждое свойство которого соответствует одной           из характеристик
графического устройства. Учитывая многообразие характеристик устройства, разработчики сгруппировали
часть свойств в логические группы (структуры), то есть некоторые свойства класса
GraphicsDeviceCapabilities в свою очередь тоже содержат набор свойств по некоторой тематике:
// Некоторые фрагменты определения класса GraphicsDeviceCapabilities
public sealed class GraphicsDeviceCapabilities : IDisposable
{
// Группа свойств, описывающих возможности графического устройства по визуализации примитивов
       public GraphicsDeviceCapabilities.PrimitiveCaps PrimitiveCapabilities { get; }
// Группа свойств с информацией о возможностях декларации вершин
       public GraphicsDeviceCapabilities.DeclarationTypeCaps DeclarationTypeCapabilities {
 get; }
// Группа свойств с информацией о вершинных шейдерах
       public GraphicsDeviceCapabilities.VertexShaderCaps VertexShaderCapabilities { get; }
// Группа свойств с информацией о пиксельных шейдерах
       public GraphicsDeviceCapabilities.PixelShaderCaps PixelShaderCapabilities { get; }
// Группа свойств с информацией о драйвере устройства
       public GraphicsDeviceCapabilities.DriverCaps DriverCapabilities { get; }
// Группа свойств с информацией об устройстве, которая может пригодится при создании
// устройства


       public GraphicsDeviceCapabilities.DeviceCaps DeviceCapabilities { get; }3
...

// Свойства без подсвойств:
// Максимальная версия языка Vertex Shader, поддерживаемая графическим устройством
       public Version VertexShaderVersion { get; }
// Максимальная версия языка Pixel Shader, поддерживаемая графическим устройством
       public Version PixelShaderVersion { get; }
// Максимальный размер точки, которую способно отображать графическое устройство
       public float MaxPointSize { get; }
// Максимальное количество примитивов, которое способно отобразить графическое устройство за
// один вызов метода DrawUserPrimitives
       public int MaxPrimitiveCount { get; }
// Остальные свойства
...
}
Информация, которая может понадобиться при создании графического устройства, сосредоточена в
свойствах свойства GraphicsDeviceCapabilities.DeviceCaps DeviceCapabilities:
// Некоторые фрагменты определения структуры DeviceCaps
public struct DeviceCaps
{
// Поддерживает ли графическое устройство метод DrawUserPrimitives на аппаратном уровне
       public bool SupportsDrawPrimitives2Ex { get; }
// Поддерживает ли графическое устройство аппаратную растеризацию примитивов (при отсутствии
// подобной поддержки визуализация будет выполняться с неприемлемо низкой
// производительностью)
       public bool SupportsHardwareRasterization { get; }
// Имеет ли графическое устройство аппаратные вершинные процессоры
       public bool SupportsHardwareTransformAndLight { get; }
...
}
Как видно, информация о наличии аппаратных вершинных процессоров содержится в свойстве
SupportsHardwareTransformAndLight. Таким образом, нашему приложению необходимо просто
проверить                               значение                                 свойства
GraphicsDeviceCapabilities.DeviceCapabilities.SupportsHardwareTransformAndLight.    Если
оно равно true, приложение может создать графическое устройство с использованием флага
CreateOptions.HardwareVertexProcessing, в противном случае должен использоваться флаг
CreateOptions.SoftwareVertexProcessing.
XNA Framework предоставляет разработчику два способа получения доступа к экземпляру объекта
GraphicsDeviceCapabilities. Наиболее простым из них является использование свойства
GraphicsDeviceCapabilities экземпляра класса графического устройства:
public GraphicsDeviceCapabilities GraphicsDeviceCapabilities { get; }
Не смотря на простоту данный способ обладает существенным недостатком: для получения доступа к
свойству GraphicsDeviceCapabilities приложение должно создать графическое устройство. Получается
замкнутый круг: чтобы получить информацию, необходимую для создания графического устройства,
приложение должно создать это устройство. В принципе, мы можем попробовать написать что-то вроде:
// Создаем графическое устройство без аппаратной поддержки вершинных шейдеров.
device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle,
    CreateOptions.SoftwareVertexProcessing | CreateOptions.SingleThreaded, presentParams);

// Если GPU имеет аппаратные вершинные процессоры
if (device.GraphicsDeviceCapabilities.DeviceCapabilities.SupportsHardwareTransformAndLight)
{
// Уничтожаем устройство
    device.Dispose();

// Снова создаем устройство, но уже с аппаратной поддержкой вершинных шейдеров
    device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,
this.Handle,


        CreateOptions.HardwareVertexProcessing | CreateOptions.SingleThreaded,
presentParams);
}
Хотя данная технология и работает, всѐ это напоминает поездку из Киева в Харьков через Жмеринку.
Поэтому разработчики XNA Framework предусмотрели альтернативный способ получения экземпляра
класса GraphicsDeviceCapabilities без создания графического устройства. Как вы знаете, конструктор
класса GraphicsDevice принимает в качестве первого параметра экземпляр класса GraphicsAdapter,
описывающий используемую видеокарту. Так вот, заботливые разработчики XNA Framework снабдили этот
класс методом GetCapabilities, возвращающем экземпляр класса GraphicsDeviceCapabilities,
соответствующий этому устройству:
public GraphicsDeviceCapabilities GetCapabilities(DeviceType deviceType);
где
     deviceType – тип устройства, задаваемый с использованием перечислимого типа DeviceType (таблица
      1.4).
Зачем нужен параметр deviceType? Дело в том, что метод GetCapabilities не может предугадать, какой
тип устройства вы собираетесь создать (DeviceType.Hardware, DeviceType.Reference или
DeviceType.NullReference), в то время как все эти типы устройств имеют совершенно разные
характеристики. Соответственно, при помощи параметра deviceType вы указываете методу
GetCapabilities, какое значение вы планируете передать параметру deviceType конструктора класса
графического устройства (GraphicsDevice).
Таким образом, проверку наличия аппаратных вершинных процессоров можно организовать с
использованием следующего фрагмента кода:
GraphicsDeviceCapabilities caps =
GraphicsAdapter.DefaultAdapter.GetCapabilities(DeviceType.Hardware);
CreateOptions options = CreateOptions.SingleThreaded;
if (caps.DeviceCapabilities.SupportsHardwareTransformAndLight)
    options |= CreateOptions.HardwareVertexProcessing;
else
    options |= CreateOptions.SoftwareVertexProcessing;

device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle,
    options, presentParams);
Полная версия приложения находится на CD с книгой в каталоге Examples\Ch02\Ex02.

2.4.2. Управление размером точек.
Точка, визуализируемая нашим приложением (Ex02), имеет достаточно небольшой размер, в результате чего
еѐ достаточно тяжело различить на поверхности формы. К счастью, этот недочет можно достаточно легко
исправить. В классе GraphivsDevice имеется свойство RenderState, позволяющее управлять различными
параметрами визуализации примитивов:
public RenderState RenderState {get; }
Это свойство возвращает экземпляр класса RenderState, содержащий множество свойств, влияющих на
процесс визуализации. В частности, свойство RenderState.PointSize отвечает за размер точек:
// По умолчанию значение этого свойства равно 1.0f
float PointSize { get; set; }
Так, присвоив свойству PointSize значение 10, мы увеличите размер визуализируемых точек до 10x10
пикселей (рисунок 2.9):
// Фрагмент обработчика события Paint формы
device.RenderState.PointSize = 10.0f;



    
Яндекс цитирования Яндекс.Метрика