Частина 10 - Малювання примітивів

Візуалізація текстур, як обговорювалося в попередніх главах, іноді супроводжується операціями малювання. Для деяких додатків та ігор операції малювання навіть важливі, напр. вам потрібно намалювати кнопки динамічного розміру у вашому додатку. Обговорення більш докладної справи знаходимо відразу після прикладу коду.

Підтримувані примітиви: точки, лінії, прямокутники

SDL2 SDL2 підтримує власне малювання

  • Точок
  • Ліній
  • Прямокутників (лише контур)
  • Заповнених прямокутників.

Малювання в SDL 2.0

Тепер давайте перейдемо прямо до коду:

program Chapter5_SDL2;

uses SDL2;

var
  i: Integer;
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlRect1: TSDL_Rect;
  sdlPoints: array[0..499] of TSDL_Point;

begin
  //ініціалізація підсистеми відео
  if SDL_Init(SDL_INIT_VIDEO) < 0 then
    halt;

  sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
  if sdlWindow1 = nil then
    halt;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then
    halt;

  //візуалізувати і відобразити очищене вікно з кольором фону
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 255, 255);
  SDL_RenderClear(sdlRenderer);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);

  //візуалізувати і показати лінію
  SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, 255);
  SDL_RenderDrawLine(sdlRenderer, 10, 10, 490, 490);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);

  //візуалізувати і намалювати точки по діагоналі з дистанцією між ними
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
  for i := 0 to 47 do
    SDL_RenderDrawPoint(sdlRenderer, 490-i*10, 10+i*10);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);

  //підготувати, візуалізувати і намалювати прямокутник
  sdlRect1.x := 260;
  sdlRect1.y := 10;
  sdlRect1.w := 230;
  sdlRect1.h := 230;
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, 255);
  SDL_RenderDrawRect(sdlRenderer, @sdlRect1);

  //перемістити, візуалізувати і намалювати прямокутник
  sdlRect1.x := 10;
  sdlRect1.y := 260;
  SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BLENDMODE_BLEND);
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 255, 128);
  SDL_RenderFillRect(sdlRenderer, @sdlRect1);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000);

  //підготувати, візуалізувати і намалювати 500 точок з випадковими координатами x і y
  Randomize;
  for i := 0 to 499 do
  begin
    sdlPoints[i].x := Random(500);
    sdlPoints[i].y := Random(500);
  end;
  SDL_SetRenderDrawColor(sdlRenderer, 128, 128, 128, 255);
  SDL_RenderDrawPoints(sdlRenderer, sdlPoints, 500);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(3000);

  //очистити пам'ять
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //завершити роботу SDL2
  SDL_Quit;
end.

Нічого собі, це виглядає як велике навантаження новими функціями, але я обіцяю, малювання в SDL2 дуже просте. Як виглядатиме виконана програма, показано на наступному скріншоті.

Result screenshot for chapter 5

Тепер давайте детальніше розглянемо перші рядки коду.

program Chapter5_SDL2;

uses SDL2;

var
  i: Integer;
  sdlWindow1: PSDL_Window;
  sdlRenderer: PSDL_Renderer;
  sdlRect1: TSDL_Rect;
  sdlPoints: array[0..499] of TSDL_Point;

Програма називається “Chapter5_SDL2”. Пізніше нам буде потрібна змінна для підрахунку “i” рідного для Pascal типу integer. Нам потрібне вікно і візуалізатор, назвемо їх “sdlWindow1” і “sdlRenderer” як вже знаємо з попередніх частин. Далі ми опишемо змінну “sdlRect1” типу TSDL_Rect. Те ж саме стосується змінної “sdlPoints”, яка буде масивом розміром 500 елементів типу TSDL_Point.

begin
  //ініціалізація підсистеми відео  
  if SDL_Init(SDL_INIT_VIDEO) < 0 then
    halt;

  sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
  if sdlWindow1 = nil then
    halt;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then
    halt;

Тут нічого нового, ми ініціалізуємо SDL2, створюємл вікно розміром 500 пікселів ширини і 500 пікселів висоти та прив'яжемо візуалізатор асоціюємо візуалізатор з цим вікном.

Кольори, значення альфа і позначення RGB(A) в SDL 2.0

  //візуалізувати і відобразити очищене вікно з кольором фону 
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 255, 255);
  SDL_RenderClear(sdlRenderer);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000); 

Тепер тут у нас є дещо нове, SDL_SetRenderDrawColor() встановлює колір для операцій малювання, на зразок як Ви вибираєте кольоровий олівець щоб щось намалювати. Однак ця функція нічого не малює. Вона повертає 0 при іспішному виконанні і негативний код при невдачі.

SDL_SetRenderDrawColor(renderer: PSDL_Renderer; r: UInt8; g: UInt8; b: UInt8; a: UInt8): SInt32

Спочатку потрібно вказати візуалізатор, для якого призначений цей колір малювання. Після цього Вам потрібно встановити складові кольору червоний, зелений, синій та значення альфа. Вони можуть мати значення від 0 до 255 (лише цілі значення). Просто думайте, що 255 - це 100%, а 0 - 0% від цього кольору або альфа значення. Для початку нехтуймо значенням альфа.

Якщо Вам подобається червоний колір, потрібно встановити значення для червоного кольору високим, напр. 100%, максимальне значення - 255, і оскільки Ви не хочете змішувати зелений та синій кольори, вони отримують мінімальне значення 0 (отже, 0%). Таким чином, налаштування червоного кольору відповідає 255/0/0 з точки зору r / g / b. Для порівняння 0/255/0 призведе до зеленого, 255/255/255 - білого, 0/0/0 - чорного тощо. У прикладі коду ми використовували 0/255/255, що призводить до блакитного (змішання зеленого та синього кольорів). За допомогою цих трьох значень ви можете генерувати всі можливі кольори.

То що тоді означає альфа? Ну, це визначає прозорість. 255 означає повністю непрозоро, а 0 - повну прозорість. Це дуже важливо для поєднання ефектів в комп'ютерній графіці, і це буде продемонстровано пізніше в коді. До речі, замість 255 ви могли б використовувати SDL_ALPHA_OPAQUE. Якщо врахувати значення альфа також як варіацію кольору, у вас є 4,29 мільярда різних можливостей.

Встановлення фону в SDL2

Функція SDL_RenderClear() служить для очистки екрану кольором малювання. Як аргумент Вам просто достатньо передати візуалізатор Це так просто :-). Оскільки ми раніше встановили для кольору малювання блакитний, екран очиститься блакитним кольором.

SDL_RenderClear(renderer: PSDL_Renderer): SInt32

Ця функція повертає 0 при успішному виконанні і негативний код помилкипри невдачі. Очищений екран відображатиметься на одну секунду процедурами SDL_RenderPresent() і SDL_Delay(). Ці дві процедури відомі з попередніх частин.

Малювання ліній і точок в SDL2

  //візуалізувати і показати лінію 
  SDL_SetRenderDrawColor(sdlRenderer, 255, 0, 0, 255);
  SDL_RenderDrawLine(sdlRenderer, 10, 10, 490, 490);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000); 

Тепер ми змінюємо колір малювання за допомогою SDL_SetRenderDrawColor() на червоний і використовуємо SDL_RenderDrawLine() щоб намалювати просту лінію.

SDL_RenderDrawLine(renderer: PSDL_Renderer; x1: SInt32; y1: SInt32; x2: SInt32; y2: SInt32): SInt32

І знову нам потрібен візуалізатор, який в нашому випадку називається “sdlRenderer”. Після того ми задаємо координати x/y де буде починатись лінія і тоді координати x/y де лінія буде закінчуватись. Пам'ятайте, що початок координат 0/0 знаходиться в верхньому лівому кутку вікна. Координати 10/10 означають почати з точки десять пікселів правіше і десять пікселів нижче відносно початку координат. Тож, координати 490/490 для другої точки приведуть до діагоналі через вікно. Ця діагональ буде на 10 пікселів коротше від границь вікна. Ця функція повертає 0 при успішному виконанні і негативний код помилки у випадку невдачі.

Після того знову ми просимо візуалізувати цю лінію на екран за допомогою SDL_RenderPresent() і чекаємо одну секунду за допомогою SDL_Delay().

  //візуалізувати і намалювати точки по діагоналі з дистанцією між ними
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
  for i := 0 to 47 do
    SDL_RenderDrawPoint(sdlRenderer, 490-i*10, 10+i*10);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000); 

Тепер ми змінюємо колір на чорний і малюємо трохи точок за допомогою функції SDL_RenderDrawPoint(). Вона майже ідентична до SDL_RenderDrawLine() але замість чотирьох координат Вам потрібно лише дві координати, де слід намалювати точку.

SDL_RenderDrawPoint(renderer: PSDL_Renderer; x: SInt32; y: SInt32): SInt32

Ця функція повертає 0 при успіху та негативний код помилки у випадку невдачі.

Я вважав, що було б непогано намалювати більше однієї точки, тому функція використовується у циклі for, щоб намалювати загалом 48 точок. Тут нам потрібна змінна-лічильник "i". Можливо, Ви можете здогадатися з коду, де знаходяться точки і як вони розташовані, якщо ні, просто запустіть код ;-). Нарешті результат відображається на екрані за допомогою SDL_RenderPresent(), а програма чекає одну секунду за допомогою SDL_Delay().

Перейдемо до наступної частини коду.

  //підготувати, візуалізувати і намалювати прямокутник
  sdlRect1.x := 260;
  sdlRect1.y := 10;
  sdlRect1.w := 230;
  sdlRect1.h := 230;
  SDL_SetRenderDrawColor(sdlRenderer, 0, 255, 0, 255);
  SDL_RenderDrawRect(sdlRenderer, @sdlRect1);

Встановлюється колір малювання функцією SDL_SetRenderDrawColor() і малюється прямокутник функцією SDL_RenderDrawRect().

SDL_RenderDrawRect(renderer: PSDL_Renderer; rect: PSDL_Rect): SInt32

Тут потрібен візуалізатор, який в нас “sdlRenderer” і PSDL_Rect, тож ми використовуємо оператор @ до оголошеного прямокутника типу TSDL_Rect щоб отримати значення вказівника на нього. Ця функція повертає 0 при успіху негативний код помилки при збої. Зауважте, ми ні візуалізуємо зараз результат на екран, ні виконуємо тут затримки. В будь-якому випадку, ми хочемо другий прямокутник! 🙂

  //перемістити, візуалізувати і намалювати прямокутник
  sdlRect1.x := 10;
  sdlRect1.y := 260;
  SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BLENDMODE_BLEND);
  SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 255, 128);
  SDL_RenderFillRect(sdlRenderer, @sdlRect1);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(1000); 

Ми змінюємо координати x/y для другого прямокутника, але зберігаємо його ширину та висоту. Те, що ми хочемо це заповнений прямокутник з деякою прозорістю. Дотепер ми завжди використовували 255 (непрозорість) як значення альфа. Ми встановлюємо колір щоб намалювати другий прямокутник блакитним за допомогою SDL_SetRenderDrawColor(). Зверніть увагу, що четверте значення - 128 (напівпрозорість) замість 255 (непрозорість). Тож все, що знаходиться за синім прямокутником, напр. блакитний фон, повинне просвічувати. Для створення заповненного прямокутника використовується функція SDL_RenderFillRect():

SDL_RenderFillRect(renderer: PSDL_Renderer; rect: PSDL_Rect): SInt32

Параметри цієї функції - візуалізатор і прямокутник типу PSDL_Rect. Тому ми знову використовуємо “sdlRenderer” та “sdlRect1” (з оператором @), щоб намалювати прямокутник. Ця функція повертає 0 при успіху та негативний код помилки при відмові.

Режим змішування в SDL 2.0

Але чесно кажучи, навіть якщо Ви зміните значення альфа, воно буде непрозорим. Це пов’язано з тим, що за замовчуванням для режиму змішування встановлено значення SDL_BLENDMODE_NONE. Нам потрібно змінити це, щоб мати можливість використовувати значення альфа за бажанням. SDL_SetRenderDrawBlendMode() - це те, що ми шукаємо:

SDL_SetRenderDrawBlendMode(renderer: PSDL_Renderer; blendMode: TSDL_BlendMode): SInt32

Спочатку вибирається візуалізатор, для якого повинен бути встановлений режим змішування. У нашому випадку це знову “sdlRenderer”. Тоді доступні чотири режими змішування. Їх опис взято з офіційної SDL2 Wiki.

  1. SDL_BLENDMODE_NONE
    • відсутність змішування
    • dstRGBA = srcRGBA
  2. SDL_BLENDMODE_BLEND
    • альфа-змішування
    • dstRGB = (srcRGB * srcA) + (dstRGB * (1-srcA))
    • dstA = srcA + (dstA * (1-srcA))
  3. SDL_BLENDMODE_ADD
    • аддитивне змішування
    • dstRGB = (srcRGB * srcA) + dstRGB
    • dstA = dstA
  4. SDL_BLENDMODE_MOD
    • модуляція кольору
    • dstRGB = srcRGB * dstRGB
    • dstA = dstA

Ми шукаємо альфа змішування, тож ми використовуємо SDL_BLENDMODE_BLEND як аргумент для режиму змішування. Ця функція повертає 0 при успіху та негативний код помилки при відмові.

після цього результат візуалізується на екран SDL_RenderPresent() і відображається на 1 секунду функцією SDL_Delay(). Обидва прямокутники, зелений і напівпрозорий синій, з'являються одночасно.

Випадкове розподілення точок за допомогою PSDL_Point

  //підготувати, візуалізувати і намалювати 500 точок з випадковими координатами x і y
  Randomize;
  for i := 0 to 499 do
  begin
    sdlPoints[i].x := Random(500);
    sdlPoints[i].y := Random(500);
  end; 

Randomize - це процедура Free Pascal (з модуля system) для ініціалізації генератора випадкових чисел. Уявіть це як кидання кубика.

Давайте подивимось на TSDL_Point. Це лише запис цієї структури:

PSDL_Point = ^TSDL_Point;
TSDL_Point = record
  x: SInt32;
  y: SInt32;
end;

Як і слід було очікувати, запис TSDL_Point rмає два значення - координати x / y точки. Зверніть увагу, що PSDL_Point всього лиш вказівник на це. Фунекція Random() Free Pascal генерує випадкові числа між 0 і числом, яке використовується як аргумент за мінусом одиниці. Отже, ми 500 разів генеруємо випадкові значення x і y між 0 і 499 і зберігаємо їх в 500 записів SDL_Point.

  SDL_SetRenderDrawColor(sdlRenderer, 128, 128, 128, 255);
  SDL_RenderDrawPoints(sdlRenderer, sdlPoints, 500);
  SDL_RenderPresent(sdlRenderer);
  SDL_Delay(3000);

Для точок використовується сірий колір, який встановлюється функцією SDL_SetRenderDrawColor(). Щоб намалювати точки в масиві “sdlPoints” ми використовуємо функцію SDL_RenderDrawPoints(). /p>

SDL_RenderDrawPoints(renderer: PSDL_Renderer; points: PSDL_Point; count: SInt32): SInt32

Ця функція повертає 0 при успіху та негативний код помилки при відмові. Спочатку Вам потрібно встановити візуалізатор, тоді лише вказівник на масив TSDL_Point (повертається просто передачею імені масиву) і нарешті кількість точок. Останнє має узгоджуватися з масивом.Тож, якщо Ваш масив має 500 елементів, кількість може бути максимум 500. Зверніть увагу, що ми тут не використовуємо цикл, щоб намалювати всі 500 точок, викликаючи певну функцію 500 разів. Натомість ми один раз передаємо масив точок. Таким чином ми економимо багато часу під час виконання, особливо якщо Ви думаєте про реальну програму де буде малюватись навіть більше точок. Існують подібні функції для ліній, прямокутників і заповнених прямокутників. Вони не використовуються в цьому прикладі, але буде корисно про них знати, тож ось вони:

SDL_RenderDrawLines(renderer: PSDL_Renderer; points: PSDL_Point; count: SInt32): SInt32

SDL_RenderDrawRects(renderer: PSDL_Renderer; rects: PSDL_Rect; count: SInt32): SInt32

SDL_RenderFillRects(renderer: PSDL_Renderer; rects: PSDL_Rect; count: SInt32): SInt32

Як підказка, спробуйте замінити SDL_RenderDrawPoints на SDL_RenderDrawLines, і змініть кількість на 499. Ви будете винагороджені гарним візерунком. 🙂

  //очистити пам'ять
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //завершити роботу SDL2
  SDL_Quit;
end. 

Нарешті, вся пам'ять, виділена для точок, прямкутника, візуалізатора та вікна звільняється і SDL2 завершує роботу функцією SDL_Quit.

Вітаємо, ви щойно закінчили цей розділ :-).

Також можете почитати нижче:

  • Проблема: Використання масиву PSDL_Point
  • Справа: Малювання для динамічного забарвлення
  • Розуміння кольорів у комп’ютерній графіці

або безпосередньо перейти до наступного розділу.

← попередня частина | наступна частина →

Проблема: Використання масиву PSDL_Point

Щоб розмістити свій масив у купі, Ви хотіли б використовувати масив PSDL_Point замість масиву TSDL_Point. З якихось причин, здається, це погано працює з функцією SDL_RenderDrawPoints(). Це означає, що замість, припустимо, 500 точок відображається лише чверть усіх точок (125 у прикладі), якщо Ви не вказали, що кількість у чотири рази більша (у прикладі 2000). Причина цього мені не зрозуміла.

Справа: Малювання для динамічного забарвлення

SDL2 drawing and images diagram

Як приклад, коли малювання може бути дуже корисним, подумайте про наступну ситуацію. Ви створюєте гру, скажімо, автогонки. Є різні гравці. Звісно, машина кожного гравця повинна мати різний колір. Як правило, цього можна досягти двома способами:

Ви створюєте кілька зображень з різними кольорами автомобілів і зберігаєте їх на жорсткому диску.

Це цілком нормально, якщо у Вас є невелика кількість автомобілів (і кольорів), які потрібно вибрати. Але що, якщо Ви хочете, щоб гравець вибирав із великої різноманітності кольорів, 100 кольорів або більше, або що, якщо Ви хочете дозволити гравцеві самостійно вибрати колір своєї машини? Зверніть увагу, у випадку 16-бітового забарвлення це означає 65536 можливостей! Ви хочете створити стільки зображень? У випадку 32-бітного забарвлення у вас є фантастичні 4,29 мільярда кольорів!! Дивно, але Ви ніколи не зможете створити стільки зображень лише за життя однієї людини. Крім того, це зайняло б багато пам'яті на жорсткому диску (у 4,29 мільярда разів більше, ніж розмір файлу зображення автомобіля) для простої гоночної гри. Подивіться на наступне зображення. Воно містить обговорюваний метод зліва до середини. Справа до середини - рішення :-).

Замість того, щоб кожен автомобіль був кольоровим як файл зображення, чому б не використати лише один файл зображення автомобіля без забарвлення? Це свого роду шаблон. Тепер Ви можете легко запитати гравця, якому кольору він надає перевагу (і він може вибрати з 4,29 мільярда кольорів, якщо це необхідно), і тоді Ви просто розмалюєте машину на льоту. Це другий спосіб:

2) Ви створюєте одне зображення шаблону на жорсткому диску і розфарбовуєте його під час роботи програми.

Це лише один приклад, коли малювання дуже корисне.

Розуміння кольорів у комп’ютерній графіці

Колір складається з чотирьох компонентів, RGBA, тобто червоного, зеленого, синього та значення альфа. Фізичний екран складається з безлічі невеликих блоків. Кожен блок складається з трьох кольорів. Ці кольори - червоний, зелений та синій. Якщо їх змішати, можна отримати будь-який інший колір. Ці кольори змішуються адитивно. Наприклад, якщо Ви змішаєте червоний і зелений, Ви отримаєте жовтий. Для трьох кольорів, які можна змішувати один з одним, можливі вісім комбінацій, які призводять до різних кольорів (RGB, RG, RB, R, GB, G, B, усі кольори вимкнені). Якщо Ви змішаєте червоний, зелений та синій (усі кольори увімкнено, RGB), Ви отримаєте білий, а якщо всі кольори вимкнені, Ви отримаєте чорний. Хтось може сказати, що білий і чорний - це зовсім не кольори. Ну, це правильно, але тут не має значення, і для простоти я буду говорити про кольори, навіть якщо я говорю про чорний і білий.

На Вашому екрані точно більше восьми кольорів, чи не так? Причина в тому, що Ваш екран не просто може вмикати та вимикати світло. До того ж він здатний відрізняти інтенсивність світла. Чим більше рівнів інтенсивності у Вас, тим більше кольорів Ви можете відобразити. Випадок, коли у вас є вісім кольорів, як обговорювалося раніше, означає, що у вас є лише один рівень інтенсивності, включений або вимкнений. Якщо ваш екран перебуває у 8-бітному режимі, кожен піксель на екрані має можливість відобразити 28 кольорів. Це 256 різних кольорів. Отже, кожен з трьох кольорів має певну кількість різних рівнів інтенсивності світла. Якщо у вас 16-бітний режим, у вас є 216 і це 65536 кольорів. Тому кожен колір має відповідну кількість рівнів інтенсивності. Оскільки ми віддаємо перевагу 32-бітному режиму, у нас є 4,29 мільярда різних кольорів!

Немає коментарів:

Дописати коментар