From a020907844360253f694596e5104a25e6d8e7bc7 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Mon, 25 Aug 2025 14:23:35 +0200 Subject: [PATCH] Multires Bake: Implement vector displacement baking Supports baking to object and tangent space. Compatible with Cycles Vector Displacement node which has the (tangent, normal, bitangent) convention. The viewport situation is a bit confusing: seems that Eevee does not handle vector displacement properly and rips all faces apart. Cycles renders the displaced object correctly. Not entirely happy with the UI, as displacement space does not really belong to the Output, but so doesn't Low Resolution Mesh. Perhaps the best would be to have a separate pass to revisit the settings, and also make it more clear what the Low Resolution Mesh actually does. Pull Request: https://projects.blender.org/blender/blender/pulls/145014 --- intern/cycles/blender/addon/ui.py | 2 + .../blenloader/intern/versioning_500.cc | 6 + source/blender/editors/object/object_bake.cc | 4 + source/blender/makesdna/DNA_scene_defaults.h | 1 + source/blender/makesdna/DNA_scene_types.h | 10 +- source/blender/makesrna/intern/rna_scene.cc | 24 ++-- source/blender/render/RE_multires_bake.h | 1 + source/blender/render/intern/multires_bake.cc | 108 +++++++++++------- ..._multires_vector_displacement_object.blend | 3 + ...multires_vector_displacement_tangent.blend | 3 + ...om_multires_vector_displacement_object.png | 3 + ...m_multires_vector_displacement_tangent.png | 3 + 12 files changed, 118 insertions(+), 50 deletions(-) create mode 100644 tests/files/render/bake/bake_from_multires_vector_displacement_object.blend create mode 100644 tests/files/render/bake/bake_from_multires_vector_displacement_tangent.blend create mode 100644 tests/files/render/bake/cycles_renders/bake_from_multires_vector_displacement_object.png create mode 100644 tests/files/render/bake/cycles_renders/bake_from_multires_vector_displacement_tangent.png diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 0594ba3138b..61a6c03511e 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -2147,6 +2147,8 @@ class CYCLES_RENDER_PT_bake_output(CyclesButtonsPanel, Panel): layout.prop(cbk, "use_clear", text="Clear Image") if cbk.type in {'DISPLACEMENT', 'VECTOR_DISPLACEMENT'}: layout.prop(cbk, "use_lores_mesh") + if cbk.type == 'VECTOR_DISPLACEMENT': + layout.prop(cbk, "displacement_space", text="Space") else: layout.prop(cbk, "target") if cbk.target == 'IMAGE_TEXTURES': diff --git a/source/blender/blenloader/intern/versioning_500.cc b/source/blender/blenloader/intern/versioning_500.cc index 110e5f43c25..3b42ed149aa 100644 --- a/source/blender/blenloader/intern/versioning_500.cc +++ b/source/blender/blenloader/intern/versioning_500.cc @@ -2329,6 +2329,12 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 62)) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + scene->r.bake.displacement_space = R_BAKE_SPACE_OBJECT; + } + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/editors/object/object_bake.cc b/source/blender/editors/object/object_bake.cc index e22e1468cfe..f78745951a9 100644 --- a/source/blender/editors/object/object_bake.cc +++ b/source/blender/editors/object/object_bake.cc @@ -95,6 +95,7 @@ struct MultiresBakeJob { char bake_margin_type; /** mode of baking (displacement, normals, AO) */ eBakeType type; + eBakeSpace displacement_space; /** Use low-resolution mesh when baking displacement maps */ bool use_low_resolution_mesh; }; @@ -315,6 +316,7 @@ static wmOperatorStatus multiresbake_image_exec_locked(bContext *C, wmOperator * bake.bake_margin_type = eBakeMarginType(scene->r.bake.margin_type); } bake.type = eBakeType(scene->r.bake.type); + bake.displacement_space = eBakeSpace(scene->r.bake.displacement_space); bake.use_low_resolution_mesh = scene->r.bake.flag & R_BAKE_LORES_MESH; bake.ob_image = bake_object_image_get_array(object); @@ -352,6 +354,7 @@ static void init_multiresbake_job(bContext *C, MultiresBakeJob *bkj) bkj->bake_margin_type = eBakeMarginType(scene->r.bake.margin_type); } bkj->type = eBakeType(scene->r.bake.type); + bkj->displacement_space = eBakeSpace(scene->r.bake.displacement_space); bkj->use_low_resolution_mesh = scene->r.bake.flag & R_BAKE_LORES_MESH; bkj->bake_clear = scene->r.bake.flag & R_BAKE_CLEAR; @@ -402,6 +405,7 @@ static void multiresbake_startjob(void *bkv, wmJobWorkerStatus *worker_status) bake.bake_margin = bkj->bake_margin; bake.bake_margin_type = eBakeMarginType(bkj->bake_margin_type); bake.type = bkj->type; + bake.displacement_space = bkj->displacement_space; bake.use_low_resolution_mesh = bkj->use_low_resolution_mesh; bake.ob_image = data->ob_image; diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index c5b33a90dc0..e0aef924d5a 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -38,6 +38,7 @@ .margin_type = R_BAKE_ADJACENT_FACES, \ .normal_space = R_BAKE_SPACE_TANGENT, \ .normal_swizzle = {R_BAKE_POSX, R_BAKE_POSY, R_BAKE_POSZ}, \ + .displacement_space = R_BAKE_SPACE_OBJECT, \ } #define _DNA_DEFAULT_FFMpegCodecData \ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 9b96845ae61..4d5a95d09c3 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -665,11 +665,15 @@ typedef struct BakeData { char normal_swizzle[3]; char normal_space; + char displacement_space; + char target; char save_mode; char margin_type; char view_from; + char _pad[7]; + struct Object *cage_object; } BakeData; @@ -744,13 +748,13 @@ typedef enum eBakePassFilter { R_BAKE_PASS_FILTER_COLOR = (1 << 8), } eBakePassFilter; -/** #BakeData::normal_space */ -enum { +/** #BakeData::normal_space and #BakeData::displacement_space */ +typedef enum eBakeSpace { R_BAKE_SPACE_CAMERA = 0, R_BAKE_SPACE_WORLD = 1, R_BAKE_SPACE_OBJECT = 2, R_BAKE_SPACE_TANGENT = 3, -}; +} eBakeSpace; #define R_BAKE_PASS_FILTER_ALL (~0) diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index 78e9c7b16b4..ddfd1a36bc8 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -5844,15 +5844,17 @@ static void rna_def_bake_data(BlenderRNA *brna) //{R_BAKE_AO, "AO", 0, "Ambient Occlusion", "Bake ambient occlusion"}, {R_BAKE_NORMALS, "NORMALS", 0, "Normals", "Bake normals"}, {R_BAKE_DISPLACEMENT, "DISPLACEMENT", 0, "Displacement", "Bake displacement"}, + {R_BAKE_VECTOR_DISPLACEMENT, + "VECTOR_DISPLACEMENT", + 0, + "Vector Displacement", + "Bake vector displacement"}, + {0, nullptr, 0, nullptr, nullptr}, + }; - /* TODO(sergey): Uncomment once tangent space displacement is supported. */ - /* Use C++ style comment because #if 0 breaks indentation. */ - // {RE_BAKE_VECTOR_DISPLACEMENT, - // "VECTOR_DISPLACEMENT", - // 0, - // "Vector Displacement", - // "Bake vector displacement"}, - + static const EnumPropertyItem displacement_space_items[] = { + {R_BAKE_SPACE_OBJECT, "OBJECT", 0, "Object", "Bake the displacement in object space"}, + {R_BAKE_SPACE_TANGENT, "TANGENT", 0, "Tangent", "Bake the displacement in tangent space"}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -6054,6 +6056,12 @@ static void rna_def_bake_data(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Low Resolution Mesh", "Calculate heights against unsubdivided low resolution mesh"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); + + prop = RNA_def_property(srna, "displacement_space", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "displacement_space"); + RNA_def_property_enum_items(prop, displacement_space_items); + RNA_def_property_ui_text(prop, "Displacement Space", "Choose displacement space for baking"); + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); } static void rna_def_view_layers(BlenderRNA *brna, PropertyRNA *cprop) diff --git a/source/blender/render/RE_multires_bake.h b/source/blender/render/RE_multires_bake.h index 441c279ee87..063ac375cfd 100644 --- a/source/blender/render/RE_multires_bake.h +++ b/source/blender/render/RE_multires_bake.h @@ -29,6 +29,7 @@ struct MultiresBakeRender { int bake_margin = 0; eBakeMarginType bake_margin_type = R_BAKE_ADJACENT_FACES; eBakeType type = R_BAKE_NORMALS; + eBakeSpace displacement_space = R_BAKE_SPACE_OBJECT; /* Use low-resolution mesh when baking displacement maps. * When true displacement is calculated between the final position in the SubdivCCG and the diff --git a/source/blender/render/intern/multires_bake.cc b/source/blender/render/intern/multires_bake.cc index 58e95c77f86..7534be5a82c 100644 --- a/source/blender/render/intern/multires_bake.cc +++ b/source/blender/render/intern/multires_bake.cc @@ -473,7 +473,61 @@ static float2 get_tile_uv(Image &image, ImageTile &tile) static bool need_tangent(const MultiresBakeRender &bake) { - return bake.type == R_BAKE_NORMALS; + return (bake.type == R_BAKE_NORMALS) || (bake.type == R_BAKE_VECTOR_DISPLACEMENT && + bake.displacement_space == R_BAKE_SPACE_TANGENT); +} + +/* Get matrix which converts tangent space to object space in the (tangent, bitangent, normal) + * convention. */ +static float3x3 get_from_tangent_matrix_tbn(const RasterizeTriangle &triangle, + const float2 &bary_uv) +{ + if (!triangle.has_uv_tangents) { + return float3x3::identity(); + } + + const float u = bary_uv.x; + const float v = bary_uv.y; + const float w = 1 - u - v; + + const float3 &no0 = triangle.normals[0]; + const float3 &no1 = triangle.normals[1]; + const float3 &no2 = triangle.normals[2]; + + const float4 &tang0 = triangle.uv_tangents[0]; + const float4 &tang1 = triangle.uv_tangents[1]; + const float4 &tang2 = triangle.uv_tangents[2]; + + /* The sign is the same at all face vertices for any non-degenerate face. + * Just in case we clamp the interpolated value though. */ + const float sign = (tang0.w * u + tang1.w * v + tang2.w * w) < 0 ? (-1.0f) : 1.0f; + + /* x - tangent + * y - bitangent (B = sign * cross(N, T)) + * z - normal */ + float3x3 from_tangent; + from_tangent.x = tang0.xyz() * u + tang1.xyz() * v + tang2.xyz() * w; + from_tangent.z = no0.xyz() * u + no1.xyz() * v + no2.xyz() * w; + from_tangent.y = sign * math::cross(from_tangent.z, from_tangent.x); + + return from_tangent; +} + +/* Get matrix which converts object space to tangent space in the (tangent, bitangent, normal) + * convention. */ +static float3x3 get_to_tangent_matrix_tbn(const RasterizeTriangle &triangle, const float2 &bary_uv) +{ + const float3x3 from_tangent = get_from_tangent_matrix_tbn(triangle, bary_uv); + return math::invert(from_tangent); +} + +/* Get matrix which converts object space to tangent space in the (tangent, normal, bitangent) + * convention. */ +static float3x3 get_to_tangent_matrix_tnb(const RasterizeTriangle &triangle, const float2 &bary_uv) +{ + float3x3 from_tangent = get_from_tangent_matrix_tbn(triangle, bary_uv); + std::swap(from_tangent.y, from_tangent.z); + return math::invert(from_tangent); } /** \} */ @@ -704,10 +758,11 @@ class MultiresDisplacementBaker : public MultiresBaker { class MultiresVectorDisplacementBaker : public MultiresBaker { const SubdivCCG &high_subdiv_ccg_; + eBakeSpace space_; public: - explicit MultiresVectorDisplacementBaker(const SubdivCCG &subdiv_ccg) - : high_subdiv_ccg_(subdiv_ccg) + MultiresVectorDisplacementBaker(const SubdivCCG &subdiv_ccg, const eBakeSpace space) + : high_subdiv_ccg_(subdiv_ccg), space_(space) { } @@ -721,7 +776,14 @@ class MultiresVectorDisplacementBaker : public MultiresBaker { const float3 high_level_position = sample_position_on_subdiv_ccg( high_subdiv_ccg_, triangle.grid_index, grid_uv); - return high_level_position - bake_level_position; + const float3 displacement = high_level_position - bake_level_position; + + if (space_ == R_BAKE_SPACE_TANGENT) { + const float3x3 to_tangent = get_to_tangent_matrix_tnb(triangle, bary_uv); + return to_tangent * displacement; + } + + return displacement; } void write_pixel(const RasterizeTile &tile, @@ -749,7 +811,7 @@ class MultiresNormalsBaker : public MultiresBaker { const float2 &grid_uv, RasterizeResult & /*result*/) const override { - const float3x3 to_tangent = get_to_tangent_matrix(triangle, bary_uv); + const float3x3 to_tangent = get_to_tangent_matrix_tbn(triangle, bary_uv); const float3 normal = sample_normal_on_subdiv_ccg(subdiv_ccg_, triangle.grid_index, grid_uv); return math::normalize(to_tangent * normal) * 0.5f + float3(0.5f, 0.5f, 0.5f); } @@ -760,39 +822,6 @@ class MultiresNormalsBaker : public MultiresBaker { { write_pixel_to_image_buffer(*tile.ibuf, coord, value); } - - private: - float3x3 get_to_tangent_matrix(const RasterizeTriangle &triangle, const float2 &bary_uv) const - { - if (!triangle.has_uv_tangents) { - return float3x3::identity(); - } - - const float u = bary_uv.x; - const float v = bary_uv.y; - const float w = 1 - u - v; - - const float3 &no0 = triangle.normals[0]; - const float3 &no1 = triangle.normals[1]; - const float3 &no2 = triangle.normals[2]; - - const float4 &tang0 = triangle.uv_tangents[0]; - const float4 &tang1 = triangle.uv_tangents[1]; - const float4 &tang2 = triangle.uv_tangents[2]; - - /* The sign is the same at all face vertices for any non-degenerate face. - * Just in case we clamp the interpolated value though. */ - const float sign = (tang0.w * u + tang1.w * v + tang2.w * w) < 0 ? (-1.0f) : 1.0f; - - /* This sequence of math is designed specifically as is with great care to be compatible with - * our shader. Please don't change without good reason. */ - float3x3 from_tang; - from_tang.x = tang0.xyz() * u + tang1.xyz() * v + tang2.xyz() * w; - from_tang.z = no0.xyz() * u + no1.xyz() * v + no2.xyz() * w; - from_tang.y = sign * math::cross(from_tang[2], from_tang[0]); /* `B = sign * cross(N, T)` */ - - return math::invert(from_tang); - } }; /** \} */ @@ -829,7 +858,8 @@ static std::unique_ptr create_baker(const MultiresBakeRender &bak case R_BAKE_DISPLACEMENT: return std::make_unique(subdiv_ccg, ibuf, extra_buffers); case R_BAKE_VECTOR_DISPLACEMENT: - return std::make_unique(subdiv_ccg); + return std::make_unique(subdiv_ccg, + bake.displacement_space); case R_BAKE_AO: /* Not implemented, should not be used. */ break; diff --git a/tests/files/render/bake/bake_from_multires_vector_displacement_object.blend b/tests/files/render/bake/bake_from_multires_vector_displacement_object.blend new file mode 100644 index 00000000000..ce8951ec79a --- /dev/null +++ b/tests/files/render/bake/bake_from_multires_vector_displacement_object.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:573c5487f7c5944036ade00d65c118fb1340d8b480004b32c7358e23405c0f68 +size 341691 diff --git a/tests/files/render/bake/bake_from_multires_vector_displacement_tangent.blend b/tests/files/render/bake/bake_from_multires_vector_displacement_tangent.blend new file mode 100644 index 00000000000..50c7b0da1f8 --- /dev/null +++ b/tests/files/render/bake/bake_from_multires_vector_displacement_tangent.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5695afc5330dc97fa95783d027ec2a7c8fb1516df4e7bd9ca152a0adb6249bf8 +size 341735 diff --git a/tests/files/render/bake/cycles_renders/bake_from_multires_vector_displacement_object.png b/tests/files/render/bake/cycles_renders/bake_from_multires_vector_displacement_object.png new file mode 100644 index 00000000000..c1dc68a86b2 --- /dev/null +++ b/tests/files/render/bake/cycles_renders/bake_from_multires_vector_displacement_object.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8336c517abef5ed62a4b36529d9a014a5178399066067931522ae0e11c9aec59 +size 54268 diff --git a/tests/files/render/bake/cycles_renders/bake_from_multires_vector_displacement_tangent.png b/tests/files/render/bake/cycles_renders/bake_from_multires_vector_displacement_tangent.png new file mode 100644 index 00000000000..6955129f918 --- /dev/null +++ b/tests/files/render/bake/cycles_renders/bake_from_multires_vector_displacement_tangent.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2e1a5de61d691df188abe52219a9a6a17d146c1c1a59ba78cba8c5846e22dc3 +size 76237