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

Введение в 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-документа и предназначен для предварительного просмотра.
Изображения (картинки, формулы, графики) отсутствуют.
    Рисунок 2.9 Точка размером 10x10 пикселей.

Однако мы не можем просто так взять и присвоить свойству GraphicsDevice.RenderState.PointSize
произвольное значение. Ведь никто не может гарантировать, что ваши программы будут запускаться
исключительно на тех видеокартах, которые умеют работать с большими точками размером 10x10.
Следовательно, необходимо предусмотреть поведение приложения в ситуации, когда видеокарта не
удовлетворяет минимальным требованиям к размеру точек: наиболее логичное действие приложения в
подобной ситуации – выдача соответствующего сообщение об ошибке с последующим завершением работы.
В разделе 2.4.1 упоминалось, что в XNA Framework имеется класс GraphicsDeviceCapabilities с
информацией о возможностях графического устройства. В частности, свойство PointSize содержит
максимальный размер точки в пикселях, поддерживаемый указанным графическим устройством:
public float MaxPointSize { get; }


Дополнительная информация
Для быстрого получения информации о возможностях текущей видеокарты я обычно пользуюсь тестовым
пакетом D3D RightMark, инсталлятор которого находится на CD с книгой в каталоге RightMark D3D.
Достаточно запустить D3D RightMark, щелкнуть левой кнопкой мыши на узле D3D RightMark |
Direct3D 9.0 Information (вкладка Available Tests) и в правой части экрана появится древовидный
список возможностей видеокарты. В частности на рисунке 2.10 видно, что видеокарта ATI Radeon 9800
XT может визуализировать точки размером не более 256x256 пикселей.
К сожалению D3D RightMark имеет одну нехорошую особенность – он всегда загружает процессор на
100%. Не забывайте закрывать D3D RightMark, когда он вам больше не нужен; в противном случае вы
рискуете столкнуться с резким падением производительности других приложений.




Рисунок 2.10. Тестовый пакет D3D RightMark

Думаю, вам не составит труда написать код, проверяющий аппаратную поддержку видеокартой точек
размером 10x10 пикселей (Ex02). Для этого достаточно вставить в обработчик события Load после создания
графического устройства командой new GraphicsDevice следующий код:
// Если устройство не поддерживает точки размером 10x10 пикселей
if (device.GraphicsDeviceCapabilities.MaxPointSize < 10)
{
// Выводим сообщение об ошибке
    MessageBox.Show("Устройство не поддерживает точки размером 10 пикселей", 
        "Критическая ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
// Устанавливаем флаг завершения работы приложения
    closing = true;
// Задаем обработчик события Idle, выполняющий закрытие формы (вызов метода Close внутри
// обработчика Load может привести к утечке ресурсов)
    Application.Idle += new EventHandler(Application_Idle);
// Выходим из обработчика события Load


      return;
}
В таблице 2.6 приведены значения свойства MaxPointSize для некоторых графических процессоров с
аппаратной поддержкой пиксельных шейдеров. Обратите внимание, что все они поддерживают точки
размером не менее 64-х пикселей. Следовательно, так как XNA Framework требует от видеокарты
обязательной поддержки пиксельных шейдеров, приложению, использующему XNA Framework вовсе не
обязательно проверять поддержку пикселей размером менее 64-х пикселей. Это обстоятельство позволит
нам несколько сократить код некоторых примеров без ущерба надежности.
                                          Таблица 2.6. Максимальные размеры точек для некоторых GPU.

GPU                                               Максимальный размер точки (в пикселях)
NV20 (NVIDIA GeForce3)                            64
NV25 (NVIDIA GeForce4)                            8192
NV3x (NVIDIA GeForce FX)                          8192
R2xx – R5xx (ATI Radeon)                          256
GMA 900 (Intel 915G)                              256
GMA 950 (Intel 945G)                              256



2.4.3. Визуализация набора точек.
В этом разделе мы доработаем нашу программу, включив в неѐ возможность добавления новых точек путем
простых щелчков левой кнопкой мыши на поверхности формы. Для этого мы добавим в программу
обработчик события MouseDown, который при нажатии левой кнопки мыши будет добавлять в массив
вершин новые точки с координатами курсора мыши. Ну и, разумеется, немного подправим обработчик
события Paint. Основные фрагменты кода полученного приложения приведены в листинге 2.8 (Ex04).

 Листинг 2.8.

public partial class MainForm : Form
{
...
// Массив вершин
    VertexPositionColor[] vertices = null;
// Количество вершин
    int pointCount = 0;
...

      private void MainForm_Load(object sender, EventArgs e)
      {
...
// Вычисляем максимальное количество вершин, которые видеокарта может визуализировать за один
// вызов метода DrawUserPrimitives
        maxVertexCount = Math.Min(device.GraphicsDeviceCapabilities.MaxPrimitiveCount,
 device.GraphicsDeviceCapabilities.MaxVertexIndex);
// Создаем массив вершин, рассчитанный на хранение 16-ти вершин
        vertices = new VertexTransformedPositionColor[16];
}

      private void MainForm_Paint(object sender, PaintEventArgs e)
      {
...
// Очищаем экран
            device.Clear(XnaGraphics.Color.CornflowerBlue);

// Если количество точек больше нуля (метод DrawUserPrimitives некорректно работает с
// массивами нулевого размера)
            if (pointCount > 0)


            {
                device.VertexDeclaration = decl;
                device.RenderState.PointSize = 10.0f;

// Рисуем набор точек
                effect.Begin();
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Begin();
                    device.DrawUserPrimitives(PrimitiveType.PointList, vertices, 0,
pointCount);
                    pass.End();
                }
                effect.End();
            }

            device.Present();
...
      }

    private void MainForm_MouseDown(object sender, MouseEventArgs e)
    {
// Если нажата левая кнопка мыши
        if (e.Button == MouseButtons.Left)
        {
// Если количество вершин достигло предельной величины
            if (pointCount == maxVertexCount)
            {
// Выводим предупреждение и выходим из обработчика события
                MessageBox.Show(String.Format("Количество точек достигло максимального”+
 “значения для данного GPU: {0}.", maxVertexCount), "Внимание", MessageBoxButtons.OK,
 MessageBoxIcon.Warning);
                return;
            }

// Если массив вершин полностью заполнен
            if (pointCount == vertices.Length)
            {
// Вычисляем новый размер массива (удваиваем размер массива)
                int newSize = vertices.Length * 2;
// Размер массива не может превышать предельного значения
                if (newSize > maxVertexCount)
                    newSize = maxVertexCount;

// Создаем новый массив увеличенного размера
                VertexPositionColor[] newVertices = new VertexPositionColor[newSize];

// Копируем в него первоначальный массив
                vertices.CopyTo(newVertices, 0);
// Присваиваем полю vertices ссылку на новый массив
                vertices = newVertices;
            }
// Заносим в массив информацию о новой точки, формируемой на основе текущих координат
// указателя мыши. Для перевода координат указателя мыши в логическую систему координат XNA
// Framework используется “самодельный” метод MouseToLogicalCoords.
            vertices[pointCount] = new VertexPositionColor(
 Helper.MouseToLogicalCoords(e.Location, ClientSize), XnaGraphics.Color.Aqua);

// Увеличиваем счетчик количества точек


                pointCount++;

// Перерисовываем экран
            Invalidate();
        }
    }
}


Пройдемся по наиболее интересным особенностям приложения. Как известно, массивы .NET Framework не
умеют изменять свой размер, поэтому при добавлении элемента в массив необходимо создать массив
увеличенного размера, скопировать в него первоначальный массив и занести в последний элемент массива
информацию о новой точке. Однако это не совсем оптимальный вариант, так как каждое добавление новой
вершины сопряжено с довольно затратными операциями копирования массива, включая, возможную сборку
мусора. В нашей программе используется более агрессивный подход: при каждом увеличении размера
массива его размер увеличивается с неким запасом (размер массива увеличивается не на один элемент, а
сразу удваивается), чтобы уменьшить вероятность повторного выделения памяти при добавлении
следующих точек и уменьшить частоту запуска сборщика мусора.
      П р им еч а н ие
      На первый взгляд, может показаться, что информацию о вершинах было бы рациональнее хранить в Genetic-
      коллекции list<>. Однако у подобного подхода есть один неочевидный недостаток. Дело в том, что метод
      DrawUserPrimitives умеет работать исключительно с классическими массивами System.Array, в результате чего
      нам придется постоянно преобразовывать список в массив посредством метода ToArray(), неявно создающим
      новый массив и копирующим в него содержимое списка. Таким образом, использование класса list<> снизит
      производительность приложения за счет неявного копирования информации из списка в массив, и, что ещѐ хуже,
      повысит интенсивность вызовов сборщика мусора для удаления предыдущих массивов.

Другой очень полезный приѐм, используемый в программе – вывод всех точек одним вызовом метода
GraphicsDevice.DrawUserPrimitives. Дело в том, что метод GraphicsDevice.DrawUserPrimitives
тратит относительно много времени центрального процессора на подготовку графического ускорителя к
визуализации примитивов, при этом собственно процесс визуализации выполняется графическим
ускорителем и практически не требует вмешательства со стороны центрального процессора. Таким образом,
рисуя все точки за один присест, мы значительно снижаем нагрузку на центральный процессор,
распараллеливая работу между CPU и GPU.
Однако метод DrawUserPrimitives имеет ограничения на максимальное количество примитивов, которые
можно визуализировать за один вызов этого метода. Количество вершин, которые можно вывести за один
присест, тоже далеко не бесконечно. Информация о возможностях текущей видеокарты по визуализации
примитивов хранится в двух свойствах класса GraphicsDeviceCapabilities:
// Максимальное количество примитивов, которые можно визуализировать за один присест
public int MaxPrimitiveCount { get; }
// Максимальное количество вершин, которые можно визуализировать за один присест.
public int MaxVertexIndex { get; }
В таблицах 2.7 и 2.8 приведены значения этого свойства для наиболее распространенных моделей
видеокарт. Например, интегрированная видеокарта Intel GMA 900 могут визуализировать не более 65535
примитивов и не более 65534 вершины. При запуске приложения на данной видеокарте оно будет
упираться в максимальное количество вершин (65534). А вот на видеокартах корпорации ATI наше
приложение будет упираться в максимальное количество визуализируемых примитивов. Таким образом, при
оценке максимального количества точек, которые приложение может вывести на экран, необходимо
учитывать как значение свойства MaxPrimitiveCount, так и MaxVertexIndex:
maxVertexCount = Math.Min(device.GraphicsDeviceCapabilities.MaxVertexIndex,
 device.GraphicsDeviceCapabilities.MaxPrimitiveCount)


                                         Таблица 2.7. Значение свойства MaxPrimitiveCount для некоторых GPU

GPU                                                     Значение
NVIDIA NV2x - NV3x                                      1.048.575
ATI R2xx - R5xx                                         1.048.575
Intel GMA 9xx                                           65.535


Intel GMA 3000                                          65.535 – 1.048.57535




                                            Таблица 2.8. Значение свойства MaxVertexIndex для некоторых GPU

GPU                                                     Значение
ATI R2xx - R5xx                                         16.777.215
NVIDIA NV2x - NV3x                                      1.048.575
Intel GMA 9xx                                           65.534
Intel GMA 3000                                          65.534 – 16.777.21536


      В ни ма н ие !
      Если количество визуализируемых примитивов превысит допустимый лимит, на некоторых компьютерах могут
      начать происходить странные вещи вплоть до полного краха системы и “синего экрана смерти” (blue screen of
      death). Эта особенность является обратной стороной медали высокой производительности XNA Framework –
      любое некорректно написанной XNA-приложение теоретически может нарушить работу всей системы.

И так, теоретически приложение вполне может столкнуться с видеокартой, способной выводить не более
65534 примитивов за один присест. Много это и ли мало? Например, если пользователь будет каждую
секунду добавлять на экран по точке, то через 18 часов он достигнет лимита для Intel GMA 900. Иными
словами, это довольно внушительное значение для нашего приложения, но вполне достижимое. Поэтому в
приложение на всякий случай встроена проверка: при достижении предала на количество визуализируемых
примитивов, точки просто перестают добавляться в массив. Как говорится, дешево и сердито 37.
Так же стоит обратить внимание на проверку размера массива на неравенство нулю перед тем, как вывести
его на экран. Дело в том, что метод DrawUserPrimitives при попытке визуализации массива генерирует
исключение System.IndexOutOfRangeException. Хотя подобное поведение метода нельзя назвать
безупречным, эту особенность приходится учитывать.
В заключении следует обратить внимание на преобразование координат указателя мыши из системы
координат клиентской области окна в логическую систему XNA Framework, в которой координаты
компонентов вершин лежат в диапазоне от -1 .. +1. Кроме того, следует учитывать, что в Windows
положительное направление оси Y направленно вниз, а в XNA Framework – вверх. Так подобные
преобразования будут довольно часто применяться в наших приложениях, они были вынесены в отдельный
класс Helper, расположенный в файле Helper.cs (листинг 2.9). В дальнейшем мы продолжим размещать в
этом классе различные вспомогательные методы, облегчающие работу с XNA Framework.

 Листинг 2.9.

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace GSP.XNA
{
    class Helper
    {
// Принимает в качестве параметров координаты указателя мыши и размер клиентской области
окна. Возвращает координаты указателя мыши в оконной системе координат.
        public static Vector3 MouseToLogicalCoords(System.Drawing.Point location,
 System.Drawing.Size clientSize)

35
   Зависит от версии драйвера.
36
   Зависит от версии драйвера.
37
   Более практичным подходом является автоматическая генерация дополнительных массивов по мере
достижения размера предыдущего массива значения MaxPrimitiveCount. Однако эта функциональность
заметно усложнит приложение, а еѐ полезность в данном случае весьма сомнительна.


         {
            Vector3 v;
// Приводим координаты указателя мыши к диапазону [-1, +1]. Для предотвращения деления на 0
// используется метод Math.Max, не дающий знаменателю дроби стать меньше 1.
            v.X = (float)location.X / (float)Math.Max(clientSize.Width - 1, 1) * 2.0f - 1.0f;
                v.Y = 1.0f - (float)location.Y / (float)Math.Max(clientSize.Height - 1, 1)*2.0f;
                v.Z = 0.0f;


                return v;
         }
     }
}

2.4.4. Управление цветом точек средствами HLSL.
В этом, заключительно разделе, посвященном точкам, мы добавим в наше приложение возможность
визуализации разноцветных пикселей. В принципе, это довольно тривиальная операция, если бы не одно но:
в настоящее время наше приложение визуализирует точки исключительно фиксированного цвета морской
волны (aqua), который жестко задан в файле эффекта (в нашем случае это SimpleEffect.fx) и не может
быть изменен C#-приложением. К примеру, если вы исправите код
vertices[pointCount] = new VertexPositionColor(
 Helper.MouseToLogicalCoords(e.Location, ClientSize), XnaGraphics.Color.Aqua);
на
vertices[pointCount] = new VertexPositionColor(
 Helper.MouseToLogicalCoords(e.Location, ClientSize), XnaGraphics.Color.Red);
цвет точек не изменится, так как вершинные и пиксельные шейдеры игнорируют данное значение.
Следовательно в первую очередь нам необходимо модифицировать fx-файл приложения, научив эффект
адекватно реагировать на информацию о цвете вершины.

Входные и выходные параметры функций языка HLSL
Начнем модификацию эффекта с вершинного шейдера. Теперь на вход шейдера будут подаются два
параметра: координаты вершины (iPos) и цвет вершины (iColor). Результаты выполнения шейдера –
однородные координаты вершины (oPos) и цвет вершины (oColor). Для указания компилятору связи
входного параметра iColor с цветом вершины используется семантика COLOR (листинг 2.10). Семантика
COLOR выходного параметра oColor указывает компилятору на то, что в этом параметре хранится
результирующий цвет вершины.

Листинг 2.10.

void MainVS(in float3 iPos:POSITION, in float4 iColor:COLOR,        out float4 oPos:POSITION, 
out float4 oColor:COLOR)
{
       oPos = float4(iPos, 1);

// Просто копируем параметр Color без изменения.
       oColor = iColor;
}

Обратите внимание на использование новых ключевых слов: in и out. Ключевое слово in используется для
указания входных параметров, передающихся по значению. Ключевое слово out указывает на то, что
параметр является возвращаемым: по завершению работы функции значение out-параметра копируется в
вызывающий метод. Если параметр является одновременно и входным и выходным, то для указания этого
факта используется ключевое слово inout. Например, мы можем объединить параметры iColor и oColor в
один параметр Color, что позволит немного упростить код шейдера (листинг 2.11).

Листинг 2.11.

// Цвет вершины (параметр color) проходит через вершинный шейдер без изменений
void MainVS(in float3 iPos:POSITION, inout float4 color:COLOR, out float4 oPos:POSITION)


{
          oPos = float4(iPos, 1);
}
Если не указан тип параметра функции (in, out или inout), HLSL делает этот параметр входящим (in).
Соответственно, ключевое слово in указывать не обязательно. Кстати, мы активно использовали эту
возможность в прошлом разделе.

Структуры
Как известно, передача в функцию большого количества параметров делает код трудночитаемым 38, поэтому
параметры шейдера обычно группируют в структуры входных и выходных параметров. Объявление
структуры в HLSL аналогично языку C (листинг 2.12).

 Листинг 2.12.

// Объявляем структуру входных данных шейдера. Обратите внимание на возможность назначения
// каждому полю структуры семантики
struct VertexInput
{
       float3 pos : POSITION;
       float4 color : COLOR;
};

// Объявляем структуру выходных данных шейдера
struct VertexOutput
{
       float4 pos : POSITION;
       float4 color : COLOR;
};

void MainVS(in VertexInput input, out VertexOutput output)
{
       output.pos = float4(input.pos, 1.0f);
       output.color = input.color;
}


Как видно, использование структур делает код значительно более понятным: для определения формата
входных данных вершинного шейдера, достаточно лишь беглого взгляда на определение структуры
VertexInput. После этой модификации наш шейдер MainVS возвращает в качестве результата лишь один
параметр (output). Следовательно, процедуру MainVS можно заменить функцией, что сделает код
программы ещѐ более интуитивно понятным (листинг 2.13).

 Листинг 2.13.

VertexOutput MainVS(VertexInput input)
{
// Создаѐм структуру output
       VertexOutput output;
       output.pos = float4(input.pos, 1.0f);
       output.color = input.color;
// Возвращаем результаты работы шейдера
       return output;
}

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

38
     Сложный шейдер может принимать до нескольких десятков различных параметров.


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

 Листинг 2.14.

float4 MainPS(float4 color:COLOR):COLOR
{
       return color;
}


Для привязки входных данных пиксельного шейдера к интерполированным выходным данным из
вершинного шейдера используется семантика COLOR. Хочу обратить ваше внимание на то, что семантики
выходных данных вершинного шейдера и входных данных пиксельного шейдера нечего не говорят о
смысле этих данных39. Главное предназначение этих семантик – связь между выходными параметрами
вершинного шейдера и входными параметрами пиксельным шейдера. Например, замена семантики COLOR
на TEXCOORD некоим образом не повлияет на работу приложения (листинг 2.14). Главное, чтобы выходные
параметры вершинного шейдера и входные параметры пиксельного шейдера использовали одинаковые
семантики.
      П р им еч а н ие
      Так как профили семейства ps_1_x не позволяют использовать четырех компонентные текстурные координаты,
      нам пришлось применить профиль ps_2_0. Использование текстурных координат будет рассмотрено в разделе
      2.6.

 Листинг 2.14.

struct VertexInput
{
       float3 pos : POSITION;
       float4 color : COLOR;
};

struct VertexOutput
{
       float4 pos : POSITION;
// Рассчитанный цвет вершины, передаѐтся как текстурные координаты
       float4 color : TEXCOORD;
};



VertexOutput MainVS(VertexInput input)
{
       VertexOutput output;
       output.pos = float4(input.pos, 1.0f);
       output.color = input.color;
       return output;
}

// Пиксельный шейдер получает входные параметры из интерполированных текстурных координат
float4 MainPS(float4 color:TEXCOORD):COLOR
{
       return color;
}

technique Fill
{
       pass p0

39
 В профилях до ps_3_0 семантики иногда всѐ же могут оказывать незначительное влияние на работу
шейдера. Эта тема подробно будет рассмотрена в разделе 4.x.


       {
                  VertexShader = compile vs_2_0 MainVS();
                  PixelShader = compile ps_2_0 MainPS();
       }
}


Доработка C#-приложения
С кодом эффекта мы вполне разобрались и, следовательно, можем приступать к модификации C#-кода
нашего приложения: теперь при каждом щелчке левой кнопкой мыши на форму будут добавляться
разноцветные точки случайного цвета. Для этого достаточно лишь немного подправить обработчик события
MouseDown (листинг 2.15).

Листинг 2.15.

private void MainForm_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
// Если достигли предельного количества точек, выходим
        if (pointCount == maxVertexCount)
        {
            MessageBox.Show(String.Format("Количество точек достигло максимального значения”+
 “ для данного GPU: {0}.", maxVertexCount), "Внимание", MessageBoxButtons.OK,
 MessageBoxIcon.Warning);
            return;
        }

// При необходимости удваиваем размер массива.
        if (pointCount == vertices.Length)
        {
            int newSize = vertices.Length * 2;
            if (newSize > maxVertexCount)
                newSize = maxVertexCount;

                VertexPositionColor[] newVertices = new VertexPositionColor[newSize];
                vertices.CopyTo(newVertices, 0);
                vertices = newVertices;
        }

        XnaGraphics.Color color;
        double delta;
        do
        {
// Вычисляем случайные значение компонентов R, G, B цвета точки
            byte[] bytes = new byte[3];
            rnd.NextBytes(bytes);
// Формируем цвет
            color = new XnaGraphics.Color(bytes[0], bytes[1], bytes[2]);
// Вычисляем квадрат “расстояния” между рассчитанным случайным цветом и цветом фона формы
            delta = Math.Pow((color.R - XnaGraphics.Color.CornflowerBlue.R), 2) +
                Math.Pow((color.G - XnaGraphics.Color.CornflowerBlue.G), 2) +
                Math.Pow((color.B - XnaGraphics.Color.CornflowerBlue.B), 2);

        }
// Если цвет точки слабо отличается от цвета фона, повторяем вычисления.
        while(delta < 1000);

// Заносим информацию о точке в массив вершин
        vertices[pointCount] = new VertexPositionColor(Helper.MouseToLogicalCoords(


 e.Location, ClientSize), color);

           pointCount++;
       }

       Invalidate();
}


При генерации случайного цвета точки приложение проверяет, не сольѐтся ли полученный цвет с цветом
фона. Так как в компьютерной графике цвет задаѐтся яркостью трех компонентов, мы можем трактовать
значения этих трех компонентов как координаты цвета в некотором цветовом пространстве (рисунок 2.11).
Соответственно, в качестве критерия похожести двух цветов можно использовать расстояние между этими
цветами:

r  (c1r  c2r ) 2  (c1g  c2 g ) 2  (c1b  c2b ) 2                                       (2.1)

где
     r – расстояние между цветами в цветовом пространстве.
     c1r , c1g , c1b – яркости красного, зеленого и синего компонента первого цвета;
     c2 r , c2 g , c2b – яркости красного, зеленого и синего компонента второго цвета.
Однако учитывая высокую ресурсоѐмкость операции вычисления квадратного корня, в качестве критерия
похожести цветов рациональнее использовать не само расстояние, а его квадрат. Полная версия приложения
находится на CD с книгой в каталоге Ch02\Ex05.



                                       R


                                                        Color1




                                           Color2



                                                                 G




             B


Рисунок 2.11. Цветовое пространство.


Практическое упражнение №2.1
Создайте приложение, рисующее поточечный график функции y=cos(x), где x находится в диапазоне
0°…720° (рисунок 2.12). Если у вас возникнут трудности при выполнении упражнения, посмотрите готовое
приложение на CD с книгой (Ch02\Ex06).



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