feat: godot-engine-source-4.3-stable
This commit is contained in:
parent
c59a7dcade
commit
7125d019b5
11149 changed files with 5070401 additions and 0 deletions
13
engine/modules/lightmapper_rd/SCsub
Normal file
13
engine/modules/lightmapper_rd/SCsub
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_lightmapper_rd = env_modules.Clone()
|
||||
env_lightmapper_rd.GLSL_HEADER("lm_raster.glsl")
|
||||
env_lightmapper_rd.GLSL_HEADER("lm_compute.glsl")
|
||||
env_lightmapper_rd.GLSL_HEADER("lm_blendseams.glsl")
|
||||
env_lightmapper_rd.Depends(Glob("*.glsl.gen.h"), ["lm_common_inc.glsl", "#glsl_builders.py"])
|
||||
|
||||
# Godot source files
|
||||
env_lightmapper_rd.add_source_files(env.modules_sources, "*.cpp")
|
||||
6
engine/modules/lightmapper_rd/config.py
Normal file
6
engine/modules/lightmapper_rd/config.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def can_build(env, platform):
|
||||
return env.editor_build and platform not in ["android", "ios"]
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
2079
engine/modules/lightmapper_rd/lightmapper_rd.cpp
Normal file
2079
engine/modules/lightmapper_rd/lightmapper_rd.cpp
Normal file
File diff suppressed because it is too large
Load diff
302
engine/modules/lightmapper_rd/lightmapper_rd.h
Normal file
302
engine/modules/lightmapper_rd/lightmapper_rd.h
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
/**************************************************************************/
|
||||
/* lightmapper_rd.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef LIGHTMAPPER_RD_H
|
||||
#define LIGHTMAPPER_RD_H
|
||||
|
||||
#include "core/templates/local_vector.h"
|
||||
#include "scene/3d/lightmapper.h"
|
||||
#include "scene/resources/mesh.h"
|
||||
#include "servers/rendering/rendering_device.h"
|
||||
|
||||
class RDShaderFile;
|
||||
class LightmapperRD : public Lightmapper {
|
||||
GDCLASS(LightmapperRD, Lightmapper)
|
||||
|
||||
struct BakeParameters {
|
||||
float world_size[3] = {};
|
||||
float bias = 0.0;
|
||||
|
||||
float to_cell_offset[3] = {};
|
||||
int32_t grid_size = 0;
|
||||
|
||||
float to_cell_size[3] = {};
|
||||
uint32_t light_count = 0;
|
||||
|
||||
float env_transform[12] = {};
|
||||
|
||||
int32_t atlas_size[2] = {};
|
||||
float exposure_normalization = 0.0f;
|
||||
uint32_t bounces = 0;
|
||||
|
||||
float bounce_indirect_energy = 0.0f;
|
||||
uint32_t pad[3] = {};
|
||||
};
|
||||
|
||||
struct MeshInstance {
|
||||
MeshData data;
|
||||
int slice = 0;
|
||||
Vector2i offset;
|
||||
};
|
||||
|
||||
struct Light {
|
||||
float position[3] = {};
|
||||
uint32_t type = LIGHT_TYPE_DIRECTIONAL;
|
||||
float direction[3] = {};
|
||||
float energy = 0.0;
|
||||
float color[3] = {};
|
||||
float size = 0.0;
|
||||
float range = 0.0;
|
||||
float attenuation = 0.0;
|
||||
float cos_spot_angle = 0.0;
|
||||
float inv_spot_attenuation = 0.0;
|
||||
float indirect_energy = 0.0;
|
||||
float shadow_blur = 0.0;
|
||||
uint32_t static_bake = 0;
|
||||
uint32_t pad = 0;
|
||||
|
||||
bool operator<(const Light &p_light) const {
|
||||
return type < p_light.type;
|
||||
}
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
float position[3] = {};
|
||||
float normal_z = 0.0;
|
||||
float uv[2] = {};
|
||||
float normal_xy[2] = {};
|
||||
|
||||
bool operator==(const Vertex &p_vtx) const {
|
||||
return (position[0] == p_vtx.position[0]) &&
|
||||
(position[1] == p_vtx.position[1]) &&
|
||||
(position[2] == p_vtx.position[2]) &&
|
||||
(uv[0] == p_vtx.uv[0]) &&
|
||||
(uv[1] == p_vtx.uv[1]) &&
|
||||
(normal_xy[0] == p_vtx.normal_xy[0]) &&
|
||||
(normal_xy[1] == p_vtx.normal_xy[1]) &&
|
||||
(normal_z == p_vtx.normal_z);
|
||||
}
|
||||
};
|
||||
|
||||
struct Edge {
|
||||
Vector3 a;
|
||||
Vector3 b;
|
||||
Vector3 na;
|
||||
Vector3 nb;
|
||||
bool operator==(const Edge &p_seam) const {
|
||||
return a == p_seam.a && b == p_seam.b && na == p_seam.na && nb == p_seam.nb;
|
||||
}
|
||||
Edge() {
|
||||
}
|
||||
|
||||
Edge(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_na, const Vector3 &p_nb) {
|
||||
a = p_a;
|
||||
b = p_b;
|
||||
na = p_na;
|
||||
nb = p_nb;
|
||||
}
|
||||
};
|
||||
|
||||
struct Probe {
|
||||
float position[4] = {};
|
||||
};
|
||||
|
||||
Vector<Probe> probe_positions;
|
||||
|
||||
struct EdgeHash {
|
||||
_FORCE_INLINE_ static uint32_t hash(const Edge &p_edge) {
|
||||
uint32_t h = hash_murmur3_one_float(p_edge.a.x);
|
||||
h = hash_murmur3_one_float(p_edge.a.y, h);
|
||||
h = hash_murmur3_one_float(p_edge.a.z, h);
|
||||
h = hash_murmur3_one_float(p_edge.b.x, h);
|
||||
h = hash_murmur3_one_float(p_edge.b.y, h);
|
||||
h = hash_murmur3_one_float(p_edge.b.z, h);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
struct EdgeUV2 {
|
||||
Vector2 a;
|
||||
Vector2 b;
|
||||
Vector2i indices;
|
||||
bool operator==(const EdgeUV2 &p_uv2) const {
|
||||
return a == p_uv2.a && b == p_uv2.b;
|
||||
}
|
||||
bool seam_found = false;
|
||||
EdgeUV2(Vector2 p_a, Vector2 p_b, Vector2i p_indices) {
|
||||
a = p_a;
|
||||
b = p_b;
|
||||
indices = p_indices;
|
||||
}
|
||||
EdgeUV2() {}
|
||||
};
|
||||
|
||||
struct Seam {
|
||||
Vector2i a;
|
||||
Vector2i b;
|
||||
uint32_t slice;
|
||||
bool operator<(const Seam &p_seam) const {
|
||||
return slice < p_seam.slice;
|
||||
}
|
||||
};
|
||||
|
||||
struct VertexHash {
|
||||
_FORCE_INLINE_ static uint32_t hash(const Vertex &p_vtx) {
|
||||
uint32_t h = hash_murmur3_one_float(p_vtx.position[0]);
|
||||
h = hash_murmur3_one_float(p_vtx.position[1], h);
|
||||
h = hash_murmur3_one_float(p_vtx.position[2], h);
|
||||
h = hash_murmur3_one_float(p_vtx.uv[0], h);
|
||||
h = hash_murmur3_one_float(p_vtx.uv[1], h);
|
||||
h = hash_murmur3_one_float(p_vtx.normal_xy[0], h);
|
||||
h = hash_murmur3_one_float(p_vtx.normal_xy[1], h);
|
||||
h = hash_murmur3_one_float(p_vtx.normal_z, h);
|
||||
return hash_fmix32(h);
|
||||
}
|
||||
};
|
||||
|
||||
struct Triangle {
|
||||
uint32_t indices[3] = {};
|
||||
uint32_t slice = 0;
|
||||
float min_bounds[3] = {};
|
||||
float pad0 = 0.0;
|
||||
float max_bounds[3] = {};
|
||||
float pad1 = 0.0;
|
||||
bool operator<(const Triangle &p_triangle) const {
|
||||
return slice < p_triangle.slice;
|
||||
}
|
||||
};
|
||||
|
||||
struct ClusterAABB {
|
||||
float min_bounds[3];
|
||||
float pad0 = 0.0f;
|
||||
float max_bounds[3];
|
||||
float pad1 = 0.0f;
|
||||
};
|
||||
|
||||
Vector<MeshInstance> mesh_instances;
|
||||
|
||||
Vector<Light> lights;
|
||||
|
||||
struct TriangleSort {
|
||||
uint32_t cell_index = 0;
|
||||
uint32_t triangle_index = 0;
|
||||
AABB triangle_aabb;
|
||||
|
||||
bool operator<(const TriangleSort &p_triangle_sort) const {
|
||||
return cell_index < p_triangle_sort.cell_index; //sorting by triangle index in this case makes no sense
|
||||
}
|
||||
};
|
||||
|
||||
template <int T>
|
||||
struct TriangleSortAxis {
|
||||
bool operator()(const TriangleSort &p_a, const TriangleSort &p_b) const {
|
||||
return p_a.triangle_aabb.get_center()[T] < p_b.triangle_aabb.get_center()[T];
|
||||
}
|
||||
};
|
||||
|
||||
void _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);
|
||||
void _sort_triangle_clusters(uint32_t p_cluster_size, uint32_t p_cluster_index, uint32_t p_index_start, uint32_t p_count, LocalVector<TriangleSort> &p_triangle_sort, LocalVector<ClusterAABB> &p_cluster_aabb);
|
||||
|
||||
struct RasterPushConstant {
|
||||
float atlas_size[2] = {};
|
||||
float uv_offset[2] = {};
|
||||
float to_cell_size[3] = {};
|
||||
uint32_t base_triangle = 0;
|
||||
float to_cell_offset[3] = {};
|
||||
float bias = 0.0;
|
||||
int32_t grid_size[3] = {};
|
||||
uint32_t pad2 = 0;
|
||||
};
|
||||
|
||||
struct RasterSeamsPushConstant {
|
||||
uint32_t base_index = 0;
|
||||
uint32_t slice = 0;
|
||||
float uv_offset[2] = {};
|
||||
uint32_t debug = 0;
|
||||
float blend = 0.0;
|
||||
uint32_t pad[2] = {};
|
||||
};
|
||||
|
||||
struct PushConstant {
|
||||
uint32_t atlas_slice = 0;
|
||||
uint32_t ray_count = 0;
|
||||
uint32_t ray_from = 0;
|
||||
uint32_t ray_to = 0;
|
||||
uint32_t region_ofs[2] = {};
|
||||
uint32_t probe_count = 0;
|
||||
uint32_t pad = 0;
|
||||
};
|
||||
|
||||
Vector<Ref<Image>> bake_textures;
|
||||
Vector<Color> probe_values;
|
||||
|
||||
struct DenoiseParams {
|
||||
float spatial_bandwidth;
|
||||
float light_bandwidth;
|
||||
float albedo_bandwidth;
|
||||
float normal_bandwidth;
|
||||
|
||||
int half_search_window;
|
||||
float filter_strength;
|
||||
float pad[2];
|
||||
};
|
||||
|
||||
BakeError _blit_meshes_into_atlas(int p_max_texture_size, int p_denoiser_range, 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);
|
||||
void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, uint32_t p_cluster_size, Vector<Probe> &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 &r_triangle_indices_buffer, RID &r_cluster_indices_buffer, RID &r_cluster_aabbs_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata);
|
||||
void _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);
|
||||
|
||||
BakeError _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);
|
||||
BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function);
|
||||
|
||||
Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name);
|
||||
Ref<Image> _read_pfm(const String &p_name);
|
||||
BakeError _denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe);
|
||||
|
||||
public:
|
||||
virtual void add_mesh(const MeshData &p_mesh) override;
|
||||
virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) override;
|
||||
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override;
|
||||
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override;
|
||||
virtual void add_probe(const Vector3 &p_position) override;
|
||||
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override;
|
||||
|
||||
int get_bake_texture_count() const override;
|
||||
Ref<Image> get_bake_texture(int p_index) const override;
|
||||
int get_bake_mesh_count() const override;
|
||||
Variant get_bake_mesh_userdata(int p_index) const override;
|
||||
Rect2 get_bake_mesh_uv_scale(int p_index) const override;
|
||||
int get_bake_mesh_texture_slice(int p_index) const override;
|
||||
int get_bake_probe_count() const override;
|
||||
Vector3 get_bake_probe_point(int p_probe) const override;
|
||||
Vector<Color> get_bake_probe_sh(int p_probe) const override;
|
||||
|
||||
LightmapperRD();
|
||||
};
|
||||
|
||||
#endif // LIGHTMAPPER_RD_H
|
||||
108
engine/modules/lightmapper_rd/lm_blendseams.glsl
Normal file
108
engine/modules/lightmapper_rd/lm_blendseams.glsl
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#[versions]
|
||||
|
||||
lines = "#define MODE_LINES";
|
||||
triangles = "#define MODE_TRIANGLES";
|
||||
|
||||
#[vertex]
|
||||
|
||||
#version 450
|
||||
|
||||
#VERSION_DEFINES
|
||||
|
||||
#include "lm_common_inc.glsl"
|
||||
|
||||
layout(push_constant, std430) uniform Params {
|
||||
uint base_index;
|
||||
uint slice;
|
||||
vec2 uv_offset;
|
||||
bool debug;
|
||||
float blend;
|
||||
uint pad[2];
|
||||
}
|
||||
params;
|
||||
|
||||
layout(location = 0) out vec3 uv_interp;
|
||||
|
||||
void main() {
|
||||
#ifdef MODE_TRIANGLES
|
||||
uint triangle_idx = params.base_index + gl_VertexIndex / 3;
|
||||
uint triangle_subidx = gl_VertexIndex % 3;
|
||||
|
||||
vec2 uv;
|
||||
if (triangle_subidx == 0) {
|
||||
uv = vertices.data[triangles.data[triangle_idx].indices.x].uv;
|
||||
} else if (triangle_subidx == 1) {
|
||||
uv = vertices.data[triangles.data[triangle_idx].indices.y].uv;
|
||||
} else {
|
||||
uv = vertices.data[triangles.data[triangle_idx].indices.z].uv;
|
||||
}
|
||||
|
||||
uv_interp = vec3(uv, float(params.slice));
|
||||
gl_Position = vec4((uv + params.uv_offset) * 2.0 - 1.0, 0.0001, 1.0);
|
||||
#endif
|
||||
|
||||
#ifdef MODE_LINES
|
||||
uint seam_idx = params.base_index + gl_VertexIndex / 4;
|
||||
uint seam_subidx = gl_VertexIndex % 4;
|
||||
|
||||
uint src_idx;
|
||||
uint dst_idx;
|
||||
|
||||
if (seam_subidx == 0) {
|
||||
src_idx = seams.data[seam_idx].b.x;
|
||||
dst_idx = seams.data[seam_idx].a.x;
|
||||
} else if (seam_subidx == 1) {
|
||||
src_idx = seams.data[seam_idx].b.y;
|
||||
dst_idx = seams.data[seam_idx].a.y;
|
||||
} else if (seam_subidx == 2) {
|
||||
src_idx = seams.data[seam_idx].a.x;
|
||||
dst_idx = seams.data[seam_idx].b.x;
|
||||
} else if (seam_subidx == 3) {
|
||||
src_idx = seams.data[seam_idx].a.y;
|
||||
dst_idx = seams.data[seam_idx].b.y;
|
||||
}
|
||||
|
||||
vec2 src_uv = vertices.data[src_idx].uv;
|
||||
vec2 dst_uv = vertices.data[dst_idx].uv + params.uv_offset;
|
||||
|
||||
uv_interp = vec3(src_uv, float(params.slice));
|
||||
gl_Position = vec4(dst_uv * 2.0 - 1.0, 0.0001, 1.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#[fragment]
|
||||
|
||||
#version 450
|
||||
|
||||
#VERSION_DEFINES
|
||||
|
||||
#include "lm_common_inc.glsl"
|
||||
|
||||
layout(push_constant, std430) uniform Params {
|
||||
uint base_index;
|
||||
uint slice;
|
||||
vec2 uv_offset;
|
||||
bool debug;
|
||||
float blend;
|
||||
uint pad[2];
|
||||
}
|
||||
params;
|
||||
|
||||
layout(location = 0) in vec3 uv_interp;
|
||||
|
||||
layout(location = 0) out vec4 dst_color;
|
||||
|
||||
layout(set = 1, binding = 0) uniform texture2DArray src_color_tex;
|
||||
|
||||
void main() {
|
||||
if (params.debug) {
|
||||
#ifdef MODE_TRIANGLES
|
||||
dst_color = vec4(1, 0, 1, 1);
|
||||
#else
|
||||
dst_color = vec4(1, 1, 0, 1);
|
||||
#endif
|
||||
} else {
|
||||
vec4 src_color = textureLod(sampler2DArray(src_color_tex, linear_sampler), uv_interp, 0.0);
|
||||
dst_color = vec4(src_color.rgb, params.blend); //mix
|
||||
}
|
||||
}
|
||||
126
engine/modules/lightmapper_rd/lm_common_inc.glsl
Normal file
126
engine/modules/lightmapper_rd/lm_common_inc.glsl
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
|
||||
/* SET 0, static data that does not change between any call */
|
||||
|
||||
layout(set = 0, binding = 0) uniform BakeParameters {
|
||||
vec3 world_size;
|
||||
float bias;
|
||||
|
||||
vec3 to_cell_offset;
|
||||
int grid_size;
|
||||
|
||||
vec3 to_cell_size;
|
||||
uint light_count;
|
||||
|
||||
mat3x4 env_transform;
|
||||
|
||||
ivec2 atlas_size;
|
||||
float exposure_normalization;
|
||||
uint bounces;
|
||||
|
||||
float bounce_indirect_energy;
|
||||
}
|
||||
bake_params;
|
||||
|
||||
struct Vertex {
|
||||
vec3 position;
|
||||
float normal_z;
|
||||
vec2 uv;
|
||||
vec2 normal_xy;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 1, std430) restrict readonly buffer Vertices {
|
||||
Vertex data[];
|
||||
}
|
||||
vertices;
|
||||
|
||||
struct Triangle {
|
||||
uvec3 indices;
|
||||
uint slice;
|
||||
vec3 min_bounds;
|
||||
uint pad0;
|
||||
vec3 max_bounds;
|
||||
uint pad1;
|
||||
};
|
||||
|
||||
struct ClusterAABB {
|
||||
vec3 min_bounds;
|
||||
uint pad0;
|
||||
vec3 max_bounds;
|
||||
uint pad1;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 2, std430) restrict readonly buffer Triangles {
|
||||
Triangle data[];
|
||||
}
|
||||
triangles;
|
||||
|
||||
layout(set = 0, binding = 3, std430) restrict readonly buffer TriangleIndices {
|
||||
uint data[];
|
||||
}
|
||||
triangle_indices;
|
||||
|
||||
#define LIGHT_TYPE_DIRECTIONAL 0
|
||||
#define LIGHT_TYPE_OMNI 1
|
||||
#define LIGHT_TYPE_SPOT 2
|
||||
|
||||
struct Light {
|
||||
vec3 position;
|
||||
uint type;
|
||||
|
||||
vec3 direction;
|
||||
float energy;
|
||||
|
||||
vec3 color;
|
||||
float size;
|
||||
|
||||
float range;
|
||||
float attenuation;
|
||||
float cos_spot_angle;
|
||||
float inv_spot_attenuation;
|
||||
|
||||
float indirect_energy;
|
||||
float shadow_blur;
|
||||
bool static_bake;
|
||||
uint pad;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 4, std430) restrict readonly buffer Lights {
|
||||
Light data[];
|
||||
}
|
||||
lights;
|
||||
|
||||
struct Seam {
|
||||
uvec2 a;
|
||||
uvec2 b;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 5, std430) restrict readonly buffer Seams {
|
||||
Seam data[];
|
||||
}
|
||||
seams;
|
||||
|
||||
layout(set = 0, binding = 6, std430) restrict readonly buffer Probes {
|
||||
vec4 data[];
|
||||
}
|
||||
probe_positions;
|
||||
|
||||
layout(set = 0, binding = 7) uniform utexture3D grid;
|
||||
|
||||
layout(set = 0, binding = 8) uniform texture2DArray albedo_tex;
|
||||
layout(set = 0, binding = 9) uniform texture2DArray emission_tex;
|
||||
|
||||
layout(set = 0, binding = 10) uniform sampler linear_sampler;
|
||||
|
||||
layout(set = 0, binding = 11, std430) restrict readonly buffer ClusterIndices {
|
||||
uint data[];
|
||||
}
|
||||
cluster_indices;
|
||||
|
||||
layout(set = 0, binding = 12, std430) restrict readonly buffer ClusterAABBs {
|
||||
ClusterAABB data[];
|
||||
}
|
||||
cluster_aabbs;
|
||||
|
||||
// Fragment action constants
|
||||
const uint FA_NONE = 0;
|
||||
const uint FA_SMOOTHEN_POSITION = 1;
|
||||
966
engine/modules/lightmapper_rd/lm_compute.glsl
Normal file
966
engine/modules/lightmapper_rd/lm_compute.glsl
Normal file
|
|
@ -0,0 +1,966 @@
|
|||
#[versions]
|
||||
|
||||
primary = "#define MODE_DIRECT_LIGHT";
|
||||
secondary = "#define MODE_BOUNCE_LIGHT";
|
||||
dilate = "#define MODE_DILATE";
|
||||
unocclude = "#define MODE_UNOCCLUDE";
|
||||
light_probes = "#define MODE_LIGHT_PROBES";
|
||||
denoise = "#define MODE_DENOISE";
|
||||
|
||||
#[compute]
|
||||
|
||||
#version 450
|
||||
|
||||
#VERSION_DEFINES
|
||||
|
||||
#extension GL_EXT_samplerless_texture_functions : enable
|
||||
|
||||
// One 2D local group focusing in one layer at a time, though all
|
||||
// in parallel (no barriers) makes more sense than a 3D local group
|
||||
// as this can take more advantage of the cache for each group.
|
||||
|
||||
#ifdef MODE_LIGHT_PROBES
|
||||
|
||||
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
#else
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||
|
||||
#endif
|
||||
|
||||
#include "lm_common_inc.glsl"
|
||||
|
||||
#ifdef MODE_LIGHT_PROBES
|
||||
|
||||
layout(set = 1, binding = 0, std430) restrict buffer LightProbeData {
|
||||
vec4 data[];
|
||||
}
|
||||
light_probes;
|
||||
|
||||
layout(set = 1, binding = 1) uniform texture2DArray source_light;
|
||||
layout(set = 1, binding = 2) uniform texture2D environment;
|
||||
#endif
|
||||
|
||||
#ifdef MODE_UNOCCLUDE
|
||||
|
||||
layout(rgba32f, set = 1, binding = 0) uniform restrict image2DArray position;
|
||||
layout(rgba32f, set = 1, binding = 1) uniform restrict readonly image2DArray unocclude;
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(MODE_DIRECT_LIGHT) || defined(MODE_BOUNCE_LIGHT)
|
||||
|
||||
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
|
||||
layout(set = 1, binding = 1) uniform texture2DArray source_light;
|
||||
layout(set = 1, binding = 2) uniform texture2DArray source_position;
|
||||
layout(set = 1, binding = 3) uniform texture2DArray source_normal;
|
||||
layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MODE_BOUNCE_LIGHT
|
||||
layout(set = 1, binding = 5) uniform texture2D environment;
|
||||
#endif
|
||||
|
||||
#if defined(MODE_DILATE) || defined(MODE_DENOISE)
|
||||
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
|
||||
layout(set = 1, binding = 1) uniform texture2DArray source_light;
|
||||
#endif
|
||||
|
||||
#ifdef MODE_DENOISE
|
||||
layout(set = 1, binding = 2) uniform texture2DArray source_normal;
|
||||
layout(set = 1, binding = 3) uniform DenoiseParams {
|
||||
float spatial_bandwidth;
|
||||
float light_bandwidth;
|
||||
float albedo_bandwidth;
|
||||
float normal_bandwidth;
|
||||
|
||||
int half_search_window;
|
||||
float filter_strength;
|
||||
}
|
||||
denoise_params;
|
||||
#endif
|
||||
|
||||
layout(push_constant, std430) uniform Params {
|
||||
uint atlas_slice;
|
||||
uint ray_count;
|
||||
uint ray_from;
|
||||
uint ray_to;
|
||||
|
||||
ivec2 region_ofs;
|
||||
uint probe_count;
|
||||
}
|
||||
params;
|
||||
|
||||
//check it, but also return distance and barycentric coords (for uv lookup)
|
||||
bool ray_hits_triangle(vec3 from, vec3 dir, float max_dist, vec3 p0, vec3 p1, vec3 p2, out float r_distance, out vec3 r_barycentric) {
|
||||
const float EPSILON = 0.00001;
|
||||
const vec3 e0 = p1 - p0;
|
||||
const vec3 e1 = p0 - p2;
|
||||
vec3 triangle_normal = cross(e1, e0);
|
||||
|
||||
float n_dot_dir = dot(triangle_normal, dir);
|
||||
|
||||
if (abs(n_dot_dir) < EPSILON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const vec3 e2 = (p0 - from) / n_dot_dir;
|
||||
const vec3 i = cross(dir, e2);
|
||||
|
||||
r_barycentric.y = dot(i, e1);
|
||||
r_barycentric.z = dot(i, e0);
|
||||
r_barycentric.x = 1.0 - (r_barycentric.z + r_barycentric.y);
|
||||
r_distance = dot(triangle_normal, e2);
|
||||
|
||||
return (r_distance > bake_params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0)));
|
||||
}
|
||||
|
||||
const uint RAY_MISS = 0;
|
||||
const uint RAY_FRONT = 1;
|
||||
const uint RAY_BACK = 2;
|
||||
const uint RAY_ANY = 3;
|
||||
|
||||
bool ray_box_test(vec3 p_from, vec3 p_inv_dir, vec3 p_box_min, vec3 p_box_max) {
|
||||
vec3 t0 = (p_box_min - p_from) * p_inv_dir;
|
||||
vec3 t1 = (p_box_max - p_from) * p_inv_dir;
|
||||
vec3 tmin = min(t0, t1), tmax = max(t0, t1);
|
||||
return max(tmin.x, max(tmin.y, tmin.z)) <= min(tmax.x, min(tmax.y, tmax.z));
|
||||
}
|
||||
|
||||
#if CLUSTER_SIZE > 32
|
||||
#define CLUSTER_TRIANGLE_ITERATION
|
||||
#endif
|
||||
|
||||
uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out vec3 r_normal, out uint r_triangle, out vec3 r_barycentric) {
|
||||
// World coordinates.
|
||||
vec3 rel = p_to - p_from;
|
||||
float rel_len = length(rel);
|
||||
vec3 dir = normalize(rel);
|
||||
vec3 inv_dir = 1.0 / dir;
|
||||
|
||||
// Cell coordinates.
|
||||
vec3 from_cell = (p_from - bake_params.to_cell_offset) * bake_params.to_cell_size;
|
||||
vec3 to_cell = (p_to - bake_params.to_cell_offset) * bake_params.to_cell_size;
|
||||
|
||||
// Prepare DDA.
|
||||
vec3 rel_cell = to_cell - from_cell;
|
||||
ivec3 icell = ivec3(from_cell);
|
||||
ivec3 iendcell = ivec3(to_cell);
|
||||
vec3 dir_cell = normalize(rel_cell);
|
||||
vec3 delta = min(abs(1.0 / dir_cell), bake_params.grid_size); // Use bake_params.grid_size as max to prevent infinity values.
|
||||
ivec3 step = ivec3(sign(rel_cell));
|
||||
vec3 side = (sign(rel_cell) * (vec3(icell) - from_cell) + (sign(rel_cell) * 0.5) + 0.5) * delta;
|
||||
|
||||
uint iters = 0;
|
||||
while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(bake_params.grid_size))) && (iters < 1000)) {
|
||||
uvec2 cell_data = texelFetch(grid, icell, 0).xy;
|
||||
uint triangle_count = cell_data.x;
|
||||
if (triangle_count > 0) {
|
||||
uint hit = RAY_MISS;
|
||||
float best_distance = 1e20;
|
||||
uint cluster_start = cluster_indices.data[cell_data.y * 2];
|
||||
uint cell_triangle_start = cluster_indices.data[cell_data.y * 2 + 1];
|
||||
uint cluster_count = (triangle_count + CLUSTER_SIZE - 1) / CLUSTER_SIZE;
|
||||
uint cluster_base_index = 0;
|
||||
while (cluster_base_index < cluster_count) {
|
||||
// To minimize divergence, all Ray-AABB tests on the clusters contained in the cell are performed
|
||||
// before checking against the triangles. We do this 32 clusters at a time and store the intersected
|
||||
// clusters on each bit of the 32-bit integer.
|
||||
uint cluster_test_count = min(32, cluster_count - cluster_base_index);
|
||||
uint cluster_hits = 0;
|
||||
for (uint i = 0; i < cluster_test_count; i++) {
|
||||
uint cluster_index = cluster_start + cluster_base_index + i;
|
||||
ClusterAABB cluster_aabb = cluster_aabbs.data[cluster_index];
|
||||
if (ray_box_test(p_from, inv_dir, cluster_aabb.min_bounds, cluster_aabb.max_bounds)) {
|
||||
cluster_hits |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the triangles in any of the clusters that were intersected by toggling off the bits in the
|
||||
// 32-bit integer counter until no bits are left.
|
||||
while (cluster_hits > 0) {
|
||||
uint cluster_index = findLSB(cluster_hits);
|
||||
cluster_hits &= ~(1 << cluster_index);
|
||||
cluster_index += cluster_base_index;
|
||||
|
||||
// Do the same divergence execution trick with triangles as well.
|
||||
uint triangle_base_index = 0;
|
||||
#ifdef CLUSTER_TRIANGLE_ITERATION
|
||||
while (triangle_base_index < triangle_count)
|
||||
#endif
|
||||
{
|
||||
uint triangle_start_index = cell_triangle_start + cluster_index * CLUSTER_SIZE + triangle_base_index;
|
||||
uint triangle_test_count = min(CLUSTER_SIZE, triangle_count - triangle_base_index);
|
||||
uint triangle_hits = 0;
|
||||
for (uint i = 0; i < triangle_test_count; i++) {
|
||||
uint triangle_index = triangle_indices.data[triangle_start_index + i];
|
||||
if (ray_box_test(p_from, inv_dir, triangles.data[triangle_index].min_bounds, triangles.data[triangle_index].max_bounds)) {
|
||||
triangle_hits |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
while (triangle_hits > 0) {
|
||||
uint cluster_triangle_index = findLSB(triangle_hits);
|
||||
triangle_hits &= ~(1 << cluster_triangle_index);
|
||||
cluster_triangle_index += triangle_start_index;
|
||||
|
||||
uint triangle_index = triangle_indices.data[cluster_triangle_index];
|
||||
Triangle triangle = triangles.data[triangle_index];
|
||||
|
||||
// Gather the triangle vertex positions.
|
||||
vec3 vtx0 = vertices.data[triangle.indices.x].position;
|
||||
vec3 vtx1 = vertices.data[triangle.indices.y].position;
|
||||
vec3 vtx2 = vertices.data[triangle.indices.z].position;
|
||||
vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2)));
|
||||
bool backface = dot(normal, dir) >= 0.0;
|
||||
float distance;
|
||||
vec3 barycentric;
|
||||
if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) {
|
||||
if (p_any_hit) {
|
||||
// Return early if any hit was requested.
|
||||
return RAY_ANY;
|
||||
}
|
||||
|
||||
vec3 position = p_from + dir * distance;
|
||||
vec3 hit_cell = (position - bake_params.to_cell_offset) * bake_params.to_cell_size;
|
||||
if (icell != ivec3(hit_cell)) {
|
||||
// It's possible for the ray to hit a triangle in a position outside the bounds of the cell
|
||||
// if it's large enough to cover multiple ones. The hit must be ignored if this is the case.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!backface) {
|
||||
// The case of meshes having both a front and back face in the same plane is more common than
|
||||
// expected, so if this is a front-face, bias it closer to the ray origin, so it always wins
|
||||
// over the back-face.
|
||||
distance = max(bake_params.bias, distance - bake_params.bias);
|
||||
}
|
||||
|
||||
if (distance < best_distance) {
|
||||
hit = backface ? RAY_BACK : RAY_FRONT;
|
||||
best_distance = distance;
|
||||
r_distance = distance;
|
||||
r_normal = normal;
|
||||
r_triangle = triangle_index;
|
||||
r_barycentric = barycentric;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CLUSTER_TRIANGLE_ITERATION
|
||||
triangle_base_index += CLUSTER_SIZE;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
cluster_base_index += 32;
|
||||
}
|
||||
|
||||
if (hit != RAY_MISS) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
|
||||
if (icell == iendcell) {
|
||||
break;
|
||||
}
|
||||
|
||||
// There should be only one axis updated at a time for DDA to work properly.
|
||||
bvec3 mask = bvec3(true, false, false);
|
||||
float m = side.x;
|
||||
if (side.y < m) {
|
||||
m = side.y;
|
||||
mask = bvec3(false, true, false);
|
||||
}
|
||||
if (side.z < m) {
|
||||
mask = bvec3(false, false, true);
|
||||
}
|
||||
side += vec3(mask) * delta;
|
||||
icell += ivec3(vec3(mask)) * step;
|
||||
iters++;
|
||||
}
|
||||
|
||||
return RAY_MISS;
|
||||
}
|
||||
|
||||
uint trace_ray_closest_hit_triangle(vec3 p_from, vec3 p_to, out uint r_triangle, out vec3 r_barycentric) {
|
||||
float distance;
|
||||
vec3 normal;
|
||||
return trace_ray(p_from, p_to, false, distance, normal, r_triangle, r_barycentric);
|
||||
}
|
||||
|
||||
uint trace_ray_closest_hit_distance(vec3 p_from, vec3 p_to, out float r_distance, out vec3 r_normal) {
|
||||
uint triangle;
|
||||
vec3 barycentric;
|
||||
return trace_ray(p_from, p_to, false, r_distance, r_normal, triangle, barycentric);
|
||||
}
|
||||
|
||||
uint trace_ray_any_hit(vec3 p_from, vec3 p_to) {
|
||||
float distance;
|
||||
vec3 normal;
|
||||
uint triangle;
|
||||
vec3 barycentric;
|
||||
return trace_ray(p_from, p_to, true, distance, normal, triangle, barycentric);
|
||||
}
|
||||
|
||||
// https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/
|
||||
uint hash(uint value) {
|
||||
uint state = value * 747796405u + 2891336453u;
|
||||
uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
|
||||
return (word >> 22u) ^ word;
|
||||
}
|
||||
|
||||
uint random_seed(ivec3 seed) {
|
||||
return hash(seed.x ^ hash(seed.y ^ hash(seed.z)));
|
||||
}
|
||||
|
||||
// generates a random value in range [0.0, 1.0)
|
||||
float randomize(inout uint value) {
|
||||
value = hash(value);
|
||||
return float(value / 4294967296.0);
|
||||
}
|
||||
|
||||
const float PI = 3.14159265f;
|
||||
|
||||
// http://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.4.pdf (chapter 15)
|
||||
vec3 generate_hemisphere_cosine_weighted_direction(inout uint noise) {
|
||||
float noise1 = randomize(noise);
|
||||
float noise2 = randomize(noise) * 2.0 * PI;
|
||||
|
||||
return vec3(sqrt(noise1) * cos(noise2), sqrt(noise1) * sin(noise2), sqrt(1.0 - noise1));
|
||||
}
|
||||
|
||||
// Distribution generation adapted from "Generating uniformly distributed numbers on a sphere"
|
||||
// <http://corysimon.github.io/articles/uniformdistn-on-sphere/>
|
||||
vec3 generate_sphere_uniform_direction(inout uint noise) {
|
||||
float theta = 2.0 * PI * randomize(noise);
|
||||
float phi = acos(1.0 - 2.0 * randomize(noise));
|
||||
return vec3(sin(phi) * cos(theta), sin(phi) * sin(theta), cos(phi));
|
||||
}
|
||||
|
||||
vec3 generate_ray_dir_from_normal(vec3 normal, inout uint noise) {
|
||||
vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
|
||||
vec3 tangent = normalize(cross(v0, normal));
|
||||
vec3 bitangent = normalize(cross(tangent, normal));
|
||||
mat3 normal_mat = mat3(tangent, bitangent, normal);
|
||||
return normal_mat * generate_hemisphere_cosine_weighted_direction(noise);
|
||||
}
|
||||
|
||||
#if defined(MODE_DIRECT_LIGHT) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
|
||||
|
||||
float get_omni_attenuation(float distance, float inv_range, float decay) {
|
||||
float nd = distance * inv_range;
|
||||
nd *= nd;
|
||||
nd *= nd; // nd^4
|
||||
nd = max(1.0 - nd, 0.0);
|
||||
nd *= nd; // nd^2
|
||||
return nd * pow(max(distance, 0.0001), -decay);
|
||||
}
|
||||
|
||||
void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise) {
|
||||
r_light = vec3(0.0f);
|
||||
|
||||
vec3 light_pos;
|
||||
float dist;
|
||||
float attenuation;
|
||||
float soft_shadowing_disk_size;
|
||||
Light light_data = lights.data[p_light_index];
|
||||
if (light_data.type == LIGHT_TYPE_DIRECTIONAL) {
|
||||
vec3 light_vec = light_data.direction;
|
||||
light_pos = p_position - light_vec * length(bake_params.world_size);
|
||||
r_light_dir = normalize(light_pos - p_position);
|
||||
dist = length(bake_params.world_size);
|
||||
attenuation = 1.0;
|
||||
soft_shadowing_disk_size = light_data.size;
|
||||
} else {
|
||||
light_pos = light_data.position;
|
||||
r_light_dir = normalize(light_pos - p_position);
|
||||
dist = distance(p_position, light_pos);
|
||||
if (dist > light_data.range) {
|
||||
return;
|
||||
}
|
||||
|
||||
soft_shadowing_disk_size = light_data.size / dist;
|
||||
|
||||
attenuation = get_omni_attenuation(dist, 1.0 / light_data.range, light_data.attenuation);
|
||||
|
||||
if (light_data.type == LIGHT_TYPE_SPOT) {
|
||||
vec3 rel = normalize(p_position - light_pos);
|
||||
float cos_spot_angle = light_data.cos_spot_angle;
|
||||
float cos_angle = dot(rel, light_data.direction);
|
||||
|
||||
if (cos_angle < cos_spot_angle) {
|
||||
return;
|
||||
}
|
||||
|
||||
float scos = max(cos_angle, cos_spot_angle);
|
||||
float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle));
|
||||
attenuation *= 1.0 - pow(spot_rim, light_data.inv_spot_attenuation);
|
||||
}
|
||||
}
|
||||
|
||||
attenuation *= max(0.0, dot(p_normal, r_light_dir));
|
||||
if (attenuation <= 0.0001) {
|
||||
return;
|
||||
}
|
||||
|
||||
float penumbra = 0.0;
|
||||
if ((light_data.size > 0.0) && p_soft_shadowing) {
|
||||
vec3 light_to_point = -r_light_dir;
|
||||
vec3 aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
||||
vec3 light_to_point_tan = normalize(cross(light_to_point, aux));
|
||||
vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan));
|
||||
|
||||
const uint shadowing_rays_check_penumbra_denom = 2;
|
||||
uint shadowing_ray_count = p_soft_shadowing ? params.ray_count : 1;
|
||||
|
||||
uint hits = 0;
|
||||
vec3 light_disk_to_point = light_to_point;
|
||||
for (uint j = 0; j < shadowing_ray_count; j++) {
|
||||
// Optimization:
|
||||
// Once already traced an important proportion of rays, if all are hits or misses,
|
||||
// assume we're not in the penumbra so we can infer the rest would have the same result
|
||||
if (p_soft_shadowing) {
|
||||
if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) {
|
||||
if (hits == j) {
|
||||
// Assume totally lit
|
||||
hits = shadowing_ray_count;
|
||||
break;
|
||||
} else if (hits == 0) {
|
||||
// Assume totally dark
|
||||
hits = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float r = randomize(r_noise);
|
||||
float a = randomize(r_noise) * 2.0 * PI;
|
||||
vec2 disk_sample = (r * vec2(cos(a), sin(a))) * soft_shadowing_disk_size * light_data.shadow_blur;
|
||||
light_disk_to_point = normalize(light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan);
|
||||
|
||||
if (trace_ray_any_hit(p_position - light_disk_to_point * bake_params.bias, p_position - light_disk_to_point * dist) == RAY_MISS) {
|
||||
hits++;
|
||||
}
|
||||
}
|
||||
|
||||
penumbra = float(hits) / float(shadowing_ray_count);
|
||||
} else {
|
||||
if (trace_ray_any_hit(p_position + r_light_dir * bake_params.bias, light_pos) == RAY_MISS) {
|
||||
penumbra = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
r_light = light_data.color * light_data.energy * attenuation * penumbra;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
|
||||
|
||||
vec3 trace_environment_color(vec3 ray_dir) {
|
||||
vec3 sky_dir = normalize(mat3(bake_params.env_transform) * ray_dir);
|
||||
vec2 st = vec2(atan(sky_dir.x, sky_dir.z), acos(sky_dir.y));
|
||||
if (st.x < 0.0) {
|
||||
st.x += PI * 2.0;
|
||||
}
|
||||
|
||||
return textureLod(sampler2D(environment, linear_sampler), st / vec2(PI * 2.0, PI), 0.0).rgb;
|
||||
}
|
||||
|
||||
vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise) {
|
||||
// The lower limit considers the case where the lightmapper might have bounces disabled but light probes are requested.
|
||||
vec3 position = p_position;
|
||||
vec3 ray_dir = p_ray_dir;
|
||||
uint max_depth = max(bake_params.bounces, 1);
|
||||
vec3 throughput = vec3(1.0);
|
||||
vec3 light = vec3(0.0);
|
||||
for (uint depth = 0; depth < max_depth; depth++) {
|
||||
uint tidx;
|
||||
vec3 barycentric;
|
||||
uint trace_result = trace_ray_closest_hit_triangle(position + ray_dir * bake_params.bias, position + ray_dir * length(bake_params.world_size), tidx, barycentric);
|
||||
if (trace_result == RAY_FRONT) {
|
||||
Vertex vert0 = vertices.data[triangles.data[tidx].indices.x];
|
||||
Vertex vert1 = vertices.data[triangles.data[tidx].indices.y];
|
||||
Vertex vert2 = vertices.data[triangles.data[tidx].indices.z];
|
||||
vec3 uvw = vec3(barycentric.x * vert0.uv + barycentric.y * vert1.uv + barycentric.z * vert2.uv, float(triangles.data[tidx].slice));
|
||||
position = barycentric.x * vert0.position + barycentric.y * vert1.position + barycentric.z * vert2.position;
|
||||
|
||||
vec3 norm0 = vec3(vert0.normal_xy, vert0.normal_z);
|
||||
vec3 norm1 = vec3(vert1.normal_xy, vert1.normal_z);
|
||||
vec3 norm2 = vec3(vert2.normal_xy, vert2.normal_z);
|
||||
vec3 normal = barycentric.x * norm0 + barycentric.y * norm1 + barycentric.z * norm2;
|
||||
|
||||
vec3 direct_light = vec3(0.0f);
|
||||
#ifdef USE_LIGHT_TEXTURE_FOR_BOUNCES
|
||||
direct_light += textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
|
||||
#else
|
||||
// Trace the lights directly. Significantly more expensive but more accurate in scenarios
|
||||
// where the lightmap texture isn't reliable.
|
||||
for (uint i = 0; i < bake_params.light_count; i++) {
|
||||
vec3 light;
|
||||
vec3 light_dir;
|
||||
trace_direct_light(position, normal, i, false, light, light_dir, r_noise);
|
||||
direct_light += light * lights.data[i].indirect_energy;
|
||||
}
|
||||
|
||||
direct_light *= bake_params.exposure_normalization;
|
||||
#endif
|
||||
|
||||
vec3 albedo = textureLod(sampler2DArray(albedo_tex, linear_sampler), uvw, 0).rgb;
|
||||
vec3 emissive = textureLod(sampler2DArray(emission_tex, linear_sampler), uvw, 0).rgb;
|
||||
emissive *= bake_params.exposure_normalization;
|
||||
|
||||
light += throughput * emissive;
|
||||
throughput *= albedo;
|
||||
light += throughput * direct_light * bake_params.bounce_indirect_energy;
|
||||
|
||||
// Use Russian Roulette to determine a probability to terminate the bounce earlier as an optimization.
|
||||
// <https://computergraphics.stackexchange.com/questions/2316/is-russian-roulette-really-the-answer>
|
||||
float p = max(max(throughput.x, throughput.y), throughput.z);
|
||||
if (randomize(r_noise) > p) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Boost the throughput from the probability of the ray being terminated early.
|
||||
throughput *= 1.0 / p;
|
||||
|
||||
// Generate a new ray direction for the next bounce from this surface's normal.
|
||||
ray_dir = generate_ray_dir_from_normal(normal, r_noise);
|
||||
} else if (trace_result == RAY_MISS) {
|
||||
// Look for the environment color and stop bouncing.
|
||||
light += throughput * trace_environment_color(ray_dir);
|
||||
break;
|
||||
} else {
|
||||
// Ignore any other trace results.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return light;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
// Check if invocation is out of bounds.
|
||||
#ifdef MODE_LIGHT_PROBES
|
||||
int probe_index = int(gl_GlobalInvocationID.x);
|
||||
if (probe_index >= params.probe_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
#else
|
||||
ivec2 atlas_pos = ivec2(gl_GlobalInvocationID.xy) + params.region_ofs;
|
||||
if (any(greaterThanEqual(atlas_pos, bake_params.atlas_size))) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MODE_DIRECT_LIGHT
|
||||
|
||||
vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
|
||||
if (length(normal) < 0.5) {
|
||||
return; //empty texel, no process
|
||||
}
|
||||
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
|
||||
vec3 light_for_texture = vec3(0.0);
|
||||
vec3 light_for_bounces = vec3(0.0);
|
||||
|
||||
#ifdef USE_SH_LIGHTMAPS
|
||||
vec4 sh_accum[4] = vec4[](
|
||||
vec4(0.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0));
|
||||
#endif
|
||||
|
||||
// Use atlas position and a prime number as the seed.
|
||||
uint noise = random_seed(ivec3(atlas_pos, 43573547));
|
||||
for (uint i = 0; i < bake_params.light_count; i++) {
|
||||
vec3 light;
|
||||
vec3 light_dir;
|
||||
trace_direct_light(position, normal, i, true, light, light_dir, noise);
|
||||
|
||||
if (lights.data[i].static_bake) {
|
||||
light_for_texture += light;
|
||||
|
||||
#ifdef USE_SH_LIGHTMAPS
|
||||
float c[4] = float[](
|
||||
0.282095, //l0
|
||||
0.488603 * light_dir.y, //l1n1
|
||||
0.488603 * light_dir.z, //l1n0
|
||||
0.488603 * light_dir.x //l1p1
|
||||
);
|
||||
|
||||
for (uint j = 0; j < 4; j++) {
|
||||
sh_accum[j].rgb += light * c[j] * 8.0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
light_for_bounces += light * lights.data[i].indirect_energy;
|
||||
}
|
||||
|
||||
light_for_bounces *= bake_params.exposure_normalization;
|
||||
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_for_bounces, 1.0));
|
||||
|
||||
#ifdef USE_SH_LIGHTMAPS
|
||||
// Keep for adding at the end.
|
||||
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 0), sh_accum[0]);
|
||||
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 1), sh_accum[1]);
|
||||
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 2), sh_accum[2]);
|
||||
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 3), sh_accum[3]);
|
||||
#else
|
||||
light_for_texture *= bake_params.exposure_normalization;
|
||||
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_for_texture, 1.0));
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MODE_BOUNCE_LIGHT
|
||||
|
||||
#ifdef USE_SH_LIGHTMAPS
|
||||
vec4 sh_accum[4] = vec4[](
|
||||
vec4(0.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0));
|
||||
#else
|
||||
vec3 light_accum = vec3(0.0);
|
||||
#endif
|
||||
|
||||
// Retrieve starting normal and position.
|
||||
vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
|
||||
if (length(normal) < 0.5) {
|
||||
// The pixel is empty, skip processing it.
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
|
||||
uint noise = random_seed(ivec3(params.ray_from, atlas_pos));
|
||||
for (uint i = params.ray_from; i < params.ray_to; i++) {
|
||||
vec3 ray_dir = generate_ray_dir_from_normal(normal, noise);
|
||||
vec3 light = trace_indirect_light(position, ray_dir, noise);
|
||||
|
||||
#ifdef USE_SH_LIGHTMAPS
|
||||
float c[4] = float[](
|
||||
0.282095, //l0
|
||||
0.488603 * ray_dir.y, //l1n1
|
||||
0.488603 * ray_dir.z, //l1n0
|
||||
0.488603 * ray_dir.x //l1p1
|
||||
);
|
||||
|
||||
for (uint j = 0; j < 4; j++) {
|
||||
sh_accum[j].rgb += light * c[j] * 8.0;
|
||||
}
|
||||
#else
|
||||
light_accum += light;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Add the averaged result to the accumulated light texture.
|
||||
#ifdef USE_SH_LIGHTMAPS
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i));
|
||||
accum.rgb += sh_accum[i].rgb / float(params.ray_count);
|
||||
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), accum);
|
||||
}
|
||||
#else
|
||||
vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice));
|
||||
accum.rgb += light_accum / float(params.ray_count);
|
||||
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), accum);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MODE_UNOCCLUDE
|
||||
|
||||
//texel_size = 0.5;
|
||||
//compute tangents
|
||||
|
||||
vec4 position_alpha = imageLoad(position, ivec3(atlas_pos, params.atlas_slice));
|
||||
if (position_alpha.a < 0.5) {
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 vertex_pos = position_alpha.xyz;
|
||||
vec4 normal_tsize = imageLoad(unocclude, ivec3(atlas_pos, params.atlas_slice));
|
||||
|
||||
vec3 face_normal = normal_tsize.xyz;
|
||||
float texel_size = normal_tsize.w;
|
||||
|
||||
vec3 v0 = abs(face_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
|
||||
vec3 tangent = normalize(cross(v0, face_normal));
|
||||
vec3 bitangent = normalize(cross(tangent, face_normal));
|
||||
vec3 base_pos = vertex_pos + face_normal * bake_params.bias; // Raise a bit.
|
||||
|
||||
vec3 rays[4] = vec3[](tangent, bitangent, -tangent, -bitangent);
|
||||
float min_d = 1e20;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vec3 ray_to = base_pos + rays[i] * texel_size;
|
||||
float d;
|
||||
vec3 norm;
|
||||
|
||||
if (trace_ray_closest_hit_distance(base_pos, ray_to, d, norm) == RAY_BACK) {
|
||||
if (d < min_d) {
|
||||
// This bias needs to be greater than the regular bias, because otherwise later, rays will go the other side when pointing back.
|
||||
vertex_pos = base_pos + rays[i] * d + norm * bake_params.bias * 10.0;
|
||||
min_d = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
position_alpha.xyz = vertex_pos;
|
||||
|
||||
imageStore(position, ivec3(atlas_pos, params.atlas_slice), position_alpha);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MODE_LIGHT_PROBES
|
||||
|
||||
vec3 position = probe_positions.data[probe_index].xyz;
|
||||
|
||||
vec4 probe_sh_accum[9] = vec4[](
|
||||
vec4(0.0),
|
||||
vec4(0.0),
|
||||
vec4(0.0),
|
||||
vec4(0.0),
|
||||
vec4(0.0),
|
||||
vec4(0.0),
|
||||
vec4(0.0),
|
||||
vec4(0.0),
|
||||
vec4(0.0));
|
||||
|
||||
uint noise = random_seed(ivec3(params.ray_from, probe_index, 49502741 /* some prime */));
|
||||
for (uint i = params.ray_from; i < params.ray_to; i++) {
|
||||
vec3 ray_dir = generate_sphere_uniform_direction(noise);
|
||||
vec3 light = trace_indirect_light(position, ray_dir, noise);
|
||||
|
||||
float c[9] = float[](
|
||||
0.282095, //l0
|
||||
0.488603 * ray_dir.y, //l1n1
|
||||
0.488603 * ray_dir.z, //l1n0
|
||||
0.488603 * ray_dir.x, //l1p1
|
||||
1.092548 * ray_dir.x * ray_dir.y, //l2n2
|
||||
1.092548 * ray_dir.y * ray_dir.z, //l2n1
|
||||
//0.315392 * (ray_dir.x * ray_dir.x + ray_dir.y * ray_dir.y + 2.0 * ray_dir.z * ray_dir.z), //l20
|
||||
0.315392 * (3.0 * ray_dir.z * ray_dir.z - 1.0), //l20
|
||||
1.092548 * ray_dir.x * ray_dir.z, //l2p1
|
||||
0.546274 * (ray_dir.x * ray_dir.x - ray_dir.y * ray_dir.y) //l2p2
|
||||
);
|
||||
|
||||
for (uint j = 0; j < 9; j++) {
|
||||
probe_sh_accum[j].rgb += light * c[j];
|
||||
}
|
||||
}
|
||||
|
||||
if (params.ray_from > 0) {
|
||||
for (uint j = 0; j < 9; j++) { //accum from existing
|
||||
probe_sh_accum[j] += light_probes.data[probe_index * 9 + j];
|
||||
}
|
||||
}
|
||||
|
||||
if (params.ray_to == params.ray_count) {
|
||||
for (uint j = 0; j < 9; j++) { //accum from existing
|
||||
probe_sh_accum[j] *= 4.0 / float(params.ray_count);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint j = 0; j < 9; j++) { //accum from existing
|
||||
light_probes.data[probe_index * 9 + j] = probe_sh_accum[j];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MODE_DILATE
|
||||
|
||||
vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0);
|
||||
//sides first, as they are closer
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 0), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, 1), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 0), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, -1), params.atlas_slice), 0);
|
||||
//endpoints second
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, -1), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 1), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, -1), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 1), params.atlas_slice), 0);
|
||||
|
||||
//far sides third
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 0), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, 2), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 0), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, -2), params.atlas_slice), 0);
|
||||
|
||||
//far-mid endpoints
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, -1), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 1), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, -1), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 1), params.atlas_slice), 0);
|
||||
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, -2), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 2), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, -2), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 2), params.atlas_slice), 0);
|
||||
//far endpoints
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, -2), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 2), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, -2), params.atlas_slice), 0);
|
||||
c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 2), params.atlas_slice), 0);
|
||||
|
||||
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), c);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MODE_DENOISE
|
||||
// Joint Non-local means (JNLM) denoiser.
|
||||
//
|
||||
// Based on YoctoImageDenoiser's JNLM implementation with corrections from "Nonlinearly Weighted First-order Regression for Denoising Monte Carlo Renderings".
|
||||
//
|
||||
// <https://github.com/ManuelPrandini/YoctoImageDenoiser/blob/06e19489dd64e47792acffde536393802ba48607/libs/yocto_extension/yocto_extension.cpp#L207>
|
||||
// <https://benedikt-bitterli.me/nfor/nfor.pdf>
|
||||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 ManuelPrandini
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Most of the constants below have been hand-picked to fit the common scenarios lightmaps
|
||||
// are generated with, but they can be altered freely to experiment and achieve better results.
|
||||
|
||||
// Half the size of the patch window around each pixel that is weighted to compute the denoised pixel.
|
||||
// A value of 1 represents a 3x3 window, a value of 2 a 5x5 window, etc.
|
||||
const int HALF_PATCH_WINDOW = 3;
|
||||
|
||||
// Half the size of the search window around each pixel that is denoised and weighted to compute the denoised pixel.
|
||||
const int HALF_SEARCH_WINDOW = denoise_params.half_search_window;
|
||||
|
||||
// For all of the following sigma values, smaller values will give less weight to pixels that have a bigger distance
|
||||
// in the feature being evaluated. Therefore, smaller values are likely to cause more noise to appear, but will also
|
||||
// cause less features to be erased in the process.
|
||||
|
||||
// Controls how much the spatial distance of the pixels influences the denoising weight.
|
||||
const float SIGMA_SPATIAL = denoise_params.spatial_bandwidth;
|
||||
|
||||
// Controls how much the light color distance of the pixels influences the denoising weight.
|
||||
const float SIGMA_LIGHT = denoise_params.light_bandwidth;
|
||||
|
||||
// Controls how much the albedo color distance of the pixels influences the denoising weight.
|
||||
const float SIGMA_ALBEDO = denoise_params.albedo_bandwidth;
|
||||
|
||||
// Controls how much the normal vector distance of the pixels influences the denoising weight.
|
||||
const float SIGMA_NORMAL = denoise_params.normal_bandwidth;
|
||||
|
||||
// Strength of the filter. The original paper recommends values around 10 to 15 times the Sigma parameter.
|
||||
const float FILTER_VALUE = denoise_params.filter_strength * SIGMA_LIGHT;
|
||||
|
||||
// Formula constants.
|
||||
const int PATCH_WINDOW_DIMENSION = (HALF_PATCH_WINDOW * 2 + 1);
|
||||
const int PATCH_WINDOW_DIMENSION_SQUARE = (PATCH_WINDOW_DIMENSION * PATCH_WINDOW_DIMENSION);
|
||||
const float TWO_SIGMA_SPATIAL_SQUARE = 2.0f * SIGMA_SPATIAL * SIGMA_SPATIAL;
|
||||
const float TWO_SIGMA_LIGHT_SQUARE = 2.0f * SIGMA_LIGHT * SIGMA_LIGHT;
|
||||
const float TWO_SIGMA_ALBEDO_SQUARE = 2.0f * SIGMA_ALBEDO * SIGMA_ALBEDO;
|
||||
const float TWO_SIGMA_NORMAL_SQUARE = 2.0f * SIGMA_NORMAL * SIGMA_NORMAL;
|
||||
const float FILTER_SQUARE_TWO_SIGMA_LIGHT_SQUARE = FILTER_VALUE * FILTER_VALUE * TWO_SIGMA_LIGHT_SQUARE;
|
||||
const float EPSILON = 1e-6f;
|
||||
|
||||
#ifdef USE_SH_LIGHTMAPS
|
||||
const uint slice_count = 4;
|
||||
const uint slice_base = params.atlas_slice * slice_count;
|
||||
#else
|
||||
const uint slice_count = 1;
|
||||
const uint slice_base = params.atlas_slice;
|
||||
#endif
|
||||
|
||||
for (uint i = 0; i < slice_count; i++) {
|
||||
uint lightmap_slice = slice_base + i;
|
||||
vec3 denoised_rgb = vec3(0.0f);
|
||||
vec4 input_light = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, lightmap_slice), 0);
|
||||
vec3 input_albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb;
|
||||
vec3 input_normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
|
||||
if (length(input_normal) > EPSILON) {
|
||||
// Compute the denoised pixel if the normal is valid.
|
||||
float sum_weights = 0.0f;
|
||||
vec3 input_rgb = input_light.rgb;
|
||||
for (int search_y = -HALF_SEARCH_WINDOW; search_y <= HALF_SEARCH_WINDOW; search_y++) {
|
||||
for (int search_x = -HALF_SEARCH_WINDOW; search_x <= HALF_SEARCH_WINDOW; search_x++) {
|
||||
ivec2 search_pos = atlas_pos + ivec2(search_x, search_y);
|
||||
vec3 search_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(search_pos, lightmap_slice), 0).rgb;
|
||||
vec3 search_albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(search_pos, params.atlas_slice), 0).rgb;
|
||||
vec3 search_normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(search_pos, params.atlas_slice), 0).xyz;
|
||||
float patch_square_dist = 0.0f;
|
||||
for (int offset_y = -HALF_PATCH_WINDOW; offset_y <= HALF_PATCH_WINDOW; offset_y++) {
|
||||
for (int offset_x = -HALF_PATCH_WINDOW; offset_x <= HALF_PATCH_WINDOW; offset_x++) {
|
||||
ivec2 offset_input_pos = atlas_pos + ivec2(offset_x, offset_y);
|
||||
ivec2 offset_search_pos = search_pos + ivec2(offset_x, offset_y);
|
||||
vec3 offset_input_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(offset_input_pos, lightmap_slice), 0).rgb;
|
||||
vec3 offset_search_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(offset_search_pos, lightmap_slice), 0).rgb;
|
||||
vec3 offset_delta_rgb = offset_input_rgb - offset_search_rgb;
|
||||
patch_square_dist += dot(offset_delta_rgb, offset_delta_rgb) - TWO_SIGMA_LIGHT_SQUARE;
|
||||
}
|
||||
}
|
||||
|
||||
patch_square_dist = max(0.0f, patch_square_dist / (3.0f * PATCH_WINDOW_DIMENSION_SQUARE));
|
||||
|
||||
float weight = 1.0f;
|
||||
|
||||
// Ignore weight if search position is out of bounds.
|
||||
weight *= step(0, search_pos.x) * step(search_pos.x, bake_params.atlas_size.x - 1);
|
||||
weight *= step(0, search_pos.y) * step(search_pos.y, bake_params.atlas_size.y - 1);
|
||||
|
||||
// Ignore weight if normal is zero length.
|
||||
weight *= step(EPSILON, length(search_normal));
|
||||
|
||||
// Weight with pixel distance.
|
||||
vec2 pixel_delta = vec2(search_x, search_y);
|
||||
float pixel_square_dist = dot(pixel_delta, pixel_delta);
|
||||
weight *= exp(-pixel_square_dist / TWO_SIGMA_SPATIAL_SQUARE);
|
||||
|
||||
// Weight with patch.
|
||||
weight *= exp(-patch_square_dist / FILTER_SQUARE_TWO_SIGMA_LIGHT_SQUARE);
|
||||
|
||||
// Weight with albedo.
|
||||
vec3 albedo_delta = input_albedo - search_albedo;
|
||||
float albedo_square_dist = dot(albedo_delta, albedo_delta);
|
||||
weight *= exp(-albedo_square_dist / TWO_SIGMA_ALBEDO_SQUARE);
|
||||
|
||||
// Weight with normal.
|
||||
vec3 normal_delta = input_normal - search_normal;
|
||||
float normal_square_dist = dot(normal_delta, normal_delta);
|
||||
weight *= exp(-normal_square_dist / TWO_SIGMA_NORMAL_SQUARE);
|
||||
|
||||
denoised_rgb += weight * search_rgb;
|
||||
sum_weights += weight;
|
||||
}
|
||||
}
|
||||
|
||||
denoised_rgb /= sum_weights;
|
||||
} else {
|
||||
// Ignore pixels where the normal is empty, just copy the light color.
|
||||
denoised_rgb = input_light.rgb;
|
||||
}
|
||||
|
||||
imageStore(dest_light, ivec3(atlas_pos, lightmap_slice), vec4(denoised_rgb, input_light.a));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
167
engine/modules/lightmapper_rd/lm_raster.glsl
Normal file
167
engine/modules/lightmapper_rd/lm_raster.glsl
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#[vertex]
|
||||
|
||||
#version 450
|
||||
|
||||
#VERSION_DEFINES
|
||||
|
||||
#include "lm_common_inc.glsl"
|
||||
|
||||
layout(location = 0) out vec3 vertex_interp;
|
||||
layout(location = 1) out vec3 normal_interp;
|
||||
layout(location = 2) out vec2 uv_interp;
|
||||
layout(location = 3) out vec3 barycentric;
|
||||
layout(location = 4) flat out uvec3 vertex_indices;
|
||||
layout(location = 5) flat out vec3 face_normal;
|
||||
layout(location = 6) flat out uint fragment_action;
|
||||
|
||||
layout(push_constant, std430) uniform Params {
|
||||
vec2 atlas_size;
|
||||
vec2 uv_offset;
|
||||
vec3 to_cell_size;
|
||||
uint base_triangle;
|
||||
vec3 to_cell_offset;
|
||||
float bias;
|
||||
ivec3 grid_size;
|
||||
uint pad2;
|
||||
}
|
||||
params;
|
||||
|
||||
void main() {
|
||||
uint triangle_idx = params.base_triangle + gl_VertexIndex / 3;
|
||||
uint triangle_subidx = gl_VertexIndex % 3;
|
||||
|
||||
vertex_indices = triangles.data[triangle_idx].indices;
|
||||
|
||||
uint vertex_idx;
|
||||
if (triangle_subidx == 0) {
|
||||
vertex_idx = vertex_indices.x;
|
||||
barycentric = vec3(1, 0, 0);
|
||||
} else if (triangle_subidx == 1) {
|
||||
vertex_idx = vertex_indices.y;
|
||||
barycentric = vec3(0, 1, 0);
|
||||
} else {
|
||||
vertex_idx = vertex_indices.z;
|
||||
barycentric = vec3(0, 0, 1);
|
||||
}
|
||||
|
||||
vertex_interp = vertices.data[vertex_idx].position;
|
||||
uv_interp = vertices.data[vertex_idx].uv;
|
||||
normal_interp = vec3(vertices.data[vertex_idx].normal_xy, vertices.data[vertex_idx].normal_z);
|
||||
|
||||
face_normal = -normalize(cross((vertices.data[vertex_indices.x].position - vertices.data[vertex_indices.y].position), (vertices.data[vertex_indices.x].position - vertices.data[vertex_indices.z].position)));
|
||||
|
||||
{
|
||||
const float FLAT_THRESHOLD = 0.99;
|
||||
const vec3 norm_a = vec3(vertices.data[vertex_indices.x].normal_xy, vertices.data[vertex_indices.x].normal_z);
|
||||
const vec3 norm_b = vec3(vertices.data[vertex_indices.y].normal_xy, vertices.data[vertex_indices.y].normal_z);
|
||||
const vec3 norm_c = vec3(vertices.data[vertex_indices.z].normal_xy, vertices.data[vertex_indices.z].normal_z);
|
||||
fragment_action = (dot(norm_a, norm_b) < FLAT_THRESHOLD || dot(norm_a, norm_c) < FLAT_THRESHOLD || dot(norm_b, norm_c) < FLAT_THRESHOLD) ? FA_SMOOTHEN_POSITION : FA_NONE;
|
||||
}
|
||||
|
||||
gl_Position = vec4((uv_interp + params.uv_offset) * 2.0 - 1.0, 0.0001, 1.0);
|
||||
}
|
||||
|
||||
#[fragment]
|
||||
|
||||
#version 450
|
||||
|
||||
#VERSION_DEFINES
|
||||
|
||||
#include "lm_common_inc.glsl"
|
||||
|
||||
layout(push_constant, std430) uniform Params {
|
||||
vec2 atlas_size;
|
||||
vec2 uv_offset;
|
||||
vec3 to_cell_size;
|
||||
uint base_triangle;
|
||||
vec3 to_cell_offset;
|
||||
float bias;
|
||||
ivec3 grid_size;
|
||||
uint pad2;
|
||||
}
|
||||
params;
|
||||
|
||||
layout(location = 0) in vec3 vertex_interp;
|
||||
layout(location = 1) in vec3 normal_interp;
|
||||
layout(location = 2) in vec2 uv_interp;
|
||||
layout(location = 3) in vec3 barycentric;
|
||||
layout(location = 4) in flat uvec3 vertex_indices;
|
||||
layout(location = 5) in flat vec3 face_normal;
|
||||
layout(location = 6) in flat uint fragment_action;
|
||||
|
||||
layout(location = 0) out vec4 position;
|
||||
layout(location = 1) out vec4 normal;
|
||||
layout(location = 2) out vec4 unocclude;
|
||||
|
||||
void main() {
|
||||
vec3 vertex_pos = vertex_interp;
|
||||
|
||||
if (fragment_action == FA_SMOOTHEN_POSITION) {
|
||||
// smooth out vertex position by interpolating its projection in the 3 normal planes (normal plane is created by vertex pos and normal)
|
||||
// because we don't want to interpolate inwards, normals found pointing inwards are pushed out.
|
||||
vec3 pos_a = vertices.data[vertex_indices.x].position;
|
||||
vec3 pos_b = vertices.data[vertex_indices.y].position;
|
||||
vec3 pos_c = vertices.data[vertex_indices.z].position;
|
||||
vec3 center = (pos_a + pos_b + pos_c) * 0.3333333;
|
||||
vec3 norm_a = vec3(vertices.data[vertex_indices.x].normal_xy, vertices.data[vertex_indices.x].normal_z);
|
||||
vec3 norm_b = vec3(vertices.data[vertex_indices.y].normal_xy, vertices.data[vertex_indices.y].normal_z);
|
||||
vec3 norm_c = vec3(vertices.data[vertex_indices.z].normal_xy, vertices.data[vertex_indices.z].normal_z);
|
||||
|
||||
{
|
||||
vec3 dir_a = normalize(pos_a - center);
|
||||
float d_a = dot(dir_a, norm_a);
|
||||
if (d_a < 0) {
|
||||
//pointing inwards
|
||||
norm_a = normalize(norm_a - dir_a * d_a);
|
||||
}
|
||||
}
|
||||
{
|
||||
vec3 dir_b = normalize(pos_b - center);
|
||||
float d_b = dot(dir_b, norm_b);
|
||||
if (d_b < 0) {
|
||||
//pointing inwards
|
||||
norm_b = normalize(norm_b - dir_b * d_b);
|
||||
}
|
||||
}
|
||||
{
|
||||
vec3 dir_c = normalize(pos_c - center);
|
||||
float d_c = dot(dir_c, norm_c);
|
||||
if (d_c < 0) {
|
||||
//pointing inwards
|
||||
norm_c = normalize(norm_c - dir_c * d_c);
|
||||
}
|
||||
}
|
||||
|
||||
float d_a = dot(norm_a, pos_a);
|
||||
float d_b = dot(norm_b, pos_b);
|
||||
float d_c = dot(norm_c, pos_c);
|
||||
|
||||
vec3 proj_a = vertex_pos - norm_a * (dot(norm_a, vertex_pos) - d_a);
|
||||
vec3 proj_b = vertex_pos - norm_b * (dot(norm_b, vertex_pos) - d_b);
|
||||
vec3 proj_c = vertex_pos - norm_c * (dot(norm_c, vertex_pos) - d_c);
|
||||
|
||||
vec3 smooth_position = proj_a * barycentric.x + proj_b * barycentric.y + proj_c * barycentric.z;
|
||||
|
||||
if (dot(face_normal, smooth_position) > dot(face_normal, vertex_pos)) { //only project outwards
|
||||
vertex_pos = smooth_position;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// unocclusion technique based on:
|
||||
// https://ndotl.wordpress.com/2018/08/29/baking-artifact-free-lightmaps/
|
||||
|
||||
/* compute texel size */
|
||||
vec3 delta_uv = max(abs(dFdx(vertex_interp)), abs(dFdy(vertex_interp)));
|
||||
float texel_size = max(delta_uv.x, max(delta_uv.y, delta_uv.z));
|
||||
texel_size *= sqrt(2.0); //expand to unit box edge length (again, worst case)
|
||||
|
||||
unocclude.xyz = face_normal;
|
||||
unocclude.w = texel_size;
|
||||
|
||||
//continued on lm_compute.glsl
|
||||
}
|
||||
|
||||
position = vec4(vertex_pos, 1.0);
|
||||
normal = vec4(normalize(normal_interp), 1.0);
|
||||
}
|
||||
73
engine/modules/lightmapper_rd/register_types.cpp
Normal file
73
engine/modules/lightmapper_rd/register_types.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.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 "register_types.h"
|
||||
|
||||
#include "lightmapper_rd.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "scene/3d/lightmapper.h"
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
static Lightmapper *create_lightmapper_rd() {
|
||||
return memnew(LightmapperRD);
|
||||
}
|
||||
#endif
|
||||
|
||||
void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/low_quality_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 32);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/medium_quality_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 128);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/high_quality_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 512);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/ultra_quality_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 2048);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_performance/max_rays_per_pass", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 32);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_performance/region_size", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 512);
|
||||
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/low_quality_probe_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 64);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/medium_quality_probe_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 256);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/high_quality_probe_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 512);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 2048);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/bake_performance/max_rays_per_probe_pass", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64);
|
||||
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/denoising/denoiser", PROPERTY_HINT_ENUM, "JNLM,OIDN"), 0);
|
||||
#ifndef _3D_DISABLED
|
||||
GDREGISTER_CLASS(LightmapperRD);
|
||||
Lightmapper::create_gpu = create_lightmapper_rd;
|
||||
#endif
|
||||
}
|
||||
|
||||
void uninitialize_lightmapper_rd_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
39
engine/modules/lightmapper_rd/register_types.h
Normal file
39
engine/modules/lightmapper_rd/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef LIGHTMAPPER_RD_REGISTER_TYPES_H
|
||||
#define LIGHTMAPPER_RD_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_lightmapper_rd_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // LIGHTMAPPER_RD_REGISTER_TYPES_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue