Basic fast filtering implementation

This commit is contained in:
clayjohn 2020-02-20 15:27:34 -08:00
parent e0cc5209ff
commit 3e299aa225
12 changed files with 831 additions and 66 deletions

View file

@ -1037,23 +1037,28 @@
Lower-end override for [member rendering/quality/intended_usage/framebuffer_allocation] on mobile devices, due to performance concerns or driver support.
</member>
<member name="rendering/quality/reflection_atlas/reflection_count" type="int" setter="" getter="" default="64">
Number of cubemaps to store in the reflection atlas. The number of [ReflectionProbe]s in a scene will be limited by this amount. A higher number requires more VRAM.
</member>
<member name="rendering/quality/reflection_atlas/reflection_size" type="int" setter="" getter="" default="256">
<member name="rendering/quality/reflection_atlas/reflection_size" type="int" setter="" getter="" default="128">
Size of cubemap faces for [ReflectionProbe]s. A higher number requires more VRAM and may make reflection probe updating slower.
</member>
<member name="rendering/quality/reflection_atlas/reflection_size.mobile" type="int" setter="" getter="" default="128">
Lower-end override for [member rendering/quality/reflection_atlas/reflection_size] on mobile devices, due to performance concerns or driver support.
</member>
<member name="rendering/quality/reflections/fast_filter_high_quality" type="bool" setter="" getter="" default="false">
Use a higher quality variant of the fast filtering algorithm. Significantly slower than using default quality, but results in smoother reflections. Should only be used when the scene is especially detailed.
</member>
<member name="rendering/quality/reflections/ggx_samples" type="int" setter="" getter="" default="1024">
Sets the number of samples to take when using importance sampling for [Sky]s and [ReflectionProbe]s. A higher value will result in smoother, higher quality reflections, but increases time to calculate radiance maps. In general, fewer samples are needed for simpler, low dynamic range environments while more samples are needed for HDR environments and environments with a high level of detail.
</member>
<member name="rendering/quality/reflections/ggx_samples.mobile" type="int" setter="" getter="" default="128">
</member>
<member name="rendering/quality/reflections/ggx_samples_realtime" type="int" setter="" getter="" default="64">
</member>
<member name="rendering/quality/reflections/ggx_samples_realtime.mobile" type="int" setter="" getter="" default="16">
Lower-end override for [member rendering/quality/reflections/ggx_samples] on mobile devices, due to performance concerns or driver support.
</member>
<member name="rendering/quality/reflections/roughness_layers" type="int" setter="" getter="" default="6">
Limits the number of layers to use in radiance maps when using importance sampling. A lower number will be slightly faster and take up less VRAM.
</member>
<member name="rendering/quality/reflections/texture_array_reflections" type="bool" setter="" getter="" default="true">
If [code]true[/code], uses texture arrays instead of mipmaps for reflection probes and panorama backgrounds (sky). This reduces jitter noise on reflections, but costs more performance and memory.
If [code]true[/code], uses texture arrays instead of mipmaps for reflection probes and panorama backgrounds (sky). This reduces jitter noise and upscaling artifacts on reflections, but is significantly slower to compute and uses [member rendering/quality/reflections/roughness_layers] times more memory.
</member>
<member name="rendering/quality/reflections/texture_array_reflections.mobile" type="bool" setter="" getter="" default="false">
Lower-end override for [member rendering/quality/reflections/texture_array_reflections] on mobile devices, due to performance concerns or driver support.

View file

@ -4,7 +4,7 @@
Captures its surroundings to create reflections.
</brief_description>
<description>
Capture its surroundings as a dual parabolid image, and stores versions of it with increasing levels of blur to simulate different material roughnesses.
Captures its surroundings as a cubemap, and stores versions of it with increasing levels of blur to simulate different material roughnesses.
The [ReflectionProbe] is used to create high-quality reflections at the cost of performance. It can be combined with [GIProbe]s and Screen Space Reflections to achieve high quality reflections. [ReflectionProbe]s render all objects within their [member cull_mask], so updating them can be quite expensive. It is best to update them once with the important static objects and then leave them.
</description>
<tutorials>
@ -52,7 +52,7 @@
</members>
<constants>
<constant name="UPDATE_ONCE" value="0" enum="UpdateMode">
Update the probe once on the next frame.
Update the probe once on the next frame. The corresponding radiance map will be generated over the following six frames. This is slower to update than [constant UPDATE_ALWAYS] but can result in higher quality reflections.
</constant>
<constant name="UPDATE_ALWAYS" value="1" enum="UpdateMode">
Update the probe every frame. This is needed when you want to capture dynamic objects. However, it results in an increased render time. Use [constant UPDATE_ONCE] whenever possible.

View file

@ -12,6 +12,7 @@
</methods>
<members>
<member name="process_mode" type="int" setter="set_process_mode" getter="get_process_mode" enum="Sky.ProcessMode" default="0">
Sets the method for generating the radiance map from the sky. The radiance map is a cubemap with increasingly blurry versions of the sky corresponding to different levels of roughness. Radiance maps can be expensive to calculate. See [enum ProcessMode] for options.
</member>
<member name="radiance_size" type="int" setter="set_radiance_size" getter="get_radiance_size" enum="Sky.RadianceSize" default="2">
The [Sky]'s radiance map size. The higher the radiance map size, the more detailed the lighting from the [Sky] will be.
@ -45,8 +46,11 @@
Represents the size of the [enum RadianceSize] enum.
</constant>
<constant name="PROCESS_MODE_QUALITY" value="0" enum="ProcessMode">
Uses high quality importance sampling to process the radiance map. In general, this results in much higher quality than [constant PROCESS_MODE_REALTIME] but takes much longer to generate. This should not be used if you plan on changing the sky at runtime.
</constant>
<constant name="PROCESS_MODE_REALTIME" value="1" enum="ProcessMode">
Uses the fast filtering algorithm to process the radiance map. In general this results in lower quality, but substantially faster run times.
[b]Note:[/b] The fast filtering algorithm is limited to 128x128 cubemaps, so [member radiance_size] must be set to [constant RADIANCE_SIZE_128].
</constant>
</constants>
</class>

File diff suppressed because one or more lines are too long

View file

@ -30,6 +30,8 @@
#include "rasterizer_effects_rd.h"
#include "core/os/os.h"
#include "core/project_settings.h"
#include "cubemap_coeffs.h"
static _FORCE_INLINE_ void store_transform_3x3(const Basis &p_basis, float *p_array) {
p_array[0] = p_basis.elements[0][0];
@ -750,6 +752,44 @@ void RasterizerEffectsRD::roughness_limit(RID p_source_normal, RID p_roughness,
RD::get_singleton()->compute_list_end();
}
void RasterizerEffectsRD::cubemap_downsample(RID p_source_cubemap, bool p_source_is_panorama, RID p_dest_cubemap, const Size2i &p_size) {
cubemap_downsampler.push_constant.face_size = p_size.x;
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, cubemap_downsampler.pipelines[p_source_is_panorama ? CUBEMAP_DOWNSAMPLER_SOURCE_PANORAMA : CUBEMAP_DOWNSAMPLER_SOURCE_CUBEMAP]);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_texture(p_source_cubemap), 0);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_dest_cubemap), 1);
int x_groups = (p_size.x - 1) / 8 + 1;
int y_groups = (p_size.y - 1) / 8 + 1;
RD::get_singleton()->compute_list_set_push_constant(compute_list, &cubemap_downsampler.push_constant, sizeof(CubemapDownsamplerPushConstant));
RD::get_singleton()->compute_list_dispatch(compute_list, x_groups, y_groups, 6); // one z_group for each face
RD::get_singleton()->compute_list_end();
}
void RasterizerEffectsRD::cubemap_filter(RID p_source_cubemap, Vector<RID> p_dest_cubemap, bool p_use_array) {
int pipeline = p_use_array ? FILTER_MODE_HIGH_QUALITY_ARRAY : FILTER_MODE_HIGH_QUALITY;
pipeline = filter.use_high_quality ? pipeline : pipeline + 1;
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, filter.pipelines[pipeline]);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_texture(p_source_cubemap, true), 0);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, filter.uniform_set, 1);
for (int i = 0; i < p_dest_cubemap.size(); i++) {
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_dest_cubemap[i]), i + 2);
}
int x_groups = p_use_array ? 1792 : 342; // (128 * 128 * 7) / 64 : (128*128 + 64*64 + 32*32 + 16*16 + 8*8 + 4*4 + 2*2) / 64
RD::get_singleton()->compute_list_dispatch(compute_list, x_groups, 6, 1); // one y_group for each face
RD::get_singleton()->compute_list_end();
}
RasterizerEffectsRD::RasterizerEffectsRD() {
{
@ -930,7 +970,7 @@ RasterizerEffectsRD::RasterizerEffectsRD() {
}
{
// Initialize copier
// Initialize roughness limiter
Vector<String> shader_modes;
shader_modes.push_back("");
@ -941,6 +981,55 @@ RasterizerEffectsRD::RasterizerEffectsRD() {
roughness_limiter.pipeline = RD::get_singleton()->compute_pipeline_create(roughness_limiter.shader.version_get_shader(roughness_limiter.shader_version, 0));
}
{
//Initialize cubemap downsampler
Vector<String> cubemap_downsampler_modes;
cubemap_downsampler_modes.push_back("\n#define MODE_SOURCE_PANORAMA\n");
cubemap_downsampler_modes.push_back("\n#define MODE_SOURCE_CUBEMAP\n");
cubemap_downsampler.shader.initialize(cubemap_downsampler_modes);
cubemap_downsampler.shader_version = cubemap_downsampler.shader.version_create();
for (int i = 0; i < CUBEMAP_DOWNSAMPLER_SOURCE_MAX; i++) {
cubemap_downsampler.pipelines[i] = RD::get_singleton()->compute_pipeline_create(cubemap_downsampler.shader.version_get_shader(cubemap_downsampler.shader_version, i));
}
}
{
// Initialize cubemap filter
filter.use_high_quality = GLOBAL_GET("rendering/quality/reflections/fast_filter_high_quality");
Vector<String> cubemap_filter_modes;
cubemap_filter_modes.push_back("\n#define USE_HIGH_QUALITY\n");
cubemap_filter_modes.push_back("\n#define USE_LOW_QUALITY\n");
cubemap_filter_modes.push_back("\n#define USE_HIGH_QUALITY\n#define USE_TEXTURE_ARRAY\n");
cubemap_filter_modes.push_back("\n#define USE_LOW_QUALITY\n#define USE_TEXTURE_ARRAY\n");
filter.shader.initialize(cubemap_filter_modes);
filter.shader_version = filter.shader.version_create();
for (int i = 0; i < FILTER_MODE_MAX; i++) {
filter.pipelines[i] = RD::get_singleton()->compute_pipeline_create(filter.shader.version_get_shader(filter.shader_version, i));
}
if (filter.use_high_quality) {
filter.coefficient_buffer = RD::get_singleton()->storage_buffer_create(sizeof(high_quality_coeffs));
RD::get_singleton()->buffer_update(filter.coefficient_buffer, 0, sizeof(high_quality_coeffs), &high_quality_coeffs[0], false);
} else {
filter.coefficient_buffer = RD::get_singleton()->storage_buffer_create(sizeof(low_quality_coeffs));
RD::get_singleton()->buffer_update(filter.coefficient_buffer, 0, sizeof(low_quality_coeffs), &low_quality_coeffs[0], false);
}
Vector<RD::Uniform> uniforms;
{
RD::Uniform u;
u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
u.binding = 0;
u.ids.push_back(filter.coefficient_buffer);
uniforms.push_back(u);
}
filter.uniform_set = RD::get_singleton()->uniform_set_create(uniforms, filter.shader.version_get_shader(filter.shader_version, filter.use_high_quality ? 0 : 1), 1);
}
RD::SamplerState sampler;
sampler.mag_filter = RD::SAMPLER_FILTER_LINEAR;
sampler.min_filter = RD::SAMPLER_FILTER_LINEAR;
@ -987,4 +1076,7 @@ RasterizerEffectsRD::~RasterizerEffectsRD() {
ssao.gather_shader.version_free(ssao.gather_shader_version);
ssao.blur_shader.version_free(ssao.blur_shader_version);
roughness_limiter.shader.version_free(roughness_limiter.shader_version);
cubemap_downsampler.shader.version_free(cubemap_downsampler.shader_version);
filter.shader.version_free(filter.shader_version);
RD::get_singleton()->free(filter.coefficient_buffer);
}

View file

@ -36,6 +36,8 @@
#include "servers/visual/rasterizer_rd/shaders/blur.glsl.gen.h"
#include "servers/visual/rasterizer_rd/shaders/bokeh_dof.glsl.gen.h"
#include "servers/visual/rasterizer_rd/shaders/copy.glsl.gen.h"
#include "servers/visual/rasterizer_rd/shaders/cubemap_downsampler.glsl.gen.h"
#include "servers/visual/rasterizer_rd/shaders/cubemap_filter.glsl.gen.h"
#include "servers/visual/rasterizer_rd/shaders/cubemap_roughness.glsl.gen.h"
#include "servers/visual/rasterizer_rd/shaders/luminance_reduce.glsl.gen.h"
#include "servers/visual/rasterizer_rd/shaders/roughness_limiter.glsl.gen.h"
@ -355,6 +357,45 @@ class RasterizerEffectsRD {
} roughness_limiter;
enum CubemapDownsamplerSource {
CUBEMAP_DOWNSAMPLER_SOURCE_PANORAMA,
CUBEMAP_DOWNSAMPLER_SOURCE_CUBEMAP,
CUBEMAP_DOWNSAMPLER_SOURCE_MAX
};
struct CubemapDownsamplerPushConstant {
uint32_t face_size;
float pad[3];
};
struct CubemapDownsampler {
CubemapDownsamplerPushConstant push_constant;
CubemapDownsamplerShaderRD shader;
RID shader_version;
RID pipelines[CUBEMAP_DOWNSAMPLER_SOURCE_MAX];
} cubemap_downsampler;
enum CubemapFilterMode {
FILTER_MODE_HIGH_QUALITY,
FILTER_MODE_LOW_QUALITY,
FILTER_MODE_HIGH_QUALITY_ARRAY,
FILTER_MODE_LOW_QUALITY_ARRAY,
FILTER_MODE_MAX,
};
struct CubemapFilter {
CubemapFilterShaderRD shader;
RID shader_version;
RID pipelines[FILTER_MODE_MAX];
RID uniform_set;
RID coefficient_buffer;
bool use_high_quality;
} filter;
RID default_sampler;
RID default_mipmap_sampler;
RID index_buffer;
@ -424,6 +465,8 @@ public:
void generate_ssao(RID p_depth_buffer, RID p_normal_buffer, const Size2i &p_depth_buffer_size, RID p_depth_mipmaps_texture, const Vector<RID> &depth_mipmaps, RID p_ao1, bool p_half_size, RID p_ao2, RID p_upscale_buffer, float p_intensity, float p_radius, float p_bias, const CameraMatrix &p_projection, VS::EnvironmentSSAOQuality p_quality, VS::EnvironmentSSAOBlur p_blur, float p_edge_sharpness);
void roughness_limit(RID p_source_normal, RID p_roughness, const Size2i &p_size, float p_curve);
void cubemap_downsample(RID p_source_cubemap, bool p_source_is_panorama, RID p_dest_cubemap, const Size2i &p_size);
void cubemap_filter(RID p_source_cubemap, Vector<RID> p_dest_cubemap, bool p_use_array);
RasterizerEffectsRD();
~RasterizerEffectsRD();

View file

@ -40,21 +40,29 @@ void RasterizerSceneRD::_clear_reflection_data(ReflectionData &rd) {
rd.layers.clear();
rd.radiance_base_cubemap = RID();
if (rd.downsampled_radiance_cubemap.is_valid()) {
RD::get_singleton()->free(rd.downsampled_radiance_cubemap);
}
rd.downsampled_radiance_cubemap = RID();
rd.downsampled_layer.mipmaps.clear();
rd.coefficient_buffer = RID();
}
void RasterizerSceneRD::_update_reflection_data(ReflectionData &rd, int p_size, int p_mipmaps, bool p_use_array, RID p_base_cube, int p_base_layer) {
void RasterizerSceneRD::_update_reflection_data(ReflectionData &rd, int p_size, int p_mipmaps, bool p_use_array, RID p_base_cube, int p_base_layer, bool p_low_quality) {
//recreate radiance and all data
int mipmaps = p_mipmaps;
uint32_t w = p_size, h = p_size;
if (p_use_array) {
int layers = p_low_quality ? 7 : roughness_layers;
for (int i = 0; i < roughness_layers; i++) {
for (int i = 0; i < layers; i++) {
ReflectionData::Layer layer;
uint32_t mmw = w;
uint32_t mmh = h;
layer.mipmaps.resize(mipmaps);
layer.views.resize(mipmaps);
for (int j = 0; j < mipmaps; j++) {
ReflectionData::Layer::Mipmap &mm = layer.mipmaps.write[j];
mm.size.width = mmw;
@ -66,6 +74,8 @@ void RasterizerSceneRD::_update_reflection_data(ReflectionData &rd, int p_size,
mm.framebuffers[k] = RD::get_singleton()->framebuffer_create(fbtex);
}
layer.views.write[j] = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), p_base_cube, p_base_layer + i * 6, j, RD::TEXTURE_SLICE_CUBEMAP);
mmw = MAX(1, mmw >> 1);
mmh = MAX(1, mmh >> 1);
}
@ -74,12 +84,14 @@ void RasterizerSceneRD::_update_reflection_data(ReflectionData &rd, int p_size,
}
} else {
mipmaps = p_low_quality ? 7 : mipmaps;
//regular cubemap, lower quality (aliasing, less memory)
ReflectionData::Layer layer;
uint32_t mmw = w;
uint32_t mmh = h;
layer.mipmaps.resize(roughness_layers);
for (int j = 0; j < roughness_layers; j++) {
layer.mipmaps.resize(mipmaps);
layer.views.resize(mipmaps);
for (int j = 0; j < mipmaps; j++) {
ReflectionData::Layer::Mipmap &mm = layer.mipmaps.write[j];
mm.size.width = mmw;
mm.size.height = mmh;
@ -90,6 +102,8 @@ void RasterizerSceneRD::_update_reflection_data(ReflectionData &rd, int p_size,
mm.framebuffers[k] = RD::get_singleton()->framebuffer_create(fbtex);
}
layer.views.write[j] = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), p_base_cube, p_base_layer, j, RD::TEXTURE_SLICE_CUBEMAP);
mmw = MAX(1, mmw >> 1);
mmh = MAX(1, mmh >> 1);
}
@ -98,13 +112,35 @@ void RasterizerSceneRD::_update_reflection_data(ReflectionData &rd, int p_size,
}
rd.radiance_base_cubemap = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), p_base_cube, p_base_layer, 0, RD::TEXTURE_SLICE_CUBEMAP);
RD::TextureFormat tf;
tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
tf.width = 64; // Always 64x64
tf.height = 64;
tf.type = RD::TEXTURE_TYPE_CUBE;
tf.array_layers = 6;
tf.mipmaps = 7;
tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
rd.downsampled_radiance_cubemap = RD::get_singleton()->texture_create(tf, RD::TextureView());
{
uint32_t mmw = 64;
uint32_t mmh = 64;
rd.downsampled_layer.mipmaps.resize(7);
for (int j = 0; j < rd.downsampled_layer.mipmaps.size(); j++) {
ReflectionData::DownsampleLayer::Mipmap &mm = rd.downsampled_layer.mipmaps.write[j];
mm.size.width = mmw;
mm.size.height = mmh;
mm.view = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), rd.downsampled_radiance_cubemap, 0, j, RD::TEXTURE_SLICE_CUBEMAP);
mmw = MAX(1, mmw >> 1);
mmh = MAX(1, mmh >> 1);
}
}
}
void RasterizerSceneRD::_create_reflection_from_panorama(ReflectionData &rd, RID p_panorama, bool p_quality) {
#ifndef _MSC_VER
#warning TODO, should probably use this algorithm instead. Volunteers? - https://www.ppsloan.org/publications/ggx_filtering.pdf / https://github.com/dariomanesku/cmft
#endif
if (sky_use_cubemap_array) {
if (p_quality) {
@ -115,19 +151,18 @@ void RasterizerSceneRD::_create_reflection_from_panorama(ReflectionData &rd, RID
}
}
} else {
//render to first mipmap
for (int j = 0; j < 6; j++) {
storage->get_effects()->cubemap_roughness(p_panorama, true, rd.layers[0].mipmaps[0].framebuffers[j], j, sky_ggx_samples_realtime, 0.0);
// Use fast filtering. Render directly to base mip levels
storage->get_effects()->cubemap_downsample(p_panorama, true, rd.downsampled_layer.mipmaps[0].view, rd.downsampled_layer.mipmaps[0].size);
for (int i = 1; i < rd.downsampled_layer.mipmaps.size(); i++) {
storage->get_effects()->cubemap_downsample(rd.downsampled_layer.mipmaps[i - 1].view, false, rd.downsampled_layer.mipmaps[i].view, rd.downsampled_layer.mipmaps[i].size);
}
//do the rest in other mipmaps and use cubemap itself as source
for (int i = 1; i < roughness_layers; i++) {
//render using a smaller mipmap, then copy to main layer
for (int j = 0; j < 6; j++) {
//storage->get_effects()->cubemap_roughness(rd.radiance_base_cubemap, false, rd.layers[0].mipmaps[i].framebuffers[0], j, sky_ggx_samples_realtime, float(i) / (rd.layers.size() - 1.0));
storage->get_effects()->cubemap_roughness(p_panorama, true, rd.layers[0].mipmaps[i].framebuffers[0], j, sky_ggx_samples_realtime, float(i) / (rd.layers.size() - 1.0));
storage->get_effects()->region_copy(rd.layers[0].mipmaps[i].views[0], rd.layers[i].mipmaps[0].framebuffers[j], Rect2());
}
Vector<RID> views;
for (int i = 0; i < rd.layers.size(); i++) {
views.push_back(rd.layers[i].views[0]);
}
storage->get_effects()->cubemap_filter(rd.downsampled_radiance_cubemap, views, true);
}
} else {
@ -139,16 +174,13 @@ void RasterizerSceneRD::_create_reflection_from_panorama(ReflectionData &rd, RID
}
}
} else {
// Use fast filtering. Render directly to each mip level
storage->get_effects()->cubemap_downsample(p_panorama, true, rd.downsampled_layer.mipmaps[0].view, rd.downsampled_layer.mipmaps[0].size);
for (int j = 0; j < 6; j++) {
storage->get_effects()->cubemap_roughness(p_panorama, true, rd.layers[0].mipmaps[0].framebuffers[j], j, sky_ggx_samples_realtime, 0);
}
for (int i = 1; i < rd.layers[0].mipmaps.size(); i++) {
for (int j = 0; j < 6; j++) {
storage->get_effects()->cubemap_roughness(rd.radiance_base_cubemap, false, rd.layers[0].mipmaps[i].framebuffers[j], j, sky_ggx_samples_realtime, float(i) / (rd.layers[0].mipmaps.size() - 1.0));
}
for (int i = 1; i < rd.downsampled_layer.mipmaps.size(); i++) {
storage->get_effects()->cubemap_downsample(rd.downsampled_layer.mipmaps[i - 1].view, false, rd.downsampled_layer.mipmaps[i].view, rd.downsampled_layer.mipmaps[i].size);
}
storage->get_effects()->cubemap_filter(rd.downsampled_radiance_cubemap, rd.layers[0].views, false);
}
}
}
@ -163,12 +195,18 @@ void RasterizerSceneRD::_create_reflection_from_base_mipmap(ReflectionData &rd,
storage->get_effects()->cubemap_roughness(rd.radiance_base_cubemap, false, rd.layers[i].mipmaps[0].framebuffers[p_cube_side], p_cube_side, sky_ggx_samples_quality, float(i) / (rd.layers.size() - 1.0));
}
} else {
//do the rest in other mipmaps and use cubemap itself as source
for (int i = 1; i < roughness_layers; i++) {
//render using a smaller mipmap, then copy to main layer
storage->get_effects()->cubemap_roughness(rd.radiance_base_cubemap, false, rd.layers[0].mipmaps[i].framebuffers[0], p_cube_side, sky_ggx_samples_realtime, float(i) / (rd.layers.size() - 1.0));
storage->get_effects()->region_copy(rd.layers[0].mipmaps[i].views[0], rd.layers[i].mipmaps[0].framebuffers[p_cube_side], Rect2());
storage->get_effects()->cubemap_downsample(rd.radiance_base_cubemap, false, rd.downsampled_layer.mipmaps[0].view, rd.downsampled_layer.mipmaps[0].size);
for (int i = 1; i < rd.downsampled_layer.mipmaps.size(); i++) {
storage->get_effects()->cubemap_downsample(rd.downsampled_layer.mipmaps[i - 1].view, false, rd.downsampled_layer.mipmaps[i].view, rd.downsampled_layer.mipmaps[i].size);
}
Vector<RID> views;
for (int i = 0; i < rd.layers.size(); i++) {
views.push_back(rd.layers[i].views[0]);
}
storage->get_effects()->cubemap_filter(rd.downsampled_radiance_cubemap, views, true);
}
} else {
@ -179,9 +217,12 @@ void RasterizerSceneRD::_create_reflection_from_base_mipmap(ReflectionData &rd,
}
} else {
for (int i = 1; i < rd.layers[0].mipmaps.size(); i++) {
storage->get_effects()->cubemap_roughness(rd.radiance_base_cubemap, false, rd.layers[0].mipmaps[i].framebuffers[p_cube_side], p_cube_side, sky_ggx_samples_realtime, float(i) / (rd.layers[0].mipmaps.size() - 1.0));
storage->get_effects()->cubemap_downsample(rd.radiance_base_cubemap, false, rd.downsampled_layer.mipmaps[0].view, rd.downsampled_layer.mipmaps[0].size);
for (int i = 1; i < rd.downsampled_layer.mipmaps.size(); i++) {
storage->get_effects()->cubemap_downsample(rd.downsampled_layer.mipmaps[i - 1].view, false, rd.downsampled_layer.mipmaps[i].view, rd.downsampled_layer.mipmaps[i].size);
}
storage->get_effects()->cubemap_filter(rd.downsampled_radiance_cubemap, rd.layers[0].views, false);
}
}
}
@ -224,6 +265,12 @@ void RasterizerSceneRD::sky_set_radiance_size(RID p_sky, int p_radiance_size) {
return;
}
sky->radiance_size = p_radiance_size;
if (sky->mode == VS::SKY_MODE_REALTIME && sky->radiance_size != 128) {
WARN_PRINT("Realtime Skies can only use a radiance size of 128. Radiance size will be set to 128 internally.");
sky->radiance_size = 128;
}
_sky_invalidate(sky);
if (sky->radiance.is_valid()) {
RD::get_singleton()->free(sky->radiance);
@ -241,7 +288,18 @@ void RasterizerSceneRD::sky_set_mode(RID p_sky, VS::SkyMode p_mode) {
}
sky->mode = p_mode;
if (sky->mode == VS::SKY_MODE_REALTIME && sky->radiance_size != 128) {
WARN_PRINT("Realtime Skies can only use a radiance size of 128. Radiance size will be set to 128 internally.");
sky_set_radiance_size(p_sky, 128);
}
_sky_invalidate(sky);
if (sky->radiance.is_valid()) {
RD::get_singleton()->free(sky->radiance);
sky->radiance = RID();
}
_clear_reflection_data(sky->reflection);
}
void RasterizerSceneRD::sky_set_texture(RID p_sky, RID p_panorama) {
@ -275,27 +333,24 @@ void RasterizerSceneRD::_update_dirty_skys() {
if (sky->radiance.is_null()) {
int mipmaps = Image::get_image_required_mipmaps(sky->radiance_size, sky->radiance_size, Image::FORMAT_RGBAH) + 1;
if (sky->mode != VS::SKY_MODE_QUALITY) {
//use less mipmaps
mipmaps = MIN(8, mipmaps);
}
uint32_t w = sky->radiance_size, h = sky->radiance_size;
int layers = sky->mode == VS::SKY_MODE_REALTIME ? 7 : roughness_layers;
if (sky_use_cubemap_array) {
//array (higher quality, 6 times more memory)
RD::TextureFormat tf;
tf.array_layers = roughness_layers * 6;
tf.array_layers = layers * 6;
tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
tf.type = RD::TEXTURE_TYPE_CUBE_ARRAY;
tf.mipmaps = mipmaps;
tf.width = w;
tf.height = h;
tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
sky->radiance = RD::get_singleton()->texture_create(tf, RD::TextureView());
_update_reflection_data(sky->reflection, sky->radiance_size, mipmaps, true, sky->radiance, 0);
_update_reflection_data(sky->reflection, sky->radiance_size, mipmaps, true, sky->radiance, 0, sky->mode == VS::SKY_MODE_REALTIME);
} else {
//regular cubemap, lower quality (aliasing, less memory)
@ -303,14 +358,14 @@ void RasterizerSceneRD::_update_dirty_skys() {
tf.array_layers = 6;
tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
tf.type = RD::TEXTURE_TYPE_CUBE;
tf.mipmaps = roughness_layers;
tf.mipmaps = MIN(mipmaps, layers);
tf.width = w;
tf.height = h;
tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
sky->radiance = RD::get_singleton()->texture_create(tf, RD::TextureView());
_update_reflection_data(sky->reflection, sky->radiance_size, mipmaps, false, sky->radiance, 0);
_update_reflection_data(sky->reflection, sky->radiance_size, MIN(mipmaps, layers), false, sky->radiance, 0, sky->mode == VS::SKY_MODE_REALTIME);
}
}
@ -591,6 +646,9 @@ void RasterizerSceneRD::reflection_atlas_set_size(RID p_ref_atlas, int p_reflect
return; //no changes
}
ra->size = p_reflection_size;
ra->count = p_reflection_count;
if (ra->reflection.is_valid()) {
//clear and invalidate everything
RD::get_singleton()->free(ra->reflection);
@ -673,17 +731,27 @@ bool RasterizerSceneRD::reflection_probe_instance_begin_render(RID p_instance, R
ERR_FAIL_COND_V(!atlas, false);
ReflectionProbeInstance *rpi = reflection_probe_instance_owner.getornull(p_instance);
ERR_FAIL_COND_V(!rpi, false);
if (storage->reflection_probe_get_update_mode(rpi->probe) == VS::REFLECTION_PROBE_UPDATE_ALWAYS && atlas->reflection.is_valid() && atlas->size != 128) {
WARN_PRINT("ReflectionProbes set to UPDATE_ALWAYS must have an atlas size of 128. Please update the atlas size in the ProjectSettings.");
reflection_atlas_set_size(p_reflection_atlas, 128, atlas->count);
}
if (atlas->reflection.is_null()) {
int mipmaps = MIN(roughness_layers, Image::get_image_required_mipmaps(atlas->size, atlas->size, Image::FORMAT_RGBAH) + 1);
mipmaps = storage->reflection_probe_get_update_mode(rpi->probe) == VS::REFLECTION_PROBE_UPDATE_ALWAYS ? 7 : mipmaps; // always use 7 mipmaps with real time filtering
{
//reflection atlas was unused, create:
RD::TextureFormat tf;
tf.array_layers = 6 * atlas->count;
tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
tf.type = RD::TEXTURE_TYPE_CUBE_ARRAY;
tf.mipmaps = roughness_layers;
tf.mipmaps = mipmaps;
tf.width = atlas->size;
tf.height = atlas->size;
tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
atlas->reflection = RD::get_singleton()->texture_create(tf, RD::TextureView());
}
@ -698,7 +766,7 @@ bool RasterizerSceneRD::reflection_probe_instance_begin_render(RID p_instance, R
}
atlas->reflections.resize(atlas->count);
for (int i = 0; i < atlas->count; i++) {
_update_reflection_data(atlas->reflections.write[i].data, atlas->size, roughness_layers, false, atlas->reflection, i * 6);
_update_reflection_data(atlas->reflections.write[i].data, atlas->size, mipmaps, false, atlas->reflection, i * 6, storage->reflection_probe_get_update_mode(rpi->probe) == VS::REFLECTION_PROBE_UPDATE_ALWAYS);
for (int j = 0; j < 6; j++) {
Vector<RID> fb;
fb.push_back(atlas->reflections.write[i].data.layers[0].mipmaps[0].views[j]);
@ -712,9 +780,6 @@ bool RasterizerSceneRD::reflection_probe_instance_begin_render(RID p_instance, R
atlas->depth_fb = RD::get_singleton()->framebuffer_create(fb);
}
ReflectionProbeInstance *rpi = reflection_probe_instance_owner.getornull(p_instance);
ERR_FAIL_COND_V(!rpi, false);
if (rpi->atlas_index == -1) {
for (int i = 0; i < atlas->reflections.size(); i++) {
if (atlas->reflections[i].owner.is_null()) {
@ -761,6 +826,13 @@ bool RasterizerSceneRD::reflection_probe_instance_postprocess_step(RID p_instanc
_create_reflection_from_base_mipmap(atlas->reflections.write[rpi->atlas_index].data, false, storage->reflection_probe_get_update_mode(rpi->probe) == VS::REFLECTION_PROBE_UPDATE_ONCE, rpi->processing_side);
if (storage->reflection_probe_get_update_mode(rpi->probe) == VS::REFLECTION_PROBE_UPDATE_ALWAYS) {
// Using real time reflections, all roughness is done in one step
rpi->rendering = false;
rpi->processing_side = 0;
return true;
}
rpi->processing_side++;
if (rpi->processing_side == 6) {
@ -814,7 +886,7 @@ void RasterizerSceneRD::shadow_atlas_set_size(RID p_atlas, int p_size) {
ERR_FAIL_COND(!shadow_atlas);
ERR_FAIL_COND(p_size < 0);
p_size = next_power_of_2(p_size);
p_size = MAX(p_size, 1 << roughness_layers);
p_size = MAX(p_size, 1 << roughness_layers); // TODO: use a number related to shadows rather than reflections
if (p_size == shadow_atlas->size)
return;
@ -3025,7 +3097,6 @@ RasterizerSceneRD::RasterizerSceneRD(RasterizerStorageRD *p_storage) {
roughness_layers = GLOBAL_GET("rendering/quality/reflections/roughness_layers");
sky_ggx_samples_quality = GLOBAL_GET("rendering/quality/reflections/ggx_samples");
sky_ggx_samples_realtime = GLOBAL_GET("rendering/quality/reflections/ggx_samples_realtime");
sky_use_cubemap_array = GLOBAL_GET("rendering/quality/reflections/texture_array_reflections");
// sky_use_cubemap_array = false;

View file

@ -85,15 +85,28 @@ private:
RID views[6];
Size2i size;
};
Vector<Mipmap> mipmaps; //per-face view
Vector<RID> views; // per-cubemap view
};
struct DownsampleLayer {
struct Mipmap {
RID view;
Size2i size;
};
Vector<Mipmap> mipmaps;
};
RID radiance_base_cubemap; //cubemap for first layer, first cubemap
RID downsampled_radiance_cubemap;
DownsampleLayer downsampled_layer;
RID coefficient_buffer;
Vector<Layer> layers;
};
void _clear_reflection_data(ReflectionData &rd);
void _update_reflection_data(ReflectionData &rd, int p_size, int p_mipmaps, bool p_use_array, RID p_base_cube, int p_base_layer);
void _update_reflection_data(ReflectionData &rd, int p_size, int p_mipmaps, bool p_use_array, RID p_base_cube, int p_base_layer, bool p_low_quality);
void _create_reflection_from_panorama(ReflectionData &rd, RID p_panorama, bool p_quality);
void _create_reflection_from_base_mipmap(ReflectionData &rd, bool p_use_arrays, bool p_quality, int p_cube_side);
void _update_reflection_mipmaps(ReflectionData &rd, bool p_quality);
@ -102,7 +115,7 @@ private:
struct Sky {
RID radiance;
RID uniform_set;
int radiance_size = 256;
int radiance_size = 128;
VS::SkyMode mode = VS::SKY_MODE_QUALITY;
RID panorama;
ReflectionData reflection;
@ -116,7 +129,6 @@ private:
void _update_dirty_skys();
uint32_t sky_ggx_samples_quality;
uint32_t sky_ggx_samples_realtime;
bool sky_use_cubemap_array;
mutable RID_Owner<Sky> sky_owner;

View file

@ -7,6 +7,8 @@ if 'RD_GLSL' in env['BUILDERS']:
env.RD_GLSL('canvas_occlusion.glsl');
env.RD_GLSL('blur.glsl');
env.RD_GLSL('cubemap_roughness.glsl');
env.RD_GLSL('cubemap_downsampler.glsl');
env.RD_GLSL('cubemap_filter.glsl');
env.RD_GLSL('scene_high_end.glsl');
env.RD_GLSL('sky.glsl');
env.RD_GLSL('tonemap.glsl');

View file

@ -0,0 +1,220 @@
// Copyright 2016 Activision Publishing, Inc.
//
// 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.
/* clang-format off */
[compute]
#version 450
VERSION_DEFINES
#define BLOCK_SIZE 8
layout(local_size_x = BLOCK_SIZE, local_size_y = BLOCK_SIZE, local_size_z = 1) in;
/* clang-format on */
#ifdef MODE_SOURCE_PANORAMA
layout(set = 0, binding = 0) uniform sampler2D source_panorama;
#endif
#ifdef MODE_SOURCE_CUBEMAP
layout(set = 0, binding = 0) uniform samplerCube source_cubemap;
#endif
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly imageCube dest_cubemap;
layout(push_constant, binding = 1, std430) uniform Params {
uint face_size;
}
params;
#define M_PI 3.14159265359
void get_dir_0(out vec3 dir, in float u, in float v) {
dir[0] = 1.0;
dir[1] = v;
dir[2] = -u;
}
void get_dir_1(out vec3 dir, in float u, in float v) {
dir[0] = -1.0;
dir[1] = v;
dir[2] = u;
}
void get_dir_2(out vec3 dir, in float u, in float v) {
dir[0] = u;
dir[1] = 1.0;
dir[2] = -v;
}
void get_dir_3(out vec3 dir, in float u, in float v) {
dir[0] = u;
dir[1] = -1.0;
dir[2] = v;
}
void get_dir_4(out vec3 dir, in float u, in float v) {
dir[0] = u;
dir[1] = v;
dir[2] = 1.0;
}
void get_dir_5(out vec3 dir, in float u, in float v) {
dir[0] = -u;
dir[1] = v;
dir[2] = -1.0;
}
float calcWeight(float u, float v) {
float val = u * u + v * v + 1.0;
return val * sqrt(val);
}
#ifdef MODE_SOURCE_PANORAMA
vec4 texturePanorama(vec3 normal, sampler2D pano) {
vec2 st = vec2(
atan(normal.x, -normal.z),
acos(normal.y));
if (st.x < 0.0)
st.x += M_PI * 2.0;
st /= vec2(M_PI * 2.0, M_PI);
return textureLod(pano, st, 0.0);
}
#endif
vec4 get_texture(vec3 p_dir) {
#ifdef MODE_SOURCE_PANORAMA
return texturePanorama(normalize(p_dir), source_panorama);
#else
return textureLod(source_cubemap, normalize(p_dir), 0.0);
#endif
}
void main() {
uvec3 id = gl_GlobalInvocationID;
uint face_size = params.face_size;
if (id.x < face_size && id.y < face_size) {
float inv_face_size = 1.0 / float(face_size);
float u0 = (float(id.x) * 2.0 + 1.0 - 0.75) * inv_face_size - 1.0;
float u1 = (float(id.x) * 2.0 + 1.0 + 0.75) * inv_face_size - 1.0;
float v0 = (float(id.y) * 2.0 + 1.0 - 0.75) * -inv_face_size + 1.0;
float v1 = (float(id.y) * 2.0 + 1.0 + 0.75) * -inv_face_size + 1.0;
float weights[4];
weights[0] = calcWeight(u0, v0);
weights[1] = calcWeight(u1, v0);
weights[2] = calcWeight(u0, v1);
weights[3] = calcWeight(u1, v1);
const float wsum = 0.5 / (weights[0] + weights[1] + weights[2] + weights[3]);
for (int i = 0; i < 4; i++) {
weights[i] = weights[i] * wsum + .125;
}
vec3 dir;
vec4 color;
switch (id.z) {
case 0:
get_dir_0(dir, u0, v0);
color = get_texture(dir) * weights[0];
get_dir_0(dir, u1, v0);
color += get_texture(dir) * weights[1];
get_dir_0(dir, u0, v1);
color += get_texture(dir) * weights[2];
get_dir_0(dir, u1, v1);
color += get_texture(dir) * weights[3];
break;
case 1:
get_dir_1(dir, u0, v0);
color = get_texture(dir) * weights[0];
get_dir_1(dir, u1, v0);
color += get_texture(dir) * weights[1];
get_dir_1(dir, u0, v1);
color += get_texture(dir) * weights[2];
get_dir_1(dir, u1, v1);
color += get_texture(dir) * weights[3];
break;
case 2:
get_dir_2(dir, u0, v0);
color = get_texture(dir) * weights[0];
get_dir_2(dir, u1, v0);
color += get_texture(dir) * weights[1];
get_dir_2(dir, u0, v1);
color += get_texture(dir) * weights[2];
get_dir_2(dir, u1, v1);
color += get_texture(dir) * weights[3];
break;
case 3:
get_dir_3(dir, u0, v0);
color = get_texture(dir) * weights[0];
get_dir_3(dir, u1, v0);
color += get_texture(dir) * weights[1];
get_dir_3(dir, u0, v1);
color += get_texture(dir) * weights[2];
get_dir_3(dir, u1, v1);
color += get_texture(dir) * weights[3];
break;
case 4:
get_dir_4(dir, u0, v0);
color = get_texture(dir) * weights[0];
get_dir_4(dir, u1, v0);
color += get_texture(dir) * weights[1];
get_dir_4(dir, u0, v1);
color += get_texture(dir) * weights[2];
get_dir_4(dir, u1, v1);
color += get_texture(dir) * weights[3];
break;
default:
get_dir_5(dir, u0, v0);
color = get_texture(dir) * weights[0];
get_dir_5(dir, u1, v0);
color += get_texture(dir) * weights[1];
get_dir_5(dir, u0, v1);
color += get_texture(dir) * weights[2];
get_dir_5(dir, u1, v1);
color += get_texture(dir) * weights[3];
break;
}
imageStore(dest_cubemap, ivec3(id), color);
}
}

View file

@ -0,0 +1,289 @@
// Copyright 2016 Activision Publishing, Inc.
//
// 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.
/* clang-format off */
[compute]
#version 450
VERSION_DEFINES
#define GROUP_SIZE 64
layout(local_size_x = GROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
/* clang-format on */
layout(set = 0, binding = 0) uniform samplerCube source_cubemap;
layout(rgba16f, set = 2, binding = 0) uniform restrict writeonly imageCube dest_cubemap0;
layout(rgba16f, set = 3, binding = 0) uniform restrict writeonly imageCube dest_cubemap1;
layout(rgba16f, set = 4, binding = 0) uniform restrict writeonly imageCube dest_cubemap2;
layout(rgba16f, set = 5, binding = 0) uniform restrict writeonly imageCube dest_cubemap3;
layout(rgba16f, set = 6, binding = 0) uniform restrict writeonly imageCube dest_cubemap4;
layout(rgba16f, set = 7, binding = 0) uniform restrict writeonly imageCube dest_cubemap5;
layout(rgba16f, set = 8, binding = 0) uniform restrict writeonly imageCube dest_cubemap6;
#ifdef USE_HIGH_QUALITY
#define NUM_TAPS 32
#else
#define NUM_TAPS 8
#endif
#define BASE_RESOLUTION 128
#ifdef USE_HIGH_QUALITY
layout(set = 1, binding = 0, std430) buffer restrict readonly Data {
vec4[7][5][3][24] coeffs;
}
data;
#else
layout(set = 1, binding = 0, std430) buffer restrict readonly Data {
vec4[7][5][6] coeffs;
}
data;
#endif
void get_dir(out vec3 dir, in vec2 uv, in uint face) {
switch (face) {
case 0:
dir = vec3(1.0, uv[1], -uv[0]);
break;
case 1:
dir = vec3(-1.0, uv[1], uv[0]);
break;
case 2:
dir = vec3(uv[0], 1.0, -uv[1]);
break;
case 3:
dir = vec3(uv[0], -1.0, uv[1]);
break;
case 4:
dir = vec3(uv[0], uv[1], 1.0);
break;
default:
dir = vec3(-uv[0], uv[1], -1.0);
break;
}
}
void main() {
// INPUT:
// id.x = the linear address of the texel (ignoring face)
// id.y = the face
// -> use to index output texture
// id.x = texel x
// id.y = texel y
// id.z = face
uvec3 id = gl_GlobalInvocationID;
// determine which texel this is
#ifndef USE_TEXTURE_ARRAY
int level = 0;
if (id.x < (128 * 128)) {
level = 0;
} else if (id.x < (128 * 128 + 64 * 64)) {
level = 1;
id.x -= (128 * 128);
} else if (id.x < (128 * 128 + 64 * 64 + 32 * 32)) {
level = 2;
id.x -= (128 * 128 + 64 * 64);
} else if (id.x < (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16)) {
level = 3;
id.x -= (128 * 128 + 64 * 64 + 32 * 32);
} else if (id.x < (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8)) {
level = 4;
id.x -= (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16);
} else if (id.x < (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 + 4 * 4)) {
level = 5;
id.x -= (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8);
} else if (id.x < (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 + 4 * 4 + 2 * 2)) {
level = 6;
id.x -= (128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 + 4 * 4);
} else {
return;
}
int res = BASE_RESOLUTION >> level;
#else // Using Texture Arrays so all levels are the same resolution
int res = BASE_RESOLUTION;
int level = int(id.x / (BASE_RESOLUTION * BASE_RESOLUTION));
id.x -= level * BASE_RESOLUTION * BASE_RESOLUTION;
#endif
// determine dir / pos for the texel
vec3 dir, adir, frameZ;
{
id.z = id.y;
id.y = id.x / res;
id.x -= id.y * res;
vec2 uv;
uv.x = (float(id.x) * 2.0 + 1.0) / float(res) - 1.0;
uv.y = -(float(id.y) * 2.0 + 1.0) / float(res) + 1.0;
get_dir(dir, uv, id.z);
frameZ = normalize(dir);
adir = abs(dir);
}
// GGX gather colors
vec4 color = vec4(0.0);
for (int axis = 0; axis < 3; axis++) {
const int otherAxis0 = 1 - (axis & 1) - (axis >> 1);
const int otherAxis1 = 2 - (axis >> 1);
float frameweight = (max(adir[otherAxis0], adir[otherAxis1]) - .75) / .25;
if (frameweight > 0.0) {
// determine frame
vec3 UpVector;
switch (axis) {
case 0:
UpVector = vec3(1, 0, 0);
break;
case 1:
UpVector = vec3(0, 1, 0);
break;
default:
UpVector = vec3(0, 0, 1);
break;
}
vec3 frameX = normalize(cross(UpVector, frameZ));
vec3 frameY = cross(frameZ, frameX);
// calculate parametrization for polynomial
float Nx = dir[otherAxis0];
float Ny = dir[otherAxis1];
float Nz = adir[axis];
float NmaxXY = max(abs(Ny), abs(Nx));
Nx /= NmaxXY;
Ny /= NmaxXY;
float theta;
if (Ny < Nx) {
if (Ny <= -0.999)
theta = Nx;
else
theta = Ny;
} else {
if (Ny >= 0.999)
theta = -Nx;
else
theta = -Ny;
}
float phi;
if (Nz <= -0.999)
phi = -NmaxXY;
else if (Nz >= 0.999)
phi = NmaxXY;
else
phi = Nz;
float theta2 = theta * theta;
float phi2 = phi * phi;
// sample
for (int iSuperTap = 0; iSuperTap < NUM_TAPS / 4; iSuperTap++) {
const int index = (NUM_TAPS / 4) * axis + iSuperTap;
#ifdef USE_HIGH_QUALITY
vec4 coeffsDir0[3];
vec4 coeffsDir1[3];
vec4 coeffsDir2[3];
vec4 coeffsLevel[3];
vec4 coeffsWeight[3];
for (int iCoeff = 0; iCoeff < 3; iCoeff++) {
coeffsDir0[iCoeff] = data.coeffs[level][0][iCoeff][index];
coeffsDir1[iCoeff] = data.coeffs[level][1][iCoeff][index];
coeffsDir2[iCoeff] = data.coeffs[level][2][iCoeff][index];
coeffsLevel[iCoeff] = data.coeffs[level][3][iCoeff][index];
coeffsWeight[iCoeff] = data.coeffs[level][4][iCoeff][index];
}
for (int iSubTap = 0; iSubTap < 4; iSubTap++) {
// determine sample attributes (dir, weight, level)
vec3 sample_dir = frameX * (coeffsDir0[0][iSubTap] + coeffsDir0[1][iSubTap] * theta2 + coeffsDir0[2][iSubTap] * phi2) + frameY * (coeffsDir1[0][iSubTap] + coeffsDir1[1][iSubTap] * theta2 + coeffsDir1[2][iSubTap] * phi2) + frameZ * (coeffsDir2[0][iSubTap] + coeffsDir2[1][iSubTap] * theta2 + coeffsDir2[2][iSubTap] * phi2);
float sample_level = coeffsLevel[0][iSubTap] + coeffsLevel[1][iSubTap] * theta2 + coeffsLevel[2][iSubTap] * phi2;
float sample_weight = coeffsWeight[0][iSubTap] + coeffsWeight[1][iSubTap] * theta2 + coeffsWeight[2][iSubTap] * phi2;
#else
vec4 coeffsDir0 = data.coeffs[level][0][index];
vec4 coeffsDir1 = data.coeffs[level][1][index];
vec4 coeffsDir2 = data.coeffs[level][2][index];
vec4 coeffsLevel = data.coeffs[level][3][index];
vec4 coeffsWeight = data.coeffs[level][4][index];
for (int iSubTap = 0; iSubTap < 4; iSubTap++) {
// determine sample attributes (dir, weight, level)
vec3 sample_dir = frameX * coeffsDir0[iSubTap] + frameY * coeffsDir1[iSubTap] + frameZ * coeffsDir2[iSubTap];
float sample_level = coeffsLevel[iSubTap];
float sample_weight = coeffsWeight[iSubTap];
#endif
sample_weight *= frameweight;
// adjust for jacobian
sample_dir /= max(abs(sample_dir[0]), max(abs(sample_dir[1]), abs(sample_dir[2])));
sample_level += 0.75 * log2(dot(sample_dir, sample_dir));
#ifndef USE_TEXTURE_ARRAY
sample_level += float(level) / 6.0; // Hack to increase the perceived roughness and reduce upscaling artifacts
#endif
// sample cubemap
color.xyz += textureLod(source_cubemap, normalize(sample_dir), sample_level).xyz * sample_weight;
color.w += sample_weight;
}
}
}
}
color /= color.w;
// write color
color.xyz = max(vec3(0.0), color.xyz);
color.w = 1.0;
switch (level) {
case 0:
imageStore(dest_cubemap0, ivec3(id), color);
break;
case 1:
imageStore(dest_cubemap1, ivec3(id), color);
break;
case 2:
imageStore(dest_cubemap2, ivec3(id), color);
break;
case 3:
imageStore(dest_cubemap3, ivec3(id), color);
break;
case 4:
imageStore(dest_cubemap4, ivec3(id), color);
break;
case 5:
imageStore(dest_cubemap5, ivec3(id), color);
break;
default:
imageStore(dest_cubemap6, ivec3(id), color);
break;
}
}

View file

@ -2316,9 +2316,8 @@ VisualServer::VisualServer() {
GLOBAL_DEF("rendering/quality/reflections/texture_array_reflections.mobile", false);
GLOBAL_DEF("rendering/quality/reflections/ggx_samples", 1024);
GLOBAL_DEF("rendering/quality/reflections/ggx_samples.mobile", 128);
GLOBAL_DEF("rendering/quality/reflections/ggx_samples_realtime", 64);
GLOBAL_DEF("rendering/quality/reflections/ggx_samples_realtime.mobile", 16);
GLOBAL_DEF("rendering/quality/reflection_atlas/reflection_size", 256);
GLOBAL_DEF("rendering/quality/reflections/fast_filter_high_quality", false);
GLOBAL_DEF("rendering/quality/reflection_atlas/reflection_size", 128);
GLOBAL_DEF("rendering/quality/reflection_atlas/reflection_size.mobile", 128);
GLOBAL_DEF("rendering/quality/reflection_atlas/reflection_count", 64);