Всем привет! Меня зовут Дядиченко Григорий, и я основатель и CTO студии Foxsys. Сегодня хочется поговорить про шейдеры. Умение писать шейдеры (и в целом работать с рендером) очень важно при разработке под мобильные платформы или AR/VR, если хочется добиться крутой графики. Многие разработчики считают, что шейдеры — это магия. Что по ним мало хорошей информации, и что чтобы их писать нужно иметь, как мимимум, звание кандидата наук. Да, разработка шейдеров по своим принципам сильно отличается от клиентской разработки. Но основное понимать базовые принципы работы шейдеров, а так же знать их суть, чтобы в этом не было ничего магического и поиск информации по этой теме был простой задачей. Данная серия статей рассчитана на новичков, так что если вы разбираетесь в программировании шейдеров, данная серия вам не будет интересна. Всем же кто хочет разобраться в этой теме — добро пожаловать под кат!



Это вводная статья в которой я расскажу общие принципы написания шейдеров. Если тема будет интересна, то мы разберём уже подробнее в отдельных статьях: вершинные шейдеры, геометрические шейдеры, фрагментные/пиксельные шейдеры, трипланарные шейдеры, скринспейс эффекты и компьют шейдеры (OpenCL, СUDA и т.п.). И в целом всю ту магию, которую можно делать на GPU. Разбираться это будет в контексте стандартного рендер пайплайна Unity. Так LWRP и HDRP мне пока кажутся немного сыроватыми.

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



Источник: www.shadertoy.com/view/MsGSRd

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

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



Если опустить детали связанные с освещением и т.п. То в целом с точки написания тех же Unlit шейдеров на hlsl суть такова. У нас есть в шейдере

#pragma vertex vert
#pragma fragment frag

где мы определяем, что вертексная часть шейдера будет писаться в функции vert, а фрагментная — в функции frag.

Структуры которые мы описываем в шейдере определяют какие данные мы будем забирать из меша и после обработки вертексным шейдером, которые висят на нашем MeshRenderer и MeshFilter объекте.

          struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

Дальше вертексный шейдер вычисляет получив на вход данные appdata и отдаёт результат в виде структуры v2f, которая дальнейшем пойдёт в фрагментный шейдер. Который в свою очередь уже рассчитает цвет пикселя. Так как информация v2f пишется только в вершины (которых меньше, чем пикселей), данные в фрагментной части интерполируются. Всё это можно представить как то, что vert считается в каждом вертексе независимо. Потом результат передаётся в фрагментную часть, где frag для каждого пикселя считается так же независимо. Так как вычисления производятся параллельно, в данных частях нет никакой информации о соседях (если не передавать её как-то хитро).

Более детально все нюансы, а так же множество примеров описаны в документации Unity docs.unity3d.com/Manual/SL-Reference.html

Языки программирования шейдеров



Источник: www.shadertoy.com/view/WsS3Dc

О чём ещё важно не забывать. О том, что шейдеры сейчас пишутся на трёх языках программирования, которые не имеют никакого отношения к юнити. CG, GLSL и HLSL. Самый простой способ писать шейдеры в юнити — это HLSL, так как именно на нём пишутся файлы шейдеров с разрешением .shader. И если по шейдерам в контексте юнити информации сравнительно мало, то информации отдельно по HLSL, GLSL и CG — просто тонны. В документации к шейдерам описано, каким образом написанное на этих языках перенести в Unity. Поэтому получается что почти вся информация в общем про эти языки программирования валидна. Все три языка очень сильно похожи на язык С, но у каждого свои особенности.

Дальше с точки зрения изучения шейдеров, когда эти языки уже не вызывают вопросов можно посмотреть какие возможности предоставляет сам по себе«UnityCG.cginc» и другие библиотеки написанные юнити, чтобы упростить себе работу.

Почему if в шейдерах — это плохо?



Источник: www.shadertoy.com/view/Md3cWr

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

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

Там есть нюансы и исключения, но для того чтобы спокойно писать if, вы должны понимать, как он себя поведёт на целевой версии графического апи. Так как тот же самый OpenGL ES 2 или DX11 в этом плане сильно отличаются.

Зачем мне это знать, ведь есть нодовые редакторы?




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

Полезные ресурсы для изучения



Источник: www.shadertoy.com/view/4tlcWj

С точки зрения изучения шейдерного программирования хорошим упражнением является переписывание шейдеров с www.shadertoy.com или glslsandbox.com. Кроме того существует крутой профиль специалиста из Unity, где можно посмотреть много интересного github.com/keijiro

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

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

Все эффекты в статье — это запись эффектов шейдеров с shadertoy.

Комментарии (16)


  1. GCU
    29.10.2019 14:10

    Шейдеры типа warframe (где требуется понимание барицентрических координат)

    Игру такую знаю, а вот шейдер — нет. Можно больше информации?


    1. DyadichenkoGA Автор
      29.10.2019 14:12

      Спасибо. Опечатался. Конечно wireframe. Вот такой эффект www.youtube.com/watch?v=v38qwLCPZjQ

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


      1. GCU
        29.10.2019 14:24

        Ну, это как-то сильно надмозгово для шейдера на мой взгляд.
        Сам Wireframe рисовали ещё до того, как стали закрашивать треугольники одним цветом.
        В OpenGL для этого есть glPolygonOffset, например, чтобы рисовать сетку поверх.


        1. DyadichenkoGA Автор
          29.10.2019 14:31

          Зависит. Я его последний раз юзал (только другую версию) в мобильном AR на каком-то калькуляторе для красивого VFX с появлением зданий. В целом по сути wireframe является хорошим примером, что в vertex color (и других параметрах вершин) можно хранить, что хочешь. Вопрос как ты потом это будешь обрабатывать в шейдере.


  1. kilroyone
    29.10.2019 14:22

    Крайне полезно для начинающих, спасибо! Тема очень интересна и именно для Unity.


  1. coremission
    29.10.2019 14:58

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

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

    к картезианским координатам

    декартовым

    Не хочу показаться грубым, но не пишите продолжение, хорошие туториалы УЖЕ есть, а начать писать серию по сложной теме где сразу же смешались в кучу кони, люди… Мне кажется это медвежья услуга для читателей.


    1. DyadichenkoGA Автор
      29.10.2019 15:08

      Зависит от графического апи и оборудования. Забудем про как минимум три случая (и это тоже зависит от графического апи, так как на OpenGL ES 2.0 всё прям грустно), когда стоимость — это просто условие. То что выше, это условное упрощение, чуть лучше чем «выполняются обе ветки ифа», на мой взгляд. tangentvector.wordpress.com/2013/04/12/a-digression-on-divergence

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


      1. coremission
        31.10.2019 11:50

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

        Воу, на этапе компиляции шейдера создается волновой фронт?


        1. DyadichenkoGA Автор
          31.10.2019 11:52

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


          1. coremission
            31.10.2019 12:35

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


    1. DyadichenkoGA Автор
      29.10.2019 15:18

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

      Про то, в чём я себя чувствую неуверенно, я и не собираюсь углубляться. Но учитывая сколько задач мы решаем шейдерами почти каждый день, есть много вещей которые можно делать чисто на уровне практики. Почему-то те, кто во всём разбираются, пишут сразу статьи про какой-то космос. А не практические статьи для новичков, которым тоже надо учиться, как даже банальный dissolve просто написать (хз, что придумать даже проще). Можете посмотреть мою серию по математике. Мои статьи — это в большинстве своём простая практика с примерами. Так как это основное, что нужно новичкам. А не «как сделать физичную верёвку с помощью FEM». Или любой другой сложной математической модели, где нужно знать диффуры достаточно хорошо, чтобы вкурить, что происходит. Я только за, чтобы люди делились экспертизой и исправляли неточности источников, так как я сам опираюсь на какие-то. Но почему-то большинство специалистов этого не делает :)

      декартовым

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


      1. coremission
        31.10.2019 11:43

        извините, действительно Декартову систему координат по-русски еще называют «картезианской» меня как-то это покоробило — поэтому подумал — ошибка. Не понял почему Вы говорите о них как о разных системах координат


  1. flastar
    29.10.2019 23:34

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


  1. Camblu
    30.10.2019 23:18

    Добрый день, продолжайте, пожалуйста.


  1. hub-alice
    30.10.2019 23:18

    Здесь хорошо написано https://thebookofshaders.com/. И есть на русском.


  1. Gargo
    02.11.2019 09:52

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