OpenGL ES 2.0. Урок 5. Шейдер преломления света
В предыдущем уроке мы рисовали гладкую поверхности в виде бегущей волны. В этом уроке мы попробуем превратить нашу поверхность в настоящую воду. Луч света проходящий, через границу двух сред, немного изменяет свое направление. Этот эффект называют преломлением света. Пусть на глубине y = ybottom расположено дно, покрытое текстурой. Над поверхностью воды находится глаз наблюдателя, т.е. камера. Нужно выяснить, какую точку текстуры дна увидит наблюдатель. Для этого изменим направление хода луча света на обратное. Луч света будет выходить из глаза наблюдателя, преломляться на поверхности воды и попадать на дно. Точка пересечения преломленного луча и дна и будет видна наблюдателю.
Разобьем задачу на три этапа: Приступаем к решению первого этапа. Входящий вектор найти нетрудно, если известны координаты вершины и координаты камеры. В фрагментном шейдере это будет выглядеть так: vec3 IN=v_vertex - u_camera; где u_camera - координаты камеры в виде униформы, а v_vertex - это вершина на поверхности воды, на которую положил глаз наблюдатель.
Вектор IN не обязательно должен быть нормализован. В предыдущих уроках при расчете освещения мы уже использовали вектор: vec3 lookvector = normalize(u_camera - v_vertex) Поэтому если мы хотим скомбинировать преломление с освещением можно в качестве вектора IN взять lookvector с противоположным знаком (т.е. IN = - lookvector), чтобы не создавать лишних вычислений в шейдере.
Если нам известен входящий вектор IN, вектор единичной нормали N и относительный показатель преломления на границе двух сред k находим преломленный вектор OUT по закону Снелла: Здесь:
Создадим в фрагментном шейдере функцию myrefract, которая будет принимать на вход аргументы IN, NORMAL, k и возвращать преломленный вектор: Можно перенести эту функцию из фрагментного в вершинный шейдер, при этом скорость отрисовки кадра возрастет в несколько раз, но визуальное качество жидкости ухудшится. Второй этап выполнен. Найдем точку пересечения преломленного вектора с плоскостью дна. Зададим уравнение плоскости дна, оно выглядит очень просто: у = ybottom, где ybottom - глубина дна Как задать в пространстве уравнение прямой? Нужно знать какую-нибудь точку через которую проходит прямая и вектор, приложенный к этой точке. Преломленный вектор проходит через вершину v_vetrex. Хорошо, одну точку прямой мы знаем. Прицепим к ней вектор OUT, который мы уже нашли, и получим параметрическое уравнение прямой линии преломленного луча света: x = v_vertex.x +OUT.x * t где t - параметр, т.е. любое число. Все бесконечное множество чисел t дают нам бесконечное количество точек прямой. Кто хочет узнать больше про уравнение прямой линии в параметрическом виде могут обратиться к википедии. Нам нужно найти такой параметр t, который соответствует пересечению вектора OUT с плоскостью дна. Соединим вместе уравнение плоскости дна и уравнение прямой, т.е. подставим в уравнение прямой ybottom вместо у: ybottom = v_vertex.y + OUT.y * t Теперь мы знаем параметр t=(ybottom - v_vertex.y)/OUT.y Подставляем найденный t в выражения для x и z получаем получаем координаты точки пересечения вектора OUT с дном: xbottom = v_vertex.x +OUT.x * (ybottom - v_vertex.y)/OUT.y zbottom = v_vertex.z +OUT.z * (ybottom - v_vertex.y)/OUT.y Значение xbottom станет нашей текстурной координатой s, а zbottom - текстурной координатой t. Чтобы текстура не выглядела мелкой, уменьшим текстурные координаты в два раза. Получим двумерный вектор координат текстуры: vec2 texCoord = vec2(0.5*xbottom,0.5*zbottom); Пришло время оформить фрагментный шейдер. В нем я соединил эффект преломления с освещением:
void main() { Внесем в код предыдущего урока небольшие изменения. Заменим фрагментный шейдер, а также в методе onSurfaceCreated загрузим какую-нибудь текстуру, например такую:
//создадим текстурный объект Запусти приложение на исполнение и получим живую воду:
|