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


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

Folgen : (2 дней назад) Спс.
GranMinigun : (2 дней назад) Готово. Добро пожаловать на форум, механоид.
GranMinigun : (2 дней назад) Указать, кого именно активировать, например.
Гость : (2 дней назад) Активируйте акк. Хз, нужно что-либо указывать дополнительно для этого в чате, или админы сами всех подряд активируют, кто в очереди на активацию?
GranMinigun : (неделю назад) https://forums.unrea...sed-on-gis-data
Yakim (Watco... : (неделю назад) Аа да? Ну окей)
GranMinigun : (неделю назад) А это даже не обсуждается!
Yakim (Watco... : (неделю назад) А кто сказал что мы пьянели?)
GranMinigun : (неделю назад) Ну что, товарищи, протрезвели?
Yakim (Watco... : (2 недель назад) сяп)
Гость : (2 недель назад) Егорыч на праздники с каникул вернулся. За это тост! Всем маны!
lz : (3 недель назад) Наоборот.
Гость : (3 недель назад) Позвольте уточнить, для будущего наркомана прошлое это будущее или наоборот?
lz : (3 недель назад) Как ты его поймаешь, когда он знает, где ты его будешь ловить?
GranMinigun : (3 недель назад) Ловите наркомана из будущего!
PA3UJIb : (3 недель назад) С новым 2018 годом! А то старый-то 2018 мы и не видели даже
Yakim (Watco... : (3 недель назад) С наступающим)
Yakim (Watco... : (3 недель назад) гы
lz : (3 недель назад) Посоны, с наступающим всех!
Yakim (Watco... : (3 недель назад) +
lz : (3 недель назад) Много капитанства.
lz : (3 недель назад) +
Yakim (Watco... : (3 недель назад) ну такое
Gaantro : (3 недель назад) Описывает принцип работы.
Gaantro : (3 недель назад) Антиграв*
Gaantro : (3 недель назад) Система Grable в Star Citizen (Антигуа) : https://youtu.be/2VkzHbJiCAo
Yakim (Watco... : (3 недель назад) Ага :lol:
lz : (3 недель назад) Чё там у нас? Пошла жара? Главное, чтобы не такое генерилось https://youtu.be/RvAwB7ogkik
Moh : (3 недель назад) Мне, да привет? Ну привет )
Гость : (3 недель назад) И с Новым годом.
Гость : (3 недель назад) Шучу. От Крогота.
Гость : (3 недель назад) От Гостя.
Гость : (3 недель назад) Увидите моха передайте ему привет.
Yakim (Watco... : (3 недель назад) :lol: :P
lz : (3 недель назад) Лан, чё ты, нормально же общались, это нарицательное)
Yakim (Watco... : (4 недель назад) Еще и "Мехах" с маленькой буквы, бесстыжий :D
GranMinigun : (4 недель назад) Был.
lz : (4 недель назад) В мехах был такой рейтинг? Не помню уже.
lz : (4 недель назад) Опасный Яким)
Yakim (Watco... : (4 недель назад) За "маханойдов" и двор, стреляю в упор :D
GranMinigun : (4 недель назад) Я не это имел в виду!
Yakim (Watco... : (4 недель назад) :lol:
lz : (4 недель назад) За механойдов наверное.
lz : (4 недель назад) Всё, рубанул.
lz : (4 недель назад) Ща мы их.

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

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К   105 - Раз(а) скачано

 

Демонстрирует построение независимых патчей из двух рандомных треугов, шарящих 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К   102 - Раз(а) скачано

 

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

См. файл 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

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

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