A self-contained, portable implementation of the OpenPBR 1.1 BSDF, extracted from Adobe's proprietary renderer, Eclair. Written in a GLSL-style language with macros that target C++, GLSL, CUDA, MSL (Metal Shading Language), or Slang, it's designed to drop into any path tracer with minimal setup.
Overview
OpenPBR is an "uber-shader" specification that merges Adobe Standard Material and Autodesk Standard Surface into a single, physically plausible framework. We implement it as a fixed set of nested lobes (e.g., fuzz, coat), all sharing the same interface:
- Evaluate the BSDF
- Sample the BSDF
- Compute the PDF
In addition to those three core functions, the prepared BSDF exposes two additional outputs for direct use by the integrator: a homogeneous volume that consolidates all volumetric contributions from the full OpenPBR parameter set, and surface emission attenuated by the coat and fuzz layers. Volume integration is left to the host renderer, though openpbr_homogeneous_volume.h provides production-tested helpers for distance sampling, transmittance, and phase functions.
Why Open Source?
OpenPBR is a powerful but complex material model — implementing it well from scratch requires deep rendering knowledge and a lot of time. We're open-sourcing this to lower that barrier: even a simple path tracer can integrate this production-grade implementation of the full OpenPBR 1.1 parameter set. This is the same code that ships in Eclair today, not a prototype or sample, and we hope it helps teams across the ecosystem adopt OpenPBR faster and validate their own integrations against a real-world reference. The code is released under Apache 2.0.
Note: This is shared as a reference implementation, not run as a community-driven open-source project. Because this code lives in a shipping product, we can't commit to a formal external review process. Pull requests, bug reports, and feature requests are all welcome, and we'll address them on a best-effort basis as our priorities allow. We can't promise to address every item, and large or invasive changes are unlikely to be accepted.
Key Features
- Single-include API:
#include "openpbr.h"brings in the complete public API — all types, settings, and the full BSDF. - Multi-language support: C++, GLSL, CUDA, MSL, or Slang from the same source via a thin interop layer.
- Self-contained: No globals, no hidden dependencies — everything lives alongside the BSDF.
- Configurable: Compile-time settings in
openpbr_settings.hcontrol LUT mode, fast math overrides, conflict-suppression hooks, specialization constants, and custom interop. - Fixed-lobe architecture: One struct per material component.
- Energy-conserving: The BSDF is designed to be energy-conserving for most common configurations, using precomputed LUTs for multiple-scattering compensation.
- Reciprocal: The BSDF satisfies Helmholtz reciprocity for most configurations. See Path Tracing Direction for the exception with transmission. (Note that reciprocity may be traded for better energy conservation in future versions.)
High-Level Architecture
openpbr/
├── openpbr.h ← Single include for the entire public API
├── openpbr_settings.h ← Compile-time feature toggles and configuration
├── openpbr_data_constants.h ← LUT dimensions and LUT ID constants
├── interop/ ← Cross-language macro layer (auto-detected per backend)
│ ├── openpbr_interop.h ← Router: selects the correct backend header
│ └── ... ← Per-backend headers (C++, GLSL, CUDA, MSL, Slang)
├── openpbr_resolved_inputs.h ← Material parameter struct and default initialization
├── openpbr_constants.h ← Material constants
├── openpbr_basis.h ← Coordinate frame utilities
├── openpbr_diffuse_specular.h ← Diffuse/specular component type
├── openpbr_bsdf_lobe_type.h ← BSDF lobe type flags
├── openpbr_homogeneous_volume.h ← Volume type (OpenPBR_HomogeneousVolume) and volume integration helpers (distance sampling, transmittance, phase functions)
├── openpbr_api.h ← Public BSDF functions (prepare, eval, sample, pdf)
└── impl/ ← All implementation details (lobes, utilities, data)
├── openpbr_bsdf.h ← Main BSDF implementation
├── openpbr_fuzz_lobe.h ← Outermost lobe (fuzz/sheen)
├── openpbr_coating_lobe.h ← Coating lobe
├── openpbr_aggregate_lobe.h ← Base layer aggregate
├── ... ← Other lobe implementations and utilities
└── data/ ← Lookup table data arrays
├── openpbr_energy_arrays.h ← Energy compensation table declarations (7 tables)
├── openpbr_energy_array_access.h ← Energy table sampling utilities
├── openpbr_ltc_array.h ← LTC table declaration for fuzz lobe
└── ... ← LUT data files (*_data.h)
-
Resolved Inputs
OpenPBR_ResolvedInputs— A struct containing the full OpenPBR 1.1 parameter set after texture evaluation (base_color,specular_roughness, etc.), plus two required non-spec additions (geometry_basisandgeometry_coat_basis, which replace the spec'sgeometry_normal/geometry_tangent/geometry_coat_normal/geometry_coat_tangentraw vectors) and two optional extensions (specular_anisotropy_rotation_cos_sinandcoat_anisotropy_rotation_cos_sin). See Departures from the OpenPBR Specification for details.openpbr_make_default_resolved_inputs()— Creates anOpenPBR_ResolvedInputswith default parameter values from the OpenPBR specification.
-
Initialization
openpbr_prepare()→ Main entry point. ReturnsOpenPBR_PreparedBsdfcontaining the volume, emission, and all BSDF state ready for evaluation.
For renderers that need finer-grained control (e.g., shadow rays that need only the volume), the prepare functions can be called individually in stages — see step 9 in Getting Started.
-
Prepared Outputs
openpbr_prepare()populates anOpenPBR_PreparedBsdfwith two public outputs:prepared.volume(OpenPBR_HomogeneousVolume) — The interior volume of the material: the homogeneous medium that fills the closed surface, derived from the full OpenPBR parameter set. The subsurface scattering and transmission parameters are blended into a single unified volume. Apply it only to ray segments traveling inside a non-thin-walled object. Recompute it at each surface interaction: after either transmission or internal reflection, if the scattered ray is inside the object, use the newly prepared volume for that next interior segment. Contains extinction, single-scattering albedo, and phase-function anisotropy for use by your renderer's volumetric light transport system (see item 5 in the High-Level Architecture section for production-tested integration helpers).prepared.emission(vec3) — Surface emission in nits (cd/m²), attenuated by coat and fuzz layers. Zero when hitting non-thin-walled geometry from the inside.
-
BSDF Evaluation
openpbr_eval()→ Returns BSDF value multiplied by the cosine of the angle between the light direction and the shading normal (i.e., f(ωᵢ, ωₒ) × |cos θᵢ|).openpbr_sample()→ Importance-samples a light direction; returns weight, PDF, and sampled lobe type.openpbr_pdf()→ Returns sampling PDF for a given light direction.
-
Volume Integration Helpers (in
openpbr_homogeneous_volume.h)openpbr_homogeneous_volume.hprovides a complete, production-tested set of helpers for integrators that act onprepared.volume:openpbr_sample_event_distance()→ Samples a scattering-event distance with spectrally-aware MIS across color channels.openpbr_calculate_weight_for_event_at_distance()/openpbr_calculate_weight_for_surface_at_distance()→ Unbiased path weights for volume and surface interactions, respectively.openpbr_sample_isotropic_phase_function()/openpbr_sample_anisotropic_phase_function()→ Importance-sample the isotropic or Henyey-Greenstein phase function.openpbr_calculate_isotropic_phase_function_value()/openpbr_calculate_isotropic_phase_function_pdf()andopenpbr_calculate_anisotropic_phase_function_value()/openpbr_calculate_anisotropic_phase_function_pdf()→ Evaluate phase function values and PDFs for the isotropic and Henyey-Greenstein models.openpbr_calculate_transmittance_at_distance()/openpbr_calculate_transmittance_at_infinity()→ Beer–Lambert transmittance.- Multiple constructors for
OpenPBR_HomogeneousVolumefrom extinction+albedo, absorption+scattering, or extinction alone.
-
Advanced Shading API (in
impl/openpbr_bsdf.h; not yet part of the stable public API — signatures may evolve; see comments above each function for full preconditions)openpbr_translucent_shadow_weight_and_prob()→ Returns a straight-through shadow weight and probability for transmissive/SSS materials, enabling a practical biased shadow shortcut.
-
Lobe Interface Pattern
GLSL has no class system, so each lobe is implemented as a plain struct paired with free functions that take the struct as their first argument. Every lobe exposes the same three function signatures:
openpbr_eval(lobe, light_direction) openpbr_sample(lobe, rand, light_direction, weight, pdf, sampled_type) openpbr_pdf(lobe, light_direction)
The correct implementation is selected by the struct type via function overloading — compile-time polymorphism with no virtual dispatch overhead. The outermost lobe composes inward, calling the same interface on each inner lobe, so adding or replacing a lobe means implementing the same three functions for its struct type.
Lookup Tables: Dual-Mode Architecture
The BSDF uses precomputed lookup tables (LUTs) for physically accurate energy compensation and fuzz/sheen lobe evaluation:
- Energy compensation tables: 7 tables for microfacet multiple scattering (ideal/opaque dielectrics, ideal metals)
- LTC table: Linearly Transformed Cosines for the fuzz/sheen lobe
These tables support two modes via the OPENPBR_USE_TEXTURE_LUTS compile-time switch:
Array Mode (OPENPBR_USE_TEXTURE_LUTS = 0)
- Tables embedded as constant arrays in shader code
- No texture bindings needed
- Software linear/bilinear/trilinear interpolation
- Larger shader binary size
- Slower on GPU
Texture Mode (OPENPBR_USE_TEXTURE_LUTS = 1)
- Tables stored as 1D/2D/3D textures
- Requires texture binding infrastructure
- Hardware-accelerated filtering and interpolation
- Smaller shader binary size
- Faster on GPU
Array Mode is the default because it eliminates external dependencies on texture assets and binding infrastructure.
Source data format:
- All LUT data is stored in separate header files in the
impl/data/directory - Data is formatted as flattened C arrays matching the original multi-dimensional layout (e.g.,
array[x][y][z]with rightmost index contiguous) - Users can read these files directly to populate GPU textures if needed
Implementation details:
- Energy tables: 32×32 or 32×32×32 normalized integer values [0, 65535] → [0.0, 1.0]
- Storage type is auto-selected per language by the interop layer: 16-bit (
unsigned short) for C++/MSL/CUDA/Slang; 32-bit (uint) for GLSL — no user action required
- Storage type is auto-selected per language by the interop layer: 16-bit (
- LTC table: 32×32 vec3 values
- Both modes use identical indexing and coordinate mappings for exact equivalence
Getting Started
-
Clone this repository.
-
Include
openpbr.hin your renderer (this provides the complete public API). -
Select a shading language backend. Three backends are auto-detected from built-in compiler macros:
- CUDA (
__CUDACC__), MSL (__METAL_VERSION__), and C++ (__cplusplus) are detected automatically. - GLSL is the default fallback when none of the above macros are defined — no explicit setting needed.
- Slang has no standard built-in detection macro, so it requires
OPENPBR_LANGUAGE_TARGET_SLANG=1to be defined before includingopenpbr.h. Without it, the GLSL backend will be selected, which will likely fail to compile under the Slang compiler. (For HLSL-style pipelines, use this Slang path.)
Any backend can also be forced explicitly by setting the corresponding
OPENPBR_LANGUAGE_TARGET_CPP,_GLSL,_MSL,_CUDA, or_SLANGflag, which overrides auto-detection.C++ users must also pre-include GLM (
<glm/glm.hpp>) beforeopenpbr.h; the interop header will emit a clear error if it is missing. Seeminimal_cpp_example.cppfor a complete working example. - CUDA (
-
Choose lookup table mode: Set
OPENPBR_USE_TEXTURE_LUTSto 0 for self-contained array mode (default) or 1 for texture mode. In texture mode, defineOPENPBR_SAMPLE_2D_TEXTURE(lut_id, uv)andOPENPBR_SAMPLE_3D_TEXTURE(lut_id, uvw)beforeopenpbr.h; the headers emit clear errors if either macro is missing. The available texture IDs are documented inopenpbr_data_constants.h. -
Optional — Pre-include hooks: Define any of the following before
openpbr.hto customize behavior:- Fast math:
OPENPBR_FAST_RCP_SQRT(x),OPENPBR_FAST_SQRT(x),OPENPBR_FAST_NORMALIZE(v)— supply faster platform-specific implementations (GPU hardware intrinsics, CPU approximations, etc.). Any undefined hook falls back to the standard library. - Conflict suppression: Set
OPENPBR_USE_CUSTOM_SATURATE = 1if your host already definessaturate(); setOPENPBR_USE_CUSTOM_VEC_TYPES = 1if it already providesvec2/vec3/vec4and, in C++, the GLM math function aliases. (For GLSL the vec types are built-in, soOPENPBR_USE_CUSTOM_VEC_TYPEShas no effect on that backend.) - Specialization constants: Override
OPENPBR_GET_SPECIALIZATION_CONSTANT(name)to connect feature-toggle queries to your renderer's pipeline. OpenPBR calls this with one of the four toggle names (EnableSheenAndCoat,EnableDispersion,EnableTranslucency,EnableMetallic) and uses the returned bool to enable or skip entire lobe code paths. The default always returnstrue, enabling all features — correct for offline renderers and standalone use. Seeopenpbr_settings.hfor the full contract. - Custom interop (advanced): Set
OPENPBR_USE_CUSTOM_INTEROP = 1and provide your own interop header beforeopenpbr.hto replace the entire language abstraction layer. PreferOPENPBR_USE_CUSTOM_SATURATEandOPENPBR_USE_CUSTOM_VEC_TYPESfor targeted suppressions; reach for this only if you need to replace every macro OpenPBR defines.
- Fast math:
-
Populate
OpenPBR_ResolvedInputswith texture-evaluated parameters and geometry data, or start withopenpbr_make_default_resolved_inputs()and override specific properties. -
Initialize the BSDF by calling
openpbr_prepare(), which returns anOpenPBR_PreparedBsdfready for evaluation. -
Evaluate using
openpbr_eval(),openpbr_sample(), oropenpbr_pdf()with the prepared BSDF. -
Staged initialization (advanced): For renderers that need to skip unnecessary work (e.g., shadow rays that only need the volume),
openpbr_prepare_volume(),openpbr_prepare_lobes(), andopenpbr_prepare_emission()can be called individually — in that order, as needed — instead ofopenpbr_prepare(). Each function populates only its own fields withinOpenPBR_PreparedBsdf; be sure to only read fields that have been initialized by the calls you've made. Seeimpl/openpbr_bsdf.hfor full signatures and preconditions (this file is already included byopenpbr.h).
Minimal Usage Example
// 1. Include the entire OpenPBR BSDF #include "openpbr.h" // 2. Create default inputs and customize OpenPBR_ResolvedInputs inputs = openpbr_make_default_resolved_inputs(); inputs.base_color = vec3(0.1f, 0.9f, 0.1f); // green base color for illustration // 3. Prepare the BSDF, volume, and emission const OpenPBR_PreparedBsdf prepared = openpbr_prepare(inputs, vec3(1.0f), // path throughput (for importance sampling) OpenPBR_BaseRgbWavelengths_nm, // RGB wavelengths in nanometers OpenPBR_VacuumIor, // exterior IOR view_direction); // incident direction (pointing away from surface) // 4. Sample and/or evaluate the BSDF vec3 light_direction; OpenPBR_DiffuseSpecular weight; float pdf; OpenPBR_BsdfLobeType sampled_type; openpbr_sample(prepared, vec3(rand1, rand2, rand3), // three independent uniform random numbers in [0,1) light_direction, // outgoing direction weight, // BSDF * cosine / PDF; multiply into path throughput pdf, // positive if sample is valid — check before using outputs sampled_type); // the type of lobe that was sampled
Standalone C++ example
A self-contained example is provided in minimal_cpp_example.cpp. It demonstrates the core path-tracing loop — prepare the BSDF, importance-sample a new direction, and accumulate the throughput weight — with a minimal random-number generator so no external dependencies are needed beyond GLM. Build instructions are at the top of the file.
Important Usage Notes
Geometry Opacity
geometry_opacity is not consumed by the BSDF. The host renderer should implement opacity itself (e.g., stochastic cutout), skipping the entire shading evaluation for transparent samples.
Coordinate Space Requirements
All directional vectors and geometric data passed to the BSDF must be in a consistent coordinate space:
view_directionandlight_directionmust use the same coordinate system- Typically world space, but any consistent space works (object space, tangent space, etc.)
- Shading normals and basis vectors in
OpenPBR_ResolvedInputsmust also be in this same space - All directions point away from the surface (not toward it)
- All input unit vectors (
view_direction,light_direction, basis normals/tangents/bitangents) are assumed to be normalized unless otherwise specified; assertions guard this at key entry points
Internal Local Space Convention
Internally, lobes that require a local frame convert directions to a z-up local space via OpenPBR_Basis. In this frame the normal aligns with the +Z axis, the tangent with +X, and the bitangent with +Y. Variables suffixed with _local are expressed in this local frame. Callers never need to work in this space directly — it is an internal implementation detail.
Geometry Bases and Default Coordinate Frame
OpenPBR_ResolvedInputs uses two pre-orthonormalized OpenPBR_Basis structs — geometry_basis and geometry_coat_basis — in place of the spec's four geometry vector parameters (see Departures from the OpenPBR Specification). The default returned by openpbr_make_default_resolved_inputs() is a z-up identity frame (tangent = X, bitangent = Y, normal = Z). In a real renderer, populate them from the geometry at the ray-surface intersection, e.g.:
inputs.geometry_basis.t = world_tangent; // from mesh UVs or procedural tangent inputs.geometry_basis.b = world_bitangent; // derived as cross(n, t) * handedness inputs.geometry_basis.n = world_normal; // interpolated or normal-mapped
As long as view_direction, light_direction, and the basis vectors are all expressed in the same space, any consistent choice of space will produce correct results.
Distance Units
As per the OpenPBR specification, distances are assumed to be in world-space units. If different units are needed, unit conversions need to happen outside the BSDF code.
View Direction Consistency
The view_direction passed to openpbr_prepare() is cached internally for energy compensation calculations. This means:
- The same
view_directionmust be used for all subsequentopenpbr_eval(),openpbr_sample(), andopenpbr_pdf()calls on that prepared BSDF - Using a different view direction during evaluation would produce incorrect energy compensation and physically inaccurate results
- When using staged initialization (
openpbr_prepare_volume()+openpbr_prepare_lobes()),openpbr_prepare_lobes()caches the view direction inpreparedautomatically — no separate assignment is needed
Volume Integration
Actual volume integration — random walks, transmittance queries, shadow rays through volumes — is left to the integrator. openpbr_homogeneous_volume.h provides production-tested helpers; see item 5 in the High-Level Architecture section for the full list.
Path Tracing Direction
This BSDF is designed for unidirectional path tracing from camera to lights. Bidirectional methods (photon mapping, light tracing) would require an adjoint BSDF with inverted square IOR scaling for transmission.
Sampling Return Behavior
openpbr_sample() returns void, but indicates success/failure through the pdf output parameter:
pdf > 0: Valid sample generated;light_directionandweightare setpdf == 0: No valid sample; output parameters are undefinedpdfis never negative; it's always ≥ 0
Check the PDF value to determine if sampling succeeded before using the outputs.
The weight accounts for the entire BSDF — effectively MIS across all lobes — whereas sampled_type identifies only the lobe that generated the sample.
Diffuse/Specular Splits
The BSDF functions return BSDF values and weights in OpenPBR_DiffuseSpecular format, which contains diffuse and specular components that together add up to the overall value. In cases where only the overall value is needed, it can be retrieved with openpbr_get_sum_of_diffuse_specular().
RGB Wavelength Sampling
The rgb_wavelengths_nm parameter to openpbr_prepare() specifies the wavelength (in nanometers) associated with each color channel. For standard RGB rendering, pass OpenPBR_BaseRgbWavelengths_nm, or substitute your own representative center wavelengths for the color channels your renderer uses.
Materials with dispersion (wavelength-dependent IOR in dielectrics) or thin-film iridescence (interference that shifts with wavelength) benefit from stochastically-sampled wavelengths instead. For each path, draw one random wavelength per channel from that channel's empirical camera spectral sensitivity curve and pass the three samples as rgb_wavelengths_nm. The three channels are evaluated independently, with no path reuse across them. Over many paths, this lets spectral effects integrate smoothly across the visible spectrum: dispersion produces natural rainbow colors rather than discrete RGB bands, and thick thin-film converges to a neutral gray rather than unresolvable fine-scale ripples. Purely wavelength-independent materials are unaffected.
To check whether a material activates these effects before deciding how to sample wavelengths, call openpbr_needs_rgb_wavelengths(resolved_inputs) (in impl/openpbr_bsdf.h).
Departures from the OpenPBR Specification
OpenPBR_ResolvedInputs implements the full OpenPBR 1.1 parameter set with two intentional departures described below.
1. Geometry parameters replaced by pre-orthonormalized basis structs
The OpenPBR 1.1 specification defines four geometry input parameters:
| Spec parameter | Type | Description |
|---|---|---|
geometry_normal |
vector3 |
Surface shading normal |
geometry_tangent |
vector3 |
Surface tangent |
geometry_coat_normal |
vector3 |
Coat-layer shading normal |
geometry_coat_tangent |
vector3 |
Coat-layer tangent |
This implementation replaces those four parameters with two pre-orthonormalized OpenPBR_Basis structs:
| Struct field | Encodes |
|---|---|
geometry_basis |
geometry_normal, geometry_tangent, and the derived bitangent |
geometry_coat_basis |
geometry_coat_normal, geometry_coat_tangent, and the derived coat bitangent |
Callers must supply orthonormalized bases. openpbr_make_basis() (in openpbr_basis.h) provides overloads that accept (normal), (normal, tangent, handedness), or (normal, tangent, bitangent) and perform orthonormalization via modified Gram–Schmidt.
This representation is used because the BSDF performs numerous dot products, cross products, and coordinate-frame changes in local space. Storing a pre-orthonormalized frame eliminates repeated normalization inside the hot path and ensures the local frame is consistent across all lobes regardless of floating-point rounding in the caller's tangent computation.
2. Anisotropy rotation: (cos θ, sin θ) extension (not in OpenPBR spec)
OpenPBR 1.1 defines specular_roughness_anisotropy and coat_roughness_anisotropy (anisotropy magnitude in [0, 1]), but provides no rotation angle parameter. This implementation adds two optional extension fields:
| Extension field | Type | Default | Description |
|---|---|---|---|
specular_anisotropy_rotation_cos_sin |
vec2 |
(1, 0) |
(cos θ, sin θ) of the specular anisotropy rotation |
coat_anisotropy_rotation_cos_sin |
vec2 |
(1, 0) |
(cos θ, sin θ) of the coat anisotropy rotation |
Both default to (1, 0) = (cos 0°, sin 0°), which is a no-op equivalent to having no rotation at all. The vec2 does not need to be unit-length (as can happen after texture filtering); the BSDF normalizes it internally and treats (0, 0) as no rotation.
This representation is used for two main reasons:
- Texture-filtering correctness. A scalar angle texture has a hard discontinuity at the 0°/360° wrap boundary: two texels that are nearly identical in angle would be filtered toward 180° across the seam. Storing (cos θ, sin θ) keeps both components smooth and continuous everywhere, so bilinear or trilinear filtering across that boundary produces the correct interpolated rotation.
- Computational efficiency. The BSDF uses the rotation directly as (cos, sin) to rotate the anisotropy tangent frame, so storing it in that form avoids a per-shading-point trig conversion from an angle representation.
When sourcing rotation from a scalar angle (e.g., a material parameter rather than a texture), encode it with vec2(cos(angle_radians), sin(angle_radians)).
Known Limitations
Thin Film with Thin-Walled Geometry
Thin-film iridescence and thin-walled transmission are not currently compatible: when geometry_thin_walled is enabled, iridescence is not visible on the transmission component. (Thin-walled subsurface scattering is unaffected and shows iridescence normally.)
Note that thin-walled mode is not the right tool for soap-bubble or iridescent membrane effects regardless of this limitation — it models an incoherent two-surface windowpane (suited to thick glass), not a nanometer-scale interference coating. For those effects, use a closed solid surface (geometry_thin_walled = false) with the interior specular IOR set to 1.0 (air) and thin film enabled.
CUDA Backend: Vector Types
The CUDA interop header aliases vec2/vec3/vec4 to CUDA's float2/float3/float4 plain structs, which lack the constructors and operators the OpenPBR codebase relies on. As a workaround, set OPENPBR_USE_CUSTOM_VEC_TYPES=1 and provide compatible vector types before including openpbr.h.
Contribution and Roadmap
We welcome issues and pull requests, for example:
- Enhancements to the simple demo app (with the goal of keeping it minimal, self-contained, and readable — it illustrates how to use the BSDF API, not how to build a renderer; more complex tests belong in a separate program)
- Expanded unit tests for evaluation, sampling, and energy conservation
- Testing of the CUDA backend, which hasn't yet been used in production code
- Testing of the Slang backend in HLSL-style pipelines (and, if needed, adapting the Slang interop aliases or proposing a dedicated HLSL interop header)
Planned or potential future work:
- Specialization constants currently cover four per-lobe feature toggles (
EnableSheenAndCoat,EnableDispersion,EnableTranslucency,EnableMetallic), all defaulting totrue; more may be added (e.g., for thin-film and thin-wall) to cover additional lobes - Reciprocity may be traded for better energy conservation in a future version
- 1D texture sampler support: energy tables with a single dimension are currently stored as thin 2D textures (1 × N); using a real 1D sampler when the platform supports it would reduce sampler overhead
- Implementing OpenPBR 1.2
- Continued iteration on the implementation — some naming conventions and API details may evolve as this is live production code
When contributing code, please follow the existing style: snake_case for names, const on every variable and parameter that won't be reassigned, and descriptive full-word names — no nonstandard abbreviations or single-letter variables except in tightly scoped math contexts.
Please refer to CONTRIBUTING.md for details and guidelines.
License
This code is released under the Apache License 2.0.