От нашего спонсора: Рынок умнее с инструментами автоматизированного обмена сообщениями Mailchimp.
Сегодня мы собираемся создать анимированное облако с использованием пользовательского материала шейдера, расширяя встроенный материал Sprite Three.js.
Предположим, что вы знакомы с React (включая крючки), Three.js и React-Three-Fiber. Если нет, вы можете найти эту статью, которую я написал как введение для начинающих в библиотеку, полезной для быстрого начала.
Техника, которую мы объясним, использовалась в двух недавних проектах, сделанных в Low:
Мы не будем охватывать другие элементы, такие как фон, и также не будем создавать наиболее производительное облако, поскольку оно будет получается слишком сложно, и цель этой статьи — познакомить вас с техникой, оставив при этом ее простой.
Обратите внимание, что использование React здесь не является обязательным, но я начал использовать React-Three-Fiber для всех своих демонстраций и проектов, поэтому я решил использовать его и здесь.
В этой статье мы рассмотрим два основных момента:
- Как расширить SpriteMaterial
- Как написать шейдер облака
Содержание статьи
Расширение материала спрайта
Поскольку целью является не создание объемного облака (реального трехмерного облака), я решил расширить SpriteMaterial Three.js. Вместо этого мы можем использовать тот факт, что при использовании Sprite облако всегда будет обращено к камере, независимо от положения или ориентации камеры. Поэтому, если вы перемещаете облако или перемещаете камеру, вы всегда будете видеть ее, и это поможет имитировать отсутствие трехмерного тома (ознакомьтесь с режимом отладки, чтобы получить представление).
Примечание: Если вы перейдете к демонстрационной версии и добавите
/? Debug = true
к URL, это активирует элементы управления орбитой, что даст вам некоторое визуальное представление о почему я решил использовать материал Sprite.
Существует несколько способов расширить встроенный материал Three.js, и вы можете найти хорошее объяснение в этой статье Dusan Bosnjak.
импорт {ShaderMaterial, UniformsUtils, ShaderLib} из «трех»
импортировать фрагмент из '~ shaders / cloud.frag'
импортировать вершину из ~ ~ shaders / cloud.vert '
/ **
* Мы собираемся взять форму материала Sprite
* и мы сольемся с нашей униформой
* /
const myUniforms = useMemo (() => ({
.......
}), [])
const material = useMemo (() => {
const mat = new ShaderMaterial ({
униформа: {... UniformsUtils.clone (ShaderLib.sprite.uniforms), ... myUniforms},
vertexShader: вершина,
FragShader: фрагмент,
прозрачный: правда,
})
возвратный коврик
}, [])
Нам нужно составить наш вершинный шейдер, добавив необходимые фрагменты кода #include
. Если вас интересует, как создаются материалы в Three.js, вы можете взглянуть на исходный код.
равномерное вращение поплавка;
единый центр vec2;
#include
#include
#include
#include
#include
варьирующий vec2 vUv;
пустая функция() {
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4 (0.0, 0.0, 0.0, 1.0);
шкала vec2;
scale.x = length (vec3 (модель Matrix [ 0 ] .x, модель Matrix [ 0 ] .y, модель Matrix [ 0 ] .z));
scale.y = length (vec3 (modelMatrix [ 1 ] .x, modelMatrix [ 1 ] .y, modelMatrix [ 1 ] .z));
vec2 alignPosition = (position.xy - (center - vec2 (0.5))) * масштаб;
vec2 rotatedPosition;
rotatedPosition.x = cos (вращение) * alignPosition.x - sin (вращение) * alignPosition.y;
rotatedPosition.y = sin (вращение) * alignPosition.x + cos (вращение) * alignPosition.y;
mvPosition.xy + = rotatedPosition;
gl_Position = projectionMatrix * mvPosition;
#include
#include
#include
}
Таким образом, мы создали специальный материал Sprite
. Мы можем добиться того же эффекта другими способами, но я решил расширить встроенный материал, потому что в будущем может быть полезно добавить собственную логику. Пришло время покопаться во фрагменте
.
Фрагмент облака
Для создания облака нам нужны два актива. Одна — это грубая начальная форма облака, другая — начальная точка текстуры / шаблона.
Имейте в виду, что обе текстуры могут быть созданы непосредственно в шейдере, но это потребует некоторых вычислений графического процессора. Это хорошо, но если вы можете избежать этого, то стоит также оптимизировать шейдер.
Использование некоторых изображений вместо создания их с помощью кода может сэкономить некоторые вычислительные ресурсы.
Скользящие текстуры
Прежде всего давайте создадим две скользящие текстуры, используя изображение текстуры выше ( uTxtCloudNoise
), которое мы будем использовать позже для обработки альфа-канала выходных данных. Это скользящие текстуры, которые помогают нам создавать «ложный» шумовой эффект путем объединения, добавления и умножения их.
vec4 txtNoise1 = texture2D (uTxtCloudNoise, vec2 (vUv.x + uTime * 0,0001, vUv.y - uTime * 0,00014));
vec4 txtNoise2 = texture2D (uTxtCloudNoise, vec2 (vUv.x - uTime * 0.00002, vUv.y + uTime * 0.000017 + 0.2))
Шум
Теперь нам нужен некоторый GLSL-шум : симпекс-шум и дробное броуновское движение (FBM) позволяющее нам преобразоваться форма и создать эффект парообразной границы.
Давайте сначала создадим шум Simplex и FBM чтобы исказить наш УФ .
Мы будем использовать FBM чтобы добиться эффекта границы облака, сделать его похожим на дым, и мы будем использовать Simplex чтобы сделать морфинг формы облако.
Искаженный UV теперь называемый newUv будет использоваться при объявлении txtShape :
#pragma glslify: fbm3d = require ('glsl-fractal-brownian-noise / 3d')
#pragma glslify: snoise3 = require (glsl-noise / simplex / 3d)
// FBM
float noiseBig = fbm3d (vec3 (vUv, uTime), 4) + 1,0 * 0,5;
newUv + = noiseBig * uDisplStrenght1;
// SIMPLEX
float noiseSmall = snoise3 (vec3 (newUv, uTime)) + 1,0 * 0,5;
newUv + = noiseSmall * uDisplStrenght2;
......
vec4 txtShape = texture2D (uTxtShape, newUv);
А вот так выглядит шум:
Маска и Альфа
Чтобы создать маску для облака, мы будем использовать текстуру формы (uTxtShape), которую мы видели в начале и результат скольжения текстур, которые мы упоминали ранее.
Следующий результат является результатом только маскирования. Граница и эффект формы хороши, но внутренний шаблон / цвет — нет:
Теперь мы вычислим альфа, использовавшуюся на скользящих текстурах из ранее. Мы будем использовать функцию уровней
которая была взята отсюда, которая более или менее похожа на функцию уровней Photoshop.
Объединение искаженной формы (uTxtShape) и красного канала скользящих текстур даст нам внешнюю форму и даже внутренний «облачный рисунок» для создания более реалистичного вида:
vec4 txtShape = texture2D (uTxtShape, newUv);
плавающая альфа = уровни ((txtNoise1 + txtNoise2) * 0,6, 0,2, 0,4, 0,7) .r;
альфа * = txtShape.r;
gl_FragColor = vec4 (vec3 (0,95,0,95,0,95), альфа);
Объединяя все
Теперь пришло время обернуть все, чтобы отобразить окончательный результат:
void main () {
vec2 newUv = vUv;
// Скользящие текстуры
vec4 txtNoise1 = texture2D (uTxtCloudNoise, vec2 (vUv.x + uTime * 0,0001, vUv.y - uTime * 0,00014)); // шум TXT
vec4 txtNoise2 = texture2D (uTxtCloudNoise, vec2 (vUv.x - uTime * 0.00002, vUv.y + uTime * 0.000017 + 0.2)); // шум TXT
// Расчет FBM и искажение УФ
float noiseBig = fbm3d (vec3 (vUv * uFac1, uTime * uTimeFactor1), 4) + 1,0 * 0,5;
newUv + = noiseBig * uDisplStrenght1;
// Рассчитать симплекс и исказить УФ
float noiseSmall = snoise3 (vec3 (newUv * uFac2, uTime * uTimeFactor2)) + 1,0 * 0,5;
newUv + = noiseSmall * uDisplStrenght2;
// Создать форму (маску)
vec4 txtShape = texture2D (uTxtShape, newUv);
// Альфа
плавающая альфа = уровни ((txtNoise1 + txtNoise2) * 0,6, 0,2, 0,4, 0,7) .r;
альфа * = txtShape.r;
gl_FragColor = vec4 (vec3 (0,95,0,95,0,95), альфа);
}
Заключительные мысли
Имейте в виду, что это не самый эффективный способ создания облака, но это простой. Использование шумовых функций обходится дорого, но для этого урока этого должно быть достаточно.
Если у вас есть какие-либо мысли, улучшения или сомнения, пожалуйста, не стесняйтесь писать мне в Twitter, я буду рад помочь.