godot/modules/lightmapper_rd/lightmapper_rd.cpp
Rémi Verschelde d95794ec8a
One Copyright Update to rule them all
As many open source projects have started doing it, we're removing the
current year from the copyright notice, so that we don't need to bump
it every year.

It seems like only the first year of publication is technically
relevant for copyright notices, and even that seems to be something
that many companies stopped listing altogether (in a version controlled
codebase, the commits are a much better source of date of publication
than a hardcoded copyright statement).

We also now list Godot Engine contributors first as we're collectively
the current maintainers of the project, and we clarify that the
"exclusive" copyright of the co-founders covers the timespan before
opensourcing (their further contributions are included as part of Godot
Engine contributors).

Also fixed "cf." Frenchism - it's meant as "refer to / see".
2023-01-05 13:25:55 +01:00

1713 lines
60 KiB
C++

/**************************************************************************/
/* lightmapper_rd.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "lightmapper_rd.h"
#include "core/config/project_settings.h"
#include "core/math/geometry_2d.h"
#include "lm_blendseams.glsl.gen.h"
#include "lm_compute.glsl.gen.h"
#include "lm_raster.glsl.gen.h"
#include "servers/rendering/rendering_device_binds.h"
//uncomment this if you want to see textures from all the process saved
//#define DEBUG_TEXTURES
void LightmapperRD::add_mesh(const MeshData &p_mesh) {
ERR_FAIL_COND(p_mesh.albedo_on_uv2.is_null() || p_mesh.albedo_on_uv2->is_empty());
ERR_FAIL_COND(p_mesh.emission_on_uv2.is_null() || p_mesh.emission_on_uv2->is_empty());
ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_width() != p_mesh.emission_on_uv2->get_width());
ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_height() != p_mesh.emission_on_uv2->get_height());
ERR_FAIL_COND(p_mesh.points.size() == 0);
MeshInstance mi;
mi.data = p_mesh;
mesh_instances.push_back(mi);
}
void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) {
Light l;
l.type = LIGHT_TYPE_DIRECTIONAL;
l.direction[0] = p_direction.x;
l.direction[1] = p_direction.y;
l.direction[2] = p_direction.z;
l.color[0] = p_color.r;
l.color[1] = p_color.g;
l.color[2] = p_color.b;
l.energy = p_energy;
l.static_bake = p_static;
l.size = Math::tan(Math::deg_to_rad(p_angular_distance));
l.shadow_blur = p_shadow_blur;
lights.push_back(l);
}
void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) {
Light l;
l.type = LIGHT_TYPE_OMNI;
l.position[0] = p_position.x;
l.position[1] = p_position.y;
l.position[2] = p_position.z;
l.range = p_range;
l.attenuation = p_attenuation;
l.color[0] = p_color.r;
l.color[1] = p_color.g;
l.color[2] = p_color.b;
l.energy = p_energy;
l.static_bake = p_static;
l.size = p_size;
l.shadow_blur = p_shadow_blur;
lights.push_back(l);
}
void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) {
Light l;
l.type = LIGHT_TYPE_SPOT;
l.position[0] = p_position.x;
l.position[1] = p_position.y;
l.position[2] = p_position.z;
l.direction[0] = p_direction.x;
l.direction[1] = p_direction.y;
l.direction[2] = p_direction.z;
l.range = p_range;
l.attenuation = p_attenuation;
l.cos_spot_angle = Math::cos(Math::deg_to_rad(p_spot_angle));
l.inv_spot_attenuation = 1.0f / p_spot_attenuation;
l.color[0] = p_color.r;
l.color[1] = p_color.g;
l.color[2] = p_color.b;
l.energy = p_energy;
l.static_bake = p_static;
l.size = p_size;
l.shadow_blur = p_shadow_blur;
lights.push_back(l);
}
void LightmapperRD::add_probe(const Vector3 &p_position) {
Probe probe;
probe.position[0] = p_position.x;
probe.position[1] = p_position.y;
probe.position[2] = p_position.z;
probe.position[3] = 0;
probe_positions.push_back(probe);
}
void LightmapperRD::_plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[3], uint32_t p_triangle_index, LocalVector<TriangleSort> &triangles, uint32_t p_grid_size) {
int half_size = p_size / 2;
for (int i = 0; i < 8; i++) {
AABB aabb = p_bounds;
aabb.size *= 0.5;
Vector3i n = p_ofs;
if (i & 1) {
aabb.position.x += aabb.size.x;
n.x += half_size;
}
if (i & 2) {
aabb.position.y += aabb.size.y;
n.y += half_size;
}
if (i & 4) {
aabb.position.z += aabb.size.z;
n.z += half_size;
}
{
Vector3 qsize = aabb.size * 0.5; //quarter size, for fast aabb test
if (!Geometry3D::triangle_box_overlap(aabb.position + qsize, qsize, p_points)) {
//does not fit in child, go on
continue;
}
}
if (half_size == 1) {
//got to the end
TriangleSort ts;
ts.cell_index = n.x + (n.y * p_grid_size) + (n.z * p_grid_size * p_grid_size);
ts.triangle_index = p_triangle_index;
triangles.push_back(ts);
} else {
_plot_triangle_into_triangle_index_list(half_size, n, aabb, p_points, p_triangle_index, triangles, p_grid_size);
}
}
}
Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata) {
Vector<Size2i> sizes;
for (int m_i = 0; m_i < mesh_instances.size(); m_i++) {
MeshInstance &mi = mesh_instances.write[m_i];
Size2i s = Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height());
sizes.push_back(s);
atlas_size.width = MAX(atlas_size.width, s.width + 2);
atlas_size.height = MAX(atlas_size.height, s.height + 2);
}
int max = nearest_power_of_2_templated(atlas_size.width);
max = MAX(max, nearest_power_of_2_templated(atlas_size.height));
if (max > p_max_texture_size) {
return BAKE_ERROR_LIGHTMAP_TOO_SMALL;
}
if (p_step_function) {
p_step_function(0.1, RTR("Determining optimal atlas size"), p_bake_userdata, true);
}
atlas_size = Size2i(max, max);
Size2i best_atlas_size;
int best_atlas_slices = 0;
int best_atlas_memory = 0x7FFFFFFF;
Vector<Vector3i> best_atlas_offsets;
//determine best texture array atlas size by bruteforce fitting
while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) {
Vector<Vector2i> source_sizes;
Vector<int> source_indices;
source_sizes.resize(sizes.size());
source_indices.resize(sizes.size());
for (int i = 0; i < source_indices.size(); i++) {
source_sizes.write[i] = sizes[i] + Vector2i(2, 2); // Add padding between lightmaps
source_indices.write[i] = i;
}
Vector<Vector3i> atlas_offsets;
atlas_offsets.resize(source_sizes.size());
int slices = 0;
while (source_sizes.size() > 0) {
Vector<Vector3i> offsets = Geometry2D::partial_pack_rects(source_sizes, atlas_size);
Vector<int> new_indices;
Vector<Vector2i> new_sources;
for (int i = 0; i < offsets.size(); i++) {
Vector3i ofs = offsets[i];
int sidx = source_indices[i];
if (ofs.z > 0) {
//valid
ofs.z = slices;
atlas_offsets.write[sidx] = ofs + Vector3i(1, 1, 0); // Center lightmap in the reserved oversized region
} else {
new_indices.push_back(sidx);
new_sources.push_back(source_sizes[i]);
}
}
source_sizes = new_sources;
source_indices = new_indices;
slices++;
}
int mem_used = atlas_size.x * atlas_size.y * slices;
if (mem_used < best_atlas_memory) {
best_atlas_size = atlas_size;
best_atlas_offsets = atlas_offsets;
best_atlas_slices = slices;
best_atlas_memory = mem_used;
}
if (atlas_size.width == atlas_size.height) {
atlas_size.width *= 2;
} else {
atlas_size.height *= 2;
}
}
atlas_size = best_atlas_size;
atlas_slices = best_atlas_slices;
// apply the offsets and slice to all images, and also blit albedo and emission
albedo_images.resize(atlas_slices);
emission_images.resize(atlas_slices);
if (p_step_function) {
p_step_function(0.2, RTR("Blitting albedo and emission"), p_bake_userdata, true);
}
for (int i = 0; i < atlas_slices; i++) {
Ref<Image> albedo = Image::create_empty(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBA8);
albedo->set_as_black();
albedo_images.write[i] = albedo;
Ref<Image> emission = Image::create_empty(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH);
emission->set_as_black();
emission_images.write[i] = emission;
}
//assign uv positions
for (int m_i = 0; m_i < mesh_instances.size(); m_i++) {
MeshInstance &mi = mesh_instances.write[m_i];
mi.offset.x = best_atlas_offsets[m_i].x;
mi.offset.y = best_atlas_offsets[m_i].y;
mi.slice = best_atlas_offsets[m_i].z;
albedo_images.write[mi.slice]->blit_rect(mi.data.albedo_on_uv2, Rect2i(Vector2i(), mi.data.albedo_on_uv2->get_size()), mi.offset);
emission_images.write[mi.slice]->blit_rect(mi.data.emission_on_uv2, Rect2(Vector2i(), mi.data.emission_on_uv2->get_size()), mi.offset);
}
return BAKE_OK;
}
void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &p_probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) {
HashMap<Vertex, uint32_t, VertexHash> vertex_map;
//fill triangles array and vertex array
LocalVector<Triangle> triangles;
LocalVector<Vertex> vertex_array;
LocalVector<Seam> seams;
slice_triangle_count.resize(atlas_slices);
slice_seam_count.resize(atlas_slices);
for (int i = 0; i < atlas_slices; i++) {
slice_triangle_count.write[i] = 0;
slice_seam_count.write[i] = 0;
}
bounds = AABB();
for (int m_i = 0; m_i < mesh_instances.size(); m_i++) {
if (p_step_function) {
float p = float(m_i + 1) / mesh_instances.size() * 0.1;
p_step_function(0.3 + p, vformat(RTR("Plotting mesh into acceleration structure %d/%d"), m_i + 1, mesh_instances.size()), p_bake_userdata, false);
}
HashMap<Edge, EdgeUV2, EdgeHash> edges;
MeshInstance &mi = mesh_instances.write[m_i];
Vector2 uv_scale = Vector2(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height()) / Vector2(atlas_size);
Vector2 uv_offset = Vector2(mi.offset) / Vector2(atlas_size);
if (m_i == 0) {
bounds.position = mi.data.points[0];
}
for (int i = 0; i < mi.data.points.size(); i += 3) {
Vector3 vtxs[3] = { mi.data.points[i + 0], mi.data.points[i + 1], mi.data.points[i + 2] };
Vector2 uvs[3] = { mi.data.uv2[i + 0] * uv_scale + uv_offset, mi.data.uv2[i + 1] * uv_scale + uv_offset, mi.data.uv2[i + 2] * uv_scale + uv_offset };
Vector3 normal[3] = { mi.data.normal[i + 0], mi.data.normal[i + 1], mi.data.normal[i + 2] };
AABB taabb;
Triangle t;
t.slice = mi.slice;
for (int k = 0; k < 3; k++) {
bounds.expand_to(vtxs[k]);
Vertex v;
v.position[0] = vtxs[k].x;
v.position[1] = vtxs[k].y;
v.position[2] = vtxs[k].z;
v.uv[0] = uvs[k].x;
v.uv[1] = uvs[k].y;
v.normal_xy[0] = normal[k].x;
v.normal_xy[1] = normal[k].y;
v.normal_z = normal[k].z;
uint32_t *indexptr = vertex_map.getptr(v);
if (indexptr) {
t.indices[k] = *indexptr;
} else {
uint32_t new_index = vertex_map.size();
t.indices[k] = new_index;
vertex_map[v] = new_index;
vertex_array.push_back(v);
}
if (k == 0) {
taabb.position = vtxs[k];
} else {
taabb.expand_to(vtxs[k]);
}
}
//compute seams that will need to be blended later
for (int k = 0; k < 3; k++) {
int n = (k + 1) % 3;
Edge edge(vtxs[k], vtxs[n], normal[k], normal[n]);
Vector2i edge_indices(t.indices[k], t.indices[n]);
EdgeUV2 uv2(uvs[k], uvs[n], edge_indices);
if (edge.b == edge.a) {
continue; //degenerate, somehow
}
if (edge.b < edge.a) {
SWAP(edge.a, edge.b);
SWAP(edge.na, edge.nb);
SWAP(uv2.a, uv2.b);
SWAP(edge_indices.x, edge_indices.y);
}
EdgeUV2 *euv2 = edges.getptr(edge);
if (!euv2) {
edges[edge] = uv2;
} else {
if (*euv2 == uv2) {
continue; // seam shared UV space, no need to blend
}
if (euv2->seam_found) {
continue; //bad geometry
}
Seam seam;
seam.a = edge_indices;
seam.b = euv2->indices;
seam.slice = mi.slice;
seams.push_back(seam);
slice_seam_count.write[mi.slice]++;
euv2->seam_found = true;
}
}
t.min_bounds[0] = taabb.position.x;
t.min_bounds[1] = taabb.position.y;
t.min_bounds[2] = taabb.position.z;
t.max_bounds[0] = taabb.position.x + MAX(taabb.size.x, 0.0001);
t.max_bounds[1] = taabb.position.y + MAX(taabb.size.y, 0.0001);
t.max_bounds[2] = taabb.position.z + MAX(taabb.size.z, 0.0001);
t.pad0 = t.pad1 = 0; //make valgrind not complain
triangles.push_back(t);
slice_triangle_count.write[t.slice]++;
}
}
//also consider probe positions for bounds
for (int i = 0; i < p_probe_positions.size(); i++) {
Vector3 pp(p_probe_positions[i].position[0], p_probe_positions[i].position[1], p_probe_positions[i].position[2]);
bounds.expand_to(pp);
}
bounds.grow_by(0.1); //grow a bit to avoid numerical error
triangles.sort(); //sort by slice
seams.sort();
if (p_step_function) {
p_step_function(0.4, RTR("Optimizing acceleration structure"), p_bake_userdata, true);
}
//fill list of triangles in grid
LocalVector<TriangleSort> triangle_sort;
for (uint32_t i = 0; i < triangles.size(); i++) {
const Triangle &t = triangles[i];
Vector3 face[3] = {
Vector3(vertex_array[t.indices[0]].position[0], vertex_array[t.indices[0]].position[1], vertex_array[t.indices[0]].position[2]),
Vector3(vertex_array[t.indices[1]].position[0], vertex_array[t.indices[1]].position[1], vertex_array[t.indices[1]].position[2]),
Vector3(vertex_array[t.indices[2]].position[0], vertex_array[t.indices[2]].position[1], vertex_array[t.indices[2]].position[2])
};
_plot_triangle_into_triangle_index_list(grid_size, Vector3i(), bounds, face, i, triangle_sort, grid_size);
}
//sort it
triangle_sort.sort();
Vector<uint32_t> triangle_indices;
triangle_indices.resize(triangle_sort.size());
Vector<uint32_t> grid_indices;
grid_indices.resize(grid_size * grid_size * grid_size * 2);
memset(grid_indices.ptrw(), 0, grid_indices.size() * sizeof(uint32_t));
Vector<bool> solid;
solid.resize(grid_size * grid_size * grid_size);
memset(solid.ptrw(), 0, solid.size() * sizeof(bool));
{
uint32_t *tiw = triangle_indices.ptrw();
uint32_t last_cell = 0xFFFFFFFF;
uint32_t *giw = grid_indices.ptrw();
bool *solidw = solid.ptrw();
for (uint32_t i = 0; i < triangle_sort.size(); i++) {
uint32_t cell = triangle_sort[i].cell_index;
if (cell != last_cell) {
//cell changed, update pointer to indices
giw[cell * 2 + 1] = i;
solidw[cell] = true;
}
tiw[i] = triangle_sort[i].triangle_index;
giw[cell * 2]++; //update counter
last_cell = cell;
}
}
#if 0
for (int i = 0; i < grid_size; i++) {
for (int j = 0; j < grid_size; j++) {
for (int k = 0; k < grid_size; k++) {
uint32_t index = i * (grid_size * grid_size) + j * grid_size + k;
grid_indices.write[index * 2] = float(i) / grid_size * 255;
grid_indices.write[index * 2 + 1] = float(j) / grid_size * 255;
}
}
}
#endif
#if 0
for (int i = 0; i < grid_size; i++) {
Vector<uint8_t> grid_usage;
grid_usage.resize(grid_size * grid_size);
for (int j = 0; j < grid_usage.size(); j++) {
uint32_t ofs = i * grid_size * grid_size + j;
uint32_t count = grid_indices[ofs * 2];
grid_usage.write[j] = count > 0 ? 255 : 0;
}
Ref<Image> img = Image::create_from_data(grid_size, grid_size, false, Image::FORMAT_L8, grid_usage);
img->save_png("res://grid_layer_" + itos(1000 + i).substr(1, 3) + ".png");
}
#endif
/*****************************/
/*** CREATE GPU STRUCTURES ***/
/*****************************/
lights.sort();
Vector<Vector2i> seam_buffer_vec;
seam_buffer_vec.resize(seams.size() * 2);
for (uint32_t i = 0; i < seams.size(); i++) {
seam_buffer_vec.write[i * 2 + 0] = seams[i].a;
seam_buffer_vec.write[i * 2 + 1] = seams[i].b;
}
{ //buffers
Vector<uint8_t> vb = vertex_array.to_byte_array();
vertex_buffer = rd->storage_buffer_create(vb.size(), vb);
Vector<uint8_t> tb = triangles.to_byte_array();
triangle_buffer = rd->storage_buffer_create(tb.size(), tb);
Vector<uint8_t> tib = triangle_indices.to_byte_array();
triangle_cell_indices_buffer = rd->storage_buffer_create(tib.size(), tib);
Vector<uint8_t> lb = lights.to_byte_array();
if (lb.size() == 0) {
lb.resize(sizeof(Light)); //even if no lights, the buffer must exist
}
lights_buffer = rd->storage_buffer_create(lb.size(), lb);
Vector<uint8_t> sb = seam_buffer_vec.to_byte_array();
if (sb.size() == 0) {
sb.resize(sizeof(Vector2i) * 2); //even if no seams, the buffer must exist
}
seams_buffer = rd->storage_buffer_create(sb.size(), sb);
Vector<uint8_t> pb = p_probe_positions.to_byte_array();
if (pb.size() == 0) {
pb.resize(sizeof(Probe));
}
probe_positions_buffer = rd->storage_buffer_create(pb.size(), pb);
}
{ //grid
RD::TextureFormat tf;
tf.width = grid_size;
tf.height = grid_size;
tf.depth = grid_size;
tf.texture_type = RD::TEXTURE_TYPE_3D;
tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
Vector<Vector<uint8_t>> texdata;
texdata.resize(1);
//grid and indices
tf.format = RD::DATA_FORMAT_R32G32_UINT;
texdata.write[0] = grid_indices.to_byte_array();
grid_texture = rd->texture_create(tf, RD::TextureView(), texdata);
}
}
void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform) {
Vector<RID> framebuffers;
for (int i = 0; i < atlas_slices; i++) {
RID slice_pos_tex = rd->texture_create_shared_from_slice(RD::TextureView(), position_tex, i, 0);
RID slice_unoc_tex = rd->texture_create_shared_from_slice(RD::TextureView(), unocclude_tex, i, 0);
RID slice_norm_tex = rd->texture_create_shared_from_slice(RD::TextureView(), normal_tex, i, 0);
Vector<RID> fb;
fb.push_back(slice_pos_tex);
fb.push_back(slice_norm_tex);
fb.push_back(slice_unoc_tex);
fb.push_back(raster_depth_buffer);
framebuffers.push_back(rd->framebuffer_create(fb));
}
RD::PipelineDepthStencilState ds;
ds.enable_depth_test = true;
ds.enable_depth_write = true;
ds.depth_compare_operator = RD::COMPARE_OP_LESS; //so it does render same pixel twice
RID raster_pipeline = rd->render_pipeline_create(rasterize_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), ds, RD::PipelineColorBlendState::create_disabled(3), 0);
RID raster_pipeline_wire;
{
RD::PipelineRasterizationState rw;
rw.wireframe = true;
raster_pipeline_wire = rd->render_pipeline_create(rasterize_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_TRIANGLES, rw, RD::PipelineMultisampleState(), ds, RD::PipelineColorBlendState::create_disabled(3), 0);
}
uint32_t triangle_offset = 0;
Vector<Color> clear_colors;
clear_colors.push_back(Color(0, 0, 0, 0));
clear_colors.push_back(Color(0, 0, 0, 0));
clear_colors.push_back(Color(0, 0, 0, 0));
for (int i = 0; i < atlas_slices; i++) {
RasterPushConstant raster_push_constant;
raster_push_constant.atlas_size[0] = atlas_size.x;
raster_push_constant.atlas_size[1] = atlas_size.y;
raster_push_constant.base_triangle = triangle_offset;
raster_push_constant.to_cell_offset[0] = bounds.position.x;
raster_push_constant.to_cell_offset[1] = bounds.position.y;
raster_push_constant.to_cell_offset[2] = bounds.position.z;
raster_push_constant.bias = p_bias;
raster_push_constant.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size);
raster_push_constant.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size);
raster_push_constant.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size);
raster_push_constant.grid_size[0] = grid_size;
raster_push_constant.grid_size[1] = grid_size;
raster_push_constant.grid_size[2] = grid_size;
raster_push_constant.uv_offset[0] = 0;
raster_push_constant.uv_offset[1] = 0;
RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
//draw opaque
rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline);
rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
rd->draw_list_set_push_constant(draw_list, &raster_push_constant, sizeof(RasterPushConstant));
rd->draw_list_draw(draw_list, false, 1, slice_triangle_count[i] * 3);
//draw wire
rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline_wire);
rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
rd->draw_list_set_push_constant(draw_list, &raster_push_constant, sizeof(RasterPushConstant));
rd->draw_list_draw(draw_list, false, 1, slice_triangle_count[i] * 3);
rd->draw_list_end();
triangle_offset += slice_triangle_count[i];
}
}
LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) {
Vector<RD::Uniform> uniforms;
{
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 0;
u.append_id(dest_light_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 1;
u.append_id(source_light_tex);
uniforms.push_back(u);
}
}
RID compute_shader_dilate = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("dilate"));
ERR_FAIL_COND_V(compute_shader_dilate.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
RID compute_shader_dilate_pipeline = rd->compute_pipeline_create(compute_shader_dilate);
RID dilate_uniform_set = rd->uniform_set_create(uniforms, compute_shader_dilate, 1);
RD::ComputeListID compute_list = rd->compute_list_begin();
rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_dilate_pipeline);
rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1);
push_constant.region_ofs[0] = 0;
push_constant.region_ofs[1] = 0;
Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1); //restore group size
for (int i = 0; i < atlas_slices; i++) {
push_constant.atlas_slice = i;
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
//no barrier, let them run all together
}
rd->compute_list_end();
rd->free(compute_shader_dilate);
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices; i++) {
Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
img->convert(Image::FORMAT_RGBA8);
img->save_png("res://5_dilated_" + itos(i) + ".png");
}
#endif
return BAKE_OK;
}
LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
if (p_step_function) {
p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true);
}
bake_textures.clear();
int grid_size = 128;
/* STEP 1: Fetch material textures and compute the bounds */
AABB bounds;
Size2i atlas_size;
int atlas_slices;
Vector<Ref<Image>> albedo_images;
Vector<Ref<Image>> emission_images;
BakeError bake_error = _blit_meshes_into_atlas(p_max_texture_size, albedo_images, emission_images, bounds, atlas_size, atlas_slices, p_step_function, p_bake_userdata);
if (bake_error != BAKE_OK) {
return bake_error;
}
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices; i++) {
albedo_images[i]->save_png("res://0_albedo_" + itos(i) + ".png");
emission_images[i]->save_png("res://0_emission_" + itos(i) + ".png");
}
#endif
RenderingDevice *rd = RenderingDevice::get_singleton()->create_local_device();
RID albedo_array_tex;
RID emission_array_tex;
RID normal_tex;
RID position_tex;
RID unocclude_tex;
RID light_source_tex;
RID light_dest_tex;
RID light_accum_tex;
RID light_accum_tex2;
RID light_primary_dynamic_tex;
RID light_environment_tex;
#define FREE_TEXTURES \
rd->free(albedo_array_tex); \
rd->free(emission_array_tex); \
rd->free(normal_tex); \
rd->free(position_tex); \
rd->free(unocclude_tex); \
rd->free(light_source_tex); \
rd->free(light_accum_tex2); \
rd->free(light_accum_tex); \
rd->free(light_primary_dynamic_tex); \
rd->free(light_environment_tex);
{ // create all textures
Vector<Vector<uint8_t>> albedo_data;
Vector<Vector<uint8_t>> emission_data;
for (int i = 0; i < atlas_slices; i++) {
albedo_data.push_back(albedo_images[i]->get_data());
emission_data.push_back(emission_images[i]->get_data());
}
RD::TextureFormat tf;
tf.width = atlas_size.width;
tf.height = atlas_size.height;
tf.array_layers = atlas_slices;
tf.texture_type = RD::TEXTURE_TYPE_2D_ARRAY;
tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
tf.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
albedo_array_tex = rd->texture_create(tf, RD::TextureView(), albedo_data);
tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
emission_array_tex = rd->texture_create(tf, RD::TextureView(), emission_data);
//this will be rastered to
tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
normal_tex = rd->texture_create(tf, RD::TextureView());
tf.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
position_tex = rd->texture_create(tf, RD::TextureView());
unocclude_tex = rd->texture_create(tf, RD::TextureView());
tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
light_source_tex = rd->texture_create(tf, RD::TextureView());
rd->texture_clear(light_source_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
light_primary_dynamic_tex = rd->texture_create(tf, RD::TextureView());
rd->texture_clear(light_primary_dynamic_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
if (p_bake_sh) {
tf.array_layers *= 4;
}
light_accum_tex = rd->texture_create(tf, RD::TextureView());
rd->texture_clear(light_accum_tex, Color(0, 0, 0, 0), 0, 1, 0, tf.array_layers);
light_dest_tex = rd->texture_create(tf, RD::TextureView());
rd->texture_clear(light_dest_tex, Color(0, 0, 0, 0), 0, 1, 0, tf.array_layers);
light_accum_tex2 = light_dest_tex;
//env
{
Ref<Image> panorama_tex;
if (p_environment_panorama.is_valid()) {
panorama_tex = p_environment_panorama;
panorama_tex->convert(Image::FORMAT_RGBAF);
} else {
panorama_tex.instantiate();
panorama_tex->initialize_data(8, 8, false, Image::FORMAT_RGBAF);
panorama_tex->fill(Color(0, 0, 0, 1));
}
RD::TextureFormat tfp;
tfp.width = panorama_tex->get_width();
tfp.height = panorama_tex->get_height();
tfp.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
tfp.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
Vector<Vector<uint8_t>> tdata;
tdata.push_back(panorama_tex->get_data());
light_environment_tex = rd->texture_create(tfp, RD::TextureView(), tdata);
#ifdef DEBUG_TEXTURES
panorama_tex->save_exr("res://0_panorama.exr", false);
#endif
}
}
/* STEP 2: create the acceleration structure for the GPU*/
Vector<int> slice_triangle_count;
RID vertex_buffer;
RID triangle_buffer;
RID lights_buffer;
RID triangle_cell_indices_buffer;
RID grid_texture;
RID seams_buffer;
RID probe_positions_buffer;
Vector<int> slice_seam_count;
#define FREE_BUFFERS \
rd->free(vertex_buffer); \
rd->free(triangle_buffer); \
rd->free(lights_buffer); \
rd->free(triangle_cell_indices_buffer); \
rd->free(grid_texture); \
rd->free(seams_buffer); \
rd->free(probe_positions_buffer);
_create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata);
if (p_step_function) {
p_step_function(0.47, RTR("Preparing shaders"), p_bake_userdata, true);
}
//shaders
Ref<RDShaderFile> raster_shader;
raster_shader.instantiate();
Error err = raster_shader->parse_versions_from_text(lm_raster_shader_glsl);
if (err != OK) {
raster_shader->print_errors("raster_shader");
FREE_TEXTURES
FREE_BUFFERS
memdelete(rd);
}
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
RID rasterize_shader = rd->shader_create_from_spirv(raster_shader->get_spirv_stages());
ERR_FAIL_COND_V(rasterize_shader.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //this is a bug check, though, should not happen
RID sampler;
{
RD::SamplerState s;
s.mag_filter = RD::SAMPLER_FILTER_LINEAR;
s.min_filter = RD::SAMPLER_FILTER_LINEAR;
s.max_lod = 0;
sampler = rd->sampler_create(s);
}
Vector<RD::Uniform> base_uniforms;
{
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
u.binding = 1;
u.append_id(vertex_buffer);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
u.binding = 2;
u.append_id(triangle_buffer);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
u.binding = 3;
u.append_id(triangle_cell_indices_buffer);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
u.binding = 4;
u.append_id(lights_buffer);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
u.binding = 5;
u.append_id(seams_buffer);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
u.binding = 6;
u.append_id(probe_positions_buffer);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 7;
u.append_id(grid_texture);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 8;
u.append_id(albedo_array_tex);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 9;
u.append_id(emission_array_tex);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER;
u.binding = 10;
u.append_id(sampler);
base_uniforms.push_back(u);
}
}
RID raster_base_uniform = rd->uniform_set_create(base_uniforms, rasterize_shader, 0);
RID raster_depth_buffer;
{
RD::TextureFormat tf;
tf.width = atlas_size.width;
tf.height = atlas_size.height;
tf.depth = 1;
tf.texture_type = RD::TEXTURE_TYPE_2D;
tf.usage_bits = RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
tf.format = RD::DATA_FORMAT_D32_SFLOAT;
raster_depth_buffer = rd->texture_create(tf, RD::TextureView());
}
rd->submit();
rd->sync();
/* STEP 3: Raster the geometry to UV2 coords in the atlas textures GPU*/
_raster_geometry(rd, atlas_size, atlas_slices, grid_size, bounds, p_bias, slice_triangle_count, position_tex, unocclude_tex, normal_tex, raster_depth_buffer, rasterize_shader, raster_base_uniform);
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices; i++) {
Vector<uint8_t> s = rd->texture_get_data(position_tex, i);
Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAF, s);
img->save_exr("res://1_position_" + itos(i) + ".exr", false);
s = rd->texture_get_data(normal_tex, i);
img->set_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
img->save_exr("res://1_normal_" + itos(i) + ".exr", false);
}
#endif
#define FREE_RASTER_RESOURCES \
rd->free(rasterize_shader); \
rd->free(sampler); \
rd->free(raster_depth_buffer);
/* Plot direct light */
Ref<RDShaderFile> compute_shader;
compute_shader.instantiate();
err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, p_bake_sh ? "\n#define USE_SH_LIGHTMAPS\n" : "");
if (err != OK) {
FREE_TEXTURES
FREE_BUFFERS
FREE_RASTER_RESOURCES
memdelete(rd);
compute_shader->print_errors("compute_shader");
}
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
// Unoccluder
RID compute_shader_unocclude = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("unocclude"));
ERR_FAIL_COND_V(compute_shader_unocclude.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); // internal check, should not happen
RID compute_shader_unocclude_pipeline = rd->compute_pipeline_create(compute_shader_unocclude);
// Direct light
RID compute_shader_primary = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("primary"));
ERR_FAIL_COND_V(compute_shader_primary.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); // internal check, should not happen
RID compute_shader_primary_pipeline = rd->compute_pipeline_create(compute_shader_primary);
// Indirect light
RID compute_shader_secondary = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("secondary"));
ERR_FAIL_COND_V(compute_shader_secondary.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
RID compute_shader_secondary_pipeline = rd->compute_pipeline_create(compute_shader_secondary);
// Light probes
RID compute_shader_light_probes = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("light_probes"));
ERR_FAIL_COND_V(compute_shader_light_probes.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
RID compute_shader_light_probes_pipeline = rd->compute_pipeline_create(compute_shader_light_probes);
RID compute_base_uniform_set = rd->uniform_set_create(base_uniforms, compute_shader_primary, 0);
#define FREE_COMPUTE_RESOURCES \
rd->free(compute_shader_unocclude); \
rd->free(compute_shader_primary); \
rd->free(compute_shader_secondary); \
rd->free(compute_shader_light_probes);
PushConstant push_constant;
{
//set defaults
push_constant.atlas_size[0] = atlas_size.width;
push_constant.atlas_size[1] = atlas_size.height;
push_constant.world_size[0] = bounds.size.x;
push_constant.world_size[1] = bounds.size.y;
push_constant.world_size[2] = bounds.size.z;
push_constant.to_cell_offset[0] = bounds.position.x;
push_constant.to_cell_offset[1] = bounds.position.y;
push_constant.to_cell_offset[2] = bounds.position.z;
push_constant.bias = p_bias;
push_constant.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size);
push_constant.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size);
push_constant.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size);
push_constant.light_count = lights.size();
push_constant.grid_size = grid_size;
push_constant.atlas_slice = 0;
push_constant.region_ofs[0] = 0;
push_constant.region_ofs[1] = 0;
push_constant.environment_xform[0] = p_environment_transform.rows[0][0];
push_constant.environment_xform[1] = p_environment_transform.rows[1][0];
push_constant.environment_xform[2] = p_environment_transform.rows[2][0];
push_constant.environment_xform[3] = 0;
push_constant.environment_xform[4] = p_environment_transform.rows[0][1];
push_constant.environment_xform[5] = p_environment_transform.rows[1][1];
push_constant.environment_xform[6] = p_environment_transform.rows[2][1];
push_constant.environment_xform[7] = 0;
push_constant.environment_xform[8] = p_environment_transform.rows[0][2];
push_constant.environment_xform[9] = p_environment_transform.rows[1][2];
push_constant.environment_xform[10] = p_environment_transform.rows[2][2];
push_constant.environment_xform[11] = 0;
}
Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1);
rd->submit();
rd->sync();
if (p_step_function) {
p_step_function(0.49, RTR("Un-occluding geometry"), p_bake_userdata, true);
}
/* UNOCCLUDE */
{
Vector<RD::Uniform> uniforms;
{
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 0;
u.append_id(position_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 1;
u.append_id(unocclude_tex); //will be unused
uniforms.push_back(u);
}
}
RID unocclude_uniform_set = rd->uniform_set_create(uniforms, compute_shader_unocclude, 1);
RD::ComputeListID compute_list = rd->compute_list_begin();
rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_unocclude_pipeline);
rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
rd->compute_list_bind_uniform_set(compute_list, unocclude_uniform_set, 1);
for (int i = 0; i < atlas_slices; i++) {
push_constant.atlas_slice = i;
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
//no barrier, let them run all together
}
rd->compute_list_end(); //done
}
if (p_step_function) {
p_step_function(0.5, RTR("Plot direct lighting"), p_bake_userdata, true);
}
/* PRIMARY (direct) LIGHT PASS */
{
Vector<RD::Uniform> uniforms;
{
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 0;
u.append_id(light_source_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 1;
u.append_id(light_dest_tex); //will be unused
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 2;
u.append_id(position_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 3;
u.append_id(normal_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 4;
u.append_id(light_accum_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 5;
u.append_id(light_primary_dynamic_tex);
uniforms.push_back(u);
}
}
RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1);
switch (p_quality) {
case BAKE_QUALITY_LOW: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_ray_count");
} break;
case BAKE_QUALITY_MEDIUM: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_ray_count");
} break;
case BAKE_QUALITY_HIGH: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_ray_count");
} break;
case BAKE_QUALITY_ULTRA: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_ray_count");
} break;
}
push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u);
RD::ComputeListID compute_list = rd->compute_list_begin();
rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_primary_pipeline);
rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
rd->compute_list_bind_uniform_set(compute_list, light_uniform_set, 1);
push_constant.environment_xform[11] = p_exposure_normalization;
for (int i = 0; i < atlas_slices; i++) {
push_constant.atlas_slice = i;
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
//no barrier, let them run all together
}
rd->compute_list_end(); //done
push_constant.environment_xform[11] = 0.0;
}
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices; i++) {
Vector<uint8_t> s = rd->texture_get_data(light_source_tex, i);
Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
img->save_exr("res://2_light_primary_" + itos(i) + ".exr", false);
}
#endif
/* SECONDARY (indirect) LIGHT PASS(ES) */
if (p_step_function) {
p_step_function(0.6, RTR("Integrate indirect lighting"), p_bake_userdata, true);
}
if (p_bounces > 0) {
Vector<RD::Uniform> uniforms;
{
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 0;
u.append_id(light_dest_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 1;
u.append_id(light_source_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 2;
u.append_id(position_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 3;
u.append_id(normal_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 4;
u.append_id(light_accum_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 5;
u.append_id(unocclude_tex); //reuse unocclude tex
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 6;
u.append_id(light_environment_tex);
uniforms.push_back(u);
}
}
RID secondary_uniform_set[2];
secondary_uniform_set[0] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1);
uniforms.write[0].set_id(0, light_source_tex);
uniforms.write[1].set_id(0, light_dest_tex);
secondary_uniform_set[1] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1);
int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/lightmapping/bake_performance/region_size")));
int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_pass");
int x_regions = (atlas_size.width - 1) / max_region_size + 1;
int y_regions = (atlas_size.height - 1) / max_region_size + 1;
int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
rd->submit();
rd->sync();
for (int b = 0; b < p_bounces; b++) {
int count = 0;
if (b > 0) {
SWAP(light_source_tex, light_dest_tex);
SWAP(secondary_uniform_set[0], secondary_uniform_set[1]);
}
for (int s = 0; s < atlas_slices; s++) {
push_constant.atlas_slice = s;
for (int i = 0; i < x_regions; i++) {
for (int j = 0; j < y_regions; j++) {
int x = i * max_region_size;
int y = j * max_region_size;
int w = MIN((i + 1) * max_region_size, atlas_size.width) - x;
int h = MIN((j + 1) * max_region_size, atlas_size.height) - y;
push_constant.region_ofs[0] = x;
push_constant.region_ofs[1] = y;
group_size = Vector3i((w - 1) / 8 + 1, (h - 1) / 8 + 1, 1);
for (int k = 0; k < ray_iterations; k++) {
RD::ComputeListID compute_list = rd->compute_list_begin();
rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_secondary_pipeline);
rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
rd->compute_list_bind_uniform_set(compute_list, secondary_uniform_set[0], 1);
push_constant.ray_from = k * max_rays;
push_constant.ray_to = MIN((k + 1) * max_rays, int32_t(push_constant.ray_count));
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
rd->compute_list_end(); //done
rd->submit();
rd->sync();
count++;
if (p_step_function) {
int total = (atlas_slices * x_regions * y_regions * ray_iterations);
int percent = count * 100 / total;
float p = float(count) / total * 0.1;
p_step_function(0.6 + p, vformat(RTR("Bounce %d/%d: Integrate indirect lighting %d%%"), b + 1, p_bounces, percent), p_bake_userdata, false);
}
}
}
}
}
if (b == 0) {
// This disables the environment for subsequent bounces
push_constant.environment_xform[3] = -99.0f;
}
}
// Restore the correct environment transform
push_constant.environment_xform[3] = 0.0f;
}
/* LIGHTPROBES */
RID light_probe_buffer;
if (probe_positions.size()) {
light_probe_buffer = rd->storage_buffer_create(sizeof(float) * 4 * 9 * probe_positions.size());
if (p_step_function) {
p_step_function(0.7, RTR("Baking lightprobes"), p_bake_userdata, true);
}
Vector<RD::Uniform> uniforms;
{
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
u.binding = 0;
u.append_id(light_probe_buffer);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 1;
u.append_id(light_dest_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 2;
u.append_id(light_primary_dynamic_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 3;
u.append_id(light_environment_tex);
uniforms.push_back(u);
}
}
RID light_probe_uniform_set = rd->uniform_set_create(uniforms, compute_shader_light_probes, 1);
switch (p_quality) {
case BAKE_QUALITY_LOW: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_probe_ray_count");
} break;
case BAKE_QUALITY_MEDIUM: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_probe_ray_count");
} break;
case BAKE_QUALITY_HIGH: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_probe_ray_count");
} break;
case BAKE_QUALITY_ULTRA: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count");
} break;
}
push_constant.atlas_size[0] = probe_positions.size();
push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u);
int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_probe_pass");
int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
for (int i = 0; i < ray_iterations; i++) {
RD::ComputeListID compute_list = rd->compute_list_begin();
rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_light_probes_pipeline);
rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
rd->compute_list_bind_uniform_set(compute_list, light_probe_uniform_set, 1);
push_constant.ray_from = i * max_rays;
push_constant.ray_to = MIN((i + 1) * max_rays, int32_t(push_constant.ray_count));
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
rd->compute_list_dispatch(compute_list, (probe_positions.size() - 1) / 64 + 1, 1, 1);
rd->compute_list_end(); //done
rd->submit();
rd->sync();
if (p_step_function) {
int percent = i * 100 / ray_iterations;
float p = float(i) / ray_iterations * 0.1;
p_step_function(0.7 + p, vformat(RTR("Integrating light probes %d%%"), percent), p_bake_userdata, false);
}
}
push_constant.atlas_size[0] = atlas_size.x; //restore
}
#if 0
for (int i = 0; i < probe_positions.size(); i++) {
Ref<Image> img = Image::create_empty(6, 4, false, Image::FORMAT_RGB8);
for (int j = 0; j < 6; j++) {
Vector<uint8_t> s = rd->texture_get_data(lightprobe_tex, i * 6 + j);
Ref<Image> img2 = Image::create_from_data(2, 2, false, Image::FORMAT_RGBAF, s);
img2->convert(Image::FORMAT_RGB8);
img->blit_rect(img2, Rect2i(0, 0, 2, 2), Point2i((j % 3) * 2, (j / 3) * 2));
}
img->save_png("res://3_light_probe_" + itos(i) + ".png");
}
#endif
{
SWAP(light_accum_tex, light_accum_tex2);
BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1));
if (unlikely(error != BAKE_OK)) {
return error;
}
}
/* DENOISE */
if (p_use_denoiser) {
if (p_step_function) {
p_step_function(0.8, RTR("Denoising"), p_bake_userdata, true);
}
Ref<LightmapDenoiser> denoiser = LightmapDenoiser::create();
if (denoiser.is_valid()) {
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
Ref<Image> denoised = denoiser->denoise_image(img);
if (denoised != img) {
denoised->convert(Image::FORMAT_RGBAH);
Vector<uint8_t> ds = denoised->get_data();
denoised.unref(); //avoid copy on write
{ //restore alpha
uint32_t count = s.size() / 2; //uint16s
const uint16_t *src = (const uint16_t *)s.ptr();
uint16_t *dst = (uint16_t *)ds.ptrw();
for (uint32_t j = 0; j < count; j += 4) {
dst[j + 3] = src[j + 3];
}
}
rd->texture_update(light_accum_tex, i, ds);
}
}
}
{
SWAP(light_accum_tex, light_accum_tex2);
BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1));
if (unlikely(error != BAKE_OK)) {
return error;
}
}
}
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
img->save_exr("res://4_light_secondary_" + itos(i) + ".exr", false);
}
#endif
/* BLEND SEAMS */
//shaders
Ref<RDShaderFile> blendseams_shader;
blendseams_shader.instantiate();
err = blendseams_shader->parse_versions_from_text(lm_blendseams_shader_glsl);
if (err != OK) {
FREE_TEXTURES
FREE_BUFFERS
FREE_RASTER_RESOURCES
FREE_COMPUTE_RESOURCES
memdelete(rd);
blendseams_shader->print_errors("blendseams_shader");
}
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
RID blendseams_line_raster_shader = rd->shader_create_from_spirv(blendseams_shader->get_spirv_stages("lines"));
ERR_FAIL_COND_V(blendseams_line_raster_shader.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
RID blendseams_triangle_raster_shader = rd->shader_create_from_spirv(blendseams_shader->get_spirv_stages("triangles"));
ERR_FAIL_COND_V(blendseams_triangle_raster_shader.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
#define FREE_BLENDSEAMS_RESOURCES \
rd->free(blendseams_line_raster_shader); \
rd->free(blendseams_triangle_raster_shader);
{
//pre copy
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
rd->texture_copy(light_accum_tex, light_accum_tex2, Vector3(), Vector3(), Vector3(atlas_size.width, atlas_size.height, 1), 0, 0, i, i);
}
Vector<RID> framebuffers;
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
RID slice_tex = rd->texture_create_shared_from_slice(RD::TextureView(), light_accum_tex, i, 0);
Vector<RID> fb;
fb.push_back(slice_tex);
fb.push_back(raster_depth_buffer);
framebuffers.push_back(rd->framebuffer_create(fb));
}
Vector<RD::Uniform> uniforms;
{
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 0;
u.append_id(light_accum_tex2);
uniforms.push_back(u);
}
}
RID blendseams_raster_uniform = rd->uniform_set_create(uniforms, blendseams_line_raster_shader, 1);
bool debug = false;
RD::PipelineColorBlendState bs = RD::PipelineColorBlendState::create_blend(1);
bs.attachments.write[0].src_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
bs.attachments.write[0].dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
RD::PipelineDepthStencilState ds;
ds.enable_depth_test = true;
ds.enable_depth_write = true;
ds.depth_compare_operator = RD::COMPARE_OP_LESS; //so it does not render same pixel twice, this avoids wrong blending
RID blendseams_line_raster_pipeline = rd->render_pipeline_create(blendseams_line_raster_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_LINES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), ds, bs, 0);
RID blendseams_triangle_raster_pipeline = rd->render_pipeline_create(blendseams_triangle_raster_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), ds, bs, 0);
uint32_t seam_offset = 0;
uint32_t triangle_offset = 0;
Vector<Color> clear_colors;
clear_colors.push_back(Color(0, 0, 0, 1));
for (int i = 0; i < atlas_slices; i++) {
int subslices = (p_bake_sh ? 4 : 1);
if (slice_seam_count[i] == 0) {
continue;
}
for (int k = 0; k < subslices; k++) {
RasterSeamsPushConstant seams_push_constant;
seams_push_constant.slice = uint32_t(i * subslices + k);
seams_push_constant.debug = debug;
RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1);
const int uv_offset_count = 9;
static const Vector3 uv_offsets[uv_offset_count] = {
Vector3(0, 0, 0.5), //using zbuffer, so go inwards-outwards
Vector3(0, 1, 0.2),
Vector3(0, -1, 0.2),
Vector3(1, 0, 0.2),
Vector3(-1, 0, 0.2),
Vector3(-1, -1, 0.1),
Vector3(1, -1, 0.1),
Vector3(1, 1, 0.1),
Vector3(-1, 1, 0.1),
};
/* step 1 use lines to blend the edges */
{
seams_push_constant.base_index = seam_offset;
rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline);
seams_push_constant.uv_offset[0] = uv_offsets[0].x / float(atlas_size.width);
seams_push_constant.uv_offset[1] = uv_offsets[0].y / float(atlas_size.height);
seams_push_constant.blend = uv_offsets[0].z;
rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
rd->draw_list_draw(draw_list, false, 1, slice_seam_count[i] * 4);
}
/* step 2 use triangles to mask the interior */
{
seams_push_constant.base_index = triangle_offset;
rd->draw_list_bind_render_pipeline(draw_list, blendseams_triangle_raster_pipeline);
seams_push_constant.blend = 0; //do not draw them, just fill the z-buffer so its used as a mask
rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
rd->draw_list_draw(draw_list, false, 1, slice_triangle_count[i] * 3);
}
/* step 3 blend around the triangle */
rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline);
for (int j = 1; j < uv_offset_count; j++) {
seams_push_constant.base_index = seam_offset;
seams_push_constant.uv_offset[0] = uv_offsets[j].x / float(atlas_size.width);
seams_push_constant.uv_offset[1] = uv_offsets[j].y / float(atlas_size.height);
seams_push_constant.blend = uv_offsets[0].z;
rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
rd->draw_list_draw(draw_list, false, 1, slice_seam_count[i] * 4);
}
rd->draw_list_end();
}
seam_offset += slice_seam_count[i];
triangle_offset += slice_triangle_count[i];
}
}
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
img->save_exr("res://5_blendseams" + itos(i) + ".exr", false);
}
#endif
if (p_step_function) {
p_step_function(0.9, RTR("Retrieving textures"), p_bake_userdata, true);
}
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
img->convert(Image::FORMAT_RGBH); //remove alpha
bake_textures.push_back(img);
}
if (probe_positions.size() > 0) {
probe_values.resize(probe_positions.size() * 9);
Vector<uint8_t> probe_data = rd->buffer_get_data(light_probe_buffer);
memcpy(probe_values.ptrw(), probe_data.ptr(), probe_data.size());
rd->free(light_probe_buffer);
#ifdef DEBUG_TEXTURES
{
Ref<Image> img2 = Image::create_from_data(probe_values.size(), 1, false, Image::FORMAT_RGBAF, probe_data);
img2->save_exr("res://6_lightprobes.exr", false);
}
#endif
}
FREE_TEXTURES
FREE_BUFFERS
FREE_RASTER_RESOURCES
FREE_COMPUTE_RESOURCES
FREE_BLENDSEAMS_RESOURCES
memdelete(rd);
return BAKE_OK;
}
int LightmapperRD::get_bake_texture_count() const {
return bake_textures.size();
}
Ref<Image> LightmapperRD::get_bake_texture(int p_index) const {
ERR_FAIL_INDEX_V(p_index, bake_textures.size(), Ref<Image>());
return bake_textures[p_index];
}
int LightmapperRD::get_bake_mesh_count() const {
return mesh_instances.size();
}
Variant LightmapperRD::get_bake_mesh_userdata(int p_index) const {
ERR_FAIL_INDEX_V(p_index, mesh_instances.size(), Variant());
return mesh_instances[p_index].data.userdata;
}
Rect2 LightmapperRD::get_bake_mesh_uv_scale(int p_index) const {
ERR_FAIL_COND_V(bake_textures.size() == 0, Rect2());
Rect2 uv_ofs;
Vector2 atlas_size = Vector2(bake_textures[0]->get_width(), bake_textures[0]->get_height());
uv_ofs.position = Vector2(mesh_instances[p_index].offset) / atlas_size;
uv_ofs.size = Vector2(mesh_instances[p_index].data.albedo_on_uv2->get_width(), mesh_instances[p_index].data.albedo_on_uv2->get_height()) / atlas_size;
return uv_ofs;
}
int LightmapperRD::get_bake_mesh_texture_slice(int p_index) const {
ERR_FAIL_INDEX_V(p_index, mesh_instances.size(), Variant());
return mesh_instances[p_index].slice;
}
int LightmapperRD::get_bake_probe_count() const {
return probe_positions.size();
}
Vector3 LightmapperRD::get_bake_probe_point(int p_probe) const {
ERR_FAIL_INDEX_V(p_probe, probe_positions.size(), Variant());
return Vector3(probe_positions[p_probe].position[0], probe_positions[p_probe].position[1], probe_positions[p_probe].position[2]);
}
Vector<Color> LightmapperRD::get_bake_probe_sh(int p_probe) const {
ERR_FAIL_INDEX_V(p_probe, probe_positions.size(), Vector<Color>());
Vector<Color> ret;
ret.resize(9);
memcpy(ret.ptrw(), &probe_values[p_probe * 9], sizeof(Color) * 9);
return ret;
}
LightmapperRD::LightmapperRD() {
}