Candera

Candera Graphic Engine enables embedded applications to render and control externally designed content, especially 3D content.

Candera Architecture Overview

Introduction

Candera Graphic Engine enables embedded applications to render and control externally designed content, especially 3D content. Applications typically do not manipulate content directly but provide widgets which basically translate high-level events into graphical representation by e.g. moving or rotate visual objects.

The figure below shows a typical architecture of a Candera-based HMI application:

drawing-4-1676875296.png

Such an application consists of a 'Business Logic' component which communicates with the system environment, e.g. via field buses such as CAN, processes the signals and forwards them to application widgets on a high abstraction level (e.g. current speed in km/h). The application specific widgets visualize this information by manipulating content that is rendered via Candera.

  • Candera requires small integration layers for graphical output devices, hardware accelerated or software realised basic mathematical functions, access to system memory and other platform related functionality which is typically implemented in scope of the hosting application.
  • Candera provides a set of interfaces which need to be connected with platform functionality for this purpose.
  • Candera is intended to drive different graphic systems, such as OpenGL ES 2.0 which are also required to be provided by the hosting platform.

Candera-based HMI applications typically define a run-time environment potentially including thread contexts and signal paths in which Candera is embedded. Candera does not define its own execution container to avoid threading and timing issues that are not transparent to the application and to provide the degree of freedom to design the application fitting best into the overall system. Candera Graphic Engine only supports single-thread. Therefore, Candera implementation is not thread-safe to avoid potential unnecessary overhead in execution time and code-size. Hence the hosting application is required to execute any functionality on Candera from within the same thread.

Interfaces & Namespaces

Interfaces

The Candera public interfaces are covered by the namespaces Candera and various subnamespaces of namespace Candera.

All namespaces containing either Internal or Private in the name cover private namespaces, which are not recommended to be used in any application, as it is not guaranteed that these interfaces remain stable respectively exist in any future release. Changes in any of these private namespaces can be applied without any (prior) notifications.

Namespace Candera

Namespace Candera::Animation

Namespace Candera::Diagnostics

Namespace Candera::DynamicProperties

Namespace Candera::Globalization

Namespace Candera::Monitor

Namespace Candera::TextRendering

Candera MISRA Compliance

Candera tries to be MISRA compliant as much as possible. For checking MISRA compliance, Coverity® Static Analysis together with the supported MISRA rule set is used. For some reasons we cannot fulfil all MISRA rules, a set of Candera MISRA Rules for Coverity® Static Analysis is disabled due to specified reasons.

Candera MISRA Rules for Coverity® Static Analysis

The following file is taken from the Coverity® Static Analysis distribution and is modified for the needs of Candera. Reasons are given for deviations.

//########################################################################
// (C) Candera GmbH
// All rights reserved.
// -----------------------------------------------------
// This document contains proprietary information belonging to
// Candera GmbH.
// Passing on and copying of this document, use and communication
// of its contents is not permitted without prior written authorization.
//########################################################################
//
// The following configuration is based on MISRA 2008 required rules.
// Any further deviation is added here with proper explanation and justification.
//
//########################################################################

// Configuration for MISRA C++-2008, rule categories: Required
{   "version": "2.0",
    "standard": "misrac++2008",
    "title": "CGI Studio - MISRA C++-2008 Project Specific",
    "deviations": [

// Project-specific suppressions defined
        //0-x-x
        { "deviation": "Rule 0-1-5", "reason": "Suppressed based on MISRA C++ 2008 Chapter 3.5, Library issues." },
        { "deviation": "Rule 0-1-10", "reason": "Suppressed based on MISRA C++ 2008 Chapter 3.5, Library issues." },
        { "deviation": "Rule 0-1-12", "reason": "Override of virtual functions may be in customer code, CGI Studio provides the API"},
        //2-x-x
        { "deviation": "Rule 2-10-1", "reason": "Temporary disabled, justification needed." },
        { "deviation": "Rule 2-10-3", "reason": "Typedefs with same name are used in different scopes. Typedef is not a unique declarator." },
        { "deviation": "Rule 2-10-4", "reason": "Type names are used twice in different namespaces." },
        { "deviation": "Rule 2-10-6", "reason": "Too many false positives by interpreting the rule also for forward declarations and different scopes." },
        //3-x-x
        { "deviation": "Rule 3-1-1", "reason": "object/function definitions in header files" },
        { "deviation": "Rule 3-2-3", "reason": "Too many false positives by interpreting the rule also for forward declarations." },
        { "deviation": "Rule 3-9-1", "reason": "Token-for-token compliance means that even a typedef of the same type breaks compliance. CGI Studio requires such typedefs in Meta Information and for SharedPointer." },
        //4-x-x
        { "deviation": "Rule 4-5-3", "reason": "Char used as 8 bit integer also for operations" },
        { "deviation": "Rule 4-10-2", "reason": "Deliberately use 0. See Scott Myers Effective C++, Item 25" },
        //5-x-x
        { "deviation": "Rule 5-0-15", "reason": "Pointer arithmetic is used by CGI Studio where address computation is required e.g. as part of device abstraction." },
        { "deviation": "Rule 5-0-21", "reason": "Bitwise operator applied to signed underlying type" },
        { "deviation": "Rule 5-2-5", "reason": "Attempt to cast away const/volatile from a pointer or reference" },
        { "deviation": "Rule 5-2-7", "reason": "Pointer cast deliberately not prohibited" },
        { "deviation": "Rule 5-2-12", "reason": "Array type passed to function expecting a pointer" },
        { "deviation": "Rule 5-14-1", "reason": "side effects on right hand side of logical operator" },
        //6-x-x
        { "deviation": "Rule 6-2-1", "reason": "assignment operator used in sub-expression" },
        { "deviation": "Rule 6-4-3", "reason": "departure from MISRA switch syntax" },
        { "deviation": "Rule 6-6-4", "reason": "This might lead to badly structured code." },
        { "deviation": "Rule 6-6-5", "reason": "This might lead to badly structured code." },
        //7-x-x
        { "deviation": "Rule 7-1-1", "reason": "Deliberately disabled: Cost - Value ratio is negative" },
        { "deviation": "Rule 7-3-1", "reason": "Use global declarations" },
        { "deviation": "Rule 7-3-2", "reason": "Non-global function, main, declared" },
        { "deviation": "Rule 7-3-3", "reason": "Unnamed namespace in header" },
        { "deviation": "Rule 7-3-4", "reason": "Using-directive used" },
        { "deviation": "Rule 7-3-6", "reason": "Using-directive/declaration in header file" },
        //8-x-x
        { "deviation": "Rule 8-4-1", "reason": "Function has variable number of arguments using the ellipsis notation" },
        //9-x-x
        { "deviation": "Rule 9-3-2", "reason": "Non-const members allow to expose members." },
        { "deviation": "Rule 9-5-1", "reason": "Unions are used very carefully and allow saving space in memory." },
        //10-x-x
        { "deviation": "Rule 10-3-1", "reason": "Suppressed because the choice to use C++ includes also the decision to use polymorphism and overriding virtual methods." },
        //14-x-x
        { "deviation": "Rule 14-7-1", "reason": "Suppressed based on MISRA C++ 2008 Chapter 3.5, Library issues." },
        { "deviation": "Rule 14-7-2", "reason": "Suppressed based on MISRA C++ 2008 Chapter 3.5, Library issues." },
        //15-x-x
        { "deviation": "Rule 15-1-1", "reason": "CGI Studio does not use exception handling." },
        { "deviation": "Rule 15-3-3", "reason": "CGI Studio does not use exception handling." },
        { "deviation": "Rule 15-3-6", "reason": "CGI Studio does not use exception handling." },
        { "deviation": "Rule 15-5-1", "reason": "CGI Studio does not use exception handling." },
        { "deviation": "Rule 15-5-2", "reason": "CGI Studio does not use exception handling." },
        { "deviation": "Rule 15-5-3", "reason": "CGI Studio does not use exception handling." },
        //16-x-x
        { "deviation": "Rule 16-0-4", "reason": "Temporary disabled, justification needed." },
        { "deviation": "Rule 16-0-8", "reason": "Temporary disabled, justification needed." },
        { "deviation": "Rule 16-2-1", "reason": "CGI Studio uses macros." },
        { "deviation": "Rule 16-2-2", "reason": "Temporary disabled, justification needed." },
        //18-x-x
        { "deviation": "Rule 18-4-1", "reason": "CGI Studio requires dynamic memory allocation." },

// Document Rules
        //document rules are not checked according to the current configuration

// Suppressing Advisory Rules
        { "deviation": "Rule 2-5-1", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 2-7-3", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 2-10-5", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 3-9-2", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 5-0-2", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 5-2-3", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 5-2-9", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 5-2-10", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 5-19-1", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 7-5-4", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 10-1-1", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 10-2-1", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 12-1-2", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 14-8-2", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 15-0-2", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 15-3-2", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 16-2-5", "reason": "Advisory rules are suppressed." },
        { "deviation": "Rule 16-3-2", "reason": "Advisory rules are suppressed." },
    ]

}

Tutorial for Application Life Cycle

Tutorial for Application Life Cycle

Startup and Asset Management


Description

This tutorial leads through all required steps to initialize and prepare a Candera application for loading a SceneComposer generated asset and accessing content provided by the asset.


Initializing AssetLoader and Animations 
AssetLoader

For loading assets and accessing content, the application needs to create an instance of an Candera::AssetProvider and Candera::ContentLoader to have them available for later usage.

    m_contentLoader = &ContentLoader::GetInstance();
    m_contentLoader->Init(&m_assetProvider);
Animations

If Animations are used in the scene, a time dispatcher for dispatching world time to animation players needs to be constructed during initialization.

    m_animationDispatcher = Animation::AnimationTimeDispatcher::Create();
    UpdateSystem* updateSystem = EntitySystem::Get<UpdateSystem>();
    if (0 != updateSystem) {
        UpdateSystem::Handle animationUpdateComponent = updateSystem->CreateComponent();
        CANDERA_SUPPRESS_LINT_FOR_SYMBOL(1025, Candera::UpdateDelegate::UpdateWithMilliseconds, "False positive because the template arguments match.")
        UpdateSystem::Delegate animationUpdateDelegate = UpdateSystem::Delegate::UpdateWithMilliseconds<Animation::AnimationTimeDispatcher,
            &Animation::AnimationTimeDispatcher::Update>();
        result = updateSystem->SetComponentUpdateDelegate(animationUpdateComponent, animationUpdateDelegate) && result;
        result = updateSystem->AttachComponent(animationUpdateComponent, m_animationDispatcher.GetPointerToSharedInstance()) && result;
    }

#ifdef CANDERA_TRANSITIONS_ENABLED
    if (0 != m_transitionStrategy) {
        m_transitionStrategy->SetAnimationTimeDispatcher(m_animationDispatcher);
    }
#endif


Initializing 3D DefaultRenderMode 
3D DefaultRenderMode

The default render mode is kind of a "global" render mode which is applied to all Nodes within a 3D Candera::Scene which have no separate Candera::RenderMode set. Further, if a Candera::Node has its own RenderMode set, still dedicated components can be derived from Candera::DefaultRenderMode - like e.g. CullMode - if the "inheritance bit" in the Node's associated RenderMode is set to true.

If RenderMode is defined for a Candera::Camera, the RenderMode of the camera replaces the DefaultRenderMode, temporarily during the camera's render pass.

The DefaultRenderMode can be created and manipulated as follows:

    /* To overwrite the default render mode as specified
       in scene composer you can create your own render mode.
       This would have to be set via Renderer::SetDefaultRenderMode(renderMode);
     */
    Candera::MemoryManagement::SharedPointer<RenderMode> renderMode = RenderMode::Create();
    renderMode->SetWinding(RenderMode::CounterClockWise);
    renderMode->SetCulling(RenderMode::BackFaceCulling);
    renderMode->SetDepthWriteEnabled(true);
    renderMode->SetDepthTestEnabled(true);
Apply DefaultRenderMode

When initializing an asset the default render mode in Candera will be replaced by the values from the asset!


Loading an Asset 
Initializing AssetProvider

To initialize Candera::AssetProvider either a Candera::AssetRepository or Candera::AssetConfig has to be specified.

AssetRepository

Candera::AssetRepository is an abstraction of the actual location of the asset. Currently following implementations are available:

Custom AssetRepository implementations are possible.

AssetConfig

Candera::AssetConfig allows to specify multiple AssetRepository instances, typically for assets that are split with asset shaper and accessed on different locations. The application has to derive from the abstract Candera::AssetConfig to create an AssetConfig that supports the AssetRepository instances according to the specific needs. This AssetConfig can then be used to initialize Candera::AssetProvider:

    FEATSTD_LOG_INFO("- Initializing asset provider ...");

    static UInt32 validationFlags =
        AssetValidation::Flag<CffVersionAttribute, ErrorLevel>() |
        AssetValidation::Flag<FractionalNumberRepresentationAttribute, ErrorLevel>() |
        AssetValidation::Flag<PlatformNameAttribute, ErrorLevel>();

    if (!m_assetProvider.Initialize(&m_assetConfig, validationFlags)) {
        m_loadError = "Error: Asset initialization and validation failed!";
        FEATSTD_LOG_ERROR("%s\n", m_loadError);
        return false;
    }
Creating a FileAssetRepository

Before loading an asset from a file, ensure that the file exists at the given path. Then a Candera::FileAssetRepository can be created using the file path:

    FileAssetRepository* repo = CANDERA_NEW(FileAssetRepository)(fileName);
    FEATSTD_DEBUG_ASSERT(repo != 0);

If this operation succeeds, the asset repository can be added to an Candera::AssetConfig or specified directly to initialize Candera::AssetProvider.

Creating a MemoryAssetRepository

To create a Candera::MemoryAssetRepository the start address, where the asset can be found (e.g. the address in flash memory) has to be specified.

    MemoryAssetRepository* repo = CANDERA_NEW(MemoryAssetRepository)(startMemoryAddress);
    FEATSTD_DEBUG_ASSERT(repo != 0);

If this operation succeeds, the asset repository can be added to an Candera::AssetConfig or specified directly to initialize Candera::AssetProvider.

Retrieving AssetDescriptor

After initializing the Candera::AssetRepository, the Candera::AssetDescriptor can be retrieved:

    const AssetDescriptor& assetDescriptor = m_assetProvider.GetAssetDescriptor();

The AssetDescriptor contains information about the asset library file and Candera::AssetNamesList lists of all scenes, animations, displays etc. can be queried.

Create Displays and Render Targets
    if (assetDescriptor.GetAssetObjectCount(DisplayLib) == 0) {
        m_loadError = "Error: Missing elements.\n Please make sure that there are at least one display and at least one display render target and the cameras are attached to the corresponding render targets.";
        m_assetProvider.Finalize();
        return false;
    }

Next, the displays configured in the assets are created:

    Int displayId = -1;
    bool result = true;

    const AssetDescriptor& assetDescriptor = m_assetProvider.GetAssetDescriptor();

    Display* display = 0;
    Int displayWidth = -1;
    Int displayHeight = -1;
    for (AssetDescriptor::AssetIdIterator displayList = assetDescriptor.GetAssetIdIterator(DisplayLib); displayList.IsValid(); ++displayList) {

        displayId = m_assetProvider.GetDisplayDataById(*displayList, displayWidth, displayHeight);

        //check if already created before and destroy
        display = DevicePackageInterface::GetDisplay(displayId);
        if(display != 0) {
            DevicePackageInterface::DestroyDisplay(display);
        }
        display = DevicePackageInterface::CreateDisplay(displayId);
        if (display == 0) {
            FEATSTD_LOG_ERROR("Could not create display.\n");
            result = false;
        }
        static_cast<void>(m_displays.Add(display));

        // Load display meta information
        if (result) {
            if (!m_assetProvider.LoadDisplayProperties(*displayList)) {
                FEATSTD_LOG_INFO("- Loading of display properties failed");
            }
        }
        // Upload display if required
        if (result && upload) {
            Display::CommonSettings settings;
            settings.height = displayHeight;
            settings.width = displayWidth;
            if (!display->Upload(settings)) {
                FEATSTD_LOG_ERROR("Could not upload display.\n");
                result = false;
            }
        }
        // For simulation displays, set AutoKick disabled
        if ((display != 0)
            && (display->ToSimulatedDisplay() != 0)) {
                display->ToSimulatedDisplay()->SetAutoKickEnabled(false);
                FEATSTD_LOG_INFO("- AutoKick disabled");
        }
#ifdef FEATSTD_OS_WIN32
        //AppEnvironment::GetInstance().SetWindowPointer(display);
#endif
    }
    return result;

Here is an example how all render targets/layers can be read out of the asset file for further usage (like uploading, swapping, finalizing):

    GraphicDeviceUnit* gdu = 0;
    const AssetDescriptor& assetDescriptor = m_assetProvider.GetAssetDescriptor();
    Int rtCount = assetDescriptor.GetAssetObjectCount(RenderTargetLib);
    FEATSTD_UNUSED(rtCount);
    FEATSTD_LOG_INFO("Render target lib count = %d", rtCount);

#if (defined(CANDERA_3D_ENABLED) && defined(CGIAPP_STATISTICS_OVERLAY_ENABLED))
    static bool isRenderStatisticOverlayAttached = false;
#endif

    for (AssetDescriptor::RenderTargetIdIterator gduList = assetDescriptor.GetRenderTargetIdIterator(); gduList.IsValid(); ++gduList) {
        gdu = m_assetProvider.GetGraphicDeviceUnitById(*gduList);
        if (gdu != 0) {
            const Char* gduName = m_assetProvider.GetGraphicDeviceUnitById(*gduList)->GetName();
            FEATSTD_UNUSED(gduName);

#if (defined(CANDERA_3D_ENABLED) && defined(CGIAPP_STATISTICS_OVERLAY_ENABLED))
            Candera::DevicePackageDescriptor::UnitCategory unitCategory = DevicePackageDescriptor::GetUnitCategory(gdu->GetUnitType());
            switch (unitCategory) {
                case DevicePackageDescriptor::DisplayTarget3D:
                case DevicePackageDescriptor::DisplayTarget2D:
                case DevicePackageDescriptor::Mixed2D3DDisplayTarget:
                    // attach listener to first valid RT
                    if (!isRenderStatisticOverlayAttached) {
                        Candera::RenderTarget2D* renderTarget = gdu->ToRenderTarget2D();

                        if (0 != renderTarget) {
                            renderTarget->AddEventListener(m_renderTargetEventListener);
                            isRenderStatisticOverlayAttached = true;
                        }
                    }
                    break;
                default:
                    //do nothing;
                    break;

            }
#endif
            FEATSTD_LOG_INFO("GDU name = %s", gduName);
            if (upload) {
                //only upload if specified
                if (gdu->Upload()) {
                    FEATSTD_LOG_INFO("GDU upload sucessful");
                }
                else {
                    FEATSTD_LOG_ERROR("Upload of GDU %s failed\n", gduName);
                }
            }
            static_cast<void>(m_gdus.Add(gdu));
        }
        else {
            FEATSTD_LOG_INFO("GDU null pointer returned");
        }
    }

Creating 3D Scenes from the Asset 
Getting 3D Scene List

In case that Candera::AssetProvider has successfully loaded an asset file, 3D scenes part of the asset are constructed like in the following example.

By means of Candera::AssetDescriptor::GetAssetIdIterator, it is possible to retrieve a list of 3D Scenes:

    for (AssetDescriptor::AssetIdIterator sceneIdList = assetDescriptor.GetAssetIdIterator(SceneLib); sceneIdList.IsValid(); ++sceneIdList) {
        static_cast<void>(sceneList.Add(m_assetProvider.GetSceneById(*sceneIdList)->GetScene()->GetName()));
    }
Loading 3D Scene

The following listing shows how a Candera::Scene instance is finally loaded from the asset via Candera::ContentLoader::BuildSceneContext():

        // load scene by building the regarding scene context
        ContentLoader::UploadPolicy uploadPolicy = ContentLoader::NoVramUploadPolicy;
        if (upload) {
            uploadPolicy = ContentLoader::DefaultUploadPolicy;
        }

        ContentLoader::BuildState buildState = contentLoader->BuildSceneContextById(sceneId, uploadPolicy);

         // load all packages of the scene without interleaved rendering.
        while (buildState == ContentLoader::MorePackets) {
            buildState = contentLoader->BuildSceneContextById(sceneId, uploadPolicy);
        }
        if (buildState == ContentLoader::Completed) {
            sceneContext = contentLoader->GetSceneContextById(sceneId);
        } else {
            sceneContext = 0;
        }

The Candera::ContentLoader::DefaultUploadPolicy argument of the BuildSceneContext method forces the ContentLoader to create the scene data in the System RAM and to upload the asset data to VRAM. The upload of the asset data to VRAM can be avoided, if necessary, by using the argument Candera::ContentLoader::NoVramUploadPolicy instead.

The setting Candera::DefaultAssetProvider::SetIterativeLoadingEnabled() allows iterative loading of device objects during calls to Candera::ContentLoader::BuildSceneContext(). In combination with Candera::ContentLoader::NoVramUploadPolicy it is possible to load and upload a scene iteratively in background while another scene is rendered.

Further, all 3D Widgets related to the given scene can be retrieved from the SceneContext and configured as required. To support animated widgets, the application needs to provide a Candera::Animation::AnimationTimeDispatcher to its widgets, like this:

            Animation::AnimationTimeDispatcher::SharedPointer animationTimeDispatcher = GetAnimationTimeDispatcher();
            WidgetBase* widget = 0;
            for (bool success = sceneContext->GetFirstWidget(widget);
                success;
                success = sceneContext->GetNextWidget(widget)) {
                    if (widget != 0) {
                        widget->SetAnimationTimeDispatcher(animationTimeDispatcher);
                        // Register Controller for all GenericEventWidgets
                        ExampleWidgets::CgiEventWidget* gew = Dynamic_Cast<ExampleWidgets::CgiEventWidget*>(widget);

                        if (gew != 0) {
                        #ifdef USE_STATE_MACHINE
                            // Register Statemachine for Widget Events
                            gew->Register(GenericController::UiEventCallback, 0);
                        #endif
                            // Set input event provider for every widget
                            gew->SetInputEventProvider(GetInputEventProvider());
                        // FEATSTD_LOG_INFO("* ++ widget registered for Input-Events ...\n");
                        }
                    }
            }

            for (TreeIterator<Node> nodeIterator(sceneContext->GetScene()); nodeIterator.IsValid(); nodeIterator.Advance(TreeIterator<Node>::AdvanceToDescendant)) {
                CompositeGroup* compositeGroup = Dynamic_Cast<CompositeGroup*>(nodeIterator.GetNode());
                if (compositeGroup != 0) {
                    for (CompositeGroup::WidgetIterator widgetIterator = compositeGroup->GetWidgetIterator(); widgetIterator.IsValid(); ++widgetIterator) {
                        widget = *widgetIterator;
                        widget->SetAnimationTimeDispatcher(animationTimeDispatcher);
                    }
                }
            }

Creating 2D Scenes from the Asset 
Getting 2D Scene List

It is possible to retrieve a list of 2D Scenes from Candera::AssetDescriptor:

    for (AssetDescriptor::AssetIdIterator scene2DIdList = assetDescriptor.GetAssetIdIterator(Scene2DLib); scene2DIdList.IsValid(); ++scene2DIdList) {
        static_cast<void>(sceneList.Add(m_assetProvider.GetScene2DById(*scene2DIdList)->GetScene()->GetName()));
    }
Loading 2D Scene

The following listing shows how a Candera::Scene2D instance is finally loaded from the asset:

        // load scene by building the regarding scene context
        ContentLoader::UploadPolicy uploadPolicy = ContentLoader::NoVramUploadPolicy;
        if (upload) {
            uploadPolicy = ContentLoader::DefaultUploadPolicy;
        }

        ContentLoader::BuildState buildState = contentLoader->BuildScene2DContextById(sceneId, uploadPolicy);

        // load all packages of the scene without interleaved rendering.
        while (buildState == ContentLoader::MorePackets) {
            buildState = contentLoader->BuildScene2DContextById(sceneId, uploadPolicy);
        }
        if (buildState == ContentLoader::Completed) {
            sceneContext = contentLoader->GetScene2DContextById(sceneId);
        } else {
            sceneContext = 0;
        }

As in the 3D case, the Candera::ContentLoader::DefaultUploadPolicy argument of the BuildScene2DContext method forces the ContentLoader to create the scene data in the System RAM and to upload the asset data to VRAM. The upload of the asset data to VRAM can be avoided, if necessary, using the argument Candera::ContentLoader::NoVramUploadPolicy instead.

The setting Candera::DefaultAssetProvider::SetIterativeLoadingEnabled() allows iterative loading of device objects during calls to Candera::ContentLoader::BuildSceneContext(). In combination with Candera::ContentLoader::NoVramUploadPolicy it is possible to load and upload a scene iteratively in background while another scene is rendered.

Again, similar to 3D widgets, 2D widgets related to the given scene can be retrieved from the SceneContext. Also in this 2D example the application provides a Candera::Animation::AnimationTimeDispatcher to its 2D widgets:

            Animation::AnimationTimeDispatcher::SharedPointer animationTimeDispatcher = GetAnimationTimeDispatcher();
            WidgetBase* widget = 0;
            for (bool success = sceneContext->GetFirstWidget(widget);
                success;
                success = sceneContext->GetNextWidget(widget)) {
                    if (widget != 0) {
                        widget->SetAnimationTimeDispatcher(animationTimeDispatcher);
                        // Register Controller for all GenericEventWidgets
                        ExampleWidgets::CgiEventWidget2D* gew = Dynamic_Cast<ExampleWidgets::CgiEventWidget2D*>(widget);

                        if (gew != 0) {
                        #ifdef USE_STATE_MACHINE
                            // Register Statemachine for Widget Events
                            gew->Register(GenericController::UiEventCallback, 0);
                        #endif
                            // Set input event provider for every widget
                            gew->SetInputEventProvider(GetInputEventProvider());
                        // FEATSTD_LOG_INFO("* ++ widget registered for Input-Events ...\n");
                        }
                    }
            }



            for (TreeIterator<Node2D> nodeIterator(sceneContext->GetScene()); nodeIterator.IsValid(); nodeIterator.Advance(TreeIterator<Node2D>::AdvanceToDescendant)) {
                CompositeGroup2D* compositeGroup = Dynamic_Cast<CompositeGroup2D*>(nodeIterator.GetNode());
                if (compositeGroup != 0) {
                    for (CompositeGroup2D::WidgetIterator widgetIterator = compositeGroup->GetWidgetIterator(); widgetIterator.IsValid(); ++widgetIterator) {
                        widget = *widgetIterator;
                        widget->SetAnimationTimeDispatcher(animationTimeDispatcher);
                    }
                }
            }

Loading Animation an AnimationGroup 
Loading Animation

As can be seen from application initialization, the application already configured a Candera::AnimationTimeDispatcher.

Animations configured in SceneComposer can be loaded from the asset into a Candera::AnimationPlayer and started like following:

        Animation::AnimationPlayerBase::SharedPointer animationPlayer = m_assetProvider.GetAnimationById(assetId);
        if (animationPlayer != 0) {
            static_cast<void>(m_animationDispatcher->AddPlayer(animationPlayer));
            if (!m_animationPlayers.Contains(animationPlayer)) {
                static_cast<void>(m_animationPlayers.Add(animationPlayer));
            }
            if (!animationPlayer->Start()) {
                FEATSTD_LOG_ERROR(" ERROR: animation player start failed for'%s\n", animationName);
            }
        }
Loading AnimationGroup

Candera::AnimationGroup allows to start a hierarchical group of animations as configured in SceneComposer. The steps are similar besides that all children within the group have to be added to the Candera::AnimationTimeDispatcher, so it is advised to iterate through the tree recursively adding each child. The group can then be started at once:

        if (animationPlayer->IsTypeOf(Animation::AnimationGroupPlayer::GetTypeId())) {

            Animation::AnimationGroupPlayer::SharedPointer animationGroupPlayer
                = Dynamic_Cast<Animation::AnimationGroupPlayer::SharedPointer>(animationPlayer);
            if (animationGroupPlayer != 0) {
                AddAnimationGroupChildren(animationGroupPlayer,
                    animationGroupPlayer->GetFirstSuccessor(SharedPointer<Animation::AnimationPlayer>(0), Animation::AnimationGroupPlayer::WithPrevious));
                if (!animationGroupPlayer->Start()) {
                    FEATSTD_LOG_ERROR(" ERROR: animation player group start failed for'%s\n", animationName);
                }
            }

Selecting a Theme 
Retrieving Available Themes

The available themes in the asset can be retrieved via Candera::AssetDescriptor:

    for (AssetDescriptor::AssetIdIterator themeIdList = assetDescriptor.GetAssetIdIterator(ThemeLib); themeIdList.IsValid(); ++themeIdList) {
        static_cast<void>(themeList.Add(*themeIdList));
    }
Selecting a Theme

Afterwards one of the available themes can be set to Candera::AssetProvider:

            Candera::Id themeId = AssetProviderFunctions::GetIdByName(&m_assetProvider, ThemeLib, themeName);
            m_assetProvider.SetCurrentThemeById(themeId);

From now on this theme will be used when loading new scenes.

You need to reload the scene explicitly when you change the theme!


Loading User Data 

User data are raw data of any arbitrary type or format.

A SceneComposer asset can be used as a pass-through for such kind of proprietary data to the application.

Steps required:

  • Import Raw Data to the SceneComposer solution
  • Assign a numerical asset id like "1" to the data item in SceneComposer, optionally assign a symbolic name for this item
  • Export the asset (also the symbolic names header file is exported) and load it in the application
  • Access the raw data via a ResourceHandle retrieved from Candera::AssetProvider based on the Asset ID(see below)

    Id jpgId = CDA_LIBRARY_ASSETID(0x01); // Instead, a generated symbolic name could be used
    ResourceHandle resH = m_assetProvider->OpenRawResourceById(jpgId);
    Int32 resSize = m_assetProvider->GetResourceSize(resH);
    const void* dataPointer = m_assetProvider->GetResourceAddress(resH);
    UInt8* buffer = CANDERA_NEW_ARRAY(UInt8, resSize);
    UInt32 readBytes = m_assetProvider->ReadResource(resH, buffer, 0, resSize);
    CANDERA_DEBUG_ASSERT(resSize == readBytes);
    m_assetProvider->CloseResource(resH);   
    //...
    CANDERA_DELETE_ARRAY(buffer);

Accessing Items via Asset Id 
Item Identification: Asset Id instead of String

All items in a solution have a type and a name which identifies them in their parent container and a full name which uniquely identifies them in the entire solution. For example, a mesh in a scene could have the full name /Global/Scenes/MyScene/Group:RootNode/Mesh:MyMesh.

Before CGI-Studio V3.0.0, for accessing items from an application, the CanderaPath property displayed the string, which uniquely identified this item in the asset library. For the example above, the same mesh would have been accessed from Candera via the name /Scene:Global::Scenes::MyScene/Group:RootNode/Mesh:MyMesh.

To improve asset loading performance, CGI-Studio V3.0.0 introduces numerical Ids for accessing items from asset libraries.

Using Asset Ids

As an example, a bitmap called "cosmosbgBmp" is assigned a symbolic name in a SceneComposer solution:

drawing-4-1676876821.png

The "Id" field in SceneComposer displays by default "0000000". For this default, SceneComposer will generate a hash value for the Id. In case a custom value greater than '0' is entered in this field, the custom value will be generated instead, which is guaranteed to remain the same permanently, while the generated hash value could change over time.

Either during asset export, or explicitly via 'File -> Export Symbolic Names ...' menu, a header file is generated by SceneComposer containing all symbolic names defined in the solution. For the bitmap symbolic names, the export looks like:

namespace CgiAssetNames {
    static const Candera::Id cosmosbgBmp = CDA_LIBRARY_ASSETID(0x0U, 0x11008CU);
} //namespace CgiAssetNames

To access this bitmap, use the interface Candera::AssetProvider::GetBitmapById:

        Bitmap::SharedPointer bitmap = Base::GetAssetProvider()->GetBitmapById(CgiAssetNames::cosmosbgBmp);

Use the same approach for all other assets like displays, render target, nodes, font and raw data, ...


Query Candera Compile Switches 
Candera Compile Switches

Note that Candera compile switches are documented in Candera Configuration.

As an example, in case of DEVICE device, following queries are supported: (DEVICE stands for your device)

#if CANDERA_DEVICE==DEVICE
    ...
#endif

Similarily:

#ifdef CANDERA_DEVICE_DEVICE
    ...
#endif


Tutorial for Application Life Cycle

Render Loop

Description

After loading an asset and creating scenes, this chapter will now explain how an application can trigger and control rendering of 3D and 2D scenes.

Basically it is up to the application to define an event loop triggering render calls. Candera provides interfaces to render specific or all cameras, but scheduling and triggering these render calls is what the application is responsible for.


3D Render Loop 
3D Render Calls

Static class Candera::Renderer is part of Candera 3D Engine and provides two render call interfaces:

  1. Render all 3D cameras: Candera::Renderer::RenderAllCameras
  2. Render a specific camera only: Candera::Renderer::RenderCamera

Usually, rendering of all cameras is the safer and most convenient way, hence chosen here for the tutorial:

    FEATSTD_LINT_NEXT_EXPRESSION(534, "return value not needed");
    Renderer::RenderAllCameras();
Render Buffer Swapping

If the draw target operates on multiple buffers after each rendering cycle the presentation buffer is exchanged with the current background buffer, on which was written during the cycle.

For this, three interfaces are available:

  • Candera::RenderTarget::SetAutoSwapEnabled(bool enabled): if true, render target buffers are swapped automatically, when a camera within the render target has been rendered. Default: false
  • Candera::Camera::SetSwapEnabled(bool enable): if true, buffers are swapped automatically after rendering the according camera. Default: false
  • Candera::RenderTarget::SwapBuffers(): Trigger buffer swapping 'by hand'

It is very important to control buffer swapping in the application correctly:

  • If swapping is never done, the display will stay black
  • If buffers are swapped too often, the display will flicker.

The application doesn't need to swap its render targets in case:

  • the render target is set to swap automatically. For this, set the render target property "SetAutoSwapEnabled" to true.
  • the proper camera is set to swap automatically. For this, set the camera property "SwapEnabled" to true.

If you use more than one camera on a render target, take care to enable swapping only for the last camera (highest sequence number).


2D Render Loop 
2D Render Calls

2D Render Call interfaces are provided by Candera::Renderer2D.

This is the 2D specific render call equivalent to 3D.

    Renderer2D::RenderAllCameras();
Render Buffer Swapping

For 2D, render buffer swapping is analogous to 3D: The default value of 2D RenderTarget property "AutoSwapEnabled" and 2D Camera property "SwapEnabled" is false.

So take care to

  • either swap render target
  • or swap one of the cameras used
  • or trigger buffer swapping on the render target 'by hand'.


Tutorial for Application Life Cycle

Finalization

Description

This tutorial explains how to shut down a 2D or 3D application safely.

Releasing Scenes

First all 3D or 2D scenes must be released.

  • 3D Scenes:

    contentLoader->ReleaseAllSceneContexts();
    m_sceneContexts.Clear();

  • 2D Scenes: The same procedure as for 3D Scenes, whereas the Candera::ContentLoader interface for releasing 2D scene contexts is different:

    contentLoader->ReleaseAllScene2DContexts();
    m_sceneContexts.Clear();

Don't forget to shutdown the TextEngine, if there is one in use: Candera::TextRendering::FontEngine::Instance().Shutdown();

Releasing AnimationPlayer

Next, all used Candera::AnimationPlayer and Candera::AnimationPlayerGroup instances have to be removed from the Candera::AnimationTimeDispatcher and freed.

Unloading RenderTargets and Displays

All involved render targets / layers need to be unloaded from video memory:

    if (m_isRenderTargetsUploaded) {
        for (Int index = FeatStd::Internal::NumericConversion<Int>(m_gdus.Size()) - 1; index >= 0; --index) {
            GraphicDeviceUnit* gdu = m_gdus[index];
            if (gdu != 0) {
                if (0 != gdu->ToRenderTarget3D()) {
                    static_cast<void>(gdu->ToRenderTarget3D()->Activate());
                }
                gdu->Unload();
            }
            m_gdus[index] = 0;
        }
    }
    m_gdus.Clear();

Destory Displays in use:

    FEATSTD_LOG_INFO("- Destroy displays ...");
    Vector<Display*>::Iterator it;
    for (it = m_displays.Begin(); it != m_displays.End(); ++it) {
        Display* display = *it;
        if (display != 0) {
            display->Unload();
            DevicePackageInterface::DestroyDisplay(display);
        }
    }
    FEATSTD_LOG_INFO("- done");
    m_displays.Clear();
Finalizing AssetProvider

Finally, Candera::AssetProvider and Candera::ContentLoader shall be finalized:

    m_assetProvider.Finalize();
    m_contentLoader = 0;
Destructing Other Resources

Don't forget to destruct any other resources in the application destructor.

Tutorial for Application Life Cycle

Asset Library Verification (Optional)

Description

The optional Asset Library Verification feature allows asset version checks and provides means to verify the compatibility between an asset library file and an application.

Asset Version Information 

The asset file contains the following data to support asset verification:

  • Asset Version (Int32): Describes the version of the asset file format. This version must match the version of the Candera AssetLoader to be able to load the asset file.
  • File Size (Int32): Size of the file in bytes.
  • File Timestamp (Int64): Contains the creation time of the asset.
  • Widget Hash (UInt32): Hash value generated from the available widgets and their properties.
  • Widget Set Version (Int32): Version of the Widget Set.
  • Candera Version (Int32[4]): Candera version used within SceneComposer.
  • Solution GUID (Char[16]): Unique ID of the Solution.
  • Custom ID (Int32): Custom Id that can be set by the user upon asset generation.
  • SceneComposer Version (Int32[4]): SceneComposer version used to generate the asset file.
Feature Comparison

Without the optional asset verification, only a limited set of information is available. The table below gives a comparison of asset information available by default in Candera, and asset information available with enabled asset verification feature.


Candera Default Asset Verification Enabled
Asset Version X
File Size X
Time Stamp
X
Widget Hash
X
Widget Set Version
X
Candera Version
X
Solution GUID
X
Custom ID
X
SceneComposer Version
X
Export of Asset Version Info 
Automatic / User Defined Values

Most of the asset information is automatically generated by SceneComposer during asset export, but some can be controlled directly. The following table shows which properties can be set manually.


User Defined SceneComposer
Asset Version
X
File Size
X
Time Stamp
X
Widget Hash
X
Widget Set Version X
Candera Version
X
Solution GUID
X
Custom ID X
Scenecomposer Version
X


Set the WidgetSet Version Number

To set the user-defined WidgetSet version number the new macro CdaVersion can be used in the WidgetSet definition.

CdaWidgetSet(DemoWidgetSet)
    CdaDescription("Widget Set Demo")
    CdaVersion(1) // Sets the Widget-Set version
    CdaWidgets()
    
    CdaWidget(DemoWidget1)
    CdaWidget(DemoWidget2)
    
    CdaWidgetsEnd()
CdaWidgetSetEnd()
Set the Custom-Id

The Custom-Id can be set inside of the asset creation dialog of SceneComposer.

drawing-4-1676877218.png
Asset Verification at Runtime 
Read Asset Version Data from Asset

The following code example will show how to extract the asset version information from an Asset Repository.

    AssetData::AssetVersionInfo versionInfo;
    m_assetDescriptor->GetVersionInfo(versionInfo);

The information is now available in the versionInfo structure.

Get Runtime Version Info from Application

To compare asset version info from the loaded asset with the runtime values from the application, use following interfaces:

  • Get Candera Version Number
  • Get the Supported Asset Version
  • WidgetSet verification
Get Candera Version Number

The version number of the currently used Candera can be found in the CanderaVersionGen.h file. This file is generated automatically by the build system and contains defines similar to this, describing the exact version of Candera.

#define CANDERA_VERSION_MAJOR 3
#define CANDERA_VERSION_MINOR 12
#define CANDERA_VERSION_PATCH 0
#define CANDERA_VERSION_TWEAK 0

And can be used for example like this:

    AssetData::AssetVersionInfo versionInfo;
    assetDescriptor->GetVersionInfo(versionInfo);
    FEATSTD_LOG_INFO("Current Candera version = %d.%d.%d.%d, asset generated with version =  %d.%d.%d.%d\n",
        CANDERA_VERSION_MAJOR, CANDERA_VERSION_MINOR, CANDERA_VERSION_PATCH, CANDERA_VERSION_TWEAK,
        versionInfo.m_canderaVersion[0],versionInfo.m_canderaVersion[1],versionInfo.m_canderaVersion[2],versionInfo.m_canderaVersion[3]);
Get the Supported Asset Version

The number of the asset file format that is supported by the asset loader is located in the AssetValidation.h file and can be accessed like this:

    if (versionInfo.m_fileVersion != Candera::AssetValidation::CURRENT_VERSION) {
        FEATSTD_LOG_ERROR("Asset version (%d) does not match supported asset file version (%d)\n", versionInfo.m_fileVersion, Candera::AssetValidation::CURRENT_VERSION);
    }
WidgetSet verification

To retrieve the currently used WidgetSet version number and WidgetSet hash value, the following code can be used.

    if (GetWidgetSet() != 0) {
        FEATSTD_LOG_INFO("Widget set version: %d, asset generated with version: %d",
            GetWidgetSet()->GetVersion(), versionInfo.m_widgetSetVersion);
        FEATSTD_LOG_INFO("Widget set hash: %d, asset generated with hash: %d",
            GetWidgetSet()->GetHash(), versionInfo.m_widgetHash);
    }

The GetWidgetSet-Function is part of the WidgetMetaInfo.h file.

Candera::AssetValidation 

Another option of asset verification is to use Candera::AssetValidation during asset loading. In this case, specific asset validation flags will be evaluated during asset loading

Validation Attributes

Refer to Candera::AssetValidationAttributes for all attributes which can be used for asset validation during asset loading.

Validation Levels

Refer to Candera::AssetValidationLevels for the actions, which shall be taken after asset validation.

Asset Validation during Asset Loading

Candera::DefaultAssetProvider::Initialize method allows to specify asset validation flags as a parameter:

    if (!m_assetProvider->Initialize(&m_assetConfig, m_validationFlags)) {
        m_assetConfig.ClearRepositoryList();
        return false;
    }

Tutorial for Application Life Cycle

Dynamic Properties

How to attach a Dynamic Property to CanderaObject

An application may want to attach any kind of application specific data to an arbitrary CanderaObject. This is easily possible as CanderaObject derives from DynamicPropertyHost, which can store dynamic properties. Different to conventional object properties, a dynamic property can be attached to an object, without being declared in the object itself. Further, a dynamic property does not allocate memory unless its value is set explicitly and differs from its default value.

Find below a simple example how to attach an application specific object, acting as dynamic property, to an object that derives from DynamicPropertyHost, like e.g. CanderaObject, Node, Mesh, Appearance, Texture, only to name a few.

  • If possible the dynamic property definition (see class LayouterDynamicProperties) should be contained in the source file and not in the header file. Otherwise, the code size may get unnecessarily increased due to the inability of Gcc and Multi linker to get rid of duplicate template instantiation in the object files.
  • Dynamic properties
    • can be set (see SetSize and SetLayoutDirection)
    • can be retrieved (see GetSize and GetLayoutDirection)
    • can be checked if they are set (see IsSizeSet and IsLayoutDirectionSet)
    • can be cleared (see ClearLayoutDirection)
    • have default values: C++ default initialization for simple types (see LayoutDirection) or specific values for complex types (see Size)
    • can have an optional change notification (see OnSizeChanged and OnLayoutDirectionChanged)
  • A convenience interface should be provided to set, get, clear and check the dynamic property (see class Layouter). The dynamic property is only accessed via that interface.
  • The DynamicPropertyHost (see class Layouter) has to provide the ParentProvider method for inherited dynamic properties.
    • In the Layouter sample it is guaranteed by the Layouter convenience interface that only CanderaObject instances are supported. Hence, a static_cast to CanderaObject is safe.
  • Layouter.h
    class Layouter : public CanderaObject
    {
        …
        static void SetSize(CanderaObject& node, const Vector2& size);
        static const Vector2& GetSize(const CanderaObject& node);
        static bool IsSizeSet(const CanderaObject &node);
        …
        static void SetLayoutDirection(CanderaObject& node, LayoutAlignment::LayoutDirection::Enum direction);
        static LayoutAlignment::LayoutDirection::Enum GetLayoutDirection(const CanderaObject& node);
        static bool IsLayoutDirectionSet(const CanderaObject &node);
        static void ClearLayoutDirection(CanderaObject& node);
        …
        static const Candera::DynamicProperties::DynamicPropertyHost* ParentProvider(const Candera::DynamicProperties::DynamicPropertyHost* host);
        …
        static const Vector2& SizeDefault();
        …
        static void OnSizeChanged(DynamicPropertyHost* obj, const DynamicProperties::ValueChangedArgs<Vector2>& /*args*/) { InvalidateLayout(obj); }
        …
        static void OnLayoutDirectionChanged(DynamicPropertyHost* obj, const DynamicProperties::ValueChangedArgs<LayoutAlignment::LayoutDirection::Enum>& /*args*/) { InvalidateLayout(obj); }
        …
    };
    

Tutorial for Application Life Cycle

Touch Support

Touch events are derived from generic events and are equally easy to implement.
For an example of use have a look at the ToggleButtonWidget and it's base class CgiEventWidget.

  • Set EventMask. The EventMask determines which event types will be sent.
            const UInt cTbTouchEventExit = CgiEventWidget::TouchEventExit;
            const UInt cTbTouchEventUp = CgiEventWidget::TouchEventUp;
            const UInt cTbTouchEventDown = CgiEventWidget::TouchEventDown;
    
    
            Base::SetEventMask(cTbTouchEventExit | cTbTouchEventUp | cTbTouchEventDown);
    

  • Override listener function.
            protected:
    
                virtual void OnTouchEvent(TouchEvent touchEvent, Candera::Node* node, const CgiApplication::MouseEvent& event);
    
    

  • Define your actions inside the listener function OnTouchEvent.
            switch(touchEvent) {
                case TouchEventDown:
                    m_focus = true;
                    SetOn(!IsOn());
                    break;
                case TouchEventUp:
                    if (m_focus) {
                        SendUiEvent(TouchEventClick, 0);
                    }
                    break;
                case TouchEventExit:
                    m_focus = false;
                    m_down = !m_down;
                    SwitchTexture();
                    break;
                default:
                    break;
            }
    

Tutorial for Application Life Cycle

Loading of Shaped / Compressed Assets

Loading Shaped Assets

Shaped assets are the result of partitioning an asset into several smaller ones, each containing a part of the original asset, based on a predefined set of rules. Please refer to Shape Asset Library chapter for information on how to create them.

The routine for loading shaped assets is similar to the one presented in Loading an Asset section. In this case, Candera::AssetConfig might be used because it allows the definition of multiple instances of Candera::AssetRepository.

The application should derive from the abstract Candera::AssetConfig in order to support specific Candera::AssetRepository instances. The shaped asset repositories need be added, one by one, to Candera::AssetConfig before initializing Candera::AssetProvider.

Loading shaped assets during runtime

There is also the possibility of adding or removing asset repositories even after the initialization of the asset provider. In this case, the application should derive from Candera::DynamicAssetConfig, which allows the asset configurations to be changed during runtime.

To add a repository during runtime, please use Candera::DynamicAssetConfig::AddRepository. In order to remove one, please use Candera::DynamicAssetConfig::RemoveRepository.

Candera::AssetConfigListener::OnAssetRepositoryAdded should be called in the derived classes of Candera::AssetConfig after a new asset has been added, to notify all listeners and load the asset repository. Call Candera::AssetConfigListener::OnAssetRepositoryRemoved when removing an asset repository.

When dynamically adding of partitioned assets, the newly added assets should belong to the same original asset that the previous loaded are belonging to.


Using Asset Decompression

Candera AssetLoader is able to support and manage asset data compressed by CGI Studio Asset Shaper Tool. Please refer to Compression section for more information related to data compression.

Using of compressed assets inside the application is be possible only if CANDERA_ASSET_DECOMPRESSION_ENABLED variable is set to true during the build process of Candera. After enabling this feature, CANDERA_ASSET_DECOMPRESSION_CHUNK_SIZE will be enabled for specifying the chunk buffer size for decompression.

A chunk represents the buffer size that is used for pulling data from the asset to perform the decompression operation, one chunk at a time. Using a smaller buffer size would be time consuming, so a larger buffer size would be recommended in this case, if enough memory is available.

CANDERA_ASSET_DECOMPRESSION_CHUNK_SIZE is relevant only for Candera::FileAssetRepository.



Tutorial for 3D Scene Graph and Nodes

Tutorial for 3D Scene Graph and Nodes

3D Scenes and Nodes

Scenes and Nodes

Candera::Node is an abstract base class for all scene graph nodes.

drawing-4-1676880912.png

Each node defines a local coordinate system relative to the coordinate system of the parent node. The functionality to define the local coordinate system is derived from Candera::Transformable and consists of following components:

  • position,
  • rotation,
  • scale,
  • and a generic matrix called transform matrix.

If the node is transformed from the local coordinate system to the world's coordinate system, then the node's local transformation is multiplied with all its parent's transformations. A node can also store a set of nodes as its children but a node can only have one parent at a time. Cycles are prohibited. The nodes alpha value is multiplied with the alpha value of its descendent nodes.

Candera::Scene is the top level scene node which cannot be part of any other node.



Tutorial for 3D Scene Graph and Nodes

3D Node Transformations

3D Node Manipulation Example

To illustrate basic node transformation operations, the NodeManipulationWidget_3D and the DisplayPositionWidget3D part of the Tutorial Widgets can be used. An usage example for NodeManipulationWidget_3D widget is presented in NodeManipulationSolution_3D solution provided in the content folder of cgi_studio_player.

Please consider:

  • The widget property Node is derived from a base class and provides the node associated to the widget (e.g. the root node for content managed by the widget).
  • Only if the widget is enabled (this property is also derived from a base class), the setter methods of the widget properties will take effect.
  • If a widget property setter changes a node property of the associated node, the visual effect can be seen, but note that the static node properties maintained by SceneComposer are not changed accordingly!
  • Widget dynamics are never reflected to static scene structure. It is recommended to use SceneComposer only for property configuration and dynamics only in the Player.

Translations

Once the NodeManipulationWidget_3D is linked to a node from the static scene tree and enabled, the position coordinates can be set in the Player for this property to change the position.

        // Set node position
        node->SetPosition(m_position);
        // end Set node position

Use TranslateX property to move the node on the x axis with a delta value only. Candera::Transformable::Translate adds the given Vector to the actual position .

       // Translate node on x axis
        node->Translate(Vector3(m_translateX, 0.0F, 0.0F));
        // end Translate node on x axis

Rotations

Similar to translation, rotation can also be applied via Candera::Transformable::SetRotation() or Candera::Transformable::Rotate().

        // Set node rotation
        node->SetRotation(m_rotation);
        // end Set node rotation

Scaling

Again there is also a Candera::Transformable::SetScale and Candera::Transformable::Scale method.

        // set node scale
        node->SetScale(m_scale);
        // end node scale

Pivot Point

It is possible to change your pivot point with the Candera::Transformable::TranslatePivotPoint (or Candera::Transformable::SetPivotPoint) method. The pivot point can be used to define e.g. the rotation point that the node should rotate around. It affects the scaling too.

How to get the screen space coordinates of a node

With the DisplayPositionWidget3D you can retrieve the screen coordinates of an associated node. Please consider that a node and a camera have to be selected in the widget. In the solution the widget is disabled by default. To see the output of the node's display position, enabling the widget and log filter (via CgiAppLog -> Info) in SceneComposer is mandatory. To get the screen space coordinates of an associated node you need the function Candera::Math3D::TransformPointToScreen, which transforms a point in world space into a point in screen space.

  • First you need the world position of your node. With Node::GetWorldPosition you get the screen coordinates as output.
            Vector3 worldPosition = node->GetWorldPosition();
    
  • With the world position of your node and with your camera you can transform the point to screen space and you retrieve the display position as a Candera::Vector2.
            Vector2 displayPos;
            static_cast<void>(Math3D::TransformPointToScreen(worldPosition, camera, displayPos));
    
  • displayPos now contains the X and Y screen space coordinates of the selected node.

Tutorial for 3D Scene Graph and Nodes

3D Node Appearance

Description

Candera::Appearance groups following render attributes:

Render attributes define the distinctive visualization of a geometry like Candera::Mesh, Candera::Billboard, and Candera::PointSprite. Further, render attributes can be shared across multiple objects, which conserve memory and enable sharing of appearance characteristics.

A Candera::Appearance object is mandatory for any object in order to get rendered. Per default no Appearance attributes are attached.

If the Appearance is activated, all render attributes that are set become activated. If no RenderMode is defined (null), then the default RenderMode is used instead.

Appearance: Material

Material Attributes

Candera::Material describes the color attributes of an object's surface and is primarily used for lighting computations. If no material is set, lighting calculations in associated shaders can not be applied.

The color attributes are defined as follows:

Ambient RGB color that interacts with the ambient attribute of light.
Emissive RGB color that defines the self-lighting of the material. This color is visible, even when Material is unlighted.
The emissive color attribute does neither interact with any type of light source nor with other 3D objects.
Diffuse RGBA color that interacts with the diffuse attribute of light. The alpha value of the diffuse color, defines
the alpha factor of the entire Material, supposed that alpha blending is enabled (For details see Candera::RenderMode).
Specular RGB color that interacts with the specular attribute of light.

Furthermore with Specular Power the sharpness of a specular highlight, if lit by specular light, can be defined.

Create a new Material
    m_material = Material::Create();
Set Colors

This sample code shows how to change the ambient color, the code for changing the diffuse color, the emissive color or the specular color looks similarly.

    // Set ambient color
    if (node->GetAppearance() != 0 && node->GetAppearance()->GetMaterial() != 0){
        node->GetAppearance()->GetMaterial()->SetAmbient(m_color);
    }
    // end Set ambient color

Appearance: Texture 

Description

Candera::Texture encapsulates a Candera::TextureImage and a set of attributes specifying how it is applied to a vertex's texture coordinate. Candera implements a sharing mechanism based on TextureImages. These hold the actual VRAM handle. Multiple textures can share one TextureImage object. The TextureImage manages its upload to the VRAM.

Attention:

  • The Texture is only useable with an associated TextureImage.
  • Do not share TextureImages, if TextureImage modification shall not be shared across Textures.
  • In order to support MipMapping or repeated wrapping, the bitmap's width and height must be a power of 2 (n^2), like e.g. 2, 4, 8, 16 ,32, etc. However, height and width can be different, e.g.: 256x128.
Texture Filtering
  • Minification and magnification:

Following texture filters can be used to improve visual quality of Textures: Magnification to upscale, Minification to downscale.

Nearest Value of the texel that is nearest to the center of the pixel being textured.
Linear The weighted average of the four texels that are closest to the center of the pixel being textured.


  • Mipmapping: MipMapping can be used to avoid texture aliasing effects.
None MipMapping disabled
Nearest MipMapping uses nearest mip map level
Linear MipMapping uses bilinear mipmap interpolation of the two nearest mip map levels


  • WrapMode:

It specifies how the texture is wrapped:

Repeat Repeat the texture
ClampToEdge Clamp fetches to the edge of the texture
Linear MipMapping uses bilinear mipmap interpolation of the two nearest mip map levels


  • MaxAnisotropy:

It specifies the maximum degree of anisotropy to account for in texture filtering for the Texture object. Anisotropic filtering improves the quality of the textures when they are not uniformly scaled because, for example, the textured triangle is not exactly facing the camera. The value of maxAnisotropy must be greater or equal to 1.0f (isotropy) and is limited by the max detail of anisotropy supported by hardware, which can be retrieved with the function Candera::RenderDevice::GetMaxAnisotropySupportedByDevice.

Changing Texture Image And Setting Filters
    m_textureImage = BitmapTextureImage::Create();
    m_textureImage->SetName("LabelTextureImage");
    static_cast<void>(m_textureImage->SetBitmap(m_bitmap));
    if (m_textureImage == 0) {
        FEATSTD_DEBUG_ASSERT(false);
        return; // memory is freed in destructor/UpdateBitmap()
    }


    m_texture = Texture::Create();
    m_texture->SetName("LabelTexture");
    m_texture->SetTextureImage(m_textureImage);
    m_texture->SetWrapModeU(Texture::ClampToEdge);
    m_texture->SetWrapModeV(Texture::ClampToEdge);
    m_texture->SetMinificationFilter(Texture::MinMagLinear);
    m_texture->SetMagnificationFilter(Texture::MinMagLinear);

Appearance: Render Mode 

Description

Candera::RenderMode is an Appearance component that encapsulates polygon-level and per-fragment compositing render attributes. If an object's Appearance has set RenderMode to null, then the default RenderMode is used instead. For details how to set the default render mode, see Candera::Renderer::SetDefaultRenderMode.

If a Camera has a RenderMode attached [camera->GetApperance()->SetRenderMode(...)], then the Camera's RenderMode overrules the DefaultRenderMode during its render pass. Thus, all Nodes rendered by the Camera that do not have their own RenderMode set, use the RenderMode applied to the Camera.

If a RenderMode has set an inheritance bit for a certain render attribute, then the property of the base render mode is used, this is either the Camera's RenderMode if set or the DefaultRenderMode otherwise.

        bool m_isColorWriteRedEnabled;       // Default value: true
        bool m_isColorWriteGreenEnabled;     // Default value: true
        bool m_isColorWriteBlueEnabled;      // Default value: true
        bool m_isColorWriteAlphaEnabled;     // Default value: true
        bool m_isDepthWriteEnabled;          // Default value: true
        bool m_isDepthTestEnabled;           // Default value: true
        bool m_isStencilTestEnabled;         // Default value: false
        bool m_isBlendingEnabled;            // Default value: false


Set a rendermode:

    SharedPointer<RenderMode> rm = RenderMode::Create();
    rm->SetBlendingEnabled(true);
    rm->SetBlendMode(RenderMode::SourceAlpha, RenderMode::InverseSourceAlpha, RenderMode::Add);
    rm->SetDepthTestEnabled(false);
    rm->SetDepthWriteEnabled(false);
    appearance->SetRenderMode(rm);

Render Mode Attributes
  • blending

The next chapter Color Blending describes possible blending operations in detail.

  • culling

Culling determines which side of a polygon is removed before rasterisation.

FrontFaceCulling Front of the polygon is removed
BackFaceCulling Back of the polygon is removed
NoCulling Front and back of the polygon are rendered
  • winding

Winding defines the front face of a polygon. A polygon side is the front-face if its screen-space vertices are in the same order as the winding specifies.

ClockWise Clockwise ordered vertices define the front of the polygon.
CounterClockWise Counterclockwise ordered vertices define the front of the polygon.

Candera::RenderMode::ComparisonFunction can be used with depth-, stencil-, or sampler state operations. It specifies how the source (new) data is compared against the destination (existing) data before passing the comparison operations (storing the data).

CompareNever Never pass the comparison.
CompareLess If source data is less than destination data, the comparison passes.
CompareEqual If source data is equal than destination data, the comparison passes.
CompareLessEqual If source data is less than or equal to destination data, the comparison passes.
CompareGreater If source data is greater than destination data, the comparison passes.
CompareNotEqual If source data is not equal to destination data, the comparison passes.
CompareGreaterEqual If source data is greater than or equal to destination data, the comparison passes.
CompareAlways Always pass the comparison.
  • depth bias

Depth bias that can be applied to co-planar primitives to reduce z-fighting, according to following function: Depth bias = (max * scaleFactor) + (r * units); where max is the maximum depth slope of the triangle being rendered and r is an implementation-defined constant that is guaranteed to produce the smallest resolvable offset.

olygons that are coplanar can be made to appear not coplanar by adding a z-bias to each one. This is a technique commonly used to ensure that shadows, decals, or hidden-line images on coplanar surfaces are displayed properly. The depth bias is added before the depth test is performed but does not influence the original depth value written into depth buffer.

Via Candera::RenderMode::SetDepthBias the depth bias can be set and Candera::RenderMode::SetDepthTestEnabled and Candera::RenderMode::SetDepthWriteEnabled enables it.

  • stencil buffer

StencilPlane specifies the face associated with the provided stencil function, which are FrontFace, BackFace and FrontAndBackFace. Front face stencil affects non-polygons and front-facing polygons whereas back face stencil affects back-facing polygons only.
StencilOperation defines the stencil-buffer operation.

SetToZero Set the stencil-buffer entry to zero.
Keep Do not update the entry in the stencil buffer.
Replace Replace the stencil-buffer entry with the reference value.
Increment Increment the stencil-buffer entry, clamping to 2^n where n is the number of bits in the stencil buffer.
Decrement Decrement the stencil-buffer entry, clamping to zero.
Invert Invert the bits in the stencil-buffer entry.
IncrementWrap Increment the stencil-buffer entry, wrapping to zero if the new value exceeds the maximum value
DecrementWrap Decrement the stencil-buffer entry, wrapping to the maximum value if the new value is less than zero.

Appearance: Render Mode - Color Blending 

Description

When Blending is enabled, the output from the fragment shader is blended with the current contents of the frame buffer, rather than merely overwriting it. Blending is mostly used to make objects appear transparent, but can also produce various other effects. (eg. anti-aliasing, DOF, or multi-pass rendering.) There are multiple ways of using blending, but generally, the procedure is as follows:

  • Set alpha value of a node
  • Set alpha value of a material color
  • Set alpha value of the material (this changes the alpha value of the materials diffuse color)
  • Enable blending via RenderMode and use the alpha value of the texture in combination with Candera::RenderMode::BlendMode
Blend Operations
        enum BlendOperation
        {
            Add = 0,               
            Subtract = 1,          
            ReverseSubtract = 2,   
            Min = 3,               
            Max = 4                
        };
Blend Factor
        enum BlendFactor
        {
            Zero = 0,                   
            One  = 1,                   
            SourceColor = 2,            
            InverseSourceColor = 3,     
            SourceAlpha = 4,            
            InverseSourceAlpha = 5,     
            DestColor = 6,              
            InverseDestColor = 7,       
            DestAlpha = 8,              
            InverseDestAlpha = 9,       
            ConstantColor = 10,         
            InverseConstantColor = 11,  
            ConstantAlpha = 12,         
            InverseConstantAlpha = 13,  
            SourceAlphaSaturate = 14    
        };


BlendMode combines the source and destination blend factors with the operation used in blending equation.

  • RGB blending equation: final_RGB = source_RGB * sourceRGBFactor (+operationRGB+) destination_RGB * destRGBFactor.
  • Alpha blending equation: final_alpha = source_alpha * sourceAlphaFactor (+operationAlpha+) destination_alpha * destAlphaFactor.

For the Blending Options ConstantColor, InverseConstantColor, ConstantAlpha, or InverseConstantAlpha a blending color can be set.

Set blending:

    SharedPointer<RenderMode> rm = RenderMode::Create();
    rm->SetBlendingEnabled(true);
    rm->SetBlendMode(RenderMode::SourceAlpha, RenderMode::InverseSourceAlpha, RenderMode::Add);
    rm->SetDepthTestEnabled(false);
    rm->SetDepthWriteEnabled(false);
    appearance->SetRenderMode(rm);

Appearance: Shader 

Description

Candera::Shader is an Appearance component representing a graphical processing unit (GPU) program, thus a vertex and fragment shader pair.

  • The Shader program can be parametrized with uniforms and attributes. It's guaranteed by the shader that the program is only created once in VRAM.
  • Shader is a device object that counts its uploads to VRAM automatically. Thus, a shader is only uploaded if it has not been uploaded before.
  • Further, a shader is unloaded from VRAM, if the upload count is decreased to zero. Take care, that upload is invoked as many times as unload.

Candera and SceneComposer offer reference shaders of both vertex and fragment shaders.
For details refer to section Shader Usage.


In SceneComposer via the appearance of a node its ShaderProgram can be chanced easily. Further it is possible to change shaders, the default shader configuration, to create new shaders and combine two of them to a new ShaderProgram.

The Default Shader Configuration in SceneComposer
Node Vertex Shader Fragment Shader
Billboard RefTrans RefTex
Mesh without texture RefTransLight1 RefColor
Mesh with texture RefTransLight1 RefColorTex
MorphingMesh without texture RefTransLight1Morph RefColor
MorphingMesh with texture RefTransLight1Morph RefColorTex
PointSprite RefTransPointSprite RefPointSprite
SkyBox RefTransCubeMap RefCubeMapTex

Appearance: Shader Parameter Setters 

Description

The Candera::ShaderParamSetter class is used to pass uniform parameters to a Shader program. The most relevant methods for this class are Candera::ShaderParamSetter::SetUniform which creates a new uniform (see example below):

    shaderParamSetter->SetUniform(m_uniformName, Shader::Float, &m_uniformValue);

and the Candera::ShaderParamSetter::Activate which activates all the uniforms previously set by the SetUniform method in the Shader.

In a class derived from ShaderParamSetter, the methods which calculates the auto-uniforms (e.g. ModelViewProjectionMatrix4), should be invoked in the Activate method.

Generic Shader Param Setters

Candera::GenericShaderParamSetter is a class derived from Candera::ShaderParamSetter which bundles uniform shader parameters that are calculated by Candera. The class interface offers, for each of these parameters, a specialized method which enables / disables the parameter.

    shaderParamSetter->SetModelMatrix4Enabled(true);
    shaderParamSetter->SetModelMatrix3Enabled(true);
    shaderParamSetter->SetNormalModelMatrix3Enabled(true);
    shaderParamSetter->SetModelViewMatrix4Enabled(true);
    shaderParamSetter->SetModelViewMatrix3Enabled(true);
    shaderParamSetter->SetNormalModelViewMatrix3Enabled(true);
    shaderParamSetter->SetModelViewProjectionMatrix4Enabled(true);
    shaderParamSetter->SetCameraLookAtVectorEnabled(true);
    shaderParamSetter->SetCameraPositionEnabled(true);
    shaderParamSetter->SetLightActivationEnabled(true);
    shaderParamSetter->SetMaterialActivationEnabled(true);
    shaderParamSetter->SetTextureActivationEnabled(true);
    shaderParamSetter->SetLightsCoordinateSpace(Light::World);

If a parameter is enabled then it will be calculated and passed to the shader, if disabled the parameter is neither calculated nor passed.

Default Parameters

Some often needed shader parameters are enabled by default:

  • ModelViewProjectionMatrix4
  • LightActivation
  • MaterialActivation
  • TextureActivation

If needed, for performance reasons, those parameters might be disabled.

Generic Param Setters in SceneComposer

In SceneComposer the generic parameter setters are accessible through the graphic interface in two ways:

  • as customizable uniform setter, from the Toolbox panel in the Attachments bar. Once added in the Appearance node list this uniform setter should be configured in the Properties panel by enabling the desired uniforms.
  • as a predefined uniform setter, from the Templates panel in the UniformSetters list.

SceneComposer provides the following predefined uniform setters:

  • TransAnisotropicLightShaderParamSetter
  • TransLightBumpMapShaderParamSetter
  • TransLightShaderParamSetter
  • TransLightSphereMapShaderParamSetter
  • TransShaderParamSetter

The table below shows some examples of shaders that can be set by each of the predefined shader parameter setters:

Shader Param Setter Shader Program
TransAnisotropicLightShaderParamSetter RefTransAnisotropicLight1_RefAnisotropicLight1SpecularTex
TransLightBumpMapShaderParamSetter RefTransLight1BumpMap_RefLight1BumpMap
TransShaderParamSetter RefTransLight1_RefColor
TransLightSphereMapShaderParamSetter RefTransLight1SphereMap_RefColorTex
TransLightShaderParamSetter RefTransWorldLight1_RefColor

Shader parameter setters can be applied to a 3D node by selecting it in the panel and then drag-and-drop it in the Appearance list of the node.

Customized Shader Parameter Setter 

Customized Shader Parameter Setter - Example

If for any reason the standard shader parameter setters are not sufficient, it is possible to create a customized shader parameter setter. However, a custom shader parameter setter cannot be configured in SceneComposer, it must be associated to the desired node by coding.

The following example explains how to create and use a customized shader parameter setter in a widget. Refer to the

  • ShaderParamSetterWidget in combination with the
  • ShaderParamSetterSolution,
    both present in the cgi_studio_player folder.

The widget ShaderParamSetterWidget allows to set the u_Material.emissive and the u_MVPMatrix uniform parameters for any node using an appropriate shader program (for example, the nodes in the ShaderParamSetterSolution are using the RefTransLight1_RefColor shader program).

Simple Shader Parameter Setter for u_Material.emissive & u_MVPMatrix Uniforms

In order to pass the u_Material.emissive and u_MVPMatrix uniforms to a shader program, the widget uses an instance of the class SimpleShaderParameterSetter:

    // Create uniform setter instance
    // m_shaderParamSetter is of type SharedPointer<SimpleShaderParamSetter> ;
    m_shaderParamSetter = SimpleShaderParamSetter::Create();
    // end Create uniform setter instance


The only method that needs to be overridden in the SimpleShaderParameterSetter is Candera::ShaderParamSetter::Activate.

The example ShaderParamSetterWidget uses this SimpleShaderParameterSetter by attaching the created instance to the node, which is associated to the widget:

    // Attach uniform setter
    if ( (GetNode() != 0) && (GetNode()->GetAppearance() != 0) ) {
        GetNode()->GetAppearance()->SetShaderParamSetter(m_shaderParamSetter);
    }
    // end Attach uniform setter


Whenever the widget property "Uniform Color" is modified, the widget sets the given color value (m_color) as u_Material.emissive uniform to the SimpleShaderParameterSetter:

    // Set u_Material.emissive uniform
    FEATSTD_UNUSED(m_shaderParamSetter->SetUniform(ShaderParamNames::GetUniformName(ShaderParamNames::MaterialEmissive), Shader::FloatVec4, reinterpret_cast<Float*>(&m_shaderColor[0]), 1));
    // end Set u_Material.emissive uniform


The node vertices are mapped from the model space to the screen space using the Model-View-Projection Matrix (u_MVPMatrix). SimpleShaderParameterSetter calculates the u_MVPMatrix using the node and camera attributes and then passes it to the shader:

    // Set u_MVPMatrix uniform
    const Matrix4 mvpMatrix = node.GetWorldTransform() * RenderDevice::GetActiveCamera()->GetViewProjectionMatrix();
    return shader.SetUniform( mvpMatrix.GetData(), ShaderParamNames::GetUniformName(ShaderParamNames::ModelViewProjectionMatrix4), Shader::FloatMat4 );
    // end Set u_MVPMatrix uniform


Modifications made using the SetUniform method become effective each time the camera renders the scene because the method Activate is invoked internally by the node.

Multipass Appearance 

Description

A Candera::MultiPassAppearance is a dedicated Appearance which enables a node to be rendered multiple times with different appearance settings which are blended in order to achieve a certain visual result like e.g. fur or blurred rendering.

The sequence of render passes is created by chaining instances of MultiPassAppearance using the SetNextPass method:

    multiPassAppearance1->SetNextPass(multiPassAppearance2);
    multiPassAppearance2->SetNextPass(multiPassAppearance3);


Tutorial for 3D Scene Graph and Nodes

3D Scene Graph Dynamics

Description

Usually all required nodes are already specified and preconfigured via SceneComposer, so in most cases it won't be necessary to make any adaptations within the scene graph loaded from an asset at runtime.

However, some use cases might require such adaptations. This tutorial covers the most common means to manipulate the scene graph structure dynamically.

Example Solution

  • SceneGraphDynamicsSolution_3D in folder cgi_studio_player/content/Tutorials/03_SceneGraphAndNodes


Adding and Removing 3D Nodes 

3D Scene Graph Dynamics Example

For creating and adding a new node, the following can be used:

  • SceneGraphDynamicsSolution_3D from folder cgi_studio_player/content/Tutorials/03_SceneGraphAndNodes
  • SceneGraphDynamicsWidget_3D (this is used in the example solution - refer to SceneGraphDynamicsWidget_3D)

As an example use case, a Candera::Billboard can be created, added and removed. Use the SceneGraphDynamicsWidget_3D properties described below:

  • AddBillboard: If AddBillboard is enabled a new Billboard is created and added to the widget node. If disabled, the Billboard will be removed.

        // Definition of WidgetProperty "AddBillboard"
        CdaProperty(AddBillboard, bool, GetAddBillboard, SetAddBillboard)
            CdaDescription("If AddBillboard is enabled a new Billboard is created and added to the widget Candera::Node. If disabled, the Billboard will be removed.")
        CdaPropertyEnd()
        // End Definition of WidgetProperty "AddBillboard"


Creating a new Billboard

The following code snippet generates a basic Candera::Billboard (size 30 x 30) with a Candera::Material and a matching Candera::Shader. The Shader has to be provided in the asset and is delivered through the AssetProvider by its name. Further, some translation is applied to the new Billboard.

        // Create and init a new Billboard

        SharedPointer<Appearance> myAppearance = Appearance::Create();
        SharedPointer<Material> myMaterial = Material::Create();
        myMaterial->SetEmissive(Color(0.5F, 0.0F, 0.0F, 1.0F));
        myAppearance->SetMaterial(myMaterial);
        myAppearance->SetShader(Base::GetAssetProvider()->GetShader(CgiAssetNames::RefTransLight1_RefColor));
        myAppearance->SetName("Generated Appearance");

        m_billboard = Billboard::Create(30.0F, 30.0F);
        m_billboard->SetAppearance(myAppearance);
        m_billboard->Translate(Vector3(30.0F, 0.0F, 0.0F));

        // end Create and init a new Billboard


Adding the new Billboard to the scene graph

Next the Billboard is uploaded to VRAM and appended to the node associated with the widget.

        // Add Billboard

        static_cast<void>(m_billboard->Upload());
        static_cast<void>(node->AddChild(m_billboard));

        // end Add Billboard
Removing and destructing the Billboard

To remove the Billboard simply from the scene graph, use Candera::Node::RemoveChild. If the Billboard will be used for further operations e.g. attaching it to an other node removing would be enough. Since in this example the Billboard isn't used further it should not only be removed but also be destructed completely (freeing the resources).

The following code snippet removes the previously generated billboard from its parents, unloads the Billboard from VRAM and frees with Candera::Node::Dispose the resources.

        // Remove previously generated billboard and destruct it

        Node* parent = m_billboard->GetParent();
        if (parent != 0){
            static_cast<void>(parent->RemoveChild(m_billboard));
            FEATSTD_LOG_ERROR("- Node %s removed from parent %s ...\n", m_billboard->GetName(), parent->GetName());
        }
        // Destruct node

        static_cast<void>(m_billboard->Unload());
        m_billboard->Dispose();
        m_billboard = 0;

        // end Destruct node

        // end Remove previously generated billboard and destruct it


As Candera::Node::Dispose internally calls the Candera::Node::RemoveChild on parent node following code would be enough for removing and destructing a node.

        // Destruct node

        static_cast<void>(m_billboard->Unload());
        m_billboard->Dispose();
        m_billboard = 0;

        // end Destruct node

In SceneComposer the newly added Node does not appear in the Scene Tree view because it is only added dynamically by the widget.
- The used Shader for the generated Billboard in the widget has to be included when generating an asset from the solution - ensure that "Always include in asset" flag is marked at the Shaders properties.
- The widget must control memory and VRAM management of its internal dynamic subtree autonomously.

Widget Interaction in the Player

First, the asset must be exported and loaded in the Player. Then, one of the two existing SceneGraphDynamicsWidget_3D widgets must be selected for the 3D scene. After, ChangeAppearance, CloneOperation and AddBillboard properties can be enabled/disabled (1/0) to obtain different results.

Changing Appearance and other 3D Node Attachments 

For changing the appearance of a node, the following property of the SceneGraphDynamicsWidget_3D can be used:

  • ChangeAppearance: If ChangeAppearance is enabled, the appearance of the widgets associated node will be changed to a new generated appearance. If disabled, the previous appearance will be restored.

        // Definition of WidgetProperty "ChangeAppearance"
        CdaProperty(ChangeAppearance, bool, GetChangeAppearance, SetChangeAppearance)
            CdaDescription("If ChangeAppearance is enabled, the appearance will be exchanged to a new generated appearance. If disabled, the previus appearance will be restored.")
        CdaPropertyEnd()
        // End Definition of WidgetProperty "ChangeAppearance"

Try the ChangeAppearance property at the SceneGraphDynamics_3D_Billboard widget in the example solution to change the Billboards appearance from a texture appearance to a material appearance.

Creating and changing an Appearance

The following code snippet generates a basic Candera::Appearance. A Candera::Material is created and its emissive color is set to a light green. The material and a proper shader is set to the Appearance. The shader has to be provided in the asset and is delivered through the AssetProvider by its name.

    // Create and set a new Appearance

    SharedPointer<Appearance> myAppearance = Appearance::Create();
    myAppearance->SetMaterial(Material::Create());
    myAppearance->GetMaterial()->SetEmissive(Color(0.0F, 0.5F, 0.0F, 1.0F));
    myAppearance->SetShader(Base::GetAssetProvider()->GetShader(CgiAssetNames::RefTransLight1_RefColor));
    myAppearance->SetName("Generated Appearance");

    // end Create and set new Appearance

In the next step the node's previous Appearance is saved (for restoring purpose) and the new generated appearance is set to the node associated with the widget.

    // Change Appearance

    m_previousAppearance = node->GetAppearance();
    node->SetAppearance(myAppearance);
    static_cast<void>(myAppearance->Upload());

    // end Change Appearance
Restoring / releasing an Appearance

Restore the previous Appearance and destruct the dynamically generated appearance.

        // Restoring appearance and destructing

        SharedPointer<Appearance> currentAppearance = node->GetAppearance();
        static_cast<void>(currentAppearance->Unload());
        currentAppearance.Release();
        node->SetAppearance(m_previousAppearance);

        // end Restoring appearance and destructing

In SceneComposer the changed Appearance does not appear in the Appearance panel because it is only changed dynamically by the widget.
- The used shader for the generated Billboard in the widget has to be included when generating an asset from the solution - ensure that "Always include in asset" flag is marked at the Shaders properties.
- The widget must control memory and VRAM management of dynamically created node attachments autonomously.

Cloning 3D Nodes 

For cloning a node, the following property of the SceneGraphDynamicsWidget_3D can be used:

  • NodeToClone: Select an arbitrary node in the scene graph, which shall be cloned and added to the widget node

        // Definition of WidgetProperty "NodeToClone"
        CdaProperty(NodeToClone, Candera::Node*, GetNodeToClone, SetNodeToClone)
            CdaDescription("Select a Candera::Node which shall be cloned and added to the widget Candera::Node. The Candera::Node must be a child of this widget's scene.")
        CdaPropertyEnd()
        // End Definition of WidgetProperty "NodeToClone"


  • CloneOperation: If enabled, the clone operation will be executed and the cloned node will be added to the widget node.

        // Definition of WidgetProperty "CloneOperation"
        CdaProperty(CloneOperation, bool, GetCloneOperation, SetCloneOperation)
            CdaDescription("If CloneOperation is enabled, the Candera::Node to clone will be cloned and added to the widget Candera::Node. If disabled, the clone will be removed.")
        CdaPropertyEnd()
        // End Definition of WidgetProperty "CloneOperation"


For the cloning operation, refer to Candera::Node::Clone. The following code snippets illustrate, what the SceneGraphDynamicsWidget_3D is actually doing for cloning a node and destructing it again.

Validating Node to Clone

This example should allow to clone nodes, which are part of the scene graph the widget is linked to. The scene graph can be traversed from a given node up to its actual top level scene node to check whether it is part of the scene graph.

    // Search the parent scene of a given node

    Node* currentNode = node;

    while (currentNode != 0) {
        if (currentNode->IsTypeOf(Scene::GetTypeId())) {
            return static_cast<Scene*>(currentNode);
        }
        currentNode = currentNode->GetParent();
    }
    return 0;

    // end Search the parent scene of a given node
Creating and Adding Clone

If the widget node and the node to clone have same top level scene, the node can be cloned and added to the widget node according to the following code snippet.

        // Clone the node and add it to the widgets associated node

        m_clonedNode = TreeCloner().CreateClone(*m_nodeToClone);
        static_cast<void>(node->AddChild(m_clonedNode));
        static_cast<void>(m_clonedNode->UploadAll());
        m_clonedNode->SetName("Clone");

        // end Clone the node and add it to the widgets associated node

Nodes can be attached to any 3D node.

TreeCloner and Cloning Strategies

For more complex cloning see Candera::TreeCloner and Candera::TreeCloneStrategy. Candera::TreeClonerBase is provided for cloning trees.

  • The default clone strategy is to call the Clone interface on each node, and build the clone tree with the same node pattern as the original tree.
  • For deeper cloning, TreeClonerBase supports attaching a Candera::TreeCloneStrategy. This TreeCloneStrategy is typically aware of different types of nodes, and delegates to other strategies.
Removing and Destructing Clone

Analogous to Removing and destructing the Billboard (removing/destructing a 3D node) the cloned node is destructed.

        // Remove a previously cloned node

        static_cast<void>(m_clonedNode->UnloadAll());
        m_clonedNode->Dispose();
        m_clonedNode = 0;

        // end Remove a previously cloned node

In SceneComposer the newly added node clone does not appear in the Scene Tree view because it is only added dynamically by the widget.
- It is not allowed to modify the scene graph maintained by SceneComposer outside of the subtree owned by the widget!
- The widget must control memory and VRAM management of its internal dynamic subtree autonomously.


Tutorial for 3D Scene Graph and Nodes

3D Render Order

Description

There are various reasons why sorting of objects to render is required:

  • Transparency: Transparent objects must be rendered from back to front, while opaque objects must be rendered from front to back.
  • Performance: Front to back order or advanced techniques like state sorting provide the best performance
  • Other: Custom rank, Skybox, other background geometry, dialogs, pop-ups, etc.

Render Order: Involved Classes 

Involved Render Order Classes
Candera::RenderOrder

Defines the sequence of nodes to render per scene and camera.
Sorting is done according to following criteria:

  1. Rank of render order bins: Candera::RenderOrder::SetBinRank.
  2. Sorting of Nodes within bin (see below).

Default bins are 'opaque' and 'transparent'. If no assignment is set, nodes are automatically assigned to opaque or transparent, according to the Node's transparency settings (RenderMode state).

Candera::RenderOrderBin RenderOrder maintains a set of RenderOrderBins. A bin
  • Contains nodes that have rendering enabled
  • Candera::RenderOrderBin::SetOrderCriterion: Sorts nodes according to applied Candera::OrderCriterion
  • Candera::RenderOrderBin::SetRank: defines sequence of bin within render order
  • Distinction between camera dependent and camera independent bins
Candera::OrderCriterion

Nodes within a bin are sorted according to an OrderCriterion.
Predefined for camera dependent bins:

Predefined for camera independent bins, sorted by explicit rank values:

Candera::Node A node must be assigned to a bin, and if this bin is sorted by rank, the node must also specify it's rank inside the bin.
Candera::Scene Candera::Scene::SetRenderOrder: One RenderOrder is exactly assigned to one Scene.
Candera::Renderer Uses RenderOrder to render Nodes according to its defined sequence.

Default Render Order: Sorting by Distance to Camera 

Default Render Order

By default, all objects part of a scene are automatically sorted by Candera according to their render attributes into two predefined render order bins:

Both default bins are protected, which means that they are under control of Candera and cannot be deleted.

Predefined Render Order in SceneComposer

In SceneComposer, each 3D scene gets the predefined render order bins assigned by default:

drawing-4-1676882062.png

Predefined Render Order in Candera

To apply the default behaviour with auto-assignment of transparent and opaque objects via Candera API, refer to following example:

// Create default render order with 2 default bins and max. number of 10 nodes each (opaque and transparent bin).
// Default rank for opaque bin is 10 and transparent bin is 20
static RenderOrder* renderOrder = RenderOrder::Create(10, 10);
// Assign render order to scene.
m_scene->SetRenderOrder(&renderOrder);

Fixed Render Order 

To customize the default render order with a fixed render order, scene nodes must be assigned to custom render order bins sorted by explicit rank values.

Code Example: Fixed Render Order

As an example, create a render order with 3 bins and max. number of 10 nodes for O&T bin:

static RenderOrder renderOrder(3, 10, 10);
m_scene->SetRenderOrder(&renderOrder);

Create new render order bin with name "UserBin", rank "30", max. Number of 10 nodes:

renderOrder.CreateBin("UserBin", 30, 10);

Create dedicated order criterion which sorts nodes by its render order rank:

static RankOrderCriterion userCriterion;

Assign order criterion to UserBin:

renderOrder.SetBinOrderCriterion("UserBin", &userCriterion);

Assign nodes to dedicated bins:

m_mesh1->SetRenderOrderBinAssignment("UserBin");
m_mesh2->SetRenderOrderBinAssignment("UserBin");

Set dedicated render order rank for nodes:

m_mesh1->SetRenderOrderRank(2);
m_mesh2->SetRenderOrderRank(1);

Performance Optimized Render Order 

Candera::RenderStateOrderCriterion implements a proof of concept OrderCriterion used for batching nodes to minimize render state changes. It takes into consideration the nodes shader, texture and render mode, whose changes are considered the most expensive.

  • After sorting all the nodes using the Candera::OrderCriterionValue, rendering is done in batches of nodes with the same shader.
  • Inside a batch of nodes with the same shader, the rendering will be done in batches of nodes with the same texture.
  • Inside a batch of nodes with the same shader and texture, the rendering will be done in batches of nodes with the same blending enabled value.

The batching continues until the least expensive state change (CullingMode).

- Usage of Candera::RenderStateOrderCriterion does not guarantee best batching algorithm, which should be implemented after benchmarking the target render device's state change costs.
- RenderStateOrderCriterion groups the nodes only by their first appearance and texture. Nodes with multi-pass rendering (MultiPassAppearance), or with multiple texture units, most probably will break the batches and cancel some performance gains.
- CANDERA_RENDER_STATE_CACHING_ENABLED should be defined to use Candera's render state caching mechanism.



Tutorial for 3D Scene Graph and Nodes

Searching for Nodes with SearchTreeTraverser

Searching for Nodes

With Candera::SearchTreeTraverser it is possible to find nodes within a given scene tree by certain criteria. The search scene graph traverser is a Candera::TreeTraverserBase implementation that evaluates each node with a given Candera::SearchCriterion.

Following code snippets illustrate how to use the traverser.

Creating a plugin that registers a configuration and uses the default configuration editor.

First create a Candera::SearchTreeTraverser and then set its Candera::SearchCriterion. The search criterion validates nodes based on the implemented criterion.

In the example below the criterion is a Candera::ScopeMaskSearchCriterion.

    // Create criterion
    ScopeMaskSearchCriterion<Node> scopeMaskCriterion;
    scopeMaskCriterion.SetScopeMask(scopeA);
    
    // Create SearchTreeTraverser
    SearchTreeTraverser<Node> scopeSearchTraverser;
    scopeSearchTraverser.SetSearchCriterion(&scopeMaskCriterion);

Tree Traversing

To start a search within a node tree, call Candera::SearchTreeTraverser::Traverse(). With the Candera::SearchTreeTraverser::SetOnFoundAction you can define if the search should stop after the first node that fulfills the search criterion is found, or if it should continue to gather all valid nodes.

Possible TraverserActions are:

  • ProceedTraversing: Find all nodes matching the given search criterion (traversing is stopped at the end of the tree)
  • StopTraversingForDescendants: Find only nodes on the top level of the tree (traversing stops at child nodes and proceeds at siblings, until the end of the tree)
  • StopTraversing: Find only the first node matching the search criterion (traversing is stopped immediately).

The nodes fulfilling the search criterion are retained and are available to be later retrieved. Get the nodes one by one with Candera::SearchTreeTraverser::GetSearchResult. The order of the nodes is not defined and each node can be retrieved only once. When no more nodes are available, the method returns 0.

Following are examples for the different TraverserActions.

ProceedTraversing

This action is used if the traversing should continue with the next child (if the current node has any) or with the next sibling.

First define the action and then call the Traverse() method. All nodes that fulfill the search criterion are found and stored internally in a list.

    // Set on found action and traverse
    scopeSearchTraverser.SetOnFoundAction(Candera::TreeTraverserBase<Node>::ProceedTraversing);
    scopeSearchTraverser.Traverse(*groupRoot);
    
    // Get results
    UInt32 nodeCount = 0;
    for (Node* node = scopeSearchTraverser.GetSearchResult(); node != 0; node = scopeSearchTraverser.GetSearchResult()) {
        nodeCount++;
    }

StopTraversingForDescendants

Used if the traversing should stop for all child nodes and continue with the next sibling.

    // Set on found action and traverse
    scopeSearchTraverser.SetOnFoundAction(Candera::TreeTraverserBase<Node>::StopTraversingForDescendants);
    scopeSearchTraverser.Traverse(*groupRoot);

StopTraversing

If you search for only one node which fulfills your criteria, you can use StopTraversing. The traversing will stop immediately after the first node is found.

    // Set on found action and traverse
    scopeSearchTraverser.SetOnFoundAction(Candera::TreeTraverserBase<Node>::StopTraversing);
    scopeSearchTraverser.Traverse(*groupRoot);
    Node* resultNode = scopeSearchTraverser.GetSearchResult();

Another call of Candera::SearchTreeTraverser::GetSearchResult would return 0, because we use StopTraversing here. This leads to one result only.

Tutorial for 3D Scene Graph and Nodes

Vertex Geometry Builder

Description

The Candera::VertexGeometryBuilder class helps effectively build Candera::VertexGeometry objects.


Procedural Geometry 

Example: Wireframe Cube with LineList

First example shows how to generate a wireframe cube using a Candera::LineList object.

The vertex geometry will be generated procedurally using a Candera::VertexGeometryBuilder object (e.g. m_builder).

Vertex Element Format: Position and Color

At the beginning we have to specify what kind of information will be set in the vertices. Of course we need information about the position and, optionally, for an improved visual effect, we could also add color information.

    // Set the data type to be used: position & color
    m_builder.SetVertexElementFormat(VertexGeometry::Position, 0, VertexGeometry::Float32_3);
    m_builder.SetVertexElementFormat(VertexGeometry::Color, 0, VertexGeometry::Float32_4);
Vertex Data: Position

The position information for the vertices is obtained from the array c_cubeData which, in fact, stores the coordinates of the points A and G. The coordinates for the rest of the vertices are obtained relatively to this points (e.g. F(XA, YG, ZG) or D(XG, YA, ZA)

drawing-4-1676882299.png


// Coordinates for the points A & G
static const Float c_cubeData[2][3] = {
    {-1.0F, -1.0F, -1.0F },
    {1.0F, 1.0F, 1.0F }
};
VertexGeometryBuilder: Set Vertex Elements

Because the LineType property of the LineList object will be set to LineType::Lines, the vertex buffer will have to contain a pair of vertices for each line of the cube: 2 vertices/line * 12 lines = 24 vertices.

    // Set the position & color information for each vertex
    // Line segment AD
    m_builder.SetVertexElement(VertexGeometry::Position, 0, c_cubeData[0][0], c_cubeData[0][1], c_cubeData[0][2]); // A
    m_builder.SetVertexElement(VertexGeometry::Color, 0, 1.F, 0.F, 1.F, 1.F); // magenta
    // Increment the vertex cursor
    m_builder.IncrementVertexCursor();
    m_builder.SetVertexElement(VertexGeometry::Position, 0, c_cubeData[1][0], c_cubeData[0][1], c_cubeData[0][2]); // D
    m_builder.SetVertexElement(VertexGeometry::Color, 0, 1.F, 1.F, 1.F, 1.F); // white
    m_builder.IncrementVertexCursor();
    // Line segment AB
    m_builder.SetVertexElement(VertexGeometry::Position, 0, c_cubeData[0][0], c_cubeData[0][1], c_cubeData[0][2]); // A
    m_builder.SetVertexElement(VertexGeometry::Color, 0, 1.F, 0.F, 1.F, 1.F); // magenta
    m_builder.IncrementVertexCursor();
    m_builder.SetVertexElement(VertexGeometry::Position, 0, c_cubeData[0][0], c_cubeData[0][1], c_cubeData[1][2]); // B
    m_builder.SetVertexElement(VertexGeometry::Color, 0, 1.F, 1.F, 0.F, 1.F); // yellow
    // ...
Create Vertex Buffer

Create a Candera::VertexBuffer object and attach to it the vertex geometry obtained from the Candera::VertexGeometryBuilder:

    VertexGeometry* vertexGeom = m_builder.GetVertexGeometry();
    SharedPointer<VertexBuffer> vertexBuffer = VertexBuffer::Create();
    static_cast<void>(vertexBuffer->SetVertexGeometry(vertexGeom, VertexBuffer::VertexGeometryDisposer::Dispose));
    vertexBuffer->SetPrimitiveType(VertexBuffer::Lines);
Create LineList using the Vertex Buffer

Attach the vertex buffer to LineList object:

    // Create and configure the line list object
    m_lineList = LineList::Create();
    m_lineList->SetAppearance(Appearance::Create());
    m_lineList->GetAppearance()->SetMaterial(Material::Create());
    m_lineList->GetAppearance()->GetMaterial()->SetEmissive(Color(1.0F, 1.0F, 0.0F));
    m_lineList->GetAppearance()->SetShader(m_shaderLightMaterial);
    m_lineList->SetLineType(LineList::Lines);
    m_lineList->SetWidth(1.0);
    m_lineList->SetIntersectionTestEnabled(false);
    m_lineList->SetVertexBuffer(vertexBuffer);
    m_lineList->SetScopeMask(ScopeMask(false));
    static_cast<void>(m_lineList->SetScopeEnabled(1, true));
    m_lineList->SetRenderingEnabled(true);
    m_lineList->SetName("Wireframe");
    static_cast<void>(m_lineList->Upload());
Wireframe Cube Result

The image below shows the resulting wireframe cube:

drawing-4-1676882342.png

For an easier manipulation of the widget in SceneComposer all the shaders are hardcoded in the source file and then created by the widget at the initialization time.

Alternatively obtain shaders from Candera::AssetProvider (e.g. GetAssetProvider()->GetShader("...")) in case the application uses an asset library.


Indexed Geometry 

Example: Solid Cube from one Triangle Strip

The next example will show how to create a solid cube from one triangle strip using a Candera::Mesh object. In this case the cube is determined by only 14 vertices which have to be ordered in a special way.

The figure below presents a cube unfolded and a possible solution of ordering the vertices:

drawing-4-1676882414.png

Indexed Vertex Sequence

The vertex sequence is stored in the c_index_order array, encoding the order:

  • G->C->F->B->A->C->D->H->A->E->F->H->G->C.

Each array element represents an offset it the vertex buffer.

// The vertex sequence - triangle strip
static const UInt16 c_index_order[] = { 12, 11, 7, 3, 0, 11, 1, 17, 0, 5, 7, 17, 12, 11 };

The vertex geometry builder object used in the previous example has already 24 vertices set with position and color information. Because only 14 vertices out of 24 are needed in this case and, also, because their sequence has to be different, an index buffer is added:

    // Change the order of the vertices using indexes
    for (Int index = 0; index < 14; index++) {
        // Define the value of the index
        m_builder.SetIndexElement(c_index_order[index]);
        // Increment the index cursor
        m_builder.IncrementIndexCursor();
    }
Create TriangleStrip Vertex Buffer

Create the vertex buffer using the geometry from the builder:

    VertexGeometry* vertexGeom = m_builder.GetVertexGeometry();
    SharedPointer<VertexBuffer> vertexBuffer = VertexBuffer::Create();
    static_cast<void>(vertexBuffer->SetVertexGeometry(vertexGeom, VertexBuffer::VertexGeometryDisposer::Dispose));
    vertexBuffer->SetPrimitiveType(VertexBuffer::TriangleStrip);
Create Mesh using the Vertex Buffer

Configure mesh and attach the vertex buffer:

    m_mesh = Mesh::Create();
    m_mesh->SetVertexBuffer(vertexBuffer);
    m_mesh->SetAppearance(appearance);
    m_mesh->SetRenderingEnabled(false);
    m_mesh->SetName("SolidNonTextured");
    static_cast<void>(m_mesh->Upload());
Solid Cube Result

The image below shows the resulting solid cube:

drawing-4-1676882451.png



Remove & Add Vertex Elements 

Example: Textured Cube

In this example the vertex geometry builder object will be used to generate the vertex geometry for a textured cube.

Adding texture to a mesh requires that its vertices should contain specific information like normal and texture coordinate. Also, the color information is no longer needed so it can be removed:

    // Vertex Elements Manipulation
    // Remove color data associated to the vertices
    m_builder.RemoveVertexElement(VertexGeometry::Color,0);
    // Add data type to be used: texture coordinate & normal
    m_builder.SetVertexElementFormat(VertexGeometry::TextureCoordinate, 0, VertexGeometry::Float32_2);
    m_builder.SetVertexElementFormat(VertexGeometry::Normal, 0, VertexGeometry::Float32_3);
Vertex Data: Normals and Texture Coordinates

After adding the new data types for the vertices it is time to add the corresponding information. First, add normals for vertices:

    // Move the cursor to the first vertex
    m_builder.SetVertexCursor();
    for (UInt32 i = 0; i < m_builder.GetVertexCount(); i++ )
    {
        m_builder.SetVertexElement(VertexGeometry::Normal, 0, 0.0F, 0.0F, 1.0F);
        m_builder.IncrementVertexCursor();
    }

In order to map the UV texture coordinate values correctly, the cube faces should be drawn using 4 vertices per face, so, in this case, all 24 vertices will be used. Of course, the vertex order has to be adjusted.

The new vertex order is stored in the c_indexDataTexturedCube array.

// Vertex order - textured cube
static const UInt16 c_indexDataTexturedCube[24] = {
    1,17,0,5,
    2,6,3,7,
    9,8,11,12,
    13,14,19,21,
    15,23,16,22,
    4,10,20,18
};

Now, reorder the vertices:

    // Overwrite the index buffer
    // Move the cursor to the first index
    m_builder.SetIndexCursor();
    for (UInt32 i = 0; i < m_builder.GetVertexCount(); i++ )
    {
        // Define the value of the index
        m_builder.SetIndexElement(c_indexDataTexturedCube[i]);
        m_builder.IncrementIndexCursor();
    }

The vertices of each face of the cube should be set with texture coordinate information as shown below:

drawing-4-1676882513.png



Set the texture coordinates for vertices:

    Int idx = 0;
    for (Int nb_of_faces = 0; nb_of_faces<6; nb_of_faces++) {
        for (Int u=0; u<=1; u++) {
            for (Int v=0; v<=1; v++) {
                //Set the position of the vertex cursor
                m_builder.SetVertexCursor(c_indexDataTexturedCube[idx]);
                m_builder.SetVertexElement(VertexGeometry::TextureCoordinate, 0, static_cast<Float>(u), static_cast<Float>(v));
                idx++;
            }
        }
    }

The vertex buffer is created and configured in the same way as in the previous example.

The mesh setup is also similar but, unlike the solid cube, the textured cube appearance has to be set with a texture and a texture shader.

Textured Cube Result

Here is the final result:

drawing-4-1676882532.png



Range Data Usage 

Example: Solid Cube using Range Data

This example will show how to create a solid cube as in the second example but using range data.

First, all existing data from the vertex geometry builder is removed.

    // Clear all data stored by the builder
    m_builder.Clear();

The vertex data is taken from the two buffers c_positionData and c_colorData.

static const Float c_positionData[8][3] = {
    { -1.0F, 1.0F, 1.0F},
    { 1.0F, 1.0F, 1.0F},
    { -1.0F, -1.0F, 1.0F},
    { 1.0F, -1.0F, 1.0F},
    { -1.0F, 1.0F, -1.0F},
    { 1.0F, 1.0F, -1.0F},
    { -1.0F, -1.0F, -1.0F},
    { 1.0F, -1.0F, -1.0F}
};

static const Float c_colorData[8][4] = {
    { 0.0F, 0.0F, 1.0F, 1.0F},
    { 0.0F, 1.0F, 0.0F, 1.0F},
    { 0.0F, 1.0F, 1.0F, 1.0F},
    { 1.0F, 0.0F, 0.0F, 1.0F},
    { 1.0F, 0.0F, 1.0F, 1.0F},
    { 1.0F, 1.0F, 0.0F, 1.0F},
    { 1.0F, 1.0F, 1.0F, 1.0F},
    { 1.0F, 0.5F, 1.0F, 1.0F}
};

The vertex sequence is taken from the array c_indexData.

// Range data index
static const UInt16 c_indexData[] = {3, 2, 1, 0, 4, 2, 6, 3, 7, 1, 5, 4, 7, 6 };

Use the methods Candera::VertexGeometryBuilder::SetVertexElementRange and Candera::VertexGeometryBuilder::SetIndexElementRange to add the required information.

    // Add position, color and an index from range data
    // Splice values to the position buffer starting with the first vertex
    static_cast<void>(m_builder.SetVertexElementRange(VertexGeometry::Position, 0, 0, VertexGeometry::Float32_3,
        8, 3*sizeof(Float), c_positionData ));
    // Splice values to the color buffer starting with the first vertex
    static_cast<void>(m_builder.SetVertexElementRange(VertexGeometry::Color, 0, 0, VertexGeometry::Float32_4,
        8, 4*sizeof(Float), c_colorData ));

    // Splice values to the index buffer starting with the first index
    static_cast<void>(m_builder.SetIndexElementRange(0, sizeof(c_indexData)/sizeof(UInt16),c_indexData));

The vertex buffer configuration and the mesh setup are identical with the ones from the second example.

Here is the final result:

drawing-4-1676882596.png


Concatenate Geometries 

VertexGeometryBuilder::SpliceGeometry

In some cases it might be useful to concatenate two vertex geometries. This can be done by using the Candera::VertexGeometryBuilder::SpliceGeometry method as shown below:

    m_builder.SpliceGeometry(0, 0, vertexGeometry1);
    m_builder.SpliceGeometry(vertexGeometry1->GetVertexCount(), vertexGeometry1->GetIndexCount(), vertexGeometry2);
    vertexGeometry1cat2 = builder.GetGeometry();


Tutorial for 3D Scene Graph and Nodes

Vertex Geometry Modifier

Overview

The VertexGeometryModifier, in conjunction with a VertexAccessor, helps to modify VertexGeometry objects by the means of the setters offered by the class interface. The interface offers also getters which are helpful to retrieve the values of the vertex elements from a given vertex buffer.

Example

The example below shows how to change the position values for the vertices from a vertex buffer of a given mesh.

            VertexGeometry* vertexGeom;
            bool isMutable = m_mesh3->GetVertexBuffer()->GetVertexGeometry()->GetVertexArrayResourceHandle().m_isMutable;
            // Get the vertex geometry of the mesh
            if (isMutable) {
                vertexGeom = m_mesh3->GetVertexBuffer()->GetVertexGeometry();
            }
            else {
                DiagnosticPlatform::ConsoleOut("VertexGeometryData not mutable");
                break;
            }
             // The vertex accessor will be used by the vertex geometry modifier to access the vertices
            VertexAccessor accessor(*vertexGeom);

            // Create a vertex geometry modifier specifying as usage the type of vertex attribute
            // which will be changed
            VertexGeometryModifier modifier(*vertexGeom, VertexGeometry::Position, 0);
            Float vertexData[3];
            for(Int i = 0; i<8; i++) {
                if(s_inflate) {
                // Retrieve the position data of the vertex "i" in the vertexData array
                modifier.GetVertexElement(accessor.GetVertex(i), vertexData, 3);

                // Alter the data and set it back
                modifier.SetVertexElement(accessor.GetMutableVertex(i), vertexData[0]*1.3F, vertexData[1]*1.3F, vertexData[2]*1.3F);
                } 
                else {
                    // Set the position of the vertex with data get from an array (Float c_positionData[8][3])
                    modifier.SetVertexElement(accessor.GetMutableVertex(i), c_positionData[i], 3);
                }
            }

            // Update the vertex buffer on the mesh
            m_mesh3->GetVertexBuffer()->Update(0, m_mesh3->GetVertexBuffer()->GetVertexGeometry()->GetVertexCount());

Tutorial for 3D Scene Graph and Nodes

Candera 3D Listeners

Applications can receive notifications on scene graph events. Therefore implement and use a listener, which defines hooks to receive those notifications.

Following listener interfaces are provided in Candera 3D:

  • Candera::RendererListener defines hooks that are called before or after a node is rendered. It supports events for OnNodePreRender and OnNodePostRender, so you are informed before and after a node is rendered.
  • Candera::NodeListener defines hooks that are called when certain node functions are triggered, e.g. when a node's transformation changes.
  • Candera::SceneListener defines hooks that are called on certain scene actions. The listener informs when a scene is activated.
  • Candera::AnimationPlayerListener defines several hooks for the AnimationPlayer. It informs about changes of the animation, like when an animation has started, stopped, finished, resumed, paused and when the direction has changed.
  • Candera::CameraListener defines hooks that are called before or after a camera is rendered. It supports events for OnPreRender, OnPostRender, OnProjectionChanged and OnViewChanged.
  • Candera::ProjectionListener defines hooks that are called when projection parameters are changed. It supports OnProjectionChanged, which means the camera reacts to changes of the projection matrix associated and updates frustums accordingly.

In order to register a listener simply derive from the listener class and override pure virtual functions with custom code.

Example Listener Implementation

Refer to Using Animation Callback Functions for an example how to implement and use an Animation listener.


Tutorial for 2D Scene Graph and Nodes

Tutorial for 2D Scene Graph and Nodes

2D Scenes and Nodes

Scene Graph

The 2D scene graph is a tree structure which organizes nodes of type Candera::Node2D. The root of each scene graph has to be a node of type Candera::Scene2D. All nodes of type Candera::Node2D can hold sub nodes added by Candera::Node2D::AddChild().

Nodes of type Candera::RenderNode are representing content which has to be rendered to the target, e.g. text or a bitmap. At least one node of type Candera::Camera2D which is the link to the render target (e.g. a display) has to be part of the scene graph.

drawing-4-1676946524.png

RenderNode

Only nodes of type RenderNode are considered during rendering. Each render node requires a list of effects (effect chain) which implements the visual representation of that node. Each render node has a depth value as parameter which defines the order of rendering (z-order). Higher value means background, lower value means foreground.

drawing-4-1676946545.png

Transformations

The functionality to define the local coordinate system is derived from Candera::Transformable2D and consists of following components:

  • position,
  • rotation,
  • scale,
  • and the pivot offset.

This information is always relative to its parent node. Changing one of these parameters affects the presentation of all child nodes. If for example a node is rotated, all child nodes are rotated accordingly. Each nodes rotation, translation (position) and scale values are combined in one transformation matrix which is multiplied with the parent nodes transformation matrix.


Tutorial for 2D Scene Graph and Nodes

2D Node Transformations

2D Node Manipulation Example

To illustrate basic node transformation operations, the NodeManipulationWidget_2D and the DisplayPositionWidget2D part of the Tutorial Widgets cand be used. An usage example for NodeManipulationWidget_2D can be seen in NodeManipulationSolution_2D solution provided in the content folder of cgi_studio_player.

Please consider:

  • A Node2DToTransform on which the following transformations are made has to be selected in the widget.
  • Only if the widget is enabled (this property is also derived from a base class), the setter methods of the widget properties will take effect.
  • If a widget property setter changes a node property of the associated node, the visual effect can be seen, but note that the static node properties maintained by SceneComposer are not changed accordingly!
  • Widget dynamics are never reflected to static scene structure. It is recommended to use SceneComposer only for property configuration and dynamics only in the Player.

Translations

Once the NodeManipulationWidget_2D is linked with a node from the static scene tree and enabled, the position coordinates can be set in the Player for this property to change the position.

            // Set node position
            m_node->SetPosition(m_position);    // set absolute node position
            // End Set node position


            // Translate node
            m_node->Translate(m_translate);     // translate actual node position
            // End Translate node

Candera::Transformable2D::SetPosition sets the absolute position of a node compared to Candera::Transformable2D::Translate, which adds the given vector to the current position. So setting the position first and then translating the node gives a different behavior than vice versa.

Rotations

Similar to translation, rotation can also be applied via Candera::Transformable2D::Rotate or Candera::Transformable2D::SetRotation.

            // Rotate node
            m_node->Rotate(m_rotate);
            // End Rotate node

Scaling

Again there is also a Candera::Transformable2D::Scale and Candera::Transformable::SetScale method.

            // Scale node
            m_node->Scale(m_scale);
            // End Scale node

Pivot Point

It is possible to change your pivot point with the Candera::Transformable2D::TranslatePivotPoint (or Candera::Transformable2D::SetPivotPoint) method. The pivot point can be used to define e.g. the rotation point that the node should rotate around. It affects the scaling too.

How to get the screen space coordinates of a node

With the DisplayPositionWidget2D you can retrieve the screen coordinates of an associated node. Please consider that a node and a camera have to be selected in the widget. In the solution the widget is disabled by default, so if you want to see an output of your display position, you need to enable it. To get the screen space coordinates of an associated node, you simply need to add the render target position of your node to the window position.

  • First you need the world position of your node. With this position you can retrieve the viewport position, which you need for retrieving the render target position.
            Vector2 worldPosition = node->GetWorldPosition();
            Vector2 viewportPos = Math2D::TransformSceneToViewport(*camera, worldPosition);
            Vector2 renderTargetPos = Math2D::TransformViewportToRenderTarget(*camera, viewportPos);
    
  • Now you need the window.
            Window* w = camera->GetRenderTarget()->GetGraphicDeviceUnit()->ToWindow();
            if (w != 0) {
                Vector2 delta(static_cast<Float>(w->GetX()), static_cast<Float>(w->GetY()));
    
  • With these two Candera::Vector2 you can calculate the screen space Candera::Vector2 of your node.
                Vector2 displayPos = delta + renderTargetPos;
    
  • displayPos now contains the X and Y screen space coordinates of the selected node.

Tutorial for 2D Scene Graph and Nodes

2D Effects

Description

Candera2D renders content exclusively by pieces of code which are named effects. Effects represent semantic functionality which considers hardware and driver capabilities.

The set of effects supported depends on the feature set of the underlying graphic device unit. This tutorial is based on the reference platform iMX6. Refer to the CanderaPlatformDevice API documentation to learn about the set of 2D effects supported on the platform used.

Example Solution

  • 2DEffects Sample Solution in SceneComposer.

2D Effect Types

Effects can be distinguished in three types of operations:

  1. Candera::BrushEffect2D provides means of generating graphical content out of user-defined data. An example would be an effect which generates a rectangle of solid color based on red, green and blue values. Brush effects generate pixel data as output.
  2. Candera::InPlaceEffect2D requires input data provided. Input data is always given in form of pixel data and is supposed to be provided by either a Brush Effect or another InPlace Effect. These kinds of effects can utilize any additional parameters to transform the pixel data to output data. The output is again pixel data.
  3. Candera::BlendEffect2D is very similar to InPlaceEffect2D. To be specific, every InPlace effect resembles a specialization of blend effect, namely that they blend with a fixed functionality. What makes Blend Effects special is only their purpose of being the effect which also takes the render target into account as an input. This allows blend effects to transform the input and the render target to any kind of combination of both. Output is again pixel data.

The effect types can be linked together so that they resemble an Effect Chain. An effect chain consists of one brush effect, zero to n InPlace effects and one blend effect.

  • The Brush Effect references already existing pixel data, so it defines the source data of the node (bitmap, solid color, text).
  • InPlace Effects define pixel manipulation functions like mask, color transformation, etc.
  • The Blend Effect is attached at the end of the chain so that the pixel data generated by the Brush Effect is processed together with the render target's pixel data to a combination.
Combined Effects

A Combined Effect is a single effect which represents a whole sequence of other effects in an effect chain. For example, if you want to render bitmap data with an alpha function, there usually is a combined effect which represents the Brush Effect and the Blend Effect in one single object.

This single object can take the advantage to perform operation in a single step as the external interfaces (pixel data) can be replaced internally by setting options in the native rendering API.

Refer to Candera::CombinedEffect2D about which CombinedEffects are supported by Candera.

Combined Effects Examples

An example of various effects and how they are configured can be seen in 2DEffects Sample Solution in SceneComposer.

Bitmap Effects

In order to render an image, a BitmapNode with one of the following effects must be set in a 2D scene:

For the above effects, the following properties can be set to obtain different rendering results:

  • Color - values for red, green, blue and alpha must be set
  • Color Blend Factor for source and destination
  • Color Blend Operation
  • Alpha Blend Factor for source and destination
  • Alpha Blend Operation

An example of Bitmap effects with different properties is shown below:

drawing-4-1676946798.png

Candera::BitmapBrushColorBlend is a combined effect between Candera::BitmapBrushBlend and Candera::BitmapBrushColor. The properties of this effect sum up the properties of the other two. The values for the properties for Candera::BitmapBrushColorBlend used in the example, for left most, centre and right most images, are:

drawing-4-1676946814.png

Text with Bitmap Effects

In order to render text, a Text Node with one the following effect must be set in a 2D Scene.

Text node is a Render node that render text using the attached Bitmap Brush effect.

drawing-4-1763533313.png

Brush Effects

The following effects can be used with a RenderNode in a 2D scene:

Fill Color must be set for the above effects.

For Candera::SolidColorBrushBlend, the properties Color Blend Factor and Operation and Alpha Blend Factor and Operation can also be set to obtain different rendering results.

An example of Candera::SolidColorBrushBlend and related properties is shown below:

drawing-4-1676946859.png


drawing-4-1676946878.png

Mask Effects

As the name suggests, Mask effects allows masking different areas of a given image by applying a mask image with transparent areas.

The following effects can be used with a BitmapNode:

For the above effects, the following properties must be set:

  • Image – the image on which the mask will be applied
  • Mask – the mask image with transparent areas
  • Mask Node – node used to control the position of the mask. If set to 0, the position of the current node is used. For iMX6, the mask takes into account the scale, the rotation and the pivot of the node.

An example of mask effects is presented below:

drawing-4-1676946920.png

Left most image has no Mask Node set, therefore the BitmapNode associated with the effect is used. The image from the center has Mask Node set to a RenderNode with Rotation property = -50. Thus, the mask also appears rotated. Right most image has Mask Node set to a RenderNode with different values for Scale property (x= 0,5 ; y = 0,5). Thus, the mask also appears smaller.

The properties Color Blend Factor and Operation and Alpha Blend Factor and Operation can also be set to obtain different rendering results.

Manipulating an Effect on a Render Node

To illustrate effect manipulation operations, the NodeManipulationWidget_2D part of the Tutorial Widgets in combination with the NodeManipulationSolution_2D provided in the content folder of cgi_studio_player can be used.


The type Candera::RenderNode defines a node that shall be rendered. Each render node requires an effect chain, which implements the visual representation of the node.

The following properties manipulate a Candera::SolidColorBrushBlend effect on a render node (this is a already combined effect with brush, inplace and blend):

  • RenderNodeToManipulate: Select a RenderNode to manipulate its effect. Because of casts in the widgets code only select a render node with Candera::SolidColorBrushBlend, otherwise the casts will fail! (See Manipulating an effect)
  • Set the widgets SolidColor, FillArea, Blend values to change the properties of the effect dynamically.
Manipulating an Effect

First the selected render node (which comes with type Candera::Node2D by the CDAProperty) is casted to a Candera::RenderNode.

    // Cast Node2D to RenderNode
    m_renderNode = Dynamic_Cast<RenderNode*>(node);
    // End Cast Node2D to RenderNode

Next the first effect of the selected render node of type Candera::Effect2D is casted to Candera::SolidColorBrushBlend to get access to specialised properties.

This simple example is based on a RenderNode with SolidColorBrushBlend effect, so if the example solution is changed, please take care to use only SolidColorBrushBlend on the node to manipulate.


    // Get and cast first effect of render node
    Effect2D* effect2D = node->GetEffect(0);
    SolidColorBrushBlend* effect = Dynamic_Cast<SolidColorBrushBlend*> (effect2D);
    // End Get and cast first effect of render node

Now properties of the effect can be set.

        // Set solid color
        SolidColorBrush& brush = effect->GetSolidColorBrush();
        brush.Color() = m_color;
        // End Set solid color


        // Set fill area
        brush.Size() = m_rect;
        // End Set fill area

The effect should be updated each time a property is changed. A good place for updating the effect might be the widget Update method.

        // Update effect
        static_cast<void>(effect->Update());
        // End Update effect

Arbitrary properties on arbitrary effects can be manipulated the same way.

How to Integrate a Custom Combined Effect into SceneComposer

Integrate a custom effect to Candera and SceneComposer
  • Create and implement your own effect.
  • Integrate the effect into the effect library of the device.

For DEVICE that would mean to modify cgi_studio_candera/src/CanderaPlatform/Device/DEVICE/2D/DEVICEEffectLibrary.cpp, so that the new effect implementation is included and the Effect2DLibrary MetaInfo is extended by the new effect. (DEVICE stands for your specific device)

  • Integrate the new device library in SceneComposer. Therefore set CMake source to <cgi-studio-root>/cmake/Candera/Player and build a SCHost.dll. The library CanderaDevice<device>.lib is linked to the SCHost.dll, which than has the new effect included.
  • Start SceneComposer with the new SCHost.dll. The new effect is listed in the toolbox for a 2D scene.
How to create a combination of two effects

There are several effects which can be combined to a new effect. How to combine two effects to a new one is shown in Candera::GlBitmapBrushColorMaskBlend implementation.

For the new effect Candera::GlBitmapBrushColorMaskBlend you need a combination of Candera::BitmapBrushColorBlend and Candera::GlBitmapBrushMaskBlend. Create and implement the new effect analogous to Candera::GlBitmapBrushMaskBlend. Now apply following changes:

  • The new effect contains both, MaskEffect and ColorEffect as member (wrapper, see in .h file).
  • The implementation of the method Render() is a combination of Candera::BitmapBrushColorBlend::Render() and Candera::GlBitmapBrushMaskBlend::Render(). This method has to handle all four effects in the following order:
    • blend effect,
    • color effect,
    • mask effect and
    • brush effect.
  • Upload(), Unload(), and Update() have to handle all four effects.
  • Apply changes to the Effect Meta Info. The new effect needs all four effects (BitmapBrush, ColorEffect, MaskEffect, BlendEffect).


Tutorial for 2D Scene Graph and Nodes

2D Scene Graph Dynamics

Description

Usually all required nodes are already specified and preconfigured via SceneComposer, so in most cases it won't be necessary to make any adaptations within the scene graph loaded from an asset at runtime.

However, some use cases might require such adaptations. This tutorial covers the most common means to manipulate the scene graph structure dynamically.

Example Solution

  • SceneGraphDynamicsSolution_2D in folder cgi_studio_player/content/Tutorials/03_SceneGraphAndNodes


Adding and Removing 2D Nodes

Scene Graph Dynamics 2D Example

There are some differences between a 2D and a 3D scene graph. This chapter will explain the differences in detail.

For 2D scene graph dynamics, the following examples can be used:

  • SceneGraphDynamicsSolution_2D from folder cgi_studio_player/content/Tutorials/03_SceneGraphAndNodes
  • SceneGraphDynamicsWidget_2D
Adding and Removing 2D Nodes

In the widget a RenderNode with text is created and added/removed to/from the 2D scene graph. For adding and removing 2D nodes on the scene graph the following properties can be used:

  • AddToNode2D: Select the node where the new RenderNode should be attached. This node can be any sub-type of Candera::Node2D.

        CdaProperty(AddToNode, Candera::Node2D*, GetAddToNode, SetAddToNode)
            CdaDescription("Node2D, where a new TextNode shall be added to")
            CdaCategory("Adding and Removing 2D Nodes")
        CdaPropertyEnd()

  • TextNodeStyle : Sets the style to be used for the text node. This allows the user to provide a custom style for the dynamic text.
CdaProperty(TextNodeStyle, Candera::TextRendering::SharedStyle::SharedPointer, GetTextNodeStyle, SetTextNodeStyle)
 CdaDescription("Style for the new Text Node ")
 CdaCategory("Adding and Removing 2D Nodes ")

  • AddTextNode: If AddTextNode is enabled, a new text node with the selected font is created and attached to the selected node. If disabled, the text node is removed from the scene graph again.

        CdaProperty(AddTextNode, bool, GetAddTextNode, SetAddTextNode)
            CdaDescription("If AddTextNode is enabled, a new TextNode with Selected TextNodeStyle Will be added to the selected AddToNode property. If disabled the new TextNode will be removed.")
            CdaCategory("Adding and Removing 2D Nodes")
        CdaPropertyEnd()
Creating a new Text Node

To display new text, a new TextNode2D is created. TextNode2D represents a Render Node (Candera::TextNode2D). It requires a BitmapBrush effect for rendering. The Candera::BitmapTextNodeRenderer then generates the bitmap images from the given text, and these images are finally rendered using the attached Bitmap Brush effect.

Create an instance of BitmapBrushEffect and a text node for the scene graph using the following code used.

BitmapBrushColorBlend::SharedPointer bbc = BitmapBrushColorBlend::Create();
bbc->GetColorEffect().Color().Set(Color(255 / 255, 255, 255 / 255, 1));
m_addedNode = TextNode2D::Create();
m_addedNode ->AddEffect(bbc.GetPointerToSharedInstance());
m_addedNode ->SetName("AddedTextNode");
m_addedNode ->SetText("AddedNode");
m_addedNode ->SetStyle(m_textNodeStyle);
m_addedNode ->SetTextNodeRenderer(BitmapTextNodeRenderer::Create());
m_addedNode ->SetPosition(100, 200);    
Adding Text Node to Scene Graph

Next the text node is uploaded to VRAM and appended to the selected node.

        static_cast<void>(m_addedNode->Upload());
        static_cast<void>(m_addToNode->AddChild(m_addedNode));
Removing and Destructing Text Node

A 2D node can be removed by calling Candera::Node2D::RemoveChild. This is also described in the 3D part of this chapter (see Removing and destructing the Billboard). For removing and completely destructing the node, following code is used:

        static_cast<void>(m_addedNode->Unload());
        m_addedNode->Dispose();
        m_addedNode = 0;

The text node is unloaded from VRAM. The method Candera::Node2D::Dispose removes the node from its parent and frees the resources.

In SceneComposer the manipulated scene graph and the changed properties do not appear because it is only changed dynamically by the widget.

The widget must control memory and VRAM management of its internal dynamic subtree autonomously.

Replacing a 2D Effect 

Replacing an Effect

In the SceneGraphDynamicsWidget_2D example, the effect of a Candera::RenderNode can be either replaced by a Candera::SolidColorBrushBlend or a Candera::BitmapBrushBlend effect. Of course the same procedure works for Text effects.

To replace the effect in the example, the following properties can be used:

  • NodeToChange: Select any render node with an effect whose appearance should be changed. Ensure that just a render node which has an effect is selected (in the sample solution this could be e.g. the node BitmapNode_BarTop). For further information see Adding Replacing Effect.

        CdaProperty(NodeToChange, Candera::Node2D* , GetNodeToChange, SetNodeToChange)
            CdaDescription("Select a RenderNode which effect should be exchanged")
            CdaCategory("Replacing a 2D Effect")
        CdaPropertyEnd()

  • ChangeEffect: If ChangeEffect is enabled, the effect of the selected NodeToChange will be removed and a Candera::SolidColorBrushBlend effect is added to the node, instead. If disabled, the actual effect will be removed too and a Candera::BitmapBrushBlend effect is added to the node.

        CdaProperty(ChangeEffect, bool, GetChangeEffect, SetChangeEffect)
            CdaDescription("Change effect of selected NodeToChange. Set either a SolidColorBrushAlphaBlend (enabled) or BitmapBrushAlphaBlend effect (disabled).")
            CdaCategory("Replacing a 2D Effect")
        CdaPropertyEnd()
Creating a Color Effect

First a new effect is created by creating an instance of Candera::SolidColorBrushBlend.

2D effects are part of the device package, therefore in order to create a concrete instance of the effect, the effect must be enabled for the device. Please see chapter 2D Effects of this tutorial to see how to enable an effect for a certain device.


        Candera::SolidColorBrushBlend::SharedPointer brush = Candera::SolidColorBrushBlend::Create();
        brush->GetSolidColorBrush().Size().Set(m_Rect);
        brush->GetSolidColorBrush().Color().Set(m_ColorBlue);
        brush->GetBlendEffect().SetBlendMode(RenderDevice2D::SourceAlpha, RenderDevice2D::InverseSourceAlpha, RenderDevice2D::Add);
Adding Replacing Effect

Once the effect is created it can be added to the render node. In the following code snippet the selected NodeToChange is cast to a Candera::RenderNode (for this a RenderNode must be selected, otherwise the cast will fail). The first effect of the node is removed via its reference and the new created effect added.

    RenderNode* rn = Dynamic_Cast<RenderNode*>(m_nodeToChange);
    if (rn == 0) {
        return;
    }

    static_cast<void>(rn->Unload());

    //remove old effect
    Effect2D* oldEffect = rn->GetEffect(0);
    static_cast<void>(rn->RemoveEffect(oldEffect));

    //add new effect
    static_cast<void>(rn->AddEffect(effect));
    static_cast<void>(rn->Upload());
Creating a Bitmap Effect

Creating a Candera::BitmapBrushBlend effect is similar to creating other effects (see Creating a Color Effect). Just apply other properties.

        Candera::MemoryManagement::SharedPointer<Candera::BitmapBrushBlend> brush = Candera::BitmapBrushBlend::Create();
        Bitmap::SharedPointer bitmap = Base::GetAssetProvider()->GetBitmapById(CgiAssetNames::cosmosbgBmp);
        if (m_image != 0) {
            static_cast<void>(m_image->SetBitmap(bitmap));
        }
        brush.GetSharedInstance().GetBitmapBrush().Image().Set(m_image);
        brush.GetSharedInstance().GetBlendEffect().SetBlendMode(RenderDevice2D::SourceAlpha, RenderDevice2D::InverseSourceAlpha, RenderDevice2D::Add);


In SceneComposer the manipulated scene graph and the changed properties do not appear because it is only changed dynamically by the widget.

The widget must control memory and VRAM management of its internal dynamic subtree autonomously.

Don't forget to set a name for the dynamically created nodes.

Cloning 2D Nodes 

Cloning 2D nodes is equivalent to cloning of 3D nodes. In accordance to Tree Cloning Strategies, the following support is provided for deep cloning:

Refer to

as well as the example for cloning 3D nodes:

Tutorial for 2D Scene Graph and Nodes

Camera / Viewport 

The following example shows the interaction between scene and camera. The objects are placed relatively to the scene origin (which is the root node). The camera defines the view to that scene by defining also a position relative to the scene.

drawing-4-1676947701.png

The view port dimensions (width and height) relate to the area on the render target the camera is painting on.

drawing-4-1676947712.png

Tutorial for 2D Scene Graph and Nodes

2D Layout

Description

In a typical user interface users need to distribute screen space to different elements. If the size of elements is dynamic, it's desired that the screen space distribution is done automatically based on the contents's natural size. To enable easy arrangement of elements, Candera2D supports layout functionalities.

Example Solution

  • Layout2DSolution in folder cgi_studio_player/content/Tutorials/Layout2DSolution.zip


Candera::GridLayouter 

Introduction

Basically, in SceneComposer for each Candera::Group2D a Candera::Layouter can be attached. The attached layouter defines the arrangements of the group's children.

Cell Spanning

Candera::GridLayouter provides a feature that allows joining of adjacent columns and rows into a larger single cell.

Cells can be joined horizontally, vertically or both. In order to achieve this, you must specify Row Span and Column Span properties for elements inside a Candera::GridLayouter:

  • Row Span: Specifies the number of rows that the element should span across.
  • Column Span: Specifies the number of columns that the element should span across.

Default value for row and column span properties is 1.

Example

The Layout2DSolution provided in the content folder of cgi_studio_player gives an example how the 2D layout can be applied in SceneComposer. This section gives just a brief overview on layout features and shows how it looks like.

In the example on the outermost group, a Candera::GridLayouter with 6 rows and 3 columns is defined.

All child nodes, here a bunch of bitmap-, text- and solid-nodes, are arranged along the grid. Each inner grid element can set various dynamic properties which define its size and alignment inside a grid cell.

Layouters can be nested. The outer grid contains a inner grid and two inner stack layouters (Candera::StackLayouter).

drawing-4-1676947861.png

The example consists of a grid layouter with fixed size 1280 x 756 px.

  • The 3 column's width values are: 100 / 0,5 / 0,5.
    • The first column has a absolute width of 100 px,
    • the second and third column share the rest of the space (1180 px) relatively to each other (indicated by 0.0 <= value <= 1.0).
    • In fact the effective width column 2 and 3 is calculated by the layouter as follows: (1180 * 0,5) / (0,5 + 0,5) = 590 px.
  • The row heights are defined as 126 / 0,1 / 0,1 / 0,1 / 0,1 / 0,1..
    • The first row has a absolute height of 126 px,
    • and the remaining height (630 px) is partitioned relatively to the other rows. Row height = (630 * 0,1) / (0,1 + 0,1 + 0,1 + 0,1 + 0,1) = 126 px.

If a size value is set to -1 the width or height is set to the preferred size of the inner elements.

The bitmaps in the left column have different alignments (Top-Left, Centered, Bottom-Right). In the inner grid 9 bitmaps are scaled down (by the stretching property) to a fixed element size.

The grey grid raster in the background is set up manually by solid nodes to illustrate the inner grid borders.

Cell Spanning Example

In the example, below a Candera::GridLayouter with 5 rows and 4 columns defined is used to exemplify how cell spanning is working:

  • Horizontal Spanning: Element in Row(0) and Column(1) has a Column Span of 2.
  • Vertical Spanning: Element in Row(1) and Column(3) has a Row Span of 3.
  • Horizontal and Vertical Spanning: Element in Row(2) and Column(0) has a Row Span of 2 and Column Span of 2.
drawing-4-1676947967.png

Please note that single cells might still be used for other elements if needed, like in the following example:

A Candera::GridLayouter with 2 rows and 2 columns is defined. Element in (0,0) has a row and column span of 2, but element at (1,1) is still accessible.

drawing-4-1676948004.png



See also:

Candera::BaseLineLayouter 

Introduction

Candera::BaseLineLayouter behaves similar to a Horizontal StackLayouter but instead of aligning the objects to the client area of the Layouter, it aligns them to a line.

The idea is to be able to align text with different sizes in different ways.

  • For instance it can align all the text by the top of its bounding box (VTop).
  • It can align the text by taking into consideration the bottom of the bounding box (VBottom).
  • Or, the most interesting feature it can align the items with different size by their base line (VCenter).
Example

The Layout2DSolution provided in the content folder of cgi_studio_player gives an example how the 2D BaseLineLayouter can be applied in SceneComposer.

  • In the Scene2DBaseLineLayouter scene, on three groups, a Candera::BaseLineLayouter is defined.
  • The base line offset configured on the groups is visualized by an orange line.
  • The text parts of each group are defined with "VTop", "VBottom", or "VCenter", for the Vertical Alignment attribute.

The baseline can be either fixed or automatic. The automatic baseline is configured such that all the children fit from the top of the client area.

drawing-4-1676948049.png

See also:

Candera::OverlayLayouter 

Introduction

A Candera::OverlayLayouter is a container that layouts enfolded nodes by overlaying them in same place in a sorted sequence. With this layouter, elements can be arranged that they fulfill a given area and lay on top of one another.

Example

The Layout2DSolution provided in the content folder of cgi_studio_player gives an example how the OverlayLayouter can be applied in SceneComposer. Some important use cases are:

  • The outer group represents the given area, in which the nodes are arranged. You can set the size of the group to define the height and the width of the area. If you don't set any size, it will automatically be set to the size of the biggest inner child element.
  • The sequence of the nodes in the overlay group is important. In the example the Inner_Overlay_Group is displayed in front of the Outer_Overlay_Group.
  • Three very small images (mainBmp, gradient_verticalBmp, gradient_horizontalBmp, see in ExampleSolution) are stretched and blended to create a big image. They fulfill the given area.
  • The text nodes are aligned to the center and in the corners. You can set several vertical and horizontal alignments for each node.
drawing-4-1676948095.png

See also:

Candera::DockPanelLayouter 

Introduction

The Candera::DockPanelLayouter provides an easy docking of elements to the left, right, top, bottom or center of the panel. To dock an element to the center of the panel, it must be the last child of the panel.

Example

The Layout2DSolution provided in the content folder of cgi_studio_player gives an example how the DockPanelLayouter can be applied in SceneComposer.

drawing-4-1676948156.png

See also:

Automatic Resize Depending on Content 

Introduction

This tutorial will show how to create resizable dialog popups using a hierarchy of groups and different kind of nodes (bitmap, solid color and text) arranged specifically using layouters.

Examples


Resizable Dialog Example Using Grid Layouter

For this example the dialog elements are arranged using Grid and Overlay layouts.

Group Hierarchy

Create the parent group for the pop-up and name it GridDialog. Create another two groups (Background and Content) and add them as children of GridDialog group.

drawing-4-1676948340.png

Layout Configuration

Set the layout for each group as shown in the image below:

drawing-4-1676948359.png

Configure the column width and row height:

drawing-4-1676948390.png

In SceneComposer this can be achieved in the "Grid Layout Editor" (right click on the Background group and select "Configure Layout").

Adding Nodes

The Background group should be populated with bitmap or solid color nodes with the following settings:

drawing-4-1676948508.png

The Content group might be filled with one or more nodes of any type, as desired:

drawing-4-1676948531.png

Result

After putting everything together the dialog will look as above:

drawing-4-1676948555.png

Resizable Dialog Example Using DockPanel Layouter 

For this example the dialog elements are arranged using DockPanel and Overlay layouts.

Group Hierarchy

Create the following structure of groups:

drawing-4-1676948675.png

Layout Configuration

Set the layout for each group as shown in the image below:

drawing-4-1676948697.png


Be sure the order of adding the children groups in the Background group is Top - Bottom - Center otherwise the elements will not be arranged correctly.

Adding Nodes

The children groups of Background should be populated with bitmap or solid color nodes with the following settings:

drawing-4-1676948734.png

Be sure the order of nodes in the groups is Left - Right - Center otherwise the elements will not be arranged correctly.

The Content group should be configured as explained in the previous chapter for the dialog based on grid layout with the only difference that the text node will be replaced with a bitmap. Please see the Content group part from the Adding Nodes section.

Result

After putting everything together the dialog will look as above:

drawing-4-1676948769.png

Tutorial for 2D Scene Graph and Nodes

Candera 2D Listener

Applications can receive notifications on scene graph events. Therefore implement a listener, which defines hooks to receive those notifications.
Following listener interfaces are provided in Candera 2D:

  • Candera::Camera2DListener defines hooks that are called before or after a Camera2D is rendered. In order to register a Camera2DListener to a Camera2D simply derive from Camera2DListener and override pure virtual functions with custom code.
  • Candera::Renderer2DListener defines hooks that are called before or after a node is rendered. It supports events for OnNodePreRender and OnNodePostRender, so you are informed before and after a node is rendered.

Example Listener Implementation

Refer to Using Animation Callback Functions for an example how to implement and use an Animation listener.


Tutorial for Shader Usage

Tutorial for Shader Usage

Programmable Graphics Pipeline

Description

When working with shaders, a fundamental knowledge of OpenGL ES 2.0 / 3.0 and the corresponding GLSL ES 1.0 / 3.0 shader language is required, to unleash the freedom and power programmable graphic pipelines provide, compared to the former fixed-function graphics pipeline. The focus of this chapter is to give the reader a basic overview of the OpenGL ES 2.0 / 3.0 rendering pipeline, in order to understand how OpenGL ES 2.0 / 3.0 works, and how the advantages that come with shader programming can be leveraged.



OpenGL ES 2.0 / 3.0 Graphics Pipeline 

The OpenGL ES 2.0 / 3.0 Graphics Pipeline determines how data stored in Vertex Buffers and Textures get processed, in order to produce the final image in the framebuffer (which might be displayed on screen or an off-screen render buffer, see Tutorial 7).

Only a simplified view is described here, in order to enable the reader to gain a basic knowledge and comprehension of shader programs, their purpose and principles of operation.

Basically the data passed to the OpenGL ES 2.0 API (encapsulated by Candera::Engine3D::Core Classes, such as Candera::VertexBuffer, Candera::Texture) is stored in Vertex Buffers and Texture Memory.

Vertex Buffers get processed by the Vertex Shader unit, and are transformed and projected from model space into world space, and finally screen space. This data is then assembled into primitives (triangles, lines, points) which subsequently get rasterized to determine which pixels are covered by the primitive and are subject to be rendered.

Pixels at this stage of the pipeline, are known as fragments, as they might be discarded by the pipeline in subsequent tests and thus never appear as a pixel in color buffer.

The Fragment Shader then takes these fragments, interpolates the attributes of each vertex inside a primitive and combines these with the colors stored in Texture Memory. After applying some tests, blending and dithering, the final pixel gets written to the Framebuffer. The following image describes this workflow graphically.[2]


drawing-4-1677048280.png


The following table shortly describes the single stages in the image above.

Stage Description
API OpenGL ES application programming interface, this is encapsulated by Candera.
Vertex Arrays / Buffer Objects Buffer in VRAM that stores vertices along with their vertex attributes. Vertex attributes are typically positions, normals, texture coordinates, colors etc. which are stored for every vertex of every primitive. This array is filled and managed by Candera::VertexBuffer.
Vertex Shader Processes the data stored in vertex buffer, and transforms them to screen coordinates with depth.
Primitive Assembly Assembles the data processed shaded vertices into individual geometric primitives that can be drawn. Such primitives can be triangles, lines or points. The necessary settings here are also set in Candera::VertexBuffer.
Rasterization Single primitives are converted to a set of two-dimensional fragments (describing the normalized position in the framebuffer). This is the input data to the fragment shader.
Texture Memory Texture memory is the location in VRAM where textures (2D or CubeMaps. In OpenGL ES 2.0, 1D and 3D textures are only supported through extensions) are stored. This memory is filled and managed by Candera::Texture.
Fragment Shader Fragment shader takes the processed fragments as input, and combines them with the data stored in Texture memory. This stage results in assigning the fragment a color.
Per-Fragment Operations Each fragment is tested, to determine whether it is to be drawn. Blending and dithering operations conclude the pipeline. Various configurations are set, using Candera::RenderMode. Here Depth-, Stencil-, Scissoring Tests, Blending and Dithering apply.
Framebuffer Includes color, depth-, and stencil buffer to store final render output.

This chapter described the stages Vertex Shader and Fragment Shader. The next chapter explains how to interact and control programmable shaders.

Programmable Shaders 

Description

This chapter is about user-programmable Vertex and Fragment Shader units.

Chapters


Shader Programs Introduction 
Programmable Shaders

Before OpenGL 2.0 and OpenGL ES 2.0, Vertex Shader and Fragment Shader units had fixed functionality. The common wording for this is "fixed function pipeline". Since OpenGL 2.0 and OpenGL ES 2.0 the behavior of these shaders became user-programmable. The wording is "programmable pipeline".

Being able to program these stages of the pipe offers a whole new set of opportunities. The shaders still have the same inputs and outputs, but the way in which these outputs are generated can now be defined by the user. Also, using user-defined variables may now be passed to the shader.

Programmable Vertex Shader units offer the possibility of manipulating every single vertex of geometry in a user-defined way, using different input attributes, user-defined uniform variables or even textures to compute the final outcome.

Fragment operations allows the final color to be computed from different sources, as the user wishes. This gives the user the powerful ability to produce almost every desired effect. Typical examples range from per pixel-lighting, normal mapping, multi-texturing, environment mapping (using cubemaps) and many more.

However, there is one small disadvantage. The "fixed-function" pipeline has been replaced by the programmable one, which forces the user to re-implement any behaviour of the "fixed function pipeline", which may be required.

Shader Programs

A shader program is the user-generated code that gives the pipeline its user-defined behavior. A user of OpenGL ES 2.0 / 3.0 has to provide this before rendering any object. In Candera this is encapsulated in Candera::Shader as described in Tutorial 3. A shader program consists of two parts, a vertex shader and a fragment shader, which must be linked together to form the shader program.

These two shader objects are generated from a special programming language known as the OpenGL Shading Language (ES) or GLSL (ES), which uses a C-like syntax. The method in which the shader is passed, is platform dependent. On Desktop GPU's the shader compiler is normally integrated into the graphics driver, and therefore the GLSL source is directly passed to Candera::Shader::SetVertexShader and Candera::Shader::SetFragmentShader, while on some target devices, pre-compiled binaries are expected.

For a detailed step-by-step tutorial see the corresponding section. What follows is an overview on how the different types of shader objects work and which data they process.


Vertex Shaders 

Vertex shader objects are the program objects that give Vertex Shaders user-defined behavior. The following image illustrates the input and output data processed by this kind of shader object..[3]

drawing-4-1677048500.png

Attributes are the data components of a vertex, describing values such as position, normal, texture coordinates, color, tangent or binormal. This data is stored inside the vertex buffer and is the input that comes over the OpenGL ES 2.0 / 3.0 pipeline. The vertex shader is executed once for each incoming vertex.

Uniforms and Samplers (actually also passed as uniform) are user-defined input variables that are passed to every instance of a shader. A user defines it once for a shader object, but all shaders get this value. This input can consist of 4x4-, 3x3-, 2x2- matrices, 2-4 component vectors, single values and arrays, all of type Float, Int or Bool. Special sampler uniforms are handles to uploaded 2D- or CubeMap Textures. Textures can also be used in vertex shaders, a good example for this is Displacement Mapping, where the values stored in the textures colors are interpreted as vectors to offset the vertices. This can be used e.g. to transform a plane into a virtual terrain.

Varyings are user-defined output variables of a vertex shader, which are finally interpolated between all members of a primitive after primitive assembly. These can have the same data types as the incoming uniforms, except for sampler handles. For each fragment, the interpolated data then gets passed into the fragment shader. Typically texture coordinates are forwarded from the vertex buffer to the fragment shader in this way, but you can pass any kind of data where it makes sense to interpolate between the vertices of a primitive.

Temporary Variables are variables which live inside the scope of one shader object. They can have the same data types as uniforms or varyings.

Vertex Shader Special Output Variables gl_Position and gl_PointSize are only available in vertex shaders. gl_FrontFacing is not available in OpenGL ES, but only in full OpenGL. gl_Position is reserved for writing the vertex position in homogeneous coordinates (after transforming into world space, camera space and projected to screen space). This value has to be written by every vertex shader, in order to enable primitive assembly and rasterization. Not setting gl_Position results in undefined behavior. gl_PointSize can be used optionally to set the point size of vertices, if the primitive type points is used.


Fragment Shaders 

Fragment shader objects are the program objects that give Fragment Shaders user-defined behavior. The following image illustrates the input and output data processed by this kind of shader object.[4]

drawing-4-1677048673.png


Varyings are user-defined input variables for a fragment shader. These values have to match the varyings in the vertex shader (data type and name). A fragment shader is called once for each rendered pixel, which implies that the incoming varyings hold the interpolated value of all varyings passed per primitive from the vertex shader.

Uniforms and Samplers are user-defined input variables that are passed to every instance of a shader. They have the same meaning as in the vertex shader. Additionally uniforms in vertex and fragment shaders can have the same data type and name. In this case, when passing the variables value, it'll be passed to both the vertex and the fragment shader.

Temporary Variables are variables which live inside the scope of one shader object. They can have the same data types as uniforms or varyings.

Fragment Shader Special Input Variables gl_FragCoord, gl_FrontFacing and gl_PointCoord are read-only variables only available in fragment shaders. gl_FragCoord holds the window relative coordinates x,y,z and 1/w values for the fragment. Z-value is the value that's used for depth testing after passing the Fragment Shader stage. gl_FrontFacing holds a boolean value which determines whether the fragment belongs to a front facing primitive (true) or not (false). gl_PointCoord specifies where the fragment is located, within a point primitive. If the primitive is of type line or triangle, this value is undefined.

Fragment Shader Special Output Variable gl_FragColor is used to write the output of the fragment shader into the current fragment. gl_FragColor holds a 4-component vector of floats ranging from 0.0 to 1.0 for each component. This describes the RGBA color of the current fragment, used by the rest of the OpenGL ES 2.0 / 3.0 rendering pipeline. This will be the color you actually see on the screen or will be blended against another color, if the fragment passes all succeeding tests. Not setting this value will result in undefined colors on the screen.


References 

The knowledge presented in this tutorial is a summary of:

[1] Munshi, Aaftab; Ginsburg, Dan; Shreiner, Dave: OpenGL ES 2.0 Programming Guide. Addison Wesley, 2008. ISBN 978-0-321-50279-7

The images were taken from:

[2] Munshi, Aaftab; Ginsburg, Dan; Shreiner, Dave: OpenGL ES 2.0 Programming Guide. p.4. Addison Wesley, 2008. ISBN 978-0-321-50279-7

[3] Munshi, Aaftab; Ginsburg, Dan; Shreiner, Dave: OpenGL ES 2.0 Programming Guide. p.5. Addison Wesley, 2008. ISBN 978-0-321-50279-7

[4] Munshi, Aaftab; Ginsburg, Dan; Shreiner, Dave: OpenGL ES 2.0 Programming Guide. p.8. Addison Wesley, 2008. ISBN 978-0-321-50279-7



Tutorial for Shader Usage

The OpenGL ES Shading Language

Description

The focus of this chapter is to understand how to write shader programs in conjunction with Candera. In OpenGL ES 2.0 / 3.0, the OpenGL ES Shader Language (GLSL ES) is used as programming language for creating various effects, from simple texturing to stunning effects, like bump mapping, anisotropic lighting, and so forth. The programmable pipeline of ES 2.0 is also valid for ES 3.0, but the shading language has been extended somewhat. OpenGL ES 2.0 uses the GLSL ES 1.0 shading language. OpenGL ES 3.0 uses the GLSL ES 3.0 shading language.

For more detailed information you may refer to the GLSL ES specification or to the OpenGL ES 2.0 Programming Guide. Additionally, vast resources of GLSL as well as GLSL ES shader tutorials can be found on the internet. GLSL is to a large extent compatible to the GLSL ES shader language.

See also:

For hints on how to optimize shaders refer to



A Minimal Shader Program

Here is a small example to demonstrate the syntax of GLSL ES. The following example simply transforms the vertices into clip coordinate space, forwards texture coordinates, and applies a texture to the rasterized image.

Vertex Shader

This is the vertex shader code of our example:

#ifndef GL_ES

// An OpenGL environment might not support precision qualifiers. Thus, define them to void.
#define lowp
#define mediump
#define highp
#define precision

#endif

/*
 Uniforms
 */
uniform mat4 u_MVPMatrix;       // Model-View-Projection Matrix

/*
 Attributes
 */
attribute vec4 a_Position;
attribute vec2 a_TextureCoordinate;

/*
 Varyings
 */
varying mediump vec2 v_TexCoord;

/*---------------------------------- MAIN ------------------------------------*/
void main(void)
{
    /* Transform vertex into world space */
    gl_Position = u_MVPMatrix * a_Position;

    v_TexCoord = a_TextureCoordinate;
}

Purpose of this vertex shader is simply to transform the incoming a_Position vertex attribute into clip coordinate space, using the model-view-transformation matrix provided as uniform u_MVPMatrix. Additionally, the incoming attribute a_TextureCoordinate is forwarded to the next stage as varying v_TexCoord and will be available in the fragment shader, if a varying with same name exists. In the fragment shader the incoming value will be interpolated between the vertices of a primitive.

The preprocessor directives (#ifndef #define ... #endif) simply check if the shader is running inside a OpenGL ES or OpenGL rendering pipeline and defines the precision qualifiers to nothing if not running in an ES context. This simply transforms the OpenGL ES shader into a compatible OpenGL shader if necessary, as the only differences are the precision qualifiers.

Fragment Shader

This is the fragment shader code of our example:

#ifndef GL_ES

// An OpenGL environment might not support precision qualifiers. Thus, define them to void.
#define lowp
#define mediump
#define highp
#define precision

#endif

/*
 Uniforms
 */
uniform sampler2D u_Texture;

/*
 Varyings
 */
varying mediump vec2 v_TexCoord;

/*---------------------------------- MAIN ------------------------------------*/
void main(void)
{
    gl_FragColor = texture2D(u_Texture, v_TexCoord);
}

Purpose of this fragment shader is to query the color from the passed sampler u_Texture (handle to a texture) at the address of the incoming varying v_TexCoord and to apply this color to the fragment. This will be the color that is finally displayed, if all succeeding tests have passed. Nothing more, nothing less. It can be observed that the varying variable v_TexCoord has the same name than in the vertex shader.

The preprocessor fulfills the same purpose as in the vertex shader.

Output

The following image shows the result of rendering a vertex buffer (containing vertices of a cube) using the vertex and fragment shader above. The model-view-projection matrix was set to rotate the cube, a 2D texture was applied to it as sampler2D u_Texture.

drawing-4-1677049271.png


Basic OpenGL ES 2.0 Shading Language elements

Basic OpenGL ES 2.0 / 3.0 Shading Language elements

The subject of this section is to comprehend the syntax and structure of the vertex and fragment shaders, presented in the lean shader example, see the previous A Minimal Shader Program. Knowledge of C like languages is assumed to understand GLSL syntax.

Declaring Uniform variables

The qualifier uniform is used to declare immutable variables in vertex or fragment shaders, which are set from client code.

Uniforms are global variables, and therefore have to be declared outside of function scope.

In our example the following uniforms are used:

 uniform mat4 u_MVPMatrix; 

u_MVPMatrix is used to pass the model-view-projection matrix to the vertex shader object, which is used to transform all vertices.

 uniform sampler2D u_Texture; 

u_Texture is the handle to the texture, which is addressed using the texture2D function.

Declaring Varying variables

The qualifier varying is used in two different ways.

  • In the vertex shader it is used to declare variables which are written by the shader, and are further passed over primitive assembly and rasterization. Therefore they get interpolated inside the fragments, which are finally input of the fragment shader.
  • In fragment shader the same keyword is used to declare incoming variables. Like uniforms these varying variables are global, and therefore have to be defined outside function scope.

Varying variables have to have exactly the same name and type in vertex and fragment shader, otherwise linking of them together will fail.

In our example only one varying is used:

 varying mediump vec2 v_TexCoord; 

v_TexCoord is used to pass the texture coordinates stored inside the vertices attributes to the fragment shader. While passing the OpenGL ES 2.0 pipe they get interpolated between the vertices of a primitive. Inside the fragment shader the interpolated coordinates are finally used to address the texture sampler, in order to determine the final color of a fragment.

Precision Qualifiers

Compared to "full" GLSL, GLSL ES has the specialty of precision qualifiers, which can be used to declare variables to be handled with different precision. Reason for this language feature was the focus on embedded hardware, which might provide faster medium precision floating point or integer units than the high precision ones. Precision of a float or uniform variable is defined using one of the qualifiers lowp, mediump or highp.

For further details on how these kinds of variables behave when they get assigned to each other, or when arithmetic is used with different precision, please refer to the GLSL ES standard.

In our example this was used for the varying variable in both shaders:

 varying mediump vec2 v_TexCoord; 
Function Declaration

Functions can be defined like in almost every other C-like programming language. Function definitions follow the syntax shown below:

returnType functionName (type0 arg0, type1 arg1, ... , typen argn)
{
    //Method Body
    return returnValue;
}

Like in C they can be prototyped with leaving the method body and closing the declaration using a semicolon. For calling the function of course it has to be defined.

A function then is called using its name followed by a list of arguments in parentheses (or empty ones if no arguments apply).

Function names can further be overloaded as long as the argument types are different.

Return and argument types can be any type allowed in GLSL (such as int, float, double, vec4, mat4, sampler2D, ...). Additionally returnType can be void, which indicates that the function doesn't return anything. Arrays can be passed as argument, but cannot be returned.

In our examples above only one function is defined for the vertex and the fragment shader:

void main(void)
{
    //Main Body
}

The main function is mandatory for both vertex and fragment shader, this is the entry point to the shader. It has to take no argument and has to return no value (void). From here further functions can be called.

Writing Output Data

There are two possibilities to output a vertex shaders result.

  • The first and mandatory way is to write the transformed vertex coordinates in clip coordinate space to gl_Position. This one is required to be set by any vertex shader. The behavior is undefined, if gl_Position is not set.
  • The second, optional way is to use variables declared as varying to output any desired data, as described in the varying section. The content of varyings is passed as input to the fragment shader, the fragment shader will receive the interpolated values per transformed primitive.

Results of fragment shaders are written to gl_FragColor, which is a 4-component vector (vec4) representing the final color that will be displayed if all further tests pass. It is possible to use the discard keyword to abort a fragment shader based on conditions, which might be useful if some fragments shall be completely skipped, in this case also no depth or stencil value will be written. This trick can be applied for e.g. creating an interlace mask for interlaced stereoscopic 3D.

Accessing textures

One very important function of fragment shaders is to fetch colors from textures and use it for composing the final gl_FragColor. This task is done using the GLSL ES built-in function texture2D:

vec4 texture2D (sampler2D sampler, vec2 coord );

where sampler is the handle to the texture to query the color from, coord is the texture coordinate pair to address a certain "texel" (point of a texture).

The sampler2D variable has to be passed as uniform to the shader, this is the OpenGL handle to the texture in VRAM. In our simple fragment shader example the color read from the texture is passed directly as gl_FragColor, therefore the resulting cube has the wall image directly applied.

The following snippet shows how the uniform containing the texture handle is declared.

uniform sampler2D u_Texture;

The following incoming varying vec2 is used to address a texel.

varying mediump vec2 v_TexCoord;

The final color is queried from the texture using the texture2D function.

gl_FragColor = texture2D(u_Texture, v_TexCoord);

Texturing can also be used in vertex shaders. This can be used to e.g. realize a technique called Displacement Mapping where the vectors stored inside a texel are not interpreted as color but as offset being applied to the current vertex position. Consequently, a displacement map can be used e.g. to transform a flat plane to a terrain.

Also other types of samplers (e.g. samplerCube) and corresponding texture addressing functions (e.g. textureCube) exist, again for more details please have a look at the GLSL ES Standard.

Commenting shader programs

Comments in GLSL are delimited, as in other C-like languages, by

/* and */

or

 // and EOL.

Everything in between these delimiters will be ignored by the compiler.

Other language features, data types and function library.

For a more detailed view on how to specify control structures, available data types, built-in functions, and other language features please refer to the GLSL ES Standard.




Tutorial for Shader Usage

Shader Usage in Candera

Description

This chapter demonstrates how customized shader programs can be used with Candera. Subject of this chapter depicts how to use the Candera classes to set custom shaders for each node, and how to apply shader parameters. Further, it shows how to realize different classes of effects, namely single pass-, local multi pass- and global multi pass effects.

This section is about on how the already introduced Candera::Appearance class and its members can be used to realize customized shader effects.

Loading shader programs

First step to realize shader programs is to actually write them. See the GLSL chapter for details on that. For a developer it is recommended to develop using SceneComposer, where the effects can be seen immediately in the Scene Editor window.

This tutorial further focuses on programming with Candera, but the concepts are supported as well in collaboration with SceneComposer. Beside of SceneComposer's shader editor, shader authoring tools like AMD's RenderMonkey can also be used. Benefits from using either SceneComposer or shader authoring tools are syntax highlighting, seeing compiler and linker errors and having a preview scene where the effect can be examined. One unique advantage of using SceneComposer is the support for device dependent shader compilers.

After having finished your shader program you need to load it in your application based on Candera. Use your favorite file I/O libraries among the available ones. It depends on the platform if the underlying OpenGL ES implementation needs the shader source or pre-compiled shader objects. On host you typically load the source of your shader program directly, it will be compiled and linked at runtime by the OpenGL driver invoked by Candera's Upload mechanisms. On targets you might need to provide pre-compiled shader objects, where pre-compiling is done using dedicated shader compile tools. Please refer to the device manufacturers manual.

Using Candera::Shader

Candera::Shader is the class that abstracts from OpenGL ES shader interfaces and manages the shaders handle after creation. For a small description see the tutorial on the Appearance class. See below how to use the class.

First you need to create an instance of the Candera::Shader class using its Create() method.

static MemoryManagement::SharedPointer<Shader> Create();

Next step is to provide the loaded shader sources or objects to the class using the according setters:

bool SetVertexShader(const void* vertexShader, DisposerFn disposerFn);
bool SetFragmentShader(const void* fragmentShader, DisposerFn disposerFn);

The shaders are passed as void* to ensure that platform dependent representations of the shader program can be passed. The parameter DisposerFn hands over lifetime responsibility of shader passed: If it's set to 0 you'll have to take care of the provided void* by yourself.

Finally, the shader needs to be associated to a node's appearance in order to leave the remaining steps (like Uploading and further Activating) to Candera. Upload takes care of compiling, linking, and retrieving the shaders handle. Vertex attribute binding is done in the appearances activation method, automatically.

The following code snippet demonstrates how a Billboard node with a custom shader can be created.

void Initialize() {
    Char* vertexShader = "";
    Char* fragmentShader = "";
    // Do File I/O here, filling vertexShader and fragmentShader with the read data.

    Scene* scene = Scene::Create();
    //Do camera setup here, add it to scene graph.

    SharedPointer<Shader> shader = Shader::Create();
    shader->SetVertexShader(vertexShader, 0); //vertexShader needs to be disposed manually.
    shader->SetFragmentShader(fragmentShader, 0); //fragmentShader needs to be disposed manually.

    Billboard* billboard = Billboard::Create(2.0f, 2.0f);
    billboard->SetAppearance(Appearance::Create());
    billboard->GetAppearance()->SetShader(shader); //Here the previously instantiated and configured shader is added.
    //Set and configure remaining appearance members.

    scene->AddChild(billboard);

    scene->UploadAll(); //All nodes, all their Appearance objects and therefore all set shader objects get uploaded.

}

void Render() {
    Renderer::RenderAllCameras(); //Here the scene graph gets traversed, for every node the shaders get activated, and the vertex buffers attribute get bound
                                  //to the shaders attributes.
}
Auto Uniforms

Candera::ShaderParamSetter and derived classes are used to set uniform parameters of shaders, they abstract from OpenGL ES's glUniform_ interfaces. They are part of a node's appearance, how to use them is already described in the Appearance Tutorial.

As described the derived class Candera::GenericShaderParamSetter offers to automatically compute and set shader parameters from the configured scene graph. The following table shows which uniforms are reserved for this purpose, what Candera classes they affect and what their semantics are.

Note:
The values of reserved variables cannot be edited in SceneComposer, here the Candera::GenericShaderParamSetter has to be used.

The table is structured as follows:

  • Column Uniform Variable is the name of the reserved variable.
  • Column Data Type describes its data type.
  • Column GenericShaderParamSetter Flag describes which properties have to be enabled in Candera::GenericShaderParamSetter in order to compute and set the variable.
  • Column Affected Classes describes which classes are used to compute the parameters.
  • The last parameter Semantic describes for what purpose this parameter is needed.

If no instance of the affected classes exist, the corresponding shader parameters will not be set, even if they are enabled in Candera::GenericShaderParamSetter.

Uniform Variable Data Type GenericShaderParamSetter Flag Affected Classes Semantic
u_MMatrix3 Matrix3 ModelMatrix3Enabled Candera::Node Computes the ModelMatrix3 from the Node's transformation data, used to transform 3-component vectors from model to world space.
u_MVMatrix3 Matrix3 ModelViewMatrix3Enabled Candera::Node, Candera::Camera Computes the ModelViewMatrix3 from the Node's transformation data and the currently rendered Camera's ViewMatrix. Used to transform 3-component vectors from model to view space.
u_NormalMMatrix3 Matrix3 NormalModelMatrix3Enabled Candera::Node Computes the NormalModelMatrix3 from the Node's transformation data, used to transform 3-component directional vectors (especially normals) from model to world space.
u_NormalMMatrix4 Matrix4 NormalModelMatrix4Enabled Candera::Node Computes the NormalModelMatrix4 (4×4 normal-transform matrix) from the node’s transform and passes it to the shader.
u_NormalMVMatrix3 Matrix3 NormalModelViewMatrix3Enabled Candera::Node, Candera::Camera Computes the NormalModelViewMatrix3 from the Node's transformation data and the currently rendered Camera's ViewMatrix. Used to transform 3-component directional vectors (especially normals) from model to view space.
u_MMatrix Matrix4 ModelMatrix4Enabled Candera::Node Computes the ModelMatrix from the Node's transformation data, used to transform 4-component vectors from model to world space.
u_MVMatrix Matrix4 ModelViewMatrix4Enabled Candera::Node, Candera::Camera Computes the ModelViewMatrix4 from the Node's transformation data and the currently rendered Camera's ViewMatrix. Used to transform 4-component vectors from model to view space.
u_PMatrix Matrix4 ProjectionMatrix4Enabled Candera::Camera Passes the ProjectionMatrix4 from the active camera to the shader.
u_VPMatrix Matrix4 ViewProjectionMatrix4Enabled Candera::Camera Passes the active camera’s the ViewProjectionMatrix4 (View × Projection) to the shader.
u_MVPMatrix Matrix4 ModelViewProjectionMatrix4Enabled Candera::Node, Candera::Camera Computes the ModelViewProjectionMatrix4 from the Node's transformation data and the currently rendered Camera's ViewProjectionMatrix. Used to transform 4-component vectors from model to homogeneous screen space.
u_CamDirection Vector3 CameraLookAtVectorEnabled, LightActivationEnabled Candera::Camera, Candera::Light Passes the currently rendered Camera's look-at vector in world space to the shader. Transforms it to object space if LightsCoordinateSpace is set to Object. Can be done explicitly, or inside lighting calculations.
u_CamPosition Vector3 CameraPositionEnabled Candera::Camera Passes the currently rendered Camera's position in world space to the shader.
u_Size Float - Candera::PointSprite Passes the size of a Candera::PointSprite node to the shader. This is always done, if node is type of Candera::PointSprite.
u_Material.ambient Vector4 MaterialActivationEnabled Candera::Material Passes the ambient color component of the node's material (member of Appearance, see the section on material.)
u_Material.diffuse Vector4 MaterialActivationEnabled Candera::Material Passes the diffuse color component of the node's material (member of Appearance, see the section on material.)
u_Material.emissive Vector4 MaterialActivationEnabled Candera::Material Passes the emissive color component of the node's material (member of Appearance, see the section on material.)
u_Material.specular Vector4 MaterialActivationEnabled Candera::Material Passes the specular color component of the node's material (member of Appearance, see the section on material.)
u_Material.shininess Float MaterialActivationEnabled Candera::Material Passes the shininess of the node's material (member of Appearance, see the section on material.)
u_Texture[i] Integer TextureActivationEnabled Candera::Texture, Candera::BitmapTextureImage, Candera::ProxyTextureImage Passes the activated textures handle to the shader if the textures Candera::TextureImage is of type Candera::BitmapTextureImage or Candera::ProxyTextureImage(with TextureTargetType Texture2D). i can be from 1 to 7 or nothing (e.g u_Texture, u_Texture1,..).
u_CubeMapTexture[i] Integer TextureActivationEnabled Candera::Texture, Candera::CubeMapTextureImage, Candera::ProxyTextureImage Passes the activated textures handle to the shader if the textures Candera::TextureImage is of type Candera::CubeMapTextureImage or Candera::ProxyTextureImage(with TextureTargetType TextureCubeMap). i can be from 1 to 7 or nothing (e.g u_CubeMapTexture, u_CubeMapTexture1,..).
u_Light[i].type Integer LightActivationEnabled Candera::Light Passes an integer defining the type of light i to the shader, i can range from 0 to 7.
u_Light[i].ambient Vector4 LightActivationEnabled Candera::Light Passes the ambient color component of light i to the shader, i can range from 0 to 7.
u_Light[i].diffuse Vector4 LightActivationEnabled Candera::Light Passes the diffuse color component of light i to the shader, i can range from 0 to 7.
u_Light[i].intensity float LightActivationEnabled Candera::Light Passes the intensity factor applied to the diffuse color of light i to the shader, i can range from 0 to 7. Ignored if the light type is Ambient.
u_Light[i].specular Vector4 LightActivationEnabled Candera::Light Passes the specular color component of light i to the shader, i can range from 0 to 7.
u_Light[i].position Vector4 LightActivationEnabled Candera::Light Passes the position of a point or spotlight i to the shader, i can range from 0 to 7.
u_Light[i].direction Vector3 LightActivationEnabled Candera::Light Passes the direction vector of a directional or spotlight i to the shader, i can range from 0 to 7.
u_Light[i].halfplane Vector3 LightActivationEnabled Candera::Light Passes the halfplane vector of a directional, point or spotlight i to the shader, i can range from 0 to 7.
u_Light[i].attenuation Vector4 LightActivationEnabled Candera::Light Passes the attenuation weights of a point or spotlight i to the shader, i can range from 0 to 7.
u_Light[i].spotCosCutoff Float LightActivationEnabled Candera::Light Passes the cut off angle of a spotlight i to the shader, i can range from 0 to 7.
u_Light[i].spotExponent Float LightActivationEnabled Candera::Light Passes the spot exponent of a directional, point or spotlight i to the shader, i can range from 0 to 7.
u_Light[i].range Float LightActivationEnabled Candera::Light Passes the range of a point or spotlight i to the shader, i can range from 0 to 7.
u_Light[i].enabled Bool LightActivationEnabled Candera::Light Passes whether light i is enabled or not to the shader, i can range from 0 to 7.
u_Light[i].cameraLookAtVector Vector3 LightActivationEnabled Candera::Light, Candera::Camera Computes the look-at vector of the active Camera, and passes it to the shader. Depending on the Light::CoordinateSpace the look-at vector is either in world space or in the object space of the light.
u_MorphWeight Float Array - Candera::MorphingMesh Passes the weights of a Candera::MorphingMesh to the shader. This is always done, if node is type of Candera::MorphingMesh.
u_CanvasPivot Vector2 CanvasActivationEnabled Candera::CanvasRenderable Passes the position of the pivot of a Candera::CanvasRenderable to the shader.
u_CanvasSize Vector2 CanvasActivationEnabled Candera::CanvasRenderable Passes the actual size of a Candera::CanvasRenderable to the shader.
ub_Material Uniform Buffer Object MaterialActivationEnabled Candera::Material Passes all of the material parameters from above in a uniform buffer object to the shader.
ub_Lights Uniform Buffer Object LightActivationEnabled Candera::Light, Candera::Camera Passes all of the light parameters from above in a uniform buffer object to the shader.
u_JointMatrices Matrix4 SkinningMeshActivationEnable Candera::SkinningMesh Passes the Inverse Bind Matrix of a Joint to the shader.
Attribute Names

Candera uses pre-defined attribute names to bind a vertex buffers attributes. When writing shader programs the following attribute names shall be used:

Attribute Name Semantic
PositionAttribute[i] Use this attribute for passing vertex positions (local space). i ranges from 1 to 7 or is nothing (e.g. PositionAttribute, PositionAttribute1,...).
PositionTransformedAttribute[i] Use this attribute for passing transformed vertex positions. i ranges from 1 to 7 or is nothing (e.g. PositionTransformedAttribute, PositionTransformedAttribute1,...).
NormalAttribute[i] Use this attribute for passing the vertices normals. i ranges from 1 to 7 or is nothing (e.g. NormalAttribute, NormalAttribute1,...).
TextureCoordinateAttribute[i] Use this attribute for passing the vertices texture coordinates. i ranges from 1 to 7 or is nothing (e.g. TextureCoordinateAttribute, TextureCoordinateAttribute1,...).
ColorAttribute[i] Use this attribute for passing the vertices colors. i ranges from 1 to 7 or is nothing (e.g. ColorAttribute, ColorAttribute1,...).
BlendWeightAttribute[i] Use this attribute for passing the vertices blend weight attributes. i ranges from 1 to 7 or is nothing (e.g. BlendWeightAttribute, BlendWeightAttribute1,...).
BlendIndexAttribute[i] Use this attribute for passing the vertices blend index attributes. i ranges from 1 to 7 or is nothing (e.g. BlendIndexAttribute, BlendIndexAttribute1,...).
PointSizeAttribute[i] Use this attribute for passing the vertices point sizes. i ranges from 1 to 7 or is nothing (e.g. PositionAttribute, PositionAttribute1,...).
TangentAttribute[i] Use this attribute for passing the vertices tangents. i ranges from 1 to 7 or is nothing (e.g. TangentAttribute, TangentAttribute1,...).
BiNormalAttribute[i] Use this attribute for passing the vertices binormals. i ranges from 1 to 7 or is nothing (e.g. BiNormalAttribute, BiNormalAttribute1,...).
TesselationFactorAttribute[i] Use this attribute for passing the vertices tesselation factors. i ranges from 1 to 7 or is nothing (e.g. TesselationFactorAttribute, TesselationFactorAttribute1,...).
FogAttribute[i] Use this attribute for passing the vertices fog attributes. i ranges from 1 to 7 or is nothing (e.g. FogAttribute, FogAttribute1,...).
DepthAttribute[i] Use this attribute for passing the vertices depth attributes. i ranges from 1 to 7 or is nothing (e.g. DepthAttribute, DepthAttribute1,...).
SampleAttribute[i] Use this attribute for passing the vertices sample attributes. i ranges from 1 to 7 or is nothing (e.g. SampleAttribute, SampleAttribute1,...).
CustomAttribute[i] Use this attribute for passing your custom data per vertex. i ranges from 1 to 7 or is nothing (e.g. CustomAttribute, CustomAttribute1,...).


Single Pass Effects

Single pass effects can be achieved in one camera pass only. They are simply realized with a single Candera::Appearance attached to a Candera::Node.

Examples for single pass effects can be found in the Special Render Techniques chapter.


Local Multi Pass Effects

Local multi pass effects describe effects with local impact only, thus, they belong to one certain node. They can be realized using Candera::MultiPassAppearance. These appearances are processed in a sequence; for each appearance the node is rendered once and the results are blended together using the different RenderMode settings.

Examples for local multi pass effects can be found in the Special Render Techniques chapter.


Global Multi Pass Effects

Global multi pass (also known as screen-based effects) typically operate on render targets, often they are also referred to as post-processing effects. To realize post-processing effects first images have to be rendered into an offscreen render target. How this is done is presented in Render Targets Tutorial.

The resulting image can further be processed in any desired way.

Examples for global multi pass effects can be found in chapter Special Render Techniques.

Creating Full Screen Effects

You can of course process the resulting texture on any mesh or object you like, but often post processing effects are applied to full screen images. For that purpose a dedicated scene with only a camera and a billboard can be used. The trick is to give the Billboard a size of 1.0 unit in both dimensions.

Billboard* fullScreenBB = Billboard::Create(1.0F, 1.0F);

This causes the billboard to have a vertex buffer with coordinates that match the corners of the window/screen in Canderas viewport space. To draw the billboard full screen you just need to use a vertex shader that doesn't transform the vertex coordinates, use ReferenceShaders\Core\RefViewportSpace.vertp for this purpose. The fragment shader then does the actual post processing. As texture for the billboard you need to use the image provided by the offscreen render target.

RefViewportSpace.vertp takes two float uniforms (u_offsetLeft, u_offsetTop) as inputs, which determine the top left corner of the billboard to render. These values are defined as being in normalized viewport space, where values range from 0.0 to 1.0, where 0.0 is interpreted as the left border respectively top border of the viewport, and 1.0 as the right, respectively bottom border.

The vertex position attribute of a vertex buffer used with this vertex shader is not transformed using a model-view-projection matrix, but simply translated into OpenGL's clip coordinate space with the specified offset applied.

The following examples demonstrate how to use the RefViewportSpace.vert shader in conjunction with the depth-of-field global multi pass effect.

Specify a full screen rectangle:

//Do Shader File I/O here and initialize m_dofCombineShader shader object.
Float m_viewportLeft = 1.0F;
Float m_vieportTop = 1.0F;
m_dofCombineSps = GenericShaderParamSetter::Create();
m_dofCombineSps->SetModelViewProjectionMatrix4Enabled(false);
m_dofCombineSps->SetUniform("u_offsetLeft", Shader::Float, &m_viewportLeft);
m_dofCombineSps->SetUniform("u_offsetTop", Shader::Float, &m_viewportTop);
m_fullScreenQuadDof = Billboard::Create(1.0f,1.0f);
m_fullScreenQuadDof->GetAppearance()->SetShader(m_dofCombineShader);
m_fullScreenQuadDof->GetAppearance()->SetShaderParamSetter(m_dofCombineSps);

This results in the following image:

drawing-4-1677054213.png


Specify a centered rectangle with half viewport resolution:

//Do Shader File I/O here and initialize m_dofCombineShader shader object.
Float m_viewportLeft = 0.25F;
Float m_vieportTop = 0.25F;
m_dofCombineSps = GenericShaderParamSetter::Create();
m_dofCombineSps->SetModelViewProjectionMatrix4Enabled(false);
m_dofCombineSps->SetUniform("u_offsetLeft", Shader::Float, &m_viewportLeft);
m_dofCombineSps->SetUniform("u_offsetTop", Shader::Float, &m_viewportTop);
m_fullScreenQuadDof = Billboard::Create(0.5f,0.5f);
m_fullScreenQuadDof->GetAppearance()->SetShader(m_dofCombineShader);
m_fullScreenQuadDof->GetAppearance()->SetShaderParamSetter(m_dofCombineSps);

This results in the following image:

drawing-4-1677054197.png


Specify a rectangle in bottom right corner with quarter viewport resolution:

//Do Shader File I/O here and initialize m_dofCombineShader shader object.
Float m_viewportLeft = 0.75F;
Float m_vieportTop = 0.75F;
m_dofCombineSps = GenericShaderParamSetter::Create();
m_dofCombineSps->SetModelViewProjectionMatrix4Enabled(false);
m_dofCombineSps->SetUniform("u_offsetLeft", Shader::Float, &m_viewportLeft);
m_dofCombineSps->SetUniform("u_offsetTop", Shader::Float, &m_viewportTop);
m_fullScreenQuadDof = Billboard::Create(0.25f,0.25f);
m_fullScreenQuadDof->GetAppearance()->SetShader(m_dofCombineShader);
m_fullScreenQuadDof->GetAppearance()->SetShaderParamSetter(m_dofCombineSps);

This results in the following image:

drawing-4-1677054180.png

Overlapping Billboards (in screen space) would produce depth-fighting artifacts. Disabling depth test and using render order as well as using depth bias can be used to avoid them.


m_fullScreenQuadDof->GetAppearance()->SetRenderMode(RenderMode::Create());
m_fullScreenQuadDof->GetAppearance()->GetRenderMode()->SetDepthBias(0.1F);

For details on how to use render order please refer to Tutorial 3.

Tutorial for Shader Usage

Candera Reference Shaders

Description

This document describes content and usage of Candera Reference Shaders part of CGI Studio releases. Within this document the set of Candera Reference Shaders is listed with additional information how to use and combine them in 3D applications based on Candera.

Location of Reference Shaders 

Candera delivers a set of reference shader implementations, which can be found at following location:

  • \cgi_studio_candera\src\Candera\ReferenceShaders

The same set of reference shaders is available in SceneComposer.

Preferred way of usage is to export shaders to a binary asset file and to load it in an application based on Candera. Shaders part of exported scenes are automatically added to the asset file and loaded by Candera.

Used Include Files 

Some parts of shader code used in Candera Reference Shaders have been collected into several files in order to be reused as include files.

Following files are available and can be used as include files:

Include File Supports Include In Note Displaces
RefEnvironment.inc Platform specific defines All Shaders Shall be included in each Shader as first statement Environment.incv, Environment.incf
RefLighting.inc Lighting specific functions, also functions for anisotropic lighting Shaders that support lighting Useful helper functions for vertex lighting and pixel lighting DefaultLighting.incv
RefTexturing.inc Texturing specific functions, e.g. Environment Mapping Shaders that support certain texture effects Useful helper functions especially for common texture effects Texturing.incv
RefSkinning.inc Skinning specific features Shaders that support skinning Useful helper functions for skinning Skinning.incv

Reference Vertex Shaders 

Vertex Shaders Supports Combine with Fragment Shaders GenericShaderParamSetter Configuration Note
Core/RefTrans Transformation,
Texture
Core/RefTex,
Core/RefUniColor,
Core/RefUniColorTex,
Core/RefTex2LightMap,
Core/RefUniDiffuseMatTex,
Effects/RefTexDofBlur,
Effects/RefTexDofCombine
SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Lighting has no influence.
Core/RefTransColor Transformation,
VertexColor,
Texture
Core/RefColor,
Core/RefColorTex,
Core/RefColorTexAlpha,
Effects/RefColorTexAlphaOutline
SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Lighting has no influence.
Core/RefTransLight1 Transformation,
Vertex Lighting with one light source,
Texture
Core/RefColor,
Core/RefColorTex
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
If Texture is used, depends on corresponding fragment shader only.
Core/RefTransLight2 Transformation,
Vertex Lighting with two light sources.
Texture
Core/RefColor,
Core/RefColorTex
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
If Texture is used, depends on corresponding fragment shader only.
Core/RefTransLight3 Transformation,
Vertex Lighting with three light sources.
Texture
Core/RefColor,
Core/RefColorTex
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
If Texture is used, depends on corresponding fragment shader only.
Core/RefTransWorldLight1 Transformation,
Lighting in WorldSpace with 1 Light
Texture
Core/RefColor,
Core/RefColorTex
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetLightsCoordinateSpace(World)
Lighting calculations are processed in world space instead of model space (For details see function Light::SetCoordinateSpace).
If Texture is used, depends on corresponding fragment shader only.
Effects/RefTransSphereMap Transformation,
Sphere Texture Mapping
Core/RefTex,
Core/RefUniColorTex,
Core/RefUniDiffuseMatTex
SetModelViewMatrix4Enabled(true)
SetNormalModelViewMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Used to process a sphere texture for environment mapping.
Effects/RefTransLight1SphereMap Transformation,
Vertex Lighting with one light source,
Sphere Texture Mapping
Core/RefColorTex SetModelViewMatrix4Enabled(true)
SetNormalModelViewMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Used to process a sphere texture for environment mapping.
Effects/RefTransLight2SphereMap Transformation,
Vertex Lighting with two light sources,
Sphere Texture Mapping
Core/RefColorTex SetModelViewMatrix4Enabled(true)
SetNormalModelViewMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Used to process a sphere texture for environment mapping.
Effects/RefTransLight3SphereMap Transformation,
Vertex Lighting with three light sources,
Sphere Texture Mapping
Core/RefColorTex SetModelViewMatrix4Enabled(true)
SetNormalModelViewMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Used to process a sphere texture for environment mapping.
Core/RefTransPointSprite Transformation of PointSprite Core/RefPointSprite SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Use with PointSprite.
As usual for point sprites, lighting has no influence.
Core/RefTransLight1Morph Transformation,
VertexLighting with one lighe source,
Morphing Weights
Core/RefColor,
Core/RefColorTex
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Use with MorphingMesh.
If Texture is used, depends on corresponding fragment shader only.
Effects/RefTransLight1BumpMap Transformation,
Calculation of lighting vectors in tangent space.
Effects/RefLight1BumpMap SetModelMatrix4Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetCameraPositionEnabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetLightsCoordinateSpace(World)
Used to prepare lighting vectors for bump map shaders.
Effects/RefTransLight1ProceduralWood Transformation,
Vertex Lighting with one light source,
Passing position to fragment shader for procedural calculations.
Effects/RefProceduralWood SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)

Effects/RefTransAnisotropicLight1Strand Transformation, passes vertex attributes in world space to fragment shader. Calculates direction of anisotropy, eye vector and light direction. Effects/RefAnisotropicLight1StrandTex SetModelViewProjectionMatrix4Enabled(true)
SetModelMatrix4Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetCameraPositionEnabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetLightsCoordinateSpace(World)

Effects/RefTransAnisotropicLight1 Transformation,
Passes vertex attributes in world space to fragment shader.
Effects/RefAnisotropicLight1SpecularTex SetModelMatrix4Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetCameraPositionEnabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetLightsCoordinateSpace(World)

Effects/RefTransFur Transformation,
Indexed Multi pass upscaling in direction of normals, Texture
Effects/RefUniColorTexFur SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Lighting has no influence.
Effects/RefTransCubeMapReflection Transformation,
Texturing reflection using one CubeMap texture
Core/RefCubeMapTex SetModelMatrix4Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetCameraPositionEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Environment mapping reflections using a CubeMap texture. Lighting is not considered.
Effects/RefTransCubeMapRefraction Transformaion,
Texturing refraction using one cubemap texture.
Core/RefCubeMapTex SetModelMatrix4Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetCameraPositionEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Environment mapping refractions using a CubeMap texture. Lighting is not considered. Refraction ratio has to be passed as additional uniform float refractionRatio.
Effects/RefTransCubeMapReflectionRefraction Transformation,
Texturing reflection and refraction using one cubemap texture.
Core/RefCubeMapTex2 SetModelMatrix4Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetCameraPositionEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Environment mapping refractions and reflections using a CubeMap texture. Lighting is not considered. Refraction ratio has to be passed as additional uniform float refractionRatio.
Core/RefTransCubeMap Transformation,
Texturing using one CubeMap texture, addressed directly by object space vertex coordinates.
Core/RefCubeMapTex SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Direct addressing of CubeMap texture using object space vertex positions. Can be used for e.g. a SkyBox using a CubeMap texture.
Effects/RefTransDof Transformation,
Texturing with one texture,
First render pass of depth-of-field rendering: Rendering the scene,
Scaled depth value passed as varying.
Effects/RefTexDof SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
First pass of depth-of-field rendering has to be applied to every node that is rendered. The resulting image then contains the scene's depth values as alpha value, which is used for further processing. If other effects (e.g. Light, bump mapping etc.) have to be applied on the nodes, then also the corresponding shaders have to be adapted to meet the depth-of-field requirements. Additional uniform u_depthScale has to be passed to scale the depth values.
Effects/RefTransLight1Dof Transformation,
Texturing with one Texture
Vertex Lighting with one light source,
First render pass of depth-of-field rendering: Rendering the scene,
Scaled depth value passed as varying.
Effects/RefColorTexDof SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)

SetLightsCoordinateSpace(Light::Object);
First pass of depth-of-field rendering has to be applied to every node that is rendered. The resulting image then contains the scene's depth values as alpha value, which is used for further processing. If other effects (e.g. bump mapping etc.) have to be applied on the nodes, then also the corresponding shaders have to be adapted to meet the depth-of-field requirements. Additional uniform u_depthScale has to be passed to scale the depth values.
Supports per vertex lighting.
Effects/RefTransFragmentLightDof Transformation,
Texturing with one Texture,
Normal and position for per fragment lighting,
First render pass of depth-of-field rendering: Rendering the scene,
Scaled depth value passed as varying.
Effects/RefTexFragmentLightDof SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)

SetLightsCoordinateSpace(Light::World);
SetModelMatrix4Enabled(true);
SetNormalModelMatrix3Enabled(true);
First pass of depth-of-field rendering has to be applied to every node that is rendered. The resulting image then contains the scene's depth values as alpha value, which is used for further processing. If other effects (e.g. bump mapping etc.) have to be applied on the nodes, then also the corresponding shaders have to be adapted to meet the depth-of-field requirements. Additional uniform u_depthScale has to be passed to scale the depth values.
Supports per Fragment lighting.
Core/RefTransPos Transformation,
Texture,
Vertex position to fragment shader
Core/RefFlatLight1 SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetLightActivationEnabled(true)
No light calculation, passing vertex position to fragment shader for FlatShading calculations.
Effects/RefTransLight1Gooch Transformation, Assignment of ambient light component, Calculation of varyings needed for computation of specular component in world space. First pass of gooch-shading: Shading. Effects/RefGoochShading SetModelMatrix4Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetCameraPositionEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetLightActivationEnabled(true)
SetLightsCoordinateSpace(Light::World)
This vertex shader in conjunction with RefGoochShading.fragp realizes the first pass of an implementation of Gooch's rendering technique to simulate the appearance of technical illustrations, often occuring in e.g. manuals or schematic presentations etc. (SIGGRAPH'98 paper "A Non-Photorealistic Lighting Model For Automatic Technical Illustration"). The first pass renders the object using a transition from "cool" to "warm" colors according to the lighting model. While phong shading is normally used to simulate real lighting, this lighting model aims more on preserving details away from the light source. The second pass then draws the silhoutte of the object. For this the object is upscaled and rendered with a fixed color (e.g. black). On Candera side the first and the second pass have to be rendered with different Culling states, such that first pass renders the front of the object, and the second only the back side.
Effects/RefTransScale Transformation, Upscaling in direction of normals, texture coordinates. Core/RefTex,
Core/RefUniColor,
Core/RefUniColorTex,
Core/RefUniDiffuseMat,
Core/RefTex2LightMap
SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Lighting has no influence. Used to upscale the vertices inside the vertex shader, using a float scale factor passed as uniform u_Scale. Is used for silhouette rendering in gooch-shading, but may be also used for other techniques.
Core/RefViewportSpace Texture
Adjusting the position of the screen aligned billboard by setting uniform u_offsetLeft and u_offsetTop.
Effects/RefTexGaussianBlurX,
Effects/RefTexGaussianBlurY,
Effects/RefTexDofCombine,
Effects/RefBloomThreshold,
Effects/RefCombineBloom,
Core/RefInterlaceMask
SetModelViewProjectionMatrix4Enabled(false)SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
Used in subsequent post processing passes in conjuction with a screen-aligned billboard. The billboard should be of size 0.0 to 1.0 with 1.0 being a fullscreen billboard. The position of the billboard on the screen can be adjusted by setting the left and top offset in the range of 0.0 to 1.0.
Passes the position and texture coordinate to the fragment shader without any matrix transformation. Therefore the incoming vertex buffer should already be in homogenous screen coordinates.
Note: Overlapping Billboards (in screen space) would produce Z-fighting artifacts. Disabling depth test and using render order as well as using depth bias can be used to avoid them.
Effects/RefTransLight1Carbon Transformation, Lighting with one light source, Texture, Line of Sight Effects/RefCarbon SetModelMatrix3Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetLightsCoordinateSpace(Light::Object)
Seamless texture utilized as carbon image
Effects/RefTransLight1FlopCarPaint Transformation, Lighting with one light source, Line of Sight, Mixes two color components diffuse and emissive based on camera position Effects/RefFlopCarPaint SetModelMatrix3Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(false)
SetLightsCoordinateSpace(Light::Object)
Mixes two color components (diffuse and emissive) based on the camera position
Effects/RefTransLight1MetalFlakesCarPaint Transformation, Lighting with one light source, Line of Sight, Texture Effects/RefMetalFlakesCarPaint SetModelMatrix3Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetLightsCoordinateSpace(Light::Object)
Metal flake effect with one noise texture
Effects/RefTransLight1FlopMetalFlakesCarPaint Transformation, Lighting with one light source, Line of Sight, Texture, Mixes two color components diffuse and emissive based on camera position Effects/RefFlopMetalFlakesCarPaint SetModelMatrix3Enabled(true)
SetNormalModelMatrix3Enabled(true)
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetLightsCoordinateSpace(Light::Object)
Metal flake effect with one noise texture
Mixes two color components (diffuse and emissive) based on the camera position
Core/RefCanvasTrans Applying scale and offset to vertices before transformation,
Transformation into world space,
Texturing with one texture.
Core/RefTex,
Core/RefUniColor,
Core/RefUniColorTex,
Core/RefTex2LightMap,
Core/RefUniDiffuseMatTex
SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetCanvasActivationEnabled(true)
Similar to RefTrans, but additionally applies canvas offset and size before multiplying the mvp matrix.
Core/RefCanvasTransLight1 Applying scale and offset to vertices before transformation,
Transformation,
Texturing with one tetxture,
One light source with lighting in model space.
Core/RefColor,
Core/RefColorTex
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetCanvasActivationEnabled(true)
SetLightsCoordinateSpace(Light::Object)
Similar to RefTransLight1, but additionally applies canvas offset and size before multiplying the mvp matrix.
Core/RefCanvasTransUniDiffuseMat Applying scale and offset to vertices before transformation,
Transformation into world space,
Texturing with one texture,
Forwarding of material diffuse color to fragment shader.
Core/RefCanvasUniDiffuseMat SetModelViewProjectionMatrix4Enabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetCanvasActivationEnabled(true)
Similar to RefTrans, but additionally applies canvas offset and size before multiplying the mvp matrix. Additionally it passes material diffuse color to fragment shader. This shader is used to achieve the same effect as a SolidColorBrush in 2D.
Effects/RefFxaa Coordinates in normalized device space. Effects/RefFxaa SetModelViewProjectionMatrix4Enabled(false)
SetLightActivationEnabled(false)
SetMaterialActivationEnabled(false)
Applies the "Fast approximate anti-aliasing" algorithm to its input, which should be a quad using normalized device coordinates. The uniform (u_Resolution) needs to be set to the width and height of the input texture measured in texels.
Effects/Ref Texture Effects/RefTexGaussianBlurX,
Effects/RefTexGaussianBlurY,
Effects/RefTexDofCombine,
Effects/RefBloomThreshold,
Effects/RefCombineBloom
SetModelViewProjectionMatrix4Enabled(false)
SetLightActivationEnabled(false)
SetMaterialActivationEnabled(false)
SetTextureActivationEnabled(true)
Passes texture coordinates and vertex position to fragment shader without applying a transformation. This is useful for post processing effects when the vertex buffer is just a quad in normalized device coordinates (-1,1).
Effects/RefTransGradientLinear Transformation into world space,
Vertex based linear gradient computations.
Effects/RefGradientLinear SetModelViewProjectionMatrix4Enabled(true) Vertex Shader for linear gradient computations.
Effects/RefTransGradientRadial Transformation into world space,
Passing vertex position to fragment shader.
Effects/RefGradientRadial SetModelViewProjectionMatrix4Enabled(true) Vertex Shader for radial gradient computations. 

Core/RefTransLight1Skin Skinning Core/RefColor,
Core/RefColorTex
SetModelViewProjectionMatrix4Enabled(true)
SetLightActivationEnabled(true)
SetMaterialActivationEnabled(true)
SetTextureActivationEnabled(true)
SetSkinningMeshActivationEnabled(true)
If Texture is used, depends on corresponding fragment shader only. And, use with SkinningMesh.
Vertex Shader for radial gradient computations. 

Reference Fragment Shaders 

Fragment Shaders Supports Combine with Vertex Shaders GenericShaderParamSetter Configuration Note
Core/RefColor Color from varying Core/RefTransLight*,
Core/RefTransWorldLight*,
Core/RefTransLight1Morph
See configuration of RefTransLight* or RefTransWorldLight1 Normaly used for models that support lighting but no texture.
varying color (v_Color) is typically either set by lit material or by vertex color.
Core/RefTex Texture Core/RefTrans,
Effects/RefTransSphereMap,
Effects/RefTransScale
See configuration of RefTrans or RefTransSphereMap Lighting has no influence.
Core/RefColorTex Color from varying,
Texture
Core/RefTransLight*,
Core/RefTransWorldLight*,
Core/RefTransColor,
Core/RefTransLight1Morph,
Core/RefTransLight*SphereMap
See configuration of RefTransLight* or RefTransWorldLight1 Normaly used for models that support lighting and one texture.
varying color (v_Color) is normaly either set by lit material or by vertex color.
Core/RefUniColor Color from uniform Core/RefTrans,
Effects /RefTransScale
See configuration of RefTrans Useful if certain color is set via uniform (u_Color) directly.
Lighting or vertex colors have no influence.
Core/RefUniColorTex Color from uniform,
Texture
Core/RefTrans,
Effects/RefTransSphereMap,
Effects /RefTransScale
See configuration of RefTrans or RefTransSphereMap Useful if certain color is set via uniform (u_Color) directly and blended with one texture.
Lighting or vertex colors have no influence.
Core/RefUniDiffuseMatTex Material diffuse color,
Texture
Core/RefTrans,
Effects/RefTransSphereMap
See configuration of RefTrans or RefTransSphereMap Final color = materials diffuse color * texel color.
The uniform value of the materials diffuse color is set by Candera automatically if a Material is applied to a Node. Lighting or vertex colors have no influence.
Core/RefPointSprite Pointsprite incl. Texturing Core/RefTransPointSprite See configuration of RefTransPointSprite Used for point sprites only.
Core/RefTex2LightMap Texture,
LightMap
Core/RefTrans,
Effects /RefTransScale
See configuration of RefTrans Lightmap Multitexturing: A Lightmap typically defines an illumination texture at unit 1 that is multiplied with texture at unit 0.
Effects/RefLight1BumpMap Per pixel lighting using the normals supplied in u_Texture1 (normal map). Computed color is combined with the texture in u_Texture0. Effects/RefTransLight1BumpMap See configuration of RefTransLight1BumpMap Used to display rough surfaces without adding polygon complexity to it.
The shader accepts two textures. u_Texture0 defines the color map used. u_Texture1
defines a normal map, that stores normals in tangent space to be used for lighting
calculations.
Effects/RefProceduralWood Procedural calculation of interpolated rings, which appears like annual rings of wood if the right colors are chosen. Effects/RefTransLight1ProceduralWood See configuration of RefTransLight1ProceduralWood For procedural calculations two color, the orgin of the annual rings and a multiplier that controls the densitiy of the rings can be passed.
Effects/RefAnisotropicLight1StrandTex Per pixel anisotropic lighting, using a strand texture Effects/RefTransAnisotropicLight1Strand First texture for color, second for strand. The strand texture must hold the diffuse and specular light intensity in the R and G channels, respectively.
Effects/RefAnisotropicLight1SpecularTex Per pixel anisotropic lighting Effects/RefTransAnisotropicLight1 See configuration of RefTransAnisotropicLight1 Using alternative anisotropic specular highlights, according to Ward's SIGGRAPH 92 paper "Measuring and Modeling Anisotropic Reflection". Additional calculated specular component is combined with u_Texture0.
Used to display the reflection behavior of multiple surfaces. Uniforms u_AlphaX and u_AlphaY model the distribution of the specular highlight. Combined with a texture it can for example be used to simulate metallic surfaces like brushed steel. In the cited paper alphaX and alphaY for several materials can be found.
Effects/RefUniColorTexFur Assignment of product of alpha mask, texture and fixed color. Effects/RefTransFur See configuration of RefTransFur Simulates the appearance of fur if applied with MulitPassRendering. Is also intended to demonstrate multi pass rendering.
Core/RefCubeMapTex Addressing one cube map texture using a single varying vec3. Effects/RefTransCubeMapReflection, Effects/RefTransCubeMapRefraction,
Core/RefTransCubeMap
See configuration of RefTransCubeMapReflection, RefTransCubeMapRefraction,
RefTransCubeMap
Cube Map addressing, material colors or lighting are not considered.
Core/RefCubeMapTex2 Mixing two different addressed fragments of a CubeMap using a varying ratio. Addressing is done using two varying vec3. Effects/RefTransCubeMapReflectionRefraction See configuration of RefTransCubeMapReflectionRefraction Cube Map addressing, material colors or lighting are not considered.
Core/RefUniDiffuseMat Material diffuse color Core/RefTrans See configuration of RefTrans Final color = materials diffuse color
The uniform value of the materials diffuse color is set by Candera automatically if a Material is applied to a Node. Lighting or vertex colors have no influence.
Effects/RefTexDof Assignment of Texture, First render pass of depth-of-field rendering: Rendering the scene, Depth encoded into alpha channel. Effects/RefTransDof See configuration of RefTransDof First pass of depth-of-field rendering has to be applied to every node that is rendered. The resulting image then contains the scene's depth values as alpha value, which is used for further processing. If other effects (e.g. Light, bump mapping etc.) have to be applied on the nodes, then also the corresponding shaders have to be adapted to meet the depth-of-field requirements.
Effects/RefColorTexDof Color from varying,
Assignment of Texture,
First render pass of depth-of-field rendering: Rendering the scene, Depth encoded into alpha channel.
Effects/RefTransLight1Dof See configuration of RefTransLight1Dof First pass of depth-of-field rendering has to be applied to every node that is rendered. The resulting image then contains the scene's depth values as alpha value, which is used for further processing. If other effects (e.g. bump mapping etc.) have to be applied on the nodes, then also the corresponding shaders have to be adapted to meet the depth-of-field requirements.
Support per vertex lighting.
Effects/RefTexFragmentLightDof Per fragment lighting with one light source,
Assignment of Texture,
First render pass of depth-of-field rendering: Rendering the scene, Depth encoded into alpha channel.
Effects/RefTransFragmentLightDof See configuration of RefTransFragmentLightDof First pass of depth-of-field rendering has to be applied to every node that is rendered. The resulting image then contains the scene's depth values as alpha value, which is used for further processing. If other effects (e.g. bump mapping etc.) have to be applied on the nodes, then also the corresponding shaders have to be adapted to meet the depth-of-field requirements.
Supports per fragment lighting.
Effects/RefTexDofBlur Assignment of texture, second render pass of depth-of-field: blurring the texture, simulating an optical circle of confusion, caused by lens refraction out of focus. Core/RefTrans See configuration of RefTrans Second pass of depth-of-field rendering: blurring the incoming image. Result is a blurred image of the passed texture. u_CocScale controls the size of the circle of confusion, and therefore the blur strength.
Effects/RefTexDofCombine Third pass of depth of field: Assignment and blending of two textures, representing the sharp and blurred image. Blend weights result from focus and focus range of the simulated camera lens. Core/RefViewportSpace, Effects/Ref See configuration of RefTrans Third pass of depth rendering: Combines the sharp and the blurred image according to the set focus of the simulated camera lens. u_Focus controls the distance of the focus plane (where a sharp image would be formed). u_Range controls the size of the area around the focus plane, where a sharp image would be drawn.
Effects/RefTexGaussianBlurX Assignment of texture,
Subsequent pass of post-processing effect: blurring the texture with a Gaussian blur,
Gaussian blur step width from unifrom
Core/RefViewportSpace, Effects/Ref See configuration of RefViewportSpace
plus
SetUniform("u_blurFactor",Shader::Float, &blurFactor)
Subsequent pass of post-processing effect: blurring the incoming image horizontally. Result is a blurred image of the passed texture. u_blurFactor controls the step width of the Gaussian function. u_blurFactor should always be 1.0/TextureWidth so that every step will correlate with one pixel.
Effects/RefTexGaussianBlurY Assignment of texture,
Subsequent pass of post-processing effect: blurring the texture with a Gaussian blur,
Gaussian blur step width from unifrom
Core/RefViewportSpace, Effects/Ref See configuration of RefViewportSpace
plus
SetUniform("u_blurFactor",Shader::Float, &blurFactor)
Subsequent pass of post-processing effect: blurring the incoming image vertically. Result is a blurred image of the passed texture. u_blurFactor controls the step width of the Gaussian function. u_blurFactor should always be 1.0/TextureHeight so that every step will correlate with one pixel.
Core/RefFlatLight1 Per fragment lighting with one light source for Flat Shading. Core/RefTransPos See configuration of RefTransPos In order to use derivative functions dFdx and dFdy the extension GL_OES_standard_derivatives has to be enabled and supported by driver.
Effects/RefGoochShading Per Fragment calculation of specular component, Mixing cool and warm color using the dot product of light direction and vertex normal as weight, Output: Mixed color + specular and ambient lightcomponent. Effects/RefTransLight1Gooch See configuration of RefTransLight1Gooch This fragment shader in conjunction with RefTransLight1Gooch.vertp realizes the first pass of an implementation of Gooch's rendering technique to simulate the appearance of technical illustrations, often occuring in e.g. manuals or schematic presentations etc. (SIGGRAPH'98 paper "A Non-Photorealistic Lighting Model For Automatic Technical Illustration"). For details please refer to the description of the vertex shader, and to the cited paper. The following unifiorms are needed: u_CoolColor and u_CoolColor are 4-component vectors that describe the colors for the cool to warm transition. u_CoolDiffuseWeight and u_WarmDiffuseWeight are float values that describe, how the original diffuse color is combined with the warm and cool colors.
Effects/RefBloomThreshold Assignment of texture,
Subsequent pass of bloom effect: extracting only the bright parts of the image that should be blurred,
brightness threshold from unifrom
Core/RefViewportSpace, Effects/Ref See configuration of Core/RefViewportSpace Subsequent pass of post-processing bloom effect: extracting only the bright parts of the incoming texture. Those bright parts are blurred in the next step to generate a bloom effect.
Effects/RefCombineBloom Subsequent pass of bloom effect: Assignment and djustable combination of the original and the bloomed texture Core/RefViewportSpace, Effects/Ref See configuration of RefViewportSpace Subsequent pass of post-processing bloom effect: The incoming texture of the original scene and the incoming texture of the bright, bloomed parts are combined. The blending of the two textures is adjustable by uniforms representing the intensity and saturation of the images.
Core/RefInterlaceMask Uses a texture's color to create a mask through discarding pixels with low transparency. No value is written to the discarded fragments, so also no depth or stencil values. Core/RefViewportSpace See configuration of RefViewportSpace Is especially useful for e.g. Creating stencil masks for stereoscopic 3D.
Effects/RefCarbon Wraps a seamless texture multible times around the object to achieve carbon look. Effects/RefTransLight1Carbon See configuration RefTransLight1Carbon Creates a car paint with typical carbon look.
Effects/RefFlopCarPaint Mixes the two color components together based on the camera position and a user defined bias Effects/RefTransLight1FlopCarPaint See configuration RefTransLight1FlopCarPaint Creates a car paint with shiny, anisotropic two color look.
Effects/RefMetalFlakesCarPaint Adding a noise to the spectral part of the light. Noise can be wrapped around the object multible times. Create the illusion of metal flakes in the car paint. Effects/RefTransLight1MetalFlakesCarPaint See configuration RefTransLight1MetalFlakesCarPaint Creates a car paint with shiny, sparkling look produced by metal flakes within specular highlight
Effects/RefFlopMetalFlakesCarPaint Mixes the two color components together based on the camera position and a user defined bias.
Adding a noise to the spectral part of the light. Noise can be wrapped around the object multible times. Create the illusion of metal flakes in the car paint.
Effects/RefTransLight1FlopMetalFlakesCarPaint See configuration RefTransLight1FlopMetalFlakesCarPaint Creates a car paint with shiny, sparkling look produced by metal flakes within specular highlight combined with anisotropic two color look.
Core/RefCanvasUniDiffuseMat Assignment of material's diffuse color to fragment color Core/RefCanvasTransUniDiffuseMat See configuration
RefCanvasTransUniDiffuseMat
This shader is used to achieve the same effect as a SolidColorBrush in 2D.
Effects/RefFxaa Coordinates in normalized device space. Effects/RefFxaa See configuration of Effects/RefFxaa Applies the "Fast approximate anti-aliasing" algorithm to its input, which should be a quad using normalized device coordinates. The uniform (u_Resolution) needs to be set to the width and height of the input texture measured in texels.
Effects/RefGradientLinear Mixing fragment color according to gradient computations. Effects/RefTransGradientLinear See configuration
RefTransGradientLinear
Fragment Shader for linear gradient computations.
Effects/RefGradientRadial Assignment of radial gradient color to fragment color. Effects/RefTransGradientRadial See configuration
RefTransGradientRadial
Fragment Shader for radial gradient computations.
Core/RefColorTexAlpha, Assignment of product of varying color and texture color to fragment color. Core/RefTransColor See configuration RefTransColor This fragment shader shall be used for text or alpha mask rendering.
Effects/RefColorTexAlphaOutline This fragment shader renders an outline of color "u_outlineColor" and width specified by
"u_outlineWidth" around the contents in the texture "u_Texture". The input texture is also
multiplied by the vertex color. If the "u_outlineWidth" value is bigger than 10, the contents
of the texture need be bigger than 10 too, otherwise artefacts will start to appear with increasing
outline width.
There is no instancing version of this shader, because it will usually be applied to text
rendering which is batched by Canvas rather than the Renderer.
Core/RefTransColor See configuration RefTransColor
Additionally:
SetTexelSizeActivationEnabled(true)
Custom uniforms:
uniform vec4 u_outlineColor; // Color of outline.
uniform float u_outlineWidth; //Width of outline.
This fragment shader shall be used if a text or an alpha mask shall receive an outline.



Tutorial for Text Rendering

Tutorial for Text Rendering

Introduction

Text Rendering involves both the handling of Unicode standards regarding character encoding as well as the generation and rendering of the glyphs. The sub-chapters here explain the basic principles.


Character Encodings

character encoding system consists of a code that pairs each character from a given repertoire with something else such as a bit pattern, sequence of natural numbers, octets, or electrical pulses (Source: Wikipedia).

Unicode is the most important standard for character encodings and defines (among other things) the mapping of a character to a Unicode code point (bit pattern):

  • Fixed length encodings of code points (ASCII, UCS2, UCS4 etc).
  • Variable length encoding of code points (UTF-8, UTF-16, UTF-32)
  • Chinese GB18030 is the only relevant other encoding standard

All texts which are rendered within the TextEngine have to be of type TChar*. Text in TChar* have to be encoded in UTF-8. UTF-8 uses a varying number of TChars to encode one glyph. E.g. a UTF-8 encoded string with special characters: "&auml&Auml&ouml&Ouml&uuml&Uuml&szlig&#8364" consists of 8 glyphs but of 17 bytes because several of these special characters are encoded in more than one byte.

The methods FeatStd::String::GetCharCount() and FeatStd::String::GetCodePointCount() can be used to get the numbers as explained in the example above. An instance of FeatStd::String class can be constructed with TChar* by the constructor FeatStd::String::String(const TChar *).

Char data type must not be used for I18N text and always maps to ASCII.

Complex Script Languages

A complex script is a writing system which requires complex transformation of glyphs like substitution and positioning. Examples of such writing systems are arabic, hebrew and thai. Complex transformations include

  • bi-directional ordering of characters,
  • contextual shaping which means that displayed glyphs depend on the surrounding code points in the text - displayed glyphs can have different shapes depending on their position in a word or can be combined to ligatures,
  • combining characters where several characters are combined into one shape
drawing-4-1677033945.png

Above the characters as they are written and below as they are displayed.

Text Rendering - Functional Steps

Text Rendering involves the following functional steps:

  1. Glyph Rendering
  2. Shaping
  3. Layouting
1. Glyph Rendering

Takes a single code point and font metrics to render the according glyph. The Freetype font engine for example takes a code point and outputs a 8 bit alpha.

2. Shaping

Shaping is the transformation from logical to display presentation of a text. The Shaping process is responsible for considering complex scripts. The Shaping process is applied on paragraph level.

GPOS/GSUB Tables

OpenType fonts may include GSUB (glyph substitution) and GPOS (glyph positioning) tables.

The GSUB table contains (among other things)

  • Ligature substitution (replace multiple code points with single code point)
  • Contextual substitution (alternative presentation of the glyph depending on his context) If no GSUB table is available, the standard substitutions defined by Unicode apply (eg. mandatory set of Arabic ligatures).

The GPOS table contains (among other things)

  • glyph placement
  • adjustment of marks
  • kerning
  • attachment points for combining characters

Shaping Libraries

  • FEAT ComplexScript library does shaping for Arabic language
  • FreeBidi - functional equivalent to FEAT ComplexScript
  • Harfbuzz - Arabic + many other scripts

A Candera feature define is available in CMake to select between the 3rd party software components ComplexScriptLib and HarfBuzz for text shaping.


3. Layouting

Layout text that has been shaped. Typical operations:

  • Line breaking
  • Word breaking
  • Text alignment (horizontal and vertical)
Tutorial for Text Rendering

Fonts and Styles

Description

This section contains information about how to use fonts and styles.

Accessing Fonts 

SceneComposer: Import Fonts

For using fonts, first of all the fonts which shall be used need to be imported into the SceneComposer solution.

The font will only be available in the finally generated asset, if: 
- it is either used in any scene (as part of a style which is used in a widget style property for example) 
- or the "Always include in asset" flag is enabled in the font resource.

See also:

Initializing FontEngine

Next, the application needs to create a Candera::TextRendering::FontEngine instance and initialize it for using the Candera::AssetFontStore. AssetFontStore will access the font part of the loaded asset and provide it to the TextEngine.

    static AssetFontStore s_fontStore;

    result = result &
&
Candera::TextRendering::FontEngine::Instance().Init(&
s_fontStore);


Using a Font

The easiest way to use a font is to setup the font as part of a Candera::TextRendering::Style and use the style as a widget property within SceneComposer. This allows to specify the FontFamily and the FontSize via SceneComposer GUI. FontSize has to be specified in pixel size (see Font Metrics and Text Sizes). In case the application needs to switch the font dynamically, at runtime, setup the font and create a style:

    Font font;

    font.Setup("openSans", 20);

    m_style = SharedStyle::Create();

    m_style->
SetDefaultFont(font);


Access to AssetFontStore

Include FontFaceName and FontFaceSize Property in the header file of the widget.

// Add Property
            CdaProperty(FontFaceName, const Candera::Char*, GetFontName, SetFontName)
                CdaDescription("Font Face Name for the text (used only if \"Use Text Font Property\" is disabled).")
                CdaCategory("Accessing Fonts from FontEngine")
            CdaPropertyEnd()

            CdaProperty(FontFaceSize, Candera::UInt16, GetFontSize, SetFontSize)
                CdaDescription("Font Face Size for the text (used only if \"Use Text Font Property\" is disabled).")
                CdaCategory("Accessing Fonts from FontEngine")
            CdaPropertyEnd()
// end add Property

The FontFaceName property must be set with a valid Candera path. Keep in mind that, unlike paths generated by the Font dialog where the; the "/" character, the Candera paths use "#" as delimiter.

Also, do not forget to initialize member variables in the constructor, otherw; e SceneComposer will not be able to work with the widget.

SimpleTextWidget::SimpleTextWidget() :
    m_text(0),
    m_style(SharedStyle::Create()),
    m_color(),
    m_shader(0),
    m_fontName(0),
    m_fontSize(12),
    m_bb(0),
    m_bitmap(0),
    m_bitmapPixels(0),
    m_defautScaleVector(1.0,1.0,1.0),
    m_useTextFont(true),
    m_;
UpdateRequired(true),
    mTextStartPos(0, 0)
{
}

Setup the font as described below.

        static Font f;
        if (f.Setup(m_fontName, static_cast<Candera::Int16>(m_fontSize))) {
            m_font = f;
        }

And update the style used for rendering:

    m_style->SetDefaultFont(m_font);
AssetFontStore Load Strategies

AssetFontStore supports two font load strategies:

  • PreloadStrategy: entire fonts are loaded into memory once and font data; accessed from there by Text Engine.
  • OnRequestLoadStrategy: small font portions are streamed directly from the asset repository when Freetype (or indirectly Harfbuzz) needs them.

Use SetDefaultFontLoadStrategy to select one of these load strategies.

Use SetDefaultFontLoadStrategyExceptionL; t to exclude a l; t of fonts from the selected font load strategy.

Example:

    // Load fonts with OnRequestLoadStrategy
    selector.SetDefaultFontLoadStrategy(Candera::AssetFontStore::LoadStrategySelector::OnRequestLoadStrategy);
    // "Bitstream Vera Sans" font shall be loaded with PreloadStrategy:
    const Char* g_fontName[2] = { "ConstructionKit##ConstructionKit#Resources#Fonts#Bitstream Vera Sans", 0 };
    selector.SetDefaultFontLoadStrategyExceptionL;
t(g_fontName);
    fontStore.SetLoadStrategySelector(&selector);

Courier applications can use Courier::ViewHandler::SetFontStoreProviderCallback to provide specific settings for the Candera::AssetFontStore in use.

Text Style: A Composition of Fonts 

Text Style: Introduction

A Style can be seen as a composition of fonts.

Basically for a range of glyphs a proper font can be specified (also called style entry). Beside the style entries a style also cons; ts of a default font and a base style.

  • The style entries are parsed from the first to the last style entry until a code point range that contains the input code point; found.
  • All glyphs which do not match to one of the specified style entry range are rendered with the default font.
  • The same parsing order; applied to code points which match to a range of a style entry, but the font does not contain a glyph for th; code point
  • Each style can use another style as base style, which allows an arbitrary complex composition of fonts.

The TextRenderingSolution provided in the content folder of cgi_studio_player gives an example how text features can be applied in SceneComposer.

See also:

Text Style: Example 

Example

Use TextRenderingSolution provided in the content folder of cgi_studio_player to see how text features can be applied in SceneComposer.

drawing-4-1677044019.png


Select Scene2D_StyleExample of the solution, which gives just a brief overview of text features and how they look like. It cons; ts of

Lets consider the configured style MyStyle set in the TextNode2D's style property:

  • The Default Font property is set with predefined DefaultFont (Vera size 20)
  • No Base Style, therefore for this field Empty, is defined

Expanding the Children panel shows the defined style entries. The example style consists of three style entries.

  • Entry 0 defines the font BigLetter (Comic size 60) for all glyphs in the code point range from 65 to 90. These upper and lower bounds represent the glyph range 'A' (65) - 'Z' (90) in decimal representation. In the example therefore all upper case letters are rendered with font BigLetter.
  • Entry 1 defines font BigLetter for numbers '0'(48) - '9'(57).
  • Entry 2 defines font Korean (UnBom size 40) for Korean glyphs. Code points for asian glyphs are in range 44032 - 55215 (in decimal representation). So using styles, glyphs from different cultures can be rendered without changing the font/style settings.

In case of this example all lower case letters do not have a proper style entry and the default font is taken to render.

If the code ranges of different style entries overlap, the style entry with the higher number has priority (e.g. font in entry 2 has higher priority than font in entry 1).

Bitmap Font Engine 

Overview

A bitmap font is essentially an image file which consists of several characters images and a header that depicts the size and location of each character inside the image. Each bitmap font might contain several fonts - mainly one font face with multiple sizes. One advantage of utilizing bitmap fonts is that the rendering to the screen requires very little resources and since each character is represented as a sub-texture, they can be reused without requiring additional memory on the GPU.

CMake configuration

In order to be able to use the bitmap font engine the Candera CMake project should be configured as follows:

  • CANDERA_FONTENGINE should be set to "Bitmap Font"
  • CANDERA_BITMAPFONT_FACE_SIZE_COUNT_MAX represents the number of font sizes supported. Default value is set to 6.
  • CANDERA_BITMAPFONT_GLYPH_CACHE_SIZE represents the size of the buffer to cache glyph bitmaps. By default the value is set to 24*24*100.

CANDERA_TEXTSHAPER should be set to "NoShaping" or "ComplexScriptLib" because Harbuzz Text Shaper cannot be used with BitmapFont.

Font Setup

This font engine uses a proprietary format to describe glyph bitmaps and glyph meta info. In order to use a bitmap font, provide an implementation for Candera::TextRendering::FontStore.

Bitmap fonts require memory mapped assets. So, when the faces are loaded in be sure the storageType is set correctly:

    TextRendering::FontResourceDescriptor descriptor.storageType = TextRendering::FontResourceDescriptor::MemoryResource;
Generating Bitmap Fonts

The following steps need to be considered:

1. Install Bitmap Font Generator from http://www.angelcode.com/products/bmfont

2. Open the application. From the Options menu:

  • Choose Font Settings. Set as needed the settings for "Font", "Add font file", "Size" etc.
  • Configure the Export Options. Set "Font descriptor" to "XML" and Textures to "png - Portable Network Graphics"

3. From the right panel of the main window select the needed subsets of glyphs.

4. Export the font by choosing "Save bitmap font as ..." from the Options menu.

Please see Import Fonts for details about the importing of bitmap fonts in SceneComposer.

A bitmap font should be used at its native pixel size. However, they are scalable because of the use of bitmaps, but only scaling down is recommended. In case of up scaling, the visual quality will be reduced. Including separate font sizes for bitmap fonts is possible in order to achieve the best looking results.

Tutorial for Text Rendering

Using Candera TextEngine to draw Text

Description

The simplest way to draw text with Candera is using a TextNode2D, a 3D text widget from CGI Studio Widget Library, or the 2D TextBrush effect. Refer to:

To learn about how to use Candera::TextNode2D, please refer to the following section:

To learn about details, how to use the Candera TextEngine interface directly, refer to the following chapters.

Using Candera::TextNode2D 

Overview

TextNode2D represents a RenderNode specialized for fast text rendering.

Text and style have to be provided. In addition, it requires a BitmapBrush effect for rendering and a Candera::TextNodeRenderer object used for generating the images, which will be then rendered by the associated BitmapBrush effect.

There are four Candera::TextNodeRenderer implementations available, each corresponding to one CacheType option from the existing Candera::TextBrush rendering concept (Bitmap, Surface, Glyph and Glyph Cache).

Text layouting is supported by attaching a Candera::TextNode2DLayouter to the node. It is responsible for arranging and truncating the associated text of a Candera::TextNode2D. The default layouter implements both horizontal and vertical truncation.

Renderer

Candera::TextNodeRenderer object is used for generating the correct images from provided text.

The level cache strategies which apply for a Candera::TextNode2D are described below:

  • Bitmap: very large memory footprint, very large update times, small render time
  • Surface: large video memory footprint, large update times, small render time
  • Glyph (former NoCache): small memory footprint, small update times, very large render time.
  • GlyphCache: small memory footprint, small update times, medium render time
  • GlyphAtlas: small (but global) video memory footprint, small update times, small render time.

Choose the appropriate cache for a balance in performance. It is recommended to use either Bitmap or Surface rendering type when static texts are used, while for dynamic texts, GlyphCache is recommended.

Associated Effect

Please consider that only BitmapBrush type effects apply to a Candera::TextNode2D. In case other effects are used nothing will be rendered. The text appearance can be altered by the type of effect chosen, either by modifying the in-place effects (color, mask, HSL transformations, etc.) or by changing blending effects.

List of BitmapBrush effects which can be used are described below:

Not all BitmapBrush effects have the Color property available. Default color for a TextNode2D is white.

Layouter

Candera::TextNode2DLayouter is responsible for arranging and truncating the text associated to a Candera::TextNode2D. Candera::DefaultTextNode2DLayouter, derived from Candera::TextNode2DLayouter, is used for text truncation. More details are provided in the following section.

Candera::TextNode2D Guidelines

The minimum necessary steps needed in order to render a TextNode2D are:

  • Create a TextNode2D instance
  • Set a Text: Text to be rendered.
  • Set a Style: Style object to be used for rendering.
  • Define a TextNodeRenderer: TextNodeRenderer used for generating the correct images from provided text.
  • Attach a BitmapBrush effect: The associated BitmapBrush effect is responsible for rendering the generated images.

Define node and effect

Define a Candera::TextNode2D and the associated BitmapBrush effect:

    TextNode2D* m_textNode;
    BitmapBrushColorBlend::SharedPointer m_textNodeEffect;

Create node and effect

Create an instance of Candera::TextNode2D class by using Candera::TextNode2D::Create() method:

    m_textNode = TextNode2D::Create();

In similar way create the BitmapBrush effect and add this effect to the newly created Candera::TextNode2D:

    m_textNodeEffect = BitmapBrushColorBlend::Create();
    m_textNode->AddEffect(m_textNodeEffect.GetPointerToSharedInstance());

Set Text

Use Candera::TextNode2D::SetText() to specify the text to be rendered:

    m_textNode->SetText("TextNode2D");

Set Style

Use Candera::TextNode2D::SetText() to specify the style object:

    m_textNode->SetText("TextNode2D");

Renderer

Create and set a Candera::TextNodeRenderer to Candera::TextNode2D using Candera::TextNode2D::SetTextNodeRenderer() method:

    m_textNode->SetTextNodeRenderer(&GlyphCacheTextNodeRenderer::GetInstance());

Add to Scene

Finally, add the textNode directly to a Candera::Scene2D or a Candera::Group2D:

    m_textNodeGroup->AddChild(m_textNode);
Candera::TextNode2D Properties

Text Color

Changing the color of a Candera::TextNode2D is done through the associated BitmapBrush effect. Depending on the effect use, not all BitmapBrush effects expose a color property. In this case, the rendered text will be white.

    m_textNodeEffect->GetColorEffect().Color().Set(Color(0.0f, 0.0f, 0.0f, 1.0f));

Text Alignment

Text alignment is realized through the layouter properties of the Candera::TextNode2D, meaning that the position inside a layouter, as well as text position inside the Candera::TextNode2D will now be changed by the same Candera::TextNode2DLayouter::SetHorizontalAlignment() and Candera::TextNode2DLayouter::SetHorizontalAlignment() methods the layouter. In case it in necessary to have a different text position than the provided layouting options, it is possible to nest Candera::TextNode2D and set different alignment values for the inner TextNode.

    TextNode2DLayouter::SetHorizontalAlignment(*m_textNode, Candera::HLeft);
    TextNode2DLayouter::SetVerticalAlignment(*m_textNode, Candera::VTop);

Truncation

Candera::TextNode2D provides only one truncation type - in case truncation is needed, the text gets cut and trailing ellipsis are added to fit the space. Truncation is not supported by default. For this, the Candera::DefaultTextNode2DLayouter needs to be attached to Candera::TextNode2D (see snippet below) and the text will be automatically truncated whenever the size of the text no longer fits in the specified Layout Size property of Candera::TextNode2D. Bounding rectangle size is not taken in account when performing text truncations.

    m_textNode->SetLayouter(&TextNode2DLayouter::GetDefault());

Multiline Support

Multiline layouting is enabled by default for a TextNode2D. This property can be enabled/disabled using Candera::TextNode2DLayouter::SetMultiLineEnabled() method.

Wordwrap Support

Wordwrap is not enabled by default for a TextNode2D. This property can be enabled/disabled using Candera::TextNode2DLayouter::SetWordWrapEnabled() method.

Text Height

Glyph based height computation is still done, but it is not used for laying out. The rectangle in which the text is laid out, if Size is (-1;-1), will be the one measured with old TextBrushConstantHeight option. The TextBrushActualHeight is always used for retrieving the bounding rectangle of the node, but it is also possible to retrieve the layout rectangle of the node too (see information about text length calculation below).

Text Length

The following methods are provided for retrieving the size of the rendered text:

Candera::TextNode2D in SceneComposer

The new TextNode2D is located in the Toolbox panel. In order to add a text to the scene, drag a TextNode from the panel to a Scene2D node.

drawing-4-1677045059.png

From the Properties panel of the TextNode, configure the necessary properties for rendering: Text, Style.

drawing-4-1677045072.png

Candera::TextNodeRenderer type of Candera::TextNode2D can be changed using the Renderer property. Default value in SceneComposer is Bitmap.

drawing-4-1677045090.png

Candera::DefaultTextNode2DLayouter is attached by checking Enable Text Layout. Additional layouter properties are exposed in the Properties Panel:

  • Multi line (enabled by default)
  • Word wrap (disabled by default)

For truncation to occur, Enable Text Layout property needs to be enabled.

drawing-4-1677045120.png

When a TextNode2D is created, a Candera::BitmapBrushColorBlend is automatically associated with the node, which is used for changing text color only. Any BitmapBrush effect present in the list of effects from Toolbox Panel can be used for text rendering. The visual appearance of the text will be affected depending on the type of effect chosen.

drawing-4-1677045137.png



TextEngine Render Interface 

Overview: Render Interface

The main interface to render text is the method

Text Render Context

A Candera::TextRendering::TextRenderContext is required to define a target for text rendering i.e. where to render the text to.

Configure Bitmap as Target for Text Rendering

A Candera::TextRendering::BitmapTextRenderContext can be used to draw text into a bitmap. So first of all the bitmap which shall be drawn needs to be defined with

  • the Candera::TextRendering::BitmapTextRenderContext::SetBitmap() method to assign a bitmap to render to,
  • the Candera::TextRendering::BitmapTextRenderContext::SetPenColor() method to define the text color
  • the Candera::TextRendering::BitmapTextRenderContext::SetClipRect() method to set a clipping rectangle for the text.

    BitmapTextRenderContext bmpRenderContext;
    static_cast<void>(bmpRenderContext.SetBitmap(m_bitmap));
    bmpRenderContext.SetPenColor(m_color);

Layouting Options

The Candera::TextRendering::LayoutingOptions define the layout of the rendered text with

See also:
Candera::TextRendering::LayoutingOptions for further options.
Shaping Options

The Candera::TextRendering::LayoutingOptions define how the text shall be transformed from logical to display presentation. The usage of one of the constructors is recommended:

  • Candera::TextRendering::ShapingOptions::ShapingOptions(const StylePtr &style)
  • Candera::TextRendering::ShapingOptions::ShapingOptions(const StylePtr &style, const CulturePtr &culture)
Text Properties

The Candera::TextRendering::TextProperties simply define the text itself which shall be rendered.


Font Metrics and Text Sizes 

Font Size

In Candera the font size (respectively the font height) has to be provided in pixel size. Note that in several word processing programs, like Microsoft Word, fonts are defined in point size. The conversion between pixel size and point size is calculated as follows (see FreeType Glyph Conventions):

  • pixel_size = point_size * resolution / 72

Microsoft Windows e.g. defines for historical reasons a default resolution of 96 PPI (DPI). A font in Microsoft Word with defined point size 18 has the same height as the font defined in Candera with pixel size 24 (24 = 18 * 96/72).

Font Metrics

For a font, static font metrics are provided describing font ascender, descender values in device pixel, the font line height and the maximum vertical advance of a font character: Candera::TextRendering::Font::GetMetrics()

    TextRendering::Metrics metrics;
    if (!m_font.GetMetrics(metrics)) {
        return false;
    }

Text Bounds

Further, the text bounds (width, height, etc.) of a given string can be retrieved by calling GetTextRectangle method of Candera::TextRendering::GlyphTextMeasureContext or Candera::TextRendering::CursorTextMeasureContext.

Candera::TextRendering::GlyphTextMeasureContext builds a rectangle corresponding to the smallest surface covered by all the glyph bitmaps. The position of this rectangle( represented by (left, top) coordinates) can be offset from the text position. Therefore, when rendering the text, the Layouting Options should be constructed using the inverse of this offset.

Candera::TextRendering::CursorTextMeasureContext builds a rectangle corresponding to the whole surface swept by the cursor when moving while processing the text. The cursor is considered to be a segment of length equal to the style height. This rectangle does not usually have an offset and it contains the final advancement of the cursor.

    GlyphTextMeasureContext glyphContext;
    static_cast<void>(textRenderer.Render(glyphContext, LayoutingOptions(), ShapingOptions(m_style), TextProperties(m_text)));
    TextRect textBound = glyphContext.GetTextRectangle();
    mTextStartPos.SetX(-textBound.GetPosition().GetX());
    mTextStartPos.SetY(-textBound.GetPosition().GetY());
    Int16 textWidth = textBound.GetWidth();
    Int16 textHeight = textBound.GetHeight();
    static_cast<void>(textRenderer.Render(bmpRenderContext, LayoutingOptions(mTextStartPos), ShapingOptions(m_style), TextProperties(m_text)));

The Layouting Options describe the layout of the text. The Shaping Options define how the text is shaped (ligatures, text direction, ..) and they also contain the style.


Simple Text Rendering 

Rendering Simple Text

The Candera::TextRendering::TextRenderer provides the core function to render text.

For simple text rendering the Candera::TextRendering::TextRenderer::Render() method is called. Besides the actual text (type TChar*) and the BitmapTextRenderContext as target, layouting and shaping can be specified.

    static_cast<void>(textRenderer.Render(bmpRenderContext, LayoutingOptions(mTextStartPos), ShapingOptions(m_style), TextProperties(m_text)));

Finally the image has to be updated to apply the changes.

    bool success = m_textureImage->Update();


Character-Glyph Position Mapping 

Overview

Candera text engine provides an interface which allows to map positions in character string to positions in glyph string. This feature can be useful for applications and widgets in order to perform various operations depending on the character position like, for instance, highlighting text.

Example

For the convenience, we choose to improve the TextTextureWidget with this highlighting feature. The widget will highlight the text by rendering in red all the glyphs corresponding to the characters having the index in the interval m_start and m_end.

The character position information is accessible in the Blit method of the Candera::BitmapTextRenderContext class. Consequently, this class should be derived and the Blit method overwritten as follows:

class BitmapTextRenderContextSelection : public BitmapTextRenderContext
{      
public:   
    BitmapTextRenderContextSelection(Int selectionStart, Int selectionEnd, Color color) : m_start(selectionStart), m_end(selectionEnd), m_color(color) {}
    virtual void Blit(Int16 x, Int16 y, const GlyphBitmap& glyph);
private:
    typedef BitmapTextRenderContext Base;

    Color ComputeColorForCharPosition(TextPosition position);
    Int m_start;
    Int m_end;
    Color m_color;
};

void BitmapTextRenderContextSelection::Blit(Int16 x, Int16 y, const GlyphBitmap& glyph){

    SetPenColor(ComputeColorForCharPosition(glyph.characterPosition));
    Base::Blit(x, y, glyph);
}

Color BitmapTextRenderContextSelection::ComputeColorForCharPosition(TextPosition position){
    if(position >= m_start && position <= m_end){
        return Color(255,0,0);
    } else {
        return m_color;
    }
}

In TextTextureWidget::UpdateTextureImage(), use the newly created class instead of Candera::BitmapTextRenderContext:

    BitmapTextRenderContextSelection bmpRenderContext(m_selectionStart, m_selectionEnd, GetTextColorPrv());


Shortening Support For Bidirectional Text 

Feature Overview

This feature ensures that the bidirectional text shall be shortened whenever it does not fit into its designated display area. The algorithm which does the shortening can deal with different levels of embedding of right-to-left text in left-to-right text and vice versa. Depending on the text direction of the last chunk, the shortening string is inserted after the last fitting text segment, on the left or on the right end.

You can take advantage of this feature by using one of the following, depending on the type of scene being rendered:

Scene type Node name Widget name
2D Candera::TextNode2D -
3D - TextTextureWidget
Truncation using TextNode2D

This feature is active only when the node parameter Enable Text Layout is enabled. The text will be automatically truncated whenever its size no longer fits in the specified Size property of the node. TextNode2D provides only one truncation method, by cutting text and adding trailing ellipsis to fit the space.

Truncation using TextTextureWidget

This feature is active only when the widget parameter Truncation method is set to the Text value. The character(s) used for visual feedback of text shortening is configurable by setting the Truncation text property of the 3D widget.

Examples

The image below exemplifies three scenarios :

  1. Shortening of the right-to-left text (Arabic)
  2. Shortening of the left-to-right text (Latin)
  3. Shortening of right-to-left text embedded in left-to-right text (left-to-right and right-to-left)
drawing-4-1677045787.png



Font Hinting 

Overview

Font hinting is the use of mathematical instructions to adjust the display of an outline font so that it lines up with a rasterized grid. This is done by interpolating the pixels in order to more clearly render the font. At low screen resolutions, hinting is critical for producing clear, legible text. It can be accompanied by antialiasing and (on liquid crystal displays) subpixel rendering for further clarity.

The hinting applies only to outline fonts so the bitmap fonts and the stroke fonts will not be affected by this feature.

Hints are usually created in a font editor during the typeface design process and embedded in the font. In general, there are three possible ways to hint a glyph:

  • The font contains hints to guide the rasterizer, telling it which shapes of the glyphs need special consideration. The hinting logic is partly in the font and partly in the rasterizer.
  • The font contains exact instructions (also called bytecode) on how to move the points of its outlines, depending on the resolution of the output device, and which intentionally distort the (outline) shape to produce a well-rasterized result. The hinting logic is in the font; ideally, all rasterizers simply process these instructions to get the same result on all platforms.
  • The font gets auto-hinted (at run-time). The hinting logic is completely in the rasterizer. No hints in the font are used or needed; instead, the rasterizer scans and analyzes the glyphs to apply corrections by itself.
Enabling Hinting

The hinting can be enabled or disabled by the means of the method Candera::TextRendering::Font::SetHintingEnabled(). When hinting is disabled, the glyphs may appear blurred, as you can see below. The text was rendered with a small size font (9px) and then zoomed in:

drawing-4-1677045859.png

Enabling AutoHint

The usage of the automatic hinter of the font driver can be controlled by calling the method Candera::TextRendering::Font::SetAutohintEnabled(). When the automatic hinter is disabled either the native hinter of the font is used either the hinting is disabled (zoomed in image):

drawing-4-1677045895.png

Enabling Force AutoHint

If a given font driver doesn't provide its own hinter, the auto-hinter is used by default. If a format-specific hinter is provided, it is still possible to use the auto-hinter by enabling it using the method Candera::TextRendering::Font::SetForceAutohintEnabled().

Hinting Mode

The driver autohinter can be configured by calling the Candera::TextRendering::Font::SetHintingMode() method with one of the following parameters(hinting algorithms):

  • GlyphHinter::Normal : normal hinting, optimized for standard gray-level rendering
    drawing-4-1677045933.png

  • GlyphHinter::Light : applies less corrections than normal hinting for better performance
    drawing-4-1677045994.png


  • GlyphHinter::Monochrome : appropriate for monochrome rendering
    drawing-4-1677046010.png


  • GlyphHinter::Lcd : appropriate for horizontally decimated LCD displays
    drawing-4-1677046031.png

  • GlyphHinter::VerticalLcd : appropriate for vertically decimated LCD displays
    drawing-4-1677046060.png

All modes except GlyphHinter::Monochrome use 256 levels of opacity.

Code Example
    m_font1.SetHintingMode(GlyphHinter::Monochrome);
    m_font2.SetHintingMode(GlyphHinter::Light);
Glyph Bitmap Format

The output type of the glyph rendering process can be configured by calling the method Candera::TextRendering::Font::SetRequestedGlyphFormat() with one of the following render modes as parameters:

  • GlyphBitmap::Unknown : format is unspecified
  • GlyphBitmap::Monochrome : correspond to 1-bit bitmaps (two levels of opacity)
  • GlyphBitmap::Lcd : format use with horizontally decimated RGB or BGR sub-pixel LCD displays
  • GlyphBitmap::VerticalLcd : format use with vertically decimated LCD displays
  • GlyphBitmap::Grayscale : format is grayscale, one byte per pixel; this is the default render mode

Each of the render modes above correspond to a specific type of scanline conversion performed on the outline.

Code Example
    m_font1.SetRequestedGlyphFormat(GlyphBitmap::VerticalLcd);
    m_font2.SetRequestedGlyphFormat(GlyphBitmap::Grayscale);
Monochrome Render Mode

The monochrome render mode removes anti-aliasing because it's using only two levels of opacity and gives clearer appearance for small size fonts (no blurring). Please see below a comparison between monochrome and grayscale render modes:

drawing-4-1677046185.png
drawing-4-1677046236.png

An another advantage of using this render mode is the smaller memory footprint for glyphs because of 1bit format.



Tutorial for Text Rendering

Using asynchronous text rendering

Description

The Candera engine provides asynchronous text rendering to improve the handling of time-consuming text processing. The focus is not to improve performance but to improve resource handling.

Asynchronous text rendering requires knowledge of how text nodes work.

General information of how to use Candera::TextNode2D can be found in the following section:

Asynchronous text rendering requires further knowledge of which steps are necessary to display a text. Please refer to the following section:

Based on the knowledge about the TextNode2D and CanvasText, the following chapters describe how asynchronous text rendering works. Further, these chapters explain how to customize the asynchronous text rendering and which restrictions should be considered when using this feature. The documentation focuses mainly on Candera::TextNode2D. The concepts described inside these chapters are applicable to Candera::CanvasText, too.

Text rendering - Useful knowledge 

TextNode2D – text render process

A text uses three steps in its render process:

  1. Measurement and placement
  2. Prerendering – Prepares the render device for the text (if necessary)
  3. Render – Actual visible output

All of these steps have to be fulfilled to come to a visible result. Using a TextNode2D has three different ways to update its text and therefore three different ways to approach the final result. The main difference lies in providing information and handling the three process steps:

First case: No layout usage

Candera::TextNode2D has no real layout information. There is also no layout in which the Candera::TextNode2D is integrated. Therefore, trigger the render process for text render process is enough. The text is not bound to an area and has no relationship other than the visible layer to any other node in the scene graph.

Pro:

  • Lightweight version of text and Candera.
  • Simple text can be shown without much effort.

Contra:

  • Most of text placement features are disabled.
  • No arrangement exists.
drawing-4-1677046993.png

Second case: Layout process but no TextNode2D layouter

Candera::TextNode2D has no real layout information. However, it is arranged within a layout (e.g. GridLayout). This implies that Candera::TextNode2D has to provide actual layout results to arrange the text within the layout area. Therefore, the layout process has to start the text render process beforehand. The text render process has to complete the first step (measurement and placement) when the layout process evaluates the bounding box of the text. The layout process has no influence on the size of the bounding box. It only receives the information of the bounding box and can place all the other nodes around the text. The actual handling of this information is defined by the used layouter.

Pro:

  • Lightweight version of text with advantages of an arrangement of scene graph nodes.
  • Switching between first and second case is normally without major issues.

Contra:

  • The text placement cannot be influenced by the layout process.
  • TextNode2D itself cannot distinguish between first case and second case.
drawing-4-1677046938.png

Third case: Layout process with TextNode2D layouter

Candera::TextNode2D provides layout information. Additionally, the general layout process can influence the way text is displayed, too. The layout process can propose a maximum area in which the text has to fit. This has a direct impact on the placement. The text itself provides the layout process a bounding box which were actually used by it.

Pro:

  • Full feature set for text placement integrated in the scene graph arrangements.

Contra:

  • Heavyweight version of text; more calculations have to be done.
  • An attached layouter expects using the layouter.

When a layouter is attached, it has to be used as it replaces the default behavior with a complex one. However, the information has to be provided by triggering the layout process otherwise it misses most of the information. This implies that other usages are wrong and lead to undefined or unwanted behavior.

drawing-4-1677046891.png

Transformations like scale and rotation are applied after the text has been rendered. This means that you cannot use scaling to produce narrow or wide text, because the visual layout would be altered (right-aligned text will not be aligned any more after scaling). In this case, import a scaled font instead.

CanvasText – text render process

Candera::CanvasText uses the third approach of Candera::TextNode2D:

All other approaches have been removed from Candera::CanvasText to reduce the complexity of processing. Another difference to Candera::TextNode2D is the actual Render() part. Whilst the Candera::TextNode2D calls the Render() on each node, Candera::CanvasText renders it in a batched form by collecting texts with equal render settings and drawing them with a single draw call. Candera::CanvasText uses GlyphAtlas.

Please note also the following restriction when using asynchronous text processing:



Asynchronous text rendering 

Asynchronous text rendering is a feature introduced to move text render steps to an own thread or render slot. To be precise only the first of the three render steps can be handled asynchronously. All the other steps are possibly bound to GPU or has to extend the complexity of how to receive the desired output for both framework and user application.

Asynchronous text rendering – Worker thread

The default way of asynchronous text rendering is the usage of a worker thread. The render loop delegates the first step, Measurement and Placement, to a worker thread. Every delegate is placed in a queue and ready for processing. Using a worker thread has its main advantage in not blocking the render loop. The render loop itself will not block until the text has been measured and placed. The disadvantage of this approach is that there is no control over the execution moment and result arrival time. It can happen after a single render cycle but also after an undefined amount of cycles.

The next figure shows in a draft how the measure and placement process has been replaced when asynchronous text rendering is used. For simplicity only the way without a layouter is used. All other ways do replace the measurement and placement process with the same approach in their flow chart.

The function which actually triggers the asynchronous process has to be triggered twice. One time to queue the process and a second time to retrieve the result. In this flow chart sample the Render-method has to be called twice. The second call has to be done when the asynchronous process has actually finished.

drawing-4-1677047084.png



Asynchronous text rendering - Single threaded

Asynchronous does not mean the work load has to be done by another thread. It can also be done by the same thread at another point in time. The main disadvantage of the single threaded version is that the render loop thread is still handling the text. However, this time it does not have to handle every text in a single render cycle. The amount of texts which will be rendered in a cycle can be determined.

This implies that two factors are given: Text/cycle and text to handle. Based on this information the amount of cycles needed to calculate all texts can be estimated. The duration of each cycle cannot be determined, though.

drawing-4-1677047132.png



Synchronization methods 

Text-Synchronization

With the introduction of asynchronous text rendering a way to synchronize all text nodes have to be provided. The basic idea is to visually update all asynchronous texts at the same render cycle. It should prevent text popping up randomly. Another aspect is to decrease layout processes. When a layouter is attached to a Candera::TextNode2D, the layout process will evaluate the size of a Candera::TextNode2D. This also means the layout process starts the asynchronous text rendering if necessary. When the layout process starts the process, it should not wait for the result as this results in the same blocking issue as before.

The layouter uses the old information and text which is still valid as long as the asynchronous text rendering is not done yet. Rerunning the layout process will check whether the new text is valid yet or not. If not, it will continue using the old information. If the new text is valid now, it will update the text. The same procedure happens without a layouter attached. However, the part of starting and collecting the results is done by the render call itself. In short an asynchronous text rendering has to be started and the result has to be received in a minimum of two calls. It depends on the usage of layout procedures who the caller really is.

This section describes the possibilities of synchronization:

Text-Synchronization – Default synchronization

The default synchronization is kept as simple as possible. This section describes the lifecycle of the validation and how does the synchronization works including its flaws.

Lifecycle

Every object which has to be synchronized implements the interface Candera::TextRendering::ITextValidationUser, e.g. TextNode2D. In the next step this object has to be attached to a Candera::TextRendering::TextValidationGroup. The default group is a singleton instance. The Candera::TextRendering::TextValidationGroup creates a specific validator and attaches it to the object. Detaching the validator is comparable to this process.

drawing-4-1677047325.png

Synchronization - flow

The object can be invalidated at any time by calling the invalidation method of the Candera::TextRendering::TextValidator. Every time an invalidated object changes it state to validate, the Candera::TextRendering::TextValidator notifies the group of its validation change. If all objects of the group are valid now, all update methods of these objects will be called.

drawing-4-1677047365.png

In case of the Candera::TextNode2D with an attached layouter, the layout of the scene in which the Candera::TextNode2D lies will be invalidated. If there is layout process used at all (disabled Candera layout functionality) nothing will be done in the update method. However, the render method itself can handle this case when it is newly called. The issue here is that it is not explicitly specified who will trigger the render. Both render and layout mechanisms of will check whether the whole group is valid or not. Only in case that the whole group is valid, the text will be updated. If the layout is triggered by another object despite the fact that the group is not valid, the text will not be updated.

Candera::TextNode2D attaches itself to the Candera::TextRendering::TextValidationGroup singleton instance per default. To exclude it from the synchronization group it has to be explicitly detached first.

Synchronization - Restrictions

The default synchronization is as lightweight as possible and still provides the feature that all texts in the group have to be valid until the text can be visibly updated. Due to the variety of combinations a text can be used, it is not a universal solution for synchronization. Therefore in some cases it is not as practical as it should be.

A Candera::TextNode2D can be synchronous or asynchronous or switch between both cases at runtime. Therefore it is not advisable per default to attach/detach the TextNode2D to the group when the asynchronous flag changes. Pending requests are one of the major issues here.

A synchronous Candera::TextNode2D has to be synchronous. This implies when the text has been changed the text has to be visible in the first render cycle afterwards. So it becomes deterministic that the text is truly displayed. Using the synchronization groups on these nodes is opposing this determinism. The node is attached to the group but it is always in the valid state. The node will be ignored completely by the default synchronization.

Another critical restriction is the update rate of a text. This is an issue that happens even without a synchronization but will be intensified by it. In short, every time a text changes, it invalidates the group. When the invalidation rate is higher than the overall text render process of the whole group needs to render, text which should have been shown will never occur. If two texts are invalidating itself in a higher rate, it can also happen that whole group is always in an invalid state. The group itself has a mechanism to count already finished texts and is therefore able to update the text based on the fact that the group is valid or has pending valid texts which are not displayed yet. Based on this information it can recognize a valid group even if one of the texts already changed back to invalid due to setting a new text. However this is not a guarantee that the texts will be updated correctly.

Because of the simplicity of the synchronization it cannot link text updates to each other. For example changing all texts at once to another language is working. There is only one issue with already pending text changes. It is not possible to generally specify which text changes are related to each other and have to be changed at the same time.

Not every use case of a customer can be satisfied by a single way of synchronization. One way produce other side effects than another.

Therefore, own synchronizations can be implemented or used.

Text-Synchronization – Custom implementation

The customization of the synchronization has several levels of complexity and several different goals.

Changing the way of synchronization during runtime should only be done, when the affected text object (e.g. Candera::TextNode2D) has no pending text changes – as this can have unexpected side effects.

A new synchronization group

The simplest customization is an additional synchronization group. As default, all TextNode2Ds are attached to the singleton instance of the Candera::TextRendering::TextValidationGroup. It is possible to create an additional instance of this class in custom code. Now two steps have to be done to correctly bind a Candera::TextNode2D to the new group.
First, detach the Candera::TextNode2D from its old group by using the Detach-method of the old Candera::TextRendering::TextValidationGroup.
Second, attach the Candera::TextNode2D to the new group by using the Attach-method of the new Candera::TextRendering::TextValidationGroup.

Additional trigger objects

A common issue is that setting the underlying scene layout of a Candera::TextNode2D to invalid is not enough to trigger the actual render cycle. For example a render wake-up mechanism needs a trigger to wake up and start the render cycle, too.

The Candera::TextNode2D itself does not know which additional trigger are needed because this depends on the given application.

An extension of this would be to update nodes within the scene graph as soon as the group is valid. A simple use case is to set a new scene visible only when all texts in the new scene are valid or a control pops up only when its text in it is valid.

To accomplish this the specified object which is responsible for these calls has to implement the interface Candera::TextRendering::ITextValidationUser. The object can be attached to the Candera::TextRendering::TextValidationGroup equal to the described life cycle of the default synchronization. The Candera::TextRendering::ITextValidationUser::TriggerValidUpdate()-method in it will be called as soon as the whole group is valid and the custom code can be executed. Additionally, the object receives an own Candera::TextRendering::TextValidator which can influence the time when a group is valid or when the Candera::TextRendering::ITextValidationUser::TriggerValidUpdate() on all objects in the whole group has to be additionally called.

Implementation of an own synchronization

The most customized solution is to write an own synchronization mechanism.

There are two important interfaces for an own implementation:

Candera::TextRendering::ITextValidationUser is the interface which provides the Candera::TextNode2D with any validation method. It correctly attaches and detaches such a validation method. And Candera::TextRendering::ITextValidationUser::TriggerValidUpdate() invalidates the scene layout of the Candera::TextNode2D. The interface FeatStd::ValidationHelperBase provides functionality to validate and invalidate the object. It is a custom implementation how it will work. There is only one restriction here. A synchronously used TextNode2D will always ignore the valid state of the custom implementation. It will call the validate and invalidate methods, though. A Candera::TextNode2D calls the functions as following:


Configuration possibilities 

General setup and configuration possibilities

This section describes how to configure the asynchronous text rendering.

Asynchronous TextNode2D and CanvasText

Enabling the asynchronous text rendering is something that has to be done on each Candera::TextNode2D or Candera::CanvasText. This can be done within the SceneComposer:

drawing-4-1677047433.png

Or in code itself:

void SetAsyncPreRenderEnabled(bool enabled);
bool IsAsyncPreRenderEnabled() const;

Example usage:

TextNode2D node; 
node.SetAsyncPreRenderEnabled(true);   //Enables async rendering 
node.IsAsyncPreRenderEnabled();        //returns true
node.SetAsyncPreRenderEnabled(false);  //Disables async rendering 
node.IsAsyncPreRenderEnabled();        //returns false

The flag in SceneComposer and code can be equally configured in Candera::CanvasText.

Asynchronous dispatching methods

CMake provides a flag to choose between the worker thread solution and the single threaded version. When the flag is enabled a worker thread is used. The flag is enabled by default. If threading is not available or thread safety is disabled, the code will fall back to the single threaded version ignoring this flag. The flag is system wide. It counts for all asynchronous TextNode2D. This can also be defined programmatically, whereas there are no thread safety checks.

drawing-4-1677047461.png

Dispatcher settings

The TextRenderDispatcher contains a settings object which can be retrieved by calling:

class Candera::TextRendering::TextRenderDispatcher::TextRenderSettings{…};

Candera::TextRendering:: TextRenderDispatcher::GetTextRenderSettings();

To programmatically change the asynchronous dispatching method use the function of settings:

#include <Candera/TextEngine/Async/ AsyncTextRenderDispatcher.h>
#include <Candera/TextEngine/Async/ ThreadingTextRenderDispatcher.h>

void SetDefaultTextRenderer(Candera::TextRendering::TextRenderer * val);

If the single threaded version is used, the amount of processed text per DispatchNext can be set:

void SetAsyncLoopCount(UInt8 const val);


Restrictions 

This section describes issues which should be kept in mind when using the asynchronous feature.

Performance improvements

The main idea behind asynchronous text rendering is not to improve the performance of text rendering but to improve the performance of the render loop itself. Even if a second cycle through the render loop is necessary to display the results. The second cycle is mandatory as the first cycle only adds the text render call into a queue. The first cycle uses the old result to handle the text.

Using a worker thread and having a minimum of dual core processor is mandatory to gain an actual performance improvement as the worker thread can be handled by the second core.

High frequency changes

The most mandatory restriction is the update interval of an asynchronous text. Changing the text faster than the text engine can handle will definitely break the determinism of a system. Three different approaches can be outlined for handling fast updated texts. The first approach is to visibly show every text change. It is deterministic to see every text. Even if they are only visible for a single frame and it is not deterministic when the text is shown. The second approach is overriding the old queued text with the new one. The determinism of seeing every text has been lost. However, rendering a text which is already outdated before the render process started is a performance drop which will not occur. The third solution is to render these texts synchronously.

Currently available are the second and the third approach. This decision is based on the possible use cases.

The first use case is an alternating text, e.g. frame rate, clock time. Basically such a text type has three things in common. First, it changes its content all the time in a specified time interval. The logic behind the text is already broken when the update is visible the first time after a single frame and the second time after six frames. Second, the texts are usually short texts. A high frequency alternation of a long text is most probably an issue in the usability. Third, if the frequency is high enough to outrun itself and the text is alternating all the time, the queue will stack up indefinitely. There is no time to show all queued text until new texts arrive. The visual text itself becomes more and more outdated. The length of the text would increase the change of queuing text even more.

Therefore, it is recommended for cyclic alternation to render them synchronously. Short texts will not affect the render loop itself that much. And long texts should be reconsidered and checked against its reasonableness.

The second use case is to have occasionally a fast switch between texts, e.g. a text will be shown, but a user input already triggered the next text update. In this case the text can have any length. The main difference is that the text update does not queue indefinitely and there is most probably not a hard time interval deadline. In this case asynchronous text rendering can be used but rendering both texts can also impact the user experience. In most cases these will be noticeable in long texts. So there will be a visible delay until the outdated text is displayed and another visible delay until the new text is displayed.

Therefore, the asynchronous approach overrides the old text with the new one as soon as possible. If the old text is already rendering, the old text will be displayed. If the old text have not even started rendering, the old text will be replaced. If the old text and the new text both are already rendered and the render loop had no time to show the old text, the new text will be shown. In this case it would be most likely that the old text has only a visibility time of a single frame.

Destruction of a TextNode2D

Destroying a TextNode2D while the attached text is being rendered can lead to undefined behavior.

Layout process calls

Using an asynchronous approach means that there a functionality which starts the asynchronous process and another one which handles the result of the process. Based on the synchronization the check for a valid result can be solved by polling until a result is available.

If the synchronization is done properly for a given use case, no polling will be needed.

However, the asynchronous process requires layout information to start. And the layout process requires the result to place the text node and depending nodes. Therefore, it is required to run the layout process twice now. Two calls are the minimum requirement. If the text has not been finished until the second call, more calls are necessary as this is equal to polling for results.

Cache type restrictions

Some cache types are GPU context bound. Therefore, the upload of rasterized glyphs has to happen in the correct context. Using a worker thread cannot guarantee the upload at the correct location. This might require an additional rasterization and upload per glyph within the render thread. Expected cache types to have these issues are SurfaceCache and GlyphAtlas. Candera::CanvasText uses primarily GlyphAtlas. A shared context and the non-threading method is preferred in this case.


Tutorial for Text Rendering

Text rendering: Caching/rendering systems

Description

The rasterization of a glyph is a very costly operation. Paired with shaping and positioning algorithms, it becomes even more expensive. One strategy to counter this is a caching and rendering mechanism. A cache is a storage type which maps a key to a specific representation of a value. The transformation of the rasterization into the value of a cache entry might differ from cache type to cache type. Based on the difference an optimal render strategy has to be internally selected. This is the reason why the text renderer and the caching system are strongly related.

Candera supports different caching and rendering systems with different backgrounds and goals.

No Caching

The easiest form is to cache nothing. This strategy has very little performance but requires the least memory. Its strategy is to retrieve a rasterized glyph, blit it to the display and free all the memory content. This is a strategy which is only advised to use on very low end devices. Otherwise it is not recommended.

Bitmap Cache

Bitmap caching is a strategy to store all the glyphs of a single text within a bitmap. When the text or its properties changes, the bitmap has to be generated. This bitmap will be uploaded and stays uploaded. Every time the visual area of the text becomes invalidated but the text has not been changed, it will rerender the bitmap but not generate it newly. The bitmap is generated with a software blit and therefore generates CPU load.

Glyph Atlas

The glyph atlas is an uploaded texture which contains all cached glyphs. If a glyph is not available there, the texture will be updated by adding the rasterized glyph. A text change requires only an update of a vertex buffer which references the glyphs within the atlas. Therefore, glyphs do not have to be uploaded anymore as soon as they are in the cache. The glyph atlas has a learning curve to fill up the glyph atlas. This solution is a GPU load solution.

Glyph Cache

The glyph cache stores the rasterized glyphs. Which then will be uploaded and rendered in every render call. This is typically a performance bottleneck but useful for very specific use cases. It is not recommended to use glyph cache in common use cases.

Surface Cache

The surface cache uses the same glyph storage mechanism as glyph cache but renders it similar to the bitmap cache. However, surface cache does this on GPU side whilst bitmap cache does it on CPU side. A change within a text leads to a draw call for every single glyph. If the new text requires a larger area to display the text, a new FBO will be created. FBO creation is an expensive operation. This cache is made for specific hardware devics. It has more drawbacks than advantages in common use cases.

Cache Usages

This section describes where the cache types are available and what are recommended usages.

TextNode2D:

  • Bitmap
  • Glyph atlas
  • Surface cache (Freetype only)
  • Glyph/No cache (Freetype only)
  • Glyph cache (Freetype only)

TextBrush:

  • Bitmap
  • Glyph atlas
  • No cache (Freetype only)
  • Glyph cache (Freetype only)
  • Surface cache (Freetype only)

CanvasText:

  • Glyph atlas

Tutorial for Globalization Support

Tutorial for Globalization Support

Introduction

Multi-Language Support

Preparing an application to perform in multiple locales requires the CGI Studio license "Globalization" and in terms of Candera build settings to enable the CMake feature switch "CANDERA_GLOBALIZATION_ENABLED".

Preparing an application to perform in a specific locale requires the following workflow:

  • Application Development
    • Create Master Language Pack Source (either directly in SceneComposer or by importing from an external tool)
    • Will be automatically added as binary to the asset library on asset export from SceneComposer
  • Production
    • Remove Master Language Pack Source from asset library
    • Create Language Pack Source for each language
    • Append Language Pack for each language to the asset library

Language Pack Source Format

In general, a Language Pack Source file

  • has the file extension '.lps'
  • is a proprietary file format: Zipped XML files in a predefined structure
  • contains texts associated to a specific culture

Master Language Pack Source

The Master Language Pack Source file is a design-time artifact not to be kept at production time. It defines all available translatable texts in an artificial Master Language (locale 00).

Extracting the content of a Master Language Pack Source .lps file shows following content:

  • Cultures.xml
      <lps:Culture Name="Master Language" Locale="00" TextDirection="LeftToRight" />
    
  • LanguagePack_00.xml: Lists all unique Text-IDs with related default text representation, a translation hint and a maximum text length for the Locale="00"
  • Partitions.xml
  • Preamble.xml

Cultures

SceneComposer cultures are defined in the "Cultures" panel accesible either via File->Solution Options menu:

drawing-4-1677034464.png

either via toolbar:

drawing-4-1677034485.png

The default culture for a solution is defined in the 'General Configuration' panel as explained here.

Multi Language Pack Source

A Multi Language Pack Source provides translations for all translatable texts as defined in the Master Language Pack Source. All supported languages can be combined into a single .lps file, or be separated in single .lps files per language.

The culture definitions in language pack source files should syntactically match the culture definitions in SceneComposer to make the default culture setting of a solution work at application runtime. Example matching to the SceneComposer culture definitions above:

  <lps:Culture Name="German" Locale="de_DE" TextDirection="LeftToRight" />
  <lps:Culture Name="French" Locale="fr_FR" TextDirection="LeftToRight" />  

In case the default language is not set or not present in the asset, the first culture available in the asset will be used as fallback.

Appending Language Packs to Assets

Language Pack Source files are compiled into a binary format and appended to asset libraries

More Details

More details on the globalization workflow are provided here:

Tutorial for Globalization Support

SceneComposer Translator Plugin

Overview

CGI Studio SceneComposer Translator Plugin provides the following features:

  • Create, edit and delete translatable texts in the Language Table dialog
  • Set and manage translatable texts in the Text property editor of text nodes or controls and behaviors that expose Text properties.
  • Alternatively import translatable texts from an existing CGI Language Pack Source or Excel file.
  • Export translatable texts to a CGI Language Pack Source or Excel file.
  • Append a compiled CGI Language Pack file to an asset after asset export.


Globalization License

If the Translator Plugin files are present but no translation features are accessible in Scene Composer, please ensure that the Globalization license is acquired. Refer to the menu Help > Licenses.


Create and Manage Cultures and Translatable Texts

Accessing Translator Plugin in SceneComposer

The translator plugin is fully integrated into SceneComposer. The SceneComposer toolbar provides an icon for opening the Solution Options: Cultures dialog as well as a drop-down menu for selecting an already created culture.

drawing-4-1677043273.png

There are five additional menu entries under Language:

  • Culture Solution Option: Open the Culture Solution Option Dialog, where the user can add, edit or remove Culture as the multiple languages setting. By selecting a Culture in SceneComposer, you can switch between the displayed language of the solution. The translated texts displayed when selecting a Culture are configured in the Language Table.
  • Export Languages: Opens the Select Cultures dialog where you can select the languages you want to export and - after browsing to an export file path - you can export your selected languages in the format you prefer to the location you selected by pressing Export.
  • Import Languages: Opens the Import Languages Dialog where Languages can be imported into a solution either as Language Pack Source or as Excel file, by going to Language > Import Languages and selecting the desired flavor.
  • Language Usage: Opens the Language Usage Dialog, where all Text Properties of the solution are listed. For those Text Properties that have a translatable text assigned, the Text Id and the Text value for the current culture are displayed. It is also possible to assign or unassign a translatable text for a Text Property.
  • Language Table: Opens the Language Table Dialog, where the user can add, edit or remove translatable texts. The Id of each translatable text is unique. The Text value for a certain culture can be accessed by selecting the respective culture in the Current Culture drop-down.
drawing-4-1677043286.png

Creating Cultures

In order to have texts in different languages, cultures have to be created. This is done by clicking on the button Culture Solution Options in the toolbar.

drawing-4-1677043314.png

With the icons on top, cultures can be added (using the green plus sign) and deleted (with the red "x"):

  • to add a new culture: use the button with the green plus sign
  • to delete a culture: use the button with the red "x" For each entry the culture name can be selected from the drop-down and the text direction can be set. Once there are translated texts available for a certain culture, the number of available text for this culture is displayed in the Translated Texts column. In the above example there are two translatable texts for culture de and two translatable texts for en.
Translate Texts

Texts can be translated in the Language Table Dialog, which is opened via the menu Language > Language Table....

drawing-4-1677043341.png

The drop-down on the top right allows the Current Culture selection. The texts of the Master culture or another culture in the solution can be accessed by changing the Current Culture. If the Master culture is selected, the Add and Delete buttons are enabled, otherwise they are disabled.

A search field on top helps to easily find specific texts once the number of texts increases. With the checkbox Unused only it is possible to identify texts that have not yet been assigned to any Text Property of the solution.

Clicking on the globe icon on the top right opens the Solution Options: Cultures dialog.

Translatable Text

A translatable text is a text which supports translation into other languages and which provides information that facilitates its translation to other languages. The translatable text has the following properties:

Id: Unique identifier of the translatable text:
  • Computed TextId: An alphanumerical string, which will be exported into a computed hash value as TextId.
  • Direct Id: When the format HEX_VALUE is used, the hex value is directly exported into the asset without computing a hash value.
Text: Text in the master language
Translation hint: Hint for the process of translation
Editing Text Properties

For editing the texts' properties, Scene Composer provides the Text Properties Dialog, which can be opened via the menu Language > Language Usage....

drawing-4-1677043361.png

This dialog lists all Text Properties of the solution. For those Text Properties that have a translatable text assigned, the Text Id and the Text value for the current culture are displayed. The current culture can be changed in the drop-down menu on the right. If necessary, the Solution Options: Cultures dialog can be opened with the globe icon on the left of the drop-down menu.

When pressing Save, the changes made to the Text Properties will be applied to the solution.

Assigning Texts to TextNodes

Assigning texts to Text Properties can either be done in the Language Usage Dialog or in the Text Properties Panel.

Assigning Texts using the Language Usage Dialog

In the Language Usage Dialog a Text property can be selected. When pressing the Assign button on the bottom of the dialog, the Language Table Selection dialog is opened where a text can be chosen from the list.

Assigning Texts using the Text Properties Panel

The Language Table Selection dialog can also be opened when editing a Text property by clicking on the icon Select template for item.

drawing-4-1677043388.png

Switching Cultures

If cultures and texts have been created and the texts are assigned to Text properties, the culture can be switched using the drop-down in the toolbar. As soon as another culture is chosen, the solution will be displayed in the other language.

If there is no text configured for a certain language, the Text Id will be displayed instead. Using this id, the missing text can easily be identified and found in the Language Table using the search function.

drawing-4-1677043434.png


Import and Export of Language Packs

Import of Language Pack Sources

Languages can be imported into a solution either as CGI Language Pack Source or as Excel file, by going to Language > Import Languages and selecting the desired flavor.

drawing-4-1677043472.png

The final step of the language import process is to show the differences between the texts in the import file and the texts in solution. There are three possible actions:

  • Add : Indicates that the language does not already exist in the solution and will be imported from the file.
  • Merge: Means that the language already exists in the solution and only the content differs. New texts will be added and already existing texts will be updated.
  • Drop : When unchecking a language entry in the Translatable Text Differences dialogue, the Action changes automatically from Add/Merge to Drop. If the initial action was Add, this means that the language will not be added to the solution. If the initial action was Merge, this means its texts will not be updated and new texts from the import files will not be added to this language in the solution.

The Drop action does not mean that the language will be removed from the solution. The removal of a language can be done from File > Solution Options in the Cultures tab.

drawing-4-1677043519.png
Export of Language Pack Sources

The translatable texts of the current solution can be exported by going to Language > Export Languages..., which opens the Select Cultures dialog.

drawing-4-1677043537.png

The Select Cultures dialog allows the choice of which cultures to be exported or not. Languages can be exported in Excel or CGI Language Pack Source (.LPS). Click the Browse button to select the Export file path, choose the format in the Save as type drop down and click Save. Click the Export button to finalize the languages export.

Please note that only the texts that have a correspondent in the master language texts, considering their Id, are included in the export, all the others being ignored. In this case warnings will be added to the Output tab in Scene Composer.

  • Exporting languages as Language Pack Source file creates a .LPS file that contains the master language and all the other languages available in the solution. The exported .LPS file can be compiled and appended using the CGI Studio Language Appender.
  • Exporting languages as Excel document creates an Excel file that contains one sheet for the master language (named Master) and a sheet for each of the other languages in the solution having an identical name with the respective culture (e.g. de-DE, en-US, fr-FR). Each sheet has a header on the first row, with 3 columns: Id, Text, and Translation Hint. The following rows contain the translatable texts.
drawing-4-1677043571.png





Tutorial for Globalization Support

Managing Language Packs using AssetTool

Overview

AssetTool application can be used to manage language packs for a given asset. AssetTool application is located in /cgi_studio_bin/AssetTool/ folder and provides the following functionality:

  • Appending a new language pack to an asset
  • Removing an existing language pack from an asset

A usage example of AssetTool application for managing language packs can be seen in /cgi_studio_player/content/Tutorials/05_Globalization/AppendMultiLanguagePack.bat script.

Appending a New Language Pack to an Asset

For the example solution, the following command can be called for appending language pack MultiLanguagePack_Source.lps to the existing asset MasterLanguagePack_Asset_Host.bin:

AssetTool.exe -append -a MasterLanguagePack_Asset_Host.bin MultiLanguagePack_Asset_Host.bin MultiLanguagePack_Source.lps

This will result in a new generated asset called MultiLanguagePack_Asset_Host.bin, which will contain the translation for all languages available in the language pack.

Removing an Existing Language Pack from an Asset

For the example solution, the following command can be called for removing master language, with code "00", from the existing asset MasterLanguagePack_Asset_Host.bin:

AssetTool.exe -append -r MasterLanguagePack_Asset_Host.bin temp.bin 00

This will result in a new generated asset called temp.bin, which will no longer contain the translation for the master language pack.

For more details on AssetTool application, see Tool Chain/AssetTool chapter.

Tutorial for Globalization Support

Using Globalization in Applications

Requirements for Multi Language Support

  • CMake setting CANDERA_GLOBALIZATION_ENABLED is enabled
  • Sources of CanderaGlobalization available

Example: Set Culture in the Player

The Player provides, if Globalization is enabled in Candera, a checkbox to select all cultures, which are available as language packs in the loaded asset.

  1. Open the asset containing language packs (MultiLanguagePack_Asset_Host.bin) and load the scene in the Player.
  2. Select the desired language in the drop down list next to the globe icon in the top left corner
drawing-4-1677042956.png


Candera Globalization API

The following table explains the usage of Globalization interfaces and how they are used in application code.

Candera::Globalization::Culture Represents a language, region, and text direction
Candera::Globalization::CultureManager

Access to cultures and changing of current culture
Singleton: Candera::Globalization::CultureManager::GetInstance()

Retrieve available cultures:

    return CultureManager::GetInstance().GetCultureCount();

Get current culture:

    Culture::SharedPointer culture = CultureManager::GetInstance().GetCurrentCulture();

Get culture name:

    Culture::SharedPointer culture = CultureManager::GetInstance().GetCulture(index);
    return culture->GetDisplayName();

Set new culture:

    Culture::SharedPointer culture = CultureManager::GetInstance().GetCulture(index);
    FEATSTD_LOG_INFO(" -> Select culture '%s'", culture->GetDisplayName());
    rc = CultureManager::GetInstance().SetCurrentCulture(culture->GetLocale());
Candera::Globalization::CultureChangeListener

Base class for listeners to culture change events

Notification of CultureChangeEvents

FeatStd::String and FeatStd::TextId

Point of access to a text in currently active language

Non-translatable String

Translatable String

Tutorial for Globalization Support

Custom Localizer Support

Requirements for Custom Localizer

In order to use a custom Localizer, a CMake feature switch CANDERA_CUSTOM_LOCALIZER_ENABLED has to be enabled. This flag will disable the usage of DefaultLocalizer which loads LanguagePacks from an asset.

In addition, please ensure that the following are fulfilled:

  • Candera Globalization sources available
  • CMake setting CANDERA_GLOBALIZATION_ENABLED is enabled

Defining a Custom Localizer

A custom Localizer class (eg: CustomLocalizer) should derive from abstract class Candera::Globalization::Localizer and provide implementations for the pure virtual functions:

For consistency, use FEATSTD_TYPEDEF_SHARED_POINTER(CLASS) and FEATSTD_SHARED_POINTER_CREATE_DECLARATION() macros to make the class manageable by SharedPointer.

Please note that FEATSTD_SHARED_POINTER_DECLARATION() macro should not be used in this case, because some base class methods will be overwritten and SharedPointer memory management will not work correctly.

Implement corresponding Create() method like in the following snippet:

/******************************************************************************
  Create
 ******************************************************************************/
CustomLocalizer::SharedPointer CustomLocalizer::Create()
{
    return CustomLocalizer::SharedPointer(FEATSTD_NEW(CustomLocalizer));
}

Example: Using a Custom Localizer in Applications

Define cultures

Create new cultures and add them to the list of available cultures of Candera::Globalization::GlobalizationCultureManager:

    m_cultureManager.RemoveAllCultures();
    for (UInt16 i = 0; i < cultureListSize; ++i) {
        Candera::Globalization::Culture::SharedPointer culture = Culture::Create();
        culture->SetLocale(cultureList[i].locale);
        culture->SetTextDirection(cultureList[i].textDirection);
        culture->SetDisplayName(cultureList[i].locale);
        m_cultureManager.AddCulture(culture);
        m_cultureVct.Add(culture);
    }

Set default culture to be used in case no current culture is set. This step is mandatory since the default culture will be used for initialization.

        m_cultureManager.SetDefaultCulture(m_cultureVct[0]);
Use a custom localizer

Declare a custom localizer:

    CustomLocalizer::SharedPointer m_localizer1;
    CustomLocalizer::SharedPointer m_localizer2;

Create the custom localizer:

    m_localizer1(CustomLocalizer::Create()),
    m_localizer2(CustomLocalizer::Create())

After a custom localizer has been created, the new localizer needs to be registered to the CultureManager:

    GlobalizationCultureManager::GetInstance().SetLocalizer(m_localizer1);

Initialize culture by setting current culture. If current culture is not set, default culture will be used.

        m_cultureManager.SetCurrentCulture(m_cultureVct[0]);
Switching localizer during run-time

Since only one localizer is allowed to be registered, in order to set a new localizer, the previous one has to be unregistered by calling SetLocalizer(0), like in the following snippet.

            GlobalizationCultureManager::GetInstance().SetLocalizer(CustomLocalizer::SharedPointer(0));
            GlobalizationCultureManager::GetInstance().SetLocalizer((toggleLocalizer1) ? m_localizer1 : m_localizer2);

Corresponding localizer cultures have to be added to the CultureManager and default culture has to be defined if no current culture is available.

Tutorial for Animation Manipulation

Tutorial for Animation Manipulation

Configuring and Loading an Animation

Candera Animation Concept

The following key concepts are required to run animations in a Candera application:

In SceneComposer, animations can be configured in the AnimationDesign view:

  • Create a new animation - New Animation in SceneComposer User Manual
  • Select a node property to animate - Add Animated Property in SceneComposer User Manual
  • Specify the key frame sequence including property values and interpolation strategy - Animation Values in SceneComposer User Manual

It is also possible to import an animation to SceneComposer from a content generation tool via FBX format - How to Import Resources in SceneComposer User Manual

Configuring Animation Player

In the Player or in an application, the animation can be retrieved from the generated asset by specifying its Candera name (e.g. MyModule#Animations#Animation).

    AssetProvider* assetProvider = GetAssetProvider();
    m_animPlayer = assetProvider->GetAnimation(m_animName);

In SceneComposer the animation can be retrieved from AssetProvider only if it was previously loaded in the scene. A better way to access it directly is to define it as a widget property.


                // Define Animation property
                CdaProperty(Animation,Candera::Animation::AnimationPlayer::SharedPointer, GetAnimation, SetAnimation)
                    CdaDescription("Animation")
                CdaPropertyEnd()
                // End Define Animation property


Next, as already explained in Initializing AssetLoader and Animations, an instance of Candera::Animation::AnimationTimeDispatcher is needed to provide current world time to animations.

For this, the new animation player must be added to the Candera::Animation::AnimationTimeDispatcher instance maintained by the application.

        // Add player to time dispatcher

        Animation::AnimationTimeDispatcher::SharedPointer timeDispatcher = GetAnimationTimeDispatcher();
        if (timeDispatcher == 0) {
            return;
        }
        static_cast<void>(timeDispatcher->AddPlayer(m_animPlayer));

        // End Add player to time dispatcher


Now the animation player is ready to be used.

Tutorial for Animation Manipulation

Controlling Animations

Controlling Animation Flow

This section describes the control flow interfaces of Candera::Animation::AnimationPlayer.

Animation Speed, Repeats, Time Modulation

This section specifies more interfaces to set animation player properties relevant for timing and execution flow.

Tutorial for Animation Manipulation

Using Animation Callback Functions

Animation Event Listener

If an application shall be notified about events of the triggered animation, a listener needs to be implemented. The class Candera::Animation::AnimationPlayerListener allows to listen to a versatile set of animation events like:

  • OnStartAntimation,
  • OnResumeAnimation,
  • OnPauseAnimation,
  • OnDirectionChange,
  • OnStopAnimation,
  • OnFinishAnimation,
  • OnPastEnd.

Implement AnimationPlayerListener Functions

In order to register an AnimationPlayerListener to an AnimationPlayer simply derive from Candera::Animation::AnimationPlayerListener and override pure virtual functions with custom code.

In the example below the 3D application derives from AnimationPlayerListener and overrides the listener's functions.

class AnimationPlayerWidget : public CgiWidget3D, public Candera::Animation::AnimationPlayerListener

        virtual void OnPastEnd(Candera::Animation::AnimationPlayerBase* animationPlayer, Candera::Int32 completedIterationsCount);
        virtual void OnStartAnimation(Candera::Animation::AnimationPlayerBase* animationPlayer);
        virtual void OnStopAnimation(Candera::Animation::AnimationPlayerBase* animationPlayer);
        virtual void OnFinishAnimation(Candera::Animation::AnimationPlayerBase* animationPlayer);
        virtual void OnResumeAnimation(Candera::Animation::AnimationPlayerBase* animationPlayer);
        virtual void OnPauseAnimation(Candera::Animation::AnimationPlayerBase* animationPlayer);

Use Candera::Animation::AnimationPlayerBase::AddAnimationPlayerListener and Candera::Animation::AnimationPlayerBase::RemoveAnimationPlayerListener for registering or removing a listener.

        static_cast<void>(m_animPlayer->AddAnimationPlayerListener(this));

        static_cast<void>(m_animPlayer->RemoveAnimationPlayerListener(this));

Now the application is notified about all animation listener events it has implemented. For example when implementing the Candera::Animation::AnimationPlayerListener::OnStartAnimation, the application is notified about all started animations.

void AnimationPlayerWidget::OnStartAnimation(Animation::AnimationPlayerBase* /* animationPlayer */)
{
    // do something when an animation has started
}
Tutorial for Animation Manipulation

Manipulating Animation Keyframe Sequences

This method allows to modify specific keyframes of a previously initialized keyframe sequence.

Define Keyframe Values

Size of array must be equal with the number of values that are already assigned to each keyframe (numberOfComponents).

    Float keyFrameManipulationValues[3] =
    {
        1.0, 1.0, 11.0
    };

Set Keyframe Values

Values will be attached to one keyframe.

    m_keyframeSequence->SetKeyframeValues(1, keyFrameManipulationValues[0]);

Tutorial for Graphic Device Interface

Tutorial for Graphic Device Interface

Cameras and Render Targets

Description

Content specified in a Candera scene graph is rendered by configuring at least one camera on it. The render result of the camera is projected on a so called render target.

This chapter gives an overview about cameras and render targets in Candera.

Go to EGL Context Handling to skip the introduction and proceed with the first example for this topic.


Camera Orientation 

drawing-4-1677207143.png

  • setting a "LookAt-Node." If such a node is set the camera will always look at that node. This behavior overrides other rotation transformations set by the methods mentioned above. The node can be set by the function Candera::Camera::SetLookAtNode.

The viewing orientation of the Candera::Camera2D can be controlled only by modifying the local rotation using the Candera::Transformable2D::SetRotation function.

See also:

Camera Projections 

Candera::Camera::SetProjection is analogous to choosing a lens for the camera.

  • Candera::PerspectiveProjection: This is the most commonly used projection. It provides proper visual effects depending on the distance between 3D objects and the camera projecting them. Specify the field of view (FoV) in degrees and the aspect ratio to adjust the camera lens properly.
drawing-4-1677207320.png
  • Candera::OrthographicProjection: This tutorial chapter has a use case where an OrthographicProjection is needed instead of a PerspectiveProjection. This projection type provides an orthogonal view on the projection plane.
    drawing-4-1677207400.png



Camera Viewport 

Use Candera::Camera::SetViewport to specify a rectangular region on the render target for projecting the camera output. The position and size of the viewport should be given as normalized screen coordinates [0..1]. If the viewport aspect ratio differs from the aspect ratio of the Camera's projection matrix, the final render result will appear stretched or compressed. The default value for viewport to cover the complete render target area is: left 0, right 0, width 1, height 1.

The example below shows how to set the viewport in case of two cameras connected to the same display render target:

drawing-4-1677207440.png


    perspectiveProjection->SetAspectRatio(1.3333f)); // (1280/2)/480
    ...
    m_camera1 = Camera::Create();
    m_camera1->SetViewport(Candera::Rectangle(0.0f, 0.0f, 0.5f, 1.0f));
    m_camera1->SetRenderTarget(m_gdu->ToRenderTarget3D());
    m_camera1->SetProjection(perspectiveProjection);
    m_camera1->SetClearMode(m_clearMode1);
    m_camera1->SetClearModeEnabled(true);
    m_camera1->SetSwapEnabled(true);

    m_camera2 = Camera::Create();
    m_camera2->SetViewport(Candera::Rectangle(0.5f, 0.0f, 0.5f, 1.0f));
    m_camera2->SetRenderTarget(m_gdu->ToRenderTarget3D());
    m_camera2->SetProjection(perspectiveProjection);
    m_camera2->SetClearMode(m_clearMode2);
    m_camera2->SetClearModeEnabled(true);
    m_camera2->SetSwapEnabled(false)

Candera::Camera2D::SetViewport works similarly but in 2D context. The default value for a viewport of a 2D camera are:

  • left: 0
  • right: 0
  • width: -1
  • height: -1

The viewport position and size of a Candera::Camera2D are given in pixels.

Below is presented an example similar with the one above but adapted for 2D.

drawing-4-1677207477.png


    m_camera2D_1 = Camera2D::Create();
    m_camera2D_1->SetViewport(Candera::Rectangle(0.0f, 0.0f, 640.0f, 480.0f));
    m_camera2D_1->SetRenderTarget(m_gdu->ToRenderTarget2D());
    m_camera2D_1->SetClearColor(Color(0.8f, 0.9f, 0.7f, 0.1f));
    m_camera2D_1->SetClearColorEnabled((true);
    m_camera2D_1->SetSwapEnabled(true);

    m_camera2D_2 = Camera2D::Create();
    m_camera2D_2->SetViewport(Candera::Rectangle(640.0f, 0.0f, 640.0f, 480.0f));
    m_camera2D_2->SetRenderTarget(m_gdu->ToRenderTarget2D());
    m_camera2D_2->SetClearColor(Color(0.9f, 0.8f, 0.7f, 0.1f));
    m_camera2D_2->SetClearColorEnabled((true);
    m_camera2D_2->SetSwapEnabled(false);

Render Targets 

A render target is a buffer where the video card draws pixels for a scene that is being rendered. Consequently, once a camera is configured, it must be linked to a render target to specify the target for the camera output.

Display and Offscreen Render Targets

There are basically two different types of render targets (Candera::GraphicDeviceUnit) available in Candera, which can render only 2D content, only 3D content or mixed 2D/3D content:

  • Display/Onscreen Render Targets: A frame buffer is used as render target and directly linked to a Display.
    • DisplayTarget3D
    • DisplayTarget2D
    • Mixed2D3DDisplayTarget
  • Offscreen Render Targets: A frame buffer is used as render target and can be used as image source in the same or another scene.
    • OffscreenTarget3D
    • OffscreenTarget2D
    • OffscreenTarget2Dto3D
    • OffscreenTarget3Dto2D
    • Mixed2D3DOffscreenTarget

For iMX6 device there is one more type of render target: Candera:SoftwareLayer. More details about this can be found in Software Layers chapter.

Device Capabilities

For an overview about what kind of render targets are supported on which device please refer to: Device Specific Documentation

Following is an overview about what kind of render targets are supported on which device:

Device Number of Displays Supported Types of Layers Supported Unit Types

See also:

Render Target Lifetime 

  • Create: A graphic device unit (GDU) which hosts a render target and/or an image source can be created with function DevicePackageInterface::CreateGraphicDeviceUnit.
  • Upload: Subsequently, in order to utilize GDU an upload to video memory is required, which is processed by calling function GraphicDeviceUnit::Upload on GDU object.
  • Destroy: In order to destroy video and system memory resources associated to GDU call functions GraphicDeviceUnit::Unload and DevicePackageInterface::DestroyGraphicDeviceUnit in exactly that order.

In general, follow the sequence below to upload and unload GDUs:

  • Construction:
    • 1.) Upload all displays
    • 2.) Upload all onscreen framebuffers (WindowSurface)
    • 3.) Upload all offscreen framebuffers (FramebufferBufferObject)
  • Destruction:
    • 1.) Unload all offscreen framebuffers (FramebufferBufferObject)
    • 2.) Unload all onscreen framebuffers (WindowSurface)
    • 3.) Unload all displays

Render Buffer Swapping and Clearing 

Render Buffer Swapping

If the draw target operates on multiple buffers after each rendering cycle the presentation buffer is exchanged with the current background buffer, on which was written during the cycle.

For this, three interfaces are available:

  • Candera::RenderTarget::SetAutoSwapEnabled(bool enabled): if true, render target buffers are swapped automatically, when a camera within the render target has been rendered. Default: false
  • Candera::Camera::SetSwapEnabled(bool enable): if true, buffers are swapped automatically after rendering the according camera. Default: false
  • Candera::RenderTarget::SwapBuffers(): Trigger buffer swapping 'by hand'

It is very important to control buffer swapping in the application correctly:

  • If swapping is never done, the display will stay black
  • If buffers are swapped too often, the display will flicker.

The application doesn't need to swap its render targets in case:

  • the render target is set to swap automatically. For this, set the render target property "SetAutoSwapEnabled" to true.
  • the proper camera is set to swap automatically. For this, set the camera property "SwapEnabled" to true.

If you use more than one camera on a render target, take care to enable swapping only for the last camera (highest sequence number).

Render Buffer Clearing

The render buffer can be cleared by one of the cameras attached to the render target or by the render target itself by the means of a shared clear mode object.

Render Buffer Clearing by Camera

Camera2D interface offers two methods related to render buffer clearing. First one, Candera::Camera2D::SetClearColor(), sets the clear color and the other one, Candera::Camera2D::SetClearColorEnabled(), sets whether the clear is performed or not at each render call.

    camera2D->SetClearColor(Candera::Color(0.5f, 0.5f, 1.0f, 1.0f));
    camera2D->SetClearColorEnabled(true);

Comparing to the 2D context where only the color buffer needs to be cleared, in 3D, Camera has to clear three buffers: color, depth and stencil. Camera performs the clearing using a Candera::ClearMode object which is attached to it using the method Candera::Camera::SetClearMode().

    Candera::ClearMode clearmode;
    clearmode.SetClearColor(Candera::Color(0.5f, 1.0f, 0.5f, 1.0f));          
    clearmode.SetColorClearEnabled(true);
    clearmode.SetClearDepth(1.0f);
    clearmode.SetDepthClearEnabled(true);
    
    camera3D->SetClearMode(clearmode);

Candera::Camera and Candera::Camera2D clear only the area specified by the viewport.

Render Buffer Clearing by Render Target

The render buffer clearing using the render target is useful especially when the camera viewport is not covering the entire render target. The 2D render target clears the color buffer using a shared clear mode object as shown in the example below:

    Candera::SharedClearMode2D::SharedPointer shClearMode2D = SharedClearMode2D::Create();
    shClearModem2D->GetClearColor() = Candera::Color(0.0f, 0.5f, 0.5f, 1.0f); 
    m_gdu2D->ToRenderTarget2D()->SetAutoClearEnabled(true);
    m_gdu2D->ToRenderTarget2D()->SetClearMode(shClearModem2D);

In 3D, the clearing it realized in a similar way but the render target will use a shared clear mode object which has attached a clear mode object.

    Candera::ClearMode clearmode;
    clearmode.SetClearColor(Candera::Color(0.8f, 0.4f, 0.4f, 1.0f));
    clearmode.SetColorClearEnabled(true);
    clearmode.SetClearDepth(1.0f);
    clearmode.SetDepthClearEnabled(true);

    Candera::SharedClearMode::SharedPointer shClearMode = Candera::SharedClearMode::Create();
    shClearMode->SetClearMode(&clearmode);

    gdu->ToRenderTarget3D()->SetClearMode(shClearMode);
    gdu->ToRenderTarget3D()->SetAutoClearEnabled(true);
Example

Consider two cameras & render targets, 2D and 3D, connected to a common display (see picture below). The clearing is enabled on both cameras and render targets, while clear colors for the render targets and cameras are all different.

  • Background color of area (1) is set by the clear color of Camera2D respectively the 3D Camera in the 3D Scene, since the clearing setting of the camera overrides the one from the render target.
  • Since the camera viewport is smaller than the render target size, area (2) is cleared by the render target.
  • Area (3) cannot be cleared because it is not covered by any of the two render targets.
drawing-4-1677207702.png


SceneComposer Example Solution

In SceneComposer, the configuration of render target clearing in 2D context is done similarly to the one of a 2D camera: select the Render Target and, in the Properties panel, enable the "Auto Clear Enabled 2D" property and set the clear color by modifying the "Clear Mode 2D" property.

In 3D context, the activation of auto-clearing on a render target implies to create first a ClearMode: in the Solution Explorer, right click on any of the folders from "Solution" and choose "Add->New Item...->ClearMode". In the "Add new item" dialog enable and set an appropriate value for the Color Clear, Depth Clear and Stencil Clear and then press "OK". Now, select the 3D Render Target and, in the Properties panel, set "Auto Clear Enabled" property to "True" and then click on the "Clear Mode" property and select the previously created ClearMode. An example of usage the render target auto-clear feature in SceneComposer is presented in the solution RenderTargetAutoClearSolution example in folder cgi_studio_player/content/Tutorials/07_RenderTargetsCameras.

Software Layers 

Overview

The Candera::SoftwareLayer is a special mixed graphic device unit supported only on specific device packages. This graphic device unit, despite the fact it is a texture render target, it is not meant to be used as Image source in a texture but rather it acts like a display render target by the means of a composition camera (see diagram below).

Software Layers are not supported on all devices! Software Layers are supported on iMX6. An example solution for this concept can be seen in cgi_studio_player/content/Tutorials/07_RenderTargetsCameras solution.

Configuration

For a correct behaviour of the scene editor the composition camera has to be placed in a separate scene.

The number of software layers which can be blended in one pass is limited by the number of texture units specific for the device.

  • For specific devices the maximum number of software layers is 8.

Each of these virtual layers can have a distinct blending mode, buffer settings, size or position, but all have to use the same composition camera, the Candera::Camera which is linked to the display render target:

drawing-4-1677207787.png

The software layers are delimited by a 1 px border. If needed, this border can be disabled using the "BorderEnabled" property.

Layer Order

The software layers are ordered depending on the value set for the Layer property, bigger values meaning that the layer will be closest to the composition camera rendering the main scene.

Blend Modes

Following blend modes are supported for Software Layers:

Source Destination Constant Alpha Blend Mode Formula Output


0.5 SoftwareLayerBlendModeStandard SRC * SRC.A + DST * (1 - SRC.A)
SoftwareLayerBlendModePreMultiplied SRC + DST * (1 - SRC.A)
SoftwareLayerBlendModeConstantAlpha SRC * CNS.A + DST * (1 - CNS.A)
SoftwareLayerBlendModeSimultaneousAlpha SRC * SRC.A * CNS.A + DST * (1 - SRC.A * CNS.A)
SoftwareLayerBlendModeSimultaneousPreMultiplied SRC * CNS.A + DST * (1 - SRC.A * CNS.A)
SoftwareLayerBlendModeOff SRC

For more information about alpha blending please read also the Blending chapther.

Insides

As any other graphic device unit the Candera::SoftwareLayer can be created via Candera::DevicePackageInterface::CreateGraphicDeviceUnit.

  • Use Candera::DevicePackageDescriptor::GetUnitTypes to get its type handle as a second parameter for the offscreen render target categories.
  • The SoftwareLayer properties are specified in the Candera::SoftwareLayerProperties class (see Candera::SoftwareLayer::GetSoftwareLayerProperties)
  • and the pixel format information is kept in the Candera::GlFrameBufferProperties class (see Candera::SoftwareLayer::GetFrameBufferProperties()).
Tutorial for Graphic Device Interface

EGL Context Handling

Description

EGL is an interface between rendering APIs like OpenGL ES and the underlying native platform window system. Rendering APIs like OpenGL (ES) and OpenVG use EGL contexts to manage their states and resources.

EGL Concepts 

EGL Context: WindowSurface

An EGL context typically stores render states, shaders, textures, vertex buffers and framebuffer resources. In Candera each WindowSurface (onscreen framebuffer) creates an EGL context when uploaded. Thus, each WindowSurface owns its separate context to store related render states, like for instance depth buffer, and blend mode settings.

EGL Context Sharing: ContextResourcePool

An EGL context can also be shared to store resources like textures, vertex buffer objects, and shaders. This feature avoids redundant video memory uploads of shared resources to multiple EGL contexts and reduces memory footprint. Candera provides context sharing functionality with class ContextResourcePool. Each display is associated to exactly one context resource pool. One context resource pool can be shared accross different displays, if supported by platform. In case context sharing is not supported, for instance as displays are driven by separate display controllers, Candera allows to upload resources of a single scene graph to multiple EGL contexts.

Framebuffer Objects using ContextResourcePool

Framebuffer objects (FBOs) are defined in OpenGL ES API and do, different to WindowSurfaces, not own a dedicated EGL context. Instead, FBOs require a context provided from an EGL surface to store related data in video memory. In Candera a WindowSurface is such a context provider, which registers its EGL context to ContextResourcePool to be shared. FBOs

Uploading To Multiple Contexts 

Overview

A device object can be uploaded to multiple context resource pools but there is no association from a device object to its context resource pools, before the uploading. Further, AssetLoader could upload a device object to a context resource pool activated accidentally. In order to prevent such situations, Candera offers a dedicated interface meant to give more control in the process of uploading/unloading device objects in multiple contexts.

Multiple Contexts Uploading

The uploading to more then one context at a time can be accomplished by the means of the Node::UploadAll() method. There is also a method, Node::UnloadAll(), which does the reverse operation, the unloading from video memory.

These methods take two parameters, a scope mask and a multi context flag. The multiContext flag controls if the operation is performed strictly on the active context or on all existing context resource pools belonging to the cameras which are in the scope of the upload/unload operations. The scopeMask parameter is useful to filter the nodes which are to be uploaded/unloaded from scenes containing multiple nodes. The algorithm of filtering using the scope mask works as follows:

  • First it creates a list of cameras which are in the scope of the upload/unload operation.
  • Then, for each of these cameras, it will iterate over the nodes "seen" by the camera and it will upload/unload only the nodes (and their descendants) which are in the scope of the camera intersected with the scope of the upload/unload operation. The context resource pool targeted by this operation is the one belonging to the camera.

For a better understanding of the feature, please have a look on the example presented on the next item.

Code Example
    Candera::ScopeMask m_scopeMask;
    m_scopeMask.SetAllScopesEnabled(false);
    m_scopeMask.SetScopeEnabled(2, true);

    m_group->UploadAll(m_scopeMask, true);
    //...
    m_group->UnloadAll(m_scopeMask, true);

Uploading To Multiple Contexts Example 

Introduction

The example below tries to offer you a better understanding of the way the method Candera::Node::UploadAll() / Candera::Node::UnloadAll() works. In order to do that, we chose a complex configuration in which the nodes to be uploaded, the cameras and the uploading method itself, are set with different values for the scope masks.

Scene Setup

The setup consists in a 3D scene which contains three nodes: a sphere, a cube and a pyramid, all three nodes are children of a Candera::Group. The 3D content is rendered by two cameras on two displays.

We assume that the context resource pools associated to the displays are not shared, so each display will have its own context resource pool. Each of these nodes and cameras have assigned a distinct Candera::ScopeMask as follows:

Object Scope Mask
Sphere 1
Pyramid 2
Cube All
Camera 1 1
Camera 2 All

The scene setup is shown also in the image below:

drawing-4-1677216099.png

The mask scopes are created as follows:

    Candera::ScopeMask m_scopeMask1;
    m_scopeMask1.SetAllScopesEnabled(false);
    m_scopeMask1.SetScopeEnabled(1, true);

    Candera::ScopeMask m_scopeMask2;
    m_scopeMask2.SetAllScopesEnabled(false);
    m_scopeMask2.SetScopeEnabled(2, true);

    Candera::ScopeMask m_scopeMaskAll;
    m_scopeMaskAll.SetAllScopesEnabled(true);

Then they are set as scope masks for the nodes:

    m_meshSphere->SetScopeMask(m_scopeMask1);
    m_meshPyramid->SetScopeMask(m_scopeMask2);
    m_meshCube->SetScopeMask(m_scopeMaskAll);
    m_camera1->SetScopeMask(m_scopeMask1);
    m_camera2->SetScopeMask(m_scopeMask2);

For the convenience, we will assume that the last context activated by the application is Context Resource Pool 1.

Upload Scenarios

The table below shows in yellow the input parameters for the UploadAll method and in blue whether the nodes are uploaded or not and in which context.

For example, the first row in the table data says that the UploadAll method was called with the parameters:

    m_group->UploadAll(/*scopeMask=*/m_scopeMask1, /*multiContext=*/true);

Fifth row correspond to the following call:

    m_group->UploadAll(/*scopeMask=*/m_scopeMask2, /*multiContext=*/false);

And so on.

Index MultiContext flag Upload scope mask Uploaded in CRP 0 Uploaded in CRP 1
1 2 All Cube Sphere Pyramid Cube Sphere Pyramid
1

ok.png

ok.png

cancel.png

cancel.png
ok.png
ok.png
cancel.png
ok.png
ok.png
cancel.png
2
ok.png
cancel.png
ok.png
cancel.png
cancel.png
cancel.png
cancel.png
ok.png
cancel.png
ok.png
3
ok.png
cancel.png
cancel.png
ok.png
ok.png
ok.png
cancel.png
ok.png
ok.png
ok.png
4
cancel.png
ok.png
cancel.png
cancel.png
cancel.png
cancel.png
cancel.png
ok.png
ok.png
cancel.png
5
cancel.png
cancel.png
ok.png
cancel.png
cancel.png
cancel.png
cancel.png
ok.png
cancel.png
ok.png
6
cancel.png
cancel.png
cancel.png
ok.png
cancel.png
cancel.png
cancel.png
ok.png
ok.png
ok.png

Result explanation:

1) Because multi context flag is enabled, the application will try to upload device objects in both context resource pools. Both cameras are in the scope of the upload (scope 1). Camera 1, corresponding to the Context Resource Pool 0 has the scope mask 1 so the application will upload the Cube (scope mask all) and the Sphere (scope mask 1) but not the pyramid (scope mask 2). In the Context Resource Pool 1, corresponding to the Camera 2 (scope mask all) the application will upload Cube (scope mask all) and the Sphere (scope mask 1). The pyramid will not be uploaded because it is not in the scope of upload intersected with scope of the camera (1 & "all" = 1).

2) Because multi context flag is enabled, the application will try to upload device objects in both context resource pools but only Camera 2 is in the scope of the upload (scope mask 2). Consequently, application will not upload anything in the Context Resource Pool 0 corresponding to the Camera 1. In the Context Resource Pool 1, corresponding to the Camera 2 (scope mask all), the application will upload the Cube (scope mask all) and the Pyramid (scope mask 2). The Sphere (scope mask 1) will not be uploaded because it is not in the scope of upload intersected with scope of the camera (2 & "all" = 2).

3) Because multi context flag is enabled, the application will try to upload device objects in both context resource pools. Both cameras are in the scope of the upload (scope maks all). Camera 1, corresponding to the Context Resource Pool 0 has the scope mask 1 so the application will upload the Cube (scope mask all) and the Sphere (scope mask 1). The pyramid will not be uploaded because it is not in the scope of upload intersected with scope of the camera ("all" & 1 = 1). In the Context Resource Pool 1, corresponding to the Camera 2, all three nodes will be uploaded.

4) The multi context flag is disabled, so the upload will be performed only in the last activated context (Context Resource Pool 1). Consequently, the application will not upload anything in the Context Resource Pool 0. Camera 2 (scope mask all) is in the scope of the upload (scope mask 1) so it will be taken into account for the upload. In the Context Resource Pool 1, corresponding to the Camera 2 (scope mask all) the application will upload Cube (scope mask all) and the Sphere (scope mask 1). The pyramid will not be uploaded because it is not in the scope of upload intersected with scope of the camera (1 & "all" = 1).

5) The multi context flag is disabled, so the upload will be performed only in the last activated context (Context Resource Pool 1). Consequently, the application will not upload anything in the Context Resource Pool 0. Camera 2 (scope mask all) is in the scope of the upload (scope mask 2) so it will be taken into account for the upload. In the Context Resource Pool 1, corresponding to the Camera 2 (scope mask all), the application will upload the Cube (scope mask all) and the Pyramid (scope mask 2). The Sphere (scope mask 1) will not be uploaded because it is not in the scope of upload intersected with scope of the camera (2 & "all" = 2).

6) The multi context flag is disabled, so the upload will be performed only in the last activated context (Context Resource Pool 1). Consequently, the application will not upload anything in the Context Resource Pool 0. Camera 2 (scope mask all) is in the scope of the upload (scope mask 1) so it will be taken into account for the upload. In the Context Resource Pool 1, corresponding to the Camera 2, all three nodes will be uploaded because their scope are in the scope of upload intersected with scope of the camera ("all" & "all" = "all") .


Tutorial for Graphic Device Interface

Multiple Render Targets

Description

This section explains how to handle Multiple Render Targets by means of a small ping pong like tutorial solution.

Example Solution

  • RenderTargetsCamerasSolution in folder cgi_studio_player/content/Tutorials/07_RenderTargetsCameras



Handling Multiple Render Targets 

The example solution in cgi_studio_player/content/Tutorials/07_RenderTargetsCameras consists of the following:

  • a main scene rendered to Candera::iMX6WindowSurface render target
  • three different scenes rendered to three different Candera::SoftwareLayer render targets.

The three Candera::SoftwareLayer render targets are linked to the Candera::iMX6WindowSurface render target through their Compostion Camera property and will be blended against each other.

Layer 1: Animated 2D Background

The lowermost layer is a collection of bubbles which are animated to bubble endlessly from the bottom of the 2D screen to the top. It is the lowest layer, because the dreary, transparent bubbles shall be colored by blending with a more colorful layer.

drawing-4-1677216810.png

Layer 2: Static 2D Background

This is the layer which will give the animated bubbles some colors.

drawing-4-1677216831.png

Layer 3: Dynamic 3D Top Layer

Finally, the top layer shows a 3D scene with a sphere.

drawing-4-1677216849.png

The sphere can be linked with the "BouncingNodeWidget". Once this widget is enabled in the Player, it will send the sphere into several directions like in a Ping Pong game. This is the reason, why the camera of this scene needs an OrthographicProjection: The widget needs to match the sphere position on the x and y axis with the boundaries of the 2D projection plane.

Use the velocity widget property to play with different velocities.


Layer Order and Layer Blending 

Layer Order from Lowest to Top

The correct layer order must be specified on the property "Layer" on the render targets, the bigger value for the top layer:

  • Lowest Layer: Layer-ID 0
  • Medium Layer: Layer-ID 1
  • Top Layer: Layer-ID 2
drawing-4-1677216954.png

Export Asset and Load with the Player
  • Export the example solution to a simulation asset
  • When the asset is loaded in the Player, take care to first activate the main scene (Scene3DOwner from solution) and then the other scenes.


Render Target Manipulation 

Accessing Render Target Properties

The BouncingNodeWidget uses a property to specify the name of a layer. Width and height of this layer is used by the widget to let the node bounce within these boundaries.

Refer to the following code example to access width and height of this layer:

        if (m_layer != 0){
            RenderTarget3D* rt3d = m_layer->ToRenderTarget3D();
            if (rt3d != 0){
                m_layerWidth = rt3d->GetWidth();
                m_layerHeight = rt3d->GetHeight();
            }
        }

Accessing Render Target Properties by MetaInfo

Same properties could be accessed by meta information as follows:

    const MetaInfo::GraphicDeviceUnitMetaInfo *meta = DevicePackageDescriptor::GetMetaInformation(gdu->GetUnitType());
    if (meta){
        if (meta->LookupItem("Width") != 0){
            meta->LookupItem("Width")->Get(gdu,m_width,sizeof(m_width));
        }
        if (meta->LookupItem("Height") != 0){
            meta->LookupItem("Height")->Get(gdu,m_height,sizeof(m_height));
        }
        if (meta->LookupItem("BlendMode") != 0){
            meta->LookupItem("BlendMode")->Get(gdu, m_blendMode,sizeof(m_blendMode));
        }
        // ...
    }
Accessing Software Layer Properties

The code snippet below shows how to set the software layer properties for an iMX6 device.

    Candera::GraphicDeviceUnit* m_gdu = Candera::DevicePackageInterface::CreateGraphicDeviceUnit(c_softwareLayerTypeHandle); // c_softwareLayerTypeHandle = 0x1a7e4
    static_cast<Candera::SoftwareLayer*>(m_gdu)->GetSoftwareLayerProperties().CompositionCamera() = m_camera3DiMX6;
    //...


Tutorial for Graphic Device Interface

Texture Render Targets

Description

This section explains how to handle Texture Render Targets in 2D and 3D.

Example Solutions

  • TextureRenderTargetsSolution in folder cgi_studio_player/content/Tutorials/07_RenderTargetsCameras
  • MixedRenderTargetsSolution in folder cgi_studio_player/content/Tutorials/07_RenderTargetsCameras


Define Texture Render Targets 

To illustrate the usage of an OffScreenTarget2D, open the solution cgi_studio_player/content/Tutorial/07_RenderTargetCameras/TextureRenderTargetsSolution.

Offscreen Scene Content

First of all, "offscreen" scenes must be defined, which shall later be rendered into textures:

  • OffscreenScene2D: This 2D scene contains a white box with the text "2D" in it.
  • OffscreenScene3D: This 3D scene contains a Text3DWidget with text "3D" and a light.
Offscreen Scene Cameras

Next, cameras must be defined for rendering the offscreen scenes into textures.

In case of the OffscreenScene2D, two different cameras are defined:

  • 2dcam_to_2d: This camera will be linked to a OffscreenRenderTarget2D, for rendering into a 2D texture.
  • 2dcam_to_3d: This camera will be linked to a OffscreenRenderTarget2Dto3D, for rendering into a 3D texture.
drawing-4-1677217476.png

Similarly, the OffscreenScene3D defines following two cameras:

  • 3dcam_to_3d: This camera will be linked to a OffscreenRenderTarget3D, for rendering into a 3D texture.
  • 3dcam_to_2d: This camera will be linked to a OffscreenRenderTarget3Dto2D, for rendering into a 2D texture.
drawing-4-1677217501.png

Define Offscreen Render Targets

Finally, all the cameras from Offscreen scenes must be linked to the related offscreen render targets, for rendering the content of offscreen scenes:

  • OffscreenTarget2D: 2D render target for the text "2D"
  • OffscreenTarget2Dto3D: 3D render target for the text "2D"
  • OffscreenTarget3D: 3D render target for the text "3D"
  • OffscreenTarget3Dto2D: 2D render target for the text "3D"



Use Texture Render Targets 

Use Texture Render Targets in a 2D Scene

The 2D scene "OnscreenScene2D" is rendered on a DisplayRenderTarget2D and shall include the render outputs from both the 2D and 3D offscreen scenes.

For this, bitmap nodes are defined in this scene which use the following offscreen render targets as textures:

  • OffscreenTarget2D
  • OffscreenTarget3Dto2D
drawing-4-1677217557.png

Use Texture Render Targets in a 3D Scene

The 3D scene "OnscreenScene3D" is rendered on a DisplayRenderTarget3D and shall include the render outputs from both the 2D and 3D offscreen scenes.

For this, two cubes are defined in this scene which use the following offscreen render targets as textures:

  • OffscreenTarget3D
  • OffscreenTarget2Dto3D
drawing-4-1677217572.png

Composition Camera Property Set

The Composition Camera property of the DisplayRenderTarget3D and DisplayRenderTarget2D render targets must be set to the camera linked with RenderTargetOwner. These is mandatory for rendering the composed contend of the two display render targets.

The Layer property must be correctly set for Candera::SoftwareLayer render targets (DisplayRenderTarget3D and DisplayRenderTarget2D). The layers must have different values, with the lowest value for the lowest layer.


Example Solution 

Another example of using Texture Render targets can be seen in the tutorial solution cgi_studio_player/content/Tutorials/07_RenderTargetsCameras/MixedRenderTargetsSolution.

OffscreenTarget2Dto3D, OffscreenTarget3Dto2D and Mixed2D3DOffscreenTarget render targets are used as texture for 3D meshes or as Image for BitmapBrush effects. The Mixed2D3DDisplayTarget is linked to the display and can be used for 2D and/or 3D content. In the tutorial solution MixedRenderTargetsSolution there are two cameras (2D and 3D) attached to the Mixed2D3DDisplayTarget. The resulted image on display shows the content of the both scenes overlapped.

drawing-4-1677217606.png

Rules to configure multiple cameras attached to a render target 
- Camera 2D: Clear Color Buffer = false; Enable Swapping = true
- Camera 3D: Clear Color Buffer = true; Enable Swapping = false


The pictures from this tutorial where obtained from the following sources:

[1] www.shutterstock.com/pic-12442312/stock-vector-a-business-man-runs-amp-power-walks-to-success-in-animation-sequence-frame-loops-with.html




Tutorial for Graphic Device Interface

Camera Groups

Description

This section explains how to use Camera Groups to handle cameras uniformly across multiple scenes.

Example Solution

  • CameraGroupSolution example in folder cgi_studio_player/content/Tutorials/07_RenderTargetsCameras


Camera Group - Interface 

Candera::CameraGroup is a container that groups an arbitrary number of 2D and/or 3D cameras from different scenes. This feature is especially useful for complex applications because it offers a simpler way to control which cameras are rendering at a certain moment.

The snippet below shows how to add cameras to a Candera::CameraGroup object:

    m_cameraGroup[0].AddCamera2D(m_cameraCubeTex1);
    m_cameraGroup[0].AddCamera2D(m_cameraSphereTex1);
    m_cameraGroup[0].AddCamera(m_cameraFront);

    m_cameraGroup[1].AddCamera2D(m_cameraCubeTex2);
    m_cameraGroup[1].AddCamera2D(m_cameraSphereTex1);
    m_cameraGroup[1].AddCamera(m_cameraFront);

    m_cameraGroup[2].AddCamera2D(m_cameraCubeTex2);
    m_cameraGroup[2].AddCamera2D(m_cameraSphereTex2);
    m_cameraGroup[2].AddCamera(m_cameraFront);

    m_cameraGroup[3].AddCamera2D(m_cameraCubeTex2);
    m_cameraGroup[3].AddCamera2D(m_cameraSphereTex2);
    m_cameraGroup[3].AddCamera(m_cameraSide);

    m_cameraGroup[4].AddCamera2D(m_cameraCubeTex1);
    m_cameraGroup[4].AddCamera2D(m_cameraSphereTex2);
    m_cameraGroup[4].AddCamera(m_cameraSide);

    m_cameraGroup[5].AddCamera2D(m_cameraCubeTex1);
    m_cameraGroup[5].AddCamera2D(m_cameraSphereTex1);
    m_cameraGroup[5].AddCamera(m_cameraSide);

Then, any group can be enabled for rendering as follows:

    m_cameraGroup[idx].SetRenderingEnabled(true);
    if (idx < 3){
        m_cameraFront->SetSwapEnabled(true);
        m_cameraSide->SetSwapEnabled(false);
    }
    else{
        m_cameraFront->SetSwapEnabled(false);
        m_cameraSide->SetSwapEnabled(true);
    }

Camera Groups - Example 

The diagram below presents a brief example of how to dynamically set 3D nodes textures by enabling the rendering of different camera groups.

drawing-4-1677217896.png


The texture of each of the two nodes from the 3D scene is an offscreen render target linked to a pair of cameras 2D. Each of the 2D cameras is "seeing" a pattern which is "projected" on the node surface when the camera is enabled for rendering. The cameras are added in CameraGroups, each camera group contains three cameras: a camera 3D and two camera 2D corresponding to the cube and sphere textures. By enabling/disabling the rendering of different camera groups the textures on the nodes will change accordingly. For best results enable for rendering only one camera group at a time.

A camera can belong to multiple groups, but its state is unique, meaning that enabling the camera in one group will enable it in all groups and also in the Render Target panel where it is associated to a render target. If a group is disabled or enabled, all the cameras inside it will be disabled or enabled. If one or many from those cameras are members of another group, the second group will be enabled or disabled too.

See also:

Example Solution
  • CameraGroupSolution example in folder cgi_studio_player/content/Tutorials/07_RenderTargetsCameras illustrates the scenario presented above. In order to enable/disable camera groups in SceneComposer, select the Render Targets panel and press on the third button showing the tooltip "Go to Camera Group Explorer view".


Tutorial for Graphic Device Interface

Proxy Texture Image

Description

This section describes how to use the proxy texture images and how to create custom image sources.



Texture Images 

Candera offers out of the box usage of 2D and CubeMap textures in 3D scenes, which is sufficient in many cases. These two texture types are directly represented through the classes

However, these two derivations of Candera::TextureImage don't represent all use cases that can be realized with textures.

ProxyTextureImage

For this reason a third derivation, namely, Candera::ProxyTextureImage exists, that simply forwards an external texture handle to the texture activation procedures, in order to display other input than the previously mentioned image data. Detailed description of usage is given in chapter Proxy Texture Images.

One use case that can be realized with Candera::ProxyTextureImage is the usage of off-screen render targets in order to use arbitrary rendered images of 3D scenes as texture image. Refer to the first example part of this tutorial:

Other use cases, such as video textures can only be realized using (often proprietary) OpenGL ES 2.0 extensions. How to create an ImageSource3D that can be used for these use cases is the second part of this tutorial. Refer to:



Proxy Texture Images 

In Candera Candera::TextureImage provides an interface to upload and activate a textures VRAM handle. This interface is further used by Candera::Texture to separate the common settings of textures and the specialized texture types. This chapter focuses on the use of Candera::ProxyTextureImage, for details on texturing itself, please refer to the Appearance Tutorial on Textures.

Knowledge of the following Candera::TextureImage functions is needed to understand Candera::ProxyTextureImage.

virtual Handle GetVideoMemoryHandle() const = 0;

exposes a textures VRAM handle to all clients that use Candera::TextureImage. This is needed in order to enable texture render states and to activate the texture.

virtual bool IsMipMappingEnabled() const = 0;

returns if mip mapping is enabled on a texture or not.

These functions are directly implemented and coupled to fixed functionality in the derivations Candera::BitmapTextureImage and Candera::CubeMapTextureImage.

Candera::ProxyTextureImage also implements the Candera::TextureImage interface, but in contrast to the other implementations it takes Candera::ImageSource3D objects, and simply forwards their VRAM handles through the GetVideoMemoryHandle function. Setting of the ImageSource can only be done at creation time, use the ProxyTextureImage's factory function for doing this.

static MemoryManagement::SharedPointer<ProxyTextureImage> Create(ImageSource3D* imageSource);
ImageSource3D Interface

As explained in the previous section, Candera::ProxyTextureImage takes an Candera::ImageSource3D and forwards its VRAM handle through the Candera::TextureImage interface. This section explains what the Candera::ImageSource3D interface is, and how it's used.

A Candera::ImageSource3D object in Candera is basically an interface that provides a handle to an OpenGL ES 2.0 texture in VRAM. It is derived from Candera::ImageSource, which inherits from Candera::Surface which itself inherits from Candera::Synchronizable.

Therefore the following interface has to be implemented:

  • Candera::Synchronizable Methods
    virtual void Sync() = 0;
    
    If some kind of synchronization needs to be done, implement it inside this method. It will be called every time the Candera::ProxyTextureImage, where this ImageSource3D is associated to, gets activated. Prerequisite is that Candera::ProxyTextureImage::IsSyncEnabled is set to true.
    virtual void WaitSync() = 0;
    
    Blocks the current thread until the object is in synchronized state. In the context of Candera::ProxyTextureImage this is never called, but has to be implemented at least empty.
  • Candera::Surface Methods
    virtual Int GetHeight() const = 0;
    
    virtual Int GetWidth() const = 0;
    
    These functions have to return the width and the height of the texture in texels.
  • Candera::ImageSource Methods

No special interface to implement.

  • Candera::ImageSource3D Methods
    virtual Handle GetVideoMemoryHandle() const = 0;
    
    Exposes the video memory handle of the Candera::ImageSource3D implementation. This is the most important function to create ImageSources as this is the handle which gets forwarded from Candera::ProxyTextureImage::GetVideoMemoryHandle. This handle is further used for setting render states and activation of the texture. Note: This handle has also to be uploaded and eventually updated, which is not done by Candera itself, but has to be done by the application. Imagine e.g. a video texture using an appropriate extension, the handle has to be uploaded before rendering. The content of the VRAM buffer then has to be updated before the Candera::Renderer::RenderCamera or Candera::Renderer::RenderAllCameras is called.
    virtual bool IsMipMappingEnabled() const = 0;
    
    Returns if mip mapping is enabled or not. Set it to your needs or make it configurable.

This interface is used for multiple purposes:

  • Candera uses it for multiple device objects, such as off-screen render targets. With Candera::ProxyTextureImage this is used to realize rendering scenes into textures.
  • Own ImageSources that use e.g. an OpenGL ES extension for texturing have to implement the ImageSource3D interface and can also be used with Candera::ProxyTextureImage.



Example: Offscreen Render Targets 

Offscreen Render Targets

The following source code example demonstrates the additional steps that need to be done in order to use offscreen render targets with Candera::ProxyTextureImage.

For specific devices the prefix "Device" is used in the beginning of class identifiers. Simply exchange it with the according device name.

First, after display creation, create and configure an off-screen render target:

DeviceFrameBufferObject* CreateFrameBufferObject() {
    Int count = 0;
    const GraphicDeviceUnitTypeHandle* handle = DevicePackageDescriptor::GetUnitTypes(DevicePackageDescriptor::OffscreenTarget3D, 0, count);
    if (handle == 0 || count < 1){
        return 0; //Handle creation error.
    }
    GraphicDeviceUnit* gdu = DevicePackageInterface::CreateGraphicDeviceUnit(handle[0]);
    if (gdu != 0) {
        gdu->SetDisplay(displayId);
    }

    return (DeviceFrameBufferObject*)gdu;
}

GraphicDeviceUnit* m_gdu;
DeviceFrameBufferObject* m_fbo;


void Init() {
    ...
    //Display creation
    m_gdu = ...;
    ...
    m_gdu->Upload();
    
    //Create and configure offscreen render target.
    m_fbo = CreateFrameBufferObject();
    m_fbo->SetWidth(m_gdu->ToRenderTarget3D()->GetWidth());   //Offscreen RT now has same dimensions than GDU.
    m_fbo->SetHeight(m_gdu->ToRenderTarget3D()->GetHeight()); //Of course this can vary.
    m_fbo->SetColorFormat(RgbaUnpackedColorFormat);
    m_fbo->SetDepthFormat(Depth32Format);
    m_fbo->Upload();
    ...
    InitCamera();
    InitScene();
    m_scene->UploadAll();
}

Further specify a camera which renders the offscreen render target. This is basically the same as normal camera creation, except that the fbo is set as render target.

void InitCamera() {
    //Initialize main camera
    m_camera = Camera::Create();
    ...
    
    //Initialize offscreen camera
    m_offscreenCam = Camera::Create();
    m_offscreenCam->SetName("OffScreen Camera");
    m_offscreenCam->SetViewport(Candera::Rectangle(0.0f, 0.0f, 1.0f, 1.0f));
    //Here the offscreen RT is used as camera render target.
    m_offscreenCam->SetRenderTarget(m_fbo->ToRenderTarget3D());

    const Int vpWidth = 1600;//m_width;
    const Int vpHeight = 600;//m_height;

    SharedPointer<PerspectiveProjection> perspectiveProjection = PerspectiveProjection::Create();
    perspectiveProjection->SetNearZ(0.001F);
    perspectiveProjection->SetFarZ(100.0F);
    perspectiveProjection->SetFovYDegrees(45.0F);
    perspectiveProjection->SetAspectRatio(static_cast<Float>(vpWidth) / static_cast<Float>(vpHeight));

    m_offscreenCam->SetProjection(perspectiveProjection);
    m_offscreenCam->SetPosition(Vector3(0.0f, 0.0f, 0.0f));
    m_offscreenCam->SetUpVector(Vector3(0.0f, 1.0f, 0.0f));
    m_offscreenCam->LookAtWorldPoint(Vector3(0.0f, 0.0f, -1.0f));

    m_clearMode.SetClearColor(Color(0.8f,0.8f,1.0f,1.0f));
    m_clearMode.SetColorClearEnabled(true);
    m_clearMode.SetDepthClearEnabled(true);
    m_clearMode.SetClearDepth(1.0f);
    m_offscreenCam->SetClearMode(m_clearMode);
    m_offscreenCam->SetClearModeEnabled(true);
    m_offscreenCam->SetSwapEnabled(true);

    m_scene->AddChild(m_offscreenCam); //You can off course also render different scenes
                                       //your offscreen camera.
}

Finally, set the off-screen render target as image source of the proxy texture.

void InitScene() {
    //Init your scenes here
    ...
    //The texture the offscreen RT is rendered into.
    SharedPointer<Texture> proxyTex = Texture::Create();
    //here the fbo is set as the textures image source!
    SharedPointer<ProxyTextureImage> proxyTexImg = ProxyTextureImage::Create(m_fbo->ToImageSource3D());
    //Use the ProxyTextureImage as the textures TextureImage.
    proxyTex->SetTextureImage(proxyTexImg);
    //Set up the texture to fit your needs.
    proxyTex->SetMipMapFilter(Texture::MipMapNone);
    proxyTex->SetMagnificationFilter(Texture::MinMagNearest);
    proxyTex->SetMinificationFilter(Texture::MinMagNearest);
    proxyTex->SetWrapModeU(Texture::ClampToEdge);
    proxyTex->SetWrapModeV(Texture::ClampToEdge);

    ...

}

The result is that the content of the off-screen render target is displayed as texture in the display render target. See the screenshot below:

drawing-4-1677218158.png
See also:
Multiple Render Targets for how to configure an offscreen render target in SceneComposer.



Example: External Image Source 

External Image Source

This section shall be understood more as a proposal on how you might implement your own external image source than as tutorial. Reason is that various OpenGL ES extensions exist, a concrete tutorial therefore would be too extension specific.

Here a proposal is presented on how a class could be realized, that encapsulates a texture handle.

Candera::ProxyTextureImage can be used with textures of type GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP. For other types, e.g. 3D textures, activation and other steps have to be done using own implementations along with node listeners.

Following class is a proposal on how an external image source could look like. Have a look at the comments to figure out the details.

class ExternalImageSource : public ImageSource3D       //First step is to derive from ImageSource3D.
{

public:
    ExternalImageSource();
    virtual ~ExternalImageSource();

    //Uploads the external image source to VRAM, ensure that the desired OpenGL ES Context is activated.
    //Implementation has to be extension specific. 
    bool Upload();

    //Unloads the external image source from VRAM, ensure that the desired OpenGL ES Context is activated.
    //Implementation has to be extension specific.
    bool Unload();

    /*
 Updates the external image source in VRAM, ensure that the desired OpenGL ES Context is activated.
 Adapt function to your needs.
 Has to be called whenever the image changes.
 Implementation has to be extension specific.
     */
    bool Update();

    //Implement ImageSource3D interface:
    virtual Handle GetVideoMemoryHandle() const;
    virtual bool IsMipMappingEnabled() const { return m_isMipMappingEnabled; }
    virtual Int GetHeight() const { return m_height; }
    virtual Int GetWidth() const { return m_width; }
    virtual void Sync() {}
    virtual void WaitSync() {}

    /*
 Getters and setters as well as members to interact with the external image source, such as a video
 player, have to be added to this header.
     */

#ifndef GL_VIV_direct_texture
    void SetBitmap(const Bitmap::SharedPointer& bitmap) { m_bitmap = bitmap; }
    const Bitmap::SharedPointer& GetBitmap() const { return m_bitmap; }
#endif

private:

    bool m_isMipMappingEnabled;
    Int m_width;
    Int m_height;
    Handle m_videoMemoryHandle[CANDERA_MAX_CONTEXT_COUNT];

#ifdef GL_VIV_direct_texture
    GLvoid* m_texels;       //Direct pointer to buffer in VRAM!
#else
    Bitmap::SharedPointer m_bitmap;
#endif


    /*
 Getters and setters as well as members to interact with the external image source, such as a video
 player, have to be added to this header.
     */

};
Handle ExternalImageSource::GetVideoMemoryHandle() const
{
    Handle handle = m_videoMemoryHandle[ContextResourcePool::GetActive().GetIndex()];
    return handle;
}

Finally, set the external image source as image source of your proxy texture. Upload should be called manually before or after uploading other nodes, but at least before your render loop starts.

Update should be called any time the external image source changes (e.g. a new frame is grabbed from a video). When calling Update, ensure that it is called before rendering the next camera frame.

    //An instance of the external image source (or pointer to) is needed.
    ExternalImageSource m_externalImage;
void ExternalImageSourceApp::InitScene()
{
    m_scene = Scene::Create();
    m_scene->SetName("Scene");

    m_light = Light::Create();
    m_light->SetType(Light::Directional);
    m_light->SetAmbient(Color(0.3f, 0.3f, 0.3f));
    m_light->SetDiffuse(Color(1.0f, 1.0f, 1.0f));
    m_light->SetSpecular(Color(1.0f,1.0f,1.0f));
    m_light->SetDirection(Vector3(0.0f, -5.0f, 0.0f));
    m_light->SetName("Light");
    m_light->SetRenderingEnabled(true);
    m_light->SetSpotAngle(30.0f);
    m_light->SetSpotExponent(30.0f);
    m_light->SetAttenuationEnabled(true);
    m_light->SetAttenuation(1.0f, 0.0f, 0.0f);
    m_scene->AddChild(m_light);


    //Create External image source, for demonstration with a bitmap.
    m_assetFactory.CreateBitmap(BitmapData_color_map.name, m_colorMapBmp);
    m_externalImage.SetBitmap(m_colorMapBmp);
   
    //Upload external image. Ensure context is active!
    m_gdu->ToRenderTarget3D()->Activate();
    m_externalImage.Upload();

    //Now create ProxyTextureImage.
    m_externalTextureImage = ProxyTextureImage::Create(&m_externalImage);

    //Create texture with proxy image.
    SharedPointer<Texture> texture = Texture::Create();
    texture->SetTextureImage(m_externalTextureImage);

    //Finally create node (billboard) to display proxy image.
    m_billboard = Billboard::Create(1.0f, 1.0f);
    m_billboard->SetAppearance(Appearance::Create());
    m_billboard->GetAppearance()->SetShader(m_refTransRefTexShader);
    m_billboard->GetAppearance()->SetMaterial(Material::Create());
    m_billboard->GetAppearance()->GetMaterial()->SetDiffuse(Color(1.0f, 1.0f, 1.0f, 1.0f));
    m_billboard->GetAppearance()->SetRenderMode(RenderMode::Create());
    m_billboard->GetAppearance()->SetShaderParamSetter(GenericShaderParamSetter::Create());
    m_billboard->GetAppearance()->SetTexture(texture,0);
    m_billboard->SetPosition(0.0f, 0.0f, -2.5f);
    m_billboard->SetRotation(0.0f, 0.0f, 0.0f);
    m_billboard->SetScale(1.5f, 1.5f, 1.5f);

    m_scene->AddChild(m_billboard);

}
{
    //You need to update your external image source before rendering.Again make sure that context is activated.
    m_gdu->ToRenderTarget3D()->Activate();
    m_externalImage.Update();
}
void ExternalImageSourceApp::OnRender()
{
    m_gdu->ToRenderTarget3D()->Activate();
    Renderer::RenderAllCameras();
    m_gdu->ToRenderTarget3D()->SwapBuffers();
}


Tutorial for Graphic Device Interface

Multisample Anti-Aliasing Offscreen Render Targets

Description

This section describes how to set the multisample anti-aliasing (MSAA) property for offscreen render targets.


Anti-Aliasing and MSAA 

Anti-Aliasing

In computer graphics, anti-aliasing is a technique used for improving image quality by smoothing sharp edges cause by aliasing. This usually occurs when rendering high-resolution signals at a lower resolution.

Multisample Anti-Aliasing

Multisample anti-aliasing is a method used for full-screen anti-aliasing by multisampling each pixel at multiple pixel locations. The fragment shader is run only once per pixel no matter the numbers of samples covered by the rendered polygon. Once the color buffer's samples are calculated, these colors are averaged per pixel to produce the final color. Depth and stencil values also make use of the multisample points which means that their buffer size will increase by the amount of samples per pixel.

The higher the number of additional samples per pixel will result in an increase in the amount of anti-aliasing which will generate even smoother edges. The application will noticeably suffer in terms of performance the more samples are used.

There are four modes for MSAA: 2x, 4x, 8x, and 16x, but out of these, 4x MSAA offers a good balance between performance and quality.

MSAA for offscreen render targets is only available on devices that support OpenGL ES 3.0 or higher. Even more, the number of supported samples differs from one device to another, some may not support more than 4x MSAA, which is the minimum defined by the OpenGL ES 3.0 standard. Using sample values higher than supported might lead to undefined behaviours or errors. It is recommended to check the device documentation for the number of supported samples.



MSAA Offscreen Render Targets 

Setting MsaaSamples Property

Enabling of multisample anti-aliasing with a given number of samples is achieved by setting the MsaaSamples property of a framebuffer object to a value larger than 1 (default value).

The MsaaSamples property values is changed either using Candera::GlFrameBufferProperties::SetMsaaSamples method or by meta information.

The following snippets show how to set MsaaSamples using the methods described above:

Using Candera::DevicePackageInterface
  • create an offscreen render target using Candera::DevicePackageInterface

    m_offscreenRenderTarget = Base::CreateGraphicDeviceUnit(DevicePackageDescriptor::OffscreenTarget3D);
    if (m_offscreenRenderTarget == 0) {
        return false;
    }

  • access render target properties (including MsaaSamples) via MetaInfo

    const MetaInfo::GraphicDeviceUnitMetaInfo *meta = DevicePackageDescriptor::GetMetaInformation(m_offscreenRenderTarget->GetUnitType());
    meta->LookupItem("Width") && meta->LookupItem("Width")->Set(m_offscreenRenderTarget, m_rtProperties.m_width);
    meta->LookupItem("Height") && meta->LookupItem("Height")->Set(m_offscreenRenderTarget, m_rtProperties.m_height);
    meta->LookupItem("ColorFormat") && meta->LookupItem("ColorFormat")->Set(m_offscreenRenderTarget, m_rtProperties.m_colorFormat);
    meta->LookupItem("DepthFormat") && meta->LookupItem("DepthFormat")->Set(m_offscreenRenderTarget, m_rtProperties.m_depthFormat);
    meta->LookupItem("MsaaSamples") && meta->LookupItem("MsaaSamples")->Set(m_offscreenRenderTarget, m_rtProperties.m_msaaSamples);

    if(m_offscreenRenderTarget->Upload() != true){
        FEATSTD_LOG_ERROR("Offscreen render target upload failed\n");
        return false;
    }
Using Candera::iMX6FrameBufferObject
  • declare an iMX6 framebuffer render target

            iMX6FrameBufferObject m_fbo;
            iMX6FrameBufferObject* m_frameBufferRenderTarget;

  • access render target properties using their corresponding setter methods

    m_frameBufferRenderTarget = &m_fbo;
    m_frameBufferRenderTarget->GetProperties().SetHeight(c_fbHeight);
    m_frameBufferRenderTarget->GetProperties().SetWidth(c_fbWidth);
    m_frameBufferRenderTarget->GetProperties().SetMsaaSamples(c_msaaSamples);
    m_frameBufferRenderTarget->Upload();
Framebuffer Blit

These multisample renderbuffers cannot be directly bound to textures. A solution is provided by OpenGL ES 3.0 by using the framebuffer blit function, such that these renderbuffers are resolved to single-sample textures with the use of an intermediate framebuffer. The blit function is responsible to transfer a region defined by 4 screen-space coordinates of the read framebuffer to another region in the draw framebuffer, turning the multisample buffer into a 2D texture that could be further used for post-processing in the fragment shader.



Example: MSAA result for different sample values 

The results of multisample anti-aliasing for 1x - default, 2x, 4x, 8x and 16x sample rates can be seen in the image below.

drawing-4-1677218858.png
Tutorial for Graphic Device Interface

External Texture Image

How to use EGLKHRExternalTexture

To use an DMA buffer as an external texture create an array to hold necessary information. This varies with platforms and types of buffers. This example shows using DMS buffers on RCarH3.

//Activate appropriate RenderTarget first.

EGLint defaultAttribs[13];
defaultAttribs[0] = EGL_DMA_BUF_PLANE0_FD_EXT;
defaultAttribs[1] = <file descriptor of the buffer>;  //file descriptor 
defaultAttribs[2] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
defaultAttribs[3] = 0; // depends on platform and requirements
defaultAttribs[4] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
defaultAttribs[5] = <pitch of the buffer>; 
defaultAttribs[6] = EGL_LINUX_DRM_FOURCC_EXT; 
defaultAttribs[7] = DRM_FORMAT_ARGB8888;  // depends on platform and requirements
defaultAttribs[8] = EGL_WIDTH;
defaultAttribs[9] = 255; // width of the image. Also set this to the EglKhrExternalTextureImage 
defaultAttribs[10] = EGL_HEIGHT;
defaultAttribs[11] = 456; // height of the image. Also set this to the EglKhrExternalTextureImage 
defaultAttribs[12] = EGL_NONE; //Terminator
Candera::EglKhrExternalTextureImage::SharedPointer ptr = Candera::EglKhrExternalTextureImage::Create();
ptr->SetRole(<Candera::EglKhrExternalTextureImage::Consumer| Candera::EglKhrExternalTextureImage::Producer>); //Can only be set once on purpose! Use producer to render to this buffer and consumer if it shall be used as texture.
ptr->SetAttributeList(defaultAttribs);  // will be deep copied. 
ptr->SetExternalBufferType(EGL_LINUX_DMA_BUF_EXT); // buffer type
ptr->SetExternalBuffer(static_cast<EGLClientBuffer>(0)); // actual buffer 
ptr->SetWidth(255); 
ptr->SetHeight(456);
ptr->Upload();

Tutorial for Special 3D Render Techniques

Tutorial for Special 3D Render Techniques

Level of Detail (LOD)

Description

This chapter briefly describes Level Of Detail techniques supported by Candera.

See also:

Example Solution:

  • LODSolution from folder cgi_studio_player/content/Tutorials/08_SpecialRenderTechniques/Special3DRenderTechniques


Introduction 

Level of Detail
Level of detail (LOD) is a discipline of interactive computer graphics attempts 
to bridge complexity and performance by regulating the amount of detail used 
to represent the virtual world.

(Level of Detail for 3D Graphics, David Luebke, Morgan Kaufmann Publisher, 2003)

drawing-4-1677219316.png



Motivation

Reduce render complexity in order to increase runtime performance with minimal or without visual penalty

Detail Simplifications
  • Geometric: Meshes, Imposters, Shadow LOD, ...
  • Non-geometric: Shader, Lighting, Textures (MipMapping), Material, ...

Level of Detail - Geometry Reduction Types 

Discrete LOD
  • Creates multiple versions of an object with different level of details. Often refered to as isotropic or view-independent LOD, as detail reduction is applied uniformly accross the object.
  • Widely used in real time environments as LOD levels are generated during an offline preprocess. Supported by Candera.
Continuous or progressive LOD
  • Simplification system creates a data structure encoding a continuous spectrum of detail. The desired LOD is extracted from this structure at run-time. Supports LOD streaming and interruptible loading.
View-dependent LOD
  • Extends continuous LOD by using view-dependent simplification criteria to dynamically select the most appropriate LOD for current view. Thus, view-dependent view is anisotropic.

drawing-4-1677219426.png

Level of Detail - Chain 

  • A LodNode accomodates a node for each level of detail.
  • Candera Nodes are e.g: Mesh, Billboard, Light, Group, etc.
Typical LOD Chain

The following figure depicts a typical LOD chain:

drawing-4-1677219460.png


Candera - Discrete LOD Concept 

Candera::LodNode, LoD Level

The class Candera::LodNode implements discrete LOD technique. Any Candera class derived from class Candera::Node is accepted as LOD representation.

Each LOD level in Candera::LodNode defines:

drawing-4-1677219530.png

LOD Render Strategy

The LOD render strategy defines the transition between adjacent LOD levels.

LOD Criterion

The LOD criterion delivers a source value to map onto the LOD nodes visibility boundaries.

Candera - LOD Render Strategy 

Discrete LOD Render Strategy
drawing-4-1677219620.png


Blend Transition LOD Render Strategy

drawing-4-1677219649.png

Candera - LOD Architecture Overview 

  • A Candera::LodNode has 0..1 associated LodRenderStrategy objects.
  • A Candera::LodRenderStrategy has 0..1 associated LodCriterion objects.
  • According to use case LodRenderStrategy and LodCriterion objects are interchangeable arbitrarily.
drawing-4-1677219708.png

Example - LOD in SceneComposer 

Example - Level of Detail in SceneComposer

This chapter shows a typical LOD use case and explains how the transition from two different LODs can be realized with CGI Studio SceneComposer.

To experiment with LOD transition, the following can be used:

  • LODSolution from folder cgi_studio_player/content/Tutorials/08_SpecialRenderTechniques/0801_Special3DRenderTechniques
Use Case

The example solution is based on following use case:

  • The tire is shown from a certain distance in sufficient resolution.
  • The camera zooms in to the tire (animation).
  • As the camera captures the tire from a close distance, the tire model shall be exchanged with a higher resolution model.
  • The transition between the low- and the high-resolution tire model shall appear smooth.

You can experiment with following node properties:

  • (Lower and Upper) Boundary Values: Modify boundary values of low and high node.
  • Criterion: Set criterion 0:DistanceToCamera 1:Generic.
  • Strategy: Set render strategy 0:Blend 1:Discrete.
See also:

Example - Blended LOD 

Blended LOD
  • Initialize LOD node with 2 LOD objects.
  • LOD is automatically changed by distance to camera criterion.
  • Desired Behavior:
    • LOD 0 is exclusively visible at camera distance 0 - 40 (in the LodNode set Lower Bound 0, and Upper Bound 40).
    • LOD 1 is exclusively visible at camera distance 60 - 100 (in the LodNode set Lower Bound 60, and Upper Bound 60).
    • Blend Transition between LOD 0 and LOD 1 is defined at camera distance 40 - 60 (in the LodNode select RenderStrategy 0:Blend).

Change CameraDistance or start CameraPosition animation to see the smooth transition.

In application code the LodNode is set up as follows:

Initializing the LodNode

    // init LodNode bounds
    static_cast<void>(lodNode->SetLodLevel(0, lodNode->GetLodLevel(0).node, m_lowBoundHigh, m_upBoundHigh));
    static_cast<void>(lodNode->SetLodLevel(1, lodNode->GetLodLevel(1).node, m_lowBoundLow, m_upBoundLow));
    // end init


Define Criterion and RenderStrategy

        // Define DistanceToCameraLodCriterion and BlendLodRenderStrategy

        static DistanceToCameraLodCriterion cameraLodCriterion; // LOD is automatically selected by distance to camera.
        static BlendLodRenderStrategy blendLodStrategy; // LOD is automatically blended between non-adjacent boundaries

        // End Define DistanceToCameraLodCriterion and BlendLodRenderStrategy


        // Init BlendLodRenderStrategy and set to LodNode

        blendLodStrategy.SetLodCriterion(&cameraLodCriterion);
        lodNode->SetLodRenderStrategy(&blendLodStrategy);

       // End Init BlendLodRenderStrategy and set to LodNode

Example - Step-wise LOD 

Step-wise LOD
  • Initialize LOD node with 2 LOD objects.
  • LOD criterion value can be set by application directly. (in the LodNode set Criterion to 1:Generic).
  • Desired Behavior:
    • LOD 0 is exclusively visible at camera distance 0 - 40 (in the LodNode set Lower Bound 0, and Upper Bound 40).
    • LOD 1 is exclusively visible at camera distance 60 - 100 (in the LodNode set Lower Bound 60, and Upper Bound 60).
    • Hysterese is defined at 40 - 60. Thus, criterion values within that range do not lead to LOD level swaps. (in the LodNode select RenderStrategy 1:DiscreteLodRenderStrategy).

Change CriterionValue property appropriate to see the transition.

In application code the LodNode is set up as follows:

Initializing the LodNode

    // init LodNode bounds
    static_cast<void>(lodNode->SetLodLevel(0, lodNode->GetLodLevel(0).node, m_lowBoundHigh, m_upBoundHigh));
    static_cast<void>(lodNode->SetLodLevel(1, lodNode->GetLodLevel(1).node, m_lowBoundLow, m_upBoundLow));
    // end init


Define Criterion and RenderStrategy

        // Define GenericLodCriterion and DiscreteLodRenderStrategy

        static GenericValueLodCriterion genericLodCriterion; // Application defined criterion value can be set.
        static DiscreteLodRenderStrategy discreteLodStrategy; // Stepwise LOD switch.

        // End Define GenericLodCriterion and DiscreteLodRenderStrategy


        // Init DiscreteLodRenderStrategy and set to LodNode

        genericLodCriterion.SetCriterionValue(m_criterionValue); // Set application defined criterion value.
        discreteLodStrategy.SetLodCriterion(&genericLodCriterion);
        lodNode->SetLodRenderStrategy(&discreteLodStrategy);

        // End Init DiscreteLodRenderStrategy and set to LodNode

Example - Set LOD directly 

Set LOD directly
  • Create LOD node with 2 LOD objects.
  • LOD level index is set by application directly.
  • Hint: Direct LOD level initialization is helpful, if first criterion value falls into hysteresis range, and no LOD is set active.

// Create LodNode object with 2 empty LOD levels.
m_lodNode = LodNode::Create(2);
// Set LOD 0 active and deactivate LOD 1. No RenderCriterion is attached.
m_lodNode->GetLodLevel(0).node->SetRenderingEnabled(true);
m_lodNode->GetLodLevel(1).node->SetRenderingEnabled(false);



Tutorial for Special 3D Render Techniques

Morphing

Description

This chapter briefly describes the morphing technique supported by Candera.

See also:

Example Solutions

Refer to following solutions in folder cgi_studio_player/content/Tutorials/08_SpecialRenderTechniques/0801_Special3DRenderTechniques

  • MorphSolution
  • MultiMorphing_MultiMorphShader
  • MultiMorphing_AnimationGroup


Introduction 

Morphing

Morphing means transforming the 3D geometry of an object smoothly from one shape into another.

Morphing itself (smooth interpolation between vertices) is done in a shader (not on the CPU).

drawing-4-1677221269.png

What you need for Morphing

Suitable content: "Morph targets" are different geometric states of the same object. The input vertices must correspond one-to-one between morph targets. This means, to morph between two shapes, you need two VertexBuffer with exactly the same number of vertices.

An instance of MorphingMesh: This is a Mesh that stores

  • the spliced vertex buffer created from the vertex buffers to be morphed (see MorphingMesh::CreateSplicedVertexBuffer)
  • weight values for each of the shapes, which are activated as shader uniforms at render time (see MorphingMesh::SetMorphWeight)
  • The appearance of the MorphingMesh must define a suitable morphing shader, which matches the number of shapes to be morphed (see RefTransLight1Morph vertex shader for morphing two shapes)

Dynamics: Morphing is about change, so an application will have to set some morphing parameters (often depending on time). Use Animation::AllMorphWeightsPropertySetter to animation all the weight values of the MorphingMesh.

Morphing Examples 

Morphing between 2 Morph Items

In below example solution, the reference shader RefTransLight1Morph_RefColor is used to morph between two morph items, by default having weight value 1,00 on the logo.

  • MorphSolution from folder cgi_studio_player/content/Tutorials/08_SpecialRenderTechniques/0801_Special3DRenderTechniques
drawing-4-1677221366.png

The animation part of this solution animates both weight values and also adds a rotation animation.

Morphing between Multiple Shapes using Shader

Since a MorphingMesh is capable of handling up to 8 different vertex buffers, a special morphing shader must be used for more than 2 shapes. A morphing shader handling 5 shapes is used in the example below:

  • MultiMorphing_MultiMorphShader from folder cgi_studio_player/content/Tutorials/08_SpecialRenderTechniques/0801_Special3DRenderTechniques
drawing-4-1677221435.png

Morphing between Multiple Shapes using AnimationGroup

As an alternative to using a multi morph shader, multi-step morphing can also be realized by using an AnimationGroup, chaining separate morphing animations for each morphing shape pair. This approach is useful in case a specific target shader compiler has some restrictions regarding the maximum number of shader parameters.

  • MultiMorphing_AnimationGroup from folder cgi_studio_player/content/Tutorials/08_SpecialRenderTechniques/0801_Special3DRenderTechniques


Tutorial for Special 3D Render Techniques

Planar Shadows

Description

This chapter describes the planar shadow technique supported by Candera.


Introduction 

Planar Shadows

Planar shadows are used to algorithmically simulate flat shadows on planar surfaces.

The shadow effect is obtained using a Candera::PlanarShadow object which is drawn using its world transformation matrix multiplied with a projection matrix, that projects its vertices onto a predefined plane with respect to the position of an associated light source.

drawing-4-1677221907.png


Workflow 

Inventory

The figure below shows the basic setup of a scene needed to generate the planar shadow effect.

drawing-4-1677221947.png

Light

Light should be of type point, spot or directional.

    m_light = Light::Create();
    m_light->SetName("Light1");
    m_light->SetType(Light::Point);
    m_light->SetDiffuse(Color(1.0f, 1.0f, 1.0f));
    m_light->SetSpecular(Color(0.0f, 0.0f, 0.0f));
    m_light->SetRenderingEnabled(true);
Shadow Caster

The following objects can be used as shadow caster: Candera::LineList, Candera::Billboard and Candera::Mesh.

Planar Shadow

The Candera::PlanarShadow is the object which actually draws the shadow onto a specified plane. By default, the parent node's vertex buffer is used. Consequently the PlanarShadow node should be attached as child of the shadow caster.

    m_shadow = PlanarShadow::Create();
    m_shadow->GetAppearance()->SetShader(m_shaderShadow); // RefTrans_RefUniDiffuseMat
    m_shadow->SetName("Shadow");
    m_shadow->SetPlane(Plane(Vector3(0.0f, 0.0f, 1.0f), 75.0f));
    m_shadow->SetLight(m_light);
    m_current->AddChild(m_shadow); // attach planar shadow to the shadow caster 

It's also possible to explicitly provide a dedicated vertex buffer which will be rendered as shadow, see the snippet below:

                m_shadow->SetAutoVertexBufferEnabled(false);
                m_shadow->GetVertexBuffer()->Unload();
                m_shadow->SetVertexBuffer(m_meshes[1]->GetVertexBuffer());
                m_shadow->GetVertexBuffer()->Upload();

Candera::PlanarShadow supports also an optional alignment of the shadow plane to a given node (alignmentNode). If this node it set, the shadow plane's distance and direction are transformed locally according to alignmentNode's transformation matrix. If node is not set, the shadow plane's parameters are not transformed at all. See snipped below to set alignmentNode:

                m_shadow->SetAlignmentNode(m_meshes[3]);
Overlapping Shadows

To avoid double blending of semi transparent shadows stencil buffers can be used. The PlanarShadow's appearance already contains a pre-configured RenderMode with stencil buffers configured to only draw on fragments with a stencil value other than this PlanarShadows unique Id. After drawing, the value gets replaced to the current stencil reference value, such that the stencil test doesn't pass any more on future fragments to write.

To get this mechanism running ensure that the camera always clears the stencil buffer to 0 every frame.

The GraphicDeviceUnit has to be initialized accordingly to support stencil buffers.

Constraints
  • Only flat planar surfaces are considered as areas to draw the shadow on.
  • Every object that intends to cast a shadow, must have a dedicated shadow child node for each light source, as well as for each plane acting as shadow receiver.
  • If vertex buffer transformations shall be considered (e.g. morphing meshes or displacement mapping), a dedicated shader has to be implemented for the shadow object.



Example 

The example shows two planar shadows casts by a cube.

drawing-4-1677222011.png

Planar Shadow Example in SceneComposer

To experiment with the Planar Shadow in SceneComposer use solution PlanarShadow from folder cgi_studio_player/content/Tutorials/08_SpecialRenderTechniques/Special3DRenderTechniques.



Tutorial for Special 3D Render Techniques

Baked Shadows

Description

This chapter describes the baked shadow technique supported by Candera.

Introduction 

Baked shadows

Baking shadows is a method of generating shadows which can be used for scenes where the lights and the objects which cast shadows are not moved with respect to each other.

Because the shadows are pre-computed and integrated into the textures the polygon count is decreased and the time needed for rendering the scene is significantly reduced. As a drawback, if the textures resulted after the baking are large, the memory can be intensive so the rendering becomes slow. Also, for complex scenes, this way of generating shadows might be too laborious to be considered as an option.[1]


drawing-4-1677222224.png

Workflow 

Create Models and Textures

The following steps should be performed in any 3D modeling tool that is able to bake shadows (Blender, Maya, 3D Studio Max ...):

  • create a 3D scene that contains some models and at least one source of light
    drawing-4-1677224453.png

  • join the models which casts shadows with the models on which the shadow is projected on
  • unwrap the 3D models
    drawing-4-1677224479.png

  • create a new image that will be used then as the texture containing the shadows
  • set the image as texture for the models
  • render the scene and bake the shadows into the texture
    drawing-4-1677224537.png

  • export the texture in any image format supported by Candera and also export the 3D models in FBX format
  • If needed, refine the baked image with an image editor
Asset Library Generation

Use SceneComposer to generate the asset library:

  • import the FBX and the image texture in SceneComposer
  • from the Imports panel add the light and the node in a 3D scene
  • set the imported image as texture for the node
  • export the asset library

Example 

The example shows a 3D node textured with an image containing baked shadows.

drawing-4-1677224574.png

References 

The pictures and the models from this tutorial where obtained from the following sources:

[1] http://apricot.blender.org/wp-content/uploads/2008/06/sbake1.jpg

Tutorial for Special 3D Render Techniques

Single Pass Effects in Candera > Examples for 3D Effect Shaders

Description

This chapter describes special 3D effect shaders techniques supported by Candera.



Bump Mapping 

Description

This chapter briefly describes bump mapping techniques supported by Candera.


Introduction 

Bump mapping is a technique in computer graphics for simulating bumps and wrinkles on the surface of an object.

This is achieved by perturbing the surface normals of the object and using the perturbed normal during lighting calculations. The result is an apparently bumpy surface rather than a smooth surface although the surface of the underlying object is not actually changed.

drawing-4-1677227144.png

Workflow 
Inventory

In order to generate the bump mapping effect on a surface you need:

  • A mesh
  • A specific shader
  • A shader parameter setter
  • A normal map texture
  • A color map texture
  • A material
Mesh

Any mesh can be used for this purpose but the mesh vertex buffer has to contain also the tangents and the binormals vertex attributes. This information can be added to the vertex buffer in the 3D content creation tool (3DS Max, Blender, etc.), before exporting the FBX, or, at run time, using the Candera::Math3D::CreateTangentsAndBinormalsFromVertexBuffer function as shown below:

    mesh->SetVertexBuffer(Math3D::CreateTangentsAndBinormalsFromVertexBuffer(mesh->GetVertexBuffer()));
Shader

Use the shader from table below:

Vertex shader RefTransLight1BumpMap
Fragment shader RefLight1BumpMap

Set the appearance shader for the mesh:

    mesh->GetAppearance()->SetShader(bumpmapShader);
Shader Parameter Setter

The uniform setter will have to be set as follows:

    shaderParamSetter->SetModelMatrix4Enabled(true);
    shaderParamSetter->SetNormalModelMatrix3Enabled(true);
    shaderParamSetter->SetModelViewProjectionMatrix4Enabled(true);
    shaderParamSetter->SetLightActivationEnabled(true);
    shaderParamSetter->SetMaterialActivationEnabled(true);
    shaderParamSetter->SetTextureActivationEnabled(true);
    shaderParamSetter->SetLightsCoordinateSpace(Light::World);
    shaderParamSetter->SetCameraPositionEnabled(true);
    mesh->GetAppearance()->SetShaderParamSetter(shaderParamSetter);    
Color Texture

The color texture applies an image to the surface of a mesh. This texture should be set on the mesh with texture unit 0.

Add the color texture to the appearance:

    mesh->GetAppearance()->SetTexture(colorTexture, 0);
Normal Map Texture

The normal map images store the direction of normals directly in the RGB values of an image. These normals are used for lighting calculation instead of the vertices normals. Normal maps are generated using special graphics software usually from a higher resolution geometry than the geometry you're applying the map to.[1]

drawing-4-1677227233.png

Add the normal map texture to the appearance:

    mesh->GetAppearance()->SetTexture(normalMapTexture, 1);
Material Configuration
        mesh->GetAppearance()->GetMaterial()->SetAmbient(Color(0.3f, 0.3f, 0.3f, 1.0f));
        mesh->GetAppearance()->GetMaterial()->SetEmissive(Color(0.0f, 0.0f, 0.0f, 1.0f));
        mesh->GetAppearance()->GetMaterial()->SetDiffuse(Color(1.0f, 1.0f, 1.0f, 1.0f));
        mesh->GetAppearance()->GetMaterial()->SetSpecular(Color(1.0f, 1.0f, 1.0f, 1.0f));
        mesh->GetAppearance()->GetMaterial()->SetSpecularPower(40.0f);

Examples 

Here is exemplified the bump map effect applied on two meshes:

drawing-4-1677228230.png




Bump Mapping in SceneComposer

To experiment with Bump mapping in Scene Compose use the solution ShaderExamples from the folder cgi_studio_player/content/Tutorials/04_ShaderUsage.


References 

The pictures from this tutorial where obtained from the following sources:

[1] http://planetpixelemporium.com/tutorialpages/normal3.html


Procedural Textures 

Description

This chapter briefly describes the procedural wood technique supported by Candera.


Introduction 
Procedural Wood

Procedural wood is texturing technique which generates an on-the-fly texture using an algorithm that creates a wood realistic 3D representation. The algorithm will generate concentric rings colored alternatively with two colors which, if chosen rightly, will show a wood-like appearance. The effect can be controlled by setting the uniforms:

  • u_CustomWoodCenter - rings center position
  • u_CustomWoodColor1 - even rings color
  • u_CustomWoodColor2 - odd rings color
drawing-4-1677228371.png

Workflow
Inventory

In order to generate the wood effect on a mesh all you need to do is to configure the material, an appropriate shader and a shader parameter setter for its appearance.

Shader

The table below presents the vertex and fragment shader which generates the wood effect:

Vertex shader RefTransLight1ProceduralWood
Fragment shader RefProceduralWood

Set the appearance shader for the mesh:

       mesh->GetAppearance()->SetShader(woodShader);
Shader Parameter Setter

The uniforms setter will have to be configured as follows:

        woodShaderParamSetter->SetModelViewProjectionMatrix4Enabled(true);
        woodShaderParamSetter->SetLightActivationEnabled(true);
        woodShaderParamSetter->SetMaterialActivationEnabled(true);
        woodShaderParamSetter->SetTextureActivationEnabled(true);

        mesh->GetAppearance()->SetShaderParamSetter(woodShaderParamSetter);    

Prepare the uniforms data:

        Float woodCenter[] = { 0.0f, 0.0f, 0.2f};
        Float woodColor1[] = { 0.81f, 0.63f, 0.41f, 1.0f };
        Float woodColor2[] = { 0.56f, 0.27f, 0.12f, 1.0f };
        Float woodMultiplier = 100.0f;

Set the uniforms using the shader parameter setter:

        woodShaderParamSetter->SetUniform("u_CustomWoodCenter", Shader::FloatVec3, woodCenter);
        woodShaderParamSetter->SetUniform("u_CustomWoodColor1", Shader::FloatVec4, woodColor1);
        woodShaderParamSetter->SetUniform("u_CustomWoodColor2", Shader::FloatVec4, woodColor2);
        woodShaderParamSetter->SetUniform("u_CustomWoodMultiplier", Shader::Float, &woodMultiplier);
Material Configuration
        mesh->GetAppearance()->GetMaterial()->SetAmbient(Color(0.0f, 0.0f, 0.0f, 1.0f));
        mesh->GetAppearance()->GetMaterial()->SetEmissive(Color(0.0f, 0.0f, 0.0f, 1.0f));
        mesh->GetAppearance()->GetMaterial()->SetDiffuse(Color(1.0f, 1.0f, 1.0f, 1.0f));
        mesh->GetAppearance()->GetMaterial()->SetSpecular(Color(1.0f, 1.0f, 1.0f, 1.0f));
        mesh->GetAppearance()->GetMaterial()->SetSpecularPower(40.0f);
 
Examples 

Here is exemplified the wood effect applied on two meshes:

drawing-4-1677228466.png

Wood Effect in SceneComposer

To experiment with the procedural wood technique in SceneComposer use solution ShaderExamples from folder cgi_studio_player/content/Tutorials/04_ShaderUsage.

References 

[1] Image taken from http://blenderartists.org/forum/showthread.php?246113-A-fine-procedural-wood-material-for-Cycles


Environment Mapping 

Description

This chapter describes the environment mapping techniques supported by Candera.

Environment mapping is an image based technique to achieve an appearance of reflecting object surface. A texture is used to show the environment as a reflection.


Sphere Map 
Description

This chapter describes the sphere mapping techniques supported by Candera.

Sphere mapping was the first environment mapping technique where the reflective environment is mapped onto a single texture as it would be reflected by a mirror ball. Sphere mapping is suitable on concave surfaces while else Cube mapping achieves better results.

drawing-4-1677228682.png

SphereMap in SceneComposer

To experiment with the sphere mapping technique in SceneComposer use solution ShaderExamples from folder cgi_studio_player/content/Tutorials/04_ShaderUsage.

Cube Map 
Description

This chapter describes the cube mapping techniques supported by Candera.

Cube mapping is a rendering technique whereby six bitmaps are mapped to the sides of a cube, to produce environment mapping effects, such as reflections, or a skybox background. Unlike the old sphere mapping technique, cube map images do not need to be distorted to match the current view-angle, and is therefore a more efficient and flexible method to achieve reflections. However, in OpenGL ES 2.0, any texture filtering is applied separately to the six individual images, which may cause rendering artifacts along the image borders. This issue was corrected in OpenGL ES 3.0, which provides seamless cubemap filtering. There is nothing you need to do to enable seamless cubemap filtering. All linear filter kernels will automatically use it, when targeting OpenGL ES 3.0.

A special application for cube mapping is the Skybox.

CubeMap in SceneComposer

To experiment with CubeMap in SceneComposer, use solution ShaderExamples from folder cgi_studio_player/content/Tutorials/04_ShaderUsage.

SkyBox 
Description

This chapter describes the skybox technique supported by Candera.

SkyBox was implemented to support cubic panorama backgrounds ("SkyBoxes"). A Skybox displays the background environment of a scene, by using a world-space axis-aligned cube, with six images assigned to the faces of the cube. This cube is centered on the active camera's current position, which creates the illusion that the background image is infinitely far away. The skybox is rendered before any other nodes, with depth writing and testing turned off, to ensure that it appears behind all other nodes in the scene.

In OpenGL ES 2.0, linear filtering is applied separately to the six images, which may cause rendering artifacts (seams) along the cube edges. In OpenGL ES 3.0, this minor defect was corrected, by applying filtering across image borders, to create seamless cubemaps. No action is required to enable seamless cubemaps under OpenGL ES 3.0. However, this feature is not available in OpenGL ES 2.0 or its extensions.

drawing-4-1677228752.png

SkyBox in SceneComposer

To experiment with SkyBox in SceneComposer use solution ShaderExamples from folder cgi_studio_player/content/Tutorials/04_ShaderUsage.

Reflection & Refraction
Description

This chapter describes the reflection and refraction techniques supported by Candera.

Candera uses new shaders to achieve reflection and refraction appearances:

  • RefTransCubeMapReflection.vert
  • RefTransCubeMapRefraction.vert
  • RefTransCubeMapReflectionRefraction.vert
  • RefCubeMapTex.frag
  • RefCubeMapTex2.frag

These shaders demonstrate how environment mapping reflections, refraction and the combination of both can be realized using CubeMap textures. The combined reflection and refraction shading results in a glass effect especially when combined with a skybox using the same cubemap textures

drawing-4-1677228794.png

Reflection and Refraction in SceneComposer

To experiment with Reflection and Refraction in SceneComposer use solution ShaderExamples from folder cgi_studio_player/content/Tutorials/04_ShaderUsage.



Anisotropic Lighting 

Description

This chapter briefly describes the anisotropic lighting technique supported by Candera.

Introduction 
Anisotropic Lighting

The Candera implementation of the anisotropic lighting model can be used to generate the effect of a surface having grooves or fine directional grain like, for instance, brushed metal, shiny side of a CD, vinyl records etc.

The effect can be configured by setting the uniforms:

  • u_AlphaU - distribution of specular light component in U direction
  • u_AlphaV - distribution of specular light component in V direction
drawing-4-1677228898.png



Workflow 
Inventory

In order to generate the brushed metal effect on a mesh you need to configure the material, a texture, an appropriate shader and a shader parameters setter for its appearance.

Shader

The table below presents the vertex and fragment shader which generates the brushed metal effect:

Vertex shader RefTransAnisotropicLight1
Fragment shader RefAnisotropicLight1SpecularTex

Set the appearance shader for the mesh:

       mesh->GetAppearance()->SetShader(anisotropicLightingShader);
Shader Parameter Setter

The uniforms setter will have to be configured as follows:

       m_anisotropicLightSetter->SetModelMatrix4Enabled(true);
       m_anisotropicLightSetter->SetNormalModelMatrix3Enabled(true);
       m_anisotropicLightSetter->SetModelViewProjectionMatrix4Enabled(true);
       m_anisotropicLightSetter->SetCameraPositionEnabled(true);
       m_anisotropicLightSetter->SetLightActivationEnabled(true);
       m_anisotropicLightSetter->SetMaterialActivationEnabled(true);
       m_anisotropicLightSetter->SetTextureActivationEnabled(true);
       m_anisotropicLightSetter->SetLightsCoordinateSpace(Light::World);

       mesh->GetAppearance()->SetShaderParamSetter(m_anisotropicLightSetter);    

Prepare the uniforms data:

       Float m_alphaU = 0.28f;
       Float m_alphaV = 0.23f;

You can generate other effects like plastic laminate, glossy grey paper, rolled aluminium etc. by simply changing the values for m_alphaU, m_alphaV and the diffuse and specular colors for the material.

Set the uniforms using the shader parameters setter:

       m_anisotropicLightSetter->SetUniform("u_AlphaU", Shader::Float, &m_alphaU);
       m_anisotropicLightSetter->SetUniform("u_AlphaV", Shader::Float, &m_alphaV);
Material Configuration
       mesh->GetAppearance()->GetMaterial()->SetAmbient(Color(0.0f, 0.0f, 0.0f, 1.0f));
       mesh->GetAppearance()->GetMaterial()->SetEmissive(Color(0.0f, 0.0f, 0.0f, 1.0f));
       mesh->GetAppearance()->GetMaterial()->SetDiffuse(Color(0.4f, 0.4f, 0.4f, 0.4f));
       mesh->GetAppearance()->GetMaterial()->SetSpecular(Color(1.0f, 1.0f, 1.0f, 1.0f));
       mesh->GetAppearance()->GetMaterial()->SetSpecularPower(40.0f);
Texture
drawing-4-1677228965.png

Set the appearance texture:

       mesh->GetAppearance()->SetTexture(texture, 0);   

Example 

The picture below shows the final result after setting the mesh appearance as described above:

drawing-4-1677228992.png



Tutorial for Special 3D Render Techniques

Multi Pass Effects in Candera > Examples for Local 3D Effect Shaders

Description

This chapter describes special 3D effect shaders techniques supported by Candera.

Fur

Description

This chapter describes how to generate the fur effect using the multipass appearance technique supported by Candera.

Introduction 

The fur effect is produce by rendering the node multiple times with different appearances which are blended together. Each appearance will use the same combination of noise and color map textures but with different and specific render states. For each appearance the following uniforms will have to be set specifically:  [1]

  • u_ColorScale - fur color scale.
  • u_Scale - scale factor to manipulate vertices along normals
  • u_PassIndex - render pass index
drawing-4-1677461268.png

Workflow 
Appearances

Because the generation of the fur effect implies that the node has to be rendered multiple times, a special appearance needs to be used: Candera::MultiPassAppearance. The particularity of this appearance is that it keeps a pointer to the next appearance, defining so a sequence of render passes.

A nice fur effect is obtained with at least 10 render passes.


    for (int i = 0; i<= 15; i++) {
        m_furAppearances[i] = MultiPassAppearance::Create();
        // Configure the appearances
        // ...
        // End of appearances configuration
        if (i > 0) {
            m_furAppearances[i-1]->SetNextPass(m_furAppearances[i]);
        }
    }
    mesh->SetAppearance(m_furAppearances[0]);
Shader

The table below presents the vertex and fragment shader needed to generate the fur effect:

Vertex shader RefTransFur
Fragment shader RefUniColorTexFur

Set this shader for all the appearances:

    m_furAppearances[i]->SetShader(m_furShader);      

Lighting and material has no influence on the fur effect.

Shader Parameters Setter

Configure the uniforms setter as follows:

        m_furSetter[i] = GenericShaderParamSetter::Create();
        m_furSetter[i]->SetModelViewProjectionMatrix4Enabled(true);
        m_furSetter[i]->SetLightActivationEnabled(true);
        m_furSetter[i]->SetMaterialActivationEnabled(true);
        m_furSetter[i]->SetTextureActivationEnabled(true);

Prepare the uniforms data:

        m_furColorScale[0] = 0.2196f;
        m_furColorScale[1] = 0.2202f;
        m_furColorScale[2] = 0.2202f;
        m_furColorScale[3] = 1.0f;        
        
        if ( i == 0 ) {
            m_furScale = 1.0f;
        } 
        else {
            m_furScale = 0.01f;        
        }        
        m_passIndex[i] = ((Float) i);

Set the uniforms using the shader parameter setter:

        m_furSetter[i]->SetUniform("u_ColorScale",Shader::FloatVec4,m_furColorScale);
        m_furSetter[i]->SetUniform("u_Scale",Shader::Float,&m_furScale);
        m_furSetter[i]->SetUniform("u_PassIndex",Shader::Float,&m_passIndex[i]);
Material Configuration
        if ( i == 0) {
            m_furAppearances[i]->SetMaterial(Material::Create());
        } 
        else {
            m_furAppearances[i]->SetMaterial(m_furAppearances[0]->GetMaterial());
        }
Noise texture
drawing-4-1677461358.png

Set the fur appearance noise texture:

    m_furAppearances[i]->SetTexture(m_furNoise, 0);
Color map texture

The color map texture is meant to provide a color for the fur.

drawing-4-1677461381.png

Set the fur appearance color map texture:

    m_furAppearances[i]->SetTexture(m_furColor, 1);
Render Mode

Set and configure a Render Mode for all transparency layers (appearances 1 to 15).

    if (i > 0) {
        m_furAppearances[i]->GetRenderMode()->SetBlendingEnabled(true);
        m_furAppearances[i]->GetRenderMode()->SetBlendMode(RenderMode::SourceAlpha, RenderMode::One, RenderMode::Add);
    }

Example 

Here is exemplified the fur effect applied on sphere: 

drawing-4-1677461417.png

Fur Effect in SceneComposer

To experiment with the fur effect in SceneComposer use solution ShaderExamples from folder cgi_studio_player/content/Tutorials/04_ShaderUsage.

References 

[1] Image taken from http://www.xbdev.net/directx3dx/specialX/Fur/index.php


Gooch Effect 

Description

This chapter describes how to generate the Gooch effect using the multipass appearance technique supported by Candera.

Introduction 
Gooch Effect

The Gooch effect is a non photorealistic technique which simulates the appearance of technical illustration.

The effect is generated in two render passes. In the first pass the object is drawn in warm - cool colors: all surfaces that are facing toward the light source are drawn in warm colors (red, yellow etc), all surfaces away from the light source are drawn in cool colors (blue, violet etc.). The second render pass draws the silhouette of the object with a fixed color (e.g. black). [1]

drawing-4-1677461536.png


Workflow 
Inventory

In order to generate the Gooch effect on a node you need:

  • Two MultiPassAppearances
  • Specific shader for each appearance
  • Specific shader parameters setter for each appearance
  • Material
  • Render Modes
Appearances

The Gooch effect is generated in two render passes so we need two MultiPassAppearances:

    m_goochAppearance1 = MultiPassAppearance::Create();
    m_goochAppearance2 = MultiPassAppearance::Create();
    m_goochAppearance1->SetNextPass(m_goochAppearance2);

    m_mesh->SetAppearance(m_goochAppearance1);
Shader

Each appearance need a specific shader, see table below:

Appearance Vertex shader Fragment shader
m_goochAppearance1 RefTransLight1Gooch RefGoochShading
m_goochAppearance2 RefTransScale RefUniColor

Set the shader for each appearance:

    m_goochAppearance1->SetShader(m_goochPass1Shader);
    m_goochAppearance2->SetShader(m_goochPass2Shader);
Shader Parameter Setter

The uniform setter will have to be set as follows:

    m_goochUniformSetter1 = GenericShaderParamSetter::Create();
    m_goochUniformSetter1->SetMaterialActivationEnabled(true);
    m_goochUniformSetter1->SetLightActivationEnabled(true);
    m_goochUniformSetter1->SetLightsCoordinateSpace(Light::World);
    m_goochUniformSetter1->SetModelViewProjectionMatrix4Enabled(true);
    m_goochUniformSetter1->SetModelMatrix4Enabled(true);
    m_goochUniformSetter1->SetNormalModelMatrix3Enabled(true);
    m_goochUniformSetter1->SetCameraPositionEnabled(true);
    m_goochAppearance1->SetShaderParamSetter(m_goochUniformSetter1);

    m_goochUniformSetter2 = GenericShaderParamSetter::Create();
    m_goochUniformSetter2->SetModelViewProjectionMatrix4Enabled(true); 
    m_goochAppearance2->SetShaderParamSetter(m_goochUniformSetter2);

Prepare uniform data:

    m_coolDiffuseWeight= 0.25f;
    m_warmDiffuseWeight = 0.5f;
    m_goochScale = 0.1f;

    m_coolColor[0] = 0.0f;
    m_coolColor[1] = 0.0f;
    m_coolColor[2] = 0.55f;
    m_coolColor[3] = 1.0f;

    m_warmColor[0] = 0.3f;
    m_warmColor[1] = 0.3f;
    m_warmColor[2] = 0.0f;
    m_warmColor[3] = 1.0f;

    m_silhouetteColor[0] = 0.0f;
    m_silhouetteColor[1] = 0.0f;
    m_silhouetteColor[2] = 0.0f;
    m_silhouetteColor[3] = 1.0f;

Set the uniforms:

    m_goochUniformSetter1->SetUniform("u_CoolDiffuseWeight", Shader::Float, &m_coolDiffuseWeight);
    m_goochUniformSetter1->SetUniform("u_WarmDiffuseWeight", Shader::Float, &m_warmDiffuseWeight);
    m_goochUniformSetter1->SetUniform("u_CoolColor", Shader::FloatVec4, m_coolColor);
    m_goochUniformSetter1->SetUniform("u_WarmColor", Shader::FloatVec4, m_warmColor);

    m_goochUniformSetter2->SetUniform("u_Scale", Shader::Float, &m_goochScale);
    m_goochUniformSetter2->SetUniform("u_Color", Shader::FloatVec4, m_silhouetteColor);
Material Configuration
    m_goochAppearance1->SetMaterial(Material::Create());
    m_goochAppearance1->GetMaterial()->SetDiffuse(Color(0.75f, 0.75f, 0.75f, 1.0f));
    m_goochAppearance1->GetMaterial()->SetAmbient(Color(0.0f, 0.0f, 0.0f, 0.0f));
    m_goochAppearance1->GetMaterial()->SetSpecular(Color(1.0f, 1.0f, 1.0f, 1.0f));
    m_goochAppearance1->GetMaterial()->SetSpecularPower(20.0f);

    m_goochAppearance2->SetMaterial(Material::Create());
Render Mode
    m_goochAppearance1->SetRenderMode(RenderMode::Create());
    m_goochAppearance1->GetRenderMode()->SetCulling(RenderMode::BackFaceCulling);
    
    m_goochAppearance2->SetRenderMode(RenderMode::Create());
    m_goochAppearance2->GetRenderMode()->SetCulling(RenderMode::FrontFaceCulling);

Example 

Here is exemplified the Gooch effect:

drawing-4-1677461853.png


Gooch Effect in SceneComposer

To experiment with the Gooch effect in SceneComposer use solution ShaderExamples from folder cgi_studio_player/content/Tutorials/04_ShaderUsage.

Tutorial for Special 3D Render Techniques

Multi Pass Effects in Candera > Examples for Global 3D Effect Shaders

Description

This chapter describes how global multi pass effects in Candera can be achieved and special 3D effect shaders techniques supported by Candera.

Depth of Field 

Description

This chapter describes how to generate the Depth of Field effect using the global multipass appearance technique supported by Candera.

Introduction 
Depth of Field

The Depth-of-Field effect simulates the natural blurring of foreground and background scene elements when viewed through a camera lens. The effect firstly separates the scene in Z order and then blurs the foreground and background images according to the values set in the Depth of Field effect parameters. The final image is composited from the processed originals.

In Candera, the effect is realized using a global multipass technique which implies the rendering in four steps:  [1]

  • Render pass 1 - rendering the sharp scene, storing depth values in alpha channel
  • Render pass 2 - blurring the result of the first render pass horizontally
  • Render pass 3 - blurring the result of the second render pass vertically
  • Render pass 4 - combine sharp and blurred picture, using focus-, focus range- and stored depth values, in order to realize the final result
drawing-4-1677462269.png
Workflow 
Scenes

For an easier understanding on how to realize the Depth of Field effect we will use four scenes, each scene will correspond to a rendering step.

First of all, the original main scene is taken and used as a texture on a billboard. The depth values of the images are saved in the alpha value in the offscreen frame buffer. In the second and third steps, the blurring is being done. For reasons of the performance, this step is parted into two steps:
The resulting billboard of the first workflow step is blurred horizontally using a fragment shader. The resulting image is used as texture input for the next scene, which is subsequently blurred vertically.
In the final step, the blurred images are combined with the sharp images from the first offscreen render target. The result is rendered as texture on a billboard placed in the final scene. A fourth camera displays the resulting Depth of Field effect applied on the main scene.

The Billboards should be rendered such that they cover the whole screen

The technique is also described schematically in the diagram below:  [1]

drawing-4-1677462360.png


Remember to set a Candera::RenderOrder object for each scene.

The sections below offer you some extra information needed to correctly set up the effect.

Offscreen Render Targets

Create and configure all offscreen render targets that shall be used for cameras 1 - 3 as shown below:

    m_offscreenRenderTargetDof = Base::CreateGraphicDeviceUnit(DevicePackageDescriptor::OffscreenTarget3D);
    if (m_offscreenRenderTargetDof == 0) {
        return false;
    }

    sprintf(m_rtProperties.m_width, "%d", m_gdu->ToRenderTarget3D()->GetWidth());
    sprintf(m_rtProperties.m_height, "%d", m_gdu->ToRenderTarget3D()->GetHeight());
    sprintf(m_rtProperties.m_colorFormat, "%d", RgbaUnpackedColorFormat);
    sprintf(m_rtProperties.m_depthFormat, "%d", Depth32Format);

    const MetaInfo::GraphicDeviceUnitMetaInfo *meta = DevicePackageDescriptor::GetMetaInformation(m_offscreenRenderTargetDof->GetUnitType());
    meta->LookupItem("Width") && meta->LookupItem("Width")->Set(m_offscreenRenderTargetDof, m_rtProperties.m_width);
    meta->LookupItem("Height") && meta->LookupItem("Height")->Set(m_offscreenRenderTargetDof, m_rtProperties.m_height);
    meta->LookupItem("ColorFormat") && meta->LookupItem("ColorFormat")->Set(m_offscreenRenderTargetDof, m_rtProperties.m_colorFormat);
    meta->LookupItem("DepthFormat") && meta->LookupItem("DepthFormat")->Set(m_offscreenRenderTargetDof, m_rtProperties.m_depthFormat);

#if CANDERA_DEVICE_IMX6
    static_cast<iMX6FrameBufferObject*>(m_offscreenRenderTargetDof)->GetProperties().SetOwner(m_gdu->ToRenderTarget3D());
#endif
    m_offscreenRenderTargetDof->Upload();
Shader Parameter Setter

The meshes from each scene will use a dedicated shader which will set specific uniforms for each scene. The uniforms needed to be set are:

  • Scaled depth value (u_depthScale): factor used to scale the depth value
  • Circle of confusion (u_CocScale): factor used to scale the size of circle of confusion
  • Focus range (u_Range): factor used in the computation of the weight of sharpness / blurriness
  • Focus (u_Focus): factor used in the computation of the weight of sharpness / blurriness

    // DoF Scene
    m_objectSps = GenericShaderParamSetter::Create();
    m_objectSps->SetLightsCoordinateSpace(Light::Object);
    m_objectSps->SetModelMatrix4Enabled(false);
    m_objectSps->SetNormalModelMatrix3Enabled(false);
    m_objectSps->SetUniform("u_depthScale",Shader::Float, &m_depthScale);

    // Blur Scene vertically
    m_blurXSps = GenericShaderParamSetter::Create();
    m_blurXSps->SetModelViewProjectionMatrix4Enabled(false);
    m_blurXSps->SetUniform("u_blurFactor",Shader::Float, &m_blurFactor);
    m_blurXSps->SetUniform("u_offsetLeft", Shader::Float, &m_viewportLeft);
    m_blurXSps->SetUniform("u_offsetTop", Shader::Float, &m_viewportTop);

    // Blur Scene horizontally
    m_blurYSps = GenericShaderParamSetter::Create();
    m_blurYSps->SetModelViewProjectionMatrix4Enabled(false);
    m_blurYSps->SetUniform("u_blurFactor",Shader::Float, &m_blurFactor);
    m_blurYSps->SetUniform("u_offsetLeft", Shader::Float, &m_viewportLeft);
    m_blurYSps->SetUniform("u_offsetTop", Shader::Float, &m_viewportTop);

    // Combine Scene
    m_dofCombineSps = GenericShaderParamSetter::Create();
    m_dofCombineSps->SetModelViewProjectionMatrix4Enabled(false);
    m_dofCombineSps->SetUniform("u_Range",Shader::Float, &m_range);
    m_dofCombineSps->SetUniform("u_Focus",Shader::Float, &m_focus);
    m_dofCombineSps->SetUniform("u_offsetLeft", Shader::Float, &m_viewportLeft);
    m_dofCombineSps->SetUniform("u_offsetTop", Shader::Float, &m_viewportTop);
Camera

The cameras from the all three scenes should use perspective projection.

    SharedPointer<PerspectiveProjection> perspectiveProjection = PerspectiveProjection::Create();
    perspectiveProjection->SetNearZ(0.001F);
    perspectiveProjection->SetFarZ(100.0F);
    perspectiveProjection->SetFovYDegrees(45.0F);
    perspectiveProjection->SetAspectRatio(static_cast<Float>(dispSettings->width) / static_cast<Float>(dispSettings->height));

Set the clear mode for the first two cameras as below:

    m_clearMode.SetClearColor(Color(0.8f,0.8f,0.8f,1.0f));
    m_clearMode.SetColorClearEnabled(true);
    m_clearMode.SetDepthClearEnabled(true);
    m_clearMode.SetClearDepth(1.0f);
Billboard Textures

This code snippet shows you how to create the textures for the billboards. First create a Candera::ProxyTextureImage:

    SharedPointer<ProxyTextureImage> proxyTexImgBlurredX = ProxyTextureImage::Create(m_offscreenRenderTargetBlurX->ToImageSource3D());
    SharedPointer<ProxyTextureImage> proxyTexImgBlurredY = ProxyTextureImage::Create(m_offscreenRenderTargetBlurY->ToImageSource3D());

Then create and configure the texture:

    SharedPointer<Texture> proxyTexBlurredX = Texture::Create();
    proxyTexBlurredX->SetTextureImage(proxyTexImgBlurredX);
    proxyTexBlurredX->SetMipMapFilter(Texture::MipMapNone);
    proxyTexBlurredX->SetMagnificationFilter(Texture::MinMagNearest);
    proxyTexBlurredX->SetMinificationFilter(Texture::MinMagNearest);
    proxyTexBlurredX->SetWrapModeU(Texture::ClampToEdge);
    proxyTexBlurredX->SetWrapModeV(Texture::ClampToEdge);

    SharedPointer<Texture> proxyTexBlurredY = Texture::Create();
    proxyTexBlurredY->SetTextureImage(proxyTexImgBlurredY);
    proxyTexBlurredY->SetMipMapFilter(Texture::MipMapNone);
    proxyTexBlurredY->SetMagnificationFilter(Texture::MinMagNearest);
    proxyTexBlurredY->SetMinificationFilter(Texture::MinMagNearest);
    proxyTexBlurredY->SetWrapModeU(Texture::ClampToEdge);
    proxyTexBlurredY->SetWrapModeV(Texture::ClampToEdge);

Repeat these two steps for all textures.

The billboard from the Combine Scene uses two textures. When you set the textures be sure that the texture unit value is set to "0" for the texture corresponding to the camera 1. Analogous, the other texture will have the texture unit value set to "1".

Placing Billboards in Scenes

See Shader Usage in Candera: Global Multi Pass Effects

Example 

Here is exemplified the Bloom effect on a scene.

First image shows the scene without the effect:

drawing-4-1677462432.png

Second image shows same scene but with the effect:

drawing-4-1677462450.png
References 

[1] http://www.geeks3d.com/20090710/javascript-depth-of-field-effect


Bloom 

Description

This chapter describes how to generate the Bloom effect using the global multipass appearance technique supported by Candera.

Introduction 
Bloom

The Bloom-Effect is a shader effect used to simulate the appearance of very bright light. The effect firstly extracts the bright area of an image using a threshold filter. The extracted regions are further blurred in order to realize the simulation of light bleeding over darker regions.

The bloom effect is realized using a global multipass technique which implies the rendering in five steps:  [1]

  • Render pass 1 - rendering the sharp scene
  • Render pass 2 - extract bright area using a threshold filter
  • Render pass 3 - blurring the result of the second render pass horizontally
  • Render pass 4 - blurring the result of the third render pass vertically
  • Render pass 5 - combine sharp and blurred picture, using intensity as well as saturation of bloom texture and original scene texture, in order to generate the final result
drawing-4-1677462702.png

Workflow 
Scenes

For an easier understanding on how to realize the Bloom effect we will use five scenes, each scene will correspond to a rendering step.

First of all, the original main scene is taken and used as a texture on a billboard. This billboard is rendered with a special shader that will extract the bright area of the texture using a threshold filter.
In the second and third steps, the blurring is being done. For reasons of the performance, this step is parted into two steps: The resulting billboard of the first workflow step is rendered with a shader that will blur the images horizontally and these blurred images are rendered as texture on an another billboard placed in the third scene. The billboard of the third scene is then rendered with a shader that will blur the images vertically. This billboard is part of the fourth scene, viewed by a camera which is connected to a fourth offscreen render target.
In the final step, the images of the blurred, bright area of the scene are combined with the sharp image from the first offscreen render target. The result is rendered as texture on a billboard placed in the final fifth scene. A fifth camera displays the resulting Bloom effect applied on the main scene.

The Billboards should be rendered such that they cover the whole screen

The technique is also described schematically in the diagram below:

drawing-4-1677462763.png

Remember to set a Candera::RenderOrder object for each scene.

The sections below offer you some extra information needed to correctly set up the effect.

Shader Parameter Setter

The meshes from each scene will use a dedicated shader which will set specific uniforms for each scene. The uniforms needed to be set are:

  • Bloom Brightness Threshold value (u_brightnessThreshold): brightness threshold value to render the bloom shape. lower = darker parts of the image
  • Blur Factor (u_blurFactor): factor used in the computation of the weight of sharpness / blurriness
  • Intensity amount on original scene (u_originalIntensity): factor used to set the intensity of the original scene texture
  • Saturation amount on original scene (u_originalSaturation): factor used to set the saturation of the original scene texture
  • Bloom Intensity (u_bloomIntensity): bloom intensity factor used in the combining scene
  • Bloom Saturation (u_bloomSaturation): bloom saturation value used in the combining scene

    // Sharp Scene
    m_objectSps = GenericShaderParamSetter::Create();
    m_objectSps->SetLightsCoordinateSpace(Light::Object);
    m_objectSps->SetModelMatrix4Enabled(false);
    m_objectSps->SetNormalModelMatrix3Enabled(false);

    // Bloom Threshold Scene
    m_bloomThresholdSps = GenericShaderParamSetter::Create();
    m_bloomThresholdSps->SetModelViewProjectionMatrix4Enabled(false);
    m_bloomThresholdSps->SetUniform("u_brightnessThreshold",Shader::Float, &m_bloomBrightnessThreshold);
    m_bloomThresholdSps->SetUniform("u_offsetLeft", Shader::Float, &m_offsetLeft);
    m_bloomThresholdSps->SetUniform("u_offsetTop", Shader::Float, &m_offsetTop);

    // Blur Scene vertically
    m_bloomBlurXSps = GenericShaderParamSetter::Create();
    m_bloomBlurXSps->SetModelViewProjectionMatrix4Enabled(false);
    m_blurFactor = 1.0F / m_gdu->ToRenderTarget3D()->GetWidth();
    m_bloomBlurXSps->SetUniform("u_blurFactor",Shader::Float, &m_blurFactor);
    m_bloomBlurXSps->SetUniform("u_offsetLeft", Shader::Float, &m_offsetLeft);
    m_bloomBlurXSps->SetUniform("u_offsetTop", Shader::Float, &m_offsetTop);

    // Blur Scene horizontally
    m_bloomBlurYSps = GenericShaderParamSetter::Create();
    m_bloomBlurYSps->SetModelViewProjectionMatrix4Enabled(false);
    m_blurFactor = 1.0F / m_gdu->ToRenderTarget3D()->GetHeight();
    m_bloomBlurYSps->SetUniform("u_blurFactor",Shader::Float, &m_blurFactor);
    m_bloomBlurYSps->SetUniform("u_offsetLeft", Shader::Float, &m_offsetLeft);
    m_bloomBlurYSps->SetUniform("u_offsetTop", Shader::Float, &m_offsetTop);

    // Combine Scene
    m_bloomCombineSps = GenericShaderParamSetter::Create();
    m_bloomCombineSps->SetModelViewProjectionMatrix4Enabled(false);
    m_bloomCombineSps->SetUniform("u_originalIntensity",Shader::Float, &m_originalIntensity);
    m_bloomCombineSps->SetUniform("u_originalSaturation",Shader::Float, &m_originialSaturation);
    m_bloomCombineSps->SetUniform("u_bloomIntensity",Shader::Float, &m_bloomIntensity);
    m_bloomCombineSps->SetUniform("u_bloomSaturation",Shader::Float, &m_bloomSaturation);
    m_bloomCombineSps->SetUniform("u_offsetLeft",Shader::Float, &m_offsetLeft);
    m_bloomCombineSps->SetUniform("u_offsetTop",Shader::Float, &m_offsetTop);
Camera

The cameras from all the five scenes should use perspective projection.

    SharedPointer<PerspectiveProjection> perspectiveProjection = PerspectiveProjection::Create();
    perspectiveProjection->SetNearZ(0.001F);
    perspectiveProjection->SetFarZ(100.0F);
    perspectiveProjection->SetFovYDegrees(45.0F);
    perspectiveProjection->SetAspectRatio(static_cast<Float>(dispSettings->width) / static_cast<Float>(dispSettings->height));

Set the clear mode for the cameras as below:

    m_clearMode.SetClearColor(Color(0.8f,0.8f,0.8f,1.0f));
    m_clearMode.SetColorClearEnabled(true);
    m_clearMode.SetDepthClearEnabled(true);
    m_clearMode.SetClearDepth(1.0f);
Billboard Textures

This code snippet shows you how to create the textures for the billboards. First create a Candera::ProxyTextureImage:

    SharedPointer<ProxyTextureImage> proxyTexImgBloomThreshold = ProxyTextureImage::Create(m_offscreenRenderTargetThreshold->ToImageSource3D());
    SharedPointer<ProxyTextureImage> proxyTexImgBlurredX = ProxyTextureImage::Create(m_offscreenRenderTargetBlurX->ToImageSource3D());
    SharedPointer<ProxyTextureImage> proxyTexImgBlurredY = ProxyTextureImage::Create(m_offscreenRenderTargetBlurY->ToImageSource3D());

Then create and configure the texture:

    SharedPointer<Texture> proxyTexBloomThreshold = Texture::Create();
    proxyTexBloomThreshold->SetTextureImage(proxyTexImgBloomThreshold);
    proxyTexBloomThreshold->SetMipMapFilter(Texture::MipMapNone);
    proxyTexBloomThreshold->SetMagnificationFilter(Texture::MinMagNearest);
    proxyTexBloomThreshold->SetMinificationFilter(Texture::MinMagNearest);
    proxyTexBloomThreshold->SetWrapModeU(Texture::ClampToEdge);
    proxyTexBloomThreshold->SetWrapModeV(Texture::ClampToEdge);

    SharedPointer<Texture> proxyTexBlurredX = Texture::Create();
    proxyTexBlurredX->SetTextureImage(proxyTexImgBlurredX);
    proxyTexBlurredX->SetMipMapFilter(Texture::MipMapNone);
    proxyTexBlurredX->SetMagnificationFilter(Texture::MinMagNearest);
    proxyTexBlurredX->SetMinificationFilter(Texture::MinMagNearest);
    proxyTexBlurredX->SetWrapModeU(Texture::ClampToEdge);
    proxyTexBlurredX->SetWrapModeV(Texture::ClampToEdge);

    SharedPointer<Texture> proxyTexBlurredY = Texture::Create();
    proxyTexBlurredY->SetTextureImage(proxyTexImgBlurredY);
    proxyTexBlurredY->SetMipMapFilter(Texture::MipMapNone);
    proxyTexBlurredY->SetMagnificationFilter(Texture::MinMagNearest);
    proxyTexBlurredY->SetMinificationFilter(Texture::MinMagNearest);
    proxyTexBlurredY->SetWrapModeU(Texture::ClampToEdge);
    proxyTexBlurredY->SetWrapModeV(Texture::ClampToEdge);

The billboard from the Combine Scene uses two textures. When you set the textures be sure that the texture unit value is set to "0" for the texture corresponding to the camera 1. Analogous, the other texture will have the texture unit value set to "1".

Placing Billboards in Scenes

See Shader Usage in Candera: Global Multi Pass Effects

Example 

Here is exemplified the Bloom effect on a scene.

First image shows the scene without the effect:

drawing-4-1677462810.png

Second image shows same scene but with the effect:

drawing-4-1677462819.png


Tutorial for Special 3D Render Techniques

Transform Feedback

Transform Feedback is the process of capturing primitives generated by the vertex processing step(s), recording data from those primitives into a vertex buffer. This allows one to preserve the post-transform rendering state of an object and resubmit this data multiple times.

Use Cases

This feature enables the user to utilize the vertex processing unit of the GPU to perform calculations and reuse the results. This is especially useful for debugging vertex transformations. Applications can also use it for use cases like speeding up particle systems.

drawing-4-1677055208.png

Usage in Candera:

  1. Before uploading your vertex shader that processes your data, use Shader::SetTransformVaryings to tell the engine which shader varying(s) need to be recorded into the feedback buffer.
  2. Upload the shader, activate the shader’s appearance, and source vertex buffer as usual. If you want to disable the output of the pixel pipeline stage, use RenderMode::SetRasterizerDiscardEnabled.
  3. Bind the source vertex buffer using Shader::BindAttributes.
  4. Call Renderer::BindTransformFeedbackBuffer, and Renderer::BeginTransformFeedbackBuffer using the destination vertex buffer that the processed primitives will be recorded into.
  5. Render the source vertex buffer as usual.
  6. Call Renderer::EndTransformFeedbackBuffer
  7. Call Renderer::MapTransformFeedbackBuffer to get a pointer to the processed, and recorded data.
  8. Before using that pointer for your own purposes, call Renderer::UnmapTransformFeedbackBuffer.
  9. The lifetime of that pointer’s data is tied to the lifetime of your destination vertex buffer.

Tutorial for Special 2D Render Techniques

Tutorial for Special 2D Render Techniques

2D Shadow Effect

Description

This chapter briefly describes how to use the 2D shadow effect.

Ensure that this effect is supported by the device platform in use.

2D Configure a Shadow Effect 

Shadow Effect

By using Candera::ShadowBitmapBrushBlend, a colored shadow can be applied to a bitmap.

Following properties of the shadow can be configured:

  • Color (including alpha value)
  • Scale (the shadow size related to the bitmap)
  • Position Offset (related to the bitmap)

Following is an example of a grey colored shadow, with a scale of 70% and an offset of 80/80 related to the bitmap:

drawing-4-1677466312.png

Refer to SceneComposer User Manual about how to configure a ShadowBitmapBrushBlend effect in a 2D scene.

Modify 2D Shadow Effect Properties 

Access Shadow Effect

To modify the shadow effect properties, first the shadow effect needs to be retrieved from its render node:

    // Get and cast first effect of render node
    Effect2D* effect2D = renderNode->GetEffect(0);
    ShadowBitmapBrushBlend* shadow = 0;
    if (effect2D != 0){
        shadow = (ShadowBitmapBrushBlend*) effect2D;
    }
    // End Get and cast first effect of render node
Set Shadow Color

Specify a color with a red, a blue, a green and an alpha component and apply the color to the Shadow Effect.

Example:

Color::Data c;
c.alpha = 0.5f;
c.blue = 0.0f;
c.green = 0.0f;
c.red = 1.0f;
shadow->ShadowColor().Set(c);
Set Shadow Scale

For the shadow's scale you need the scale X and Y value. Add the two values to a Vector2 and set the shadow's scale.

Example:

Float shadowScaleX = 0.7f;
Float shadowScaleY = 0.7f;
shadow->ShadowScale().Set(Vector2(shadowScaleX, shadowScaleY));
Set Shadow Position Offset

It is possible to change the position of the shadow by changing its offset to the node. Therefore you need a Vector2 with the X and Y offset values again.

Example:

Float shadowPosOffX = 5.0f;
Float shadowPosOffY = 7.0f;
shadow->ShadowPositionOffset().Set(Vector2(shadowPosOffX, shadowPosOffY));




Tutorial for Special 2D Render Techniques

2D Mirror Effect

Description

This chapter briefly describes how to use the 2D mirror effect.

Ensure that this effect is supported by the device platform in use.

Configure a 2D Mirror Effect 

Mirror Effect

By using Candera::MirrorBitmapBrushBlend, a mirror reflection can be applied to a bitmap node.

Following properties of a mirror effect can be configured:

  • Mirror Axis Modifications
  • Alpha Value

Following is an example of a mirror effect with a diagonal mirror axis and an alpha value of 1.0f:

drawing-4-1677466536.png

Refer to SceneComposer User Manual about how to configure a MirrorBitmapBrushBlend effect in a 2D scene.

Modify 2D Mirror Effect Properties 

Set Mirror Axis

To set the Mirror Axis you need to set two vectors.

  • The first vector defines the start point of the mirror axis (the startpoint's X and Y value),
  • and the second vector defines the end of the axis.

The following example shows how to implement a mirror axis from the startpoint (0, 400) to the endpoint (700, 400).

Float fromX = 0.0f;
Float fromY = 400.0f;
Float toX = 700.0f;
Float toY = 400.0f;

mirror->MirrorAxisFrom().Set(Vector2(fromX, fromY));
mirror->MirrorAxisTo().Set(Vector2(toX, toY));
Set Mirror Alpha Value

It is possible to set the mirrors alpha value, so that the mirror looks more or less intensive.

Float mirrorAlpha = 0.7f;
mirror->Alpha()= mirrorAlpha;
drawing-4-1677466579.png
Tutorial for Special 2D Render Techniques

2D Blur Effect

Description

This chapter briefly describes how to use the 2D blur effect.

Ensure that this effect is supported by the device platform in use.

Configure a 2D Blur Effect 

Blur Effect

By using Candera::BlurBitmapBrushBlend, a bitmap node can be blurred, applying a gaussean filter convolution.

Blur Effect supports the following property:

  • Filtersize (Number of neighbour pixels)

In this example, the upper two pictures are the original ones, the lower two are blurred with a filter size of 15.

drawing-4-1677466738.png

Refer to SceneComposer User Manual about how to configure a BlurBitmapBrushBlend effect in a 2D scene.

Modify 2D Blur Effect Property 

Set Blur Filtersize

When using this effect you only have to set the filtersize. This value defines the blur factor of, for example, an image. The bigger the value, the more the image is blurred. The following example shows how to set the filtersize.

UInt8 factor = 5;
blur->FilterSize() = factor;



Tutorial for Special 2D Render Techniques

2D Shear Effect

Description

This chapter briefly describes how to use the 2D shear effect.

Ensure that this effect is supported by the device platform in use.

2D Configure a Shear Effect 

Shear Effect

By using Candera::ShearBitmapBrushBlend, the effect shears the incoming surface horizontally and vertically using the given angles.

Following properties for shearing can be configured:

  • Shear Angle X
  • Shear Angle Y

Following is an example of a skewed bitmap, with the angles (in degree): X = 20 and Y = 30.

drawing-4-1677466983.png

Refer to SceneComposer User Manual about how to configure a ShearBitmapBrushBlend effect in a 2D scene.

Modify 2D Shear Effect Properties 

Set Shear Angles

Horizontal and a vertical skew can be modified by setting the ShearAngle to proper values. You can set both, horizontal and vertical skew, to values from -180 to 180.

Example:

    // Set Shear Angles X and Y
    shear.ShearAngleX().Set(30.0f);
    shear.ShearAngleY().Set(20.0f);
    // End Set Shear Angles X and Y
Tutorial for Special 2D Render Techniques

2D HSL Effect

Description

This chapter briefly describes how to use the 2D HSL effect.

Ensure that this effect is supported by the device platform in use.

Configure a 2D HSL Effect 

HSL Effect

The BitmapBrushHslBlend effect outputs an bitmap image having applied hue, saturation and lightness correction and blended with the store buffer.

Following properties of a HSL effect can be configured:

Hue

Hue is one of the main properties of a color, defined, technically, as the degree to which a stimulus can be described as similar to or different from stimuli that are described as red, green, blue, yellow, orange and violet. The image below shows the circle of hues for the color red (R=255, G=0, B=0).

drawing-4-1677467179.png

 On the HSL effect, the hue is expressed as degrees in the interval [-180 .. 180]; the original color is at a hue value of 0.

Saturation

Saturation describes the intensity of a color, defined as a range from pure color to gray.

drawing-4-1677467197.png

On the HSL effect, "0" means grayscale, "1" means color unchanged and "-1" means color inverted.

Lightness

Lightness measures the relative degree of black or white that's been mixed with a given hue.

drawing-4-1677467220.png

On the HSL effect "0" means black, "1" means the color unchanged.

Example

Following is an example of a HSL effect configured with hue = 120, saturation = -1 and lightness = 1.3:

drawing-4-1677467235.png

Refer to SceneComposer User Manual about how to configure a BitmapBrushHslBlend effect in a 2D scene.

Modify 2D HSL Effect Properties

Set Hue, Saturation and Lightness

In order to change the BitmapBrushHslBlend effect properties, call the corresponding method on the HslCorrectionEffect as shown bellow:

            m_bitmap_hslBlend = BitmapBrushHslBlend::Create();
            m_bitmap_hslBlend->GetBitmapBrush().Image().Set(m_image.GetPointerToSharedInstance());

            HslCorrectionEffect& hslCorrection = m_bitmap_hslBlend->GetHslCorrectionEffect();
            hslCorrection.Hue() = 17.0F;
            hslCorrection.Saturation() = -0.5F;
            hslCorrection.Lightness() = 1.2F;


 


Tutorial for Special 2D Render Techniques

2D Alpha Mask Effect

Description

This chapter briefly describes how to use the 2D alpha mask effect.

Ensure that this effect is supported by the device platform in use.

Configure a 2D Alpha Mask Effect 

Alpha Mask Effect

The alpha mask effect creates the appearance of partial or full transparency of an image by using the information from the alpha channel of a mask.

This appearance can be applied to a node by using one of the following effects:

Configuration

Following properties can be configured:

Image

Represents the image attached to the effect.

Mask

It's the image used as mask. Only the alpha channel of the mask is used.

Mask Node

It's the node used to control the transformation of the mask (position, rotation and scaling). If set to 0 the transformation of the current node is used.

Effect Example

The image below presents an alpha mask effect example. The mask node is rotated 45 degrees, the pivot offset is set to (-50;-50) and scaled to (1.5;1.5).

drawing-4-1677467438.png

Refer to SceneComposer User Manual about how to configure an alpha mask effect in a 2D scene.

Modify 2D Alpha Mask Effect Properties 

Set Effect Properties
//create effect
bitmap_maskBlend = GlBitmapBrushMaskBlend::Create();

//set image property
bitmap_maskBlend->GetBitmapBrush().Image().Set(m_image.GetPointerToSharedInstance());

//set mask property
SharedPointer<BitmapImage2D> maskBitImage = BitmapImage2D::Create();
maskBitImage->SetBitmap(mask_bitmap);
maskBitImage->Upload();
GlMaskEffect& maskEffect = bitmap_maskBlend->GetMaskEffect();
maskEffect.Mask() = maskBitImage.GetPointerToSharedInstance();

//set mask node
RenderNode* maskNode = RenderNode::Create();
maskNode->SetPosition(400.f, 30.f);
maskNode->SetScale(0.2f, 0.2f);
maskNode->SetPosition(m_bitmapNode5->GetPosition() + Vector2(8, 8));
maskEffect.MaskNode() = maskNode;

bitmap_maskBlend->Update();

//attach effect to the node
node = RenderNode::Create();
node->AddEffect(bitmap_maskBlend.GetPointerToSharedInstance());

Tutorial for Diagnostics

Tutorial for Diagnostics

Assertions

Refer to FeatStd Assertions how to use and configure assertions.

Tutorial for Diagnostics

Logging

Refer to FeatStd Logging how to use and configure FeatStd logging feature.

Candera Log Output

For a list of all LogRealms supported by Candera, refer to Candera/System/Diagnostics/LogRealm.h:

FEATSTD_LOG_DECLARE_REALM(CanderaEngine2D);
FEATSTD_LOG_DECLARE_REALM(CanderaEngine3D);
FEATSTD_LOG_DECLARE_REALM(CanderaEngineBase);
FEATSTD_LOG_DECLARE_REALM(CanderaAnimation);
FEATSTD_LOG_DECLARE_REALM(CanderaSystem);
FEATSTD_LOG_DECLARE_REALM(CanderaTextEngine);
FEATSTD_LOG_DECLARE_REALM(CanderaTransitions);

FEATSTD_LOG_DECLARE_REALM(CanderaPlatformDevice);
FEATSTD_LOG_DECLARE_REALM(CanderaPlatformOs);

FEATSTD_LOG_DECLARE_REALM(CanderaAssetLoader);

FEATSTD_LOG_DECLARE_REALM(CanderaGlobalization);

FEATSTD_LOG_DECLARE_REALM(CanderaMonitor);

FEATSTD_LOG_DECLARE_REALM(CanderaScripting);

FEATSTD_LOG_DECLARE_REALM(CanderaEntityComponentSystem);

FEATSTD_LOG_DECLARE_REALM(CanderaBehavior);

Player Logging Support

The Player supports logging output displayed in a seperate window that can be controlled from the CGI-Panel.

Starting Player with Log Output

To show the log output window of the Player, the CGIPanel.exe has to be started with option -l. This will open a console window in addition to the Panel and display windows to show log output. In the default configuration this window will only show the log output of the CgiApplication and default Candera level.

If the Player is started via SceneComposer plugin notification, it will be automatically started with the -l option.

Logging Menu Controls

After loading the asset the logging menu controls will appear on the panel. You have two options:

  • Enable log checkbox: Will enable/disable the log output via the CgiAppLogAppender
  • Candera log level dropdown: Will set all Candera realms to the specified log level

Menu controls will only appear if your DLL was built with FEATSTD_LOG_ENABLED option

Log Control via SceneComposer

If the Player is started via SceneComposer plugin notification during asset generation, it is possible to use the SceneComposer logging menu options. The changed settings will be sent to the Player.

Tutorial for Diagnostics

Performance Analysis > Introduction

Performance Analysis with CGI Analyzer

By enabling the Candera Monitor component (CMake: FEATSTD_MONITOR_ENABLED), Candera code is instrumented with performance traces, which are evaluated by the CGI Analyzer tool. In addition, also custom instrumentations can be added to applications, which will be visible in CGI Analyzer.

drawing-4-1677055717.png



Offline Analysis

Performance logs are recorded with Candera Monitor and can be evaluated in CGI Analyzer regarding:

  • frame rates
  • CPU/GPU load
  • CPU/GPU memory usage

Online Analysis

With online experiments, performance bottlenecks can be discovered: If framerate goes up after deactivating a dedicated part of the Candera Engine or the rendering pipeline, the bottleneck is located.

Following online experiments are supported:

  • Replace textures by 2x2 texture
  • Replace fragment shader with a very simple one
  • Ignore draw calls

Besides online experiments, CGI Analyzer Online Analysis supports:

  • Live display of key performance indicators (frame rate, CPU load, GPU load), where available
  • Computation and transfer of Render Benchmarks from target to host (for multi frequency rendering)
  • Online determination of scene graph characteristics (number of vertices, texture sizes, render order)
  • Online measurement of render times (for each node on a per-camera base)
  • Guards (FPS, unconditional)

For more information about CGI Analyzer, refer to CGI Analyzer User Manual.

Tutorial for Diagnostics

Performance Analysis > Howto Instrument for Performance Measurement

Performance Measurement with Analyzer

This document shall convey the basics of performance measurement instrumentation.


General Setup

Project Setup 

Copy your own project folder to your CGI Studio root folder (containing for example the Candera folder named cgi_studio_candera) named <CGI-STUDIO-ROOT>.

Point CMake GUI "Where is the source code" to the location of your source code.

Select an arbitrary folder for "Where to build the binaries".
After pressing "configure", following Candera Performance Measurement specific CMake flags can be configured:

FEATSTD_MONITOR_TYPE

Enables or disables Candera Monitor component and Candera performance monitoring instrumentation macros.

By default, Candera is configured to not include instrumentation on graphics engine level.

On / Off

FEATSTD_MONITOR_TCPIP_ADDRESS

FEATSTD_MONITOR_TCPIP_PORT

If FEATSTD_MONITOR_TYPE is set to FEATSTD_COM_TYPE_TCPIP , TCP/IP online connection to be established with Analyzer can be configured.

Default:

  • localhost (127.0.0.1)
  • 13047

TCP/IP Address and Port


FEATSTD_MONITOR_SERIALPORT_ADDRESS

FEATSTD_MONITOR_SERIALPORT_BAUDRATE

If FEATSTD_MONITOR_TYPE is set to FEATSTD_COM_TYPE_SERIALPORT , Serial port online connection to be established with Analyzer can be configured.

Default:

  • empty
  • Baud: 9600
Serial COM Port and Baudrate
CANDERA_PERFORMANCE_RECORDER_DISABLED

Enables or disables Candera performance instrumentations.

Default: Off

On / Off

Performance recording functionality is exposed by macros; hence no performance recording code will be included in Candera and Candera application builds, if performance recording is disabled.

In this tutorial, instead of using a TCP/IP online connection, performance log data is being written to a local file. In this case, performance log data needs to be transferred to Analyzer tool manually, i.e. via TFTP from target to host.

And the following cmake setting were chosen:

FEATSTD_MONITOR_TYPE = FEATSTD_COM_TYPE_FILE_DUMP

Program Skeleton (Recording to File) 

For this tutorial, the following program skeleton is used:

#include <stdio.h>
#include <time.h>
#include <Candera/System/Monitor/PerfMonPublicIF.h>
#include <FeatStd/Monitor/PerformanceRecording/FileDumper.h>

using namespace Candera;
using namespace FeatStd::PerfMon;

int main(int argc, char *argv[])
{
    // activate recording
    CANDERA_PERF_SET_ENABLED(true);
   
    FileDumper::Open("c:\\temp\\tutorial.perflog");

    // BEGIN do the recording here

    // END do the recording here

    // Flush recording buffer at regular, small intervals to prevent buffer overflow
    FileDumper::Flush();
    // Close file and open new file in order to keep performance log files small
    FileDumper::FlushAndClose();

    fgetc(stdin);

    return 0;
}

Important is the activation of recording. Without this line, no performance recording will be written to the recording buffer.

The call to FeatStd::PerfMon::FileDumper::Open opens the file to write to. FeatStd::PerfMon::FileDumper::Flush writes the data recorded in the buffer to the file, if possible. FeatStd::PerfMon::FileDumper::FlushAndClose calls DumpFile::Flush, just in case, and closes the file.

The performance log API was designed with different storage media and transport mechanisms in mind. As you can see from the following code snippet, the function FeatStd::PerfMon::FileDumper::Flush calls the function FeatStd::PerfMon::Controller::DumpPerfData and passes a pointer to a member function to it. This function is called in return to actually write data to the provided mechanism, in this case, to a file.

static void Flush()
{
    FEATSTD_PERF_DUMP_DATA(FlushLog, false, 0);
}

Program Skeleton (Recording to Memory) 

The following code snippet shows the program skeleton for recording to memory:

#include <stdio.h>
#include <time.h>
#include <Candera/System/Monitor/PerfMonPublicIF.h>
#include <FeatStd/Monitor/PerformanceRecording/MemoryDumper.h>

using namespace Candera;
using namespace FeatStd::PerfMon;

int main(int argc, char *argv[])
{
    const UInt32 perflogLength = 256;
    UInt8 perflog[perflogLength];

    // set destination for recording
    MemoryDumper::Initialize(perflog, perflogLength);

    // activate recording
    CANDERA_PERF_SET_ENABLED(true);
   
    // BEGIN do the recording here

    // END do the recording here

    // Flush recording buffer at regular, small intervals to prevent buffer overflow
    MemoryDumper::Flush();

    // Print performance log location and size to provided stream 
    // (default: std::cout) and disable performance log recording
    // A breakpoint has to be set here to prevent recorded data from being overwritten
    MemoryDumper::Dump();

    // Reset the dumper to restart recording
    MemoryDumper::Clear();

    // re-activate recording; 
    CANDERA_PERF_SET_ENABLED(true);

    fgetc(stdin);

    return 0;
}

Recording to memory is done similar to recording to file. Instead of closing the old file and opening a new one, the storage location along with the length of the performance log is dumped (written) to a specified stream (defaulting to std::cout) using the function FeatStd::PerfMon::MemoryDumper::Dump(). Performance log recording is automatically disabled at this point. A breakpoint has to be set here to stop the application and copy the performance log from memory to a file for opening with Analyzer. To continue recording, the performance log dumper has to be reset using the function FeatStd::PerfMon::MemoryDumper::Clear() to re-start recording at the beginning of the user-provided buffer.

 
Recording of Performance Values 


Using Event Recorders 

Event Recorders are the simplest recorders. They just record events at given points in time.
Their usage is straight forward:

...
CANDERA_PERF_RECORD_EVENT(EventRecId::Application0);
...

Here, an event with event id Application0 is recorded. The namespace EventRecId is defined in the file Recorder.h. If we compile and run our application, a performance log file is created with just this one recording.

We can now open this file in Analyzer. In the Flat / Hierarchical Grid View (Figure 1), as well as in the Time Bars View, the recording can be inspected. It is possible to change the predefined recorder name for display using the Configuration Dialog from the View Ribbon Bar. You have to reopen the performance log for the changes to take effect.

drawing-4-1677474167.png


Using Value Recorders 

Value recorders can record integral numbers (positive and negative). The example below shows a succession of value recordings.

...
CANDERA_PERF_RECORD_VALUE(ValueRecId::Application0, 10);
busywait();
CANDERA_PERF_RECORD_VALUE(ValueRecId::Application0, 10);
busywait();
CANDERA_PERF_RECORD_VALUE(ValueRecId::Application0, 10);
busywait();s
CANDERA_PERF_RECORD_VALUE(ValueRecId::Application0, 10);
busywait();
...

As with event recorders, value recorders are identified by their id. The namespace ValueRecId is again defined in the file Recorder.h. In addition to the id, the value to record is passed to the recording function. The function busywait() waits in a loop for some time to pass, just to make things more interesting.

Opening the new performance log in Analyzer now shows five recordings (one event and four values). The "Value" column shows the value 10 for each value recorder (Figure below).

drawing-4-1677474210.png

"The Absolute Value" column shows also the values 10. This is the case, because as a default, Analyzer assumes as relationship between recorded values "Unrelated". To regard the values of a value recorder as related, the value relationship "Differential" has to be set. This can be accomplished again via the Configuration Dialog from the View Ribbon Bar. Remember to close and reopen the performance log after changing settings. Figure 3 shows the value recordings of recorder Application0, interpreted as having the relationship "Differential". The "Value" column is still the same, but the "Absolute" column shows, that each recording has its recorded value added to the value of its predecessor.

drawing-4-1677474227.png

Let's add another value recorder:

...
CANDERA_PERF_RECORD_VALUE(ValueRecId::Application1, -50);
busywait();
...

If we run our program again and view the new performance log in Analyzer, we see the following:

drawing-4-1677474248.png

What happened? The problem here is, that Analyzer interprets a recorded value by default as positive long value. In order to interpret it correctly, we have to help Analyzer by using the Configuration Dialog from the View Ribbon Bar. By checking the checkbox labeled "Data values are signed" for the value recorder Application1, the values will from now on be interpreted correctly. Remember to reopen the performance log for the changes to take effect (Figure below).

drawing-4-1677474261.png


Using Cumulative Value Recorders 

The listing below shows the usage of a Cumulative Value Recorder with an annotation. An annotation is a way to differentiate between different flavors of the same recorder.

...
{
    CANDERA_PERF_RECORDER(CumulativeValue, (ValueRecId::Application2, "cumval"));
    busywait();
    CANDERA_PERF_RECORD_VALUE(ValueRecId::Application2, 10);
    busywait();
    CANDERA_PERF_RECORD_VALUE(ValueRecId::Application2, 20);
    busywait();
    CANDERA_PERF_RECORD_VALUE(ValueRecId::Application2, 30);
    busywait();
    CANDERA_PERF_RECORD_VALUE(ValueRecId::Application2, 40);
    busywait();
}
...

A Cumulative Value Recorder has to be instantiated. This is done with the first statement. All further references to this recorder add the respective values to the existing recording entry and do not instantiate new recording entries. In order for this to work, the references to the Cumulative Value Recorder have to be in the same block, or a sub-block. The image below shows the new result of the recording in Analyzer.

drawing-4-1677474296.png

We see, that the values are summed-up and the value of "Call Count" is 4, the number of recordings.


Using Timing Recorders 

Timing Recorders are, as the name implies, used to measure time. The time is measured in relation to the execution block where they are used. The life of a Timing Recorder ends with its execution block, as does the time measurement. Listing 8 shows the usage of a Timing Recorder.

...
{
    CANDERA_PERF_RECORDER(Timing, (TimingRecId::Application0));
    busywait();

    CANDERA_PERF_RECORD_EVENT(EventRecId::Application0);
    busywait();
}
...

Timing Recorders have to be instantiated. Everything (direct or called) in the same execution block contributes to the measured time. Also, all recordings in this execution block are regarded as being nested, being child recordings of the timing recording. The figure below shows the Hierarchical Grid View in Analyzer.

drawing-4-1677474328.png

A Timing Recorder can have further Timing Recorders in its execution block. What we gain here is a call stack-like hierarchy of recordings. The listing below shows the code snippet and the next figure shows the performance log opened in Analyzer.

drawing-4-1677474408.png


Using Cumulative Timing Recorders 

Cumulative Timing Recorder, as Cumulative Value Recorder, store one value per left execution block only. The listing below shows the usage and the next figure shows the result of this instrumentation in Analyzer. Cumulative Timing Recorders have to be instantiated.

...
{
    CANDERA_PERF_RECORDER(Timing, (TimingRecId::Application3, true));
    busywait();

    {
        {
            CANDERA_PERF_RECORDER(Timing, (TimingRecId:: Application3));
            busywait();
        }
        {
            CANDERA_PERF_RECORDER(Timing, (TimingRecId:: Application3));
            busywait();
        }
    }
}    
...
drawing-4-1677474494.png


Using Asynchronous Timing Recorders 

Asynchronous Timing Recorders are the only timing recorders whose recordings can outlive them. An Asynchronous Timing Recorder has to be instantiated and the end of the recording operation has to signaled explicitely (Listing below).

...

{
    // Start recording of ansychronous timing.
    CANDERA_PERF_RECORDER(AsyncTiming, (AsyncTimingRecId::Application0));
    busywait();
}

...

// End of asynchronous recording, outside of creation scope
CANDERA_PERF_ASYNCREC_FINISHED(AsyncTimingRecId::Application0);

...

Tutorial for Lua-Scripting

Description

This tutorial gives an overview of what Lua-Scripting is and shows how to create and use your own scripts.
See Introduction to Candera Lua Scripts and Candera Lua Reference Manual for in-depth information.

Tutorial for Lua-Scripting

Overview

This chapter gives and overview of Lua-Scripting.

What is Lua-Scripting?

Lua-Scripting is an extension of the base scripting system binding Lua to Candera. It gives you the opportunity to manipulate graphical objects using the Candera engine API.

Compared to Behaviors there are no predefined Lua-Scripts but however they offer an easy and fast way to define your own mechanics and functions. Although there are many advantages you nearly have to write them from the scratch. To learn the basics follow this step by step tutorial to see how to create, write and use them. Furthermore there is a Sample Solution called Lua-Scripting where you can click through several sample scripts for reading, learning or copying useful snippets of the code. You can find them in the Toolbox under the category called Scripts:

drawing-4-1676957493.png

Tutorial for Lua-Scripting

Create an own Script

This chapter provides an explanation of how a new Lua-Script can be created to make it ready to be usable in the SceneComposer.

How to create a new Script.

In difference to a Widget or a Behavior Lua-Scripts are completely generated in the SceneComposer by one simple step:

Click "Script" in the menu...

drawing-4-1676957601.png

... and chose "New Script". Browse for the desired folder and define where to save the new script. Name the script "CarRootController". This will be our root node script which controls other scripts. For the code template we recommend to choose "Full" to see all the predefined Callback-Functions.

drawing-4-1676957619.png

Now you will find your new script in the Toolbox under Scripts. To apply it to the desired node just drag and drop the script from the Toolbox to your node. (For the tutorial example apply it to the root node of the car)

Tutorial for Lua-Scripting

Lua Introduction

Callback Functions

If you open your script in the Script Editor you can see that there are already the most important lines of code. These are the so called callback functions which are called by Candera.

drawing-4-1676957696.png


Init(self, id)

The Init function gets called before the first Update call or when a script component is enabled for the first time. At this point all script instances have been created and optionally initialized via Awake, so scripts are ready to interact with each other to perform final initializations before the Update loop starts.


Update(self, id)

Called every render frame if the script component is enabled. Update is the commonly used function to implement application behavior.

Note: if you use one of the callback functions or if you want to make an own function public then don't forget to add them to the public table! (see next paragraph)


return{}

It returns the public interface as table. Variables that have been exposed via the public interface table, and are either of type integer, float, string, or boolean, are automatically displayed in SceneComposer's GUI. In the SceneComposer this variables can be edited and are saved/loaded with the scene. Functions in the return section are not displayed in SceneComposer's GUI but they are public and can get called by another script or by Candera (see "Callback Functions").

drawing-4-1676957709.png

To provide access to variables of the public table of the instance, the table is passed in the function parameter 'self' in all functions that are being called by Candera's script system. So if you define a variable in the public table you can use it in any functions of this script via "self.yourVariable".


Save and Start a Script

To validate your script press %<F8%> or choose "Script" - "Validate Script" from the menu.

When the script has been validated and there are no errors, you can press the start button to start the execution of the script system and stop it with the second button.

drawing-4-1676957751.png


Candera Logging

drawing-4-1676957810.png


To check whether your script crashes during runtime or not you can see errors or further information in the Candera Logging panel. If this panel is not activated go to View -> Output -> and check Candera Logging

Tip: Candera Logging is also very useful to test individual parts of your code like if-cases or loops via:

Candera.LogInfo(message)
Tutorial for Lua-Scripting

Id Handling

This chapter explains how to get access to other nodes via the id of their attached script. Therefore, create a new Scene Composer solution using the "3D Getting started" template, which includes a 3D model of a car.

To start it's important to know that there are two functions that are fundamental to get access to other nodes:

  • Candera.GetScriptIds(id) returns all ids of script components that are attached to the same node as 'id'.
  • Candera.GetChildScriptIds(id) returns all ids of script components that are attached to any node in the subtree of the node identified by 'id'.

Once you have the id of the child nodes you can get the script itself with:

  • Candera.IdToScriptComponent(scriptId) returns the public table of the script identified by 'scriptId'

The first goal is to rotate all four wheels in the same speed and to rotate the front wheels according to the steering lock.

drawing-4-1676958053.png

Therefore it is necessary that a script from the root node of the car communicates with all four wheels. But as we have already learned it's absolutely necessary that each node we want to find also has a script component. So before we start to get access to the child nodes we equip all four wheels with a script. As front wheels and back wheels have different behaviors (steering lock) create two different scripts called "Wheel_back" and "Wheel_front":


Wheel_back Script:

Get the current rotation of the wheel and set the new rotation with a modified x-value. In this case we add the 'rotationSpeed' value from the public table to the current x-rotation. Afterwards the public value 'rotationSpeed' will get set by the root script.

    local function Update(self, id)
        local pitch, yaw, roll =Candera.GetRotation(id)                                
        Candera.SetRotation(id,  pitch+self.rotationSpeed, yaw, roll) 
    end
    
    return { 
      Init = Init,
      Update = Update,
      GetName = GetName, 
      rotationSpeed =0.0
    }

To enable the root script to identify the wheel scripts it's also important that the wheel scripts get a value which contains a proper name. This value must not get changed by SceneComposer's GUI so we enable the access with a public function called GetName():

    local scriptName = "Wheel_back"
    local function GetName()
        return scriptName
    end

(don't forget to add the function to the public table: GetName = GetName, )


Wheel_front Script:

Just repeat the same steps for the front wheel with a modification in the update concerning the z-rotation which gets set to the steering lock angle.

    local function Update(self, id)
        local pitch, yaw, roll =Candera.GetRotation(id) 
        Candera.SetRotation(id,  pitch+self.rotationSpeed, yaw, self.steerLock) 
    end
    
    return { 
      Init = Init,
      Update = Update,
      GetName = GetName, 
      rotationSpeed =0.0,
      steerLock =0.0
    }

The GetName functions is nearly the same except a different value for the name (Wheel_front).

The next step is to drag and drop the scripts to the proper nodes.

Now we can concentrate on the root node:


CarRootController Script:

Switch back to the root script called "CarRootController". The first thing we have to do is to get the id of every script component which is attached to any child of the root and store it to a local variable which we will need later:

    local frontWheelScripts={}
    local backWheelsScripts={}
    local childIds = {}
    local function Init(self, id)
        childIds = Candera.GetChildScriptIds(id)
    end

Now it's possible to iterate through the array "childIds" (also in the Init-Function) to get their scripts and to look for a special script via its name. But to be able to call their GetName function and to use the scripts afterwards in the Update-Function we first have to get access to the scripts public table via Candera.IdToScriptComponent[childId]. Now as you have the script you can call the GetName function to search for the desired script name. Then insert each of them to a local array which got defined above the Init-Function (shown in the previous paragraph).

    for i, childId in ipairs(childIds) do
        local script = Candera.IdToScriptComponent[childId]
        
        if script and script.GetName() == "Wheel_front"  then
                      
            table.insert(frontWheelScripts, script)
            --table.insert(value, array) inserts the element in the last position of the array
            
        elseif script and script.GetName() == "Wheel_back" then
            table.insert(backWheelsScripts, script)
            
        end    
        
    end

Now we have access to each wheel script so we can also change their public values. Therefore scroll down to the Update-Function where you can iterate through the scripts and change the rotationSpeed and for the front wheels also the steer lock.

    local function Update(self, id)
        for i, frontWheelScript in ipairs(frontWheelScripts) do
            frontWheelScript.rotationSpeed = self.rotationSpeed
            frontWheelScript.steerLock = self.steerLock
        end
         
        for i, backWheelScript in ipairs(backWheelsScripts) do
            backWheelScript.rotationSpeed = self.rotationSpeed
        end
    
    end 

Be sure that every node has its proper script and set the rotationSpeed and the steerLock of the root node to a proper value (20;30). If you start the execution of the script system the wheels will rotate.

Tip: Also check the Sample Solution for further information and scripts.

Tutorial for Lua-Scripting

Communication between Children

How to find siblings

This chapter explains how to get access to sibling nodes via the id of their attached script. Because there is yet no direct way to get the ids of sibling nodes you have to use a workaround. The recommended way is to use a script on a root node which tells its child nodes the ids or even the scripts of their siblings. The diagram beneath shows the car node in the center. Its script "CarRootController" finds the door scripts of its childnodes and pass them in form of an array to the scripts of its child nodes. Now each door node has access to the other door nodes.

drawing-4-1676958460.png

The following example is for the doors of the car. If one of them gets opened the other doors should get opened too.

drawing-4-1676958488.png

Door Scripts

Therefore each door script has to know the other door scripts. So first attach a new script called "leftDoor" to the left doors and a new script called "rightDoor" to the right doors. The scripts will be nearly the same except the rotation value (a left door has to rotate in the opposite direction of a right door). As in the other example "Id Handling" the first step is to implement a name variable and a GetName() function so the parent node can identify them:

    local scriptName = "door"
    local function GetName()
        return scriptName
    end

Tip: Don't forget to add the new function to the public table! (see next step)

Next we need a public boolean which tells the script that the door should get opened. Afterwards this public value 'openDoor' will get modified by another door script.

    return { 

        Init = Init,
        Update = Update,        
        GetName = GetName,      
        openDoor = false,       
    }

Now switch to the Update-Function to add following lines of code:

local function Update(self, id)
    if self.openDoor == true then
        -- get the current x- and z-rotation of the door and change the y-rotation to a proper value
        local pitch, yaw, roll = Candera.GetRotation(id)                                
        Candera.SetRotation(id,  pitch, yaw, -30)
            
        -- now iterate over the array with the other door scripts stored in the public variable doorScripts 
        --and change their openDoor boolean to true so they can get opened
        for i, otherDoorScript in ipairs(self.doorScripts) do
            
            otherDoorScript.openDoor = true 
                             
        end
                    
      end
end

However yet the public array doorScripts will be nil as a root node has to find all door scripts and save them to the public array of each script. Therefore add a new array called doorScripts to the public table...

    return { 

        Init = Init,
        Update = Update,        
        GetName = GetName,      
        openDoor = false,
        doorScripts ={}
    }

Lua uses 'nil' as a kind non-value, to represent the absence of a useful value.


Root Script

... and switch to the CarRootController script to the Init-Function . To find the door scripts iterate over the array childIds, get the script of each id and store it to a array. Therefore add a local array called "doorScripts" to your CarRootController script.

When all the door scripts are found iterate over the doorScripts array and store the local array of door scripts to the doorScripts array of each script:

    for i, childId in ipairs(childIds) do
        local script = Candera.IdToScriptComponent[childId]
                  
        if script and script.GetName() == "door"  then
                      
            table.insert(doorScripts, script)
            
        end    
    end
       
    for i, doorScript in ipairs(doorScripts) do      
        doorScript.doorScripts = doorScripts              
    end

Don't forget to set the openDoor value in the properties panel to true at least for one door script to test if the other doors will get opened.

drawing-4-1676958413.png


Global Variables

Another way to communicate between two sibling scripts is to use global variables. In Lua variables are automatically global if you declare them without an access modifier. But we would recommend you to set variables with a root script as explained above.

Tutorial for Lua-Scripting

OnEnable() and OnDisable()

How to use OnEnable() and OnDisable()

As in this example the root node script "CarRootController" controls nearly all of the other scripts it would be advisable to disable all the child scripts in case of a disabled root script. Therefore use the OnDisable() function of the "CarRootController" script:

You already stored all child ids to the local array childIds so you just need to iterate over this array to disable all other scripts. Therefore we use the callback function OnDisable() which gets called by Candera automatically when disabling this script.

    local function OnDisable(self, id) 

        for i, scriptId in ipairs(childIds) do 
            Candera.SetEnabled(scriptId, false)     
        end
    end

If the root script gets enabled the child scripts have to get enabled too. Therefore use the OnEnable() function:

    local function OnEnable(self, id)
   
        for i, scriptId in ipairs(childIds) do
            Candera.SetEnabled(scriptId, true)
        end
   
        -- toggle the rotation angle of the wheels each time it got enabled
        self.steerLock= self.steerLock*-1
   
    end

Tip: don't forget to add the callback functions OnDisable() and OnEnable() to the public table so they can get called by Candera.

If you start the execution of the script system and disable the CarRootController script during runtime also the wheels should stop rotating. Furthermore if you enable the CarRootController again, the wheels start rotating and the steering lock switches.

Tutorial for Lua-Scripting

Set Priority of Lua-Scripts

Script Priority

Another useful feature of Lua-Scripting is the priority. It defines in which order the scripts get called (f.e. the Update or the Init). To test this feature in its simplest way create four cubes and equip them with the same script called "SimpleLogInfo".

    local function Init(self, id)
        Candera.LogInfo(self.logString)
    end

    return { 
        Init = Init,    
        logString = "logString"
    }

This script allows to enter an output string in the public table which will get written in the CanderaLogging panel. Now apply the script to each of the cubes and enter a proper log info to the public value "logString" in the Properties panel. The next step is to assign a priority value to each instance of the script:

drawing-4-1676958567.png

Now if you start the execution of the script system you can read the log strings in the order according to their priority. (open the CanderaLogging as explained in "Lua Introduction")

drawing-4-1676958585.png

Tip: for ordering script execution you can also use call-back functions. F.e. LateUpdate(self, id) is called every render frame after all Update callbacks.

Tutorial for Asynchronous Asset-Loading

Tutorial for Asynchronous Asset-Loading

Asynchronous Asset-Loading

Description

This tutorial describes Asynchronous Asset-Loading and provides short examples on how to load a bitmap.

Asynchronous Asset-Loading

Asset Loading, especially reading from the asset repositories, can be a time consuming task, which blocks the CPU from further processing. This can even slow down rendering in certain cases. To improve this, asset loading requests can now be submitted as asynchronous jobs. These jobs can be done either in parallel on a separate thread, or interleaved with the rendering in smaller chunks. Whenever an asset is available, the main thread can retrieve it and add it to the render loop.

How to activate this in the application

For a Courier based application, the AsyncLoadReqMsg message can be used to load Views. For a Candera based application, Async version of the asset loader methods can be used (see AsyncAssetProviderProxy and new ContentLoader methods).

Number of threads for asynchronous asset loading

To load the assets asynchronously in one single thread, the interface "AsyncAssetProviderProxy" can be used. It is available from any AssetProvider implementation via the AssetProvider::AsyncProxy() method. Using a second thread for async loading can be achieved by checking the CANDERA_ASSETLOADER_WORKER_THREAD_ENABLED in Cmake. AsyncRequests will be dispatched on the worker thread as soon as possible. For multithreaded async asset loading, the CMake flag CANDERA_ASSETLOADER_WORKER_THREAD_ENABLED specifies if the asynchronous asset load requests are handled on a dedicated thread or on the caller one.

Upload

Upload can be done asynchronously too, as ContentLoader::BuildSceneContextAsync can also upload to VRAM (depending on a parameter value). In this case, the single threaded variant must be used.

Examples

In the AsyncAssetProviderProxy, each Get* (GetScene) function schedules a Get* (GetSceneById) call on the AssetProvider that created the Proxy. In the case that CANDERA_ASSETLOADER_WROKER_THREAD_ENABLED flag is enabled in CMAKE, the AssetProvider::Get* call will be dispatched on a separate thread and the result will be available as soon as the worker thread completes the dispatch. In the case that the above mentioned CMake option is disabled, one or more calls to DispatchNext() need to be performed on the current thread to assure that the request is dispatched. It is advised to call the DispatchNext() routine periodically during the Update part of the render loop of the application to ensure all asynchronous asset load requests are dispatched in a controlled and limited time. One option to call DispatchNext would be to introduce it in the LateUpdate part of the UpdateSystem provided by Candera.

       AssetProvider* assetProvider = ContentLoader::GetInstance().GetAssetProvider();
       if (assetProvider != 0) {
         UpdateSystem* updateSystem = EntityComponentSystem::EntitySystem::Get<UpdateSystem>();
         if (updateSystem != 0) {
           UpdateSystem::Handle updateHandle = updateSystem->CreateComponent();
           UpdateSystem::Delegate delegate = UpdateSystem::Delegate::Update<AsyncAssetProviderProxy, &AsyncAssetProviderProxy::DispatchNext>();
           updateSystem->SetComponentLateUpdateDelegate(updateHandle, delegate);
           updateSystem->AttachComponent(updateHandle, &assetProvider->AsyncProxy());
         }
       }

Samples to retrieve a Bitmap Asynchronously:

Tutorial for 9-patch image

Tutorial for 9-patch image

9-patch image

For full description of the 9-patch check 9-patch image chapter.

BitmapNode sample

  1. Import an image in Solution Explorer window. 
    drawing-4-1676956678.png


  2. To configure the image as a 9patch image, click on my_image from the Solution Explorer window and change in the Properties window Bitmap -> Bitmap Layout from Stretch to NinePatch. 4 additional properties are available Bitmap -> 9-patch Stretchable Area: Left, Top, Right and Bottom. These properties define the distance in pixel from the border. 
    drawing-4-1676956701.png


  3. Drag the image from the Solution Explorer window in the Scene Tree window into Scene2D. 
    drawing-4-1676956729.png


  4. In order to have sharp borders, click on my_image from the Scene Tree window, then click on BitmapBrushBlend and change in the Properties window Filter from BilinearFilter to NereastFilter. 
    drawing-4-1676956745.png


  5. To resize a 9patch image, click on my_image from the Scene Tree window and change in the Properties window Layout -> Size X and Y values.

    • Default X, Y values
      drawing-4-1676957037.png
  • Changed X, Y values
drawing-4-1676957014.png

  • If (X < Left + Right) or (Y < Top + Bottom) where Left, Top, Right and Bottom are the 9-patch Stretchable Area, then the borders themselves will also start scaling down.
    drawing-4-1676956973.png