Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
ccl_device ccl_private ShaderClosure *closure_alloc(ccl_private ShaderData *sd,
const uint size,
ClosureType type,
Spectrum weight)
{
kernel_assert(size <= sizeof(ShaderClosure));
(void)size;
if (sd->num_closure_left == 0) {
return nullptr;
}
ccl_private ShaderClosure *sc = &sd->closure[sd->num_closure];
sc->type = type;
sc->weight = weight;
sd->num_closure++;
sd->num_closure_left--;
return sc;
}
ccl_device ccl_private void *closure_alloc_extra(ccl_private ShaderData *sd, const int size)
{
/* Allocate extra space for closure that need more parameters. We allocate
* in chunks of sizeof(ShaderClosure) starting from the end of the closure
* array.
*
* This lets us keep the same fast array iteration over closures, as we
* found linked list iteration and iteration with skipping to be slower. */
const int num_extra = ((size + sizeof(ShaderClosure) - 1) / sizeof(ShaderClosure));
if (num_extra > sd->num_closure_left) {
/* Remove previous closure if it was allocated. */
sd->num_closure--;
sd->num_closure_left++;
return nullptr;
}
sd->num_closure_left -= num_extra;
return (ccl_private void *)(sd->closure + sd->num_closure + sd->num_closure_left);
}
ccl_device_inline float closure_sample_weight(const int flag, ccl_private Spectrum &weight)
{
kernel_assert(isfinite_safe(weight));
/* No negative weights allowed. */
weight = max(weight, zero_float3());
const float sample_weight = fabsf(average(weight));
/* Do not perform weight cutoff for volume shaders, because large volume with low density could
* still contribute significantly to the scene. It should be up to the volume shader to decide
* the cutoff. */
/* Use comparison this way to help dealing with non-finite weight: if the average is not finite
* we will not allocate new closure. */
if ((sample_weight >= CLOSURE_WEIGHT_CUTOFF) || (flag & SD_IS_VOLUME_SHADER_EVAL)) {
return sample_weight;
}
return 0.0f;
}
ccl_device_inline ccl_private ShaderClosure *bsdf_alloc(ccl_private ShaderData *sd,
const int size,
Spectrum weight)
{
const float sample_weight = closure_sample_weight(sd->flag, weight);
if (!(sample_weight > 0.0f)) {
return nullptr;
}
ccl_private ShaderClosure *sc = closure_alloc(sd, size, CLOSURE_NONE_ID, weight);
if (!sc) {
return nullptr;
}
sc->sample_weight = sample_weight;
return sc;
}
/* Allocate BSDF closures that are possibly used for emission. */
template<class Bsdf>
ccl_device_inline ccl_private Bsdf *bsdf_alloc_maybe_emission(ccl_private ShaderData *sd,
ccl_private Bsdf *bsdf,
const uint32_t path_flag,
Spectrum weight)
{
if (path_flag & PATH_RAY_EMISSION) {
/* When evaluating emission we don't allocate closures, but we still need a valid closure to
* compute the weight. */
const float sample_weight = closure_sample_weight(sd->flag, weight);
if (!(sample_weight > 0.0f)) {
return nullptr;
}
bsdf->weight = weight;
bsdf->sample_weight = sample_weight;
}
else {
bsdf = (ccl_private Bsdf *)bsdf_alloc(sd, sizeof(Bsdf), weight);
}
return bsdf;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,725 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
// clang-format off
#include "kernel/closure/bsdf_ashikhmin_velvet.h"
#include "kernel/closure/bsdf_diffuse.h"
#include "kernel/closure/bsdf_oren_nayar.h"
#include "kernel/closure/bsdf_phong_ramp.h"
#include "kernel/closure/bsdf_diffuse_ramp.h"
#include "kernel/closure/bsdf_microfacet.h"
#include "kernel/closure/bsdf_burley.h"
#include "kernel/closure/bsdf_sheen.h"
#include "kernel/closure/bsdf_transparent.h"
#include "kernel/closure/bsdf_ray_portal.h"
#include "kernel/closure/bsdf_ashikhmin_shirley.h"
#include "kernel/closure/bsdf_toon.h"
#include "kernel/closure/bsdf_hair.h"
#include "kernel/closure/bsdf_principled_hair_chiang.h"
#include "kernel/closure/bsdf_principled_hair_huang.h"
// clang-format on
CCL_NAMESPACE_BEGIN
/* Returns the square of the roughness of the closure if it has roughness,
* 0 for singular closures and 1 otherwise. */
ccl_device_inline float bsdf_get_specular_roughness_squared(const ccl_private ShaderClosure *sc)
{
if (CLOSURE_IS_BSDF_SINGULAR(sc->type)) {
return 0.0f;
}
if (CLOSURE_IS_BSDF_MICROFACET(sc->type)) {
ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)sc;
return bsdf->alpha_x * bsdf->alpha_y;
}
return 1.0f;
}
ccl_device_inline float bsdf_get_roughness_pass_squared(const ccl_private ShaderClosure *sc)
{
if (sc->type == CLOSURE_BSDF_OREN_NAYAR_ID || sc->type == CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID) {
ccl_private OrenNayarBsdf *bsdf = (ccl_private OrenNayarBsdf *)sc;
return sqr(sqr(bsdf->param.roughness));
}
/* For the Principled BSDF, we want the Roughness pass to return the value that
* was set in the node. However, this value doesn't affect all closures (e.g.
* diffuse), so skip those that don't really have a concept of roughness. */
if (CLOSURE_IS_BSDF_DIFFUSE(sc->type)) {
return -1.0f;
}
return bsdf_get_specular_roughness_squared(sc);
}
/* Widen the compact ray differential dD after a non-specular bounce so that
* texture mip selection on subsequent hits reflects the BSDF lobe's angular
* spread. This significantly save memory, and is needed to make image cache
* memory usage scale with render tile size rather than overall resolution.
*
* This must be done consistently between next event estimation and forward
* sampling for both to converge to the same result for MIS. This is not just
* a theoretical concern, but can otherwise lead to seams.
*
* To achieve that, the sampled roughness is computed as a MIS weighted
* average. This makes it so directions with high contribution from sharp
* BSDFs have a lower roughness, as they will have a high MIS weight. */
ccl_device_forceinline float bsdf_widen_dD(const float prev_dD, const float avg_roughness_squared)
{
if (!(avg_roughness_squared > 0.0f)) {
return prev_dD;
}
return max(prev_dD, sqrtf(avg_roughness_squared));
}
/* An additional term to smooth illumination on grazing angles when using bump mapping
* based on "A Microfacet-Based Shadowing Function to Solve the Bump Terminator Problem"
* by Alejandro Conty Estevez, Pascal Lecocq, and Clifford Stein. It preserves detail
* close to the shadow terminator, and doesn't "wash out" intermediate bumps using a
* Cook-Torrance GGX function for shading. */
ccl_device_inline float bump_shadowing_term(const ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 I,
const bool is_eval)
{
if (isequal(sc->N, sd->N)) {
return 1.0f;
}
/* Smoothing doesn't apply to curve geometry. */
if (sd->type & PRIMITIVE_CURVE) {
return 1.0f;
}
/* In order to avoid artifacts at the shadow terminator when using smooth normals,
* the BSDF evaluation functions allow for light leaking through the actual geometry
* and only checks that the directions are in the correct hemisphere w.r.t. the
* shading normal.
* However, when using bump/normal mapping, this can lead to light leaking not just
* "around" the shadow terminator, but to the rear side of supposedly opaque geometry.
* In order to detect this case, we can ensure that the direction is also valid w.r.t.
* the smoothed (but non-bump-mapped) normal `sd->N` (or `Ns` for short below).
*
* `dot(Ns, I) * dot(Ns, N)` tells us if I and N are on the same side of the smoothed geometry.
* If incoming(I) and normal(N) are on the same side we reject refractions, `dot(N, I) < 0`.
* If they are on different sides we reject reflections, `dot(N, I) > 0`. */
const float cosNsI = dot(sd->N, I);
const float cosNsN = dot(sd->N, sc->N);
const float cosNI = dot(sc->N, I);
const bool is_diffuse = CLOSURE_IS_BSDF_DIFFUSE(sc->type);
if (cosNsI * cosNsN * cosNI < 0.0f && (is_eval || is_diffuse)) {
return 0.0f;
}
/* The above test applies to all closures, but the softening only applies to diffuse ones. */
if (!is_diffuse) {
return 1.0f;
}
/* When bump map correction is not used do skip the smoothing. */
if ((sd->flag & SD_USE_BUMP_MAP_CORRECTION) == 0) {
return 1.0f;
}
/* Get absolute incoming and shader normal deviation from smoothed normal, then clamp. */
const float cos_i = fabsf(cosNsI);
const float cos_d = fabsf(cosNsN);
if (cos_d >= 1.0f || cos_i >= 1.0f) {
return 1.0f;
}
if (cos_i < 1e-6f) {
return 0.0f;
}
/* Get GGX shading values for final smoothing. */
const float tan2_d = 1.0f / sqr(cos_d) - 1.0f;
const float bump_alpha2 = saturatef(0.125f * tan2_d);
/* Return smoothed value to avoid discontinuity at perpendicular angle. */
return bsdf_G<MicrofacetType::GGX>(bump_alpha2, cos_i);
}
ccl_device_inline float shift_cos_in(float cos_in, const float frequency_multiplier)
{
/* Shadow terminator workaround, taken from Appleseed.
* SPDX-License-Identifier: MIT
* Copyright (c) 2019 Francois Beaune, The appleseedhq Organization */
cos_in = min(cos_in, 1.0f);
const float angle = fast_acosf(cos_in);
const float val = max(cosf(angle * frequency_multiplier), 0.0f) / cos_in;
return val;
}
ccl_device_inline bool bsdf_is_transmission(const ccl_private ShaderClosure *sc, const float3 wo)
{
return dot(sc->N, wo) < 0.0f;
}
ccl_device_inline int bsdf_sample(KernelGlobals kg,
ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness,
ccl_private float *eta)
{
/* For curves use the smooth normal, particularly for ribbons the geometric
* normal gives too much darkening otherwise. */
*eval = zero_spectrum();
*pdf = 0.f;
int label = LABEL_NONE;
const float3 Ng = (sd->type & PRIMITIVE_CURVE) ? sc->N : sd->Ng;
const float2 rand_xy = make_float2(rand);
switch (sc->type) {
case CLOSURE_BSDF_DIFFUSE_ID:
label = bsdf_diffuse_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
#if defined(__SVM__) || defined(__OSL__)
case CLOSURE_BSDF_OREN_NAYAR_ID:
label = bsdf_oren_nayar_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness, eta);
break;
case CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID:
label = bsdf_rough_translucent_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness, eta);
break;
# ifdef __OSL__
case CLOSURE_BSDF_BURLEY_ID:
label = bsdf_burley_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_PHONG_RAMP_ID:
label = bsdf_phong_ramp_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
case CLOSURE_BSDF_DIFFUSE_RAMP_ID:
label = bsdf_diffuse_ramp_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
# endif
case CLOSURE_BSDF_TRANSLUCENT_ID:
label = bsdf_translucent_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_TRANSPARENT_ID:
label = bsdf_transparent_sample(sc, Ng, sd->wi, eval, wo, pdf);
*sampled_roughness = zero_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_RAY_PORTAL_ID:
/* ray portals are not handled by the BSDF code, we should never get here */
kernel_assert(false);
break;
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
label = bsdf_microfacet_ggx_sample(
kg, sc, Ng, sd->wi, rand, eval, wo, pdf, sampled_roughness, eta);
break;
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
label = bsdf_thin_glass_transmission_sample(
kg, sc, Ng, sd->wi, rand, eval, wo, pdf, sampled_roughness, eta);
break;
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID:
label = bsdf_microfacet_beckmann_sample(
kg, sc, Ng, sd->wi, rand, eval, wo, pdf, sampled_roughness, eta);
break;
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID:
label = bsdf_ashikhmin_shirley_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
case CLOSURE_BSDF_ASHIKHMIN_VELVET_ID:
label = bsdf_ashikhmin_velvet_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_DIFFUSE_TOON_ID:
label = bsdf_diffuse_toon_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_GLOSSY_TOON_ID:
label = bsdf_glossy_toon_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
// double check if this is valid
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_REFLECTION_ID:
label = bsdf_hair_reflection_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_TRANSMISSION_ID:
label = bsdf_hair_transmission_sample(
sc, Ng, sd->wi, rand_xy, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
label = bsdf_hair_chiang_sample(kg, sc, sd, rand, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
label = bsdf_hair_huang_sample(kg, sc, sd, rand, eval, wo, pdf, sampled_roughness);
*eta = 1.0f;
break;
# endif
case CLOSURE_BSDF_SHEEN_ID:
label = bsdf_sheen_sample(sc, Ng, sd->wi, rand_xy, eval, wo, pdf);
*sampled_roughness = one_float2();
*eta = 1.0f;
break;
#endif
default:
label = LABEL_NONE;
break;
}
/* Test if BSDF sample should be treated as transparent for background. */
if (label & LABEL_TRANSMIT) {
const float threshold_squared = kernel_data.background.transparent_roughness_squared_threshold;
if (threshold_squared >= 0.0f && !(label & LABEL_DIFFUSE)) {
if (bsdf_get_specular_roughness_squared(sc) <= threshold_squared) {
label |= LABEL_TRANSMIT_TRANSPARENT;
}
}
}
else if (label != LABEL_NONE) {
/* Shadow terminator offset. */
const float frequency_multiplier =
kernel_data_fetch(objects, sd->object).shadow_terminator_shading_offset;
if (frequency_multiplier > 1.0f) {
const float cosNO = dot(*wo, sc->N);
*eval *= shift_cos_in(cosNO, frequency_multiplier);
}
*eval *= bump_shadowing_term(sd, sc, *wo, false);
}
#ifdef WITH_CYCLES_DEBUG
kernel_assert(*pdf >= 0.0f);
kernel_assert(eval->x >= 0.0f && eval->y >= 0.0f && eval->z >= 0.0f);
#endif
return label;
}
ccl_device_inline void bsdf_roughness_eta(const ccl_private ShaderClosure *sc,
const float3 wo,
ccl_private float2 *roughness,
ccl_private float *eta)
{
#ifdef __SVM__
float alpha = 1.0f;
#endif
switch (sc->type) {
case CLOSURE_BSDF_DIFFUSE_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
#ifdef __SVM__
case CLOSURE_BSDF_OREN_NAYAR_ID:
case CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
# ifdef __OSL__
case CLOSURE_BSDF_BURLEY_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_PHONG_RAMP_ID:
alpha = phong_ramp_exponent_to_roughness(((const ccl_private PhongRampBsdf *)sc)->exponent);
*roughness = make_float2(alpha, alpha);
*eta = 1.0f;
break;
case CLOSURE_BSDF_DIFFUSE_RAMP_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
# endif
case CLOSURE_BSDF_TRANSLUCENT_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_TRANSPARENT_ID:
case CLOSURE_BSDF_RAY_PORTAL_ID:
*roughness = zero_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID: {
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
*roughness = make_float2(bsdf->alpha_x, bsdf->alpha_y);
*eta = (bsdf_is_transmission(sc, wo)) ? bsdf->ior : 1.0f;
break;
}
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID: {
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
*roughness = make_float2(bsdf->alpha_x, bsdf->alpha_y);
*eta = 1.0f;
break;
}
case CLOSURE_BSDF_ASHIKHMIN_VELVET_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_DIFFUSE_TOON_ID:
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_GLOSSY_TOON_ID:
// double check if this is valid
*roughness = one_float2();
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_REFLECTION_ID:
*roughness = make_float2(((ccl_private HairBsdf *)sc)->roughness1,
((ccl_private HairBsdf *)sc)->roughness2);
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_TRANSMISSION_ID:
*roughness = make_float2(((ccl_private HairBsdf *)sc)->roughness1,
((ccl_private HairBsdf *)sc)->roughness2);
*eta = 1.0f;
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
alpha = ((ccl_private ChiangHairBSDF *)sc)->m0_roughness;
*roughness = make_float2(alpha, alpha);
*eta = 1.0f;
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
alpha = ((ccl_private HuangHairBSDF *)sc)->roughness;
*roughness = make_float2(alpha, alpha);
*eta = 1.0f;
break;
# endif
case CLOSURE_BSDF_SHEEN_ID:
alpha = ((ccl_private SheenBsdf *)sc)->roughness;
*roughness = make_float2(alpha, alpha);
*eta = 1.0f;
break;
#endif
default:
*roughness = one_float2();
*eta = 1.0f;
break;
}
}
ccl_device_inline int bsdf_label(const KernelGlobals kg,
const ccl_private ShaderClosure *sc,
const float3 wo)
{
/* For curves use the smooth normal, particularly for ribbons the geometric
* normal gives too much darkening otherwise. */
int label;
switch (sc->type) {
case CLOSURE_BSDF_DIFFUSE_ID:
case CLOSURE_BSSRDF_BURLEY_ID:
case CLOSURE_BSSRDF_RANDOM_WALK_ID:
case CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID:
case CLOSURE_BSSRDF_RANDOM_WALK_LEGACY_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
#ifdef __SVM__
case CLOSURE_BSDF_OREN_NAYAR_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
# ifdef __OSL__
case CLOSURE_BSDF_BURLEY_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
case CLOSURE_BSDF_PHONG_RAMP_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_DIFFUSE_RAMP_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
# endif
case CLOSURE_BSDF_TRANSLUCENT_ID:
case CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID:
label = LABEL_TRANSMIT | LABEL_DIFFUSE;
break;
case CLOSURE_BSDF_TRANSPARENT_ID:
label = LABEL_TRANSMIT | LABEL_TRANSPARENT;
break;
case CLOSURE_BSDF_RAY_PORTAL_ID:
label = LABEL_TRANSMIT | LABEL_RAY_PORTAL;
break;
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID: {
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
label = ((bsdf_is_transmission(sc, wo)) ? LABEL_TRANSMIT : LABEL_REFLECT) |
((bsdf_microfacet_eval_flag(bsdf)) ? LABEL_GLOSSY : LABEL_SINGULAR);
break;
}
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
label = LABEL_TRANSMIT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_ASHIKHMIN_VELVET_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
case CLOSURE_BSDF_DIFFUSE_TOON_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
case CLOSURE_BSDF_GLOSSY_TOON_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_HAIR_REFLECTION_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
case CLOSURE_BSDF_HAIR_TRANSMISSION_ID:
label = LABEL_TRANSMIT | LABEL_GLOSSY;
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
if (bsdf_is_transmission(sc, wo)) {
label = LABEL_TRANSMIT | LABEL_GLOSSY;
}
else {
label = LABEL_REFLECT | LABEL_GLOSSY;
}
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
label = LABEL_REFLECT | LABEL_GLOSSY;
break;
# endif
case CLOSURE_BSDF_SHEEN_ID:
label = LABEL_REFLECT | LABEL_DIFFUSE;
break;
#endif
default:
label = LABEL_NONE;
break;
}
/* Test if BSDF sample should be treated as transparent for background. */
if (label & LABEL_TRANSMIT) {
const float threshold_squared = kernel_data.background.transparent_roughness_squared_threshold;
if (threshold_squared >= 0.0f) {
if (bsdf_get_specular_roughness_squared(sc) <= threshold_squared) {
label |= LABEL_TRANSMIT_TRANSPARENT;
}
}
}
return label;
}
#ifndef __KERNEL_CUDA__
ccl_device
#else
ccl_device_inline
#endif
Spectrum
bsdf_eval(KernelGlobals kg,
ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 wo,
ccl_private float *pdf)
{
Spectrum eval = zero_spectrum();
*pdf = 0.f;
const float bump_shadowing = bump_shadowing_term(sd, sc, wo, true);
if (bump_shadowing == 0.0f) {
return zero_spectrum();
}
switch (sc->type) {
case CLOSURE_BSDF_DIFFUSE_ID:
eval = bsdf_diffuse_eval(sc, sd->wi, wo, pdf);
break;
#if defined(__SVM__) || defined(__OSL__)
case CLOSURE_BSDF_OREN_NAYAR_ID:
eval = bsdf_oren_nayar_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID:
eval = bsdf_rough_translucent_eval(sc, sd->wi, wo, pdf);
break;
# ifdef __OSL__
case CLOSURE_BSDF_BURLEY_ID:
eval = bsdf_burley_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_PHONG_RAMP_ID:
eval = bsdf_phong_ramp_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_DIFFUSE_RAMP_ID:
eval = bsdf_diffuse_ramp_eval(sc, sd->wi, wo, pdf);
break;
# endif
case CLOSURE_BSDF_TRANSLUCENT_ID:
eval = bsdf_translucent_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_TRANSPARENT_ID:
eval = bsdf_transparent_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_RAY_PORTAL_ID:
eval = bsdf_ray_portal_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
eval = bsdf_microfacet_ggx_eval(kg, sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
eval = bsdf_thin_glass_transmission_eval(kg, sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID:
eval = bsdf_microfacet_beckmann_eval(kg, sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID:
eval = bsdf_ashikhmin_shirley_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_ASHIKHMIN_VELVET_ID:
eval = bsdf_ashikhmin_velvet_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_DIFFUSE_TOON_ID:
eval = bsdf_diffuse_toon_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_GLOSSY_TOON_ID:
eval = bsdf_glossy_toon_eval(sc, sd->wi, wo, pdf);
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
eval = bsdf_hair_chiang_eval(kg, sd, sc, wo, pdf);
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
eval = bsdf_hair_huang_eval(kg, sd, sc, wo, pdf);
break;
# endif
case CLOSURE_BSDF_HAIR_REFLECTION_ID:
eval = bsdf_hair_reflection_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_HAIR_TRANSMISSION_ID:
eval = bsdf_hair_transmission_eval(sc, sd->wi, wo, pdf);
break;
case CLOSURE_BSDF_SHEEN_ID:
eval = bsdf_sheen_eval(sc, sd->wi, wo, pdf);
break;
#endif
default:
break;
}
eval *= bump_shadowing;
/* Shadow terminator offset. */
const float frequency_multiplier =
kernel_data_fetch(objects, sd->object).shadow_terminator_shading_offset;
if (frequency_multiplier > 1.0f) {
const float cosNO = dot(wo, sc->N);
if (cosNO >= 0.0f) {
eval *= shift_cos_in(cosNO, frequency_multiplier);
}
}
#ifdef WITH_CYCLES_DEBUG
kernel_assert(*pdf >= 0.0f);
kernel_assert(eval.x >= 0.0f && eval.y >= 0.0f && eval.z >= 0.0f);
#endif
return eval;
}
ccl_device void bsdf_blur(ccl_private ShaderClosure *sc, const float roughness)
{
/* TODO: do we want to blur volume closures? */
#if defined(__SVM__) || defined(__OSL__)
switch (sc->type) {
case CLOSURE_BSDF_MICROFACET_GGX_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID:
case CLOSURE_BSDF_THIN_GLASS_TRANSMISSION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID:
case CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID:
/* TODO: Recompute energy preservation after blur? */
bsdf_microfacet_blur(sc, roughness);
break;
case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID:
bsdf_ashikhmin_shirley_blur(sc, roughness);
break;
# ifdef __PRINCIPLED_HAIR__
case CLOSURE_BSDF_HAIR_CHIANG_ID:
bsdf_hair_chiang_blur(sc, roughness);
break;
case CLOSURE_BSDF_HAIR_HUANG_ID:
bsdf_hair_huang_blur(sc, roughness);
break;
# endif
default:
break;
}
#endif
}
ccl_device_inline Spectrum bsdf_albedo(KernelGlobals kg,
const ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const bool reflection,
const bool transmission)
{
Spectrum albedo = sc->weight;
/* Some closures include additional components such as Fresnel terms that cause their albedo to
* be below 1. The point of this function is to return a best-effort estimation of their albedo,
* meaning the amount of reflected/refracted light that would be expected when illuminated by a
* uniform white background.
* This is used for the denoising albedo pass and diffuse/glossy/transmission color passes.
* NOTE: This should always match the sample_weight of the closure - as in, if there's an albedo
* adjustment in here, the sample_weight should also be reduced accordingly.
* TODO(lukas): Consider calling this function to determine the sample_weight? Would be a bit of
* extra overhead though. */
#if defined(__SVM__) || defined(__OSL__)
if (CLOSURE_IS_BSDF_MICROFACET(sc->type)) {
albedo *= bsdf_microfacet_estimate_albedo(
kg, sd->wi, (const ccl_private MicrofacetBsdf *)sc, reflection, transmission);
}
# ifdef __PRINCIPLED_HAIR__
else if (sc->type == CLOSURE_BSDF_HAIR_CHIANG_ID) {
/* TODO(lukas): Principled Hair could also be split into a glossy and a transmission component,
* similar to Glass BSDFs. */
albedo *= bsdf_hair_chiang_albedo(sd, sc);
}
else if (sc->type == CLOSURE_BSDF_HAIR_HUANG_ID) {
albedo *= bsdf_hair_huang_albedo(sd, sc);
}
# endif
#endif
return albedo;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,228 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/*
* ASHIKHMIN SHIRLEY BSDF
*
* Implementation of
* Michael Ashikhmin and Peter Shirley: "An Anisotropic Phong BRDF Model" (2000)
*
* The Fresnel factor is missing to get a separable bsdf (intensity*color), as is
* the case with all other microfacet-based BSDF implementations in Cycles.
*
* Other than that, the implementation directly follows the paper.
*/
#pragma once
#include "kernel/types.h"
#include "kernel/closure/bsdf_microfacet.h"
CCL_NAMESPACE_BEGIN
ccl_device int bsdf_ashikhmin_shirley_setup(ccl_private MicrofacetBsdf *bsdf)
{
bsdf->alpha_x = clamp(bsdf->alpha_x, 1e-4f, 1.0f);
bsdf->alpha_y = clamp(bsdf->alpha_y, 1e-4f, 1.0f);
bsdf->fresnel_type = MicrofacetFresnel::NONE;
bsdf->type = CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device void bsdf_ashikhmin_shirley_blur(ccl_private ShaderClosure *sc, const float roughness)
{
ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)sc;
bsdf->alpha_x = fmaxf(roughness, bsdf->alpha_x);
bsdf->alpha_y = fmaxf(roughness, bsdf->alpha_y);
}
ccl_device_inline float bsdf_ashikhmin_shirley_roughness_to_exponent(const float roughness)
{
return 2.0f / (roughness * roughness) - 2.0f;
}
ccl_device_forceinline Spectrum bsdf_ashikhmin_shirley_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
const float3 N = bsdf->N;
float NdotI = dot(N, wi);
float NdotO = dot(N, wo);
float out = 0.0f;
if (fmaxf(bsdf->alpha_x, bsdf->alpha_y) <= 1e-4f || (NdotI < 0.0f) || (NdotO < 0.0f)) {
*pdf = 0.0f;
return zero_spectrum();
}
NdotI = fmaxf(NdotI, 1e-6f);
NdotO = fmaxf(NdotO, 1e-6f);
const float3 H = normalize(wi + wo);
const float HdotI = fmaxf(fabsf(dot(H, wi)), 1e-6f);
const float HdotN = fmaxf(dot(H, N), 1e-6f);
/* pump from original paper
* (first derivative disc., but cancels the HdotI in the pdf nicely) */
const float pump = 1.0f / fmaxf(1e-6f, (HdotI * fmaxf(NdotI, NdotO)));
/* `pump` from D-BRDF paper. */
// float pump = 1.0f / fmaxf(1e-4f, ((NdotI + NdotO) * (NdotI * NdotO)));
const float n_x = bsdf_ashikhmin_shirley_roughness_to_exponent(bsdf->alpha_x);
const float n_y = bsdf_ashikhmin_shirley_roughness_to_exponent(bsdf->alpha_y);
if (n_x == n_y) {
/* isotropic */
const float e = n_x;
const float lobe = powf(HdotN, e);
const float norm = (n_x + 1.0f) / (8.0f * M_PI_F);
out = NdotO * norm * lobe * pump;
/* this is p_h / 4(H.I) (conversion from `wh measure` to `wi measure`, eq. 8 in paper). */
*pdf = norm * lobe / HdotI;
}
else {
/* anisotropic */
float3 X;
float3 Y;
make_orthonormals_tangent(N, bsdf->T, &X, &Y);
const float HdotX = dot(H, X);
const float HdotY = dot(H, Y);
float lobe;
if (HdotN < 1.0f) {
const float e = (n_x * HdotX * HdotX + n_y * HdotY * HdotY) / (1.0f - HdotN * HdotN);
lobe = powf(HdotN, e);
}
else {
lobe = 1.0f;
}
const float norm = sqrtf((n_x + 1.0f) * (n_y + 1.0f)) / (8.0f * M_PI_F);
out = NdotO * norm * lobe * pump;
*pdf = norm * lobe / HdotI;
}
return make_spectrum(out);
}
ccl_device_inline void bsdf_ashikhmin_shirley_sample_first_quadrant(float n_x,
const float n_y,
const float2 rand,
ccl_private float *phi,
ccl_private float *cos_theta)
{
*phi = atanf(sqrtf((n_x + 1.0f) / (n_y + 1.0f)) * tanf(M_PI_2_F * rand.x));
const float cos_phi = cosf(*phi);
const float sin_phi = sinf(*phi);
*cos_theta = powf(rand.y, 1.0f / (n_x * cos_phi * cos_phi + n_y * sin_phi * sin_phi + 1.0f));
}
ccl_device int bsdf_ashikhmin_shirley_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
const ccl_private MicrofacetBsdf *bsdf = (const ccl_private MicrofacetBsdf *)sc;
*sampled_roughness = make_float2(bsdf->alpha_x, bsdf->alpha_y);
const float3 N = bsdf->N;
int label = LABEL_REFLECT | LABEL_GLOSSY;
const float NdotI = dot(N, wi);
if (!(NdotI > 0.0f)) {
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
const float n_x = bsdf_ashikhmin_shirley_roughness_to_exponent(bsdf->alpha_x);
const float n_y = bsdf_ashikhmin_shirley_roughness_to_exponent(bsdf->alpha_y);
/* get x,y basis on the surface for anisotropy */
float3 X;
float3 Y;
if (n_x == n_y) {
make_orthonormals(N, &X, &Y);
}
else {
make_orthonormals_tangent(N, bsdf->T, &X, &Y);
}
/* sample spherical coords for h in tangent space */
float phi;
float cos_theta;
if (n_x == n_y) {
/* isotropic sampling */
phi = M_2PI_F * rand.x;
cos_theta = powf(rand.y, 1.0f / (n_x + 1.0f));
}
else {
/* anisotropic sampling */
if (rand.x < 0.25f) { /* first quadrant */
rand.x *= 4.0f;
bsdf_ashikhmin_shirley_sample_first_quadrant(n_x, n_y, rand, &phi, &cos_theta);
}
else if (rand.x < 0.5f) { /* second quadrant */
rand.x = 4.0f * (0.5f - rand.x);
bsdf_ashikhmin_shirley_sample_first_quadrant(n_x, n_y, rand, &phi, &cos_theta);
phi = M_PI_F - phi;
}
else if (rand.x < 0.75f) { /* third quadrant */
rand.x = 4.0f * (rand.x - 0.5f);
bsdf_ashikhmin_shirley_sample_first_quadrant(n_x, n_y, rand, &phi, &cos_theta);
phi = M_PI_F + phi;
}
else { /* fourth quadrant */
rand.x = 4.0f * (1.0f - rand.x);
bsdf_ashikhmin_shirley_sample_first_quadrant(n_x, n_y, rand, &phi, &cos_theta);
phi = 2.0f * M_PI_F - phi;
}
}
/* get half vector in tangent space */
const float3 h = spherical_cos_to_direction(cos_theta, phi);
/* half vector to world space */
float3 H = to_global(h, X, Y, N);
const float HdotI = dot(H, wi);
if (HdotI < 0.0f) {
H = -H;
}
/* reflect wi on H to get wo */
*wo = -wi + (2.0f * HdotI) * H;
/* Check hemisphere. */
if (dot(Ng, *wo) < 0.0f) {
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
if (fmaxf(bsdf->alpha_x, bsdf->alpha_y) <= 1e-4f) {
/* Some high number for MIS. */
*pdf = 1e6f;
*eval = make_spectrum(1e6f);
label = LABEL_REFLECT | LABEL_SINGULAR;
}
else {
/* leave the rest to eval */
*eval = bsdf_ashikhmin_shirley_eval(sc, wi, *wo, pdf);
}
return label;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,134 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
struct VelvetBsdf {
SHADER_CLOSURE_BASE;
float sigma;
float invsigma2;
};
static_assert(sizeof(ShaderClosure) >= sizeof(VelvetBsdf), "VelvetBsdf is too large!");
ccl_device int bsdf_ashikhmin_velvet_setup(ccl_private VelvetBsdf *bsdf)
{
const float sigma = fmaxf(bsdf->sigma, 0.01f);
bsdf->invsigma2 = 1.0f / (sigma * sigma);
bsdf->type = CLOSURE_BSDF_ASHIKHMIN_VELVET_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device Spectrum bsdf_ashikhmin_velvet_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private VelvetBsdf *bsdf = (const ccl_private VelvetBsdf *)sc;
const float m_invsigma2 = bsdf->invsigma2;
const float3 N = bsdf->N;
const float cosNI = dot(N, wi);
const float cosNO = dot(N, wo);
if (!(cosNI > 0 && cosNO > 0)) {
*pdf = 0.0f;
return zero_spectrum();
}
const float3 H = normalize(wi + wo);
const float cosNH = dot(N, H);
const float cosHI = fabsf(dot(wi, H));
if (!(fabsf(cosNH) < 1.0f - 1e-5f && cosHI > 1e-5f)) {
*pdf = 0.0f;
return zero_spectrum();
}
float cosNHdivHI = cosNH / cosHI;
cosNHdivHI = fmaxf(cosNHdivHI, 1e-5f);
const float fac1 = 2 * fabsf(cosNHdivHI * cosNI);
const float fac2 = 2 * fabsf(cosNHdivHI * cosNO);
const float sinNH2 = 1 - cosNH * cosNH;
const float sinNH4 = sinNH2 * sinNH2;
const float cotangent2 = (cosNH * cosNH) / sinNH2;
const float D = expf(-cotangent2 * m_invsigma2) * m_invsigma2 * M_1_PI_F / sinNH4;
const float G = fminf(1.0f, fminf(fac1, fac2)); // TODO: derive G from D analytically
const float out = 0.25f * (D * G) / cosNI;
*pdf = 0.5f * M_1_PI_F;
return make_spectrum(out);
}
ccl_device int bsdf_ashikhmin_velvet_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private VelvetBsdf *bsdf = (const ccl_private VelvetBsdf *)sc;
const float m_invsigma2 = bsdf->invsigma2;
const float3 N = bsdf->N;
// we are viewing the surface from above - send a ray out with uniform
// distribution over the hemisphere
sample_uniform_hemisphere(N, rand, wo, pdf);
if (!(dot(Ng, *wo) > 0)) {
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
const float3 H = normalize(wi + *wo);
const float cosNI = dot(N, wi);
const float cosNO = dot(N, *wo);
const float cosHI = fabsf(dot(wi, H));
const float cosNH = dot(N, H);
if (!(cosNI > 1e-5f && fabsf(cosNH) < 1.0f - 1e-5f && cosHI > 1e-5f)) {
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
float cosNHdivHI = cosNH / cosHI;
cosNHdivHI = fmaxf(cosNHdivHI, 1e-5f);
const float fac1 = 2 * fabsf(cosNHdivHI * cosNI);
const float fac2 = 2 * fabsf(cosNHdivHI * cosNO);
const float sinNH2 = 1 - cosNH * cosNH;
const float sinNH4 = sinNH2 * sinNH2;
const float cotangent2 = (cosNH * cosNH) / sinNH2;
const float D = expf(-cotangent2 * m_invsigma2) * m_invsigma2 * M_1_PI_F / sinNH4;
const float G = fminf(1.0f, fminf(fac1, fac2)); // TODO: derive G from D analytically
const float power = 0.25f * (D * G) / cosNI;
*eval = make_spectrum(power);
return LABEL_REFLECT | LABEL_DIFFUSE;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2009-2025 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2025 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/closure/bsdf_util.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
#ifdef __OSL__
struct BurleyBsdf {
SHADER_CLOSURE_BASE;
float roughness;
};
static_assert(sizeof(ShaderClosure) >= sizeof(BurleyBsdf), "BurleyBsdf is too large!");
ccl_device Spectrum bsdf_burley_get_intensity(const float roughness,
const float3 n,
const float3 v,
const float3 l)
{
const float NdotL = dot(n, l);
const float NdotV = dot(n, v);
const float fl = schlick_fresnel(NdotL);
const float fv = schlick_fresnel(NdotV);
const float LdotH = dot(l, normalize(l + v));
const float F90 = 0.5f + (2.0f * roughness * LdotH * LdotH);
return make_spectrum(M_1_PI_F * NdotL * mix(1.0f, F90, fl) * mix(1.0f, F90, fv));
}
ccl_device int bsdf_burley_setup(ccl_private BurleyBsdf *bsdf, const float roughness)
{
bsdf->type = CLOSURE_BSDF_BURLEY_ID;
bsdf->roughness = saturatef(roughness);
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device Spectrum bsdf_burley_eval(ccl_private const ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
ccl_private const BurleyBsdf *bsdf = (ccl_private const BurleyBsdf *)sc;
const float cosNO = dot(bsdf->N, wo);
if (cosNO > 0.0f) {
*pdf = cosNO * M_1_PI_F;
return bsdf_burley_get_intensity(bsdf->roughness, bsdf->N, wi, wo);
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_burley_sample(ccl_private const ShaderClosure *sc,
float3 Ng,
float3 wi,
float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
ccl_private const BurleyBsdf *bsdf = (ccl_private const BurleyBsdf *)sc;
float3 N = bsdf->N;
// distribution over the hemisphere
sample_cos_hemisphere(N, rand, wo, pdf);
if (dot(Ng, *wo) > 0.0f) {
*eval = bsdf_burley_get_intensity(bsdf->roughness, bsdf->N, wi, *wo);
}
else {
*pdf = 0.0f;
*eval = zero_spectrum();
}
return LABEL_REFLECT | LABEL_DIFFUSE;
}
#endif /* __OSL__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,127 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/alloc.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
struct DiffuseBsdf {
SHADER_CLOSURE_BASE;
};
static_assert(sizeof(ShaderClosure) >= sizeof(DiffuseBsdf), "DiffuseBsdf is too large!");
/* DIFFUSE */
ccl_device void bsdf_diffuse_setup(ccl_private ShaderData *sd,
const float3 N,
const Spectrum weight)
{
ccl_private DiffuseBsdf *bsdf = (ccl_private DiffuseBsdf *)bsdf_alloc(
sd, sizeof(DiffuseBsdf), weight);
if (bsdf) {
bsdf->N = N;
bsdf->type = CLOSURE_BSDF_DIFFUSE_ID;
sd->flag |= (SD_BSDF | SD_BSDF_HAS_EVAL);
}
}
ccl_device Spectrum bsdf_diffuse_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private DiffuseBsdf *bsdf = (const ccl_private DiffuseBsdf *)sc;
const float3 N = bsdf->N;
const float cosNO = fmaxf(dot(N, wo), 0.0f) * M_1_PI_F;
*pdf = cosNO;
return make_spectrum(cosNO);
}
ccl_device int bsdf_diffuse_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private DiffuseBsdf *bsdf = (const ccl_private DiffuseBsdf *)sc;
const float3 N = bsdf->N;
// distribution over the hemisphere
sample_cos_hemisphere(N, rand, wo, pdf);
if (dot(Ng, *wo) > 0.0f) {
*eval = make_spectrum(*pdf);
}
else {
*pdf = 0.0f;
*eval = zero_spectrum();
}
return LABEL_REFLECT | LABEL_DIFFUSE;
}
/* TRANSLUCENT */
ccl_device void bsdf_translucent_setup(ccl_private ShaderData *sd,
const float3 N,
const Spectrum weight)
{
ccl_private DiffuseBsdf *bsdf = (ccl_private DiffuseBsdf *)bsdf_alloc(
sd, sizeof(DiffuseBsdf), weight);
if (bsdf) {
bsdf->N = N;
bsdf->type = CLOSURE_BSDF_TRANSLUCENT_ID;
sd->flag |= (SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION);
}
}
ccl_device Spectrum bsdf_translucent_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private DiffuseBsdf *bsdf = (const ccl_private DiffuseBsdf *)sc;
const float3 N = bsdf->N;
const float cosNO = fmaxf(-dot(N, wo), 0.0f) * M_1_PI_F;
*pdf = cosNO;
return make_spectrum(cosNO);
}
ccl_device int bsdf_translucent_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private DiffuseBsdf *bsdf = (const ccl_private DiffuseBsdf *)sc;
const float3 N = bsdf->N;
// we are viewing the surface from the right side - send a ray out with cosine
// distribution over the hemisphere
sample_cos_hemisphere(-N, rand, wo, pdf);
if (dot(Ng, *wo) < 0) {
*eval = make_spectrum(*pdf);
}
else {
*pdf = 0;
*eval = zero_spectrum();
}
return LABEL_TRANSMIT | LABEL_DIFFUSE;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/sample/mapping.h"
#include "kernel/util/colorspace.h"
CCL_NAMESPACE_BEGIN
#ifdef __OSL__
struct DiffuseRampBsdf {
SHADER_CLOSURE_BASE;
ccl_private float3 *colors;
};
static_assert(sizeof(ShaderClosure) >= sizeof(DiffuseRampBsdf), "DiffuseRampBsdf is too large!");
ccl_device float3 bsdf_diffuse_ramp_get_color(const float3 colors[8], float pos)
{
const int MAXCOLORS = 8;
const float npos = pos * (float)(MAXCOLORS - 1);
const int ipos = float_to_int(npos);
if (ipos < 0) {
return colors[0];
}
if (ipos >= (MAXCOLORS - 1)) {
return colors[MAXCOLORS - 1];
}
const float offset = npos - (float)ipos;
return colors[ipos] * (1.0f - offset) + colors[ipos + 1] * offset;
}
ccl_device int bsdf_diffuse_ramp_setup(DiffuseRampBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_DIFFUSE_RAMP_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device void bsdf_diffuse_ramp_blur(ccl_private ShaderClosure * /*sc*/,
const float /*roughness*/)
{
}
ccl_device Spectrum bsdf_diffuse_ramp_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const DiffuseRampBsdf *bsdf = (const DiffuseRampBsdf *)sc;
const float3 N = bsdf->N;
const float cosNO = fmaxf(dot(N, wo), 0.0f);
if (cosNO >= 0.0f) {
*pdf = cosNO * M_1_PI_F;
return rgb_to_spectrum(bsdf_diffuse_ramp_get_color(bsdf->colors, cosNO) * M_1_PI_F);
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_diffuse_ramp_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const DiffuseRampBsdf *bsdf = (const DiffuseRampBsdf *)sc;
const float3 N = bsdf->N;
// distribution over the hemisphere
sample_cos_hemisphere(N, rand, wo, pdf);
if (dot(Ng, *wo) > 0.0f) {
*eval = rgb_to_spectrum(bsdf_diffuse_ramp_get_color(bsdf->colors, *pdf * M_PI_F) * M_1_PI_F);
}
else {
*pdf = 0.0f;
*eval = zero_spectrum();
}
return LABEL_REFLECT | LABEL_DIFFUSE;
}
#endif /* __OSL__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,261 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "util/math_fast.h"
CCL_NAMESPACE_BEGIN
struct HairBsdf {
SHADER_CLOSURE_BASE;
float3 T;
float roughness1;
float roughness2;
float offset;
};
static_assert(sizeof(ShaderClosure) >= sizeof(HairBsdf), "HairBsdf is too large!");
ccl_device int bsdf_hair_reflection_setup(ccl_private HairBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_HAIR_REFLECTION_ID;
bsdf->roughness1 = clamp(bsdf->roughness1, 0.001f, 1.0f);
bsdf->roughness2 = clamp(bsdf->roughness2, 0.001f, 1.0f);
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device int bsdf_hair_transmission_setup(ccl_private HairBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_HAIR_TRANSMISSION_ID;
bsdf->roughness1 = clamp(bsdf->roughness1, 0.001f, 1.0f);
bsdf->roughness2 = clamp(bsdf->roughness2, 0.001f, 1.0f);
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device Spectrum bsdf_hair_reflection_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private HairBsdf *bsdf = (const ccl_private HairBsdf *)sc;
if (dot(bsdf->N, wo) < 0.0f) {
*pdf = 0.0f;
return zero_spectrum();
}
const float offset = bsdf->offset;
const float3 Tg = bsdf->T;
const float roughness1 = bsdf->roughness1;
const float roughness2 = bsdf->roughness2;
const float Iz = dot(Tg, wi);
const float3 locy = normalize(wi - Tg * Iz);
const float theta_r = M_PI_2_F - fast_acosf(Iz);
const float wo_z = dot(Tg, wo);
const float3 wo_y = normalize(wo - Tg * wo_z);
const float theta_i = M_PI_2_F - fast_acosf(wo_z);
const float cosphi_i = dot(wo_y, locy);
if (M_PI_2_F - fabsf(theta_i) < 0.001f || cosphi_i < 0.0f) {
*pdf = 0.0f;
return zero_spectrum();
}
const float roughness1_inv = 1.0f / roughness1;
const float roughness2_inv = 1.0f / roughness2;
float phi_i = fast_acosf(cosphi_i) * roughness2_inv;
phi_i = fabsf(phi_i) < M_PI_F ? phi_i : M_PI_F;
const float costheta_i = fast_cosf(theta_i);
const float a_R = fast_atan2f(((M_PI_2_F + theta_r) * 0.5f - offset) * roughness1_inv, 1.0f);
const float b_R = fast_atan2f(((-M_PI_2_F + theta_r) * 0.5f - offset) * roughness1_inv, 1.0f);
const float theta_h = (theta_i + theta_r) * 0.5f;
const float t = theta_h - offset;
const float phi_pdf = fast_cosf(phi_i * 0.5f) * 0.25f * roughness2_inv;
const float theta_pdf = roughness1 /
(2 * (t * t + roughness1 * roughness1) * (a_R - b_R) * costheta_i);
*pdf = phi_pdf * theta_pdf;
return make_spectrum(*pdf);
}
ccl_device Spectrum bsdf_hair_transmission_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private HairBsdf *bsdf = (const ccl_private HairBsdf *)sc;
if (dot(bsdf->N, wo) >= 0.0f) {
*pdf = 0.0f;
return zero_spectrum();
}
const float offset = bsdf->offset;
const float3 Tg = bsdf->T;
const float roughness1 = bsdf->roughness1;
const float roughness2 = bsdf->roughness2;
const float Iz = dot(Tg, wi);
const float3 locy = normalize(wi - Tg * Iz);
const float theta_r = M_PI_2_F - fast_acosf(Iz);
const float wo_z = dot(Tg, wo);
const float3 wo_y = normalize(wo - Tg * wo_z);
const float theta_i = M_PI_2_F - fast_acosf(wo_z);
const float phi_i = fast_acosf(dot(wo_y, locy));
if (M_PI_2_F - fabsf(theta_i) < 0.001f) {
*pdf = 0.0f;
return zero_spectrum();
}
const float costheta_i = fast_cosf(theta_i);
const float roughness1_inv = 1.0f / roughness1;
const float a_TT = fast_atan2f(((M_PI_2_F + theta_r) / 2 - offset) * roughness1_inv, 1.0f);
const float b_TT = fast_atan2f(((-M_PI_2_F + theta_r) / 2 - offset) * roughness1_inv, 1.0f);
const float c_TT = 2 * fast_atan2f(M_PI_2_F / roughness2, 1.0f);
const float theta_h = (theta_i + theta_r) / 2;
const float t = theta_h - offset;
const float phi = fabsf(phi_i);
const float p = M_PI_F - phi;
const float theta_pdf = roughness1 /
(2 * (t * t + roughness1 * roughness1) * (a_TT - b_TT) * costheta_i);
const float phi_pdf = roughness2 / (c_TT * (p * p + roughness2 * roughness2));
*pdf = phi_pdf * theta_pdf;
return make_spectrum(*pdf);
}
ccl_device int bsdf_hair_reflection_sample(const ccl_private ShaderClosure *sc,
const float3 /*Ng*/,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
const ccl_private HairBsdf *bsdf = (const ccl_private HairBsdf *)sc;
const float offset = bsdf->offset;
const float3 Tg = bsdf->T;
const float roughness1 = bsdf->roughness1;
const float roughness2 = bsdf->roughness2;
*sampled_roughness = make_float2(roughness1, roughness2);
const float Iz = dot(Tg, wi);
const float3 locy = normalize(wi - Tg * Iz);
const float3 locx = cross(locy, Tg);
const float theta_r = M_PI_2_F - fast_acosf(Iz);
const float roughness1_inv = 1.0f / roughness1;
const float a_R = fast_atan2f(((M_PI_2_F + theta_r) * 0.5f - offset) * roughness1_inv, 1.0f);
const float b_R = fast_atan2f(((-M_PI_2_F + theta_r) * 0.5f - offset) * roughness1_inv, 1.0f);
const float t = roughness1 * tanf(rand.x * (a_R - b_R) + b_R);
const float theta_h = t + offset;
const float theta_i = 2 * theta_h - theta_r;
float costheta_i;
float sintheta_i;
fast_sincosf(theta_i, &sintheta_i, &costheta_i);
const float phi = 2 * safe_asinf(1 - 2 * rand.y) * roughness2;
const float phi_pdf = fast_cosf(phi * 0.5f) * 0.25f / roughness2;
const float theta_pdf = roughness1 /
(2 * (t * t + roughness1 * roughness1) * (a_R - b_R) * costheta_i);
float sinphi;
float cosphi;
fast_sincosf(phi, &sinphi, &cosphi);
*wo = (cosphi * costheta_i) * locy - (sinphi * costheta_i) * locx + (sintheta_i)*Tg;
*pdf = fabsf(phi_pdf * theta_pdf);
if (M_PI_2_F - fabsf(theta_i) < 0.001f) {
*pdf = 0.0f;
}
*eval = make_spectrum(*pdf);
return LABEL_REFLECT | LABEL_GLOSSY;
}
ccl_device int bsdf_hair_transmission_sample(const ccl_private ShaderClosure *sc,
const float3 /*Ng*/,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
const ccl_private HairBsdf *bsdf = (const ccl_private HairBsdf *)sc;
const float offset = bsdf->offset;
const float3 Tg = bsdf->T;
const float roughness1 = bsdf->roughness1;
const float roughness2 = bsdf->roughness2;
*sampled_roughness = make_float2(roughness1, roughness2);
const float Iz = dot(Tg, wi);
const float3 locy = normalize(wi - Tg * Iz);
const float3 locx = cross(locy, Tg);
const float theta_r = M_PI_2_F - fast_acosf(Iz);
const float roughness1_inv = 1.0f / roughness1;
const float a_TT = fast_atan2f(((M_PI_2_F + theta_r) / 2 - offset) * roughness1_inv, 1.0f);
const float b_TT = fast_atan2f(((-M_PI_2_F + theta_r) / 2 - offset) * roughness1_inv, 1.0f);
const float c_TT = 2 * fast_atan2f(M_PI_2_F / roughness2, 1.0f);
const float t = roughness1 * tanf(rand.x * (a_TT - b_TT) + b_TT);
const float theta_h = t + offset;
const float theta_i = 2 * theta_h - theta_r;
float costheta_i;
float sintheta_i;
fast_sincosf(theta_i, &sintheta_i, &costheta_i);
const float p = roughness2 * tanf(c_TT * (rand.y - 0.5f));
const float phi = p + M_PI_F;
const float theta_pdf = roughness1 /
(2 * (t * t + roughness1 * roughness1) * (a_TT - b_TT) * costheta_i);
const float phi_pdf = roughness2 / (c_TT * (p * p + roughness2 * roughness2));
float sinphi;
float cosphi;
fast_sincosf(phi, &sinphi, &cosphi);
*wo = (cosphi * costheta_i) * locy - (sinphi * costheta_i) * locx + (sintheta_i)*Tg;
*pdf = fabsf(phi_pdf * theta_pdf);
if (M_PI_2_F - fabsf(theta_i) < 0.001f) {
*pdf = 0.0f;
}
*eval = make_spectrum(*pdf);
/* TODO(sergey): Should always be negative, but seems some precision issue
* is involved here.
*/
kernel_assert(dot(locy, *wo) < 1e-4f);
return LABEL_TRANSMIT | LABEL_GLOSSY;
}
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,243 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/bsdf_diffuse.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
struct OrenNayarParam {
float roughness;
float a;
float b;
Spectrum multiscatter_term;
};
struct OrenNayarBsdf {
SHADER_CLOSURE_BASE;
OrenNayarParam param;
};
static_assert(sizeof(ShaderClosure) >= sizeof(OrenNayarBsdf), "OrenNayarBsdf is too large!");
/* NOTE: This implements the improved Oren-Nayar model by Yasuhiro Fujii
* (https://mimosa-pudica.net/improved-oren-nayar.html), plus an
* energy-preserving multi-scattering term based on the OpenPBR specification
* (https://academysoftwarefoundation.github.io/OpenPBR). */
/* Above certain roughness threshold we switch to Oren Nayar model. */
ccl_device_forceinline bool diffuse_roughness_is_almost_zero(const float alpha)
{
return alpha < 1e-5f;
}
ccl_device_inline float bsdf_oren_nayar_G(const float cosTheta)
{
if (cosTheta < 1e-6f) {
/* The tan(theta) term starts to act up at low cosTheta, so fall back to Taylor expansion. */
return (M_PI_2_F - 2.0f / 3.0f) - cosTheta;
}
const float sinTheta = sin_from_cos(cosTheta);
const float theta = safe_acosf(cosTheta);
return sinTheta * (theta - 2.0f / 3.0f - sinTheta * cosTheta) +
2.0f / 3.0f * (sinTheta / cosTheta) * (1.0f - sqr(sinTheta) * sinTheta);
}
ccl_device Spectrum bsdf_oren_nayar_get_intensity(const ccl_private OrenNayarBsdf *bsdf,
const float3 n,
const float3 v,
const float3 l)
{
const OrenNayarParam param = bsdf->param;
const float nl = max(dot(n, l), 0.0f);
if (param.b <= 0.0f) {
return make_spectrum(nl * M_1_PI_F);
}
const float nv = max(dot(n, v), 0.0f);
float t = dot(l, v) - nl * nv;
if (t > 0.0f) {
t /= max(nl, nv) + FLT_MIN;
}
const float single_scatter = param.a + param.b * t;
const float El = param.a * M_PI_F + param.b * bsdf_oren_nayar_G(nl);
const Spectrum multi_scatter = param.multiscatter_term * (1.0f - El);
return nl * (make_spectrum(single_scatter) + multi_scatter);
}
ccl_device_inline OrenNayarParam bsdf_oren_nayar_param(const Spectrum color,
const float nv,
const float roughness)
{
const float sigma = saturatef(roughness);
const float a = 1.0f / (M_PI_F + sigma * (M_PI_2_F - 2.0f / 3.0f));
const float b = sigma * a;
/* Compute energy compensation term (except for (1.0f - El) factor since it depends on wo). */
const Spectrum albedo = saturate(color);
const float Eavg = a * M_PI_F + ((M_2PI_F - 5.6f) / 3.0f) * b;
const Spectrum Ems = M_1_PI_F * sqr(albedo) * (Eavg / (1.0f - Eavg)) /
(one_spectrum() - albedo * (1.0f - Eavg));
const float Ev = a * M_PI_F + b * bsdf_oren_nayar_G(max(nv, 0.0f));
return {/* .roughness = */ roughness,
/* .a = */ a,
/* .b = */ b,
/* .multiscatter_term = */ Ems * (1.0f - Ev)};
}
ccl_device void bsdf_oren_nayar_setup(ccl_private ShaderData *sd,
const float3 N,
const Spectrum weight,
const float roughness,
const Spectrum color)
{
ccl_private OrenNayarBsdf *bsdf = (ccl_private OrenNayarBsdf *)bsdf_alloc(
sd, sizeof(OrenNayarBsdf), weight);
if (bsdf) {
bsdf->N = N;
bsdf->type = CLOSURE_BSDF_OREN_NAYAR_ID;
bsdf->param = bsdf_oren_nayar_param(color, dot(bsdf->N, sd->wi), roughness);
sd->flag |= SD_BSDF | SD_BSDF_HAS_EVAL;
}
}
ccl_device Spectrum bsdf_oren_nayar_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private OrenNayarBsdf *bsdf = (const ccl_private OrenNayarBsdf *)sc;
const float cosNO = dot(bsdf->N, wo);
if (cosNO > 0.0f) {
*pdf = cosNO * M_1_PI_F;
return bsdf_oren_nayar_get_intensity(bsdf, bsdf->N, wi, wo);
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_oren_nayar_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness,
ccl_private float *eta)
{
const ccl_private OrenNayarBsdf *bsdf = (const ccl_private OrenNayarBsdf *)sc;
sample_cos_hemisphere(bsdf->N, rand, wo, pdf);
if (dot(Ng, *wo) > 0.0f) {
*eval = bsdf_oren_nayar_get_intensity(bsdf, bsdf->N, wi, *wo);
}
else {
*pdf = 0.0f;
*eval = zero_spectrum();
}
*sampled_roughness = one_float2();
*eta = 1.0f;
return LABEL_REFLECT | LABEL_DIFFUSE;
}
/* ---------------------------------------------------------------------------------------- */
/** \name Subsurface in thin-walled mode
*
* An infinitesimally thin sheet of dense scattering material, following OpenPBR spec
* https://academysoftwarefoundation.github.io/OpenPBR/#model/thin-walledcase
*
* It is approximated by a diffuse lobe and a translucent lobe, the respective weights of both
* lobes are given by subsurface anisotropy, with specifies the relative amount of backward and
* forward scattering.
*
* \{ */
ccl_device_inline void bsdf_thin_subsurface_setup(ccl_private ShaderData *sd,
const float3 N,
const Spectrum weight,
const float anisotropy,
const float roughness,
const Spectrum color)
{
const Spectrum reflection_weight = saturatef(0.5f * (1.0f - anisotropy)) * weight;
const Spectrum transmission_weight = saturatef(0.5f * (1.0f + anisotropy)) * weight;
if (is_zero(reflection_weight) && is_zero(transmission_weight)) {
return;
}
if (sd->num_closure_left == 0) {
return;
}
if (diffuse_roughness_is_almost_zero(roughness)) {
bsdf_diffuse_setup(sd, N, reflection_weight);
bsdf_translucent_setup(sd, N, transmission_weight);
return;
}
const OrenNayarParam param = bsdf_oren_nayar_param(color, dot(N, sd->wi), roughness);
/* Reflection. */
{
ccl_private OrenNayarBsdf *bsdf = (ccl_private OrenNayarBsdf *)bsdf_alloc(
sd, sizeof(OrenNayarBsdf), reflection_weight);
if (bsdf) {
bsdf->type = CLOSURE_BSDF_OREN_NAYAR_ID;
bsdf->N = N;
bsdf->param = param;
sd->flag |= SD_BSDF | SD_BSDF_HAS_EVAL;
}
}
/* Transmission. */
{
ccl_private OrenNayarBsdf *bsdf = (ccl_private OrenNayarBsdf *)bsdf_alloc(
sd, sizeof(OrenNayarBsdf), transmission_weight);
if (bsdf) {
bsdf->type = CLOSURE_BSDF_ROUGH_TRANSLUCENT_ID;
bsdf->N = -N;
bsdf->param = param;
sd->flag |= SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
}
}
ccl_device int bsdf_rough_translucent_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness,
ccl_private float *eta)
{
bsdf_oren_nayar_sample(sc, -Ng, reflect(wi, sc->N), rand, eval, wo, pdf, sampled_roughness, eta);
return LABEL_TRANSMIT | LABEL_DIFFUSE;
}
ccl_device Spectrum bsdf_rough_translucent_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
return bsdf_oren_nayar_eval(sc, reflect(wi, sc->N), wo, pdf);
}
/** \} */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,127 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/util/colorspace.h"
CCL_NAMESPACE_BEGIN
#ifdef __OSL__
struct PhongRampBsdf {
SHADER_CLOSURE_BASE;
float exponent;
ccl_private float3 *colors;
};
static_assert(sizeof(ShaderClosure) >= sizeof(PhongRampBsdf), "PhongRampBsdf is too large!");
ccl_device float3 bsdf_phong_ramp_get_color(const float3 colors[8], float pos)
{
const int MAXCOLORS = 8;
const float npos = pos * (float)(MAXCOLORS - 1);
const int ipos = float_to_int(npos);
if (ipos < 0) {
return colors[0];
}
if (ipos >= (MAXCOLORS - 1)) {
return colors[MAXCOLORS - 1];
}
const float offset = npos - (float)ipos;
return colors[ipos] * (1.0f - offset) + colors[ipos + 1] * offset;
}
ccl_device int bsdf_phong_ramp_setup(ccl_private PhongRampBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_PHONG_RAMP_ID;
bsdf->exponent = max(bsdf->exponent, 0.0f);
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device Spectrum bsdf_phong_ramp_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private PhongRampBsdf *bsdf = (const ccl_private PhongRampBsdf *)sc;
const float m_exponent = bsdf->exponent;
const float cosNI = dot(bsdf->N, wi);
const float cosNO = dot(bsdf->N, wo);
if (cosNI > 0 && cosNO > 0) {
// reflect the view vector
const float3 R = (2 * cosNI) * bsdf->N - wi;
const float cosRO = dot(R, wo);
if (cosRO > 0) {
const float cosp = powf(cosRO, m_exponent);
const float common = 0.5f * M_1_PI_F * cosp;
const float out = cosNO * (m_exponent + 2) * common;
*pdf = (m_exponent + 1) * common;
return rgb_to_spectrum(bsdf_phong_ramp_get_color(bsdf->colors, cosp) * out);
}
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device_inline float phong_ramp_exponent_to_roughness(const float exponent)
{
return sqrt(1.0f / ((exponent + 2.0f) / 2.0f));
}
ccl_device int bsdf_phong_ramp_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
const ccl_private PhongRampBsdf *bsdf = (const ccl_private PhongRampBsdf *)sc;
const float cosNI = dot(bsdf->N, wi);
const float m_exponent = bsdf->exponent;
const float m_roughness = phong_ramp_exponent_to_roughness(m_exponent);
*sampled_roughness = make_float2(m_roughness, m_roughness);
if (cosNI > 0) {
// reflect the view vector
const float3 R = (2 * cosNI) * bsdf->N - wi;
float3 T;
float3 B;
make_orthonormals(R, &T, &B);
const float phi = M_2PI_F * rand.x;
const float cosTheta = powf(rand.y, 1 / (m_exponent + 1));
*wo = to_global(spherical_cos_to_direction(cosTheta, phi), T, B, R);
if (dot(Ng, *wo) > 0.0f) {
// common terms for pdf and eval
const float cosNO = dot(bsdf->N, *wo);
// make sure the direction we chose is still in the right hemisphere
if (cosNO > 0) {
const float cosp = powf(cosTheta, m_exponent);
const float common = 0.5f * M_1_PI_F * cosp;
*pdf = (m_exponent + 1) * common;
const float out = cosNO * (m_exponent + 2) * common;
*eval = rgb_to_spectrum(bsdf_phong_ramp_get_color(bsdf->colors, cosp) * out);
}
}
}
else {
*eval = zero_spectrum();
*pdf = 0.0f;
}
return LABEL_REFLECT | LABEL_GLOSSY;
}
#endif /* __OSL__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,481 @@
/* SPDX-FileCopyrightText: 2018-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0
*
* This code implements the paper [A practical and controllable hair and fur model for production
* path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang, Matt Jen-Yuan, et al. */
#pragma once
#ifndef __KERNEL_GPU__
# include <cfenv>
#endif
#include "kernel/types.h"
#include "kernel/closure/bsdf_util.h"
#include "kernel/util/colorspace.h"
CCL_NAMESPACE_BEGIN
struct ChiangHairBSDF {
SHADER_CLOSURE_BASE;
/* Absorption coefficient. */
Spectrum sigma;
/* Variance of the underlying logistic distribution. */
float v;
/* Scale factor of the underlying logistic distribution. */
float s;
/* Cuticle tilt angle. */
float alpha;
/* IOR. */
float eta;
/* Effective variance for the diffuse bounce only. */
float m0_roughness;
/* Azimuthal offset. */
float h;
};
static_assert(sizeof(ShaderClosure) >= sizeof(ChiangHairBSDF), "ChiangHairBSDF is too large!");
/* Gives the change in direction in the normal plane for the given angles and p-th-order
* scattering. */
ccl_device_inline float delta_phi(const int p, const float gamma_o, const float gamma_t)
{
return 2.0f * p * gamma_t - 2.0f * gamma_o + p * M_PI_F;
}
/* Remaps the given angle to [-pi, pi]. */
ccl_device_inline float wrap_angle(const float a)
{
return (a + M_PI_F) - M_2PI_F * floorf((a + M_PI_F) / M_2PI_F) - M_PI_F;
}
/* Logistic distribution function. */
ccl_device_inline float logistic(const float x, const float s)
{
const float v = expf(-fabsf(x) / s);
return v / (s * sqr(1.0f + v));
}
/* Logistic cumulative density function. */
ccl_device_inline float logistic_cdf(const float x, const float s)
{
const float arg = -x / s;
/* expf() overflows if arg >= 89.0. */
if (arg > 88.0f) {
return 0.0f;
}
return 1.0f / (1.0f + expf(arg));
}
/* Numerical approximation to the Bessel function of the first kind. */
ccl_device_inline float bessel_I0(float x)
{
x = sqr(x);
float val = 1.0f + 0.25f * x;
float pow_x_2i = sqr(x);
uint64_t i_fac_2 = 1;
int pow_4_i = 16;
for (int i = 2; i < 10; i++) {
i_fac_2 *= i * i;
const float newval = val + pow_x_2i / (pow_4_i * i_fac_2);
if (val == newval) {
return val;
}
val = newval;
pow_x_2i *= x;
pow_4_i *= 4;
}
return val;
}
/* Logarithm of the Bessel function of the first kind. */
ccl_device_inline float log_bessel_I0(const float x)
{
if (x > 12.0f) {
/* log(1/x) == -log(x) if x > 0.
* This is only used with positive cosines. */
return x + 0.5f * (1.f / (8.0f * x) - M_LN_2PI_F - logf(x));
}
return logf(bessel_I0(x));
}
/* Logistic distribution limited to the interval [-pi, pi]. */
ccl_device_inline float trimmed_logistic(const float x, const float s)
{
/* The logistic distribution is symmetric and centered around zero,
* so logistic_cdf(x, s) = 1 - logistic_cdf(-x, s).
* Therefore, logistic_cdf(x, s)-logistic_cdf(-x, s) = 1 - 2*logistic_cdf(-x, s) */
const float scaling_fac = 1.0f - 2.0f * logistic_cdf(-M_PI_F, s);
const float val = logistic(x, s);
return safe_divide(val, scaling_fac);
}
/* Sampling function for the trimmed logistic function. */
ccl_device_inline float sample_trimmed_logistic(const float u, const float s)
{
const float cdf_minuspi = logistic_cdf(-M_PI_F, s);
const float x = -s * logf(1.0f / (u * (1.0f - 2.0f * cdf_minuspi) + cdf_minuspi) - 1.0f);
return clamp(x, -M_PI_F, M_PI_F);
}
/* Azimuthal scattering function Np. */
ccl_device_inline float azimuthal_scattering(
float phi, const int p, const float s, float gamma_o, const float gamma_t)
{
const float phi_o = wrap_angle(phi - delta_phi(p, gamma_o, gamma_t));
const float val = trimmed_logistic(phi_o, s);
return val;
}
/* Longitudinal scattering function Mp. */
ccl_device_inline float longitudinal_scattering(float sin_theta_i,
const float cos_theta_i,
const float sin_theta_o,
const float cos_theta_o,
const float v)
{
const float inv_v = 1.0f / v;
const float cos_arg = cos_theta_i * cos_theta_o * inv_v;
const float sin_arg = sin_theta_i * sin_theta_o * inv_v;
if (v <= 0.1f) {
const float i0 = log_bessel_I0(cos_arg);
const float val = expf(i0 - sin_arg - inv_v + 0.6931f + logf(0.5f * inv_v));
kernel_assert(isfinite_safe(val));
return val;
}
const float i0 = bessel_I0(cos_arg);
const float val = (expf(-sin_arg) * i0) / (sinhf(inv_v) * 2.0f * v);
kernel_assert(isfinite_safe(val));
return val;
}
#ifdef __HAIR__
/* Set up the hair closure. */
ccl_device int bsdf_hair_chiang_setup(ccl_private ShaderData *sd, ccl_private ChiangHairBSDF *bsdf)
{
bsdf->type = CLOSURE_BSDF_HAIR_CHIANG_ID;
bsdf->v = clamp(bsdf->v, 0.001f, 1.0f);
bsdf->s = clamp(bsdf->s, 0.001f, 1.0f);
/* Apply Primary Reflection Roughness modifier. */
bsdf->m0_roughness = clamp(bsdf->m0_roughness * bsdf->v, 0.001f, 1.0f);
/* Map from roughness_u and roughness_v to variance and scale factor. */
bsdf->v = sqr(0.726f * bsdf->v + 0.812f * sqr(bsdf->v) + 3.700f * pow20(bsdf->v));
bsdf->s = (0.265f * bsdf->s + 1.194f * sqr(bsdf->s) + 5.372f * pow22(bsdf->s)) * M_SQRT_PI_8_F;
bsdf->m0_roughness = sqr(0.726f * bsdf->m0_roughness + 0.812f * sqr(bsdf->m0_roughness) +
3.700f * pow20(bsdf->m0_roughness));
/* Compute local frame, aligned to curve tangent and ray direction. */
const float3 X = safe_normalize(sd->dPdu);
const float3 Y = safe_normalize(cross(X, sd->wi));
const float3 Z = safe_normalize(cross(X, Y));
/* h -1..0..1 means the rays goes from grazing the hair, to hitting it at
* the center, to grazing the other edge. This is the sine of the angle
* between sd->Ng and Z, as seen from the tangent X. */
/* TODO: we convert this value to a cosine later and discard the sign, so
* we could probably save some operations. */
bsdf->h = ((sd->type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) ? -sd->v :
dot(cross(sd->Ng, X), Z);
kernel_assert(fabsf(bsdf->h) < 1.0f + 1e-4f);
kernel_assert(isfinite_safe(Y));
kernel_assert(isfinite_safe(bsdf->h));
bsdf->N = Y;
bsdf->alpha = -bsdf->alpha;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
#endif /* __HAIR__ */
/* Given the Fresnel term and transmittance, generate the attenuation terms for each bounce. */
ccl_device_inline void hair_attenuation(KernelGlobals kg,
const float f,
Spectrum T,
ccl_private Spectrum *Ap,
ccl_private float *Ap_energy)
{
/* Primary specular (R). */
Ap[0] = make_spectrum(f);
Ap_energy[0] = f;
/* Transmission (TT). */
Spectrum col = sqr(1.0f - f) * T;
Ap[1] = col;
Ap_energy[1] = spectrum_to_gray(kg, col);
/* Secondary specular (TRT). */
col *= T * f;
Ap[2] = col;
Ap_energy[2] = spectrum_to_gray(kg, col);
/* Residual component (TRRT+). */
col *= safe_divide(T * f, one_spectrum() - T * f);
Ap[3] = col;
Ap_energy[3] = spectrum_to_gray(kg, col);
/* Normalize sampling weights. */
const float totweight = Ap_energy[0] + Ap_energy[1] + Ap_energy[2] + Ap_energy[3];
const float fac = safe_divide(1.0f, totweight);
Ap_energy[0] *= fac;
Ap_energy[1] *= fac;
Ap_energy[2] *= fac;
Ap_energy[3] *= fac;
}
/* Update sin_theta_o and cos_theta_o to account for scale tilt for each bounce. */
ccl_device_inline void hair_alpha_angles(const float sin_theta_o,
const float cos_theta_o,
const float alpha,
ccl_private float *angles)
{
const float sin_1alpha = sinf(alpha);
const float cos_1alpha = cos_from_sin(sin_1alpha);
const float sin_2alpha = 2.0f * sin_1alpha * cos_1alpha;
const float cos_2alpha = sqr(cos_1alpha) - sqr(sin_1alpha);
const float sin_4alpha = 2.0f * sin_2alpha * cos_2alpha;
const float cos_4alpha = sqr(cos_2alpha) - sqr(sin_2alpha);
angles[0] = sin_theta_o * cos_2alpha - cos_theta_o * sin_2alpha;
angles[1] = fabsf(cos_theta_o * cos_2alpha + sin_theta_o * sin_2alpha);
angles[2] = sin_theta_o * cos_1alpha + cos_theta_o * sin_1alpha;
angles[3] = fabsf(cos_theta_o * cos_1alpha - sin_theta_o * sin_1alpha);
angles[4] = sin_theta_o * cos_4alpha + cos_theta_o * sin_4alpha;
angles[5] = fabsf(cos_theta_o * cos_4alpha - sin_theta_o * sin_4alpha);
}
/* Evaluation function for our shader. */
ccl_device Spectrum bsdf_hair_chiang_eval(KernelGlobals kg,
const ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 wo,
ccl_private float *pdf)
{
kernel_assert(isfinite_safe(sd->P) && isfinite_safe(sd->ray_length));
const ccl_private ChiangHairBSDF *bsdf = (const ccl_private ChiangHairBSDF *)sc;
const float3 Y = bsdf->N;
const float3 X = safe_normalize(sd->dPdu);
kernel_assert(fabsf(dot(X, Y)) < 1e-3f);
const float3 Z = safe_normalize(cross(X, Y));
/* local_I is the illumination direction. */
const float3 local_O = to_local(sd->wi, X, Y, Z);
const float3 local_I = to_local(wo, X, Y, Z);
const float sin_theta_o = local_O.x;
const float cos_theta_o = cos_from_sin(sin_theta_o);
const float phi_o = atan2f(local_O.z, local_O.y);
const float sin_theta_t = sin_theta_o / bsdf->eta;
const float cos_theta_t = cos_from_sin(sin_theta_t);
const float sin_gamma_o = bsdf->h;
const float cos_gamma_o = cos_from_sin(sin_gamma_o);
const float gamma_o = safe_asinf(sin_gamma_o);
const float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
const float cos_gamma_t = cos_from_sin(sin_gamma_t);
const float gamma_t = safe_asinf(sin_gamma_t);
const Spectrum T = exp(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
Spectrum Ap[4];
float Ap_energy[4];
hair_attenuation(
kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap, Ap_energy);
const float sin_theta_i = local_I.x;
const float cos_theta_i = cos_from_sin(sin_theta_i);
const float phi_i = atan2f(local_I.z, local_I.y);
const float phi = phi_i - phi_o;
float angles[6];
hair_alpha_angles(sin_theta_o, cos_theta_o, bsdf->alpha, angles);
Spectrum F = zero_spectrum();
float F_energy = 0.0f;
/* Primary specular (R), Transmission (TT) and Secondary Specular (TRT). */
for (int i = 0; i < 3; i++) {
const float Mp = longitudinal_scattering(sin_theta_i,
cos_theta_i,
angles[2 * i],
angles[2 * i + 1],
(i == 0) ? bsdf->m0_roughness :
(i == 1) ? 0.25f * bsdf->v :
4.0f * bsdf->v);
const float Np = azimuthal_scattering(phi, i, bsdf->s, gamma_o, gamma_t);
F += Ap[i] * Mp * Np;
F_energy += Ap_energy[i] * Mp * Np;
kernel_assert(isfinite_safe(F) && isfinite_safe(F_energy));
}
/* Residual component (TRRT+). */
{
const float Mp = longitudinal_scattering(
sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f * bsdf->v);
const float Np = M_1_2PI_F;
F += Ap[3] * Mp * Np;
F_energy += Ap_energy[3] * Mp * Np;
kernel_assert(isfinite_safe(F) && isfinite_safe(F_energy));
}
*pdf = F_energy;
return F;
}
/* Sampling function for the hair shader. */
ccl_device int bsdf_hair_chiang_sample(KernelGlobals kg,
const ccl_private ShaderClosure *sc,
ccl_private ShaderData *sd,
float3 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
ccl_private ChiangHairBSDF *bsdf = (ccl_private ChiangHairBSDF *)sc;
*sampled_roughness = make_float2(bsdf->m0_roughness, bsdf->m0_roughness);
const float3 Y = bsdf->N;
const float3 X = safe_normalize(sd->dPdu);
kernel_assert(fabsf(dot(X, Y)) < 1e-3f);
const float3 Z = safe_normalize(cross(X, Y));
/* `wo` in PBRT. */
const float3 local_O = to_local(sd->wi, X, Y, Z);
const float sin_theta_o = local_O.x;
const float cos_theta_o = cos_from_sin(sin_theta_o);
const float phi_o = atan2f(local_O.z, local_O.y);
const float sin_theta_t = sin_theta_o / bsdf->eta;
const float cos_theta_t = cos_from_sin(sin_theta_t);
const float sin_gamma_o = bsdf->h;
const float cos_gamma_o = cos_from_sin(sin_gamma_o);
const float gamma_o = safe_asinf(sin_gamma_o);
const float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
const float cos_gamma_t = cos_from_sin(sin_gamma_t);
const float gamma_t = safe_asinf(sin_gamma_t);
const Spectrum T = exp(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
Spectrum Ap[4];
float Ap_energy[4];
hair_attenuation(
kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap, Ap_energy);
int p = 0;
for (; p < 3; p++) {
if (rand.z < Ap_energy[p]) {
break;
}
rand.z -= Ap_energy[p];
}
rand.z /= Ap_energy[p];
float v = bsdf->v;
if (p == 1) {
v *= 0.25f;
}
if (p >= 2) {
v *= 4.0f;
}
float angles[6];
hair_alpha_angles(sin_theta_o, cos_theta_o, bsdf->alpha, angles);
float sin_theta_o_tilted = sin_theta_o;
float cos_theta_o_tilted = cos_theta_o;
if (p < 3) {
sin_theta_o_tilted = angles[2 * p];
cos_theta_o_tilted = angles[2 * p + 1];
}
rand.z = max(rand.z, 1e-5f);
const float fac = 1.0f + v * logf(rand.z + (1.0f - rand.z) * expf(-2.0f / v));
const float sin_theta_i = -fac * sin_theta_o_tilted +
sin_from_cos(fac) * cosf(M_2PI_F * rand.y) * cos_theta_o_tilted;
const float cos_theta_i = cos_from_sin(sin_theta_i);
float phi;
if (p < 3) {
phi = delta_phi(p, gamma_o, gamma_t) + sample_trimmed_logistic(rand.x, bsdf->s);
}
else {
phi = M_2PI_F * rand.x;
}
const float phi_i = phi_o + phi;
Spectrum F = zero_spectrum();
float F_energy = 0.0f;
/* Primary specular (R), Transmission (TT) and Secondary Specular (TRT). */
for (int i = 0; i < 3; i++) {
const float Mp = longitudinal_scattering(sin_theta_i,
cos_theta_i,
angles[2 * i],
angles[2 * i + 1],
(i == 0) ? bsdf->m0_roughness :
(i == 1) ? 0.25f * bsdf->v :
4.0f * bsdf->v);
const float Np = azimuthal_scattering(phi, i, bsdf->s, gamma_o, gamma_t);
F += Ap[i] * Mp * Np;
F_energy += Ap_energy[i] * Mp * Np;
kernel_assert(isfinite_safe(F) && isfinite_safe(F_energy));
}
/* Residual component (TRRT+). */
{
const float Mp = longitudinal_scattering(
sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f * bsdf->v);
const float Np = M_1_2PI_F;
F += Ap[3] * Mp * Np;
F_energy += Ap_energy[3] * Mp * Np;
kernel_assert(isfinite_safe(F) && isfinite_safe(F_energy));
}
*eval = F;
*pdf = F_energy;
*wo = to_global(spherical_cos_to_direction(sin_theta_i, phi_i), Y, Z, X);
return LABEL_GLOSSY | ((p == 0) ? LABEL_REFLECT : LABEL_TRANSMIT);
}
/* Implements Filter Glossy by capping the effective roughness. */
ccl_device void bsdf_hair_chiang_blur(ccl_private ShaderClosure *sc, const float roughness)
{
ccl_private ChiangHairBSDF *bsdf = (ccl_private ChiangHairBSDF *)sc;
bsdf->v = fmaxf(roughness, bsdf->v);
bsdf->s = fmaxf(roughness, bsdf->s);
bsdf->m0_roughness = fmaxf(roughness, bsdf->m0_roughness);
}
/* Hair Albedo. */
ccl_device Spectrum bsdf_hair_chiang_albedo(const ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc)
{
ccl_private ChiangHairBSDF *bsdf = (ccl_private ChiangHairBSDF *)sc;
const float cos_theta_o = cos_from_sin(dot(sd->wi, safe_normalize(sd->dPdu)));
const float cos_gamma_o = cos_from_sin(bsdf->h);
const float f = fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta);
const float roughness_scale = bsdf_principled_hair_albedo_roughness_scale(bsdf->v);
/* TODO(lukas): Adding the Fresnel term here as a workaround until the proper refactor. */
return exp(-sqrt(bsdf->sigma) * roughness_scale) + make_spectrum(f);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,935 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* This code implements the paper [A Microfacet-based Hair Scattering
* Model](https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.14588) by Weizhen Huang, Matthias B.
* Hullin and Johannes Hanika. */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/bsdf_microfacet.h"
#include "kernel/closure/bsdf_principled_hair_chiang.h"
#include "kernel/closure/bsdf_transparent.h"
#include "kernel/closure/bsdf_util.h"
#include "kernel/sample/lcg.h"
CCL_NAMESPACE_BEGIN
struct HuangHairExtra {
/* Optional modulation factors. */
float R, TT, TRT;
/* Local coordinate system. X is stored as `bsdf->N`. */
float3 Y, Z;
/* Incident direction in local coordinate system. */
float3 wi;
/* Projected radius from the view direction. */
float radius;
/* Squared Eccentricity. */
float e2;
/* The projected width of half a pixel at `sd->P` in `h` space. */
float pixel_coverage;
/* Valid integration interval, pre-divided by the radius so the range is [-1, 1]. */
Interval<float> h;
};
struct HuangHairBSDF {
SHADER_CLOSURE_BASE;
/* Absorption coefficient. */
Spectrum sigma;
/* Microfacet distribution roughness. */
float roughness;
/* Cuticle tilt angle. */
float tilt;
/* Index of refraction. */
float eta;
/* The ratio of the minor axis to the major axis. */
float aspect_ratio;
/* Azimuthal offset. */
float h;
/* Extra closure for optional modulation factors and local coordinate system. */
ccl_private HuangHairExtra *extra;
};
static_assert(sizeof(ShaderClosure) >= sizeof(HuangHairBSDF), "HuangHairBSDF is too large!");
static_assert(sizeof(ShaderClosure) >= sizeof(HuangHairExtra), "HuangHairExtra is too large!");
/* -------------------------------------------------------------------- */
/** \name Hair coordinate system utils.
* \{ */
/* TODO(weizhen): add a namespace `huanghair` for the below utility functions. */
/* Returns `sin(theta)` of the given direction. */
ccl_device_inline float sin_theta(const float3 w)
{
return w.y;
}
/* Returns `cos(theta)` of the given direction. */
ccl_device_inline float cos_theta(const float3 w)
{
return safe_sqrtf(sqr(w.x) + sqr(w.z));
}
/* Returns `tan(theta)` of the given direction. */
ccl_device_inline float tan_theta(const float3 w)
{
return sin_theta(w) / cos_theta(w);
}
/* Returns `sin(phi)` and `cos(phi)` of the given direction. */
ccl_device float sin_phi(const float3 w)
{
return w.x / cos_theta(w);
}
/* Check whether the hair cross-section is circular. */
ccl_device_inline bool is_circular(const float b)
{
return b == 1.0f;
}
ccl_device float2 sincos_phi(const float3 w)
{
const float c = cos_theta(w);
return make_float2(w.x / c, w.z / c);
}
/* Extract the theta coordinate from the given direction.
* -pi < theta < pi */
ccl_device_inline float dir_theta(const float3 w)
{
return atan2f(sin_theta(w), cos_theta(w));
}
/* Extract the phi coordinate from the given direction, assuming `phi(wi) == 0`.
* -pi < phi < pi */
ccl_device_inline float dir_phi(const float3 w)
{
return atan2f(w.x, w.z);
}
/* Extract theta and phi coordinates from the given direction, assuming `phi(wi) == 0`.
* -pi/2 < theta < pi/2, -pi < phi < pi */
ccl_device_inline float2 dir_sph(const float3 w)
{
return make_float2(dir_theta(w), dir_phi(w));
}
/* Conversion between `gamma` and `phi`. Notations see Figure 5 in the paper. */
ccl_device_inline float to_phi(const float gamma, const float b)
{
if (is_circular(b)) {
return gamma;
}
float sin_gamma;
float cos_gamma;
fast_sincosf(gamma, &sin_gamma, &cos_gamma);
return atan2f(b * sin_gamma, cos_gamma);
}
ccl_device_inline float to_gamma(const float phi, const float b)
{
if (is_circular(b)) {
return phi;
}
float sin_phi;
float cos_phi;
fast_sincosf(phi, &sin_phi, &cos_phi);
return atan2f(sin_phi, b * cos_phi);
}
/* Intersecting `wi` with the ellipse defined by `x = sin_gamma, y = b * cos_gamma` results in
* equation `h = -cos_phi_i * sin_gamma + b * sin_phi_i * cos_gamma`. */
ccl_device_inline float phi_to_h(const float phi, const float b, const float3 wi)
{
if (is_circular(b)) {
return -fast_sinf(phi);
}
float sin_gamma, cos_gamma;
fast_sincosf(to_gamma(phi, b), &sin_gamma, &cos_gamma);
const float2 sin_cos_phi_i = sincos_phi(wi);
return -sin_cos_phi_i.y * sin_gamma + b * sin_cos_phi_i.x * cos_gamma;
}
/* Solve for `gamma` in equation `h = -cos_phi_i * sin_gamma + b * sin_phi_i * cos_gamma`.
* Also, make use of `r = sqrt(sqr(cos_phi_i) + sqr(b * sin_phi_i))` to pre-map `h` to [-1, 1]. */
ccl_device_inline float h_to_gamma(const float h_div_r, const float b, const float3 wi)
{
return is_circular(b) ? -asinf(h_div_r) : atan2f(wi.z, -b * wi.x) - acosf(-h_div_r);
}
/* Jacobian |d_gamma/d_h|, used for changing variable in the integration. */
ccl_device_inline float d_gamma_d_h(const float2 sincos_phi_i, const float gamma, const float b)
{
if (is_circular(b)) {
return safe_divide(1.0f, fast_cosf(gamma));
}
float sin_gamma, cos_gamma;
fast_sincosf(gamma, &sin_gamma, &cos_gamma);
return safe_divide(1.0f, sincos_phi_i.y * cos_gamma + b * sincos_phi_i.x * sin_gamma);
}
/* Compute the coordinate on the ellipse, given `gamma` and the aspect ratio between the minor axis
* and the major axis. */
ccl_device_inline float2 to_point(const float gamma, const float b)
{
float sin_gamma;
float cos_gamma;
fast_sincosf(gamma, &sin_gamma, &cos_gamma);
return make_float2(sin_gamma, b * cos_gamma);
}
/* Compute the vector direction given by `theta` and `gamma`. */
ccl_device_inline float3 sphg_dir(const float theta, const float gamma, const float b)
{
float sin_theta;
float cos_theta;
float sin_gamma;
float cos_gamma;
float sin_phi;
float cos_phi;
fast_sincosf(theta, &sin_theta, &cos_theta);
fast_sincosf(gamma, &sin_gamma, &cos_gamma);
if (is_circular(b) || fabsf(cos_gamma) < 1e-6f) {
sin_phi = sin_gamma;
cos_phi = cos_gamma;
}
else {
const float tan_gamma = sin_gamma / cos_gamma;
const float tan_phi = b * tan_gamma;
cos_phi = signf(cos_gamma) * inversesqrtf(sqr(tan_phi) + 1.0f);
sin_phi = cos_phi * tan_phi;
}
return make_float3(sin_phi * cos_theta, sin_theta, cos_phi * cos_theta);
}
ccl_device_inline float arc_length(const float e2, const float gamma)
{
return e2 == 0 ? 1.0f : sqrtf(1.0f - e2 * sqr(sinf(gamma)));
}
ccl_device_inline bool is_nearfield(const ccl_private HuangHairBSDF *bsdf)
{
return bsdf->extra->radius > bsdf->extra->pixel_coverage;
}
/** \} */
#ifdef __HAIR__
/* Set up the hair closure. */
ccl_device int bsdf_hair_huang_setup(ccl_private ShaderData *sd,
ccl_private HuangHairBSDF *bsdf,
const uint32_t path_flag)
{
bsdf->type = CLOSURE_BSDF_HAIR_HUANG_ID;
bsdf->roughness = clamp(bsdf->roughness, 0.001f, 1.0f);
/* Negate to keep it consistent with principled hair BSDF. */
bsdf->tilt = -bsdf->tilt;
/* Compute local frame. The Y axis is aligned with the curve tangent; the X axis is perpendicular
* to the ray direction for circular cross-sections, or aligned with the major axis for
* elliptical cross-sections. */
bsdf->extra->Y = safe_normalize(sd->dPdu);
const float3 X = safe_normalize(cross(sd->dPdu, sd->wi));
/* h from -1..0..1 means the rays goes from grazing the hair, to hitting it at the center, to
* grazing the other edge. This is the cosine of the angle between `sd->N` and `X`. */
bsdf->h = ((sd->type & PRIMITIVE_CURVE) == PRIMITIVE_CURVE_RIBBON) ? -sd->v : -dot(X, sd->N);
kernel_assert(fabsf(bsdf->h) < 1.0f + 1e-4f);
kernel_assert(isfinite_safe(bsdf->h));
if (bsdf->aspect_ratio != 1.0f && (sd->type & PRIMITIVE_CURVE)) {
/* Adjust `bsdf->N` to be orthogonal to `sd->dPdu`. */
bsdf->N = safe_normalize(cross(sd->dPdu, safe_normalize(cross(bsdf->N, sd->dPdu))));
/* Align local frame with the curve normal. */
if (bsdf->aspect_ratio > 1.0f) {
/* Switch major and minor axis. */
bsdf->aspect_ratio = 1.0f / bsdf->aspect_ratio;
const float3 minor_axis = safe_normalize(cross(sd->dPdu, bsdf->N));
bsdf->N = safe_normalize(cross(minor_axis, sd->dPdu));
}
}
else {
/* Align local frame with the ray direction so that `phi_i == 0`. */
bsdf->N = X;
}
/* Fill extra closure. */
if (is_zero(bsdf->N) || !isfinite_safe(bsdf->N)) {
/* Construct arbitrary local coordinate system. The implementation should ensure smooth
* transition along the hair shaft. */
make_orthonormals(bsdf->extra->Y, &bsdf->extra->Z, &bsdf->N);
}
else {
bsdf->extra->Z = safe_normalize(cross(bsdf->N, sd->dPdu));
}
const float3 I = to_local(sd->wi, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
bsdf->extra->wi = I;
bsdf->extra->e2 = 1.0f - sqr(bsdf->aspect_ratio);
bsdf->extra->radius = bsdf->extra->e2 == 0 ?
1.0f :
sqrtf(1.0f - bsdf->extra->e2 * sqr(I.x) / (sqr(I.x) + sqr(I.z)));
/* Treat as transparent material if intersection lies outside of the projected radius. */
if (fabsf(bsdf->h) >= bsdf->extra->radius) {
/* Remove allocated closures. */
sd->num_closure--;
sd->num_closure_left += 2;
/* Allocate transparent closure. */
bsdf_transparent_setup(sd, bsdf->weight, path_flag);
return 0;
}
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
#endif /* __HAIR__ */
/* Albedo correction, treat as glass. `rough` has already applied square root. */
ccl_device_forceinline float bsdf_hair_huang_energy_scale(KernelGlobals kg,
const float mu,
const float rough,
const float ior)
{
const bool inv_table = (ior < 1.0f);
const int ofs = inv_table ? kernel_data.tables.ggx_glass_inv_E : kernel_data.tables.ggx_glass_E;
const float z = sqrtf(fabsf((ior - 1.0f) / (ior + 1.0f)));
return 1.0f / lookup_table_read_3D(kg, rough, mu, z, ofs, 16, 16, 16);
}
/* Sample microfacets from a tilted mesonormal. */
ccl_device_inline float3 sample_wh(const float roughness,
const float3 wi,
const float3 wm,
const float2 rand)
{
/* Coordinate transformation for microfacet sampling. */
float3 s;
float3 t;
make_orthonormals(wm, &s, &t);
const float3 wi_wm = to_local(wi, s, t, wm);
const float3 wh_wm = microfacet_ggx_sample_vndf(wi_wm, roughness, roughness, rand);
const float3 wh = to_global(wh_wm, s, t, wm);
return wh;
}
/* Check micronormal/mesonormal direct visibility from direction `v`. */
ccl_device_inline bool microfacet_visible(const float3 v, const float3 m, const float3 h)
{
return (dot(v, h) > 0.0f && dot(v, m) > 0.0f);
}
/* Check micronormal/mesonormal direct visibility from directions `wi` and `wo`. */
ccl_device_inline bool microfacet_visible(const float3 wi,
const float3 wo,
const float3 m,
const float3 h)
{
return microfacet_visible(wi, m, h) && microfacet_visible(wo, m, h);
}
/* Combined shadowing-masking term divided by the shadowing-masking in the incoming direction. */
ccl_device_inline float bsdf_Go(const float alpha2, const float cos_NI, const float cos_NO)
{
const float lambdaI = bsdf_lambda<MicrofacetType::GGX>(alpha2, cos_NI);
const float lambdaO = bsdf_lambda<MicrofacetType::GGX>(alpha2, cos_NO);
return (1.0f + lambdaI) / (1.0f + lambdaI + lambdaO);
}
ccl_device Spectrum bsdf_hair_huang_eval_r(KernelGlobals kg,
const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
if (bsdf->extra->R <= 0.0f) {
return zero_float3();
}
/* Get minor axis, assuming major axis is 1. */
const float b = bsdf->aspect_ratio;
const float3 wh = normalize(wi + wo);
const float roughness = bsdf->roughness;
const float roughness2 = sqr(roughness);
const float2 sincos_phi_i = sincos_phi(wi);
/* Maximal sample resolution. */
float res = roughness * 0.7f;
const float h_range = bsdf->extra->h.length();
/* Number of intervals should be even. */
const size_t intervals = 2 * (size_t)ceilf(h_range / res * 0.5f);
/* Modified resolution based on numbers of intervals. */
res = h_range / float(intervals);
/* Integrate using Composite Simpson's 1/3 rule. */
float integral = 0.0f;
for (size_t i = 0; i <= intervals; i++) {
const float h = bsdf->extra->h.min + i * res;
const float gamma_m = h_to_gamma(h, b, wi);
const float3 wm = sphg_dir(bsdf->tilt, gamma_m, b);
if (microfacet_visible(wi, wo, make_float3(wm.x, 0.0f, wm.z), wh)) {
const float jacobian = d_gamma_d_h(sincos_phi_i, gamma_m, b);
const float weight = ((i == 0 || i == intervals) ? 0.5f : (i % 2 + 1)) * jacobian;
const float cos_mi = dot(wm, wi);
const float G = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi, dot(wm, wo));
integral += weight * bsdf_D<MicrofacetType::GGX>(roughness2, dot(wm, wh)) * G *
arc_length(bsdf->extra->e2, gamma_m) *
bsdf_hair_huang_energy_scale(kg, cos_mi, sqrtf(roughness), bsdf->eta);
}
}
/* Simpson coefficient */
integral *= (2.0f / 3.0f * res);
const float F = fresnel_dielectric_cos(dot(wi, wh), bsdf->eta);
return make_spectrum(bsdf->extra->R * 0.25f * F * integral);
}
/* Approximate components beyond TRT (starting TRRT) by summing up a geometric series. Attenuations
* are approximated from previous interactions. */
ccl_device Spectrum bsdf_hair_huang_eval_trrt(const float T, const float R, const Spectrum A)
{
/* `T` could be zero due to total internal reflection. Clamp to avoid numerical issues. */
const float T_avg = max(1.0f - R, 1e-5f);
const Spectrum TRRT_avg = T * sqr(R) * T_avg * A * A * A;
return TRRT_avg / (one_spectrum() - A * (1.0f - T_avg));
}
/* Evaluate components beyond R using numerical integration. TT and TRT are computed via combined
* Monte Carlo-Simpson integration; components beyond TRRT are integrated via Simpson's method. */
ccl_device Spectrum bsdf_hair_huang_eval_residual(KernelGlobals kg,
const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private uint *rng_quadrature)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
if (bsdf->extra->TT <= 0.0f && bsdf->extra->TRT <= 0.0f) {
return zero_spectrum();
}
/* Get minor axis, assuming major axis is 1. */
const float b = bsdf->aspect_ratio;
const Spectrum mu_a = bsdf->sigma;
const float eta = bsdf->eta;
const float inv_eta = 1.0f / eta;
const float roughness = bsdf->roughness;
const float roughness2 = sqr(roughness);
const float sqrt_roughness = sqrtf(roughness);
const float2 sincos_phi_i = sincos_phi(wi);
float res = roughness * 0.8f;
const float h_range = bsdf->extra->h.length();
const size_t intervals = 2 * (size_t)ceilf(h_range / res * 0.5f);
res = h_range / intervals;
Spectrum S_tt = zero_spectrum();
Spectrum S_trt = zero_spectrum();
Spectrum S_trrt = zero_spectrum();
for (size_t i = 0; i <= intervals; i++) {
const float h = bsdf->extra->h.min + i * res;
const float gamma_mi = h_to_gamma(h, b, wi);
const float3 wmi = sphg_dir(bsdf->tilt, gamma_mi, b);
const float3 wmi_ = sphg_dir(0.0f, gamma_mi, b);
/* Sample `wh1`. */
const float2 sample1 = make_float2(lcg_step_float(rng_quadrature),
lcg_step_float(rng_quadrature));
const float3 wh1 = sample_wh(roughness, wi, wmi, sample1);
const float cos_hi1 = dot(wi, wh1);
if (!(cos_hi1 > 0.0f)) {
continue;
}
const float cos_mi1 = dot(wi, wmi);
float cos_theta_t1;
const float T1 = 1.0f - fresnel_dielectric(cos_hi1, eta, &cos_theta_t1);
const float scale1 = bsdf_hair_huang_energy_scale(kg, cos_mi1, sqrt_roughness, eta);
/* Refraction at the first interface. */
const float3 wt = refract_angle(wi, wh1, cos_theta_t1, inv_eta);
const float phi_t = dir_phi(wt);
const float gamma_mt = 2.0f * to_phi(phi_t, b) - gamma_mi;
const float3 wmt = sphg_dir(-bsdf->tilt, gamma_mt, b);
const float3 wmt_ = sphg_dir(0.0f, gamma_mt, b);
const float cos_mo1 = dot(-wt, wmi);
const float cos_mi2 = dot(-wt, wmt);
const float G1o = bsdf_Go(roughness2, cos_mi1, cos_mo1);
if (!microfacet_visible(wi, -wt, wmi, wh1) || !microfacet_visible(wi, -wt, wmi_, wh1)) {
continue;
}
const float jacobian = d_gamma_d_h(sincos_phi_i, gamma_mi, b);
const float weight = ((i == 0 || i == intervals) ? 0.5f : (i % 2 + 1)) * jacobian;
const Spectrum A_t = exp(mu_a / cos_theta(wt) *
(is_circular(b) ?
2.0f * cosf(gamma_mi - phi_t) :
-len(to_point(gamma_mi, b) - to_point(gamma_mt + M_PI_F, b))));
const float scale2 = bsdf_hair_huang_energy_scale(kg, cos_mi2, sqrt_roughness, inv_eta);
/* TT */
if (bsdf->extra->TT > 0.0f) {
if (dot(wo, wt) >= inv_eta - 1e-5f) { /* Total internal reflection otherwise. */
float3 wh2 = -wt + inv_eta * wo;
const float rcp_norm_wh2 = 1.0f / len(wh2);
wh2 *= rcp_norm_wh2;
const float cos_mh2 = dot(wmt, wh2);
if (cos_mh2 >= 0.0f) { /* Microfacet visibility from macronormal. */
const float cos_hi2 = dot(-wt, wh2);
const float cos_ho2 = dot(-wo, wh2);
const float cos_mo2 = dot(-wo, wmt);
const float T2 = (1.0f - fresnel_dielectric_cos(cos_hi2, inv_eta)) * scale2;
const float D2 = bsdf_D<MicrofacetType::GGX>(roughness2, cos_mh2);
const float G2 = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi2, cos_mo2);
const Spectrum result = weight * T1 * scale1 * T2 * D2 * G1o * G2 * A_t / cos_mo1 *
cos_mi1 * cos_hi2 * cos_ho2 * sqr(rcp_norm_wh2);
if (isfinite_safe(result)) {
S_tt += bsdf->extra->TT * result * arc_length(bsdf->extra->e2, gamma_mt);
}
}
}
}
/* TRT and beyond. */
if (bsdf->extra->TRT > 0.0f) {
/* Sample `wh2`. */
const float2 sample2 = make_float2(lcg_step_float(rng_quadrature),
lcg_step_float(rng_quadrature));
const float3 wh2 = sample_wh(roughness, -wt, wmt, sample2);
const float cos_hi2 = dot(-wt, wh2);
if (!(cos_hi2 > 0.0f)) {
continue;
}
const float R2 = fresnel_dielectric_cos(cos_hi2, inv_eta);
const float3 wtr = -reflect(wt, wh2);
if (dot(-wtr, wo) < inv_eta - 1e-5f) {
/* Total internal reflection. */
S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
continue;
}
if (!microfacet_visible(-wt, -wtr, wmt, wh2) || !microfacet_visible(-wt, -wtr, wmt_, wh2)) {
continue;
}
const float phi_tr = dir_phi(wtr);
const float gamma_mtr = gamma_mi - 2.0f * (to_phi(phi_t, b) - to_phi(phi_tr, b)) + M_PI_F;
const float3 wmtr = sphg_dir(-bsdf->tilt, gamma_mtr, b);
const float3 wmtr_ = sphg_dir(0.0f, gamma_mtr, b);
float3 wh3 = wtr + inv_eta * wo;
const float rcp_norm_wh3 = 1.0f / len(wh3);
wh3 *= rcp_norm_wh3;
const float cos_mh3 = dot(wmtr, wh3);
if (cos_mh3 < 0.0f || !microfacet_visible(wtr, -wo, wmtr, wh3) ||
!microfacet_visible(wtr, -wo, wmtr_, wh3))
{
S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
continue;
}
const float cos_hi3 = dot(wh3, wtr);
const float cos_ho3 = dot(wh3, -wo);
const float cos_mi3 = dot(wmtr, wtr);
const float T3 = (1.0f - fresnel_dielectric_cos(cos_hi3, inv_eta)) *
bsdf_hair_huang_energy_scale(kg, cos_mi3, sqrt_roughness, inv_eta);
const float D3 = bsdf_D<MicrofacetType::GGX>(roughness2, cos_mh3);
const Spectrum A_tr = exp(mu_a / cos_theta(wtr) *
-(is_circular(b) ?
2.0f * fabsf(cosf(phi_tr - gamma_mt)) :
len(to_point(gamma_mtr, b) - to_point(gamma_mt, b))));
const float cos_mo2 = dot(wmt, -wtr);
const float G2o = bsdf_Go(roughness2, cos_mi2, cos_mo2);
const float G3 = bsdf_G<MicrofacetType::GGX>(roughness2, cos_mi3, dot(wmtr, -wo));
const Spectrum result = weight * T1 * scale1 * R2 * scale2 * T3 * D3 * G1o * G2o * G3 * A_t *
A_tr / (cos_mo1 * cos_mo2) * cos_mi1 * cos_mi2 * cos_hi3 * cos_ho3 *
sqr(rcp_norm_wh3);
if (isfinite_safe(result)) {
S_trt += bsdf->extra->TRT * result * arc_length(bsdf->extra->e2, gamma_mtr);
}
S_trrt += weight * bsdf_hair_huang_eval_trrt(T1, R2, A_t);
}
}
/* TRRT+ terms, following the approach in [A practical and controllable hair and fur model for
* production path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang, Matt Jen-Yuan, et
* al. */
const float M = longitudinal_scattering(
sin_theta(wi), cos_theta(wi), sin_theta(wo), cos_theta(wo), 4.0f * bsdf->roughness);
const float N = M_1_2PI_F;
const float simpson_coeff = 2.0f / 3.0f * res;
return ((S_tt + S_trt) * sqr(inv_eta) + S_trrt * M * N * M_2_PI_F) * simpson_coeff;
}
ccl_device int bsdf_hair_huang_sample(const KernelGlobals kg,
const ccl_private ShaderClosure *sc,
ccl_private ShaderData *sd,
const float3 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float2 *sampled_roughness)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
const float roughness = bsdf->roughness;
*sampled_roughness = make_float2(roughness, roughness);
kernel_assert(fabsf(bsdf->h) < bsdf->extra->radius);
/* Generate samples. */
float sample_lobe = rand.x;
const float sample_h = rand.y;
const float2 sample_h1 = make_float2(rand.z, lcg_step_float(&sd->lcg_state));
const float2 sample_h2 = make_float2(lcg_step_float(&sd->lcg_state),
lcg_step_float(&sd->lcg_state));
const float2 sample_h3 = make_float2(lcg_step_float(&sd->lcg_state),
lcg_step_float(&sd->lcg_state));
/* Get `wi` in local coordinate. */
const float3 wi = bsdf->extra->wi;
/* Get minor axis, assuming major axis is 1. */
const float b = bsdf->aspect_ratio;
/* Sample `h` for farfield model, as the computed intersection might have numerical issues. */
const float h_div_r = is_nearfield(bsdf) ? bsdf->h / bsdf->extra->radius :
(sample_h * 2.0f - 1.0f);
const float gamma_mi = h_to_gamma(h_div_r, b, wi);
/* Macronormal. */
const float3 wmi_ = sphg_dir(0, gamma_mi, b);
/* Mesonormal. */
float st;
float ct;
fast_sincosf(bsdf->tilt, &st, &ct);
const float3 wmi = make_float3(wmi_.x * ct, st, wmi_.z * ct);
const float cos_mi1 = dot(wmi, wi);
if (cos_mi1 < 0.0f || dot(wmi_, wi) < 0.0f) {
/* Macro/mesonormal invisible. */
*pdf = 0.0f;
return LABEL_NONE;
}
/* Sample R lobe. */
const float roughness2 = sqr(roughness);
const float sqrt_roughness = sqrtf(roughness);
const float3 wh1 = sample_wh(roughness, wi, wmi, sample_h1);
const float3 wr = -reflect(wi, wh1);
/* Ensure that this is a valid sample. */
if (!microfacet_visible(wi, wmi_, wh1)) {
*pdf = 0.0f;
return LABEL_NONE;
}
float cos_theta_t1;
const float R1 = fresnel_dielectric(dot(wi, wh1), bsdf->eta, &cos_theta_t1);
const float scale1 = bsdf_hair_huang_energy_scale(kg, cos_mi1, sqrt_roughness, bsdf->eta);
const float R = bsdf->extra->R * R1 * scale1 * microfacet_visible(wr, wmi_, wh1) *
bsdf_Go(roughness2, cos_mi1, dot(wmi, wr));
/* Sample TT lobe. */
const float inv_eta = 1.0f / bsdf->eta;
const float3 wt = refract_angle(wi, wh1, cos_theta_t1, inv_eta);
const float phi_t = dir_phi(wt);
const float gamma_mt = 2.0f * to_phi(phi_t, b) - gamma_mi;
const float3 wmt = sphg_dir(-bsdf->tilt, gamma_mt, b);
const float3 wmt_ = sphg_dir(0.0f, gamma_mt, b);
const float3 wh2 = sample_wh(roughness, -wt, wmt, sample_h2);
const float3 wtr = -reflect(wt, wh2);
float3 wh3;
float3 wtt;
float3 wtrt;
float3 wmtr;
float3 wtrrt;
Spectrum TT = zero_spectrum();
Spectrum TRT = zero_spectrum();
Spectrum TRRT = zero_spectrum();
const float cos_mi2 = dot(-wt, wmt);
if (cos_mi2 > 0.0f && microfacet_visible(-wt, wmi_, wh1) && microfacet_visible(-wt, wmt_, wh2)) {
const Spectrum mu_a = bsdf->sigma;
const Spectrum A_t = exp(mu_a / cos_theta(wt) *
(is_circular(b) ?
2.0f * cosf(phi_t - gamma_mi) :
-len(to_point(gamma_mi, b) - to_point(gamma_mt + M_PI_F, b))));
float cos_theta_t2;
const float R2 = fresnel_dielectric(dot(-wt, wh2), inv_eta, &cos_theta_t2);
const float T1 = (1.0f - R1) * scale1 * bsdf_Go(roughness2, cos_mi1, dot(wmi, -wt));
const float T2 = 1.0f - R2;
const float scale2 = bsdf_hair_huang_energy_scale(kg, cos_mi2, sqrt_roughness, inv_eta);
wtt = refract_angle(-wt, wh2, cos_theta_t2, bsdf->eta);
if (dot(wmt, -wtt) > 0.0f && T2 > 0.0f && microfacet_visible(-wtt, wmt_, wh2)) {
TT = bsdf->extra->TT * T1 * A_t * T2 * scale2 * bsdf_Go(roughness2, cos_mi2, dot(wmt, -wtt));
}
/* Sample TRT lobe. */
const float phi_tr = dir_phi(wtr);
const float gamma_mtr = gamma_mi - 2.0f * (to_phi(phi_t, b) - to_phi(phi_tr, b)) + M_PI_F;
wmtr = sphg_dir(-bsdf->tilt, gamma_mtr, b);
wh3 = sample_wh(roughness, wtr, wmtr, sample_h3);
float cos_theta_t3;
const float R3 = fresnel_dielectric(dot(wtr, wh3), inv_eta, &cos_theta_t3);
wtrt = refract_angle(wtr, wh3, cos_theta_t3, bsdf->eta);
const float cos_mi3 = dot(wmtr, wtr);
if (cos_mi3 > 0.0f) {
const Spectrum A_tr = exp(mu_a / cos_theta(wtr) *
-(is_circular(b) ?
2.0f * fabsf(cosf(phi_tr - gamma_mt)) :
len(to_point(gamma_mt, b) - to_point(gamma_mtr, b))));
const Spectrum TR = T1 * R2 * scale2 * A_t * A_tr *
bsdf_hair_huang_energy_scale(kg, cos_mi3, sqrt_roughness, inv_eta) *
bsdf_Go(roughness2, cos_mi2, dot(wmt, -wtr));
const float T3 = 1.0f - R3;
if (T3 > 0.0f && microfacet_visible(wtr, -wtrt, make_float3(wmtr.x, 0.0f, wmtr.z), wh3)) {
TRT = bsdf->extra->TRT * TR * make_spectrum(T3) *
bsdf_Go(roughness2, cos_mi3, dot(wmtr, -wtrt));
}
/* Sample TRRT+ terms, following the approach in [A practical and controllable hair and fur
* model for production path tracing](https://doi.org/10.1145/2775280.2792559) by Chiang,
* Matt Jen-Yuan, et al. */
/* Sample `theta_o`. */
const float rand_theta = max(lcg_step_float(&sd->lcg_state), 1e-5f);
const float fac = 1.0f +
4.0f * bsdf->roughness *
logf(rand_theta + (1.0f - rand_theta) * expf(-0.5f / bsdf->roughness));
const float sin_theta_o = -fac * sin_theta(wi) +
cos_from_sin(fac) *
cosf(M_2PI_F * lcg_step_float(&sd->lcg_state)) * cos_theta(wi);
const float cos_theta_o = cos_from_sin(sin_theta_o);
/* Sample `phi_o`. */
const float phi_o = M_2PI_F * lcg_step_float(&sd->lcg_state);
float sin_phi_o;
float cos_phi_o;
fast_sincosf(phi_o, &sin_phi_o, &cos_phi_o);
/* Compute outgoing direction. */
wtrrt = make_float3(sin_phi_o * cos_theta_o, sin_theta_o, cos_phi_o * cos_theta_o);
/* Compute residual term by summing up the geometric series `A * T + A^2 * R * T + ...`.
* Attenuations are approximated from previous interactions. */
const Spectrum A_avg = sqrt(A_t * A_tr);
/* `T` could be zero due to total internal reflection. Clamp to avoid numerical issues. */
const float T_avg = max(0.5f * (T2 + T3), 1e-5f);
const Spectrum A_res = A_avg * T_avg / (one_spectrum() - A_avg * (1.0f - T_avg));
TRRT = TR * R3 * A_res * bsdf_Go(roughness2, cos_mi3, dot(wmtr, -reflect(wtr, wh3)));
}
}
/* Select lobe based on energy. */
const float r = R;
const float tt = average(TT);
const float trt = average(TRT);
const float trrt = average(TRRT);
const float total_energy = r + tt + trt + trrt;
if (total_energy == 0.0f) {
*pdf = 0.0f;
return LABEL_NONE;
}
float3 local_O;
sample_lobe *= total_energy;
if (sample_lobe < r) {
local_O = wr;
*eval = make_spectrum(total_energy);
}
else if (sample_lobe < (r + tt)) {
local_O = wtt;
*eval = TT / tt * total_energy;
}
else if (sample_lobe < (r + tt + trt)) {
local_O = wtrt;
*eval = TRT / trt * total_energy;
}
else {
local_O = wtrrt;
*eval = TRRT / trrt * make_spectrum(total_energy);
}
/* Transform `wo` to global coordinate system. */
*wo = to_global(local_O, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
/* Ensure the same pdf is returned for BSDF and emitter sampling. The importance sampling pdf is
* already factored in the value so this value is only used for MIS. */
*pdf = 1.0f;
return LABEL_GLOSSY | LABEL_REFLECT;
}
ccl_device Spectrum bsdf_hair_huang_eval(KernelGlobals kg,
ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc,
const float3 wo,
ccl_private float *pdf)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
kernel_assert(fabsf(bsdf->h) < bsdf->extra->radius);
/* Transform `wi`/`wo` from global coordinate system to local. */
const float3 local_I = bsdf->extra->wi;
const float3 local_O = to_local(wo, bsdf->N, bsdf->extra->Y, bsdf->extra->Z);
/* TODO: better estimation of the pdf */
*pdf = 1.0f;
/* Early detection of `dot(wo, wmo) < 0`. */
const float tan_tilt = tanf(bsdf->tilt);
if (tan_tilt * tan_theta(local_O) < -1.0f) {
return zero_spectrum();
}
/* Compute visible azimuthal range from the incoming direction. */
const float half_span = acosf(fmaxf(-tan_tilt * tan_theta(local_I), 0.0f));
if (isnan_safe(half_span)) {
/* Early detection of `dot(wi, wmi) < 0`. */
return zero_spectrum();
}
const float r = bsdf->extra->radius;
const float b = bsdf->aspect_ratio;
const float phi_i = is_circular(b) ? 0.0f : dir_phi(local_I);
Interval<float> h = {phi_to_h(phi_i + half_span, b, local_I),
phi_to_h(phi_i - half_span, b, local_I)};
/* Length of the integral interval. */
float dh = 2.0f;
if (is_nearfield(bsdf)) {
/* Reduce the integration interval to the subset that's visible to the current pixel.
* Inspired by [An Efficient and Practical Near and Far Field Fur Reflectance Model]
* (https://sites.cs.ucsb.edu/~lingqi/publications/paper_fur2.pdf) by Ling-Qi Yan, Henrik Wann
* Jensen and Ravi Ramamoorthi. */
const float half_pixel = bsdf->extra->pixel_coverage;
const Interval<float> nearfield_h = intervals_intersection(
Interval<float>{-r, r}, {bsdf->h - half_pixel, bsdf->h + half_pixel});
dh = nearfield_h.length() / r;
h = intervals_intersection(h, nearfield_h);
}
/* Pre-divide by radius for easier conversion to `gamma`. */
h /= r;
/* Clamp for numerical stability at the boundaries. */
h = intervals_intersection(h, {-0.999f, 0.999f});
if (h.is_empty()) {
/* No overlap between the valid range and the visible range. Can happen at grazing `theta`
* angles. */
return zero_spectrum();
}
bsdf->extra->h = h;
const float projected_area = cos_theta(local_I) * dh;
return (bsdf_hair_huang_eval_r(kg, sc, local_I, local_O) +
bsdf_hair_huang_eval_residual(kg, sc, local_I, local_O, &sd->lcg_state)) /
projected_area;
}
/* Implements Filter Glossy by capping the effective roughness. */
ccl_device void bsdf_hair_huang_blur(ccl_private ShaderClosure *sc, const float roughness)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
bsdf->roughness = fmaxf(roughness, bsdf->roughness);
}
/* Hair Albedo. Computed by summing up geometric series, assuming circular cross-section and
* specular reflection. */
ccl_device Spectrum bsdf_hair_huang_albedo(const ccl_private ShaderData * /*sd*/,
const ccl_private ShaderClosure *sc)
{
ccl_private HuangHairBSDF *bsdf = (ccl_private HuangHairBSDF *)sc;
const float3 wmi = make_float3(bsdf->h, 0.0f, cos_from_sin(bsdf->h));
float cos_t;
const float f = fresnel_dielectric(dot(wmi, bsdf->extra->wi), bsdf->eta, &cos_t);
const float3 wt = refract_angle(bsdf->extra->wi, wmi, cos_t, 1.0f / bsdf->eta);
const Spectrum A = exp(2.0f * bsdf->sigma * cos_t / (1.0f - sqr(wt.y)));
return safe_divide(A - 2.0f * f * A + f, one_spectrum() - f * A);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/alloc.h"
CCL_NAMESPACE_BEGIN
struct RayPortalClosure {
SHADER_CLOSURE_BASE;
float3 P;
float3 D;
};
static_assert(sizeof(ShaderClosure) >= sizeof(RayPortalClosure), "RayPortalClosure is too large!");
ccl_device void bsdf_ray_portal_setup(ccl_private ShaderData *sd,
const Spectrum weight,
const float3 position,
float3 direction)
{
/* Check cutoff weight. */
const float sample_weight = fabsf(average(weight));
if (!(sample_weight >= CLOSURE_WEIGHT_CUTOFF)) {
return;
}
sd->closure_transparent_extinction += weight;
ccl_private RayPortalClosure *pc = (ccl_private RayPortalClosure *)closure_alloc(
sd, sizeof(RayPortalClosure), CLOSURE_BSDF_RAY_PORTAL_ID, weight);
if (pc) {
sd->flag |= SD_BSDF | SD_RAY_PORTAL;
if (is_zero(direction)) {
direction = -sd->wi;
}
pc->sample_weight = sample_weight;
pc->N = sd->N;
pc->P = position;
pc->D = safe_normalize(direction);
}
}
ccl_device Spectrum bsdf_ray_portal_eval(const ccl_private ShaderClosure * /*sc*/,
const float3 /*wi*/,
const float3 /*wo*/,
ccl_private float *pdf)
{
*pdf = 0.0f;
return zero_spectrum();
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,112 @@
/* SPDX-FileCopyrightText: Copyright 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
/* Shading model by Tizian Zeltner, Brent Burley, Matt Jen-Yuan Chiang:
* "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines" (2022)
* https://tizianzeltner.com/projects/Zeltner2022Practical/
*/
#include "kernel/sample/mapping.h"
#include "kernel/util/lookup_table.h"
CCL_NAMESPACE_BEGIN
struct SheenBsdf {
SHADER_CLOSURE_BASE;
float roughness;
float transformA, transformB;
float3 T, B;
};
static_assert(sizeof(ShaderClosure) >= sizeof(SheenBsdf), "SheenBsdf is too large!");
ccl_device int bsdf_sheen_setup(KernelGlobals kg,
const ccl_private ShaderData *sd,
ccl_private SheenBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_SHEEN_ID;
bsdf->roughness = clamp(bsdf->roughness, 1e-3f, 1.0f);
make_orthonormals_safe_tangent(bsdf->N, sd->wi, &bsdf->T, &bsdf->B);
const float cosNI = dot(bsdf->N, sd->wi);
const int offset = kernel_data.tables.sheen_ltc;
bsdf->transformA = lookup_table_read_2D(kg, cosNI, bsdf->roughness, offset, 32, 32);
bsdf->transformB = lookup_table_read_2D(kg, cosNI, bsdf->roughness, offset + 32 * 32, 32, 32);
const float albedo = lookup_table_read_2D(
kg, cosNI, bsdf->roughness, offset + 2 * 32 * 32, 32, 32);
/* If the given roughness and angle result in an invalid LTC, skip the closure. */
if (fabsf(bsdf->transformA) < 1e-5f || albedo < 1e-5f) {
bsdf->type = CLOSURE_NONE_ID;
bsdf->sample_weight = 0.0f;
return 0;
}
bsdf->weight *= albedo;
bsdf->sample_weight *= albedo;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
ccl_device Spectrum bsdf_sheen_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private SheenBsdf *bsdf = (const ccl_private SheenBsdf *)sc;
const float3 N = bsdf->N;
const float3 T = bsdf->T;
const float3 B = bsdf->B;
const float a = bsdf->transformA;
const float b = bsdf->transformB;
const float3 localO = to_local(wo, T, B, N);
const float lenSqr = sqr(a * localO.x + b * localO.z) + sqr(a * localO.y) + sqr(localO.z);
const float val = M_1_PI_F * fmaxf(localO.z, 0.0f) * sqr(a / lenSqr);
*pdf = val;
return make_spectrum(val);
}
ccl_device int bsdf_sheen_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private SheenBsdf *bsdf = (const ccl_private SheenBsdf *)sc;
const float3 N = bsdf->N;
const float3 T = bsdf->T;
const float3 B = bsdf->B;
const float a = bsdf->transformA;
const float b = bsdf->transformB;
const float2 disk = sample_uniform_disk(rand);
const float diskZ = safe_sqrtf(1.0f - dot(disk, disk));
const float3 localO = normalize(make_float3((disk.x - diskZ * b), disk.y, diskZ * a));
*wo = to_global(localO, T, B, N);
if (dot(Ng, *wo) <= 0) {
*eval = zero_spectrum();
*pdf = 0.0f;
return LABEL_REFLECT | LABEL_DIFFUSE;
}
const float lenSqr = sqr(a * localO.x + b * localO.z) + sqr(a * localO.y) + sqr(localO.z);
const float val = M_1_PI_F * localO.z * sqr(a / lenSqr);
*pdf = val;
*eval = make_spectrum(val);
return LABEL_REFLECT | LABEL_DIFFUSE;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,189 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
struct ToonBsdf {
SHADER_CLOSURE_BASE;
float size;
float smooth;
};
static_assert(sizeof(ShaderClosure) >= sizeof(ToonBsdf), "ToonBsdf is too large!");
ccl_device_inline int bsdf_toon_setup_common(ccl_private ToonBsdf *bsdf)
{
bsdf->size = clamp(bsdf->size, 1e-5f, 1.0f) * M_PI_2_F;
bsdf->smooth = saturatef(bsdf->smooth) * M_PI_2_F;
return SD_BSDF | SD_BSDF_HAS_EVAL;
}
/* DIFFUSE TOON */
ccl_device int bsdf_diffuse_toon_setup(ccl_private ToonBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_DIFFUSE_TOON_ID;
return bsdf_toon_setup_common(bsdf);
}
ccl_device float bsdf_toon_get_intensity(const float max_angle,
const float smooth,
const float angle)
{
float is;
if (angle < max_angle) {
is = 1.0f;
}
else if (angle < (max_angle + smooth) && smooth != 0.0f) {
is = (1.0f - (angle - max_angle) / smooth);
}
else {
is = 0.0f;
}
return is;
}
ccl_device float bsdf_toon_get_sample_angle(const float max_angle, const float smooth)
{
return fminf(max_angle + smooth, M_PI_2_F);
}
ccl_device Spectrum bsdf_diffuse_toon_eval(const ccl_private ShaderClosure *sc,
const float3 /*wi*/,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private ToonBsdf *bsdf = (const ccl_private ToonBsdf *)sc;
const float max_angle = bsdf->size;
const float smooth = bsdf->smooth;
const float cosNO = dot(bsdf->N, wo);
if (cosNO >= 0.0f) {
const float angle = safe_acosf(fmaxf(cosNO, 0.0f));
const float sample_angle = bsdf_toon_get_sample_angle(max_angle, smooth);
if (angle < sample_angle) {
const float eval = bsdf_toon_get_intensity(max_angle, smooth, angle);
*pdf = M_1_2PI_F / one_minus_cos(sample_angle);
return make_spectrum(*pdf * eval);
}
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_diffuse_toon_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 /*wi*/,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private ToonBsdf *bsdf = (const ccl_private ToonBsdf *)sc;
const float max_angle = bsdf->size;
const float smooth = bsdf->smooth;
const float sample_angle = bsdf_toon_get_sample_angle(max_angle, smooth);
float cosNO;
*wo = sample_uniform_cone(bsdf->N, one_minus_cos(sample_angle), rand, &cosNO, pdf);
if (dot(Ng, *wo) > 0.0f) {
const float angle = acosf(cosNO);
*eval = make_spectrum(*pdf * bsdf_toon_get_intensity(max_angle, smooth, angle));
return LABEL_REFLECT | LABEL_DIFFUSE;
}
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
/* GLOSSY TOON */
ccl_device int bsdf_glossy_toon_setup(ccl_private ToonBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_GLOSSY_TOON_ID;
return bsdf_toon_setup_common(bsdf);
}
ccl_device Spectrum bsdf_glossy_toon_eval(const ccl_private ShaderClosure *sc,
const float3 wi,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private ToonBsdf *bsdf = (const ccl_private ToonBsdf *)sc;
const float max_angle = bsdf->size;
const float smooth = bsdf->smooth;
const float cosNI = dot(bsdf->N, wi);
const float cosNO = dot(bsdf->N, wo);
if (cosNI > 0 && cosNO > 0) {
/* reflect the view vector */
const float3 R = (2 * cosNI) * bsdf->N - wi;
const float cosRO = dot(R, wo);
const float angle = safe_acosf(fmaxf(cosRO, 0.0f));
const float sample_angle = bsdf_toon_get_sample_angle(max_angle, smooth);
if (angle < sample_angle) {
const float eval = bsdf_toon_get_intensity(max_angle, smooth, angle);
*pdf = M_1_2PI_F / one_minus_cos(sample_angle);
return make_spectrum(*pdf * eval);
}
}
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_glossy_toon_sample(const ccl_private ShaderClosure *sc,
const float3 Ng,
const float3 wi,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private ToonBsdf *bsdf = (const ccl_private ToonBsdf *)sc;
const float max_angle = bsdf->size;
const float smooth = bsdf->smooth;
const float cosNI = dot(bsdf->N, wi);
if (cosNI > 0) {
/* reflect the view vector */
const float3 R = (2 * cosNI) * bsdf->N - wi;
const float sample_angle = bsdf_toon_get_sample_angle(max_angle, smooth);
float cosRO;
*wo = sample_uniform_cone(R, one_minus_cos(sample_angle), rand, &cosRO, pdf);
/* make sure the direction we chose is still in the right hemisphere */
if (dot(Ng, *wo) > 0.0f && dot(bsdf->N, *wo) > 0.0f) {
const float angle = acosf(cosRO);
*eval = make_spectrum(*pdf * bsdf_toon_get_intensity(max_angle, smooth, angle));
return LABEL_GLOSSY | LABEL_REFLECT;
}
}
*pdf = 0.0f;
*eval = zero_spectrum();
return LABEL_NONE;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/alloc.h"
CCL_NAMESPACE_BEGIN
ccl_device void bsdf_transparent_setup(ccl_private ShaderData *sd,
const Spectrum weight,
const uint32_t path_flag)
{
/* Check cutoff weight. */
const float sample_weight = fabsf(average(weight));
if (!(sample_weight >= CLOSURE_WEIGHT_CUTOFF)) {
return;
}
sd->closure_transparent_extinction += weight;
if (sd->flag & SD_TRANSPARENT) {
/* Add weight to existing transparent BSDF. */
for (int i = 0; i < sd->num_closure; i++) {
ccl_private ShaderClosure *sc = &sd->closure[i];
if (sc->type == CLOSURE_BSDF_TRANSPARENT_ID) {
sc->weight += weight;
sc->sample_weight += sample_weight;
break;
}
}
}
else {
sd->flag |= SD_BSDF | SD_TRANSPARENT;
if (path_flag & PATH_RAY_TERMINATE) {
/* In this case the number of closures is set to zero to disable
* all others, but we still want to get transparency so increase
* the number just for this. */
sd->num_closure_left = 1;
}
/* Create new transparent BSDF. */
ccl_private ShaderClosure *bsdf = closure_alloc(
sd, sizeof(ShaderClosure), CLOSURE_BSDF_TRANSPARENT_ID, weight);
if (bsdf) {
bsdf->sample_weight = sample_weight;
bsdf->N = sd->N;
}
else if (path_flag & PATH_RAY_TERMINATE) {
sd->num_closure_left = 0;
}
}
}
ccl_device Spectrum bsdf_transparent_eval(const ccl_private ShaderClosure * /*sc*/,
const float3 /*wi*/,
const float3 /*wo*/,
ccl_private float *pdf)
{
*pdf = 0.0f;
return zero_spectrum();
}
ccl_device int bsdf_transparent_sample(const ccl_private ShaderClosure * /*sc*/,
const float3 /*Ng*/,
const float3 wi,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
// only one direction is possible
*wo = -wi;
/* Some high number for MIS. */
*pdf = 1e6f;
*eval = one_spectrum() * 1e6f;
return LABEL_TRANSMIT | LABEL_TRANSPARENT;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,574 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
#include "kernel/util/colorspace.h"
#include "kernel/util/lookup_table.h"
#include "util/color.h"
#include "util/types_spectrum.h"
CCL_NAMESPACE_BEGIN
struct FresnelThinFilm {
float thickness;
float ior;
};
template<typename T> struct complex {
T re;
T im;
ccl_device_inline_method complex<T> operator*=(ccl_private const complex<T> &other)
{
const T im = this->re * other.im + this->im * other.re;
this->re = this->re * other.re - this->im * other.im;
this->im = im;
return *this;
}
ccl_device_inline_method complex<T> operator*(ccl_private const float &other)
{
return complex<T>{this->re * other, this->im * other};
}
};
/* Compute fresnel reflectance for perpendicular (aka S-) and parallel (aka P-) polarized light.
* If requested by the caller, r_cos_phi is set to the cosine of the phase shift on reflection.
* Also returns the dot product of the refracted ray and the normal as `cos_theta_t`, as it is
* used when computing the direction of the refracted ray. */
ccl_device float2 fresnel_dielectric_polarized(float cos_theta_i,
const float eta,
ccl_private float *r_cos_theta_t,
ccl_private float2 *r_cos_phi)
{
kernel_assert(!isnan_safe(cos_theta_i));
/* Using Snell's law, calculate the squared cosine of the angle between the surface normal and
* the transmitted ray. */
const float eta_cos_theta_t_sq = sqr(eta) - (1.0f - sqr(cos_theta_i));
if (eta_cos_theta_t_sq <= 0) {
/* Total internal reflection. */
if (r_cos_phi) {
/* The following code would compute the proper phase shift on TIR.
* However, for the current user of this computation (the iridescence code),
* this doesn't actually affect the result, so don't bother with the computation for now.
*
* `const float fac = sqrtf(1.0f - sqr(cosThetaI) - sqr(eta));`
* `r_phi->x = -2.0f * atanf(fac / cosThetaI);`
* `r_phi->y = -2.0f * atanf(fac / (cosThetaI * sqr(eta)));`
*/
*r_cos_phi = one_float2();
}
return one_float2();
}
cos_theta_i = fabsf(cos_theta_i);
/* Relative to the surface normal. */
const float cos_theta_t = -safe_sqrtf(eta_cos_theta_t_sq) / eta;
if (r_cos_theta_t) {
*r_cos_theta_t = cos_theta_t;
}
/* Amplitudes of reflected waves. */
const float r_s = (cos_theta_i + eta * cos_theta_t) / (cos_theta_i - eta * cos_theta_t);
const float r_p = (cos_theta_t + eta * cos_theta_i) / (eta * cos_theta_i - cos_theta_t);
if (r_cos_phi) {
*r_cos_phi = make_float2(2 * (r_s >= 0.0f) - 1, 2 * (r_p >= 0.0f) - 1);
}
/* Return squared amplitude to get the fraction of reflected energy. */
return make_float2(sqr(r_s), sqr(r_p));
}
/* Compute fresnel reflectance for unpolarized light. */
ccl_device_forceinline float fresnel_dielectric(const float cos_theta_i,
const float eta,
ccl_private float *r_cos_theta_t)
{
return average(fresnel_dielectric_polarized(cos_theta_i, eta, r_cos_theta_t, nullptr));
}
/* Refract the incident ray, given the cosine of the refraction angle and the relative refractive
* index of the incoming medium w.r.t. the outgoing medium. */
ccl_device_inline float3 refract_angle(const float3 incident,
const float3 normal,
const float cos_theta_t,
const float inv_eta)
{
return (inv_eta * dot(normal, incident) + cos_theta_t) * normal - inv_eta * incident;
}
ccl_device float fresnel_dielectric_cos(const float cosi, const float eta)
{
// compute fresnel reflectance without explicitly computing
// the refracted direction
const float c = fabsf(cosi);
float g = eta * eta - 1 + c * c;
if (g > 0) {
g = sqrtf(g);
const float A = (g - c) / (g + c);
const float B = (c * (g + c) - 1) / (c * (g - c) + 1);
return 0.5f * A * A * (1 + B * B);
}
return 1.0f; // TIR(no refracted component)
}
/* Approximates the average single-scattering Fresnel for a given IOR.
* This is defined as the integral over 0...1 of 2*cosI * F(cosI, eta) d_cosI, with F being
* the real dielectric Fresnel.
* The implementation here uses a numerical fit from "Revisiting Physically Based Shading
* at Imageworks" by Christopher Kulla and Alejandro Conty. */
ccl_device_inline float fresnel_dielectric_Fss(const float eta)
{
if (eta < 1.0f) {
return 0.997118f + eta * (0.1014f - eta * (0.965241f + eta * 0.130607f));
}
return (eta - 1.0f) / (4.08567f + 1.00071f * eta);
}
/* Computes the average single-scattering Fresnel for the F82 metallic model. */
ccl_device_inline Spectrum fresnel_f82_Fss(const Spectrum F0, const Spectrum B)
{
return mix(F0, one_spectrum(), 1.0f / 21.0f) - B * (1.0f / 126.0f);
}
/* Precompute the B term for the F82 metallic model, given a tint factor. */
ccl_device_inline Spectrum fresnel_f82tint_B(const Spectrum F0, const Spectrum tint)
{
/* In the classic F82 model, the F82 input directly determines the value of the Fresnel
* model at ~82°, similar to F0 and F90.
* With F82-Tint, on the other hand, the value at 82° is the value of the classic Schlick
* model multiplied by the tint input.
* Therefore, the factor follows by setting F82Tint(cosI) = FSchlick(cosI) - b*cosI*(1-cosI)^6
* and F82Tint(acos(1/7)) = FSchlick(acos(1/7)) * f82_tint and solving for b. */
const float f = 6.0f / 7.0f;
const float f5 = sqr(sqr(f)) * f;
const Spectrum F_schlick = mix(F0, one_spectrum(), f5);
return F_schlick * (7.0f / (f5 * f)) * (one_spectrum() - tint);
}
/* Precompute the B term for the F82 metallic model, given the F82 value. */
ccl_device_inline Spectrum fresnel_f82_B(const Spectrum F0, const Spectrum F82)
{
const float f = 6.0f / 7.0f;
const float f5 = sqr(sqr(f)) * f;
const Spectrum F_schlick = mix(F0, one_spectrum(), f5);
return (7.0f / (f5 * f)) * (F_schlick - F82);
}
ccl_device_inline float fresnel_f82_B(const float F0, const float F82)
{
const float f = 6.0f / 7.0f;
const float f5 = sqr(sqr(f)) * f;
const float F_schlick = mix(F0, 1.0f, f5);
return (7.0f / (f5 * f)) * (F_schlick - F82);
}
/* Evaluate the F82 metallic model for the given parameters. */
ccl_device_inline Spectrum fresnel_f82(const float cosi, const Spectrum F0, const Spectrum B)
{
const float s = saturatef(1.0f - cosi);
const float s5 = sqr(sqr(s)) * s;
const Spectrum F_schlick = mix(F0, one_spectrum(), s5);
return saturate(F_schlick - B * cosi * s5 * s);
}
ccl_device_inline float fresnel_f82(const float cosi, const float F0, const float B)
{
const float s = saturatef(1.0f - cosi);
const float s5 = sqr(sqr(s)) * s;
const float F_schlick = mix(F0, 1.0f, s5);
return clamp(F_schlick - B * cosi * s5 * s, 0.0f, 1.0f);
}
/* Evaluates the Fresnel equations at a dielectric-conductor interface, calculating reflectances
* and phase shifts due to reflection if requested. The phase shifts phi_s and phi_p are returned
* as phasor_s = exp(i * phi_s) and phasor_p = exp(i * phi_p).
* This code is based on equations from section 14.4.1 of Principles of Optics 7th ed. by Born and
* Wolf, but uses `n + ik` instead of `n(1 + ik)` for IOR. The phase shifts are calculated so that
* phi_p = phi_s at 90 degree incidence to match fresnel_dielectric_polarized. */
ccl_device_forceinline void fresnel_conductor_polarized(
const float cosi,
const float ambient_ior,
const complex<float> conductor_ior,
const float F82,
ccl_private float &r_R_s,
ccl_private float &r_R_p,
ccl_private complex<float> *r_phasor_s = nullptr,
ccl_private complex<float> *r_phasor_p = nullptr)
{
const float eta1 = ambient_ior;
const float eta2 = conductor_ior.re;
const float k2 = conductor_ior.im;
const float eta1_sq = sqr(eta1);
const float eta2_sq = sqr(eta2);
const float k2_sq = sqr(k2);
const float two_eta2_k2 = 2.0f * eta2 * k2;
const float t1 = eta2_sq - k2_sq - eta1_sq * (1.0f - sqr(cosi));
const float t2 = sqrt(sqr(t1) + sqr(two_eta2_k2));
const float u_sq = max(0.5f * (t2 + t1), 0.0f);
const float v_sq = max(0.5f * (t2 - t1), 0.0f);
const float u = sqrt(u_sq);
const float v = sqrt(v_sq);
if (F82 >= 0.0f) {
/* Calculate reflectance using the F82 model if the caller requested it. */
/* Scale n and k by the film ior, and recompute F0. */
const float n = eta2 / eta1;
const float k_sq = sqr(k2 / eta1);
const float F0 = (sqr(n - 1.0f) + k_sq) / (sqr(n + 1.0f) + k_sq);
r_R_s = fresnel_f82(cosi, F0, fresnel_f82_B(F0, F82));
r_R_p = r_R_s;
}
else {
r_R_s = safe_divide(sqr(eta1 * cosi - u) + v_sq, sqr(eta1 * cosi + u) + v_sq);
const float t3 = (eta2_sq - k2_sq) * cosi;
const float t4 = two_eta2_k2 * cosi;
r_R_p = safe_divide(sqr(t3 - eta1 * u) + sqr(t4 - eta1 * v),
sqr(t3 + eta1 * u) + sqr(t4 + eta1 * v));
}
if (r_phasor_s && r_phasor_p) {
const float re_s = -u_sq - v_sq + sqr(eta1 * cosi);
const float im_s = -2.0f * eta1 * cosi * v;
const float mag_s = sqrt(sqr(re_s) + sqr(im_s));
r_phasor_s->re = (mag_s == 0.0f) ? 1.0f : re_s / mag_s;
r_phasor_s->im = (mag_s == 0.0f) ? 0.0f : im_s / mag_s;
const float re_p = sqr((eta2_sq + k2_sq) * cosi) - eta1_sq * (u_sq + v_sq);
const float im_p = 2.0f * eta1 * cosi * (two_eta2_k2 * u - (eta2_sq - k2_sq) * v);
const float mag_p = sqrt(sqr(re_p) + sqr(im_p));
r_phasor_p->re = mag_p == 0.0f ? 1.0f : re_p / mag_p;
r_phasor_p->im = mag_p == 0.0f ? 0.0f : im_p / mag_p;
}
}
/* Calculates Fresnel reflectance at a dielectric-conductor interface given the relative IOR.
*/
ccl_device Spectrum fresnel_conductor(const float cosi, const complex<Spectrum> ior)
{
float R_s_x, R_s_y, R_s_z, R_p_x, R_p_y, R_p_z;
fresnel_conductor_polarized(
cosi, 1.0f, {ior.re.x, ior.im.x}, -1.0f, R_s_x, R_p_x, nullptr, nullptr);
fresnel_conductor_polarized(
cosi, 1.0f, {ior.re.y, ior.im.y}, -1.0f, R_s_y, R_p_y, nullptr, nullptr);
fresnel_conductor_polarized(
cosi, 1.0f, {ior.re.z, ior.im.z}, -1.0f, R_s_z, R_p_z, nullptr, nullptr);
return (make_float3(R_s_x, R_s_y, R_s_z) + make_float3(R_p_x, R_p_y, R_p_z)) * 0.5f;
}
/* Approximates the average single-scattering Fresnel for a physical conductor. */
ccl_device_inline Spectrum fresnel_conductor_Fss(const complex<Spectrum> ior)
{
/* In order to estimate Fss of the conductor, we fit the F82 model to it based on the
* value at 0° and ~82° and then use the analytic expression for its Fss. */
const Spectrum F0 = fresnel_conductor(1.0f, ior);
const Spectrum F82 = fresnel_conductor(1.0f / 7.0f, ior);
return saturate(fresnel_f82_Fss(F0, fresnel_f82_B(F0, F82)));
}
ccl_device float ior_from_F0(const float f0)
{
const float sqrt_f0 = sqrtf(clamp(f0, 0.0f, 0.99f));
return (1.0f + sqrt_f0) / (1.0f - sqrt_f0);
}
ccl_device float F0_from_ior(const float ior)
{
return sqr((ior - 1.0f) / (ior + 1.0f));
}
ccl_device float schlick_fresnel(const float u)
{
const float m = clamp(1.0f - u, 0.0f, 1.0f);
const float m2 = m * m;
return m2 * m2 * m; // pow(m, 5)
}
/* Calculate the fresnel color, which is a blend between white and the F0 color */
ccl_device_forceinline Spectrum interpolate_fresnel_color(const float3 L,
const float3 H,
const float ior,
Spectrum F0)
{
/* Compute the real Fresnel term and remap it from real_F0..1 to F0..1.
* The reason why we use this remapping instead of directly doing the
* Schlick approximation mix(F0, 1.0, (1.0-cosLH)^5) is that for cases
* with similar IORs (e.g. ice in water), the relative IOR can be close
* enough to 1.0 that the Schlick approximation becomes inaccurate. */
const float real_F = fresnel_dielectric_cos(dot(L, H), ior);
const float real_F0 = fresnel_dielectric_cos(1.0f, ior);
return mix(F0, one_spectrum(), inverse_lerp(real_F0, 1.0f, real_F));
}
/* If the shading normal results in specular reflection in the lower hemisphere, raise the shading
* normal towards the geometry normal so that the specular reflection is just above the surface.
* Only used for glossy materials. */
ccl_device float3 ensure_valid_specular_reflection(const float3 Ng, const float3 I, float3 N)
{
const float3 R = 2 * dot(N, I) * N - I;
const float Iz = dot(I, Ng);
kernel_assert(Iz >= 0);
/* Reflection rays may always be at least as shallow as the incoming ray. */
const float threshold = min(0.9f * Iz, 0.01f);
if (dot(Ng, R) >= threshold) {
return N;
}
/* Form coordinate system with Ng as the Z axis and N inside the X-Z-plane.
* The X axis is found by normalizing the component of N that's orthogonal to Ng.
* The Y axis isn't actually needed.
*/
const float3 X = safe_normalize_fallback(N - dot(N, Ng) * Ng, N);
/* Calculate N.z and N.x in the local coordinate system.
*
* The goal of this computation is to find a N' that is rotated towards Ng just enough
* to lift R' above the threshold (here called t), therefore dot(R', Ng) = t.
*
* According to the standard reflection equation,
* this means that we want dot(2*dot(N', I)*N' - I, Ng) = t.
*
* Since the Z axis of our local coordinate system is Ng, dot(x, Ng) is just x.z, so we get
* 2*dot(N', I)*N'.z - I.z = t.
*
* The rotation is simple to express in the coordinate system we formed -
* since N lies in the X-Z-plane, we know that N' will also lie in the X-Z-plane,
* so N'.y = 0 and therefore dot(N', I) = N'.x*I.x + N'.z*I.z .
*
* Furthermore, we want N' to be normalized, so N'.x = sqrt(1 - N'.z^2).
*
* With these simplifications, we get the equation
* 2*(sqrt(1 - N'.z^2)*I.x + N'.z*I.z)*N'.z - I.z = t,
* or
* 2*sqrt(1 - N'.z^2)*I.x*N'.z = t + I.z * (1 - 2*N'.z^2),
* after rearranging terms.
* Raise both sides to the power of two and substitute terms with
* a = I.x^2 + I.z^2,
* b = 2*(a + Iz*t),
* c = (Iz + t)^2,
* we obtain
* 4*a*N'.z^4 - 2*b*N'.z^2 + c = 0.
*
* The only unknown here is N'.z, so we can solve for that.
*
* The equation has four solutions in general, two can immediately be discarded because they're
* negative so N' would lie in the lower hemisphere; one solves
* 2*sqrt(1 - N'.z^2)*I.x*N'.z = -(t + I.z * (1 - 2*N'.z^2))
* instead of the original equation (before squaring both sides).
* Therefore only one root is valid.
*/
const float Ix = dot(I, X);
const float a = sqr(Ix) + sqr(Iz);
const float b = 2.0f * (a + Iz * threshold);
const float c = sqr(threshold + Iz);
/* In order that the root formula solves 2*sqrt(1 - N'.z^2)*I.x*N'.z = t + I.z - 2*I.z*N'.z^2,
* Ix and (t + I.z * (1 - 2*N'.z^2)) must have the same sign (the rest terms are non-negative by
* definition). */
const float Nz2 = (Ix < 0) ? 0.25f * (b + safe_sqrtf(sqr(b) - 4.0f * a * c)) / a :
0.25f * (b - safe_sqrtf(sqr(b) - 4.0f * a * c)) / a;
const float Nx = safe_sqrtf(1.0f - Nz2);
const float Nz = safe_sqrtf(Nz2);
return Nx * X + Nz * Ng;
}
/* Do not call #ensure_valid_specular_reflection if the primitive type is curve or if the geometry
* normal and the shading normal is the same. */
ccl_device float3 maybe_ensure_valid_specular_reflection(ccl_private ShaderData *sd,
const float3 N)
{
if ((sd->flag & SD_USE_BUMP_MAP_CORRECTION) == 0) {
return N;
}
if ((sd->type & PRIMITIVE_CURVE) || isequal(sd->Ng, N)) {
return N;
}
return ensure_valid_specular_reflection(sd->Ng, sd->wi, N);
}
/* Principled Hair albedo and absorption coefficients. */
ccl_device_inline float bsdf_principled_hair_albedo_roughness_scale(
const float azimuthal_roughness)
{
const float x = azimuthal_roughness;
return (((((0.245f * x) + 5.574f) * x - 10.73f) * x + 2.532f) * x - 0.215f) * x + 5.969f;
}
ccl_device_inline Spectrum
bsdf_principled_hair_sigma_from_reflectance(const Spectrum color, const float azimuthal_roughness)
{
const Spectrum sigma = log(max(color, zero_spectrum())) /
bsdf_principled_hair_albedo_roughness_scale(azimuthal_roughness);
return sigma * sigma;
}
ccl_device_inline Spectrum bsdf_principled_hair_sigma_from_concentration(const float eumelanin,
const float pheomelanin)
{
const float3 eumelanin_color = make_float3(0.506f, 0.841f, 1.653f);
const float3 pheomelanin_color = make_float3(0.343f, 0.733f, 1.924f);
return eumelanin * rgb_to_spectrum(eumelanin_color) +
pheomelanin * rgb_to_spectrum(pheomelanin_color);
}
/* Computes the weight for base closure(s) which are layered under another closure.
* layer_albedo is an estimate of the top layer's reflectivity, while weight is the closure weight
* of the entire base+top combination. */
ccl_device_inline Spectrum closure_layering_weight(const Spectrum layer_albedo,
const Spectrum weight)
{
return weight * saturatef(1.0f - reduce_max(safe_divide_color(layer_albedo, weight)));
}
/* ******** Thin-film iridescence implementation ********
*
* Based on "A Practical Extension to Microfacet Theory for the Modeling of Varying Iridescence"
* by Laurent Belcour and Pascal Barla.
* https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html.
*/
/**
* Evaluate the sensitivity functions for the Fourier-space spectral integration.
*/
ccl_device_inline complex<float> iridescence_lookup_sensitivity_channel(KernelGlobals kg,
const int channel,
const float OPD)
{
/* The LUT covers 0 to 60 um. */
const float x = M_2PI_F * OPD / 60000.0f;
const int size = THIN_FILM_TABLE_SIZE;
const int base = kernel_data.tables.thin_film_table;
return {lookup_table_read(kg, x, base + channel * size, size),
lookup_table_read(kg, x, base + (channel + 3) * size, size)};
}
ccl_device_inline float iridescence_airy_summation_channel(KernelGlobals kg,
const int channel,
const float R12,
const float R23,
const float OPD,
const complex<float> phasor)
{
const float T121 = 1.0f - R12;
const float R123 = R12 * R23;
const float r123 = sqrtf(R123);
const float Rs = sqr(T121) * R23 / (1.0f - R123);
/* Initialize complex number for exp(i * phi)^m, equivalent to {cos(m * phi), sin(m * phi)} as
* used in equation 10. */
complex<float> accumulator = phasor;
/* Perform summation over path order differences (equation 10). */
float R = Rs + R12; /* C0 */
float Cm = Rs - T121;
/* Truncate after m=3, higher differences have barely any impact. */
for (int m = 1; m < 4; m++) {
Cm *= r123;
const complex<float> S = iridescence_lookup_sensitivity_channel(kg, channel, m * OPD);
R += Cm * 2.0f * (accumulator.re * S.re + accumulator.im * S.im);
accumulator *= phasor;
}
return R;
}
template<bool conductive>
ccl_device float fresnel_iridescence_channel(KernelGlobals kg,
const int channel,
const float ambient_ior,
const FresnelThinFilm thin_film,
const float substrate_n,
const float substrate_k,
const float F82,
const float cos_theta_1,
ccl_private float *r_cos_theta_3)
{
/* For films below 1nm, the wave-optic-based Airy summation approach no longer applies,
* so blend towards the case without coating. */
float film_ior = thin_film.ior;
if (thin_film.thickness < 1.0f) {
film_ior = mix(ambient_ior, film_ior, smoothstep(0.0f, 1.0f, thin_film.thickness));
}
float cos_theta_2;
/* The real component of exp(i * phi12), equivalent to cos(phi12). */
float2 phasor12_real;
/* Compute reflection at the top interface (ambient to film). */
const float2 R12 = fresnel_dielectric_polarized(
cos_theta_1, film_ior / ambient_ior, &cos_theta_2, &phasor12_real);
if (isequal(R12, one_float2())) {
/* TIR at the top interface. */
return 1.0f;
}
/* Compute reflection at the bottom interface (film to substrate). */
float R23_s, R23_p;
complex<float> phasor23_s, phasor23_p;
if constexpr (conductive) {
/* Material is a conductor. */
fresnel_conductor_polarized(-cos_theta_2,
film_ior,
{substrate_n, substrate_k},
F82,
R23_s,
R23_p,
&phasor23_s,
&phasor23_p);
}
else {
/* Material is a dielectric. */
float2 phasor23_real;
const float2 R23 = fresnel_dielectric_polarized(
-cos_theta_2, substrate_n / film_ior, r_cos_theta_3, &phasor23_real);
if (isequal(R23, one_float2())) {
/* TIR at the bottom interface.
* All the Airy summation math still simplifies to 1.0 in this case. */
return 1.0f;
}
R23_s = R23.x;
R23_p = R23.y;
phasor23_s = {phasor23_real.x, 0.0f};
phasor23_p = {phasor23_real.y, 0.0f};
}
/* Compute optical path difference inside the thin film. */
const float OPD = -2.0f * film_ior * thin_film.thickness * cos_theta_2;
/* Compute full phase shifts due to reflection, as a complex number exp(i * (phi23 + phi21)).
* This complex form avoids the atan2 and cos calls needed to directly get the phase shift. */
const complex<float> phasor_s = phasor23_s * -phasor12_real.x;
const float R_s = iridescence_airy_summation_channel(kg, channel, R12.x, R23_s, OPD, phasor_s);
/* Perform Airy summation and average the polarizations. */
const complex<float> phasor_p = phasor23_p * -phasor12_real.y;
const float R_p = iridescence_airy_summation_channel(kg, channel, R12.y, R23_p, OPD, phasor_p);
return saturatef(0.5f * (R_s + R_p));
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,355 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/alloc.h"
#include "kernel/closure/bsdf_diffuse.h"
CCL_NAMESPACE_BEGIN
struct Bssrdf {
SHADER_CLOSURE_BASE;
Spectrum radius;
Spectrum albedo;
float anisotropy;
/* Parameters for refractive entry bounce. */
float ior;
float alpha;
};
static_assert(sizeof(ShaderClosure) >= sizeof(Bssrdf), "Bssrdf is too large!");
/* Random Walk BSSRDF */
ccl_device float bssrdf_dipole_compute_Rd(const float alpha_prime, const float fourthirdA)
{
const float s = sqrtf(3.0f * (1.0f - alpha_prime));
return 0.5f * alpha_prime * (1.0f + expf(-fourthirdA * s)) * expf(-s);
}
ccl_device float bssrdf_dipole_compute_alpha_prime(const float rd, const float fourthirdA)
{
/* Little Newton solver. */
if (rd < 1e-4f) {
return 0.0f;
}
if (rd >= 0.995f) {
return 0.999999f;
}
float x0 = 0.0f;
float x1 = 1.0f;
float xmid;
float fmid;
constexpr const int max_num_iterations = 12;
for (int i = 0; i < max_num_iterations; ++i) {
xmid = 0.5f * (x0 + x1);
fmid = bssrdf_dipole_compute_Rd(xmid, fourthirdA);
if (fmid < rd) {
x0 = xmid;
}
else {
x1 = xmid;
}
}
return xmid;
}
ccl_device void bssrdf_setup_radius(ccl_private Bssrdf *bssrdf, const ClosureType type)
{
if (type == CLOSURE_BSSRDF_BURLEY_ID || type == CLOSURE_BSSRDF_RANDOM_WALK_LEGACY_ID) {
/* Scale mean free path length so that Burley and Random Walk Legacy look similar to before. */
bssrdf->radius *= 0.25f * M_1_PI_F;
}
else if (type == CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID) {
/* Adjust radius based on IOR and albedo. */
const float inv_eta = 1.0f / bssrdf->ior;
const float F_dr = inv_eta * (-1.440f * inv_eta + 0.710f) + 0.668f + 0.0636f * bssrdf->ior;
const float fourthirdA = (4.0f / 3.0f) * (1.0f + F_dr) /
(1.0f - F_dr); /* From Jensen's `Fdr` ratio formula. */
Spectrum alpha_prime;
FOREACH_SPECTRUM_CHANNEL (i) {
GET_SPECTRUM_CHANNEL(alpha_prime, i) = bssrdf_dipole_compute_alpha_prime(
GET_SPECTRUM_CHANNEL(bssrdf->albedo, i), fourthirdA);
}
bssrdf->radius *= sqrt(3.0f * (one_spectrum() - alpha_prime));
}
}
/* Christensen-Burley BSSRDF.
*
* Approximate Reflectance Profiles from
* http://graphics.pixar.com/library/ApproxBSSRDF/paper.pdf
*/
/* This is a bit arbitrary, just need big enough radius so it matches
* the mean free length, but still not too big so sampling is still
* effective. */
#define BURLEY_TRUNCATE 16.0f
#define BURLEY_TRUNCATE_CDF 0.9963790093708328f // cdf(BURLEY_TRUNCATE)
ccl_device_inline float bssrdf_burley_fitting(const float A)
{
/* Diffuse surface transmission, equation (6). */
return 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f);
}
/* Scale mean free path length so it gives similar looking result
* to Cubic and Gaussian models. */
ccl_device_inline Spectrum bssrdf_burley_compatible_mfp(Spectrum r)
{
return 0.25f * M_1_PI_F * r;
}
ccl_device void bssrdf_burley_setup(ccl_private Bssrdf *bssrdf)
{
/* Mean free path length. */
const Spectrum l = bssrdf_burley_compatible_mfp(bssrdf->radius);
/* Surface albedo. */
const Spectrum A = bssrdf->albedo;
Spectrum s;
FOREACH_SPECTRUM_CHANNEL (i) {
GET_SPECTRUM_CHANNEL(s, i) = bssrdf_burley_fitting(GET_SPECTRUM_CHANNEL(A, i));
}
bssrdf->radius = l / s;
}
ccl_device float bssrdf_burley_eval(const float d, const float r)
{
const float Rm = BURLEY_TRUNCATE * d;
if (r >= Rm) {
return 0.0f;
}
/* Burley reflectance profile, equation (3).
*
* NOTES:
* - Surface albedo is already included into `sc->weight`, no need to
* multiply by this term here.
* - This is normalized diffuse model, so the equation is multiplied
* by `2*pi`, which also matches `cdf()`.
*/
const float exp_r_3_d = expf(-r / (3.0f * d));
const float exp_r_d = exp_r_3_d * exp_r_3_d * exp_r_3_d;
return (exp_r_d + exp_r_3_d) / (4.0f * d);
}
ccl_device float bssrdf_burley_pdf(const float d, const float r)
{
if (r == 0.0f) {
return 0.0f;
}
return bssrdf_burley_eval(d, r) * (1.0f / BURLEY_TRUNCATE_CDF);
}
/* Find the radius for desired CDF value.
* Returns scaled radius, meaning the result is to be scaled up by d.
* Since there's no closed form solution we do Newton-Raphson method to find it.
*/
ccl_device_forceinline float bssrdf_burley_root_find(const float xi)
{
const float tolerance = 1e-6f;
const int max_iteration_count = 10;
/* Do initial guess based on manual curve fitting, this allows us to reduce
* number of iterations to maximum 4 across the [0..1] range. We keep maximum
* number of iteration higher just to be sure we didn't miss root in some
* corner case.
*/
float r;
if (xi <= 0.9f) {
r = expf(xi * xi * 2.4f) - 1.0f;
}
else {
/* TODO(sergey): Some nicer curve fit is possible here. */
r = 15.0f;
}
/* Solve against scaled radius. */
for (int i = 0; i < max_iteration_count; i++) {
const float exp_r_3 = expf(-r / 3.0f);
const float exp_r = exp_r_3 * exp_r_3 * exp_r_3;
const float f = 1.0f - 0.25f * exp_r - 0.75f * exp_r_3 - xi;
const float f_ = 0.25f * exp_r + 0.25f * exp_r_3;
if (fabsf(f) < tolerance || f_ == 0.0f) {
break;
}
r = r - f / f_;
r = fmaxf(r, 0.0f);
}
return r;
}
ccl_device void bssrdf_burley_sample(const float d,
const float xi,
ccl_private float *r,
ccl_private float *h)
{
const float Rm = BURLEY_TRUNCATE * d;
const float r_ = bssrdf_burley_root_find(xi * BURLEY_TRUNCATE_CDF) * d;
*r = r_;
/* h^2 + r^2 = Rm^2 */
*h = safe_sqrtf(Rm * Rm - r_ * r_);
}
ccl_device float bssrdf_num_channels(const Spectrum radius)
{
float channels = 0;
FOREACH_SPECTRUM_CHANNEL (i) {
if (GET_SPECTRUM_CHANNEL(radius, i) > 0.0f) {
channels += 1.0f;
}
}
return channels;
}
ccl_device void bssrdf_sample(const Spectrum radius,
float xi,
ccl_private float *r,
ccl_private float *h)
{
const float num_channels = bssrdf_num_channels(radius);
float sampled_radius;
/* Sample color channel and reuse random number. Only a subset of channels
* may be used if their radius was too small to handle as BSSRDF. */
xi *= num_channels;
sampled_radius = 0.0f;
float sum = 0.0f;
FOREACH_SPECTRUM_CHANNEL (i) {
const float channel_radius = GET_SPECTRUM_CHANNEL(radius, i);
if (channel_radius > 0.0f) {
const float next_sum = sum + 1.0f;
if (xi < next_sum) {
xi -= sum;
sampled_radius = channel_radius;
break;
}
sum = next_sum;
}
}
/* Sample BSSRDF. */
bssrdf_burley_sample(sampled_radius, xi, r, h);
}
ccl_device_forceinline Spectrum bssrdf_eval(const Spectrum radius, const float r)
{
Spectrum result;
FOREACH_SPECTRUM_CHANNEL (i) {
GET_SPECTRUM_CHANNEL(result, i) = bssrdf_burley_pdf(GET_SPECTRUM_CHANNEL(radius, i), r);
}
return result;
}
ccl_device_forceinline float bssrdf_pdf(const Spectrum radius, const float r)
{
const Spectrum pdf = bssrdf_eval(radius, r);
return reduce_add(pdf) / bssrdf_num_channels(radius);
}
/* Setup */
ccl_device_inline ccl_private Bssrdf *bssrdf_alloc(ccl_private ShaderData *sd, Spectrum weight)
{
const float sample_weight = fabsf(average(weight));
if (sample_weight < CLOSURE_WEIGHT_CUTOFF) {
return nullptr;
}
ccl_private Bssrdf *bssrdf = (ccl_private Bssrdf *)closure_alloc(
sd, sizeof(Bssrdf), CLOSURE_NONE_ID, weight);
if (bssrdf == nullptr) {
return nullptr;
}
bssrdf->sample_weight = sample_weight;
return bssrdf;
}
ccl_device int bssrdf_setup(ccl_private ShaderData *sd,
ccl_private Bssrdf *bssrdf,
const uint32_t path_flag,
ClosureType type)
{
/* Clamp anisotropy to avoid delta function. */
if (type == CLOSURE_BSSRDF_RANDOM_WALK_ID) {
bssrdf->anisotropy = clamp(bssrdf->anisotropy, -0.99f, 0.99f);
}
else {
bssrdf->anisotropy = clamp(bssrdf->anisotropy, -0.99f, 0.9f);
}
bssrdf->ior = clamp(bssrdf->ior, 1.01f, 3.8f);
int flag = 0;
if (type == CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID) {
/* CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID uses a fixed roughness. */
bssrdf->alpha = 1.0f;
}
/* Verify if the radii are large enough to sample without precision issues. */
int bssrdf_channels = SPECTRUM_CHANNELS;
Spectrum diffuse_weight = zero_spectrum();
if (type == CLOSURE_BSSRDF_BURLEY_ID && (path_flag & PATH_RAY_DIFFUSE_ANCESTOR)) {
/* Fall back to diffuse after a diffuse ancestor for Christensen-Burley. Can't see it that
* well and it adds considerable noise due to probabilities of continuing the path getting
* lower and lower. The disk sampling must probe in directions where most of the time
* nothing will be hit. */
bssrdf_channels = 0;
diffuse_weight = bssrdf->weight;
}
else {
FOREACH_SPECTRUM_CHANNEL (i) {
if (GET_SPECTRUM_CHANNEL(bssrdf->radius, i) < BSSRDF_MIN_RADIUS) {
GET_SPECTRUM_CHANNEL(diffuse_weight, i) = GET_SPECTRUM_CHANNEL(bssrdf->weight, i);
GET_SPECTRUM_CHANNEL(bssrdf->weight, i) = 0.0f;
GET_SPECTRUM_CHANNEL(bssrdf->radius, i) = 0.0f;
bssrdf_channels--;
}
}
}
if (bssrdf_channels < SPECTRUM_CHANNELS) {
/* Add diffuse BSDF if any radius too small. */
bsdf_diffuse_setup(sd, bssrdf->N, diffuse_weight);
}
/* Setup BSSRDF if radius is large enough. */
if (bssrdf_channels > 0) {
bssrdf->type = type;
bssrdf->sample_weight = fabsf(average(bssrdf->weight)) * bssrdf_channels;
bssrdf_setup_radius(bssrdf, type);
flag |= SD_BSSRDF;
}
else {
bssrdf->type = CLOSURE_NONE_ID;
bssrdf->sample_weight = 0.0f;
}
return flag;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2009-2010 Sony Pictures Imageworks Inc., et al. All Rights Reserved.
* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Adapted code from Open Shading Language. */
#pragma once
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
/* BACKGROUND CLOSURE */
ccl_device void background_setup(ccl_private ShaderData *sd, const Spectrum weight)
{
if (sd->flag & SD_EMISSION) {
sd->closure_emission_background += weight;
}
else {
sd->flag |= SD_EMISSION;
sd->closure_emission_background = weight;
}
}
/* EMISSION CLOSURE */
ccl_device void emission_setup(ccl_private ShaderData *sd, const Spectrum weight)
{
if (sd->flag & SD_EMISSION) {
sd->closure_emission_background += weight;
}
else {
sd->flag |= SD_EMISSION;
sd->closure_emission_background = weight;
}
}
/* return the probability distribution function in the direction wi,
* given the parameters and the light's surface normal. This MUST match
* the PDF computed by sample(). */
ccl_device float emissive_pdf(const float3 Ng, const float3 wi)
{
const float cosNI = fabsf(dot(Ng, wi));
return (cosNI > 0.0f) ? 1.0f : 0.0f;
}
ccl_device void emissive_sample(const float3 /*Ng*/,
const float /*randu*/,
const float /*randv*/,
ccl_private float3 * /*wi*/,
ccl_private float * /*pdf*/)
{
/* todo: not implemented and used yet */
}
ccl_device Spectrum emissive_simple_eval(const float3 Ng, const float3 wi)
{
const float res = emissive_pdf(Ng, wi);
return make_spectrum(res);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,191 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_draine.h"
#include "kernel/closure/volume_fournier_forand.h"
#include "kernel/closure/volume_henyey_greenstein.h"
#include "kernel/closure/volume_rayleigh.h"
CCL_NAMESPACE_BEGIN
/* VOLUME EXTINCTION */
ccl_device void volume_extinction_setup(ccl_private ShaderData *sd, Spectrum weight)
{
if (sd->flag & SD_EXTINCTION) {
sd->closure_transparent_extinction += weight;
}
else {
sd->flag |= SD_EXTINCTION;
sd->closure_transparent_extinction = weight;
}
}
/* VOLUME SCATTERING */
ccl_device Spectrum volume_phase_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private float *pdf)
{
switch (svc->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
return volume_fournier_forand_eval(sd, svc, wo, pdf);
case CLOSURE_VOLUME_RAYLEIGH_ID:
return volume_rayleigh_eval(sd, wo, pdf);
case CLOSURE_VOLUME_DRAINE_ID:
return volume_draine_eval(sd, svc, wo, pdf);
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
return volume_henyey_greenstein_eval(sd, svc, wo, pdf);
default:
kernel_assert(false);
*pdf = 0.0f;
return zero_spectrum();
}
}
ccl_device int volume_phase_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
switch (svc->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
return volume_fournier_forand_sample(sd, svc, rand, eval, wo, pdf);
case CLOSURE_VOLUME_RAYLEIGH_ID:
return volume_rayleigh_sample(sd, rand, eval, wo, pdf);
case CLOSURE_VOLUME_DRAINE_ID:
return volume_draine_sample(sd, svc, rand, eval, wo, pdf);
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
return volume_henyey_greenstein_sample(sd, svc, rand, eval, wo, pdf);
default:
kernel_assert(false);
*pdf = 0.0f;
return 0;
}
}
/* Widen the compact ray differential dD after a phase function scatter to
* match the lobe's angular spread. See bsdf_widen_dD for details. */
ccl_device_forceinline float volume_phase_widen_dD(const float prev_dD,
const float sampled_roughness)
{
return max(prev_dD, sampled_roughness);
}
ccl_device bool volume_phase_equal(const ccl_private ShaderClosure *c1,
const ccl_private ShaderClosure *c2)
{
if (c1->type != c2->type) {
return false;
}
switch (c1->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID: {
ccl_private FournierForandVolume *v1 = (ccl_private FournierForandVolume *)c1;
ccl_private FournierForandVolume *v2 = (ccl_private FournierForandVolume *)c2;
return v1->c1 == v2->c1 && v1->c2 == v2->c2 && v1->c3 == v2->c3;
}
case CLOSURE_VOLUME_RAYLEIGH_ID:
return true;
case CLOSURE_VOLUME_DRAINE_ID: {
ccl_private DraineVolume *v1 = (ccl_private DraineVolume *)c1;
ccl_private DraineVolume *v2 = (ccl_private DraineVolume *)c2;
return v1->g == v2->g && v1->alpha == v2->alpha;
}
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: {
ccl_private HenyeyGreensteinVolume *v1 = (ccl_private HenyeyGreensteinVolume *)c1;
ccl_private HenyeyGreensteinVolume *v2 = (ccl_private HenyeyGreensteinVolume *)c2;
return v1->g == v2->g;
}
default:
return false;
}
return false;
}
/* Approximate phase functions as Henyey-Greenstein for volume guiding.
* TODO: This is not ideal, we should use RIS guiding for non-HG phase functions. */
ccl_device float volume_phase_get_g(const ccl_private ShaderVolumeClosure *svc)
{
switch (svc->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
/* TODO */
return 1.0f;
case CLOSURE_VOLUME_RAYLEIGH_ID:
/* Approximate as isotropic */
return 0.0f;
case CLOSURE_VOLUME_DRAINE_ID:
/* Approximate as HG, TODO */
return ((ccl_private DraineVolume *)svc)->g;
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
return ((ccl_private HenyeyGreensteinVolume *)svc)->g;
default:
return 0.0f;
}
}
/* Volume sampling utilities. */
/* Ignore paths that have volume throughput below this value, to avoid unnecessary work
* and precision issues.
* TODO: this value could be tweaked or turned into a probability to avoid unnecessary work in
* volumes and subsurface scattering. */
#define VOLUME_THROUGHPUT_EPSILON 1e-6f
ccl_device Spectrum volume_color_transmittance(Spectrum sigma, const float t)
{
return exp(-sigma * t);
}
ccl_device float volume_channel_get(Spectrum value, const int channel)
{
return GET_SPECTRUM_CHANNEL(value, channel);
}
/* Sample color channel proportional to throughput and single scattering albedo, to significantly
* reduce noise with many bounce, following:
*
* "Practical and Controllable Subsurface Scattering for Production Path Tracing".
* Matt Jen-Yuan Chiang, Peter Kutz, Brent Burley. SIGGRAPH 2016. */
ccl_device_inline Spectrum volume_sample_channel_pdf(Spectrum albedo, Spectrum throughput)
{
const Spectrum weights = fabs(throughput * albedo);
const float sum_weights = reduce_add(weights);
if ((1.0f - sum_weights) < 1.0f) {
/* The same as `sum_weights > 0.0f`, but avoids the case where `sum_weight` is denormal, which
* could produce `nan` after division. */
return weights / sum_weights;
}
return make_spectrum(1.0f / SPECTRUM_CHANNELS);
}
ccl_device int volume_sample_channel(Spectrum albedo,
Spectrum throughput,
ccl_private float *rand,
ccl_private Spectrum *pdf)
{
*pdf = volume_sample_channel_pdf(albedo, throughput);
float pdf_sum = 0.0f;
FOREACH_SPECTRUM_CHANNEL (i) {
const float channel_pdf = GET_SPECTRUM_CHANNEL(*pdf, i);
if (*rand < pdf_sum + channel_pdf) {
/* Rescale to reuse. */
*rand = (*rand - pdf_sum) / channel_pdf;
return i;
}
pdf_sum += channel_pdf;
}
return SPECTRUM_CHANNELS - 1;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,62 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_util.h"
CCL_NAMESPACE_BEGIN
/* DRAINE CLOSURE */
struct DraineVolume {
SHADER_CLOSURE_VOLUME_BASE;
float g;
float alpha;
};
static_assert(sizeof(ShaderVolumeClosure) >= sizeof(DraineVolume), "DraineVolume is too large!");
ccl_device int volume_draine_setup(ccl_private DraineVolume *volume)
{
volume->type = CLOSURE_VOLUME_DRAINE_ID;
/* clamp anisotropy */
volume->g = signf(volume->g) * min(fabsf(volume->g), 1.0f - 1e-3f);
return SD_SCATTER;
}
ccl_device Spectrum volume_draine_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private DraineVolume *volume = (const ccl_private DraineVolume *)svc;
/* note that wi points towards the viewer */
const float cos_theta = dot(-sd->wi, wo);
*pdf = phase_draine(cos_theta, volume->g, volume->alpha);
return make_spectrum(*pdf);
}
ccl_device int volume_draine_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private DraineVolume *volume = (const ccl_private DraineVolume *)svc;
/* note that wi points towards the viewer and so is used negated */
*wo = phase_draine_sample(-sd->wi, volume->g, volume->alpha, rand, pdf);
*eval = make_spectrum(*pdf); /* perfect importance sampling */
return LABEL_VOLUME_SCATTER;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,73 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_util.h"
CCL_NAMESPACE_BEGIN
/* FOURNIER-FORAND CLOSURE */
struct FournierForandVolume {
SHADER_CLOSURE_VOLUME_BASE;
/* Precomputed coefficients, based on B and IOR */
float c1, c2, c3;
};
static_assert(sizeof(ShaderVolumeClosure) >= sizeof(FournierForandVolume),
"FournierForandVolume is too large!");
ccl_device int volume_fournier_forand_setup(ccl_private FournierForandVolume *volume,
float B,
float IOR)
{
volume->type = CLOSURE_VOLUME_FOURNIER_FORAND_ID;
/* clamp backscatter fraction to avoid delta function */
B = min(fabsf(B), 0.5f - 1e-3f);
IOR = max(IOR, 1.0f + 1e-3f);
const float3 coeffs = phase_fournier_forand_coeffs(B, IOR);
volume->c1 = coeffs.x;
volume->c2 = coeffs.y;
volume->c3 = coeffs.z;
return SD_SCATTER;
}
ccl_device Spectrum volume_fournier_forand_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private FournierForandVolume *volume = (const ccl_private FournierForandVolume *)svc;
const float3 coeffs = make_float3(volume->c1, volume->c2, volume->c3);
/* note that wi points towards the viewer */
const float cos_theta = dot(-sd->wi, wo);
*pdf = phase_fournier_forand(cos_theta, coeffs);
return make_spectrum(*pdf);
}
ccl_device int volume_fournier_forand_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private FournierForandVolume *volume = (const ccl_private FournierForandVolume *)svc;
const float3 coeffs = make_float3(volume->c1, volume->c2, volume->c3);
/* note that wi points towards the viewer and so is used negated */
*wo = phase_fournier_forand_sample(-sd->wi, coeffs, rand, pdf);
*eval = make_spectrum(*pdf); /* perfect importance sampling */
return LABEL_VOLUME_SCATTER;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,64 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_util.h"
CCL_NAMESPACE_BEGIN
/* HENYEY-GREENSTEIN CLOSURE */
struct HenyeyGreensteinVolume {
SHADER_CLOSURE_VOLUME_BASE;
float g;
};
static_assert(sizeof(ShaderVolumeClosure) >= sizeof(HenyeyGreensteinVolume),
"HenyeyGreensteinVolume is too large!");
ccl_device int volume_henyey_greenstein_setup(ccl_private HenyeyGreensteinVolume *volume)
{
volume->type = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID;
/* clamp anisotropy to avoid delta function */
volume->g = signf(volume->g) * min(fabsf(volume->g), 1.0f - 1e-3f);
return SD_SCATTER;
}
ccl_device Spectrum volume_henyey_greenstein_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private float *pdf)
{
const ccl_private HenyeyGreensteinVolume *volume = (const ccl_private HenyeyGreensteinVolume *)
svc;
/* note that wi points towards the viewer */
const float cos_theta = dot(-sd->wi, wo);
*pdf = phase_henyey_greenstein(cos_theta, volume->g);
return make_spectrum(*pdf);
}
ccl_device int volume_henyey_greenstein_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
const ccl_private HenyeyGreensteinVolume *volume = (const ccl_private HenyeyGreensteinVolume *)
svc;
/* note that wi points towards the viewer and so is used negated */
*wo = phase_henyey_greenstein_sample(-sd->wi, volume->g, rand, pdf);
*eval = make_spectrum(*pdf); /* perfect importance sampling */
return LABEL_VOLUME_SCATTER;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "kernel/closure/volume_util.h"
CCL_NAMESPACE_BEGIN
/* RAYLEIGH CLOSURE */
struct RayleighVolume {
SHADER_CLOSURE_VOLUME_BASE;
};
static_assert(sizeof(ShaderVolumeClosure) >= sizeof(RayleighVolume),
"RayleighVolume is too large!");
ccl_device int volume_rayleigh_setup(ccl_private RayleighVolume *volume)
{
volume->type = CLOSURE_VOLUME_RAYLEIGH_ID;
return SD_SCATTER;
}
ccl_device Spectrum volume_rayleigh_eval(const ccl_private ShaderData *sd,
const float3 wo,
ccl_private float *pdf)
{
/* note that wi points towards the viewer */
const float cos_theta = dot(-sd->wi, wo);
*pdf = phase_rayleigh(cos_theta);
return make_spectrum(*pdf);
}
ccl_device int volume_rayleigh_sample(const ccl_private ShaderData *sd,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
/* note that wi points towards the viewer and so is used negated */
*wo = phase_rayleigh_sample(-sd->wi, rand, pdf);
*eval = make_spectrum(*pdf); /* perfect importance sampling */
return LABEL_VOLUME_SCATTER;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,310 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/math_fast.h"
#include "util/projection.h"
CCL_NAMESPACE_BEGIN
/* Given a random number, sample a direction that makes an angle of theta with direction D. */
ccl_device float3 phase_sample_direction(const float3 D, const float cos_theta, const float rand)
{
const float phi = M_2PI_F * rand;
const float3 dir = spherical_cos_to_direction(cos_theta, phi);
float3 T;
float3 B;
make_orthonormals(D, &T, &B);
return to_global(dir, T, B, D);
}
/* Given cosine between rays, return probability density that a photon bounces
* to that direction. The g parameter controls how different it is from the
* uniform sphere. g=0 uniform diffuse-like, g=1 close to sharp single ray. */
ccl_device float phase_henyey_greenstein(const float cos_theta, const float g)
{
if (fabsf(g) < 1e-3f) {
return M_1_4PI_F;
}
const float fac = 1 + g * (g - 2 * cos_theta);
return (1 - sqr(g)) / (M_4PI_F * fac * safe_sqrtf(fac));
}
ccl_device float3 phase_henyey_greenstein_sample(const float3 D,
const float g,
const float2 rand,
ccl_private float *pdf)
{
float cos_theta = 1 - 2 * rand.x;
if (fabsf(g) >= 1e-3f) {
const float k = (1 - sqr(g)) / (1 - g * cos_theta);
cos_theta = (1 + sqr(g) - sqr(k)) / (2 * g);
}
*pdf = phase_henyey_greenstein(cos_theta, g);
return phase_sample_direction(D, cos_theta, rand.y);
}
/* Given cosine between rays, return probability density that a photon bounces to that direction
* according to the constant Rayleigh phase function.
* See https://doi.org/10.1364/JOSAA.28.002436 for details. */
ccl_device float phase_rayleigh(const float cos_theta)
{
return (0.1875f * M_1_PI_F) * (1.0f + sqr(cos_theta));
}
ccl_device float3 phase_rayleigh_sample(const float3 D, const float2 rand, ccl_private float *pdf)
{
const float a = 2 - 4 * rand.x;
/* Metal doesn't have cbrtf, but since we compute u - 1/u anyways, we can just as well
* use the inverse cube root for which there is a simple Quake-style fast implementation. */
const float inv_u = -fast_inv_cbrtf(sqrtf(1 + sqr(a)) + a);
const float cos_theta = 1 / inv_u - inv_u;
*pdf = phase_rayleigh(cos_theta);
return phase_sample_direction(D, cos_theta, rand.y);
}
/* Given cosine between rays, return probability density that a photon bounces to that direction
* according to the Draine phase function. This is a generalization of the Henyey-Greenstein
* function which bridges the cases of HG and Rayleigh scattering. The parameter g mainly controls
* the first moment <cos theta>, and alpha the second moment <cos2 theta> of the exact phase
* function. alpha=0 reduces to HG function, g=0, alpha=1 reduces to Rayleigh function, alpha=1
* reduces to Cornette-Shanks function.
* See https://doi.org/10.1086/379118 for details. */
ccl_device float phase_draine(const float cos_theta, const float g, float alpha)
{
/* Check special cases. */
if (fabsf(g) < 1e-3f && alpha > 0.999f) {
return phase_rayleigh(cos_theta);
}
if (fabsf(alpha) < 1e-3f) {
return phase_henyey_greenstein(cos_theta, g);
}
const float g2 = sqr(g);
const float fac = 1 + g2 - 2 * g * cos_theta;
return ((1 - g2) * (1 + alpha * sqr(cos_theta))) /
((1 + (alpha * (1 + 2 * g2)) * (1 / 3.0f)) * M_4PI_F * fac * sqrtf(fac));
}
/* Adapted from the HLSL code provided in https://research.nvidia.com/labs/rtr/approximate-mie/ */
ccl_device float phase_draine_sample_cos(const float g, const float alpha, const float rand)
{
if (fabsf(g) < 1e-2f) {
/* Special case to prevent division by zero.
* The sample technique is similar as in https://doi.org/10.1364/JOSAA.28.002436. */
const float inv_alpha = 1.0f / alpha;
const float b_2 = (3 + alpha) * inv_alpha * (0.5f - rand);
const float inv_u = -fast_inv_cbrtf(b_2 + sqrtf(sqr(b_2) + sqr(inv_alpha) * inv_alpha));
return 1 / inv_u - inv_u / alpha;
}
const float g2 = sqr(g);
const float g3 = g * g2;
const float g4 = sqr(g2);
const float g6 = g2 * g4;
const float pgp1_2 = sqr(1 + g2);
const float T1a = alpha * (g4 - 1);
const float T1a3 = sqr(T1a) * T1a;
const float T2 = -1296 * (g2 - 1) * (alpha - alpha * g2) * T1a * (4 * g2 + alpha * pgp1_2);
const float T9 = 2 + g2 + g3 * (1 + 2 * g2) * (2 * rand - 1);
const float T3 = 3 * g2 * (1 + g * (2 * rand - 1)) + alpha * T9;
const float T4a = 432 * T1a3 + T2 + 432 * (alpha * (1 - g2)) * sqr(T3);
const float T10 = alpha * (2 * g4 - g2 - g6);
const float T4b = 144 * T10;
const float T4b3 = sqr(T4b) * T4b;
const float T4 = T4a + sqrtf(-4 * T4b3 + sqr(T4a));
const float inv_T4p3 = fast_inv_cbrtf(T4);
const float T8 = 48 * M_CBRT2_F * T10;
const float T6 = (2 * T1a + T8 * inv_T4p3 + 1 / (3 * M_CBRT2_F * inv_T4p3)) / (alpha * (1 - g2));
const float T5 = 6 * (1 + g2) + T6;
const float T7 = 6 * (1 + g2) - (8 * T3) / (alpha * (g2 - 1) * sqrtf(T5)) - T6;
return (1 + g2 - 0.25f * sqr(sqrtf(T7) - sqrtf(T5))) / (2 * g);
}
ccl_device float3 phase_draine_sample(
const float3 D, const float g, float alpha, const float2 rand, ccl_private float *pdf)
{
/* Check special cases. */
if (fabsf(g) < 1e-3f && alpha > 0.999f) {
return phase_rayleigh_sample(D, rand, pdf);
}
if (fabsf(alpha) < 1e-3f) {
return phase_henyey_greenstein_sample(D, g, rand, pdf);
}
const float cos_theta = phase_draine_sample_cos(g, alpha, rand.x);
*pdf = phase_draine(cos_theta, g, alpha);
return phase_sample_direction(D, cos_theta, rand.y);
}
ccl_device float phase_fournier_forand_delta(const float n, const float sin_htheta_sqr)
{
const float u = 4 * sin_htheta_sqr;
return u / (3 * sqr(n - 1));
}
ccl_device_inline float3 phase_fournier_forand_coeffs(const float B, const float IOR)
{
const float d90 = phase_fournier_forand_delta(IOR, 0.5f);
const float d180 = phase_fournier_forand_delta(IOR, 1.0f);
const float v = -logf(2 * B * (d90 - 1) + 1) / logf(d90);
return make_float3(IOR, v, (powf(d180, -v) - 1) / (d180 - 1));
}
/* Given cosine between rays, return probability density that a photon bounces to that direction
* according to the Fournier-Forand phase function. The n parameter is the particle index of
* refraction and controls how much of the light is refracted. B is the particle backscatter
* fraction, B = b_b / b.
* See https://doi.org/10.1117/12.366488 for details. */
ccl_device_inline float phase_fournier_forand_impl(float cos_theta,
const float delta,
const float pow_delta_v,
const float v,
float sin_htheta_sqr,
const float pf_coeff)
{
const float m_delta = 1 - delta;
const float m_pow_delta_v = 1 - pow_delta_v;
float pf;
if (fabsf(m_delta) < 1e-3f) {
/* Special case (first-order Taylor expansion) to avoid singularity at delta near 1.0 */
pf = v * ((v - 1) - (v + 1) / sin_htheta_sqr) * (1 / (8 * M_PI_F));
pf += v * (v + 1) * m_delta * (2 * (v - 1) - (2 * v + 1) / sin_htheta_sqr) *
(1 / (24 * M_PI_F));
}
else {
pf = (v * m_delta - m_pow_delta_v + (delta * m_pow_delta_v - v * m_delta) / sin_htheta_sqr) /
(M_4PI_F * sqr(m_delta) * pow_delta_v);
}
pf += pf_coeff * (3 * sqr(cos_theta) - 1);
return pf;
}
ccl_device float phase_fournier_forand(const float cos_theta, const float3 coeffs)
{
if (fabsf(cos_theta) >= 1.0f) {
return 0.0f;
}
const float n = coeffs.x;
const float v = coeffs.y;
const float pf_coeff = coeffs.z * (1.0f / (16.0f * M_PI_F));
const float sin_htheta_sqr = 0.5f * (1 - cos_theta); /* `sin^2(theta / 2)`. */
const float delta = phase_fournier_forand_delta(n, sin_htheta_sqr);
return phase_fournier_forand_impl(cos_theta, delta, powf(delta, v), v, sin_htheta_sqr, pf_coeff);
}
ccl_device float phase_fournier_forand_newton(const float rand, const float3 coeffs)
{
const float n = coeffs.x;
const float v = coeffs.y;
const float cdf_coeff = coeffs.z * (1.0f / 8.0f);
const float pf_coeff = coeffs.z * (1.0f / (16.0f * M_PI_F));
float cos_theta = 0.64278760968f; /* Initial guess: 50 degrees */
for (int it = 0; it < 20; it++) {
const float sin_htheta_sqr = 0.5f * (1 - cos_theta); /* `sin^2(theta / 2)`. */
const float delta = phase_fournier_forand_delta(n, sin_htheta_sqr);
const float pow_delta_v = powf(delta, v);
const float m_delta = 1 - delta;
const float m_pow_delta_v = 1 - pow_delta_v;
/* Evaluate CDF and phase functions */
float cdf;
if (fabsf(m_delta) < 1e-3f) {
/* Special case (first-order Taylor expansion) to avoid singularity at delta near 1.0 */
cdf = 1 + v * (1 - sin_htheta_sqr) * (1 - 0.5f * (v + 1) * m_delta);
}
else {
cdf = (1 - pow_delta_v * delta - m_pow_delta_v * sin_htheta_sqr) / (m_delta * pow_delta_v);
}
cdf += cdf_coeff * cos_theta * (1 - sqr(cos_theta));
const float pf = phase_fournier_forand_impl(
cos_theta, delta, pow_delta_v, v, sin_htheta_sqr, pf_coeff);
/* Perform Newton iteration step */
float new_cos_theta = cos_theta + M_1_2PI_F * (cdf - rand) / pf;
/* Don't step off past 1.0, approach the peak slowly */
if (new_cos_theta >= 1.0f) {
new_cos_theta = max(mix(cos_theta, 1.0f, 0.5f), 0.99f);
}
if (fabsf(cos_theta - new_cos_theta) < 1e-6f || new_cos_theta == 1.0f) {
return new_cos_theta;
}
cos_theta = new_cos_theta;
}
/* Reached iteration limit, so give up and use what we have. */
return cos_theta;
}
ccl_device float3 phase_fournier_forand_sample(const float3 D,
const float3 coeffs,
const float2 rand,
ccl_private float *pdf)
{
const float cos_theta = phase_fournier_forand_newton(rand.x, coeffs);
*pdf = phase_fournier_forand(cos_theta, coeffs);
return phase_sample_direction(D, cos_theta, rand.y);
}
/* We approximate the Mie phase function for water droplets with diameters 0 < d < 50 um using a
* mixture of Draine and Henyey-Greenstein, following
* "An Approximate Mie Scattering Function for Fog and Cloud Rendering (Supplemental)"
* https://research.nvidia.com/labs/rtr/approximate-mie/publications/approximate-mie-supplemental.pdf
* For d > 1, the phase function is strong forward-scattering. For d very close to 0, the phase
* function is a mixture of Henyey-Greenstein and Rayleigh.
*/
ccl_device void phase_mie_fitted_parameters(float d,
ccl_private float *g_HG,
ccl_private float *g_D,
ccl_private float *alpha,
ccl_private float *w)
{
d = fmaxf(d, 0.0f);
if (d <= 0.1f) {
/* Eq (11 - 14). */
*g_HG = 13.8f * sqr(d);
*g_D = 1.1456f * d * fast_sinf(9.29044f * d);
*alpha = 250.0f;
*w = 0.252977f - 312.983f * powf(d, 4.3f);
}
else if (d < 1.5f) {
/* Eq (15 - 18). */
const float log_d = fast_logf(d);
*g_HG = 0.862f - 0.143f * sqr(log_d);
const float a = (log_d - 0.238604f) * (log_d + 1.00667f);
const float b = 0.507522f - 0.15677f * log_d;
const float c = 1.19692f * fast_cosf(a / b) + 1.37932f * log_d + 0.0625835f;
*g_D = 0.379685f * fast_cosf(c) + 0.344213f;
*alpha = 250.0f;
*w = 0.146209f * fast_cosf(3.38707f * log_d + 2.11193f) + 0.316072f + 0.0778917f * log_d;
}
else if (d < 5.0f) {
/* Eq (19 - 22). */
const float log_d = fast_logf(d);
*g_HG = 0.0604931f * fast_logf(log_d) + 0.940256f;
*g_D = 0.500411f - (0.081287f / (-2.0f * log_d + fast_tanf(log_d) + 1.27551f));
*alpha = 7.30354f * log_d + 6.31675f;
const float temp = fast_cosf(5.68947f * (fast_logf(log_d) - 0.0292149f));
*w = 0.026914f * (log_d - temp) + 0.3764f;
}
else {
/* Eq (7 - 10). */
*g_HG = fast_expf(-0.0990567f / (d - 1.67154f));
*g_D = fast_expf(-2.20679f / (d + 3.91029f) - 0.428934f);
*alpha = fast_expf(3.62489f - 8.29288f / (d + 5.52825f));
*w = fast_expf(-0.599085f / (d - 0.641583f) - 0.665888f);
}
}
CCL_NAMESPACE_END