Skip to content

Kuesa basic Example

Demonstrates the use of the KuesaSerenity API to import a glTF2 file inside a Window

basic-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/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(Kuesa::Serenity::AssetCollections *assetCollections,
                                                     ::Serenity::LayerManager *layerManager)
{
    Kuesa::Serenity::GLTF2Importer importer;
    importer.assetCollections = assetCollections;
    importer.layerManager = layerManager;
    importer.source = String(SHARED_ASSETS_DIR "/models/car/DodgeViper.gltf");

    // 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/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
    Kuesa::Serenity::AssetCollections collections;
    ::Serenity::LayerManager layerManager;
    layerManager.addLayer(Kuesa::Serenity::Layers::ZFillLayerName);
    layerManager.addLayer(Kuesa::Serenity::Layers::OpaqueLayerName);
    layerManager.addLayer(Kuesa::Serenity::Layers::AlphaLayerName);

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

Filename: basic/main.cpp

Retrieving Animations

 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
    // Animations
    {
        auto *camCenterAnimator = root->createComponent<::Serenity::ClipAnimator>();
        camCenterAnimator->clip = collections.animationClips("SweepCamCenterAction");
        camCenterAnimator->mapper = collections.animationMappers("SweepCamCenterAction");
        camCenterAnimator->loops = -1;
        camCenterAnimator->setRunning(true);

        auto *camPitchAnimator = root->createComponent<::Serenity::ClipAnimator>();
        camPitchAnimator->clip = collections.animationClips("SweepCamPitchAction");
        camPitchAnimator->mapper = collections.animationMappers("SweepCamPitchAction");
        camPitchAnimator->loops = -1;
        camPitchAnimator->setRunning(true);

        auto *hoodAnimator = root->createComponent<::Serenity::ClipAnimator>();
        hoodAnimator->clip = collections.animationClips("HoodAction");
        hoodAnimator->mapper = collections.animationMappers("HoodAction");
        hoodAnimator->loops = 1;
        hoodAnimator->setRunning(true);

        const String wheelNames[] = {
            String("WheelBLDriveAction"),
            String("WheelBRDriveAction"),
            String("WheelFLDriveAction"),
            String("WheelFRDriveAction"),
        };
        for (const String &wheelName : wheelNames) {
            auto *wheelAnimator = root->createComponent<::Serenity::ClipAnimator>();
            wheelAnimator->clip = collections.animationClips(wheelName);
            wheelAnimator->mapper = collections.animationMappers(wheelName);
            wheelAnimator->loops = -1;
            wheelAnimator->setRunning(true);
        }
    }

Filename: basic/main.cpp

Setting up serenity

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

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

Filename: basic/main.cpp

And then we configure the Serenity Rendering algorithm. Rendering will be split in 2 phases, one for Opaque 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
    // Create Render Algo
    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,
            {},
            true
        };

        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 createAlphaPhase = [&layerManager]() {
        ::Serenity::ForwardAlgorithm::RenderPhase alphaPhase{
            layerManager.layerMask({ Kuesa::Serenity::Layers::AlphaLayerName }),
            ::Serenity::ForwardAlgorithm::RenderPhase::Type::Alpha,
            ::Serenity::LayerFilterType::AcceptAll,
            {},
            true
        };

        auto depthState = std::make_shared<::Serenity::DepthStencilState>();
        depthState->depthTestEnabled = true;
        depthState->depthWritesEnabled = false;
        depthState->depthCompareOperation = KDGpu::CompareOperation::LessOrEqual;
        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::SrcAlpha;
        attachmentBlendState.blending.color.srcFactor = KDGpu::BlendFactor::SrcAlpha;
        attachmentBlendState.blending.color.dstFactor = KDGpu::BlendFactor::OneMinusSrcAlpha;
        attachmentBlendState.blending.alpha.dstFactor = KDGpu::BlendFactor::One;
        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(),
                                                 createAlphaPhase() },
                                               overlay,
                                               sweepCamera)
    };

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

Filename: basic/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/main.cpp


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