Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Разработка игрового движка с нуля
GAMEINATOR forums > Общие разделы > Создание и модификация игр. Геймдев.
Страницы: 1, 2, 3
Молния в вакууме
refuse, это что, для каждой текстуры нужен будет такой скрипт? Я в своём движке думаю сделать больше похоже на сталкерский: у каждой модели есть список текстур и "шейдер" ( конечно, у меня там FFP =) ). В каждой стадии шейдера хранится индекс текстуры из этого списка. Таким образом можно уменьшить количество переключений состояния, т.к. текстуры можно менять отдельно.
autistic
Цитата(saas @ 27.12.2017, 20:46) *
это что, для каждой текстуры нужен будет такой скрипт?

Немного сложнее. Каждый скрипт будет описывать некую схему в виде определенного набора констант, на основании этой схемы будут компилироваться шейдера. Из этого набора констант будут вычисляться хеши которым будут сопоставлены скомпилированные шейдера, и при парсинге следующего материала ему будет назначен уже скомпилированный шейдер, если материал с подобной структурой уже был загружен. В самом скрипте материала используемый шейдер явно не будет указываться.
Glsl шейдер будет всего один, но он будет порезан на секции заключенные в блоки #ifdef ... #elif ... #else, перед компиляцией шейдера строка с исходным кодом шейдера будет добавляться к строке с заголовком, в котором и будут объявлены соответствующие дефайны. Этот заголовок будет строиться при парсинге материала и из него же будет вычисляться хеш.
Вместе с этим каждый материал будет содержать наборы значений констант, а кроми этого он может быть мультипроходным (привет шкурке, которая отрисовывается в несколько проходов) и состоять из нескольких стейджей для которых могут быть использованы разные шейдера.
Сложновато? smile.gif Я тоже так думаю, полагаю на реализацию все новогодние каникулы уйдут (в перерывах между пьянством) если не больше >_<

Цитата(saas @ 27.12.2017, 20:46) *
Я в своём движке думаю сделать больше похоже на сталкерский: у каждой модели есть список текстур и "шейдер"

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


Цитата(saas @ 27.12.2017, 20:46) *
Таким образом можно уменьшить количество переключений состояния, т.к. текстуры можно менять отдельно.

Сортировку я представляю себе примерно так: при сортировке оперируют более мелкими сущностями которые получаются в результате дробления материалов и моделей на кусочки геометрии, текстуры, шейдера трансформации, и объединения их в рендер таргеты, которые помещаются в очередь отрисовки в определенном порядке. Но до этого еще ой как далеко и много другой работы предстоит проделать laugh.gif
Молния в вакууме
Цитата(refuse @ 27.12.2017, 21:36) *
Сложновато?

Да, и в правду сложновато.

Но всё-таки, для каждой текстуры свои константы. Это же сколько лишней информации может быть. А если что-то поменять в этих константах потребуется для набора аналогичных текстур? Может добавить возможность наследования настроек из материала-шаблона?
Tron
У меня JSON используется, формат примерно на твой похож.

Pass
{
Shader ...
Defines ...

{
Diffuse ....
Specular ...
Metallness ...
... ...
PBR PARAMS{ ... }
Bump params {...}
}
}

Вообще по хорошему еще бы дописать добавить сразу RT, что бы рендер сам подстраивался под материал
autistic
Начал реализовывать задуманную систему материалов и в процессе понял, что такая система не должна использоваться в движке, слишком долго будут парситься материалы и компилироваться шейдера. Решил отказаться от нее и использовать набор заранее скомпилированных шейдеров, которые будут явно указываться в скрипте материала, как описано в сообщении выше. Чуть позже покажу результаты.

P.S. потер собственный флуд, дабы не загромождать тему бесполезной информацией.
RayTwitty
Любое начинание это конечно хорошо, да и вам самим решать в любом случае, но не попахивает ли это все велосипедом? Чем потенциальный движок будет отличаться от существующих аналогов? Не думаете ли вы, что все эти этапы проектирования архитектуры были уже пройдены многими разработчиками и в итоге получится примерно тоже самое?
jamakasi
Цитата(refuse @ 08.01.2018, 17:03) *
слишком долго будут парситься материалы и компилироваться шейдера

Сделай кэширование, первое обращение к нему и он компилится, в последующем берется из кэша, так делают очень многие. Дабы избавиться от парсинга можно тоже уйти к унификации путем компиляции в малый бинарный формат по одному из 2х путей:
1) Компилятор из твоих json во что то бинарное для снижения размеров и времени загрузки\парсинга. Далее простой компилятор который берет некий уровень, пробегает по всем материалам и составляет список. Дальше по этому списку делаешь 1 файл со всеми материалами. Далее этот файл разово грузится вместе с уровнем и выгружается при смене уровня.
2) Решение в лоб, делаешь список материалов уровня, и перед загрузкой делаешь их компил. Этакий прекэшинг тупой.

Касательно решения возможного вопроса "а если материалы настраивать то надо сначало чистить кэш что не удобно", тут уже тоже все придумано как то так:
- нужно загрузить материал Х
- считаем sha(любой удобный быстрый алгоритм, хоть мд5) материала
- смотрим в %TEMP%\YouGame\cache\хэш
- если есть берем его, нет значит компилим\рекомпилим и сохраняем в кэш

PS компиляция шейдера на целевой точка игрока это очень хорошо на самом деле.
Tron
Цитата(refuse @ 08.01.2018, 17:03) *
Начал реализовывать задуманную систему материалов и в процессе понял, что такая система не должна использоваться в движке, слишком долго будут парситься материалы и компилироваться шейдера.

ShaderCache в помощь

Цитата(jamakasi @ 08.01.2018, 18:34) *
Цитата(refuse @ 08.01.2018, 17:03) *
слишком долго будут парситься материалы и компилироваться шейдера

Сделай кэширование, первое обращение к нему и он компилится, в последующем берется из кэша, так делают очень многие. Дабы избавиться от парсинга можно тоже уйти к унификации путем компиляции в малый бинарный формат по одному из 2х путей:
1) Компилятор из твоих json во что то бинарное для снижения размеров и времени загрузки\парсинга. Далее простой компилятор который берет некий уровень, пробегает по всем материалам и составляет список. Дальше по этому списку делаешь 1 файл со всеми материалами. Далее этот файл разово грузится вместе с уровнем и выгружается при смене уровня.
2) Решение в лоб, делаешь список материалов уровня, и перед загрузкой делаешь их компил. Этакий прекэшинг тупой.

Касательно решения возможного вопроса "а если материалы настраивать то надо сначало чистить кэш что не удобно", тут уже тоже все придумано как то так:
- нужно загрузить материал Х
- считаем sha(любой удобный быстрый алгоритм, хоть мд5) материала
- смотрим в %TEMP%\YouGame\cache\хэш
- если есть берем его, нет значит компилим\рекомпилим и сохраняем в кэш

PS компиляция шейдера на целевой точка игрока это очень хорошо на самом деле.

И очень плохо на самом деле, если капнуть глубже.
Берем SPIR-V/HLSL, генерируем байт-код. Упаковываем этот байт-код и отдаем пользователю. PROOOFIIIT!

В случае с чистым GLSL, то только вариант парсинга прокатит. Можно сделать кэширование, но оно совсем не безупречно работает.

1) Генерация шейдерного кэша, происходит при первом обращении.
2) Если обновился драйвер - шейдерный кэш устаревает, необходима перегенерация.

Либо использовать трюк с конвертацией HLSL байт-кода в GLSL
autistic
Цитата(RayTwitty @ 08.01.2018, 20:22) *
Любое начинание это конечно хорошо, да и вам самим решать в любом случае, но не попахивает ли это все велосипедом? Чем потенциальный движок будет отличаться от существующих аналогов? Не думаете ли вы, что все эти этапы проектирования архитектуры были уже пройдены многими разработчиками и в итоге получится примерно тоже самое?

Я планирую создать некий Фреймворк с конечной целью сделать какую-нибудь игру. Конечно, можно было бы взять какое-нибудь готовое решение и сконцентрироваться на создании именно игры, но мне этот вариант не подходит по нескольким причинам:
1. Мне интересно не игру делать, интересен сам процесс ее создания, т.е. кодить и придумывать архитектурные решения самостоятельно.
2. Использование сторонней технологии предполагает 80-90% нудной рутины и лишь 10-20% творческой работы. Этого добра мне хватает на основной работе.
В общем считайте что это хобби, некоторые читают книги после работы, некоторые в качалку ходят, а я вот велосипед строю smile.gif

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

Upd.
Вот результаты моих экспериментов с убершейдером:

shader.h
Код
namespace Engine
{
    class Shader
    {
    public:
        virtual ~Shader() {}

        virtual void Compile(const String &shaderName, bool isDynamic) = 0;

        virtual void Draw(GraphicsShapePtr shape) = 0;

        virtual void SetWorldViewProj(const Matrix4 &worldViewProj) = 0;
    };

    Shader *GetShader();
}


shader.cpp
Код
const char geomVertexShader[] =
"\
layout(location = 1) in vec4 vertexPosition;\n\
layout(location = 2) in vec4 vertexNormal;\n\
layout(location = 3) in vec4 vertexTangent;\n\
layout(location = 4) in vec4 vertexBinormal;\n\
layout(location = 5) in vec2 vertexTexcoords;\n\
layout(location = 6) in uint bones;\n\
layout(location = 7) in vec4 weights;\n\
out vec2 texCoords;\n\
uniform mat4 worldViewProj;\n\
void main()\n\
{\n\
    gl_Position = worldViewProj * vertexPosition;\n\
    texCoords = vertexTexcoords;\n\
}\n\
\0";

const char geomFragmentShader[] =
"\
in vec2 texCoords;\n\
out vec4 color;\n\
#ifdef DIFFUSE_MAP\n\
uniform sampler2D diffuseMap;\n\
#endif\n\
void main()\n\
{\n\
#ifdef DIFFUSE_MAP\n\
    color = texture(diffuseMap, texCoords);\n\
#else\n\
    color = vec4(1.0, 1.0, 1.0, 1.0);\n\
#endif\n\
}\n\
\0";

namespace Engine
{
    // Shader parameters
    const String tagDiffuseMap = "DiffuseMap";

    // Macro definitions
    const String tagDiffuseMapMacro = "DIFFUSE_MAP";
    const String tagDynamicMacro = "DYNAMIC";

    struct ShaderDataHolder
    {
        ShaderDataHolder(const ShaderDataHolder &) = delete;
        ShaderDataHolder &operator = (const ShaderDataHolder &) = delete;

        ShaderDataHolder()
            : gpuProgramId(INVALID_GPU_PROGRAM_ID)
        {
        }

        ShaderDataHolder(GpuProgramId gpuProgramId)
            : gpuProgramId(gpuProgramId)
        {
        }

        ~ShaderDataHolder()
        {
            if (gpuProgramId != INVALID_GPU_PROGRAM_ID)
                GetRenderer()->DestroyGpuProgram(gpuProgramId);
        }

        GpuProgramId gpuProgramId;
    };

    using ShaderDataHolderPtr = std::shared_ptr<ShaderDataHolder>;
    using ShaderDataMap = std::unordered_map<String, ShaderDataHolderPtr>;

    struct Material
    {
        TexturePtr diffuseMap;
    };

    using MaterialPtr = std::shared_ptr<Material>;
    using MaterialMap = std::unordered_map<String, MaterialPtr>;

    template <class AssociativeContainer_T, class Key_T>
    bool HasEntry(const AssociativeContainer_T &container, const Key_T &key)
    {
        auto elementIt = container.find(key);
        if (elementIt != container.end())
            return true;

        return false;
    }

    template <class Key_T, class Val_T>
    bool GetEntry(const std::unordered_map<Key_T, Val_T> &container, const Key_T &key, Val_T &value)
    {
        auto elementIt = container.find(key);
        if (elementIt == container.end())
            return false;

        value = elementIt->second;
        return true;
    }

    class ShaderImpl : public Shader
    {
    public:
        ShaderImpl()
        {
            worldViewProj.SetIdentity();
            staticShaders.reserve(MAX_SHADER_DATAS);
            dynamicShaders.reserve(MAX_SHADER_DATAS);
        }

        virtual void Compile(const String &shaderName, bool isDynamic) override
        {
            if (shaderName.empty()) {
                //TODO: log error
                return;
            }

            if ((isDynamic && HasEntry(dynamicShaders, shaderName))
                || (!isDynamic && HasEntry(staticShaders, shaderName))) {
                //TODO: log warning
                return;
            }

            ByteArray source;
            if (!LoadFileSource(shaderName + ".shader", source)) {
                //TODO: log error
                return;
            }

            ScriptPtr shaderScript = ParseScript(shaderName, source);
            if (!shaderScript) {
                //TODO: log error
                return;
            }

            String shaderIdentifierStr;
            const String shaderBase = MakeShaderBase(shaderScript, isDynamic, shaderIdentifierStr);
            const String vertexShaderStr = String(shaderBase) + geomVertexShader;
            const String fragmentShaderStr = String(shaderBase) + geomFragmentShader;

            ByteArray vertexShaderSrc(vertexShaderStr.begin(), vertexShaderStr.end());
            ByteArray fragmentShaderSrc(fragmentShaderStr.begin(), fragmentShaderStr.end());
            GpuProgramId gpuProgramId = GetRenderer()->CreateGpuProgram(vertexShaderSrc, fragmentShaderSrc);

            if (gpuProgramId == INVALID_GPU_PROGRAM_ID) {
                //TODO: log error
                return;
            }

            if (!ParseMaterial(shaderScript, ))

            auto shaderData = std::make_shared<ShaderDataHolder>(gpuProgramId);
            if (isDynamic)
                dynamicShaders[shaderIdentifierStr] = shaderData;
            else
                staticShaders[shaderIdentifierStr] = shaderData;
        }

        virtual void Draw(GraphicsShapePtr shape) override
        {
            ShaderDataHolderPtr shaderData;
            bool isDynamic = IsDynamicShape(*shape);
            if ((isDynamic && !GetEntry(dynamicShaders, shape->shaderName, shaderData))
            || (!isDynamic && !GetEntry(staticShaders, shape->shaderName, shaderData))) {
                //TODO: log warning
                return;
            }

            if (shaderData != currentShaderData) {
                currentShaderData = shaderData;
                GetRenderer()->BindGpuProgram(shaderData->gpuProgramId);
                if (shaderData->diffuseMap)
                    GetRenderer()->BindHardwareTexture(shaderData->diffuseMap->GetHardwareTexture(), DIFFUSE_MAP);

                GetRenderer()->SetGpuProgramConstant(shaderData->gpuProgramId, "worldViewProj", worldViewProj);
            }

            GetRenderer()->DrawHardwareBuffer(shape->hardwareBufferId);
        }

        virtual void SetWorldViewProj(const Matrix4 &worldViewProj) override
        {
            this->worldViewProj = worldViewProj;
            if (currentShaderData) {
                GetRenderer()->SetGpuProgramConstant(
                    currentShaderData->gpuProgramId, "worldViewProj", worldViewProj);
            }
        }

    private:
        bool LoadFileSource(const String &fileName, ByteArray &source) const
        {
            FilePtr file = GetFileSystem()->OpenFileRead(fileName);
            if (!file)
                return false;

            const size_t fileSize = file->GetSize();
            if (fileSize == 0)
                return false;

            source.resize(fileSize);
            file->Read(&source[0], source.size());
            return true;
        }

        String MakeMacroDefinition(const String &macroName, const String &macroDefinition) const
        {
            return String("#ifndef ") + macroName + '\n'
                + String("#define ") + macroName + ' ' + macroDefinition + '\n'
                + String("#endif") + "\n\n";
        }

        String MakeShaderBase(ScriptPtr shaderScript, bool isDynamic, String &usedDefinesString) const
        {
            String result = "#version 330\n\n";
            const String separator = ";";

            if (isDynamic) {
                result += MakeMacroDefinition(tagDynamicMacro, "1");
                usedDefinesString += tagDynamicMacro + separator;
            }

            if (shaderScript->HasValue(tagDiffuseMap)) {
                result += MakeMacroDefinition(tagDiffuseMapMacro, "1");
                usedDefinesString += tagDiffuseMapMacro + separator;
            }

            return result;
        }

        bool IsDynamicShape(const GraphicsShape &shape) const
        {
            switch (shape.vertexFormat) {
            case VA_BONES:
            case VA_SKINNED_W2:
            case VA_SKINNED_W3:
            case VA_SKINNED_W4:
            case VA_SKINNED_W1:
                return true;
            }

            return false;
        }

        bool ParseMaterial(ScriptPtr shaderScript, Material &material) const
        {
            const String diffuseMapName = shaderScript->GetString(tagDiffuseMap);
            if (!diffuseMapName.empty())
                material.diffuseMap = GetTextureManager()->GetTexture(diffuseMapName);

            return true;
        }

    private:
        static const int MAX_SHADER_DATAS = 128;

        Matrix4 worldViewProj;

        ShaderDataMap staticShaders;
        ShaderDataMap dynamicShaders;

        ShaderDataHolderPtr currentShaderData;
    };

    Shader *GetShader()
    {
        static ShaderImpl shader;
        return &shader;
    }
}


Я планировал сделать отдельный кэш для GLSL программ на основе unordered_map, а в качестве ключа использовать строку с перечислением использованных дефайнов, отсортированных в алфавитном порядке.
Tron
Цитата
Я планировал сделать отдельный кэш для GLSL программ на основе unordered_map, а в качестве ключа использовать строку с перечислением использованных дефайнов, отсортированных в алфавитном порядке.

Зачем так усложнять? Если этот кеш нигде храниться не будет. Используй id шейдера/программы и все

Тебе именно охото в рендер вкатиться? - просто взял бы какой-нибудь рендер фреймворк и не парился.
autistic
Цитата(Tron @ 09.01.2018, 00:52) *
Зачем так усложнять? Если этот кеш нигде храниться не будет. Используй id шейдера/программы и все

Ну под кэшем я и подразумеваю мапу ключ/id шейдера, т.е. обычный менеджер. Если по соответствующему ключу записей в мапе не найдено, то компилируется новый шейдер, если запись существует, то используется существующий.

Цитата(Tron @ 09.01.2018, 00:52) *
Тебе именно охото в рендер вкатиться? - просто взял бы какой-нибудь рендер фреймворк и не парился

Да, мне интересно самому во всех тонкостях разбираться.
autistic
Реализовал зачатки системы материалов (или скорее шейдерную систему, если такое название вообще применимо). За основу все-таки взял убершейдер. Система пока ничего не умеет делать, т.к. мне пока самому непонятно какие параметры должны быть у шейдера и как их организовать, так что в текущем виде скрипт шейдера имеет только один параметр:
Код
{
    // Diffuse map
    DiffuseMap = cat_diff;
}


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

P.S. Ну и напоследок немного котика с отключенным буфером глубины smile.gif


Кстати, код тестового приложения заметно сократился:
+
Код
#include <engine/model.h>
#include <engine/platform.h>
#include <engine/renderer.h>
#include <engine/shader.h>

int main()
{
    Engine::Platform *platform = Engine::GetPlatform();
    if (!platform->CreateMainWindow())
        return -1;

    Engine::Renderer *renderer = Engine::GetRenderer();
    Engine::ModelManager *modelManager = Engine::GetModelManager();
    Engine::ModelPtr teapot = modelManager->GetModel("teapot");

    Engine::Matrix4 rotation(Engine::Matrix3(M_PI * 0.25f, M_PI * 0.1f, 0));

    Engine::Matrix4 proj, view, model, modelViewProj;
    view.SetIdentity();
    proj.SetPerspective(1.3f, 1.78f, 0.1f, 100000.0f);
    model.SetTranslation(Engine::Vector3(-0.35, -0.3f, -1.5f));
    modelViewProj = rotation * model * proj;


    Engine::Shader *shader = Engine::GetShader();
    shader->Compile("cat", false);
    shader->SetWorldViewProj(modelViewProj);

    platform->SetMainWindowSize(1280, 720, false);
    renderer->SetViewport(1280, 720);

    for (;;) {
        if (!platform->UpdateMainWindow()) {
            break;
        }

        shader->Draw(teapot->GetGraphicsShapes()[0]);
    }
}
autistic
Снова размышлял над тем какую бы игру можно было бы сделать используя собственный графический движок, подумал про про бильярд. 3-х мерное окружение с качественными текстурами и освещением + честная физика + мультиплеер.
Я уже даже прикинул какой стек технологий можно использовать:
В качестве фронтенда мобильное приложение, скажем под андроид, естественно с использованием самописного движка.
В качестве бэкенда самописный сервер на С++, который будет осуществлять авторизацию, регистрацию, матчмейкинг и соблюдение игровых правил (определение очередности хода, подсчет очков и т.д).
Также предусмотреть самописный REST, скажем на php, для предоставления некоего АПИ для внешних сервисов (регистрация с сайта/форума, осуществление внутриигровых покупок и т.п.)
В качестве БД PostgreSQL или MySQL Server.
Вобщем фантазирую smile.gif
Единственной серьезной проблемой является тот факт, что идея не нова и стор должен быть забит похожими играми.
Примеры:
https://play.google.com/store/apps/details?id=com.xs.pooltd
https://itunes.apple.com/ca/app/3d-pool-gam...1175740795?mt=8
shurabich
Цитата(refuse @ 14.01.2018, 21:47) *
Единственной серьезной проблемой является тот факт, что идея не нова и стор должен быть забит похожими играми.
Примеры:
https://play.google.com/store/apps/details?id=com.xs.pooltd
https://itunes.apple.com/ca/app/3d-pool-gam...1175740795?mt=8


В основном все делают пул по идее. А как насчет русского бильярда? А вообще на NES был Lunar Ball... занятная игрушка, в которой можно было менять гравитацию, а сами столы имели причудливую форму. Было бы прикольно что то такое увидеть в 3D. wink_old.gif
https://www.youtube.com/watch?v=bHilCMWwBYI
solitary.wanderer94
Цитата(refuse @ 14.01.2018, 21:47) *
Снова размышлял над тем какую бы игру можно было бы сделать используя собственный графический движок, подумал про про бильярд. 3-х мерное окружение с качественными текстурами и освещением + честная физика + мультиплеер.
Я уже даже прикинул какой стек технологий можно использовать:
В качестве фронтенда мобильное приложение, скажем под андроид, естественно с использованием самописного движка.
В качестве бэкенда самописный сервер на С++, который будет осуществлять авторизацию, регистрацию, матчмейкинг и соблюдение игровых правил (определение очередности хода, подсчет очков и т.д).
Также предусмотреть самописный REST, скажем на php, для предоставления некоего АПИ для внешних сервисов (регистрация с сайта/форума, осуществление внутриигровых покупок и т.п.)
Вобщем фантазирую smile.gif
Единственной серьезной проблемой является тот факт, что идея не нова и стор должен быть забит похожими играми.


Я тут по-быстрому накидал один вариант:
shurabich
Кстати, раз форум по большей части сталкеру посвящен, да и что то уникальное хочется сделать, то было бы прикольно увидеть бильярд в Зоне. Как раз столики на старом Баре были, да вот в игре не использовались... Ну а что? Было бы здорово: сталкеры играют в бильярд, фоном слышны анекдоты и музыка... атмосферно и действительно уникально. wink.gif

solitary.wanderer94
Цитата(shurabich @ 15.01.2018, 00:21) *
Как раз столики на старом Баре были, да вот в игре не использовались...

Наверно разрабы их специально для модеров оставили
autistic
Реализованы основы техники отложенного затенения, пока только geometry pass. Собственно затенение (lightning pass) будет добавлено позже.
Содержимое G-Buffer'а с котиком (цвет, текстурные координаты, нормали, позиции соответственно)



Следующим этапом планирую добавить источники света и собственно затенение.

P.S. Кстати, столкнулся с таким странным поведением OpenGL - не получается изменить размер RT в большую сторону. В меньшую - пожалуйста, а в большую конечный результат обрезается вот таким образом


Думаю пока что с этим делать. Мб кто-то знает как пофиксить?
Tron
Размер буфера фиксирован же, вроде как. Насколько мне помнится не больше 4 текстур.
Takke
Оффтоп
Цитата(shurabich @ 15.01.2018, 02:21) *
Было бы здорово: сталкеры играют в бильярд, фоном слышны анекдоты и музыка... атмосферно и действительно уникально.

Я прям представил эту божественную анимацию и мне на секунду стало страшно biggrin.gif
jamakasi
Цитата(refuse @ 21.01.2018, 23:22) *
Кстати, столкнулся с таким странным поведением OpenGL - не получается изменить размер RT в большую сторону.

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

Цитата(jamakasi @ 22.01.2018, 10:27) *
конечно врядли но проверь может ли твоя видяшка такие размеры текстур, если указываешь что то большое.

С этим тоже все норм, проверял на исходном разрешении 800х600, изменял до 1280х720, оба разрешения поддерживаются видеокартой. Да и glTexImage2d вернула бы статус ошибки в случае неподходящего размера. Тут скорее всего я что-то не так делаю.
jamakasi
Цитата(refuse @ 22.01.2018, 08:56) *
800х600, изменял до 1280х720

О, так желательно квадратные текстуры делать аля 512х512 1024х1024 2048х2048 и т.д. , Помню гдето сталкивался с такой бякой и это помогло.
SkyLoader
jamakasi, это же RT, а не просто текстуры smile.gif Всегда должны поддерживать практически любые размеры (размеры 4К и больше в расчет не берем).

refuse, а масок нет никаких при рендере? Может при создании девайса некорректно параметры указаны?
Вообще я делал похожее на дх9, проблем не было. Но вот с дх10/11 появились, часть RT почему-то не захотела подстраиваться под увеличенный размер. Но это скорее всего мой косяк, ибо в играх подобное реализовано нормально.
jamakasi
Цитата(SkyLoader @ 22.01.2018, 09:41) *
это же RT, а не просто текстуры smile.gif Всегда должны поддерживать любые размеры (размеры 4К и больше в расчет не берем).

Так то оно так, но это openGL который построен на костылях и магии laugh.gif
ps Кстати, у меня тут для одного проекта получилась этакая cvs для файлов с дедубликацией. Никому не надо? а то думаю вот обернуть ее в нечто самостоятельное. Достаточно удобно применяется для последующего хранения чегото на хостинге и отдачи клиенту с возможностью отката на предыдущие версии.
autistic
Цитата(jamakasi @ 22.01.2018, 11:35) *
О, так желательно квадратные текстуры делать аля 512х512 1024х1024 2048х2048 и т.д.

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

Цитата(SkyLoader @ 22.01.2018, 11:41) *
а масок нет никаких при рендере? Может при создании девайса некорректно параметры указаны?

Вроде нету, буфер глубины разве что, но я его размер тоже меняю.
abramcumner
Цитата(refuse @ 21.01.2018, 23:22) *
P.S. Кстати, столкнулся с таким странным поведением OpenGL - не получается изменить размер RT в большую сторону. В меньшую - пожалуйста, а в большую конечный результат обрезается вот таким образом

Может все меняется, просто какой-нибудь Scissor Test работает?
Молния в вакууме
refuse, что-то у меня вообще ничего не рендерится. Скрипт и тестовое приложение из 112 поста подходят к последней ревизии?
autistic
saas, ну да, тестовое приложение со скриптом немного изменились. А кроме того в GraphicsShape появилось поле shaderName, я туда пока что название шейдера хардкодом добавляю, надо дорабатывать конвертер, чтоб это название из свойств FBX-материала бралось.

Код
struct GraphicsShape
{
    VertexFormat vertexFormat;
    HardwareBufferId hardwareBufferId;
    String shaderName = "cat";
};



Тестовое приложение
Код
#include <engine/model.h>
#include <engine/platform.h>
#include <engine/shader.h>

int main()
{
    Engine::Platform *platform = Engine::GetPlatform();
    if (!platform->CreateMainWindow())
        return -1;

    platform->SetMainWindowSize(1280, 720, false);

    Engine::ModelManager *modelManager = Engine::GetModelManager();
    Engine::ModelPtr teapot = modelManager->GetModel("teapot");

    Engine::Matrix4 rotation(Engine::Matrix3(M_PI * 0.25f, M_PI * 0.1f, 0));

    Engine::Matrix4 proj, view, world;
    view.SetIdentity();
    proj.SetPerspective(1.3f, 1.78f, 0.1f, 100000.0f);
    world.SetTranslation(Engine::Vector3(-0.35, -0.3f, -1.5f));
    world = rotation * world;

    Engine::Shader *shader = Engine::GetShader();
    shader->SetFrameBufferSize(1280, 720);
    shader->Compile("cat", false);
    shader->SetWorldViewProj(world, view, proj);

    for (;;) {
        if (!platform->UpdateMainWindow()) {
            break;
        }

        shader->BeginFrame();
        shader->Draw(teapot->GetGraphicsShapes()[0]);
        shader->EndFrame();
    }
}


Скрипт
Код
#include <exception>
#include <fstream>
#include <iostream>
#include <set>
#include <vector>

#include <fbxsdk.h>

void ConvertMesh(FbxMesh *fbxMesh, std::vector<short> &indices, std::vector<float> &vertices)
{
    if (!fbxMesh->IsTriangleMesh()) {
        throw std::exception("The mesh should be a triangle mesh");
    }

    const FbxVector4 *controlPoints = fbxMesh->GetControlPoints();

    FbxStringList uvSets;
    fbxMesh->GetUVSetNames(uvSets);
    fbxMesh->GenerateNormals();

    short k = 0;
    for (int i = 0; i < fbxMesh->GetPolygonCount(); ++i) {
        for (int j = 0; j < 3; ++j) {
            int index = fbxMesh->GetPolygonVertex(i, j);
            indices.push_back(k++);

            const FbxVector4 &controlPoint = controlPoints[index];
            vertices.push_back(controlPoint[0]);
            vertices.push_back(controlPoint[1]);
            vertices.push_back(controlPoint[2]);

            FbxVector4 normal;
            fbxMesh->GetPolygonVertexNormal(i, j, normal);
            vertices.push_back(normal[0]);
            vertices.push_back(normal[1]);
            vertices.push_back(normal[2]);

            vertices.resize(vertices.size() + 6);

            bool unmapped;
            FbxVector2 textureUV;
            fbxMesh->GetPolygonVertexUV(i, j, uvSets[0], textureUV, unmapped);
            vertices.push_back(textureUV[0]);
            vertices.push_back(1-textureUV[1]);
        }
    }
}

void ConvertMesh(const std::vector<short> &indices, const std::vector<float> &vertices, FbxMesh *mesh)
{
    int count = vertices.size() / 14;
    mesh->InitControlPoints(count);

    const float *verts = vertices.data();
    FbxVector4 *points = mesh->GetControlPoints();

    for (int i = 0; i < count; ++i) {
        float x = *verts++;
        float y = *verts++;
        float z = *verts++;
        verts += 11;
        points[i] = FbxVector4(x, y, z);
    }

    count = indices.size();
    for (int i = 0; i < count; i +=3) {
        mesh->BeginPolygon();
        for (int j = 0; j < 3; ++j) {
            int index = indices[i + j];
            mesh->AddPolygon(index);
        }
        mesh->EndPolygon();
    }
}

void ExportMesh(const char *source, const char *target)
{
    std::vector<short> indices;
    std::vector<float> vertices;

    FbxManager *sdkManager = FbxManager::Create();
    FbxIOSettings *ios = FbxIOSettings::Create(sdkManager, IOSROOT);
    sdkManager->SetIOSettings(ios);

    FbxImporter* importer = FbxImporter::Create(sdkManager, "");
    if (!importer->Initialize(source, -1, sdkManager->GetIOSettings()))
        throw std::exception("Call to FbxImporter::Initialize() failed.");

    FbxScene* scene = FbxScene::Create(sdkManager, "ImportedScene");
    importer->Import(scene);
    importer->Destroy();

    std::ofstream file;
    file.open(target, std::ios::out | std::ios::binary);
    file.write("mdl\0", 4);

    short minor = 1, major = 0;
    file.write((char*)&major, 2);
    file.write((char*)&minor, 2);

    FbxNode *root = scene->GetRootNode();
    for (int i = 0; i < root->GetChildCount(); ++i) {
        std::vector<short> indices;
        std::vector<float> vertices;
        FbxNode *child = root->GetChild(i);
        ConvertMesh(child->GetMesh(), indices, vertices);

        int chunkId = 1, size = indices.size() * 2, fmt = 31;
        file.write((char*)&chunkId, 4);
        file.write((char*)&size, 4);
        file.write((const char*)indices.data(), indices.size() * 2);

        size = vertices.size() * 4;
        file.write((char*)&fmt, 4);
        file.write((char*)&size, 4);
        file.write((const char*)vertices.data(), vertices.size() * 4);
    }

    file.close();
}

void ImportMesh(const char *source, const char *target)
{
    std::vector<short> indices;
    std::vector<float> vertices;

    std::ifstream file;
    file.open(source, std::ios::in | std::ios::binary);
    if (!file)
        throw std::exception("Can't open source file");

    int size;
    file.seekg(12, SEEK_CUR);
    file.read((char*)&size, 4);

    indices.resize(size);
    file.read((char*)&indices[0], size);

    file.seekg(4, SEEK_CUR);
    file.read((char*)&size, 4);
    vertices.resize(size);
    file.read((char*)&vertices[0], size);

    FbxManager *sdkManager = FbxManager::Create();
    FbxIOSettings *ios = FbxIOSettings::Create(sdkManager, IOSROOT);
    sdkManager->SetIOSettings(ios);

    FbxExporter* exporter = FbxExporter::Create(sdkManager, "");
    if (!exporter->Initialize(source, -1, sdkManager->GetIOSettings()))
        throw std::exception("Call to FbxImporter::Initialize() failed.");

    FbxScene *scene = FbxScene::Create(sdkManager, "ExportedScene");
    FbxNode *root = scene->GetRootNode();

    FbxNode *child = FbxNode::Create(sdkManager, "teapot");
    FbxMesh *mesh = FbxMesh::Create(scene, "teapot");

    ConvertMesh(indices, vertices, mesh);

    child->SetNodeAttribute(mesh);
    root->AddChild(child);

    exporter->Export(scene);
    exporter->Destroy();
}

int main(int argc, char *argv[])
{
    try {
        if (argc != 3) {
            throw std::exception("Invalid parameters passed.");
        }

#if 0
        ExportMesh(argv[1], argv[2]);
#elif 0
        ImportMesh(argv[2], argv[1]);
#else
        ExportMesh(argv[1], argv[2]);
        ImportMesh(argv[2], argv[1]);
#endif
    }
    catch (const std::exception &error) {
        std::clog << error.what() << std::endl;
    }
}


cat.shader
Код
{
    // Diffuse map
    DiffuseMap = cat_diff;
}
Молния в вакууме
Цитата(refuse @ 22.01.2018, 19:39) *
А кроме того в GraphicsShape появилось поле shaderName, я туда пока что название шейдера хардкодом добавляю

Ага, вот в чём дело.

Баг с рендертаргетом у тебя из за того, что ты i передал вторым параметром в glTexImage2D в ResizeRenderTarget. У рендертаргета вообще могут быть мипмапы? smile.gif

Ещё надо #include <algorithm> добавить в renderer.cpp, GCC не может найти std::find_if без него.
autistic
Ох, моя рассеянность меня доканает когда-нибудь =( Спасибо! smile.gif
autistic
Итак, отложенное затенение работает. Освещение реализовано самым примитивным способом, с ограничением в 32 точечных источника освещения и скорее всего будет переработано, но тем не менее поставленная цель достигнута.


Далее возникла небольшая дилемма, в связи с тем, что время которое я могу уделить этому проекту весьма ограничено возникает естественный вопрос, на что его лучше потратить: продолжать разработку в формате изучения отдельных компонентов движка, либо сконцентрироваться на разработке вполне конкретной игры? Создал опрос
autistic
Исправил освещение.
Освещенность вычисляется как скалярное произведение вектора направления света с вектором нормали.
В предыдущей версии нормали не были преобразованы в пространство камеры и поэтому освещенность текселей вычислялась неправильно.
Для того чтобы выполнить соответствующие преобразования необходимо каждую нормаль умножить на специальную матрицу, которая вычисляется как обратная от произведения мировой матрицы на матрицу вида. Вычислять такую матрицу шейдером для каждой вершины слишком накладно, разумнее вычислить ее на процессоре один раз и передать в шейдерную программу.
Пришлось дописать код который инвертирует матрицу, дифф можно посмотреть тут
Использованные материалы (осторожно, траффик)











Supple Hope
Цитата(Орхетектор @ 26.02.2018, 00:33) *
Исправил освещение.
Освещенность вычисляется как скалярное произведение вектора направления света с вектором нормали.

Так это ты только по Ламберту освещение сделал, не? Щас уже по Орен-Найяру модно.
autistic
Победил экспорт моделей из макса и рассчет освещения. Слева направо освещение модели с нормалями без учета групп сглаживания, с учетом групп сглаживания, из карты нормалей.


Цитата(Hoyt Ridge @ 26.02.2018, 06:02) *
Так это ты только по Ламберту освещение сделал, не? Щас уже по Орен-Найяру модно.

Сейчас модель освещения самая примитивная, без затухания и всего такого, т.к. главная задача сейчас чтобы все правильно заработало в комплексе. После того как более-менее стабильно все заработает уже можно будет любую технику заиспользовать.
autistic
Реализовано отражение (specular lighting)



EDIT: Немного доработал отражения, на скрине справа результат затенения новой техникой.
autistic
Продолжаю эксперименты с освещением. Oren-Nayar и Cook-Torrance для диффузного освещения и отражений.

Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2024 IPS, Inc.