Cycles: Change normal map node to work with undisplaced normal and tangent

This fits better with the way normal and displacement maps are typically
combined. Previously there was a mixing of displaced normal and undisplaced
tangent, which was broken behavior.

Additionally, to undisplaced_N and undisplaced_tangent attributes must now
always be used to get undisplaced coordinates. The regular N and tangent
attributes now always include displacement.

Ref #142022

Pull Request: https://projects.blender.org/blender/blender/pulls/143109
This commit is contained in:
Brecht Van Lommel
2025-08-11 12:08:12 +02:00
parent d256cce766
commit 2193096106
12 changed files with 109 additions and 54 deletions

View File

@@ -793,6 +793,11 @@ bool OSLRenderServices::get_object_standard_attribute(
#endif
if (name == u_normal_map_normal) {
if (sd->type & PRIMITIVE_TRIANGLE) {
const AttributeDescriptor desc = find_attribute(
kg, sd->object, sd->prim, ATTR_STD_NORMAL_UNDISPLACED);
if (desc.offset != ATTR_STD_NOT_FOUND) {
return get_object_attribute(kg, sd, desc, type, derivatives, val);
}
const float3 f = triangle_smooth_normal_unnormalized(kg, sd, sd->Ng, sd->prim, sd->u, sd->v);
return set_attribute(f, type, derivatives, val);
}

View File

@@ -879,6 +879,11 @@ ccl_device_inline bool get_object_standard_attribute(KernelGlobals kg,
else if (name == DeviceStrings::u_normal_map_normal) {
if (sd->type & PRIMITIVE_TRIANGLE) {
const AttributeDescriptor desc = find_attribute(
kg, sd->object, sd->prim, ATTR_STD_NORMAL_UNDISPLACED);
if (desc.offset != ATTR_STD_NOT_FOUND) {
return get_object_attribute(kg, sd, desc, type, derivatives, val);
}
float3 f = triangle_smooth_normal_unnormalized(kg, sd, sd->Ng, sd->prim, sd->u, sd->v);
return set_attribute(f, type, derivatives, val);
}

View File

@@ -7,8 +7,8 @@
shader node_normal_map(float Strength = 1.0,
color Color = color(0.5, 0.5, 1.0),
string space = "tangent",
string attr_name = "geom:tangent",
string attr_sign_name = "geom:tangent_sign",
string attr_name = "geom:undisplaced_tangent",
string attr_sign_name = "geom:undisplaced_tangent_sign",
output normal Normal = N)
{
color mcolor = 2.0 * color(Color[0] - 0.5, Color[1] - 0.5, Color[2] - 0.5);

View File

@@ -361,7 +361,15 @@ ccl_device_noinline void svm_node_normal_map(KernelGlobals kg,
float3 normal;
if (sd->shader & SHADER_SMOOTH_NORMAL) {
normal = triangle_smooth_normal_unnormalized(kg, sd, sd->Ng, sd->prim, sd->u, sd->v);
const AttributeDescriptor attr_undisplaced_normal = find_attribute(
kg, sd->object, sd->prim, ATTR_STD_NORMAL_UNDISPLACED);
if (attr_undisplaced_normal.offset != ATTR_STD_NOT_FOUND) {
normal =
primitive_surface_attribute<float3>(kg, sd, attr_undisplaced_normal, false, false).val;
}
else {
normal = triangle_smooth_normal_unnormalized(kg, sd, sd->Ng, sd->prim, sd->u, sd->v);
}
}
else {
normal = sd->Ng;

View File

@@ -870,6 +870,8 @@ enum AttributeStandard {
ATTR_STD_UV,
ATTR_STD_UV_TANGENT,
ATTR_STD_UV_TANGENT_SIGN,
ATTR_STD_UV_TANGENT_UNDISPLACED,
ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED,
ATTR_STD_VERTEX_COLOR,
ATTR_STD_GENERATED,
ATTR_STD_GENERATED_TRANSFORM,

View File

@@ -320,6 +320,10 @@ const char *Attribute::standard_name(AttributeStandard std)
return "tangent";
case ATTR_STD_UV_TANGENT_SIGN:
return "tangent_sign";
case ATTR_STD_UV_TANGENT_UNDISPLACED:
return "undisplaced_tangent";
case ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED:
return "undisplaced_tangent_sign";
case ATTR_STD_VERTEX_COLOR:
return "vertex_color";
case ATTR_STD_POSITION_UNDEFORMED:
@@ -520,9 +524,11 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name)
attr = add(name, TypeFloat2, ATTR_ELEMENT_CORNER);
break;
case ATTR_STD_UV_TANGENT:
case ATTR_STD_UV_TANGENT_UNDISPLACED:
attr = add(name, TypeVector, ATTR_ELEMENT_CORNER);
break;
case ATTR_STD_UV_TANGENT_SIGN:
case ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED:
attr = add(name, TypeFloat, ATTR_ELEMENT_CORNER);
break;
case ATTR_STD_VERTEX_COLOR:

View File

@@ -798,7 +798,10 @@ void GeometryManager::device_update(Device *device,
/* Apply generated attribute if needed or remove if not needed */
mesh->update_generated(scene);
/* Apply tangents for generated and UVs (if any need them) or remove if not needed */
mesh->update_tangents(scene);
mesh->update_tangents(scene, true);
if (!mesh->has_true_displacement()) {
mesh->update_tangents(scene, false);
}
if (progress.get_cancel()) {
return;

View File

@@ -550,13 +550,6 @@ void GeometryManager::device_update_attributes(Device *device,
for (AttributeRequest &req : attributes.requests) {
Attribute *attr = geom->attributes.find(req);
/* Keep "N" attribute undisplaced for backwards compatibility in Blender 4.5. */
if (attr && attr->std == ATTR_STD_VERTEX_NORMAL) {
if (Attribute *undisplaced_attr = geom->attributes.find(ATTR_STD_NORMAL_UNDISPLACED)) {
attr = undisplaced_attr;
}
}
if (attr) {
/* force a copy if we need to reallocate all the data */
attr->modified |= attributes_need_realloc[Attribute::kernel_type(*attr)];

View File

@@ -115,7 +115,13 @@ struct MikkMeshWrapper {
float *tangent_sign;
};
static void mikk_compute_tangents(Attribute *attr_uv, Mesh *mesh, const bool need_sign)
static void mikk_compute_tangents(Attribute *attr_uv,
Mesh *mesh,
const bool need_sign,
const AttributeStandard tangent_std,
const AttributeStandard tangent_sign_std,
const char *tangent_postfix,
const char *tangent_sign_postfix)
{
/* Create tangent attributes. */
AttributeSet &attributes = mesh->attributes;
@@ -129,11 +135,11 @@ static void mikk_compute_tangents(Attribute *attr_uv, Mesh *mesh, const bool nee
const float3 *normal = attr_vN->data_float3();
const float2 *uv = (attr_uv) ? attr_uv->data_float2() : nullptr;
const ustring name = ustring((attr_uv) ? attr_uv->name.string() + ".tangent" :
Attribute::standard_name(ATTR_STD_UV_TANGENT));
const ustring name = ustring((attr_uv) ? attr_uv->name.string() + tangent_postfix :
Attribute::standard_name(tangent_std));
Attribute *attr;
if (attr_uv == nullptr || attr_uv->std == ATTR_STD_UV) {
attr = attributes.add(ATTR_STD_UV_TANGENT, name);
attr = attributes.add(tangent_std, name);
}
else {
attr = attributes.add(name, TypeVector, ATTR_ELEMENT_CORNER);
@@ -142,12 +148,11 @@ static void mikk_compute_tangents(Attribute *attr_uv, Mesh *mesh, const bool nee
/* Create bitangent sign attribute. */
float *tangent_sign = nullptr;
if (need_sign) {
const ustring name_sign = ustring((attr_uv) ?
attr_uv->name.string() + ".tangent_sign" :
Attribute::standard_name(ATTR_STD_UV_TANGENT_SIGN));
const ustring name_sign = ustring((attr_uv) ? attr_uv->name.string() + tangent_sign_postfix :
Attribute::standard_name(tangent_sign_std));
Attribute *attr_sign;
if (attr_uv == nullptr || attr_uv->std == ATTR_STD_UV) {
attr_sign = attributes.add(ATTR_STD_UV_TANGENT_SIGN, name_sign);
attr_sign = attributes.add(tangent_sign_std, name_sign);
}
else {
attr_sign = attributes.add(name_sign, TypeFloat, ATTR_ELEMENT_CORNER);
@@ -754,9 +759,7 @@ void Mesh::add_undisplaced(Scene *scene)
std::copy_n(verts.data(), size, attr->data_float3());
}
/* Keep "N" attribute undisplaced for backwards compatibility in Blender 4.5. */
if (((need_attribute(scene, ATTR_STD_VERTEX_NORMAL) && has_true_displacement()) ||
need_attribute(scene, ATTR_STD_NORMAL_UNDISPLACED)) &&
if (need_attribute(scene, ATTR_STD_NORMAL_UNDISPLACED) &&
!attributes.find(ATTR_STD_NORMAL_UNDISPLACED))
{
/* Copy vertex normal to attribute */
@@ -789,7 +792,7 @@ void Mesh::update_generated(Scene *scene)
}
}
void Mesh::update_tangents(Scene *scene)
void Mesh::update_tangents(Scene *scene, bool undisplaced)
{
if (!num_triangles()) {
return;
@@ -800,9 +803,22 @@ void Mesh::update_tangents(Scene *scene)
ccl::set<ustring> uv_maps;
Attribute *attr_std_uv = attributes.find(ATTR_STD_UV);
AttributeStandard tangent_std = (undisplaced) ? ATTR_STD_UV_TANGENT_UNDISPLACED :
ATTR_STD_UV_TANGENT;
AttributeStandard tangent_sign_std = (undisplaced) ? ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED :
ATTR_STD_UV_TANGENT_SIGN;
const char *tangent_postfix = (undisplaced) ? ".undisplaced_tangent" : ".tangent";
const char *tangent_sign_postfix = (undisplaced) ? ".undisplaced_tangent_sign" : ".tangent_sign";
/* standard UVs */
if (need_attribute(scene, ATTR_STD_UV_TANGENT) && !attributes.find(ATTR_STD_UV_TANGENT)) {
mikk_compute_tangents(attr_std_uv, this, true); /* sign */
if (need_attribute(scene, tangent_std) && !attributes.find(tangent_std)) {
mikk_compute_tangents(attr_std_uv,
this,
true,
tangent_std,
tangent_sign_std,
tangent_postfix,
tangent_sign_postfix); /* sign */
}
/* now generate for any other UVs requested */
@@ -811,10 +827,16 @@ void Mesh::update_tangents(Scene *scene)
continue;
}
const ustring tangent_name = ustring(attr.name.string() + ".tangent");
const ustring tangent_name = ustring(attr.name.string() + tangent_postfix);
if (need_attribute(scene, tangent_name) && !attributes.find(tangent_name)) {
mikk_compute_tangents(&attr, this, true); /* sign */
mikk_compute_tangents(&attr,
this,
true,
tangent_std,
tangent_sign_std,
tangent_postfix,
tangent_sign_postfix); /* sign */
}
}
}

View File

@@ -212,7 +212,7 @@ class Mesh : public Geometry {
void add_vertex_normals();
void add_undisplaced(Scene *scene);
void update_generated(Scene *scene);
void update_tangents(Scene *scene);
void update_tangents(Scene *scene, bool undisplaced);
void get_uv_tiles(ustring map, unordered_set<int> &tiles) override;

View File

@@ -323,6 +323,8 @@ bool GeometryManager::displace(Device *device, Scene *scene, Mesh *mesh, Progres
}
}
mesh->update_tangents(scene, false);
return true;
}

View File

@@ -4,6 +4,7 @@
#include "scene/shader_nodes.h"
#include "kernel/svm/types.h"
#include "kernel/types.h"
#include "scene/colorspace.h"
#include "scene/constant_fold.h"
#include "scene/film.h"
@@ -7382,13 +7383,15 @@ void NormalMapNode::attributes(Shader *shader, AttributeRequestSet *attributes)
{
if (shader->has_surface_link() && space == NODE_NORMAL_MAP_TANGENT) {
if (attribute.empty()) {
attributes->add(ATTR_STD_UV_TANGENT);
attributes->add(ATTR_STD_UV_TANGENT_SIGN);
attributes->add(ATTR_STD_UV_TANGENT_UNDISPLACED);
attributes->add(ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED);
}
else {
attributes->add(ustring((string(attribute.c_str()) + ".tangent").c_str()));
attributes->add(ustring((string(attribute.c_str()) + ".tangent_sign").c_str()));
attributes->add(ustring((string(attribute.c_str()) + ".undisplaced_tangent").c_str()));
attributes->add(ustring((string(attribute.c_str()) + ".undisplaced_tangent_sign").c_str()));
}
attributes->add(ATTR_STD_NORMAL_UNDISPLACED);
}
ShaderNode::attributes(shader, attributes);
@@ -7404,13 +7407,14 @@ void NormalMapNode::compile(SVMCompiler &compiler)
if (space == NODE_NORMAL_MAP_TANGENT) {
if (attribute.empty()) {
attr = compiler.attribute(ATTR_STD_UV_TANGENT);
attr_sign = compiler.attribute(ATTR_STD_UV_TANGENT_SIGN);
attr = compiler.attribute(ATTR_STD_UV_TANGENT_UNDISPLACED);
attr_sign = compiler.attribute(ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED);
}
else {
attr = compiler.attribute(ustring((string(attribute.c_str()) + ".tangent").c_str()));
attr = compiler.attribute(
ustring((string(attribute.c_str()) + ".undisplaced_tangent").c_str()));
attr_sign = compiler.attribute(
ustring((string(attribute.c_str()) + ".tangent_sign").c_str()));
ustring((string(attribute.c_str()) + ".undisplaced_tangent_sign").c_str()));
}
}
@@ -7427,13 +7431,15 @@ void NormalMapNode::compile(OSLCompiler &compiler)
{
if (space == NODE_NORMAL_MAP_TANGENT) {
if (attribute.empty()) {
compiler.parameter("attr_name", ustring("geom:tangent"));
compiler.parameter("attr_sign_name", ustring("geom:tangent_sign"));
compiler.parameter("attr_name", ustring("geom:undisplaced_tangent"));
compiler.parameter("attr_sign_name", ustring("geom:undisplaced_tangent_sign"));
}
else {
compiler.parameter("attr_name", ustring((string(attribute.c_str()) + ".tangent").c_str()));
compiler.parameter("attr_sign_name",
ustring((string(attribute.c_str()) + ".tangent_sign").c_str()));
compiler.parameter("attr_name",
ustring((string(attribute.c_str()) + ".undisplaced_tangent").c_str()));
compiler.parameter(
"attr_sign_name",
ustring((string(attribute.c_str()) + ".undisplaced_tangent_sign").c_str()));
}
}
@@ -7656,12 +7662,12 @@ void VectorDisplacementNode::attributes(Shader *shader, AttributeRequestSet *att
{
if (shader->has_surface_link() && space == NODE_NORMAL_MAP_TANGENT) {
if (attribute.empty()) {
attributes->add(ATTR_STD_UV_TANGENT);
attributes->add(ATTR_STD_UV_TANGENT_SIGN);
attributes->add(ATTR_STD_UV_TANGENT_UNDISPLACED);
attributes->add(ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED);
}
else {
attributes->add(ustring((string(attribute.c_str()) + ".tangent").c_str()));
attributes->add(ustring((string(attribute.c_str()) + ".tangent_sign").c_str()));
attributes->add(ustring((string(attribute.c_str()) + ".undisplaced_tangent").c_str()));
attributes->add(ustring((string(attribute.c_str()) + ".undisplaced_tangent_sign").c_str()));
}
}
@@ -7679,13 +7685,14 @@ void VectorDisplacementNode::compile(SVMCompiler &compiler)
if (space == NODE_NORMAL_MAP_TANGENT) {
if (attribute.empty()) {
attr = compiler.attribute(ATTR_STD_UV_TANGENT);
attr_sign = compiler.attribute(ATTR_STD_UV_TANGENT_SIGN);
attr = compiler.attribute(ATTR_STD_UV_TANGENT_UNDISPLACED);
attr_sign = compiler.attribute(ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED);
}
else {
attr = compiler.attribute(ustring((string(attribute.c_str()) + ".tangent").c_str()));
attr = compiler.attribute(
ustring((string(attribute.c_str()) + ".undisplaced_tangent").c_str()));
attr_sign = compiler.attribute(
ustring((string(attribute.c_str()) + ".tangent_sign").c_str()));
ustring((string(attribute.c_str()) + ".undisplaced_tangent_sign").c_str()));
}
}
@@ -7704,13 +7711,15 @@ void VectorDisplacementNode::compile(OSLCompiler &compiler)
{
if (space == NODE_NORMAL_MAP_TANGENT) {
if (attribute.empty()) {
compiler.parameter("attr_name", ustring("geom:tangent"));
compiler.parameter("attr_sign_name", ustring("geom:tangent_sign"));
compiler.parameter("attr_name", ustring("geom:undisplaced_tangent"));
compiler.parameter("attr_sign_name", ustring("geom:undisplaced_tangent_sign"));
}
else {
compiler.parameter("attr_name", ustring((string(attribute.c_str()) + ".tangent").c_str()));
compiler.parameter("attr_sign_name",
ustring((string(attribute.c_str()) + ".tangent_sign").c_str()));
compiler.parameter("attr_name",
ustring((string(attribute.c_str()) + ".undisplaced_tangent").c_str()));
compiler.parameter(
"attr_sign_name",
ustring((string(attribute.c_str()) + ".undisplaced_tangent_sign").c_str()));
}
}