Sculpt: Sharpen Mesh Filter

This mesh filter sharpens and smooths the mesh based on its curvature,
resulting in pinching hard edges and polishing flat surfaces. It fixes
most of the artifacts of the voxel remesher and those produced when
sculpting hard surfaces and stylized models with creasing and flattening
brushes.

It needs and accumulate_displacement step before each filter iteration which
can't be multithreaded in an easy way (it would need something to sync the
threads when modifying the data of neighbors in a different node), but this
does not affect performance in a significant way.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D7335
This commit is contained in:
Pablo Dobarro 2020-04-05 02:00:50 +02:00
parent f881ff129f
commit 7dd8c889f1
3 changed files with 98 additions and 4 deletions

View File

@ -1034,6 +1034,8 @@ class _defs_sculpt:
if (props.type == "SURFACE_SMOOTH"):
layout.prop(props, "surface_smooth_shape_preservation", expand=False)
layout.prop(props, "surface_smooth_current_vertex", expand=False)
if (props.type == "SHARPEN"):
layout.prop(props, "sharpen_smooth_ratio", expand=False)
return dict(
idname="builtin.mesh_filter",

View File

@ -128,6 +128,8 @@ void SCULPT_filter_cache_free(SculptSession *ss)
MEM_SAFE_FREE(ss->filter_cache->prev_face_set);
MEM_SAFE_FREE(ss->filter_cache->automask);
MEM_SAFE_FREE(ss->filter_cache->surface_smooth_laplacian_disp);
MEM_SAFE_FREE(ss->filter_cache->sharpen_factor);
MEM_SAFE_FREE(ss->filter_cache->accum_disp);
MEM_SAFE_FREE(ss->filter_cache);
}
@ -140,6 +142,7 @@ typedef enum eSculptMeshFilterTypes {
MESH_FILTER_RELAX = 5,
MESH_FILTER_RELAX_FACE_SETS = 6,
MESH_FILTER_SURFACE_SMOOTH = 7,
MESH_FILTER_SHARPEN = 8,
} eSculptMeshFilterTypes;
static EnumPropertyItem prop_mesh_filter_types[] = {
@ -159,6 +162,7 @@ static EnumPropertyItem prop_mesh_filter_types[] = {
0,
"Surface Smooth",
"Smooth the surface of the mesh, preserving the volume"},
{MESH_FILTER_SHARPEN, "SHARPEN", 0, "Sharpen", "Sharpen the cavities of the mesh"},
{0, NULL, 0, NULL, NULL},
};
@ -181,7 +185,8 @@ static bool sculpt_mesh_filter_needs_pmap(int filter_type, bool use_face_sets)
MESH_FILTER_SMOOTH,
MESH_FILTER_RELAX,
MESH_FILTER_RELAX_FACE_SETS,
MESH_FILTER_SURFACE_SMOOTH);
MESH_FILTER_SURFACE_SMOOTH,
MESH_FILTER_SHARPEN);
}
static void mesh_filter_task_cb(void *__restrict userdata,
@ -324,6 +329,26 @@ static void mesh_filter_task_cb(void *__restrict userdata,
ss->filter_cache->surface_smooth_shape_preservation);
break;
}
case MESH_FILTER_SHARPEN: {
const float smooth_ratio = ss->filter_cache->sharpen_smooth_ratio;
/* This filter can't work at full strength as it needs multiple iterations to reach a
* stable state. */
fade = clamp_f(fade, 0.0f, 0.5f);
float disp_sharpen[3];
copy_v3_v3(disp_sharpen, ss->filter_cache->accum_disp[vd.index]);
mul_v3_fl(disp_sharpen, 1.0f - ss->filter_cache->sharpen_factor[vd.index]);
float disp_avg[3];
float avg_co[3];
SCULPT_neighbor_coords_average(ss, avg_co, vd.index);
sub_v3_v3v3(disp_avg, avg_co, vd.co);
mul_v3_v3fl(
disp_avg, disp_avg, smooth_ratio * pow2f(ss->filter_cache->sharpen_factor[vd.index]));
add_v3_v3v3(disp, disp_avg, disp_sharpen);
break;
}
}
for (int it = 0; it < 3; it++) {
@ -332,7 +357,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
}
}
if (filter_type == MESH_FILTER_SURFACE_SMOOTH) {
if (ELEM(filter_type, MESH_FILTER_SURFACE_SMOOTH, MESH_FILTER_SHARPEN)) {
madd_v3_v3v3fl(final_pos, vd.co, disp, clamp_f(fade, 0.0f, 1.0f));
}
else {
@ -348,6 +373,46 @@ static void mesh_filter_task_cb(void *__restrict userdata,
BKE_pbvh_node_mark_update(node);
}
static void mesh_filter_sharpen_init_factors(SculptSession *ss)
{
const int totvert = SCULPT_vertex_count_get(ss);
for (int i = 0; i < totvert; i++) {
float avg[3];
SCULPT_neighbor_coords_average(ss, avg, i);
ss->filter_cache->sharpen_factor[i] = len_v3v3(avg, SCULPT_vertex_co_get(ss, i));
}
float max_factor = 0.0f;
for (int i = 0; i < totvert; i++) {
if (ss->filter_cache->sharpen_factor[i] > max_factor) {
max_factor = ss->filter_cache->sharpen_factor[i];
}
}
max_factor = 1.0f / max_factor;
for (int i = 0; i < totvert; i++) {
ss->filter_cache->sharpen_factor[i] *= max_factor;
ss->filter_cache->sharpen_factor[i] = 1.0f - pow2f(1.0f - ss->filter_cache->sharpen_factor[i]);
}
}
static void mesh_filter_sharpen_accumulate_displacement(SculptSession *ss)
{
const int totvert = SCULPT_vertex_count_get(ss);
for (int i = 0; i < totvert; i++) {
zero_v3(ss->filter_cache->accum_disp[i]);
}
for (int i = 0; i < totvert; i++) {
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) {
float disp_n[3];
sub_v3_v3v3(disp_n, SCULPT_vertex_co_get(ss, i), SCULPT_vertex_co_get(ss, ni.index));
mul_v3_fl(disp_n, ss->filter_cache->sharpen_factor[i]);
add_v3_v3(ss->filter_cache->accum_disp[ni.index], disp_n);
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
}
}
static void mesh_filter_surface_smooth_displace_task_cb(
void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
{
@ -403,6 +468,10 @@ static int sculpt_mesh_filter_modal(bContext *C, wmOperator *op, const wmEvent *
bool needs_pmap = sculpt_mesh_filter_needs_pmap(filter_type, use_face_sets);
BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_pmap, false);
if (filter_type == MESH_FILTER_SHARPEN) {
mesh_filter_sharpen_accumulate_displacement(ss);
}
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
@ -470,6 +539,7 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
bool needs_pmap = sculpt_mesh_filter_needs_pmap(filter_type, use_face_sets);
BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_pmap, false);
const int totvert = SCULPT_vertex_count_get(ss);
if (BKE_pbvh_type(pbvh) == PBVH_FACES && needs_pmap && !ob->sculpt->pmap) {
return OPERATOR_CANCELLED;
}
@ -487,14 +557,22 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
}
if (RNA_enum_get(op->ptr, "type") == MESH_FILTER_SURFACE_SMOOTH) {
ss->filter_cache->surface_smooth_laplacian_disp = MEM_mallocN(
3 * sizeof(float) * SCULPT_vertex_count_get(ss), "surface smooth disp");
ss->filter_cache->surface_smooth_laplacian_disp = MEM_mallocN(3 * sizeof(float) * totvert,
"surface smooth disp");
ss->filter_cache->surface_smooth_shape_preservation = RNA_float_get(
op->ptr, "surface_smooth_shape_preservation");
ss->filter_cache->surface_smooth_current_vertex = RNA_float_get(
op->ptr, "surface_smooth_current_vertex");
}
if (RNA_enum_get(op->ptr, "type") == MESH_FILTER_SHARPEN) {
ss->filter_cache->sharpen_smooth_ratio = RNA_float_get(op->ptr, "sharpen_smooth_ratio");
ss->filter_cache->sharpen_factor = MEM_mallocN(sizeof(float) * totvert, "sharpen factor");
ss->filter_cache->accum_disp = MEM_mallocN(3 * sizeof(float) * totvert, "orco");
mesh_filter_sharpen_init_factors(ss);
}
ss->filter_cache->enabled_axis[0] = deform_axis & MESH_FILTER_DEFORM_X;
ss->filter_cache->enabled_axis[1] = deform_axis & MESH_FILTER_DEFORM_Y;
ss->filter_cache->enabled_axis[2] = deform_axis & MESH_FILTER_DEFORM_Z;
@ -568,4 +646,13 @@ void SCULPT_OT_mesh_filter(struct wmOperatorType *ot)
"How much the position of each individual vertex influences the final result",
0.0f,
1.0f);
RNA_def_float(ot->srna,
"sharpen_smooth_ratio",
0.35f,
0.0f,
1.0f,
"Smooth Ratio",
"How much smoothing is applied to polished surfaces",
0.0f,
1.0f);
}

View File

@ -818,6 +818,11 @@ typedef struct FilterCache {
float surface_smooth_shape_preservation;
float surface_smooth_current_vertex;
/* Sharpen mesh filter. */
float sharpen_smooth_ratio;
float *sharpen_factor;
float (*accum_disp)[3];
/* unmasked nodes */
PBVHNode **nodes;
int totnode;