diff options
14 files changed, 394 insertions, 106 deletions
diff --git a/release/scripts/startup/bl_ui/properties_material.py b/release/scripts/startup/bl_ui/properties_material.py index b914bafaf3f..9aed338bad4 100644 --- a/release/scripts/startup/bl_ui/properties_material.py +++ b/release/scripts/startup/bl_ui/properties_material.py @@ -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 = ( diff --git a/source/blender/draw/engines/eevee/eevee_lights.c b/source/blender/draw/engines/eevee/eevee_lights.c index 09a090955c2..17295713001 100644 --- a/source/blender/draw/engines/eevee/eevee_lights.c +++ b/source/blender/draw/engines/eevee/eevee_lights.c @@ -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; diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c index dbd953553cd..4a9633b4f44 100644 --- a/source/blender/draw/engines/eevee/eevee_materials.c +++ b/source/blender/draw/engines/eevee/eevee_materials.c @@ -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) { diff --git a/source/blender/draw/engines/eevee/eevee_private.h b/source/blender/draw/engines/eevee/eevee_private.h index b1ed108bad0..6a3adb36a7c 100644 --- a/source/blender/draw/engines/eevee/eevee_private.h +++ b/source/blender/draw/engines/eevee/eevee_private.h @@ -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); diff --git a/source/blender/draw/engines/eevee/eevee_subsurface.c b/source/blender/draw/engines/eevee/eevee_subsurface.c index 6827d44aea4..fe6e77ddf29 100644 --- a/source/blender/draw/engines/eevee/eevee_subsurface.c +++ b/source/blender/draw/engines/eevee/eevee_subsurface.c @@ -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); diff --git a/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl b/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl index bd6c1923bfe..32d27838da3 100644 --- a/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl @@ -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 diff --git a/source/blender/draw/engines/eevee/shaders/effect_subsurface_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_subsurface_frag.glsl index 9ee713ab483..ea9711a6cad 100644 --- a/source/blender/draw/engines/eevee/shaders/effect_subsurface_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/effect_subsurface_frag.glsl @@ -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; diff --git a/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl b/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl index 5ca40cd06a2..c2e3f81aefd 100644 --- a/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/lamps_lib.glsl @@ -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, diff --git a/source/blender/draw/engines/eevee/shaders/lit_surface_frag.glsl b/source/blender/draw/engines/eevee/shaders/lit_surface_frag.glsl index f63a9665810..44410be700b 100644 --- a/source/blender/draw/engines/eevee/shaders/lit_surface_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/lit_surface_frag.glsl @@ -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; +} diff --git a/source/blender/gpu/GPU_material.h b/source/blender/gpu/GPU_material.h index aeb268cef36..71bf6c897e7 100644 --- a/source/blender/gpu/GPU_material.h +++ b/source/blender/gpu/GPU_material.h @@ -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); diff --git a/source/blender/gpu/intern/gpu_material.c b/source/blender/gpu/intern/gpu_material.c index 728e0033660..c30a28616e0 100644 --- a/source/blender/gpu/intern/gpu_material.c +++ b/source/blender/gpu/intern/gpu_material.c @@ -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; - - float sign = (x < 0.0f) ? -1.0f : 1.0f; - x = fabsf(x); - - 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; -} +#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); -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)); -} + if(r >= Rm) + return 0.0f; -static float gaussian_integral(float x0, float x1) { - return gaussian_primitive(x1) - gaussian_primitive(x0); + return expf(-r * r / (2.0f * v)) / (2.0f * M_PI * v); } -/* 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; - - mul_v3_fl(kd->radii_n, 1.0f + sharpness); + 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]); - /* 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; + copy_v3_v3(kd->param, rad); + } /* 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; +} + +#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 int *falloff_type, float *sharpness) +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; diff --git a/source/blender/gpu/shaders/gpu_shader_material.glsl b/source/blender/gpu/shaders/gpu_shader_material.glsl index 918ae2f6f92..4aaaab2c5ad 100644 --- a/source/blender/gpu/shaders/gpu_shader_material.glsl +++ b/source/blender/gpu/shaders/gpu_shader_material.glsl @@ -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); diff --git a/source/blender/makesdna/DNA_material_types.h b/source/blender/makesdna/DNA_material_types.h index 34b9f9f83bf..bc33b7ba6e2 100644 --- a/source/blender/makesdna/DNA_material_types.h +++ b/source/blender/makesdna/DNA_material_types.h @@ -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 */ diff --git a/source/blender/makesrna/intern/rna_material.c b/source/blender/makesrna/intern/rna_material.c index a78ad4262ce..a2845b3a2f8 100644 --- a/source/blender/makesrna/intern/rna_material.c +++ b/source/blender/makesrna/intern/rna_material.c @@ -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); |