Создание игр » DirectX 9, Featured, Теория » Система материалов
Система материалов
Система материалов это очень важный компонент любого графического движка. От гибкости системы зависит то, насколько материалы в движке будут гибкие, настраиваемые, удобные и простые в использовании. Тот метод, который мы применяем с Вами сейчас (т.е. просто включение нужного шейдера и передача параметров), довольно неудобен – для отрисовки каждого объекта приходится писать пусть и не много, но всё же несколько строчек кода. Чем больше объектов (и материалов) в сцене – тем больше кода. Потому, помимо основной своей задачи, система материалов позволяет так же уменьшить количество кода, необходимого для отображения объекта, а так же уменьшает количество ошибок (т.к. задача контроля какой шейдер сейчас включен, когда включить другой и т.д. полностью возлагается на систему).
Задачи системы материалов
Первым делом давайте определимся с тем, какие задачи мы хотели бы возложить на систему материалов. Просто напишем список того, что бы нам хотелось от этой системы и от класса материала:
- Материал должен загружаться из файла. Этот файл должен иметь простую структуру, которая позволила бы без проблем создавать новые материалы в любом текстовом редакторе. Уже по привычке выберем формат XML, который будем загружать с помощью PugiXML.
- Если материал загружается повторно – система должна понять это и просто выдать указатель на уже загруженный ранее материал.
- Материал должен как минимум содержать данные об используемых им текстурах и шейдерах и должен уметь сам “включить” их при рендере объекта с этим материалом.
По минимуму нам этого должно хватить, а со временем разовьём систему дальше, для большей эффективности.
Класс материала
Давайте при реализации класса материалов, да и самой системы, как и всегда, двигаться по пути максимальной простоты. Реализуем лишь строго необходимый минимум функционала:
class CMaterial { CShaderPtr m_pVertexShader; CShaderPtr m_pPixelShader; CTexturePtr m_pTextures[4]; public: CMaterial(void); virtual ~CMaterial(void); bool Load(const std::string& strFileName); }; |
В классе графики добавим функцию загрузки материала:
CMaterialPtr Graphics::LoadMaterial( const std::string& strFileName ) { // ищем среди уже загруженных материалов for (size_t i=0; i<m_vecMaterials.size(); i++) if (m_vecMaterials[i]->GetFileName()==strFileName) return m_vecMaterials[i]; // создаём новый и загружаем из файла CMaterialPtr pMat = new CMaterial(); if (!pMat->Load(strFileName)) { delete pMat; return NULL; } m_vecMaterials.push_back(pMat); return pMat; } |
И функции “включения” материала (для рендера) и отключения его (на всякий случай)):
void Graphics::BindMaterial( CMaterialPtr pMat ) { SetPixelShader(pMat->GetPS()); SetVertexShader(pMat->GetVS()); for (int i=0; i<pMat->GetTexSlots(); i++) { CTexturePtr pTex = pMat->GetTexture(i); if (pTex) SetTexture(i, pTex); } } void Graphics::UnbindMaterial() { SetPixelShader(NULL); SetVertexShader(NULL); } |
Теперь мы можем использовать наш класс в программе, вот так мы сможем загрузить материал в функции bool CTutorial3D::Init:
m_pMaterial = Graphics::get().LoadMaterial("Data/mat1.mtl"); if (!m_pMaterial) return false; |
И вот так использовать его в рендере:
gr.BindMaterial(m_pMaterial); // передаём константы в шейдеры m_pMaterial->GetVS()->SetMatrix("mViewProjection", &m_camera.GetViewProjection()); m_pMaterial->GetVS()->SetVector("vLight", &D3DXVECTOR4(250, 100, 100, 0)); m_pMaterial->GetVS()->SetVector("vCam", &m_camera.GetPos4()); // gr.SetTexture(0, m_pTexDiffuse); gr.SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_ANISOTROPIC); gr.SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC); gr.SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR); m_pMaterial->GetVS()->SetMatrix("mWorld", &m_modelTop.GetMatrix()); gr.Draw(m_modelTop.GetMesh()); m_pMaterial->GetVS()->SetMatrix("mWorld", &m_modelMiddle.GetMatrix()); gr.Draw(m_modelMiddle.GetMesh()); m_pMaterial->GetVS()->SetMatrix("mWorld", &m_modelBottom.GetMatrix()); gr.Draw(m_modelBottom.GetMesh()); |
Помимо этого я, как обычно, внёс в наши (старые) исходники небольшие косметические изменения. На этом класс материала можно считать готовым к использованию.
В следующих уроках мы не много доделаем и разовьём класс материала эту систему для того, что бы в будущем минимизировать наш труд по использованию материалов, шейдеров и текстур в программе. Однако, уже на данный момент класс материала позволил нам немного упростить наш код. Кроме того, у нас появилась возможность настраивать материалы (т.е. указать pixel shader, vertex shader, а так же текстуры) из конфигурационного файла материала, а значит, без компиляции приложения заново. Это очень удобно, когда над игрой работает не только программист, но и 3д-художники, т.к. они могут настраивать внешний вид без участия программиста и, следовательно, у него появляется больше времени для работы именно над программированием игры, а не правкой всякой мелочи вроде материалов, путей к текстурами и так далее.
Как обычно, прилагаются:
Раздел: DirectX 9, Featured, Теория · Теги: Direct3D, Оптимизация, Создание движка
Спасибо за урок!
Можно вопрос?
Почему именно четыре указателя на текстуру в классе материала?
Будет ли в будущем урок по тексту – как выводить быстро рисовать текст, без GDI, в direct3d-приложении?
Redline, четыре текстуры взял просто с запасом – на самом деле нам пока вполне будет хватать и двух-трёх. В дальнейшем, при необходимости, это количество можно будет без проблем увеличить.
Если урок по тексту нужен – сделаем. Вообще, эту задачу вполне можно возложить на систему GUI. Одну из готовых реализаций (myGUI) этой системы я планировал рассмотреть в скором будущем.
Вообще, как правило, рендер текста не является “узким местом” программы, потому особого смысла глубоко погружаться в эту тему и делать какие-то особо сложные оптимизации я не вижу, если честно.
Почему материал не хранит такие данные, как ambient, diffuse, specular и shininess компоненты освещения? Ведь от этого очень зависит вид объекта. Можно было сделать отдельную структуру, хранящую эти параметры, а в классе материала хранить указатель на неё.
lds, потому как оно нам сейчас не нужно. А я уже много раз писал, что я предпочитаю не делать пустую работу
Надеюсь, всё в порядке?
Не смею торопить, просто какое-то затишье крепкое…
Antony, затишье крепкое получилось, да )
Всё в порядке, просто загруженность очень большая. С выходных, надеюсь, всё вернётся в норму и постараюсь наверстать всё упущенное время, сделав сразу несколько уроков
Есть маленькое пожелание – сделайте уроков именно по шейдерам, и, если не очень сложно, разберите тени, пожалуйста.
>> Одну из готовых реализаций (myGUI) этой системы я планировал рассмотреть в скором будущем.
Ждем с нетерпением! Я уже пробовал myGUI и самостоятельно кое с чем разобраться не получилось.
Демка с работающей системой материалов не запускается, пишет не удалось найти d3dx9_43.dll
Это нормальное явление, если стоит старая версия ДХ-9, обновить надо и должно заработать.
Давно читаю уроки, спасибо за труд…
В демке заметил неприятный эффект, когда окно появляется модель выглядит нормально, но при растяжении окна – изображение растягивается (деформируется)…
Очень рад, если они хоть чем-то помогли )
Касательно растяжения – да, есть такая недоработка. В ближайших уроках доделаем… Найти бы только время на это – со свободным временем проблемы 3ий месяц 8-(
Привет!
Скопилось несколько вопросов, если можно:) Сразу извиняюсь за дилетантство.
Допустим у нас уже есть небольшой 3d мир, который представляет собой немало разнообразных объектов.
Правильно ли я думаю, что для ренедера всех объектов их лучше сгруппировать по их материалу? Т.е. рисовать их не подряд, а сначала выводим все объекты, которые имеют материал A, потом переключаем стейты для материала B и выводим все объекты с этим материалом?
Или это не имеет особого значения, всё равно мировая матрица у каждого объекта своя и мы её ставим в шейдер перед выводом каждого объекта?
Далее, когда материал считается разный (в смысле по каким признакам отличать материалы для формирования грумм рендера)? Если у двух материалов одинаковые вертексные и пиксельные шейдеры, но, например, цвет разный – это будет одна группа объектов или две?
И, наконец, у нас разные шейдеры с разными наборами глобальных данных (тех, которые устанавливаются из программы на с++). Тогда получается и на каждый шейдер у нас отдельная ф-ия, устанавливающая эти шейдерные переменные? Иначе как лучше узнать, какие установки ждёт шейдер?
Вы всё верно понимаете. То, что Вы описали – это называется батчинг (batching), можете погуглить по этому слову и найти некоторое количество статей. Я через некоторое время собирался описать основы процесса батчинга, которые я использую в своём движке.
Ещё такое вопрос: стоит ли использовать файлы-эффектов или лучше обойтись без них? И если использовать, то у OpenGL есть аналоги, чтобы можно было переписать графику под MacOS, например?