Razum писал(а):
А что касается хдр, он вроде неплох.
Ах блин, это ж не HDR - это Bloom! ХДР - это эмуляция подстраивания глаза к сменившейся яркости картинки: все пикселы суммируются, вычисляется средняя яркость, и по ней подстраиваются яркости всех источников света или просто вычисляется новый коэффициент для яркости каждого пиксела. Симулируется адаптация глаза к темноте/светлоте. А Блум - это как раз продемонстрированный Трёхмерным эффект: засвеченные пикселы размазываются в некотором радиусе, симулируя рассеяние интенсивного пучка света на сетчатке глаза. Если бы наш хрусталик был идеально прозрачным, а сетчатка полностью поглощала весь падающий на неё свет, Блума бы в реале не было.
Ладно, я обещал о тенях рассказать.
Добавлено спустя 1 час 55 минут 59 секунд:
Учти, совсем на пальцах у меня не выйдет. И ещё нужен рендер в текстуру, т.е. FBO придётся юзать.
Знач, два прохода для карты тени - сначала рендер сцены в текстуру с камерой в позиции светильника.
Размер карты тени "традиционно" больше, чем разрешение экрана, а рендер карты тени идёт в текстуру в отдельный ФБО. Тут миллион подводных камней - с созданием ФБО не один десяток костылей сломаешь. Отдельно писать буду.
Перед тем, как отобрать камеру у наблюдателя, запушай матрицу проэкции и модельновидовую матрицу (glPushMatrix()), чтобы можно было после рендера тени легко вернуть камеру на место.
Установка камеры в позицию светильника - одна из наивеличайших головомоек: нужно, чтобы весь видимый наблюдателем объём сцены входил в зону видимости при рендере от светильника. А ещё консервативные чайники из инета напоминают, что нужно не забыть о тех объектах, что вне зоны видимости наблюдателя, но отбрасывают тени на видимые объекты. Для второго условия я изобрёл хитрый шейдерный чит, которым поделюсь позже. Обязательное условие - вся усечённая пирамида видимости наблюдателя должна входить в зону видимости при рендере карты тени от светильника.
Другими словами, все 8 вершин усечённой пирамиды видимости наблюдателя должны войти в карту тени. А это значит, что трансформируя эти вершины требуемой матрицей для карты тени, координаты всех 8-ми вершин должны лежать в диапазоне (-1...1).
Для этого тебе нужно знать мировые координаты 8-ми вершин усечённой пирамиды видимости наблюдателя. Это делается так:
1) умножь матрицу проекции на модельновидовую матрицу (т.е. строки матрицы проэкции на столбцы модельновидовой) - получится результирующая матрица трансформации (эквивалент gl_ModelViewProjectionMatrix):
gl_ModelViewProjectionMatrix = gl_ProjectionMatrix * gl_ModelViewMatrix;
Матрица трансформации нам ещё понадобится - назовём её "To" (
Transformation matrix of
observer - матрица трансформации наблюдателя). К тому же, из этой матрицы мы можем вытянуть направление взгляда наблюдателя - это первые три члена третьего ряда этой матрицы. Назовём этот вектор "Zo" (
Z axis of
observer - ось Z наблюдателя).
2) После этого найдём обратную матрицу для To и назовём её "iTo" (
inverse
Transformation matrix of
observer - обратная матрица трансформации наблюдателя). Эта матрица трансформирует вершины из экранной системы координат в объектные.
Функциёвины спирания, умножения матриц, нахождения обратных могу дать, если надо.
3) Теперь можно найти объектные координаты любой видимой на экране точки, трансформировав их этой матрицей. Чтобы найти координаты углов пирамиды видимости, нужно "трансформировать углы экрана": левый край имеет координату x=-1, правый - это x=1, верх экрана - это y=1, а низ - это у=-1 (в ОпенГЛ). И тут нужно не забывать, что твой плоский монитор считается квадратным ящиком, где ближняя плоскость экрана имеет координату z=-1, а дальняя грань - z=1. Вот 8 углов этого ящика (x,y,z,1) умножь на iTo, раздели на получившиеся w, и получишь объектные координаты своего кубического экрана расфлюстраченного в усечённую пирамиду. Эти 8 точек (c[0],c[1],...,c[7])должны быть видны при рендере карты тени от светильника. Обязательное условие.
Упростим задачу до направленного света. Так мы имеем прямоугольник видимости, ортогональную проекцию и меньше всего геморроев.
Но над матрицей трансформации придётся потрудиться всё равно.
При рендере от светильника расположим камеру так, чтобы направлением взгляда стал вектор направления света "Zl" (
Z axis of
light - ось Z света) а ориентиром вверх пусть будет... допустим, направление взгляда наблюдателя. Если солнце печёт наблюдателю лысину, то при рендере от солнца пирамида видимости наблюдателя выглядит как почти-треугольник (трапеция
) с широким основанием в верхней части экрана, и сужением к середине нижней части экрана. Опространствил картину?
Начнём составление матрицы для рендера карты тени!
Сперва вычислим координатные оси матрицы трансформации светильника:
//Нормализуем ось Z светильниковой системы координат (направление света):
Zl = normalize(Zl);
//Вычислим направление оси Х (направлена вправо):
vec3 Xl = cross(Zl,Zo);
//Вполне возможно, что направление взгляда наблюдателя совпадает с направлением света.
//В таком случае в качестве оси Х для светильника возьмём направление оси Х наблюдателя:
if(dot(Xl,Xl)==0.0)Xl = Xo;
//Напомню, что ось Х наблюдателя - это три первых члена первого ряда матрицы То.
//Теперь нормализуем ось Х светильника:
Xl = normalize(Xl);
//Ну и наконец вычислим ось Y для светильниковой системы координат:
Yl = cross(Xl,Zl);
Здорово! Теперь можно начать двигать камеру "к светильнику" (позиция наблюдателя - "P"):
glMatrixMode(GL_MODELVIEW_MATRIX);
glLoadIdentity();
glLookAt(P.x-Zl.x,P.y-Zl.y,P.z-Zl.z, P.x,P.y,P.z, Yl.x,Yl.y,Yl.z);
Мы только что впялили камеру в наблюдателя, расположив её очень неподалёку. Ортогональная проекция же - пофигу как далеко. Остальное поправим матрицей проекции. Но чтобы её задать, нам нужно знать координаты пирамиды видимости после трансформации только что заданной модельновидовой матрицей.
Сопрём её (назовём её Ml - модельновидовая матрица лайта). Умножим все углы пирамидки на неё (Mo * c[0], Mo*c[1]...). И среди получившихся вершин найдём крайние значения (минимум и максимум) для координат x, y и z.
Назовём их xMin, xMax, yMin, yMax, zMin, zMax. Всё, мы готовы задать ортографическую проекцию, да такую, что вся пирамида видимости войдёт в карту тени!
Во:
glMatrixMode(GL_PROJECTION_MATRIX);
glLoadIdentity();
glOrtho(xMin,xMax, yMin,yMax, -zMax, -zMin);
Всё, можно составлять карту тени!
Добавлено спустя 54 минуты 10 секунд:
^
|
это было полдела.
Как обращаться с картой тени при её наложении?
И вообще, как её настраивать при создании? Вот так я настраиваю её:
unsigned int TexID=0;
glGenTextures(1,&TexID);
glBindTexture(GL_TEXTURE_2D,TexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
Создав текстуру так, я её уже не меняю и не изменяю никаких параметров - работаеть.
Я делаю проверку глубины пикселя в карте тени во фрагментном шейдере (хоть и рациональнее было бы накладывать её в вершинном, но у меня куча мала везде, а ворошить код не охота, как и давать тебе непроверенные коды). Короче говоря, мы находимся во фрагментном шейдере, и условия такие:
vec4 Position - это координаты рисуемого пикселя в диапазоне (-1.0,...,+1.0),
uniform sampler2DShadow TexShadow - сэмплер карты тени;
и самое главное - матрица трансформации из системы координат наблюдателя в систему координат светильника находится тут: gl_TextureMatrix[0]. Видимо, нужно пояснить, откуда она взялась, и как туда попала...
Как бы начать...
В общем, нам нужно будет каждый рисуемый фрагмент трансформировать обратно в мировые координаты, затем трансформировать оттуда в систему координат светильника, чтобы получить его координаты в карте тени и сравнить глубину. Т.е. мы сначала умножаем координаты пиксела (в диапазоне -1...1) на обратную матрицу трансформации наблюдателя (iTo), затем полученные координаты умножаем на матрицу трансформации светильника (Tl), которую мы использовали при рендере карты тени.
Tl мы получали, умножив ортогональную матрицу проекции светильника на его модельвьювую матрицу. Откуда взялась iTo - см. раньше.
Описанные действия мы не будем совершать последовательным умножением вершины на две матрицы - мы просто переменожим две матрицы (Tl*iTo), и будем использовать полученную матрицу "М" (M=Tl*iTo), запихав её в слот текстурных матриц:
glMatrixMode(GL_TEXTURE);
glLoadMatrixd(M);
Смотри только, чтоб активной была текстура номер ноль, а то ведь с учётом мультитекстуринга мы имеем 8+ независимых стаков матриц - один на каждый текстурный слот.
Итак. Мы умножаем нормализованные координаты фрагмента (т.е. Position) на супер-матрицу трансформации, чтобы получить объектные координаты:
vec4 PosShadow = gl_TextureMatrix[0] * Position;
Не забываем разделить на w:
PosShadow = PosShadow / PosShadow.w;
Только что мы получили координаты пикселя в системе координат светильника (как будто это была точка, которую мы рендерим в карту тени). Однако нам нужно ещё перевести координаты из диапазона (-1...1) в диапазон (0...1):
PosShadow = PosShadow * 0.5 + 0.5;
Усё, можно сравнивать глубину точки (PosShadow.z). Но не "вручную", а так:
float Intensity = shadow2D( TexShadow, PosShadow.xyz );
Полученное значение (Intensity) лежит в диапазоне (0...1), и симулирует освещённость точки в процентах (0-тень, 1-освещена). Мы настроили текстуру так, что края тени будут гладкими, поэтому промежуточные значения тоже будут получаться.