diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index c912de8f3e4..63dd18cb703 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -1141,6 +1141,11 @@ def brush_texture_settings(layout, brush, sculpt): # texture_sample_bias layout.prop(brush, "texture_sample_bias", slider=True, text="Sample Bias") + if brush.sculpt_tool == 'DRAW': + col = layout.column() + col.active = tex_slot.map_mode == 'AREA_PLANE' + col.prop(brush, "use_color_as_displacement", text="Vector Displacement") + def brush_mask_texture_settings(layout, brush): mask_tex_slot = brush.mask_texture_slot diff --git a/source/blender/editors/sculpt_paint/paint_cursor.cc b/source/blender/editors/sculpt_paint/paint_cursor.cc index 11b8ae1b3fb..bf18bf1ddbe 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.cc +++ b/source/blender/editors/sculpt_paint/paint_cursor.cc @@ -37,6 +37,7 @@ #include "WM_api.h" #include "wm_cursors.h" +#include "IMB_colormanagement.h" #include "IMB_imbuf_types.h" #include "ED_image.h" @@ -200,10 +201,18 @@ static void load_tex_task_cb_ex(void *__restrict userdata, y = len * sinf(angle); } - if (col) { - float rgba[4]; + float avg; + float rgba[4]; + paint_get_tex_pixel(mtex, x, y, pool, thread_id, &avg, rgba); - paint_get_tex_pixel_col(mtex, x, y, rgba, pool, thread_id, convert_to_linear, colorspace); + if (col) { + if (convert_to_linear) { + IMB_colormanagement_colorspace_to_scene_linear_v3(rgba, colorspace); + } + + linearrgb_to_srgb_v3_v3(rgba, rgba); + + clamp_v4(rgba, 0.0f, 1.0f); buffer[index * 4] = rgba[0] * 255; buffer[index * 4 + 1] = rgba[1] * 255; @@ -211,8 +220,6 @@ static void load_tex_task_cb_ex(void *__restrict userdata, buffer[index * 4 + 3] = rgba[3] * 255; } else { - float avg = paint_get_tex_pixel(mtex, x, y, pool, thread_id); - avg += br->texture_sample_bias; /* Clamp to avoid precision overflow. */ diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 313941ee654..de4694ac34b 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -352,16 +352,17 @@ void paint_calc_redraw_planes(float planes[4][4], float paint_calc_object_space_radius(struct ViewContext *vc, const float center[3], float pixel_radius); -float paint_get_tex_pixel( - const struct MTex *mtex, float u, float v, struct ImagePool *pool, int thread); -void paint_get_tex_pixel_col(const struct MTex *mtex, - float u, - float v, - float rgba[4], - struct ImagePool *pool, - int thread, - bool convert, - struct ColorSpace *colorspace); + +/** + * Returns true when a color was sampled and false when a value was sampled. + */ +bool paint_get_tex_pixel(const struct MTex *mtex, + float u, + float v, + struct ImagePool *pool, + int thread, + float *r_intensity, + float r_rgba[4]); /** * Used for both 3D view and image window. diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c index 98fefd86d95..ef794ea6a39 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.c +++ b/source/blender/editors/sculpt_paint/paint_utils.c @@ -146,45 +146,29 @@ float paint_calc_object_space_radius(ViewContext *vc, const float center[3], flo return len_v3(delta) / scale; } -float paint_get_tex_pixel(const MTex *mtex, float u, float v, struct ImagePool *pool, int thread) -{ - float intensity; - float rgba_dummy[4]; - const float co[3] = {u, v, 0.0f}; - - RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba_dummy); - - return intensity; -} - -void paint_get_tex_pixel_col(const MTex *mtex, - float u, - float v, - float rgba[4], - struct ImagePool *pool, - int thread, - bool convert_to_linear, - struct ColorSpace *colorspace) +bool paint_get_tex_pixel(const MTex *mtex, + float u, + float v, + struct ImagePool *pool, + int thread, + /* Return arguments. */ + float *r_intensity, + float r_rgba[4]) { const float co[3] = {u, v, 0.0f}; float intensity; + const bool has_rgb = RE_texture_evaluate( + mtex, co, thread, pool, false, false, &intensity, r_rgba); + *r_intensity = intensity; - const bool hasrgb = RE_texture_evaluate(mtex, co, thread, pool, false, false, &intensity, rgba); - - if (!hasrgb) { - rgba[0] = intensity; - rgba[1] = intensity; - rgba[2] = intensity; - rgba[3] = 1.0f; + if (!has_rgb) { + r_rgba[0] = intensity; + r_rgba[1] = intensity; + r_rgba[2] = intensity; + r_rgba[3] = 1.0f; } - if (convert_to_linear) { - IMB_colormanagement_colorspace_to_scene_linear_v3(rgba, colorspace); - } - - linearrgb_to_srgb_v3_v3(rgba, rgba); - - clamp_v4(rgba, 0.0f, 1.0f); + return has_rgb; } void paint_stroke_operator_properties(wmOperatorType *ot) diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index 10ce391c397..d6065481a85 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -54,6 +54,7 @@ #include "BKE_scene.h" #include "BKE_subdiv_ccg.h" #include "BKE_subsurf.h" +#include "BLI_math_vector.hh" #include "NOD_texture.h" @@ -2555,80 +2556,12 @@ static float brush_strength(const Sculpt *sd, } } -float SCULPT_brush_strength_factor(SculptSession *ss, - const Brush *br, - const float brush_point[3], - float len, - const float vno[3], - const float fno[3], - float mask, - const PBVHVertRef vertex, - const int thread_id, - AutomaskingNodeData *automask_data) +static float sculpt_apply_hardness(const SculptSession *ss, const float input_len) { - StrokeCache *cache = ss->cache; - const Scene *scene = cache->vc->scene; - const MTex *mtex = BKE_brush_mask_texture_get(br, OB_MODE_SCULPT); - float avg = 1.0f; - float rgba[4]; - float point[3]; - - sub_v3_v3v3(point, brush_point, cache->plane_offset); - - if (!mtex->tex) { - avg = 1.0f; - } - else if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { - /* Get strength by feeding the vertex location directly into a texture. */ - avg = BKE_brush_sample_tex_3d(scene, br, mtex, point, rgba, 0, ss->tex_pool); - } - else { - float symm_point[3], point_2d[2]; - /* Quite warnings. */ - float x = 0.0f, y = 0.0f; - - /* If the active area is being applied for symmetry, flip it - * across the symmetry axis and rotate it back to the original - * position in order to project it. This insures that the - * brush texture will be oriented correctly. */ - if (cache->radial_symmetry_pass) { - mul_m4_v3(cache->symm_rot_mat_inv, point); - } - flip_v3_v3(symm_point, point, cache->mirror_symmetry_pass); - - ED_view3d_project_float_v2_m4(cache->vc->region, symm_point, point_2d, cache->projection_mat); - - /* Still no symmetry supported for other paint modes. - * Sculpt does it DIY. */ - if (mtex->brush_map_mode == MTEX_MAP_MODE_AREA) { - /* Similar to fixed mode, but projects from brush angle - * rather than view direction. */ - - mul_m4_v3(cache->brush_local_mat, symm_point); - - x = symm_point[0]; - y = symm_point[1]; - - x *= mtex->size[0]; - y *= mtex->size[1]; - - x += mtex->ofs[0]; - y += mtex->ofs[1]; - - avg = paint_get_tex_pixel(mtex, x, y, ss->tex_pool, thread_id); - - avg += br->texture_sample_bias; - } - else { - const float point_3d[3] = {point_2d[0], point_2d[1], 0.0f}; - avg = BKE_brush_sample_tex_3d(scene, br, mtex, point_3d, rgba, 0, ss->tex_pool); - } - } - - /* Hardness. */ - float final_len = len; + const StrokeCache *cache = ss->cache; + float final_len = input_len; const float hardness = cache->paint_brush.hardness; - float p = len / cache->radius; + float p = input_len / cache->radius; if (p < hardness) { final_len = 0.0f; } @@ -2640,9 +2573,100 @@ float SCULPT_brush_strength_factor(SculptSession *ss, final_len = p * cache->radius; } + return final_len; +} + +static void sculpt_apply_texture(const SculptSession *ss, + const Brush *brush, + const float brush_point[3], + const int thread_id, + float *r_value, + float r_rgba[4]) +{ + StrokeCache *cache = ss->cache; + const Scene *scene = cache->vc->scene; + const MTex *mtex = BKE_brush_mask_texture_get(brush, OB_MODE_SCULPT); + + if (!mtex->tex) { + *r_value = 1.0f; + copy_v4_fl(r_rgba, 1.0f); + return; + } + + float point[3]; + sub_v3_v3v3(point, brush_point, cache->plane_offset); + + if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { + /* Get strength by feeding the vertex location directly into a texture. */ + *r_value = BKE_brush_sample_tex_3d(scene, brush, mtex, point, r_rgba, 0, ss->tex_pool); + } + else { + float symm_point[3]; + + /* If the active area is being applied for symmetry, flip it + * across the symmetry axis and rotate it back to the original + * position in order to project it. This insures that the + * brush texture will be oriented correctly. */ + if (cache->radial_symmetry_pass) { + mul_m4_v3(cache->symm_rot_mat_inv, point); + } + flip_v3_v3(symm_point, point, cache->mirror_symmetry_pass); + + /* Still no symmetry supported for other paint modes. + * Sculpt does it DIY. */ + if (mtex->brush_map_mode == MTEX_MAP_MODE_AREA) { + /* Similar to fixed mode, but projects from brush angle + * rather than view direction. */ + + mul_m4_v3(cache->brush_local_mat, symm_point); + + float x = symm_point[0]; + float y = symm_point[1]; + + x *= mtex->size[0]; + y *= mtex->size[1]; + + x += mtex->ofs[0]; + y += mtex->ofs[1]; + + paint_get_tex_pixel(mtex, x, y, ss->tex_pool, thread_id, r_value, r_rgba); + + add_v3_fl(r_rgba, brush->texture_sample_bias); // v3 -> Ignore alpha + *r_value -= brush->texture_sample_bias; + } + else { + float point_2d[2]; + ED_view3d_project_float_v2_m4( + cache->vc->region, symm_point, point_2d, cache->projection_mat); + const float point_3d[3] = {point_2d[0], point_2d[1], 0.0f}; + *r_value = BKE_brush_sample_tex_3d(scene, brush, mtex, point_3d, r_rgba, 0, ss->tex_pool); + } + } +} + +float SCULPT_brush_strength_factor(SculptSession *ss, + const Brush *brush, + const float brush_point[3], + float len, + const float vno[3], + const float fno[3], + float mask, + const PBVHVertRef vertex, + int thread_id, + AutomaskingNodeData *automask_data) +{ + StrokeCache *cache = ss->cache; + + float avg = 1.0f; + float rgba[4]; + sculpt_apply_texture(ss, brush, brush_point, thread_id, &avg, rgba); + + /* Hardness. */ + const float final_len = sculpt_apply_hardness(ss, len); + /* Falloff curve. */ - avg *= BKE_brush_curve_strength(br, final_len, cache->radius); - avg *= frontface(br, cache->view_normal, vno, fno); + avg *= BKE_brush_curve_strength(brush, final_len, cache->radius); + avg *= frontface(brush, cache->view_normal, vno, fno); /* Paint mask. */ avg *= 1.0f - mask; @@ -2653,6 +2677,69 @@ float SCULPT_brush_strength_factor(SculptSession *ss, return avg; } +void SCULPT_brush_strength_color(SculptSession *ss, + const Brush *brush, + const float brush_point[3], + float len, + const float vno[3], + const float fno[3], + float mask, + const PBVHVertRef vertex, + int thread_id, + AutomaskingNodeData *automask_data, + float r_rgba[4]) +{ + StrokeCache *cache = ss->cache; + + float avg = 1.0f; + sculpt_apply_texture(ss, brush, brush_point, thread_id, &avg, r_rgba); + + /* Hardness. */ + const float final_len = sculpt_apply_hardness(ss, len); + + /* Falloff curve. */ + const float falloff = BKE_brush_curve_strength(brush, final_len, cache->radius) * + frontface(brush, cache->view_normal, vno, fno); + + /* Paint mask. */ + const float paint_mask = 1.0f - mask; + + /* Auto-masking. */ + const float automasking_factor = SCULPT_automasking_factor_get( + cache->automasking, ss, vertex, automask_data); + + const float masks_combined = falloff * paint_mask * automasking_factor; + + mul_v4_fl(r_rgba, masks_combined); +} + +void SCULPT_calc_vertex_displacement(SculptSession *ss, + const Brush *brush, + float rgba[4], + float out_offset[3]) +{ + mul_v3_fl(rgba, ss->cache->bstrength); + /* Handle brush inversion */ + if (ss->cache->bstrength < 0) { + rgba[0] *= -1; + rgba[1] *= -1; + } + + /* Apply texture size */ + for (int i = 0; i < 3; ++i) { + rgba[i] *= blender::math::safe_divide(1.0f, pow2f(brush->mtex.size[i])); + } + + /* Transform vector to object space */ + mul_mat3_m4_v3(ss->cache->brush_local_mat_inv, rgba); + + /* Handle symmetry */ + if (ss->cache->radial_symmetry_pass) { + mul_m4_v3(ss->cache->symm_rot_mat, rgba); + } + flip_v3_v3(out_offset, rgba, ss->cache->mirror_symmetry_pass); +} + bool SCULPT_search_sphere_cb(PBVHNode *node, void *data_v) { SculptSearchSphereData *data = static_cast(data_v); @@ -2920,7 +3007,10 @@ static void calc_local_y(ViewContext *vc, const float center[3], float y[3]) mul_m4_v3(ob->world_to_object, y); } -static void calc_brush_local_mat(const MTex *mtex, Object *ob, float local_mat[4][4]) +static void calc_brush_local_mat(const MTex *mtex, + Object *ob, + float local_mat[4][4], + float local_mat_inv[4][4]) { const StrokeCache *cache = ob->sculpt->cache; float tmat[4][4]; @@ -2966,6 +3056,8 @@ static void calc_brush_local_mat(const MTex *mtex, Object *ob, float local_mat[4 scale_m4_fl(scale, radius); mul_m4_m4m4(tmat, mat, scale); + /* Return tmat as is (for converting from local area coords to model-space coords). */ + copy_m4_m4(local_mat_inv, tmat); /* Return inverse (for converting from model-space coords to local area coords). */ invert_m4_m4(local_mat, tmat); } @@ -3000,7 +3092,7 @@ static void update_brush_local_mat(Sculpt *sd, Object *ob) if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0) { const Brush *brush = BKE_paint_brush(&sd->paint); const MTex *mask_tex = BKE_brush_mask_texture_get(brush, OB_MODE_SCULPT); - calc_brush_local_mat(mask_tex, ob, cache->brush_local_mat); + calc_brush_local_mat(mask_tex, ob, cache->brush_local_mat, cache->brush_local_mat_inv); } } diff --git a/source/blender/editors/sculpt_paint/sculpt_brush_types.cc b/source/blender/editors/sculpt_paint/sculpt_brush_types.cc index 8bec48bfb65..d36259a1a51 100644 --- a/source/blender/editors/sculpt_paint/sculpt_brush_types.cc +++ b/source/blender/editors/sculpt_paint/sculpt_brush_types.cc @@ -265,18 +265,35 @@ static void do_draw_brush_task_cb_ex(void *__restrict userdata, SCULPT_automasking_node_update(ss, &automask_data, &vd); /* Offset vertex. */ - const float fade = SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.vertex, - thread_id, - &automask_data); - - mul_v3_v3fl(proxy[vd.i], offset, fade); + if (ss->cache->brush->flag2 & BRUSH_USE_COLOR_AS_DISPLACEMENT && + (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA)) { + float r_rgba[4]; + SCULPT_brush_strength_color(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.vertex, + thread_id, + &automask_data, + r_rgba); + SCULPT_calc_vertex_displacement(ss, brush, r_rgba, proxy[vd.i]); + } + else { + float fade = SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.vertex, + thread_id, + &automask_data); + mul_v3_v3fl(proxy[vd.i], offset, fade); + } if (vd.is_mesh) { BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.hh b/source/blender/editors/sculpt_paint/sculpt_intern.hh index 37e7a522ccf..5117b49407f 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/sculpt_intern.hh @@ -586,8 +586,13 @@ struct StrokeCache { float sculpt_normal_symm[3]; /* Used for area texture mode, local_mat gets calculated by - * calc_brush_local_mat() and used in tex_strength(). */ + * calc_brush_local_mat() and used in sculpt_apply_texture(). + * Transforms from model-space coords to local area coords. + */ float brush_local_mat[4][4]; + /* The matrix from local area coords to model-space coords is used to calculate the vector + * displacement in area plane mode. */ + float brush_local_mat_inv[4][4]; float plane_offset[3]; /* used to shift the plane around when doing tiled strokes */ int tile_pass; @@ -1241,6 +1246,30 @@ float SCULPT_brush_strength_factor(SculptSession *ss, int thread_id, AutomaskingNodeData *automask_data); +/** + * Return a color of a brush texture on a particular vertex multiplied by active masks. + */ +void SCULPT_brush_strength_color(SculptSession *ss, + const Brush *brush, + const float brush_point[3], + float len, + const float vno[3], + const float fno[3], + float mask, + const PBVHVertRef vertex, + int thread_id, + AutomaskingNodeData *automask_data, + float r_rgba[4]); + +/** + * Calculates the vertex offset for a single vertex depending on the brush setting rgb as vector + * displacement. + */ +void SCULPT_calc_vertex_displacement(SculptSession *ss, + const Brush *brush, + float rgba[3], + float out_offset[3]); + /** * Tilts a normal by the x and y tilt values using the view axis. */ diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 72357ea6734..3ea2c3d70b7 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -412,6 +412,7 @@ typedef enum eBrushFlags2 { BRUSH_CLOTH_USE_COLLISION = (1 << 6), BRUSH_AREA_RADIUS_PRESSURE = (1 << 7), BRUSH_GRAB_SILHOUETTE = (1 << 8), + BRUSH_USE_COLOR_AS_DISPLACEMENT = (1 << 9), } eBrushFlags2; typedef enum { diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 8e6908841a6..13832457c23 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -2899,6 +2899,14 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Texture Sample Bias", "Value added to texture samples"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "use_color_as_displacement", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag2", BRUSH_USE_COLOR_AS_DISPLACEMENT); + RNA_def_property_ui_text(prop, + "Vector Displacement", + "Handles each pixel color as individual vector for displacement. Works " + "only with area plane mapping"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "normal_weight", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "normal_weight"); RNA_def_property_float_default(prop, 0);