Чтобы поместить шейдер, который вы только что написали, в ваш графический движок, вы делаете в основном те же самые вещи, которые FX Composer делает, чтобы показать вам шейдер. Благодаря DirectX или XNA легко загрузить файл шейдерного эффекта и установить все требуемые параметры (вы уже делали это в предыдущей главе для шейдера визуализации линий). В следующей главе вы перейдете к использованию более общего класса, который принимает много различных шейдеров и использует более оптимизированный способ установки всех необходимых шейдерам параметров, а в этой главе мы просто заставим файл SimpleShader.fx работать в вашем движке.
Как обычно, начнем с определения тестового модуля. Создайте новый файл с именем simpleShader.cs в пространстве имен Shaders и напишите следующий код:
Тестовый модуль должен компилироваться, за исключением метода renderModel нового класса SimpleShader, который еще не определен. Давайте быстро определим этот метод:
Теперь тестовый модуль компилируется, и вы можете запустить его, чтобы увидеть пустой экран.
Компиляция шейдеров
Загрузить скомпилированный эффект так же просто, как загрузить текстуру. Просто определите переменную эффекта, а затем загрузите ее в конструкторе SimpleShader:
На платформе Windows вы также можете загружать шейдеры динамически (в этом случае они не будут заранее скомпилированы XNA Framework), что может быть полезно для тестирования и изменения шейдеров во время выполнения вашей игры. Обычно во время разработки для большинства шейдеров я использую некомпилированные файлы шейдеров, а затем помещаю их в конвейер содержимого, когда игра завершена и я больше не собираюсь изменять шейдеры. Для самостоятельной загрузки и компиляции шейдеров используется следующий код:
Использование параметров
Как вы узнали в процессе создания шейдера, мировая матрица, матрица вида и матрица проекции действительно важны для преобразования ваших трехмерных данных и получения корректного результата на экране. Для установки всех этих параметров шейдера вы просто используете метод RenderModel, который вызывается из вашего тестового модуля:
Сначала в этом коде устанавливается мировая матрица. Это очень важно, поскольку если не устанавливать мировую матрицу, она может содержать абсолютно сумасшедшие значения от каких-нибудь предыдущих операций, и трехмерная модель будет визуализирована в каком-нибудь случайном месте, а не там, где вам надо. Поскольку модель яблока достаточно большая, вы слегка уменьшаете ее масштаб, чтобы она соответствовала размерам экрана в используемом по умолчанию классе SimpleCamera из предыдущей главы.
Формат вершин
Перед тем, как визуализировать данные из вашей трехмерной модели яблока, вы должны убедиться, что ваше приложение и шейдер знают, какой формат вершин использовать. В DirectX это достигалось путем простой установки одного из предопределенных форматов вершин фиксированного конвейера функций, но эти форматы больше не существуют, и вы не можете установить их. Вместо этого вы должны определить полное объявление вершины, также как делали это в шейдере для структуры VertexInput. Поскольку мы просто используем встроенную структуру VertexPositionNormalTexture, нам не надо ничего определять самостоятельно, но в следующей главе вы узнаете, как сделать это для собственного нестандартного формата TangentVertex.
На самом деле вам не нужно создавать новое объявление вершин каждый раз, когда вы вызываете RenderModel, но чтобы упростить материал я строю новое объявление вершин при каждом вызове. В первом параметре передается графическое устройство, а во втором — массив элементов вершин из структуры XNA VertexPositionNormalTexture. За дополнительной информацией обращайтесь к главе 7.
Визуализация с шейдерами
Чтобы теперь визуализировать яблоко с вашим шейдером необходимо сначала указать, какую технику вы хотите использовать (в любом случае, по умолчанию будет установлена первая техника, но полезно знать, как устанавливать техники). Для визуализации шейдера вы всегда будете использовать свойство CurrentTechnique класса эффекта. Затем вы проходите по технике так же, как это делает FX Composer, — вы рисуете все трехмерные данные для каждого прохода, который есть в технике (как я говорил раньше, обычно у вас будет только один проход). Визуализация самого яблока не то чтобы очень проста, поскольку единственный метод, который XNA предоставдяет вам для этого, это mesh.Draw, который надо вызывать для каждой сетки модели после установки необходимых параметров шейдеров, что вы видели в предыдущей главе, когда писали класс Model.
Другая вещь, отсутствующая в XNA Framework, это вспомогательные методы для создания сеток кубов, сфер или чайников. Вы также заметите, что большинство функциональности пространства имен Direct3DX просто не существует в XNA. Вы только можете получить доступ к некоторым методам, когда пишете собственный обработчик содержимого, но это не поможет вам при написании своего движка, или когда вы захотите проверить модели, сетки или шейдеры. Вы также не можете получить доступ к любым данным вершин или индексов загруженных моделей, потому что все буферы вершин и индексов доступны только для записи, что хорошо с точки зрения производительности, но плохо, если вы захотите что-нибудь модифицировать. В вашем небольшом фрагменте кода вы просто имитируете поведение метода mesh.Draw, только без кода эффектов, поскольку для этой цели у вас здесь используется собственный класс эффектов.
Если подробнее, это означает, что вы перебираете каждый проход (снова у вас только один) и рисуете все сетки (также только одну для вашего яблока), визуализируя все части каждой сетки (представьте себе, и здесь одну), устанавливая буферы вершин и индексов с помощью свойств класса XNA Device. Затем вы вызываете DrawIndexedPrimitives для визуализации всех вершин внутри шейдера. После этого проход и шейдер закрываются и вы наконец видите свое яблоко с текстурой marble.dds на экране (рис. 6.16).
Тестирование шейдера
Теперь все работает, но это не означает, что вы не можете немного поэкспериментировать со своим шейдером и проверить другие параметры материалов, текстуры или режимы визуализации.
Чтобы достичь эффекта проволочного каркаса, который вы видели раньше на рис. 6.5, просто измените значение FillMode перед запуском шейдера:
Или вы можете немного поуправлять материалами шейдера; если хотите, можете загрузить другую текстуру или даже модель. Класс шейдера написан таким образом, что позволяет вам загружать любую модель XNA и текстуру и тестировать их. Простейшим способом модификации результата работы шейдера могло бы быть изменение значений фоновой, рассеиваемой и отражаемой компонент, чтобы получить злое инопланетное яблоко (рис. 6.17).
Пожалуйста, обратите внимание, что вы преобразуете цвета в Vector4, поскольку у SetValue нет перегрузки для цветовых значений (похоже, кто-то был очень ленивый). Когда вы устанавливаете любой параметр эффекта между вызовами Begin и End для шейдера, вы должны также вызвать метод Effect.CommitChanges, чтобы гарантировать, что все ваши изменения будут отправлены GPU. Если вы вызываете Begin после установки параметров, подобно тому, как это делается в классе SimpleShader, можете не беспокоиться об этом.
В следующей главе мы перейдем к более эффективному использованию параметров эффекта, поскольку установка их по имени не слишком производительна и может служить источником ошибок. Если вы допустите опечатку в имени какого-нибудь параметра, будет сгенерировано исключение, а в этом нет ничего хорошего.
Этот учебник не рассматривает Effects11 (FX11), но основан на оригинальном HLSL.
Текущие методы компиляции и загрузки шейдеров следующие:
В персональном проекте DX11 используется смешанная форма метода 1 (приоритет) и метода 3. Хотя метод 2 был недавно изучен, люди в настоящее время не рассматривают возможность перехода на этот метод.
Добро пожаловать в группу QQ: 727623616 Вы можете обсудить DX11 вместе и сообщить о любых вопросах здесь.
Расширения файлов, связанные с шейдерами
Чтобы соответствовать соглашениям Microsoft, вам необходимо использовать следующие (измененные) расширения для своего кода шейдера:
Метод 1: Генерация объектных файлов во время компиляции и загрузка их во время выполнения
среди Triangle.hlsli Заголовочный файл HLSL по умолчанию не участвует в процессе компиляции проекта.
После генерации проекта необходимо обратить внимание на то, появляется ли следующий контент в окне вывода (генерация):
Только когда появляется вышеуказанное содержимое, это означает, что объектный файл был успешно скомпилирован, в противном случае это означает, что он не был скомпилирован.Если вы скомпилировали объектный файл ранее, и при перекомпиляции вывод не отображается, вам может потребоваться удалить ранее скомпилированный объектный файл и повторить попытку.
Связанные операции над кодом или файлами шейдеров находятся в заголовочном файле. d3dcompiler.h И нужно добавить статическую библиотеку d3dcompiler.lib
Далее мы используем следующую функцию для чтения двоичной информации скомпилированного шейдера:
Примечание. Если эта функция не существует в вашем проекте, это означает, что вы, возможно, включили DX SDK заранее. Однако в этом руководстве используется Windows SDK. Эта функция находится в версии D3DCompiler> = 46, поэтому необходимо исключить путь включения DX SDK и Путь к библиотеке.
Он также очень прост в использовании (на примере создания вершинного шейдера и вершинного макета):
Тогда вы можете получить это ID3DBlob Пришло время создать шейдер. Часть создания макета шейдера и вершины не обсуждается в этой статье, пожалуйста, вернитесь к уроку 02, чтобы продолжить просмотр.
Особенностью этого метода является то, что скомпилированный бинарный файл шейдера будет сгенерирован в папке вашего проекта, и он требует, чтобы вы читали его непосредственно во время работы программы.
Способ 2: компилятор создает файл заголовка и включает этот файл в проект
для Triangle_VS.hlsl и Triangle_PS.hlsl Свойства проекта должны быть установлены так:
Здесь имя файла заголовка и имя внутренней глобальной переменной можно определить самостоятельно.
Теперь вам нужно включить эти два заголовочных файла над исходным файлом, где вам нужно написать соответствующий код для создания шейдера:
Тогда код для создания вершинного шейдера и вершинного макета становится таким:
Поскольку вышеупомянутые два заголовочных файла генерируются (то есть шейдер компилируется) до компиляции проекта, когда эти два заголовочных файла не генерируются, вы также можете выдержать ошибку компиляции, сначала добавив приведенный выше код, а затем скомпилировать все Это нормально
Отличительной особенностью этого метода является то, что все процессы завершаются во время компиляции, а байт-код шейдера внедряется в ваше приложение, что может привести к расширению приложения.
Способ 3: скомпилировать код шейдера во время выполнения для генерации байт-кода
Теперь вам нужно понять эти функции
Еще раз обратите внимание: если функция не существует в вашем проекте, это означает, что вы, возможно, включили DX SDK заранее. Однако в этом руководстве используется Windows SDK. Эта функция находится в версии D3DCompiler> = 46, поэтому необходимо исключить путь включения DX SDK И путь к библиотеке.
для bOverwrite С точки зрения TRUE или FALSE Это не имеет значения, потому что мы запускаем компиляцию во время выполнения, только когда обнаруживаем, что нет файла скомпилированного шейдера, и затем сохраняем его в файл.
Конкретное использование было включено в следующее CreateShaderFromFile Функция в
Реализация функции CreateShaderFromFile
Ниже CreateShaderFromFile Реализация функции,Теперь эта функция была помещена в d3dUtil.h, Зависит от dxerr И стандартная библиотека filesystem Вы также можете изменить реализацию этой функции самостоятельно:
Использование заключается в следующем:
Добро пожаловать в группу QQ: 727623616 Вы можете обсудить DX11 вместе и сообщить о любых вопросах здесь.
Что такое шейдеры? Просто о сложном для начинающих
«Что такое шейдеры?» – очень частый вопрос любопытных игроков и начинающих игровых разработчиков. В этой статье доходчиво и понятно об этих страшных шейдерах расскажу.
Двигателем прогресса в сторону фотореалистичности картинки в компьютерной графике я считаю именно компьютерные игры, поэтому давайте именно в разрезе видео-игр и поговорим о том, что такое “шейдеры”.
До того, как появились первые графические ускорители, всю работу по отрисовке кадров видеоигры выполнял бедняга центральный процессор.
Так вот, центральный процессор (CPU – Central Processing Unit) слишком умный парень, чтобы заставлять его заниматься такой рутиной. Вместо этого логично выделить какой-то аппаратный модуль, который разгрузит CPU, чтобы тот смог заниматься более важным интеллектуальным трудом.
Таким аппаратным модулем стал – графический ускоритель или видеокарта (GPU – Graphics Processing Unit). Теперь CPU подготавливает данные и загружает рутинной работой коллегу. Учитывая, что GPU сейчас это не просто один коллега, это толпа миньонов-ядер, то он с такой работой справляется на раз.
Но мы пока не получили ответа на главный вопрос: Что такое шейдеры? Подождите, я подвожу к этому.
Хорошая, интересная и близкая к фото-реализму графика, требовала от разработчиков видеокарт реализовывать многие алгоритмы на аппаратном уровне. Тени, свет, блики и так далее. Такой подход – с реализацией алгоритмов аппаратно называется “Фиксированный пайплайн или конвейер” и там где требуется качественная графика он теперь не встречается. Его место занял “Программируемый пайплайн”.
Запросы игроков “давайте, завозите хороший графоний! удивляйте!”, толкали разработчиков игр (и производителей видеокарт соответственно) все к более и более сложным алгоритмам. Пока в какой-то момент зашитых аппаратных алгоритмов им стало слишком мало.
Наступило время видеокартам стать более интеллектуальными. Было принято решение позволить разработчикам программировать блоки графического процессора в произвольные конвейеры, реализующие разные алгоритмы. То есть разработчики игр, графические программисты отныне смогли писать программы для видеокарточек.
И вот, наконец, мы дошли до ответа на наш главный вопрос.
“Что такое шейдеры?”
Ше́йдер (англ. shader — затеняющая программа) — это программа для видеокарточки, которая используется в трёхмерной графике для определения окончательных параметров объекта или изображения, может включать в себя описание поглощения и рассеяния света, наложения текстуры, отражения и преломление, затенение, смещение поверхности и множество других параметров.
Графический пайплайн
Преимущество программируемого конвейера перед его предшественником в том, что теперь программистам можно создавать свои алгоритмы самостоятельно, а не пользоваться зашитым аппаратно набором опций.
Сначала видеокарты оснастили несколькими специализированными процессорами, поддерживающими разные наборы инструкций. Шейдеры делили на три типа в зависимости от того, какой процессор будет их исполнять. Но затем видеокарты стали оснащать универсальными процессорами, поддерживающими наборы инструкций всех трёх типов шейдеров. Деление шейдеров на типы сохранилось для описания назначения шейдера.
Помимо графических задач с такими интеллектуальными видеокартами появилась возможность выполнения на GPU вычислений общего назначения (не связанных с компьютерной графикой).
Впервые полноценная поддержка шейдеров появилась в видеокартах серии GeForce 3, но зачатки были реализованы ещё в GeForce256 (в виде Register Combiners).
Виды шейдеров
В зависимости от стадии конвейера шейдеры делятся на несколько типов: вершинный, фрагментный (пиксельный) и геометрический. А в новейших типах конвейеров есть еще шейдеры тесселяции. Подробно обсуждать графический конвейер мы не будем, я все думаю не написать ли об этом отдельную статью, для тех кто решит заняться изучением шейдеров и программирования графики. Напишите в комментариях если Вам интересно, я буду знать, стоит ли тратить время.
Вершинный шейдер
Вершинными шейдерами делают анимации персонажей, травы, деревьев, создают волны на воде и многие другие штуки. В вершинном шейдере программисту доступны данные, связанные с вершинами например: координаты вершины в пространстве, её текстурные координатами, её цвет и вектор нормали.
Геометрический шейдер
Геометрические шейдеры способны создавать новую геометрию, и могут использоваться для создания частиц, изменения детализации модели «на лету», создание силуэтов и т.п. В отличие от предыдущего вершинного, способны обработать не только одну вершину, но и целый примитив. Примитивом может быть отрезок (две вершины) и треугольник (три вершины), а при наличии информации о смежных вершинах (англ. adjacency) для треугольного примитива может быть обработано до шести вершин.
Пиксельный шейдер
Пиксельными шейдерами выполняют наложение текстур, освещение, и разные текстурные эффекты, такие как отражение, преломление, туман, Bump Mapping и пр. Пиксельные шейдеры также используются для пост-эффектов.
Пиксельный шейдер работает с фрагментами растрового изображения и с текстурами — обрабатывает данные, связанные с пикселями (например, цвет, глубина, текстурные координаты). Пиксельный шейдер используется на последней стадии графического конвейера для формирования фрагмента изображения.
На чем пишут шейдеры?
Изначально шейдеры можно было писать на assembler-like языке, но позже появились шейдерные языки высокого уровня, похожие на язык С, такие как: Cg, GLSL и HLSL.
Такие языки намного проще чем C, ведь задачи решаемые с их помощью, гораздо проще. Система типов в таких языках отражает нужды программистов графики. Поэтому они предоставляют программисту специальные типы данных: матрицы, семплеры, векторы и тп.
RenderMan
Все что мы обсудили выше относится к realtime графике. Но существуют non-realtime графика. В чем разница – realtime – реальное время, тоесть здесь и сейчас – давать 60 кадров в секунду в игре, это процесс реального времени. А вот рендерить комплексный кадр для ультрасовременной анимации по несколько минут это non-realtime. Суть во времени.
Например, графику такого качества как в последних мультипликационных фильмах студии Pixar получить в реальном времени мы сейчас получить не можем. Очень большие рендер-фермы обсчитывают симуляции света по совсем другим алгоритмам, очень затратным, но дающим почти фотореалистичные картинки.
Супер-реалистичная графика в Sand piper
Например, посмотрите, на вот этот милый мультфильм, песчинки, перышки птички, волны, все выглядит невероятно реальным.
*Видео могут забанить на Youtube, если оно не открывается, погуглите pixar sandpiper – короткометражный мультфильм про храброго песочника очень милый и пушистый. Умилит и продемонстрирует насколько крутой может быть компьютерная графика.
Так вот это RenderMan от фирмы Pixar. Он стал первым языком программирования шейдеров. API RenderMan является фактическим стандартом для профессионального рендеринга, используется во всех работах студии Pixar и не только их.
Полезная информация
Теперь Вы знаете что такое шейдеры, но помимо шейдеров, есть другие очень интересные темы в разработке игр и компьютерной графике, которые наверняка Вас заинтересуют:
Если остались вопросы
Как обычно, если у Вас остались какие-то вопросы, задавайте их в комментариях, я всегда отвечу. За любое доброе слово или правку ошибок я буду очень признателен.
Расскажу, как в общем случае они работают, что умеют и как их используют
Сразу оговорюсь, что материал рассчитан на тех, кто никогда не работал с шейдерами или вообще не знаком с разработкой игр, то есть это в некотором смысле научпоп.
Слово «шейдер» в контексте разработки игр очень популярно, слышать его могли и те, кто игры не делает. Само слово изначально появилось от англ. shading (затенение) — первые шейдеры использовались, чтобы передавать глубину с помощью работы со светом, блеском, тенями и прочим. Со временем шейдеры стали использоваться для совершенно разного вида постобработки и вообще отрисовки примерно всего.
Говоря общими словами, шейдер — это просто программа для графической карты. То есть то, что пишется школьниками на паскале (хипстерами на пайтоне) — это программы для вашего центрального процессора (CPU), а шейдеры — для графического (GPU). Особенность же этих программ выходит из особенностей GPU — они работают параллельно на сотнях маленьких ядех вместо нескольких больших, преимущественно осуществляя математические операции.
Теперь разберемся, как это все работает.
В общем случае цель шейдера — отрисовать некоторый объект. Поэтому возьмем куб, распишем процесс его отрисовки и посмотрим, где используются шейдеры и зачем. Сначала опишем сам куб. Для графической карты это 8 точек, между некоторыми из которых есть плоскость. Каждая из точек описывается тремя числами (правильно сказать, что это вершины). Помимо этого у кубика есть цвет и положение внутри мира.
Процесс отрисовки, если его достаточно упростить (что я и сделаю в рамках этой статьи), можно поделить на несколько шагов:
1. Получение входных данных из памяти. 2. Выполнение шейдера вершин. 3. Растеризация. 4. Выполнение шейдера пикселей (фрагментов). 5. Проведение тестов «глубины». 6. Отрисовка на текстуру для экрана.
В первом шаге видеокарта каким-то образом получает данные (вершины, плоскости, текстуры) в свою видеопамять, для нас это сейчас не так важно. Далее происходит конвертация координат относительно объекта в координаты на экране относительно камеры. После происходит растеризация — высчитывается, в каких пикселях уже на экране находится объект. Такие пиксели называют фрагментами. Отличие от пикселей заключается в том, что фрагмент помимо информации о пикселе, содержит еще и некоторую побочную информацию, полученную после растеризации. Для упрощения будем считать, что это все просто пиксели на экране. Далее для каждого пикселя выполняется шейдер фрагмента. А затем проверяется, что расстояние от камеры до фрагмента соответствует высчитанному заранее в нужном направлении в буфере глубины. Проще говоря, проверяется, нет ли перед объектом чего-либо еще, и нужно ли его отрисовывать на итоговое изображение.
Как видите, в процессе отрисовки можно заметить два вида шейдера. На самом деле, сейчас есть чуть больше видов, но они не столь важны для разбора, так как имеют более специфичный характер использования, а мы рассказываем на пальцах. Но вот те два, что нас волнуют:
1. Шейдер вершин. 2. Шейдер фрагментов.
Как сказано было ранее, этот шейдер (или группа шейдеров по очереди) занимается переводом координат относительно объекта, в координаты на текстуре.
На картинке начало координат немного не соответствует реальным, что все так же не влияет на понимание процесса 🙂
Пройдемся по состояниям. В первом у нас, очевидно, входные координаты без излишков. На втором они были перенесены в координаты относительно начала «мира». Потом они переносятся в координаты относительно точки смотрящего (видно на второй картинке), но заметно, что картинка плоская. Их проекция происходит далее и мы получаем наши итоговые координаты. Все эти операции производятся шейдером. Помимо прочего, он позволяет не только отобразить реальные координаты, но и модифицировать их так, чтобы исказить объект для эффекта. Например, я недавно писал шейдер, который переворачивал спрайт, чтобы отрисовать его тень:
После преобразований вершин и растеризации нужно высчитать цвет каждого фрагмента (помним, что для упрощения это пиксели). Для примера возьмём наш куб: мы помним, что он залит одним цветом. Просто сделаем так, чтобы цвет каждого фрагмента стал цвета куба и все:
Выглядит немного странно, да? Проблема в том, что мы не видим ни теней, ни текстур. Будь на кубе какая-либо текстура, мы бы заметили переход между гранями. Вот возьмем текстуру:
Теперь достаточно в каждом пикселе просто брать цвет из текстуры. Но чтобы это сделать, нужно добавить для каждой точки куба еще информацию: UV канал. Это координат вида (u, v). Отсюда и название, так как x и y были заняты. Она присваивается вершине объекта и обозначает точку на текстуре, которая ей соответствует. Чтобы было понятнее, если мы хотим на каждую грань куба нарисовать знакомое нам лицо, то UV координаты для каждой грани будут выглядеть предельно просто:
Модифицировать их никак не надо. Более того, считать координаты для каждой точки — тоже не нужно. Этим занимается GPU, самостоятельно интерполируя точки из вершин. Тогда достаточно просто сказать для каждой точки что-то вроде
Это очень условный пример, но примерно так в простейшем случае оно и работает:
Помимо натягивания текстур в пиксельном шейдере можно, например, получить информацию об освещенности и добавить к цвету черного пропорционально затемнению в этой точке, тогда объект будет менее плоским. Это, конечно, если просчет освещенности где-то написан, потому что сама по себе видеокарта о понятиях освещения и теней мало чего знает. Вообще делать с цветами можно что угодно, например подменять их в зависимости от игрока:
Помимо UV канала в шейдер в зависимости от его вида приходят разные данные. Они зависят от игрового движка и графической библиотеки. Обычно туда входят данные о местоположении точки, нормаль (вектор исходящий от поверхности в точке), UV канал. Но также шейдер вершин может передавать данные в пиксельный шейдер. Если это координата, то она будет проинтеполирована на основе положения фрагмента относительно вершин, между которыми он находится, как, например, UV данные.