Cycles: Implement approximate reflectance profiles

Using this paper:

  http://graphics.pixar.com/library/ApproxBSSRDF/paper.pdf

This model gives less blurry results than the Cubic and Gaussian
we had implemented:

- Cubic: https://developer.blender.org/F279670
- Burley: https://developer.blender.org/F279671

The model is called "Christensen-Burley" in the interface, which
actually should be read as "Physically based" or "Realistic".

Reviewers: juicyfruit, dingto, lukasstockner97, brecht

Reviewed By: brecht, dingto

Subscribers: robocyte

Differential Revision: https://developer.blender.org/D1759
This commit is contained in:
Sergey Sharybin 2016-02-04 03:34:49 +05:00
parent d8a998ce71
commit ad26407b52
15 changed files with 174 additions and 10 deletions

@ -596,8 +596,10 @@ static void xml_read_shader_graph(const XMLReadState& state, Shader *shader, pug
xml_read_string(&falloff, node, "falloff");
if(falloff == "cubic")
sss->closure = CLOSURE_BSSRDF_CUBIC_ID;
else
else if(falloff == "gaussian")
sss->closure = CLOSURE_BSSRDF_GAUSSIAN_ID;
else /*if(falloff == "burley")*/
sss->closure = CLOSURE_BSSRDF_BURLEY_ID;
snode = sss;
}

@ -405,6 +405,9 @@ static ShaderNode *add_node(Scene *scene,
case BL::ShaderNodeSubsurfaceScattering::falloff_GAUSSIAN:
subsurface->closure = CLOSURE_BSSRDF_GAUSSIAN_ID;
break;
case BL::ShaderNodeSubsurfaceScattering::falloff_BURLEY:
subsurface->closure = CLOSURE_BSSRDF_BURLEY_ID;
break;
}
node = subsurface;

@ -192,6 +192,110 @@ ccl_device void bssrdf_cubic_sample(ShaderClosure *sc, float xi, float *r, float
*h = sqrtf(Rm*Rm - r_*r_);
}
/* Approximate Reflectance Profiles
* http://graphics.pixar.com/library/ApproxBSSRDF/paper.pdf
*/
ccl_device_inline float bssrdf_burley_fitting(float A)
{
/* Diffuse surface transmission, equation (6). */
return 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f);
}
/* Scale mean free path length so it gives similar looking result
* to Cubic and Gaussian models.
*/
ccl_device_inline float bssrdf_burley_compatible_mfp(float r)
{
return 0.5f * M_1_PI_F * r;
}
ccl_device float bssrdf_burley_eval(ShaderClosure *sc, float r)
{
/* Mean free path length. */
const float l = bssrdf_burley_compatible_mfp(sc->data0);
/* Surface albedo. */
const float A = sc->data2;
const float s = bssrdf_burley_fitting(A);
/* Burley refletance profile, equation (3).
*
* Note that surface albedo is already included into sc->weight, no need to
* multiply by this term here.
*/
float exp_r_3_d = expf(-s*r / (3.0f * l));
float exp_r_d = exp_r_3_d * exp_r_3_d * exp_r_3_d;
return s * (exp_r_d + exp_r_3_d) / (8*M_PI_F*l*r);
}
ccl_device float bssrdf_burley_pdf(ShaderClosure *sc, float r)
{
return bssrdf_burley_eval(sc, r);
}
/* Find the radius for desired CDF value.
* Returns scaled radius, meaning the result is to be scaled up by d.
* Since there's no closed form solution we do Newton-Raphson method to find it.
*/
ccl_device float bssrdf_burley_root_find(float xi)
{
const float tolerance = 1e-6f;
const int max_iteration_count = 10;
/* Do initial guess based on manual curve fitting, this allows us to reduce
* number of iterations to maximum 4 across the [0..1] range. We keep maximum
* number of iteration higher just to be sure we didn't miss root in some
* corner case.
*/
float r;
if (xi <= 0.9f) {
r = expf(xi * xi * 2.4f) - 1.0f;
}
else {
float a = expf(xi * xi * 4.0f) - 1.0f;
r = a*a;
}
/* Solve against scaled radius. */
for(int i = 0; i < max_iteration_count; i++) {
float exp_r_3 = expf(-r / 3.0f);
float exp_r = exp_r_3 * exp_r_3 * exp_r_3;
float f = 1.0f - 0.25f * exp_r - 0.75f * exp_r_3 - xi;
float f_ = 0.25f * exp_r + 0.25f * exp_r_3;
if(fabsf(f) < tolerance || f_ == 0.0f) {
break;
}
r = r - f/f_;
if(r < 0.0f) {
r = 0.0f;
}
}
return r;
}
ccl_device void bssrdf_burley_sample(ShaderClosure *sc,
float xi,
float *r,
float *h)
{
/* Mean free path length. */
const float l = bssrdf_burley_compatible_mfp(sc->data0);
/* Surface albedo. */
const float A = sc->data2;
const float s = bssrdf_burley_fitting(A);
const float d = l / s;
/* This is a bit arbitrary, just need big enough radius so it matches
* the mean free length, but still not too big so sampling is still
* effective. Might need some further tweaks.
*/
const float Rm = 10.0f*d;
const float r_ = bssrdf_burley_root_find(xi) * d;
*r = r_;
/* h^2 + r^2 = Rm^2 */
*h = sqrtf(Rm*Rm - r_*r_);
}
/* None BSSRDF falloff
*
* Samples distributed over disk with no falloff, for reference. */
@ -230,16 +334,20 @@ ccl_device void bssrdf_sample(ShaderClosure *sc, float xi, float *r, float *h)
{
if(sc->type == CLOSURE_BSSRDF_CUBIC_ID)
bssrdf_cubic_sample(sc, xi, r, h);
else
else if(sc->type == CLOSURE_BSSRDF_GAUSSIAN_ID)
bssrdf_gaussian_sample(sc, xi, r, h);
else /*if(sc->type == CLOSURE_BSSRDF_BURLEY_ID)*/
bssrdf_burley_sample(sc, xi, r, h);
}
ccl_device float bssrdf_pdf(ShaderClosure *sc, float r)
{
if(sc->type == CLOSURE_BSSRDF_CUBIC_ID)
return bssrdf_cubic_pdf(sc, r);
else
else if(sc->type == CLOSURE_BSSRDF_GAUSSIAN_ID)
return bssrdf_gaussian_pdf(sc, r);
else /*if(sc->type == CLOSURE_BSSRDF_BURLEY_ID)*/
return bssrdf_burley_pdf(sc, r);
}
CCL_NAMESPACE_END

@ -105,5 +105,34 @@ ClosureParam *closure_bssrdf_gaussian_params()
CCLOSURE_PREPARE(closure_bssrdf_gaussian_prepare, GaussianBSSRDFClosure)
/* Burley */
class BurleyBSSRDFClosure : public CBSSRDFClosure {
public:
BurleyBSSRDFClosure()
{}
void setup()
{
sc.type = CLOSURE_BSSRDF_BURLEY_ID;
sc.data0 = fabsf(average(radius));
}
};
ClosureParam *closure_bssrdf_burley_params()
{
static ClosureParam params[] = {
CLOSURE_FLOAT3_PARAM(BurleyBSSRDFClosure, sc.N),
CLOSURE_FLOAT3_PARAM(BurleyBSSRDFClosure, radius),
CLOSURE_FLOAT_PARAM(BurleyBSSRDFClosure, sc.data1),
CLOSURE_FLOAT3_PARAM(BurleyBSSRDFClosure, albedo),
CLOSURE_STRING_KEYPARAM(BurleyBSSRDFClosure, label, "label"),
CLOSURE_FINISH_PARAM(BurleyBSSRDFClosure)
};
return params;
}
CCLOSURE_PREPARE(closure_bssrdf_burley_prepare, BurleyBSSRDFClosure)
CCL_NAMESPACE_END

@ -49,6 +49,7 @@ class CBSSRDFClosure : public CClosurePrimitive {
public:
ShaderClosure sc;
float3 radius;
float3 albedo;
CBSSRDFClosure() : CClosurePrimitive(BSSRDF) { }
int scattering() const { return LABEL_DIFFUSE; }

@ -236,6 +236,8 @@ void OSLShader::register_closures(OSLShadingSystem *ss_)
closure_bssrdf_cubic_params(), closure_bssrdf_cubic_prepare);
register_closure(ss, "bssrdf_gaussian", id++,
closure_bssrdf_gaussian_params(), closure_bssrdf_gaussian_prepare);
register_closure(ss, "bssrdf_burley", id++,
closure_bssrdf_burley_params(), closure_bssrdf_burley_prepare);
register_closure(ss, "hair_reflection", id++,
bsdf_hair_reflection_params(), bsdf_hair_reflection_prepare);

@ -50,6 +50,7 @@ OSL::ClosureParam *closure_bsdf_diffuse_ramp_params();
OSL::ClosureParam *closure_bsdf_phong_ramp_params();
OSL::ClosureParam *closure_bssrdf_cubic_params();
OSL::ClosureParam *closure_bssrdf_gaussian_params();
OSL::ClosureParam *closure_bssrdf_burley_params();
OSL::ClosureParam *closure_henyey_greenstein_volume_params();
void closure_emission_prepare(OSL::RendererServices *, int id, void *data);
@ -60,6 +61,7 @@ void closure_bsdf_diffuse_ramp_prepare(OSL::RendererServices *, int id, void *da
void closure_bsdf_phong_ramp_prepare(OSL::RendererServices *, int id, void *data);
void closure_bssrdf_cubic_prepare(OSL::RendererServices *, int id, void *data);
void closure_bssrdf_gaussian_prepare(OSL::RendererServices *, int id, void *data);
void closure_bssrdf_burley_prepare(OSL::RendererServices *, int id, void *data);
void closure_henyey_greenstein_volume_prepare(OSL::RendererServices *, int id, void *data);
#define CCLOSURE_PREPARE(name, classname) \

@ -281,11 +281,17 @@ static void flatten_surface_closure_tree(ShaderData *sd, int path_flag,
if(path_flag & PATH_RAY_DIFFUSE_ANCESTOR)
bssrdf->radius = make_float3(0.0f, 0.0f, 0.0f);
float3 albedo =
(bssrdf->sc.type == CLOSURE_BSSRDF_BURLEY_ID)
? bssrdf->albedo
: make_float3(0.0f, 0.0f, 0.0f);
/* create one closure for each color channel */
if(fabsf(weight.x) > 0.0f) {
sc.weight = make_float3(weight.x, 0.0f, 0.0f);
sc.data0 = bssrdf->radius.x;
sc.data1 = 0.0f;
sc.data2 = albedo.x;
sd->flag |= bssrdf_setup(&sc, sc.type);
sd->closure[sd->num_closure++] = sc;
}
@ -294,6 +300,7 @@ static void flatten_surface_closure_tree(ShaderData *sd, int path_flag,
sc.weight = make_float3(0.0f, weight.y, 0.0f);
sc.data0 = bssrdf->radius.y;
sc.data1 = 0.0f;
sc.data2 = albedo.y;
sd->flag |= bssrdf_setup(&sc, sc.type);
sd->closure[sd->num_closure++] = sc;
}
@ -302,6 +309,7 @@ static void flatten_surface_closure_tree(ShaderData *sd, int path_flag,
sc.weight = make_float3(0.0f, 0.0f, weight.z);
sc.data0 = bssrdf->radius.z;
sc.data1 = 0.0f;
sc.data2 = albedo.z;
sd->flag |= bssrdf_setup(&sc, sc.type);
sd->closure[sd->num_closure++] = sc;
}

@ -28,7 +28,9 @@ shader node_subsurface_scattering(
{
if (Falloff == "Gaussian")
BSSRDF = Color * bssrdf_gaussian(N, Scale * Radius, TextureBlur);
else
else if (Falloff == "Cubic")
BSSRDF = Color * bssrdf_cubic(N, Scale * Radius, TextureBlur, Sharpness);
else
BSSRDF = Color * bssrdf_burley(N, Scale * Radius, TextureBlur, Color);
}

@ -510,6 +510,7 @@ closure color ambient_occlusion() BUILTIN;
// BSSRDF
closure color bssrdf_cubic(normal N, vector radius, float texture_blur, float sharpness) BUILTIN;
closure color bssrdf_gaussian(normal N, vector radius, float texture_blur) BUILTIN;
closure color bssrdf_burley(normal N, vector radius, float texture_blur, color albedo) BUILTIN;
// Hair
closure color hair_reflection(normal N, float roughnessu, float roughnessv, vector T, float offset) BUILTIN;

@ -442,8 +442,10 @@ ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *
# define sc_next(sc) sc = ccl_fetch_array(sd, closure, ccl_fetch(sd, num_closure))
#endif
case CLOSURE_BSSRDF_CUBIC_ID:
case CLOSURE_BSSRDF_GAUSSIAN_ID: {
case CLOSURE_BSSRDF_GAUSSIAN_ID:
case CLOSURE_BSSRDF_BURLEY_ID: {
ShaderClosure *sc = ccl_fetch_array(sd, closure, ccl_fetch(sd, num_closure));
float3 albedo = sc->weight;
float3 weight = sc->weight * mix_weight;
float sample_weight = fabsf(average(weight));
@ -467,7 +469,7 @@ ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *
sc->sample_weight = sample_weight;
sc->data0 = radius.x;
sc->data1 = texture_blur;
sc->data2 = 0.0f;
sc->data2 = albedo.x;
sc->T.x = sharpness;
#ifdef __OSL__
sc->prim = NULL;
@ -484,7 +486,7 @@ ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *
sc->sample_weight = sample_weight;
sc->data0 = radius.y;
sc->data1 = texture_blur;
sc->data2 = 0.0f;
sc->data2 = albedo.y;
sc->T.x = sharpness;
#ifdef __OSL__
sc->prim = NULL;
@ -501,7 +503,7 @@ ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *
sc->sample_weight = sample_weight;
sc->data0 = radius.z;
sc->data1 = texture_blur;
sc->data2 = 0.0f;
sc->data2 = albedo.z;
sc->T.x = sharpness;
#ifdef __OSL__
sc->prim = NULL;

@ -410,6 +410,7 @@ typedef enum ClosureType {
/* BSSRDF */
CLOSURE_BSSRDF_CUBIC_ID,
CLOSURE_BSSRDF_GAUSSIAN_ID,
CLOSURE_BSSRDF_BURLEY_ID,
/* Other */
CLOSURE_EMISSION_ID,
@ -432,8 +433,8 @@ typedef enum ClosureType {
#define CLOSURE_IS_BSDF_TRANSMISSION(type) (type >= CLOSURE_BSDF_TRANSMISSION_ID && type <= CLOSURE_BSDF_HAIR_TRANSMISSION_ID)
#define CLOSURE_IS_BSDF_BSSRDF(type) (type == CLOSURE_BSDF_BSSRDF_ID)
#define CLOSURE_IS_BSDF_ANISOTROPIC(type) (type >= CLOSURE_BSDF_MICROFACET_GGX_ANISO_ID && type <= CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ANISO_ID)
#define CLOSURE_IS_BSDF_OR_BSSRDF(type) (type <= CLOSURE_BSSRDF_GAUSSIAN_ID)
#define CLOSURE_IS_BSSRDF(type) (type >= CLOSURE_BSSRDF_CUBIC_ID && type <= CLOSURE_BSSRDF_GAUSSIAN_ID)
#define CLOSURE_IS_BSDF_OR_BSSRDF(type) (type <= CLOSURE_BSSRDF_BURLEY_ID)
#define CLOSURE_IS_BSSRDF(type) (type >= CLOSURE_BSSRDF_CUBIC_ID && type <= CLOSURE_BSSRDF_BURLEY_ID)
#define CLOSURE_IS_VOLUME(type) (type >= CLOSURE_VOLUME_ID && type <= CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID)
#define CLOSURE_IS_EMISSION(type) (type == CLOSURE_EMISSION_ID)
#define CLOSURE_IS_HOLDOUT(type) (type == CLOSURE_HOLDOUT_ID)

@ -2243,6 +2243,7 @@ static ShaderEnum subsurface_falloff_init()
enm.insert("Cubic", CLOSURE_BSSRDF_CUBIC_ID);
enm.insert("Gaussian", CLOSURE_BSSRDF_GAUSSIAN_ID);
enm.insert("Burley", CLOSURE_BSSRDF_BURLEY_ID);
return enm;
}

@ -1057,6 +1057,7 @@ enum {
#endif
SHD_SUBSURFACE_CUBIC = 1,
SHD_SUBSURFACE_GAUSSIAN = 2,
SHD_SUBSURFACE_BURLEY = 3,
};
/* blur node */

@ -4247,6 +4247,7 @@ static void def_sh_subsurface(StructRNA *srna)
static EnumPropertyItem prop_subsurface_falloff_items[] = {
{SHD_SUBSURFACE_CUBIC, "CUBIC", 0, "Cubic", "Simple cubic falloff function"},
{SHD_SUBSURFACE_GAUSSIAN, "GAUSSIAN", 0, "Gaussian", "Normal distribution, multiple can be combined to fit more complex profiles"},
{SHD_SUBSURFACE_BURLEY, "BURLEY", 0, "Christensen-Burley", "Approximation to physically based volume scattering"},
{0, NULL, 0, NULL, NULL}
};