DirectX Графика в проектах Delphi

       

Доступ к пикселам в 16-битном режиме



В таком режиме информация о цвете пиксела разделяется на три цветовые составляющие, но шестнадцать на три нацело не делится, поэтому разработчики вынуждены прибегать к неравномерному распределению. Наиболее распространенной является схема 5-6-5. В этом формате первые пять битов хранят значение красного оттенка, следующие шесть битов отводятся под зеленую составляющую, ну и последние пять битов заняты оттенком синего. Всего получается 65 536 (216) различных цветов. Из них по 32 градации красного и синего, 64 градации зеленого.
Схема 5-6-5 является самой распространенной. Поэтому для начала будем опираться именно на нее. Как быть в случае другого формата, рассмотрим позднее.
Для примера возьмем цвет, образованный следующими значениями составляющих:
  • красный, 5 бит: 00011;
  • зеленый, 6 бит: 001011;
  • синий, 5 бит: 00101.

Значение пиксела с таким цветом будет следующим (пробелы вставлены для удобочитаемости):

0001 1001 ОНО 0101

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

blue + green * 2"5 + red * 2Л11 или blue + green * 64 + red * 4096

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

blue OR (green SHL 5) OR (red SHL 11)

Иллюстрация в виде примера последует позже, а сейчас задержимся на том, как вырезать из пиксела значения составляющих. Для этого применяются битовые маски. Так, для получения значения пяти битов красной составляющей надо использовать бинарное число
1111 1000 0000 0000
и логическую операцию AND для вырезания значения первых пяти битов. Вот так:

0001 1001 ОНО 0101 &
1111 1000 0000 0000
-------------------------------
0001 1000 0000 0000

Результат найден, как видим, верно, но ему предшествуют одиннадцать нулей. Чтобы получить значение составляющей, надо применить к этому выражению операцию битового сдвига вправо. Вот пример для красной составляющей:

Red : Byte;
Red := (pixel & $F800) SHR 11;




Или, если поменять порядок действий, вырезать ее можно так:

Red := (pixel SHR 11) AND $lf;

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

const
MAX ENERGY =60; // Максимальная энергия частицы
DEFAULT_SIZE =200; // Количество частиц во вспышке
DEFAULT_POWER =30; // Для зарядки энергии частицы
type
TParticle = record // Данные на отдельную частицу
X, Y : Single; // Позиция
SpeedX, SpeedY : Single; // Скорости по осям
Energy : Integer; // Энергия
Angle : Integer; // Направление движения
R, G, В : Byte; // Цвет
end;
TParticleSystem = class // Класс системы частиц
public
procedure Init (NewSize, Power : Integer); // Инициализация
procedure Calculate; // Пересчет положений частиц
function Render : HRESULT; // Отображение вспышки
private
Particle : Array [0..1000] of TParticle; // Массив частиц
Size : integer; // Размер
end;

Инициализация системы выглядит так:

procedure TParticleSystem.Init (NewSize, Power : Integer);
var
i : Integer;
X, Y : Integer; // Стартовая точка вспышки Speed : Single;
begin
Size := NewSize; // Устанавливаем размер системы
// Центр вспышки располагаем вдали от границ экрана
X := random (ScreenWidth - 80) + 40;
Y := random (ScreenHeight - 80) + 40;
for i := 0 to Size do begin // Частицы системы
Particle[i].X := X;
Particle[i].Y := Y;
Particle[i].Energy := random (MAX_ENERGY); // Энергия
Particle[i].Angle := random (360); // Угол движения
Speed := random (Power) - Power / 2;
Particle[i].SpeedX := sinAfParticle[i].Angle] * Speed;
Particle [i] . SpeedY := cosA[Particle [i] .Angle] * Speed;
Particle [i] . r := random (256); // Сине-красный цвет
Particle [i] . g := 0;
Particle[i] .b := random (256);
end;
end;

Первый раз система инициализируется в начале работы приложения. Здесь же заполняются вспомогательные массивы, хранящие синусы и косинусы углов:
sinA : Array [0..360] of Single;
cosA : Array [0..360] of Single;
PS : TParticleSystem;
for j := 0 to 360 do begin // Для оптимизации, чтобы вычислять
sinA[j] := sin(j * Pi / 180); // только один раз
cosA[j] := cos(j * Pi / 180); end;
PS := TParticleSystem. Create; // Создание системы
PS.Init (DEFAULT_SIZE, DEFAULT_POWER) ; // Инициализация системы



В методе calculate класса вспышки пересчитываются текущие координаты частиц:

procedure TParticleSystem. Calculate;
var
i : Integer;
begin
for i := 0 to Size do begin
if Particle [i] .Energy > 0 then begin
Particle [i] .X := Particle [i] .X + Particle [i]. SpeedX;
// Частицы отскакивают от границ экрана
if Particle [i] .X >= ScreenWidth - 1 then begin
Particle [i ] .SpeedX :="-0.5 * Particle [i]. SpeedX;
Particle [i] .X := ScreenWidth - 1;
end;
if Particle [i] .X < 0 then begin
Particle [i] .SpeedX := -0.5 * Particle [i]. SpeedX;
Particle [i] .X := 0;
end;
Particle [i].Y := Particle [i] .Y + Particle [i] . SpeedY;
if Particle [i] .Y >= ScreenHeight - 1 then begin
Particle [i] .SpeedY := -0.3 * Particle [i] . SpeedY;
Particle[i] .Y := ScreenHeight - 1;
end;
if Particle [i] .Y < 0 then begin
Particle [i] .SpeedY := -Particle [i] . SpeedY;
Particle[i].Y := 0;
end;
Particle[i].Energy := Particle[i].Energy - 1;
Particle[i].SpeedY := Particle[i].SpeedY + 0.2;
end;
end;
end;

Самый главный для нас метод - воспроизведение частиц системы:

function TParticleSystem.Render : HRESULT;
var
i : Integer;
desc : TDDSURFACEDESC2;
hRet : HRESULT;
begin
ZeroMemory (@desc, SizeOf(desc));
desc.dwSize := SizeOf(desc);
hRet := frmDD.FDDSBack.Lock (nil, desc, DDLOCKJSAIT, 0);
if Failed (hRet) then begin Result := hRet;
Exit;
end;
// Очистка экрана
ZeroMemory (desc.IpSurface,
desc.lPitch * ScreenHeight * (ScreenBitDepth div 8));
// Заполняем пикселы в соответствии с состоянием системы частиц
for i := 0 to Size do
if (Particle[i].Energy > 0) then
PWord (Integer(desc.IpSurface) +
trunc (Particle[i].Y) * desc.lPitch +
trunc (Particle[i].X) * (ScreenBitDepth div 8))^ :=
Particle[i].B or (Particle[i].G shl 5) or (Particle[i].R shl 11);
Result := frmDD.FDDSBack.Unlock(nil) ;
end;

При каждой перерисовке экрана отображается текущее состояние системы:

function TfrmDD.UpdateFraine : HRESULT;
var
hRet : HRESULT;
begin
Result := DD_FALSE;
PS.Calculate; // Пересчитываем положения частиц
// Воспроизведение состояния системы
hRet := PS.Render;
if Failed (hRet) then begin
Result := hRet;
Exit;
end;
Time := Time + 1; // Простейший эмулятор таймера
if Time > 15 then begin // Прошел срок существования системы
PS.Init(DEFAULT_SIZE, DEFAULT_POWER); // Вспышка в новом месте
Time := 0;
end;
Result := DD_OK;
end;


Содержание раздела