Eevee : SSS : Add Translucency support.

This adds the possibility to simulate things like red ears with strong backlight or material with high scattering distances.

To enable it you need to turn on the "Subsurface Translucency" option in the "Options" tab of the Material Panel (and of course to have "regular" SSS enabled in both render settings and material options).
Since the effect is adding another overhead I prefer to make it optional. But this is open to discussion.

Be aware that the effect only works for direct lights (so no indirect/world lighting) that have shadowmaps, and is affected by the "softness" of the shadowmap and resolution.

Technical notes:

This is inspired by http://www.iryoku.com/translucency/ but goes a bit beyond that.
We do not use a sum of gaussian to apply in regards to the object thickness but we precompute a 1D kernel texture.
This texture stores the light transmited to a point at the back of an infinite slab of material of variying thickness.
We make the assumption that the slab is perpendicular to the light so that no fresnel or diffusion term is taken into account.
The light is considered constant.
If the setup is similar to the one assume during the profile baking, the realtime render matches cycles reference.
Due to these assumptions the computed transmitted light is in most cases too bright for curvy objects.

Finally we jitter the shadow map sample per pixel so we can simulate dispersion inside the medium.
Radius of the dispersion is in world space and derived by from the "soft" shadowmap parameter.
Idea for this come from this presentation http://www.iryoku.com/stare-into-the-future (slide 164).
This commit is contained in:
Clément Foucault 2017-11-22 04:51:21 +01:00
parent ff810f08d8
commit 846cdf5318
14 changed files with 394 additions and 106 deletions

View File

@ -1178,7 +1178,7 @@ class EEVEE_MATERIAL_PT_options(MaterialButtonsPanel, Panel):
row = layout.row()
row.active = ((mat.blend_method == "CLIP") or (mat.transparent_shadow_method == "CLIP"))
layout.prop(mat, "alpha_threshold")
row.prop(mat, "alpha_threshold")
if mat.blend_method not in {"OPAQUE", "CLIP", "HASHED"}:
layout.prop(mat, "transparent_hide_backside")
@ -1187,6 +1187,9 @@ class EEVEE_MATERIAL_PT_options(MaterialButtonsPanel, Panel):
layout.prop(mat, "refraction_depth")
layout.prop(mat, "use_screen_subsurface")
row = layout.row()
row.active = mat.use_screen_subsurface
row.prop(mat, "use_sss_translucency")
classes = (

View File

@ -530,6 +530,7 @@ static void eevee_shadow_cube_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE_La
ubo_data->shadow_start = (float)(sh_data->layer_id);
ubo_data->data_start = (float)(sh_data->cube_id);
ubo_data->multi_shadow_count = (float)(sh_nbr);
ubo_data->shadow_blur = la->soft * 0.02f; /* Used by translucence shadowmap blur */
ubo_data->contact_dist = (la->mode & LA_SHAD_CONTACT) ? la->contact_dist : 0.0f;
ubo_data->contact_bias = 0.05f * la->contact_bias;
@ -777,6 +778,7 @@ static void eevee_shadow_cascade_setup(Object *ob, EEVEE_LampsInfo *linfo, EEVEE
ubo_data->shadow_start = (float)(sh_data->layer_id);
ubo_data->data_start = (float)(sh_data->cascade_id);
ubo_data->multi_shadow_count = (float)(sh_nbr);
ubo_data->shadow_blur = la->soft * 0.02f; /* Used by translucence shadowmap blur */
ubo_data->contact_dist = (la->mode & LA_SHAD_CONTACT) ? la->contact_dist : 0.0f;
ubo_data->contact_bias = 0.05f * la->contact_bias;

View File

@ -307,6 +307,9 @@ static char *eevee_get_defines(int options)
if ((options & VAR_MAT_SSS) != 0) {
BLI_dynstr_appendf(ds, "#define USE_SSS\n");
}
if ((options & VAR_MAT_TRANSLUC) != 0) {
BLI_dynstr_appendf(ds, "#define USE_TRANSLUCENCY\n");
}
if ((options & VAR_MAT_VSM) != 0) {
BLI_dynstr_appendf(ds, "#define SHADOW_VSM\n");
}
@ -636,7 +639,7 @@ struct GPUMaterial *EEVEE_material_world_volume_get(struct Scene *scene, World *
struct GPUMaterial *EEVEE_material_mesh_get(
struct Scene *scene, Material *ma, EEVEE_Data *vedata,
bool use_blend, bool use_multiply, bool use_refract, bool use_sss, int shadow_method)
bool use_blend, bool use_multiply, bool use_refract, bool use_sss, bool use_translucency, int shadow_method)
{
const void *engine = &DRW_engine_viewport_eevee_type;
int options = VAR_MAT_MESH;
@ -645,6 +648,7 @@ struct GPUMaterial *EEVEE_material_mesh_get(
if (use_multiply) options |= VAR_MAT_MULT;
if (use_refract) options |= VAR_MAT_REFRACT;
if (use_sss) options |= VAR_MAT_SSS;
if (use_translucency) options |= VAR_MAT_TRANSLUC;
if (vedata->stl->effects->use_volumetrics && use_blend) options |= VAR_MAT_VOLUME;
options |= eevee_material_shadow_option(shadow_method);
@ -977,6 +981,7 @@ static void material_opaque(
const bool use_gpumat = (ma->use_nodes && ma->nodetree);
const bool use_refract = ((ma->blend_flag & MA_BL_SS_REFRACTION) != 0) && ((stl->effects->enabled_effects & EFFECT_REFRACT) != 0);
const bool use_sss = ((ma->blend_flag & MA_BL_SS_SUBSURFACE) != 0) && ((stl->effects->enabled_effects & EFFECT_SSS) != 0);
const bool use_translucency = ((ma->blend_flag & MA_BL_TRANSLUCENCY) != 0) && ((stl->effects->enabled_effects & EFFECT_SSS) != 0);
EeveeMaterialShadingGroups *emsg = BLI_ghash_lookup(material_hash, (const void *)ma);
@ -987,7 +992,7 @@ static void material_opaque(
/* This will have been created already, just perform a lookup. */
*gpumat = (use_gpumat) ? EEVEE_material_mesh_get(
scene, ma, vedata, false, false, use_refract, use_sss, linfo->shadow_method) : NULL;
scene, ma, vedata, false, false, use_refract, use_sss, use_translucency, linfo->shadow_method) : NULL;
*gpumat_depth = (use_gpumat) ? EEVEE_material_mesh_depth_get(
scene, ma, (ma->blend_method == MA_BM_HASHED), false) : NULL;
return;
@ -995,7 +1000,8 @@ static void material_opaque(
if (use_gpumat) {
/* Shading */
*gpumat = EEVEE_material_mesh_get(scene, ma, vedata, false, false, use_refract, use_sss, linfo->shadow_method);
*gpumat = EEVEE_material_mesh_get(scene, ma, vedata, false, false, use_refract,
use_sss, use_translucency, linfo->shadow_method);
*shgrp = DRW_shgroup_material_create(*gpumat,
(use_refract) ? psl->refract_pass :
@ -1007,9 +1013,17 @@ static void material_opaque(
add_standard_uniforms(*shgrp, sldata, vedata, ssr_id, &ma->refract_depth, use_refract, false);
if (use_sss) {
struct GPUTexture *sss_tex_profile = NULL;
struct GPUUniformBuffer *sss_profile = GPU_material_sss_profile_get(*gpumat,
stl->effects->sss_sample_count);
stl->effects->sss_sample_count,
&sss_tex_profile);
if (sss_profile) {
if (use_translucency) {
DRW_shgroup_uniform_block(*shgrp, "sssProfile", sss_profile);
DRW_shgroup_uniform_texture(*shgrp, "sssTexProfile", sss_tex_profile);
}
DRW_shgroup_stencil_mask(*shgrp, e_data.sss_count + 1);
EEVEE_subsurface_add_pass(vedata, e_data.sss_count + 1, sss_profile);
e_data.sss_count++;
@ -1099,7 +1113,7 @@ static void material_transparent(
if (ma->use_nodes && ma->nodetree) {
/* Shading */
*gpumat = EEVEE_material_mesh_get(scene, ma, vedata, true, (ma->blend_method == MA_BM_MULTIPLY), use_refract,
false, linfo->shadow_method);
false, false, linfo->shadow_method);
*shgrp = DRW_shgroup_material_create(*gpumat, psl->transparent_pass);
if (*shgrp) {

View File

@ -116,6 +116,7 @@ enum {
VAR_MAT_REFRACT = (1 << 12),
VAR_MAT_VOLUME = (1 << 13),
VAR_MAT_SSS = (1 << 14),
VAR_MAT_TRANSLUC = (1 << 15),
};
/* Shadow Technique */
@ -280,7 +281,7 @@ typedef struct EEVEE_Light {
typedef struct EEVEE_Shadow {
float near, far, bias, exp;
float shadow_start, data_start, multi_shadow_count, pad;
float shadow_start, data_start, multi_shadow_count, shadow_blur;
float contact_dist, contact_bias, contact_spread, contact_thickness;
} EEVEE_Shadow;
@ -639,7 +640,7 @@ struct GPUMaterial *EEVEE_material_world_background_get(struct Scene *scene, str
struct GPUMaterial *EEVEE_material_world_volume_get(struct Scene *scene, struct World *wo);
struct GPUMaterial *EEVEE_material_mesh_get(
struct Scene *scene, Material *ma, EEVEE_Data *vedata,
bool use_blend, bool use_multiply, bool use_refract, bool use_sss, int shadow_method);
bool use_blend, bool use_multiply, bool use_refract, bool use_sss, bool use_translucency, int shadow_method);
struct GPUMaterial *EEVEE_material_mesh_volume_get(struct Scene *scene, Material *ma);
struct GPUMaterial *EEVEE_material_mesh_depth_get(struct Scene *scene, Material *ma, bool use_hashed_alpha, bool is_shadow);
struct GPUMaterial *EEVEE_material_hair_get(struct Scene *scene, Material *ma, int shadow_method);

View File

@ -123,7 +123,6 @@ void EEVEE_subsurface_add_pass(EEVEE_Data *vedata, unsigned int sss_id, struct G
DRW_shgroup_uniform_buffer(grp, "depthBuffer", &dtxl->depth);
DRW_shgroup_uniform_buffer(grp, "sssData", &txl->sss_data);
DRW_shgroup_uniform_block(grp, "sssProfile", sss_profile);
DRW_shgroup_uniform_int(grp, "sampleCount", &effects->sss_sample_count, 1);
DRW_shgroup_uniform_float(grp, "jitterThreshold", &effects->sss_jitter_threshold, 1);
DRW_shgroup_stencil_mask(grp, sss_id);
DRW_shgroup_call_add(grp, quad, NULL);
@ -134,7 +133,6 @@ void EEVEE_subsurface_add_pass(EEVEE_Data *vedata, unsigned int sss_id, struct G
DRW_shgroup_uniform_buffer(grp, "depthBuffer", &dtxl->depth);
DRW_shgroup_uniform_buffer(grp, "sssData", &txl->sss_blur);
DRW_shgroup_uniform_block(grp, "sssProfile", sss_profile);
DRW_shgroup_uniform_int(grp, "sampleCount", &effects->sss_sample_count, 1);
DRW_shgroup_uniform_float(grp, "jitterThreshold", &effects->sss_jitter_threshold, 1);
DRW_shgroup_stencil_mask(grp, sss_id);
DRW_shgroup_call_add(grp, quad, NULL);

View File

@ -103,6 +103,7 @@ struct ShadowCascadeData {
#define sh_tex_start shadow_data_start_end.x
#define sh_data_start shadow_data_start_end.y
#define sh_multi_nbr shadow_data_start_end.z
#define sh_blur shadow_data_start_end.w
#define sh_contact_dist contact_shadow_data.x
#define sh_contact_offset contact_shadow_data.y
#define sh_contact_spread contact_shadow_data.z

View File

@ -5,9 +5,9 @@
layout(std140) uniform sssProfile {
vec4 kernel[MAX_SSS_SAMPLES];
vec4 radii_max_radius;
int sss_samples;
};
uniform int sampleCount;
uniform float jitterThreshold;
uniform sampler2D depthBuffer;
uniform sampler2D sssData;
@ -59,7 +59,7 @@ void main(void)
/* Center sample */
vec3 accum = sss_data.rgb * kernel[0].rgb;
for (int i = 1; i < sampleCount && i < MAX_SSS_SAMPLES; i++) {
for (int i = 1; i < sss_samples && i < MAX_SSS_SAMPLES; i++) {
vec2 sample_uv = uvs + kernel[i].a * finalStep * ((abs(kernel[i].a) > jitterThreshold) ? dir : dir_rand);
vec3 color = texture(sssData, sample_uv).rgb;
float sample_depth = texture(depthBuffer, sample_uv).r;

View File

@ -277,6 +277,142 @@ vec3 light_specular(LightData ld, vec3 N, vec3 V, vec4 l_vector, float roughness
#endif
}
#define MAX_SSS_SAMPLES 65
#define SSS_LUT_SIZE 64.0
#define SSS_LUT_SCALE ((SSS_LUT_SIZE - 1.0) / float(SSS_LUT_SIZE))
#define SSS_LUT_BIAS (0.5 / float(SSS_LUT_SIZE))
layout(std140) uniform sssProfile {
vec4 kernel[MAX_SSS_SAMPLES];
vec4 radii_max_radius;
int sss_samples;
};
uniform sampler1D sssTexProfile;
vec3 sss_profile(float s) {
s /= radii_max_radius.w;
return texture(sssTexProfile, saturate(s) * SSS_LUT_SCALE + SSS_LUT_BIAS).rgb;
}
vec3 light_translucent(LightData ld, vec3 W, vec3 N, vec4 l_vector, float scale)
{
vec3 vis = vec3(1.0);
/* Only shadowed light can produce translucency */
if (ld.l_shadowid >= 0.0) {
ShadowData data = shadows_data[int(ld.l_shadowid)];
float delta;
vec4 L = (ld.l_type != SUN) ? l_vector : vec4(-ld.l_forward, 1.0);
vec3 T, B;
make_orthonormal_basis(L.xyz / L.w, T, B);
vec3 rand = texture(utilTex, vec3(gl_FragCoord.xy / LUT_SIZE, 2.0)).xzw;
/* XXX This is a hack to not have noise correlation artifacts.
* A better solution to have better noise is welcome. */
rand.yz *= fast_sqrt(fract(rand.x * 7919.0)) * data.sh_blur;
/* We use the full l_vector.xyz so that the spread is minimize
* if the shading point is further away from the light source */
W = W + T * rand.y + B * rand.z;
if (ld.l_type == SUN) {
ShadowCascadeData scd = shadows_cascade_data[int(data.sh_data_start)];
vec4 view_z = vec4(dot(W - cameraPos, cameraForward));
vec4 weights = step(scd.split_end_distances, view_z);
float id = abs(4.0 - dot(weights, weights));
if (id > 3.0) {
return vec3(0.0);
}
float range = abs(data.sh_far - data.sh_near); /* Same factor as in get_cascade_world_distance(). */
vec4 shpos = scd.shadowmat[int(id)] * vec4(W, 1.0);
float dist = shpos.z * range;
if (shpos.z > 1.0 || shpos.z < 0.0) {
return vec3(0.0);
}
#if defined(SHADOW_VSM)
vec2 moments = texture(shadowTexture, vec3(shpos.xy, data.sh_tex_start + id)).rg;
delta = dist - moments.x;
#else
float z = texture(shadowTexture, vec3(shpos.xy, data.sh_tex_start + id)).r;
delta = dist - z;
#endif
}
else {
vec3 cubevec = W - shadows_cube_data[int(data.sh_data_start)].position.xyz;
float dist = length(cubevec);
/* If fragment is out of shadowmap range, do not occlude */
/* XXX : we check radial distance against a cubeface distance.
* We loose quite a bit of valid area. */
if (dist < data.sh_far) {
cubevec /= dist;
#if defined(SHADOW_VSM)
vec2 moments = texture_octahedron(shadowTexture, vec4(cubevec, data.sh_tex_start)).rg;
delta = dist - moments.x;
#else
float z = texture_octahedron(shadowTexture, vec4(cubevec, data.sh_tex_start)).r;
delta = dist - z;
#endif
}
}
/* XXX : Removing Area Power. */
/* TODO : put this out of the shader. */
float falloff;
if (ld.l_type == AREA) {
vis *= 0.0962 * (ld.l_sizex * ld.l_sizey * 4.0 * M_PI);
vis /= (l_vector.w * l_vector.w);
falloff = dot(N, l_vector.xyz / l_vector.w);
}
else if (ld.l_type == SUN) {
falloff = dot(N, -ld.l_forward);
}
else {
vis *= 0.0248 * (4.0 * ld.l_radius * ld.l_radius * M_PI * M_PI);
vis /= (l_vector.w * l_vector.w);
falloff = dot(N, l_vector.xyz / l_vector.w);
}
vis *= M_1_PI; /* Normalize */
/* Applying profile */
vis *= sss_profile(abs(delta) / scale);
/* No transmittance at grazing angle (hide artifacts) */
vis *= saturate(falloff * 2.0);
if (ld.l_type == SPOT) {
float z = dot(ld.l_forward, l_vector.xyz);
vec3 lL = l_vector.xyz / z;
float x = dot(ld.l_right, lL) / ld.l_sizex;
float y = dot(ld.l_up, lL) / ld.l_sizey;
float ellipse = 1.0 / sqrt(1.0 + x * x + y * y);
float spotmask = smoothstep(0.0, 1.0, (ellipse - ld.l_spot_size) / ld.l_spot_blend);
vis *= spotmask;
vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward));
}
else if (ld.l_type == AREA) {
vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward));
}
}
else {
vis = vec3(0.0);
}
return vis;
}
#ifdef HAIR_SHADER
void light_hair_common(
LightData ld, vec3 N, vec3 V, vec4 l_vector, vec3 norm_view,

View File

@ -810,3 +810,61 @@ vec3 eevee_surface_glass(vec3 N, vec3 transmission_col, float roughness, float i
return out_light;
}
/* ----------- Translucency ----------- */
vec3 eevee_surface_translucent_lit(vec3 N, vec3 albedo, float sss_scale)
{
#ifndef USE_TRANSLUCENCY
return vec3(0.0);
#endif
vec3 V = cameraVec;
/* Zero length vectors cause issues, see: T51979. */
#if 0
N = normalize(N);
#else
{
float len = length(N);
if (isnan(len)) {
return vec3(0.0);
}
N /= len;
}
#endif
/* We only enlit the backfaces */
N = -N;
/* ---------------- SCENE LAMPS LIGHTING ----------------- */
#ifdef HAIR_SHADER
vec3 norm_view = cross(V, N);
norm_view = normalize(cross(norm_view, N)); /* Normal facing view */
#endif
vec3 diff = vec3(0.0);
for (int i = 0; i < MAX_LIGHT && i < light_count; ++i) {
LightData ld = lights_data[i];
vec4 l_vector; /* Non-Normalized Light Vector with length in last component. */
l_vector.xyz = ld.l_position - worldPosition;
l_vector.w = length(l_vector.xyz);
#ifdef HAIR_SHADER
vec3 norm_lamp, view_vec;
float occlu_trans, occlu;
light_hair_common(ld, N, V, l_vector, norm_view, occlu_trans, occlu, norm_lamp, view_vec);
diff += ld.l_color * light_translucent(ld, worldPosition, norm_lamp, l_vector, sss_scale) * occlu_trans;
#else
diff += ld.l_color * light_translucent(ld, worldPosition, N, l_vector, sss_scale);
#endif
}
/* Accumulate outgoing radiance */
vec3 out_light = diff * albedo;
return out_light;
}

View File

@ -235,8 +235,9 @@ void GPU_material_enable_alpha(GPUMaterial *material);
GPUBuiltin GPU_get_material_builtins(GPUMaterial *material);
GPUBlendMode GPU_material_alpha_blend(GPUMaterial *material, float obcol[4]);
void GPU_material_sss_profile_create(GPUMaterial *material, float *radii, short int *falloff_type, float *sharpness);
struct GPUUniformBuffer *GPU_material_sss_profile_get(GPUMaterial *material, int sample_ct);
void GPU_material_sss_profile_create(GPUMaterial *material, float *radii, short *falloff_type, float *sharpness);
struct GPUUniformBuffer *GPU_material_sss_profile_get(
GPUMaterial *material, int sample_ct, struct GPUTexture **tex_profile);
/* High level functions to create and use GPU materials */
GPUMaterial *GPU_material_world(struct Scene *scene, struct World *wo);

View File

@ -45,6 +45,7 @@
#include "BLI_math.h"
#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
#include "BLI_rand.h"
#include "BKE_anim.h"
#include "BKE_colortools.h"
@ -143,6 +144,7 @@ struct GPUMaterial {
GPUUniformBuffer *ubo; /* UBOs for shader uniforms. */
GPUUniformBuffer *sss_profile; /* UBO containing SSS profile. */
GPUTexture *sss_tex_profile; /* Texture containing SSS profile. */
float *sss_radii; /* UBO containing SSS profile. */
int sss_samples;
short int *sss_falloff;
@ -275,6 +277,10 @@ void GPU_material_free(ListBase *gpumaterial)
GPU_uniformbuffer_free(material->ubo);
}
if (material->sss_tex_profile != NULL) {
GPU_texture_free(material->sss_tex_profile);
}
if (material->sss_profile != NULL) {
GPU_uniformbuffer_free(material->sss_profile);
}
@ -493,52 +499,35 @@ void GPU_material_uniform_buffer_tag_dirty(ListBase *gpumaterials)
typedef struct GPUSssKernelData {
float kernel[SSS_SAMPLES][4];
float radii_n[3], max_radius;
float param[3], max_radius;
int samples;
} GPUSssKernelData;
static void sss_calculate_offsets(GPUSssKernelData *kd, int count)
static void sss_calculate_offsets(GPUSssKernelData *kd, int count, float exponent)
{
float step = 2.0f / (float)(count - 1);
for (int i = 0; i < count; i++) {
float o = ((float)i) * step - 1.0f;
float sign = (o < 0.0f) ? -1.0f : 1.0f;
float ofs = sign * fabsf(powf(o, SSS_EXPONENT));
float ofs = sign * fabsf(powf(o, exponent));
kd->kernel[i][3] = ofs;
}
}
static float error_function(float x) {
/* Approximation of the error function by Abramowitz and Stegun
* https://en.wikipedia.org/wiki/Error_function#Approximation_with_elementary_functions */
const float a1 = 0.254829592f;
const float a2 = -0.284496736f;
const float a3 = 1.421413741f;
const float a4 = -1.453152027f;
const float a5 = 1.061405429f;
const float p = 0.3275911f;
#define GAUSS_TRUNCATE 12.46f
static float gaussian_profile(float r, float radius)
{
const float v = radius * radius * (0.25f * 0.25f);
const float Rm = sqrtf(v * GAUSS_TRUNCATE);
float sign = (x < 0.0f) ? -1.0f : 1.0f;
x = fabsf(x);
if(r >= Rm)
return 0.0f;
float t = 1.0f / (1.0f + p * x);
float y = 1.0f - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * expf(-(x * x));
return sign * y;
return expf(-r * r / (2.0f * v)) / (2.0f * M_PI * v);
}
static float gaussian_primitive(float x) {
const float sigma = 0.3f; /* Contained mostly between -1..1 */
return 0.5f * error_function(x / ((float)M_SQRT2 * sigma));
}
static float gaussian_integral(float x0, float x1) {
return gaussian_primitive(x1) - gaussian_primitive(x0);
}
/* Resolution for each sample of the precomputed kernel profile */
#define INTEGRAL_RESOLUTION 32
#define BURLEY_TRUNCATE 16.0f
#define BURLEY_TRUNCATE_CDF 0.9963790093708328f // cdf(BURLEY_TRUNCATE)
static float burley_profile(float r, float d)
{
float exp_r_3_d = expf(-r / (3.0f * d));
@ -546,21 +535,6 @@ static float burley_profile(float r, float d)
return (exp_r_d + exp_r_3_d) / (4.0f * d);
}
static float burley_integral(float x0, float x1, float d)
{
const float range = x1 - x0;
const float step = range / INTEGRAL_RESOLUTION;
float integral = 0.0f;
for(int i = 0; i < INTEGRAL_RESOLUTION; ++i) {
float x = x0 + range * ((float)i + 0.5f) / (float)INTEGRAL_RESOLUTION;
float y = burley_profile(fabsf(x), d);
integral += y * step;
}
return integral;
}
static float cubic_profile(float r, float radius, float sharpness)
{
float Rm = radius * (1.0f + sharpness);
@ -583,7 +557,24 @@ static float cubic_profile(float r, float radius, float sharpness)
return (10.0f * num) / (Rmy5 * M_PI);
}
static float cubic_integral(float x0, float x1, float radius, float sharpness)
static float eval_profile(float r, short falloff_type, float sharpness, float param)
{
r = fabsf(r);
if (falloff_type == SHD_SUBSURFACE_BURLEY) {
return burley_profile(r, param) / BURLEY_TRUNCATE_CDF;
}
else if (falloff_type == SHD_SUBSURFACE_CUBIC) {
return cubic_profile(r, param, sharpness);
}
else {
return gaussian_profile(r, param);
}
}
/* Resolution for each sample of the precomputed kernel profile */
#define INTEGRAL_RESOLUTION 32
static float eval_integral(float x0, float x1, short falloff_type, float sharpness, float param)
{
const float range = x1 - x0;
const float step = range / INTEGRAL_RESOLUTION;
@ -591,46 +582,50 @@ static float cubic_integral(float x0, float x1, float radius, float sharpness)
for(int i = 0; i < INTEGRAL_RESOLUTION; ++i) {
float x = x0 + range * ((float)i + 0.5f) / (float)INTEGRAL_RESOLUTION;
float y = cubic_profile(fabsf(x), radius, sharpness);
float y = eval_profile(x, falloff_type, sharpness, param);
integral += y * step;
}
return integral;
}
#undef INTEGRAL_RESOLUTION
static void compute_sss_kernel(GPUSssKernelData *kd, float *radii, int sample_ct, int falloff_type, float sharpness)
static void compute_sss_kernel(
GPUSssKernelData *kd, float *radii, int sample_ct, int falloff_type, float sharpness)
{
for (int i = 0; i < 3; ++i) {
/* Minimum radius */
kd->radii_n[i] = MAX2(radii[i], 1e-15f);
}
float rad[3];
/* Minimum radius */
rad[0] = MAX2(radii[0], 1e-15f);
rad[1] = MAX2(radii[1], 1e-15f);
rad[2] = MAX2(radii[2], 1e-15f);
/* Christensen-Burley fitting */
float l[3], d[3];
if (falloff_type == SHD_SUBSURFACE_BURLEY) {
mul_v3_v3fl(l, kd->radii_n, 0.25f * M_1_PI);
mul_v3_v3fl(l, rad, 0.25f * M_1_PI);
const float A = 1.0f;
const float s = 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f);
/* XXX 0.6f Out of nowhere to match cycles! Empirical! Can be tweak better. */
mul_v3_v3fl(d, l, 0.6f / s);
mul_v3_v3fl(kd->radii_n, d, BURLEY_TRUNCATE);
mul_v3_v3fl(rad, d, BURLEY_TRUNCATE);
kd->max_radius = MAX3(rad[0], rad[1], rad[2]);
copy_v3_v3(kd->param, d);
}
else if (falloff_type == SHD_SUBSURFACE_CUBIC) {
/* XXX Black magic but it seems to fit. Maybe because we integrate -1..1 */
sharpness *= 0.5f;
copy_v3_v3(kd->param, rad);
mul_v3_fl(rad, 1.0f + sharpness);
kd->max_radius = MAX3(rad[0], rad[1], rad[2]);
}
else {
kd->max_radius = MAX3(rad[0], rad[1], rad[2]);
mul_v3_fl(kd->radii_n, 1.0f + sharpness);
copy_v3_v3(kd->param, rad);
}
/* Normalize size */
kd->max_radius = MAX3(kd->radii_n[0], kd->radii_n[1], kd->radii_n[2]);
kd->radii_n[0] /= kd->max_radius;
kd->radii_n[1] /= kd->max_radius;
kd->radii_n[2] /= kd->max_radius;
/* Compute samples locations on the 1d kernel [-1..1] */
sss_calculate_offsets(kd, sample_ct);
sss_calculate_offsets(kd, sample_ct, SSS_EXPONENT);
/* Weights sum for normalization */
float sum[3] = {0.0f, 0.0f, 0.0f};
@ -653,25 +648,12 @@ static void compute_sss_kernel(GPUSssKernelData *kd, float *radii, int sample_ct
x1 = (kd->kernel[i][3] + kd->kernel[i + 1][3]) / 2.0f;
}
if (falloff_type == SHD_SUBSURFACE_BURLEY) {
x0 *= kd->max_radius;
x1 *= kd->max_radius;
kd->kernel[i][0] = burley_integral(x0, x1, d[0]);
kd->kernel[i][1] = burley_integral(x0, x1, d[1]);
kd->kernel[i][2] = burley_integral(x0, x1, d[2]);
}
else if (falloff_type == SHD_SUBSURFACE_CUBIC) {
x0 *= kd->max_radius;
x1 *= kd->max_radius;
kd->kernel[i][0] = cubic_integral(x0, x1, radii[0], sharpness);
kd->kernel[i][1] = cubic_integral(x0, x1, radii[1], sharpness);
kd->kernel[i][2] = cubic_integral(x0, x1, radii[2], sharpness);
}
else {
kd->kernel[i][0] = gaussian_integral(x0 / kd->radii_n[0], x1 / kd->radii_n[0]);
kd->kernel[i][1] = gaussian_integral(x0 / kd->radii_n[1], x1 / kd->radii_n[1]);
kd->kernel[i][2] = gaussian_integral(x0 / kd->radii_n[2], x1 / kd->radii_n[2]);
}
x0 *= kd->max_radius;
x1 *= kd->max_radius;
kd->kernel[i][0] = eval_integral(x0, x1, falloff_type, sharpness, kd->param[0]);
kd->kernel[i][1] = eval_integral(x0, x1, falloff_type, sharpness, kd->param[1]);
kd->kernel[i][2] = eval_integral(x0, x1, falloff_type, sharpness, kd->param[2]);
sum[0] += kd->kernel[i][0];
sum[1] += kd->kernel[i][1];
@ -682,7 +664,7 @@ static void compute_sss_kernel(GPUSssKernelData *kd, float *radii, int sample_ct
if (sum[i] > 0.0f) {
/* Normalize */
for (int j = 0; j < sample_ct; j++) {
kd->kernel[j][i] /= sum[i];
kd->kernel[j][i] /= sum[i];
}
}
else {
@ -698,9 +680,71 @@ static void compute_sss_kernel(GPUSssKernelData *kd, float *radii, int sample_ct
copy_v4_v4(kd->kernel[i], kd->kernel[i - 1]);
}
copy_v4_v4(kd->kernel[0], tmpv);
kd->samples = sample_ct;
}
void GPU_material_sss_profile_create(GPUMaterial *material, float *radii, short int *falloff_type, float *sharpness)
#define INTEGRAL_RESOLUTION 512
static void compute_sss_translucence_kernel(
const GPUSssKernelData *kd, int resolution, short falloff_type, float sharpness, float **output)
{
float (*texels)[4];
texels = MEM_callocN(sizeof(float) * 4 * resolution, "compute_sss_translucence_kernel");
*output = (float *)texels;
/* Last texel should be black, hence the - 1. */
for (int i = 0; i < resolution - 1; ++i) {
/* Distance from surface. */
float d = kd->max_radius * ((float)i + 0.00001f) / ((float)resolution);
/* For each distance d we compute the radiance incomming from an hypothetic parallel plane. */
/* Compute radius of the footprint on the hypothetic plane */
float r_fp = sqrtf(kd->max_radius * kd->max_radius - d * d);
float r_step = r_fp / INTEGRAL_RESOLUTION;
float area_accum = 0.0f;
for (float r = 0.0f; r < r_fp; r += r_step) {
/* Compute distance to the "shading" point through the medium. */
/* r_step * 0.5f to put sample between the area borders */
float dist = hypotf(r + r_step * 0.5f, d);
float profile[3];
profile[0] = eval_profile(dist, falloff_type, sharpness, kd->param[0]);
profile[1] = eval_profile(dist, falloff_type, sharpness, kd->param[1]);
profile[2] = eval_profile(dist, falloff_type, sharpness, kd->param[2]);
/* Since the profile and configuration are radially symetrical we
* can just evaluate it once and weight it accordingly */
float r_next = r + r_step;
float disk_area = (M_PI * r_next * r_next) - (M_PI * r * r);
mul_v3_fl(profile, disk_area);
add_v3_v3(texels[i], profile);
area_accum += disk_area;
}
/* Normalize over the disk. */
mul_v3_fl(texels[i], 1.0f / (area_accum));
}
/* Normalize */
for (int j = resolution - 2; j > 0; j--) {
texels[j][0] /= (texels[0][0] > 0.0f) ? texels[0][0] : 1.0f;
texels[j][1] /= (texels[0][1] > 0.0f) ? texels[0][1] : 1.0f;
texels[j][2] /= (texels[0][2] > 0.0f) ? texels[0][2] : 1.0f;
}
/* First texel should be white */
texels[0][0] = (texels[0][0] > 0.0f) ? 1.0f : 0.0f;
texels[0][1] = (texels[0][1] > 0.0f) ? 1.0f : 0.0f;
texels[0][2] = (texels[0][2] > 0.0f) ? 1.0f : 0.0f;
/* dim the last few texels for smoother transition */
mul_v3_fl(texels[resolution - 2], 0.25f);
mul_v3_fl(texels[resolution - 3], 0.5f);
mul_v3_fl(texels[resolution - 4], 0.75f);
}
#undef INTEGRAL_RESOLUTION
void GPU_material_sss_profile_create(GPUMaterial *material, float *radii, short *falloff_type, float *sharpness)
{
material->sss_radii = radii;
material->sss_falloff = falloff_type;
@ -713,10 +757,7 @@ void GPU_material_sss_profile_create(GPUMaterial *material, float *radii, short
}
}
#undef SSS_EXPONENT
#undef SSS_SAMPLES
struct GPUUniformBuffer *GPU_material_sss_profile_get(GPUMaterial *material, int sample_ct)
struct GPUUniformBuffer *GPU_material_sss_profile_get(GPUMaterial *material, int sample_ct, GPUTexture **tex_profile)
{
if (material->sss_radii == NULL)
return NULL;
@ -726,17 +767,39 @@ struct GPUUniformBuffer *GPU_material_sss_profile_get(GPUMaterial *material, int
float sharpness = (material->sss_sharpness != NULL) ? *material->sss_sharpness : 0.0f;
/* XXX Black magic but it seems to fit. Maybe because we integrate -1..1 */
sharpness *= 0.5f;
compute_sss_kernel(&kd, material->sss_radii, sample_ct, *material->sss_falloff, sharpness);
/* Update / Create UBO */
GPU_uniformbuffer_update(material->sss_profile, &kd);
/* Update / Create Tex */
float *translucence_profile;
compute_sss_translucence_kernel(&kd, 64, *material->sss_falloff, sharpness, &translucence_profile);
if (material->sss_tex_profile != NULL) {
GPU_texture_free(material->sss_tex_profile);
}
material->sss_tex_profile = GPU_texture_create_1D_custom(64, 4, GPU_RGBA16F, translucence_profile, NULL);
MEM_freeN(translucence_profile);
material->sss_samples = sample_ct;
material->sss_dirty = false;
}
if (tex_profile != NULL) {
*tex_profile = material->sss_tex_profile;
}
return material->sss_profile;
}
#undef SSS_EXPONENT
#undef SSS_SAMPLES
void GPU_material_vertex_attributes(GPUMaterial *material, GPUVertexAttribs *attribs)
{
*attribs = material->attribs;

View File

@ -2880,7 +2880,9 @@ void node_bsdf_principled_simple(vec4 base_color, float subsurface, vec3 subsurf
#ifdef USE_SSS
/* OPTI : Make irradiance computation shared with the diffuse. */
result.sss_data.rgb = eevee_surface_diffuse_lit(N, vec3(1.0), 1.0) * mix(vec3(0.0), subsurface_color.rgb, subsurface);
result.sss_data.rgb = eevee_surface_diffuse_lit(N, vec3(1.0), 1.0);
result.sss_data.rgb += eevee_surface_translucent_lit(N, vec3(1.0), 1.0);
result.sss_data.rgb *= mix(vec3(0.0), subsurface_color.rgb, subsurface);
result.sss_data.a = 1.0; /* TODO Find a parametrization */
#endif
#else
@ -2951,7 +2953,9 @@ void node_bsdf_principled_clearcoat(vec4 base_color, float subsurface, vec3 subs
#ifdef USE_SSS
/* OPTI : Make irradiance computation shared with the diffuse. */
result.sss_data.rgb = eevee_surface_diffuse_lit(N, vec3(1.0), 1.0) * mix(vec3(0.0), subsurface_color.rgb, subsurface);
result.sss_data.rgb = eevee_surface_translucent_lit(N, subsurface_color.rgb, 1.0);
result.sss_data.rgb += eevee_surface_diffuse_lit(N, vec3(1.0), 1.0);
result.sss_data.rgb *= mix(vec3(0.0), subsurface_color.rgb, subsurface);
result.sss_data.a = 1.0; /* TODO Find a parametrization */
#endif
@ -2995,7 +2999,8 @@ void node_subsurface_scattering(
result.ssr_data = vec4(0.0);
result.ssr_normal = normal_encode(vN, viewCameraVec);
result.ssr_id = -1;
result.sss_data.rgb = eevee_surface_diffuse_lit(N, vec3(1.0), 1.0) * color.rgb;
result.sss_data.rgb = eevee_surface_translucent_lit(N, color.rgb, scale);
result.sss_data.rgb += eevee_surface_diffuse_lit(N, color.rgb, 1.0);
result.sss_data.a = scale;
#else
node_bsdf_diffuse(color, 0.0, N, result);

View File

@ -514,6 +514,7 @@ enum {
MA_BL_HIDE_BACKSIDE = (1 << 0),
MA_BL_SS_REFRACTION = (1 << 1),
MA_BL_SS_SUBSURFACE = (1 << 2),
MA_BL_TRANSLUCENCY = (1 << 3),
};
/* blend_shadow */

View File

@ -1882,6 +1882,11 @@ void RNA_def_material(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Screen Space Subsurface Scattering", "Use post process subsurface scattering");
RNA_def_property_update(prop, 0, "rna_Material_draw_update");
prop = RNA_def_property(srna, "use_sss_translucency", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "blend_flag", MA_BL_TRANSLUCENCY);
RNA_def_property_ui_text(prop, "Subsurface Translucency", "Add translucency effect to subsurface");
RNA_def_property_update(prop, 0, "rna_Material_draw_update");
prop = RNA_def_property(srna, "refraction_depth", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, NULL, "refract_depth");
RNA_def_property_range(prop, 0.0f, FLT_MAX);