Rendering Photometric Properties on a Cylinder#

This example demonstrates how to use the Bouguer law and Ward’s BRDF model from the pysdic library to render photometric properties on a cylindrical mesh.

See also

Note

Other implemented BRDF models can be use in a similar way, such as Beckmann’s model using the pysdic.compute_brdf_beckmann() function.

Define some points on a cylinder surface#

Construct some points on a cylinder surface using cylindrical coordinates. The cylinder is defined by its radius and height range.

import numpy

theta_min = -numpy.pi
theta_max = numpy.pi
height_min = -1.0
height_max = 1.0
radius = 1.0
nt = 1000  # Number of points along the circumference
nh = 100  # Number of points along the height

theta = numpy.linspace(theta_min, theta_max, nt)
height = numpy.linspace(height_min, height_max, nh)
theta_grid, height_grid = numpy.meshgrid(theta, height)

# points
x = radius * numpy.cos(theta_grid)
y = radius * numpy.sin(theta_grid)
z = height_grid
points_coordinates = numpy.stack([x.flatten(), y.flatten(), z.flatten()], axis=1)  # (N_points, 3)

# normals
normals_x = numpy.cos(theta_grid)
normals_y = numpy.sin(theta_grid)
normals_z = numpy.zeros_like(normals_x)
points_normals = numpy.stack([normals_x.flatten(), normals_y.flatten(), normals_z.flatten()], axis=1)  # (N_points, 3)

Define light source and view directions#

Define the light source direction and observer position for the photometric rendering.

For this example, we will use two light sources positioned at different locations. We also define the observer position and the objective is the render the appearance of the cylinder as seen from this position.

light_positions = numpy.array([
    [1.0, 1.0, 0.0],
    [1.0, -1.0, 0.0]
])  # (N_light_sources, 3)

observer_positions = numpy.array([5.0, 0.0, 0.0])  # (3,)

Compute photometric properties using Bouguer law and Ward’s BRDF model#

Use the compute_bouguer_law and compute_brdf_ward functions to compute the photometric properties at the defined points on the cylinder surface.

The first one is the ratio between irradiance \(E\) at source intensity \(I\), the second one is the BRDF ratio between outgoing radiance \(L_o\) and incoming irradiance \(E\), using 3 parameters: diffuse reflectance \(\rho_d\), specular reflectance \(\rho_s\), and surface roughness \(\sigma\) (See the official documentation for more details).

Assuming a given intensity for the light sources \(I = 1.0\), the image brightness is proportional to the incoming radiance \(L_o\) on the camera sensor.

from pysdic import compute_bouguer_law, compute_brdf_ward

bouger_law_ratio = compute_bouguer_law(
    surface_points=points_coordinates,
    surface_normals=points_normals,
    light_positions=light_positions,
) # (N_points, N_light_sources)
print(f"Bouguer law ratio shape: {bouger_law_ratio.shape}, min: {bouger_law_ratio.min()}, max: {bouger_law_ratio.max()}")

brdf_ward_values = compute_brdf_ward(
    surface_points=points_coordinates,
    surface_normals=points_normals,
    light_positions=light_positions,
    observer_positions=observer_positions,
    parameters = [0.1, 0.1, 0.5]  # rho_d, rho_s, sigma
) # (N_points, N_light_sources, N_observers, N_models)

# Here N_observers = 1 and N_models = 1, so we can squeeze these dimensions
brdf_ward_values = brdf_ward_values[:, :, 0, 0]  # (N_points, N_light_sources)
print(f"BRDF Ward values shape: {brdf_ward_values.shape}, min: {brdf_ward_values.min()}, max: {brdf_ward_values.max()}")

# Compute the radiance
radiance = bouger_law_ratio * brdf_ward_values  # (N_points, N_light_sources)

# Sum the contributions from all light sources
total_radiance = numpy.sum(radiance, axis=1)  # (N_points,)
print(f"Total radiance shape: {total_radiance.shape}, min: {total_radiance.min()}, max: {total_radiance.max()}")
Bouguer law ratio shape: (100000, 2), min: 0.0, max: 5.822776397789505
/home/fdahe241/Programs/pysdic/docs/source/../../pysdic/core/photometric_operations.py:396: RuntimeWarning: divide by zero encountered in divide
  brdf = (rho_d / numpy.pi) + (rho_s / (4 * numpy.pi * sigma**2 * numpy.sqrt(cos_theta_i * cos_theta_0))) * \
/home/fdahe241/Programs/pysdic/docs/source/../../pysdic/core/photometric_operations.py:396: RuntimeWarning: invalid value encountered in multiply
  brdf = (rho_d / numpy.pi) + (rho_s / (4 * numpy.pi * sigma**2 * numpy.sqrt(cos_theta_i * cos_theta_0))) * \
BRDF Ward values shape: (100000, 2), min: 0.0, max: 0.07336558608523565
Total radiance shape: (100000,), min: 0.0, max: 0.318519420048296

Visualize the rendered photometric properties on the cylinder surface#

Lets build a Point Cloud object to visualize the rendered photometric properties on the cylinder surface using pyvista.

from pysdic import PointCloud

pc = PointCloud(points_coordinates)
pc['radiance'] = total_radiance
pc.visualize(
    property='radiance',
    property_cmap='plasma',
    point_size=5,
    camera_position=[10.0, 0.0, 0.0],
    camera_view_up=[0, 0, 1],
    camera_focal_point=[0, 0, 0],
    title="Photometric Rendering on Cylinder Surface"
)
example photometric rendering on cylinder

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

Gallery generated by Sphinx-Gallery