Опубликован: 28.04.2009 | Доступ: свободный | Студентов: 1745 / 80 | Оценка: 4.36 / 4.40 | Длительность: 16:40:00
Специальности: Программист
Лекция 3:

Усложненные технологии визуализации

3.4.1. Смешивание цветов

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

 Принцип работы блоков ROP

Рис. 3.13. Принцип работы блоков ROP

Примечание

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

Блоки ROP имеют очень ограниченную функциональность и программируются с использованием весьма запутанного синтаксиса. Блок ROP может выполнить над двумя аргументами одну из пяти векторных операций вида:

\qquard d’=op(b-c,c-d)
( 3.2)

где

\qquard d' - новый цвет пикселя кадрового буфера [\qquard d'_r,\qquard d'_g,\qquard d'_b,\qquard d'_a]

op - операция (см. таблица 3.5)

s - цвет пикселя [s_r, s_g, s_b, s_a ]

d - текущий цвет пикселя кадрового буфера [d_r,d_g,d_b,d_a]

b, с - векторные коэффициенты [b_r,b_g,b_b,b_a] и [c_r,c_g,c_b,c_a]

Примечание

Наряду с операциями смешения блоки ROP могут выполнять множество других полезных простых операций над пикселями вроде отсечения невидимых областей объектов при визуализации трехмерных цен, реализовывать полноэкранное сглаживание ( FSAA - Full Scene Anti Aliasing ) и т.д.

Управление смешением пикселей осуществляется посредством свойств свойства RenderState экземпляра класса GraphicsDevice. Активация режима смешения осуществляется путем присвоения значения true свойству AlphaBlendEnable.

public bool AlphaBlendEnable { get; set; }

Операция, выполняемая над цветами графического примитива и кадрового буфера задается путем присвоения соответствующего значения перечислимого типа BlendFunction (таблица 3.5) свойству RenderState.BlendFunction.

public BlendFunction BlendFunction { get; set; }
Таблица 3.5. Значения перечислимого типа BlendFunction
Значение Описание
Add (значение по умолчанию) Покомпонентно складывает два аргумента
Subtract Покомпонентно вычитает из первого аргумента (цвет пикселя) второй (цвет кадрового буфера).
ReverseSubtract Покомпонентно вычитает из второго аргумента (цвет кадрового буфера) первый (цвет пикселя).
Max Покомпонентно сравнивает оба аргумента и возвращает компоненты с наибольшим значением
Min Покомпонентно сравнивает оба аргумента и возвращает компоненты с наименьшим значением

Каждый из аргументов операции предварительно умножается на коэффициент, определяемый посредством перечислимого типа Blen (таблица 3.6). При этом коэффициент b задается свойством RenderState.SourceBlend, а коэффициент c – свойством RenderState.DestinationBlend:

public Blend SourceBlend { get; set; } public 
Blend DestinationBlend { get; set; }
Таблица 3.6. Некоторые значения перечислимого типа Blend
Значение Описание
BlendFactor В качестве коэффициента используется вектор, присвоенный свойству RenderState.BlendFactor
InverseBlendFactor Коэффициент получается путем вычитания из единичного вектора (1, 1, 1, 1) значения свойства RenderState.BlendFactor.
Zero Коэффициент равен вектору (0, 0, 0, 0).
One Коэффициент равен вектору (1, 1, 1, 1).
SourceColor В качестве коэффициента используются цвета примитива [s_r , s_g ,s_b, s_a ]
InverseSourceColor В качестве коэффициента используется вектор [1-s_r,1-s_g,1-s_b,1-s_a]
SourceAlpha В качестве коэффициента используется вектор альфа каналов примитива [s_a ,s_a ,s_a ,s_a ] .
InverseSourceAlpha В качестве коэффициента используется вектор [1-s_a,1-s_a,1-s_a,1-s_a] .
DestinationColor В качестве коэффициента используется цвет пикселя кадрового буфера [d_r ,d_g ,d_b,d_a ] /.
InverseDestinationColor В качестве коэффициента используется вектор [1-d_r,1-d_g,1-d_b,1-d_a].
DestinationAlpha В качестве коэффициента используется вектор альфа-каналов пикселя кадрового буфера [d_a,d_a,d_a,d_a ] .
InverseDestinationAlpha В качестве коэффициента используется вектор [1-d_a,1-d_a ,1-d_a,1-d_a] .
SourceAlphaSaturation Вектор коэффициентов равен [f,f,f,1], где f = min(s_a,d_a) .

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

\qquard d’_r=(1-s_r)*s_r-s_a*d_r\\
\qquard d’_g=(1-s_g)*s_g-s_a*d_g\\
\qquard d’_b=(1-s_b)*s_b-s_a*d_b
( 3.3)

Значение альфа канала нас не интересует, так как он все равно не учитывается при выводе изображения на экран. Вдобавок, часто используемые форматы пикселей SurfaceFormat.Bgr565 и SurfaceFormat.Bgr32 не содержат альфа канала.

Для начала давайте определим, какой операцией связаны между собой цвет примитива и цвет кадрового буфера. Во всех трех выражениях из произведения с множителями s_r ,s_g ,s_b вычитается произведение, содержащее множители d_r ,d_g ,d_b, следовательно для реализации этой формулы необходимо использовать операцию вычитания BlendFunction.Subtract.

Перейдем к коэффициентам. Сопоставив коэффициенты перед s_r ,s_g ,s_b с таблицей 3.6 мы придем к выводу, что они соответствуют значению Blend.InverseSourceColor. Все коэффициенты перед d_r ,d_g ,d_b равны s_a, следовательно они могут быть заданы с использованием константы Blend.SourceAlpha.

Таким образом, программирование блоков ROP для смешения цветов по формуле 3.3 реализуется посредством следующих четырех строчек кода:

device.RenderState.AlphaBlendEnable = true;
device.RenderState.BlendFunction = BlendFunction.Subtract; 
device.RenderState.SourceBlend = Blend.InverseSourceColor
 device.RenderState.DestinationBlend = Blend.SourceAlpha;

Дополнительная информация

Хотя по умолчанию XNA Framework применяет для смешения всех цветовых компонентов общие выражения, он так же позволяет использовать раздельные выражения для смешивания RGB и Alpha компонентов цвета. Эта функциональность активируется посредством присвоения свойству RenderState.SeparateAlphaBlendEnabled значения true, после чего вышеописанные свойства RenderState.BlendFunction, RenderState.SourceBlend и RenderState.DestinationBlend будут влиять исключительно на R, G и B составляющие цвета. Режим смешения альфа-компоненты цвета управляется аналогичной тройкой свойств: RenderState.AlphaBlendOperation, RenderState.AlphaSourceBlend, RenderState.AlphaDestinationBlend. Так как раздельное смешения каналов применяется достаточно редко, мы пока не будет акцентировать на нем внимание.

3.4.2. Использование смешивания цветов для реализации эффекта полупрозрачности

Для реализации визуализации полупрозрачных примитивов, мы должны переложить выражение 3.1 на причудливый язык программирования блоков ROP из XNA Framework. Для простоты мы положим, что весь примитив имеет одну и ту же прозрачность, что позволит нам использовать для задания коэффициента непрозрачности параметр RenderState.BlendFactor. Преимуществом такого подхода является возможность изменения прозрачности всех вершин примитива посредством коррекции одного единственного параметра. Само выражение 3.1 содержит операцию сложения и два коэффициента непрозрачности, так что оно легко реализуется средствами XNA Framework:

// Задаем коэффициент непрозрачности (0 – 
абсолютно прозрачен, 255 – полностью непрозрачен).
// При присвоении свойству RenderState.BlendFactor 
он автоматически приводится к диапазону
// 0..1 путем деления на 255.
const byte opacity = 50;
// Включаем режим смешивания цветов
device.RenderState.AlphaBlendEnable = true;
// Используем операцию сложения
device.RenderState.BlendFunction = BlendFunction.Add;
// Коэффициент смешения всех трех компонентов цвета 
равен opacity (точнее opacity / 255)
device.RenderState.BlendFactor = new XnaGraphics.Color
(opacity, opacity, opacity, 0);
// Коэффициент, на который умножается цвет примитива,
 равен opacity / 255
device.RenderState.SourceBlend = Blend.BlendFactor;
// Коэффициент, на который умножается цвет кадрового 
буфера, равен 1 - opacity / 255
device.RenderState.DestinationBlend = Blend.InverseBlendFactor;

Для демонстрации использования эффекта полупрозрачности мы строим в решение практического упражнения 3.1 ( Examples\Ch03\Ex02 ) возможность управления прозрачностью круга (рисунок 3.14). Для этого необходимо добавить в группу Параметры ползунок ( TrackBar ) и две метки, свойства которых перечислены в таблице 3.7.

 Приложение, визуализирующее полупрозрачный круг

Рис. 3.14. Приложение, визуализирующее полупрозрачный круг
Таблица 3.7. Свойства новых элементов управления группы Параметры
Класс Свойство Значение
TrackBar Name opacityTrackBar
Minimum 0
Maximum 255
TickFrequency 0
Label Name opacityLabel
Label Text Непрозрачность

Так же мне необходимо реализовать обработчик события Scrol ползунка и немного подправить обработчик события Paint (листинг 3.24). Готовый проект приложения находится в example.zip в каталоге Examples\Ch03\Ex13.

private void opacityTrackBarScroll(object sender, EventArgs e)
{
// Отображаем текущий коэффициент непрозрачности
opacityLabel.Text = ((float)opacityTrackBar.Value / 255.0f).
ToString("0.00");
// Перерисовывает компонент xnaPanel
xnaPanel.Invalidate();
 }
private void xnaPanel_Paint(object sender, PaintEventArgs e)
{
...
// Рисуем шахматную доску (код визуализации шахматной доски 
взят из пример Ch01\Ex03
// (см. раздел 1.2).
...
device.RenderState.CullMode = CullMode.None;
 device.RenderState.PointSize = 3;
device.RenderState.AlphaBlendEnable = true;
device.RenderState.BlendFunction = BlendFunction.Add; 
// Значение параметра BlendFactor вычисляется на основе
 текущего положения ползунка прокрутки
 device.RenderState.BlendFactor = new XnaGraphics.
 Color((byte)opacityTrackBar.Value,
(byte)opacityTrackBar.Value, (byte)opacityTrackBar.Value, 0);
 device.RenderState.SourceBlend = Blend.BlendFactor; 
device.RenderState.DestinationBlend = Blend.InverseBlendFactor;
// Визуализируем круг
...
}
Листинг 3.24.
3.4.3. Анимация построения фигуры Лиссажу

В следующем примере мы создадим приложение, анимирующее построение фигуры Лиссажу, заданной выражением

x = \sin(2-a) 
y =\cos(3-a)

где

  • x, y - координаты текущей точки
  • а - угол, пробегающий с определенным шагом значения от 0 до 360 градусов (0…2·?)

Вначале все пиксели фигуры будут прозрачными. Затем мы начнем постепенно увеличивать непрозрачность вершин фигуры, при этом непрозрачность всех вершин будет расти неравномерно: первыми непрозрачными станут вершины, соответствующие углу а равному 0, а последними - а равному 360 градусов. Соответственно, фигура Лиссажу будет как бы рисоваться как бы невидимым пером (рисунок 3.15).

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

device. RenderState.AlphaBlendEnable = true;
device.RenderState.BlendFunction = BlendFunction.Add;
device.RenderState.SourceBlend = Blend.SourceAlpha;
device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

Чтобы облегчить задачу мы воспользуемся в качестве отправной точки решением практического упражнения 2.5. Все что от нас требуется - переписать обработчик события Idle и немного подправить обработчики событий Load и Paint формы (листинг 3.25).

 Построение фигуры Лиссажу

Рис. 3.15. Построение фигуры Лиссажу
// Пример Examples\Ch03\Ex14
public partial class MainForm : Form
{
// Количество сегментов в кривой
const int QuadStrips = 200; 
// Количество треугольников в кривой
* 2;
const int TriStrips = QuadStrips 
... 
// Массив вершин фигуры Лиссажу
VertexPositionColor[] vertices = null; 
// Таймер
Stopwatch stopwatch = null; 
...
private void MainForm_Load(object sender, EventArgse)
{
//
Создаем массив вершин объекта
vertices = new VertexPositionColor[TriStrips + 2];
// Перебираем все вершины фигуры Лиссажу
for (int i = 0; i <= QuadStrips; i++) 
{ 
// Вычисляем текущий угол
float angle = 2.0f * (float)Math.PI * (float)i 
// Рассчитываем координаты текущей точки фигуры 
Лиссажу float cos = (float)Math.Cos(angle);
 float x = 0.85f * (float)Math.Sin(2 * angle);
/ (float)QuadStrips;
float y = 0.85f * (float)Math.Cos(3 * angle); 
// Вычисляем цвет точки
byte green = (byte)(Math.Pow(0.5f + cos * 0.5f, 0.3f) * 255.0f); byte
 red = (byte)(Math.Pow(0.5f - cos * 0.5f, 0.3f) * 255.0f);
/// Рассчитываем вектор, перпендикулярный графику фигуры Лиссажу длиной 
0.015 float sx = -2.55f * (float)Math.Sin(3 * angle); float sy =-
1.7f *(float)Math.Cos(2 * angle); float length =
 (float)Math.Sqrt(sx * sx + sy * sy); float nx = sx / length *
 0.015f; float ny = sy / length * 0.015f;
// Заносим в массив информацию о двух вершинах 
фигуры Лиссажу, смещенных на 0.015 в
// направление, перпендикулярном фигуре. Таким 
образом, кривая фигуры Лиссажу будет иметь
// ширину 0.03 единицы.
vertices[i * 2] = new VertexPositionColor(new 
Vector3(x - nx, y - ny, 0.0f),
new XnaGraphics.Color(red, green, 0)); vertices
[i * 2 + 1] = new VertexPositionColor(new
 Vector3(x + nx, y + ny, 0.0f), new XnaGraphics.
 Color(red, green, 0)); 
}; 
... 
// Запускаем таймер
stopwatch = new Stopwatch();
 stopwatch.Start();
// Задаем обработчик события Idle, выполняющий 
коррекцию прозрачности вершин
Application.Idle+=new EventHandler(Application_Idle); 
// Рассчитываем текущие коэффициенты непрозрачности вершин
Application_Idle(this, null); 
}
// Рассчитывает текущие коэффициенты непрозрачности 
вершин и перерисовывает изображение void
 Application_Idle(object sender, EventArgs e) 
{
if (closing) 
Close();
// Получаем текущее время
float currentTime = (float)stopwatch.ElapsedTicks / 
(float)Stopwatch.Frequency;
// Перебираем все вершины фигуры Лиссажу
for (int i = 0; i <= QuadStrips; i++) 
{ 
// Вычисляем коэффициент непрозрачности для текущей пары вершин
byte opacity = (byte)Math.Max(Math.Min((currentTime - 15.0f * (float)i / (float)QuadStrips) *
 255.0f, 255f), 0.0f); 
// Изменяем коэффициент непрозрачности вершин. К 
сожалению, структура Color не позволяет 
// изменять отдельные компоненты цвета, поэтому 
приходится создавать новую структуру и 
// указывать значения всех цветовых компонентов.
vertices[i * 2].Color = new XnaGraphics.Color(vertices[i * 2].Color.R,
 vertices[i * 2].Color.G, vertices[i * 2].Color.B, opacity);
vertices[i * 2 + 1].Color = new XnaGraphics.Color
(vertices[i * 2 + 1].Color.R,
 vertices[i * 2 + 1].Color.G, vertices[i * 2 + 1].Color.B, opacity); };
// Если фигура Лиссажу полностью визуализирована 
(она визуализируется ровно 16 секунд) if
 (currentTime > 16.0f) {
// Прекращаем анимацию, дабы не загружать центральный
 процессор и видеокарту бесполезной 
// работой.
Application.Idle -= new EventHandler(Application_Idle); 
// Выключаем таймер
stopwatch.Stop(); 
}
// Перерисовываем форму 
Invalidate(); 
}
private void MainForm_Paint(object sender, PaintEventArgs e) 
{ 
...
device.RenderState.CullMode = CullMode.None;
device.RenderState.FillMode = fillMode; 
// Задаем режим смешения пикселей
device.RenderState.AlphaBlendEnable = true;
device.RenderState.BlendFunction = BlendFunction.Add;
device.RenderState.SourceBlend = Blend.SourceAlpha;
device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
device.VertexDeclaration = decl;
// Визуализируем фигуру Лиссажу effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes) 
{
pass.Begin();
device.DrawUserPrimitives(PrimitiveType.TriangleStrip, vertices, 0, 
vertices.Length - 2);
pass.End(); 
} effect.End();
device.Present();
}
}
Листинг 3.25.
Заключение

В этой лекции мы научились осуществлять визуализацию как на поверхность компонентов Windows Forms, так и на целый экран с выбором требуемого видеорежима, и даже на поверхность элементов Win32. Так же мы научились реализовать плавную анимацию и моделировать полупрозрачность посредством смешивания с учетом альфа канала цвета

Андрей Леонов
Андрей Леонов

Reference = add reference, в висуал студия 2010 не могу найти в вкладке Solution Explorer, Microsoft.Xna.Framework. Его нету.

Олег Корсак
Олег Корсак
Латвия, Рига
Александр Петухов
Александр Петухов
Россия, Екатеринбург