Effects for 2D nodes

Use effects to apply post-processing effects to 2D nodes.

In Kanzi Studio you can find the effects in the Library > Effects.

../../_images/effects-library.png

These effects are available in Kanzi:

How Kanzi applies 2D effects

Kanzi applies 2D effects to the visual shape of the content in a node and its descendant nodes.

../../_images/effect-node.png ../../_images/parent-children-effect-preview.png

When you want to apply an effect to a node but not to its descendant nodes, place the descendant nodes in a sibling node of the node to which you apply the effect. Position that sibling node in the node tree below the node to which you apply the effect. Make sure that the sibling nodes are of the same size, layout, transformation, and opacity.

../../_images/effect-node-2.png ../../_images/parent-only-effect-preview.png

Effects and layout

When you use effects, keep in mind that:

  • Kanzi does not take effects into account when calculating layout. When your design does not allow an effect to exceed the layout bounds, make sure that there is enough space in the layout for that effect.

    For example, in a layout that contains a Text Block 2D node where you apply a drop shadow, in that Text Block 2D node ensure sufficient space for the shadow by setting the Horizontal Margin and Vertical Margin properties.

    ../../_images/shadow-margin.png
  • When Kanzi applies an effect to a 2D node, it composites that node and clips its descendant nodes to the node bounds, regardless of the value of the Clip Children property. This happens because the size of the render target texture matches the size of the node.

Impact of effects on application performance

For every instance of an effect Kanzi requests the composition manager to provide a temporary composition target. Kanzi composition manager reuses temporary composition targets if they are compatible in size and parameters.

The final performance of an application that uses effects depends on:

  • The amount of allocated memory, and therefore the bandwidth required to render the UI of the application

  • The number of draw calls

  • The number of render target or texture switches

To find the most efficient way of using effects, test the performance of your application with these approaches:

  • Apply a separate effect to each UI element to minimize the area.

  • Apply an effect to a group of UI elements that cover a larger area.

Using effects in the API

To create an effect:

// An effect that is assigned to a 2D node is an effect prefab.
// Before you can construct an effect prefab, you must create an effect template.
// This example creates an effect template for the ShadowEffect2D effect prefab.
NodeEffectTemplate2DSharedPtr shadowEffectTemplate =
    NodeEffectTemplate2D::create(ShadowEffect2D::getStaticMetaclass()->getName(), "DropShadow");

// Create a node effect prefab from the effect template.
NodeEffectPrefab2DSharedPtr shadowEffectPrefab =
    NodeEffectPrefab2D::create(getDomain(), "DropShadow prefab", shadowEffectTemplate);

To set a 2D node to use an effect:

// To assign an effect prefab to a 2D node, either set the
// Node2D::EffectPrefabProperty...
node2d->setProperty(Node2D::EffectPrefabProperty, shadowEffectPrefab);
// ...or call the Node2D::setEffectPrefab method.
node2d->setEffectPrefab(shadowEffectPrefab);

To disable an effect:

// Enable an effect on a 2D node by assigning an effect prefab.
node2d->setEffectPrefab(shadowEffectPrefab);
// Disable an effect by assigning a nullptr or an empty shared pointer.
node2d->setEffectPrefab(nullptr);

To set the properties of an effect:

// Create an effect template.
NodeEffectTemplate2DSharedPtr shadowEffectTemplate =
    NodeEffectTemplate2D::create(ShadowEffect2D::getStaticMetaclass()->getName(), "DropShadow");

// Set the value of the AngleProperty in the template. This value serves as the default value
// of the property in instances of the effect prefab.
shadowEffectTemplate->addPropertyValue(ShadowEffect2D::AngleProperty, Variant(35.f));

// Create an effect prefab.
NodeEffectPrefab2DSharedPtr shadowEffectPrefab =
    NodeEffectPrefab2D::create(getDomain(), "DropShadow prefab", shadowEffectTemplate);

// Assign the effect to a 2D node.
node2d->setEffectPrefab(shadowEffectPrefab);

// Get the node-specific effect instance created from the assigned prefab.
ShadowEffect2DSharedPtr shadowEffect = dynamic_pointer_cast<ShadowEffect2D>(node2d->getEffect());

// Set the shadow distance to 15 pixels for this node only. This overrides
// the property default value defined by the ShadowEffect2D metaclass.
shadowEffect->setDistance(15.f);

// Set the direction of the shadow to 55 degrees relative to the positive x axis.
// This overrides the default value of 35 degrees set in the shadow effect template.
shadowEffect->setAngle(55.f);

To set multiple nodes to use the same effect:

NodeEffectTemplate2DSharedPtr shadowEffectTemplate =
    NodeEffectTemplate2D::create(ShadowEffect2D::getStaticMetaclass()->getName(), "DropShadow");

// Create the effect prefab.
NodeEffectPrefab2DSharedPtr shadowEffectPrefab =
    NodeEffectPrefab2D::create(getDomain(), "DropShadow prefab", shadowEffectTemplate);

// Set two nodes to use the same effect prefab. The nodes create separate instances
// of NodeEffect2D but share the same NodeEffectPrefab2D instance.
node1->setEffectPrefab(shadowEffectPrefab);
node2->setEffectPrefab(shadowEffectPrefab);

To create an effect type:

// To create an effect resource type, inherit a new class from NodeEffect2D
// and define a new associated metaclass. Each effect resource class must
// have an associated renderer class inherited from NodeEffectRenderer2D.

class EmptyEffectRenderer2D;
using EmptyEffectRenderer2DUniquePtr = unique_ptr<EmptyEffectRenderer2D>;

class EmptyEffect2D;
using EmptyEffect2DSharedPtr = shared_ptr<EmptyEffect2D>;

// EmptyEffect2D defines a new effect that defines no properties
// nor padding.
class EmptyEffect2D : public NodeEffect2D
{
public:
    KZ_METACLASS_BEGIN(EmptyEffect2D, NodeEffect2D, "Kanzi.EmptyEffect2D")
    KZ_METACLASS_END()

    // Create an instance of EmptyEffect2D.
    static EmptyEffect2DSharedPtr create(Domain* domain, string_view name)
    {
        return make_polymorphic_shared_ptr<NodeEffect2D>(new EmptyEffect2D(domain, name));
    }
protected:

    // Constructor.
    explicit EmptyEffect2D(Domain* domain, string_view name) :
        NodeEffect2D(domain, name)
    {
    }

    // NodeEffect2D::createRendererOverride implementation.
    NodeEffectRenderer2DUniquePtr createRendererOverride() override;

    // NodeEffect2D::initializeRendererOverride implementation.
    void initializeRendererOverride() override
    {
    }
};

// EmptyEffectRenderer2D implements the rendering of the effect.
// Because this example effect does not render anything, Kanzi renders
// the original node as is.
class EmptyEffectRenderer2D : public NodeEffectRenderer2D
{
public:
    // Creates an instance of MinimalEffectRenderer2D. Never call this
    // function directly. Instead, use NodeEffect2D::createRenderer().
    static EmptyEffectRenderer2DUniquePtr create(EmptyEffect2DSharedPtr effect)
    {
        return unique_ptr<EmptyEffectRenderer2D>(new EmptyEffectRenderer2D(effect));
    }

private:
    // Constructor.
    explicit EmptyEffectRenderer2D(EmptyEffect2DSharedPtr effect) :
        NodeEffectRenderer2D(effect)
    {
    }

    // Implementation of NodeEffectRenderer2D::beginEffectOverride.
    void beginEffectOverride(Renderer3D& /*renderer*/, CompositionStack& /*compositionStack*/, CompositionManager* /*compositionManager*/,
        const Matrix3x3& /*worldTransform*/, Vector2 /*requiredSize*/, bool /*alphaRequired*/, bool /*depthRequired*/, bool /*stencilRequired*/) override
    {
    }

    // Implementation of NodeEffectRenderer2D::endEffectOverride.
    void endEffectOverride(Renderer3D& /*renderer*/, CompositionStack& /*compositionStack*/, CompositionManager* /*compositionManager*/) override
    {
    }

    // Implementation of NodeEffectRenderer2D::blitEffectOverride.
    void blitEffectOverride(Renderer3D& /*renderer*/, CompositionStack& /*compositionStack*/, CompositionManager* /*compositionManager*/,
        QuadDescription& /*effectQuad*/, const Matrix3x3& /*transform*/, const Matrix4x4* /*perspectiveMatrix*/,
        const Matrix4x4* /*projectionMatrix*/, bool /*needsClear*/) override
    {
    }

    // Implementation of NodeEffectRenderer2D::restoreResourcesOverride.
    virtual void restoreResourcesOverride() override
    {
    }
};

NodeEffectRenderer2DUniquePtr EmptyEffect2D::createRendererOverride()
{
    return EmptyEffectRenderer2D::create(static_pointer_cast<EmptyEffect2D>(shared_from_this()));
}

For details, see the NodeEffect2D class in the Kanzi Engine API reference.