Eevee: Add support for Alpha clip and Hashed Alpha transparency.

Hashed Alpha transparency offers a noisy output but has the benefit of being correctly ordered. Noise can be attenuated with Multisampling / AntiAliasing.
This commit is contained in:
Clément Foucault 2017-07-09 12:01:29 +02:00
parent e2c0197a96
commit 05bef13b53
11 changed files with 263 additions and 25 deletions

View File

@ -1155,6 +1155,26 @@ class EEVEE_MATERIAL_PT_surface(MaterialButtonsPanel, Panel):
layout.prop(raym, "gloss_factor", text="Roughness")
class EEVEE_MATERIAL_PT_options(MaterialButtonsPanel, Panel):
bl_label = "Options"
bl_context = "material"
COMPAT_ENGINES = {'BLENDER_EEVEE'}
@classmethod
def poll(cls, context):
engine = context.scene.render.engine
return context.material and (engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
mat = context.material
layout.prop(mat, "blend_method")
if mat.blend_method == "CLIP":
layout.prop(mat, "alpha_threshold")
classes = (
MATERIAL_MT_sss_presets,
MATERIAL_MT_specials,
@ -1185,6 +1205,7 @@ classes = (
MATERIAL_PT_custom_props,
EEVEE_MATERIAL_PT_context_material,
EEVEE_MATERIAL_PT_surface,
EEVEE_MATERIAL_PT_options,
)
if __name__ == "__main__": # only for live edit.

View File

@ -207,6 +207,8 @@ void BKE_material_init(Material *ma)
ma->mode2 = MA_CASTSHADOW;
ma->shade_flag = MA_APPROX_OCCLUSION;
ma->preview = NULL;
ma->alpha_threshold = 0.5f;
}
Material *BKE_material_add(Main *bmain, const char *name)

View File

@ -92,14 +92,12 @@ static void EEVEE_cache_populate(void *vedata, Object *ob)
}
}
struct Gwn_Batch *geom = DRW_cache_object_surface_get(ob);
if (geom) {
EEVEE_materials_cache_populate(vedata, sldata, ob, geom);
if (ELEM(ob->type, OB_MESH)) {
EEVEE_materials_cache_populate(vedata, sldata, ob);
const bool cast_shadow = true;
if (cast_shadow) {
EEVEE_lights_cache_shcaster_add(sldata, psl, geom, ob->obmat);
BLI_addtail(&sldata->shadow_casters, BLI_genericNodeN(ob));
EEVEE_ObjectEngineData *oedata = EEVEE_object_data_get(ob);
oedata->need_update = ((ob->deg_update_flag & DEG_RUNTIME_DATA_UPDATE) != 0);

View File

@ -179,8 +179,6 @@ static char *eevee_get_defines(int options)
{
char *str = NULL;
BLI_assert(options < VAR_MAT_MAX);
DynStr *ds = BLI_dynstr_new();
BLI_dynstr_appendf(ds, SHADER_DEFINES);
@ -202,6 +200,12 @@ static char *eevee_get_defines(int options)
if ((options & VAR_MAT_BENT) != 0) {
BLI_dynstr_appendf(ds, "#define USE_BENT_NORMAL\n");
}
if ((options & VAR_MAT_CLIP) != 0) {
BLI_dynstr_appendf(ds, "#define USE_ALPHA_CLIP\n");
}
if ((options & VAR_MAT_HASH) != 0) {
BLI_dynstr_appendf(ds, "#define USE_ALPHA_HASH\n");
}
str = BLI_dynstr_get_cstring(ds);
BLI_dynstr_free(ds);
@ -491,6 +495,42 @@ struct GPUMaterial *EEVEE_material_mesh_get(
return mat;
}
struct GPUMaterial *EEVEE_material_mesh_depth_get(struct Scene *scene, Material *ma, bool use_hashed_alpha)
{
const void *engine = &DRW_engine_viewport_eevee_type;
int options = VAR_MAT_MESH;
if (use_hashed_alpha) {
options |= VAR_MAT_HASH;
}
else {
options |= VAR_MAT_CLIP;
}
GPUMaterial *mat = GPU_material_from_nodetree_find(&ma->gpumaterial, engine, options);
if (mat) {
return mat;
}
char *defines = eevee_get_defines(options);
DynStr *ds_frag = BLI_dynstr_new();
BLI_dynstr_append(ds_frag, e_data.frag_shader_lib);
BLI_dynstr_append(ds_frag, datatoc_prepass_frag_glsl);
char *frag_str = BLI_dynstr_get_cstring(ds_frag);
BLI_dynstr_free(ds_frag);
mat = GPU_material_from_nodetree(
scene, ma->nodetree, &ma->gpumaterial, engine, options,
datatoc_lit_surface_vert_glsl, NULL, frag_str,
defines);
MEM_freeN(frag_str);
MEM_freeN(defines);
return mat;
}
struct GPUMaterial *EEVEE_material_hair_get(
struct Scene *scene, Material *ma,
bool use_ao, bool use_bent_normals)
@ -637,7 +677,7 @@ void EEVEE_materials_cache_init(EEVEE_Data *vedata)
} \
} while (0)
void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_SceneLayerData *sldata, Object *ob, struct Gwn_Batch *geom)
void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_SceneLayerData *sldata, Object *ob)
{
EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
EEVEE_StorageList *stl = ((EEVEE_Data *)vedata)->stl;
@ -650,12 +690,6 @@ void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_SceneLayerData *sl
const bool is_sculpt_mode = is_active && (ob->mode & OB_MODE_SCULPT) != 0;
const bool is_default_mode_shader = is_sculpt_mode;
/* Depth Prepass */
DRWShadingGroup *depth_shgrp = do_cull ? stl->g_data->depth_shgrp_cull : stl->g_data->depth_shgrp;
DRWShadingGroup *depth_clip_shgrp = do_cull ? stl->g_data->depth_shgrp_clip_cull : stl->g_data->depth_shgrp_clip;
ADD_SHGROUP_CALL(depth_shgrp, ob, geom);
ADD_SHGROUP_CALL(depth_clip_shgrp, ob, geom);
/* First get materials for this mesh. */
if (ELEM(ob->type, OB_MESH)) {
const int materials_len = MAX2(1, (is_sculpt_mode ? 1 : ob->totcol));
@ -740,6 +774,47 @@ void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_SceneLayerData *sl
if (mat_geom) {
for (int i = 0; i < materials_len; ++i) {
ADD_SHGROUP_CALL(shgrp_array[i], ob, mat_geom[i]);
/* Depth Prepass */
DRWShadingGroup *depth_shgrp = NULL;
DRWShadingGroup *depth_clip_shgrp;
Material *ma = give_current_material(ob, i + 1);
if (ma != NULL && (ma->use_nodes && ma->nodetree)) {
if (ELEM(ma->blend_method, MA_BM_CLIP, MA_BM_HASHED)) {
Scene *scene = draw_ctx->scene;
DRWPass *depth_pass, *depth_clip_pass;
struct GPUMaterial *gpumat = EEVEE_material_mesh_depth_get(scene, ma, (ma->blend_method == MA_BM_HASHED));
depth_pass = do_cull ? psl->depth_pass_cull : psl->depth_pass;
depth_clip_pass = do_cull ? psl->depth_pass_clip_cull : psl->depth_pass_clip;
/* Use same shader for both. */
depth_shgrp = DRW_shgroup_material_create(gpumat, depth_pass);
depth_clip_shgrp = DRW_shgroup_material_create(gpumat, depth_clip_pass);
if (ma->blend_method == MA_BM_CLIP) {
DRW_shgroup_uniform_float(depth_shgrp, "alphaThreshold", &ma->alpha_threshold, 1);
DRW_shgroup_uniform_float(depth_clip_shgrp, "alphaThreshold", &ma->alpha_threshold, 1);
}
}
/* Shadow Pass */
/* TODO clipped shadow map */
EEVEE_lights_cache_shcaster_add(sldata, psl, mat_geom[i], ob->obmat);
}
if (depth_shgrp == NULL) {
depth_shgrp = do_cull ? stl->g_data->depth_shgrp_cull : stl->g_data->depth_shgrp;
depth_clip_shgrp = do_cull ? stl->g_data->depth_shgrp_clip_cull : stl->g_data->depth_shgrp_clip;
/* Shadow Pass */
EEVEE_lights_cache_shcaster_add(sldata, psl, mat_geom[i], ob->obmat);
}
ADD_SHGROUP_CALL(depth_shgrp, ob, mat_geom[i]);
ADD_SHGROUP_CALL(depth_clip_shgrp, ob, mat_geom[i]);
}
}
}

View File

@ -69,7 +69,11 @@ enum {
/* Max number of variation */
/* IMPORTANT : Leave it last and set
* it's value accordingly. */
VAR_MAT_MAX = (1 << 6)
VAR_MAT_MAX = (1 << 6),
/* These are options that are not counted in VAR_MAT_MAX
* because they are not cumulative with the others above. */
VAR_MAT_CLIP = (1 << 7),
VAR_MAT_HASH = (1 << 8),
};
typedef struct EEVEE_PassList {
@ -438,14 +442,14 @@ EEVEE_LampEngineData *EEVEE_lamp_data_get(Object *ob);
struct GPUTexture *EEVEE_materials_get_util_tex(void); /* XXX */
void EEVEE_materials_init(EEVEE_StorageList *stl);
void EEVEE_materials_cache_init(EEVEE_Data *vedata);
void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_SceneLayerData *sldata, Object *ob, struct Gwn_Batch *geom);
void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_SceneLayerData *sldata, Object *ob);
void EEVEE_materials_cache_finish(EEVEE_Data *vedata);
struct GPUMaterial *EEVEE_material_world_lightprobe_get(struct Scene *scene, struct World *wo);
struct GPUMaterial *EEVEE_material_world_background_get(struct Scene *scene, struct World *wo);
struct GPUMaterial *EEVEE_material_world_volume_get(
struct Scene *scene, struct World *wo, bool use_lights, bool use_volume_shadows, bool is_homogeneous, bool use_color_transmit);
struct GPUMaterial *EEVEE_material_mesh_lightprobe_get(struct Scene *scene, Material *ma);
struct GPUMaterial *EEVEE_material_mesh_get(struct Scene *scene, Material *ma, bool use_ao, bool use_bent_normals);
struct GPUMaterial *EEVEE_material_mesh_depth_get(struct Scene *scene, Material *ma, bool use_hashed_alpha);
struct GPUMaterial *EEVEE_material_hair_get(struct Scene *scene, Material *ma, bool use_ao, bool use_bent_normals);
void EEVEE_materials_free(void);
void EEVEE_draw_default_passes(EEVEE_PassList *psl);

View File

@ -19,8 +19,6 @@ uniform vec4 viewvecs[2];
/* ------- Structures -------- */
#ifdef VOLUMETRICS
#define NODETREE_EXEC
struct Closure {
vec3 absorption;
vec3 scatter;
@ -49,12 +47,35 @@ Closure closure_add(Closure cl1, Closure cl2)
cl.anisotropy = (cl1.anisotropy + cl2.anisotropy) / 2.0; /* Average phase (no multi lobe) */
return cl;
}
#else
Closure nodetree_exec(void); /* Prototype */
struct Closure {
vec3 radiance;
float opacity;
};
#define CLOSURE_DEFAULT Closure(vec3(0.0), 0.0)
Closure closure_mix(Closure cl1, Closure cl2, float fac)
{
Closure cl;
cl.radiance = mix(cl1.radiance, cl2.radiance, fac);
cl.opacity = mix(cl1.opacity, cl2.opacity, fac);
return cl;
}
Closure closure_add(Closure cl1, Closure cl2)
{
Closure cl;
cl.radiance = cl1.radiance + cl2.radiance;
cl.opacity = cl1.opacity + cl2.opacity;
return cl;
}
#endif /* VOLUMETRICS */
Closure nodetree_exec(void); /* Prototype */
struct LightData {
vec4 position_influence; /* w : InfluenceRadius */

View File

@ -1,7 +1,85 @@
#ifdef USE_ALPHA_HASH
/* From the paper "Hashed Alpha Testing" by Chris Wyman and Morgan McGuire */
float hash(vec2 a) {
return fract(1e4 * sin(17.0 * a.x + 0.1 * a.y) * (0.1 + abs(sin(13.0 * a.y + a.x))));
}
float hash3d(vec3 a) {
return hash(vec2(hash(a.xy), a.z));
}
//uniform float hashScale;
float hashScale = 0.001;
float hashed_alpha_threshold(vec3 co)
{
/* Find the discretized derivatives of our coordinates. */
float max_deriv = max(length(dFdx(co)), length(dFdy(co)));
float pix_scale = 1.0 / (hashScale * max_deriv);
/* Find two nearest log-discretized noise scales. */
float pix_scale_log = log2(pix_scale);
vec2 pix_scales;
pix_scales.x = exp2(floor(pix_scale_log));
pix_scales.y = exp2(ceil(pix_scale_log));
/* Compute alpha thresholds at our two noise scales. */
vec2 alpha;
alpha.x = hash3d(floor(pix_scales.x * co));
alpha.y = hash3d(floor(pix_scales.y * co));
/* Factor to interpolate lerp with. */
float fac = fract(log2(pix_scale));
/* Interpolate alpha threshold from noise at two scales. */
float x = mix(alpha.x, alpha.y, fac);
/* Pass into CDF to compute uniformly distrib threshold. */
float a = min(fac, 1.0 - fac);
float one_a = 1.0 - a;
float denom = 1.0 / (2 * a * one_a);
float one_x = (1 - x);
vec3 cases = vec3(
(x * x) * denom,
(x - 0.5 * a) / one_a,
1.0 - (one_x * one_x * denom)
);
/* Find our final, uniformly distributed alpha threshold. */
float threshold = (x < one_a) ? ((x < a) ? cases.x : cases.y) : cases.z;
/* Avoids threshold == 0. */
threshold = clamp(threshold, 1.0e-6, 1.0);
return threshold;
}
#endif
#ifdef USE_ALPHA_CLIP
uniform float alphaThreshold;
#endif
void main()
{
/* For now do nothing.
* In the future, output object motion blur.
* This pass could also be controlled but nodetree (pixel depth offset, stochastic transparency). */
* In the future, output object motion blur. */
#if defined(USE_ALPHA_HASH) || defined(USE_ALPHA_CLIP)
#define NODETREE_EXEC
Closure cl = nodetree_exec();
#if defined(USE_ALPHA_HASH)
/* Hashed Alpha Testing */
if (cl.opacity < hashed_alpha_threshold(worldPosition))
discard;
#elif defined(USE_ALPHA_CLIP)
/* Alpha clip */
if (cl.opacity <= alphaThreshold)
discard;
#endif
#endif
}

View File

@ -1,6 +1,8 @@
#ifdef VOLUMETRICS
#define NODETREE_EXEC
#define VOLUMETRIC_INTEGRATION_MAX_STEP 256
#define VOLUMETRIC_SHADOW_MAX_STEP 128

View File

@ -14,7 +14,7 @@ uniform vec4 CameraTexCoFactors;
/* Old glsl mode compat. */
#ifndef NODETREE_EXEC
#ifndef CLOSURE_DEFAULT
struct Closure {
vec3 radiance;
@ -41,7 +41,7 @@ Closure closure_add(Closure cl1, Closure cl2)
Closure nodetree_exec(void); /* Prototype */
#endif /* NODETREE_EXEC */
#endif /* CLOSURE_DEFAULT */
/* Converters */
@ -2890,7 +2890,7 @@ void node_bsdf_transparent(vec4 color, out Closure result)
{
/* this isn't right */
result.radiance = color.rgb;
result.opacity = 0.0;
result.opacity = color.a;
}
void node_bsdf_velvet(vec4 color, float sigma, vec3 N, out Closure result)
@ -4031,7 +4031,11 @@ void node_eevee_specular(
void node_output_eevee_material(Closure surface, out Closure result)
{
#if defined(USE_ALPHA_HASH) || defined(USE_ALPHA_CLIP)
result = surface;
#else
result = Closure(surface.radiance, length(viewPosition));
#endif
}
#endif /* EEVEE_ENGINE */

View File

@ -211,6 +211,10 @@ typedef struct Material {
char nmap_tangent_names[9][64]; /* [MAX_MTFACE+1][MAX_NAME]; +1 for empty name */
int nmap_tangent_names_count, pad5;
/* Transparency */
float alpha_threshold;
char blend_method, pad6[3];
/* image to use for image/uv space, also bake target
* (not to be used shading/rendering pipeline, this is editor featyure only!). */
struct Image *edit_image;
@ -491,5 +495,14 @@ typedef struct Material {
#define MA_VOL_SHADE_MULTIPLE 3
#define MA_VOL_SHADE_SHADEDPLUSMULTIPLE 4
/* blend_method */
enum {
MA_BM_SOLID,
MA_BM_ADD,
MA_BM_MULTIPLY,
MA_BM_CLIP,
MA_BM_HASHED,
};
#endif

View File

@ -1805,6 +1805,15 @@ void RNA_def_material(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL}
};
static EnumPropertyItem prop_eevee_blend_items[] = {
{MA_BM_SOLID, "OPAQUE", 0, "Opaque", "Render surface without transparency"},
// {MA_BM_ADD, "ADD", 0, "Additive", "Render surface and blend the result with additive blending"},
// {MA_BM_MULTIPLY, "MULTIPLY", 0, "Multiply", "Render surface and blend the result with multiplicative blending"},
{MA_BM_CLIP, "CLIP", 0, "Alpha Clip", "Use the alpha threshold to clip the visibility (binary visibility)"},
{MA_BM_HASHED, "HASHED", 0, "Alpha Hashed", "Use noise to dither the binary visibility (works well with multi-samples)"},
{0, NULL, 0, NULL, NULL}
};
srna = RNA_def_struct(brna, "Material", "ID");
RNA_def_struct_ui_text(srna, "Material",
"Material data-block to define the appearance of geometric objects for rendering");
@ -1827,7 +1836,18 @@ void RNA_def_material(BlenderRNA *brna)
RNA_def_property_enum_items(prop, transparency_items);
RNA_def_property_ui_text(prop, "Transparency Method", "Method to use for rendering transparency");
RNA_def_property_update(prop, 0, "rna_Material_update");
/* Blending (only Eevee for now) */
prop = RNA_def_property(srna, "blend_method", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, prop_eevee_blend_items);
RNA_def_property_ui_text(prop, "Blend Mode", "Blend Mode for Transparent Faces");
RNA_def_property_update(prop, 0, "rna_Material_draw_update");
prop = RNA_def_property(srna, "alpha_threshold", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_range(prop, 0, 1);
RNA_def_property_ui_text(prop, "Clip Threshold", "A pixel is rendered only if its alpha value is above this threshold");
RNA_def_property_update(prop, 0, "rna_Material_update");
/* For Preview Render */
prop = RNA_def_property(srna, "preview_render_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "pr_type");