Cycles: add Principled Hair BSDF.
authorL. E. Segovia <leonardo.segovia@cs.uns.edu.ar>
Wed, 18 Jul 2018 09:14:43 +0000 (11:14 +0200)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Wed, 18 Jul 2018 11:59:06 +0000 (13:59 +0200)
This is a physically-based, easy-to-use shader for rendering hair and fur,
with controls for melanin, roughness and randomization.

Based on the paper "A Practical and Controllable Hair and Fur Model for
Production Path Tracing".

Implemented by Leonardo E. Segovia and Lukas Stockner, part of Google
Summer of Code 2018.

30 files changed:
intern/cycles/blender/blender_shader.cpp
intern/cycles/kernel/CMakeLists.txt
intern/cycles/kernel/closure/bsdf.h
intern/cycles/kernel/closure/bsdf_hair_principled.h [new file with mode: 0644]
intern/cycles/kernel/geom/geom_curve.h
intern/cycles/kernel/geom/geom_curve_intersect.h
intern/cycles/kernel/kernel_compat_opencl.h
intern/cycles/kernel/kernel_volume.h
intern/cycles/kernel/osl/osl_closures.cpp
intern/cycles/kernel/osl/osl_closures.h
intern/cycles/kernel/shaders/CMakeLists.txt
intern/cycles/kernel/shaders/node_principled_hair_bsdf.osl [new file with mode: 0644]
intern/cycles/kernel/shaders/stdosl.h
intern/cycles/kernel/svm/svm_closure.h
intern/cycles/kernel/svm/svm_types.h
intern/cycles/render/graph.cpp
intern/cycles/render/nodes.cpp
intern/cycles/render/nodes.h
intern/cycles/util/util_math.h
intern/cycles/util/util_math_float3.h
release/scripts/startup/nodeitems_builtins.py
source/blender/blenkernel/BKE_node.h
source/blender/blenkernel/intern/node.c
source/blender/editors/space_node/drawnode.c
source/blender/makesdna/DNA_node_types.h
source/blender/makesrna/intern/rna_nodetree.c
source/blender/nodes/CMakeLists.txt
source/blender/nodes/NOD_shader.h
source/blender/nodes/NOD_static_types.h
source/blender/nodes/shader/nodes/node_shader_bsdf_hair_principled.c [new file with mode: 0644]

index 956f8f767a6b1819b7d8743505912455cc492e6b..25e6db18588be9d8fab47872ffee0b0bf5134c27 100644 (file)
@@ -524,6 +524,12 @@ static ShaderNode *add_node(Scene *scene,
                }
                node = hair;
        }
+       else if(b_node.is_a(&RNA_ShaderNodeBsdfHairPrincipled)) {
+               BL::ShaderNodeBsdfHairPrincipled b_principled_hair_node(b_node);
+               PrincipledHairBsdfNode *principled_hair = new PrincipledHairBsdfNode();
+               principled_hair->parametrization = (NodePrincipledHairParametrization) get_enum(b_principled_hair_node.ptr, "parametrization", NODE_PRINCIPLED_HAIR_NUM, NODE_PRINCIPLED_HAIR_REFLECTANCE);
+               node = principled_hair;
+       }
        else if(b_node.is_a(&RNA_ShaderNodeBsdfPrincipled)) {
                BL::ShaderNodeBsdfPrincipled b_principled_node(b_node);
                PrincipledBsdfNode *principled = new PrincipledBsdfNode();
index 092bec08a51214d8b512aa24dbc0c2de14f1d5f7..c4cad17429dfeb7a1e6f4b0910b640af69f065bb 100644 (file)
@@ -156,6 +156,7 @@ set(SRC_CLOSURE_HEADERS
        closure/volume.h
        closure/bsdf_principled_diffuse.h
        closure/bsdf_principled_sheen.h
+    closure/bsdf_hair_principled.h
 )
 
 set(SRC_SVM_HEADERS
index f191b812f1103ccef8dabf4d4f5da52260aec060..3a9629ea9d74dff81e6c98e41e0756ee5c442128 100644 (file)
@@ -27,6 +27,7 @@
 #include "kernel/closure/bsdf_ashikhmin_shirley.h"
 #include "kernel/closure/bsdf_toon.h"
 #include "kernel/closure/bsdf_hair.h"
+#include "kernel/closure/bsdf_hair_principled.h"
 #include "kernel/closure/bsdf_principled_diffuse.h"
 #include "kernel/closure/bsdf_principled_sheen.h"
 #include "kernel/closure/bssrdf.h"
@@ -171,6 +172,10 @@ ccl_device_inline int bsdf_sample(KernelGlobals *kg,
                        label = bsdf_hair_transmission_sample(sc, sd->Ng, sd->I, sd->dI.dx, sd->dI.dy, randu, randv,
                                eval, omega_in, &domega_in->dx, &domega_in->dy, pdf);
                        break;
+               case CLOSURE_BSDF_HAIR_PRINCIPLED_ID:
+                       label = bsdf_principled_hair_sample(kg, sc, sd, randu, randv,
+                               eval, omega_in, &domega_in->dx, &domega_in->dy, pdf);
+                       break;
 #ifdef __PRINCIPLED__
                case CLOSURE_BSDF_PRINCIPLED_DIFFUSE_ID:
                case CLOSURE_BSDF_BSSRDF_PRINCIPLED_ID:
@@ -284,6 +289,9 @@ float3 bsdf_eval(KernelGlobals *kg,
                        case CLOSURE_BSDF_GLOSSY_TOON_ID:
                                eval = bsdf_glossy_toon_eval_reflect(sc, sd->I, omega_in, pdf);
                                break;
+                       case CLOSURE_BSDF_HAIR_PRINCIPLED_ID:
+                               eval = bsdf_principled_hair_eval(kg, sd, sc, omega_in, pdf);
+                               break;
                        case CLOSURE_BSDF_HAIR_REFLECTION_ID:
                                eval = bsdf_hair_reflection_eval_reflect(sc, sd->I, omega_in, pdf);
                                break;
@@ -366,6 +374,9 @@ float3 bsdf_eval(KernelGlobals *kg,
                        case CLOSURE_BSDF_GLOSSY_TOON_ID:
                                eval = bsdf_glossy_toon_eval_transmit(sc, sd->I, omega_in, pdf);
                                break;
+                       case CLOSURE_BSDF_HAIR_PRINCIPLED_ID:
+                               eval = bsdf_principled_hair_eval(kg, sd, sc, omega_in, pdf);
+                               break;
                        case CLOSURE_BSDF_HAIR_REFLECTION_ID:
                                eval = bsdf_hair_reflection_eval_transmit(sc, sd->I, omega_in, pdf);
                                break;
@@ -424,6 +435,9 @@ ccl_device void bsdf_blur(KernelGlobals *kg, ShaderClosure *sc, float roughness)
                case CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ANISO_ID:
                        bsdf_ashikhmin_shirley_blur(sc, roughness);
                        break;
+               case CLOSURE_BSDF_HAIR_PRINCIPLED_ID:
+                       bsdf_principled_hair_blur(sc, roughness);
+                       break;
                default:
                        break;
        }
diff --git a/intern/cycles/kernel/closure/bsdf_hair_principled.h b/intern/cycles/kernel/closure/bsdf_hair_principled.h
new file mode 100644 (file)
index 0000000..4ee5808
--- /dev/null
@@ -0,0 +1,502 @@
+/*
+ * Copyright 2018 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef __KERNEL_CPU__
+#include <fenv.h>
+#endif
+
+#include "kernel/kernel_color.h"
+
+#ifndef __BSDF_HAIR_PRINCIPLED_H__
+#define __BSDF_HAIR_PRINCIPLED_H__
+
+CCL_NAMESPACE_BEGIN
+
+typedef ccl_addr_space struct PrincipledHairExtra {
+       /* Geometry data. */
+       float4 geom;
+} PrincipledHairExtra;
+
+typedef ccl_addr_space struct PrincipledHairBSDF {
+       SHADER_CLOSURE_BASE;
+
+       /* Absorption coefficient. */
+       float3 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;
+
+       /* Extra closure. */
+       PrincipledHairExtra *extra;
+} PrincipledHairBSDF;
+
+static_assert(sizeof(ShaderClosure) >= sizeof(PrincipledHairBSDF), "PrincipledHairBSDF is too large!");
+static_assert(sizeof(ShaderClosure) >= sizeof(PrincipledHairExtra), "PrincipledHairExtra is too large!");
+
+ccl_device_inline float cos_from_sin(const float s)
+{
+       return safe_sqrtf(1.0f - s*s);
+}
+
+/* Gives the change in direction in the normal plane for the given angles and p-th-order scattering. */
+ccl_device_inline float delta_phi(int p, float gamma_o, 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(float a)
+{
+       while(a > M_PI_F) {
+               a -= M_2PI_F;
+       }
+       while(a < -M_PI_F) {
+               a += M_2PI_F;
+       }
+       return a;
+}
+
+/* Logistic distribution function. */
+ccl_device_inline float logistic(float x, float s)
+{
+       float v = expf(-fabsf(x)/s);
+       return v / (s * sqr(1.0f + v));
+}
+
+/* Logistic cumulative density function. */
+ccl_device_inline float logistic_cdf(float x, float s)
+{
+       float arg = -x/s;
+       /* expf() overflows if arg >= 89.0. */
+       if(arg > 88.0f) {
+               return 0.0f;
+       }
+       else {
+               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;
+               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(float x)
+{
+       if (x > 12.0f) {
+               /* log(1/x) == -log(x) iff x > 0.
+                * This is only used with positive cosines */
+               return x + 0.5f * (1.f / (8.0f * x) - M_LN_2PI_F - logf(x));
+       }
+       else {
+               return logf(bessel_I0(x));
+       }
+}
+
+/* Logistic distribution limited to the interval [-pi, pi]. */
+ccl_device_inline float trimmed_logistic(float x, 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) */
+       float scaling_fac = 1.0f - 2.0f*logistic_cdf(-M_PI_F, s);
+       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(float u, float s)
+{
+       float cdf_minuspi = logistic_cdf(-M_PI_F, s);
+       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,
+                                             int p,
+                                             float s,
+                                             float gamma_o,
+                                             float gamma_t)
+{
+       float phi_o = wrap_angle(phi - delta_phi(p, gamma_o, gamma_t));
+       float val = trimmed_logistic(phi_o, s);
+       return val;
+}
+
+/* Longitudinal scattering function Mp. */
+ccl_device_inline float longitudinal_scattering(float sin_theta_i,
+                                                float cos_theta_i,
+                                                float sin_theta_o,
+                                                float cos_theta_o,
+                                                float v)
+{
+       float inv_v = 1.0f/v;
+       float cos_arg = cos_theta_i * cos_theta_o * inv_v;
+       float sin_arg = sin_theta_i * sin_theta_o * inv_v;
+       if(v <= 0.1f) {
+               float i0 = log_bessel_I0(cos_arg);
+               float val = expf(i0 - sin_arg - inv_v + 0.6931f + logf(0.5f*inv_v));
+               return val;
+       }
+       else {
+               float i0 = bessel_I0(cos_arg);
+               float val = (expf(-sin_arg) * i0) / (sinhf(inv_v) * 2.0f * v);
+               return val;
+       }
+}
+
+/* Combine the three values using their luminances. */
+ccl_device_inline float4 combine_with_energy(KernelGlobals *kg, float3 c)
+{
+       return make_float4(c.x, c.y, c.z, linear_rgb_to_gray(kg, c));
+}
+
+#ifdef __HAIR__
+/* Set up the hair closure. */
+ccl_device int bsdf_principled_hair_setup(ShaderData *sd, PrincipledHairBSDF *bsdf)
+{
+       bsdf->type = CLOSURE_BSDF_HAIR_PRINCIPLED_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. */
+       float3 X = safe_normalize(sd->dPdu);
+       float3 Y = safe_normalize(cross(X, sd->I));
+       float3 Z = safe_normalize(cross(X, Y));
+       /* TODO: the solution below works where sd->Ng is the normal
+        * pointing from the center of the curve to the shading point.
+        * It doesn't work for triangles, see https://developer.blender.org/T43625 */
+
+       /* 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. */
+       float h = dot(cross(sd->Ng, X), Z);
+
+       kernel_assert(fabsf(h) < 1.0f + 1e-4f);
+       kernel_assert(isfinite3_safe(Y));
+       kernel_assert(isfinite_safe(h));
+
+       bsdf->extra->geom = make_float4(Y.x, Y.y, Y.z, h);
+
+       return SD_BSDF|SD_BSDF_HAS_EVAL|SD_BSDF_NEEDS_LCG;
+}
+
+#endif /* __HAIR__ */
+
+/* Given the Fresnel term and transmittance, generate the attenuation terms for each bounce. */
+ccl_device_inline void hair_attenuation(KernelGlobals *kg,
+                                        float f,
+                                        float3 T,
+                                        float4 *Ap)
+{
+       /* Primary specular (R). */
+       Ap[0] = make_float4(f, f, f, f);
+
+       /* Transmission (TT). */
+       float3 col = sqr(1.0f - f) * T;
+       Ap[1] = combine_with_energy(kg, col);
+
+       /* Secondary specular (TRT). */
+       col *= T*f;
+       Ap[2] = combine_with_energy(kg, col);
+
+       /* Residual component (TRRT+). */
+       col *= safe_divide_color(T*f, make_float3(1.0f, 1.0f, 1.0f) - T*f);
+       Ap[3] = combine_with_energy(kg, col);
+
+       /* Normalize sampling weights. */
+       float totweight = Ap[0].w + Ap[1].w + Ap[2].w + Ap[3].w;
+       float fac = safe_divide(1.0f, totweight);
+
+       Ap[0].w *= fac;
+       Ap[1].w *= fac;
+       Ap[2].w *= fac;
+       Ap[3].w *= fac;
+}
+
+/* Given the tilt angle, generate the rotated theta_i for the different bounces. */
+ccl_device_inline void hair_alpha_angles(float sin_theta_i,
+                                         float cos_theta_i,
+                                         float alpha,
+                                         float *angles)
+{
+       float sin_1alpha = sinf(alpha);
+       float cos_1alpha = cos_from_sin(sin_1alpha);
+       float sin_2alpha = 2.0f*sin_1alpha*cos_1alpha;
+       float cos_2alpha = sqr(cos_1alpha) - sqr(sin_1alpha);
+       float sin_4alpha = 2.0f*sin_2alpha*cos_2alpha;
+       float cos_4alpha = sqr(cos_2alpha) - sqr(sin_2alpha);
+
+       angles[0] = sin_theta_i*cos_2alpha + cos_theta_i*sin_2alpha;
+       angles[1] = fabsf(cos_theta_i*cos_2alpha - sin_theta_i*sin_2alpha);
+       angles[2] = sin_theta_i*cos_1alpha - cos_theta_i*sin_1alpha;
+       angles[3] = fabsf(cos_theta_i*cos_1alpha + sin_theta_i*sin_1alpha);
+       angles[4] = sin_theta_i*cos_4alpha - cos_theta_i*sin_4alpha;
+       angles[5] = fabsf(cos_theta_i*cos_4alpha + sin_theta_i*sin_4alpha);
+}
+
+/* Evaluation function for our shader. */
+ccl_device float3 bsdf_principled_hair_eval(KernelGlobals *kg,
+                                            const ShaderData *sd,
+                                            const ShaderClosure *sc,
+                                            const float3 omega_in,
+                                            float *pdf)
+{
+       kernel_assert(isfinite3_safe(sd->P) && isfinite_safe(sd->ray_length));
+
+       const PrincipledHairBSDF *bsdf = (const PrincipledHairBSDF*) sc;
+       float3 Y = float4_to_float3(bsdf->extra->geom);
+
+       float3 X = safe_normalize(sd->dPdu);
+       kernel_assert(fabsf(dot(X, Y)) < 1e-4f);
+       float3 Z = safe_normalize(cross(X, Y));
+
+       float3 wo = make_float3(dot(sd->I, X), dot(sd->I, Y), dot(sd->I, Z));
+       float3 wi = make_float3(dot(omega_in, X), dot(omega_in, Y), dot(omega_in, Z));
+
+       float sin_theta_o = wo.x;
+       float cos_theta_o = cos_from_sin(sin_theta_o);
+       float phi_o = atan2f(wo.z, wo.y);
+
+       float sin_theta_t = sin_theta_o / bsdf->eta;
+       float cos_theta_t = cos_from_sin(sin_theta_t);
+
+       float sin_gamma_o = bsdf->extra->geom.w;
+       float cos_gamma_o = cos_from_sin(sin_gamma_o);
+       float gamma_o = safe_asinf(sin_gamma_o);
+
+       float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
+       float cos_gamma_t = cos_from_sin(sin_gamma_t);
+       float gamma_t = safe_asinf(sin_gamma_t);
+
+       float3 T = exp3(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
+       float4 Ap[4];
+       hair_attenuation(kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap);
+
+       float sin_theta_i = wi.x;
+       float cos_theta_i = cos_from_sin(sin_theta_i);
+       float phi_i = atan2f(wi.z, wi.y);
+
+       float phi = phi_i - phi_o;
+
+       float angles[6];
+       hair_alpha_angles(sin_theta_i, cos_theta_i, bsdf->alpha, angles);
+
+       float4 F;
+       float Mp, Np;
+
+       /* Primary specular (R). */
+       Mp = longitudinal_scattering(angles[0], angles[1], sin_theta_o, cos_theta_o, bsdf->m0_roughness);
+       Np = azimuthal_scattering(phi, 0, bsdf->s, gamma_o, gamma_t);
+       F  = Ap[0] * Mp * Np;
+       kernel_assert(isfinite3_safe(float4_to_float3(F)));
+
+       /* Transmission (TT). */
+       Mp = longitudinal_scattering(angles[2], angles[3], sin_theta_o, cos_theta_o, 0.25f*bsdf->v);
+       Np = azimuthal_scattering(phi, 1, bsdf->s, gamma_o, gamma_t);
+       F += Ap[1] * Mp * Np;
+       kernel_assert(isfinite3_safe(float4_to_float3(F)));
+
+       /* Secondary specular (TRT). */
+       Mp = longitudinal_scattering(angles[4], angles[5], sin_theta_o, cos_theta_o, 4.0f*bsdf->v);
+       Np = azimuthal_scattering(phi, 2, bsdf->s, gamma_o, gamma_t);
+       F += Ap[2] * Mp * Np;
+       kernel_assert(isfinite3_safe(float4_to_float3(F)));
+
+       /* Residual component (TRRT+). */
+       Mp = longitudinal_scattering(sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f*bsdf->v);
+       Np = M_1_2PI_F;
+       F += Ap[3] * Mp * Np;
+       kernel_assert(isfinite3_safe(float4_to_float3(F)));
+
+       *pdf = F.w;
+       return float4_to_float3(F);
+}
+
+/* Sampling function for the hair shader. */
+ccl_device int bsdf_principled_hair_sample(KernelGlobals *kg,
+                                           const ShaderClosure *sc,
+                                           ShaderData *sd,
+                                           float randu,
+                                           float randv,
+                                           float3 *eval,
+                                           float3 *omega_in,
+                                           float3 *domega_in_dx,
+                                           float3 *domega_in_dy,
+                                           float *pdf)
+{
+       PrincipledHairBSDF *bsdf = (PrincipledHairBSDF*) sc;
+
+       float3 Y = float4_to_float3(bsdf->extra->geom);
+
+       float3 X = safe_normalize(sd->dPdu);
+       kernel_assert(fabsf(dot(X, Y)) < 1e-4f);
+       float3 Z = safe_normalize(cross(X, Y));
+
+       float3 wo = make_float3(dot(sd->I, X), dot(sd->I, Y), dot(sd->I, Z));
+
+       float2 u[2];
+       u[0] = make_float2(randu, randv);
+       u[1].x = lcg_step_float_addrspace(&sd->lcg_state);
+       u[1].y = lcg_step_float_addrspace(&sd->lcg_state);
+
+       float sin_theta_o = wo.x;
+       float cos_theta_o = cos_from_sin(sin_theta_o);
+       float phi_o = atan2f(wo.z, wo.y);
+
+       float sin_theta_t = sin_theta_o / bsdf->eta;
+       float cos_theta_t = cos_from_sin(sin_theta_t);
+
+       float sin_gamma_o = bsdf->extra->geom.w;
+       float cos_gamma_o = cos_from_sin(sin_gamma_o);
+       float gamma_o = safe_asinf(sin_gamma_o);
+
+       float sin_gamma_t = sin_gamma_o * cos_theta_o / sqrtf(sqr(bsdf->eta) - sqr(sin_theta_o));
+       float cos_gamma_t = cos_from_sin(sin_gamma_t);
+       float gamma_t = safe_asinf(sin_gamma_t);
+
+       float3 T = exp3(-bsdf->sigma * (2.0f * cos_gamma_t / cos_theta_t));
+       float4 Ap[4];
+       hair_attenuation(kg, fresnel_dielectric_cos(cos_theta_o * cos_gamma_o, bsdf->eta), T, Ap);
+
+       int p = 0;
+       for(; p < 3; p++) {
+               if(u[0].x < Ap[p].w) {
+                       break;
+               }
+               u[0].x -= Ap[p].w;
+       }
+
+       float v = bsdf->v;
+       if(p == 1) {
+               v *= 0.25f;
+       }
+       if(p >= 2) {
+               v *= 4.0f;
+       }
+
+       u[1].x = max(u[1].x, 1e-5f);
+       float fac = 1.0f + v*logf(u[1].x + (1.0f - u[1].x)*expf(-2.0f/v));
+       float sin_theta_i = -fac * sin_theta_o + cos_from_sin(fac) * cosf(M_2PI_F * u[1].y) * cos_theta_o;
+       float cos_theta_i = cos_from_sin(sin_theta_i);
+
+       float angles[6];
+       if(p < 3) {
+               hair_alpha_angles(sin_theta_i, cos_theta_i, -bsdf->alpha, angles);
+               sin_theta_i = angles[2*p];
+               cos_theta_i = angles[2*p+1];
+       }
+
+       float phi;
+       if(p < 3) {
+               phi = delta_phi(p, gamma_o, gamma_t) + sample_trimmed_logistic(u[0].y, bsdf->s);
+       }
+       else {
+               phi = M_2PI_F*u[0].y;
+       }
+       float phi_i = phi_o + phi;
+
+       hair_alpha_angles(sin_theta_i, cos_theta_i, bsdf->alpha, angles);
+
+       float4 F;
+       float Mp, Np;
+
+       /* Primary specular (R). */
+       Mp = longitudinal_scattering(angles[0], angles[1], sin_theta_o, cos_theta_o, bsdf->m0_roughness);
+       Np = azimuthal_scattering(phi, 0, bsdf->s, gamma_o, gamma_t);
+       F  = Ap[0] * Mp * Np;
+       kernel_assert(isfinite3_safe(float4_to_float3(F)));
+
+       /* Transmission (TT). */
+       Mp = longitudinal_scattering(angles[2], angles[3], sin_theta_o, cos_theta_o, 0.25f*bsdf->v);
+       Np = azimuthal_scattering(phi, 1, bsdf->s, gamma_o, gamma_t);
+       F += Ap[1] * Mp * Np;
+       kernel_assert(isfinite3_safe(float4_to_float3(F)));
+
+       /* Secondary specular (TRT). */
+       Mp = longitudinal_scattering(angles[4], angles[5], sin_theta_o, cos_theta_o, 4.0f*bsdf->v);
+       Np = azimuthal_scattering(phi, 2, bsdf->s, gamma_o, gamma_t);
+       F += Ap[2] * Mp * Np;
+       kernel_assert(isfinite3_safe(float4_to_float3(F)));
+
+       /* Residual component (TRRT+). */
+       Mp = longitudinal_scattering(sin_theta_i, cos_theta_i, sin_theta_o, cos_theta_o, 4.0f*bsdf->v);
+       Np = M_1_2PI_F;
+       F += Ap[3] * Mp * Np;
+       kernel_assert(isfinite3_safe(float4_to_float3(F)));
+
+       *eval = float4_to_float3(F);
+       *pdf = F.w;
+
+       *omega_in = X*sin_theta_i + Y*cos_theta_i*cosf(phi_i) + Z*cos_theta_i*sinf(phi_i);
+
+#ifdef __RAY_DIFFERENTIALS__
+       float3 N = safe_normalize(sd->I + *omega_in);
+       *domega_in_dx = (2 * dot(N, sd->dI.dx)) * N - sd->dI.dx;
+       *domega_in_dy = (2 * dot(N, sd->dI.dy)) * N - sd->dI.dy;
+#endif
+
+       return LABEL_GLOSSY|((p == 0)? LABEL_REFLECT : LABEL_TRANSMIT);
+}
+
+/* Implements Filter Glossy by capping the effective roughness. */
+ccl_device void bsdf_principled_hair_blur(ShaderClosure *sc, float roughness)
+{
+       PrincipledHairBSDF *bsdf = (PrincipledHairBSDF*)sc;
+
+       bsdf->v = fmaxf(roughness, bsdf->v);
+       bsdf->s = fmaxf(roughness, bsdf->s);
+       bsdf->m0_roughness = fmaxf(roughness, bsdf->m0_roughness);
+}
+
+CCL_NAMESPACE_END
+
+#endif /* __BSDF_HAIR_PRINCIPLED_H__ */
index e35267f02bf43186dacc32905325b53da8e0bf87..dea0c742ed75bb6a2cb45b55fa8979ee3ddf5417 100644 (file)
@@ -23,6 +23,33 @@ CCL_NAMESPACE_BEGIN
 
 #ifdef __HAIR__
 
+/* Interpolation of curve geometry */
+
+ccl_device_inline float3 curvetangent(float t, float3 p0, float3 p1, float3 p2, float3 p3)
+{
+       float fc = 0.71f;
+       float data[4];
+       float t2 = t * t;
+       data[0] = -3.0f * fc          * t2  + 4.0f * fc * t                  - fc;
+       data[1] =  3.0f * (2.0f - fc) * t2  + 2.0f * (fc - 3.0f) * t;
+       data[2] =  3.0f * (fc - 2.0f) * t2  + 2.0f * (3.0f - 2.0f * fc) * t  + fc;
+       data[3] =  3.0f * fc          * t2  - 2.0f * fc * t;
+       return data[0] * p0 + data[1] * p1 + data[2] * p2 + data[3] * p3;
+}
+
+ccl_device_inline float3 curvepoint(float t, float3 p0, float3 p1, float3 p2, float3 p3)
+{
+       float data[4];
+       float fc = 0.71f;
+       float t2 = t * t;
+       float t3 = t2 * t;
+       data[0] = -fc          * t3  + 2.0f * fc          * t2 - fc * t;
+       data[1] =  (2.0f - fc) * t3  + (fc - 3.0f)        * t2 + 1.0f;
+       data[2] =  (fc - 2.0f) * t3  + (3.0f - 2.0f * fc) * t2 + fc * t;
+       data[3] =  fc          * t3  - fc * t2;
+       return data[0] * p0 + data[1] * p1 + data[2] * p2 + data[3] * p3;
+}
+
 /* Reading attributes on various curve elements */
 
 ccl_device float curve_attribute_float(KernelGlobals *kg, const ShaderData *sd, const AttributeDescriptor desc, float *dx, float *dy)
index 46c3f408f0b2763869bf84f331ab9cd11dcb2a81..4cfbe21685cca288c864348234fc756f25044420 100644 (file)
@@ -752,31 +752,6 @@ ccl_device_forceinline bool curve_intersect(KernelGlobals *kg,
 #endif
 }
 
-ccl_device_inline float3 curvetangent(float t, float3 p0, float3 p1, float3 p2, float3 p3)
-{
-       float fc = 0.71f;
-       float data[4];
-       float t2 = t * t;
-       data[0] = -3.0f * fc          * t2  + 4.0f * fc * t                  - fc;
-       data[1] =  3.0f * (2.0f - fc) * t2  + 2.0f * (fc - 3.0f) * t;
-       data[2] =  3.0f * (fc - 2.0f) * t2  + 2.0f * (3.0f - 2.0f * fc) * t  + fc;
-       data[3] =  3.0f * fc          * t2  - 2.0f * fc * t;
-       return data[0] * p0 + data[1] * p1 + data[2] * p2 + data[3] * p3;
-}
-
-ccl_device_inline float3 curvepoint(float t, float3 p0, float3 p1, float3 p2, float3 p3)
-{
-       float data[4];
-       float fc = 0.71f;
-       float t2 = t * t;
-       float t3 = t2 * t;
-       data[0] = -fc          * t3  + 2.0f * fc          * t2 - fc * t;
-       data[1] =  (2.0f - fc) * t3  + (fc - 3.0f)        * t2 + 1.0f;
-       data[2] =  (fc - 2.0f) * t3  + (3.0f - 2.0f * fc) * t2 + fc * t;
-       data[3] =  fc          * t3  - fc * t2;
-       return data[0] * p0 + data[1] * p1 + data[2] * p2 + data[3] * p3;
-}
-
 ccl_device_inline float3 curve_refine(KernelGlobals *kg,
                                       ShaderData *sd,
                                       const Intersection *isect,
index d1ae10a038436ade0d0a18e5fd19fe18da0cad72..3f7e264fbee77b559a1d3494bda489269d101281 100644 (file)
 #define fmaxf(x, y) fmax(((float)(x)), ((float)(y)))
 #define fminf(x, y) fmin(((float)(x)), ((float)(y)))
 #define fmodf(x, y) fmod((float)(x), (float)(y))
+#define sinhf(x) sinh(((float)(x)))
 
 #ifndef __CL_USE_NATIVE__
 #  define sinf(x) native_sin(((float)(x)))
index a7072c3ad03172aeb9c0cfefe6e77ab3c564a7d8..d71761a97bcacccb1f1553e9975798868d062a00 100644 (file)
@@ -91,7 +91,7 @@ ccl_device_inline bool volume_shader_sample(KernelGlobals *kg,
 
 ccl_device float3 volume_color_transmittance(float3 sigma, float t)
 {
-       return make_float3(expf(-sigma.x * t), expf(-sigma.y * t), expf(-sigma.z * t));
+       return exp3(-sigma * t);
 }
 
 ccl_device float kernel_volume_channel_get(float3 value, int channel)
@@ -234,7 +234,7 @@ ccl_device void kernel_volume_shadow_heterogeneous(KernelGlobals *kg,
 
                        sum += (-sigma_t * (new_t - t));
                        if((i & 0x07) == 0) { /* ToDo: Other interval? */
-                               tp = *throughput * make_float3(expf(sum.x), expf(sum.y), expf(sum.z));
+                               tp = *throughput * exp3(sum);
 
                                /* stop if nearly all light is blocked */
                                if(tp.x < tp_eps && tp.y < tp_eps && tp.z < tp_eps)
@@ -246,7 +246,7 @@ ccl_device void kernel_volume_shadow_heterogeneous(KernelGlobals *kg,
                t = new_t;
                if(t == ray->t) {
                        /* Update throughput in case we haven't done it above */
-                       tp = *throughput * make_float3(expf(sum.x), expf(sum.y), expf(sum.z));
+                       tp = *throughput * exp3(sum);
                        break;
                }
        }
index 581b38e65c09c19491ff13696e23211b1cb417d6..8c7ae30725cd7580d15bd58da03c9569cb1b6744 100644 (file)
@@ -4,7 +4,7 @@
  * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al.
  * All Rights Reserved.
  *
- * Modifications Copyright 2011, Blender Foundation.
+ * Modifications Copyright 2011-2018, Blender Foundation.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -59,6 +59,7 @@
 #include "kernel/closure/bsdf_ashikhmin_shirley.h"
 #include "kernel/closure/bsdf_toon.h"
 #include "kernel/closure/bsdf_hair.h"
+#include "kernel/closure/bsdf_hair_principled.h"
 #include "kernel/closure/bsdf_principled_diffuse.h"
 #include "kernel/closure/bsdf_principled_sheen.h"
 #include "kernel/closure/volume.h"
@@ -176,6 +177,61 @@ BSDF_CLOSURE_CLASS_BEGIN(PrincipledSheen, principled_sheen, PrincipledSheenBsdf,
        CLOSURE_FLOAT3_PARAM(PrincipledSheenClosure, params.N),
 BSDF_CLOSURE_CLASS_END(PrincipledSheen, principled_sheen)
 
+/* PRINCIPLED HAIR BSDF */
+class PrincipledHairClosure : public CBSDFClosure {
+public:
+       PrincipledHairBSDF params;
+
+       PrincipledHairBSDF *alloc(ShaderData *sd, int path_flag, float3 weight)
+       {
+               PrincipledHairBSDF *bsdf = (PrincipledHairBSDF*)bsdf_alloc_osl(sd, sizeof(PrincipledHairBSDF), weight, &params);
+               if(!bsdf) {
+                       return NULL;
+               }
+
+               PrincipledHairExtra *extra = (PrincipledHairExtra*)closure_alloc_extra(sd, sizeof(PrincipledHairExtra));
+               if(!extra) {
+                       return NULL;
+               }
+
+               bsdf->extra = extra;
+               return bsdf;
+       }
+
+       void setup(ShaderData *sd, int path_flag, float3 weight)
+       {
+               if(!skip(sd, path_flag, LABEL_GLOSSY))
+               {
+                       PrincipledHairBSDF *bsdf = (PrincipledHairBSDF*)alloc(sd, path_flag, weight);
+                       if (!bsdf)
+                       {
+                               return;
+                       }
+
+                       sd->flag |= (bsdf) ? bsdf_principled_hair_setup(sd, bsdf) : 0;
+               }
+       }
+};
+
+static ClosureParam *closure_bsdf_principled_hair_params()
+{
+       static ClosureParam params[] = {
+               CLOSURE_FLOAT3_PARAM(PrincipledHairClosure, params.N),
+               CLOSURE_FLOAT3_PARAM(PrincipledHairClosure, params.sigma),
+               CLOSURE_FLOAT_PARAM(PrincipledHairClosure, params.v),
+               CLOSURE_FLOAT_PARAM(PrincipledHairClosure, params.s),
+               CLOSURE_FLOAT_PARAM(PrincipledHairClosure, params.m0_roughness),
+               CLOSURE_FLOAT_PARAM(PrincipledHairClosure, params.alpha),
+               CLOSURE_FLOAT_PARAM(PrincipledHairClosure, params.eta),
+               CLOSURE_STRING_KEYPARAM(PrincipledHairClosure, label, "label"),
+               CLOSURE_FINISH_PARAM(PrincipledHairClosure)
+       };
+
+       return params;
+}
+
+CCLOSURE_PREPARE(closure_bsdf_principled_hair_prepare, PrincipledHairClosure)
+
 /* DISNEY PRINCIPLED CLEARCOAT */
 class PrincipledClearcoatClosure : public CBSDFClosure {
 public:
@@ -322,6 +378,9 @@ void OSLShader::register_closures(OSLShadingSystem *ss_)
        register_closure(ss, "hair_transmission", id++,
                bsdf_hair_transmission_params(), bsdf_hair_transmission_prepare);
 
+       register_closure(ss, "principled_hair", id++,
+               closure_bsdf_principled_hair_params(), closure_bsdf_principled_hair_prepare);
+
        register_closure(ss, "henyey_greenstein", id++,
                closure_henyey_greenstein_params(), closure_henyey_greenstein_prepare);
        register_closure(ss, "absorption", id++,
index 857cc84afd24b722ab82187f9301847f634662a0..d9aeb9ab9fb207b9740d9c55324ca270331bd9c3 100644 (file)
@@ -79,6 +79,7 @@ void closure_bsdf_microfacet_multi_ggx_fresnel_prepare(OSL::RendererServices *,
 void closure_bsdf_microfacet_multi_ggx_glass_fresnel_prepare(OSL::RendererServices *, int id, void *data);
 void closure_bsdf_microfacet_multi_ggx_aniso_fresnel_prepare(OSL::RendererServices *, int id, void *data);
 void closure_bsdf_principled_clearcoat_prepare(OSL::RendererServices *, int id, void *data);
+void closure_bsdf_principled_hair_prepare(OSL::RendererServices *, int id, void *data);
 
 #define CCLOSURE_PREPARE(name, classname)          \
 void name(RendererServices *, int id, void *data) \
index 9ee78d160a4981f3335b78ee20eeacba6dd82b7d..4740db27d4e38fa09e6c0efe230db8506c057235 100644 (file)
@@ -85,6 +85,7 @@ set(SRC_OSL
        node_wave_texture.osl
        node_wireframe.osl
        node_hair_bsdf.osl
+       node_principled_hair_bsdf.osl
        node_uv_map.osl
        node_principled_bsdf.osl
        node_rgb_to_bw.osl
diff --git a/intern/cycles/kernel/shaders/node_principled_hair_bsdf.osl b/intern/cycles/kernel/shaders/node_principled_hair_bsdf.osl
new file mode 100644 (file)
index 0000000..757a88f
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdosl.h"
+
+color log3(color a)
+{
+       return color(log(a[0]), log(a[1]), log(a[2]));
+}
+
+color sigma_from_concentration(float eumelanin, float pheomelanin)
+{
+       return eumelanin*color(0.506, 0.841, 1.653) + pheomelanin*color(0.343, 0.733, 1.924);
+}
+
+color sigma_from_reflectance(color c, float azimuthal_roughness)
+{
+       float x = azimuthal_roughness;
+       float roughness_fac = (((((0.245*x) + 5.574)*x - 10.73)*x + 2.532)*x - 0.215)*x + 5.969;
+       color sigma = log3(c) / roughness_fac;
+       return sigma * sigma;
+}
+
+shader node_principled_hair_bsdf(
+       color Color = color(0.017513, 0.005763, 0.002059),
+       float Melanin = 0.8,
+       float MelaninRedness = 1.0,
+       float RandomColor = 0.0,
+       color Tint = 1.0,
+       color AbsorptionCoefficient = color(0.245531, 0.52, 1.365),
+       normal Normal = Ng,
+       string parametrization = "Absorption coefficient",
+       float Offset = radians(2),
+       float Roughness = 0.3,
+       float RadialRoughness = 0.3,
+       float RandomRoughness = 0.0,
+       float Coat = 0.0,
+       float IOR = 1.55,
+       string AttrRandom = "geom:curve_random",
+       float Random = 0.0,
+
+       output closure color BSDF = 0)
+{
+       /* Get random value from curve in none is specified. */
+       float random_value = 0.0;
+
+       if (isconnected(Random)) {
+               random_value = Random;
+       }
+       else {
+               getattribute(AttrRandom, random_value);
+       }
+
+       /* Compute roughness. */
+       float factor_random_roughness = 1.0 + 2.0*(random_value - 0.5)*RandomRoughness;
+       float m0_roughness = 1.0 - clamp(Coat, 0.0, 1.0);
+       float roughness = Roughness*factor_random_roughness;
+       float radial_roughness = RadialRoughness*factor_random_roughness;
+
+       /* Compute absorption. */
+       color sigma;
+
+       if (parametrization == "Absorption coefficient") {
+               sigma = AbsorptionCoefficient;
+       }
+       else if (parametrization == "Melanin concentration") {
+               /* Randomize melanin. */
+               float factor_random_color = 1.0 + 2.0*(random_value - 0.5) * RandomColor;
+               float melanin = Melanin * factor_random_color;
+
+               /* Map melanin 0..inf from more perceptually linear 0..1. */
+               melanin = -log(max(1.0 - melanin, 0.0001));
+
+               /* Benedikt Bitterli's melanin ratio remapping. */
+               float eumelanin = melanin * (1.0 - MelaninRedness);
+               float pheomelanin = melanin * MelaninRedness;
+               color melanin_sigma = sigma_from_concentration(eumelanin, pheomelanin);
+
+               /* Optional tint. */
+               color tint_sigma = sigma_from_reflectance(Tint, radial_roughness);
+               sigma = melanin_sigma + tint_sigma;
+       }
+       else if (parametrization == "Direct coloring"){
+               sigma = sigma_from_reflectance(Color, radial_roughness);
+       }
+       else {
+               /* Fallback to brownish hair, same as defaults for melanin. */
+               sigma = sigma_from_concentration(0.0, 0.8054375);
+       }
+
+       BSDF = principled_hair(Normal, sigma, roughness, radial_roughness, m0_roughness, Offset, IOR);
+}
index 82223ca02191c56f75ef4422c86b63606cea5a00..df9c2010872610fed1a88ee2a29fed1db2aa08b5 100644 (file)
@@ -554,6 +554,7 @@ closure color bssrdf(string method, normal N, vector radius, color albedo) BUILT
 // Hair
 closure color hair_reflection(normal N, float roughnessu, float roughnessv, vector T, float offset) BUILTIN;
 closure color hair_transmission(normal N, float roughnessu, float roughnessv, vector T, float offset) BUILTIN;
+closure color principled_hair(normal N, color sigma, float roughnessu, float roughnessv, float coat, float alpha, float eta) BUILTIN;
 
 // Volume
 closure color henyey_greenstein(float g) BUILTIN;
index 4de9cfb88db8bfe0ea6c2cac61d5625dffea4a62..aa253223c930b7c332bd0b27e1ab11d5b0cb2145 100644 (file)
 
 CCL_NAMESPACE_BEGIN
 
+/* Hair Melanin */
+
+ccl_device_inline float3 sigma_from_concentration(float eumelanin, float pheomelanin)
+{
+       return eumelanin*make_float3(0.506f, 0.841f, 1.653f) + pheomelanin*make_float3(0.343f, 0.733f, 1.924f);
+}
+
+ccl_device_inline float3 sigma_from_reflectance(float3 color, float azimuthal_roughness)
+{
+       float x = azimuthal_roughness;
+       float roughness_fac = (((((0.245f*x) + 5.574f)*x - 10.73f)*x + 2.532f)*x - 0.215f)*x + 5.969f;
+       float3 sigma = log3(color) / roughness_fac;
+       return sigma * sigma;
+}
+
 /* Closure Nodes */
 
 ccl_device void svm_node_glass_setup(ShaderData *sd, MicrofacetBsdf *bsdf, int type, float eta, float roughness, bool refract)
@@ -243,7 +258,7 @@ ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *
                                        float3 spec_weight = weight * specular_weight;
 
                                        MicrofacetBsdf *bsdf = (MicrofacetBsdf*)bsdf_alloc(sd, sizeof(MicrofacetBsdf), spec_weight);
-                                       if(!bsdf){
+                                       if(!bsdf) {
                                                break;
                                        }
 
@@ -722,6 +737,107 @@ ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *
                        break;
                }
 #ifdef __HAIR__
+               case CLOSURE_BSDF_HAIR_PRINCIPLED_ID: {
+                       uint4 data_node2 = read_node(kg, offset);
+                       uint4 data_node3 = read_node(kg, offset);
+                       uint4 data_node4 = read_node(kg, offset);
+
+                       float3 weight = sd->svm_closure_weight * mix_weight;
+
+                       uint offset_ofs, ior_ofs, color_ofs, parametrization;
+                       decode_node_uchar4(data_node.y, &offset_ofs, &ior_ofs, &color_ofs, &parametrization);
+                       float alpha = stack_load_float_default(stack, offset_ofs, data_node.z);
+                       float ior = stack_load_float_default(stack, ior_ofs, data_node.w);
+
+                       uint coat_ofs, melanin_ofs, melanin_redness_ofs, absorption_coefficient_ofs;
+                       decode_node_uchar4(data_node2.x, &coat_ofs, &melanin_ofs, &melanin_redness_ofs, &absorption_coefficient_ofs);
+
+                       uint tint_ofs, random_ofs, random_color_ofs, random_roughness_ofs;
+                       decode_node_uchar4(data_node3.x, &tint_ofs, &random_ofs, &random_color_ofs, &random_roughness_ofs);
+
+                       const AttributeDescriptor attr_descr_random = find_attribute(kg, sd, data_node4.y);
+                       float random = 0.0f;
+                       if (attr_descr_random.offset != ATTR_STD_NOT_FOUND) {
+                               random = primitive_attribute_float(kg, sd, attr_descr_random, NULL, NULL);
+                       }
+                       else {
+                               random = stack_load_float_default(stack, random_ofs, data_node3.y);
+                       }
+
+
+                       PrincipledHairBSDF *bsdf = (PrincipledHairBSDF*)bsdf_alloc(sd, sizeof(PrincipledHairBSDF), weight);
+                       if(bsdf) {
+                               PrincipledHairExtra *extra = (PrincipledHairExtra*)closure_alloc_extra(sd, sizeof(PrincipledHairExtra));
+
+                               if (!extra)
+                                       break;
+
+                               /* Random factors range: [-randomization/2, +randomization/2]. */
+                               float random_roughness = stack_load_float_default(stack, random_roughness_ofs, data_node3.w);
+                               float factor_random_roughness = 1.0f + 2.0f*(random - 0.5f)*random_roughness;
+                               float roughness = param1 * factor_random_roughness;
+                               float radial_roughness = param2 * factor_random_roughness;
+
+                               /* Remap Coat value to [0, 100]% of Roughness. */
+                               float coat = stack_load_float_default(stack, coat_ofs, data_node2.y);
+                               float m0_roughness = 1.0f - clamp(coat, 0.0f, 1.0f);
+
+                               bsdf->N = N;
+                               bsdf->v = roughness;
+                               bsdf->s = radial_roughness;
+                               bsdf->m0_roughness = m0_roughness;
+                               bsdf->alpha = alpha;
+                               bsdf->eta = ior;
+                               bsdf->extra = extra;
+
+                               switch(parametrization) {
+                                       case NODE_PRINCIPLED_HAIR_DIRECT_ABSORPTION: {
+                                               float3 absorption_coefficient = stack_load_float3(stack, absorption_coefficient_ofs);
+                                               bsdf->sigma = absorption_coefficient;
+                                               break;
+                                       }
+                                       case NODE_PRINCIPLED_HAIR_PIGMENT_CONCENTRATION: {
+                                               float melanin = stack_load_float_default(stack, melanin_ofs, data_node2.z);
+                                               float melanin_redness = stack_load_float_default(stack, melanin_redness_ofs, data_node2.w);
+
+                                               /* Randomize melanin.  */
+                                               float random_color = stack_load_float_default(stack, random_color_ofs, data_node3.z);
+                                               random_color = clamp(random_color, 0.0f, 1.0f);
+                                               float factor_random_color = 1.0f + 2.0f * (random - 0.5f) * random_color;
+                                               melanin *= factor_random_color;
+
+                                               /* Map melanin 0..inf from more perceptually linear 0..1. */
+                                               melanin = -logf(fmaxf(1.0f - melanin, 0.0001f));
+
+                                               /* Benedikt Bitterli's melanin ratio remapping. */
+                                               float eumelanin = melanin * (1.0f - melanin_redness);
+                                               float pheomelanin = melanin * melanin_redness;
+                                               float3 melanin_sigma = sigma_from_concentration(eumelanin, pheomelanin);
+
+                                               /* Optional tint. */
+                                               float3 tint = stack_load_float3(stack, tint_ofs);
+                                               float3 tint_sigma = sigma_from_reflectance(tint, radial_roughness);
+
+                                               bsdf->sigma = melanin_sigma + tint_sigma;
+                                               break;
+                                       }
+                                       case NODE_PRINCIPLED_HAIR_REFLECTANCE: {
+                                               float3 color = stack_load_float3(stack, color_ofs);
+                                               bsdf->sigma = sigma_from_reflectance(color, radial_roughness);
+                                               break;
+                                       }
+                                       default: {
+                                               /* Fallback to brownish hair, same as defaults for melanin. */
+                                               kernel_assert(!"Invalid Principled Hair parametrization!");
+                                               bsdf->sigma = sigma_from_concentration(0.0f, 0.8054375f);
+                                               break;
+                                       }
+                               }
+
+                               sd->flag |= bsdf_principled_hair_setup(sd, bsdf);
+                       }
+                       break;
+               }
                case CLOSURE_BSDF_HAIR_REFLECTION_ID:
                case CLOSURE_BSDF_HAIR_TRANSMISSION_ID: {
                        float3 weight = sd->svm_closure_weight * mix_weight;
@@ -764,7 +880,7 @@ ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *
 
                        break;
                }
-#endif
+#endif  /* __HAIR__ */
 
 #ifdef __SUBSURFACE__
                case CLOSURE_BSSRDF_CUBIC_ID:
index e03ad3a0cfee4c2530d049002458e7f3ac6bc545..910537a2539fc90675f82388ba3b386336a63c8a 100644 (file)
@@ -418,6 +418,13 @@ typedef enum ShaderType {
        SHADER_TYPE_BUMP,
 } ShaderType;
 
+typedef enum NodePrincipledHairParametrization {
+       NODE_PRINCIPLED_HAIR_REFLECTANCE = 0,
+       NODE_PRINCIPLED_HAIR_PIGMENT_CONCENTRATION = 1,
+       NODE_PRINCIPLED_HAIR_DIRECT_ABSORPTION = 2,
+       NODE_PRINCIPLED_HAIR_NUM,
+} NodePrincipledHairParametrization;
+
 /* Closure */
 
 typedef enum ClosureType {
@@ -464,6 +471,7 @@ typedef enum ClosureType {
        CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID,
        CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_FRESNEL_ID,
        CLOSURE_BSDF_SHARP_GLASS_ID,
+       CLOSURE_BSDF_HAIR_PRINCIPLED_ID,
        CLOSURE_BSDF_HAIR_TRANSMISSION_ID,
 
        /* Special cases */
@@ -495,7 +503,7 @@ typedef enum ClosureType {
 /* watch this, being lazy with memory usage */
 #define CLOSURE_IS_BSDF(type) (type <= CLOSURE_BSDF_TRANSPARENT_ID)
 #define CLOSURE_IS_BSDF_DIFFUSE(type) (type >= CLOSURE_BSDF_DIFFUSE_ID && type <= CLOSURE_BSDF_DIFFUSE_TOON_ID)
-#define CLOSURE_IS_BSDF_GLOSSY(type) (type >= CLOSURE_BSDF_REFLECTION_ID && type <= CLOSURE_BSDF_HAIR_REFLECTION_ID)
+#define CLOSURE_IS_BSDF_GLOSSY(type) ((type >= CLOSURE_BSDF_REFLECTION_ID && type <= CLOSURE_BSDF_HAIR_REFLECTION_ID )|| (type == CLOSURE_BSDF_HAIR_PRINCIPLED_ID))
 #define CLOSURE_IS_BSDF_TRANSMISSION(type) (type >= CLOSURE_BSDF_TRANSLUCENT_ID && type <= CLOSURE_BSDF_HAIR_TRANSMISSION_ID)
 #define CLOSURE_IS_BSDF_BSSRDF(type) (type == CLOSURE_BSDF_BSSRDF_ID || type == CLOSURE_BSDF_BSSRDF_PRINCIPLED_ID)
 #define CLOSURE_IS_BSDF_SINGULAR(type) (type == CLOSURE_BSDF_REFLECTION_ID || \
index 78a2039c4234acb9469e0caa96663226e54d4c90..3a9e29814180e3e6a753e3183efce5e8a3f2d419 100644 (file)
@@ -1091,6 +1091,9 @@ int ShaderGraph::get_num_closures()
                else if(CLOSURE_IS_VOLUME(closure_type)) {
                        num_closures += VOLUME_STACK_SIZE;
                }
+               else if(closure_type == CLOSURE_BSDF_HAIR_PRINCIPLED_ID) {
+                       num_closures += 4;
+               }
                else {
                        ++num_closures;
                }
index 986004433e4cd46fa958e2ba4e9caad883e63508..96e7459a48c712a84cdee956709aace8529a051a 100644 (file)
@@ -3092,6 +3092,139 @@ void PrincipledVolumeNode::compile(OSLCompiler& compiler)
        compiler.add(this, "node_principled_volume");
 }
 
+/* Principled Hair BSDF Closure */
+
+NODE_DEFINE(PrincipledHairBsdfNode)
+{
+       NodeType* type = NodeType::add("principled_hair_bsdf", create, NodeType::SHADER);
+
+       /* Color parametrization specified as enum. */
+       static NodeEnum parametrization_enum;
+       parametrization_enum.insert("Direct coloring", NODE_PRINCIPLED_HAIR_REFLECTANCE);
+       parametrization_enum.insert("Melanin concentration", NODE_PRINCIPLED_HAIR_PIGMENT_CONCENTRATION);
+       parametrization_enum.insert("Absorption coefficient", NODE_PRINCIPLED_HAIR_DIRECT_ABSORPTION);
+       SOCKET_ENUM(parametrization, "Parametrization", parametrization_enum, NODE_PRINCIPLED_HAIR_REFLECTANCE);
+
+       /* Initialize sockets to their default values. */
+       SOCKET_IN_COLOR(color, "Color", make_float3(0.017513f, 0.005763f, 0.002059f));
+       SOCKET_IN_FLOAT(melanin, "Melanin", 0.8f);
+       SOCKET_IN_FLOAT(melanin_redness, "Melanin Redness", 1.0f);
+       SOCKET_IN_COLOR(tint, "Tint", make_float3(1.f, 1.f, 1.f));
+       SOCKET_IN_VECTOR(absorption_coefficient, "Absorption Coefficient", make_float3(0.245531f, 0.52f, 1.365f), SocketType::VECTOR);
+
+       SOCKET_IN_FLOAT(offset, "Offset", 2.f*M_PI_F/180.f);
+       SOCKET_IN_FLOAT(roughness, "Roughness", 0.3f);
+       SOCKET_IN_FLOAT(radial_roughness, "Radial Roughness", 0.3f);
+       SOCKET_IN_FLOAT(coat, "Coat", 0.0f);
+       SOCKET_IN_FLOAT(ior, "IOR", 1.55f);
+
+       SOCKET_IN_FLOAT(random_roughness, "Random Roughness", 0.0f);
+       SOCKET_IN_FLOAT(random_color, "Random Color", 0.0f);
+       SOCKET_IN_FLOAT(random, "Random", 0.0f);
+
+       SOCKET_IN_NORMAL(normal, "Normal", make_float3(0.0f, 0.0f, 0.0f), SocketType::LINK_NORMAL);
+       SOCKET_IN_FLOAT(surface_mix_weight, "SurfaceMixWeight", 0.0f, SocketType::SVM_INTERNAL);
+
+       SOCKET_OUT_CLOSURE(BSDF, "BSDF");
+
+       return type;
+}
+
+PrincipledHairBsdfNode::PrincipledHairBsdfNode()
+: BsdfBaseNode(node_type)
+{
+       closure = CLOSURE_BSDF_HAIR_PRINCIPLED_ID;
+}
+
+/* Enable retrieving Hair Info -> Random if Random isn't linked. */
+void PrincipledHairBsdfNode::attributes(Shader *shader, AttributeRequestSet *attributes)
+{
+       if(!input("Random")->link) {
+               attributes->add(ATTR_STD_CURVE_RANDOM);
+       }
+       ShaderNode::attributes(shader, attributes);
+}
+
+/* Prepares the input data for the SVM shader. */
+void PrincipledHairBsdfNode::compile(SVMCompiler& compiler)
+{
+       compiler.add_node(NODE_CLOSURE_SET_WEIGHT, make_float3(1.0f, 1.0f, 1.0f));
+
+       ShaderInput *roughness_in = input("Roughness");
+       ShaderInput *radial_roughness_in = input("Radial Roughness");
+       ShaderInput *random_roughness_in = input("Random Roughness");
+       ShaderInput *offset_in = input("Offset");
+       ShaderInput *coat_in = input("Coat");
+       ShaderInput *ior_in = input("IOR");
+       ShaderInput *melanin_in =  input("Melanin");
+       ShaderInput *melanin_redness_in = input("Melanin Redness");
+       ShaderInput *random_color_in = input("Random Color");
+
+       int color_ofs = compiler.stack_assign(input("Color"));
+       int tint_ofs = compiler.stack_assign(input("Tint"));
+       int absorption_coefficient_ofs = compiler.stack_assign(input("Absorption Coefficient"));
+
+       ShaderInput *random_in = input("Random");
+       int attr_random = random_in->link ? SVM_STACK_INVALID : compiler.attribute(ATTR_STD_CURVE_RANDOM);
+
+       /* Encode all parameters into data nodes. */
+       compiler.add_node(NODE_CLOSURE_BSDF,
+               /* Socket IDs can be packed 4 at a time into a single data packet */
+               compiler.encode_uchar4(closure,
+                       compiler.stack_assign_if_linked(roughness_in),
+                       compiler.stack_assign_if_linked(radial_roughness_in),
+                       compiler.closure_mix_weight_offset()),
+               /* The rest are stored as unsigned integers */
+               __float_as_uint(roughness),
+               __float_as_uint(radial_roughness));
+
+       compiler.add_node(compiler.stack_assign_if_linked(input("Normal")),
+               compiler.encode_uchar4(
+                       compiler.stack_assign_if_linked(offset_in),
+                       compiler.stack_assign_if_linked(ior_in),
+                       color_ofs,
+                       parametrization),
+               __float_as_uint(offset),
+               __float_as_uint(ior));
+
+       compiler.add_node(
+               compiler.encode_uchar4(
+                       compiler.stack_assign_if_linked(coat_in),
+                       compiler.stack_assign_if_linked(melanin_in),
+                       compiler.stack_assign_if_linked(melanin_redness_in),
+                       absorption_coefficient_ofs),
+               __float_as_uint(coat),
+               __float_as_uint(melanin),
+               __float_as_uint(melanin_redness));
+
+       compiler.add_node(
+               compiler.encode_uchar4(
+                       tint_ofs,
+                       compiler.stack_assign_if_linked(random_in),
+                       compiler.stack_assign_if_linked(random_color_in),
+                       compiler.stack_assign_if_linked(random_roughness_in)),
+               __float_as_uint(random),
+               __float_as_uint(random_color),
+               __float_as_uint(random_roughness));
+
+       compiler.add_node(
+               compiler.encode_uchar4(
+                       SVM_STACK_INVALID,
+                       SVM_STACK_INVALID,
+                       SVM_STACK_INVALID,
+                       SVM_STACK_INVALID),
+               attr_random,
+               SVM_STACK_INVALID,
+               SVM_STACK_INVALID);
+}
+
+/* Prepares the input data for the OSL shader. */
+void PrincipledHairBsdfNode::compile(OSLCompiler& compiler)
+{
+       compiler.parameter(this, "parametrization");
+       compiler.add(this, "node_principled_hair_bsdf");
+}
+
 /* Hair BSDF Closure */
 
 NODE_DEFINE(HairBsdfNode)
index ebe6db6e3629d295dd782be6028a3729830e5470..28bbe2de05ae998ccf3690a1fafa1a8312ef3831 100644 (file)
@@ -608,6 +608,45 @@ public:
        float temperature;
 };
 
+/* Interface between the I/O sockets and the SVM/OSL backend. */
+class PrincipledHairBsdfNode : public BsdfBaseNode {
+public:
+       SHADER_NODE_CLASS(PrincipledHairBsdfNode)
+       void attributes(Shader *shader, AttributeRequestSet *attributes);
+
+       /* Longitudinal roughness. */
+       float roughness;
+       /* Azimuthal roughness. */
+       float radial_roughness;
+       /* Randomization factor for roughnesses. */
+       float random_roughness;
+       /* Longitudinal roughness factor for only the diffuse bounce (shiny undercoat). */
+       float coat;
+       /* Index of reflection. */
+       float ior;
+       /* Cuticle tilt angle. */
+       float offset;
+       /* Direct coloring's color. */
+       float3 color;
+       /* Melanin concentration. */
+       float melanin;
+       /* Melanin redness ratio. */
+       float melanin_redness;
+       /* Dye color. */
+       float3 tint;
+       /* Randomization factor for melanin quantities. */
+       float random_color;
+       /* Absorption coefficient (unfiltered). */
+       float3 absorption_coefficient;
+
+       float3 normal;
+       float surface_mix_weight;
+       /* If linked, here will be the given random number. */
+       float random;
+       /* Selected coloring parametrization. */
+       NodePrincipledHairParametrization parametrization;
+};
+
 class HairBsdfNode : public BsdfNode {
 public:
        SHADER_NODE_CLASS(HairBsdfNode)
index 85cbd18b7ba5f65fef886a53e68b28a834f7f4d5..52aeb8d85995f5eee438d1812413f6a25f1d4d9f 100644 (file)
@@ -55,6 +55,15 @@ CCL_NAMESPACE_BEGIN
 #ifndef M_2_PI_F
 #  define M_2_PI_F  (0.6366197723675813f)  /* 2/pi */
 #endif
+#ifndef M_1_2PI_F
+#  define M_1_2PI_F (0.1591549430918953f)  /* 1/(2*pi) */
+#endif
+#ifndef M_SQRT_PI_8_F
+#  define M_SQRT_PI_8_F (0.6266570686577501f) /* sqrt(pi/8) */
+#endif
+#ifndef M_LN_2PI_F
+#  define M_LN_2PI_F (1.8378770664093454f) /* ln(2*pi) */
+#endif
 
 /* Multiplication */
 #ifndef M_2PI_F
@@ -541,6 +550,16 @@ ccl_device_inline float sqr(float a)
        return a * a;
 }
 
+ccl_device_inline float pow20(float a)
+{
+    return sqr(sqr(sqr(sqr(a))*a));
+}
+
+ccl_device_inline float pow22(float a)
+{
+    return sqr(a*sqr(sqr(sqr(a))*a));
+}
+
 ccl_device_inline float beta(float x, float y)
 {
 #ifndef __KERNEL_OPENCL__
index 3a5a2ab22442e79a2dda282f120ac2ec46022359..ba1c117cdea00c4a19b64b0f6f9569006ef70769 100644 (file)
@@ -382,6 +382,16 @@ ccl_device_inline bool isequal_float3(const float3 a, const float3 b)
 #endif
 }
 
+ccl_device_inline float3 exp3(float3 v)
+{
+       return make_float3(expf(v.x), expf(v.y), expf(v.z));
+}
+
+ccl_device_inline float3 log3(float3 v)
+{
+       return make_float3(logf(v.x), logf(v.y), logf(v.z));
+}
+
 ccl_device_inline int3 quick_floor_to_int3(const float3 a)
 {
 #ifdef __KERNEL_SSE__
index c9a15f12f7c4f565226703b182a141f7710d5898..4f4190da30b9815decbea18bf9a02c55b1b8509b 100644 (file)
@@ -246,6 +246,7 @@ shader_node_categories = [
         NodeItem("ShaderNodeVolumeAbsorption"),
         NodeItem("ShaderNodeVolumeScatter"),
         NodeItem("ShaderNodeVolumePrincipled"),
+        NodeItem("ShaderNodeBsdfHairPrincipled", poll=object_shader_nodes_poll)
     ]),
     ShaderNewNodeCategory("SH_NEW_TEXTURE", "Texture", items=[
         NodeItem("ShaderNodeTexImage"),
index 8e54c6a87c4286e6d37d18367ac73f42e5ebb1d8..23ef45416f7ea0a0be5ecdf55f1599c2a748badc 100644 (file)
@@ -794,6 +794,8 @@ struct ShadeResult;
 #define SH_NODE_DISPLACEMENT            198
 #define SH_NODE_VECTOR_DISPLACEMENT     199
 #define SH_NODE_VOLUME_PRINCIPLED       200
+/* 201..700 occupied by other node types, continue from 701 */
+#define SH_NODE_BSDF_HAIR_PRINCIPLED    701
 
 /* custom defines options for Material node */
 #define SH_NODE_MAT_DIFF   1
index f15d90100d2b0503c72ccf93b320f31a169b61e0..c830917a547d49175a52efb5e55aa6fa5c817c48 100644 (file)
@@ -3622,6 +3622,7 @@ static void registerShaderNodes(void)
        register_node_type_sh_bsdf_velvet();
        register_node_type_sh_bsdf_toon();
        register_node_type_sh_bsdf_hair();
+       register_node_type_sh_bsdf_hair_principled();
        register_node_type_sh_emission();
        register_node_type_sh_holdout();
        register_node_type_sh_volume_absorption();
index 23df1b72c37e0d67b24d860de360d7bb25bc5110..6e52af2898ebedc638861b6dfd2c2f24f0209186 100644 (file)
@@ -1111,6 +1111,11 @@ static void node_shader_buts_hair(uiLayout *layout, bContext *UNUSED(C), Pointer
        uiItemR(layout, ptr, "component", 0, "", ICON_NONE);
 }
 
+static void node_shader_buts_principled_hair(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+       uiItemR(layout, ptr, "parametrization", 0, "", ICON_NONE);
+}
+
 static void node_shader_buts_ies(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
 {
        uiLayout *row;
@@ -1301,6 +1306,9 @@ static void node_shader_set_butfunc(bNodeType *ntype)
                case SH_NODE_BSDF_HAIR:
                        ntype->draw_buttons = node_shader_buts_hair;
                        break;
+               case SH_NODE_BSDF_HAIR_PRINCIPLED:
+                       ntype->draw_buttons =   node_shader_buts_principled_hair;
+                       break;
                case SH_NODE_SCRIPT:
                        ntype->draw_buttons = node_shader_buts_script;
                        ntype->draw_buttons_ex = node_shader_buts_script_ex;
index 00758bd1379eb833de0603ff0aaa6e01a5b285be..5c692cf85da2e9dfff7e3c8526770be5a8f0f4d1 100644 (file)
@@ -963,6 +963,11 @@ typedef struct NodeCryptomatte {
 #define SHD_HAIR_REFLECTION            0
 #define SHD_HAIR_TRANSMISSION          1
 
+/* principled hair parametrization */
+#define SHD_PRINCIPLED_HAIR_REFLECTANCE                                0
+#define SHD_PRINCIPLED_HAIR_PIGMENT_CONCENTRATION      1
+#define SHD_PRINCIPLED_HAIR_DIRECT_ABSORPTION          2
+
 /* blend texture */
 #define SHD_BLEND_LINEAR                       0
 #define SHD_BLEND_QUADRATIC                    1
index 52fbadd0f54f67c9313a1ac4a346bad9d3c702b6..b41e9b1c4e57fef97f7460c73f6b891b94162f51 100644 (file)
@@ -3350,6 +3350,13 @@ static const EnumPropertyItem node_hair_items[] = {
        {0, NULL, 0, NULL, NULL}
 };
 
+static const EnumPropertyItem node_principled_hair_items[] = {
+       {SHD_PRINCIPLED_HAIR_DIRECT_ABSORPTION,     "ABSORPTION", 0, "Absorption coefficient",   "Directly set the absorption coefficient sigma_a. This is not the most intuitive way to color hair."},
+       {SHD_PRINCIPLED_HAIR_PIGMENT_CONCENTRATION, "MELANIN",    0, "Melanin concentration",    "Define the melanin concentrations below to get the most realistic-looking hair. You can get the concentrations for different types of hair online."},
+       {SHD_PRINCIPLED_HAIR_REFLECTANCE,           "COLOR",      0, "Direct coloring",          "Choose the color of your preference, and the shader will approximate the absorption coefficient to render lookalike hair."},
+       {0, NULL, 0, NULL, NULL}
+};
+
 static const EnumPropertyItem node_script_mode_items[] = {
        {NODE_SCRIPT_INTERNAL, "INTERNAL", 0, "Internal", "Use internal text data-block"},
        {NODE_SCRIPT_EXTERNAL, "EXTERNAL", 0, "External", "Use external .osl or .oso file"},
@@ -4409,6 +4416,21 @@ static void def_hair(StructRNA *srna)
        RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
 }
 
+/* RNA initialization for the custom property. */
+static void def_hair_principled(StructRNA *srna)
+{
+       PropertyRNA *prop;
+
+       prop = RNA_def_property(srna, "parametrization", PROP_ENUM, PROP_NONE);
+       RNA_def_property_enum_sdna(prop, NULL, "custom1");
+       RNA_def_property_ui_text(prop, "Color parametrization", "Select the shader's color parametrization");
+       RNA_def_property_enum_items(prop, node_principled_hair_items);
+       RNA_def_property_enum_default(prop, SHD_PRINCIPLED_HAIR_REFLECTANCE);
+       /* Upon editing, update both the node data AND the UI representation */
+       /* (This effectively shows/hides the relevant sockets) */
+       RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNode_socket_update");
+}
+
 static void def_sh_uvmap(StructRNA *srna)
 {
        PropertyRNA *prop;
index 5a5d599787883680e6086370bd2921afa5d7006b..7ef41b25de89378ff3e6e25460da51679e6e8768 100644 (file)
@@ -173,6 +173,7 @@ set(SRC
        shader/nodes/node_shader_bsdf_transparent.c
        shader/nodes/node_shader_bsdf_velvet.c
        shader/nodes/node_shader_bsdf_hair.c
+       shader/nodes/node_shader_bsdf_hair_principled.c
        shader/nodes/node_shader_bump.c
        shader/nodes/node_shader_emission.c
        shader/nodes/node_shader_fresnel.c
index ca604363e357c179dcf0bf868f265c4f4214b4ba..d4134f09597fe737683268d5cd5e2d5fba2e3337 100644 (file)
@@ -116,6 +116,7 @@ void register_node_type_sh_volume_absorption(void);
 void register_node_type_sh_volume_scatter(void);
 void register_node_type_sh_volume_principled(void);
 void register_node_type_sh_bsdf_hair(void);
+void register_node_type_sh_bsdf_hair_principled(void);
 void register_node_type_sh_subsurface_scattering(void);
 void register_node_type_sh_mix_shader(void);
 void register_node_type_sh_add_shader(void);
index a4f8a798576b848df31d0179730483b9a466bf91..2fa8cda56a56e6a667557f9fbf03ae13b2cb18ca 100644 (file)
@@ -89,6 +89,7 @@ DefNode( ShaderNode,     SH_NODE_BSDF_TRANSPARENT,   0,                      "BS
 DefNode( ShaderNode,     SH_NODE_BSDF_VELVET,        0,                      "BSDF_VELVET",        BsdfVelvet,       "Velvet BSDF",       ""       )
 DefNode( ShaderNode,     SH_NODE_BSDF_TOON,          def_toon,               "BSDF_TOON",          BsdfToon,         "Toon BSDF",         ""       )
 DefNode( ShaderNode,     SH_NODE_BSDF_HAIR,          def_hair,               "BSDF_HAIR",          BsdfHair,         "Hair BSDF",         ""       )
+DefNode( ShaderNode,     SH_NODE_BSDF_HAIR_PRINCIPLED,  def_hair_principled, "BSDF_HAIR_PRINCIPLED", BsdfHairPrincipled,  "Principled Hair BSDF", "")
 DefNode( ShaderNode,     SH_NODE_SUBSURFACE_SCATTERING, def_sh_subsurface,   "SUBSURFACE_SCATTERING",SubsurfaceScattering,"Subsurface Scattering","")
 DefNode( ShaderNode,     SH_NODE_VOLUME_ABSORPTION,  0,                      "VOLUME_ABSORPTION",  VolumeAbsorption, "Volume Absorption", ""       )
 DefNode( ShaderNode,     SH_NODE_VOLUME_SCATTER,     0,                      "VOLUME_SCATTER",     VolumeScatter,    "Volume Scatter",    ""       )
diff --git a/source/blender/nodes/shader/nodes/node_shader_bsdf_hair_principled.c b/source/blender/nodes/shader/nodes/node_shader_bsdf_hair_principled.c
new file mode 100644 (file)
index 0000000..c502985
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2018 Blender Foundation.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Stockner, L. E. Segovia
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#include "../node_shader_util.h"
+
+/* **************** OUTPUT ******************** */
+
+/* Color, melanin and absorption coefficient default to approximately same brownish hair. */
+static bNodeSocketTemplate sh_node_bsdf_hair_principled_in[] = {
+       {       SOCK_RGBA,              1, N_("Color"),                                         0.017513f, 0.005763f, 0.002059f, 1.0f, 0.0f, 1.0f},
+       {       SOCK_FLOAT,             1, N_("Melanin"),                                       0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+       {       SOCK_FLOAT,             1, N_("Melanin Redness"),                       1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+       {       SOCK_RGBA,              1, N_("Tint"),                                          1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f},
+       {       SOCK_VECTOR,    1, N_("Absorption Coefficient"),        0.245531f, 0.52f, 1.365f, 0.0f, 0.0f, 1000.0f},
+       {       SOCK_FLOAT,             1, N_("Roughness"),                                     0.3f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+       {       SOCK_FLOAT,             1, N_("Radial Roughness"),                      0.3f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+       {       SOCK_FLOAT,             1, N_("Coat"),                                          0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+       {       SOCK_FLOAT,             1, N_("IOR"),                                           1.55f, 0.0f, 0.0f, 0.0f, 0.0f, 1000.0f},
+       {       SOCK_FLOAT,             1, N_("Offset"),                                        2.f*((float)M_PI)/180.f, 0.0f, 0.0f, 0.0f, -M_PI_2, M_PI_2, PROP_ANGLE},
+       {       SOCK_FLOAT,     1, N_("Random Color"),                          0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+       {       SOCK_FLOAT,     1, N_("Random Roughness"),                      0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+       {       SOCK_FLOAT,             1, N_("Random"),                                        0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE},
+       {       -1, 0, ""       },
+};
+
+static bNodeSocketTemplate sh_node_bsdf_hair_principled_out[] = {
+       {       SOCK_SHADER, 0, N_("BSDF")},
+       {       -1, 0, ""       }
+};
+
+/* Initialize the custom Parametrization property to Color. */
+static void node_shader_init_hair_principled(bNodeTree *UNUSED(ntree), bNode *node)
+{
+       node->custom1 = SHD_PRINCIPLED_HAIR_REFLECTANCE;
+}
+
+/* Triggers (in)visibility of some sockets when changing Parametrization. */
+static void node_shader_update_hair_principled(bNodeTree *UNUSED(ntree), bNode *node)
+{
+       bNodeSocket *sock;
+       int parametrization = node->custom1;
+
+       for (sock = node->inputs.first; sock; sock = sock->next) {
+               if (STREQ(sock->name, "Color")) {
+                       if (parametrization == SHD_PRINCIPLED_HAIR_REFLECTANCE) {
+                               sock->flag &= ~SOCK_UNAVAIL;
+                       }
+                       else {
+                               sock->flag |= SOCK_UNAVAIL;
+                       }
+               }
+               else if (STREQ(sock->name, "Melanin")) {
+                       if (parametrization == SHD_PRINCIPLED_HAIR_PIGMENT_CONCENTRATION) {
+                               sock->flag &= ~SOCK_UNAVAIL;
+                       }
+                       else {
+                               sock->flag |= SOCK_UNAVAIL;
+                       }
+               }
+               else if (STREQ(sock->name, "Melanin Redness")) {
+                       if (parametrization == SHD_PRINCIPLED_HAIR_PIGMENT_CONCENTRATION) {
+                               sock->flag &= ~SOCK_UNAVAIL;
+                       }
+                       else {
+                               sock->flag |= SOCK_UNAVAIL;
+                       }
+               }
+               else if (STREQ(sock->name, "Tint")) {
+                       if (parametrization == SHD_PRINCIPLED_HAIR_PIGMENT_CONCENTRATION) {
+                               sock->flag &= ~SOCK_UNAVAIL;
+                       }
+                       else {
+                               sock->flag |= SOCK_UNAVAIL;
+                       }
+               }
+               else if (STREQ(sock->name, "Absorption Coefficient")) {
+                       if (parametrization == SHD_PRINCIPLED_HAIR_DIRECT_ABSORPTION) {
+                               sock->flag &= ~SOCK_UNAVAIL;
+                       }
+                       else {
+                               sock->flag |= SOCK_UNAVAIL;
+                       }
+               }
+               else if (STREQ(sock->name, "Random Color")) {
+                       if (parametrization == SHD_PRINCIPLED_HAIR_PIGMENT_CONCENTRATION) {
+                               sock->flag &= ~SOCK_UNAVAIL;
+                       }
+                       else {
+                               sock->flag |= SOCK_UNAVAIL;
+                       }
+               }
+       }
+}
+
+/* node type definition */
+void register_node_type_sh_bsdf_hair_principled(void)
+{
+       static bNodeType ntype;
+
+       sh_node_type_base(&ntype, SH_NODE_BSDF_HAIR_PRINCIPLED, "Principled Hair BSDF", NODE_CLASS_SHADER, 0);
+       node_type_compatibility(&ntype, NODE_NEW_SHADING);
+       node_type_socket_templates(&ntype, sh_node_bsdf_hair_principled_in, sh_node_bsdf_hair_principled_out);
+       node_type_size_preset(&ntype, NODE_SIZE_LARGE);
+       node_type_init(&ntype, node_shader_init_hair_principled);
+       node_type_storage(&ntype, "", NULL, NULL);
+       node_type_update(&ntype, node_shader_update_hair_principled, NULL);
+
+       nodeRegisterType(&ntype);
+}