Статьи

Что вы подразумеваете под «шейдерами»? Как создать их с помощью HTML5 и WebGL

Эта статья является частью серии технологий веб-разработки от Microsoft. Спасибо за поддержку партнеров, которые делают возможным использование SitePoint.

Возможно, вы заметили, что мы впервые много говорили о babylon.js в прошлом году, а совсем недавно мы выпустили babylon.js v2.0 с 3D-позиционированием звука (с WebAudio) и объемным рассеянием света.

Если вы пропустили анонс v1.0, сначала вы можете ознакомиться с основным докладом на второй день здесь и перейти непосредственно к 2:24-2:28. В нем евангелисты Microsoft Стивен Гуггенхаймер и Джон Шевчук продемонстрировали, как поддержка Oculus Rift была добавлена ​​в Babylon.js. И одна из ключевых вещей для этой демонстрации была работа над конкретным шейдером для имитации линз, как вы можете видеть на этом рисунке:

Демонстрация поддержки Oculus Rift в Babylon.js

Я также представил сессию с Фрэнком Оливье и Беном Констеблем о графике в IE и Babylon.js.

Это приводит меня к одному из часто задаваемых вопросов о babylon.js: Что вы подразумеваете под шейдерами? Итак, сегодня я попытаюсь объяснить вам, как работают шейдеры.

Теория

Прежде чем начать экспериментировать, мы должны сначала посмотреть, как все работает внутри.

Когда речь идет об аппаратном ускорении 3D, мы обсуждаем два процессора: основной и графический процессор. Графический процессор является своего рода чрезвычайно специализированным процессором.

GPU — это конечный автомат, который вы настроили с помощью CPU. Например, процессор настроит графический процессор для рендеринга линий вместо треугольников. Или это определит, что прозрачность включена и так далее.

После того, как все состояния установлены, CPU определит, что нужно визуализировать (геометрия, которая состоит из списка точек (называемых вершинами и сохраненных в массиве, называемого буфером вершин) и списка индексов (грани или треугольники). Хранится в массиве, называемом индексным буфером).

Последний шаг для CPU — определить, как визуализировать геометрию, и для этой конкретной задачи CPU определит шейдеры для GPU. Шейдеры — это кусок кода, который GPU будет выполнять для каждой вершины и пикселей, которые он должен визуализировать.

Сначала немного словарного запаса: думайте о вершине (вершинах, когда их несколько) как о «точке» в 3D-среде, а не как о точке в 2D-среде.

Существует два вида шейдеров: вершинный шейдер и пиксельный (или фрагментный) шейдер.

Графический конвейер

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

  • Используя буфер индекса, три вершины собираются для определения треугольника: буфер индекса содержит список индексов вершин. Это означает, что каждая запись в буфере индекса является номером вершины в буфере вершин. Это действительно полезно, чтобы избежать дублирования вершин. Например, следующий индексный буфер представляет собой список из двух граней: [1 2 3 1 3 4]. Первая грань содержит вершину 1, вершину 2 и вершину 3. Вторая грань содержит вершину 1, вершину 3 и вершину 4. Таким образом, в этой геометрии есть 4 вершины:

  • Диаграмма вершинного буфера

  • Вершинный шейдер применяется к каждой вершине треугольника. Основная цель вершинного шейдера — создать пиксель для каждой вершины (проекция на 2D-экран 3D-вершины):

  • Координатная диаграмма

  • Используя эти 3 пикселя (которые определяют 2d треугольник на экране), графический процессор будет интерполировать все значения, прикрепленные к пикселю (по крайней мере, его положение), и пиксельный шейдер будет применен к каждому пикселю, включенному в 2d треугольник, для генерации цвет для каждого пикселя:

  • Пиксельная интерполяция с двумерным треугольником

  • Этот процесс выполняется для каждого лица, определенного буфером индекса.

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

GLSL

Мы только что видели, что для рендеринга треугольников графическому процессору нужны два шейдера: вершинный шейдер и пиксельный шейдер. Эти шейдеры написаны на языке GLSL (Graphics Library Shader Language). Похоже на C.

Для Internet Explorer 11 мы разработали компилятор для преобразования GLSL в HLSL (высокоуровневый язык шейдеров), который является языком шейдеров DirectX 11. Это позволяет IE11 гарантировать безопасность кода шейдера (вы не хотите сбрасывать компьютер при использовании WebGL):

Диаграмма процесса

precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } 

Структура вершинного шейдера

Вершинный шейдер содержит следующее:

  • Атрибуты : Атрибут определяет часть вершины. По умолчанию вершина должна как минимум содержать позицию ( vector3:x, y, z ). Но как разработчик вы можете добавить больше информации. Например, в предыдущем шейдере есть vector2 именем uv (координаты текстуры, который позволяет применять 2D-текстуру к 3D-объекту)
  • Униформа : Униформа — это переменная, используемая шейдером и определяемая процессором. Единственная униформа, которую мы имеем здесь, это матрица, используемая для проекции положения вершины (x, y, z) на экран (x, y).
  • Варьирование : переменные — это значения, созданные вершинным шейдером и переданные в пиксельный шейдер. Здесь вершинный шейдер передает значение vUV (простая копия uv ) в пиксельный шейдер. Это означает, что здесь определен пиксель с позицией и координатами текстуры. Эти значения будут интерполироваться графическим процессором и использоваться пиксельным шейдером.
  • main : функция с именем main — это код, выполняемый графическим процессором для каждой вершины и, по крайней мере, должен генерировать значение для gl_position (позиция на экране текущей вершины).

Из нашего примера видно, что вершинный шейдер довольно прост. Он генерирует системную переменную (начиная с gl_ ) с именем gl_position для определения позиции связанного пикселя и устанавливает переменную переменную, называемую vUV .

Вуду за матрицами

В нашем шейдере есть матрица с именем worldViewProjection . Мы используем эту матрицу для проецирования позиции вершины в переменную gl_position . Это круто, но как мы можем получить значение этой матрицы? Это униформа, поэтому мы должны определить ее на стороне процессора (используя JavaScript).

Это одна из сложных частей создания 3D. Вы должны понимать сложную математику (или вам придется использовать 3D-движок, такой как babylon.js, который мы увидим позже).

Матрица worldViewProjection представляет собой комбинацию из 3 различных матриц:

WorldViewProjection матрицы

Использование полученной матрицы позволяет нам преобразовывать 3d-вершины в 2d-пиксели с учетом точки зрения и всего, что связано с положением / масштабом / вращением текущего объекта.

Это ваша ответственность как разработчика 3D: создавать и поддерживать эту матрицу в актуальном состоянии.

Вернуться к шейдерам

После выполнения вершинного шейдера в каждой вершине (затем три раза) у нас есть три пикселя с правильным gl_position и значением _vUV _. Затем графический процессор будет интерполировать эти значения на каждый пиксель, содержащийся в треугольнике, создаваемом этими пикселями.

Затем для каждого пикселя будет выполняться пиксельный шейдер:

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); } 

Структура пиксельного (или фрагментного) шейдера

Структура пиксельного шейдера похожа на вершинный шейдер:

  • Варьирование : переменные — это значения, созданные вершинным шейдером и переданные в пиксельный шейдер. Здесь пиксельный шейдер получит значение vUV от вершинного шейдера.
  • Униформа : Униформа — это переменная, используемая шейдером и определяемая процессором. Единственная униформа, которую мы имеем здесь, — это сэмплер, который используется для чтения цветов текстур.
  • main : функция с именем main является кодом, выполняемым графическим процессором для каждого пикселя, и должна по крайней мере генерировать значение для gl_FragColor (цвет текущего пикселя).

Этот пиксельный шейдер довольно прост: он считывает цвет из текстуры, используя координаты текстуры из вершинного шейдера (который, в свою очередь, получил его из вершины).

Хотите увидеть результат такого шейдера? Вот:

Результат пиксельного шейдера

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

Чтобы достичь этого результата, вам придется иметь дело с большим количеством кода WebGL. Действительно, WebGL — это действительно мощный, но очень низкоуровневый API, и вы должны делать все самостоятельно, от создания буферов до определения структур вершин. Вы также должны сделать всю математику и установить все состояния и обработать загрузку текстуры и так далее…

Слишком сложно? BABYLON.ShaderМатериал на помощь

Я знаю, что вы думаете: шейдеры действительно крутые, но я не хочу беспокоиться о внутренней сантехнике WebGL или даже математике.

И ты прав! Это вполне законный вопрос, и именно поэтому я создал Babylon.js.

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

 < !DOCTYPE html> <html> <head> <title>Babylon.js</title> <script src="Babylon.js"></script> <script type="application/vertexShader" id="vertexShaderCode"> precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Normal varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } </script> <script type="application/fragmentShader" id="fragmentShaderCode"> precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); } </script> <script src="index.js"></script> <style> html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; margin: 0px; overflow: hidden; } #renderCanvas { width: 100%; height: 100%; touch-action: none; -ms-touch-action: none; } </style> </head> <body> <canvas id="renderCanvas"></canvas> </body> </html> 

Вы заметите, что шейдеры определены тегами script . С Babylon.js вы также можете определить их в отдельных файлах (.fx файлах).

Вы можете получить babylon.js здесь или в нашем репозитории GitHub . Вы должны использовать версию 1.11 или выше, чтобы получить доступ к BABYLON.StandardMaterial.

И, наконец, основной код JavaScript выглядит следующим образом:

 "use strict"; document.addEventListener("DOMContentLoaded", startGame, false); function startGame() { if (BABYLON.Engine.isSupported()) { var canvas = document.getElementById("renderCanvas"); var engine = new BABYLON.Engine(canvas, false); var scene = new BABYLON.Scene(engine); var camera = new BABYLON.ArcRotateCamera("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene); camera.attachControl(canvas); // Creating sphere var sphere = BABYLON.Mesh.CreateSphere("Sphere", 16, 5, scene); var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] }); amigaMaterial.setTexture("textureSampler", new BABYLON.Texture("amiga.jpg", scene)); sphere.material = amigaMaterial; engine.runRenderLoop(function () { sphere.rotation.y += 0.05; scene.render(); }); } }; 

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

При создании BABYLON.ShaderMaterial необходимо указать элемент DOM, используемый для хранения шейдеров, или базовое имя файлов, в которых находятся шейдеры. Если вы решите использовать файлы, вы должны создать файл для каждого шейдера и использовать следующий шаблон basename.vertex.fx и basename.fragment,.fx . Тогда вам придется создать такой материал:

 var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader",{ attributes: ["position", "uv"], uniforms: ["worldViewProjection"] }); 

Вы также должны указать имя атрибутов и униформы, которые вы используете.

Затем вы можете напрямую установить значения ваших униформ и сэмплеров, используя функции setTexture , setFloat , setFloats , setColor3 , setColor4 , setVector2 , setVector3 , setVector4 , setMatrix functions .

Вы помните предыдущую матрицу worldViewProjection ? Используя Babylon.js и BABYLON.ShaderMaterial , вам не о чем беспокоиться! BABYLON.ShaderMaterial автоматически вычислит его для вас, потому что вы объявите его в списке униформы. BABYLON.ShaderMaterial также может обрабатывать следующие матрицы для вас:

  • Мир
  • Посмотреть
  • проекция
  • WorldView
  • worldViewProjection

Больше нет необходимости в математике. Например, каждый раз, когда вы выполняете sphere.rotation.y += 0.05 , мировая матрица сферы генерируется для вас и передается в графический процессор.

CYOS: создайте свой собственный шейдер

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

Я использовал редактор кода ACE для CYOS. Это невероятный редактор кода с подсветкой синтаксиса. Не стесняйтесь взглянуть на это здесь . Вы можете найти CYOS здесь .

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

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

Кнопка компиляции используется для создания нового BABYLON.ShaderMaterial из ваших шейдеров. Код, используемый этой кнопкой, следующий:

 // Compile shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "normal", "uv"], uniforms: ["world", "worldView", "worldViewProjection"] }); var refTexture = new BABYLON.Texture("ref.jpg", scene); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = new BABYLON.Texture("amiga.jpg", scene); shaderMaterial.setTexture("textureSampler", amigaTexture); shaderMaterial.setTexture("refSampler", refTexture); shaderMaterial.setFloat("time", 0); shaderMaterial.setVector3("cameraPosition", BABYLON.Vector3.Zero()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial; 

Материал готов отправить вам три предварительно вычисленные матрицы ( world , worldView и worldViewProjection ). Вершины будут иметь координаты положения, нормали и текстуры. Две текстуры также уже загружены для вас:

Текстура шахматной доски для использования в качестве отправной точки

Форма глобуса для использования в качестве отправной точки

И, наконец, вот renderLoop где я обновляю две удобные формы:

  • Один вызванный time , чтобы получить забавные анимации
  • Один из них называется cameraPosition чтобы получить положение камеры в ваших шейдерах (будет полезно для уравнений освещения)
 engine.runRenderLoop(function () { mesh.rotation.y += 0.001; if (shaderMaterial) { shaderMaterial.setFloat("time", time); time += 0.02; shaderMaterial.setVector3("cameraPosition", camera.position); } scene.render(); }); 

Благодаря работе, которую мы проделали с Windows Phone 8.1, мы также можем использовать CYOS на вашем Windows Phone (это всегда хорошее время для создания шейдеров):

Приборная панель CYOS

Базовый шейдер

Итак, давайте начнем с самого первого шейдера, определенного в CYOS: Базовый шейдер.

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

Чтобы вычислить позицию пикселя, нам просто нужна матрица worldViewProjection и позиция вершины:

 precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } 

Координаты текстуры (uv) передаются неизмененными в пиксельный шейдер.

Обратите внимание, что нам нужно добавить precision mediump float; в первой строке как для вершинных, так и для пиксельных шейдеров, потому что Chrome этого требует. Он определяет, что для повышения производительности мы не используем плавающие значения полной точности.

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

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); } 

Ранее мы видели, что форма textureSampler заполнена текстурой «amiga», в результате получается следующее:

Базовый результат шейдера

Черно-белый шейдер

Теперь давайте продолжим с новым шейдером: черно-белым шейдером.

Цель этого шейдера — использовать предыдущий, но с черно-белым режимом рендеринга.

Для этого мы можем сохранить тот же вершинный шейдер. Пиксельный шейдер будет немного изменен.

Первый вариант, который у нас есть, это взять только один компонент, например, зеленый:

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0); } 

Как видите, вместо использования .rgb (эта операция называется swizzle ) мы использовали .ggg .

Но если нам нужен действительно точный черно-белый эффект, лучше рассчитать яркость (которая учитывает все компоненты):

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { float luminance = dot(texture2D(textureSampler, vUV).rgb, vec3(0.3, 0.59, 0.11)); gl_FragColor = vec4(luminance, luminance, luminance, 1.0); } 

Точечная операция (или точечный продукт) вычисляется следующим образом:

 result = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z 

Итак, в нашем случае:

 luminance = r * 0.3 + g * 0.59 + b * 0.11 (This values are based on the fact that human eye is more sensible to green) 

Звучит круто, не правда ли?

Черно-белый результат шейдера

Затенение ячейки

Теперь давайте перейдем к более сложному шейдеру: шейдерный шейдер.

Для этого потребуется получить нормаль вершины и положение вершины в пиксельном шейдере. Так что вершинный шейдер будет выглядеть так:

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 world; uniform mat4 worldViewProjection; // Varying varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vPositionW = vec3(world * vec4(position, 1.0)); vNormalW = normalize(vec3(world * vec4(normal, 0.0))); vUV = uv; } 

Обратите внимание, что мы также используем матрицу world потому что положение и нормаль сохраняются без каких-либо преобразований, и мы должны применять матрицу мира, чтобы учесть вращение объекта.

Пиксельный шейдер выглядит следующим образом:

 precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { float ToonThresholds[4]; ToonThresholds[0] = 0.95; ToonThresholds[1] = 0.5; ToonThresholds[2] = 0.2; ToonThresholds[3] = 0.03; float ToonBrightnessLevels[5]; ToonBrightnessLevels[0] = 1.0; ToonBrightnessLevels[1] = 0.8; ToonBrightnessLevels[2] = 0.6; ToonBrightnessLevels[3] = 0.35; ToonBrightnessLevels[4] = 0.2; vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); vec3 color = texture2D(textureSampler, vUV).rgb; if (ndl > ToonThresholds[0]) { color *= ToonBrightnessLevels[0]; } else if (ndl > ToonThresholds[1]) { color *= ToonBrightnessLevels[1]; } else if (ndl > ToonThresholds[2]) { color *= ToonBrightnessLevels[2]; } else if (ndl > ToonThresholds[3]) { color *= ToonBrightnessLevels[3]; } else { color *= ToonBrightnessLevels[4]; } gl_FragColor = vec4(color, 1.); } 

Цель этого шейдера — моделировать свет, и вместо вычисления плавного затенения мы будем учитывать, что свет будет применяться в соответствии с определенными пороговыми значениями яркости. Например, если интенсивность света находится между 1 (максимум) и 0,95, цвет объекта (извлеченный из текстуры) будет применен напрямую. Если интенсивность составляет от 0,95 до 0,5, цвет будет ослабляться с коэффициентом 0,8 и так далее.

Итак, в этом шейдере четыре шага:

  • Сначала мы объявляем пороги и уровни констант
  • Затем нам нужно вычислить освещение, используя уравнение Фонга (мы считаем, что свет не движется):
 vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); 

Интенсивность света на пиксель зависит от угла между нормой и направлением света.

  • Тогда мы получаем цвет текстуры для пикселя
  • И наконец мы проверяем порог и применяем уровень к цвету

Результат выглядит как мультипликационный объект:

Результат затенения ячейки

Фонг шейдер

Мы используем часть уравнения Фонга в предыдущем шейдере. Итак, давайте попробуем использовать его полностью сейчас.

Вершинный шейдер здесь явно прост, потому что все будет сделано в пиксельном шейдере:

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vUV = uv; vPosition = position; vNormal = normal; } 

Согласно уравнению вы должны вычислить диффузную и зеркальную части , используя направление света и нормаль вершины:

 precision highp float; precision highp float; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; // Uniforms uniform mat4 world; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 vLightPosition = vec3(0, 20, 10); // World values vec3 vPositionW = vec3(world * vec4(vPosition, 1.0)); vec3 vNormalW = normalize(vec3(world * vec4(vNormal, 0.0))); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); vec3 color = texture2D(textureSampler, vUV).rgb; // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); // Specular vec3 angleW = normalize(viewDirectionW + lightVectorW); float specComp = max(0., dot(vNormalW, angleW)); specComp = pow(specComp, max(1., 64.)) * 2.; gl_FragColor = vec4(color * ndl + vec3(specComp), 1.); } 

Мы уже использовали диффузную часть в предыдущем шейдере, поэтому здесь нам просто нужно добавить зеркальную часть. Эта картинка из статьи в Википедии хорошо объясняет, как работает шейдер:

Компоненты отражения Phong

Результат по нашей сфере:

Phong Shader результат

Сбросить шейдер

Для шейдера Discard я хотел бы представить новую концепцию: ключевое слово discard .

Этот шейдер отбрасывает каждый не красный пиксель и создает иллюзию вырытого объекта.

Вершинный шейдер аналогичен базовому шейдеру:

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } 

Пиксельный шейдер на его стороне должен будет проверить цвет и использовать discard когда, например, зеленый компонент слишком высок:

 precision highp float; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { vec3 color = texture2D(textureSampler, vUV).rgb; if (color.g > 0.5) { discard; } gl_FragColor = vec4(color, 1.); } 

Результат смешной:

Отменить результат шейдера

Волновой шейдер

Мы много играли с пиксельными шейдерами, но я также хотел показать вам, что мы можем многое сделать с вершинными шейдерами.

Для шейдера Wave мы будем использовать пиксельный шейдер Phong.

Вершинный шейдер будет использовать унифицированное time для получения анимированных значений. Используя эту униформу, шейдер генерирует волну с позициями вершин:

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; uniform float time; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec3 v = position; vx += sin(2.0 * position.y + (time)) * 0.5; gl_Position = worldViewProjection * vec4(v, 1.0); vPosition = position; vNormal = normal; vUV = uv; } 

Синус применяется к position.y и в результате получается следующее:

Результат волнового шейдера

Сферическое картографирование среды

Этот был БОЛЬШОЙ вдохновлен этим уроком. Я дам вам прочитать эту прекрасную статью и поиграть с соответствующим шейдером.

Результат отображения сферической среды

Шейдер френеля

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

Этот шейдер используется для применения различной интенсивности в зависимости от угла между направлением обзора и нормалью вершины.

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

 precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 color = vec3(1., 1., 1.); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Fresnel float fresnelTerm = dot(viewDirectionW, vNormalW); fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.); gl_FragColor = vec4(color * fresnelTerm, 1.); } 

Результат Френеля

Твой шейдер?

Теперь вы более готовы создать свой собственный шейдер. Не стесняйтесь использовать комментарии здесь или на форуме babylon.js, указанном ниже, чтобы поделиться своими экспериментами!

Если вы хотите пойти дальше, вот несколько полезных ссылок:

И еще немного информации:

Или, возвращаясь назад, серия обучения нашей команды по JavaScript:

И, конечно же, вы всегда можете использовать некоторые из наших бесплатных инструментов для создания своего следующего веб-интерфейса: сообщество Visual Studio , пробную версию Azure и средства межбраузерного тестирования для Mac, Linux или Windows.

Эта статья является частью серии технологий веб-разработки от Microsoft. Мы рады поделиться с вами Project Spartan и его новым механизмом рендеринга . Получите бесплатные виртуальные машины или проведите удаленное тестирование на устройстве Mac, iOS, Android или Windows на сайте modern.IE .