godot/tools/editor/plugins/baked_light_editor_plugin.cpp

997 lines
20 KiB
C++

#include "baked_light_editor_plugin.h"
#include "scene/gui/box_container.h"
#include "scene/3d/mesh_instance.h"
#include "scene/3d/light.h"
class BakedLightBaker {
public:
enum {
ATTENUATION_CURVE_LEN=256
};
struct Octant {
bool leaf;
union {
struct {
float light_accum[3];
float surface_area;
Octant *next_leaf;
float offset[3];
};
Octant* children[8];
};
};
struct Triangle {
Vector3 vertices[3];
Vector2 uv[3];
};
struct BVH {
AABB aabb;
Vector3 center;
Triangle *leaf;
BVH*children[2];
};
struct BVHCmpX {
bool operator()(const BVH* p_left, const BVH* p_right) const {
return p_left->center.x < p_right->center.x;
}
};
struct BVHCmpY {
bool operator()(const BVH* p_left, const BVH* p_right) const {
return p_left->center.y < p_right->center.y;
}
};
struct BVHCmpZ {
bool operator()(const BVH* p_left, const BVH* p_right) const {
return p_left->center.z < p_right->center.z;
}
};
struct DirLight {
Vector3 pos;
Vector3 up;
Vector3 left;
Vector3 dir;
Color diffuse;
Color specular;
float energy;
float length;
int rays_thrown;
};
AABB octree_aabb;
Octant *octree;
BVH*bvh;
Vector<Triangle> triangles;
Transform base_inv;
Octant *leaf_list;
int octree_depth;
int cell_count;
uint32_t *ray_stack;
BVH **bvh_stack;
float cell_size;
float plot_size; //multiplied by cell size
Vector<DirLight> directional_lights;
int max_bounces;
void _add_mesh(const Ref<Mesh>& p_mesh,const Ref<Material>& p_mat_override,const Transform& p_xform);
void _parse_geometry(Node* p_node);
BVH* _parse_bvh(BVH** p_children,int p_size,int p_depth,int& max_depth);
void _make_bvh();
void _make_octree();
void _octree_insert(const AABB& p_aabb,Octant *p_octant,Triangle* p_triangle, int p_depth);
void _free_octree(Octant *p_octant) {
if (!p_octant->leaf) {
for(int i=0;i<8;i++) {
if (p_octant->children[i])
_free_octree(p_octant->children[i]);
}
}
memdelete(p_octant);
}
void _free_bvh(BVH* p_bvh) {
if (!p_bvh->leaf) {
if (p_bvh->children[0])
_free_bvh(p_bvh->children[0]);
if (p_bvh->children[1])
_free_bvh(p_bvh->children[1]);
}
memdelete(p_bvh);
}
void _fix_lights();
void _plot_light(const Vector3& p_plot_pos,const AABB& p_plot_aabb, Octant *p_octant, const AABB& p_aabb,const Color& p_light);
void _throw_ray(const Vector3& p_from, const Vector3& p_to,const Color& p_light,float *p_att_curve,float p_att_curve_len,int p_bounces);
void throw_rays(int p_amount);
float get_normalization() const;
void bake(Node *p_base);
void clear() {
if (octree)
_free_octree(octree);
if (bvh)
_free_bvh(bvh);
if (ray_stack)
memdelete_arr(ray_stack);
if (bvh_stack)
memdelete_arr(bvh_stack);
octree=NULL;
bvh=NULL;
leaf_list=NULL;
cell_count=0;
ray_stack=NULL;
bvh_stack=NULL;
}
BakedLightBaker() {
octree_depth=8;
octree=NULL;
bvh=NULL;
leaf_list=NULL;
cell_count=0;
ray_stack=NULL;
bvh_stack=NULL;
plot_size=2;
max_bounces=3;
}
~BakedLightBaker() {
clear();
}
};
void BakedLightBaker::_add_mesh(const Ref<Mesh>& p_mesh,const Ref<Material>& p_mat_override,const Transform& p_xform) {
for(int i=0;i<p_mesh->get_surface_count();i++) {
if (p_mesh->surface_get_primitive_type(i)!=Mesh::PRIMITIVE_TRIANGLES)
continue;
Ref<Material> mat = p_mat_override.is_valid()?p_mat_override:p_mesh->surface_get_material(i);
int facecount=0;
if (p_mesh->surface_get_format(i)&Mesh::ARRAY_FORMAT_INDEX) {
facecount=p_mesh->surface_get_array_index_len(i);
} else {
facecount=p_mesh->surface_get_array_len(i);
}
ERR_CONTINUE((facecount==0 || (facecount%3)!=0));
facecount/=3;
int tbase=triangles.size();
triangles.resize(facecount+tbase);
Array a = p_mesh->surface_get_arrays(i);
DVector<Vector3> vertices = a[Mesh::ARRAY_VERTEX];
DVector<Vector3>::Read vr=vertices.read();
if (p_mesh->surface_get_format(i)&Mesh::ARRAY_FORMAT_INDEX) {
DVector<int> indices = a[Mesh::ARRAY_INDEX];
DVector<int>::Read ir = indices.read();
for(int i=0;i<facecount;i++) {
Triangle &t=triangles[tbase+i];
t.vertices[0]=p_xform.xform(vr[ ir[i*3+0] ]);
t.vertices[1]=p_xform.xform(vr[ ir[i*3+1] ]);
t.vertices[2]=p_xform.xform(vr[ ir[i*3+2] ]);
}
} else {
for(int i=0;i<facecount;i++) {
Triangle &t=triangles[tbase+i];
t.vertices[0]=p_xform.xform(vr[ i*3+0 ]);
t.vertices[1]=p_xform.xform(vr[ i*3+1 ]);
t.vertices[2]=p_xform.xform(vr[ i*3+2 ]);
}
}
}
}
void BakedLightBaker::_parse_geometry(Node* p_node) {
if (p_node->cast_to<MeshInstance>()) {
MeshInstance *meshi=p_node->cast_to<MeshInstance>();
Ref<Mesh> mesh=meshi->get_mesh();
if (mesh.is_valid()) {
_add_mesh(mesh,meshi->get_material_override(),base_inv * meshi->get_global_transform());
}
}
if (p_node->cast_to<DirectionalLight>()) {
DirectionalLight *dl=p_node->cast_to<DirectionalLight>();
DirLight dirl;
dirl.diffuse=dl->get_color(DirectionalLight::COLOR_DIFFUSE);
dirl.specular=dl->get_color(DirectionalLight::COLOR_SPECULAR);
dirl.energy=dl->get_parameter(DirectionalLight::PARAM_ENERGY);
dirl.pos=dl->get_global_transform().origin;
dirl.up=dl->get_global_transform().basis.get_axis(1).normalized();
dirl.left=dl->get_global_transform().basis.get_axis(0).normalized();
dirl.dir=-dl->get_global_transform().basis.get_axis(2).normalized();
dirl.rays_thrown=0;
directional_lights.push_back(dirl);
}
for(int i=0;i<p_node->get_child_count();i++) {
_parse_geometry(p_node->get_child(i));
}
}
void BakedLightBaker::_fix_lights() {
for(int i=0;i<directional_lights.size();i++) {
DirLight &dl=directional_lights[i];
float up_max=-1e10;
float dir_max=-1e10;
float left_max=-1e10;
float up_min=1e10;
float dir_min=1e10;
float left_min=1e10;
for(int j=0;j<triangles.size();j++) {
for(int k=0;k<3;k++) {
Vector3 v = triangles[j].vertices[j];
float up_d = dl.up.dot(v);
float dir_d = dl.dir.dot(v);
float left_d = dl.left.dot(v);
if (up_d>up_max)
up_max=up_d;
if (up_d<up_min)
up_min=up_d;
if (left_d>left_max)
left_max=left_d;
if (left_d<left_min)
left_min=left_d;
if (dir_d>dir_max)
dir_max=dir_d;
if (dir_d<dir_min)
dir_min=dir_d;
}
}
//make a center point, then the upvector and leftvector
dl.pos = dl.left*( left_max+left_min )*0.5 + dl.up*( up_max+up_min )*0.5 + dl.dir*(dir_min-(dir_max-dir_min));
dl.left*=(left_max-left_min)*0.5;
dl.up*=(up_max-up_min)*0.5;
dl.length = (dir_max - dir_min)*10; //arbitrary number to keep it in scale
}
}
BakedLightBaker::BVH* BakedLightBaker::_parse_bvh(BVH** p_children, int p_size, int p_depth, int &max_depth) {
if (p_depth>max_depth) {
max_depth=p_depth;
}
if (p_size==1) {
return p_children[0];
} else if (p_size==0) {
return NULL;
}
AABB aabb;
aabb=p_children[0]->aabb;
for(int i=1;i<p_size;i++) {
aabb.merge_with(p_children[i]->aabb);
}
int li=aabb.get_longest_axis_index();
switch(li) {
case Vector3::AXIS_X: {
SortArray<BVH*,BVHCmpX> sort_x;
sort_x.nth_element(0,p_size,p_size/2,p_children);
//sort_x.sort(&p_bb[p_from],p_size);
} break;
case Vector3::AXIS_Y: {
SortArray<BVH*,BVHCmpY> sort_y;
sort_y.nth_element(0,p_size,p_size/2,p_children);
//sort_y.sort(&p_bb[p_from],p_size);
} break;
case Vector3::AXIS_Z: {
SortArray<BVH*,BVHCmpZ> sort_z;
sort_z.nth_element(0,p_size,p_size/2,p_children);
//sort_z.sort(&p_bb[p_from],p_size);
} break;
}
BVH* left = _parse_bvh(p_children,p_size/2,p_depth+1,max_depth);
BVH* right = _parse_bvh(&p_children[p_size/2],p_size-p_size/2,p_depth+1,max_depth);
BVH *_new = memnew(BVH);
_new->aabb=aabb;
_new->center=aabb.pos+aabb.size*0.5;
_new->children[0]=left;
_new->children[1]=right;
_new->leaf=NULL;
return _new;
}
void BakedLightBaker::_make_bvh() {
Vector<BVH*> bases;
bases.resize(triangles.size());
int max_depth=0;
for(int i=0;i<triangles.size();i++) {
bases[i]=memnew( BVH );
bases[i]->leaf=&triangles[i];
bases[i]->aabb.pos=triangles[i].vertices[0];
bases[i]->aabb.expand_to(triangles[i].vertices[1]);
bases[i]->aabb.expand_to(triangles[i].vertices[2]);
bases[i]->center=bases[i]->aabb.pos+bases[i]->aabb.size*0.5;
}
bvh=_parse_bvh(bases.ptr(),bases.size(),1,max_depth);
ray_stack = memnew_arr(uint32_t,max_depth);
bvh_stack = memnew_arr(BVH*,max_depth);
}
void BakedLightBaker::_octree_insert(const AABB& p_aabb,Octant *p_octant,Triangle* p_triangle, int p_depth) {
if (p_octant->leaf) {
if (p_aabb.has_point(p_triangle->vertices[0]) && p_aabb.has_point(p_triangle->vertices[1]) &&p_aabb.has_point(p_triangle->vertices[2])) {
//face is completely enclosed, add area
p_octant->surface_area+=Face3(p_triangle->vertices[0],p_triangle->vertices[1],p_triangle->vertices[2]).get_area();
} else {
//not completely enclosed, will need to be clipped..
Vector<Vector3> poly;
poly.push_back(p_triangle->vertices[0]);
poly.push_back(p_triangle->vertices[1]);
poly.push_back(p_triangle->vertices[2]);
//clip
for(int i=0;i<3;i++) {
//top plane
Plane p(0,0,0,0);
p.normal[i]=1.0;
p.d=p_aabb.pos[i]+p_aabb.size[i];
poly=Geometry::clip_polygon(poly,p);
//bottom plane
p.normal[i]=-1.0;
p.d=-p_aabb.pos[i];
poly=Geometry::clip_polygon(poly,p);
}
//calculate area
for(int i=2;i<poly.size();i++) {
p_octant->surface_area+=Face3(poly[0],poly[i-1],poly[i]).get_area();
}
}
} else {
for(int i=0;i<8;i++) {
AABB aabb=p_aabb;
aabb.size*=0.5;
if (i&1)
aabb.pos.x+=aabb.size.x;
if (i&2)
aabb.pos.y+=aabb.size.y;
if (i&4)
aabb.pos.z+=aabb.size.z;
AABB fit_aabb=aabb;
//fit_aabb=fit_aabb.grow(bvh->aabb.size.x*0.0001);
if (!Face3(p_triangle->vertices[0],p_triangle->vertices[1],p_triangle->vertices[2]).intersects_aabb(fit_aabb))
continue;
if (!p_octant->children[i]) {
p_octant->children[i]=memnew(Octant);
if (p_depth==0) {
p_octant->children[i]->leaf=true;
p_octant->children[i]->light_accum[0]=0;
p_octant->children[i]->light_accum[1]=0;
p_octant->children[i]->light_accum[2]=0;
p_octant->children[i]->offset[0]=aabb.pos.x+aabb.size.x*0.5;
p_octant->children[i]->offset[1]=aabb.pos.y+aabb.size.y*0.5;
p_octant->children[i]->offset[2]=aabb.pos.z+aabb.size.z*0.5;
p_octant->children[i]->surface_area=0;
p_octant->children[i]->next_leaf=leaf_list;
leaf_list=p_octant->children[i];
cell_count++;
} else {
p_octant->children[i]->leaf=false;
for(int j=0;j<8;j++) {
p_octant->children[i]->children[j]=0;
}
}
}
_octree_insert(aabb,p_octant->children[i],p_triangle,p_depth-1);
}
}
}
void BakedLightBaker::_make_octree() {
AABB base = bvh->aabb;
float lal=base.get_longest_axis_size();
//must be square because we want square blocks
base.size.x=lal;
base.size.y=lal;
base.size.z=lal;
base.grow_by(lal*0.001); //for precision
octree_aabb=base;
cell_size=base.size.x;
for(int i=0;i<=octree_depth;i++)
cell_size/=2.0;
octree = memnew( Octant );
octree->leaf=false;
for(int i=0;i<8;i++)
octree->children[i]=NULL;
for(int i=0;i<triangles.size();i++) {
_octree_insert(octree_aabb,octree,&triangles[i],octree_depth-1);
}
}
void BakedLightBaker::_plot_light(const Vector3& p_plot_pos,const AABB& p_plot_aabb, Octant *p_octant, const AABB& p_aabb,const Color& p_light) {
if (p_octant->leaf) {
float r=cell_size*plot_size;
Vector3 center=p_aabb.pos+p_aabb.size*0.5;
float d = p_plot_pos.distance_to(center);
if (d>r)
return; //oh crap! outside radius
float intensity = 1.0 - (d/r)*(d/r); //not gauss but..
p_octant->light_accum[0]+=p_light.r*intensity;
p_octant->light_accum[1]+=p_light.g*intensity;
p_octant->light_accum[2]+=p_light.b*intensity;
} else {
for(int i=0;i<8;i++) {
if (!p_octant->children[i])
continue;
AABB aabb=p_aabb;
aabb.size*=0.5;
if (i&1)
aabb.pos.x+=aabb.size.x;
if (i&2)
aabb.pos.y+=aabb.size.y;
if (i&4)
aabb.pos.z+=aabb.size.z;
if (!aabb.intersects(p_plot_aabb))
continue;
_plot_light(p_plot_pos,p_plot_aabb,p_octant->children[i],aabb,p_light);
}
}
}
void BakedLightBaker::_throw_ray(const Vector3& p_begin, const Vector3& p_end,const Color& p_light,float *p_att_curve,float p_att_curve_len,int p_bounces) {
uint32_t* stack = ray_stack;
BVH **bstack = bvh_stack;
enum {
TEST_AABB_BIT=0,
VISIT_LEFT_BIT=1,
VISIT_RIGHT_BIT=2,
VISIT_DONE_BIT=3,
};
Vector3 n = (p_end-p_begin).normalized();
real_t d=1e10;
bool inters=false;
Vector3 r_normal;
Vector3 r_point;
//for(int i=0;i<max_depth;i++)
// stack[i]=0;
int level=0;
//AABB ray_aabb;
//ray_aabb.pos=p_begin;
//ray_aabb.expand_to(p_end);
const BVH *bvhptr = bvh;
bstack[0]=bvh;
stack[0]=TEST_AABB_BIT;
while(true) {
uint32_t mode = stack[level];
const BVH &b = *bstack[level];
bool done=false;
switch(mode) {
case TEST_AABB_BIT: {
if (b.leaf) {
Face3 f3(b.leaf->vertices[0],b.leaf->vertices[1],b.leaf->vertices[2]);
Vector3 res;
if (f3.intersects_segment(p_begin,p_end,&res)) {
float nd = n.dot(res);
if (nd<d) {
d=nd;
r_point=res;
r_normal=f3.get_plane().get_normal();
inters=true;
}
}
stack[level]=VISIT_DONE_BIT;
} else {
bool valid = b.aabb.intersects_segment(p_begin,p_end);
// bool valid = b.aabb.intersects(ray_aabb);
if (!valid) {
stack[level]=VISIT_DONE_BIT;
} else {
stack[level]=VISIT_LEFT_BIT;
}
}
} continue;
case VISIT_LEFT_BIT: {
stack[level]=VISIT_RIGHT_BIT;
bstack[level+1]=b.children[0];
stack[level+1]=TEST_AABB_BIT;
level++;
} continue;
case VISIT_RIGHT_BIT: {
stack[level]=VISIT_DONE_BIT;
bstack[level+1]=b.children[1];
stack[level+1]=TEST_AABB_BIT;
level++;
} continue;
case VISIT_DONE_BIT: {
if (level==0) {
done=true;
break;
} else
level--;
} continue;
}
if (done)
break;
}
if (inters) {
//print_line("collision!");
if (n.dot(r_normal)>0)
r_normal=-r_normal;
//ok...
Color diffuse_at_point(0.8,0.8,0.8);
Color specular_at_point(0.8,0.8,0.8);
AABB aabb;
aabb.pos=r_point;
aabb.pos-=Vector3(1,1,1)*cell_size*plot_size;
aabb.size=Vector3(2,2,2)*cell_size*plot_size;
_plot_light(r_point,aabb,octree,octree_aabb,p_light);
}
}
float BakedLightBaker::get_normalization() const {
float nrg=0;
for(int i=0;i<directional_lights.size();i++) {
const DirLight &dl=directional_lights[i];
float total_area = dl.left.length()*2*dl.up.length()*2;
float cell_area = cell_size*cell_size;;
nrg+= dl.energy * (dl.rays_thrown * cell_area / total_area);
nrg*=5;
}
return nrg;
}
void BakedLightBaker::throw_rays(int p_amount) {
for(int i=0;i<directional_lights.size();i++) {
DirLight &dl=directional_lights[i];
float sr = Math::sqrt(p_amount);
float aspect = dl.up.length()/dl.left.length();
for(int j=0;j<p_amount;j++) {
Vector3 from = dl.pos;
from+=dl.up*(Math::randf()*2.0-1.0);
from+=dl.left*(Math::randf()*2.0-1.0);
Vector3 to = from+dl.dir*dl.length;
Color col=dl.diffuse;
col.r*=dl.energy;
col.g*=dl.energy;
col.b*=dl.energy;
dl.rays_thrown++;
_throw_ray(from,to,col,NULL,0,max_bounces);
}
}
}
void BakedLightBaker::bake(Node* p_node) {
cell_count=0;
_parse_geometry(p_node);
_fix_lights();
_make_bvh();
_make_octree();
}
void BakedLightEditor::_node_removed(Node *p_node) {
if(p_node==node) {
node=NULL;
p_node->remove_child(preview);
preview->set_mesh(Ref<Mesh>());
hide();
}
}
void BakedLightEditor::_menu_option(int p_option) {
switch(p_option) {
case MENU_OPTION_BAKE: {
ERR_FAIL_COND(!node);
preview->set_mesh(Ref<Mesh>());
baker->base_inv=node->get_global_transform().affine_inverse();
baker->bake(node);
baker->throw_rays(100000);
float norm = baker->get_normalization();
float max_lum=0;
print_line("CELLS: "+itos(baker->cell_count));
DVector<Color> colors;
DVector<Vector3> vertices;
colors.resize(baker->cell_count*36);
vertices.resize(baker->cell_count*36);
{
DVector<Color>::Write cw=colors.write();
DVector<Vector3>::Write vw=vertices.write();
BakedLightBaker::Octant *oct = baker->leaf_list;
int vert_idx=0;
while(oct) {
Color color;
color.r=oct->light_accum[0]/norm;
color.g=oct->light_accum[1]/norm;
color.b=oct->light_accum[2]/norm;
float lum = color.get_v();
if (lum>max_lum)
max_lum=lum;
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]*(i>=3?-1:1);
else
face_points[3-j][(i+k)%3]=v[k]*(i>=3?-1:1);
}
}
for(int j=0;j<4;j++) {
face_points[j]*=baker->cell_size;
face_points[j]+=Vector3(oct->offset[0],oct->offset[1],oct->offset[2]);
}
#define ADD_VTX(m_idx) \
vw[vert_idx]=face_points[m_idx]; \
cw[vert_idx]=color; \
vert_idx++;
//tri 1
ADD_VTX(0);
ADD_VTX(1);
ADD_VTX(2);
//tri 2
ADD_VTX(2);
ADD_VTX(3);
ADD_VTX(0);
#undef ADD_VTX
}
oct=oct->next_leaf;
}
}
print_line("max lum: "+rtos(max_lum));
Array a;
a.resize(Mesh::ARRAY_MAX);
a[Mesh::ARRAY_VERTEX]=vertices;
a[Mesh::ARRAY_COLOR]=colors;
Ref<FixedMaterial> matcol = memnew( FixedMaterial );
matcol->set_fixed_flag(FixedMaterial::FLAG_USE_COLOR_ARRAY,true);
matcol->set_fixed_flag(FixedMaterial::FLAG_USE_ALPHA,true);
matcol->set_flag(FixedMaterial::FLAG_UNSHADED,true);
matcol->set_flag(FixedMaterial::FLAG_DOUBLE_SIDED,true);
matcol->set_parameter(FixedMaterial::PARAM_DIFFUSE,Color(1,1,1));
Ref<Mesh> m = memnew( Mesh );
m->add_surface(Mesh::PRIMITIVE_TRIANGLES,a);
m->surface_set_material(0,matcol);
preview->set_mesh(m);
} break;
case MENU_OPTION_CLEAR: {
} break;
}
}
void BakedLightEditor::edit(BakedLight *p_baked_light) {
if (node==p_baked_light)
return;
if (node) {
node->remove_child(preview);
}
node=p_baked_light;
if (node)
node->add_child(preview);
}
void BakedLightEditor::_bind_methods() {
ObjectTypeDB::bind_method("_menu_option",&BakedLightEditor::_menu_option);
}
BakedLightEditor::BakedLightEditor() {
options = memnew( MenuButton );
options->set_text("BakedLight");
options->get_popup()->add_item("Bake..",MENU_OPTION_BAKE);
options->get_popup()->add_item("Clear",MENU_OPTION_CLEAR);
options->get_popup()->connect("item_pressed", this,"_menu_option");
err_dialog = memnew( AcceptDialog );
add_child(err_dialog);
node=NULL;
baker = memnew( BakedLightBaker );
preview = memnew( MeshInstance );
}
BakedLightEditor::~BakedLightEditor() {
memdelete(baker);
}
void BakedLightEditorPlugin::edit(Object *p_object) {
baked_light_editor->edit(p_object->cast_to<BakedLight>());
}
bool BakedLightEditorPlugin::handles(Object *p_object) const {
return p_object->is_type("BakedLight");
}
void BakedLightEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
baked_light_editor->show();
baked_light_editor->options->show();
} else {
baked_light_editor->hide();
baked_light_editor->options->show();
baked_light_editor->edit(NULL);
if (baked_light_editor->node) {
baked_light_editor->node->remove_child(baked_light_editor->preview);
baked_light_editor->node=NULL;
}
}
}
BakedLightEditorPlugin::BakedLightEditorPlugin(EditorNode *p_node) {
editor=p_node;
baked_light_editor = memnew( BakedLightEditor );
editor->get_viewport()->add_child(baked_light_editor);
add_custom_control(CONTAINER_SPATIAL_EDITOR_MENU,baked_light_editor->options);
baked_light_editor->hide();
baked_light_editor->options->hide();
}
BakedLightEditorPlugin::~BakedLightEditorPlugin()
{
}