Что такое шейдер в 3д
Перейти к содержимому

Что такое шейдер в 3д

  • автор:

Основы 3D графики: Шейдеры

img

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

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

img

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

  • Вершинные шейдеры (Vertex Shader) — оперируют данными, сопоставленными с вершинами многогранников. К таким данным, в частности, относятся координаты вершины в пространстве, текстурные координаты, тангенс-вектор, вектор нормали и бинормали. Вершинный шейдер может быть использован для видового и перспективного преобразования вершин, генерации текстурных координат, расчета освещения и т. д.
  • Пиксельные шейдеры (Pixel Shader) — работают с фрагментами растрового изображения. Под фрагментом изображения в данном случае понимается пиксель, которому поставлен в соответствие некоторый набор атрибутов, таких как цвет, глубина, текстурные координаты. Пиксельный шейдер используется на последней стадии графического конвейера для формирования фрагмента изображения.
  • Геометрические шейдеры (Geometry Shader) — в отличие от вершинного, способны обработать не только одну вершину, но и целый примитив. Это может быть отрезок (две вершины) и треугольник (три вершины), а при наличии информации о смежных вершинах (adjacency) может быть обработано до шести вершин для треугольного примитива. Кроме того, геометрический шейдер способен генерировать примитивы «на лету», не задействуя при этом центральный процессор. Это относительно новый вид шейдеров, поддержка которого была введена лишь начиная с восьмой серии видеокарт Nvidia.

Vertex Shader (Вершинный Шейдер)

Вершинные шейдеры — это программы, выполняемые видеочипами, которые производят математические операции с вершинами (vertex, из которых состоят 3D объекты в играх), иначе говоря, они предоставляют возможность выполнять программируемые алгоритмы по изменению параметров вершин и их освещению (T&L — Transform & Lighting). Каждая вершина определяется несколькими переменными, например, положение вершины в 3D пространстве определяется координатами: x, y и z. Вершины также могут быть описаны характеристиками цвета, текстурными координатами и т.п. Вершинные шейдеры, в зависимости от алгоритмов, изменяют эти данные в процессе своей работы, например, вычисляя и записывая новые координаты и/или цвет. То есть, входные данные вершинного шейдера — данные об одной вершине геометрической модели, которая в данный момент обрабатывается. Обычно это координаты в пространстве, нормаль, компоненты цвета и текстурные координаты. Результирующие данные выполняемой программы служат входными для дальнейшей части конвейера, растеризатор делает линейную интерполяцию входных данных для поверхности треугольника и для каждого пикселя исполняет соответствующий пиксельный шейдер. Очень простой и грубый (но наглядный, надеюсь) пример: вершинный шейдер позволяет взять 3D объект красной сферы и вершинным шейдером сделать из него зеленый куб :).

Примеры применения вершинных шейдеров:

Деформация объектов. Как самый явный и эффектный пример — создание реалистичных волн в динамике.

img

Анимация объектов. Например, травы и деревьев.

img

Реализация Скининга (skinning) для скелетной анимации персонажа. Применяется практически во всех 3d играх.

img

Toon shading/Cel shading. Используется в некоторых играх для создания эффекта «мультяшного» изображения.

img

Имитация ткани (Cloth Simulation) — для имитации поведения подобных ткани материалов.

Pixel Shader (Пиксельный Шейдер)

Пиксельные шейдеры — это программы, выполняемые видеочипом во время растеризации для каждого пикселя изображения, они производят выборку из текстур и/или математические операции над цветом и значением глубины (Z-buffer) пикселей. Все инструкции пиксельного шейдера выполняются попиксельно, после того, как операции с трансформированием и освещением геометрии завершены. Пиксельный шейдер в итоге своей работы выдает конечное значение цвета пикселя и Z-значение для последующего этапа графического конвейера, блендинга. Наиболее простой пример пиксельного шейдера, который можно привести: банальное мультитекстурирование, просто смешение двух текстур (diffuse и lightmap, например) и наложение результата вычисления на пиксель.

Примеры применения пиксельных шейдеров:

Мультитекстурирование. Использование нескольких слоев текстур (+colormap, detailmap, lightmap и т.д.).

img

Постобработка кадра. Все эти эффекты Bloom, Depth of Field, Motion Blur.

img

Процедурные текстуры, такие, как текстура дерева или мрамора.

img

Geometry Shader (Геометрический шейдер)

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

Читайте другие лекции курса «Основы 3D графики»:

Материал для статьи взят с сайта ixbt.com и из википедии

Шейдеры, что это?

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

А вопрос вот в чём — «Что такое шейдеры и почему их сложно создавать»?
Обьясните, что это за штука такая. Да, и за что они отвечают? Заранее благодарен за детальные ответы.

Guest

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

Guest

Вообщем, понял. Спасибо. Вопрос теперь в следующем — тут сложность жудожественная или техническая? Скажем, если я проф. художник, и прекрасно смог бы создать так называемые шейдеры (просто текстуры)?
Заранее спасибо за ответы.

Guest

Итак, шэйдеры. Чем они отличаются от текстур и материалов.
Собственно, shader — это свето-цвето-физически-нефотореалистические свойства поверхности или раскраска, как говорил JIJI.Это существенно больше чем просто текстура и материал (материал — золото, шэйдер — грязное, потертое исцарапанное золото, изъеденное злобными золотоежками), шэйдер включает в себя не только физические свойства материала, но и характерные свойства поверхности (например, шероховатость). Более того, существуют слоистые шейдера, состоящие из нескольких разных «материалов» (стеклянная бутылка водки с бумажной этикеткой). Таким образом, шэйдер — это некий алгоритм, по которому осуществляется визуализация элемента поверхности. Шэйдера не обязаны быть фотореалистичными : например, toon-шэйдер.
Этот самый алгоритм формируется с помощью так назывемого шэйдер нетворка, являющемся частным случаем графа зависимостей (dependency graph). Создание шэйдеров опирается как на понимание физической природы отражений-преломлений и т.п., так и четкого представления художественных особенностей желаемого результата.
К сожалению, реализация задуманного нестандартного шэйдера, как правило, требует определенной квалификации.

What is Шейдеры.

Собсно уже спросил. Да-да, три года в 3д, а до сих пор это слово для меня звучит как ругательство. В отношении графики в играх вроде уже понял — это микропрограммы для описания поведения материала в определенном окружении. А в отношении визуализации? Так понял, шо шейдинг — это так же поведение материала под действием освещения и окружения, т.е. цвет, текстура — это не шейдеры, а отражение, преломление, бамп — это шейдеры? Или по другому — шейдеринг — это приобретение материалом всех указанных ранее свойств при рендеринге?
Короче, поясните кто-то, но не давайте ссылку на Википедию, два раза уже прочел, но там больше по играм ии вообще как-то слабо разжевано.

На сайте c 21.07.2008
Сообщений: 1199

Не нужно заморачиваться, врядли ты найдешь абсолютно точное значение этому термину. Shading в играх отличается тем что он интерактивный, а в Максе или в Мае например, этим занимаются Shaders, специальные скрипты для материалов, которые в последствии рендерит, например Мental Ray. Надеюсь тебе стало легче

На сайте c 29.02.2008
Сообщений: 1558
Cg-school.org
What is Шейдеры.
материал (напр, стекло, или кожа, или другое, что можно создать в материалэдиторе)
На сайте c 21.07.2008
Сообщений: 1199

Когда болит зуб, можно ударить по колену и зуб перестанет болеть, но зато начнет болеть колено. Дай мне точное опрделение термину «ВИЗУАЛИЗАЦИЯ»

На сайте c 03.11.2007
Сообщений: 108
Не партесь. Это просто программа построения. геометрии, теней, цвета. и т.д.
На сайте c 30.08.2009
Сообщений: 438
Львов

Quant
Ну, батенька, если съезжать со сложных вопросов, то так ничему и не научишься.))
Ладно. Мое определение понятия визуализации — это наглядная демонстрация идеи любыми графическими или объемными (ну там макет, модель, т.д.) методами. Не важно кому, можно даже лично себе; не важно какими именно методами. Но в целом графическую визуалку можно разделить на ручную и компьютерную. Т.к. ручная подача в последнее время мало практикуется, то визуализацией чаще называют изображение идеи именно с помощью компьютера.
Примеры визуализаций: мазня огрызком карандаша на обрывке бумаги (но только если суть мазни в том, что бы донести идею), шикарная акварельная графика; вертолет НеилГана.

На сайте c 21.02.2007
Сообщений: 3130

кароче изменение цвета материала в зависимости от освещенности и его свойств поглощать/отражать свет.

На сайте c 31.12.2008
Сообщений: 2520
Самара
Не партесь. Это просто программа построения. геометрии, теней, цвета. и т.д.

вот именно.ПРОГРАММА.И ничего там не отражается и не преломляется и это не оптическая физика.
Тупо вычисляется цвет и светимость пиксела по заданному алгоритму.А ваш мозг решает-«вроде так в жизни должна кожа блестеть»,»Вроде так за стеклом должны выглядеть вон те штуки. «

Шейдер-это никак не материал.Это иллюзия вида материала,созданная определённого цвета и светимости пикселами расположенными в определённом порядке программой шейдинга.

Читают эту тему:

Последнее на форуме

  • Ошибки в работе сайта
  • Загрузка ЧУЖИХ или ОДИНАКОВЫХ моделей
  • Кто что делает? делитесь, показывайте
  • Вопрос по поводу модерации моделей
  • Позитивное
  • Corona 9
  • Что за ошибка?
  • не могу сгруппировать объекты
  • Пересохранить в более старую версию
  • Фотографии центра Москвы сверху

Проекты

  • Анимация архитектуры в LUMION (частные дома)
  • Ведущий архитектор в ооо тпо «Manifestor»
  • Создание вектора + рельеф, дверь храма
  • Работа со скелетом и анимацией в блендерк
  • модель геля для бровей с щеткой

Вакансии

  • Чертежник архитектор интерьеры, ландшафт.
  • Проектирование домов по каркасной технологии.
  • Визуализатор мягкой мебели по готовым моделям
  • Cпециалист в Unreal Engine
  • Визуализатор

Резюме

  • Фотореалистичная 3D визуализация
  • Визуализатор
  • 3D визуализация интерьеров
  • Архитектор, дизайнер, визуализатор
  • 3D визуализатор | Интерьер | Экстерьер | Моделирование

Шейдеры 3D-игр для начинающих: эффекты

Разобравшись с основами, в этой части статьи мы реализуем такие эффекты, как контуры объектов, bloom, SSAO, размытие, глубина резкости, пикселизация и другие.

Контуры

Создание контуров вокруг геометрии сцены придаёт игре уникальный внешний вид, напоминающий комиксы или мультфильмы.

Diffuse материала

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

uniform struct < vec4 diffuse ; >p3d_Material; out vec4 fragColor; void main()

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

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

Следует учесть, что диффузный цвет материалов не будет работать, если у отдельных частей сцены нет собственного диффузного цвета материала.

Создание краёв

Создание краёв похоже на использование фильтров распознавания краёв в GIMP.

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

// . uniform sampler2D materialDiffuseTexture; // . vec2 texSize = textureSize(materialDiffuseTexture, 0).xy; vec2 texCoord = gl_FragCoord.xy; // . 

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

 // . int separation = 1; // . 

Параметр separation можно настраивать в соответствии со своим вкусом. Чем больше разделение, тем толще края или линии.

 // . float threshold = 0; // . vec4 mx = vec4(0); vec4 mn = vec4(1); int x = -1; int y = -1; for (int i = 0; i < 9; ++i) < vec4 color = texture ( materialDiffuseTexture , (texCoord + (vec2(x, y) * separation)) / texSize ); mx = max(color, mx); mn = min(color, mn); x += 1; if (x >= 2) < x = -1; y += 1; >> float alpha = ((mx.r + mx.g + mx.b) / 3) - ((mn.r + mn.g + mn.b) / 3); if (alpha > threshold) < alpha = 1; >// . 

Техника распознавания краёв находит изменения в цветах входящей текстуры. Центрировавшись на текущем фрагменте, она использует окно фрагментов 3×3 для нахождения самого яркого и самого тёмного цветов из девяти сэмплов. Затем она вычитает из яркости одного цвета яркость другого, получая их разность.

 // . vec3 lineRgb = vec3(0.012, 0.014, 0.022); // . vec4 lineColor = vec4(lineRgb, alpha); // . fragColor = lineColor; // . 

Эта разность используется в альфа-канале выводимого цвета. Если разности нет, то край или линия не отрисовываются. Если разность есть, то край отрисовывается.

 // . float threshold = 0; // . if (alpha > threshold) < alpha = 1; >// . 

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

Исходники

Туман

Туман (или дымка (mist), как он называется в Blender) добавляет сцене атмосферной дымки, создавая загадочные смягчённые выступающие части. Выступающие части появляются, когда какая-нибудь геометрия внезапно попадает в пирамиду видимости камеры.

// . uniform struct p3d_FogParameters < vec4 color ; float start ; float end ; >p3d_Fog; // . 

В Panda3D есть удобная структура данных, содержащая все параметры тумана, но вы можете передавать их в свой шейдер и вручную.

 // . float fogIntensity = clamp ( ( p3d_Fog.end - vertexPosition.y) / ( p3d_Fog.end - p3d_Fog.start) , 0 , 1 ); fogIntensity = 1 - fogIntensity; // . 

В примере кода для вычисления яркости тумана при отдалении от камеры используется линейная модель. Вместо неё можно использовать и экспоненциальную модель. Яркость тумана равна нулю до или в начале тумана. Когда позиция вершины приближается к концу тумана, fogIntensity приближается к единице. Для всех вершин после конца тумана fogIntensity ограничивается сверху значением 1.

 // . fragColor = mix ( outputColor , p3d_Fog.color , fogIntensity ); // . 

Основываясь на яркости тумана, мы смешиваем цвет тумана с выходным цветом. При приближении fogIntensity к единице будет всё меньше outputColor и всё больше цвета тумана. Когда fogIntensity достигает единицы, останется только цвет тумана.

Туман на контурах

// . uniform sampler2D positionTexture; // . vec4 position = texture(positionTexture, texCoord / texSize); float fogIntensity = clamp ( ( p3d_Fog.end - position.y) / ( p3d_Fog.end - p3d_Fog.start) , 0 , 1 ); fogIntensity = 1 - fogIntensity; vec4 lineWithFogColor = mix ( lineColor , p3d_Fog.color , fogIntensity ); fragColor = vec4(lineWithFogColor.rgb, alpha); // . 

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

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

Исходники

Bloom

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

 //. float separation = 3; int samples = 15; float threshold = 0.5; float amount = 1; // . 

Можете настроить эти параметры по своему вкусу. Separation увеличивает размер размытия. Samples определяет силу размытия. Threshold определяет, что подвергнется и не подвергнется влиянию этого эффекта. Amount управляет величиной выводимого bloom.

 // . int size = samples; int size2 = size * size; int x = 0; int y = 0; // . float value = 0; vec4 result = vec4(0); vec4 color = vec4(0); // . for (int i = 0; i < size2; ++i) < // . >// . 

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

 // . color = texture ( bloomTexture , ( gl_FragCoord.xy + vec2(x * separation, y * separation) ) / texSize ); value = ((0.3 * color.r) + (0.59 * color.g) + (0.11 * color.b)); if (value < threshold) < color = vec4(0); >result += color; // . 

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

Проходя в цикле все сэмплы в пределах окна, он накапливает все их значения в result .

 // . result = result / size2; // . 

Завершив сбор сэмплов, он делит сумму сэмплов цветов на количество взятых сэмплов. Результат является средним цветом самого фрагмента и его соседей. Сделав так для каждого фрагмента, мы получим размытое изображение. Такой вид размытия называется box blur.

Здесь вы видите процесс выполнения алгоритма bloom.

Исходники

Screen Space Ambient Occlusion (SSAO)

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

Учтите, что термин «screen space» в названии не совсем верен, потому что не все вычисления производятся в экранном пространстве.

Входящие данные

Шейдеру SSAO потребуются следующие входящие данные.

  • Векторы позиций вершин в пространстве обзора.
  • Векторы нормалей к вершинам в пространстве обзора.
  • Векторы сэмплов в касательном пространстве.
  • Векторы шума в касательном пространстве.
  • Матрица проецирования на объектив камеры.

Позиция

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

PT(Texture) depthTexture = new Texture("depthTexture"); depthTexture->set_format(Texture::Format::F_depth_component32); PT(GraphicsOutput) depthBuffer = graphicsOutput->make_texture_buffer("depthBuffer", 0, 0, depthTexture); depthBuffer->set_clear_color(LVecBase4f(0, 0, 0, 0)); NodePath depthCameraNP = window->make_camera(); DCAST(Camera, depthCameraNP.node())->set_lens(window->get_camera(0)->get_lens()); PT(DisplayRegion) depthBufferRegion = depthBuffer->make_display_region(0, 1, 0, 1); depthBufferRegion->set_camera(depthCameraNP);

Если вы решите использовать буфер глубин, то вот как это можно настроить в Panda3D.

in vec4 vertexPosition; out vec4 fragColor; void main()

Вот простой шейдер для рендеринга позиций вершин в пространстве обзора в текстуру буфера кадров. Более сложная задача заключается в настройке текстуры буфера кадров, чтобы получаемые им компоненты вектора фрагмента не ограничивались интервалом [0, 1] , и чтобы у каждого была достаточно большая точность (достаточно большое количество бит). Например, если какая-то интерполированная позиция вершины равна , то нельзя сохранять её в текстуру как .

 // . FrameBufferProperties fbp = FrameBufferProperties::get_default(); // . fbp.set_rgba_bits(32, 32, 32, 32); fbp.set_rgb_color(true); fbp.set_float_color(true); // . 

Вот как пример кода подготавливает текстуру буфера кадров для хранения позиций вершин. Ему нужно по 32 бита для красного, зелёного, синего и альфы, поэтому он отключает ограничение значений интервалом [0, 1] . Вызов set_rgba_bits(32, 32, 32, 32) задаёт битовый объём и отключает ограничение.

 glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGB32F , 1200 , 900 , 0 , GL_RGB , GL_FLOAT , nullptr );

Вот аналогичный вызов на OpenGL. GL_RGB32F задаёт биты и отключает ограничение.

Если буфер цветов имеет фиксированную запятую, то компоненты исходных и конечных значений, а также показатели смешения перед вычислением уравнения смешивания ограничиваются значениями [0, 1] или [−1, 1], соответственно для беззнакового нормализованного и знакового нормализованного буфера цветов. Если буфер цветов имеет плавающую запятую, то ограничение не выполняется.

Здесь вы видите позиции вершин; ось y направлена вверх.

Помните, что Panda3D задаёт ось z как вектор, направленный вверх, а в OpenGL вверх смотрит ось y. Шейдер позиций выводит позиции вершин с направленной вверх z, потому что в Panda3D
настроен параметр gl-coordinate-system default .

Нормали

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

in vec3 vertexNormal; out vec4 fragColor; void main()

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

Здесь показаны нормали к вершинам; ось y направлена вверх.

Вспомним, что Panda3D считает направленным вверх вектором ось z, а OpenGL — ось y. Шейдер нормалей выводит позиции вершин с направленной вверх осью z, потому что в Panda3D настроен параметр gl-coordinate-system default .

Сэмплы

Чтобы определить величину ambient occlusion для любого отдельного фрагмента, нам нужно сэмплировать окружающую область.

 // . for (int i = 0; i < numberOfSamples; ++i) < LVecBase3f sample = LVecBase3f ( randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) ).normalized(); float rand = randomFloats(generator); sample[0] *= rand; sample[1] *= rand; sample[2] *= rand; float scale = (float) i / (float) numberOfSamples; scale = lerp(0.1, 1.0, scale * scale); sample[0] *= scale; sample[1] *= scale; sample[2] *= scale; ssaoSamples.push_back(sample); >// . 

Пример кода генерирует 64 случайных сэмплов, распределённых по полусфере. Эти ssaoSamples будут переданы в шейдер SSAO.

 LVecBase3f sample = LVecBase3f ( randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 ).normalized();

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

Шум

 // . for (int i = 0; i < 16; ++i) < LVecBase3f noise = LVecBase3f ( randomFloats(generator) * 2.0 - 1.0 , randomFloats(generator) * 2.0 - 1.0 , 0.0 ); ssaoNoise.push_back(noise); >// . 

Чтобы хорошо покрыть сэмплируемую область, нам нужно сгенерировать векторы шума. Эти векторы шума могут поворачивать сэмплы вокруг верхней части поверхности.

Ambient Occlusion

SSAO выполняет свою задачу, сэмплируя пространство обзора вокруг фрагмента. Чем больше сэмплов находится под поверхностью, тем темнее цвет фрагмента. Эти сэмплы располагаются во фрагменте и указывают в общем направлении нормали к вершине. Каждый сэмпл используется для поиска позиции в текстуре позиции буфера кадров. Возвращаемая позиция сравнивается с сэмплом. Если сэмпл дальше от камеры, чем позиция, то сэмпл по направлению к фрагменту перекрыт (occluded).

Здесь вы видите пространство над поверхностью, сэмплируемое на наличие occlusion.

 // . float radius = 1.1; float bias = 0.026; float lowerRange = -2; float upperRange = 2; // . 

Как и у некоторых других техник, у шейдера SSAO есть несколько параметров управления, которые можно изменять для получения необходимого внешнего вида. bias прибавляется к расстоянию от сэмпла до камеры. Этот параметр можно использовать для борьбы с пятнами. radius увеличивает или уменьшает область покрытия пространства сэмплов. lowerRange и upperRange изменяют стандартный диапазон показателя factor с [0, 1] на любое выбранное вами значение. Увеличивая диапазон, можно повысить контрастность.

 // . vec4 position = texture(positionTexture, texCoord); vec3 normal = texture(normalTexture, texCoord).xyz; int noiseX = int(gl_FragCoord.x - 0.5) % 4; int noiseY = int(gl_FragCoord.y - 0.5) % 4; vec3 random = noise[noiseX + (noiseY * 4)]; // . 

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

 // . vec3 tangent = normalize(random - normal * dot(random, normal)); vec3 binormal = cross(normal, tangent); mat3 tbn = mat3(tangent, binormal, normal); // . 

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

 // . float occlusion = NUM_SAMPLES; for (int i = 0; i < NUM_SAMPLES; ++i) < // . >// . 

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

 // . vec3 sample = tbn * samples[i]; sample = position.xyz + sample * radius; // . 

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

 // . vec4 offset = vec4(sample, 1.0); offset = lensProjection * offset; offset.xyz /= offset.w; offset.xyz = offset.xyz * 0.5 + 0.5; // . 

С помощью позиции сэмпла в пространстве обзора преобразуем её из пространства обзора в пространство отсечения, а затем в UV-пространство.

-1 * 0.5 + 0.5 = 0 1 * 0.5 + 0.5 = 1

Не забудем, что компоненты пространства отсечения находятся в интервале от минус единицы до единицы, а UV-координаты — в интервале от нуля до единицы. Чтобы преобразовать координаты пространства отсечения в UV-координаты, умножим их на одну вторую и прибавим одну вторую.

 // . vec4 offsetPosition = texture(positionTexture, offset.xy); float occluded = 0; if (sample.y + bias else < occluded = 1; >// . 

С помощью UV-координат смещения, полученных проецированием 3D-сэмпла на 2D-текстуру позиции, найдём соответствующий вектор позиции. Это переносит нас из пространства обзора в пространство отсечения в UV-пространство а затем обратно в пространство обзора. Шейдер выполняет этот цикл, чтобы определить есть ли какая-то геометрия за сэмплом, в месте сэмпла или перед сэмплом. Если сэмпл находится перед какой-то геометрией или в ней, то этот сэмпл просто не учитывается относительно перекрываемого фрагмента. Если сэмпл находится за какой-то геометрией, то этот сэмпл учитывается относительно перекрываемого фрагмента.

 // . float intensity = smoothstep ( 0.0 , 1.0 , radius / abs(position.y - offsetPosition.y) ); occluded *= intensity; occlusion -= occluded; // . 

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

 // . occlusion /= NUM_SAMPLES; // . fragColor = vec4(vec3(occlusion), position.a); // . 

Разделим количество перекрытых на количество сэмплов, чтобы перевести показатель occlusion из интервала [0, NUM_SAMPLES] в интервал [0, 1] . Ноль означает полную occlusion, единицы — отсутствие occlusion. Теперь назначим показатель occlusion цвету фрагмента, и на этом всё.

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

Размытие (Blur)

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

 // . for (int i = 0; i < size2; ++i) < x = size - xCount; y = yCount - size; result += texture ( ssaoTexture , texCoord + vec2(x * parameters.x, y * parameters.x) ).rgb; xCount -= 1; if (xCount < countMin) < xCount = countMax; yCount -= 1; >> result = result / size2; // . 

Шейдер размытия SSAO — это обычный box blur. Как и шейдер bloom, он проводит окно над входящей текстурой и усредняет каждый фрагмент со значениями его соседей.

Учтите, что parameters.x — это параметр разделения.

Ambient Color

 // . vec2 ssaoBlurTexSize = textureSize(ssaoBlurTexture, 0).xy; vec2 ssaoBlurTexCoord = gl_FragCoord.xy / ssaoBlurTexSize; float ssao = texture(ssaoBlurTexture, ssaoBlurTexCoord).r; vec4 ambient = p3d_Material.ambient * p3d_LightModel.ambient * diffuseTex * ssao; // . 

Последняя задача для SSAO снова находится в вычислении освещения. Здесь мы видим, как показатель occlusion находится в текстуре SSAO буфера текстуры и включается в вычисление окружающего освещения.

Исходники

Глубина резкости

Глубина резкости — это тоже такой эффект, узнав о котором, вы не сможете без него жить. С художественной точки зрения можно использовать его, чтобы привлечь внимание зрителя к определённому объекту. Но в общем случае глубина резкости (depth of field) ценой небольших усилий добавляет большую долю реализма.

В фокусе

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

Не в фокусе

 // . vec4 result = vec4(0); for (int i = 0; i < size2; ++i) < x = size - xCount; y = yCount - size; result += texture ( blurTexture , texCoord + vec2(x * parameters.x, y * parameters.x) ); xCount -= 1; if (xCount < countMin) < xCount = countMax; yCount -= 1; >> result = result / size2; // . 

Второй этап — размытие сцены как будто она полностью не в фокусе. Как и в случае bloom и SSAO, можно использовать box blur. Отрендерите эту расфокусированную сцену в текстуру буфера кадров. Она будет ещё одним входящим значением шейдера глубины резкости.

Учтите, что parameters.x — это параметр разделения.

Смешение

 // . float focalLengthSharpness = 100; float blurRate = 6; // . 

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

 // . vec4 focusColor = texture(focusTexture, texCoord); vec4 outOfFocusColor = texture(outOfFocusTexture, texCoord); // . 

Нам понадобятся цвета в фокусе и в расфокусированном изображении.

 // . vec4 position = texture(positionTexture, texCoord); // . 

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

 // . float blur = clamp ( pow ( blurRate , abs(position.y - focalLength.x) ) / focalLengthSharpness , 0 , 1 ); // . fragColor = mix(focusColor, outOfFocusColor, blur); // . 

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

Исходники

Постеризация

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

 // . float levels = 8; // . 

Можете поэкспериментировать с этим параметром. Чем он больше, тем больше цветов в результате останется.

 // . vec4 texColor = texture(posterizeTexture, texCoord); // . 

Нам понадобится входящий цвет.

 // . vec3 grey = vec3((texColor.r + texColor.g + texColor.b) / 3.0); vec3 grey1 = grey; grey = floor(grey * levels) / levels; texColor.rgb += (grey - grey1); // . 

Такого способа постеризации я раньше не видел. Проверив его, я увидел, что он создаёт более красивые результаты по сравнению с обычными способами. Чтобы уменьшить палитру цветов, сначала преобразуем цвет в значение в градациях серого. Дискретизируем цвет, привязав его к одному из уровней. Вычисляем разность между дискретизированным значением в градациях серого и недискретизированным значением в градациях серого. Прибавляем эту разность к входящему цвету. Эта разность — величина, на которую цвет должен увеличиться/уменьшиться, чтобы достичь дискретизированного значения в градациях серого.

 // . fragColor = texColor; // . 

Не забудем присвоить значение входящего цвета цвету фрагмента.

Cel Shading

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

Исходники

Пикселизация

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

 // . int pixelSize = 5; // . 

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

 // . float x = int(gl_FragCoord.x) % pixelSize; float y = int(gl_FragCoord.y) % pixelSize; x = floor(pixelSize / 2.0) - x; y = floor(pixelSize / 2.0) - y; x = gl_FragCoord.x + x; y = gl_FragCoord.y + y; // . 

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

 // . fragColor = texture(pixelizeTexture, vec2(x, y) / texSize); // . 

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

Исходники

Sharpen

Эффект sharpen (повышение резкости) увеличивает контрастность по краям изображения. Он пригождается, когда графика оказывается слишком мягкой.

 // . float amount = 0.8; // . 

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

 // . float neighbor = amount * -1; float center = amount * 4 + 1; // . 

Соседние фрагменты умножаются на amount * -1 . Текущий фрагмент умножается на amount * 4 + 1 .

 // . vec3 color = texture(sharpenTexture, vec2(gl_FragCoord.x + 0, gl_FragCoord.y + 1) / texSize).rgb * neighbor + texture(sharpenTexture, vec2(gl_FragCoord.x - 1, gl_FragCoord.y + 0) / texSize).rgb * neighbor + texture(sharpenTexture, vec2(gl_FragCoord.x + 0, gl_FragCoord.y + 0) / texSize).rgb * center + texture(sharpenTexture, vec2(gl_FragCoord.x + 1, gl_FragCoord.y + 0) / texSize).rgb * neighbor + texture(sharpenTexture, vec2(gl_FragCoord.x + 0, gl_FragCoord.y - 1) / texSize).rgb * neighbor ; // . 

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

 // . fragColor = vec4(color, texture(sharpenTexture, texCoord).a); // . 

Эта сумма является окончательным цветом фрагмента.

Исходники

Зернистость плёнки

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

Учтите, что зернистость плёнки (film grain) обычно является последним эффектом, который применяется к кадру перед выводом на экран.

Величина

 // . float amount = 0.1; // . 

amount управляет заметностью зернистости плёнки. Чем значение выше, тем больше «снега» на картинке.

Случайная яркость

// . uniform float osg_FrameTime; //. float toRadians = 3.14 / 180; //. float randomIntensity = fract ( 10000 * sin ( ( gl_FragCoord.x + gl_FragCoord.y * osg_FrameTime ) * toRadians ) ); // . 

Этот фрагмент кода вычисляет случайную яркость, необходимую для настройки величины.

Time Since F1 = 00 01 02 03 04 05 06 07 08 09 10 Frame Number = F1 F3 F4 F5 F6 osg_FrameTime = 00 02 04 07 08

Значение osg_FrameTime предоставляется Panda3D. Время кадра — это временная метка с информацией о том, сколько секунд прошло после первого кадра. Пример кода использует его для анимирования зернистости плёнки, котому что osg_FrameTime в каждом кадре будет разным.

 // . ( gl_FragCoord.x + gl_FragCoord.y * 8009 // Large number here. // . 

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

 // . * sin ( ( gl_FragCoord.x + gl_FragCoord.y * someNumber // . 

Для создания точек, или пятен зерна плёнки используются обе координаты, и x, и y. Если использовать x, то будут отображаться только вертикальные линии, если использовать y — то только горизонтальные.

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

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

Учтите, что для анимирования эффекта дождя нужно умножить вывод sin на osg_FrameTime .

Поэкспериментируйте с координатами x и y, чтобы изменять направление дождя. Для падающего вниз ливня оставьте только координату x.

input = (gl_FragCoord.x + gl_FragCoord.y * osg_FrameTime) * toRadians frame(10000 * sin(input)) = fract(10000 * sin(6.977777777777778)) = fract(10000 * 0.6400723818964882) =

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

fract(10000 * sin(6.977777777777778)) = fract(10000 * 0.6400723818964882) = fract(6400.723818964882) = 0.723818964882

sin в сочетании с fract также используется как генератор псевдослучайных чисел.

>>> [floor(fract(4 * sin(x * toRadians)) * 10) for x in range(0, 10)] [0, 0, 1, 2, 2, 3, 4, 4, 5, 6] >>> [floor(fract(10000 * sin(x * toRadians)) * 10) for x in range(0, 10)] [0, 4, 8, 0, 2, 1, 7, 0, 0, 5]

Взгляните сначала на первый ряд чисел, а потом на второй. Каждый ряд детерминирован, но во втором меньше заметен паттерн, чем во втором. Поэтому несмотря на то, что выходные данные fract(10000 * sin(. )) детерминированы, паттерн узнаётся гораздо слабее.

Здесь мы видим, как множитель sin равен сначала 1, потом 10, потом 100, а затем 1000.

При увеличении множителя выходных значений sin паттерн становится всё меньше заметен. По этой причине в коде sin умножается на 10 000.

Цвет фрагмента

 // . vec2 texSize = textureSize(filmGrainTexture, 0).xy; vec2 texCoord = gl_FragCoord.xy / texSize; vec4 color = texture(filmGrainTexture, texCoord); // . 

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

 // . amount *= randomIntensity; color.rgb += amount; // . 

Изменим величину на случайную яркость и прибавим её к цвету.

 // . fragColor = color; // . 

Задаём цвет фрагмента, и на этом всё.

Исходники

Благодарности

  • В статье использован шрифт Kiwi Soda Font

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *