Начало работы с шейдером WebGL от Element3 (1)

внешний интерфейс Element Vue.js

Список

  • начиная
    • Конфигурация среды
    • Первый работающий шейдер
    • Рисование графики — прямоугольники и круги
    • Встречайте SmoothStep
  • Первый взгляд на GLSL
    • Векторы и матрицы
    • точность с плавающей запятой
    • uniform
  • цвет и форма
    • Основы цвета
    • Создание градиентных цветов
    • Рендеринг прямоугольника со скругленными углами
    • полиморфный рендеринг
  • Математика и графика
    • полярная система координат
    • векторная геометрия
    • Тригонометрические функции
    • Тип
  • генеративное искусство
    • шум
    • шумовое поле
    • наложение
    • размыто

Конфигурация среды

Шаг 1. Прежде всего, конечно, установить nodejs, Мы можем загрузить установочный пакет соответствующей операционной системы и набора инструкций ЦП с nodejs.org или установить его с помощью таких инструментов, как доморощенный и apt. у конечных инженеров уже есть среда nodejs Подробное описание.

шаг 2. (Необязательно) Установите vite глобально.Для того, чтобы использовать vite более удобно, рекомендуется установить vite глобально. Если vite не установлен глобально, мы должны использовать npx для выполнения vite для этого проекта. использоватьnpm install -g viteЗаказал.

шаг 3. Инициализируйте проект, создайте новый каталог по любимому пути, например, здесь я создал element3-demo

mkdir element3-demo
cd element3-demo

После входа в каталог выполнитеnpm init, и заполните необходимую информацию. После этого мы получили базовый файл package.json.

шаг 4. Далее мы добавляем зависимости в проект и устанавливаем соответствующие пакеты

Сначала мы открываем package.json в нашем любимом текстовом редакторе и добавляем в него зависимости и devDependencies:

{
  "dependencies": {
    "element3-core": "0.0.7",
    "vue": "^3.0.5"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.2.2",
    "@vue/compiler-sfc": "^3.0.5",
    "rollup-plugin-element3-webgl": "0.0.5",
    "typescript": "^4.1.3",
    "vite": "^2.3.0",
    "vue-tsc": "^0.0.24"
  }
}

После этого возвращаемся в терминал и используем команду npm install.

Шаг 5. Создайте файлы и базовую структуру каталогов.

записыватьindex.htmlдокумент:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

записыватьsrc/main.tsдокумент:

import { createApp } from "vue";
import App from "./App.vue";

createApp(App).mount("#app");

записыватьsrc/app.vueдокумент:

<template>
<div>
    Hello
</div>
</template>
<script lang="ts">

import { defineComponent } from "vue";

export default defineComponent({
  name: "App",
  components: {

  },
  setup(){
    return {
      
    }
  }
});

</script>

Напишите файл vite.config.js:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import element3Webgl from "rollup-plugin-element3-webgl";

// https://vitejs.dev/config/
export default defineConfig({
  base: "/", // TODO 开发环境是 / 生产环境是 /webgl
  plugins: [vue(), element3Webgl()],
});

После написания используем командную строкуnpx vite, откройте веб-страницу и увидите Hello, что указывает на то, что среда настроена.

Первый работающий шейдер

Далее мы создаемsrc/pure.fragдокумент.

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

Сначала мы должны понять концепцию фрагментного шейдера. Фрагментный шейдер — это процесс рисования точки на экране. Его частота выполнения очень высока, чтобы отрисовать изображение размером 100x100, ему нужно выполнить код в шейдере 10 000 раз, а шейдер обычно берет на себя GPU.

Затем мы пишем фрагмент кода, чтобы закрасить область холста сплошным цветом:

precision mediump float;

void main(){
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

Подключаемый модуль свертывания element3 может напрямую загружать код шейдера в компонент vue, что может помочь нам игнорировать утомительный процесс вызова WebGL API.

Затем мы изменим код App.vue, чтобы показать эффект этого рисунка:

<template>
<div>
    <DrawBlock width=100 height=100></DrawBlock>
</div>
</template>
<script lang="ts">

import { defineComponent } from "vue";
import DrawBlock from "./pure.frag";

export default defineComponent({
  name: "App",
  components: {
    DrawBlock
  },
  setup(){
    return {
      
    }
  }
});

</script>

Мы видим сплошную красную квадратную область.

Давайте немного объясним этот код GLSL.

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

Весь код GLSL взят изmainфункция начинает выполняться. В GLSL,mainФункция не может возвращать значение.Для такого типа функций мы используем пустоту вместо части типа.

Далее мы смотрим наmainТело функции, в теле функции есть только один оператор. Здесь мы используемgl_fragColorПеременная, это имя — это имя, заданное языком GLSL, а не переменная, имя которой может быть произвольным. Как мы уже говорили, фрагментный шейдер — это код для рисования точки.gl_fragColorЭто цвет точки, которую мы хотим вывести в конце.

Далее мы смотрим на другой конец знака равенства, здесьvec4Представляет тип вектора с плавающей запятой длины 4, который может хранить 4 числа с плавающей запятой. Вы помните вектор, который вы узнали в линейной алгебре? здесьvec4Это из концепции вектора в математике. Это немного похоже на массив в JavaScript, за исключением того, что он имеет фиксированную длину.Эта структура данных очень полезна для графовых алгоритмов, и мы будем часто иметь дело с ней в будущем.

Наконец, напоминание о том, что язык GLSL не позволяет опускать точки с запятой. Если вы забудете об этом, это приведет к сбою компиляции всей программы. Обратите внимание.

На данный момент мы узнали, как использовать подключаемый модуль агрегирования element3 для загрузки фрагментного шейдера и получения базовой среды отладки и выполнения кода.

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

Рисование графики — прямоугольники и круги

Во-первых, попробуем сузить диапазон отрисовки.Для управления диапазоном мы должны знать координаты текущей точки рисования.На этот раз мы введем еще одну важную переменную в GLSL:gl_fragCoord.

если мы предположимgl_fragColorЕсли это результат фрагментного шейдера,gl_fragCoordЭто ввод фрагментного шейдера, он представляет координаты текущей точки рисования, этоvec4type, но здесь нам нужно использовать только первые два его элемента.

Мы можем использоватьgl_fragCoord.xа такжеgl_fragCoord.yчтобы получить доступ к его координатам, вы также можете использоватьgl_fragCoord.xyчтобы превратить его в двумерный вектор.

Итак, вернемся к нашему вопросу, как нарисовать прямоугольник? Нам нужно только оценить его диапазон координат, см. пример кода:

precision mediump float;

void main(){
    if(gl_FragCoord.x > 25.0 && gl_FragCoord.x < 75.0 && 
        gl_FragCoord.y > 25.0 && gl_FragCoord.y < 75.0)
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    else
        gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}

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

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

После рисования квадрата давайте попробуем более сложный круг.Согласно знаниям аналитической геометрии в средней школе, мы можем знать, что круг представляет собой набор точек, расстояние от которых до центра круга меньше радиуса, поэтому мы можем использовать формулуx²+y²<r²нарисовать круг.

Конечно, мы можем использовать умножение для возведения в квадрат, но по принципу DRY для возведения в квадрат лучше использовать встроенные функции системы.В GLSL большинство математических функций можно использовать напрямую, не добавляя их, как в JS.Math..

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

precision mediump float;

void main(){
    if(pow(gl_FragCoord.x - 50.0, 2.0) + pow(gl_FragCoord.y - 50.0, 2.0) < pow(25.0, 2.0))
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    else
        gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}

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

Познакомьтесь с плавным шагом

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

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

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

    float l = sqrt(pow(gl_FragCoord.x - 50.0, 2.0) + pow(gl_FragCoord.y - 50.0, 2.0));

Далее пробуем смешать два цвета по переменной l, здесь вводим новую функциюmix, Он может смешивать два цвета в соответствии с соотношением (на самом деле есть и другие способы использования, не указанные).mixЕсть три параметра, первые два — это значения, которые нужно смешать, а последний параметр — это соотношение смеси.

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

precision mediump float;

void main(){
    float l = sqrt(pow(gl_FragCoord.x - 50.0, 2.0) + pow(gl_FragCoord.y - 50.0, 2.0));
    gl_FragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.0, 0.0, 1.0, 1.0), l / 25.0);
}

После выполнения мы видим очевидный градиент, но это не результат нашего конечного желаемого.Мы не хотим, чтобы весь круг стал постепенно меняться.Мы только надеемся, что круг близок к краю нескольких пикселей, хотя мы Этот эффект можно комбинировать с четырьмя операциями и ЕСЛИ, но GLSL предлагает более элегантное решение, то естьsmoothstepфункция.

Smoothstep принимает три параметра min, max и x. Его функция состоит в том, чтобы возвращать 0,0, когда x меньше min, возвращать 1,0, когда x больше max, и возвращать от 0,0 до 1,0, когда x находится между min и max. доля расстояния между x и min в этом интервале.

Далее давайте изменим код GLSL, чтобы использовать плавный шаг для рисования круга с плавными краями. Для очевидного эффекта диапазон плавного шага, преднамеренно установленный здесь, относительно велик.В реальных условиях более уместно делать размытие только на 1-2 пикселя.

precision mediump float;

void main(){
    float l = sqrt(pow(gl_FragCoord.x - 50.0, 2.0) + pow(gl_FragCoord.y - 50.0, 2.0));
    gl_FragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.0, 0.0, 1.0, 1.0), smoothstep(20.5, 25.5, l));
}

На данный момент, я полагаю, вы изучили основы плавного шага. Далее, давайте изучим и используем его. Наша следующая задача — нарисовать прямые линии.

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

Теорема: Дана прямая l, вектор ее направления равен m. A — точка вне l. Если требуется расстояние d от A до прямой l, то это может быть любая точка B на l, а вектор из точки A в точка B обозначается как n, тогдаd=mnnd = \frac{|m·n|}{|n|}

Согласно этой формуле, здесь нам нужно использовать операцию векторного скалярного произведенияdot, а функция длины вектораlength, и окончательный написанный код GLSL выглядит следующим образом:

precision mediump float;

void main(){
    vec2 m = vec2(1., -1.);
    vec2 n = vec2(25., 0.) - gl_FragCoord.xy;
    
    float d = length(dot(m, n)) / length(m);
    gl_FragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.0, 0.0, 1.0, 1.0), smoothstep(0.0, 1.0, d));
}

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

практические вопросы

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

Нарисуйте логотип Vue с помощью фрагментного шейдера.

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