Skip to content

Kuesa basic-iro Example

Demonstrates the use of the KuesaSerenity API to import a glTF2 file using the Iro Materials extension inside a Window

basic-iro-example.png

Setup window

Kuesa::Serenity::Window can be conveniently used to create a Window suitable for Serenity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main(int ac, char **av)
{
    KDGui::GuiApplication app;

    // Setup Window
    Kuesa::Serenity::Window w;
    auto engine = std::make_unique<::Serenity::AspectEngine>();
    w.visible.valueChanged().connect([&app](const bool &visible) {
        if (visible == false)
            app.quit();
    });
    w.title = KDBindings::makeBinding(makeTitle(w.width, w.height, engine->fps));
    w.width = 1920;
    w.height = 1080;
    w.visible = true;

    // Setup KDGpu
    std::unique_ptr<KDGpu::GraphicsApi> api = std::make_unique<KDGpu::VulkanGraphicsApi>();
    KDGpu::Instance instance = api->createInstance(KDGpu::InstanceOptions{
            .applicationVersion = SERENITY_MAKE_API_VERSION(0, 1, 0, 0) });
    const KDGpu::SurfaceOptions surfaceOptions = KDGpuKDGui::View::surfaceOptions(&w);
    KDGpu::Surface surface = instance.createSurface(surfaceOptions);
    KDGpu::AdapterAndDevice defaultDevice = instance.createDefaultDevice(surface);
    KDGpu::Device device = std::move(defaultDevice.device);

Filename: basic-iro/main.cpp

Importing a glTF2 File

We add the default Kuesa Layers to the [LayerManager ] so that meshes can be tagged with the appropriate layer mask based on their material properties upon importing.

This will be required to filter which Entities to render at the appropriate time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
std::unique_ptr<::Serenity::Entity> createKuesaScene(::Serenity::LayerManager *layerManager)
{
    Kuesa::Serenity::GLTF2Importer importer;
    importer.layerManager = layerManager;
    importer.source = String("examples/assets/basic-iro/car/car.gltf");

    assert(importer.status() == Kuesa::Serenity::GLTF2Importer::Status::Ready);

    // Insert the generated Kuesa Entities into rootEntity
    auto rootEntity = std::make_unique<::Serenity::Entity>();

    {
        // Add a default light
        ::Serenity::Entity *e = rootEntity->createChildEntity<::Serenity::Entity>();
        ::Serenity::Light *l = e->createComponent<::Serenity::Light>();
        l->type = ::Serenity::Light::Type::Point;
        ::Serenity::SrtTransform *t = e->createComponent<::Serenity::SrtTransform>();
        t->translation = glm::vec3(0.0f, 40.0f, 0.0f);
    }

    for (std::unique_ptr<::Serenity::Entity> &sceneRoot : importer.sceneRoots()) {
        // Take ownership
        rootEntity->addChildEntity(std::move(sceneRoot));
    }

    return std::move(rootEntity);
}

Filename: basic-iro/main.cpp

We make use of the GLTF2Importer to load a glTF2 file and feed our AssectCollections .

1
2
3
4
5
6
7
8
    // Setup Kuesa Scene
    ::Serenity::LayerManager layerManager;
    layerManager.addLayer(Kuesa::Serenity::Layers::ZFillLayerName);
    layerManager.addLayer(Kuesa::Serenity::Layers::OpaqueLayerName);
    layerManager.addLayer(Kuesa::Serenity::Layers::AlphaLayerName);
    layerManager.addLayer(Kuesa::Serenity::Layers::SkyboxLayerName);

    std::unique_ptr<::Serenity::Entity> root = createKuesaScene(&layerManager);

Filename: basic-iro/main.cpp

Setting up the Camera

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
::Serenity::Camera *createDefaultCamera(::Serenity::Entity *rootEntity, KDGui::Window &w)
{
    // Add Camera into the Scene
    ::Serenity::Camera *camera = rootEntity->createChildEntity<::Serenity::Camera>();
    camera->lookAt(glm::vec3(0.0f, 10.0f, 15.0f),
                   glm::vec3(0.0f, 0.0f, 0.0f),
                   glm::vec3(0.0f, 1.0f, 0.0f));
    camera->lens()->setPerspectiveProjection(45.0f,
                                             float(w.width()) / w.height(),
                                             1.0f, 1000.0f);
    camera->lens()->aspectRatio = KDBindings::makeBinding(updateAspectRatio(w.width, w.height));

    auto cameraFrameAction = camera->createComponent<::Serenity::FrameAction>();

    auto rotateCamera = [=](::Serenity::FrameAction *) {
        const float radius = 8.0f;
        static float angle = 0.0f;
        angle += 0.05f;
        const float rad = glm::radians(angle);
        const glm::vec3 position(radius * std::cos(rad), 1.0f, radius * std::sin(rad));
        camera->lookAt(position,
                       glm::vec3(0.0f, 0.0f, 0.0f),
                       glm::vec3(0.0f, 1.0f, 0.0f));

        if (angle > 360.0f)
            angle -= 360.0f;
    };
    // cameraFrameAction->triggered.connect(rotateCamera);

    return camera;
}

Filename: basic-iro/main.cpp

Using a CameraController

1
2
3
4
5
6
7
    ::Serenity::Camera *defaultCamera = createDefaultCamera(root.get(), w);

    // Camera Controller
    Kuesa::Serenity::CameraController *controller = w.createChild<Kuesa::Serenity::CameraController>();
    controller->window = &w;
    controller->camera = defaultCamera;
    w.cameraController = controller;

Filename: basic-iro/main.cpp

Setting up Serenity

We start by creating the various aspects we will make use of.

1
2
3
4
    // Setup Serenity Engine
    auto renderAspect = engine->createAspect<::Serenity::RenderAspect>(std::move(device));
    auto spatialAspect = engine->createAspect<::Serenity::SpatialAspect>();
    auto logicAspect = engine->createAspect<::Serenity::LogicAspect>();

Filename: basic-iro/main.cpp

And then we configure the Serenity Rendering algorithm. Rendering will be split in 3 phases, one for Opaque, one for Skybox and one for Alpha objects.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
    auto algo = std::make_unique<::Serenity::ForwardAlgorithm>();

    auto createOpaquePhase = [&layerManager]() {
        ::Serenity::ForwardAlgorithm::RenderPhase opaquePhase{
            layerManager.layerMask({ Kuesa::Serenity::Layers::OpaqueLayerName }),
            ::Serenity::ForwardAlgorithm::RenderPhase::Type::Opaque,
            ::Serenity::LayerFilterType::AcceptAny
        };

        auto depthState = std::make_shared<::Serenity::DepthStencilState>();
        depthState->depthTestEnabled = true;
        depthState->depthWritesEnabled = true;
        depthState->depthCompareOperation = KDGpu::CompareOperation::Less;
        opaquePhase.renderStates.setDepthStencilState(std::move(depthState));

        auto rasterizerState = std::make_shared<::Serenity::PrimitiveRasterizerState>();
        rasterizerState->cullMode = KDGpu::CullModeFlagBits::BackBit;
        opaquePhase.renderStates.setPrimitiveRasterizerState(std::move(rasterizerState));

        return opaquePhase;
    };

    auto createSkyboxPhase = [&layerManager]() {
        ::Serenity::ForwardAlgorithm::RenderPhase opaquePhase{
            layerManager.layerMask({ Kuesa::Serenity::Layers::SkyboxLayerName }),
            ::Serenity::ForwardAlgorithm::RenderPhase::Type::Opaque,
            ::Serenity::LayerFilterType::AcceptAny
        };

        auto depthState = std::make_shared<::Serenity::DepthStencilState>();
        depthState->depthTestEnabled = true;
        depthState->depthWritesEnabled = false;
        depthState->depthCompareOperation = KDGpu::CompareOperation::LessOrEqual;
        opaquePhase.renderStates.setDepthStencilState(std::move(depthState));

        auto rasterizerState = std::make_shared<::Serenity::PrimitiveRasterizerState>();
        rasterizerState->cullMode = KDGpu::CullModeFlagBits::None;
        opaquePhase.renderStates.setPrimitiveRasterizerState(std::move(rasterizerState));

        return opaquePhase;
    };

    auto createAlphaPhase = [&layerManager]() {
        ::Serenity::ForwardAlgorithm::RenderPhase alphaPhase{
            layerManager.layerMask({ Kuesa::Serenity::Layers::AlphaLayerName }),
            ::Serenity::ForwardAlgorithm::RenderPhase::Type::Alpha,
            ::Serenity::LayerFilterType::AcceptAll
        };

        auto depthState = std::make_shared<::Serenity::DepthStencilState>();
        depthState->depthTestEnabled = true;
        depthState->depthWritesEnabled = false;
        depthState->depthCompareOperation = KDGpu::CompareOperation::Less;
        alphaPhase.renderStates.setDepthStencilState(std::move(depthState));

        auto blendState = std::make_shared<::Serenity::ColorBlendState>();
        ::Serenity::ColorBlendState::AttachmentBlendState attachmentBlendState;

        attachmentBlendState.format = KDGpu::Format::UNDEFINED;
        attachmentBlendState.blending.blendingEnabled = true;
        attachmentBlendState.blending.alpha.operation = KDGpu::BlendOperation::Add;
        attachmentBlendState.blending.color.operation = KDGpu::BlendOperation::Add;
        attachmentBlendState.blending.alpha.srcFactor = KDGpu::BlendFactor::Zero;
        attachmentBlendState.blending.alpha.dstFactor = KDGpu::BlendFactor::OneMinusSrcAlpha;
        attachmentBlendState.blending.color.srcFactor = KDGpu::BlendFactor::SrcAlpha;
        attachmentBlendState.blending.color.dstFactor = KDGpu::BlendFactor::OneMinusSrcAlpha;
        blendState->attachmentBlendStates = { attachmentBlendState };
        alphaPhase.renderStates.setColorBlendState(std::move(blendState));

        return alphaPhase;
    };

    // ImGUI Overlay
    Serenity::AbstractOverlay *overlay = createOverlay(w, engine.get(), algo.get());

    // Specify RT
    algo->renderTargetRefs = { Serenity::RenderTargetRef::fromWindow(&w, instance) };
    // Specify which RenderTarget to render to, which render phases and which camera to use
    algo->renderViews = {
        Serenity::ForwardAlgorithm::RenderView(0,
                                               { createOpaquePhase(),
                                                 createSkyboxPhase(),
                                                 createAlphaPhase() },
                                               overlay,
                                               defaultCamera)
    };

    algo->msaaSamples = Serenity::RenderAlgorithm::SamplesCount::Samples_4;

Filename: basic-iro/main.cpp

Finally we can set the Render Algorithm and the scene root before starting the Serenity engine.

1
2
3
4
    renderAspect->setRenderAlgorithm(std::move(algo));
    engine->setRootEntity(std::move(root));

    engine->running = true;

Filename: basic-iro/main.cpp


Updated on 2023-07-03 at 11:02:17 +0000