Перейти к содержимому


Свернуть чат Чат Открыть чат во всплывающем окне

Yakim (Watco... : (неделю назад) :D
Yakim (Watco... : (неделю назад) nope
Yandersen : (неделю назад) Айаяй. Поди Якимко заспамил чат стикерами. :)
Yandersen : (неделю назад) Да лан, тут каждый день кто-нить из админов заглядывает. Как пропустили?
Nextovoy : (неделю назад) Я писал
Гость : (неделю назад) Сорьки, что так у нас. Чего три года так и не попытался в чат писнуть? :)
Nextovoy : (2 недель назад) Спасибо
lz : (2 недель назад) Активировал.
Гость : (2 недель назад) Активируйте его.
Гость : (2 недель назад) Мой профиль - Nextovoy
Гость : (2 недель назад) Написать в чат. Профиль в ручную админы активируют.
Гость : (2 недель назад) Ох уж эта дурацкая привычка писать всё раздельно засоряя чат. Это всё классно, конечно, но ребята, одменестраторы, так называемые. Третий год пытаюсь зарегистрироваться (буквально, третий) на этом форуме, но ПИСЬМО С ПОДТВЕРЖДЕНИЕМ НА ПОЧТУ ТАК И НЕ ПРИХОДИТ. Что делать?
Гость : (2 недель назад) Перешёл я всё же по ссылке Redoctor'a...
Гость : (2 недель назад) Пора уже M4
Гость : (3 недель назад) итак м3
lz : (3 недель назад) Мы тебе и тут передадим.
Гость : (3 недель назад) Зачем в телеграмме делать?!Я вот например не могу зайти,написать в чат,подписаться и не только у меня это.
Redoctor : (3 недель назад) https://vk.com/away....0_23001&cc_key=
Redoctor : (3 недель назад) Тогда в телеграмме в поисковике набери Механоиды 3
Гость : (3 недель назад) Не открывается.
Redoctor : (3 недель назад) https://t.me/mechanoids3 Для тех кто в танке.
Yakim (Watco... : (15 Июнь 2018 - 01:33) КРУЗИИИС!!!11

Изображение
lz : (15 Июнь 2018 - 00:09) КРУЗИС!
lz : (15 Июнь 2018 - 00:09) ЗИС
lz : (15 Июнь 2018 - 00:09) КРУ
Yakim (Watco... : (14 Июнь 2018 - 14:50) Крузис и Королева тоже не в моем вкусе, а проигрывать нечего =D
lz : (14 Июнь 2018 - 13:55) Конечно, полюбить - так королеву, проиграть - так миллион, сделать - так крузис.
smt005 : (14 Июнь 2018 - 00:22) И от третьего лица тоже можно сделать простенькую игру. Простая игра это лучше чем ничего.
smt005 : (14 Июнь 2018 - 00:21) А, ты хочеш что-бы хит был, с "Crysis" графоном и контентом на 100500 часов игры?
Yakim (Watco... : (14 Июнь 2018 - 00:18) Ни топдовншутеры, ни стратежки)
Yakim (Watco... : (14 Июнь 2018 - 00:15) Не, спасибо, не в моем вкусе=)
smt005 : (13 Июнь 2018 - 23:13) Помнится за пару недель от скуки сделал. Делал по вечерам.
smt005 : (13 Июнь 2018 - 23:13)
smt005 : (13 Июнь 2018 - 23:11) Или например такое, только с моделями из игры -> https://youtu.be/RFDdN5dcX8s
smt005 : (13 Июнь 2018 - 23:07) Yakim, да сами себя пните... :) Сделайте что-то, хотя бы уровня "Scrolling TopDown Shooter".
Yakim (Watco... : (12 Июнь 2018 - 22:17) так что, думаю завтра с утреца стартану марафон)
Yakim (Watco... : (12 Июнь 2018 - 22:15) хе-хе, не сомневайся, я в чате по уе уже поинтересовался, сказали обалденный сериал)))
Yandersen : (12 Июнь 2018 - 20:56) Оооооо, поди ща залипнет на пару дней, стопудофф. :)
Yakim (Watco... : (12 Июнь 2018 - 18:28) Окей гляну)
Yandersen : (12 Июнь 2018 - 16:23) Сериал Пространство посмотри. Не по части Мехов, просто шикарен, авось ману доставит.
Yakim (Watco... : (11 Июнь 2018 - 18:50) Дуст и ты уже закаленные и пустые, надож где-то ману доставать)))
Yakim (Watco... : (11 Июнь 2018 - 18:49) Думаю, кого быть пнуть, что-бы тот пнул в ответ да по сильнее.
Yakim (Watco... : (11 Июнь 2018 - 18:48) Давненько и не маленько хе-хе, делать нечего, прокрастинирую =)
Yandersen : (11 Июнь 2018 - 17:46) Якимка, ты там шо, упоролсо маленько? Чиво картинами опспамилсо?
Yakim (Watco... : (09 Июнь 2018 - 22:27) Изображение

Фотография
- - - - -

OpenGL

OpenGL GLSL shader

  • Авторизуйтесь для ответа в теме
Сообщений в теме: 18

#1 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 06 Июль 2014 - 22:24

Если Вы пишите свой движок и графику выводите средствами OpenGL, то вот мой пакет С++ примочек, которыми пользуюсь:

 

LoadGL.h - все функции OpenGL, которые можно встретить в официальной документации (1-2, 3, 4), самозагружающиеся при первом запуске - просто, безопасно и без головной боли.

(текущая версия хеадера поддерживает OpenGL 4.5)

 

FileGLSL.hpp (требует LoadGL.h) - создание шейдерной программы и загрузка кода шейдеров из текстового файла формата *.glsl.

 

GLSL.hpp - векторная математика в стиле GLSL, насколько это применимо в обычном С++. Векторочки, матрицы, тригонметрические, векторные и все остальные функции...

 

 

---

 

 

LoadGL.h - единственный файл, который нужно кинуть в папку Вашего проекта и прописать

#include "LoadGL.h"

чтобы пользоваться всеми возможными функциями, начинающимися на gl* и glu*. Упрощение в том, что не нужно проверять поддерживаемые расширения и инициализировать указатели на функции - самоинициализация всех объявленных в файле LoadGL.h указателей происходит автоматически при первом вызове каждой из используемых функций, и даже если расширение не поддерживается, ничего страшного не произойдёт - вызов будет просто проигнорирован.

 

---

 

FileGLSL.hpp позволяет загружать коды шейдеров из файла формата *.glsl (это текстовый формат файла с GLSL кодом, редактируется блокнотом). Описанная в хеадере функция

GLuint LoadGLSL(const char* FileName)

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

Перед загрузкой кода шейдеров в OpenGL функция LoadGLSL вырезает строки с комментариями (начинающиеся на //) и сортирует код. Сортировка необходима потому, что в одном файле должны находиться коды всех типов шейдеров, из которых будет составлена требуемая графическая программа. Какой код какому шейдеру принадлежит, указывается с помощью идентификаторов в квадратных скобках:

[GL_VERTEX_SHADER]
[GL_FRAGMENT_SHADER]
[GL_GEOMETRY_SHADER]
[GL_TESS_CONTROL_SHADER]
[GL_TESS_EVALUATION_SHADER]

Весь код до первого идентификатора считается общим и дублируется для каждого шейдера. Код, который идёт строчкой ниже идентификатора, принадлежит конкретному типу шейдера. Так что при загрузке файла *.glsl функция LoadGLSL сканирует строки, игнорируя пустые и те, что начинаются на //, и собирает по отдельности строки для каждого из пяти типов шейдеров. Создаются только те шейдеры, для которых есть индивидуальный код (тот что под соответствующим идентификатором). Шейдеры компиллируются и из них составляется программа, после удачной линковки которой шейдерные контейнеры удаляются. Вот пример файла *.glsl:

//Общее для обоих шейдеров:
varying vec4 Color;

[GL_VERTEX_SHADER]

//Код вершинного шейдера

void main()
{
 Color = gl_Color;
 gl_Position = ftransform();
}

[GL_FRAGMENT_SHADER]

//Код фрагментного шейдера

uniform sampler2D Texture;

void main()
{
 gl_FragColor = Color;
}


#2 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 24 Август 2014 - 03:02

Забавную штуку обнаружил касательно перспективной проекции. По стандарту матрица строится вот так:

http://www.opengl.or...Perspective.xml

Т.е. задаются обе плоскости отсечения - ближняя и дальняя. И вот дальнюю я всегда недолюбливал, ибо это как сюр какой-то - ну что ж за камера имеет лимит по дальности видимости, да? А вот подумать если, можно ведь построить матрицу перспективной проекции так, что дальней плоскости отсечения не будет:

     | f/aspect 0     0     0    |
     |                           |
     |    0     f     0     0    |
P =  |                           |
     |    0     0    -1  -2*near |
     |                           |
     |    0     0    -1     0    |

В принципе, эта матрица будет эквивалентна стандартной при [far/near]->infinity.

Плюс в том, что такую матрицу проще построить и не нужно париться с определением выбора значения для far, ибо только ближнюю плоскость отсечения нужно задать. А минус в том, что z-Buffer не полностью используется в отличие от стандартного варианта. Но этот минус заметить трудно уже при отношении [far/near]==20, когда разница всего в 10%. С ростом значения [far/near] разница в утилизации буффера глубины для обоих вариантов становится и вовсе неразличимой.



#3 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 02 Декабрь 2014 - 03:31

Итак, древняя проблема: точность буффера глубины падает с расстоянием, из-за чего дальние объекты начинают "просвечивать" друг через друга, мерцать и дрожать. Решается проблема отодвиганием ближней плоскости отсечения как можно дальше и придвиганием дальней плоскости отсечения как можно ближе. Как результат - сложившаяся традиция не рисовать персонажа (в шутерах тела и ног не видно, нет тени от модели игрока), а также "взгляд сквозь стены" (это уже не так часто).

 

Чтобы максимально реалистично моделировать камеру, нам нужно иметь возможность установить ближнюю плоскость отсечения на расстоянии в несколько миллиметров (а не метров, как практикуется), а также избавиться от искусственной по своей природе дальней плоскости отсечения. Если не пользоваться w-buffer'ом от DirectX, то средствами OpenGL это должно решаться следующим образом...

 

Построим нестандартную матрицу перспективной проекции. Для этого нам сперва нужно выбрать несколько констант:

zNear - расстояние до ближней плоскости отсечения. Скажем, 1 мм или даже ближе.

Viewport.x, Viewport.y - ширина и высота экрана (в пикселях).

View_Angle_Vertical - угол обзора, по вертикали (мы имеем дело с перспективной проекцией).

Препрощитаем парочку констант (использующихся также и в обычной матрице перспективной проекции):

f = ctan(View_Angle_Vertical / 2);
aspect = Viewport.x / Viewport.y;

Теперь всё готово для построения матрицы проекции по моему "особому" рецепту:

    | f/aspect  0      0      0    |
    |                              |
    |    0      f      0      0    |
P = |                              |
    |    0      0      0    zNear  |
    |                              |
    |    0      0     -1      0    |

Посмотрим, как происходит трансформация точки с координатами v={x,y,z,1} при помощи такой матрицы:

Xc = x * (f/aspect);
Yc = y * f;
Zc = zNear;
Wc = -z;

Тут Xc,Yc,Zc,Wc - это т.н. "clipped coordinates" - конечный результат трансформаций. Называются эти координаты "clipped" потому, что их кушает clipper - не управляемый шейдерами участок пайплайна примитивов, занимающийся clipping'ом, т.е. обрезанием тех частей примитивов, что не попадают в рендеримый объём виртуального пространства. Внутрь видимого объёма попадают только те фрагменты, координаты которых удовлетворяют всем 6-ти неравенствам:

-Wc <= Xc <= Wc;
-Wc <= Yc <= Wc;
-Wc <= Zc <= Wc;

После клиппинга осуществляется деление компонент {Xc,Yc,Zc} на Wc и получаются "normalized device coordinates", значения которых оказываются всегда в диапазоне [-1...1]:

Xndc = Xc / Wc;
Yndc = Yc / Wc;
Zndc = Zc / Wc;

Ну и последнее преобразование - из нормализованных координат в оконные. В результате Xndc и Yndc конвертируются в координаты пиксела на экране, а Zndc преобразуется в значение глубины (depth), что идёт в z-buffer.

 

Но вернёмся к нашей необычной матрице перспективной проекции. Для компонент Xndc и Yndc трансформация ничем не отличается от того, что мы привыкли видеть при трансформации стандартной матрицей перспективной проекции. Отличие лишь в способе вычисления значения Zndc. Взглянем поподробнее:

//Result of the perspective transformation:
Xc = ...;
Yc = ...;
Zc = zNear;  // = const = 0.001 (which is 1mm)
Wc = -z;

//clipping:
-Wc <= Zc <= Wc
//is the same as:
z <= zNear <= -z
//or:
z <= zNear && z <= -zNear
//it is equivalent to:
z <= -zNear //z ≡ [-0.001...-inf)

Таким образом получается, что будут отсечены все части примитивов, что находятся позади ближней плоскости отсечения (позади камеры), а эффект дальней плоскости отсечения исчезает - ВСЁ, что впереди камеры, попадёт в видимый объём:

z ≡ [-zNear...-inf)

Напомню, что из-за того, что в системе координат OpenGL точкой отсчёта выбран левый нижний угол экрана, ось z тычет юзеру прямо в глаз, так что камера сканирует сцену в направлении -z, оттого и такая отрицательность везде, где интуитивно хочется чего-то положительного.

 

Теперь посмотрим, какая в результате получится нормализованная Z координата:

//Zndc = Zc / Wc
Zndc = zNear / -z;

Учитывая, при каком диапазоне значений z фрагменты попадают в видимый объём, можно сказать, что

Zndc ≡ [1...0)

Это значит, что для фрагментов, лежащих на ближней плоскости отсечения значение Zndc будет равно 1, а по мере удаления объекта вдаль от камеры значение Zndc будет стремиться к нулю. Т.е. чем дальше объект, тем меньше значение глубины. Из этого следует, что тест глубины нужно инверсировать по отношению к дефолтному значению:

glDepthFunc(GL_GREATER); //Default is GL_LESS

Если этого не сделать, то дальние объекты будут накладываться на близлежащие - что противоположно реальности.

 

Теперь ключевой момент. Подавляющее число фрагментов будут иметь значение Zndc около нуля. Прикинем, почему так:

    z          |  Zndc
 -0.001 (1mm)  |  1
 -0.01  (1cm)  |  0.1
 -0.1   (0.1m) |  0.01
 -1     (1m)   |  0.001
 -10    (10m)  |  1e-4
 -100   (100m) |  1e-5
 -1000  (1km)  |  1e-6

Заметна обратноэкспоненциальная зависимость. И поскольку большинство отображаемых на экран фрагментов сцены находятся на расстоянии в 1-100 метров, то и глубина получается в диапазоне 1е-3..1е-5. Однако поскольку графические процессоры оперируют с числами в формате float, т.е. с плавающей запятой, околонулёвость не страшна до тех пор, пока значение Zndc не приблизится к пределу, при котором ещё сохраняется 23 бита точности (что-то около 1e-104, если не ошибаюсь) - ну т.е. тысячи километров рисовать как бы можно. Теоретиццки. На практике подвох заключается в том, что по стандарту буффер глубины имеет формат не float, а int, причём 24-битный. Результат конверсии - все околонулевые значения фрагментов, хорошо различимые в формате float внезапно примут одно и то же значение, перейдя в формат int - нуль.

 

Чтобы этого избежать нам необходимо отказаться от стандартного буффера глубины и использовать 32-битный float z-buffer (это расширение, к счастью, уже давным давно в стандарт вошло). Для этого нам придётся рендерить в отдельный FBO, что не так уж страшно, ибо в стандартный фреймбуффер никто серьёзный напрямую и не рендерит сегодня (deffered shading, antialiazing и пр. изъёбы в стандартном фреймбуффере не провернёшь).

 

И всё было бы гладко, и конец сказке, если бы не один маленький гвоздь в заднице, специфичный именно для проклятых судьбой OpenGL-неудачников. Дело в том, что глубина фрагмента (depth), которая записывается в буффер глубины, получается путём преобразования Zndc в оконные координаты. По традиции считается, что Zndc должен лежать в симметричном диапазоне [-1...1], и этот диапазон должен быть приведён к диапазону значений, специфичному для буффера глубины, т.е. [0...1]:

depth = a * Zndc + b (в простейшем стандартном случае: depth = 0.5 * Zndc + 0.5)

Проблема в том, что при прибавлении 0.5 к 1е-ххх мы получим те же 0.5, т.е. точность потеряется - все наши околонулевые значения станут неразличимыми после прибавления к ним числа, на много порядков бОльшего. Чтобы этого избежать, нам необходим прямой маппинг (a=1,b=0), тогда глубина фрагментов пойдёт в буффер глубины без изменений, и наши околонулевые значения глубины фрагментов таковыми и останутся, сохранив свои 23 бита мантиссы:

depth = 1 * Zndc + 0 = Zndc

 

К сожалению, эта фича прямого маппинга, изначально доступная счастливым пользователям DirectX стала доступна ОпенГЛьщикам лишь недавно с введением расширения ARB_clip_control в составе OpenGL4.5. Вот так она включается:

glClipControl(GL_LOWER_LEFT,GL_ZERO_TO_ONE);

Если мои прикидки верны, то буффер глубины, организованный по изложенному выше алгоритму должен быть способен различать поверхности с точностью 12см на расстоянии в 1000 км (!) при ближней плоскости отсечения в 1мм. Весьма не дурно, не правда ли?  ;)

Ну т.е. фишка в том, что точность линейно падает с расстоянием. Учитывая то, что и детализация объектов, рисуемых в отдалении, также уменьшается с расстоянием, можно сказать, что панацея от z-fighting'а скоро станет доступной и пользователям OpenGL, как только версия 4.5 войдёт в состав релиз-версий драйверов (сейчас только в бета-версиях идёт). Однако "скоро" значит далеко не завтра, к сожалению...  :P

 

Бета-дрова для nVidia скачать можно тут:

https://developer.nv...m/opengl-driver

Вот поставил щас 340.82 - из 4.5. не поддерживается только версия шейдерного языка (ну и пёс с ней). А glClipControl, вроде, пашет.  :)

 

Апдэйт (13 Апреля 2015):

Накатал статейку по новому способу построения камер для ОпенГЛ.



#4 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 04 Декабрь 2014 - 05:12

Насчёт матриц ориентаций - их МОЖНО понять. Щас я объясню как, а заодно и сам вспомню и ещё раз пойму.  :)

Как мы помним из основ векторной алгебры, система координат представляет собой тройку направляющих векторов (i, j, k), ако координатных осей, а также имеет координаты центра (p), обозначающие точку, где центр системы координат находится. Векторы буду жирным выделять, окейда. Так вот для внешней системы отсчёта (по отношению к рассматриваемой), векторы i, j, k - это такие же трехкомпонетные векторочки, имеющие конкретные координаты.

Ну а какой физический смысл имеют компоненты вектора v={x,y,z}? Эти числа есть множители для соответствующих им направляющих векторов системы. Если x умножить на i, y умножить на j, z на k, и всё это сложить с p, то получим координаты точки во внешней системе кординат. Другими словами, x,y,z показывают, сколько раз нужно взять базисные векторы i, j, k рассматриваемой системы, чтобы сложив их с p получить координаты точки во внешней системе координат. Вот как это выглядит, если расписать:

 

координаты точки v в её собственной системе отсчёта с центром в точке p, и координатными осями i, j, k:

v = {x,y,z}

координаты точки v во внешней системе отсчёта:

v = {x*i + y*j + z*k + p} =

{ (x*ix + y*jx + z*kx + px),

  (x*iy + y*jy + z*ky+ py),

  (x*iz + y*jz + z*kz + pz) }

 

А теперь взглянем на то, как координаты вектора трансформируются матрицей в OpenGL:

|  m0   m4   m8    m12  |     | x |
|                       |     |   |
|  m1   m5   m9    m13  |     | y |
|                       |  x  |   |  =
|  m2   m6   m10   m14  |     | z |
|                       |     |   |
| m3=0 m7=0 m11=0 m15=1 |     | 1 |

{ (x*m0 + y*m4 + z*m8 + m12),  //x'
  (x*m1 + y*m5 + z*m9 + m13),  //y'
  (x*m2 + y*m6 + z*m10 + m14), //z'
  1 }                          //w'

Несложно провести аналогию и сделать логичный вывод: первые 3 столбца матрицы - это векторы i={m0,m1,m2}, j={m4,m5,m6}, k={m8,m9,m10}, задающие ориентацию рассматриваемой системы координат во внешней системе отсчёта, а четвёртый столбец матрицы - это координаты вектора p={m12,m13,m14}, указывающий координаты центра данной системы координат во внешней СО.

 

Ну, как минимум я понял, что из себя физически представляет матрица трансформации - она описывает систему координат.  :)  Так что для каждого 3D объекта мы имеем меш вершин (координаты которых соответствуют собственной системе отсчёта) и матрицу трансформации, описывающую ориентацию и положение этой системы в мире. Умножая координаты вершины на матрицу трансформации мы получаем координаты вершины в мировой системе координат.

 

Теперь разберёмся с модельновидовой матрицей. Она преобразует координаты из мировой системы координат в систему координат наблюдателя (точнее, объектные, а не мировые координаты, но так проще понимать). Из этого следует, что МВМ описывает не систему координат камеры в мировой системе отсчёта, как в случае обычных матриц трансформации всех объектов сцены, а наоборот, она описывает систему координат мира в системе отсчёта наблюдателя. Значит чтобы узнать направления координатных осей камеры в мировой СО, нам нужно сперва посчитать обратную матрицу для модельновидовой. Обратная модельновидовая матрица, получается, будет описывать систему координат камеры в мировой системе координат, а значит столбцы будут соответствовать осям камеры.

 

Получится, что координаты вектора, показывающего направление "взгляда" камеры (ось -Оz в OpenGL, т.е. вектор -k) - это третий столбец обратной модельновидовой матрицы, взятый со знаком минус; направление "макушки" (ось Оy, т.е. вектор j) - это второй столбец обр. матрицы, а четвёртый столбец - это координаты камеры на сцене (вектор р).

 

Тут есть пара трюков и упрощений. Если взять левый верхний участок 3х3 модельновидовой матрицы, то он будет содержать три перпендикулярных вектора единичной длины (базисные векторы же). Как-то так получается, что после инверсии этот 3х3 участок всегда выглядит как тупо-транспонированный оригинал. Этим можно воспользоваться вот как: чтобы узнать направление осей камеры, не нужно находить обратную матрицу - можно сразу взять вместо столбцов строки. Т.е. чтобы узнать направления координатных осей камеры в объектной (или мировой, в простейшем случае) системе отсчёта, мы берём первые три элемента строк модельновидовой матрицы. А если нам нужно узнать направления координатных осей объекта в системе координат камеры - то мы берём столбцы модельновидовой матрицы.

 

---

 

А, кстать, неймётся поделиться моей памяткой насчёт векторных троек. В OpenGL используется правосторонняя система координат. Чтоб её визуализировать корчу пальцы правой руки так: указательный палец - это ось Ох, вытягиваю указательный вперёд; средний палец - ось Оу, его в позу фака перпендикулярно ладони; большой палец - это ось Оz, его оттопыриваю в позу "кульно". Можно примерить такую фигуру на экран: указательный палец пусть смотрит направо, средний вверх - получается, что большой палец тычет в глаз. Вот так выглядит экранная система координат OpenGL. Настолько же неудобная, как и поза, в которой приходится держать правую руку перед экраном, чтоб её визуализировать.  :)



#5 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 05 Декабрь 2014 - 04:31

---

 

Помарал бумажки в пыпытках найти простой способ нахождения обратной матрицы для типичной матрицы трансформации (например, модельновидовой). Для чайников: если матрица производит преобразование координат из системы отсчёта №1 в систему отсчёта №2, то обратная матрица работает в обратную сторону - трансформирует координаты из СО №2 в СО №1.

Способ вычисления обратной матрицы оказался безумно простым. На практике в коде проверил - всё верно. так что вот щас истины глаголить буду.

 

Итак, типичная матрица трансформации имеет вид:

|  Ix   Jx   Kx   Px  |
|                     |
|  Iy   Jy   Ky   Py  |
|                     |
|  Iz   Jz   Kz   Pz  |
|                     |
|  0    0    0    1   |

Она характеризует систему координат объекта в мировой системе координат. Трансформируя с её помощью координаты вершины из меша мы получаем координаты этой вершины на общей сцене. В случае модельновидовой матрицы трансформация идёт из СО объекта в СО камеры. Общее у всех этих матриц, как и у координатных систем, которые они представляют - это то, что направляющие векторы (I,J,K):

1) имеют единичную длину;

2) перпендикулярны друг другу;

3) образуют правую тройку векторов.

Если матрица отвечает этим характеристикам (модельновидовая всегда отвечает), то обратная матрица будет выглядеть следующим образом:

|   Ix    Iy    Iz   -(Ix*Px+Iy*Py+Iz*Pz)  |
|                                          |
|   Jx    Jy    Jz   -(Jx*Px+Jy*Py+Jz*Pz)  |
|                                          |
|   Kx    Ky    Kz   -(Kx*Px+Ky*Py+Kz*Pz)  |
|                                          |
|   0     0     0             1            |

Надеюсь, знать этот трюк полезно будет не только мне.  :)



#6 OFFLINE   PA3UJIb

PA3UJIb

    Серый

  • Создатель
  • 171 сообщений

Отправлено 05 Декабрь 2014 - 18:01

/** генерирование матрицы 4х4, определяющей точку взгляда
@param _position вектор, куда будет смотреть наблюдатель
@param _center вектор, откуда будет смотреть наблюдатель
@param _up вектор, определяющий положение наблюдателя
*/
const mat4 makeViewpoint(const vec3& _position, const vec3 &_center, const vec3 &_up)
{
vec3 f = _viewpoint - _position; //Forward direction (Oz axis)
vec3 s = cross(_up, f); //Side direction (Ox axis)
if(dot(s,s)==0.0)return mat4(1); //Return identity matrix on error
s = normalize(s);
f = normalize(f);
vec3 u = cross(f, s); //Up direction (Oy axis)

return mat4(s.x, u.x, f.x, 0,
            s.y, u.y, f.y, 0,
            s.z, u.z, f.z, 0,
            -dot(s, _position), -dot(u, _position), -dot(f, _position), 1);
}
Вот такое будет преобразование. Что такое dot(), cross() и normalize() думаю и так ясно.

 


#7 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 26 Декабрь 2014 - 22:22

Функция нахождения инверсной матрицы для матрицы 4х4, вдруг кому надо:

void inverse4x4(const float* M, float *I){
 //Calculate intermediate values
 float tmp[12] = { M[10]*M[15] - M[11]*M[14],
                   M[ 6]*M[15] - M[ 7]*M[14],
                   M[ 6]*M[11] - M[ 7]*M[10],
                   M[ 2]*M[15] - M[ 3]*M[14],
                   M[ 2]*M[11] - M[ 3]*M[10],
                   M[ 2]*M[ 7] - M[ 3]*M[ 6],
                     M[ 8]*M[13] - M[ 9]*M[12],
                     M[ 4]*M[13] - M[ 5]*M[12],
                     M[ 4]*M[ 9] - M[ 5]*M[ 8],
                     M[ 0]*M[13] - M[ 1]*M[12],
                     M[ 0]*M[ 9] - M[ 1]*M[ 8],
                     M[ 0]*M[ 5] - M[ 1]*M[ 4] };
 //Calculate cofactors
 //First column
 I[0] = tmp[ 0]*M[ 5] - tmp[ 1]*M[ 9] + tmp[ 2]*M[13];
 I[1] = tmp[ 3]*M[ 9] - tmp[ 0]*M[ 1] - tmp[ 4]*M[13];
 I[2] = tmp[ 1]*M[ 1] - tmp[ 3]*M[ 5] + tmp[ 5]*M[13];
 I[3] = tmp[ 4]*M[ 5] - tmp[ 2]*M[ 1] - tmp[ 5]*M[ 9];
 //Second column
 I[4] = tmp[ 1]*M[ 8] - tmp[ 0]*M[ 4] - tmp[ 2]*M[12];
 I[5] = tmp[ 0]*M[ 0] - tmp[ 3]*M[ 8] + tmp[ 4]*M[12];
 I[6] = tmp[ 3]*M[ 4] - tmp[ 1]*M[ 0] - tmp[ 5]*M[12];
 I[7] = tmp[ 2]*M[ 0] - tmp[ 4]*M[ 4] + tmp[ 5]*M[ 8];
 //Third column
 I[8] = tmp[ 6]*M[ 7] - tmp[ 7]*M[11] + tmp[ 8]*M[15];
 I[9] = tmp[ 9]*M[11] - tmp[ 6]*M[ 3] - tmp[10]*M[15];
 I[10] = tmp[ 7]*M[ 3] - tmp[ 9]*M[ 7] + tmp[11]*M[15];
 I[11] = tmp[10]*M[ 7] - tmp[ 8]*M[ 3] - tmp[11]*M[11];
 //Forth column
 I[12] = tmp[ 7]*M[10] - tmp[ 6]*M[ 6] - tmp[ 8]*M[14];
 I[13] = tmp[ 6]*M[ 2] - tmp[ 9]*M[10] + tmp[10]*M[14];
 I[14] = tmp[ 9]*M[ 6] - tmp[ 7]*M[ 2] - tmp[11]*M[14];
 I[15] = tmp[ 8]*M[ 2] - tmp[10]*M[ 6] + tmp[11]*M[10];
 //Calculate determinant
 float det = M[0]*I[0]+M[4]*I[1]+M[8]*I[2]+M[12]*I[3];
 //Check if determinant is zero and divide resulting matrix component-wise
 if(det*det==0.0)return;
 else{
  float det_r = 1.f/det;
  for(int i=0;i<16;i++)I[i] *= det_r;
 }
}//inverse4x4-------------------------------------------------------------------

Её вариант, написанный в ассемблере (рассчёт идёт на мат-сопроцессоре) выкладывать не буду, так как там страниц пять кода (два дня писал), а быстродействие лишь в 4 раза больше. Ассемблерный вариант будет в новом GLSL.hpp.



#8 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 30 Декабрь 2014 - 23:15

Пожертвовал день для разбора этих грёбаных 16-битных половинных флоатов (GL_HALF_FLOAT). Вот функция для конверсии float в half_float:

unsigned short int floatToHalf(float x){
 //Separate components
 unsigned long int f = *(unsigned long int*)&x,
                   fm = f & 0x007FFFFF,
                   fe = (f & 0x7F800000)>>23,
                   fs = (f & 0x80000000)>>16;
 //Zero or denormal / underflow resulting in denormal or zero
 if(fe<112)return short int(fs);
 //Infinity or NaN
 if(fe==255){
  //NaN: ensure NaN converts to NaN
  if(fm)return short int(fs|0x7C01|(fm>>13)); //NaN
  else return short int(fs|0x7C00); //Infinity
 }
 //Overflow resulting in infinity
 if(fe>142)return short int(fs|0x7C00);
 //Normal
 return short int(fs|((fe-112)<<10)|(fm>>13));
}


#9 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 13 Апрель 2015 - 20:11

Накатал статейку по новому способу построения камер для ОпенГЛ.



#10 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 14 Октябрь 2015 - 22:12

Курвы Безье, товарищи - как их рисовать?
Пусть у нас есть две 3D точки (Va, Vb), через которые мы хотим провести изогнутую линию так, чтобы эта линия была перпендикулярна нормалям в этих точках (Na, Nb). Чтобы нарисовать требуемую линию нам нужно вычислить координаты нескольких промежуточных точек на желаемой кривой и соединить эти точки отрезками.
 
Введём коэффициент "k" для разбиения:
k = [0...1], если k==0, значит промежуточная точка находится у Va, если k==1, то мы у конца где Vb.
И добавим ещё один параметр, "Curvature", контролирующий степень изогнутости курвы:
Curvature = (0...1), Curvature==0 соответствует прямой линии, при Curvature>1 курва может выпячиться так, что узлом изогнётся в средней точке, что нам не нужно. Наилучшее значение: 0.5.
Итак, вот функция для вычисления промежуточных точек на курве:

#include "GLSL.hpp"
bool TessellateEdge(const vec3& Va, const vec3& Na, const vec3& Vb, const vec3& Nb,
                    vec3* Vm, vec3* Nm,
                    float k, float Curvature=0.5f){
 //Check for input errors
 if(k<0.f||k>1.f||Curvature<=0.f||Curvature>=1.f)return false;
 //Precalculate some values, check for errors
 vec3 AB = Vb-Va; //Direction from Va to Vb
 if(dot(AB,AB)==0.f){ //Va==Vb
  if(Vm)*Vm = Va;
  if(Nm)*Nm = normalize(Na*(1.f-k)+Nb*k);
  return true;
 }
 if(dot(cross(AB,Na),cross(AB,Nb))<=0.f)return false; //"Möbius" problem
 //Calculate curve' tangents
 vec3 Ta = Curvature*(AB-Na*dot(AB,Na)),
      Tb = Curvature*(Nb*dot(AB,Nb)-AB),
      DF = AB+Tb-Ta,
      J = k*((2.f-k)*DF - k*Tb) + Va + Ta,
      H = k*(k*DF + (2.f-k)*Ta) + Va,
      Tm = J-H;
 //Calculate coordinates of the middle point
 if(Vm)*Vm = H*(1.f-k)+J*k;
 //Calculate normal at the middle point
 vec3 P = Va+normalize(cross(Na,AB)),
      Q = Vb+normalize(cross(Nb,AB)),
      PQ = Q-P,
      RS = PQ+Tb-Ta,
      L = k*((2.f-k)*RS - k*Tb) + P + Ta,
      W = k*(k*RS + (2.f-k)*Ta) + P,
      U = W*(1.f-k)+L*k;
 if(Nm)*Nm = normalize(cross( Tm, U - (*Vm) ));
 return true;
}


#11 OFFLINE   PA3UJIb

PA3UJIb

    Серый

  • Создатель
  • 171 сообщений

Отправлено 16 Октябрь 2015 - 14:08

Тут АВ - грань, a.N,b.N - нормали у её краёв

 
Кривая Безье тоже строится по 4 точкам - они определяют два конца и две касательных к кривой.
Bezier.png
С нормалями да, это ты удачно придумал, потому как в Безье касательные могут и пересекаться, что приведет к излому нормалей.


 


#12 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 17 Октябрь 2015 - 09:47

С нормалями да, это ты удачно придумал, потому как в Безье касательные могут и пересекаться, что приведет к излому нормалей.

Точнее, курва может узлом завернуться. Чтобы не пересекались, необходимо, чтобы длина каждой была не больше половины длины между опорными точками. В моём способе длина каждой из касательной ограничена способом построения: чем ближе нормаль к перпендикуляру грани, тем длиннее касательная. Максимум длины касательной равен длине грани. Так что если обе нормали почти перпендикулярны грани, их касательные пересекли бы друг друга пополам или совпали бы. Поэтому я скалирую их длину на пармаетр Curvature и рекомендую значение 0.5, при котором излома гарантированно не может быть.

Но вот вычисление нормали в точке разбиения - это уже что-то нестандартное. При построении курвы мы получаем вектор тангенты в точке разбиения, а не нормали. Чтоб нормаль получить, нужно эту тангенту с битангентой векторно перемножить. А где битангенту взять? Для этого я строю вспомогательную курву сбоку от оригинальной и синхронно разбиваю обе. Вектор между точками разбиения я и использую как битангенту. Шаманство чистой воды.  :)



#13 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 28 Октябрь 2015 - 11:30

А теперь поверхности Безье, товарищи! Точнее, треугольные патчи.

 

Допустим, у нас есть треугольник, для вершин которого указаны позиция и нормаль (3 х PN). Наша задача - преобразовать плоский треугольник в гладкую поверхность из множества треугольников так, чтобы нормали у вершин соответствовали перпендикулярам к поверхности в этих крайних точках. Кароч, сделаем из плоского треуга гладкую поверхность.

 

Как это сделать? На основании трёх контрольных точек вычислим кординаты промежуточных точек, ну а дальше - поверхность Безье и в меш её.

 

Не очевидная на первый взгляд проблема треугольных патчей Безье в том, что если мы преобразуем два смежных треугольника в гладкие поверхности, то, скорее всего, щели между ними не будет (C0 continuity), но вот шов всё равно будет заметен из-за несовпадения нормалей на границах (C1 continuity). Нормали в крайних точках-то совпадут по-любому, а вот на гранях - вовсе не обязательно.

 

Нашёл решение проблемы тут. Оказывается, чтоб поверхности Безье стыковались без щели (C0 continuity), нужно, чтоб контрольные точки на границе патчей были одинаковыми. А чтобы шва не было видно, т.е. чтоб нормали на границе одинаковые получались (C1 continuity), "панели" на границе должны быть копланарными и симметричными.

 

Основываясь на этом материале я разработал метод, позволяющий строить поверхность по трём опорным точкам (позиция+нормаль) таким образом, чтобы любые два независимых патча стыковались без шва. Т.е. каждый патч задаётся тремя опорными точками, и любые патчи, у которых 2 из трёх опорных точек совпадают, будут стыковаться без шва (C1 continuity).

 

Очевидно, что для этого нужен особый способ построения поверхности. Как оказалось, достаточно взять поверхность 7-го уровня (7 панелей на границах) и сжать в точку по 2 панели с каждого края (отсингхулярить их к вершинам). Таким образом убирается зависимость между общими панелями соседних граней (в отличие от треугов, две совпадающие точки всегда "симметричны" и "копланарны"). А опорные точки оставшихся 3х панелей вычисляются на основании двух вершин соответствующей грани, тем самым не зависят от третьей вершины. Так что если два треуга шарят две опорные точки, то и смежные панели у них получатся симметричными, а значит и полученные поверхности будут стыковаться без шва.

Что интересно, так это то, что 3 панели каждой грани можно вытягивать вглубь (масштабировать все на одинаковую величину), и при этом связность патчей не нарушится. Это полезное свойство позволяет контролировать кривизну, и при этом не нарушает требуемого свойства бесшовности.

 

:!: Единственный нюанс данного метода в том, что при вычислении координаты и нормали точки на поверхности по её барицентрической координате нужно проверять отдельно, не является ли данная точка вершиной треуга, ибо из-за сжатия крайних панелей нормаль там получается нулевой длины. Просто нужно исходные контрольные точки хранить отдельно и подставлять их значения если барицентрическая координата равна (1,0,0), (0,1,0) или (0,0,1).

 

Короче, вот весь проект вместе с экзешником (в папке Release):

 

Прикрепленный файл  BezierDemoFullProject.zip   247,38К   123 - Раз(а) скачано

 

Демонстрирует построение независимых патчей из двух рандомных треугов, шарящих 2 вершины. Поверхности строятся по своим правилам, но независимо друг от друга, и при этом совпадают без шва. Не чудо ли это?  :)

 

Управление:

Esc - выход;

Enter - полноэкранка;

WASD,C,Space - перемещение камеры;

мышь, Q,E - вращение камеры;

R - реинициализировать поверхности (новые рандомные позиции и нормали);

+- - увеличить/уменьшить детализацию;

L - показать/скрыть линии меша;

N - показать/скрыть нормали у вершин;

P - показать/скрыть нормализованные срединные перпендикуляры треугольников меша.

 

Что из всего этого следует? Гладкая сегментная броня по трём опорным точкам на сегмент - реализуемо!



#14 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 31 Октябрь 2015 - 21:36

Продолжаю работать над стыковкой патчей. В погоне за идеалом бесшовности дошёл до поверхностей 9-го уровня. Поверхность выглядит немного "сложнее", зато переход между патчами стал совсем незаметным. Вот сравните:

Прикрепленный файл  Bezier9DemoFullProject.zip   245,2К   123 - Раз(а) скачано

 

Код функции выложишь?

См. файл Tessellator.hpp - там всё и описано. Хотя разобраться в способе построения врядли удастся, ибо в папке нет файла геометрической модели построения, что я в Риноцерусе наваял и карты топологического размещения контрольных точек, что у меня на листиках накаляканы, так что подразумевается вариант "инклюд-н-йюз" класса CTessellator, который, собсно и юзается в основном файле WindowGL.cpp.

 

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

 

Затем запускаем функцию построения меша, которая спрашивает, сколько промежуточных вершин будет всунуто в каждую грань исходного треугольника при разбиении. Если ввести 0, то будет один треуг, если единицу, то каждая грань будет пополам разбита (4 треуга), когда 2 вершины в грани, то 9 треугов выйдет, 3 - 16, и т.д.

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

 

Вопщем, когда меш построен, можно опрашивать класс на предмет вершинных аттрибутов, задавая индекс вершины, а также треугольников, содержащих 3 индекса.

 

В демошке я юзаю обычные функции glVertex3fv и glNormal3fv, т.е. рисовка неоптимальная. Ибо класс тесселлятора предназначен чисто для построения меша, а не для рисовки. Подразумевается, что после потроения меш будет переконвертирован в нужный юзеру формат и сохранён в файл или что-то вроде того. Хотя я размышляю над добавлением функции, которая будет лепить треуги в стрипсы и сохранять меш в файл *.glm (GL Model), но этот формат нужно сперва ещё доработать, возможно.

 

И не совсем ясно пока с наложением текстурных координат: они генерируются независимо для каждого меша ведь, так что, получится, по своей текстуре на меш. А чтоб меши лепить друг к другу, надо бы текстуры и тангенты с битангентами совмещать чтоле. Когда меш к мешу стыкуешь, можно текстурную карту стыкуемого меша повернуть, масштабнуть, сдвинуть и битангенты с тангентами пересчитать, чтоб всё совпало. Но это не всегда реально: что если 6 мешей в шестиугольник стукуются? Циклозависимость. Так что я думаю, пусть так и остаётся, по своей текстуре на сегмент брони.



#15 OFFLINE   SHW

SHW

    Разработчик

  • Создатель
  • 32 сообщений
  • Откуда:Самара
  • Настоящее имя:Слава

Отправлено 22 Ноябрь 2015 - 13:58

Это, конечно, замечательно. Но, думаю, стоит всё это переписать на тесселяционные шейдеры, раз уж используете современный OpenGL. Бонусом получите динамический ЛОД и экономию видеопамяти. А если уж очень хочется экспорта, то можно будет буфером возврата запечь.

Аппаратная тесселяция вроде уже давно подобное обеспечивает.


Your mind is software. Program it.
Your body is a shell. Change it.
Death is a disease. Cure it.

#16 OFFLINE   Yandersen

Yandersen

    Диванный теоретик

  • Админ
  • 454 сообщений
  • Откуда:Canada
  • Настоящее имя:Ян

Отправлено 23 Ноябрь 2015 - 08:32

Рассматривал такой вариант. Для поверхностей третьего уровня это вполне рационально, и даже алгоритмы готовые встречал, но они не дают идеальный результат - там читы некоторые для рассчёта нормалей используют. А у меня 9-ый уровень, это ппц насколько больше обсчёта. Не думаю, что для реал-тайма это годно. Плюс вычисление тангент с битангентами требует апроксимации 6-ти смежных треугов. Не, проще меш хранить, ИМХО.



#17 OFFLINE   PA3UJIb

PA3UJIb

    Серый

  • Создатель
  • 171 сообщений

Отправлено 12 Апрель 2016 - 15:54

 

Предвариловка того, над чем работаю в данный момент


 


#18 OFFLINE   lz

lz

    True Warrior

  • Админ
  • 213 сообщений

Отправлено 13 Апрель 2016 - 09:08

Вкратце, что задумывается?


Polygon-4
Документация и инструкции по установке доступны по ссылке:
 

#19 OFFLINE   PA3UJIb

PA3UJIb

    Серый

  • Создатель
  • 171 сообщений

Отправлено 13 Апрель 2016 - 19:40

Собственно, это проверка узких мест в движке, выработка каких-то дополнительных классов и структур. Хочу дать себе понять, как создавать игру.

А геймплей прост, как пять копеек:

задача - пройти все линии поля (в клетки еще вписать хочу текстуру, чтобы по мере прохождения поля проявлялась картинка),

также следует избегать встречи с противниками, количество и скорость которых растет от уровня к уровню.


 






Темы с аналогичным тегами OpenGL, GLSL, shader

Количество пользователей, читающих эту тему: 0

0 пользователей, 0 гостей, 0 анонимных