@@ -207,6 +207,7 @@ struct MxSheenParams {
207207 Color3 albedo;
208208 float roughness;
209209 // optional
210+ int mode;
210211 ustringhash label;
211212};
212213
@@ -398,6 +399,7 @@ register_closures(OSL::ShadingSystem* shadingsys)
398399 CLOSURE_COLOR_PARAM (MxSheenParams, albedo),
399400 CLOSURE_FLOAT_PARAM (MxSheenParams, roughness),
400401 CLOSURE_STRING_KEYPARAM (MxSheenParams, label, " label" ),
402+ CLOSURE_INT_KEYPARAM (MxSheenParams, mode, " mode" ),
401403 CLOSURE_FINISH_PARAM (MxSheenParams) } },
402404 { " uniform_edf" ,
403405 MX_UNIFORM_EDF_ID,
@@ -589,15 +591,15 @@ struct Phong final : public BSDF, PhongParams {
589591 float cosNO = N.dot (wo);
590592 if (cosNO > 0 ) {
591593 // reflect the view vector
592- Vec3 R = (2 * cosNO) * N - wo;
593- TangentFrame tf (R);
594+ Vec3 R = (2 * cosNO) * N - wo;
594595 float phi = 2 * float (M_PI) * rx;
595596 float sp, cp;
596597 OIIO::fast_sincos (phi, &sp, &cp);
597598 float cosTheta = OIIO::fast_safe_pow (ry, 1 / (exponent + 1 ));
598599 float sinTheta2 = 1 - cosTheta * cosTheta;
599600 float sinTheta = sinTheta2 > 0 ? sqrtf (sinTheta2) : 0 ;
600- Vec3 wi = tf.get (cp * sinTheta, sp * sinTheta, cosTheta);
601+ Vec3 wi = TangentFrame::from_normal (R).get (cp * sinTheta,
602+ sp * sinTheta, cosTheta);
601603 return eval (wo, wi);
602604 }
603605 return {};
@@ -614,7 +616,7 @@ struct Ward final : public BSDF, WardParams {
614616 // get half vector and get x,y basis on the surface for anisotropy
615617 Vec3 H = wi + wo;
616618 H.normalize (); // normalize needed for pdf
617- TangentFrame tf (N, T);
619+ TangentFrame tf = TangentFrame::from_normal_and_tangent (N, T);
618620 // eq. 4
619621 float dotx = tf.getx (H) / ax;
620622 float doty = tf.gety (H) / ay;
@@ -636,7 +638,7 @@ struct Ward final : public BSDF, WardParams {
636638 float cosNO = N.dot (wo);
637639 if (cosNO > 0 ) {
638640 // get x,y basis on the surface for anisotropy
639- TangentFrame tf (N, T);
641+ TangentFrame tf = TangentFrame::from_normal_and_tangent (N, T);
640642 // generate random angles for the half vector
641643 float phi = 2 * float (M_PI) * rx;
642644 float sp, cp;
@@ -809,8 +811,7 @@ struct Microfacet final : public BSDF, MicrofacetParams {
809811 Microfacet (const MicrofacetParams& params)
810812 : BSDF()
811813 , MicrofacetParams(params)
812- , tf(U == Vec3(0 ) || xalpha == yalpha ? TangentFrame(N)
813- : TangentFrame(N, U))
814+ , tf(TangentFrame::from_normal_and_tangent(N, U))
814815 {
815816 }
816817 Color3 get_albedo (const Vec3& wo) const override
@@ -1034,11 +1035,8 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams {
10341035 MxMicrofacet (const MxMicrofacetParams& params, float refraction_ior)
10351036 : BSDF()
10361037 , MxMicrofacetParams(params)
1037- , tf(MxMicrofacetParams::U == Vec3(0 )
1038- || MxMicrofacetParams::roughness_x
1039- == MxMicrofacetParams::roughness_y
1040- ? TangentFrame(MxMicrofacetParams::N)
1041- : TangentFrame(MxMicrofacetParams::N, MxMicrofacetParams::U))
1038+ , tf(TangentFrame::from_normal_and_tangent(MxMicrofacetParams::N,
1039+ MxMicrofacetParams::U))
10421040 , refraction_ior(refraction_ior)
10431041 {
10441042 }
@@ -1398,8 +1396,12 @@ struct MxBurleyDiffuse final : public BSDF, MxBurleyDiffuseParams {
13981396 }
13991397};
14001398
1401- struct MxSheen final : public BSDF, MxSheenParams {
1402- MxSheen (const MxSheenParams& params) : BSDF(), MxSheenParams(params) {}
1399+ // Implementation of the "Charlie Sheen" model [Conty & Kulla, 2017]
1400+ // https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_sheen.pdf
1401+ // To simplify the implementation, the simpler shadowing/masking visibility term below is used:
1402+ // https://dassaultsystemes-technology.github.io/EnterprisePBRShadingModel/spec-2022x.md.html#components/sheen
1403+ struct CharlieSheen final : public BSDF, MxSheenParams {
1404+ CharlieSheen (const MxSheenParams& params) : BSDF(), MxSheenParams(params) {}
14031405
14041406 Color3 get_albedo (const Vec3& wo) const override
14051407 {
@@ -1444,6 +1446,114 @@ struct MxSheen final : public BSDF, MxSheenParams {
14441446 }
14451447};
14461448
1449+ // Implement the sheen model proposed in:
1450+ // "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines"
1451+ // Tizian Zeltner, Brent Burley, Matt Jen-Yuan Chiang - Siggraph 2022
1452+ // https://tizianzeltner.com/projects/Zeltner2022Practical/
1453+ struct ZeltnerBurleySheen final : public BSDF, MxSheenParams {
1454+ ZeltnerBurleySheen (const MxSheenParams& params)
1455+ : BSDF(), MxSheenParams(params)
1456+ {
1457+ }
1458+
1459+ #define USE_LTC_SAMPLING 1
1460+
1461+ Color3 get_albedo (const Vec3& wo) const override
1462+ {
1463+ const float NdotV = clamp (N.dot (wo), 1e-5f , 1 .0f );
1464+ return Color3 (fetch_ltc (NdotV).z );
1465+ }
1466+
1467+ Sample eval (const Vec3& wo, const Vec3& wi) const override
1468+ {
1469+ const Vec3 L = wi, V = wo;
1470+ const float NdotV = clamp (N.dot (V), 0 .0f , 1 .0f );
1471+ const Vec3 ltc = fetch_ltc (NdotV);
1472+
1473+ const Vec3 localL = TangentFrame::from_normal_and_tangent (N, V).tolocal (
1474+ L);
1475+
1476+ const float aInv = ltc.x , bInv = ltc.y , R = ltc.z ;
1477+ Vec3 wiOriginal (aInv * localL.x + bInv * localL.z , aInv * localL.y ,
1478+ localL.z );
1479+ const float len2 = dot (wiOriginal, wiOriginal);
1480+
1481+ float det = aInv * aInv;
1482+ float jacobian = det / (len2 * len2);
1483+
1484+ #if USE_LTC_SAMPLING == 1
1485+ float pdf = jacobian * std::max (wiOriginal.z , 0 .0f ) * float (M_1_PI);
1486+ return { wi, Color3 (R), pdf, 1 .0f };
1487+ #else
1488+ float pdf = float (0.5 * M_1_PI);
1489+ // NOTE: sheen closure has no fresnel/masking
1490+ return { wi, Color3 (2 * R * jacobian * std::max (wiOriginal.z , 0 .0f )),
1491+ pdf, 1 .0f };
1492+ #endif
1493+ }
1494+
1495+ Sample sample (const Vec3& wo, float rx, float ry, float rz) const override
1496+ {
1497+ #if USE_LTC_SAMPLING == 1
1498+ const Vec3 V = wo;
1499+ const float NdotV = clamp (N.dot (V), 0 .0f , 1 .0f );
1500+ const Vec3 ltc = fetch_ltc (NdotV);
1501+ const float aInv = ltc.x , bInv = ltc.y , R = ltc.z ;
1502+ Vec3 wi;
1503+ float pdf;
1504+ Sampling::sample_cosine_hemisphere (Vec3 (0 , 0 , 1 ), rx, ry, wi, pdf);
1505+
1506+ const Vec3 w = Vec3 (wi.x - wi.z * bInv, wi.y , wi.z * aInv);
1507+ const float len2 = dot (w, w);
1508+ const float jacobian = len2 * len2 / (aInv * aInv);
1509+ const Vec3 wn = w / sqrtf (len2);
1510+
1511+ const Vec3 L = TangentFrame::from_normal_and_tangent (N, V).toworld (wn);
1512+
1513+ pdf = jacobian * std::max (wn.z , 0 .0f ) * float (M_1_PI);
1514+
1515+ return { L, Color3 (R), pdf, 1 .0f };
1516+ #else
1517+ // plain uniform-sampling for validation
1518+ Vec3 out_dir;
1519+ float pdf;
1520+ Sampling::sample_uniform_hemisphere (N, rx, ry, out_dir, pdf);
1521+ return eval (wo, out_dir);
1522+ #endif
1523+ }
1524+
1525+ private:
1526+ Vec3 fetch_ltc (float NdotV) const
1527+ {
1528+ // To avoid look-up tables, we use a fit of the LTC coefficients derived by Stephen Hill
1529+ // for the implementation in MaterialX:
1530+ // https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl
1531+ const float x = NdotV;
1532+ const float y = std::max (roughness, 1e-3f );
1533+ const float A = ((2 .58126f * x + 0 .813703f * y) * y)
1534+ / (1 .0f + 0 .310327f * x * x + 2 .60994f * x * y);
1535+ const float B = sqrtf (1 .0f - x) * (y - 1 .0f ) * y * y * y
1536+ / (0 .0000254053f + 1 .71228f * x - 1 .71506f * x * y
1537+ + 1 .34174f * y * y);
1538+ const float invs = (0 .0379424f + y * (1 .32227f + y))
1539+ / (y * (0 .0206607f + 1 .58491f * y));
1540+ const float m = y
1541+ * (-0 .193854f
1542+ + y * (-1.14885 + y * (1 .7932f - 0 .95943f * y * y)))
1543+ / (0 .046391f + y);
1544+ const float o = y * (0 .000654023f + (-0 .0207818f + 0 .119681f * y) * y)
1545+ / (1 .26264f + y * (-1 .92021f + y));
1546+ float q = (x - m) * invs;
1547+ const float inv_sqrt2pi = 0 .39894228040143f ;
1548+ float R = expf (-0 .5f * q * q) * invs * inv_sqrt2pi + o;
1549+ assert (isfinite (A));
1550+ assert (isfinite (B));
1551+ assert (isfinite (R));
1552+ return Vec3 (A, B, R);
1553+ }
1554+ };
1555+
1556+
14471557Color3
14481558evaluate_layer_opacity (const OSL::ShaderGlobals& sg,
14491559 const ClosureColor* closure)
@@ -1493,8 +1603,11 @@ evaluate_layer_opacity(const OSL::ShaderGlobals& sg,
14931603 return w * mf.get_albedo (-sg.I );
14941604 }
14951605 case MX_SHEEN_ID: {
1496- MxSheen bsdf (*comp->as <MxSheenParams>());
1497- return w * bsdf.get_albedo (-sg.I );
1606+ const MxSheenParams& params = *comp->as <MxSheenParams>();
1607+ if (params.mode == 1 )
1608+ return w * ZeltnerBurleySheen (params).get_albedo (-sg.I );
1609+ // otherwise, default to old sheen model
1610+ return w * CharlieSheen (params).get_albedo (-sg.I );
14981611 }
14991612 default : // Assume unhandled BSDFs are opaque
15001613 return Color3 (1 );
@@ -1765,7 +1878,11 @@ process_bsdf_closure(const OSL::ShaderGlobals& sg, ShadingResult& result,
17651878 }
17661879 case MX_SHEEN_ID: {
17671880 const MxSheenParams& params = *comp->as <MxSheenParams>();
1768- ok = result.bsdf .add_bsdf <MxSheen>(cw, params);
1881+ if (params.mode == 1 )
1882+ ok = result.bsdf .add_bsdf <ZeltnerBurleySheen>(cw, params);
1883+ else
1884+ ok = result.bsdf .add_bsdf <CharlieSheen>(
1885+ cw, params); // default to legacy closure
17691886 break ;
17701887 }
17711888 case MX_LAYER_ID: {
0 commit comments