Vector displacement for the sculpting draw brush #104481

Merged
Jeroen Bakker merged 18 commits from robin.hohni/blender:sculpt-vector-displacement into main 2023-02-14 15:29:40 +01:00
9 changed files with 281 additions and 137 deletions

View File

@ -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")
Jeroen-Bakker marked this conversation as resolved
Review

Python has some rules about the number of lines between statements. Here it is max 1.

Python has some rules about the number of lines between statements. Here it is max 1.
def brush_mask_texture_settings(layout, brush):
mask_tex_slot = brush.mask_texture_slot

View File

@ -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. */

View File

@ -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.

View File

@ -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)

View File

@ -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;
}
robin.hohni marked this conversation as resolved
Review

For readability would suggest to first square the size, and then do the inf check using math::safe_divide. Seems we currently don't have a safe divide in math for 2 vector types.

rgba[i] *= math::safe_divide(1.0f, POW2(brush->mtex.size[i])); perhaps?

For readability would suggest to first square the size, and then do the inf check using `math::safe_divide`. Seems we currently don't have a safe divide in math for 2 vector types. `rgba[i] *= math::safe_divide(1.0f, POW2(brush->mtex.size[i]));` perhaps?
Review

Thanks for the hint! Made a commit :)

Thanks for the hint! Made a commit :)
/* 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<SculptSearchSphereData *>(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);
}
}

View File

@ -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);

View File

@ -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.
*/

View File

@ -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 {

View File

@ -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",
robin.hohni marked this conversation as resolved
Review

Can you ensure that you use the correct code formatting. You can install clang-format or run make format in the source folder.

Can you ensure that you use the correct code formatting. You can install clang-format or run `make format` in the source folder.
"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);