4
$\begingroup$

I would like to try to exactly recreate the Principled BSDF node with Blender's BSDF nodes, I'm doing this for Testing purposes.

I would like to create a group node that behaves exactly like a Principled BSDF, keeping in mind this graphic that explains how the layers are made: https://docs.blender.org/manual/en/4.0/render/shader_nodes/shader/principled.html#layers enter image description here

I'm not sure if it can be done, for example Clearcoat doesn't seem to have a BSDF Shader equivalent.

Example of what I'm trying to do:

enter image description here

Additional note

See the answer from @X Y which points to OSL Nodes as a potential solution, but this is not what I want, for one simple reason: OSL Nodes don't support GPUs or even Eevee, so I think it's almost unthinkable to use OSL Nodes as an answer

My question is oriented towards how to do this with Blender nodes and how to mix them together within a Group Node, so that they behave like the Principled BSDF node, As I indicated in the second image, I am trying to connect the simple BSDF nodes in order to recreate the Principled BSDF

Some Test

I tried mixing the Glossy with Fresnel with IOR at 1.45 (In order to make the Specular), the results are markedly different 😅

enter image description here

$\endgroup$
7
  • 2
    $\begingroup$ The Principled BSDF, was introduced with Blender 2.8. So if you want to know how materials were made without it, have a look at 2.79 shaders. The Principled BSDF also calculates the Fresnel effect so I guess you need a Layered Weights node in your collection. $\endgroup$
    – Blunder
    Commented Oct 16, 2023 at 16:57
  • 2
    $\begingroup$ I don't think we did anything close to "exactly recreate the Principled BSDF" before that, we had lots of community-made meta/uber shaders shared by people (like this one) that would vary a lot in features and accuracy. $\endgroup$
    – Lauloque
    Commented Oct 16, 2023 at 19:17
  • $\begingroup$ @L0Lock It seems interesting, but that node seems to have multiple outputs, I would be more interested in rebuilding one with only 1 output like Blender's. $\endgroup$
    – Noob Cat
    Commented Oct 17, 2023 at 21:09
  • $\begingroup$ Just do the same with just one output. BTW works on OptiX GPUs since 3.5 and is being updated for more compatibility since. See Reference/Release Notes/3.5/Cycles - Blender Developer Wiki and Open Shading Language — Blender Manual $\endgroup$
    – Lauloque
    Commented Oct 18, 2023 at 19:10
  • 1
    $\begingroup$ Also will come questions like performances. $\endgroup$
    – Lauloque
    Commented Oct 19, 2023 at 22:58

2 Answers 2

4
+200
$\begingroup$

Method 1: Custom Node in Cycles

It's useful when you want to use an older version of node in a new blender version

enter image description here

The OSL source of Principled BSDF you can find in ..\blender-3.x.x\intern\cycles\kernel\osl\shaders\node_principled_bsdf.osl

This is 3.41 version:

/* SPDX-License-Identifier: Apache-2.0
 * Copyright 2011-2022 Blender Foundation */

#include "node_fresnel.h"
#include "stdcycles.h"

shader node_principled_bsdf(string distribution = "Multiscatter GGX",
                            string subsurface_method = "random_walk",
                            color BaseColor = color(0.8, 0.8, 0.8),
                            float Subsurface = 0.0,
                            vector SubsurfaceRadius = vector(1.0, 1.0, 1.0),
                            color SubsurfaceColor = color(0.7, 0.1, 0.1),
                            float SubsurfaceIOR = 1.4,
                            float SubsurfaceAnisotropy = 0.0,
                            float Metallic = 0.0,
                            float Specular = 0.5,
                            float SpecularTint = 0.0,
                            float Roughness = 0.5,
                            float Anisotropic = 0.0,
                            float AnisotropicRotation = 0.0,
                            float Sheen = 0.0,
                            float SheenTint = 0.5,
                            float Clearcoat = 0.0,
                            float ClearcoatRoughness = 0.03,
                            float IOR = 1.45,
                            float Transmission = 0.0,
                            float TransmissionRoughness = 0.0,
                            normal Normal = N,
                            normal ClearcoatNormal = N,
                            normal Tangent = normalize(dPdu),
                            output closure color BSDF = 0)
{
  float f = max(IOR, 1e-5);
  float diffuse_weight = (1.0 - clamp(Metallic, 0.0, 1.0)) * (1.0 - clamp(Transmission, 0.0, 1.0));
  float final_transmission = clamp(Transmission, 0.0, 1.0) * (1.0 - clamp(Metallic, 0.0, 1.0));
  float specular_weight = (1.0 - final_transmission);

  vector T = Tangent;

  float m_cdlum = luminance(BaseColor);
  color m_ctint = m_cdlum > 0.0 ? BaseColor / m_cdlum :
                                  color(1.0, 1.0, 1.0);  // normalize lum. to isolate hue+sat

  /* rotate tangent */
  if (AnisotropicRotation != 0.0)
    T = rotate(T, AnisotropicRotation * M_2PI, point(0.0, 0.0, 0.0), Normal);

  if (diffuse_weight > 1e-5) {
    if (Subsurface > 1e-5) {
      color mixed_ss_base_color = SubsurfaceColor * Subsurface + BaseColor * (1.0 - Subsurface);

      BSDF = mixed_ss_base_color * bssrdf(subsurface_method,
                                          Normal,
                                          Subsurface * SubsurfaceRadius,
                                          mixed_ss_base_color,
                                          "roughness",
                                          Roughness,
                                          "ior",
                                          SubsurfaceIOR,
                                          "anisotropy",
                                          SubsurfaceAnisotropy);
    }
    else {
      BSDF = BaseColor * principled_diffuse(Normal, Roughness);
    }

    if (Sheen > 1e-5) {
      color sheen_color = color(1.0, 1.0, 1.0) * (1.0 - SheenTint) + m_ctint * SheenTint;

      BSDF = BSDF + sheen_color * Sheen * principled_sheen(Normal);
    }

    BSDF = BSDF * diffuse_weight;
  }

  if (specular_weight > 1e-5) {
    float aspect = sqrt(1.0 - Anisotropic * 0.9);
    float r2 = Roughness * Roughness;

    float alpha_x = r2 / aspect;
    float alpha_y = r2 * aspect;

    color tmp_col = color(1.0, 1.0, 1.0) * (1.0 - SpecularTint) + m_ctint * SpecularTint;

    color Cspec0 = (Specular * 0.08 * tmp_col) * (1.0 - Metallic) + BaseColor * Metallic;

    if (distribution == "GGX" || Roughness <= 0.075) {
      BSDF = BSDF + specular_weight *
                        microfacet_ggx_aniso_fresnel(Normal,
                                                     T,
                                                     alpha_x,
                                                     alpha_y,
                                                     (2.0 / (1.0 - sqrt(0.08 * Specular))) - 1.0,
                                                     BaseColor,
                                                     Cspec0);
    }
    else {
      BSDF = BSDF + specular_weight * microfacet_multi_ggx_aniso_fresnel(
                                          Normal,
                                          T,
                                          alpha_x,
                                          alpha_y,
                                          (2.0 / (1.0 - sqrt(0.08 * Specular))) - 1.0,
                                          BaseColor,
                                          Cspec0);
    }
  }

  if (final_transmission > 1e-5) {
    color Cspec0 = BaseColor * SpecularTint + color(1.0, 1.0, 1.0) * (1.0 - SpecularTint);
    float eta = backfacing() ? 1.0 / f : f;

    if (distribution == "GGX" || Roughness <= 5e-2) {
      float cosNO = dot(Normal, I);
      float Fr = fresnel_dielectric_cos(cosNO, eta);

      float refl_roughness = Roughness;
      if (Roughness <= 1e-2)
        refl_roughness = 0.0;

      float transmission_roughness = refl_roughness;
      if (distribution == "GGX")
        transmission_roughness = 1.0 - (1.0 - refl_roughness) * (1.0 - TransmissionRoughness);

      BSDF = BSDF +
             final_transmission *
                 (Fr * microfacet_ggx_fresnel(
                           Normal, refl_roughness * refl_roughness, eta, BaseColor, Cspec0) +
                  (1.0 - Fr) * BaseColor *
                      microfacet_ggx_refraction(
                          Normal, transmission_roughness * transmission_roughness, eta));
    }
    else {
      BSDF = BSDF +
             final_transmission * microfacet_multi_ggx_glass_fresnel(
                                      Normal, Roughness * Roughness, eta, BaseColor, Cspec0);
    }
  }

  if (Clearcoat > 1e-5) {
    BSDF = BSDF + principled_clearcoat(
                      ClearcoatNormal, Clearcoat, ClearcoatRoughness * ClearcoatRoughness);
  }
}

Method 2: Node Group

A Node group can be created using built-in nodes similar to source code. In v4.0 Principled BSDF, It changed a lot and added some new stuff, like change IOR will effect the Fresnel curve but v3.6 doesn't.

It is no simple way to make a accurate version since some closure in osl not Appear in node interface.

Here is a simple example you can refer to, a node group that performs Principled BSDF in Blender 3.6.
You can play with Color, Specular, Roughness and Metallic but less accurate than the original version.

enter image description here enter image description here

Here is some test in 3.6 Cycles

enter image description here

enter image description here

enter image description here

Here are some tips for converting osl language to node

mix closure

BSDF = mix(A, B, fac);

enter image description here

Add closure

BSDF = A + B;

enter image description here

Multiply closure

BSDF = color(r, g, b) * A;

enter image description here

Condition a and b

BSDF = A;
if (a < 1.0 && b > 0.0) {BSDF = B;}

enter image description here

if else

a = condition ? b : c;
/* If the condition is true, then a is assigned to b, otherwise c */

enter image description here

$\endgroup$
4
  • $\begingroup$ This is very interesting! I don't want to use OSL nodes have limitation on Eevee if I remember correctly. It would also be interesting to understand if there is an OSL to Nodes converter that works accurately. $\endgroup$
    – Noob Cat
    Commented Oct 17, 2023 at 22:44
  • 1
    $\begingroup$ Converters don't exist because OSL can do things that node systems can't. Like for loop with variable. $\endgroup$
    – X Y
    Commented Oct 18, 2023 at 1:07
  • 1
    $\begingroup$ If I'm free in a few days, maybe I can give you a similar node group or some hints if no new answers. $\endgroup$
    – X Y
    Commented Oct 19, 2023 at 4:47
  • $\begingroup$ It would be nice to see how the Transmission integrates. I love this work you did! The result is slightly different, but I think we can work on it! Really useful! $\endgroup$
    – Noob Cat
    Commented Oct 25, 2023 at 22:19
2
$\begingroup$

For curiosity
you can find the source code for the closure in ..\intern\cycles\kernel\svm\closure.h here, and take a look at how 'BSDF_PRINCIPLED' is calculated:

   case CLOSURE_BSDF_PRINCIPLED_ID: {
      uint specular_ior_level_offset, roughness_offset, specular_tint_offset, anisotropic_offset,
          sheen_weight_offset, sheen_tint_offset, sheen_roughness_offset, coat_weight_offset,
          coat_roughness_offset, coat_ior_offset, eta_offset, transmission_weight_offset,
          anisotropic_rotation_offset, coat_tint_offset, coat_normal_offset, dummy, alpha_offset,
          emission_strength_offset, emission_offset, unused;
      uint4 data_node2 = read_node(kg, &offset);

      float3 T = stack_load_float3(stack, data_node.y);
      svm_unpack_node_uchar4(data_node.z,
                             &specular_ior_level_offset,
                             &roughness_offset,
                             &specular_tint_offset,
                             &anisotropic_offset);
      svm_unpack_node_uchar4(
          data_node.w, &sheen_weight_offset, &sheen_tint_offset, &sheen_roughness_offset, &unused);
      svm_unpack_node_uchar4(data_node2.x,
                             &eta_offset,
                             &transmission_weight_offset,
                             &anisotropic_rotation_offset,
                             &coat_normal_offset);
      svm_unpack_node_uchar4(data_node2.w,
                             &coat_weight_offset,
                             &coat_roughness_offset,
                             &coat_ior_offset,
                             &coat_tint_offset);

      // get Disney principled parameters
      float metallic = saturatef(param1);
      float subsurface_weight = saturatef(param2);
      float specular_ior_level = fmaxf(stack_load_float(stack, specular_ior_level_offset), 0.0f);
      float roughness = saturatef(stack_load_float(stack, roughness_offset));
      Spectrum specular_tint = rgb_to_spectrum(
          max(stack_load_float3(stack, specular_tint_offset), zero_float3()));
      float anisotropic = saturatef(stack_load_float(stack, anisotropic_offset));
      float sheen_weight = saturatef(stack_load_float(stack, sheen_weight_offset));
      float3 sheen_tint = stack_load_float3(stack, sheen_tint_offset);
      float sheen_roughness = saturatef(stack_load_float(stack, sheen_roughness_offset));
      float coat_weight = saturatef(stack_load_float(stack, coat_weight_offset));
      float coat_roughness = saturatef(stack_load_float(stack, coat_roughness_offset));
      float coat_ior = fmaxf(stack_load_float(stack, coat_ior_offset), 1.0f);
      float3 coat_tint = stack_load_float3(stack, coat_tint_offset);
      float transmission_weight = saturatef(stack_load_float(stack, transmission_weight_offset));
      float anisotropic_rotation = stack_load_float(stack, anisotropic_rotation_offset);
      float ior = fmaxf(stack_load_float(stack, eta_offset), 1e-5f);

      ClosureType distribution = (ClosureType)data_node2.y;
      ClosureType subsurface_method = (ClosureType)data_node2.z;

      float3 valid_reflection_N = maybe_ensure_valid_specular_reflection(sd, N);
      float3 coat_normal = stack_valid(coat_normal_offset) ?
                               stack_load_float3(stack, coat_normal_offset) :
                               sd->N;

      // get the base color
      uint4 data_base_color = read_node(kg, &offset);
      float3 base_color = stack_valid(data_base_color.x) ?
                              stack_load_float3(stack, data_base_color.x) :
                              make_float3(__uint_as_float(data_base_color.y),
                                          __uint_as_float(data_base_color.z),
                                          __uint_as_float(data_base_color.w));

      // get the subsurface scattering data
      uint4 data_subsurf = read_node(kg, &offset);

      uint4 data_alpha_emission = read_node(kg, &offset);
      svm_unpack_node_uchar4(data_alpha_emission.x,
                             &alpha_offset,
                             &emission_strength_offset,
                             &emission_offset,
                             &dummy);
      float alpha = stack_valid(alpha_offset) ? stack_load_float(stack, alpha_offset) :
                                                __uint_as_float(data_alpha_emission.y);
      alpha = saturatef(alpha);

      float emission_strength = stack_valid(emission_strength_offset) ?
                                    stack_load_float(stack, emission_strength_offset) :
                                    __uint_as_float(data_alpha_emission.z);
      float3 emission = stack_load_float3(stack, emission_offset) * fmaxf(emission_strength, 0.0f);

      Spectrum weight = closure_weight * mix_weight;

      float alpha_x = sqr(roughness), alpha_y = sqr(roughness);
      if (anisotropic > 0.0f) {
        float aspect = sqrtf(1.0f - anisotropic * 0.9f);
        alpha_x /= aspect;
        alpha_y *= aspect;
        if (anisotropic_rotation != 0.0f)
          T = rotate_around_axis(T, N, anisotropic_rotation * M_2PI_F);
      }

#ifdef __CAUSTICS_TRICKS__
      const bool reflective_caustics = (kernel_data.integrator.caustics_reflective ||
                                        (path_flag & PATH_RAY_DIFFUSE) == 0);
      const bool glass_caustics = (kernel_data.integrator.caustics_reflective ||
                                   kernel_data.integrator.caustics_refractive ||
                                   (path_flag & PATH_RAY_DIFFUSE) == 0);
#else
      const bool reflective_caustics = true;
      const bool glass_caustics = true;
#endif

      /* Before any actual shader components, apply transparency. */
      if (alpha < 1.0f) {
        bsdf_transparent_setup(sd, weight * (1.0f - alpha), path_flag);
        weight *= alpha;
      }

      /* First layer: Sheen */
      if (sheen_weight > CLOSURE_WEIGHT_CUTOFF) {
        ccl_private SheenBsdf *bsdf = (ccl_private SheenBsdf *)bsdf_alloc(
            sd, sizeof(SheenBsdf), sheen_weight * rgb_to_spectrum(sheen_tint) * weight);

        if (bsdf) {
          bsdf->N = safe_normalize(mix(N, coat_normal, saturatef(coat_weight)));
          bsdf->roughness = sheen_roughness;

          /* setup bsdf */
          const int sheen_flag = bsdf_sheen_setup(kg, sd, bsdf);

          if (sheen_flag) {
            sd->flag |= sheen_flag;

            /* Attenuate lower layers */
            Spectrum albedo = bsdf_albedo(kg, sd, (ccl_private ShaderClosure *)bsdf, true, false);
            weight = closure_layering_weight(albedo, weight);
          }
        }
      }

      /* Second layer: Coat */
      if (coat_weight > CLOSURE_WEIGHT_CUTOFF) {
        coat_normal = maybe_ensure_valid_specular_reflection(sd, coat_normal);
        if (reflective_caustics) {
          ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)bsdf_alloc(
              sd, sizeof(MicrofacetBsdf), coat_weight * weight);

          if (bsdf) {
            bsdf->N = coat_normal;
            bsdf->T = zero_float3();
            bsdf->ior = coat_ior;

            bsdf->alpha_x = bsdf->alpha_y = sqr(coat_roughness);

            /* setup bsdf */
            sd->flag |= bsdf_microfacet_ggx_setup(bsdf);
            bsdf_microfacet_setup_fresnel_dielectric(kg, bsdf, sd);

            /* Attenuate lower layers */
            Spectrum albedo = bsdf_albedo(kg, sd, (ccl_private ShaderClosure *)bsdf, true, false);
            weight = closure_layering_weight(albedo, weight);
          }
        }

        if (!isequal(coat_tint, one_float3())) {
          /* Tint is normalized to perpendicular incidence.
           * Therefore, if we define the coat thickness as length 1, the length along the ray is
           * t = sqrt(1+tan^2(angle(N, I))) = sqrt(1+tan^2(acos(dotNI))) = 1 / dotNI.
           * From Beer's law, we have T = exp(-sigma_e * t).
           * Therefore, tint = exp(-sigma_e * 1) (per def.), so -sigma_e = log(tint).
           * From this, T = exp(log(tint) * t) = exp(log(tint)) ^ t = tint ^ t;
           *
           * Note that this is only an approximation - it assumes that the outgoing ray
           * follows the same angle, and that there aren't multiple internal bounces.
           * In particular, things that could be improved:
           * - For transmissive materials, there should not be an outgoing path at all if the path
           *   is transmitted.
           * - For rough materials, we could blend towards a view-independent average path length
           *   (e.g. 2 for diffuse reflection) for the outgoing direction.
           * However, there's also an argument to be made for keeping parameters independent of
           * each other for more intuitive control, in particular main roughness not affecting the
           * coat.
           */
          float cosNI = dot(sd->wi, coat_normal);
          /* Refract incoming direction into coat material.
           * TIR is no concern here since we're always coming from the outside. */
          float cosNT = sqrtf(1.0f - sqr(1.0f / coat_ior) * (1 - sqr(cosNI)));
          float optical_depth = 1.0f / cosNT;
          weight *= power(rgb_to_spectrum(coat_tint), coat_weight * optical_depth);
        }
      }

      /* Emission (attenuated by sheen and coat) */
      if (!is_zero(emission)) {
        emission_setup(sd, rgb_to_spectrum(emission) * weight);
      }

      /* Metallic component */
      if (reflective_caustics && metallic > CLOSURE_WEIGHT_CUTOFF) {
        ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)bsdf_alloc(
            sd, sizeof(MicrofacetBsdf), metallic * weight);
        ccl_private FresnelF82Tint *fresnel =
            (bsdf != NULL) ?
                (ccl_private FresnelF82Tint *)closure_alloc_extra(sd, sizeof(FresnelF82Tint)) :
                NULL;

        if (bsdf && fresnel) {
          bsdf->N = valid_reflection_N;
          bsdf->ior = 1.0f;
          bsdf->T = T;
          bsdf->alpha_x = alpha_x;
          bsdf->alpha_y = alpha_y;

          fresnel->f0 = rgb_to_spectrum(base_color);
          const Spectrum f82 = specular_tint;

          /* setup bsdf */
          sd->flag |= bsdf_microfacet_ggx_setup(bsdf);
          const bool is_multiggx = (distribution == CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID);
          bsdf_microfacet_setup_fresnel_f82_tint(kg, bsdf, sd, fresnel, f82, is_multiggx);

          /* Attenuate other components */
          weight *= (1.0f - metallic);
        }
      }

      /* Transmission component */
      if (glass_caustics && transmission_weight > CLOSURE_WEIGHT_CUTOFF) {
        ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)bsdf_alloc(
            sd, sizeof(MicrofacetBsdf), transmission_weight * weight);
        ccl_private FresnelGeneralizedSchlick *fresnel =
            (bsdf != NULL) ? (ccl_private FresnelGeneralizedSchlick *)closure_alloc_extra(
                                 sd, sizeof(FresnelGeneralizedSchlick)) :
                             NULL;

        if (bsdf && fresnel) {
          bsdf->N = valid_reflection_N;
          bsdf->T = zero_float3();

          bsdf->alpha_x = bsdf->alpha_y = sqr(roughness);
          bsdf->ior = (sd->flag & SD_BACKFACING) ? 1.0f / ior : ior;

          fresnel->f0 = make_float3(F0_from_ior(ior)) * specular_tint;
          fresnel->f90 = one_spectrum();
          fresnel->exponent = -ior;
          fresnel->reflection_tint = one_spectrum();
          fresnel->transmission_tint = sqrt(rgb_to_spectrum(base_color));

          /* setup bsdf */
          sd->flag |= bsdf_microfacet_ggx_glass_setup(bsdf);
          const bool is_multiggx = (distribution == CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID);
          bsdf_microfacet_setup_fresnel_generalized_schlick(kg, bsdf, sd, fresnel, is_multiggx);

          /* Attenuate other components */
          weight *= (1.0f - transmission_weight);
        }
      }

      /* Apply IOR adjustment */
      float eta = ior;
      float f0 = F0_from_ior(eta);
      if (specular_ior_level != 0.5f) {
        f0 *= 2.0f * specular_ior_level;
        eta = ior_from_F0(f0);
        if (ior < 1.0f) {
          eta = 1.0f / eta;
        }
      }

      /* Specular component */
      if (reflective_caustics && eta != 1.0f) {
        ccl_private MicrofacetBsdf *bsdf = (ccl_private MicrofacetBsdf *)bsdf_alloc(
            sd, sizeof(MicrofacetBsdf), weight);
        ccl_private FresnelGeneralizedSchlick *fresnel =
            (bsdf != NULL) ? (ccl_private FresnelGeneralizedSchlick *)closure_alloc_extra(
                                 sd, sizeof(FresnelGeneralizedSchlick)) :
                             NULL;

        if (bsdf && fresnel) {
          bsdf->N = valid_reflection_N;
          bsdf->ior = eta;
          bsdf->T = T;
          bsdf->alpha_x = alpha_x;
          bsdf->alpha_y = alpha_y;

          fresnel->f0 = f0 * specular_tint;
          fresnel->f90 = one_spectrum();
          fresnel->exponent = -eta;
          fresnel->reflection_tint = one_spectrum();
          fresnel->transmission_tint = zero_spectrum();

          /* setup bsdf */
          sd->flag |= bsdf_microfacet_ggx_setup(bsdf);
          const bool is_multiggx = (distribution == CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID);
          bsdf_microfacet_setup_fresnel_generalized_schlick(kg, bsdf, sd, fresnel, is_multiggx);

          /* Attenuate lower layers */
          Spectrum albedo = bsdf_albedo(kg, sd, (ccl_private ShaderClosure *)bsdf, true, false);
          weight = closure_layering_weight(albedo, weight);
        }
      }

      /* Diffuse/Subsurface component */
#ifdef __SUBSURFACE__
      ccl_private Bssrdf *bssrdf = bssrdf_alloc(
          sd, rgb_to_spectrum(base_color) * subsurface_weight * weight);
      if (bssrdf) {
        float3 subsurface_radius = stack_load_float3(stack, data_subsurf.y);
        float subsurface_scale = stack_load_float(stack, data_subsurf.z);

        bssrdf->radius = rgb_to_spectrum(subsurface_radius * subsurface_scale);
        bssrdf->albedo = rgb_to_spectrum(base_color);
        bssrdf->N = maybe_ensure_valid_specular_reflection(sd, N);
        bssrdf->alpha = sqr(roughness);
        bssrdf->ior = eta;
        bssrdf->anisotropy = stack_load_float(stack, data_subsurf.w);
        if (subsurface_method == CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID) {
          bssrdf->ior = stack_load_float(stack, data_subsurf.x);
        }

        /* setup bsdf */
        sd->flag |= bssrdf_setup(sd, bssrdf, path_flag, subsurface_method);
      }
#else
      subsurface_weight = 0.0f;
#endif

      ccl_private DiffuseBsdf *bsdf = (ccl_private DiffuseBsdf *)bsdf_alloc(
          sd,
          sizeof(DiffuseBsdf),
          rgb_to_spectrum(base_color) * (1.0f - subsurface_weight) * weight);
      if (bsdf) {
        bsdf->N = N;

        /* setup bsdf */
        sd->flag |= bsdf_diffuse_setup(bsdf);
      }

      break;
    }

Recreating it "pixel perfect" with BSDFs will be a big headache.

I'm not sure that a large BSDF is actually made up of pieces of small BSDFs.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .