godot/scene/3d/voxel_light_baker.cpp
Rémi Verschelde a7f49ac9a1 Update copyright statements to 2020
Happy new year to the wonderful Godot community!

We're starting a new decade with a well-established, non-profit, free
and open source game engine, and tons of further improvements in the
pipeline from hundreds of contributors.

Godot will keep getting better, and we're looking forward to all the
games that the community will keep developing and releasing with it.
2020-01-01 11:16:22 +01:00

2487 lines
71 KiB
C++

/*************************************************************************/
/* voxel_light_baker.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "voxel_light_baker.h"
#include "core/os/os.h"
#include "core/os/threaded_array_processor.h"
#include <stdlib.h>
#define FINDMINMAX(x0, x1, x2, min, max) \
min = max = x0; \
if (x1 < min) min = x1; \
if (x1 > max) max = x1; \
if (x2 < min) min = x2; \
if (x2 > max) max = x2;
static bool planeBoxOverlap(Vector3 normal, float d, Vector3 maxbox) {
int q;
Vector3 vmin, vmax;
for (q = 0; q <= 2; q++) {
if (normal[q] > 0.0f) {
vmin[q] = -maxbox[q];
vmax[q] = maxbox[q];
} else {
vmin[q] = maxbox[q];
vmax[q] = -maxbox[q];
}
}
if (normal.dot(vmin) + d > 0.0f) return false;
if (normal.dot(vmax) + d >= 0.0f) return true;
return false;
}
/*======================== X-tests ========================*/
#define AXISTEST_X01(a, b, fa, fb) \
p0 = a * v0.y - b * v0.z; \
p2 = a * v2.y - b * v2.z; \
if (p0 < p2) { \
min = p0; \
max = p2; \
} else { \
min = p2; \
max = p0; \
} \
rad = fa * boxhalfsize.y + fb * boxhalfsize.z; \
if (min > rad || max < -rad) return false;
#define AXISTEST_X2(a, b, fa, fb) \
p0 = a * v0.y - b * v0.z; \
p1 = a * v1.y - b * v1.z; \
if (p0 < p1) { \
min = p0; \
max = p1; \
} else { \
min = p1; \
max = p0; \
} \
rad = fa * boxhalfsize.y + fb * boxhalfsize.z; \
if (min > rad || max < -rad) return false;
/*======================== Y-tests ========================*/
#define AXISTEST_Y02(a, b, fa, fb) \
p0 = -a * v0.x + b * v0.z; \
p2 = -a * v2.x + b * v2.z; \
if (p0 < p2) { \
min = p0; \
max = p2; \
} else { \
min = p2; \
max = p0; \
} \
rad = fa * boxhalfsize.x + fb * boxhalfsize.z; \
if (min > rad || max < -rad) return false;
#define AXISTEST_Y1(a, b, fa, fb) \
p0 = -a * v0.x + b * v0.z; \
p1 = -a * v1.x + b * v1.z; \
if (p0 < p1) { \
min = p0; \
max = p1; \
} else { \
min = p1; \
max = p0; \
} \
rad = fa * boxhalfsize.x + fb * boxhalfsize.z; \
if (min > rad || max < -rad) return false;
/*======================== Z-tests ========================*/
#define AXISTEST_Z12(a, b, fa, fb) \
p1 = a * v1.x - b * v1.y; \
p2 = a * v2.x - b * v2.y; \
if (p2 < p1) { \
min = p2; \
max = p1; \
} else { \
min = p1; \
max = p2; \
} \
rad = fa * boxhalfsize.x + fb * boxhalfsize.y; \
if (min > rad || max < -rad) return false;
#define AXISTEST_Z0(a, b, fa, fb) \
p0 = a * v0.x - b * v0.y; \
p1 = a * v1.x - b * v1.y; \
if (p0 < p1) { \
min = p0; \
max = p1; \
} else { \
min = p1; \
max = p0; \
} \
rad = fa * boxhalfsize.x + fb * boxhalfsize.y; \
if (min > rad || max < -rad) return false;
static bool fast_tri_box_overlap(const Vector3 &boxcenter, const Vector3 boxhalfsize, const Vector3 *triverts) {
/* use separating axis theorem to test overlap between triangle and box */
/* need to test for overlap in these directions: */
/* 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */
/* we do not even need to test these) */
/* 2) normal of the triangle */
/* 3) crossproduct(edge from tri, {x,y,z}-directin) */
/* this gives 3x3=9 more tests */
Vector3 v0, v1, v2;
float min, max, d, p0, p1, p2, rad, fex, fey, fez;
Vector3 normal, e0, e1, e2;
/* This is the fastest branch on Sun */
/* move everything so that the boxcenter is in (0,0,0) */
v0 = triverts[0] - boxcenter;
v1 = triverts[1] - boxcenter;
v2 = triverts[2] - boxcenter;
/* compute triangle edges */
e0 = v1 - v0; /* tri edge 0 */
e1 = v2 - v1; /* tri edge 1 */
e2 = v0 - v2; /* tri edge 2 */
/* Bullet 3: */
/* test the 9 tests first (this was faster) */
fex = Math::abs(e0.x);
fey = Math::abs(e0.y);
fez = Math::abs(e0.z);
AXISTEST_X01(e0.z, e0.y, fez, fey);
AXISTEST_Y02(e0.z, e0.x, fez, fex);
AXISTEST_Z12(e0.y, e0.x, fey, fex);
fex = Math::abs(e1.x);
fey = Math::abs(e1.y);
fez = Math::abs(e1.z);
AXISTEST_X01(e1.z, e1.y, fez, fey);
AXISTEST_Y02(e1.z, e1.x, fez, fex);
AXISTEST_Z0(e1.y, e1.x, fey, fex);
fex = Math::abs(e2.x);
fey = Math::abs(e2.y);
fez = Math::abs(e2.z);
AXISTEST_X2(e2.z, e2.y, fez, fey);
AXISTEST_Y1(e2.z, e2.x, fez, fex);
AXISTEST_Z12(e2.y, e2.x, fey, fex);
/* Bullet 1: */
/* first test overlap in the {x,y,z}-directions */
/* find min, max of the triangle each direction, and test for overlap in */
/* that direction -- this is equivalent to testing a minimal AABB around */
/* the triangle against the AABB */
/* test in X-direction */
FINDMINMAX(v0.x, v1.x, v2.x, min, max);
if (min > boxhalfsize.x || max < -boxhalfsize.x) return false;
/* test in Y-direction */
FINDMINMAX(v0.y, v1.y, v2.y, min, max);
if (min > boxhalfsize.y || max < -boxhalfsize.y) return false;
/* test in Z-direction */
FINDMINMAX(v0.z, v1.z, v2.z, min, max);
if (min > boxhalfsize.z || max < -boxhalfsize.z) return false;
/* Bullet 2: */
/* test if the box intersects the plane of the triangle */
/* compute plane equation of triangle: normal*x+d=0 */
normal = e0.cross(e1);
d = -normal.dot(v0); /* plane eq: normal.x+d=0 */
return planeBoxOverlap(normal, d, boxhalfsize); /* if true, box and triangle overlaps */
}
static _FORCE_INLINE_ void get_uv_and_normal(const Vector3 &p_pos, const Vector3 *p_vtx, const Vector2 *p_uv, const Vector3 *p_normal, Vector2 &r_uv, Vector3 &r_normal) {
if (p_pos.distance_squared_to(p_vtx[0]) < CMP_EPSILON2) {
r_uv = p_uv[0];
r_normal = p_normal[0];
return;
}
if (p_pos.distance_squared_to(p_vtx[1]) < CMP_EPSILON2) {
r_uv = p_uv[1];
r_normal = p_normal[1];
return;
}
if (p_pos.distance_squared_to(p_vtx[2]) < CMP_EPSILON2) {
r_uv = p_uv[2];
r_normal = p_normal[2];
return;
}
Vector3 v0 = p_vtx[1] - p_vtx[0];
Vector3 v1 = p_vtx[2] - p_vtx[0];
Vector3 v2 = p_pos - p_vtx[0];
float d00 = v0.dot(v0);
float d01 = v0.dot(v1);
float d11 = v1.dot(v1);
float d20 = v2.dot(v0);
float d21 = v2.dot(v1);
float denom = (d00 * d11 - d01 * d01);
if (denom == 0) {
r_uv = p_uv[0];
r_normal = p_normal[0];
return;
}
float v = (d11 * d20 - d01 * d21) / denom;
float w = (d00 * d21 - d01 * d20) / denom;
float u = 1.0f - v - w;
r_uv = p_uv[0] * u + p_uv[1] * v + p_uv[2] * w;
r_normal = (p_normal[0] * u + p_normal[1] * v + p_normal[2] * w).normalized();
}
void VoxelLightBaker::_plot_face(int p_idx, int p_level, int p_x, int p_y, int p_z, const Vector3 *p_vtx, const Vector3 *p_normal, const Vector2 *p_uv, const MaterialCache &p_material, const AABB &p_aabb) {
if (p_level == cell_subdiv - 1) {
//plot the face by guessing its albedo and emission value
//find best axis to map to, for scanning values
int closest_axis = 0;
float closest_dot = 0;
Plane plane = Plane(p_vtx[0], p_vtx[1], p_vtx[2]);
Vector3 normal = plane.normal;
for (int i = 0; i < 3; i++) {
Vector3 axis;
axis[i] = 1.0;
float dot = ABS(normal.dot(axis));
if (i == 0 || dot > closest_dot) {
closest_axis = i;
closest_dot = dot;
}
}
Vector3 axis;
axis[closest_axis] = 1.0;
Vector3 t1;
t1[(closest_axis + 1) % 3] = 1.0;
Vector3 t2;
t2[(closest_axis + 2) % 3] = 1.0;
t1 *= p_aabb.size[(closest_axis + 1) % 3] / float(color_scan_cell_width);
t2 *= p_aabb.size[(closest_axis + 2) % 3] / float(color_scan_cell_width);
Color albedo_accum;
Color emission_accum;
Vector3 normal_accum;
float alpha = 0.0;
//map to a grid average in the best axis for this face
for (int i = 0; i < color_scan_cell_width; i++) {
Vector3 ofs_i = float(i) * t1;
for (int j = 0; j < color_scan_cell_width; j++) {
Vector3 ofs_j = float(j) * t2;
Vector3 from = p_aabb.position + ofs_i + ofs_j;
Vector3 to = from + t1 + t2 + axis * p_aabb.size[closest_axis];
Vector3 half = (to - from) * 0.5;
//is in this cell?
if (!fast_tri_box_overlap(from + half, half, p_vtx)) {
continue; //face does not span this cell
}
//go from -size to +size*2 to avoid skipping collisions
Vector3 ray_from = from + (t1 + t2) * 0.5 - axis * p_aabb.size[closest_axis];
Vector3 ray_to = ray_from + axis * p_aabb.size[closest_axis] * 2;
if (normal.dot(ray_from - ray_to) < 0) {
SWAP(ray_from, ray_to);
}
Vector3 intersection;
if (!plane.intersects_segment(ray_from, ray_to, &intersection)) {
if (ABS(plane.distance_to(ray_from)) < ABS(plane.distance_to(ray_to))) {
intersection = plane.project(ray_from);
} else {
intersection = plane.project(ray_to);
}
}
intersection = Face3(p_vtx[0], p_vtx[1], p_vtx[2]).get_closest_point_to(intersection);
Vector2 uv;
Vector3 lnormal;
get_uv_and_normal(intersection, p_vtx, p_uv, p_normal, uv, lnormal);
if (lnormal == Vector3()) //just in case normal as nor provided
lnormal = normal;
int uv_x = CLAMP(int(Math::fposmod(uv.x, 1.0f) * bake_texture_size), 0, bake_texture_size - 1);
int uv_y = CLAMP(int(Math::fposmod(uv.y, 1.0f) * bake_texture_size), 0, bake_texture_size - 1);
int ofs = uv_y * bake_texture_size + uv_x;
albedo_accum.r += p_material.albedo[ofs].r;
albedo_accum.g += p_material.albedo[ofs].g;
albedo_accum.b += p_material.albedo[ofs].b;
albedo_accum.a += p_material.albedo[ofs].a;
emission_accum.r += p_material.emission[ofs].r;
emission_accum.g += p_material.emission[ofs].g;
emission_accum.b += p_material.emission[ofs].b;
normal_accum += lnormal;
alpha += 1.0;
}
}
if (alpha == 0) {
//could not in any way get texture information.. so use closest point to center
Face3 f(p_vtx[0], p_vtx[1], p_vtx[2]);
Vector3 inters = f.get_closest_point_to(p_aabb.position + p_aabb.size * 0.5);
Vector3 lnormal;
Vector2 uv;
get_uv_and_normal(inters, p_vtx, p_uv, p_normal, uv, normal);
if (lnormal == Vector3()) //just in case normal as nor provided
lnormal = normal;
int uv_x = CLAMP(Math::fposmod(uv.x, 1.0f) * bake_texture_size, 0, bake_texture_size - 1);
int uv_y = CLAMP(Math::fposmod(uv.y, 1.0f) * bake_texture_size, 0, bake_texture_size - 1);
int ofs = uv_y * bake_texture_size + uv_x;
alpha = 1.0 / (color_scan_cell_width * color_scan_cell_width);
albedo_accum.r = p_material.albedo[ofs].r * alpha;
albedo_accum.g = p_material.albedo[ofs].g * alpha;
albedo_accum.b = p_material.albedo[ofs].b * alpha;
albedo_accum.a = p_material.albedo[ofs].a * alpha;
emission_accum.r = p_material.emission[ofs].r * alpha;
emission_accum.g = p_material.emission[ofs].g * alpha;
emission_accum.b = p_material.emission[ofs].b * alpha;
normal_accum = lnormal * alpha;
} else {
float accdiv = 1.0 / (color_scan_cell_width * color_scan_cell_width);
alpha *= accdiv;
albedo_accum.r *= accdiv;
albedo_accum.g *= accdiv;
albedo_accum.b *= accdiv;
albedo_accum.a *= accdiv;
emission_accum.r *= accdiv;
emission_accum.g *= accdiv;
emission_accum.b *= accdiv;
normal_accum *= accdiv;
}
//put this temporarily here, corrected in a later step
bake_cells.write[p_idx].albedo[0] += albedo_accum.r;
bake_cells.write[p_idx].albedo[1] += albedo_accum.g;
bake_cells.write[p_idx].albedo[2] += albedo_accum.b;
bake_cells.write[p_idx].emission[0] += emission_accum.r;
bake_cells.write[p_idx].emission[1] += emission_accum.g;
bake_cells.write[p_idx].emission[2] += emission_accum.b;
bake_cells.write[p_idx].normal[0] += normal_accum.x;
bake_cells.write[p_idx].normal[1] += normal_accum.y;
bake_cells.write[p_idx].normal[2] += normal_accum.z;
bake_cells.write[p_idx].alpha += alpha;
} else {
//go down
int half = (1 << (cell_subdiv - 1)) >> (p_level + 1);
for (int i = 0; i < 8; i++) {
AABB aabb = p_aabb;
aabb.size *= 0.5;
int nx = p_x;
int ny = p_y;
int nz = p_z;
if (i & 1) {
aabb.position.x += aabb.size.x;
nx += half;
}
if (i & 2) {
aabb.position.y += aabb.size.y;
ny += half;
}
if (i & 4) {
aabb.position.z += aabb.size.z;
nz += half;
}
//make sure to not plot beyond limits
if (nx < 0 || nx >= axis_cell_size[0] || ny < 0 || ny >= axis_cell_size[1] || nz < 0 || nz >= axis_cell_size[2])
continue;
{
AABB test_aabb = aabb;
//test_aabb.grow_by(test_aabb.get_longest_axis_size()*0.05); //grow a bit to avoid numerical error in real-time
Vector3 qsize = test_aabb.size * 0.5; //quarter size, for fast aabb test
if (!fast_tri_box_overlap(test_aabb.position + qsize, qsize, p_vtx)) {
//if (!Face3(p_vtx[0],p_vtx[1],p_vtx[2]).intersects_aabb2(aabb)) {
//does not fit in child, go on
continue;
}
}
if (bake_cells[p_idx].children[i] == CHILD_EMPTY) {
//sub cell must be created
uint32_t child_idx = bake_cells.size();
bake_cells.write[p_idx].children[i] = child_idx;
bake_cells.resize(bake_cells.size() + 1);
bake_cells.write[child_idx].level = p_level + 1;
}
_plot_face(bake_cells[p_idx].children[i], p_level + 1, nx, ny, nz, p_vtx, p_normal, p_uv, p_material, aabb);
}
}
}
Vector<Color> VoxelLightBaker::_get_bake_texture(Ref<Image> p_image, const Color &p_color_mul, const Color &p_color_add) {
Vector<Color> ret;
if (p_image.is_null() || p_image->empty()) {
ret.resize(bake_texture_size * bake_texture_size);
for (int i = 0; i < bake_texture_size * bake_texture_size; i++) {
ret.write[i] = p_color_add;
}
return ret;
}
p_image = p_image->duplicate();
if (p_image->is_compressed()) {
p_image->decompress();
}
p_image->convert(Image::FORMAT_RGBA8);
p_image->resize(bake_texture_size, bake_texture_size, Image::INTERPOLATE_CUBIC);
PoolVector<uint8_t>::Read r = p_image->get_data().read();
ret.resize(bake_texture_size * bake_texture_size);
for (int i = 0; i < bake_texture_size * bake_texture_size; i++) {
Color c;
c.r = (r[i * 4 + 0] / 255.0) * p_color_mul.r + p_color_add.r;
c.g = (r[i * 4 + 1] / 255.0) * p_color_mul.g + p_color_add.g;
c.b = (r[i * 4 + 2] / 255.0) * p_color_mul.b + p_color_add.b;
c.a = r[i * 4 + 3] / 255.0;
ret.write[i] = c;
}
return ret;
}
VoxelLightBaker::MaterialCache VoxelLightBaker::_get_material_cache(Ref<Material> p_material) {
//this way of obtaining materials is inaccurate and also does not support some compressed formats very well
Ref<SpatialMaterial> mat = p_material;
Ref<Material> material = mat; //hack for now
if (material_cache.has(material)) {
return material_cache[material];
}
MaterialCache mc;
if (mat.is_valid()) {
Ref<Texture> albedo_tex = mat->get_texture(SpatialMaterial::TEXTURE_ALBEDO);
Ref<Image> img_albedo;
if (albedo_tex.is_valid()) {
img_albedo = albedo_tex->get_data();
mc.albedo = _get_bake_texture(img_albedo, mat->get_albedo(), Color(0, 0, 0)); // albedo texture, color is multiplicative
} else {
mc.albedo = _get_bake_texture(img_albedo, Color(1, 1, 1), mat->get_albedo()); // no albedo texture, color is additive
}
Ref<Texture> emission_tex = mat->get_texture(SpatialMaterial::TEXTURE_EMISSION);
Color emission_col = mat->get_emission();
float emission_energy = mat->get_emission_energy();
Ref<Image> img_emission;
if (emission_tex.is_valid()) {
img_emission = emission_tex->get_data();
}
if (mat->get_emission_operator() == SpatialMaterial::EMISSION_OP_ADD) {
mc.emission = _get_bake_texture(img_emission, Color(1, 1, 1) * emission_energy, emission_col * emission_energy);
} else {
mc.emission = _get_bake_texture(img_emission, emission_col * emission_energy, Color(0, 0, 0));
}
} else {
Ref<Image> empty;
mc.albedo = _get_bake_texture(empty, Color(0, 0, 0), Color(1, 1, 1));
mc.emission = _get_bake_texture(empty, Color(0, 0, 0), Color(0, 0, 0));
}
material_cache[p_material] = mc;
return mc;
}
void VoxelLightBaker::plot_mesh(const Transform &p_xform, Ref<Mesh> &p_mesh, const Vector<Ref<Material> > &p_materials, const Ref<Material> &p_override_material) {
for (int i = 0; i < p_mesh->get_surface_count(); i++) {
if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES)
continue; //only triangles
Ref<Material> src_material;
if (p_override_material.is_valid()) {
src_material = p_override_material;
} else if (i < p_materials.size() && p_materials[i].is_valid()) {
src_material = p_materials[i];
} else {
src_material = p_mesh->surface_get_material(i);
}
MaterialCache material = _get_material_cache(src_material);
Array a = p_mesh->surface_get_arrays(i);
PoolVector<Vector3> vertices = a[Mesh::ARRAY_VERTEX];
PoolVector<Vector3>::Read vr = vertices.read();
PoolVector<Vector2> uv = a[Mesh::ARRAY_TEX_UV];
PoolVector<Vector2>::Read uvr;
PoolVector<Vector3> normals = a[Mesh::ARRAY_NORMAL];
PoolVector<Vector3>::Read nr;
PoolVector<int> index = a[Mesh::ARRAY_INDEX];
bool read_uv = false;
bool read_normals = false;
if (uv.size()) {
uvr = uv.read();
read_uv = true;
}
if (normals.size()) {
read_normals = true;
nr = normals.read();
}
if (index.size()) {
int facecount = index.size() / 3;
PoolVector<int>::Read ir = index.read();
for (int j = 0; j < facecount; j++) {
Vector3 vtxs[3];
Vector2 uvs[3];
Vector3 normal[3];
for (int k = 0; k < 3; k++) {
vtxs[k] = p_xform.xform(vr[ir[j * 3 + k]]);
}
if (read_uv) {
for (int k = 0; k < 3; k++) {
uvs[k] = uvr[ir[j * 3 + k]];
}
}
if (read_normals) {
for (int k = 0; k < 3; k++) {
normal[k] = nr[ir[j * 3 + k]];
}
}
//test against original bounds
if (!fast_tri_box_overlap(original_bounds.position + original_bounds.size * 0.5, original_bounds.size * 0.5, vtxs))
continue;
//plot
_plot_face(0, 0, 0, 0, 0, vtxs, normal, uvs, material, po2_bounds);
}
} else {
int facecount = vertices.size() / 3;
for (int j = 0; j < facecount; j++) {
Vector3 vtxs[3];
Vector2 uvs[3];
Vector3 normal[3];
for (int k = 0; k < 3; k++) {
vtxs[k] = p_xform.xform(vr[j * 3 + k]);
}
if (read_uv) {
for (int k = 0; k < 3; k++) {
uvs[k] = uvr[j * 3 + k];
}
}
if (read_normals) {
for (int k = 0; k < 3; k++) {
normal[k] = nr[j * 3 + k];
}
}
//test against original bounds
if (!fast_tri_box_overlap(original_bounds.position + original_bounds.size * 0.5, original_bounds.size * 0.5, vtxs))
continue;
//plot face
_plot_face(0, 0, 0, 0, 0, vtxs, normal, uvs, material, po2_bounds);
}
}
}
max_original_cells = bake_cells.size();
}
void VoxelLightBaker::_init_light_plot(int p_idx, int p_level, int p_x, int p_y, int p_z, uint32_t p_parent) {
bake_light.write[p_idx].x = p_x;
bake_light.write[p_idx].y = p_y;
bake_light.write[p_idx].z = p_z;
if (p_level == cell_subdiv - 1) {
bake_light.write[p_idx].next_leaf = first_leaf;
first_leaf = p_idx;
} else {
//go down
int half = (1 << (cell_subdiv - 1)) >> (p_level + 1);
for (int i = 0; i < 8; i++) {
uint32_t child = bake_cells[p_idx].children[i];
if (child == CHILD_EMPTY)
continue;
int nx = p_x;
int ny = p_y;
int nz = p_z;
if (i & 1)
nx += half;
if (i & 2)
ny += half;
if (i & 4)
nz += half;
_init_light_plot(child, p_level + 1, nx, ny, nz, p_idx);
}
}
}
void VoxelLightBaker::begin_bake_light(BakeQuality p_quality, BakeMode p_bake_mode, float p_propagation, float p_energy) {
_check_init_light();
propagation = p_propagation;
bake_quality = p_quality;
bake_mode = p_bake_mode;
energy = p_energy;
}
void VoxelLightBaker::_check_init_light() {
if (bake_light.size() == 0) {
direct_lights_baked = false;
leaf_voxel_count = 0;
_fixup_plot(0, 0); //pre fixup, so normal, albedo, emission, etc. work for lighting.
bake_light.resize(bake_cells.size());
print_line("bake light size: " + itos(bake_light.size()));
//zeromem(bake_light.ptrw(), bake_light.size() * sizeof(Light));
first_leaf = -1;
_init_light_plot(0, 0, 0, 0, 0, CHILD_EMPTY);
}
}
static float _get_normal_advance(const Vector3 &p_normal) {
Vector3 normal = p_normal;
Vector3 unorm = normal.abs();
if ((unorm.x >= unorm.y) && (unorm.x >= unorm.z)) {
// x code
unorm = normal.x > 0.0 ? Vector3(1.0, 0.0, 0.0) : Vector3(-1.0, 0.0, 0.0);
} else if ((unorm.y > unorm.x) && (unorm.y >= unorm.z)) {
// y code
unorm = normal.y > 0.0 ? Vector3(0.0, 1.0, 0.0) : Vector3(0.0, -1.0, 0.0);
} else if ((unorm.z > unorm.x) && (unorm.z > unorm.y)) {
// z code
unorm = normal.z > 0.0 ? Vector3(0.0, 0.0, 1.0) : Vector3(0.0, 0.0, -1.0);
} else {
// oh-no we messed up code
// has to be
unorm = Vector3(1.0, 0.0, 0.0);
}
return 1.0 / normal.dot(unorm);
}
static const Vector3 aniso_normal[6] = {
Vector3(-1, 0, 0),
Vector3(1, 0, 0),
Vector3(0, -1, 0),
Vector3(0, 1, 0),
Vector3(0, 0, -1),
Vector3(0, 0, 1)
};
uint32_t VoxelLightBaker::_find_cell_at_pos(const Cell *cells, int x, int y, int z) {
uint32_t cell = 0;
int ofs_x = 0;
int ofs_y = 0;
int ofs_z = 0;
int size = 1 << (cell_subdiv - 1);
int half = size / 2;
if (x < 0 || x >= size)
return -1;
if (y < 0 || y >= size)
return -1;
if (z < 0 || z >= size)
return -1;
for (int i = 0; i < cell_subdiv - 1; i++) {
const Cell *bc = &cells[cell];
int child = 0;
if (x >= ofs_x + half) {
child |= 1;
ofs_x += half;
}
if (y >= ofs_y + half) {
child |= 2;
ofs_y += half;
}
if (z >= ofs_z + half) {
child |= 4;
ofs_z += half;
}
cell = bc->children[child];
if (cell == CHILD_EMPTY)
return CHILD_EMPTY;
half >>= 1;
}
return cell;
}
void VoxelLightBaker::plot_light_directional(const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, bool p_direct) {
_check_init_light();
float max_len = Vector3(axis_cell_size[0], axis_cell_size[1], axis_cell_size[2]).length() * 1.1;
if (p_direct)
direct_lights_baked = true;
Vector3 light_axis = p_direction;
Plane clip[3];
int clip_planes = 0;
Light *light_data = bake_light.ptrw();
const Cell *cells = bake_cells.ptr();
for (int i = 0; i < 3; i++) {
if (Math::is_zero_approx(light_axis[i]))
continue;
clip[clip_planes].normal[i] = 1.0;
if (light_axis[i] < 0) {
clip[clip_planes].d = axis_cell_size[i] + 1;
} else {
clip[clip_planes].d -= 1.0;
}
clip_planes++;
}
float distance_adv = _get_normal_advance(light_axis);
int success_count = 0;
Vector3 light_energy = Vector3(p_color.r, p_color.g, p_color.b) * p_energy * p_indirect_energy;
int idx = first_leaf;
while (idx >= 0) {
Light *light = &light_data[idx];
Vector3 to(light->x + 0.5, light->y + 0.5, light->z + 0.5);
to += -light_axis.sign() * 0.47; //make it more likely to receive a ray
Vector3 from = to - max_len * light_axis;
for (int j = 0; j < clip_planes; j++) {
clip[j].intersects_segment(from, to, &from);
}
float distance = (to - from).length();
distance += distance_adv - Math::fmod(distance, distance_adv); //make it reach the center of the box always
from = to - light_axis * distance;
uint32_t result = 0xFFFFFFFF;
while (distance > -distance_adv) { //use this to avoid precision errors
result = _find_cell_at_pos(cells, int(floor(from.x)), int(floor(from.y)), int(floor(from.z)));
if (result != 0xFFFFFFFF) {
break;
}
from += light_axis * distance_adv;
distance -= distance_adv;
}
if (result == (uint32_t)idx) {
//cell hit itself! hooray!
Vector3 normal(cells[idx].normal[0], cells[idx].normal[1], cells[idx].normal[2]);
if (normal == Vector3()) {
for (int i = 0; i < 6; i++) {
light->accum[i][0] += light_energy.x * cells[idx].albedo[0];
light->accum[i][1] += light_energy.y * cells[idx].albedo[1];
light->accum[i][2] += light_energy.z * cells[idx].albedo[2];
}
} else {
for (int i = 0; i < 6; i++) {
float s = MAX(0.0, aniso_normal[i].dot(-normal));
light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * s;
light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * s;
light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * s;
}
}
if (p_direct) {
for (int i = 0; i < 6; i++) {
float s = MAX(0.0, aniso_normal[i].dot(-light_axis)); //light depending on normal for direct
light->direct_accum[i][0] += light_energy.x * s;
light->direct_accum[i][1] += light_energy.y * s;
light->direct_accum[i][2] += light_energy.z * s;
}
}
success_count++;
}
idx = light_data[idx].next_leaf;
}
}
void VoxelLightBaker::plot_light_omni(const Vector3 &p_pos, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, bool p_direct) {
_check_init_light();
if (p_direct)
direct_lights_baked = true;
Plane clip[3];
int clip_planes = 0;
// uint64_t us = OS::get_singleton()->get_ticks_usec();
Vector3 light_pos = to_cell_space.xform(p_pos) + Vector3(0.5, 0.5, 0.5);
//Vector3 spot_axis = -light_cache.transform.basis.get_axis(2).normalized();
float local_radius = to_cell_space.basis.xform(Vector3(0, 0, 1)).length() * p_radius;
Light *light_data = bake_light.ptrw();
const Cell *cells = bake_cells.ptr();
Vector3 light_energy = Vector3(p_color.r, p_color.g, p_color.b) * p_energy * p_indirect_energy;
int idx = first_leaf;
while (idx >= 0) {
Light *light = &light_data[idx];
Vector3 to(light->x + 0.5, light->y + 0.5, light->z + 0.5);
to += (light_pos - to).sign() * 0.47; //make it more likely to receive a ray
Vector3 light_axis = (to - light_pos).normalized();
float distance_adv = _get_normal_advance(light_axis);
Vector3 normal(cells[idx].normal[0], cells[idx].normal[1], cells[idx].normal[2]);
if (normal != Vector3() && normal.dot(-light_axis) < 0.001) {
idx = light_data[idx].next_leaf;
continue;
}
float att = 1.0;
{
float d = light_pos.distance_to(to);
if (d + distance_adv > local_radius) {
idx = light_data[idx].next_leaf;
continue; // too far away
}
float dt = CLAMP((d + distance_adv) / local_radius, 0, 1);
att *= powf(1.0 - dt, p_attenutation);
}
clip_planes = 0;
for (int c = 0; c < 3; c++) {
if (Math::is_zero_approx(light_axis[c]))
continue;
clip[clip_planes].normal[c] = 1.0;
if (light_axis[c] < 0) {
clip[clip_planes].d = (1 << (cell_subdiv - 1)) + 1;
} else {
clip[clip_planes].d -= 1.0;
}
clip_planes++;
}
Vector3 from = light_pos;
for (int j = 0; j < clip_planes; j++) {
clip[j].intersects_segment(from, to, &from);
}
float distance = (to - from).length();
distance -= Math::fmod(distance, distance_adv); //make it reach the center of the box always, but this tame make it closer
from = to - light_axis * distance;
to += (light_pos - to).sign() * 0.47; //make it more likely to receive a ray
uint32_t result = 0xFFFFFFFF;
while (distance > -distance_adv) { //use this to avoid precision errors
result = _find_cell_at_pos(cells, int(floor(from.x)), int(floor(from.y)), int(floor(from.z)));
if (result != 0xFFFFFFFF) {
break;
}
from += light_axis * distance_adv;
distance -= distance_adv;
}
if (result == (uint32_t)idx) {
//cell hit itself! hooray!
if (normal == Vector3()) {
for (int i = 0; i < 6; i++) {
light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * att;
light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * att;
light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * att;
}
} else {
for (int i = 0; i < 6; i++) {
float s = MAX(0.0, aniso_normal[i].dot(-normal));
light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * s * att;
light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * s * att;
light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * s * att;
}
}
if (p_direct) {
for (int i = 0; i < 6; i++) {
float s = MAX(0.0, aniso_normal[i].dot(-light_axis)); //light depending on normal for direct
light->direct_accum[i][0] += light_energy.x * s * att;
light->direct_accum[i][1] += light_energy.y * s * att;
light->direct_accum[i][2] += light_energy.z * s * att;
}
}
}
idx = light_data[idx].next_leaf;
}
}
void VoxelLightBaker::plot_light_spot(const Vector3 &p_pos, const Vector3 &p_axis, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, float p_spot_angle, float p_spot_attenuation, bool p_direct) {
_check_init_light();
if (p_direct)
direct_lights_baked = true;
Plane clip[3];
int clip_planes = 0;
// uint64_t us = OS::get_singleton()->get_ticks_usec();
Vector3 light_pos = to_cell_space.xform(p_pos) + Vector3(0.5, 0.5, 0.5);
Vector3 spot_axis = to_cell_space.basis.xform(p_axis).normalized();
float local_radius = to_cell_space.basis.xform(Vector3(0, 0, 1)).length() * p_radius;
Light *light_data = bake_light.ptrw();
const Cell *cells = bake_cells.ptr();
Vector3 light_energy = Vector3(p_color.r, p_color.g, p_color.b) * p_energy * p_indirect_energy;
int idx = first_leaf;
while (idx >= 0) {
Light *light = &light_data[idx];
Vector3 to(light->x + 0.5, light->y + 0.5, light->z + 0.5);
Vector3 light_axis = (to - light_pos).normalized();
float distance_adv = _get_normal_advance(light_axis);
Vector3 normal(cells[idx].normal[0], cells[idx].normal[1], cells[idx].normal[2]);
if (normal != Vector3() && normal.dot(-light_axis) < 0.001) {
idx = light_data[idx].next_leaf;
continue;
}
float angle = Math::rad2deg(Math::acos(light_axis.dot(-spot_axis)));
if (angle > p_spot_angle) {
idx = light_data[idx].next_leaf;
continue; // too far away
}
float att = Math::pow(1.0f - angle / p_spot_angle, p_spot_attenuation);
{
float d = light_pos.distance_to(to);
if (d + distance_adv > local_radius) {
idx = light_data[idx].next_leaf;
continue; // too far away
}
float dt = CLAMP((d + distance_adv) / local_radius, 0, 1);
att *= powf(1.0 - dt, p_attenutation);
}
clip_planes = 0;
for (int c = 0; c < 3; c++) {
if (Math::is_zero_approx(light_axis[c]))
continue;
clip[clip_planes].normal[c] = 1.0;
if (light_axis[c] < 0) {
clip[clip_planes].d = (1 << (cell_subdiv - 1)) + 1;
} else {
clip[clip_planes].d -= 1.0;
}
clip_planes++;
}
Vector3 from = light_pos;
for (int j = 0; j < clip_planes; j++) {
clip[j].intersects_segment(from, to, &from);
}
float distance = (to - from).length();
distance -= Math::fmod(distance, distance_adv); //make it reach the center of the box always, but this tame make it closer
from = to - light_axis * distance;
uint32_t result = 0xFFFFFFFF;
while (distance > -distance_adv) { //use this to avoid precision errors
result = _find_cell_at_pos(cells, int(floor(from.x)), int(floor(from.y)), int(floor(from.z)));
if (result != 0xFFFFFFFF) {
break;
}
from += light_axis * distance_adv;
distance -= distance_adv;
}
if (result == (uint32_t)idx) {
//cell hit itself! hooray!
if (normal == Vector3()) {
for (int i = 0; i < 6; i++) {
light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * att;
light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * att;
light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * att;
}
} else {
for (int i = 0; i < 6; i++) {
float s = MAX(0.0, aniso_normal[i].dot(-normal));
light->accum[i][0] += light_energy.x * cells[idx].albedo[0] * s * att;
light->accum[i][1] += light_energy.y * cells[idx].albedo[1] * s * att;
light->accum[i][2] += light_energy.z * cells[idx].albedo[2] * s * att;
}
}
if (p_direct) {
for (int i = 0; i < 6; i++) {
float s = MAX(0.0, aniso_normal[i].dot(-light_axis)); //light depending on normal for direct
light->direct_accum[i][0] += light_energy.x * s * att;
light->direct_accum[i][1] += light_energy.y * s * att;
light->direct_accum[i][2] += light_energy.z * s * att;
}
}
}
idx = light_data[idx].next_leaf;
}
}
void VoxelLightBaker::_fixup_plot(int p_idx, int p_level) {
if (p_level == cell_subdiv - 1) {
leaf_voxel_count++;
float alpha = bake_cells[p_idx].alpha;
bake_cells.write[p_idx].albedo[0] /= alpha;
bake_cells.write[p_idx].albedo[1] /= alpha;
bake_cells.write[p_idx].albedo[2] /= alpha;
//transfer emission to light
bake_cells.write[p_idx].emission[0] /= alpha;
bake_cells.write[p_idx].emission[1] /= alpha;
bake_cells.write[p_idx].emission[2] /= alpha;
bake_cells.write[p_idx].normal[0] /= alpha;
bake_cells.write[p_idx].normal[1] /= alpha;
bake_cells.write[p_idx].normal[2] /= alpha;
Vector3 n(bake_cells[p_idx].normal[0], bake_cells[p_idx].normal[1], bake_cells[p_idx].normal[2]);
if (n.length() < 0.01) {
//too much fight over normal, zero it
bake_cells.write[p_idx].normal[0] = 0;
bake_cells.write[p_idx].normal[1] = 0;
bake_cells.write[p_idx].normal[2] = 0;
} else {
n.normalize();
bake_cells.write[p_idx].normal[0] = n.x;
bake_cells.write[p_idx].normal[1] = n.y;
bake_cells.write[p_idx].normal[2] = n.z;
}
bake_cells.write[p_idx].alpha = 1.0;
/*if (bake_light.size()) {
for(int i=0;i<6;i++) {
}
}*/
} else {
//go down
bake_cells.write[p_idx].emission[0] = 0;
bake_cells.write[p_idx].emission[1] = 0;
bake_cells.write[p_idx].emission[2] = 0;
bake_cells.write[p_idx].normal[0] = 0;
bake_cells.write[p_idx].normal[1] = 0;
bake_cells.write[p_idx].normal[2] = 0;
bake_cells.write[p_idx].albedo[0] = 0;
bake_cells.write[p_idx].albedo[1] = 0;
bake_cells.write[p_idx].albedo[2] = 0;
if (bake_light.size()) {
for (int j = 0; j < 6; j++) {
bake_light.write[p_idx].accum[j][0] = 0;
bake_light.write[p_idx].accum[j][1] = 0;
bake_light.write[p_idx].accum[j][2] = 0;
}
}
float alpha_average = 0;
int children_found = 0;
for (int i = 0; i < 8; i++) {
uint32_t child = bake_cells[p_idx].children[i];
if (child == CHILD_EMPTY)
continue;
_fixup_plot(child, p_level + 1);
alpha_average += bake_cells[child].alpha;
if (bake_light.size() > 0) {
for (int j = 0; j < 6; j++) {
bake_light.write[p_idx].accum[j][0] += bake_light[child].accum[j][0];
bake_light.write[p_idx].accum[j][1] += bake_light[child].accum[j][1];
bake_light.write[p_idx].accum[j][2] += bake_light[child].accum[j][2];
}
bake_cells.write[p_idx].emission[0] += bake_cells[child].emission[0];
bake_cells.write[p_idx].emission[1] += bake_cells[child].emission[1];
bake_cells.write[p_idx].emission[2] += bake_cells[child].emission[2];
}
children_found++;
}
bake_cells.write[p_idx].alpha = alpha_average / 8.0;
if (bake_light.size() && children_found) {
float divisor = Math::lerp(8, children_found, propagation);
for (int j = 0; j < 6; j++) {
bake_light.write[p_idx].accum[j][0] /= divisor;
bake_light.write[p_idx].accum[j][1] /= divisor;
bake_light.write[p_idx].accum[j][2] /= divisor;
}
bake_cells.write[p_idx].emission[0] /= divisor;
bake_cells.write[p_idx].emission[1] /= divisor;
bake_cells.write[p_idx].emission[2] /= divisor;
}
}
}
//make sure any cell (save for the root) has an empty cell previous to it, so it can be interpolated into
void VoxelLightBaker::_plot_triangle(Vector2 *vertices, Vector3 *positions, Vector3 *normals, LightMap *pixels, int width, int height) {
int x[3];
int y[3];
for (int j = 0; j < 3; j++) {
x[j] = vertices[j].x * width;
y[j] = vertices[j].y * height;
//x[j] = CLAMP(x[j], 0, bt.width - 1);
//y[j] = CLAMP(y[j], 0, bt.height - 1);
}
// sort the points vertically
if (y[1] > y[2]) {
SWAP(x[1], x[2]);
SWAP(y[1], y[2]);
SWAP(positions[1], positions[2]);
SWAP(normals[1], normals[2]);
}
if (y[0] > y[1]) {
SWAP(x[0], x[1]);
SWAP(y[0], y[1]);
SWAP(positions[0], positions[1]);
SWAP(normals[0], normals[1]);
}
if (y[1] > y[2]) {
SWAP(x[1], x[2]);
SWAP(y[1], y[2]);
SWAP(positions[1], positions[2]);
SWAP(normals[1], normals[2]);
}
double dx_far = double(x[2] - x[0]) / (y[2] - y[0] + 1);
double dx_upper = double(x[1] - x[0]) / (y[1] - y[0] + 1);
double dx_low = double(x[2] - x[1]) / (y[2] - y[1] + 1);
double xf = x[0];
double xt = x[0] + dx_upper; // if y[0] == y[1], special case
for (int yi = y[0]; yi <= (y[2] > height - 1 ? height - 1 : y[2]); yi++) {
if (yi >= 0) {
for (int xi = (xf > 0 ? int(xf) : 0); xi <= (xt < width ? xt : width - 1); xi++) {
//pixels[int(x + y * width)] = color;
Vector2 v0 = Vector2(x[1] - x[0], y[1] - y[0]);
Vector2 v1 = Vector2(x[2] - x[0], y[2] - y[0]);
//vertices[2] - vertices[0];
Vector2 v2 = Vector2(xi - x[0], yi - y[0]);
float d00 = v0.dot(v0);
float d01 = v0.dot(v1);
float d11 = v1.dot(v1);
float d20 = v2.dot(v0);
float d21 = v2.dot(v1);
float denom = (d00 * d11 - d01 * d01);
Vector3 pos;
Vector3 normal;
if (denom == 0) {
pos = positions[0];
normal = normals[0];
} else {
float v = (d11 * d20 - d01 * d21) / denom;
float w = (d00 * d21 - d01 * d20) / denom;
float u = 1.0f - v - w;
pos = positions[0] * u + positions[1] * v + positions[2] * w;
normal = normals[0] * u + normals[1] * v + normals[2] * w;
}
int ofs = yi * width + xi;
pixels[ofs].normal = normal;
pixels[ofs].pos = pos;
}
for (int xi = (xf < width ? int(xf) : width - 1); xi >= (xt > 0 ? xt : 0); xi--) {
//pixels[int(x + y * width)] = color;
Vector2 v0 = Vector2(x[1] - x[0], y[1] - y[0]);
Vector2 v1 = Vector2(x[2] - x[0], y[2] - y[0]);
//vertices[2] - vertices[0];
Vector2 v2 = Vector2(xi - x[0], yi - y[0]);
float d00 = v0.dot(v0);
float d01 = v0.dot(v1);
float d11 = v1.dot(v1);
float d20 = v2.dot(v0);
float d21 = v2.dot(v1);
float denom = (d00 * d11 - d01 * d01);
Vector3 pos;
Vector3 normal;
if (denom == 0) {
pos = positions[0];
normal = normals[0];
} else {
float v = (d11 * d20 - d01 * d21) / denom;
float w = (d00 * d21 - d01 * d20) / denom;
float u = 1.0f - v - w;
pos = positions[0] * u + positions[1] * v + positions[2] * w;
normal = normals[0] * u + normals[1] * v + normals[2] * w;
}
int ofs = yi * width + xi;
pixels[ofs].normal = normal;
pixels[ofs].pos = pos;
}
}
xf += dx_far;
if (yi < y[1])
xt += dx_upper;
else
xt += dx_low;
}
}
void VoxelLightBaker::_sample_baked_octree_filtered_and_anisotropic(const Vector3 &p_posf, const Vector3 &p_direction, float p_level, Vector3 &r_color, float &r_alpha) {
int size = 1 << (cell_subdiv - 1);
int clamp_v = size - 1;
//first of all, clamp
Vector3 pos;
pos.x = CLAMP(p_posf.x, 0, clamp_v);
pos.y = CLAMP(p_posf.y, 0, clamp_v);
pos.z = CLAMP(p_posf.z, 0, clamp_v);
float level = (cell_subdiv - 1) - p_level;
int target_level;
float level_filter;
if (level <= 0.0) {
level_filter = 0;
target_level = 0;
} else {
target_level = Math::ceil(level);
level_filter = target_level - level;
}
const Cell *cells = bake_cells.ptr();
const Light *light = bake_light.ptr();
Vector3 color[2][8];
float alpha[2][8];
zeromem(alpha, sizeof(float) * 2 * 8);
//find cell at given level first
for (int c = 0; c < 2; c++) {
int current_level = MAX(0, target_level - c);
int level_cell_size = (1 << (cell_subdiv - 1)) >> current_level;
for (int n = 0; n < 8; n++) {
int x = int(pos.x);
int y = int(pos.y);
int z = int(pos.z);
if (n & 1)
x += level_cell_size;
if (n & 2)
y += level_cell_size;
if (n & 4)
z += level_cell_size;
int ofs_x = 0;
int ofs_y = 0;
int ofs_z = 0;
x = CLAMP(x, 0, clamp_v);
y = CLAMP(y, 0, clamp_v);
z = CLAMP(z, 0, clamp_v);
int half = size / 2;
uint32_t cell = 0;
for (int i = 0; i < current_level; i++) {
const Cell *bc = &cells[cell];
int child = 0;
if (x >= ofs_x + half) {
child |= 1;
ofs_x += half;
}
if (y >= ofs_y + half) {
child |= 2;
ofs_y += half;
}
if (z >= ofs_z + half) {
child |= 4;
ofs_z += half;
}
cell = bc->children[child];
if (cell == CHILD_EMPTY)
break;
half >>= 1;
}
if (cell == CHILD_EMPTY) {
alpha[c][n] = 0;
} else {
alpha[c][n] = cells[cell].alpha;
for (int i = 0; i < 6; i++) {
//anisotropic read light
float amount = p_direction.dot(aniso_normal[i]);
if (amount < 0)
amount = 0;
color[c][n].x += light[cell].accum[i][0] * amount;
color[c][n].y += light[cell].accum[i][1] * amount;
color[c][n].z += light[cell].accum[i][2] * amount;
}
color[c][n].x += cells[cell].emission[0];
color[c][n].y += cells[cell].emission[1];
color[c][n].z += cells[cell].emission[2];
}
}
}
float target_level_size = size >> target_level;
Vector3 pos_fract[2];
pos_fract[0].x = Math::fmod(pos.x, target_level_size) / target_level_size;
pos_fract[0].y = Math::fmod(pos.y, target_level_size) / target_level_size;
pos_fract[0].z = Math::fmod(pos.z, target_level_size) / target_level_size;
target_level_size = size >> MAX(0, target_level - 1);
pos_fract[1].x = Math::fmod(pos.x, target_level_size) / target_level_size;
pos_fract[1].y = Math::fmod(pos.y, target_level_size) / target_level_size;
pos_fract[1].z = Math::fmod(pos.z, target_level_size) / target_level_size;
float alpha_interp[2];
Vector3 color_interp[2];
for (int i = 0; i < 2; i++) {
Vector3 color_x00 = color[i][0].linear_interpolate(color[i][1], pos_fract[i].x);
Vector3 color_xy0 = color[i][2].linear_interpolate(color[i][3], pos_fract[i].x);
Vector3 blend_z0 = color_x00.linear_interpolate(color_xy0, pos_fract[i].y);
Vector3 color_x0z = color[i][4].linear_interpolate(color[i][5], pos_fract[i].x);
Vector3 color_xyz = color[i][6].linear_interpolate(color[i][7], pos_fract[i].x);
Vector3 blend_z1 = color_x0z.linear_interpolate(color_xyz, pos_fract[i].y);
color_interp[i] = blend_z0.linear_interpolate(blend_z1, pos_fract[i].z);
float alpha_x00 = Math::lerp(alpha[i][0], alpha[i][1], pos_fract[i].x);
float alpha_xy0 = Math::lerp(alpha[i][2], alpha[i][3], pos_fract[i].x);
float alpha_z0 = Math::lerp(alpha_x00, alpha_xy0, pos_fract[i].y);
float alpha_x0z = Math::lerp(alpha[i][4], alpha[i][5], pos_fract[i].x);
float alpha_xyz = Math::lerp(alpha[i][6], alpha[i][7], pos_fract[i].x);
float alpha_z1 = Math::lerp(alpha_x0z, alpha_xyz, pos_fract[i].y);
alpha_interp[i] = Math::lerp(alpha_z0, alpha_z1, pos_fract[i].z);
}
r_color = color_interp[0].linear_interpolate(color_interp[1], level_filter);
r_alpha = Math::lerp(alpha_interp[0], alpha_interp[1], level_filter);
}
Vector3 VoxelLightBaker::_voxel_cone_trace(const Vector3 &p_pos, const Vector3 &p_normal, float p_aperture) {
float bias = 2.5;
float max_distance = (Vector3(1, 1, 1) * (1 << (cell_subdiv - 1))).length();
float dist = bias;
float alpha = 0.0;
Vector3 color;
Vector3 scolor;
float salpha;
while (dist < max_distance && alpha < 0.95) {
float diameter = MAX(1.0, 2.0 * p_aperture * dist);
_sample_baked_octree_filtered_and_anisotropic(p_pos + dist * p_normal, p_normal, log2(diameter), scolor, salpha);
float a = (1.0 - alpha);
color += scolor * a;
alpha += a * salpha;
dist += diameter * 0.5;
}
/*if (blend_ambient) {
color.rgb = mix(ambient,color.rgb,min(1.0,alpha/0.95));
}*/
return color;
}
Vector3 VoxelLightBaker::_compute_pixel_light_at_pos(const Vector3 &p_pos, const Vector3 &p_normal) {
//find arbitrary tangent and bitangent, then build a matrix
Vector3 v0 = Math::abs(p_normal.z) < 0.999 ? Vector3(0, 0, 1) : Vector3(0, 1, 0);
Vector3 tangent = v0.cross(p_normal).normalized();
Vector3 bitangent = tangent.cross(p_normal).normalized();
Basis normal_xform = Basis(tangent, bitangent, p_normal).transposed();
const Vector3 *cone_dirs = NULL;
const float *cone_weights = NULL;
int cone_dir_count = 0;
float cone_aperture = 0;
switch (bake_quality) {
case BAKE_QUALITY_LOW: {
//default quality
static const Vector3 dirs[4] = {
Vector3(Math_SQRT12, 0, Math_SQRT12),
Vector3(0, Math_SQRT12, Math_SQRT12),
Vector3(-Math_SQRT12, 0, Math_SQRT12),
Vector3(0, -Math_SQRT12, Math_SQRT12)
};
static const float weights[4] = { 0.25, 0.25, 0.25, 0.25 };
cone_dirs = dirs;
cone_dir_count = 4;
cone_aperture = 1.0; // tan(angle) 90 degrees
cone_weights = weights;
} break;
case BAKE_QUALITY_MEDIUM: {
//default quality
static const Vector3 dirs[6] = {
Vector3(0, 0, 1),
Vector3(0.866025, 0, 0.5),
Vector3(0.267617, 0.823639, 0.5),
Vector3(-0.700629, 0.509037, 0.5),
Vector3(-0.700629, -0.509037, 0.5),
Vector3(0.267617, -0.823639, 0.5)
};
static const float weights[6] = { 0.25f, 0.15f, 0.15f, 0.15f, 0.15f, 0.15f };
//
cone_dirs = dirs;
cone_dir_count = 6;
cone_aperture = 0.577; // tan(angle) 60 degrees
cone_weights = weights;
} break;
case BAKE_QUALITY_HIGH: {
//high qualily
static const Vector3 dirs[10] = {
Vector3(0.8781648411741658, 0.0, 0.478358141694643),
Vector3(0.5369754325592234, 0.6794204427701518, 0.5000452447267606),
Vector3(-0.19849436573466497, 0.8429904390140635, 0.49996710542041645),
Vector3(-0.7856196499811189, 0.3639120321329737, 0.5003696617825604),
Vector3(-0.7856196499811189, -0.3639120321329737, 0.5003696617825604),
Vector3(-0.19849436573466497, -0.8429904390140635, 0.49996710542041645),
Vector3(0.5369754325592234, -0.6794204427701518, 0.5000452447267606),
Vector3(-0.4451656858129485, 0.0, 0.8954482185892644),
Vector3(0.19124006749743122, 0.39355745585016605, 0.8991883926788214),
Vector3(0.19124006749743122, -0.39355745585016605, 0.8991883926788214),
};
static const float weights[10] = { 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.133333f, 0.133333f, 0.13333f };
cone_dirs = dirs;
cone_dir_count = 10;
cone_aperture = 0.404; // tan(angle) 45 degrees
cone_weights = weights;
} break;
}
Vector3 accum;
for (int i = 0; i < cone_dir_count; i++) {
Vector3 dir = normal_xform.xform(cone_dirs[i]).normalized(); //normal may not completely correct when transformed to cell
accum += _voxel_cone_trace(p_pos, dir, cone_aperture) * cone_weights[i];
}
return accum;
}
_ALWAYS_INLINE_ uint32_t xorshift32(uint32_t *state) {
/* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */
uint32_t x = *state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
*state = x;
return x;
}
Vector3 VoxelLightBaker::_compute_ray_trace_at_pos(const Vector3 &p_pos, const Vector3 &p_normal) {
int samples_per_quality[3] = { 48, 128, 512 };
int samples = samples_per_quality[bake_quality];
//create a basis in Z
Vector3 v0 = Math::abs(p_normal.z) < 0.999 ? Vector3(0, 0, 1) : Vector3(0, 1, 0);
Vector3 tangent = v0.cross(p_normal).normalized();
Vector3 bitangent = tangent.cross(p_normal).normalized();
Basis normal_xform = Basis(tangent, bitangent, p_normal).transposed();
float bias = 1.5;
int max_level = cell_subdiv - 1;
int size = 1 << max_level;
Vector3 accum;
float spread = Math::deg2rad(80.0);
const Light *light = bake_light.ptr();
const Cell *cells = bake_cells.ptr();
uint32_t local_rng_state = rand(); //needs to be fixed again
for (int i = 0; i < samples; i++) {
float random_angle1 = (((xorshift32(&local_rng_state) % 65535) / 65535.0) * 2.0 - 1.0) * spread;
Vector3 axis(0, sin(random_angle1), cos(random_angle1));
float random_angle2 = ((xorshift32(&local_rng_state) % 65535) / 65535.0) * Math_PI * 2.0;
Basis rot(Vector3(0, 0, 1), random_angle2);
axis = rot.xform(axis);
Vector3 direction = normal_xform.xform(axis).normalized();
Vector3 advance = direction * _get_normal_advance(direction);
Vector3 pos = p_pos /*+ Vector3(0.5, 0.5, 0.5)*/ + advance * bias;
uint32_t cell = CHILD_EMPTY;
while (cell == CHILD_EMPTY) {
int x = int(pos.x);
int y = int(pos.y);
int z = int(pos.z);
int ofs_x = 0;
int ofs_y = 0;
int ofs_z = 0;
int half = size / 2;
if (x < 0 || x >= size)
break;
if (y < 0 || y >= size)
break;
if (z < 0 || z >= size)
break;
//int level_limit = max_level;
cell = 0; //start from root
for (int j = 0; j < max_level; j++) {
const Cell *bc = &cells[cell];
int child = 0;
if (x >= ofs_x + half) {
child |= 1;
ofs_x += half;
}
if (y >= ofs_y + half) {
child |= 2;
ofs_y += half;
}
if (z >= ofs_z + half) {
child |= 4;
ofs_z += half;
}
cell = bc->children[child];
if (unlikely(cell == CHILD_EMPTY))
break;
half >>= 1;
}
pos += advance;
}
if (unlikely(cell != CHILD_EMPTY)) {
for (int j = 0; j < 6; j++) {
//anisotropic read light
float amount = direction.dot(aniso_normal[j]);
if (amount <= 0)
continue;
accum.x += light[cell].accum[j][0] * amount;
accum.y += light[cell].accum[j][1] * amount;
accum.z += light[cell].accum[j][2] * amount;
}
accum.x += cells[cell].emission[0];
accum.y += cells[cell].emission[1];
accum.z += cells[cell].emission[2];
}
}
// Make sure we don't reset this thread's RNG state
return accum / samples;
}
void VoxelLightBaker::_lightmap_bake_point(uint32_t p_x, LightMap *p_line) {
LightMap *pixel = &p_line[p_x];
if (pixel->pos == Vector3())
return;
switch (bake_mode) {
case BAKE_MODE_CONE_TRACE: {
pixel->light = _compute_pixel_light_at_pos(pixel->pos, pixel->normal) * energy;
} break;
case BAKE_MODE_RAY_TRACE: {
pixel->light = _compute_ray_trace_at_pos(pixel->pos, pixel->normal) * energy;
} break;
}
}
Error VoxelLightBaker::make_lightmap(const Transform &p_xform, Ref<Mesh> &p_mesh, float default_texels_per_unit, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float), void *p_bake_time_ud) {
//transfer light information to a lightmap
Ref<Mesh> mesh = p_mesh;
//step 1 - create lightmap
int width;
int height;
Vector<LightMap> lightmap;
Transform xform = to_cell_space * p_xform;
if (mesh->get_lightmap_size_hint() == Size2()) {
double area = 0;
double uv_area = 0;
for (int i = 0; i < mesh->get_surface_count(); i++) {
Array arrays = mesh->surface_get_arrays(i);
PoolVector<Vector3> vertices = arrays[Mesh::ARRAY_VERTEX];
PoolVector<Vector2> uv2 = arrays[Mesh::ARRAY_TEX_UV2];
PoolVector<int> indices = arrays[Mesh::ARRAY_INDEX];
ERR_FAIL_COND_V(vertices.size() == 0, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(uv2.size() == 0, ERR_INVALID_PARAMETER);
int vc = vertices.size();
PoolVector<Vector3>::Read vr = vertices.read();
PoolVector<Vector2>::Read u2r = uv2.read();
PoolVector<int>::Read ir;
int ic = 0;
if (indices.size()) {
ic = indices.size();
ir = indices.read();
}
int faces = ic ? ic / 3 : vc / 3;
for (int j = 0; j < faces; j++) {
Vector3 vertex[3];
Vector2 uv[3];
for (int k = 0; k < 3; k++) {
int idx = ic ? ir[j * 3 + k] : j * 3 + k;
vertex[k] = xform.xform(vr[idx]);
uv[k] = u2r[idx];
}
Vector3 p1 = vertex[0];
Vector3 p2 = vertex[1];
Vector3 p3 = vertex[2];
double a = p1.distance_to(p2);
double b = p2.distance_to(p3);
double c = p3.distance_to(p1);
double halfPerimeter = (a + b + c) / 2.0;
area += sqrt(halfPerimeter * (halfPerimeter - a) * (halfPerimeter - b) * (halfPerimeter - c));
Vector2 uv_p1 = uv[0];
Vector2 uv_p2 = uv[1];
Vector2 uv_p3 = uv[2];
double uv_a = uv_p1.distance_to(uv_p2);
double uv_b = uv_p2.distance_to(uv_p3);
double uv_c = uv_p3.distance_to(uv_p1);
double uv_halfPerimeter = (uv_a + uv_b + uv_c) / 2.0;
uv_area += sqrt(uv_halfPerimeter * (uv_halfPerimeter - uv_a) * (uv_halfPerimeter - uv_b) * (uv_halfPerimeter - uv_c));
}
}
if (uv_area < 0.0001f) {
uv_area = 1.0;
}
int pixels = (ceil((1.0 / sqrt(uv_area)) * sqrt(area * default_texels_per_unit)));
width = height = CLAMP(pixels, 2, 4096);
} else {
width = mesh->get_lightmap_size_hint().x;
height = mesh->get_lightmap_size_hint().y;
}
lightmap.resize(width * height);
//step 2 plot faces to lightmap
for (int i = 0; i < mesh->get_surface_count(); i++) {
Array arrays = mesh->surface_get_arrays(i);
PoolVector<Vector3> vertices = arrays[Mesh::ARRAY_VERTEX];
PoolVector<Vector3> normals = arrays[Mesh::ARRAY_NORMAL];
PoolVector<Vector2> uv2 = arrays[Mesh::ARRAY_TEX_UV2];
PoolVector<int> indices = arrays[Mesh::ARRAY_INDEX];
ERR_FAIL_COND_V(vertices.size() == 0, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(normals.size() == 0, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(uv2.size() == 0, ERR_INVALID_PARAMETER);
int vc = vertices.size();
PoolVector<Vector3>::Read vr = vertices.read();
PoolVector<Vector3>::Read nr = normals.read();
PoolVector<Vector2>::Read u2r = uv2.read();
PoolVector<int>::Read ir;
int ic = 0;
if (indices.size()) {
ic = indices.size();
ir = indices.read();
}
int faces = ic ? ic / 3 : vc / 3;
for (int j = 0; j < faces; j++) {
Vector3 vertex[3];
Vector3 normal[3];
Vector2 uv[3];
for (int k = 0; k < 3; k++) {
int idx = ic ? ir[j * 3 + k] : j * 3 + k;
vertex[k] = xform.xform(vr[idx]);
normal[k] = xform.basis.xform(nr[idx]).normalized();
uv[k] = u2r[idx];
}
_plot_triangle(uv, vertex, normal, lightmap.ptrw(), width, height);
}
}
//step 3 perform voxel cone trace on lightmap pixels
{
LightMap *lightmap_ptr = lightmap.ptrw();
uint64_t begin_time = OS::get_singleton()->get_ticks_usec();
volatile int lines = 0;
// make sure our OS-level rng is seeded
for (int i = 0; i < height; i++) {
thread_process_array(width, this, &VoxelLightBaker::_lightmap_bake_point, &lightmap_ptr[i * width]);
lines = MAX(lines, i); //for multithread
if (p_bake_time_func) {
uint64_t elapsed = OS::get_singleton()->get_ticks_usec() - begin_time;
float elapsed_sec = double(elapsed) / 1000000.0;
float remaining = lines < 1 ? 0 : (elapsed_sec / lines) * (height - lines - 1);
if (p_bake_time_func(p_bake_time_ud, remaining, lines / float(height))) {
return ERR_SKIP;
}
}
}
if (bake_mode == BAKE_MODE_RAY_TRACE) {
//blur
//gauss kernel, 7 step sigma 2
static const float gauss_kernel[4] = { 0.214607f, 0.189879f, 0.131514f, 0.071303f };
//horizontal pass
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (lightmap_ptr[i * width + j].normal == Vector3()) {
continue; //empty
}
float gauss_sum = gauss_kernel[0];
Vector3 accum = lightmap_ptr[i * width + j].light * gauss_kernel[0];
for (int k = 1; k < 4; k++) {
int new_x = j + k;
if (new_x >= width || lightmap_ptr[i * width + new_x].normal == Vector3())
break;
gauss_sum += gauss_kernel[k];
accum += lightmap_ptr[i * width + new_x].light * gauss_kernel[k];
}
for (int k = 1; k < 4; k++) {
int new_x = j - k;
if (new_x < 0 || lightmap_ptr[i * width + new_x].normal == Vector3())
break;
gauss_sum += gauss_kernel[k];
accum += lightmap_ptr[i * width + new_x].light * gauss_kernel[k];
}
lightmap_ptr[i * width + j].pos = accum /= gauss_sum;
}
}
//vertical pass
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (lightmap_ptr[i * width + j].normal == Vector3())
continue; //empty, don't write over it anyway
float gauss_sum = gauss_kernel[0];
Vector3 accum = lightmap_ptr[i * width + j].pos * gauss_kernel[0];
for (int k = 1; k < 4; k++) {
int new_y = i + k;
if (new_y >= height || lightmap_ptr[new_y * width + j].normal == Vector3())
break;
gauss_sum += gauss_kernel[k];
accum += lightmap_ptr[new_y * width + j].pos * gauss_kernel[k];
}
for (int k = 1; k < 4; k++) {
int new_y = i - k;
if (new_y < 0 || lightmap_ptr[new_y * width + j].normal == Vector3())
break;
gauss_sum += gauss_kernel[k];
accum += lightmap_ptr[new_y * width + j].pos * gauss_kernel[k];
}
lightmap_ptr[i * width + j].light = accum /= gauss_sum;
}
}
}
//add directional light (do this after blur)
{
const Cell *cells = bake_cells.ptr();
const Light *light = bake_light.ptr();
#ifdef _OPENMP
#pragma omp parallel
#endif
for (int i = 0; i < height; i++) {
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic, 1)
#endif
for (int j = 0; j < width; j++) {
//if (i == 125 && j == 280) {
LightMap *pixel = &lightmap_ptr[i * width + j];
if (pixel->pos == Vector3())
continue; //unused, skipe
int x = int(pixel->pos.x) - 1;
int y = int(pixel->pos.y) - 1;
int z = int(pixel->pos.z) - 1;
Color accum;
int size = 1 << (cell_subdiv - 1);
int found = 0;
for (int k = 0; k < 8; k++) {
int ofs_x = x;
int ofs_y = y;
int ofs_z = z;
if (k & 1)
ofs_x++;
if (k & 2)
ofs_y++;
if (k & 4)
ofs_z++;
if (x < 0 || x >= size)
continue;
if (y < 0 || y >= size)
continue;
if (z < 0 || z >= size)
continue;
uint32_t cell = _find_cell_at_pos(cells, ofs_x, ofs_y, ofs_z);
if (cell == CHILD_EMPTY)
continue;
for (int l = 0; l < 6; l++) {
float s = pixel->normal.dot(aniso_normal[l]);
if (s < 0)
s = 0;
accum.r += light[cell].direct_accum[l][0] * s;
accum.g += light[cell].direct_accum[l][1] * s;
accum.b += light[cell].direct_accum[l][2] * s;
}
found++;
}
if (found) {
accum /= found;
pixel->light.x += accum.r;
pixel->light.y += accum.g;
pixel->light.z += accum.b;
}
}
}
}
{
//fill gaps with neighbour vertices to avoid filter fades to black on edges
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (lightmap_ptr[i * width + j].normal != Vector3()) {
continue; //filled, skip
}
//this can't be made separatable..
int closest_i = -1, closest_j = 1;
float closest_dist = 1e20;
const int margin = 3;
for (int y = i - margin; y <= i + margin; y++) {
for (int x = j - margin; x <= j + margin; x++) {
if (x == j && y == i)
continue;
if (x < 0 || x >= width)
continue;
if (y < 0 || y >= height)
continue;
if (lightmap_ptr[y * width + x].normal == Vector3())
continue; //also ensures that blitted stuff is not reused
float dist = Vector2(i - y, j - x).length();
if (dist > closest_dist)
continue;
closest_dist = dist;
closest_i = y;
closest_j = x;
}
}
if (closest_i != -1) {
lightmap_ptr[i * width + j].light = lightmap_ptr[closest_i * width + closest_j].light;
}
}
}
}
{
//fill the lightmap data
r_lightmap.width = width;
r_lightmap.height = height;
r_lightmap.light.resize(lightmap.size() * 3);
PoolVector<float>::Write w = r_lightmap.light.write();
for (int i = 0; i < lightmap.size(); i++) {
w[i * 3 + 0] = lightmap[i].light.x;
w[i * 3 + 1] = lightmap[i].light.y;
w[i * 3 + 2] = lightmap[i].light.z;
}
}
// Enable for debugging
#if 0
{
PoolVector<uint8_t> img;
int ls = lightmap.size();
img.resize(ls * 3);
{
PoolVector<uint8_t>::Write w = img.write();
for (int i = 0; i < ls; i++) {
w[i * 3 + 0] = CLAMP(lightmap_ptr[i].light.x * 255, 0, 255);
w[i * 3 + 1] = CLAMP(lightmap_ptr[i].light.y * 255, 0, 255);
w[i * 3 + 2] = CLAMP(lightmap_ptr[i].light.z * 255, 0, 255);
//w[i * 3 + 0] = CLAMP(lightmap_ptr[i].normal.x * 255, 0, 255);
//w[i * 3 + 1] = CLAMP(lightmap_ptr[i].normal.y * 255, 0, 255);
//w[i * 3 + 2] = CLAMP(lightmap_ptr[i].normal.z * 255, 0, 255);
//w[i * 3 + 0] = CLAMP(lightmap_ptr[i].pos.x / (1 << (cell_subdiv - 1)) * 255, 0, 255);
//w[i * 3 + 1] = CLAMP(lightmap_ptr[i].pos.y / (1 << (cell_subdiv - 1)) * 255, 0, 255);
//w[i * 3 + 2] = CLAMP(lightmap_ptr[i].pos.z / (1 << (cell_subdiv - 1)) * 255, 0, 255);
}
}
Ref<Image> image;
image.instance();
image->create(width, height, false, Image::FORMAT_RGB8, img);
String name = p_mesh->get_name();
if (name == "") {
name = "Mesh" + itos(p_mesh->get_instance_id());
}
image->save_png(name + ".png");
}
#endif
}
return OK;
}
void VoxelLightBaker::begin_bake(int p_subdiv, const AABB &p_bounds) {
original_bounds = p_bounds;
cell_subdiv = p_subdiv;
bake_cells.resize(1);
material_cache.clear();
//find out the actual real bounds, power of 2, which gets the highest subdivision
po2_bounds = p_bounds;
int longest_axis = po2_bounds.get_longest_axis_index();
axis_cell_size[longest_axis] = (1 << (cell_subdiv - 1));
leaf_voxel_count = 0;
for (int i = 0; i < 3; i++) {
if (i == longest_axis)
continue;
axis_cell_size[i] = axis_cell_size[longest_axis];
float axis_size = po2_bounds.size[longest_axis];
//shrink until fit subdiv
while (axis_size / 2.0 >= po2_bounds.size[i]) {
axis_size /= 2.0;
axis_cell_size[i] >>= 1;
}
po2_bounds.size[i] = po2_bounds.size[longest_axis];
}
Transform to_bounds;
to_bounds.basis.scale(Vector3(po2_bounds.size[longest_axis], po2_bounds.size[longest_axis], po2_bounds.size[longest_axis]));
to_bounds.origin = po2_bounds.position;
Transform to_grid;
to_grid.basis.scale(Vector3(axis_cell_size[longest_axis], axis_cell_size[longest_axis], axis_cell_size[longest_axis]));
to_cell_space = to_grid * to_bounds.affine_inverse();
cell_size = po2_bounds.size[longest_axis] / axis_cell_size[longest_axis];
}
void VoxelLightBaker::end_bake() {
_fixup_plot(0, 0);
}
//create the data for visual server
PoolVector<int> VoxelLightBaker::create_gi_probe_data() {
PoolVector<int> data;
data.resize(16 + (8 + 1 + 1 + 1 + 1) * bake_cells.size()); //4 for header, rest for rest.
{
PoolVector<int>::Write w = data.write();
uint32_t *w32 = (uint32_t *)w.ptr();
w32[0] = 0; //version
w32[1] = cell_subdiv; //subdiv
w32[2] = axis_cell_size[0];
w32[3] = axis_cell_size[1];
w32[4] = axis_cell_size[2];
w32[5] = bake_cells.size();
w32[6] = leaf_voxel_count;
int ofs = 16;
for (int i = 0; i < bake_cells.size(); i++) {
for (int j = 0; j < 8; j++) {
w32[ofs++] = bake_cells[i].children[j];
}
{ //albedo
uint32_t rgba = uint32_t(CLAMP(bake_cells[i].albedo[0] * 255.0, 0, 255)) << 16;
rgba |= uint32_t(CLAMP(bake_cells[i].albedo[1] * 255.0, 0, 255)) << 8;
rgba |= uint32_t(CLAMP(bake_cells[i].albedo[2] * 255.0, 0, 255)) << 0;
w32[ofs++] = rgba;
}
{ //emission
Vector3 e(bake_cells[i].emission[0], bake_cells[i].emission[1], bake_cells[i].emission[2]);
float l = e.length();
if (l > 0) {
e.normalize();
l = CLAMP(l / 8.0, 0, 1.0);
}
uint32_t em = uint32_t(CLAMP(e[0] * 255, 0, 255)) << 24;
em |= uint32_t(CLAMP(e[1] * 255, 0, 255)) << 16;
em |= uint32_t(CLAMP(e[2] * 255, 0, 255)) << 8;
em |= uint32_t(CLAMP(l * 255, 0, 255));
w32[ofs++] = em;
}
//w32[ofs++]=bake_cells[i].used_sides;
{ //normal
Vector3 n(bake_cells[i].normal[0], bake_cells[i].normal[1], bake_cells[i].normal[2]);
n = n * Vector3(0.5, 0.5, 0.5) + Vector3(0.5, 0.5, 0.5);
uint32_t norm = 0;
norm |= uint32_t(CLAMP(n.x * 255.0, 0, 255)) << 16;
norm |= uint32_t(CLAMP(n.y * 255.0, 0, 255)) << 8;
norm |= uint32_t(CLAMP(n.z * 255.0, 0, 255)) << 0;
w32[ofs++] = norm;
}
{
uint16_t alpha = MIN(uint32_t(bake_cells[i].alpha * 65535.0), 65535);
uint16_t level = bake_cells[i].level;
w32[ofs++] = (uint32_t(level) << 16) | uint32_t(alpha);
}
}
}
return data;
}
void VoxelLightBaker::_debug_mesh(int p_idx, int p_level, const AABB &p_aabb, Ref<MultiMesh> &p_multimesh, int &idx, DebugMode p_mode) {
if (p_level == cell_subdiv - 1) {
Vector3 center = p_aabb.position + p_aabb.size * 0.5;
Transform xform;
xform.origin = center;
xform.basis.scale(p_aabb.size * 0.5);
p_multimesh->set_instance_transform(idx, xform);
Color col;
if (p_mode == DEBUG_ALBEDO) {
col = Color(bake_cells[p_idx].albedo[0], bake_cells[p_idx].albedo[1], bake_cells[p_idx].albedo[2]);
} else if (p_mode == DEBUG_LIGHT) {
for (int i = 0; i < 6; i++) {
col.r += bake_light[p_idx].accum[i][0];
col.g += bake_light[p_idx].accum[i][1];
col.b += bake_light[p_idx].accum[i][2];
col.r += bake_light[p_idx].direct_accum[i][0];
col.g += bake_light[p_idx].direct_accum[i][1];
col.b += bake_light[p_idx].direct_accum[i][2];
}
}
//Color col = Color(bake_cells[p_idx].emission[0], bake_cells[p_idx].emission[1], bake_cells[p_idx].emission[2]);
p_multimesh->set_instance_color(idx, col);
idx++;
} else {
for (int i = 0; i < 8; i++) {
uint32_t child = bake_cells[p_idx].children[i];
if (child == CHILD_EMPTY || child >= (uint32_t)max_original_cells)
continue;
AABB aabb = p_aabb;
aabb.size *= 0.5;
if (i & 1)
aabb.position.x += aabb.size.x;
if (i & 2)
aabb.position.y += aabb.size.y;
if (i & 4)
aabb.position.z += aabb.size.z;
_debug_mesh(bake_cells[p_idx].children[i], p_level + 1, aabb, p_multimesh, idx, p_mode);
}
}
}
Ref<MultiMesh> VoxelLightBaker::create_debug_multimesh(DebugMode p_mode) {
Ref<MultiMesh> mm;
ERR_FAIL_COND_V(p_mode == DEBUG_LIGHT && bake_light.size() == 0, mm);
mm.instance();
mm->set_transform_format(MultiMesh::TRANSFORM_3D);
mm->set_color_format(MultiMesh::COLOR_8BIT);
mm->set_instance_count(leaf_voxel_count);
Ref<ArrayMesh> mesh;
mesh.instance();
{
Array arr;
arr.resize(Mesh::ARRAY_MAX);
PoolVector<Vector3> vertices;
PoolVector<Color> colors;
#define ADD_VTX(m_idx) \
; \
vertices.push_back(face_points[m_idx]); \
colors.push_back(Color(1, 1, 1, 1));
for (int i = 0; i < 6; i++) {
Vector3 face_points[4];
for (int j = 0; j < 4; j++) {
float v[3];
v[0] = 1.0;
v[1] = 1 - 2 * ((j >> 1) & 1);
v[2] = v[1] * (1 - 2 * (j & 1));
for (int k = 0; k < 3; k++) {
if (i < 3)
face_points[j][(i + k) % 3] = v[k];
else
face_points[3 - j][(i + k) % 3] = -v[k];
}
}
//tri 1
ADD_VTX(0);
ADD_VTX(1);
ADD_VTX(2);
//tri 2
ADD_VTX(2);
ADD_VTX(3);
ADD_VTX(0);
}
arr[Mesh::ARRAY_VERTEX] = vertices;
arr[Mesh::ARRAY_COLOR] = colors;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr);
}
{
Ref<SpatialMaterial> fsm;
fsm.instance();
fsm->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
fsm->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
fsm->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
fsm->set_albedo(Color(1, 1, 1, 1));
mesh->surface_set_material(0, fsm);
}
mm->set_mesh(mesh);
int idx = 0;
_debug_mesh(0, 0, po2_bounds, mm, idx, p_mode);
return mm;
}
struct VoxelLightBakerOctree {
enum {
CHILD_EMPTY = 0xFFFFFFFF
};
uint16_t light[6][3]; //anisotropic light
float alpha;
uint32_t children[8];
};
PoolVector<uint8_t> VoxelLightBaker::create_capture_octree(int p_subdiv) {
p_subdiv = MIN(p_subdiv, cell_subdiv); // use the smaller one
Vector<uint32_t> remap;
int bc = bake_cells.size();
remap.resize(bc);
Vector<uint32_t> demap;
int new_size = 0;
for (int i = 0; i < bc; i++) {
uint32_t c = CHILD_EMPTY;
if (bake_cells[i].level < p_subdiv) {
c = new_size;
new_size++;
demap.push_back(i);
}
remap.write[i] = c;
}
Vector<VoxelLightBakerOctree> octree;
octree.resize(new_size);
for (int i = 0; i < new_size; i++) {
octree.write[i].alpha = bake_cells[demap[i]].alpha;
for (int j = 0; j < 6; j++) {
for (int k = 0; k < 3; k++) {
float l = bake_light[demap[i]].accum[j][k]; //add anisotropic light
l += bake_cells[demap[i]].emission[k]; //add emission
octree.write[i].light[j][k] = CLAMP(l * 1024, 0, 65535); //give two more bits to octree
}
}
for (int j = 0; j < 8; j++) {
uint32_t child = bake_cells[demap[i]].children[j];
octree.write[i].children[j] = child == CHILD_EMPTY ? CHILD_EMPTY : remap[child];
}
}
PoolVector<uint8_t> ret;
int ret_bytes = octree.size() * sizeof(VoxelLightBakerOctree);
ret.resize(ret_bytes);
{
PoolVector<uint8_t>::Write w = ret.write();
copymem(w.ptr(), octree.ptr(), ret_bytes);
}
return ret;
}
float VoxelLightBaker::get_cell_size() const {
return cell_size;
}
Transform VoxelLightBaker::get_to_cell_space_xform() const {
return to_cell_space;
}
VoxelLightBaker::VoxelLightBaker() {
color_scan_cell_width = 4;
bake_texture_size = 128;
propagation = 0.85;
energy = 1.0;
}