MyTetra Share
Делитесь знаниями!
Создание шейдеров - Часть 1
Время создания: 23.06.2021 22:19
Автор: Omar Shehata, Vadim Ivshin
Раздел: Компьютер - Программирование - Шейдеры - Шейдеры GLSL
Запись: xintrea/mytetra_syncro/master/base/1624475977521p5ao900/text.html на raw.github.com

Освоить создание графических шейдеров — это значит взять под свой контроль всю мощь видепроцессора с его тысячами параллельно работающих ядер. При таком способе программирования требуется другой образ мышления, но раскрытие его потенциала стоит потраченных усилий.


Практически в любой современной графической симуляции используется код, написанный для видеопроцессора: от реалистичных эффектов освещения в высокотехнологичных AAA-играх до двухмерных эффектов постпроцессинга и симуляции жидкостей.


Например, в игре Main Craft имеется возможность вставлять самодельныей шейдеры в итоговый рендер игрового движка. Поэтому если в YouTube сделать поиск по словам "shader уроки", половина результатов будет посвящена тому, как вставлять шейдеры в Main Craft:


Сцена из Minecraft, до и после добавления нескольких шейдеров .


Задача этого туториала

Иногда программирование шейдеров представляется загадочной чёрной магией и его часто понимают неправильно. Существует множество примеров кода, демонстрирующих создание невероятных эффектов, но в которых практически нет объяснений. В своём руководстве я хочу восполнить этот пробел. Я сосредоточусь в основном на базовых принципах создания и понимания кода шейдеров, чтобы вы смогли с лёгкостью настраивать, сочетать или писать свои собственные шейдеры с нуля!

Это общее руководство, поэтому всё изученное в нём можно применять в любой технологии, использующей шейдеры.


Что же такое шейдер?

Шейдер — это просто программа, выполняемая в графическом конвейере. Она сообщает компьютеру, как рендерить каждый пиксель. Эти программы называются шейдерами («затенителями»), потому что их часто используют для управления эффектами освещения и затенения , но ничего не мешает использовать их и для других спецэффектов.

Шейдеры пишут на специальном языке шейдеров. Не волнуйтесь, вам не придётся изучать совершенно новый язык: мы будем использовать GLSL (OpenGL Shading Language), который похож на C. (Существует несколько языков написания шейдеров для разных платформ, но поскольку все они адаптированы под выполнение в видеопроцессоре, то похожи друг на друга.)

Примечание: эта статья посвящена исключительно фрагментным шейдерам (fragment shader). Если вам любопытно, какие ещё виды шейдеров бывают, то можно почитать о различных этапах графического конвейера в OpenGL Wiki.


Приступаем!

В этом туториале мы будем использовать сайт ShaderToy . Он позволяет нам начать программировать шейдеры прямо в браузере, без возни с установкой и настройкой! (Для рендеринга он использует WebGL, поэтому требуется браузер с поддержкой этой технологии.) Создавать учётную запись не обязательно, но удобно для сохранения кода.

Примечание: на момент написания статьи ShaderToy находился в состоянии беты [прим. пер.: статья написана в 2015 году]. Некоторые детали интерфейса/синтаксиса могут немного отличаться.

Нажав на New Shader , вы увидите что-то вроде этого:



Если вы не зарегистрировались, интерфейс может немного отличаться.

Маленькая чёрная стрелка внизу служит для компиляции кода.


Что происходит?

Я собираюсь описать то, как работают шейдеры, одним предложением. Приготовились? Вот оно!

Единственное предназначение шейдера — возврат четырёх чисел:r, g, b и a.

Именно это, и только это может делать шейдер. Функция выполняется для каждого пикселя на экране. Она возвращает эти четыре цветовых значения, которые становятся цветом пикселя. Это то, что называется пиксельным шейдером (иногда его называют фрагментным шейдером).

Запомнив это, давайте зальём экран сплошным красным цветом. Значения RGBA (red, green, blue и alpha, определяющая прозрачность) меняются от 0 до 1, поэтому единственное, что нужно сделать — вернуть r,g,b,a = 1,0,0,1. ShaderToy ожидает, что окончательный цвет пикселя будет храниться в fragColor.


void mainImage( out vec4 fragColor, in vec2 fragCoord )

{

fragColor = vec4(1.0,0.0,0.0,1.0);

}


Поздравляю! Вот ваш первый готовый шейдер!

Задача: Попробуйте изменить цвет на сплошной серый.

vec4 — это просто тип данных, поэтому мы могли бы объявить цвет как переменную, вот так:


void mainImage( out vec4 fragColor, in vec2 fragCoord )

{

vec4 solidRed = vec4(1.0,0.0,0.0,1.0);

fragColor = solidRed;

}


Выглядит не очень вдохновляюще. У нас есть мощь, способная выполнять код для сотен тысяч пикселей одновременно, а мы закрашиваем их одним цветом.

Давайте попробуем отрендерить на экране градиент. Мы не сможем добиться многого, не зная пары вещей о пикселях, на которые мы влияем, например, об их положении на экране…


Входные данные шейдера

Пиксельный шейдер передаёт несколько переменных , которые мы можем использовать. Самая полезная — это fragCoord, содержащая координаты X и Y пикселя (и Z, если вы работаете в 3D). Давайте попробуем превратить все пиксели в левой части экрана в чёрные, а все пиксели справа — в красные:


void mainImage( out vec4 fragColor, in vec2 fragCoord )

{

vec2 xy = fragCoord.xy; //Получаем координаты текущего пикселя

vec4 solidRed = vec4(0,0.0,0.0,1.0);//Теперь он стал чёрным

if(xy.x > 300.0){//Произвольное число, мы не знаем размер экрана!

solidRed.r = 1.0;//Присваиваем красному компоненту значение 1.0

}

fragColor = solidRed;

}


Примечание: Получить доступ к компонентам любого vec4 можно с помощью obj.x, obj.y, obj.z и obj.w или с помощью obj.r, obj.g, obj.b, obj.a. Они эквивалентны, это просто удобный способ наименования, упрощающий чтение кода, потому что когда люди видят obj.r, то понимают, что obj представляет собой цвет.

Вы заметили проблему в приведённом выше коде? Попробуйте нажать на кнопку go fullscreen в правом нижнем углу окна предварительного просмотра.

Размеры красной части экрана будут разными и зависят от размера экрана. Чтобы закрасить красным ровно половину экрана, нам нужно знать его размер. Размер экрана не является встроенной переменной, как позиция пикселя, потому что обычно он выбирается программистом, создающим приложение. В нашем случае размер экрана устанавливают разработчики ShaderToy.

Если что-то не является встроенной переменной, то можно отправить эту информацию из центрального процессора (из основной программы) в видеопроцессор (в ваш шейдер). ShaderToy берёт эту задачу на себя. Все передаваемые шейдеру переменные указаны во вкладке Shader Inputs. Переменные, передаваемые таким образом из ЦП в видеопроцессор, называются в GLSL uniform (глобальными).



Давайте изменим код, чтобы правильно определять центр экрана. Нам нужно использовать входные данные шейдера iResolution:


void mainImage( out vec4 fragColor, in vec2 fragCoord )

{

vec2 xy = fragCoord.xy; //Получаем координаты текущего пикселя

xy.x = xy.x / iResolution.x; //Делим координаты на размер экрана

xy.y = xy.y / iResolution.y;

// Теперь x для самого левого пикселя равен 0, а для самого правого равен 1

vec4 solidRed = vec4(0,0.0,0.0,1.0); //Теперь он стал чёрным

if(xy.x > 0.5){

solidRed.r = 1.0; //Присваиваем красному компоненту значение 1.0

}

fragColor = solidRed;

}


Если попробовать теперь изменить размер окна предварительного просмотра, то цвета должны точно разделить экран пополам.


От разделения к градиенту

Превратить это в градиент будет довольно просто. Значения цветов изменяются от 0 до 1, и координаты тоже изменяются от 0 до 1.


void mainImage( out vec4 fragColor, in vec2 fragCoord )

{

vec2 xy = fragCoord.xy; //Получаем координаты текущего пикселя

xy.x = xy.x / iResolution.x; //Делим координаты на размер экрана

xy.y = xy.y / iResolution.y;

// Теперь x для самого левого пикселя равен 0, а для самого правого равен 1

vec4 solidRed = vec4(0,0.0,0.0,1.0); //Теперь он стал чёрным

solidRed.r = xy.x; //Присваиваем красному компоненту нормализованное значение x

fragColor = solidRed;

}


Вуаля!

Задача: Можете превратить эту картинку в вертикальный градиент? А как насчёт диагонального? Как насчёт градиента из нескольких цветов?

Если вы поэкспериментируете с кодом, то заметите, что верхний левый угол имеет координаты (0,1), а не (0,0). Это важно запомнить.


Отрисовка изображений

Экспериментировать с цветами интересно, но если мы хотим сделать что-то впечатляющее, то наш шейдер должен научиться получать в качестве входных данных изображение и изменять его. Таким образом можно получить шейдер, воздействующий на весь игровой экран (например, «подводный» эффект или цветокоррекцию), или воздействовать нужным способом только на определённые объекты (например, для создания реалистичной системы освещения).

Если бы мы программировали на обычной платформе, то нужно было бы передавать видеопроцессору изображение (или текстуру) как uniform, точно так же, как мы бы передавали разрешение экрана. ShaderToy позаботится об этом за нас. В нижней части экрана есть четыре входных канала:



Четыре входных канала ShaderToy

Нажмите на iChannel0 и выберите любую текстуру (изображение).

В результате у вас будет изображение, передаваемое шейдеру. Однако есть одна проблема: отсутствие функции DrawImage(). Не забывайте, единственное, что может делать пиксельный шейдер — менять цвет каждого пикселя.

Если мы можем только возвращать цвет, то как можно нарисовать текстуру на экране? Нам нужно каким-то образом привязать текущий пиксель шейдера к соответствующему пикселю текстуры:



В зависимости от расположения (0,0) на экране может потребоваться отразить ось Y для правильной привязки текстуры. На момент написания статьи ShaderToy был обновлён и исходная точка находится в левом верхнем углу, так что ничего отражать не требуется.

Мы можем выполнить привязку с помощью функции texture(textureData,coordinates), получающей на входе данные текстуры и пару координат (x, y), возвращающую как vec4 цвет текстуры в этих координатах.

Вы можете привязывать координаты к экрану как вам угодно. Можно отрисовать всю текстуру на четверти экрана (пропуская пиксели, то есть уменьшив её масштаб) или просто отрисовать часть текстуры.

Мы хотим только увидеть изображение, поэтому пиксели будут совпадать в масштабе 1:1:


void mainImage( out vec4 fragColor, in vec2 fragCoord )

{

vec2 xy = fragCoord.xy / iResolution.xy;//Соединяем это в одну строку

vec4 texColor = texture(iChannel0,xy);//Получаем от iChannel0 пиксель в координате xy

fragColor = texColor;//Присваиваем экранному пикселю этот цвет

}


Так мы получили своё первое изображение!



Теперь, научившись правильно получать данные из текстуры, мы можем делать с ними всё, что угодно! Мы можем растягивать или масштабировать их, экспериментировать с цветами.

Давайте попробуем наложить на изображение градиент, похожий на то, что мы делали раньше:


texColor.b = xy.x;



Поздравляю, вы только что написали свой первый эффект постпроцессинга!

Задача: Сможете написать шейдер, делающий изображение чёрно-белым?

Учтите, что хотя мы и выбрали статичное изображение, всё, что вы видите, выполняется в реальном времени. Чтобы убедиться в этом, замените статичное изображение на видео: нажмите на iChannel0 и выберите любое видео.


Добавляем движение

Пока что все наши эффекты были статичными. Мы можем делать гораздо более интересные вещи, воспользовавшись входными данными, предоставляемыми ShaderToy. iGlobalTime — это постоянно увеличивающаяся переменная. Мы можем использовать её как начальное число для создания периодических эффектов. Давайте попробуем немного поиграть с цветами:


void mainImage( out vec4 fragColor, in vec2 fragCoord )

{

vec2 xy = fragCoord.xy / iResolution.xy; // Соединяем это в одну строку

vec4 texColor = texture(iChannel0,xy); // Получаем от iChannel0 пиксель в координате xy

texColor.r *= abs(sin(iGlobalTime));

texColor.g *= abs(cos(iGlobalTime));

texColor.b *= abs(sin(iGlobalTime) * cos(iGlobalTime));

fragColor = texColor; // Присваиваем экранному пикселю этот цвет

}


В GLSL встроены функции синуса и косинуса, а также множество других полезных функций, например, получение длины вектора или расстояния между двумя векторами. Значения цветов не могут быть отрицательными, поэтому нужно получить абсолютное значение с помощью функции abs.

Задача: Можете ли вы сделать шейдер, меняющий изображение с чёрно-белого на цветное и обратно?


Примечание об отладке шейдеров

При отладке кода можно выполнять его пошагово или выводить на печать значения, но при написании шейдеров это невозможно. Можете поискать инструменты отладки для своей платформы, но в общем случае лучше всего привязать проверяемое значение к какой-то графической информации, которую можно увидеть.


Подводим итог

Это только самые основы работы с шейдерами, но освоившись с ними, вы сможете добиться гораздо большего. Посмотрите эффекты ShaderToy и проверьте, сможете ли вы понять или воспроизвести их!

В этом туториале я не упоминал вершинные шейдеры (Vertex Shaders). Они пишутся на том же языке, но выполняются для каждой вершины, а не пикселя, и возвращают вместе с цветом ещё и положение. Вершинные шейдеры обычно проецируют 3D-сцену на экран (эта функция встроена в большинство графических конвейеров). Пиксельные шейдеры обеспечивают большинство наблюдаемых на экране сложных эффектов, поэтому я рассматриваю их.

Ещё одна задача: Сможете ли вы написать шейдер, удаляющий зелёный цвет из видео с ShaderToy и добавляющий другое видео в качестве фона первого видео?

ShaderToy , с которым мы работали ранее, отлично подходит для быстрой проверки и экспериментов, но его возможности довольно ограничены. Например, вы не можете контролировать данные, передаваемые шейдеру. Если у нас будет собственная среда для запуска шейдеров, то мы сможем создавать всевозможные интересные эффекты и использовать их в собственных проектах! Для запуска шейдеров в браузере мы будем использовать в качестве фреймворка Three.js. WebGL — это Javascript API, позволяющий рендерить шейдеры, Three.js нужен только для упрощения работы.

Если вам не интересны JavaScript или веб-платформа, не волнуйтесь, мы не будем вдаваться в особенности веб-рендеринга (но если вы хотите узнать о фреймворке больше, то изучите этот туториал ). Настройка шейдеров в браузере — самый быстрый способ начать работу, но освоившись с этим процессом, вы сможете с лёгкостью настраивать и использовать шейдеры на любой платформе.


Настройка

В этом разделе мы рассмотрим локальную настройку шейдеров. Вы можете повторять за мной без необходимости скачивать что-то благодаря этому встроенному виджету CodePen :



Можете создать форк и отредактировать проект на CodePen.


Hello Three.js!

Three.js — это JavaScript-фреймворк, который берёт на себя большую часть boilerplate-кода для WebGL, необходимого для рендеринга шейдеров. Проще всего начать с использования версии, выложенной на CDN .

Можете скачать файл HTML , который является простой сценой Threejs.

Сохраните файл на диск и откройте его в браузере. Вы должны увидеть чёрный экран. Пока не очень интересно, так что давайте попробуем добавить куб, просто чтобы проверить, что всё работает.

Для создания куба нам нужно определить его геометрию и материал, а затем добавить его в сцену. Добавьте этот фрагмент кода в поле Add your code here:


var geometry = new THREE.BoxGeometry( 1, 1, 1 );

var material = new THREE.MeshBasicMaterial( { color: 0x00ff00} );// Сделаем его зелёным

var cube = new THREE.Mesh( geometry, material );

// Добавляем куб на экран

scene.add( cube );

cube.position.z = -3;// Сдвигаем куб назад, чтобы его было видно


Мы не будем подробно рассматривать куб, потому что нам больше интересен шейдер. Но если всё сделано верно, то вы увидите в центре экрана зелёный куб:



Пока мы здесь, давайте заставим его вращаться. Функция render выполняется каждый кадр. Получить доступ к повороту куба можно через cube.rotation.x (или .y, или .z). Попробуйте увеличить значение, чтобы функция рендеринга выглядела так:


function render() {

cube.rotation.y += 0.02;

requestAnimationFrame( render );

renderer.render( scene, camera );

}


Задача: Сможете повращать куб по другой оси? А как насчёт двух осей одновременно?

Итак, всё готово, пора добавить шейдеры!


Добавляем шейдеры

На этом этапе мы можем начать думать о процессе реализации шейдеров. Скорее всего, похожая ситуация будет у вас вне зависимости от платформы: всё настроено, на экране отрисовываются объекты, так как теперь получить доступ к видеопроцессору?


Шаг 1: загрузка кода GLSL

Для создания сцены мы используем JavaScript. В других ситуациях может использоваться C++, Lua или любой другой язык. Вне зависимости от этого шейдеры пишутся на специальном языке шейдеров. Язык шейдеров OpenGL называется GLSL (OpenGL Shading Language). Поскольку мы используем WebGL, основанный на OpenGL, то будем писать на GLSL.

Как и где пишется код GLSL? Основное правило: код GLSL загружается в качестве строки. Затем её можно передать для парсинга и выполнения в видеопроцессоре.

В JavaScript это можно сделать простой передачей кода внутри переменной, вот так:


var shaderCode = "Здесь код шейдера;"


Это сработает, но поскольку в JavaScript нет простых способов создания многострочных строк, это для нас не очень удобно. Большинство программистов пишет код шейдеров в текстовых файлах и даёт им расширение .glsl или .frag (сокращение от fragment shader), а затем просто загружает файл.

Это возможно, но мы в этом туториале мы будем писать код шейдеров внутри нового тега <script> и загружать его в JavaScript оттуда, чтобы весь код находился в одном файле.

Создадим новый тег <script> в HTML, который выглядит вот так:


<script id="fragShader" type="shader-code">;


</script>


Мы присвоим ему ID fragShader, чтобы позже можно было получить к нему доступ. Тип shader-code — это на самом деле несуществующий, выдуманный тип скрипта. (Можно выбрать для него любое другое имя). Мы делаем так затем, чтобы код не выполнялся и не отображался в HTML.

Теперь давайте вставим очень простой шейдер, возвращающий белый цвет.


<script id="fragShader" type="shader-code">

void main() {

gl_FragColor = vec4(1.0,1.0,1.0,1.0);

}

</script>


(Компоненты vec4 в этом случае соответствуют значению RGBA, ка объяснялось в начале туториала.)

Теперь нам нужно загрузить этот код. Мы сделаем это простой строкой на JavaScript, находящей HTML-элемент и получающей внутренний текст:


var shaderCode = document.getElementById("fragShader").innerHTML;


Он должен находится под кодом куба.

Не забывайте: только загруженный как строка код будет парситься как действительный код GLSL (то есть void main() {...}. Остальное — это просто boilerplate HTML.)

Можете создать форк и отредактировать проект на CodePen.


Шаг 2: применение шейдера

Способы применения шейдера могут отличаться, они зависят от используемой платформы и её интерфейса с видеопроцессором. С этим никогда не возникает трудностей, и быстрым поиском можно выяснить, как создать объект и применить к нему шейдеры с помощью Three.js .

Нам нужно создать специальный материал и передать его коду шейдера. В качестве объекта шейдера мы создадим плоскость (но можем использовать и куб). Вот что нужно сделать:


// Создаём объект, к которому нужно применить шейдер

var material = new THREE.ShaderMaterial({fragmentShader:shaderCode})

var geometry = new THREE.PlaneGeometry( 10, 10 );

var sprite = new THREE.Mesh( geometry,material );

scene.add( sprite );

sprite.position.z = -1;// Перемещаем его назад, чтобы его было видно


На этом этапе вы должны видеть белый экран.

Можете создать форк и отредактировать проект на CodePen.

Если заменить цвет в коде шейдера на любой другой и обновить страницу, то вы увидите новый цвет.

Задача: Сможете ли вы сделать одну часть экрана красной, а другую синей? (Если не получится, то на следующем шаге я дам подсказку!)


Шаг 3: передача данных

На этот момент мы уже можем делать с шейдером всё, что угодно, но пока не знаем, что можно сделать. У нас есть только встроенная возможность определения положения пикселя gl_FragCoord и, если вы помните, это положение нормализовано. Нам как минимум нужно знать размеры экрана.

Для передачи данных шейдеру нам нужно отправить то, что мы назвали uniform-переменной. Для этого мы создаём объект uniforms и добавляем к нему наши переменные. Вот синтаксис передачи разрешения экрана:

var uniforms = {};

uniforms.resolution = {type:'v2',value:new THREE.Vector2(window.innerWidth,window.innerHeight)};

Каждая uniform-переменная дожна иметь тип и значение. В данном случае это двухмерный вектор, координатами которого являются ширина и высота окна. В таблице ниже (взятой из документации Three.js ) представлены все типы данных, которые можно отправлять, вместе с их идентификаторами:



Строка типа Uniform

Тип GLSL

Тип JavaScript

'i', '1i'


int


Number


'f', '1f'

float


Number


'v2'


vec2


THREE.Vector2


'v3'


vec3


THREE.Vector3


'c'

vec3


THREE.Color


'v4'

vec4


THREE.Vector4


'm3'

mat3


THREE.Matrix3


'm4'

mat4


THREE.Matrix4


't'

sampler2D


THREE.Texture


't'

samplerCube


THREE.CubeTexture


Чтобы отправить его шейдеру, изменим формирователь экземпляров ShaderMaterial, добавив туда вектор:


var material = new THREE.ShaderMaterial({uniforms:uniforms,fragmentShader:shaderCode})


Мы ещё не закончили! Теперь, когда шейдер получает эту переменную, нам нужно с ней что-то сделать. Давайте создадим градиент, как мы делали это раньше: нормализовав координату и используя её для создания значения цвета.

Изменим код шейдера следующим образом:


uniform vec2 resolution;// Здесь сначала должны быть объявлены uniform-переменные

void main() {

// Теперь можно нормализовать координату

vec2 pos = gl_FragCoord.xy / resolution.xy;

// И создать градиент!

gl_FragColor = vec4(1.0,pos.x,pos.y,1.0);

}


В результате у нас получится красивый градиент!

Можете создать форк и отредактировать проект на CodePen.

Задача: Попробуйте разделить экран на четыре равных части разных цветов. Примерно вот так:


Шаг 4: обновление данных

Хорошо, что мы научились отправлять данные в шейдер, но что если нам нужно их обновить? Например, если открыть предыдущий пример в новой вкладке , а потом изменить размер окна, то градиент не обновится, потому что использует прежние размеры экрана.

Для обновления переменных обычно просто заново отправляют uniform-переменную. Однако в Three.js достаточно просто обновить объект uniforms в функции render, повторно отправлять данные шейдеру не требуется.

Вот как выглядит функция рендеринга после внесения изменений:


function render() {

cube.rotation.y += 0.02;

uniforms.resolution.value.x = window.innerWidth;

uniforms.resolution.value.y = window.innerHeight;


requestAnimationFrame( render );

renderer.render( scene, camera );

}


Если открыть новый CodePen и изменить размер окна, то вы увидите, как изменяются цвета, несмотря на то, что изначальный размер окна просмотра остался тем же). Проще всего это заметить, посмотрев на цвета в углах и убедившись, что они не меняются.

Примечание: Отправка данных в видеопроцессор обычно является затратной задачей. Отправка нескольких переменных за один кадр вполне нормальна, но при передаче сотен переменных за кадр частота смены кадров значительно снизится. Кажется, что такое маловероятно, но если на экране есть несколько сотен объектов, и ко всем ним применено освещение с разными свойствами, то всё быстро выходит из под контроля. Позже мы узнаем про оптимизацию шейдеров.

Задача: Попробуйте постепенно изменять цвета.


Шаг 5: работа с текстурами

Вне зависимости от способа загрузки и формата текстур на всех платформах они передаются в шейдер как uniform-переменные.

Небольшое примечание о загрузке файлов в JavaScript: можно без проблем загружать изображения с внешнего URL (именно так мы и будем делать). Однако если вы захотите загрузить изображение локально, то возникнут проблемы с разрешениями, потому что JavaScript обычно не может и не должен иметь доступа к файлам в системе. Простейший способ решения — запустить локальный Python-сервер , что на самом деле проще, чем кажется.

В Three.js есть небольшая удобная функция для загрузки изображения как текстуры:


THREE.ImageUtils.crossOrigin = '';// Позволяет загружать внешнее изображение

var tex = THREE.ImageUtils.loadTexture( "https://tutsplus.github.io/Beginners-Guide-to-Shaders/Part2/SIPI_Jelly_Beans.jpg" );


Первая строка задаётся только один раз. В неё можно вставить любой URL изображения.

Затем нам нужно добавить текстуру к объекту uniforms.


uniforms.texture = {type:'t',value:tex};


И, наконец, нам нужно объявить uniform-переменную в коде шейдера, а потом отрисовать её тем же способом, как мы делали ранее — с помощью функции texture2D:


uniform vec2 resolution;

uniform sampler2D texture;

void main() {

vec2 pos = gl_FragCoord.xy / resolution.xy;

gl_FragColor = texture2D(texture,pos);

}


На экране должно появиться растянутое изображение конфет:



(Это изображение является стандартным тестовым изображением в компьютерной графике, оно взято у Института обработки сигналов и изображений (SIPI) (поэтому на нём показана аббревиатура IPI) Университета Южной Калифорнии. Думаю, оно нам подходит, ведь мы как раз изучаем графические шейдеры!)

Задача: Попробуйте постепенно менять цвета текстуры с полного цвета на градации серого.


Дополнительный шаг: применяем шейдеры к другим объектам

В созданной нами плоскости нет ничего особенного. Мы могли применить шейдер и к кубу. На самом деле можно просто заменить строку с геометрией плоскости:


var geometry = new THREE.PlaneGeometry( 10, 10 );


на:


var geometry = new THREE.BoxGeometry( 1, 1, 1 );


Вуаля, конфеты нарисованы на кубе:



Вы можете возразить: «Постойте, но ведь это не похоже на правильную проекцию текстуры на куб!» Вы правы: если посмотреть на шейдер, то видно, что мы на самом деле говорим ему «размести все пиксели этого изображения на экране». То, что они находятся на кубе, просто означает, что все пиксели за его пределами отбрасываются.

Если бы мы хотели применить текстуру так, чтобы она выглядела физически нарисованной на кубе, то наша работа напоминала бы изобретение заново 3D-движка (что было бы довольно глупо, учитывая, что мы уже используем 3D-движок и мы можем просто попросить его отрисовать текстуру отдельно на каждой стороне). В этом туториале мы используем шейдеры для того, что иначе достичь было бы невозможно, так что мы не будем вдаваться в такие подробности. (Если вы жаждете узнать больше, на Udacity есть отличный курс по основам 3D-графики !)


Так же в этом разделе:
 
MyTetra Share v.0.58
Яндекс индекс цитирования