Candera
Candera Graphic Engine enables embedded applications to render and control externally designed content, especially 3D content.
- Candera Architecture Overview
- Interfaces & Namespaces
- Candera MISRA Compliance
- Tutorial for Application Life Cycle
- Startup and Asset Management
- Render Loop
- Finalization
- Asset Library Verification (Optional)
- Dynamic Properties
- Touch Support
- Loading of Shaped / Compressed Assets
- Tutorial for 3D Scene Graph and Nodes
- 3D Scenes and Nodes
- 3D Node Transformations
- 3D Node Appearance
- 3D Scene Graph Dynamics
- 3D Render Order
- Searching for Nodes with SearchTreeTraverser
- Vertex Geometry Builder
- Vertex Geometry Modifier
- Candera 3D Listeners
- Tutorial for 2D Scene Graph and Nodes
- 2D Scenes and Nodes
- 2D Node Transformations
- 2D Effects
- 2D Scene Graph Dynamics
- Camera / Viewport
- 2D Layout
- Candera 2D Listener
- Tutorial for Shader Usage
- Programmable Graphics Pipeline
- The OpenGL ES Shading Language
- Shader Usage in Candera
- Candera Reference Shaders
- Tutorial for Text Rendering
- Introduction
- Fonts and Styles
- Using Candera TextEngine to draw Text
- Using asynchronous text rendering
- Text rendering: Caching/rendering systems
- Tutorial for Globalization Support
- Introduction
- SceneComposer Translator Plugin
- Managing Language Packs using AssetTool
- Using Globalization in Applications
- Custom Localizer Support
- Tutorial for Animation Manipulation
- Configuring and Loading an Animation
- Controlling Animations
- Using Animation Callback Functions
- Manipulating Animation Keyframe Sequences
- Tutorial for Graphic Device Interface
- Cameras and Render Targets
- EGL Context Handling
- Multiple Render Targets
- Texture Render Targets
- Camera Groups
- Proxy Texture Image
- Multisample Anti-Aliasing Offscreen Render Targets
- External Texture Image
- Tutorial for Special 3D Render Techniques
- Level of Detail (LOD)
- Morphing
- Planar Shadows
- Baked Shadows
- Single Pass Effects in Candera > Examples for 3D Effect Shaders
- Multi Pass Effects in Candera > Examples for Local 3D Effect Shaders
- Multi Pass Effects in Candera > Examples for Global 3D Effect Shaders
- Transform Feedback
- Tutorial for Special 2D Render Techniques
- Tutorial for Diagnostics
- Assertions
- Logging
- Performance Analysis > Introduction
- Performance Analysis > Howto Instrument for Performance Measurement
- Tutorial for Lua-Scripting
- Overview
- Create an own Script
- Lua Introduction
- Id Handling
- Communication between Children
- OnEnable() and OnDisable()
- Set Priority of Lua-Scripts
- Tutorial for Asynchronous Asset-Loading
- Tutorial for 9-patch image
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:
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
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
- In SceneComposer the default render mode can be configured in configuration menu - see Render Mode in SceneComposer User Manual
- In Candera, use the interface Candera::Renderer::SetDefaultRenderMode.
-
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:
- Candera::FileAssetRepository for assets in file system
- Candera::MemoryAssetRepository for assets in memory, e.g. flash memory.
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
It is recommended to check if the asset contains at least one configured display, otherwise no display content will be visible in the application.
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:
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
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:
- Render all 3D cameras: Candera::Renderer::RenderAllCameras
- 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'.
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.
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.
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.
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 theWidgetMetaInfo.hfile.
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;
}
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); } … };
- Layouter.cpp
class LayouterDynamicProperties { … static void SetSize(CanderaObject& node, const Vector2& size) { static_cast<void>(node.SetValue(CdaDynamicPropertyInstance(Size), size)); } static const Vector2& GetSize(const CanderaObject& node) { return node.GetValue(CdaDynamicPropertyInstance(Size)); } static bool IsSizeSet(const CanderaObject &node) { return node.IsValueSet(CdaDynamicPropertyInstance(Size)); } … static void SetLayoutDirection(CanderaObject& node, LayoutAlignment::LayoutDirection::Enum direction) { static_cast<void>(node.SetValue(CdaDynamicPropertyInstance(LayoutDirection), direction)); } static LayoutAlignment::LayoutDirection::Enum GetLayoutDirection(const CanderaObject& node) { return node.GetValue(CdaDynamicPropertyInstance(LayoutDirection)); } static bool IsLayoutDirectionSet(const CanderaObject& node) { return node.IsValueSet(CdaDynamicPropertyInstance(LayoutDirection)); } static void ClearLayoutDirection(CanderaObject& node) { static_cast<void>(node.ClearValue(CdaDynamicPropertyInstance(LayoutDirection))); } … CdaDynamicPropertiesDefinition(Candera::Layouter, CanderaObject); … CdaDynamicProperty(Size, Vector2); CdaDynamicPropertyValueChangedCb(&Layouter::OnSizeChanged); CdaDynamicPropertyDefaultValue(Layouter::SizeDefault()); … CdaDynamicPropertyEnd(); … CdaDynamicProperty(LayoutDirection, LayoutAlignment::LayoutDirection::Enum); CdaDynamicPropertyValueChangedCb(&Layouter::OnLayoutDirectionChanged); … CdaDynamicPropertyEnd(); … CdaDynamicPropertiesEnd(); }; … void Layouter::SetSize(CanderaObject& node, const Vector2& size) { LayouterDynamicProperties::SetSize(node, size); } const Vector2& Layouter::GetSize(const CanderaObject& node) { return LayouterDynamicProperties::GetSize(node); } bool Layouter::IsSizeSet(const CanderaObject &node) { return LayouterDynamicProperties::IsSizeSet(node); } … void Layouter::SetLayoutDirection(CanderaObject& node, LayoutAlignment::LayoutDirection::Enum direction) { LayouterDynamicProperties::SetLayoutDirection(node, direction); } LayoutAlignment::LayoutDirection::Enum Layouter::GetLayoutDirection(const CanderaObject& node) { return LayouterDynamicProperties::GetLayoutDirection(node); } bool Layouter::IsLayoutDirectionSet(const CanderaObject& node) { return LayouterDynamicProperties::IsLayoutDirectionSet(node); } void Layouter::ClearLayoutDirection(CanderaObject& node) { LayouterDynamicProperties::ClearLayoutDirection(node); } … const Candera::DynamicProperties::DynamicPropertyHost* Layouter::ParentProvider(const Candera::DynamicProperties::DynamicPropertyHost* host) { CANDERA_SUPPRESS_LINT_FOR_NEXT_EXPRESSION(1774, "Layouter interface only allows CanderaObject for dynamic properties") const CanderaObject* object = static_cast<const CanderaObject*>(host); #ifdef CANDERA_2D_ENABLED const Node2D* node2D = Dynamic_Cast<const Node2D*>(object); if (0 != node2D) { return node2D->GetParent(); } #endif #ifdef CANDERA_3D_ENABLED const Node* node = Dynamic_Cast<const Node*>(object); if (0 != node) { return node->GetParent(); } #endif return 0; } void Layouter::Layout(const AbstractNodePointer& node) { … Vector2 nodeSize(Layouter::GetSize(*node.ToCanderaObject())); … }
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; }
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
3D Scenes and Nodes
Scenes and Nodes
Candera::Node is an abstract base class for all scene graph nodes.
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.
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.
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);
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.
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.
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
|
| Candera::OrderCriterion |
Nodes within a bin are sorted according to an OrderCriterion.
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:
- Opaque bin: Opaque objects are rendered using Candera::DistanceToCameraOrderCriterion
- Transparent bin: Transparent objects are rendered using Candera::ReverseDistanceToCameraOrderCriterion
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:
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.
- Using Candera::RankOrderCriterion, the scene will be rendered so that lower render order rank is before higher render order rank.
- When using Candera::ReverseRankOrderCriterion, higher render order rank is before lower render order rank.
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.
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.
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)
// 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:
-
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:
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:
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:
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:
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:
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();
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());
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
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.
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.
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.
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.
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:
- 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.
- 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.
- 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.
- Combined Effect for Brush and Blend Effect: Candera::BitmapBrushBlend.
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:
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:
Text with Bitmap Effects
In order to render text, a Text Node with one the following effect must be set in a 2D Scene.
- Candera::BitmapBrushBlend
- Candera::BitmapBrushColor
- Candera::BitmapBrushColorBlend
- Candera ::GlLinearGradientBitmapBrushBlend
Text node is a Render node that render text using the attached Bitmap Brush effect.
- Text and Style properties can be set using the Text node properties, as they are inherited from the Render node. To apply Style properties, the font of the text must be set.
- For Candera::BitmapBrushColorBlend, the properties Text Color, Color Blend Factors and Operations, Alpha Blend Factors and Operations, and the Text Clipping Area can be set to achieve different rendering effects.
- An example of Candera::BitmapBrushColorBlend and related properties is shown below.
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:
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:
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).
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:
- Candera::TreeCloner2D is a specialization for the 2D scene tree of Candera::TreeClonerBase.
- Candera::DeepNode2DCloneStrategy provides a generic node strategy that clones the delegates the cloning of derived Nodes to specialized strategies.
- Candera::DeepCompositeGroup2DCloneStrategy resolves node binding by use of Candera::TreeMatch2D.
- Candera::DeepRenderNodeCloneStrategy resolves the sharing of effects by use of a map.
- Candera::DeepTreeCloner2D is a wrapper of Candera::DeepNode2DCloneStrategy that provides an interface similar to Candera::TreeCloner2D.
Refer to
as well as the example for cloning 3D 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.
The view port dimensions (width and height) relate to the area on the render target the camera is painting on.
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).
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.
-
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.
See also:
- Grid Layouter in SceneComposer User Manual
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.
See also:
- Baseline Layouter in SceneComposer User Manual
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.
See also:
- Overlay Layouter in SceneComposer User Manual
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.
See also:
- Candera::DockPanelLayouter
- DockPanel Layouter in SceneComposer User Manual
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.
Layout Configuration
Set the layout for each group as shown in the image below:
Configure the column width and row height:
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:
The Content group might be filled with one or more nodes of any type, as desired:
Result
After putting everything together the dialog will look as above:
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:
Layout Configuration
Set the layout for each group as shown in the image below:
-
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:
-
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:
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
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]
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]
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]
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
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
- Optimizing Shader Programs in Application Development Best Practices
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.
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.
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.
Shader Related Class Usage
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:
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:
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:
-
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.
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. |
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
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: "äÄöÖüÜ߀" 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
Above the characters as they are written and below as they are displayed.
Text Rendering - Functional Steps
Text Rendering involves the following functional steps:
- Glyph Rendering
- Shaping
- 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
- 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)
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:
- How to Import Resources in SceneComposer User Manual
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
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 Palette in SceneComposer User Manual
- Candera::TextRendering::Style in Candera API
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.
Select Scene2D_StyleExample of the solution, which gives just a brief overview of text features and how they look like. It cons;
ts of
- a Candera::TextNode2D.
- Fonts for the text node are defined in a style Mystyle.
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.
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.
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.
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:
- Candera::BitmapBrushBlend: Outputs a bitmap image, and blend it with the store buffer.
- Candera::BitmapBrushColorBlend: Output a bitmap image, modulate the color, and blend it with the store buffer.
- Candera::BitmapBrushColorMaskBlend: Output a bitmap image, modulate the color, apply an alpha mask, and blend it with the store buffer.
- Candera::BitmapBrushHslBlend: Output a bitmap image, apply a HSL transformation, and blend it with the store buffer.
- Candera::BitmapBrushMaskBlend: Output a bitmap image, apply an alpha mask, and blend it with the store buffer.
- Candera::BlurBitmapBrushBlend: Output a blurred and alpha blended bitmap image.
- Candera::MirrorBitmapBrushBlend: Output includes the bitmap image and it's reflection.
- Candera::ShadowBitmapBrushBlend: Output a shadowed or glowing bitmap image, both shadow/glow and image alpha blended.
- Candera::ShearBitmapBrushBlend: Output a sheared image from the original one.
-
Not all BitmapBrush effects have the Color property available. Default color for a TextNode2D is white.
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 a Candera::TextNode2D and the associated BitmapBrush effect:
TextNode2D* m_textNode;
BitmapBrushColorBlend::SharedPointer m_textNodeEffect;
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());
Use Candera::TextNode2D::SetText() to specify the text to be rendered:
m_textNode->SetText("TextNode2D");
Use Candera::TextNode2D::SetText() to specify the style object:
m_textNode->SetText("TextNode2D");
Create and set a Candera::TextNodeRenderer to Candera::TextNode2D using Candera::TextNode2D::SetTextNodeRenderer() method:
m_textNode->SetTextNodeRenderer(&GlyphCacheTextNodeRenderer::GetInstance());
Finally, add the textNode directly to a Candera::Scene2D or a Candera::Group2D:
m_textNodeGroup->AddChild(m_textNode);
Candera::TextNode2D Properties
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 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);
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 layouting is enabled by default for a TextNode2D. This property can be enabled/disabled using Candera::TextNode2DLayouter::SetMultiLineEnabled() method.
Wordwrap is not enabled by default for a TextNode2D. This property can be enabled/disabled using Candera::TextNode2DLayouter::SetWordWrapEnabled() method.
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).
The following methods are provided for retrieving the size of the rendered text:
- Getting the layouting text box (measured with constant height, cursor to cursor from left to right):
- Getting the bounding text box (glyph limits vertically and horizontally):
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.
From the Properties panel of the TextNode, configure the necessary properties for rendering: Text, Style.
Candera::TextNodeRenderer type of Candera::TextNode2D can be changed using the Renderer property. Default value in SceneComposer is Bitmap.
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.
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.
TextEngine Render Interface
Overview: Render Interface
The main interface to render text is the method
-
Compare this interface also with the functional steps introduced in chapter Fonts and Styles.
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
- the Candera::TextRendering::LayoutingOptions::SetHorizontalAlignment() and Candera::TextRendering::LayoutingOptions::SetVerticalAlignment() methods to specify the text alignment,
- the Candera::TextRendering::LayoutingOptions::SetMultilineTextEnabled() method to toggle multi line and single line layout.
- the Candera::TextRendering::LayoutingOptions::SetLineSpacing() method to modify the space between lines of multi line text.
- the Candera::TextRendering::LayoutingOptions::SetWordWrapEnabled() method to enable automatic word wrap for multi line text.
- 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.
- See also:
- the constructor Candera::TextRendering::TextProperties::TextProperties(const TChar *text, TextLength length).
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 :
- Shortening of the right-to-left text (Arabic)
- Shortening of the left-to-right text (Latin)
- Shortening of right-to-left text embedded in left-to-right text (left-to-right and right-to-left)
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:
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):
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
- GlyphHinter::Light : applies less corrections than normal hinting for better performance
- GlyphHinter::Monochrome : appropriate for monochrome rendering
- GlyphHinter::Lcd : appropriate for horizontally decimated LCD displays
- GlyphHinter::VerticalLcd : appropriate for vertically decimated LCD displays
-
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:
An another advantage of using this render mode is the smaller memory footprint for glyphs because of 1bit format.
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:
- Measurement and placement
- Prerendering – Prepares the render device for the text (if necessary)
- 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.
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.
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.
-
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.
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.
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
- Lifecycle
- Synchronization - flow
- Synchronization - Restrictions
- Text-Synchronization – Custom implementation
- A new synchronization group
- Additional trigger objects
- Implementation of an own 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.
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.
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 implemented by Candera::TextNode2D,
- FeatStd::ValidationHelperBase to be implemented
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:
- Candera::TextNode2D starts measure and place: SetInvalid()
- Candera::TextNode2D finishes measure and place: SetValid()
- Candera::TextNode2D checks if it can handle the result: IsValid() (only asynchronous nodes)
- Candera::TextNode2D has handled the result (ready for display): ConfirmValidHandled()
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:
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.
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.
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
The recommended usages are bitmap cache as CPU solution and glyph atlas as GPU solution. The usage depends on the given project. All other cache types are written for specific use cases (e.g. specific devices).
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:
either via toolbar:
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
- by means of SceneComposer Translator Plugin after asset export (Note: Enable "Plugin Notification" to make this work!)
- by means of Managing Language Packs using AssetTool to remove the master language pack from and to append multi language packs to an asset.
More Details
More details on the globalization workflow are provided here:
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.
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.
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.
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....
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:
|
| 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....
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.
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.
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.
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.
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.
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.
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.
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.
- Open the asset containing language packs (MultiLanguagePack_Asset_Host.bin) and load the scene in the Player.
- Select the desired language in the drop down list next to the globe icon in the top left corner
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 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
|
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
Configuring and Loading an Animation
Candera Animation Concept
The following key concepts are required to run animations in a Candera application:
- Candera::Animation::AnimationPlayer provides the control interfaces (use Candera::Animation::AnimationGroupPlayer for animation groups)
- Candera::Animation::AnimationTimeDispatcher dispatches the current world time to animations; animations can also query the current world time from the dispatcher
- Candera::Animation::AnimationKeyframeSequence configures proper animation values for each time point of an animation.
- Candera::Animation::InterpolationStrategy specifies for a key frame sequence, how values between several key frames shall be calculated.
- Candera::Animation::AnimationPropertySetter applies animation values to animated properties
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.
Controlling Animations
Controlling Animation Flow
This section describes the control flow interfaces of Candera::Animation::AnimationPlayer.
- Candera::Animation::AnimationPlayer::Start starts the animation
- Candera::Animation::AnimationPlayer::Stop stops the animation
- Candera::Animation::AnimationPlayer::Finish finishes the animation within the specified time
- Candera::Animation::AnimationPlayer::Pause pauses the animation
- Candera::Animation::AnimationPlayer::Resume resumes the animation
- Candera::Animation::AnimationPlayer::SetRepeatMode sets the repeat mode, for possible modes see Candera::Animation::AnimationPlayer::RepeatMode
- Candera::Animation::AnimationPlayer::SetDirection sets the direction, for possible directions see Candera::Animation::AnimationPlayer::PlayDirection
Animation Speed, Repeats, Time Modulation
This section specifies more interfaces to set animation player properties relevant for timing and execution flow.
- Candera::Animation::AnimationPlayer::SetSpeedFactor increases / decreases animation speed
- Candera::Animation::AnimationPlayer::SetRepeatCount modifies repeat count
- Candera::Animation::AnimationPlayer::SetSequenceDurationMs modifies animation duration in milliseconds
- Candera::Animation::AnimationPlayer::SetSequenceStartTimeMs adds a start delay in milliseconds
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
}
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
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
- modifying the "rotation" part of Transformable (Candera::Transformable::SetRotation),
- setting the two orientation vectors:
- the local "LookAt" vector which can be set by the functions Candera::Camera::SetLookAtVector, Candera::Camera::LookAtWorldPoint, Candera::Camera::RotateAroundWorldAxis
- the local "Up" vector, set by the function Candera::Camera::SetUpVector
- 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 Gizmos in SceneComposer User Manual
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.
- 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.
- Candera::GenericProjection is an option to specify an arbitrary projection matrix.
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:
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.
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:
- Link Camera to Render Target in SceneComposer User Manual
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.
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:
-
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()).
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:
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.
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") .
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.
Layer 2: Static 2D Background
This is the layer which will give the animated bubbles some colors.
Layer 3: Dynamic 3D Top Layer
Finally, the top layer shows a 3D scene with a sphere.
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
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;
//...
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.
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.
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
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
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.
-
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
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.
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:
- Camera Group in SceneComposer User Manual
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".
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.
- Existing Candera::TextureImage derivations like Candera::BitmapTextureImage and Candera::CubeMapTextureImage offer a function ToImageSource3D and can therefore also be used with Candera::ProxyTextureImage.
- 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:
- 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();
}
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.
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
Level of Detail (LOD)
Description
This chapter briefly describes Level Of Detail techniques supported by Candera.
See also:
- Level of Detail in SceneComposer User Manual
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)
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.
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:
- Level 0 .. 2: Meshes.
- Level 3: Billboard.
- Level 4: null-Node
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:
- LOD level index,
- Node,
- Lower bound of its visibility range,
- Upper bound of its visibility range.
LOD Render Strategy
The LOD render strategy defines the transition between adjacent LOD levels.
- Candera::DiscreteLodRenderStrategy: Stepwise transition.
- Candera::BlendLodRenderStrategy: alpha-blended transition.
LOD Criterion
The LOD criterion delivers a source value to map onto the LOD nodes visibility boundaries.
- Candera::DistanceToCameraLodCriterion: distance from LOD node to camera.
- Candera::GenericValueLodCriterion: Application defined value.
Candera - LOD Render Strategy
Discrete LOD Render Strategy
- Stepwise transition between LOD levels
- Non-adjacent boundaries between contiguous LOD levels define a Hysteresis.
Blend Transition LOD Render Strategy
- Alpha blended transitions between LOD levels
- Non-adjacent boundaries between contiguous LOD levels define transition phase.
- During blend transition one alternating LOD node is always opaque to avoid 'see-through' effects.
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.
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:
- Level of Detail in SceneComposer User Manual
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
Link pieces together
// 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
Link pieces together
// 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);
Morphing
Description
This chapter briefly describes the morphing technique supported by Candera.
See also:
- Morphing Mesh in SceneComposer User Manual
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).
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
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
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
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.
Workflow
Inventory
The figure below shows the basic setup of a scene needed to generate the planar shadow effect.
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.
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.
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]
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
- join the models which casts shadows with the models on which the shadow is projected on
- unwrap the 3D models
- 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
- 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.
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
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.
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]
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:
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
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:
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.
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.
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
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
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
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:
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
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
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.
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:
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]
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:
Gooch Effect in SceneComposer
To experiment with the Gooch effect in SceneComposer use solution ShaderExamples from folder cgi_studio_player/content/Tutorials/04_ShaderUsage.
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
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]
-
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:
Second image shows same scene but with the effect:
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
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:
-
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:
Second image shows same scene but with the effect:
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.
Usage in Candera:
- 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.
- 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.
- Bind the source vertex buffer using Shader::BindAttributes.
- Call Renderer::BindTransformFeedbackBuffer, and Renderer::BeginTransformFeedbackBuffer using the destination vertex buffer that the processed primitives will be recorded into.
- Render the source vertex buffer as usual.
- Call Renderer::EndTransformFeedbackBuffer
- Call Renderer::MapTransformFeedbackBuffer to get a pointer to the processed, and recorded data.
- Before using that pointer for your own purposes, call Renderer::UnmapTransformFeedbackBuffer.
- The lifetime of that pointer’s data is tied to the lifetime of your destination vertex buffer.
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:
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));
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:
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;
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.
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;
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.
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
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).
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.
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.
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:
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;
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).
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
Assertions
Refer to FeatStd Assertions how to use and configure assertions.
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:
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.
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.
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.
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:
|
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:
|
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.
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).
"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.
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:
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).
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.
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.
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.
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();
}
}
}
...
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.
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:
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...
... 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.
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)
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.
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").
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.
Candera Logging
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)
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.
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.
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.
The following example is for the doors of the car. If one of them gets opened the other doors should get opened too.
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.
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.
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.
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:
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")
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
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:
-
If CANDERA_ASSETLOADER_WORKER_THREAD_ENABLED is enabled (processing is done on a separate thread)
BitmapAsyncRequestSharedPointer request = assetProvider->AsyncProxy()->GetBitmap(); Candera::AssetProvider* provider; Candera::AsyncAssetProviderProxy& proxy = provider->AsyncProxy(); Candera::AsyncAssetProviderProxy::BitmapAsyncRequestSharedPointer asyncRequest = proxy.GetBitmap(0x123); //do other stuff Candera::Bitmap::SharedPointer bitmap; if (asyncRequest->IsCompleted()) { //bitmap was asynchronously loaded bitmap = asyncRequest->GetResult(); }
- If CANDERA_ASSETLOADER_WORKER_THREAD_ENABLED is disabled (processing is done later on the same thread, by calling DispatchNext)
BitmapAsyncRequestSharedPointer request = assetProvider->AsyncProxy()->GetBitmap(); Candera::AssetProvider* provider; Candera::AsyncAssetProviderProxy& proxy = provider->AsyncProxy(); Candera::AsyncAssetProviderProxy::BitmapAsyncRequestSharedPointer asyncRequest = proxy.GetBitmap(0x123); //do other stuff proxy.DispatchNext(); //do other stuff Candera::Bitmap::SharedPointer bitmap; if (asyncRequest->IsCompleted()) { //bitmap was asynchronously loaded bitmap = asyncRequest->GetResult(); }
Tutorial for 9-patch image
9-patch image
For full description of the 9-patch check 9-patch image chapter.
BitmapNode sample
- Import an image in Solution Explorer window.
- 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.
- Drag the image from the Solution Explorer window in the Scene Tree window into Scene2D.
- 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.
- 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
- Default X, Y values
- Changed X, Y values
- 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.