.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "auto_examples/13_shaders/viz_billboard_sdf_spheres.py"
.. LINE NUMBERS ARE GIVEN BELOW.

.. only:: html

    .. note::
        :class: sphx-glr-download-link-note

        :ref:`Go to the end <sphx_glr_download_auto_examples_13_shaders_viz_billboard_sdf_spheres.py>`
        to download the full example code

.. rst-class:: sphx-glr-example-title

.. _sphx_glr_auto_examples_13_shaders_viz_billboard_sdf_spheres.py:


===============================================================================
SDF Impostors on Billboards
===============================================================================

Traditional rendering engines discretize surfaces using triangles or
quadrilateral polygons. The visual quality of these elements depends on the
number of polygons used to build the 3D mesh, i.e., a smoother surface will
require more polygons. However, increasing the amount of rendered polygons
comes at the cost of performance as it decreases the number of frames per
second (FPS), which might compromise the real-time interactivity of a
visualization.

Billboarding is a technique that changes an object's orientation to always face
a specific direction, in most cases, the camera. This technique became popular
in games and applications with a high polygonal quota requirement.

Signed Distance Functions (SDFs) are mathematical functions that take as input
a point in a metric space and return the distance from that point to the
boundary of the function. Depending on whether the point is contained within
this boundary or outside it, the function will return negative or positive
values [Hart1996]_. For visualization purposes, the task is to display only the
points within the boundary or, in other words, those whose distance to the
border is either negative or positive, depending on the definition of the SDF.

This tutorial exemplifies why FURY's billboard actor is a suitable rendering
option when thinking about performance and how it can be used to create
impostors using SDFs.

Let's start by importing the necessary modules:

.. GENERATED FROM PYTHON SOURCE LINES 32-40

.. code-block:: Python


    import os

    import fury
    import numpy as np
    from fury.shaders import compose_shader, import_fury_shader
    from fury.utils import represent_actor_as_wireframe








.. GENERATED FROM PYTHON SOURCE LINES 41-42

Now set up a new scene to place our actors in.

.. GENERATED FROM PYTHON SOURCE LINES 42-44

.. code-block:: Python

    scene = fury.window.Scene()








.. GENERATED FROM PYTHON SOURCE LINES 45-47

This tutorial is divided into two parts. First, we will render spheres in the
traditional way and then render them using SDFs on billboards.

.. GENERATED FROM PYTHON SOURCE LINES 49-53

Traditional sphere rendering
============================
FURY provides an easy way to create sphere glyphs from numpy arrays as
follows:

.. GENERATED FROM PYTHON SOURCE LINES 53-72

.. code-block:: Python

    centers = np.array(
        [
            [0, 0, 0],
            [-6, -6, -6],
            [8, 8, 8],
            [8.5, 9.5, 9.5],
            [10, -10, 10],
            [-13, 13, -13],
            [-17, -17, 17],
        ]
    )
    colors = np.array(
        [[1, 1, 0], [1, 0, 1], [0, 0, 1], [1, 1, 1], [1, 0, 0], [0, 1, 0], [0, 1, 1]]
    )
    scales = np.array([6, 1.2, 1, 0.2, 0.7, 3, 2])
    spheres_actor = fury.actor.sphere(
        centers, colors, radii=scales, phi=8, theta=8, use_primitive=False
    )








.. GENERATED FROM PYTHON SOURCE LINES 73-76

To interactively visualize the recently created actors, we only need to add
them to the previously created `scene` and set the following variable to
**True**, otherwise, we will screenshot the scene.

.. GENERATED FROM PYTHON SOURCE LINES 76-85

.. code-block:: Python

    scene.add(spheres_actor)

    interactive = False

    if interactive:
        fury.window.show(scene)
    else:
        fury.window.record(scene=scene, size=(600, 600), out_path="viz_regular_spheres.png")




.. image-sg:: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_001.png
   :alt: viz billboard sdf spheres
   :srcset: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_001.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 86-90

Now, let's explore our scene to understand what we have created. Traditional
FURY spheres are designed using a set of interconnected triangles. To
visualize them, we want to transform our representation from *Surface* to
*Wireframe* using the following command.

.. GENERATED FROM PYTHON SOURCE LINES 90-99

.. code-block:: Python

    represent_actor_as_wireframe(spheres_actor)

    if interactive:
        fury.window.show(scene)
    else:
        fury.window.record(
            scene=scene, size=(600, 600), out_path="viz_low_res_wireframe.png"
        )




.. image-sg:: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_002.png
   :alt: viz billboard sdf spheres
   :srcset: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_002.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 100-101

Let's clean the scene and play with the parameters `phi` and `theta`.

.. GENERATED FROM PYTHON SOURCE LINES 101-115

.. code-block:: Python

    scene.clear()
    spheres_actor = fury.actor.sphere(
        centers, colors, radii=scales, phi=16, theta=16, use_primitive=False
    )
    represent_actor_as_wireframe(spheres_actor)
    scene.add(spheres_actor)

    if interactive:
        fury.window.show(scene)
    else:
        fury.window.record(
            scene=scene, size=(600, 600), out_path="viz_hi_res_wireframe.png"
        )




.. image-sg:: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_003.png
   :alt: viz billboard sdf spheres
   :srcset: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_003.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 116-121

As you might have noticed, these parameters control the resolution of the
spheres. Evidently, these parameters directly impact the quality of the
visualization, but increasing such resolution comes at the cost of
performance, i.e., more computing power will be needed and drawn to interact
with these actors.

.. GENERATED FROM PYTHON SOURCE LINES 123-126

Luckily for us, a technique delivers high-resolution glyphs at a much lower
cost. This technique is known as Signed Distance Functions (SDFs), and they
work as follows:

.. GENERATED FROM PYTHON SOURCE LINES 128-132

SDF sphere rendering
====================
It is possible to render SDFs in FURY by using the following configuration,
but first, let's clear the scene.

.. GENERATED FROM PYTHON SOURCE LINES 132-134

.. code-block:: Python

    scene.clear()








.. GENERATED FROM PYTHON SOURCE LINES 135-137

The billboard actor is suited and continuously improved to render SDFs. To
create and visualize it, we can use the following instructions:

.. GENERATED FROM PYTHON SOURCE LINES 137-148

.. code-block:: Python

    billboards_actor = fury.actor.billboard(centers, colors=colors, scales=scales)
    represent_actor_as_wireframe(billboards_actor)
    scene.add(billboards_actor)

    if interactive:
        fury.window.show(scene)
    else:
        fury.window.record(
            scene=scene, size=(600, 600), out_path="viz_billboards_wireframe.png"
        )




.. image-sg:: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_004.png
   :alt: viz billboard sdf spheres
   :srcset: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_004.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 149-153

If you interacted with this actor, you might have noticed how it always
aligned itself to the camera or, in other words, your FURY window. Now that
we know how billboards work, we can start working on our Signed Distance
Spheres. Let's clear our scene first.

.. GENERATED FROM PYTHON SOURCE LINES 153-155

.. code-block:: Python

    scene.clear()








.. GENERATED FROM PYTHON SOURCE LINES 156-158

FURY already includes a shader function with the definition of a Signed
Distance Sphere. So we can load it and use it like this:

.. GENERATED FROM PYTHON SOURCE LINES 158-160

.. code-block:: Python

    sd_sphere = import_fury_shader(os.path.join("sdf", "sd_sphere.frag"))








.. GENERATED FROM PYTHON SOURCE LINES 161-164

Additionally, we need to define the radii of our spheres. Since we prefer
these to be determined by the billboards' size, we will use the maximum
radius distance allowed by our billboards.

.. GENERATED FROM PYTHON SOURCE LINES 164-166

.. code-block:: Python

    sphere_radius = "const float RADIUS = 1.;"








.. GENERATED FROM PYTHON SOURCE LINES 167-169

Let's calculate the distance to the sphere by combining the previously
defined variables.

.. GENERATED FROM PYTHON SOURCE LINES 169-171

.. code-block:: Python

    sphere_dist = "float dist = sdSphere(point, RADIUS);"








.. GENERATED FROM PYTHON SOURCE LINES 172-173

Now, evaluate the signed distance function.

.. GENERATED FROM PYTHON SOURCE LINES 173-180

.. code-block:: Python

    sdf_eval = """
        if (dist < 0)
            fragOutput0 = vec4(color, opacity);
        else
            discard;
        """








.. GENERATED FROM PYTHON SOURCE LINES 181-183

Putting all of our declarations (constants and function) and implementations
(distance calculation and evaluation) together.

.. GENERATED FROM PYTHON SOURCE LINES 183-186

.. code-block:: Python

    fs_dec = compose_shader([sphere_radius, sd_sphere])
    fs_impl = compose_shader([sphere_dist, sdf_eval])








.. GENERATED FROM PYTHON SOURCE LINES 187-188

We are ready to create and visualize our SDF-billboard actors.

.. GENERATED FROM PYTHON SOURCE LINES 188-200

.. code-block:: Python

    spheres_actor = fury.actor.billboard(
        centers, colors=colors, scales=scales, fs_dec=fs_dec, fs_impl=fs_impl
    )
    scene.add(spheres_actor)

    if interactive:
        fury.window.show(scene)
    else:
        fury.window.record(
            scene=scene, size=(600, 600), out_path="viz_billboards_circles.png"
        )




.. image-sg:: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_005.png
   :alt: viz billboard sdf spheres
   :srcset: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_005.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 201-205

Hold on, those actors don't look exactly like the ones we created using
traditional techniques; they don't even look 3D but 2D. Well, that's because
we still need an essential component: shading. So let's clear our scene and
add shading to our SDF billboard actors.

.. GENERATED FROM PYTHON SOURCE LINES 205-207

.. code-block:: Python

    scene.clear()








.. GENERATED FROM PYTHON SOURCE LINES 208-213

The first thing necessary to add shading to our SDF-billboard actors is to
calculate the normals of the SDFs. In this tutorial we are not going to get
into detail in the gradient and derivatives of SDFs, so we will use the
central differences technique implemented in the following FURY shader
function:

.. GENERATED FROM PYTHON SOURCE LINES 213-215

.. code-block:: Python

    central_diffs_normal = import_fury_shader(os.path.join("sdf", "central_diffs.frag"))








.. GENERATED FROM PYTHON SOURCE LINES 216-218

To use the central differences technique, we need to define a map function
that wraps our SDF and evaluates only a point.

.. GENERATED FROM PYTHON SOURCE LINES 218-225

.. code-block:: Python

    sd_sphere_normal = """
        float map(vec3 p)
        {
            return sdSphere(p, RADIUS);
        }
        """








.. GENERATED FROM PYTHON SOURCE LINES 226-227

Then we can load the Blinn-Phong illumination model.

.. GENERATED FROM PYTHON SOURCE LINES 227-231

.. code-block:: Python

    blinn_phong_model = import_fury_shader(
        os.path.join("lighting", "blinn_phong_model.frag")
    )








.. GENERATED FROM PYTHON SOURCE LINES 232-234

Again, let's bring all of our declarations (constants and functions)
together.

.. GENERATED FROM PYTHON SOURCE LINES 234-244

.. code-block:: Python

    fs_dec = compose_shader(
        [
            sphere_radius,
            sd_sphere,
            sd_sphere_normal,
            central_diffs_normal,
            blinn_phong_model,
        ]
    )








.. GENERATED FROM PYTHON SOURCE LINES 245-250

Now, we can start our fragment shader implementation with the signed distance
function evaluation. You might notice that in this case, we are not using an
if statement but a `step` function, which is a more efficient way to perform
this evaluation. You can also replace the `step` function with a `smoothstep`
operation and, in that way, add a very efficient form of antialiasing.

.. GENERATED FROM PYTHON SOURCE LINES 250-252

.. code-block:: Python

    sdf_eval = "opacity *= 1 - step(0, dist);"








.. GENERATED FROM PYTHON SOURCE LINES 253-255

In this case, we also need the absolute value of the distance to compensate
for the depth of the SDF sphere.

.. GENERATED FROM PYTHON SOURCE LINES 255-257

.. code-block:: Python

    abs_dist = "float absDist = abs(dist);"








.. GENERATED FROM PYTHON SOURCE LINES 258-259

We are ready to calculate the normals.

.. GENERATED FROM PYTHON SOURCE LINES 259-261

.. code-block:: Python

    normal = "vec3 normal = centralDiffsNormals(vec3(point.xy, absDist), .0001);"








.. GENERATED FROM PYTHON SOURCE LINES 262-263

With the normals we can calculate a light attenuation factor.

.. GENERATED FROM PYTHON SOURCE LINES 263-265

.. code-block:: Python

    light_attenuation = "float lightAttenuation = normal.z;"








.. GENERATED FROM PYTHON SOURCE LINES 266-267

Now, we are ready to calculate the color and output it.

.. GENERATED FROM PYTHON SOURCE LINES 267-275

.. code-block:: Python

    color = """
        color = blinnPhongIllumModel(
            lightAttenuation, lightColor0, diffuseColor, specularPower,
            specularColor, ambientColor);
        """

    frag_output = "fragOutput0 = vec4(color, opacity);"








.. GENERATED FROM PYTHON SOURCE LINES 276-277

As before, we can bring our implementation code together.

.. GENERATED FROM PYTHON SOURCE LINES 277-281

.. code-block:: Python

    fs_impl = compose_shader(
        [sphere_dist, sdf_eval, abs_dist, normal, light_attenuation, color, frag_output]
    )








.. GENERATED FROM PYTHON SOURCE LINES 282-283

Finally, recreate the SDF billboard actors and visualize them.

.. GENERATED FROM PYTHON SOURCE LINES 283-295

.. code-block:: Python

    spheres_actor = fury.actor.billboard(
        centers, colors=colors, scales=scales, fs_dec=fs_dec, fs_impl=fs_impl
    )
    scene.add(spheres_actor)

    if interactive:
        fury.window.show(scene)
    else:
        fury.window.record(
            scene=scene, size=(600, 600), out_path="viz_billboards_spheres.png"
        )




.. image-sg:: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_006.png
   :alt: viz billboard sdf spheres
   :srcset: /auto_examples/13_shaders/images/sphx_glr_viz_billboard_sdf_spheres_006.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 296-302

References
----------
.. _[Hart1996] Hart, John C. "Sphere tracing: A geometric method for the
              antialiased ray tracing of implicit surfaces." The Visual
              Computer 12.10 (1996): 527-545.



.. rst-class:: sphx-glr-timing

   **Total running time of the script:** (0 minutes 0.320 seconds)


.. _sphx_glr_download_auto_examples_13_shaders_viz_billboard_sdf_spheres.py:

.. only:: html

  .. container:: sphx-glr-footer sphx-glr-footer-example

    .. container:: sphx-glr-download sphx-glr-download-jupyter

      :download:`Download Jupyter notebook: viz_billboard_sdf_spheres.ipynb <viz_billboard_sdf_spheres.ipynb>`

    .. container:: sphx-glr-download sphx-glr-download-python

      :download:`Download Python source code: viz_billboard_sdf_spheres.py <viz_billboard_sdf_spheres.py>`


.. only:: html

 .. rst-class:: sphx-glr-signature

    `Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_