Screen-Space Ambient Occlusion¶
This example compares a synthetic molecular aggregate with and without ambient occlusion.
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_ssao (build and run), or rerun ./build/examples/c/features/technique_ssao |
| Python | Available; direct-engine adaptation | python3 -m examples.python.gallery.features.technique_ssao |
| 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¶
Both panels render the same irregular, multi-lobed aggregate of variable-sized spheres, while the right panel applies SSAO. Narrow clefts, recessed pockets, and near-contact atom clusters become easier to separate without a floor or external dataset. In live mode, use the GUI and linked arcball to inspect how radius, strength, bias, power, visibility, sample count, and blur affect local occlusion.
Source¶
#!/usr/bin/env python3
"""Screen-space ambient occlusion on a protein-like synthetic aggregate."""
from __future__ import annotations
import ctypes
import numpy as np
import datoviz as dvz
from examples.python.gallery import common as ex
LABELS = (b"Plain lighting", b"Ambient occlusion")
INITIAL_ANGLES = (ctypes.c_float * 3)(-0.239547, -0.407204, +0.824558)
INITIAL_PAN = (ctypes.c_float * 2)(0.0, -0.020)
INITIAL_ZOOM = 0.505017
AGGREGATE_TARGET_SPHERES = 2600
AGGREGATE_MAX_CANDIDATES = 200_000
AGGREGATE_HASH_STEP = 0.080
def _hash_u32(value: int) -> int:
value &= 0xFFFFFFFF
value ^= value >> 16
value = (value * 0x7FEB352D) & 0xFFFFFFFF
value ^= value >> 15
value = (value * 0x846CA68B) & 0xFFFFFFFF
value ^= value >> 16
return value & 0xFFFFFFFF
def _hash_unit(value: int) -> float:
return (_hash_u32(value) & 0x00FFFFFF) / 16777215.0
def _sphere_data():
positions = []
radii = []
spatial_hash: dict[tuple[int, int, int], list[int]] = {}
lobe_centers = (
(-0.40, +0.12, -0.02),
(+0.18, +0.17, +0.06),
(+0.55, -0.24, -0.08),
(-0.26, -0.38, +0.10),
(+0.08, -0.18, -0.30),
)
lobe_radii = (
(0.58, 0.45, 0.43),
(0.54, 0.49, 0.46),
(0.40, 0.34, 0.36),
(0.43, 0.32, 0.35),
(0.38, 0.32, 0.34),
)
for candidate in range(AGGREGATE_MAX_CANDIDATES):
if len(positions) >= AGGREGATE_TARGET_SPHERES:
break
px = -1.05 + 2.10 * _hash_unit(4 * candidate + 0)
py = -0.90 + 1.80 * _hash_unit(4 * candidate + 1)
pz = -0.80 + 1.60 * _hash_unit(4 * candidate + 2)
radius = 0.024 + 0.012 * _hash_unit(4 * candidate + 3)
inside = any(
((px - cx) / rx) ** 2 + ((py - cy) / ry) ** 2 + ((pz - cz) / rz) ** 2 <= 1
for (cx, cy, cz), (rx, ry, rz) in zip(lobe_centers, lobe_radii)
)
pocket0 = (px + 0.03) ** 2 + (py - 0.31) ** 2 + (pz - 0.48) ** 2 < 0.070
pocket1 = (px - 0.48) ** 2 + (py - 0.08) ** 2 + (pz - 0.24) ** 2 < 0.030
if not inside or pocket0 or pocket1:
continue
cell = (
int(np.floor((px + 1.05) / AGGREGATE_HASH_STEP)),
int(np.floor((py + 0.90) / AGGREGATE_HASH_STEP)),
int(np.floor((pz + 0.80) / AGGREGATE_HASH_STEP)),
)
overlaps = False
for dz in range(-1, 2):
for dy in range(-1, 2):
for dx in range(-1, 2):
for index in spatial_hash.get(
(cell[0] + dx, cell[1] + dy, cell[2] + dz), ()
):
qx, qy, qz = positions[index]
minimum = radius + radii[index] + 0.002
if (px - qx) ** 2 + (py - qy) ** 2 + (pz - qz) ** 2 < minimum**2:
overlaps = True
break
if overlaps:
break
if overlaps:
break
if overlaps:
break
if overlaps:
continue
spatial_hash.setdefault(cell, []).append(len(positions))
positions.append((px, py, pz))
radii.append(radius)
count = len(positions)
colors = np.tile(np.array([[ex.CYAN.r, ex.CYAN.g, ex.CYAN.b, ex.CYAN.a]]), (count, 1))
return (
np.asarray(positions, dtype=np.float32),
np.asarray(radii, dtype=np.float32),
colors.astype(np.uint8),
)
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 _add_sphere_cluster(scene, panel) -> None:
spheres = dvz.dvz_sphere(scene, dvz.DVZ_SPHERE_FLAGS_LIGHTING)
if not spheres:
raise RuntimeError("dvz_sphere() failed")
if dvz.dvz_sphere_set_mode(spheres, dvz.DVZ_SPHERE_MODE_RAYCAST_IMPOSTOR) != 0:
raise RuntimeError("dvz_sphere_set_mode() failed")
positions, radii, colors = _sphere_data()
if dvz.dvz_visual_set_data_many(
spheres,
{
"position": positions,
"radius": radii,
"color": colors,
},
) != 0:
raise RuntimeError("dvz_visual_set_data_many(spheres) failed")
material = dvz.dvz_standard_material_desc()
material.light_direction[:] = (-0.38, 0.52, 0.76)
material.standard.roughness = 0.72
material.standard.specular = 0.22
material.standard.rim_strength = 0.10
if dvz.dvz_visual_set_material(spheres, ctypes.byref(material)) != 0:
raise RuntimeError("dvz_visual_set_material() failed")
ex.add_visual(panel, spheres)
def _set_ssao(panel) -> None:
desc = dvz.dvz_ssao_desc()
desc.radius = 0.560
desc.strength = 3.318
desc.bias = 0.032
desc.power = 1.541
desc.min_visibility = 0.000
desc.sample_count = 32
desc.blur_radius = 4.520
desc.blur_depth_sigma = 0.834
desc.blur_normal_sigma = 0.309
desc.blur_enabled = True
desc.debug_view = False
if dvz.dvz_panel_set_ssao(panel, ctypes.byref(desc)) != 0:
raise RuntimeError("dvz_panel_set_ssao() failed")
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, 2)
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, 30.0, 0.0) != 0:
raise RuntimeError("dvz_grid_set_gutter() failed")
panels = []
for col, label in enumerate(LABELS):
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_sphere_cluster(scene, panel)
panels.append(panel)
_set_ssao(panels[1])
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_initial(arcball, INITIAL_ANGLES) != 0:
raise RuntimeError("dvz_arcball_initial() failed")
if dvz.dvz_arcball_zoom(arcball, INITIAL_ZOOM) != 0:
raise RuntimeError("dvz_arcball_zoom() failed")
if dvz.dvz_arcball_pan(arcball, INITIAL_PAN) != 0:
raise RuntimeError("dvz_arcball_pan() failed")
controllers.append(controller)
components = (
int(dvz.DVZ_CONTROLLER_LINK_ROTATION)
| int(dvz.DVZ_CONTROLLER_LINK_PAN)
| int(dvz.DVZ_CONTROLLER_LINK_ZOOM)
)
link = dvz.dvz_controller_link(
scene, controllers[0], controllers[1], 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, "Screen-Space Ambient Occlusion", 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 a synthetic molecular aggregate with and without ambient occlusion.
*
* What to look for: both panels render the same irregular, multi-lobed aggregate of variable-sized
* spheres, while the right panel applies SSAO. Narrow clefts, recessed pockets, and near-contact
* atom clusters become easier to separate without a floor or external dataset. In live mode, use
* the GUI and linked arcball to inspect how radius, strength, bias, power, visibility, sample count,
* and blur affect local occlusion.
*
* Scenario: features_technique_ssao
* Style: features, graphite_cyan, 1280x720 window target
*
* Build: just example-c features/technique_ssao
* Run: ./build/examples/c/features/technique_ssao --live
* Smoke: ./build/examples/c/features/technique_ssao --png
*/
/*************************************************************************************************/
/* Includes */
/*************************************************************************************************/
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "_alloc.h"
#include "datoviz/scene.h"
#include "example_common.h"
#include "example_controller_preview.h"
#include "example_style.h"
#include "example_tuner.h"
#include "runner/scenario_runner.h"
/*************************************************************************************************/
/* Constants */
/*************************************************************************************************/
#define WIDTH EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT
#define AGGREGATE_TARGET_SPHERES 2600u
#define AGGREGATE_MAX_CANDIDATES 200000u
#define AGGREGATE_HASH_X 27
#define AGGREGATE_HASH_Y 23
#define AGGREGATE_HASH_Z 20
#define AGGREGATE_HASH_CELLS \
(AGGREGATE_HASH_X * AGGREGATE_HASH_Y * AGGREGATE_HASH_Z)
#define AGGREGATE_HASH_STEP 0.080f
/*************************************************************************************************/
/* Helpers */
/*************************************************************************************************/
typedef struct SsaoDemoState
{
DvzPanel* ssao_panel;
DvzArcball* plain_arcball;
DvzArcball* ssao_arcball;
DvzExampleGuiSsaoControls ssao;
vec3 arcball_angles;
vec2 arcball_pan;
float arcball_zoom;
ExampleTuner tuner;
} SsaoDemoState;
static void _apply_arcball(SsaoDemoState* state)
{
if (state == NULL)
return;
if (state->plain_arcball != NULL)
{
dvz_arcball_set(state->plain_arcball, state->arcball_angles);
dvz_arcball_zoom(state->plain_arcball, state->arcball_zoom);
dvz_arcball_pan(state->plain_arcball, state->arcball_pan);
}
if (state->ssao_arcball != NULL)
{
dvz_arcball_set(state->ssao_arcball, state->arcball_angles);
dvz_arcball_zoom(state->ssao_arcball, state->arcball_zoom);
dvz_arcball_pan(state->ssao_arcball, state->arcball_pan);
}
}
/**
* Return a deterministic pseudo-random 32-bit value.
*
* @param value input value
* @return mixed value
*/
static uint32_t _hash_u32(uint32_t value)
{
value ^= value >> 16u;
value *= 0x7feb352du;
value ^= value >> 15u;
value *= 0x846ca68bu;
value ^= value >> 16u;
return value;
}
/**
* Return a deterministic value in the [0, 1] interval.
*
* @param value input value
* @return normalized mixed value
*/
static float _hash_unit(uint32_t value)
{
return (float)(_hash_u32(value) & 0x00ffffffu) / 16777215.0f;
}
/**
* Add a protein-like aggregate made from irregular lobes and recessed pockets.
*
* @param scene scene owning the visual
* @param panel panel receiving the visual
* @return true on success
*/
static bool _add_sphere_cluster(DvzScene* scene, DvzPanel* panel)
{
vec3 positions[AGGREGATE_TARGET_SPHERES] = {{0}};
float radii[AGGREGATE_TARGET_SPHERES] = {0};
DvzColor colors[AGGREGATE_TARGET_SPHERES] = {{0}};
int32_t hash_heads[AGGREGATE_HASH_CELLS] = {0};
int32_t hash_next[AGGREGATE_TARGET_SPHERES] = {0};
uint32_t count = 0;
const vec3 lobe_centers[5] = {
{-0.40f, +0.12f, -0.02f},
{+0.18f, +0.17f, +0.06f},
{+0.55f, -0.24f, -0.08f},
{-0.26f, -0.38f, +0.10f},
{+0.08f, -0.18f, -0.30f},
};
const vec3 lobe_radii[5] = {
{0.58f, 0.45f, 0.43f},
{0.54f, 0.49f, 0.46f},
{0.40f, 0.34f, 0.36f},
{0.43f, 0.32f, 0.35f},
{0.38f, 0.32f, 0.34f},
};
for (int32_t i = 0; i < AGGREGATE_HASH_CELLS; i++)
hash_heads[i] = -1;
for (uint32_t candidate = 0;
candidate < AGGREGATE_MAX_CANDIDATES && count < AGGREGATE_TARGET_SPHERES;
candidate++)
{
const float px = -1.05f + 2.10f * _hash_unit(4u * candidate + 0u);
const float py = -0.90f + 1.80f * _hash_unit(4u * candidate + 1u);
const float pz = -0.80f + 1.60f * _hash_unit(4u * candidate + 2u);
const float radius = 0.024f + 0.012f * _hash_unit(4u * candidate + 3u);
bool inside = false;
for (uint32_t i = 0; i < 5; i++)
{
const float dx = (px - lobe_centers[i][0]) / lobe_radii[i][0];
const float dy = (py - lobe_centers[i][1]) / lobe_radii[i][1];
const float dz = (pz - lobe_centers[i][2]) / lobe_radii[i][2];
if (dx * dx + dy * dy + dz * dz <= 1.0f)
{
inside = true;
break;
}
}
const float pocket0_x = px + 0.03f;
const float pocket0_y = py - 0.31f;
const float pocket0_z = pz - 0.48f;
const bool pocket0 = pocket0_x * pocket0_x + pocket0_y * pocket0_y +
pocket0_z * pocket0_z < 0.070f;
const float pocket1_x = px - 0.48f;
const float pocket1_y = py - 0.08f;
const float pocket1_z = pz - 0.24f;
const bool pocket1 = pocket1_x * pocket1_x + pocket1_y * pocket1_y +
pocket1_z * pocket1_z < 0.030f;
if (!inside || pocket0 || pocket1)
continue;
const int32_t cell_x = (int32_t)floorf((px + 1.05f) / AGGREGATE_HASH_STEP);
const int32_t cell_y = (int32_t)floorf((py + 0.90f) / AGGREGATE_HASH_STEP);
const int32_t cell_z = (int32_t)floorf((pz + 0.80f) / AGGREGATE_HASH_STEP);
if (
cell_x < 0 || cell_x >= AGGREGATE_HASH_X || cell_y < 0 ||
cell_y >= AGGREGATE_HASH_Y || cell_z < 0 || cell_z >= AGGREGATE_HASH_Z)
{
continue;
}
bool overlaps = false;
for (int32_t dz = -1; dz <= 1 && !overlaps; dz++)
{
const int32_t nz = cell_z + dz;
if (nz < 0 || nz >= AGGREGATE_HASH_Z)
continue;
for (int32_t dy = -1; dy <= 1 && !overlaps; dy++)
{
const int32_t ny = cell_y + dy;
if (ny < 0 || ny >= AGGREGATE_HASH_Y)
continue;
for (int32_t dx = -1; dx <= 1 && !overlaps; dx++)
{
const int32_t nx = cell_x + dx;
if (nx < 0 || nx >= AGGREGATE_HASH_X)
continue;
const int32_t hash_cell =
nx + AGGREGATE_HASH_X * (ny + AGGREGATE_HASH_Y * nz);
for (int32_t j = hash_heads[hash_cell]; j >= 0; j = hash_next[j])
{
const float sx = px - positions[j][0];
const float sy = py - positions[j][1];
const float sz = pz - positions[j][2];
const float minimum = radius + radii[j] + 0.002f;
if (sx * sx + sy * sy + sz * sz < minimum * minimum)
{
overlaps = true;
break;
}
}
}
}
}
if (overlaps)
continue;
positions[count][0] = px;
positions[count][1] = py;
positions[count][2] = pz;
radii[count] = radius;
colors[count] = example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_PRIMARY);
const int32_t hash_cell =
cell_x + AGGREGATE_HASH_X * (cell_y + AGGREGATE_HASH_Y * cell_z);
hash_next[count] = hash_heads[hash_cell];
hash_heads[hash_cell] = (int32_t)count;
count++;
}
if (count == 0)
return false;
DvzVisual* spheres = dvz_sphere(scene, DVZ_SPHERE_FLAGS_LIGHTING);
if (spheres == NULL)
return false;
if (dvz_sphere_set_mode(spheres, DVZ_SPHERE_MODE_RAYCAST_IMPOSTOR) != 0)
return false;
DvzVisualDataUpdate updates[] = {
{.attr_name = "position", .data = positions, .item_count = count},
{.attr_name = "radius", .data = radii, .item_count = count},
{.attr_name = "color", .data = colors, .item_count = count},
};
if (dvz_visual_set_data_many(spheres, updates, 3) != 0)
return false;
DvzMaterialDesc material = dvz_standard_material_desc();
material.light_direction[0] = -0.38f;
material.light_direction[1] = +0.52f;
material.light_direction[2] = +0.76f;
material.standard.roughness = 0.72f;
material.standard.specular = 0.22f;
material.standard.rim_strength = 0.10f;
if (dvz_visual_set_material(spheres, &material) != 0)
return false;
return dvz_panel_add_visual(panel, spheres, NULL) == 0;
}
static DvzController* _bind_arcball(DvzScenarioContext* ctx, DvzPanel* panel, vec3 angles)
{
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;
if (dvz_arcball_initial(arcball, angles) != DVZ_OK)
return NULL;
return controller;
}
/*************************************************************************************************/
/* Scenario callbacks */
/*************************************************************************************************/
/**
* Initialize the SSAO 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;
SsaoDemoState* state = (SsaoDemoState*)dvz_calloc(1, sizeof(*state));
if (state == NULL)
return false;
if (out_user != NULL)
*out_user = state;
state->tuner = example_tuner("SSAO calibration");
ctx->figure = dvz_figure(ctx->scene, ctx->width, ctx->height, 0);
if (ctx->figure == NULL)
return false;
example_tuner_figure(&state->tuner, ctx->figure);
DvzGrid* grid = dvz_figure_grid(ctx->figure, 1, 2);
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, 30.0f, 0.0f) != DVZ_OK)
return false;
DvzPanel* plain = dvz_grid_panel(grid, 0, 0);
DvzPanel* ssao_panel = dvz_grid_panel(grid, 0, 1);
if (plain == NULL || ssao_panel == NULL)
return false;
state->ssao_panel = ssao_panel;
example_graphite_cyan_set_panel_background(plain);
example_graphite_cyan_set_panel_background(ssao_panel);
DvzTextStyle label_style = example_graphite_cyan_text_style(EXAMPLE_STYLE_TEXT_PANEL_LABEL);
label_style.renderer = DVZ_TEXT_RENDERER_MSDF_ATLAS;
label_style.size_px = EXAMPLE_PANEL_LABEL_LARGE_SIZE;
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_LARGE_X_PX;
label_placement.position[1] = EXAMPLE_PANEL_LABEL_LARGE_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 = "plain lighting";
DvzAnnotation* annotation = dvz_annotation_label(plain, &label);
if (annotation == NULL || dvz_annotation_set_style(annotation, &label_style) != 0 ||
dvz_annotation_set_placement(annotation, &label_placement) != 0)
return false;
label.text = "ambient occlusion";
annotation = dvz_annotation_label(ssao_panel, &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(plain, 1.0f) == NULL ||
example_set_default_3d_camera(ssao_panel, 1.0f) == NULL)
return false;
if (!_add_sphere_cluster(ctx->scene, plain) || !_add_sphere_cluster(ctx->scene, ssao_panel))
return false;
state->arcball_angles[0] = -0.239547f;
state->arcball_angles[1] = -0.407204f;
state->arcball_angles[2] = +0.824558f;
state->arcball_zoom = 0.505017f;
state->arcball_pan[0] = +0.000f;
state->arcball_pan[1] = -0.020f;
DvzController* plain_controller = _bind_arcball(ctx, plain, state->arcball_angles);
DvzController* ssao_controller = _bind_arcball(ctx, ssao_panel, state->arcball_angles);
if (plain_controller == NULL || ssao_controller == NULL)
return false;
state->plain_arcball = dvz_controller_arcball(plain_controller);
state->ssao_arcball = dvz_controller_arcball(ssao_controller);
if (state->plain_arcball == NULL || state->ssao_arcball == NULL)
return false;
if (dvz_controller_link(
ctx->scene, plain_controller, ssao_controller,
DVZ_CONTROLLER_LINK_ROTATION | DVZ_CONTROLLER_LINK_PAN | DVZ_CONTROLLER_LINK_ZOOM,
DVZ_CONTROLLER_LINK_TWO_WAY) == NULL)
return false;
_apply_arcball(state);
state->ssao = (DvzExampleGuiSsaoControls){
.enabled = true,
.blur = true,
.debug_view = false,
.show_blur_sigmas = true,
.show_debug_view = true,
.radius = 0.560f,
.strength = 3.318f,
.bias = 0.032f,
.power = 1.541f,
.min_visibility = 0.000f,
.samples = 32.0f,
.min_samples = 4.0f,
.max_samples = 32.0f,
.blur_radius = 4.520f,
.blur_radius_max = 16.0f,
.blur_depth_sigma = 0.834f,
.blur_normal_sigma = 0.309f,
};
example_tuner_ssao(&state->tuner, "Occlusion", state->ssao_panel, &state->ssao);
example_tuner_arcball(
&state->tuner, "Arcball", state->plain_arcball, state->arcball_angles,
state->arcball_zoom, state->arcball_pan);
return true;
}
static bool _scenario_native_view(DvzScenarioContext* ctx, DvzApp* app, DvzView* view, void* user)
{
(void)app;
SsaoDemoState* state = (SsaoDemoState*)user;
if (
ctx == NULL || ctx->presentation != DVZ_RUNNER_PRESENT_GLFW || state == NULL ||
view == NULL)
return true;
return example_tuner_attach(&state->tuner, view);
}
static void _scenario_destroy(DvzScenarioContext* ctx, void* user)
{
(void)ctx;
SsaoDemoState* state = (SsaoDemoState*)user;
if (state != NULL)
example_tuner_detach(&state->tuner);
dvz_free(state);
}
static void _scenario_frame(DvzScenarioContext* ctx, void* user)
{
SsaoDemoState* state = (SsaoDemoState*)user;
if (ctx == NULL || !ctx->preview_mode || state == NULL)
return;
ExamplePreviewArcballDesc desc = example_preview_arcball_cube_desc();
example_preview_arcball(
state->plain_arcball, ctx->preview_frame_index, ctx->preview_frame_count, &desc);
example_preview_arcball(
state->ssao_arcball, ctx->preview_frame_index, ctx->preview_frame_count, &desc);
}
/**
* Return the SSAO scenario specification.
*
* @return scenario specification
*/
static DvzScenarioSpec _ssao_scenario(void)
{
return (DvzScenarioSpec){
.id = "features_technique_ssao",
.title = "Screen-Space Ambient Occlusion",
.width = WIDTH,
.height = HEIGHT,
.fps = 60.0,
.init = _scenario_init,
.frame = _scenario_frame,
.destroy = _scenario_destroy,
};
}
/*************************************************************************************************/
/* Functions */
/*************************************************************************************************/
/**
* Run the SSAO 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 = _ssao_scenario();
if (example_cli_wants_live_gui(argc, argv))
spec.native_view = _scenario_native_view;
return dvz_scenario_run_native_cli(&spec, argc, argv) == 0 ? 0 : 1;
}
Example details
- ID:
features_technique_ssao - Category:
feature - Lane:
features - Status:
supported - Source:
examples/c/features/technique_ssao.c - Approved adaptation starter:
yes - Python source:
examples/python/gallery/features/technique_ssao.py - Python adaptation: Available; direct-engine adaptation
- Browser support: Deferred
- Browser capability tags:
gbuffer,depth-postprocess - Validation:
smoke+screenshot
Data
| Field | Value |
|---|---|
kind |
synthetic |