Fix #140767: Render Simplify "Normals" option doesn't work

One obvious problem is that `mr.use_simplify_normals` was assigned after
face corner normals were retrieved. The other more complex problem is
that now the normals caches automatically mix custom normals from other
domains. This can cause the expensive "Tangent Space" normals to be
calculated even though we don't explicitly request face corner normals.
To fix this, clarify the purpose of the option to only apply to that custom
normals format and use the true normals instead in that case.

Pull Request: https://projects.blender.org/blender/blender/pulls/140879
This commit is contained in:
Hans Goudey
2025-06-24 03:52:40 +02:00
committed by Hans Goudey
parent 379fffaf0d
commit 2e568d31ed
2 changed files with 36 additions and 6 deletions

View File

@@ -8,6 +8,8 @@
* \brief Extraction of Mesh data into VBO to feed to GPU.
*/
#include "BKE_attribute.hh"
#include "DNA_mesh_types.h"
#include "DNA_scene_types.h"
@@ -73,6 +75,31 @@ static void ensure_dependency_data(MeshRenderData &mr,
/** \name Extract Loop
* \{ */
/**
* The mesh normals access functions can end up mixing face corner normals calculated with the
* costly tangent space method. The "Simplify Normals" option is supposed to avoid that, but not
* the "Free" normals which are actually cheaper than calculating true normals.
*/
static bool use_normals_simplify(const Scene &scene, const MeshRenderData &mr)
{
if (!(scene.r.mode & R_SIMPLIFY) || !(scene.r.mode & R_SIMPLIFY_NORMALS)) {
return false;
}
if (!mr.mesh) {
return true;
}
const Mesh &mesh = *mr.mesh;
const std::optional<bke::AttributeMetaData> meta_data = mesh.attributes().lookup_meta_data(
"custom_normal");
if (!meta_data) {
return false;
}
if (meta_data->domain == bke::AttrDomain::Corner && meta_data->data_type == CD_PROP_INT16_2D) {
return true;
}
return false;
}
void mesh_buffer_cache_create_requested(TaskGraph & /*task_graph*/,
const Scene &scene,
MeshBatchCache &cache,
@@ -118,10 +145,10 @@ void mesh_buffer_cache_create_requested(TaskGraph & /*task_graph*/,
MeshRenderData mr = mesh_render_data_create(
object, mesh, is_editmode, is_paint_mode, do_final, do_uvedit, use_hide, scene.toolsettings);
ensure_dependency_data(mr, ibo_requests, vbo_requests, mbc);
mr.use_subsurf_fdots = mr.mesh && !mr.mesh->runtime->subsurf_face_dot_tags.is_empty();
mr.use_simplify_normals = (scene.r.mode & R_SIMPLIFY) && (scene.r.mode & R_SIMPLIFY_NORMALS);
mr.use_simplify_normals = use_normals_simplify(scene, mr);
ensure_dependency_data(mr, ibo_requests, vbo_requests, mbc);
Array<gpu::IndexBufPtr, 16> created_ibos(ibos_to_create.size());

View File

@@ -41,23 +41,26 @@ static void extract_face_normals(const MeshRenderData &mr, MutableSpan<GPUType>
template<typename GPUType>
static void extract_normals_mesh(const MeshRenderData &mr, MutableSpan<GPUType> normals)
{
const auto get_vert_normals = [&]() {
return mr.use_simplify_normals ? mr.mesh->vert_normals_true() : mr.mesh->vert_normals();
};
if (mr.normals_domain == bke::MeshNormalDomain::Face) {
extract_face_normals(mr, normals);
}
else if (mr.normals_domain == bke::MeshNormalDomain::Point) {
extract_vert_normals(mr.corner_verts, mr.mesh->vert_normals(), normals);
extract_vert_normals(mr.corner_verts, get_vert_normals(), normals);
}
else if (!mr.corner_normals.is_empty()) {
gpu::convert_normals(mr.corner_normals, normals);
}
else if (mr.sharp_faces.is_empty()) {
extract_vert_normals(mr.corner_verts, mr.mesh->vert_normals(), normals);
extract_vert_normals(mr.corner_verts, get_vert_normals(), normals);
}
else {
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const Span<bool> sharp_faces = mr.sharp_faces;
const Span<float3> vert_normals = mr.mesh->vert_normals();
const Span<float3> vert_normals = get_vert_normals();
const Span<float3> face_normals = mr.face_normals;
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face : range) {