Files
test2/source/blender/render/intern/multires_bake.cc
Sergey Sharybin a020907844 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
2025-08-25 14:23:35 +02:00

1725 lines
60 KiB
C++

/* SPDX-FileCopyrightText: 2012-2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup render
*
* Baker from the Multires
* =======================
*
* This file is an implementation of a special baking mode which bakes data (normals, displacement)
* from the highest multi-resolution level to the current viewport subdivision level.
*
* The initial historical reasoning for having such baker was:
* - Lower memory footprint than the regular baker.
* - Performance (due to lower overhead compared to the regular baker at that time).
* - Ease of use: no need to have explicit object to define cage.
* Over the time some of these points became less relevant, but the ease of use is still there.
*
* The general idea of the algorithm is pretty simple:
* - Rasterize UV of the mesh at the bake level.
* - For every UV pixel that is rasterized, figure out attributes on the bake level mesh and the
* highest subdivision multi-resolution level (such as normal, position).
* - Do the math (like convert normal to the tangent space),
* - Write pixel to the image.
*
* SubdivCCG is used to access attributes at the highest multi-resolution subdivision level.
*
* The core rasterization logic works on triangles and those triangles are fed to the rasterizer in
* a way that makes it easy to sample attributes in the SubdivCCG:
* - Triangle knows which CCG index it corresponds to (triangle never covers multiple grids).
* - It also knows UV coordinates of its vertices within that grid.
*
* The way triangles are calculated when baking to the base level is pretty straightforward:
* - Triangles are actually calculated from a quad.
* - Quad vertices align with the grid vertices.
* This means that the top level loop iterates over face corners, calculates quad for the grids,
* and passes it to the triangle rasterization.
*
* When baking to a non-0 subdivision level a special trick is used to know grid index and its UV
* coordinates in the base mesh: for every loop in the bake-level mesh the algorithm calculates
* this information using subdiv's foreach logic. This assumes that the bake level mesh is
* calculated using the same foreach logic.
*
* Use low resolution mesh
* -----------------------
*
* This is a special option for the displacement baker.
*
* When it is ON: displacement is calculated between the multi-resolution at the highest
* subdivision level and the bake-level mesh.
*
* When it is OFF: displacement is calculated between the multi-resolution at the highest
* subdivision level and a mesh which is created from the bake level mesh by subdividing it further
* to reach the same subdivision level of the highest multi-resolution level. Additionally, the
* texture UV, and UV tangents are used from this "special" mesh.
*
* Possible optimizations
* ----------------------
*
* - Reuse mesh from the viewport as bake-level mesh.
*
* It could be a bit challenging since mesh could be in sculpt mode, where it has own SubdivCCG
* and does not have UV map on the subdivided state. Additionally, it will make it harder to
* calculate tangent space as well.
*/
#include "RE_multires_bake.h"
#include <atomic>
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "BLI_array.hh"
#include "BLI_listbase.h"
#include "BLI_math_base.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.hh"
#include "BLI_span.hh"
#include "BLI_task.hh"
#include "BLI_threads.h"
#include "BKE_attribute.hh"
#include "BKE_customdata.hh"
#include "BKE_global.hh"
#include "BKE_image.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_tangent.hh"
#include "BKE_multires.hh"
#include "BKE_subdiv.hh"
#include "BKE_subdiv_ccg.hh"
#include "BKE_subdiv_eval.hh"
#include "BKE_subdiv_foreach.hh"
#include "BKE_subdiv_mesh.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "DEG_depsgraph.hh"
#include "RE_texture_margin.h"
namespace blender::render {
namespace {
namespace subdiv = bke::subdiv;
/* -------------------------------------------------------------------- */
/** \name Math utilities that should actually be in the BLI
* The only reason they are here is that there is currently no great place to put them to.
* \{ */
template<class T> T interp_barycentric_triangle(const T data[3], const float2 &uv)
{
return data[0] * uv.x + data[1] * uv.y + data[2] * (1.0f - uv.x - uv.y);
}
template<class T>
T interp_bilinear_quad(
const float u, const float v, const T &p0, const T &p1, const T &p2, const T &p3)
{
const float w0 = (1 - u) * (1 - v);
const float w1 = u * (1 - v);
const float w2 = u * v;
const float w3 = (1 - u) * v;
return p0 * w0 + p1 * w1 + p2 * w2 + p3 * w3;
}
float2 resolve_tri_uv(const float2 &st, const float2 &st0, const float2 &st1, const float2 &st2)
{
float2 uv;
resolve_tri_uv_v2(uv, st, st0, st1, st2);
return uv;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Implementation of data accessor from the subdiv
* \{ */
template<class T> class Grid {
Span<T> data_;
int side_size_;
T get_element(const int x, const int y) const
{
const int64_t index = int64_t(y) * side_size_ + x;
return data_[index];
}
public:
Grid(const Span<T> data, const int side_size) : data_(data), side_size_(side_size)
{
BLI_assert(data.size() == side_size_ * side_size_);
}
T sample(const float2 uv) const
{
const float2 xy = uv * (side_size_ - 1);
const int x0 = int(xy.x);
const int x1 = x0 >= (side_size_ - 1) ? (side_size_ - 1) : (x0 + 1);
const int y0 = int(xy.y);
const int y1 = y0 >= (side_size_ - 1) ? (side_size_ - 1) : (y0 + 1);
const float u = xy.x - x0;
const float v = xy.y - y0;
return interp_bilinear_quad(
u, v, get_element(x0, y0), get_element(x1, y0), get_element(x1, y1), get_element(x0, y1));
}
};
template<class T>
Grid<T> get_subdiv_ccg_grid(const SubdivCCG &subdiv_ccg, const int grid_index, const Span<T> data)
{
const int64_t offset = int64_t(grid_index) * subdiv_ccg.grid_area;
return Grid(Span(data.data() + offset, subdiv_ccg.grid_area), subdiv_ccg.grid_size);
}
float3 sample_position_on_subdiv_ccg(const SubdivCCG &subdiv_ccg,
const int grid_index,
const float2 grid_uv)
{
const Grid<float3> grid = get_subdiv_ccg_grid(
subdiv_ccg, grid_index, subdiv_ccg.positions.as_span());
return grid.sample(grid_uv);
}
float3 sample_normal_on_subdiv_ccg(const SubdivCCG &subdiv_ccg,
const int grid_index,
const float2 grid_uv)
{
/* TODO(sergey): Support flat normals.
* It seems that the baker always used smooth interpolation for CCG. */
const Grid<float3> grid = get_subdiv_ccg_grid(
subdiv_ccg, grid_index, subdiv_ccg.normals.as_span());
return grid.sample(grid_uv);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Storage of mesh arrays, for quicker access without any lookup
* \{ */
struct MeshArrays {
Span<float3> vert_positions;
Span<float3> vert_normals;
Span<int> corner_verts;
Span<int3> corner_tris;
Span<float3> corner_normals;
Span<int> tri_faces;
OffsetIndices<int> faces;
Span<float3> face_normals;
VArraySpan<bool> sharp_faces;
VArraySpan<float2> uv_map;
VArraySpan<int> material_indices;
MeshArrays() = default;
explicit MeshArrays(const Mesh &mesh)
{
bke::AttributeAccessor attributes = mesh.attributes();
const StringRef active_uv_map = CustomData_get_active_layer_name(&mesh.corner_data,
CD_PROP_FLOAT2);
vert_positions = mesh.vert_positions();
vert_normals = mesh.vert_normals();
corner_verts = mesh.corner_verts();
corner_tris = mesh.corner_tris();
corner_normals = mesh.corner_normals();
tri_faces = mesh.corner_tri_faces();
faces = mesh.faces();
face_normals = mesh.face_normals();
sharp_faces = *attributes.lookup_or_default<bool>("sharp_face", bke::AttrDomain::Face, false);
uv_map = *attributes.lookup<float2>(active_uv_map, bke::AttrDomain::Corner);
material_indices = *attributes.lookup_or_default<int>(
"material_index", bke::AttrDomain::Face, 0);
}
};
/* Calculate UV map coordinates at the center of the face (grid coordinates (0, 0)). */
static float2 face_center_tex_uv_calc(const MeshArrays &mesh_arrays, const int face_index)
{
const IndexRange &face = mesh_arrays.faces[face_index];
float2 tex_uv_acc(0.0f, 0.0f);
for (const int corner : face) {
tex_uv_acc += mesh_arrays.uv_map[corner];
}
return tex_uv_acc / face.size();
}
/* Calculate smooth normal coordinates at the center of the face (grid coordinates (0, 0)).
* NOTE: The returned value is not normalized to allow linear interpolation with other grid
* elements. */
static float3 face_center_smooth_normal_calc(const MeshArrays &mesh_arrays, const int face_index)
{
const IndexRange &face = mesh_arrays.faces[face_index];
float3 normal_acc(0.0f, 0.0f, 0.0f);
for (const int corner : face) {
normal_acc += mesh_arrays.vert_normals[mesh_arrays.corner_verts[corner]];
}
/* NOTE: No normalization here: do it after interpolation at the baking point.
*
* This preserves linearity of operation. If normalization is done here interpolation will go
* wrong. */
return normal_acc / face.size();
}
/* Calculate tangent space for the given mesh state. */
Array<float4> calc_uv_tangents(const MeshArrays &mesh_arrays)
{
Array<Array<float4>> tangent_data = bke::mesh::calc_uv_tangents(mesh_arrays.vert_positions,
mesh_arrays.faces,
mesh_arrays.corner_verts,
mesh_arrays.corner_tris,
mesh_arrays.tri_faces,
mesh_arrays.sharp_faces,
mesh_arrays.vert_normals,
mesh_arrays.face_normals,
mesh_arrays.corner_normals,
{mesh_arrays.uv_map});
return tangent_data[0];
}
/* Calculate tangent space at the center of the face (grid coordinates (0, 0)). */
static float4 face_center_uv_tangent_calc(const MeshArrays &mesh_arrays,
const Span<float4> uv_tangents,
const int face_index)
{
const IndexRange &face = mesh_arrays.faces[face_index];
float4 tex_uv_acc(0.0f, 0.0f, 0.0f, 0.0f);
for (const int corner : face) {
tex_uv_acc += uv_tangents[corner];
}
return tex_uv_acc / face.size();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Common data types and utilities
* \{ */
struct ExtraBuffers {
Array<float> displacement_buffer;
Array<char> mask_buffer;
};
struct RasterizeTile {
ImBuf *ibuf = nullptr; /* Image buffer of the tile. */
ExtraBuffers *extra_buffers = nullptr;
float2 uv_offset; /* UV coordinate of the tile origin. */
};
struct RasterizeTriangle {
/* UV coordinates with the CCG. All vertices belong to the same grid. */
int grid_index;
float2 grid_uvs[3];
/* UV texture coordinates of the triangle vertices within the tile. */
float2 tex_uvs[3];
/* Positions and normals of the vertices, at the bake level. */
float3 positions[3];
float3 normals[3];
/* Triangle is shaded flat: it has the same normal at every point of its surface.
* Face normal is stored in all elements of the normals array. */
bool is_flat;
/* Optional tangents.
* The uv_tangents might be uninitialized if has_uv_tangents=false. */
bool has_uv_tangents;
float4 uv_tangents[3];
float3 get_position(const float2 &uv) const
{
return interp_barycentric_triangle(positions, uv);
}
float3 get_normal(const float2 &uv) const
{
if (is_flat) {
return normals[0];
}
return math::normalize(interp_barycentric_triangle(normals, uv));
}
};
struct RasterizeQuad {
/* UV coordinates with the CCG. All vertices belong to the same grid. */
int grid_index;
float2 grid_uvs[4];
/* UV texture coordinates of the triangle vertices within the tile. */
float2 tex_uvs[4];
/* Positions and normals of the vertices, at the bake level. */
float3 positions[4];
float3 normals[4];
/* Quad is shaded flat: it has the same normal at every point of its surface.
* Face normal is stored in all elements of the normals array. */
bool is_flat;
/* Optional tangents.
* The uv_tangents might be uninitialized if has_uv_tangents=false. */
bool has_uv_tangents;
float4 uv_tangents[4];
};
struct RasterizeResult {
float height_min = FLT_MAX;
float height_max = -FLT_MAX;
};
struct BakedImBuf {
Image *image;
ImBuf *ibuf;
ExtraBuffers extra_buffers;
float2 uv_offset;
};
struct MultiresBakeResult {
Vector<BakedImBuf> baked_ibufs;
/* Bake-level mesh subdivided to the final multi-resolution level.
* It is created by displacement baker that used "Use Low Resolution Mesh" OFF.
*
* This mesh is to be used to filter baked images. */
const Mesh *highres_bake_mesh = nullptr;
/* Minimum and maximum height during displacement baking. */
float height_min = FLT_MAX;
float height_max = -FLT_MAX;
};
class MultiresBaker {
public:
virtual ~MultiresBaker() = default;
virtual float3 bake_pixel(const RasterizeTriangle &triangle,
const float2 &bary_uv,
const float2 &grid_uv,
RasterizeResult &result) const = 0;
virtual void write_pixel(const RasterizeTile &tile,
const int2 &coord,
const float3 &value) const = 0;
protected:
void write_pixel_to_image_buffer(ImBuf &ibuf, const int2 &coord, const float3 &value) const
{
const int64_t pixel = int64_t(ibuf.x) * coord.y + coord.x;
if (ibuf.float_buffer.data) {
/* TODO(sergey): Properly tackle ibuf.channels. */
BLI_assert(ibuf.channels == 4);
float *rrgbf = ibuf.float_buffer.data + pixel * 4;
rrgbf[0] = value[0];
rrgbf[1] = value[1];
rrgbf[2] = value[2];
rrgbf[3] = 1.0f;
ibuf.userflags |= IB_RECT_INVALID;
}
if (ibuf.byte_buffer.data) {
uchar *rrgb = ibuf.byte_buffer.data + pixel * 4;
unit_float_to_uchar_clamp_v3(rrgb, value);
rrgb[3] = 255;
}
ibuf.userflags |= IB_DISPLAY_BUFFER_INVALID;
}
};
static bool multiresbake_test_break(const MultiresBakeRender &bake)
{
if (!bake.stop) {
/* This means baker is executed outside from job system (for example, from Python API).
* In this case there is no need to cancel, as it will be quite strange to cancel out
* execution of a script. */
return false;
}
return *bake.stop || G.is_break;
}
static float2 get_tile_uv(Image &image, ImageTile &tile)
{
float uv_offset[2];
BKE_image_get_tile_uv(&image, tile.tile_number, uv_offset);
return uv_offset;
}
static bool need_tangent(const MultiresBakeRender &bake)
{
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);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Baking pipeline
* \{ */
static void flush_pixel(const MultiresBaker &baker,
const RasterizeTile &tile,
const RasterizeTriangle &triangle,
const int x,
const int y,
RasterizeResult &result)
{
const float2 st{(x + 0.5f) / tile.ibuf->x + tile.uv_offset.x,
(y + 0.5f) / tile.ibuf->y + tile.uv_offset.y};
const float2 bary_uv = resolve_tri_uv(
st, triangle.tex_uvs[0], triangle.tex_uvs[1], triangle.tex_uvs[2]);
const float2 grid_uv = interp_barycentric_triangle(triangle.grid_uvs, bary_uv);
const float3 baked_pixel = baker.bake_pixel(triangle, bary_uv, grid_uv, result);
baker.write_pixel(tile, int2(x, y), baked_pixel);
}
static void set_rast_triangle(const MultiresBaker &baker,
const RasterizeTile &tile,
const RasterizeTriangle &triangle,
const int x,
const int y,
RasterizeResult &result)
{
const int w = tile.ibuf->x;
const int h = tile.ibuf->y;
if (x >= 0 && x < w && y >= 0 && y < h) {
const int64_t pixel = int64_t(y) * w + x;
if (tile.extra_buffers->mask_buffer[pixel] == FILTER_MASK_NULL) {
tile.extra_buffers->mask_buffer[pixel] = FILTER_MASK_USED;
flush_pixel(baker, tile, triangle, x, y, result);
}
}
}
static void rasterize_half(const MultiresBaker &baker,
const RasterizeTile &tile,
const RasterizeTriangle &triangle,
const float2 &s0,
const float2 &s1,
const float2 &l0,
const float2 &l1,
const int y0_in,
const int y1_in,
const bool is_mid_right,
RasterizeResult &result)
{
const bool s_stable = fabsf(s1.y - s0.y) > FLT_EPSILON;
const bool l_stable = fabsf(l1.y - l0.y) > FLT_EPSILON;
const int w = tile.ibuf->x;
const int h = tile.ibuf->y;
if (y1_in <= 0 || y0_in >= h) {
return;
}
const int y0 = y0_in < 0 ? 0 : y0_in;
const int y1 = y1_in >= h ? h : y1_in;
for (int y = y0; y < y1; y++) {
/*-b(x-x0) + a(y-y0) = 0 */
float x_l = s_stable ? (s0.x + (((s1.x - s0.x) * (y - s0.y)) / (s1.y - s0.y))) : s0.x;
float x_r = l_stable ? (l0.x + (((l1.x - l0.x) * (y - l0.y)) / (l1.y - l0.y))) : l0.x;
if (is_mid_right) {
std::swap(x_l, x_r);
}
int iXl = int(ceilf(x_l));
int iXr = int(ceilf(x_r));
if (iXr > 0 && iXl < w) {
iXl = iXl < 0 ? 0 : iXl;
iXr = iXr >= w ? w : iXr;
for (int x = iXl; x < iXr; x++) {
set_rast_triangle(baker, tile, triangle, x, y, result);
}
}
}
}
static void rasterize_triangle(const MultiresBaker &baker,
const RasterizeTile &tile,
const RasterizeTriangle &triangle,
RasterizeResult &result)
{
const float2 ibuf_size(tile.ibuf->x, tile.ibuf->y);
const float2 &st0_in = triangle.tex_uvs[0];
const float2 &st1_in = triangle.tex_uvs[1];
const float2 &st2_in = triangle.tex_uvs[2];
float2 p_low = st0_in * ibuf_size - 0.5f;
float2 p_mid = st1_in * ibuf_size - 0.5f;
float2 p_high = st2_in * ibuf_size - 0.5f;
/* Skip degenerates. */
if ((p_low.x == p_mid.x && p_low.y == p_mid.y) || (p_low.x == p_mid.x && p_low.y == p_high.y) ||
(p_mid.x == p_high.x && p_mid.y == p_high.y))
{
return;
}
/* Sort by T. */
if (p_low.y > p_mid.y && p_low.y > p_high.y) {
std::swap(p_high.x, p_low.x);
std::swap(p_high.y, p_low.y);
}
else if (p_mid.y > p_high.y) {
std::swap(p_high.x, p_mid.x);
std::swap(p_high.y, p_mid.y);
}
if (p_low.y > p_mid.y) {
std::swap(p_low.x, p_mid.x);
std::swap(p_low.y, p_mid.y);
}
/* Check if mid-point is to the left or to the right of the lo-hi edge. */
const bool is_mid_right = math::cross(p_mid - p_high, p_high - p_low) > 0;
const int ylo = int(ceilf(p_low.y));
const int yhi_beg = int(ceilf(p_mid.y));
const int yhi = int(ceilf(p_high.y));
rasterize_half(
baker, tile, triangle, p_low, p_mid, p_low, p_high, ylo, yhi_beg, is_mid_right, result);
rasterize_half(
baker, tile, triangle, p_mid, p_high, p_low, p_high, yhi_beg, yhi, is_mid_right, result);
}
static void rasterize_quad(const MultiresBaker &baker,
const RasterizeTile &tile,
const RasterizeQuad &quad,
RasterizeResult &result)
{
RasterizeTriangle triangle;
triangle.grid_index = quad.grid_index;
triangle.is_flat = quad.is_flat;
triangle.has_uv_tangents = quad.has_uv_tangents;
const int3 quad_split_data[2] = {{0, 1, 2}, {2, 3, 0}};
for (const int3 &triangle_idx : Span(quad_split_data, 2)) {
triangle.grid_uvs[0] = quad.grid_uvs[triangle_idx.x];
triangle.grid_uvs[1] = quad.grid_uvs[triangle_idx.y];
triangle.grid_uvs[2] = quad.grid_uvs[triangle_idx.z];
triangle.tex_uvs[0] = quad.tex_uvs[triangle_idx.x];
triangle.tex_uvs[1] = quad.tex_uvs[triangle_idx.y];
triangle.tex_uvs[2] = quad.tex_uvs[triangle_idx.z];
triangle.positions[0] = quad.positions[triangle_idx.x];
triangle.positions[1] = quad.positions[triangle_idx.y];
triangle.positions[2] = quad.positions[triangle_idx.z];
triangle.normals[0] = quad.normals[triangle_idx.x];
triangle.normals[1] = quad.normals[triangle_idx.y];
triangle.normals[2] = quad.normals[triangle_idx.z];
if (triangle.has_uv_tangents) {
triangle.uv_tangents[0] = quad.uv_tangents[triangle_idx.x];
triangle.uv_tangents[1] = quad.uv_tangents[triangle_idx.y];
triangle.uv_tangents[2] = quad.uv_tangents[triangle_idx.z];
}
rasterize_triangle(baker, tile, triangle, result);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Displacement Baker
* \{ */
class MultiresDisplacementBaker : public MultiresBaker {
const SubdivCCG &high_subdiv_ccg_;
public:
MultiresDisplacementBaker(const SubdivCCG &subdiv_ccg,
const ImBuf &ibuf,
ExtraBuffers &extra_buffers)
: high_subdiv_ccg_(subdiv_ccg)
{
extra_buffers.displacement_buffer.reinitialize(IMB_get_pixel_count(&ibuf));
extra_buffers.displacement_buffer.fill(0);
}
float3 bake_pixel(const RasterizeTriangle &triangle,
const float2 &bary_uv,
const float2 &grid_uv,
RasterizeResult &result) const override
{
const float3 bake_level_position = triangle.get_position(bary_uv);
const float3 bake_level_normal = triangle.get_normal(bary_uv);
const float3 high_level_position = sample_position_on_subdiv_ccg(
high_subdiv_ccg_, triangle.grid_index, grid_uv);
const float length = math::dot(bake_level_normal, (high_level_position - bake_level_position));
result.height_min = math::min(result.height_min, length);
result.height_max = math::max(result.height_max, length);
return {length, length, length};
}
void write_pixel(const RasterizeTile &tile,
const int2 &coord,
const float3 &value) const override
{
const ImBuf &ibuf = *tile.ibuf;
const int64_t pixel = int64_t(ibuf.x) * coord.y + coord.x;
tile.extra_buffers->displacement_buffer[pixel] = value.x;
write_pixel_to_image_buffer(*tile.ibuf, coord, value);
}
};
class MultiresVectorDisplacementBaker : public MultiresBaker {
const SubdivCCG &high_subdiv_ccg_;
eBakeSpace space_;
public:
MultiresVectorDisplacementBaker(const SubdivCCG &subdiv_ccg, const eBakeSpace space)
: high_subdiv_ccg_(subdiv_ccg), space_(space)
{
}
float3 bake_pixel(const RasterizeTriangle &triangle,
const float2 &bary_uv,
const float2 &grid_uv,
RasterizeResult & /*result*/) const override
{
const float3 bake_level_position = triangle.get_position(bary_uv);
const float3 high_level_position = sample_position_on_subdiv_ccg(
high_subdiv_ccg_, triangle.grid_index, grid_uv);
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,
const int2 &coord,
const float3 &value) const override
{
write_pixel_to_image_buffer(*tile.ibuf, coord, value);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Normal Maps Baker
* \{ */
class MultiresNormalsBaker : public MultiresBaker {
const SubdivCCG &subdiv_ccg_;
public:
explicit MultiresNormalsBaker(const SubdivCCG &subdiv_ccg) : subdiv_ccg_(subdiv_ccg) {}
float3 bake_pixel(const RasterizeTriangle &triangle,
const float2 &bary_uv,
const float2 &grid_uv,
RasterizeResult & /*result*/) const override
{
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);
}
void write_pixel(const RasterizeTile &tile,
const int2 &coord,
const float3 &value) const override
{
write_pixel_to_image_buffer(*tile.ibuf, coord, value);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Image initialization
* \{ */
static void initialize_images(MultiresBakeRender &bake)
{
bake.images.clear();
for (Image *image : bake.ob_image) {
if (image) {
bake.images.add(image);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bake to base (non-subdivided) mesh
* \{ */
static std::unique_ptr<MultiresBaker> create_baker(const MultiresBakeRender &bake,
const SubdivCCG &subdiv_ccg,
const ImBuf &ibuf,
ExtraBuffers &extra_buffers)
{
switch (bake.type) {
case R_BAKE_NORMALS:
return std::make_unique<MultiresNormalsBaker>(subdiv_ccg);
case R_BAKE_DISPLACEMENT:
return std::make_unique<MultiresDisplacementBaker>(subdiv_ccg, ibuf, extra_buffers);
case R_BAKE_VECTOR_DISPLACEMENT:
return std::make_unique<MultiresVectorDisplacementBaker>(subdiv_ccg,
bake.displacement_space);
case R_BAKE_AO:
/* Not implemented, should not be used. */
break;
}
BLI_assert_unreachable();
return nullptr;
}
static void rasterize_base_face(const MultiresBaker &baker,
const RasterizeTile &tile,
const MeshArrays &mesh_arrays,
const Span<float4> uv_tangents,
const int face_index,
RasterizeResult &result)
{
const IndexRange &face = mesh_arrays.faces[face_index];
const Span<int> face_verts = mesh_arrays.corner_verts.slice(face);
RasterizeQuad quad;
/* - Grid coordinate (0, 0): face center.
* - Grid axis U points from the face center to the middle of the edge connecting corner to
* next_corner.
* - Grid axis V points from the face center to the middle of the edge connecting prev_corner to
* corner. */
quad.grid_uvs[0] = float2(0.0f, 0.0f);
quad.grid_uvs[1] = float2(1.0f, 0.0f);
quad.grid_uvs[2] = float2(1.0f, 1.0f);
quad.grid_uvs[3] = float2(0.0f, 1.0f);
quad.tex_uvs[0] = face_center_tex_uv_calc(mesh_arrays, face_index);
quad.positions[0] = bke::mesh::face_center_calc(mesh_arrays.vert_positions, face_verts);
/* TODO(sergey): Support corner normals. */
quad.is_flat = mesh_arrays.sharp_faces[face_index];
if (quad.is_flat) {
quad.normals[0] = mesh_arrays.face_normals[face_index];
}
else {
quad.normals[0] = face_center_smooth_normal_calc(mesh_arrays, face_index);
}
quad.has_uv_tangents = !uv_tangents.is_empty();
if (quad.has_uv_tangents) {
quad.uv_tangents[0] = face_center_uv_tangent_calc(mesh_arrays, uv_tangents, face_index);
}
for (const int corner : face) {
const int prev_corner = bke::mesh::face_corner_prev(face, corner);
const int next_corner = bke::mesh::face_corner_next(face, corner);
const float3 &position = mesh_arrays.vert_positions[mesh_arrays.corner_verts[corner]];
const float3 &next_position =
mesh_arrays.vert_positions[mesh_arrays.corner_verts[next_corner]];
const float3 &prev_position =
mesh_arrays.vert_positions[mesh_arrays.corner_verts[prev_corner]];
quad.grid_index = corner;
quad.tex_uvs[1] = (mesh_arrays.uv_map[corner] + mesh_arrays.uv_map[next_corner]) * 0.5f;
quad.tex_uvs[2] = mesh_arrays.uv_map[corner] - tile.uv_offset;
quad.tex_uvs[3] = (mesh_arrays.uv_map[prev_corner] + mesh_arrays.uv_map[corner]) * 0.5f;
quad.positions[1] = (position + next_position) * 0.5f;
quad.positions[2] = position;
quad.positions[3] = (prev_position + position) * 0.5f;
if (quad.is_flat) {
quad.normals[1] = quad.normals[0];
quad.normals[2] = quad.normals[0];
quad.normals[3] = quad.normals[0];
}
else {
const float3 &normal = mesh_arrays.vert_normals[mesh_arrays.corner_verts[corner]];
const float3 &next_normal = mesh_arrays.vert_normals[mesh_arrays.corner_verts[next_corner]];
const float3 &prev_normal = mesh_arrays.vert_normals[mesh_arrays.corner_verts[prev_corner]];
/* NOTE: No normalization here: do it after interpolation at the baking point.
*
* This preserves linearity of operation. If normalization is done here interpolation will go
* wrong. */
quad.normals[1] = (normal + next_normal) * 0.5f;
quad.normals[2] = normal;
quad.normals[3] = (prev_normal + normal) * 0.5f;
}
if (quad.has_uv_tangents) {
const float4 &tangent = uv_tangents[corner];
const float4 &next_tangent = uv_tangents[next_corner];
const float4 &prev_tangent = uv_tangents[prev_corner];
quad.uv_tangents[1] = (tangent + next_tangent) * 0.5f;
quad.uv_tangents[2] = tangent;
quad.uv_tangents[3] = (prev_tangent + tangent) * 0.5f;
}
rasterize_quad(baker, tile, quad, result);
}
}
static void bake_single_image_to_base_mesh(MultiresBakeRender &bake,
const Mesh &bake_level_mesh,
const SubdivCCG &subdiv_ccg,
Image &image,
ImageTile &image_tile,
ImBuf &ibuf,
ExtraBuffers &extra_buffers,
MultiresBakeResult &result)
{
std::unique_ptr<MultiresBaker> baker = create_baker(bake, subdiv_ccg, ibuf, extra_buffers);
if (!baker) {
return;
}
MeshArrays mesh_arrays(bake_level_mesh);
Array<float4> uv_tangents;
if (need_tangent(bake)) {
uv_tangents = calc_uv_tangents(mesh_arrays);
}
RasterizeTile tile;
tile.ibuf = &ibuf;
tile.extra_buffers = &extra_buffers;
tile.uv_offset = get_tile_uv(image, image_tile);
SpinLock spin_lock;
BLI_spin_init(&spin_lock);
std::atomic<int> num_baked_faces = 0;
threading::parallel_for(mesh_arrays.faces.index_range(), 1, [&](const IndexRange range) {
for (const int64_t face_index : range) {
if (multiresbake_test_break(bake)) {
return;
}
/* Check whether the face is to be baked into the current image. */
const int mat_nr = mesh_arrays.material_indices[face_index];
const Image *face_image = mat_nr < bake.ob_image.size() ? bake.ob_image[mat_nr] : nullptr;
if (face_image != &image) {
continue;
}
RasterizeResult rasterize_result;
rasterize_base_face(*baker, tile, mesh_arrays, uv_tangents, face_index, rasterize_result);
++num_baked_faces;
BLI_spin_lock(&spin_lock);
result.height_min = std::min(result.height_min, rasterize_result.height_min);
result.height_max = std::max(result.height_max, rasterize_result.height_max);
if (bake.do_update) {
*bake.do_update = true;
}
if (bake.progress) {
*bake.progress = (float(bake.num_baked_objects) +
float(num_baked_faces) / mesh_arrays.faces.size()) /
bake.num_total_objects;
}
BLI_spin_unlock(&spin_lock);
}
});
BLI_spin_end(&spin_lock);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bake to subdivided mesh (base mesh with some subdivision levels)
* \{ */
struct GridCoord {
int grid_index;
float2 uv;
};
static Array<GridCoord> get_subdivided_corner_grid_coords(subdiv::Subdiv &subdiv,
const Mesh &coarse_mesh,
const int level)
{
struct SubdividedCornerGridCoordData {
MeshArrays coarse_mesh_arrays;
Array<GridCoord> corner_grid_coords;
};
subdiv::ToMeshSettings mesh_settings;
mesh_settings.resolution = (1 << level) + 1;
SubdividedCornerGridCoordData data;
data.coarse_mesh_arrays = MeshArrays(coarse_mesh);
subdiv::ForeachContext foreach_context;
foreach_context.user_data = &data;
foreach_context.topology_info = [](const subdiv::ForeachContext *context,
const int /*num_vertices*/,
const int /*num_edges*/,
const int num_corners,
const int /*num_faces*/,
const int * /*subdiv_face_offset*/) -> bool {
SubdividedCornerGridCoordData *data = static_cast<SubdividedCornerGridCoordData *>(
context->user_data);
data->corner_grid_coords.reinitialize(num_corners);
return true;
};
foreach_context.loop = [](const subdiv::ForeachContext *context,
void * /*tls*/,
const int /*ptex_face_index*/,
const float u,
const float v,
const int /*coarse_corner_index*/,
const int coarse_face_index,
const int coarse_corner,
const int subdiv_corner_index,
const int /*subdiv_vertex_index*/,
const int /*subdiv_edge_index*/) {
SubdividedCornerGridCoordData *data = static_cast<SubdividedCornerGridCoordData *>(
context->user_data);
const float2 ptex_uv(u, v);
const IndexRange coarse_face = data->coarse_mesh_arrays.faces[coarse_face_index];
GridCoord &corner_grid_coord = data->corner_grid_coords[subdiv_corner_index];
corner_grid_coord.grid_index = coarse_face.start() + coarse_corner;
if (coarse_face.size() == 4) {
corner_grid_coord.uv = subdiv::ptex_face_uv_to_grid_uv(
subdiv::rotate_quad_to_corner(coarse_corner, ptex_uv));
}
else {
corner_grid_coord.uv = subdiv::ptex_face_uv_to_grid_uv(ptex_uv);
}
};
foreach_subdiv_geometry(&subdiv, &foreach_context, &mesh_settings, &coarse_mesh);
return data.corner_grid_coords;
}
static void rasterize_subdivided_face(const MultiresBaker &baker,
const RasterizeTile &tile,
const MeshArrays &mesh_arrays,
const Span<GridCoord> &corner_grid_coords,
const Span<float4> uv_tangents,
const int face_index,
RasterizeResult &result)
{
const IndexRange &face = mesh_arrays.faces[face_index];
/* This code operates with mesh with at leats one subdivision level applied. Such mesh only has
* quad faces as per how subdivision works. */
BLI_assert(face.size() == 4);
RasterizeQuad quad;
/* TODO(sergey): Support corner normals. */
quad.is_flat = mesh_arrays.sharp_faces[face_index];
quad.has_uv_tangents = !uv_tangents.is_empty();
quad.grid_index = corner_grid_coords[face.start()].grid_index;
for (int i = 0; i < 4; ++i) {
const int corner = face[i];
const int vertex = mesh_arrays.corner_verts[corner];
BLI_assert(corner_grid_coords[corner].grid_index == quad.grid_index);
quad.grid_uvs[i] = corner_grid_coords[corner].uv;
quad.tex_uvs[i] = mesh_arrays.uv_map[corner];
quad.positions[i] = mesh_arrays.vert_positions[vertex];
if (!quad.is_flat) {
quad.normals[i] = mesh_arrays.vert_normals[vertex];
}
if (quad.has_uv_tangents) {
quad.uv_tangents[i] = uv_tangents[corner];
}
}
if (quad.is_flat) {
quad.normals[0] = mesh_arrays.face_normals[face_index];
quad.normals[1] = quad.normals[0];
quad.normals[2] = quad.normals[0];
quad.normals[3] = quad.normals[0];
}
rasterize_quad(baker, tile, quad, result);
}
static void bake_single_image_to_subdivided_mesh(MultiresBakeRender &bake,
const Mesh &bake_level_mesh,
const SubdivCCG &subdiv_ccg,
Image &image,
ImageTile &image_tile,
ImBuf &ibuf,
ExtraBuffers &extra_buffers,
MultiresBakeResult &result)
{
std::unique_ptr<MultiresBaker> baker = create_baker(bake, subdiv_ccg, ibuf, extra_buffers);
if (!baker) {
return;
}
MeshArrays mesh_arrays(bake_level_mesh);
Array<float4> uv_tangents;
if (need_tangent(bake)) {
uv_tangents = calc_uv_tangents(mesh_arrays);
}
RasterizeTile tile;
tile.ibuf = &ibuf;
tile.extra_buffers = &extra_buffers;
tile.uv_offset = get_tile_uv(image, image_tile);
const Array<GridCoord> corner_grid_coords = get_subdivided_corner_grid_coords(
*subdiv_ccg.subdiv, *bake.base_mesh, bake.multires_modifier->lvl);
SpinLock spin_lock;
BLI_spin_init(&spin_lock);
std::atomic<int> num_baked_faces = 0;
threading::parallel_for(mesh_arrays.faces.index_range(), 1, [&](const IndexRange range) {
for (const int64_t face_index : range) {
if (multiresbake_test_break(bake)) {
return;
}
/* Check whether the face is to be baked into the current image. */
const int mat_nr = mesh_arrays.material_indices[face_index];
const Image *face_image = mat_nr < bake.ob_image.size() ? bake.ob_image[mat_nr] : nullptr;
if (face_image != &image) {
continue;
}
RasterizeResult rasterize_result;
rasterize_subdivided_face(*baker,
tile,
mesh_arrays,
corner_grid_coords,
uv_tangents,
face_index,
rasterize_result);
++num_baked_faces;
BLI_spin_lock(&spin_lock);
result.height_min = std::min(result.height_min, rasterize_result.height_min);
result.height_max = std::max(result.height_max, rasterize_result.height_max);
if (bake.do_update) {
*bake.do_update = true;
}
if (bake.progress) {
*bake.progress = (float(bake.num_baked_objects) +
float(num_baked_faces) / mesh_arrays.faces.size()) /
bake.num_total_objects;
}
BLI_spin_unlock(&spin_lock);
}
});
BLI_spin_end(&spin_lock);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name High resolution displacement baking
* Used in cases of displacement baking with Low Resolution Mesh equals False.
* \{ */
/* Subdivide bake_level_mesh to the level of `total level - viewport level`.
* Essentially bring the bake_level_mesh to the same resolution level as the top multi-resolution
* level. */
static const Mesh *create_highres_mesh(const Mesh &bake_level_mesh,
const MultiresModifierData &multires_modifier)
{
const int top_level = multires_modifier.totlvl;
const int bake_level = multires_modifier.lvl;
const int subdivide_level = top_level - bake_level;
if (subdivide_level == 0) {
return &bake_level_mesh;
}
subdiv::Settings subdiv_settings;
BKE_multires_subdiv_settings_init(&subdiv_settings, &multires_modifier);
subdiv::Subdiv *subdiv = subdiv::update_from_mesh(nullptr, &subdiv_settings, &bake_level_mesh);
subdiv::ToMeshSettings mesh_settings;
mesh_settings.resolution = (1 << subdivide_level) + 1;
Mesh *result = subdiv::subdiv_to_mesh(subdiv, &mesh_settings, &bake_level_mesh);
subdiv::free(subdiv);
return result;
}
/* Get grid coordinates for every corner of the highres_bake_mesh. */
static Array<GridCoord> get_highres_mesh_loop_grid_coords(
subdiv::Subdiv &subdiv,
const MultiresModifierData &multires_modifier,
const Mesh &base_mesh,
const Mesh &bake_level_mesh,
const Mesh &highres_bake_mesh)
{
UNUSED_VARS_NDEBUG(highres_bake_mesh);
if (multires_modifier.lvl == 0) {
/* Simple case: baking from subdivided mesh highres_bake_mesh to the base mesh. */
return get_subdivided_corner_grid_coords(
subdiv, bake_level_mesh, multires_modifier.totlvl - multires_modifier.lvl);
}
/* More tricky case:
* - The base_mesh is first subdivided to the viewport level (bake_level_mesh)
* - The bake_level_mesh is then further subdivided (highres_bake_mesh).
*
* This case needs an extra level of indirection: map loops from the highres_bake_mesh to the
* faces of the bake_level_mesh, and then interpolate the grid coordinates calculated for the
* bake_level_mesh to get grid coordinates.
*
* The coarse mesh here is the same as bake_level_mesh, and the subdiv mesh is the same as
* highres_bake_mesh.
*
* It is possible to optimize the memory usage here by utilizing an implicit knowledge about
* how faces in the high-res mesh are created from the bake level mesh: Since the bake level
* mesh has some amount of subdivisions in this branch all its faces are quads. So all the
* faces in the high-res mesh are also quads, created in the grid pattern from the bake level
* faces. */
struct HighresCornerGridCoordData {
MeshArrays bake_level_mesh_arrays;
Array<GridCoord> bake_level_corner_grid_coords;
Array<GridCoord> corner_grid_coords;
};
const int top_level = multires_modifier.totlvl;
const int bake_level = multires_modifier.lvl;
const int subdivide_level = top_level - bake_level;
subdiv::ToMeshSettings mesh_settings;
mesh_settings.resolution = (1 << subdivide_level) + 1;
HighresCornerGridCoordData data;
data.bake_level_mesh_arrays = MeshArrays(bake_level_mesh);
data.bake_level_corner_grid_coords = get_subdivided_corner_grid_coords(
subdiv, base_mesh, multires_modifier.lvl);
subdiv::ForeachContext foreach_context;
foreach_context.user_data = &data;
foreach_context.topology_info = [](const subdiv::ForeachContext *context,
const int /*num_vertices*/,
const int /*num_edges*/,
const int num_corners,
const int /*num_faces*/,
const int * /*subdiv_face_offset*/) -> bool {
HighresCornerGridCoordData *data = static_cast<HighresCornerGridCoordData *>(
context->user_data);
data->corner_grid_coords.reinitialize(num_corners);
return true;
};
foreach_context.loop = [](const subdiv::ForeachContext *context,
void * /*tls*/,
const int /*ptex_face_index*/,
const float u,
const float v,
const int /*bake_level_corner_index*/,
const int bake_level_face_index,
const int /*bake_level_corner*/,
const int highres_corner_index,
const int /*highres_vertex_index*/,
const int /*highres_edge_index*/) {
HighresCornerGridCoordData *data = static_cast<HighresCornerGridCoordData *>(
context->user_data);
const Span<GridCoord> bake_level_corner_grid_coords = data->bake_level_corner_grid_coords;
const IndexRange bake_level_face = data->bake_level_mesh_arrays.faces[bake_level_face_index];
BLI_assert(bake_level_face.size() == 4);
const int bake_level_face_start = bake_level_face.start();
GridCoord &corner_grid_coord = data->corner_grid_coords[highres_corner_index];
corner_grid_coord.grid_index = bake_level_corner_grid_coords[bake_level_face_start].grid_index;
corner_grid_coord.uv = interp_bilinear_quad(
u,
v,
bake_level_corner_grid_coords[bake_level_face_start + 0].uv,
bake_level_corner_grid_coords[bake_level_face_start + 1].uv,
bake_level_corner_grid_coords[bake_level_face_start + 2].uv,
bake_level_corner_grid_coords[bake_level_face_start + 3].uv);
/* Loops of the bake level mesh are supposed to be in the same grid. */
BLI_assert(corner_grid_coord.grid_index ==
bake_level_corner_grid_coords[bake_level_face_start + 1].grid_index);
BLI_assert(corner_grid_coord.grid_index ==
bake_level_corner_grid_coords[bake_level_face_start + 2].grid_index);
BLI_assert(corner_grid_coord.grid_index ==
bake_level_corner_grid_coords[bake_level_face_start + 3].grid_index);
};
foreach_subdiv_geometry(&subdiv, &foreach_context, &mesh_settings, &bake_level_mesh);
BLI_assert(data.corner_grid_coords.size() == highres_bake_mesh.corners_num);
return data.corner_grid_coords;
}
static void bake_single_image_displacement(MultiresBakeRender &bake,
const Mesh &bake_level_mesh,
const SubdivCCG &subdiv_ccg,
Image &image,
ImageTile &image_tile,
ImBuf &ibuf,
ExtraBuffers &extra_buffers,
MultiresBakeResult &result)
{
std::unique_ptr<MultiresBaker> baker = create_baker(bake, subdiv_ccg, ibuf, extra_buffers);
if (!baker) {
return;
}
const Mesh *highres_bake_mesh = create_highres_mesh(bake_level_mesh, *bake.multires_modifier);
const Array<GridCoord> corner_grid_coords = get_highres_mesh_loop_grid_coords(
*subdiv_ccg.subdiv,
*bake.multires_modifier,
*bake.base_mesh,
bake_level_mesh,
*highres_bake_mesh);
MeshArrays mesh_arrays(*highres_bake_mesh);
Array<float4> uv_tangents;
if (need_tangent(bake)) {
uv_tangents = calc_uv_tangents(mesh_arrays);
}
RasterizeTile tile;
tile.ibuf = &ibuf;
tile.extra_buffers = &extra_buffers;
tile.uv_offset = get_tile_uv(image, image_tile);
SpinLock spin_lock;
BLI_spin_init(&spin_lock);
std::atomic<int> num_baked_faces = 0;
threading::parallel_for(mesh_arrays.faces.index_range(), 1, [&](const IndexRange range) {
for (const int64_t face_index : range) {
if (multiresbake_test_break(bake)) {
return;
}
/* Check whether the face is to be baked into the current image. */
const int mat_nr = mesh_arrays.material_indices[face_index];
const Image *face_image = mat_nr < bake.ob_image.size() ? bake.ob_image[mat_nr] : nullptr;
if (face_image != &image) {
continue;
}
RasterizeResult rasterize_result;
rasterize_subdivided_face(*baker,
tile,
mesh_arrays,
corner_grid_coords,
uv_tangents,
face_index,
rasterize_result);
++num_baked_faces;
BLI_spin_lock(&spin_lock);
result.height_min = std::min(result.height_min, rasterize_result.height_min);
result.height_max = std::max(result.height_max, rasterize_result.height_max);
if (bake.do_update) {
*bake.do_update = true;
}
if (bake.progress) {
*bake.progress = (float(bake.num_baked_objects) +
float(num_baked_faces) / mesh_arrays.faces.size()) /
bake.num_total_objects;
}
BLI_spin_unlock(&spin_lock);
}
});
BLI_spin_end(&spin_lock);
result.highres_bake_mesh = highres_bake_mesh;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Image baking entry point
* \{ */
static void bake_single_image(MultiresBakeRender &bake,
const Mesh &bake_level_mesh,
const SubdivCCG &subdiv_ccg,
Image &image,
ImageTile &image_tile,
ImBuf &ibuf,
ExtraBuffers &extra_buffers,
MultiresBakeResult &result)
{
if (ELEM(bake.type, R_BAKE_DISPLACEMENT, R_BAKE_VECTOR_DISPLACEMENT) &&
!bake.use_low_resolution_mesh &&
bake.multires_modifier->lvl != bake.multires_modifier->totlvl)
{
bake_single_image_displacement(
bake, bake_level_mesh, subdiv_ccg, image, image_tile, ibuf, extra_buffers, result);
return;
}
if (bake.multires_modifier->lvl == 0) {
bake_single_image_to_base_mesh(
bake, bake_level_mesh, subdiv_ccg, image, image_tile, ibuf, extra_buffers, result);
return;
}
bake_single_image_to_subdivided_mesh(
bake, bake_level_mesh, subdiv_ccg, image, image_tile, ibuf, extra_buffers, result);
}
static void bake_images(MultiresBakeRender &bake,
const Mesh &bake_level_mesh,
const SubdivCCG &subdiv_ccg,
MultiresBakeResult &result)
{
for (Image *image : bake.images) {
LISTBASE_FOREACH (ImageTile *, image_tile, &image->tiles) {
ImageUser iuser;
BKE_imageuser_default(&iuser);
iuser.tile = image_tile->tile_number;
ImBuf *ibuf = BKE_image_acquire_ibuf(image, &iuser, nullptr);
if (ibuf && ibuf->x > 0 && ibuf->y > 0) {
result.baked_ibufs.append({});
BakedImBuf &baked_ibuf = result.baked_ibufs.last();
baked_ibuf.image = image;
baked_ibuf.ibuf = ibuf;
baked_ibuf.uv_offset = get_tile_uv(*image, *image_tile);
ExtraBuffers &extra_buffers = baked_ibuf.extra_buffers;
extra_buffers.mask_buffer.reinitialize(int64_t(ibuf->y) * ibuf->x);
extra_buffers.mask_buffer.fill(FILTER_MASK_NULL);
bake_single_image(
bake, bake_level_mesh, subdiv_ccg, *image, *image_tile, *ibuf, extra_buffers, result);
}
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Image postprocessing
* \{ */
static void bake_ibuf_normalize_displacement(ImBuf &ibuf,
const MutableSpan<float> displacement,
const Span<char> mask,
const float displacement_min,
const float displacement_max)
{
const float *current_displacement = displacement.data();
const char *current_mask = mask.data();
const float max_distance = math::max(math::abs(displacement_min), math::abs(displacement_max));
if (max_distance <= 1e-5f) {
const float col[4] = {0.5f, 0.5f, 0.5f, 1.0f};
IMB_rectfill(&ibuf, col);
return;
}
/* TODO(sergey): Look into multi-threading this loop. */
const size_t ibuf_pixel_count = IMB_get_pixel_count(&ibuf);
for (size_t i = 0; i < ibuf_pixel_count; i++) {
if (*current_mask == FILTER_MASK_USED) {
const float normalized_displacement = (*current_displacement + max_distance) /
(max_distance * 2);
if (ibuf.float_buffer.data) {
/* TODO(sergey): Properly tackle ibuf.channels. */
BLI_assert(ibuf.channels == 4);
float *fp = ibuf.float_buffer.data + int64_t(i) * 4;
fp[0] = fp[1] = fp[2] = normalized_displacement;
fp[3] = 1.0f;
}
if (ibuf.byte_buffer.data) {
uchar *cp = ibuf.byte_buffer.data + int64_t(i) * 4;
cp[0] = cp[1] = cp[2] = unit_float_to_uchar_clamp(normalized_displacement);
cp[3] = 255;
}
}
current_displacement++;
current_mask++;
}
}
static void bake_ibuf_filter(ImBuf &ibuf,
const MutableSpan<char> mask,
const Mesh &bake_level_mesh,
const int margin,
const eBakeMarginType margin_type,
const float2 uv_offset)
{
/* NOTE: Must check before filtering. */
const bool is_new_alpha = (ibuf.planes != R_IMF_PLANES_RGBA) && BKE_imbuf_alpha_test(&ibuf);
if (margin) {
switch (margin_type) {
case R_BAKE_ADJACENT_FACES: {
const char *active_uv_map = CustomData_get_active_layer_name(&bake_level_mesh.corner_data,
CD_PROP_FLOAT2);
RE_generate_texturemargin_adjacentfaces(
&ibuf, mask.data(), margin, &bake_level_mesh, active_uv_map, uv_offset);
break;
}
default:
/* Fall through. */
case R_BAKE_EXTEND:
IMB_filter_extend(&ibuf, mask.data(), margin);
break;
}
}
/* If the bake results in new alpha then change the image setting. */
if (is_new_alpha) {
ibuf.planes = R_IMF_PLANES_RGBA;
}
else {
if (margin && ibuf.planes != R_IMF_PLANES_RGBA) {
/* Clear alpha added by filtering. */
IMB_rectfill_alpha(&ibuf, 1.0f);
}
}
}
static void finish_images(MultiresBakeRender &bake,
const Mesh &bake_level_mesh,
MultiresBakeResult &result)
{
const bool use_displacement_buffer = bake.type == R_BAKE_DISPLACEMENT;
for (BakedImBuf &baked_ibuf : result.baked_ibufs) {
Image *image = baked_ibuf.image;
ImBuf *ibuf = baked_ibuf.ibuf;
if (use_displacement_buffer) {
bake_ibuf_normalize_displacement(*ibuf,
baked_ibuf.extra_buffers.displacement_buffer,
baked_ibuf.extra_buffers.mask_buffer,
result.height_min,
result.height_max);
}
bake_ibuf_filter(*ibuf,
baked_ibuf.extra_buffers.mask_buffer,
bake_level_mesh,
bake.bake_margin,
bake.bake_margin_type,
baked_ibuf.uv_offset);
ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
BKE_image_mark_dirty(image, ibuf);
if (ibuf->float_buffer.data) {
ibuf->userflags |= IB_RECT_INVALID;
}
BKE_image_release_ibuf(image, ibuf, nullptr);
DEG_id_tag_update(&image->id, 0);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Helpers to create mesh and CCG at requested levels
* \{ */
static subdiv::Subdiv *create_subdiv(const Mesh &mesh,
const MultiresModifierData &multires_modifier)
{
subdiv::Settings subdiv_settings;
BKE_multires_subdiv_settings_init(&subdiv_settings, &multires_modifier);
subdiv::Subdiv *subdiv = subdiv::update_from_mesh(nullptr, &subdiv_settings, &mesh);
subdiv::displacement_attach_from_multires(subdiv, &mesh, &multires_modifier);
/* Initialization evaluation of the limit surface and the displacement. */
if (!subdiv::eval_begin_from_mesh(subdiv, &mesh, subdiv::SUBDIV_EVALUATOR_TYPE_CPU)) {
subdiv::free(subdiv);
return nullptr;
}
subdiv::eval_init_displacement(subdiv);
return subdiv;
}
static std::unique_ptr<SubdivCCG> create_subdiv_ccg(const Mesh &mesh,
const MultiresModifierData &multires_modifier)
{
subdiv::Subdiv *subdiv = create_subdiv(mesh, multires_modifier);
if (!subdiv) {
return nullptr;
}
SubdivToCCGSettings settings;
settings.resolution = (1 << multires_modifier.totlvl) + 1;
settings.need_normal = true;
settings.need_mask = false;
return BKE_subdiv_to_ccg(*subdiv, settings, mesh);
}
static Mesh *create_bake_level_mesh(const Mesh &base_mesh,
const MultiresModifierData &multires_modifier)
{
subdiv::Subdiv *subdiv = create_subdiv(base_mesh, multires_modifier);
if (!subdiv) {
return nullptr;
}
subdiv::ToMeshSettings mesh_settings;
mesh_settings.resolution = (1 << multires_modifier.lvl) + 1;
subdiv::displacement_attach_from_multires(subdiv, &base_mesh, &multires_modifier);
Mesh *result = subdiv::subdiv_to_mesh(subdiv, &mesh_settings, &base_mesh);
subdiv::free(subdiv);
return result;
}
/** \} */
} // namespace
} // namespace blender::render
void RE_multires_bake_images(MultiresBakeRender &bake)
{
using namespace blender::render;
std::unique_ptr<SubdivCCG> subdiv_ccg = create_subdiv_ccg(*bake.base_mesh,
*bake.multires_modifier);
Mesh *bake_level_mesh = bake.base_mesh;
if (bake.multires_modifier->lvl != 0) {
bake_level_mesh = create_bake_level_mesh(*bake.base_mesh, *bake.multires_modifier);
}
MultiresBakeResult result;
initialize_images(bake);
bake_images(bake, *bake_level_mesh, *subdiv_ccg, result);
const Mesh *filter_mesh = result.highres_bake_mesh ? result.highres_bake_mesh : bake_level_mesh;
finish_images(bake, *filter_mesh, result);
if (result.highres_bake_mesh && result.highres_bake_mesh != bake_level_mesh) {
BKE_id_free(nullptr, const_cast<Mesh *>(result.highres_bake_mesh));
}
if (bake_level_mesh != bake.base_mesh) {
BKE_id_free(nullptr, bake_level_mesh);
}
}