Вибір об’єктів засобами OpenGL

Далі я спробую розглянути два способи вибору об'єктів виключно засобами OpenGL.

Перший спосіб це, звичайно ж, буфер вибору OpenGL. Його гідності в тому, що це, мабуть, єдиний спосіб вибору об'єктів, де є хоч якась надія, що він апаратний. Тому й працює, можливо, швидше за інших. Для OpenGL це також є стандартом вибору об'єктів. При цьому мінусів цього способу достатньо. Чого тільки варта відсутність тесту на перетин з іншими об'єктами, що не виділяються. Тобто для OpenGL потрібно створити такі умови, за яких на сцені всі об'єкти мають виділятися, тоді все працюватиме коректно.

Буфер вибору

Не зовсім зрозуміла логіка інженерів-розробників OpenGL, навіщо чисто в графічній бібліотеці знадобилося виконувати функції опосередковано пов'язані з графікою. Чесно кажучи, краще зробили б функції розрахунку lightmap'ів ніж вибір об'єктів.

Ну, коли зробили, то зробили, нікуди не дінешся від цього. Фішка ця є, а якщо вона є значить, її потрібно використовувати за повною програмою.

Сенс вибору об'єктів полягає в тому, що, маючи двовимірні координати мишки, ми повинні вибрати об'єкт у тривимірному світі. Як не складно здогадатися, потрібно робити якісь переклади з 2D до 3D або назад. Координата мишки є два числа, тобто точку. Координатна система вікна у нашому випадку майже нічим не відрізняється від координатної системи Windows, але за одним винятком - координата Y повинна бути перевернута, тобто значення 600 відповідає значенню 0, значення 583 відповідає значенню 17. Зрозуміло, це тільки в тому випадку, якщо висота робочої області буде 600. Тобто з висоти (height) робочого вікна потрібно віднятизначення Y координати курсору мишки.

Для того, щоб буфер вибору мав силу, потрібно, щоб усі об'єкти мали свої ідентифікатори. Без них OpenGL не знатиме, що має відповідати у вашій програмі, а конкретніше - у вашому тривимірному світі. Ідентифікація (далі ім'я) об'єкта - це процес, у якому OpenGL отримує унікальне ім'я об'єкта. Ця дія схожа на те, що ми називаємо дисплейними списками (Display Lists) або текстурними об'єктами (Texture Objects). Але у всіх цих випадках бібліотека за допомогою спеціальних для цього функцій може сама генерувати ім'я, а в нашому випадку його доведеться або задавати вручну або писати функцію їхньої генерації.

Усі пойменовані об'єкти перебувають у стеку імен. Для того щоб почати роботу з буфером вибору, потрібно очистити стек імен. Робить це функція glInitNames(). Ця та багато наступних операцій зазвичай виконуються на етапі натискання кнопки мишки, тобто втрата в продуктивності не повинна бути настільки суттєвою. Якщо ж є необхідність динамічної перевірки вибору, варто пошукати інші методи вибору об'єктів і взяти їх найшвидший. Нехай він буде великим, складним у реалізації, але найголовніше він має бути найшвидшим.

Існує кілька способів завдання імен для об'єктів.

Перший спосіб завдання імен полягає в тому, що ми встановлюємо нульовий об'єкт, а потім завантажуємо всі імена через glLoadName(). Тобто це виглядає так:

У цій змінній OBJECT_ID_1 (у нашому випадку це макрос) є унікальний ідентифікатор об'єкта. І всі інші об'єкти, що записуються в буфер вибору, повинні мати унікальні значення.

Тобто вштовхуємо один об'єкт (glPushName) та передаємо його вершини. Запам'ятайте – текстурні координати, нормалі, освітлення, все це не цікавить OpenGLна даному етапі, тому не слід нічого цього передавати, хоч би з метою збільшення продуктивності. А потім виштовхуємо (glPopName). Все, об'єкт записано. І так пробігаємось по всіх об'єктах, які можна обирати. Але все-таки краще користуйтеся першим способом.

Також хотілося б зазначити, що запис об'єктів слід здійснювати лише під час натискання кнопки мишки. Якщо потрібно в реальному часі робити вибір об'єктів, то, як я вже казав - краще знайти інший спосіб вибору об'єктів, порівняти його з буфером вибору та вибрати найшвидший за швидкістю.

Ми підійшли до найголовнішого – вибір об'єктів. Отже, що потрібно для вибору об'єкта: 1. Віконні координати мишки. 2. Матриця робочої області (Viewport Matrix). 3. Буфер записів натискань (Hit Records) або іншими словами - буфер вибору. 4. Ну а найголовніше – коректно виконані попередні дії.

Тепер розберемо функцію вибору, саме першу частину цієї функції.

Функція glSelectBuffer() повинна дати OpenGL інформацію про буфер вибору. Не те щоб відомо, скільки куди їх записувати. Взагалі кожен об'єкт у буфері вибору має чотири поля. Для того щоб буфер вибору працював і робив покладений йому результат нам знадобиться тільки друге поле і останнє, тобто четверте. Друге поле зберігає мінімальне значення глибини об'єкта. У OpenGL глибина визначається clamp числом, тобто числом, що лежить в інтервалі від 0 до 1. А також ідентифікатор об'єкта (четверте поле). Також є перше та третє поле. У першому полі зберігається кількість об'єктів на момент натискання, тобто загальна кількість об'єктів під курсором мишки, навіть якщо вони перекривають один одного. І третє поле – це максимальна глибина (віконна координата Z) об'єкта.

А тепер все одразу: Перше поле- кількість об'єктів під курсором на момент натискання. Друге поле – мінімальна Z глибина об'єкта (екранна Z координата). Третє поле – максимальна Z глибина об'єкта (екранна Z координата). Четверте поле – ідентифікатор об'єкта.

Тому розмір буфера задається так: (4 * Кількість об'єктів).

Далі принцип роботи такий. Ми повинні перейти в 2D режим, запам'ятавши поточну матрицю та встановити режим glRenderMode() у GL_SELECT. Цей режим дозволяє нам малювати плоскі об'єкти при цьому не псувати FrameBuffer, при цьому вся система буфера вибору починає працювати в "активному режимі", тобто вести запис у буфер вибору. Потім слідує функція gluPickMatrix(), тут потрібно зупинитися докладніше. Принцип роботи цієї функції у тому, що вона створює матрицю проекції навколо курсора. Це дозволяє малювати об'єкти в регіоні (регіон буде заданий параметрами функції gluPickMatrix()). Отже, якщо об'єкт у цьому регіоні малюється, буфер вибору запам'ятовує його і записує в масив, який ми передали в glSelectBuffer(). Перші два параметри функції gluPickMatrix() - це координати мишки, тільки зверніть увагу, що координата Y має бути перевернута, тобто початок координат для Y йде не зверху вікна, а знизу. Другі два параметри – ширина і висота області, починаючи від початкової точки (перших двох параметрів). Зазвичай їх задають двійкою. Останній параметр – це матриця робочого вікна (Viewport Matrix).

Далі ми викликаємо стандартну для застосування перспективу. Це потрібно для того, щоб створити проекційну матрицю для регіону, який ми вказали на функції gluPickMatrix(). Потім переходимо в матрицю моделі glMatrixMode(GL_MODELVIEW) та малюємо наші об'єкти в режимі вибору. Це двозначна фраза, з одного боку, ми дійсно малюємо об'єктив режимі вибору GL_SELECT, з іншого боку, ми їх малюємо через glInitNames()/LoadName() і так далі. Малюємо, як я вже казав, без жодних текстур та іншого зайвого, тільки вершини. Як було зазначено раніше, ми знаходимося в режимі GL_SELECT, тому все що ми намалюємо, не позначиться на буфері кадру (FrameBuffer). Потім залишилося викликати функцію glRenderMode() із параметром GL_RENDER. Функція поверне кількість об'єктів, що знаходяться під тим регіоном, який ми вказали у функції gluPickMatrix().

Це була перша частина функції отримання вибраного об'єкта.

У першій частині ми довідалися кількість об'єктів під курсором мишки. Якщо ця кількість дорівнює нулю, то програму можна далі не виконувати, це означає, що жодних об'єктів обрано не було. Як я вже помітив мінус буфера вибору в тому, що для нього ідеальний стан, коли на сцені можна вибрати будь-який об'єкт.

Як ми вже помітили, OpenGL повертає кількість об'єктів під курсором, тобто він не може повернути той, на який ми натиснули. Але це й не потрібно, річ у тому, що якщо доведеться робити вибір усіх об'єктів під курсором, то це буде неможливо. Тому OpenGL пропонує нам універсальність, а за неї потрібно платити. Тому сортування ми будемо робити самі. Сортування в даному випадку буде йти по Z координаті кожного об'єкта, що потрапив у вибір. Значення Z записується масив, який ми вказали функцією glSelectBuffer(). І як було зазначено, кожен об'єкт має у своєму розпорядженні чотири поля. У другому полі знаходиться мінімальна глибина Z об'єкта.

Такий вигляд має друга частина функції. Спочатку ми записуємо мінімальний Z першого об'єкта (стандартний пошук мінімального елемента масиву, щоб було з чим порівнювати). А потім ідентифікатор. Хочу нагадати, що нумерація масивівпочинається з нуля, тому дані йдуть 0-3, 4-7, 8-11 і так далі. Далі ми проходимося по всіх об'єктах, знайдених функцією glRenderMode(GL_RENDER). Подальший запис не дуже важкий у розумінні - звичайний пошук мінімального елемента в масиві. Так як об'єкт був знайдений (Found більше за нуль), то в selObject запишеться найближчий обраний об'єкт.

єктів

Читайте далі. Вибір об'єктів через буфер кольору.