Eevee: Add support for Alpha clip and Hashed Alpha transparency.
authorClément Foucault <foucault.clem@gmail.com>
Sun, 9 Jul 2017 10:01:29 +0000 (12:01 +0200)
committerClément Foucault <foucault.clem@gmail.com>
Tue, 11 Jul 2017 10:39:35 +0000 (12:39 +0200)
Hashed Alpha transparency offers a noisy output but has the benefit of being correctly ordered. Noise can be attenuated with Multisampling / AntiAliasing.

release/scripts/startup/bl_ui/properties_material.py
source/blender/blenkernel/intern/material.c
source/blender/draw/engines/eevee/eevee_engine.c
source/blender/draw/engines/eevee/eevee_materials.c
source/blender/draw/engines/eevee/eevee_private.h
source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl
source/blender/draw/engines/eevee/shaders/prepass_frag.glsl
source/blender/draw/engines/eevee/shaders/volumetric_frag.glsl
source/blender/gpu/shaders/gpu_shader_material.glsl
source/blender/makesdna/DNA_material_types.h
source/blender/makesrna/intern/rna_material.c

index 2991dc110eec9d567f06a4e32776a8704a134230..f008254d9733c02d56ab28d07027e5a58afc8422 100644 (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.
index 824151f9c9854a432ade5d4a707cdea8060e2807..e7232322a3602d82dcd6b738d682a056ca258f9d 100644 (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)
index 9453cdb0b80506169b3fe9c5fb82018dc2ce86bf..3475bbb7fad0715995c898db55e6e38c44baddca 100644 (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);
index 4cc8b5ed93ca00fbadc0b7251e6d5aa497333348..151bd05358dab7da14fd57ea15be4444a082b11e 100644 (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]);
                        }
                }
        }
index 7f45e72c71301ec2b946ea639125ad7823d07001..92a4df2771892a272897704f10315053a73f4bc9 100644 (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);
index b15c3b6d452da33c475d480554ac0c62d2442535..edf13b911dc037c1b10ee58ecdd1370b11105ee5 100644 (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 */
index 6317dcea0e99d10af5550c82b4843b0290006c52..f921d56e3bc4850f01900fb9befa12a28f2cafbe 100644 (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
 }
index a99769c7fec853f5c077f37b30e54bbdc13437bc..7f44dd53163a58e110d86478e6a6973ebf9ca8af 100644 (file)
@@ -1,6 +1,8 @@
 
 #ifdef VOLUMETRICS
 
+#define NODETREE_EXEC
+
 #define VOLUMETRIC_INTEGRATION_MAX_STEP 256
 #define VOLUMETRIC_SHADOW_MAX_STEP 128
 
index e9f7fb338c9488419ad939f562c6dc953fecba1f..3235dad8d049177a4685fd492d0ef1ac1d73d1a8 100644 (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 */
index 09a4e42cb8f99956e05e35c03dcbb77a7f43d035..fc0f4df4170e243b3473bc1230af702a77f796dc 100644 (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
 
index e60832d86e6c0311b202b46bbe7642a515dda06a..a3c1ae7378eb2d2e9980173c023a62cf2fcc49f4 100644 (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");