Skip to content

Transparency Order

This example compares three transparency techniques on the same overlapping cubes.

Preview

Run And Adapt

Commands below assume a Datoviz source checkout and start at the repository root. Use your configured build environment; Python routes additionally require local bindings.

Route Availability Command or action
C Canonical native source just example-c features/technique_transparency (build and run), or rerun ./build/examples/c/features/technique_transparency
Python Available; direct-engine adaptation python3 -m examples.python.gallery.features.technique_transparency
Browser Deferred Use the native route for this example.

This example is approved as a starting point for user code and coding agents. Keep the object lifetimes and data shapes intact while adapting the data and styling.

What To Look For

Each panel draws two translucent cube meshes with the same geometry, transforms, material settings, and face alpha values, but changes the alpha mode between source-over blending, weighted order-independent transparency, and depth peeling. Rotate the linked panels and compare how back faces and overlapping colors are resolved. This helps choose a transparency mode for volumes, nested surfaces, or uncertain 3D regions.

Source

#!/usr/bin/env python3
"""Source-over transparency compared with weighted OIT and depth peeling."""

from __future__ import annotations

import ctypes

import datoviz as dvz

from examples.python.gallery import common as ex


LABELS = (b"source-over", b"weighted OIT", b"depth peel")
ALPHA_MODES = (dvz.DVZ_ALPHA_BLENDED, dvz.DVZ_ALPHA_WBOIT, dvz.DVZ_ALPHA_DEPTH_PEEL)
INITIAL_ANGLES = (ctypes.c_float * 3)(0.50, -0.18, 0.22)


def _translation(x: float, y: float, z: float):
    transform = ((ctypes.c_float * 4) * 4)()
    transform[0][0] = 1.0
    transform[1][1] = 1.0
    transform[2][2] = 1.0
    transform[3][3] = 1.0
    transform[3][0] = float(x)
    transform[3][1] = float(y)
    transform[3][2] = float(z)
    return transform


def _rgba(color, alpha: int):
    return dvz.DvzColor(color.r, color.g, color.b, int(alpha))


def _add_label(panel, label: bytes) -> None:
    desc = dvz.dvz_label_desc()
    desc.text = label

    style = dvz.dvz_text_style()
    style.size_px = 18.0
    style.renderer = dvz.DVZ_TEXT_RENDERER_MSDF_ATLAS
    style.color[:] = (ex.TEXT.r, ex.TEXT.g, ex.TEXT.b, ex.TEXT.a)

    placement = dvz.dvz_text_placement()
    placement.mode = dvz.DVZ_TEXT_PLACEMENT_SCREEN
    placement.anchor = dvz.DVZ_SCENE_ANCHOR_PANEL_TOP_LEFT
    placement.position[:] = (20.0, 20.0, 0.0)
    placement.text_anchor[:] = (0.0, 0.0)
    placement.has_text_anchor = True

    annotation = dvz.dvz_annotation_label(panel, ctypes.byref(desc))
    if not annotation:
        raise RuntimeError("dvz_annotation_label() failed")
    if dvz.dvz_annotation_set_style(annotation, ctypes.byref(style)) != 0:
        raise RuntimeError("dvz_annotation_set_style() failed")
    if dvz.dvz_annotation_set_placement(annotation, ctypes.byref(placement)) != 0:
        raise RuntimeError("dvz_annotation_set_placement() failed")


def _cube_face_colors(alpha: int, primary):
    return (dvz.DvzColor * 6)(
        _rgba(primary, alpha),
        _rgba(ex.CYAN, alpha),
        _rgba(ex.GREEN, alpha),
        _rgba(ex.YELLOW, alpha),
        _rgba(ex.TEXT, alpha),
        _rgba(ex.BLUE, alpha),
    )


def _add_transparent_cube(scene, panel, *, size: float, position, alpha: int, mode, primary) -> None:
    face_colors = _cube_face_colors(alpha, primary)

    desc = dvz.dvz_geometry_cube_desc()
    desc.size = float(size)
    desc.face_colors = face_colors
    desc.face_color_count = len(face_colors)
    geometry = dvz.dvz_geometry_cube(ctypes.byref(desc))
    if not geometry:
        raise RuntimeError("dvz_geometry_cube() failed")

    mesh = dvz.dvz_mesh(scene, 0)
    if not mesh:
        dvz.dvz_geometry_destroy(geometry)
        raise RuntimeError("dvz_mesh() failed")
    try:
        if dvz.dvz_mesh_set_geometry(mesh, geometry) != 0:
            raise RuntimeError("dvz_mesh_set_geometry() failed")
    finally:
        dvz.dvz_geometry_destroy(geometry)

    material = dvz.dvz_standard_material_desc()
    material.standard.roughness = 0.42
    material.standard.specular = 0.30
    material.standard.rim_strength = 0.20
    material.alpha_mode = mode
    if dvz.dvz_visual_set_material(mesh, ctypes.byref(material)) != 0:
        raise RuntimeError("dvz_visual_set_material() failed")
    if dvz.dvz_visual_set_alpha_mode(mesh, mode) != 0:
        raise RuntimeError("dvz_visual_set_alpha_mode() failed")

    if dvz.dvz_visual_set_transform(mesh, _translation(*position)) != 0:
        raise RuntimeError("dvz_visual_set_transform() failed")
    ex.add_visual(panel, mesh)


def _add_transparent_cubes(scene, panel, mode) -> None:
    _add_transparent_cube(
        scene,
        panel,
        size=1.06,
        position=(-0.20, -0.02, +0.00),
        alpha=112,
        mode=mode,
        primary=ex.CYAN,
    )
    _add_transparent_cube(
        scene,
        panel,
        size=0.78,
        position=(+0.26, +0.10, +0.18),
        alpha=146,
        mode=mode,
        primary=ex.YELLOW,
    )


def _build_scene():
    scene = dvz.dvz_scene()
    if not scene:
        raise RuntimeError("dvz_scene() failed")
    figure = dvz.dvz_figure(scene, ex.WIDTH, ex.HEIGHT, 0)
    if not figure:
        raise RuntimeError("dvz_figure() failed")

    grid = dvz.dvz_figure_grid(figure, 1, 3)
    if not grid:
        raise RuntimeError("dvz_figure_grid() failed")
    margins = dvz.DvzPanelReserve(42.0, 42.0, 38.0, 38.0)
    if dvz.dvz_grid_set_margins(grid, ctypes.byref(margins)) != 0:
        raise RuntimeError("dvz_grid_set_margins() failed")
    if dvz.dvz_grid_set_gutter(grid, 24.0, 0.0) != 0:
        raise RuntimeError("dvz_grid_set_gutter() failed")

    panels = []
    for col, (label, mode) in enumerate(zip(LABELS, ALPHA_MODES, strict=True)):
        panel = dvz.dvz_grid_panel(grid, 0, col)
        if not panel:
            raise RuntimeError("dvz_grid_panel() failed")
        dvz.dvz_panel_set_background_color(panel, ex.BG)
        ex.manual_camera(panel)
        _add_label(panel, label)
        _add_transparent_cubes(scene, panel, mode)
        panels.append(panel)
    return scene, figure, panels


def _configure_view(view, scene, panels) -> None:
    controllers = []
    for panel in panels:
        controller = dvz.dvz_arcball(scene, None)
        if not controller:
            raise RuntimeError("dvz_arcball() failed")
        if dvz.dvz_view_bind_controller(view, panel, controller, dvz.DVZ_DIM_MASK_XYZ) != 0:
            raise RuntimeError("dvz_view_bind_controller() failed")
        arcball = dvz.dvz_controller_arcball(controller)
        if not arcball:
            raise RuntimeError("dvz_controller_arcball() failed")
        if dvz.dvz_arcball_set(arcball, INITIAL_ANGLES) != 0:
            raise RuntimeError("dvz_arcball_set() failed")
        controllers.append(controller)

    components = (
        int(dvz.DVZ_CONTROLLER_LINK_ROTATION)
        | int(dvz.DVZ_CONTROLLER_LINK_PAN)
        | int(dvz.DVZ_CONTROLLER_LINK_ZOOM)
    )
    for controller in controllers[1:]:
        link = dvz.dvz_controller_link(
            scene, controllers[0], controller, components, dvz.DVZ_CONTROLLER_LINK_TWO_WAY
        )
        if not link:
            raise RuntimeError("dvz_controller_link() failed")


def main() -> None:
    scene, figure, panels = _build_scene()

    def configure(view) -> None:
        _configure_view(view, scene, panels)

    ex.run_with_view(scene, figure, "Transparency Order", configure)


if __name__ == "__main__":
    main()
/*
 * Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
 * Licensed under the MIT license. See LICENSE file in the project root for details.
 * SPDX-License-Identifier: MIT
 */

/* This example compares three transparency techniques on the same overlapping cubes.
 *
 * What to look for: each panel draws two translucent cube meshes with the same geometry,
 * transforms, material settings, and face alpha values, but changes the alpha mode between
 * source-over blending, weighted order-independent transparency, and depth peeling. Rotate the
 * linked panels and compare how back faces and overlapping colors are resolved. This helps choose
 * a transparency mode for volumes, nested surfaces, or uncertain 3D regions.
 *
 * Scenario: features_technique_transparency
 * Style: features, graphite_cyan, 1280x720 window target
 *
 * Build:  just example-c features/technique_transparency
 * Run:    ./build/examples/c/features/technique_transparency --live
 * Smoke:  ./build/examples/c/features/technique_transparency --png
 */



/*************************************************************************************************/
/*  Includes                                                                                     */
/*************************************************************************************************/

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "_alloc.h"
#include "datoviz/geom.h"
#include "datoviz/scene.h"
#include "example_common.h"
#include "example_controller_preview.h"
#include "example_style.h"
#include "runner/scenario_runner.h"



/*************************************************************************************************/
/*  Constants                                                                                    */
/*************************************************************************************************/

#define WIDTH  EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT



typedef struct TransparencyState
{
    DvzArcball* arcball;
} TransparencyState;



/*************************************************************************************************/
/*  Helpers                                                                                      */
/*************************************************************************************************/

/**
 * Return one visual-local translation transform.
 *
 * @param x translation x
 * @param y translation y
 * @param z translation z
 * @param out output matrix
 */
static void _translation(float x, float y, float z, mat4 out)
{
    if (out == NULL)
        return;
    memset(out, 0, sizeof(mat4));
    out[0][0] = 1.0f;
    out[1][1] = 1.0f;
    out[2][2] = 1.0f;
    out[3][3] = 1.0f;
    out[3][0] = x;
    out[3][1] = y;
    out[3][2] = z;
}



/**
 * Add one translucent cube visual.
 *
 * @param scene scene owning the visual
 * @param panel panel receiving the visual
 * @param size cube edge length
 * @param position visual-local translation
 * @param alpha vertex alpha
 * @param mode alpha technique
 * @param primary primary face color role
 * @return true on success
 */
static bool _add_transparent_cube(
    DvzScene* scene,
    DvzPanel* panel,
    double size,
    vec3 position,
    uint8_t alpha,
    DvzAlphaMode mode,
    ExampleStyleColorRole primary)
{
    const ExampleStyleColorRole roles[6] = {
        primary,
        EXAMPLE_STYLE_COLOR_ACCENT_PRIMARY,
        EXAMPLE_STYLE_COLOR_ACCENT_SECONDARY,
        EXAMPLE_STYLE_COLOR_WARNING,
        EXAMPLE_STYLE_COLOR_TEXT,
        EXAMPLE_STYLE_COLOR_GRID,
    };
    DvzColor face_colors[DVZ_GEOM_CUBE_FACE_COUNT] = {{0}};
    for (uint32_t i = 0; i < DVZ_GEOM_CUBE_FACE_COUNT; i++)
    {
        face_colors[i] = example_graphite_cyan_color(roles[i]);
        face_colors[i].a = alpha;
    }

    DvzGeometry* cube = dvz_geometry_cube(&(DvzGeometryCubeDesc){
        DVZ_STRUCT_INIT_FIELDS(DvzGeometryCubeDesc),
        .size = size,
        .face_colors = face_colors,
        .face_color_count = DVZ_GEOM_CUBE_FACE_COUNT,
    });
    if (cube == NULL)
        return false;

    bool ok = false;
    DvzVisual* visual = dvz_mesh(scene, 0);
    if (visual == NULL)
        goto cleanup;
    if (dvz_mesh_set_geometry(visual, cube) != 0)
        goto cleanup;

    DvzMaterialDesc material = dvz_standard_material_desc();
    material.standard.roughness = 0.42f;
    material.standard.specular = 0.30f;
    material.standard.rim_strength = 0.20f;
    material.alpha_mode = mode;
    if (dvz_visual_set_material(visual, &material) != 0)
        goto cleanup;
    if (dvz_visual_set_alpha_mode(visual, mode) != 0)
        goto cleanup;

    mat4 transform = {{0}};
    _translation(position[0], position[1], position[2], transform);
    if (dvz_visual_set_transform(visual, transform) != 0)
        goto cleanup;

    ok = dvz_panel_add_visual(panel, visual, NULL) == 0;

cleanup:
    dvz_geometry_destroy(cube);
    return ok;
}



/**
 * Add the same overlapping translucent cubes to one panel.
 *
 * @param scene scene owning the visual
 * @param panel panel receiving the visual
 * @param mode alpha technique
 * @return true on success
 */
static bool _add_transparent_cubes(DvzScene* scene, DvzPanel* panel, DvzAlphaMode mode)
{
    return _add_transparent_cube(
               scene, panel, 1.06, (vec3){-0.20f, -0.02f, +0.00f}, 112u, mode,
               EXAMPLE_STYLE_COLOR_ACCENT_PRIMARY) &&
           _add_transparent_cube(
               scene, panel, 0.78, (vec3){+0.26f, +0.10f, +0.18f}, 146u, mode,
               EXAMPLE_STYLE_COLOR_WARNING);
}



/**
 * Set the shared arcball orientation used by all panels.
 *
 * @param ctx scenario context
 * @param panel target panel
 * @return true on success
 */
static DvzController* _bind_arcball(DvzScenarioContext* ctx, DvzPanel* panel)
{
    DvzController* controller = dvz_arcball(ctx->scene, NULL);
    if (controller == NULL)
        return NULL;
    DvzArcball* arcball = dvz_controller_arcball(controller);
    if (arcball == NULL)
        return NULL;
    if (dvz_scenario_bind_controller(ctx, panel, controller, DVZ_DIM_MASK_XYZ) != 0)
        return NULL;
    dvz_arcball_set(arcball, (vec3){+0.50f, -0.18f, +0.22f});
    return controller;
}



/*************************************************************************************************/
/*  Scenario callbacks                                                                           */
/*************************************************************************************************/

/**
 * Initialize the transparency-order feature scenario.
 *
 * @param ctx scenario context
 * @param out_user scenario state output
 * @return true on success
 */
static bool _scenario_init(DvzScenarioContext* ctx, void** out_user)
{
    if (ctx == NULL)
        return false;
    if (out_user != NULL)
        *out_user = NULL;

    ctx->figure = dvz_figure(ctx->scene, ctx->width, ctx->height, 0);
    if (ctx->figure == NULL)
        return false;

    DvzGrid* grid = dvz_figure_grid(ctx->figure, 1, 3);
    if (grid == NULL)
        return false;
    if (dvz_grid_set_margins(
            grid, &(DvzPanelReserve){
                      .left_px = 42.0f, .right_px = 42.0f, .top_px = 38.0f, .bottom_px = 38.0f}) != DVZ_OK)
        return false;
    if (dvz_grid_set_gutter(grid, 24.0f, 0.0f) != DVZ_OK)
        return false;

    DvzPanel* blended = dvz_grid_panel(grid, 0, 0);
    DvzPanel* wboit = dvz_grid_panel(grid, 0, 1);
    DvzPanel* peel = dvz_grid_panel(grid, 0, 2);
    if (blended == NULL || wboit == NULL || peel == NULL)
        return false;
    example_graphite_cyan_set_panel_background(blended);
    example_graphite_cyan_set_panel_background(wboit);
    example_graphite_cyan_set_panel_background(peel);

    DvzTextStyle label_style = example_graphite_cyan_text_style(EXAMPLE_STYLE_TEXT_PANEL_LABEL);
    label_style.renderer = DVZ_TEXT_RENDERER_MSDF_ATLAS;
    DvzTextPlacement label_placement = dvz_text_placement();
    label_placement.mode = DVZ_TEXT_PLACEMENT_SCREEN;
    label_placement.anchor = DVZ_SCENE_ANCHOR_PANEL_TOP_LEFT;
    label_placement.position[0] = EXAMPLE_PANEL_LABEL_X_PX;
    label_placement.position[1] = EXAMPLE_PANEL_LABEL_Y_PX;
    label_placement.text_anchor[0] = 0.0f;
    label_placement.text_anchor[1] = 0.0f;
    label_placement.has_text_anchor = true;
    DvzLabelDesc label = dvz_label_desc();
    label.text = "source-over";
    DvzAnnotation* annotation = dvz_annotation_label(blended, &label);
    if (annotation == NULL || dvz_annotation_set_style(annotation, &label_style) != 0 ||
        dvz_annotation_set_placement(annotation, &label_placement) != 0)
        return false;
    label.text = "weighted OIT";
    annotation = dvz_annotation_label(wboit, &label);
    if (annotation == NULL || dvz_annotation_set_style(annotation, &label_style) != 0 ||
        dvz_annotation_set_placement(annotation, &label_placement) != 0)
        return false;
    label.text = "depth peel";
    annotation = dvz_annotation_label(peel, &label);
    if (annotation == NULL || dvz_annotation_set_style(annotation, &label_style) != 0 ||
        dvz_annotation_set_placement(annotation, &label_placement) != 0)
        return false;

    if (example_set_default_3d_camera(blended, 1.0f) == NULL ||
        example_set_default_3d_camera(wboit, 1.0f) == NULL ||
        example_set_default_3d_camera(peel, 1.0f) == NULL)
        return false;
    DvzController* controllers[3] = {
        _bind_arcball(ctx, blended),
        _bind_arcball(ctx, wboit),
        _bind_arcball(ctx, peel),
    };
    if (controllers[0] == NULL || controllers[1] == NULL || controllers[2] == NULL)
        return false;
    for (uint32_t i = 1; i < 3u; i++)
    {
        if (dvz_controller_link(
                ctx->scene, controllers[0], controllers[i],
                DVZ_CONTROLLER_LINK_ROTATION | DVZ_CONTROLLER_LINK_PAN | DVZ_CONTROLLER_LINK_ZOOM,
                DVZ_CONTROLLER_LINK_TWO_WAY) == NULL)
            return false;
    }

    if (
        !_add_transparent_cubes(ctx->scene, blended, DVZ_ALPHA_BLENDED) ||
        !_add_transparent_cubes(ctx->scene, wboit, DVZ_ALPHA_WBOIT) ||
        !_add_transparent_cubes(ctx->scene, peel, DVZ_ALPHA_DEPTH_PEEL))
        return false;

    TransparencyState* state = (TransparencyState*)dvz_calloc(1, sizeof(*state));
    if (state == NULL)
        return false;
    state->arcball = dvz_controller_arcball(controllers[0]);
    if (state->arcball == NULL)
    {
        dvz_free(state);
        return false;
    }
    if (out_user != NULL)
        *out_user = state;
    return true;
}



static void _scenario_frame(DvzScenarioContext* ctx, void* user)
{
    TransparencyState* state = (TransparencyState*)user;
    if (ctx == NULL || !ctx->preview_mode || state == NULL)
        return;
    ExamplePreviewArcballDesc desc = example_preview_arcball_cube_desc();
    example_preview_arcball(
        state->arcball, ctx->preview_frame_index, ctx->preview_frame_count, &desc);
}


static void _scenario_destroy(DvzScenarioContext* ctx, void* user)
{
    (void)ctx;
    dvz_free(user);
}



/**
 * Return the transparency-order scenario specification.
 *
 * @return scenario specification
 */
static DvzScenarioSpec _transparency_order_scenario(void)
{
    return (DvzScenarioSpec){
        .id = "features_technique_transparency",
        .title = "Transparency Order",
        .width = WIDTH,
        .height = HEIGHT,
        .fps = 60.0,
        .requirements = DVZ_SCENARIO_REQ_MESH_VISUAL | DVZ_SCENARIO_REQ_CONTROLLER |
                        DVZ_SCENARIO_REQ_ARCBALL,
        .init = _scenario_init,
        .frame = _scenario_frame,
        .destroy = _scenario_destroy,
    };
}



/*************************************************************************************************/
/*  Functions                                                                                    */
/*************************************************************************************************/

/**
 * Run the transparency-order feature example through the native scenario runner.
 *
 * @param argc command-line argument count
 * @param argv command-line argument vector
 * @return process exit code
 */
int main(int argc, char** argv)
{
    DvzScenarioSpec spec = _transparency_order_scenario();
    return dvz_scenario_run_native_cli(&spec, argc, argv) == 0 ? 0 : 1;
}
Example details

Data

Field Value
kind synthetic