Mesh: Replace auto smooth with node group #108014

Merged
Hans Goudey merged 149 commits from HooglyBoogly/blender:refactor-mesh-corner-normals-lazy into main 2023-10-20 16:54:20 +02:00
17 changed files with 91 additions and 61 deletions
Showing only changes of commit 9560c55236 - Show all commits

View File

@ -43,6 +43,31 @@ enum eMeshWrapperType {
namespace blender::bke {
/**
* The complexity requirement of attribute domains needed to process normals.
* See #Mesh::normals_domain().
*/
enum class MeshNormalDomain : int8_t {
/**
* The mesh is completely smooth shaded; either all faces or edges are sharp.
* Only #Mesh::face_normals() is necessary. This case is generally the best
* for performance, since no mixing is necessary and multithreading is simple.
*/
Face,
/**
* The mesh is completely smooth shaded; there are no sharp face or edges. Only
* #Mesh::vert_normals() is necessary. Calculating face normals is still necessary though,
* since they have to be mixed to become vertex normals.
*/
Point,
/**
* The mesh has mixed smooth and sharp shading. In order to split the normals on each side of
* sharp edges, they need to be processed per-face-corner. Normals can be retrieved with
* #Mesh::corner_normals().
*/
Corner,
};
/**
* Cache of a mesh's loose edges, accessed with #Mesh::loose_edges(). *
*/

View File

@ -449,15 +449,16 @@ static void add_orco_mesh(
static void mesh_calc_modifier_final_normals(const bool sculpt_dyntopo, Mesh *mesh_final)
{
const eAttrDomain domain = eAttrDomain(mesh_final->normals_domain());
using namespace blender::bke;
const MeshNormalDomain domain = mesh_final->normals_domain();
/* Needed as `final_datamask` is not preserved outside modifier stack evaluation. */
SubsurfRuntimeData *subsurf_runtime_data = mesh_final->runtime->subsurf_runtime_data;
if (subsurf_runtime_data) {
subsurf_runtime_data->calc_loop_normals = domain == ATTR_DOMAIN_CORNER;
subsurf_runtime_data->calc_loop_normals = domain == MeshNormalDomain::Corner;
}
if (domain == ATTR_DOMAIN_CORNER) {
if (domain == MeshNormalDomain::Corner) {
/* Compute loop normals (NOTE: will compute face and vert normals as well, if needed!). In case
* of deferred CPU subdivision, this will be computed when the wrapper is generated. */
if (!subsurf_runtime_data || subsurf_runtime_data->resolution == 0) {
@ -467,10 +468,10 @@ static void mesh_calc_modifier_final_normals(const bool sculpt_dyntopo, Mesh *me
else {
if (sculpt_dyntopo == false) {
HooglyBoogly marked this conversation as resolved Outdated

missing word after the

missing word after `the`
/* Eager normal calculation can potentially be faster than deferring to drawing code. */
if (domain == ATTR_DOMAIN_FACE) {
if (domain == MeshNormalDomain::Face) {
mesh_final->face_normals();
}
HooglyBoogly marked this conversation as resolved Outdated

Shouldn't this be ATTR_DOMAIN_VERT ?

Shouldn't this be `ATTR_DOMAIN_VERT` ?
else if (domain == ATTR_DOMAIN_POINT) {
else if (domain == MeshNormalDomain::Point) {
mesh_final->vert_normals();
}
}

View File

@ -299,16 +299,16 @@ static void normals_calc_faces_and_verts(const Span<float3> positions,
/** \name Mesh Normal Calculation
* \{ */
int Mesh::normals_domain() const
blender::bke::MeshNormalDomain Mesh::normals_domain() const
{
using namespace blender;
using namespace blender::bke;
if (this->faces_num == 0) {
return ATTR_DOMAIN_POINT;
return MeshNormalDomain::Point;
}
if (CustomData_has_layer(&this->loop_data, CD_CUSTOMLOOPNORMAL)) {
return ATTR_DOMAIN_CORNER;
return MeshNormalDomain::Corner;
}
const AttributeAccessor attributes = this->attributes();
@ -317,22 +317,22 @@ int Mesh::normals_domain() const
const array_utils::BooleanMix face_mix = array_utils::booleans_mix_calc(sharp_faces);
if (face_mix == array_utils::BooleanMix::AllTrue) {
return ATTR_DOMAIN_FACE;
return MeshNormalDomain::Face;
}
const VArray<bool> sharp_edges = *attributes.lookup_or_default<bool>(
"sharp_edge", ATTR_DOMAIN_EDGE, false);
const array_utils::BooleanMix edge_mix = array_utils::booleans_mix_calc(sharp_edges);
if (edge_mix == array_utils::BooleanMix::AllTrue) {
return ATTR_DOMAIN_FACE;
return MeshNormalDomain::Face;
}
if (edge_mix == array_utils::BooleanMix::AllFalse &&
face_mix == array_utils::BooleanMix::AllFalse) {
return ATTR_DOMAIN_POINT;
return MeshNormalDomain::Point;
}
return ATTR_DOMAIN_CORNER;
return MeshNormalDomain::Corner;
}
blender::Span<blender::float3> Mesh::vert_normals() const
@ -387,15 +387,16 @@ blender::Span<blender::float3> Mesh::face_normals() const
blender::Span<blender::float3> Mesh::corner_normals() const
{
using namespace blender;
using namespace blender::bke;
this->runtime->corner_normals_cache.ensure([&](Vector<float3> &r_data) {
r_data.reinitialize(this->totloop);
const OffsetIndices faces = this->faces();
r_data.reinitialize(faces.total_size());
switch (this->normals_domain()) {
case ATTR_DOMAIN_POINT: {
case MeshNormalDomain::Point: {
array_utils::gather(this->vert_normals(), this->corner_verts(), r_data.as_mutable_span());
break;
}
case ATTR_DOMAIN_FACE: {
case MeshNormalDomain::Face: {
const Span<float3> face_normals = this->face_normals();
threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
@ -404,30 +405,28 @@ blender::Span<blender::float3> Mesh::corner_normals() const
});
break;
}
case ATTR_DOMAIN_CORNER: {
case MeshNormalDomain::Corner: {
const bool *sharp_edges = static_cast<const bool *>(
CustomData_get_layer_named(&this->edge_data, CD_PROP_BOOL, "sharp_edge"));
const bool *sharp_faces = static_cast<const bool *>(
CustomData_get_layer_named(&this->face_data, CD_PROP_BOOL, "sharp_face"));
const short2 *custom_normals = static_cast<const short2 *>(
CustomData_get_layer(&this->loop_data, CD_CUSTOMLOOPNORMAL));
bke::mesh::normals_calc_loop(this->vert_positions(),
this->edges(),
this->faces(),
this->corner_verts(),
this->corner_edges(),
this->corner_to_face_map(),
this->vert_normals(),
this->face_normals(),
sharp_edges,
sharp_faces,
custom_normals,
nullptr,
r_data);
mesh::normals_calc_loop(this->vert_positions(),
this->edges(),
this->faces(),
this->corner_verts(),
this->corner_edges(),
this->corner_to_face_map(),
this->vert_normals(),
this->face_normals(),
sharp_edges,
sharp_faces,
custom_normals,
nullptr,
r_data);
break;
}
default:
BLI_assert_unreachable();
}
});
return this->runtime->corner_normals_cache.data();

View File

@ -142,7 +142,7 @@ bool BKE_shrinkwrap_init_tree(
if (force_normals || BKE_shrinkwrap_needs_normals(shrinkType, shrinkMode)) {
data->face_normals = reinterpret_cast<const float(*)[3]>(mesh->face_normals().data());
if (mesh->normals_domain() == ATTR_DOMAIN_CORNER) {
if (mesh->normals_domain() == blender::bke::MeshNormalDomain::Corner) {
data->clnors = reinterpret_cast<const float(*)[3]>(mesh->corner_normals().data());
}
}

View File

@ -87,7 +87,7 @@ static ModifierData *modifier_get_last_enabled_for_mode(const Scene *scene,
bool BKE_subsurf_modifier_use_custom_loop_normals(const SubsurfModifierData *smd, const Mesh *mesh)
{
return smd->flags & eSubsurfModifierFlag_UseCustomNormals &&
mesh->normals_domain() == ATTR_DOMAIN_CORNER;
mesh->normals_domain() == blender::bke::MeshNormalDomain::Corner;
}
static bool is_subdivision_evaluation_possible_on_gpu()

View File

@ -413,8 +413,9 @@ void mesh_render_data_update_normals(MeshRenderData &mr, const eMRDataType data_
if (data_flag & (MR_DATA_POLY_NOR | MR_DATA_LOOP_NOR | MR_DATA_TAN_LOOP_NOR)) {
mr.face_normals = mr.me->face_normals();
}
if (((data_flag & MR_DATA_LOOP_NOR) &&
ELEM(mr.me->normals_domain(), ATTR_DOMAIN_CORNER, ATTR_DOMAIN_FACE)) ||
if (((data_flag & MR_DATA_LOOP_NOR) && ELEM(mr.me->normals_domain(),
blender::bke::MeshNormalDomain::Corner,
blender::bke::MeshNormalDomain::Face)) ||
(data_flag & MR_DATA_TAN_LOOP_NOR))
{
HooglyBoogly marked this conversation as resolved Outdated

It seems like you could potentially use VArray::ForDerivedSpan here.

It seems like you could potentially use `VArray::ForDerivedSpan` here.
mr.loop_normals = mr.me->corner_normals();

View File

@ -2152,7 +2152,8 @@ static bool draw_subdiv_create_requested_buffers(Object *ob,
runtime_data->stats_totloop = draw_cache.num_subdiv_loops;
draw_cache.use_custom_loop_normals = (runtime_data->use_loop_normals) &&
mesh_eval->normals_domain() == ATTR_DOMAIN_CORNER;
mesh_eval->normals_domain() ==
blender::bke::MeshNormalDomain::Corner;
if (DRW_ibo_requested(mbc.buff.ibo.tris)) {
draw_subdiv_cache_ensure_mat_offsets(draw_cache, mesh_eval, batch_cache.mat_len);

View File

@ -712,7 +712,7 @@ static Mesh *bake_mesh_new_from_object(Depsgraph *depsgraph,
{
Mesh *me = BKE_mesh_new_from_object(depsgraph, object, false, preserve_origindex);
if (me->normals_domain() == ATTR_DOMAIN_CORNER) {
if (me->normals_domain() == blender::bke::MeshNormalDomain::Corner) {
ED_mesh_split_faces(me);
}

View File

@ -526,12 +526,12 @@ static void get_loop_normals(const Mesh *mesh, std::vector<Imath::V3f> &normals)
normals.clear();
switch (mesh->normals_domain()) {
case ATTR_DOMAIN_POINT: {
case blender::bke::MeshNormalDomain::Point: {
/* If all faces are smooth shaded, and there are no custom normals, we don't need to
* export normals at all. This is also done by other software, see #71246. */
break;
}
case ATTR_DOMAIN_FACE: {
case blender::bke::MeshNormalDomain::Face: {
normals.resize(mesh->totloop);
MutableSpan dst_normals(reinterpret_cast<float3 *>(normals.data()), normals.size());
@ -546,7 +546,7 @@ static void get_loop_normals(const Mesh *mesh, std::vector<Imath::V3f> &normals)
});
break;
}
case ATTR_DOMAIN_CORNER: {
case blender::bke::MeshNormalDomain::Corner: {
normals.resize(mesh->totloop);
MutableSpan dst_normals(reinterpret_cast<float3 *>(normals.data()), normals.size());
@ -563,8 +563,6 @@ static void get_loop_normals(const Mesh *mesh, std::vector<Imath::V3f> &normals)
});
break;
}
default:
BLI_assert_unreachable();
}
}

View File

@ -626,7 +626,7 @@ void GeometryExporter::create_normals(std::vector<Normal> &normals,
"sharp_face", ATTR_DOMAIN_FACE, false);
blender::Span<blender::float3> corner_normals;
if (me->normals_domain() == ATTR_DOMAIN_CORNER) {
if (me->normals_domain() == blender::bke::MeshNormalDomain::Corner) {
corner_normals = me->corner_normals();
}

View File

@ -233,7 +233,7 @@ void MeshData::write_submeshes(const Mesh *mesh)
const Span<MLoopTri> looptris = mesh->looptris();
Span<float3> corner_normals;
if (mesh->normals_domain() == ATTR_DOMAIN_CORNER) {
if (mesh->normals_domain() == blender::bke::MeshNormalDomain::Corner) {
corner_normals = mesh->corner_normals();
}

View File

@ -653,11 +653,11 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_
MutableSpan dst_normals(reinterpret_cast<float3 *>(loop_normals.data()), loop_normals.size());
switch (mesh->normals_domain()) {
case ATTR_DOMAIN_POINT: {
case bke::MeshNormalDomain::Point: {
array_utils::gather(mesh->vert_normals(), mesh->corner_verts(), dst_normals);
break;
}
case ATTR_DOMAIN_FACE: {
case bke::MeshNormalDomain::Face: {
const OffsetIndices faces = mesh->faces();
const Span<float3> face_normals = mesh->face_normals();
for (const int i : faces.index_range()) {
@ -665,12 +665,10 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_
}
break;
}
case ATTR_DOMAIN_CORNER: {
case bke::MeshNormalDomain::Corner: {
array_utils::copy(mesh->corner_normals(), dst_normals);
break;
}
default:
BLI_assert_unreachable();
}
pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);

View File

@ -368,7 +368,7 @@ void OBJMesh::store_normal_coords_and_indices()
loop_to_normal_index_.fill(-1);
Span<float3> corner_normals;
if (export_mesh_->normals_domain() == ATTR_DOMAIN_CORNER) {
if (export_mesh_->normals_domain() == blender::bke::MeshNormalDomain::Corner) {
corner_normals = export_mesh_->corner_normals();
}

View File

@ -32,6 +32,7 @@ class AttributeAccessor;
class MutableAttributeAccessor;
struct LooseVertCache;
struct LooseEdgeCache;
enum class MeshNormalDomain : int8_t;
} // namespace bke
} // namespace blender
using MeshRuntimeHandle = blender::bke::MeshRuntime;
@ -366,7 +367,7 @@ typedef struct Mesh {
* face corner normals, since there is a 2-4x performance cost increase for each more complex
* domain.
*/
HooglyBoogly marked this conversation as resolved Outdated

I wonder if we could call this normals_domain. I found "all info" more confusing than useful at first.

I wonder if we could call this `normals_domain`. I found "all info" more confusing than useful at first.
int normals_domain() const;
blender::bke::MeshNormalDomain normals_domain() const;
/**
* Normal direction of polygons, defined by positions and the winding direction of face corners.
*/
@ -438,7 +439,7 @@ enum {
ME_FLAG_DEPRECATED_2 = 1 << 2, /* deprecated */
ME_FLAG_UNUSED_3 = 1 << 3, /* cleared */
ME_FLAG_UNUSED_4 = 1 << 4, /* cleared */
HooglyBoogly marked this conversation as resolved Outdated

Could call this: ME_AUTOSMOOTH_LEGACY

Could also rename Mesh::smoothresh -> Mesh::smoothresh_legacy.

Could call this: `ME_AUTOSMOOTH_LEGACY` Could also rename `Mesh::smoothresh` -> `Mesh::smoothresh_legacy`.
ME_AUTOSMOOTH_LEGACY = 1 << 5, /* deprecated */
ME_AUTOSMOOTH_LEGACY = 1 << 5, /* deprecated */
ME_FLAG_UNUSED_6 = 1 << 6, /* cleared */
ME_FLAG_UNUSED_7 = 1 << 7, /* cleared */
ME_REMESH_REPROJECT_VERTEX_COLORS = 1 << 8,

View File

@ -23,6 +23,7 @@
#include "BKE_attribute.h"
#include "BKE_editmesh.h"
#include "BKE_mesh_types.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
@ -1619,7 +1620,7 @@ int rna_Mesh_loops_lookup_int(PointerRNA *ptr, int index, PointerRNA *r_ptr)
static int rna_Mesh_normals_domain_get(PointerRNA *ptr)
{
return rna_mesh(ptr)->normals_domain();
return int(rna_mesh(ptr)->normals_domain());
}
static void rna_Mesh_vertex_normals_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
@ -2999,11 +3000,18 @@ static void rna_def_mesh(BlenderRNA *brna)
rna_def_normal_layer_value(brna);
static const EnumPropertyItem normal_domain_items[] = {
{int(blender::bke::MeshNormalDomain::Point), "POINT", 0, "Point", ""},
{int(blender::bke::MeshNormalDomain::Face), "FACE", 0, "Face", ""},
{int(blender::bke::MeshNormalDomain::Corner), "CORNER", 0, "Corner", ""},
{0, nullptr, 0, nullptr, nullptr},
};
prop = RNA_def_property(srna, "normals_domain", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_attribute_domain_only_mesh_no_edge_items);
RNA_def_property_enum_items(prop, normal_domain_items);
RNA_def_property_ui_text(
prop,
"Normal Domain All Info",
"Normal Domain",
"The attribute domain that gives enough information to represent the mesh's normals");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_enum_funcs(prop, "rna_Mesh_normals_domain_get", NULL, NULL);

View File

@ -216,7 +216,7 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
return result;
}
const bool use_clnors = mmd->flags & eMultiresModifierFlag_UseCustomNormals &&
mesh->normals_domain() == ATTR_DOMAIN_CORNER;
mesh->normals_domain() == blender::bke::MeshNormalDomain::Corner;
/* NOTE: Orco needs final coordinates on CPU side, which are expected to be
* accessible via mesh vertices. For this reason we do not evaluate multires to
* grids when orco is requested. */

View File

@ -337,7 +337,7 @@ static void compute_normal_outputs(const Mesh &mesh,
MutableSpan<float3> r_normals)
{
switch (mesh.normals_domain()) {
case ATTR_DOMAIN_POINT: {
case bke::MeshNormalDomain::Point: {
const Span<int> corner_verts = mesh.corner_verts();
const Span<MLoopTri> looptris = mesh.looptris();
const Span<float3> vert_normals = mesh.vert_normals();
@ -347,7 +347,7 @@ static void compute_normal_outputs(const Mesh &mesh,
});
break;
}
case ATTR_DOMAIN_FACE: {
case bke::MeshNormalDomain::Face: {
const Span<int> looptri_faces = mesh.looptri_faces();
VArray<float3> face_normals = VArray<float3>::ForSpan(mesh.face_normals());
threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) {
@ -356,7 +356,7 @@ static void compute_normal_outputs(const Mesh &mesh,
});
break;
}
case ATTR_DOMAIN_CORNER: {
case bke::MeshNormalDomain::Corner: {
const Span<MLoopTri> looptris = mesh.looptris();
const Span<float3> corner_normals = mesh.corner_normals();
threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) {
@ -365,8 +365,6 @@ static void compute_normal_outputs(const Mesh &mesh,
});
break;
}
default:
BLI_assert_unreachable();
}
}