""" A catalog of useful and common BRDF models """
import numpy as np
[docs]def LAMBERTIAN(albedo):
"""
Lambertian scattering model.
:math:`BRDF = \\frac{\\rho}{\\pi}`
:param albedo: Albedo, should be between 0.0 and 1.0
:type albedo: float
:return: BRDF function, where f = BRDF(incident vector, normal_vector, outgoing_vector)
:rtype: function
"""
def BRDF(incident_vector, normal_vector, outgoing_vector):
ix, iy, iz = incident_vector
nx, ny, nz = normal_vector
ox, oy, oz = outgoing_vector
brdf = np.where(ix*nx + iy*ny + iz*nz < 0, 0, albedo / np.pi)
brdf = np.where(ox*nx + oy*ny + oz*nz < 0, 0, albedo / np.pi)
return brdf
return BRDF
[docs]def ABG(A, B, g):
"""
ABG scattering model.
Described here: `Using the ABG model <https://support.zemax.com/hc/en-us/articles/4408106806163-Fitting-ABg-scattering-coefficients-from-raw-measured-data>`_
:math:`BRDF = \\frac{A}{B + x^g}`
:param A: Amplitude parameter, A/B is the specular peak
:type A: float
:param B: Knee parameter, B determines where the BRDF transitions to exponential decay
:type B: float
:param g: Slope parameter, g determines the slope of the BRDF in log-log space
:type g: float
:return: BRDF function, where f = BRDF(incident vector, normal_vector, outgoing_vector)
:rtype: function
"""
def BRDF(incident_vector, normal_vector, outgoing_vector):
ix, iy, iz = incident_vector
nx, ny, nz = normal_vector
ox, oy, oz = outgoing_vector
dot = ix*nx + iy*ny + iz*nz
rx = 2 * dot * nx - ix
ry = 2 * dot * ny - iy
rz = 2 * dot * nz - iz
dot = ox*nx + oy*ny + oz*nz
beta_x = ox - dot * nx
beta_y = oy - dot * ny
beta_z = oz - dot * nz
dot = rx*nx + ry*ny + rz*nz
beta0_x = rx - dot * nx
beta0_y = ry - dot * ny
beta0_z = rz - dot * nz
x = np.sqrt( (beta_x - beta0_x)**2 + (beta_y - beta0_y)**2 + (beta_z - beta0_z)**2 )
brdf = A / (B + x**g)
brdf = np.where(ix*nx + iy*ny + iz*nz < 0, 0, brdf)
brdf = np.where(ox*nx + oy*ny + oz*nz < 0, 0, brdf)
return brdf
return BRDF
[docs]def GAUSSIAN(A, sigma):
"""
Gaussian scattering model.
:math:`BRDF = A exp(\\frac{\\hat{w}_r\\cdot\\hat{w}_o - 1}{\\sigma})`
:param A: Height of specular peak of BRDF
:type A: float
:param sigma: Width of specular peak
:type sigma: float
:return: BRDF function, where f = BRDF(incident vector, normal_vector, outgoing_vector)
:rtype: function
"""
def BRDF(incident_vector, normal_vector, outgoing_vector):
ix, iy, iz = incident_vector
nx, ny, nz = normal_vector
ox, oy, oz = outgoing_vector
dot = ix*nx + iy*ny + iz*nz
rx = 2 * dot * nx - ix
ry = 2 * dot * ny - iy
rz = 2 * dot * nz - iz
dot = rx * ox + ry * oy + rz * oz
brdf = A * np.exp( (dot - 1) / sigma )
brdf = np.where(ix*nx + iy*ny + iz*nz < 0, 0, brdf)
brdf = np.where(ox*nx + oy*ny + oz*nz < 0, 0, brdf)
return brdf
return BRDF
[docs]def PHONG(Kd, Ks, n):
"""
Phong scattering model.
:math:`BRDF = \\frac{K_d}{\\pi} + K_s\\frac{n + 2}{2 \\pi} (\\hat{w}_r \\cdot \\hat{w}_o)^n`
:param Kd: Diffuse coefficient. Ensure :math:`Kd + Ks \\leq 1`
:type Kd: float
:param Ks: Specular coefficient. Ensure :math:`Kd + Ks \\leq 1`
:type Ks: float
:param n: Controls width of specular peak.
:type n: float
:return: BRDF function, where f = BRDF(incident vector, normal_vector, outgoing_vector)
:rtype: function
"""
def BRDF(incident_vector, normal_vector, outgoing_vector):
ix, iy, iz = incident_vector
nx, ny, nz = normal_vector
ox, oy, oz = outgoing_vector
dot = ix*nx + iy*ny + iz*nz
rx = 2 * dot * nx - ix
ry = 2 * dot * ny - iy
rz = 2 * dot * nz - iz
dot = rx*ox + ry*oy + rz*oz
dot = np.clip(dot, 0, 1)
brdf = Kd / np.pi + Ks * (n + 2) / (2 * np.pi) * dot**n
brdf = np.where(ix*nx + iy*ny + iz*nz < 0, 0, brdf)
brdf = np.where(ox*nx + oy*ny + oz*nz < 0, 0, brdf)
return brdf
return BRDF
[docs]def BINOMIAL(B, C, d, l1):
"""
Binomial scattering model proposed by Greynolds.
Described here: `Physically Realistic BRDF Models <https://www.researchgate.net/publication/300345613_General_physically-realistic_BRDF_models_for_computing_stray_light_from_arbitrary_isotropic_surfaces>`_
:param B: b_i model coefficients
:type B: :class:`np.ndarray`
:param C: c_i model coefficients
:type C: :class:`np.ndarray`
:param d: specularity constant
:type d: float
:param l1: Minimum Gaussian index
:type l1: int
:return: BRDF function, where f = BRDF(incident vector, normal_vector, outgoing_vector)
:rtype: function
"""
def BRDF(incident_vector, normal_vector, outgoing_vector):
ix, iy, iz = incident_vector
nx, ny, nz = normal_vector
ox, oy, oz = outgoing_vector
dot = ix*nx + iy*ny + iz*nz
rx = 2 * dot * nx - ix
ry = 2 * dot * ny - iy
rz = 2 * dot * nz - iz
dot = ox*nx + oy*ny + oz*nz
rho_x = ox - dot * nx
rho_y = oy - dot * ny
rho_z = oz - dot * nz
dot = rx*nx + ry*ny + rz*nz
rho0_x = rx - dot * nx
rho0_y = ry - dot * ny
rho0_z = rz - dot * nz
D = np.sqrt( (rho_x - rho0_x)**2 + (rho_y - rho0_y)**2 + (rho_z - rho0_z)**2 )
V = rho_x * rho0_x + rho_y * rho0_y + rho_z * rho0_z
log_brdf = np.zeros_like(D)
n, m = B.shape
_, j = C.shape
for k in range(n):
term_1 = np.zeros_like(D)
for i in range(m):
term_1 = term_1 + B[k, i] * D ** i
term_2 = np.zeros_like(D)
for i in range(j):
term_2 = term_2 + C[k, i] * np.log10(1 + d**(i + l1) * D**2)
log_brdf = log_brdf + (term_1 + 0.5 * term_2) * V**k
brdf = 10**log_brdf
brdf = np.where(ix*nx + iy*ny + iz*nz < 0, 0, brdf)
brdf = np.where(ox*nx + oy*ny + oz*nz < 0, 0, brdf)
return brdf
return BRDF