Source code for optiland.nonsequential.bsdf.specular
"""Specular BRDF for Non-Sequential Raytracing.
Perfect mirror reflection (delta function BSDF). Also used for total
internal reflection.
Kramer Harrison, 2026
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import optiland.backend as be
from optiland.nonsequential.bsdf.base import BaseBSDF
if TYPE_CHECKING:
import numpy as np
from optiland.nonsequential.rng import NSQRng
[docs]
class SpecularBRDF(BaseBSDF):
"""Perfect specular reflector (mirror).
The scattered direction is the specular reflection of the incident ray.
Flux weight is always 1.0 (no energy loss at the surface itself).
"""
[docs]
def sample(
self,
num_rays: int,
incident_dirs: np.ndarray,
normals: np.ndarray,
wavelengths: np.ndarray,
rng: NSQRng | None = None,
ray_id: np.ndarray | None = None,
bounce: np.ndarray | None = None,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Sample specular reflection directions.
Args:
num_rays: Number of rays.
incident_dirs: Incident directions, shape (N, 3).
normals: Surface normals, shape (N, 3).
wavelengths: Wavelengths [µm], shape (N,).
rng: Unused for specular BRDF.
ray_id: Unused for specular BRDF.
bounce: Unused for specular BRDF.
Returns:
(reflected_dirs, ones, all_false) -- reflected unit vectors, unit
weights, and an all-False transmitted mask (a mirror lobe has no
far side).
"""
cos_theta = (incident_dirs * normals).sum(axis=1, keepdims=True)
reflected = incident_dirs - 2.0 * cos_theta * normals
norms = (reflected * reflected).sum(axis=1, keepdims=True) ** 0.5
reflected = reflected / norms
return (
reflected,
be.ones(num_rays, dtype=incident_dirs.dtype),
be.zeros(num_rays, dtype=bool),
)
[docs]
def reflectance(
self,
incident_dirs: np.ndarray,
normals: np.ndarray,
wavelengths: np.ndarray,
) -> np.ndarray:
"""Return reflectance = 1.0 (perfect mirror).
Args:
incident_dirs: Incident directions, shape (N, 3).
normals: Surface normals, shape (N, 3).
wavelengths: Wavelengths [µm], shape (N,).
Returns:
Array of ones, shape (N,).
"""
return be.ones(incident_dirs.shape[0], dtype=incident_dirs.dtype)