Sculpt: Clay Strips optimizations

This patch introduces some optimizations to Clay Strips, resulting in a
speedup of approximately 1.22x.

- The translations are calculated more efficiently using the local
  space, similar to the approach used for the Plane brush.
- Plane trimming is computed in local space.
- Strength is applied to the offset vector, avoiding an extra per-vertex
  multiplication.
- The local space is constructed such that vertices affected by
  the brush have a positive z-coordinate.

Note: this patch doesn't alter the way the brush works. Any
difference should be considered a bug.

Pull Request: https://projects.blender.org/blender/blender/pulls/137558
This commit is contained in:
Nicola
2025-04-22 20:49:05 +02:00
committed by Hans Goudey
parent bc0a9d31e5
commit e2b9839aea
4 changed files with 175 additions and 163 deletions

View File

@@ -2,6 +2,19 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edsculpt
*
* The Clay Strips brush displaces vertices toward the brush plane.
* The displacement occurs in the direction of the plane's normal.
* Only vertices located below the plane (in brush-local space) are affected.
*
* The magnitude of the displacement is determined by the product of the following factors:
* - A falloff factor based on the XY distance from the center of the plane
* - A parabolic falloff factor based on the local Z distance from the plane
* - Additional standard modifiers such as masking, brush strength, texture masking, etc.
*/
#include "editors/sculpt_paint/brushes/brushes.hh"
#include "DNA_brush_types.h"
@@ -33,43 +46,87 @@ inline namespace clay_strips_cc {
struct LocalData {
Vector<float3> positions;
Vector<float2> xy_positions;
Vector<float> z_positions;
Vector<float> factors;
Vector<float> distances;
Vector<float3> translations;
};
/**
* Applies a linear falloff based on the z distance in brush local space to the factor.
*
* Note: We may want to provide users the ability to change this falloff in the future, the
* important detail is that we reduce the influence of the brush on vertices that are potentially
* "deep" inside the cube test area (i.e. on a nearby plane).
*
* TODO: Depending on if other brushes begin to use the calc_brush_cube_distances, we may want
* to consider either inlining this falloff in that method, or making this a commonly accessible
* function.
* Transforms positions from object space positions to brush-local space. Splitting the XY and Z
* components gives slightly better performance.
*/
BLI_NOINLINE static void apply_z_axis_falloff(const Span<float3> vert_positions,
const Span<int> verts,
const float4x4 &mat,
const MutableSpan<float> factors)
static void calc_local_positions(const Span<float3> vert_positions,
const Span<int> verts,
const float4x4 &mat,
const MutableSpan<float2> xy_positions,
const MutableSpan<float> z_positions)
{
BLI_assert(factors.size() == verts.size());
for (const int i : factors.index_range()) {
const float local_z_distance = math::abs(
math::transform_point(mat, vert_positions[verts[i]]).z);
factors[i] *= 1 - local_z_distance;
BLI_assert(xy_positions.size() == verts.size());
BLI_assert(z_positions.size() == verts.size());
for (const int i : verts.index_range()) {
const float3 position = math::transform_point(mat, vert_positions[verts[i]]);
xy_positions[i] = position.xy();
z_positions[i] = position.z;
}
}
BLI_NOINLINE static void apply_z_axis_falloff(const Span<float3> positions,
const float4x4 &mat,
const MutableSpan<float> factors)
static void calc_local_positions(const Span<float3> positions,
const float4x4 &mat,
const MutableSpan<float2> xy_positions,
const MutableSpan<float> z_positions)
{
BLI_assert(factors.size() == positions.size());
BLI_assert(xy_positions.size() == positions.size());
BLI_assert(z_positions.size() == positions.size());
for (const int i : positions.index_range()) {
const float3 position = math::transform_point(mat, positions[i]);
xy_positions[i] = position.xy();
z_positions[i] = position.z;
}
}
/**
* Applies a parabolic factor of the form `z * (1 - z)` to each vertex.
* Vertices outside of the interval (0, 1) are out of range and their factors are set to zero.
* Note: The local coordinate system is constructed such that all relevant `z` values
* are non-negative.
*/
static void apply_z_axis_factors(const Span<float> z_positions, const MutableSpan<float> factors)
{
BLI_assert(factors.size() == z_positions.size());
for (const int i : factors.index_range()) {
const float local_z_distance = math::abs(math::transform_point(mat, positions[i]).z);
factors[i] *= 1 - local_z_distance;
const float local_z = z_positions[i];
/* Note: if `local_z > 1`, then `1 - local_z < 0` and the product is negative. */
factors[i] *= math::max(0.0f, local_z * (1.0f - local_z));
}
}
/**
* If plane trim is enabled, vertices with `z` values greater than the
* specified `plane_trim` threshold are ignored (i.e., their factors are set to zero).
*/
static void apply_plane_trim_factors(const Brush &brush,
const Span<float> z_positions,
const MutableSpan<float> factors)
{
BLI_assert(factors.size() == z_positions.size());
const bool use_plane_trim = brush.flag & BRUSH_PLANE_TRIM;
if (!use_plane_trim) {
return;
}
for (const int i : factors.index_range()) {
if (z_positions[i] > brush.plane_trim) {
factors[i] = 0.0f;
}
}
}
@@ -77,9 +134,7 @@ static void calc_faces(const Depsgraph &depsgraph,
const Sculpt &sd,
const Brush &brush,
const float4x4 &mat,
const float4 &plane,
const float strength,
const bool flip,
const float3 &offset,
const Span<float3> vert_normals,
const MeshAttributeData &attribute_data,
const bke::pbvh::MeshNode &node,
@@ -100,10 +155,18 @@ static void calc_faces(const Depsgraph &depsgraph,
calc_front_face(cache.view_normal_symm, vert_normals, verts, factors);
}
tls.xy_positions.resize(verts.size());
tls.z_positions.resize(verts.size());
MutableSpan<float2> xy_positions = tls.xy_positions;
MutableSpan<float> z_positions = tls.z_positions;
calc_local_positions(position_data.eval, verts, mat, xy_positions, z_positions);
apply_z_axis_factors(z_positions, factors);
apply_plane_trim_factors(brush, z_positions, factors);
tls.distances.resize(verts.size());
const MutableSpan<float> distances = tls.distances;
calc_brush_cube_distances(brush, mat, position_data.eval, verts, distances, factors);
apply_z_axis_falloff(position_data.eval, verts, mat, factors);
calc_brush_cube_distances<float2>(brush, xy_positions, distances);
filter_distances_with_radius(1.0f, distances, factors);
apply_hardness_to_distances(1.0f, cache.hardness, distances);
BKE_brush_calc_curve_factors(
@@ -113,23 +176,11 @@ static void calc_faces(const Depsgraph &depsgraph,
calc_brush_texture_factors(ss, brush, position_data.eval, verts, factors);
scale_factors(factors, strength);
if (flip) {
filter_below_plane_factors(position_data.eval, verts, plane, factors);
}
else {
filter_above_plane_factors(position_data.eval, verts, plane, factors);
}
tls.translations.resize(verts.size());
const MutableSpan<float3> translations = tls.translations;
calc_translations_to_plane(position_data.eval, verts, plane, translations);
filter_plane_trim_limit_factors(brush, cache, translations, factors);
scale_translations(translations, factors);
translations_from_offset_and_factors(offset, factors, tls.translations);
clip_and_lock_translations(sd, ss, position_data.eval, verts, translations);
position_data.deform(translations, verts);
clip_and_lock_translations(sd, ss, position_data.eval, verts, tls.translations);
position_data.deform(tls.translations, verts);
}
static void calc_grids(const Depsgraph &depsgraph,
@@ -137,9 +188,7 @@ static void calc_grids(const Depsgraph &depsgraph,
Object &object,
const Brush &brush,
const float4x4 &mat,
const float4 &plane,
const float strength,
const bool flip,
const float3 &offset,
const bke::pbvh::GridsNode &node,
LocalData &tls)
{
@@ -158,10 +207,18 @@ static void calc_grids(const Depsgraph &depsgraph,
calc_front_face(cache.view_normal_symm, subdiv_ccg, grids, factors);
}
tls.xy_positions.resize(positions.size());
tls.z_positions.resize(positions.size());
MutableSpan<float2> xy_positions = tls.xy_positions;
MutableSpan<float> z_positions = tls.z_positions;
calc_local_positions(positions, mat, xy_positions, z_positions);
apply_z_axis_factors(z_positions, factors);
apply_plane_trim_factors(brush, z_positions, factors);
tls.distances.resize(positions.size());
const MutableSpan<float> distances = tls.distances;
calc_brush_cube_distances(brush, mat, positions, distances, factors);
apply_z_axis_falloff(positions, mat, factors);
calc_brush_cube_distances<float2>(brush, xy_positions, distances);
filter_distances_with_radius(1.0f, distances, factors);
apply_hardness_to_distances(1.0f, cache.hardness, distances);
BKE_brush_calc_curve_factors(
@@ -171,23 +228,11 @@ static void calc_grids(const Depsgraph &depsgraph,
calc_brush_texture_factors(ss, brush, positions, factors);
scale_factors(factors, strength);
if (flip) {
filter_below_plane_factors(positions, plane, factors);
}
else {
filter_above_plane_factors(positions, plane, factors);
}
tls.translations.resize(positions.size());
const MutableSpan<float3> translations = tls.translations;
calc_translations_to_plane(positions, plane, translations);
filter_plane_trim_limit_factors(brush, cache, translations, factors);
scale_translations(translations, factors);
translations_from_offset_and_factors(offset, factors, tls.translations);
clip_and_lock_translations(sd, ss, positions, translations);
apply_translations(translations, grids, subdiv_ccg);
clip_and_lock_translations(sd, ss, positions, tls.translations);
apply_translations(tls.translations, grids, subdiv_ccg);
}
static void calc_bmesh(const Depsgraph &depsgraph,
@@ -195,9 +240,7 @@ static void calc_bmesh(const Depsgraph &depsgraph,
Object &object,
const Brush &brush,
const float4x4 &mat,
const float4 &plane,
const float strength,
const bool flip,
const float3 &offset,
bke::pbvh::BMeshNode &node,
LocalData &tls)
{
@@ -215,10 +258,18 @@ static void calc_bmesh(const Depsgraph &depsgraph,
calc_front_face(cache.view_normal_symm, verts, factors);
}
tls.distances.resize(verts.size());
tls.xy_positions.resize(positions.size());
tls.z_positions.resize(positions.size());
MutableSpan<float2> xy_positions = tls.xy_positions;
MutableSpan<float> z_positions = tls.z_positions;
calc_local_positions(positions, mat, xy_positions, z_positions);
apply_z_axis_factors(z_positions, factors);
apply_plane_trim_factors(brush, z_positions, factors);
tls.distances.resize(positions.size());
const MutableSpan<float> distances = tls.distances;
calc_brush_cube_distances(brush, mat, positions, distances, factors);
apply_z_axis_falloff(positions, mat, factors);
calc_brush_cube_distances<float2>(brush, xy_positions, distances);
filter_distances_with_radius(1.0f, distances, factors);
apply_hardness_to_distances(1.0f, cache.hardness, distances);
BKE_brush_calc_curve_factors(
@@ -228,23 +279,11 @@ static void calc_bmesh(const Depsgraph &depsgraph,
calc_brush_texture_factors(ss, brush, positions, factors);
scale_factors(factors, strength);
tls.translations.resize(positions.size());
translations_from_offset_and_factors(offset, factors, tls.translations);
if (flip) {
filter_below_plane_factors(positions, plane, factors);
}
else {
filter_above_plane_factors(positions, plane, factors);
}
tls.translations.resize(verts.size());
const MutableSpan<float3> translations = tls.translations;
calc_translations_to_plane(positions, plane, translations);
filter_plane_trim_limit_factors(brush, cache, translations, factors);
scale_translations(translations, factors);
clip_and_lock_translations(sd, ss, positions, translations);
apply_translations(translations, verts);
clip_and_lock_translations(sd, ss, positions, tls.translations);
apply_translations(tls.translations, verts);
}
} // namespace clay_strips_cc
@@ -271,6 +310,13 @@ void do_clay_strips_brush(const Depsgraph &depsgraph,
mat.x_axis() = math::cross(plane_normal, ss.cache->grab_delta_symm);
mat.y_axis() = math::cross(plane_normal, float3(mat[0]));
mat.z_axis() = plane_normal;
/* Flip the z-axis so that the vertices below the plane have positive z-coordinates. When the
* brush is inverted, the affected z-coordinates are already positive. */
if (!flip) {
mat.z_axis() *= -1.0f;
}
mat.location() = plane_center;
mat = math::normalize(mat);
@@ -280,10 +326,7 @@ void do_clay_strips_brush(const Depsgraph &depsgraph,
tmat.y_axis() *= brush.tip_scale_x;
mat = math::invert(tmat);
float4 plane;
plane_from_point_normal_v3(plane, plane_center, plane_normal);
const float strength = std::abs(ss.cache->bstrength);
const float3 offset = plane_normal * ss.cache->bstrength * ss.cache->radius;
threading::EnumerableThreadSpecific<LocalData> all_tls;
switch (pbvh.type()) {
@@ -299,9 +342,7 @@ void do_clay_strips_brush(const Depsgraph &depsgraph,
sd,
brush,
mat,
plane,
strength,
flip,
offset,
vert_normals,
attribute_data,
nodes[i],
@@ -318,7 +359,7 @@ void do_clay_strips_brush(const Depsgraph &depsgraph,
MutableSpan<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
node_mask.foreach_index(GrainSize(1), [&](const int i) {
LocalData &tls = all_tls.local();
calc_grids(depsgraph, sd, object, brush, mat, plane, strength, flip, nodes[i], tls);
calc_grids(depsgraph, sd, object, brush, mat, offset, nodes[i], tls);
bke::pbvh::update_node_bounds_grids(subdiv_ccg.grid_area, positions, nodes[i]);
});
break;
@@ -327,7 +368,7 @@ void do_clay_strips_brush(const Depsgraph &depsgraph,
MutableSpan<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
node_mask.foreach_index(GrainSize(1), [&](const int i) {
LocalData &tls = all_tls.local();
calc_bmesh(depsgraph, sd, object, brush, mat, plane, strength, flip, nodes[i], tls);
calc_bmesh(depsgraph, sd, object, brush, mat, offset, nodes[i], tls);
bke::pbvh::update_node_bounds_bmesh(nodes[i]);
});
break;

View File

@@ -323,17 +323,10 @@ void filter_distances_with_radius(float radius, Span<float> distances, MutableSp
* Calculate distances based on a "square" brush tip falloff and ignore vertices that are too far
* away.
*/
template<typename T>
void calc_brush_cube_distances(const Brush &brush,
const float4x4 &mat,
Span<float3> positions,
Span<int> verts,
MutableSpan<float> r_distances,
MutableSpan<float> factors);
void calc_brush_cube_distances(const Brush &brush,
const float4x4 &mat,
Span<float3> positions,
MutableSpan<float> r_distances,
MutableSpan<float> factors);
const Span<T> positions,
const MutableSpan<float> r_distances);
/**
* Scale the distances based on the brush radius and the cached "hardness" setting, which increases

View File

@@ -7165,77 +7165,32 @@ void filter_distances_with_radius(const float radius,
}
}
template<typename T>
void calc_brush_cube_distances(const Brush &brush,
const float4x4 &mat,
const Span<float3> positions,
const Span<int> verts,
const MutableSpan<float> r_distances,
const MutableSpan<float> factors)
const Span<T> positions,
const MutableSpan<float> r_distances)
{
BLI_assert(verts.size() == factors.size());
BLI_assert(verts.size() == r_distances.size());
BLI_assert(r_distances.size() == positions.size());
const float roundness = brush.tip_roundness;
const float roundness_rcp = math::safe_rcp(roundness);
const float hardness = 1.0f - roundness;
for (const int i : verts.index_range()) {
if (factors[i] == 0.0f) {
r_distances[i] = std::numeric_limits<float>::max();
continue;
}
const float3 local = math::abs(math::transform_point(mat, positions[verts[i]]));
if (!(local.x <= 1.0f && local.y <= 1.0f && local.z <= 1.0f)) {
factors[i] = 0.0f;
r_distances[i] = std::numeric_limits<float>::max();
continue;
}
if (std::min(local.x, local.y) > hardness) {
/* Corner, distance to the center of the corner circle. */
r_distances[i] = math::distance(float2(hardness), float2(local)) / roundness;
continue;
}
if (std::max(local.x, local.y) > hardness) {
/* Side, distance to the square XY axis. */
r_distances[i] = (std::max(local.x, local.y) - hardness) / roundness;
continue;
}
/* Inside the square, constant distance. */
r_distances[i] = 0.0f;
}
}
void calc_brush_cube_distances(const Brush &brush,
const float4x4 &mat,
const Span<float3> positions,
const MutableSpan<float> r_distances,
const MutableSpan<float> factors)
{
BLI_assert(positions.size() == factors.size());
BLI_assert(positions.size() == r_distances.size());
const float roundness = brush.tip_roundness;
const float hardness = 1.0f - roundness;
for (const int i : positions.index_range()) {
if (factors[i] == 0.0f) {
r_distances[i] = std::numeric_limits<float>::max();
continue;
}
const float3 local = math::abs(math::transform_point(mat, positions[i]));
const T local = math::abs(positions[i]);
if (!(local.x <= 1.0f && local.y <= 1.0f && local.z <= 1.0f)) {
factors[i] = 0.0f;
if (math::reduce_max(local) > 1.0f) {
r_distances[i] = std::numeric_limits<float>::max();
continue;
}
if (std::min(local.x, local.y) > hardness) {
/* Corner, distance to the center of the corner circle. */
r_distances[i] = math::distance(float2(hardness), float2(local)) / roundness;
r_distances[i] = math::distance(float2(hardness), float2(local)) * roundness_rcp;
continue;
}
if (std::max(local.x, local.y) > hardness) {
/* Side, distance to the square XY axis. */
r_distances[i] = (std::max(local.x, local.y) - hardness) / roundness;
r_distances[i] = (std::max(local.x, local.y) - hardness) * roundness_rcp;
continue;
}
@@ -7243,6 +7198,12 @@ void calc_brush_cube_distances(const Brush &brush,
r_distances[i] = 0.0f;
}
}
template void calc_brush_cube_distances<float2>(const Brush &brush,
const Span<float2> positions,
MutableSpan<float> r_distances);
template void calc_brush_cube_distances<float3>(const Brush &brush,
const Span<float3> positions,
MutableSpan<float> r_distances);
void apply_hardness_to_distances(const float radius,
const float hardness,

View File

@@ -13,6 +13,7 @@
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_hash.h"
#include "BLI_math_color_blend.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.hh"
#include "BLI_vector.hh"
@@ -40,6 +41,16 @@
namespace blender::ed::sculpt_paint::color {
static void calc_local_positions(const float4x4 &mat,
const Span<int> verts,
const Span<float3> positions,
const MutableSpan<float3> local_positions)
{
for (const int i : verts.index_range()) {
local_positions[i] = math::transform_point(mat, positions[verts[i]]);
}
}
template<typename Func> inline void to_static_color_type(const CPPType &type, const Func &func)
{
if (type.is<ColorGeometry4f>()) {
@@ -247,6 +258,7 @@ bke::GSpanAttributeWriter active_color_attribute_for_write(Mesh &mesh)
struct ColorPaintLocalData {
Vector<float> factors;
Vector<float> auto_mask;
Vector<float3> positions;
Vector<float> distances;
Vector<float4> colors;
Vector<float4> new_colors;
@@ -372,18 +384,23 @@ static void do_paint_brush_task(const Scene &scene,
calc_front_face(cache.view_normal_symm, vert_normals, verts, factors);
}
float radius;
tls.distances.resize(verts.size());
const MutableSpan<float> distances = tls.distances;
if (brush.tip_roundness < 1.0f) {
calc_brush_cube_distances(brush, mat, vert_positions, verts, distances, factors);
scale_factors(distances, cache.radius);
tls.positions.resize(verts.size());
calc_local_positions(mat, verts, vert_positions, tls.positions);
calc_brush_cube_distances<float3>(brush, tls.positions, distances);
radius = 1.0f;
}
else {
calc_brush_distances(
ss, vert_positions, verts, eBrushFalloffShape(brush.falloff_shape), distances);
radius = cache.radius;
}
filter_distances_with_radius(cache.radius, distances, factors);
apply_hardness_to_distances(cache, distances);
filter_distances_with_radius(radius, distances, factors);
apply_hardness_to_distances(radius, cache.hardness, distances);
calc_brush_strength_factors(cache, brush, distances, factors);
MutableSpan<float> auto_mask;