Разработка мобильных приложений. Программирование мобильных устройств на языке Kotlin. Учебное пособие для СПО
.pdfod0 = x - x0 – вектор от левого узла до точки.
od1 = x - x1 – вектор от правого узла до точки.
4.Вычисляем точечные произведения:
os = dot(g0, d0) – точечное произведение для левого узла.
ot = dot(g1, d1) – точечное произведение для правого узла.
5.Интерполируем значения:
of(x) = smoothstep(d0) – функция плавного шага (Smoothstep).
onoise = lerp(s, t, f(x)) – линейная интерполяция между s и t с использованием f(x) в качестве коэффициента интерполяции.
Для многомерного шума (2D, 3D и т.д.) процесс аналогичен, но необходимо учитывать больше узлов решетки и интерполировать значения в нескольких измерениях.
Преимущества шума Перлина:
•Природный вид: Генерируемый шум выглядит более естественным, чем простой белый шум.
•Управляемость: Частота и амплитуда шума могут быть настроены для создания различных эффектов.
•Масштабируемость: Можно легко масштабировать шум для разных уровней детализации.
•Широкое применение: Используется для создания разнообразных эффектов в компьютерной графике, играх, анимации и других областях.
Недостатки шума Перлина:
•Вычислительная сложность: Генерация шума Перлина может быть относительно затратной с вычислительной точки зрения.
•Повторяемость: Хотя шум выглядит случайным, он все же является детерминированным, что может привести к заметным повторениям при использовании на больших масштабах.
•Ограниченная рандомность: Шум Перлина не является истинно случайным, и его структура может быть заметна при внимательном рассмотрении.
Примеры использования:
•Создание текстур: Шум Перлина используется для создания текстур дерева, мрамора, облаков и других природных материалов.
•Генерация ландшафтов: Шум Перлина используется для генерации высот ландшафта, создавая реалистичные горные хребты и долины.
•Анимация: Шум Перлина может быть использован для создания анимации пламени, дыма и других динамических эффектов.
•Процедурная генерация контента: Шум Перлина используется для генерации случайных, но правдоподобных уровней, объектов и других элементов в играх.
52
51
//Вершинный шейдер (простой) attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
//Фрагментный шейдер precision mediump float;
uniform float u_Scale; // Scale factor for the noise uniform float u_Offset;
//Simple random function float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
//Linear interpolation
float lerp(float a, float b, float t) { return a + t * (b - a);
}
//Improved SmoothStep function float smoothStep(float t) {
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
}
//2D Value Noise
float valueNoise2D(vec2 st) { vec2 i = floor(st);
vec2 f = fract(st);
//Corners coordinates float a = random(i);
float b = random(i + vec2(1.0, 0.0)); float c = random(i + vec2(0.0, 1.0)); float d = random(i + vec2(1.0, 1.0));
//Smooth the values
vec2 smooth_f = smoothStep(f);
// Interpolate using smooth values
return lerp(lerp(a, b, smooth_f.x), lerp(c, d, smooth_f.x), smooth_f.y);
}
53
52
// Fractal Brownian Motion (FBM) for multiple octaves float fbm(vec2 st, int octaves) {
float value = 0.0; float amplitude = 0.5; float frequency = 1.0; float maxValue = 0.0;
for (int i = 0; i < octaves; i++) {
value += amplitude * valueNoise2D(st * frequency); maxValue += amplitude;
amplitude *= 0.5; // Adjust amplitude for each octave (gain) frequency *= 2.0; // Adjust frequency for each octave (lacunarity)
}
return value / maxValue; // Normalize to [0,1]
}
void main() {
vec2 st = gl_FragCoord.xy/u_Scale+u_Offset; // Normalize coordinates by scale
int octaves = 5;
float noise = fbm(st,octaves);
gl_FragColor = vec4(vec3(noise), 1.0); // Output grayscale noise
}
Основные моменты реализации на GLSL:
•random(vec2 st): Простая функция хеширования для создания псевдослучайных чисел на основе координат st.
•lerp(float a, float b, float t): Функция линейной интерполяции.
•smoothStep(float t): Функция Smoothstep для плавной интерполяции.
•valueNoise2D(vec2 st): Функция для генерации “value noise” –
упрощенного варианта шума Перлина, где вместо градиентов используются случайные значения в узлах решетки.
•fbm(vec2 st, int octaves): Функция Fractal Brownian Motion (FBM), которая суммирует несколько октав шума для создания более детализированного результата.
•main():
o Нормализует координаты фрагмента (gl_FragCoord.xy) в диапазон от
0 до 1.
o Вызывает функцию fbm() для генерации шума.
o Устанавливает цвет фрагмента (gl_FragColor) на основе значения шума.
Шум Перлина – это мощный инструмент для создания реалистичных и интересных визуальных эффектов. Понимание принципов его работы и умение реализовать его в GLSL ES позволит вам создавать впечатляющую графику в ваших OpenGL ES 2.0 приложениях на Kotlin.
54
53
ПРАКТИЧЕСКАЯ РАБОТА 6
Добавьте справочное описание каждой планеты и картинку, которые открываются при выборе планеты.
55
54
Водная гладь
Создание реалистичной водной глади в OpenGL ES 2.0 включает в себя несколько техник, комбинирующих шейдеры, текстуры и математические вычисления. Вот подробный разбор с примером кода на Kotlin:
Основные методы и техники:
1. Создание поверхности воды:
o Обычно используется плоская сетка, состоящая из множества треугольников. Чем больше треугольников, тем более детализированной будет водная гладь.
oВершины сетки определяют геометрию воды.
2.Волны (Waves):
oСинусоидальные волны: Простейший способ добавить волны – смещать вершины сетки по оси Y (вверх-вниз) с использованием синусоидальных функций. Можно комбинировать несколько
синусоидальных волн с разными амплитудами, частотами и направлениями для создания более сложного волнового паттерна.
oШум Перлина (Perlin Noise): Более реалистичные волны можно создать с помощью шума Перлина (описанного выше). Шум Перлина может использоваться для смещения вершин сетки или для создания карты высот (heightmap), которая затем используется для смещения вершин.
oGerstner Waves (или Trochoidal Waves): Более продвинутый метод, который создает волны, более похожие на реальные волны на воде. Они более сложные в вычислениях, но дают более реалистичный результат.
3.Отражение (Reflection):
oПланарное отражение (Planar Reflection): Простейший способ создать отражение – отрендерить сцену дважды: один раз для вида сверху (как обычно), а второй раз – отраженным видом под поверхностью воды. Отраженное изображение затем используется в
качестве текстуры для поверхности воды. Этот метод подходит только для плоских водных поверхностей.
oКубическое отражение (Cube Map Reflection): Для более сложных отражений, учитывающих кривизну поверхности воды, используются кубические карты. Сначала рендерится сцена в 6 направлениях (внутрь куба), и эти изображения сохраняются в кубической карте. Затем кубическая карта используется для определения цвета отражения для каждого фрагмента поверхности воды.
4.Преломление (Refraction):
oСвет, проходящий через воду, преломляется (изменяет направление). Эффект преломления можно имитировать, используя текстуру, которая содержит изображение того, что находится под
56
55
водой. Эта текстура смещается и искажается в зависимости от волнового паттерна на поверхности воды.
5. Fresnel Effect:
o Эффект Френеля описывает, как изменяется количество отраженного и преломленного света в зависимости от угла обзора. Когда вы смотрите на воду под прямым углом, большая часть света преломляется, и вы видите то, что находится под водой. Когда вы смотрите на воду под скользящим углом, большая часть света отражается, и вы видите отражение неба и окружающих объектов.
oЭффект Френеля реализуется с использованием математической формулы, которая вычисляет коэффициент отражения на основе угла обзора.
6.Блики (Specular Highlights):
oДобавление бликов (specular highlights) на поверхности воды создает эффект солнечных бликов или отражений от других источников света. Интенсивность бликов зависит от угла между вектором отражения света, вектором обзора и шероховатости поверхности воды.
7.Карты нормалей (Normal Maps):
oДля добавления мелких деталей на поверхности воды используются карты нормалей. Карты нормалей – это текстуры, которые содержат информацию о направлении нормалей поверхности. Они позволяют имитировать неровности и шероховатости поверхности без
увеличения количества вершин.
Рассмотрим небольшой пример базовой реализации с синусоидальными волнами:
void main() {
// Simple sine wave displacement
float waveHeight = sin(a_Position.x * 2.0 + u_Time) * 0.1 + sin(a_Position.z * 3.0 + u_Time * 1.5) * 0.1;
vec4 displacedPosition = a_Position + vec4(0.0, waveHeight, 0.0, 0.0); v_Position = displacedPosition.xyz; // Pass vertex position to fragment shader gl_Position = u_MVPMatrix * displacedPosition;
}
// Function to generate the water grid vertices
private fun generateWaterGrid(width: Int, height: Int, size: Float): FloatArray { val numVertices = (width + 1) * (height + 1) * 3 // 3 floats per vertex (x, y, z) val vertices = FloatArray(numVertices)
var index = 0
for (z in 0..height) { for (x in 0..width) {
val xPos = (x.toFloat() / width.toFloat() - 0.5f) * size val zPos = (z.toFloat() / height.toFloat() - 0.5f) * size vertices[index++] = xPos
vertices[index++] = 0f // Y coordinate (initially 0)
57
56
vertices[index++] = zPos
}
}
return vertices
}
Пояснения к коду:
•generateWaterGrid(): Создает массив вершин для плоской сетки воды.
•vertexShaderCode:
o
o o o
u_MVPMatrix: Uniform-переменная для матрицы Model-View- Projection.
a_Position: Атрибут для координат вершин.
u_Time: Uniform-переменная для времени (для анимации волн). Смещает вершины по оси Y с использованием синусоидальных функций, создавая эффект волн.
ov_Position: Передает положение вершины во фрагментный шейдер.
•fragmentShaderCode:
ov_Position: Varying-переменная, полученная из вершинного
шейдера.
oУстанавливает цвет фрагмента на синий, имитируя цвет воды.
•onDrawFrame():
oПередает матрицу MVP и значение времени в шейдеры.
o Рисует сетку воды с использованием glDrawArrays(). o Обновляет значение времени для анимации волн.
Улучшения и дальнейшие шаги:
1.Более реалистичные волны: Замените синусоидальные волны на шум Перлина или Gerstner waves.
2.Отражение и преломление: Реализуйте планарное отражение или кубическое отражение для создания отражений на поверхности воды. Добавьте преломление, чтобы имитировать искажение света, проходящего через воду.
3.Fresnel Effect: Добавьте эффект Френеля для изменения количества отраженного и преломленного света в зависимости от угла обзора.
4.Блики (Specular Highlights): Добавьте specular highlights для создания эффекта солнечных бликов.
5.Карты нормалей (Normal Maps): Используйте карты нормалей для добавления мелких деталей на поверхности воды.
6.Освещение (Lighting): Добавьте реалистичную модель освещения, чтобы вода выглядела более естественно.
7.Пена (Foam): Добавьте пену на гребнях волн и у берегов.
8.Движение камеры: Свяжите параметры волн с движением камеры, чтобы создать ощущение, что вода бесконечна.
Приведем основные моменты в реализации Gerstner Waves:
58
57
//Wave Parameters (Can be passed as uniform arrays) const int NUM_WAVES = 3;
uniform float u_Amplitude[NUM_WAVES]; uniform vec2 u_Direction[NUM_WAVES]; uniform float u_Steepness[NUM_WAVES]; uniform float u_Wavelength[NUM_WAVES];
varying vec3 v_Position; varying vec3 v_Normal;
void main() {
vec4 position = a_Position;
vec3 normal = vec3(0.0, 1.0, 0.0); //Base normal pointing up
for(int i = 0; i < NUM_WAVES; ++i) {
float k = 2.0 * 3.14159 / u_Wavelength[i]; //Wave number float c = sqrt(9.8 / k); //Wave speed (simplified)
vec2 d = normalize(u_Direction[i]); //Wave direction
float f = k * (dot(d, position.xz) - c * u_Time); float a = u_Amplitude[i];
float s = u_Steepness[i];
position.x += d.x * a * cos(f); position.z += d.y * a * cos(f); position.y += a * sin(f);
//Calculate the Tangent and Bi-Tangent vec3 tangent = vec3(
-d.x * d.x * s * a * sin(f), d.x * a * cos(f),
-d.x * d.y * s * a * sin(f)
);
vec3 bitangent = vec3( -d.x * d.y * s * a * sin(f), d.y * a * cos(f),
-d.y * d.y * s * a * sin(f)
);
normal = normalize(cross(bitangent,tangent)); //Recalculate the normal
}
v_Position = position.xyz; v_Normal = normal;
59
58
gl_Position = u_MVPMatrix * position;
}
Ключевые моменты:
•Gerstner Waves: Эта реализация использует Gerstner Waves для более реалистичного смещения вершин. Каждая волна определяется амплитудой, направлением, крутизной и длиной волны.
•Normals: В вершинном шейдере также вычисляются нормали к поверхности воды. Нормали важны для правильного освещения.
•Multiple Waves: Можно комбинировать несколько волн Gerstner для создания более сложного волнового паттерна.
•Lighting: Фрагментный шейдер использует базовую модель освещения для добавления бликов и затенения на поверхность воды.
60
59
ПРАКТИЧЕСКАЯ РАБОТА 7
Замените картинку планеты Нептун на поверхность водной глади с волнами.
61
60
