EEVEE-Next: Horizon Scan: Use Spherical harmonics

This uses Spherical Harmonics to store the indirect lighting and
distant lighting visibility.

We can then reuse this information for each closure which divide
the cost of it by 2 or 3 in many cases, doing the scanning once.

The storage cost is higher than previous method, so we split the
resolution scaling to be independant of raytracing.

The spatial filtering has been split to its own pass for performance
reason. Upsampling now only uses 4 bilinearly interpolated samples
(instead of 9) using bilateral weights to avoid bleeding.

This also add a missing dot product (which soften the lighting
around corners) and fixes the blocky artifacts seen at lower
resolution.

Pull Request: https://projects.blender.org/blender/blender/pulls/118924
This commit is contained in:
Clément Foucault 2024-03-19 19:16:21 +01:00 committed by Clément Foucault
parent 893430a2c7
commit 23dce15f67
25 changed files with 810 additions and 488 deletions

View File

@ -198,6 +198,7 @@ class RENDER_PT_eevee_next_horizon_scan(RenderButtonsPanel, Panel):
col.prop(props, "horizon_quality", text="Precision")
col.prop(props, "horizon_thickness", text="Thickness")
col.prop(props, "horizon_bias", text="Bias")
col.prop(props, "horizon_resolution", text="Resolution")
class RENDER_PT_eevee_motion_blur(RenderButtonsPanel, Panel):

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 9
#define BLENDER_FILE_SUBVERSION 10
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -3056,6 +3056,14 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 10)) {
if (!DNA_struct_member_exists(fd->filesdna, "SceneEEVEE", "int", "gtao_resolution")) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
scene->eevee.gtao_resolution = 2;
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@ -498,6 +498,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_film_cryptomatte_post_comp.glsl
engines/eevee_next/shaders/eevee_film_frag.glsl
engines/eevee_next/shaders/eevee_film_lib.glsl
engines/eevee_next/shaders/eevee_filter_lib.glsl
engines/eevee_next/shaders/eevee_forward_lib.glsl
engines/eevee_next/shaders/eevee_gbuffer_lib.glsl
engines/eevee_next/shaders/eevee_gbuffer_closure_test.glsl
@ -511,6 +512,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_hiz_debug_frag.glsl
engines/eevee_next/shaders/eevee_hiz_update_comp.glsl
engines/eevee_next/shaders/eevee_horizon_denoise_comp.glsl
engines/eevee_next/shaders/eevee_horizon_resolve_comp.glsl
engines/eevee_next/shaders/eevee_horizon_scan_eval_lib.glsl
engines/eevee_next/shaders/eevee_horizon_scan_comp.glsl
engines/eevee_next/shaders/eevee_horizon_scan_lib.glsl

View File

@ -60,19 +60,14 @@ void RayTraceModule::sync()
GPUShader *sh = inst_.shaders.static_shader_get(RAY_TILE_COMPACT);
pass.init();
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.specialize_constant(sh, "resolution_scale", &data_.resolution_scale);
pass.shader_set(sh);
pass.bind_image("tile_raytrace_denoise_img", &tile_raytrace_denoise_tx_);
pass.bind_image("tile_raytrace_tracing_img", &tile_raytrace_tracing_tx_);
pass.bind_image("tile_horizon_denoise_img", &tile_horizon_denoise_tx_);
pass.bind_image("tile_horizon_tracing_img", &tile_horizon_tracing_tx_);
pass.bind_ssbo("raytrace_tracing_dispatch_buf", &raytrace_tracing_dispatch_buf_);
pass.bind_ssbo("raytrace_denoise_dispatch_buf", &raytrace_denoise_dispatch_buf_);
pass.bind_ssbo("horizon_tracing_dispatch_buf", &horizon_tracing_dispatch_buf_);
pass.bind_ssbo("horizon_denoise_dispatch_buf", &horizon_denoise_dispatch_buf_);
pass.bind_ssbo("raytrace_tracing_tiles_buf", &raytrace_tracing_tiles_buf_);
pass.bind_ssbo("raytrace_denoise_tiles_buf", &raytrace_denoise_tiles_buf_);
pass.bind_ssbo("horizon_tracing_tiles_buf", &horizon_tracing_tiles_buf_);
pass.bind_ssbo("horizon_denoise_tiles_buf", &horizon_denoise_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.dispatch(&tile_compact_dispatch_size_);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
@ -220,6 +215,24 @@ void RayTraceModule::sync()
pass.dispatch(raytrace_denoise_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
{
PassSimple &pass = horizon_schedule_ps_;
/* Reuse tile compaction shader but feed it with horizon scan specific buffers. */
GPUShader *sh = inst_.shaders.static_shader_get(RAY_TILE_COMPACT);
pass.init();
pass.specialize_constant(sh, "closure_index", 0);
pass.specialize_constant(sh, "resolution_scale", &data_.horizon_resolution_scale);
pass.shader_set(sh);
pass.bind_image("tile_raytrace_denoise_img", &tile_horizon_denoise_tx_);
pass.bind_image("tile_raytrace_tracing_img", &tile_horizon_tracing_tx_);
pass.bind_ssbo("raytrace_tracing_dispatch_buf", &horizon_tracing_dispatch_buf_);
pass.bind_ssbo("raytrace_denoise_dispatch_buf", &horizon_denoise_dispatch_buf_);
pass.bind_ssbo("raytrace_tracing_tiles_buf", &horizon_tracing_tiles_buf_);
pass.bind_ssbo("raytrace_denoise_tiles_buf", &horizon_denoise_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.dispatch(&horizon_schedule_dispatch_size_);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
}
{
PassSimple &pass = horizon_setup_ps_;
pass.init();
@ -232,39 +245,64 @@ void RayTraceModule::sync()
pass.bind_image("out_normal_img", &downsampled_in_normal_tx_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(&tracing_dispatch_size_);
pass.dispatch(&horizon_tracing_dispatch_size_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
{
PassSimple &pass = horizon_scan_ps_;
pass.init();
GPUShader *sh = inst_.shaders.static_shader_get(HORIZON_SCAN);
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.shader_set(sh);
pass.bind_image("horizon_radiance_img", &horizon_radiance_tx_);
pass.bind_image("horizon_occlusion_img", &horizon_occlusion_tx_);
pass.bind_ssbo("tiles_coord_buf", &horizon_tracing_tiles_buf_);
pass.bind_texture("screen_radiance_tx", &downsampled_in_radiance_tx_);
pass.bind_texture("screen_normal_tx", &downsampled_in_normal_tx_);
pass.bind_image("horizon_radiance_0_img", &horizon_radiance_tx_[0]);
pass.bind_image("horizon_radiance_1_img", &horizon_radiance_tx_[1]);
pass.bind_image("horizon_radiance_2_img", &horizon_radiance_tx_[2]);
pass.bind_image("horizon_radiance_3_img", &horizon_radiance_tx_[3]);
pass.bind_ssbo("tiles_coord_buf", &horizon_tracing_tiles_buf_);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(horizon_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
{
PassSimple &pass = horizon_denoise_ps_;
pass.init();
GPUShader *sh = inst_.shaders.static_shader_get(HORIZON_DENOISE);
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.shader_set(sh);
pass.bind_texture("in_sh_0_tx", &horizon_radiance_tx_[0]);
pass.bind_texture("in_sh_1_tx", &horizon_radiance_tx_[1]);
pass.bind_texture("in_sh_2_tx", &horizon_radiance_tx_[2]);
pass.bind_texture("in_sh_3_tx", &horizon_radiance_tx_[3]);
pass.bind_texture("screen_normal_tx", &downsampled_in_normal_tx_);
pass.bind_image("out_sh_0_img", &horizon_radiance_denoised_tx_[0]);
pass.bind_image("out_sh_1_img", &horizon_radiance_denoised_tx_[1]);
pass.bind_image("out_sh_2_img", &horizon_radiance_denoised_tx_[2]);
pass.bind_image("out_sh_3_img", &horizon_radiance_denoised_tx_[3]);
pass.bind_ssbo("tiles_coord_buf", &horizon_tracing_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.hiz_buffer.front);
pass.dispatch(horizon_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
{
PassSimple &pass = horizon_resolve_ps_;
pass.init();
GPUShader *sh = inst_.shaders.static_shader_get(HORIZON_RESOLVE);
pass.shader_set(sh);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_image("horizon_radiance_img", &horizon_radiance_tx_);
pass.bind_image("horizon_occlusion_img", &horizon_occlusion_tx_);
pass.bind_image("radiance_img", &horizon_scan_output_tx_);
pass.bind_image("tile_mask_img", &tile_horizon_denoise_tx_);
pass.bind_texture("horizon_radiance_0_tx", &horizon_radiance_denoised_tx_[0]);
pass.bind_texture("horizon_radiance_1_tx", &horizon_radiance_denoised_tx_[1]);
pass.bind_texture("horizon_radiance_2_tx", &horizon_radiance_denoised_tx_[2]);
pass.bind_texture("horizon_radiance_3_tx", &horizon_radiance_denoised_tx_[3]);
pass.bind_texture("screen_normal_tx", &downsampled_in_normal_tx_);
pass.bind_image("closure0_img", &horizon_scan_output_tx_[0]);
pass.bind_image("closure1_img", &horizon_scan_output_tx_[1]);
pass.bind_image("closure2_img", &horizon_scan_output_tx_[2]);
pass.bind_ssbo("tiles_coord_buf", &horizon_denoise_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
@ -297,19 +335,26 @@ RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
bool use_horizon_scan = options.screen_trace_max_roughness < 1.0f;
const int resolution_scale = max_ii(1, power_of_2_max_i(options.resolution_scale));
const int horizon_resolution_scale = max_ii(
1, power_of_2_max_i(inst_.scene->eevee.gtao_resolution));
const int2 extent = inst_.film.render_extent_get();
const int2 tracing_res = math::divide_ceil(extent, int2(resolution_scale));
const int2 tracing_res_horizon = math::divide_ceil(extent, int2(horizon_resolution_scale));
const int2 dummy_extent(1, 1);
const int2 group_size(RAYTRACE_GROUP_SIZE);
const int2 denoise_tiles = divide_ceil(extent, group_size);
const int2 raytrace_tiles = divide_ceil(tracing_res, group_size);
const int2 raytrace_tiles_horizon = divide_ceil(tracing_res_horizon, group_size);
const int denoise_tile_count = denoise_tiles.x * denoise_tiles.y;
const int raytrace_tile_count = raytrace_tiles.x * raytrace_tiles.y;
const int raytrace_tile_count_horizon = raytrace_tiles_horizon.x * raytrace_tiles_horizon.y;
tile_classify_dispatch_size_ = int3(denoise_tiles, 1);
horizon_schedule_dispatch_size_ = int3(divide_ceil(raytrace_tiles_horizon, group_size), 1);
tile_compact_dispatch_size_ = int3(divide_ceil(raytrace_tiles, group_size), 1);
tracing_dispatch_size_ = int3(divide_ceil(tracing_res, group_size), 1);
tracing_dispatch_size_ = int3(raytrace_tiles, 1);
horizon_tracing_dispatch_size_ = int3(raytrace_tiles_horizon, 1);
/* TODO(fclem): Use real max closure count from shader. */
const int closure_count = 3;
@ -317,15 +362,16 @@ RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
eGPUTextureUsage usage_rw = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
tile_raytrace_denoise_tx_.ensure_2d_array(format, denoise_tiles, closure_count, usage_rw);
tile_raytrace_tracing_tx_.ensure_2d_array(format, raytrace_tiles, closure_count, usage_rw);
tile_horizon_denoise_tx_.ensure_2d_array(format, denoise_tiles, closure_count, usage_rw);
tile_horizon_tracing_tx_.ensure_2d_array(format, raytrace_tiles, closure_count, usage_rw);
/* Kept as 2D array for compatibility with the tile compaction shader. */
tile_horizon_denoise_tx_.ensure_2d_array(format, denoise_tiles, 1, usage_rw);
tile_horizon_tracing_tx_.ensure_2d_array(format, raytrace_tiles_horizon, 1, usage_rw);
tile_raytrace_denoise_tx_.clear(uint4(0u));
tile_raytrace_tracing_tx_.clear(uint4(0u));
tile_horizon_denoise_tx_.clear(uint4(0u));
tile_horizon_tracing_tx_.clear(uint4(0u));
horizon_tracing_tiles_buf_.resize(ceil_to_multiple_u(raytrace_tile_count, 512));
horizon_tracing_tiles_buf_.resize(ceil_to_multiple_u(raytrace_tile_count_horizon, 512));
horizon_denoise_tiles_buf_.resize(ceil_to_multiple_u(denoise_tile_count, 512));
raytrace_tracing_tiles_buf_.resize(ceil_to_multiple_u(raytrace_tile_count, 512));
raytrace_denoise_tiles_buf_.resize(ceil_to_multiple_u(denoise_tile_count, 512));
@ -344,6 +390,9 @@ RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
data_.full_resolution = extent;
data_.full_resolution_inv = 1.0f / float2(extent);
data_.horizon_resolution_scale = horizon_resolution_scale;
data_.horizon_resolution_bias = int2(inst_.sampling.rng_2d_get(SAMPLING_RAYTRACE_V) *
horizon_resolution_scale);
/* TODO(fclem): Eventually all uniform data is setup here. */
inst_.uniform_data.push_update();
@ -352,15 +401,9 @@ RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
DRW_stats_group_start("Raytracing");
if (use_horizon_scan) {
downsampled_in_radiance_tx_.acquire(tracing_res, RAYTRACE_RADIANCE_FORMAT, usage_rw);
downsampled_in_normal_tx_.acquire(tracing_res, GPU_RGBA8, usage_rw);
const bool has_active_closure = active_closures != CLOSURE_NONE;
screen_radiance_front_tx_ = screen_radiance_front_tx;
inst_.manager->submit(horizon_setup_ps_, render_view);
}
if (active_closures != CLOSURE_NONE) {
if (has_active_closure) {
inst_.manager->submit(tile_classify_ps_);
}
@ -375,12 +418,47 @@ RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
screen_radiance_front_tx,
screen_radiance_persmat,
main_view,
render_view,
use_horizon_scan);
render_view);
}
downsampled_in_radiance_tx_.release();
downsampled_in_normal_tx_.release();
if (has_active_closure) {
if (use_horizon_scan) {
DRW_stats_group_start("Horizon Scan");
screen_radiance_front_tx_ = screen_radiance_front_tx;
downsampled_in_radiance_tx_.acquire(tracing_res_horizon, RAYTRACE_RADIANCE_FORMAT, usage_rw);
downsampled_in_normal_tx_.acquire(tracing_res_horizon, GPU_RGB10_A2, usage_rw);
horizon_radiance_tx_[0].acquire(tracing_res_horizon, GPU_RGBA16F, usage_rw);
horizon_radiance_denoised_tx_[0].acquire(tracing_res_horizon, GPU_RGBA16F, usage_rw);
for (int i : IndexRange(1, 3)) {
horizon_radiance_tx_[i].acquire(tracing_res_horizon, GPU_RGBA8, usage_rw);
horizon_radiance_denoised_tx_[i].acquire(tracing_res_horizon, GPU_RGBA8, usage_rw);
}
for (int i : IndexRange(3)) {
horizon_scan_output_tx_[i] = result.closures[i].get();
}
horizon_tracing_dispatch_buf_.clear_to_zero();
horizon_denoise_dispatch_buf_.clear_to_zero();
inst_.manager->submit(horizon_schedule_ps_);
inst_.manager->submit(horizon_setup_ps_, render_view);
inst_.manager->submit(horizon_scan_ps_, render_view);
inst_.manager->submit(horizon_denoise_ps_, render_view);
inst_.manager->submit(horizon_resolve_ps_, render_view);
for (int i : IndexRange(4)) {
horizon_radiance_tx_[i].release();
horizon_radiance_denoised_tx_[i].release();
}
downsampled_in_radiance_tx_.release();
downsampled_in_normal_tx_.release();
DRW_stats_group_end();
}
}
DRW_stats_group_end();
@ -397,8 +475,7 @@ RayTraceResultTexture RayTraceModule::trace(
const float4x4 &screen_radiance_persmat,
/* TODO(fclem): Maybe wrap these two in some other class. */
View &main_view,
View &render_view,
bool use_horizon_scan)
View &render_view)
{
RayTraceBuffer::DenoiseBuffer *denoise_buf = &rt_buffer.closures[closure_index];
@ -452,8 +529,6 @@ RayTraceResultTexture RayTraceModule::trace(
/* Ray setup. */
raytrace_tracing_dispatch_buf_.clear_to_zero();
raytrace_denoise_dispatch_buf_.clear_to_zero();
horizon_tracing_dispatch_buf_.clear_to_zero();
horizon_denoise_dispatch_buf_.clear_to_zero();
inst_.manager->submit(tile_compact_ps_);
{
@ -555,20 +630,6 @@ RayTraceResultTexture RayTraceModule::trace(
denoise_variance_tx_.release();
if (use_horizon_scan) {
horizon_occlusion_tx_.acquire(tracing_res, GPU_R8, usage_rw);
horizon_radiance_tx_.acquire(tracing_res, RAYTRACE_RADIANCE_FORMAT, usage_rw);
inst_.manager->submit(horizon_scan_ps_, render_view);
horizon_scan_output_tx_ = result.get();
inst_.manager->submit(horizon_denoise_ps_, render_view);
horizon_occlusion_tx_.release();
horizon_radiance_tx_.release();
}
DRW_stats_group_end();
return result;

View File

@ -120,16 +120,20 @@ class RayTraceModule {
draw::PassSimple denoise_spatial_ps_ = {"DenoiseSpatial"};
draw::PassSimple denoise_temporal_ps_ = {"DenoiseTemporal"};
draw::PassSimple denoise_bilateral_ps_ = {"DenoiseBilateral"};
draw::PassSimple horizon_schedule_ps_ = {"HorizonScan.Schedule"};
draw::PassSimple horizon_setup_ps_ = {"HorizonScan.Setup"};
draw::PassSimple horizon_scan_ps_ = {"HorizonScan.Trace"};
draw::PassSimple horizon_denoise_ps_ = {"HorizonScan.Denoise"};
draw::PassSimple horizon_resolve_ps_ = {"HorizonScan.Resolve"};
/** Dispatch with enough tiles for the whole screen. */
int3 tile_classify_dispatch_size_ = int3(1);
/** Dispatch with enough tiles for the tile mask. */
int3 tile_compact_dispatch_size_ = int3(1);
int3 horizon_schedule_dispatch_size_ = int3(1);
/** Dispatch with enough tiles for the tracing resolution. */
int3 tracing_dispatch_size_ = int3(1);
int3 horizon_tracing_dispatch_size_ = int3(1);
/** 2D tile mask to check which unused adjacent tile we need to clear and which tile we need to
* dispatch for each work type. */
Texture tile_raytrace_denoise_tx_ = {"tile_raytrace_denoise_tx_"};
@ -145,7 +149,7 @@ class RayTraceModule {
/** Indirect dispatch denoise full-resolution tiles. */
DispatchIndirectBuf horizon_denoise_dispatch_buf_ = {"horizon_denoise_dispatch_buf_"};
/** Pointer to the texture to store the result of horizon scan in. */
GPUTexture *horizon_scan_output_tx_ = nullptr;
GPUTexture *horizon_scan_output_tx_[3] = {nullptr};
/** Tile buffer that contains tile coordinates. */
RayTraceTileBuf raytrace_tracing_tiles_buf_ = {"raytrace_tracing_tiles_buf_"};
RayTraceTileBuf raytrace_denoise_tiles_buf_ = {"raytrace_denoise_tiles_buf_"};
@ -157,10 +161,9 @@ class RayTraceModule {
TextureFromPool ray_time_tx_ = {"ray_data_tx"};
/** Texture containing the ray hit radiance (tracing-res). */
TextureFromPool ray_radiance_tx_ = {"ray_radiance_tx"};
/** Texture containing the horizon visibility mask. */
TextureFromPool horizon_occlusion_tx_ = {"horizon_occlusion_tx_"};
/** Texture containing the horizon local radiance. */
TextureFromPool horizon_radiance_tx_ = {"horizon_radiance_tx_"};
TextureFromPool horizon_radiance_tx_[4] = {{"horizon_radiance_tx_"}};
TextureFromPool horizon_radiance_denoised_tx_[4] = {{"horizon_radiance_denoised_tx_"}};
/** Texture containing the input screen radiance but re-projected. */
TextureFromPool downsampled_in_radiance_tx_ = {"downsampled_in_radiance_tx_"};
/** Texture containing the view space normal. The BSDF normal is arbitrarily chosen. */
@ -241,8 +244,7 @@ class RayTraceModule {
const float4x4 &screen_radiance_persmat,
/* TODO(fclem): Maybe wrap these two in some other class. */
View &main_view,
View &render_view,
bool use_horizon_scan);
View &render_view);
};
/** \} */

View File

@ -119,6 +119,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_hiz_update_layer";
case HORIZON_DENOISE:
return "eevee_horizon_denoise";
case HORIZON_RESOLVE:
return "eevee_horizon_resolve";
case HORIZON_SCAN:
return "eevee_horizon_scan";
case HORIZON_SETUP:

View File

@ -73,6 +73,7 @@ enum eShaderType {
HIZ_DEBUG,
HORIZON_DENOISE,
HORIZON_RESOLVE,
HORIZON_SCAN,
HORIZON_SETUP,

View File

@ -1333,6 +1333,9 @@ struct RayTraceData {
int resolution_scale;
/** View space thickness the objects. */
float thickness;
/** Scale and bias to go from horizon-trace resolution to input resolution. */
int2 horizon_resolution_bias;
int horizon_resolution_scale;
/** Determine how fast the sample steps are getting bigger. */
float quality;
/** Maximum brightness during lighting evaluation. */

View File

@ -31,22 +31,15 @@ void main()
noise.y = utility_tx_fetch(utility_tx, vec2(texel), UTIL_BLUE_NOISE_LAYER).r;
noise = fract(noise + sampling_rng_2D_get(SAMPLING_AO_U));
ClosureOcclusion occlusion;
occlusion.N = vN;
HorizonScanResult scan = horizon_scan_eval(vP,
vN,
noise,
uniform_buf.ao.pixel_size,
uniform_buf.ao.distance,
uniform_buf.ao.thickness,
uniform_buf.ao.angle_bias,
10,
false);
HorizonScanContext ctx;
ctx.occlusion = occlusion;
horizon_scan_eval(vP,
ctx,
noise,
uniform_buf.ao.pixel_size,
uniform_buf.ao.distance,
uniform_buf.ao.thickness,
uniform_buf.ao.angle_bias,
10,
false);
imageStore(
out_ao_img, ivec3(texel, out_ao_img_layer_index), vec4(saturate(ctx.occlusion_result.r)));
imageStore(out_ao_img, ivec3(texel, out_ao_img_layer_index), vec4(saturate(scan.result)));
}

View File

@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Filtering utilities.
*/
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
/**
* Return the factor to filter_gaussian_weight. This is handy utility function to compute your
* gaussian parameter in a documented manner.
* - `linear_distance` is the distance at which the filter will have covered the given amount of
* `standard_deviation`. Must not be null.
* - `standard_deviation` is the shape of the bell. Higher values sharpens the filter.
*
* https://en.wikipedia.org/wiki/Standard_deviation#/media/File:Standard_deviation_diagram.svg
*
* Example: for a 5px 1d gaussian filter, one would set `linear_distance` of 2.5.
* `standard_deviation = 1.0` will cover 68% of the gaussian weight inside the 5px radius.
* `standard_deviation = 2.0` will cover 95% of the gaussian weight inside the 5px radius.
*/
float filter_gaussian_factor(float linear_distance, float standard_deviation)
{
/* Account for `filter_gaussian_factor` using `exp2` for speed (`exp(x) = exp2(x / log(2))`). */
const float log_2_inv = 1.442695041;
return log_2_inv * standard_deviation / square(linear_distance);
}
/**
* Gaussian distance weighting. Allow weighting based on distance without null weight whatever the
* distance. `factor` is supposed to be a scaling parameter given by `filter_gaussian_factor`.
*/
float filter_gaussian_weight(float factor, float square_distance)
{
/* Using exp2 since it is faster on GPU. `filter_gaussian_factor` account for that. */
return exp2(-factor * square_distance);
}
/**
* Planar distance weighting. Allow to weight based on geometric neighborhood.
*/
float filter_planar_weight(vec3 plane_N, vec3 plane_P, vec3 P, float scale)
{
vec4 plane_eq = vec4(plane_N, -dot(plane_N, plane_P));
float plane_distance = dot(plane_eq, vec4(P, 1.0));
return filter_gaussian_weight(scale, square(plane_distance));
}
/**
* Angle weighting. Mostly used for normals.
* Expects both normals to be normalized.
*/
float filter_angle_weight(vec3 center_N, vec3 sample_N)
{
float facing_ratio = dot(center_N, sample_N);
return saturate(pow8f(facing_ratio));
}

View File

@ -6,161 +6,103 @@
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_closure_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_filter_lib.glsl)
float bilateral_depth_weight(vec3 center_N, vec3 center_P, vec3 sample_P)
vec3 sample_normal_get(ivec2 texel, out bool is_processed)
{
vec4 center_plane_eq = vec4(center_N, -dot(center_N, center_P));
/* Only compare distance to the center plane formed by the normal. */
float depth_delta = dot(center_plane_eq, vec4(sample_P, 1.0));
/* TODO(fclem): Scene parameter. This is dependent on scene scale. */
const float scale = 10000.0;
float weight = exp2(-scale * square(depth_delta));
return weight;
vec4 normal = texelFetch(screen_normal_tx, texel, 0);
is_processed = (normal.w != 0.0);
return normal.xyz * 2.0 - 1.0;
}
float bilateral_spatial_weight(float sigma, vec2 offset_from_center)
float sample_weight_get(
vec3 center_N, vec3 center_P, ivec2 sample_texel, vec2 sample_uv, ivec2 sample_offset)
{
/* From https://github.com/tranvansang/bilateral-filter/blob/master/fshader.frag */
float fac = -1.0 / square(sigma);
/* Take two standard deviation. */
fac *= 2.0;
float weight = exp2(fac * length_squared(offset_from_center));
return weight;
ivec2 sample_texel_fullres = sample_texel * uniform_buf.raytrace.horizon_resolution_scale +
uniform_buf.raytrace.horizon_resolution_bias;
float sample_depth = texelFetch(hiz_tx, sample_texel_fullres, 0).r;
bool is_valid;
vec3 sample_N = sample_normal_get(sample_texel, is_valid);
vec3 sample_P = drw_point_screen_to_world(vec3(sample_uv, sample_depth));
if (!is_valid) {
return 0.0;
}
float gauss = filter_gaussian_factor(1.5, 1.5);
/* TODO(fclem): Scene parameter. 100.0 is dependent on scene scale. */
float depth_weight = filter_planar_weight(center_N, center_P, sample_P, 100.0);
float spatial_weight = filter_gaussian_weight(gauss, length_squared(vec2(sample_offset)));
float normal_weight = filter_angle_weight(center_N, sample_N);
return depth_weight * spatial_weight * normal_weight;
}
float bilateral_normal_weight(vec3 center_N, vec3 sample_N)
SphericalHarmonicL1 load_spherical_harmonic(ivec2 texel)
{
float facing_ratio = dot(center_N, sample_N);
float weight = saturate(pow8f(facing_ratio));
return weight;
}
/* In order to remove some more fireflies, "tone-map" the color samples during the accumulation. */
vec3 to_accumulation_space(vec3 color)
{
/* This 4 factor is to avoid killing too much energy. */
/* TODO(fclem): Parameter? */
color /= 4.0;
color = color / (1.0 + reduce_add(color));
return color;
}
vec3 from_accumulation_space(vec3 color)
{
color = color / (1.0 - reduce_add(color));
color *= 4.0;
return color;
}
vec3 load_normal(ivec2 texel)
{
return gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel).surface_N;
SphericalHarmonicL1 sh;
sh.L0.M0 = texelFetch(in_sh_0_tx, texel, 0);
sh.L1.Mn1 = texelFetch(in_sh_1_tx, texel, 0);
sh.L1.M0 = texelFetch(in_sh_2_tx, texel, 0);
sh.L1.Mp1 = texelFetch(in_sh_3_tx, texel, 0);
sh = spherical_harmonics_decompress(sh);
return sh;
}
void main()
{
const uint tile_size = RAYTRACE_GROUP_SIZE;
uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]);
ivec2 texel = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size);
ivec2 texel_fullres = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size);
ivec2 texel = (texel_fullres) / uniform_buf.raytrace.resolution_scale;
vec2 texel_size = 1.0 / vec2(textureSize(in_sh_0_tx, 0).xy);
ivec2 texel_fullres = texel * uniform_buf.raytrace.horizon_resolution_scale +
uniform_buf.raytrace.horizon_resolution_bias;
ivec2 extent = textureSize(gbuf_header_tx, 0).xy;
if (any(greaterThanEqual(texel_fullres, extent))) {
return;
}
vec2 center_uv = (vec2(texel_fullres) + 0.5) * uniform_buf.raytrace.full_resolution_inv;
float center_depth = texelFetch(depth_tx, texel_fullres, 0).r;
bool is_valid;
float center_depth = texelFetch(hiz_tx, texel_fullres, 0).r;
vec2 center_uv = vec2(texel) * texel_size;
vec3 center_P = drw_point_screen_to_world(vec3(center_uv, center_depth));
vec3 center_N = sample_normal_get(texel, is_valid);
if (center_depth == 1.0) {
/* Do not trace for background */
if (!is_valid) {
#if 0 /* This is not needed as the next stage doesn't do bilinear filtering. */
imageStore(out_sh_0_img, texel, vec4(0.0));
imageStore(out_sh_1_img, texel, vec4(0.0));
imageStore(out_sh_2_img, texel, vec4(0.0));
imageStore(out_sh_3_img, texel, vec4(0.0));
#endif
return;
}
ClosureUndetermined closure_center = gbuffer_read_bin(
gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel_fullres, closure_index);
if (closure_center.type == CLOSURE_NONE_ID) {
return;
}
vec3 center_N = closure_center.N;
float roughness = closure_apparent_roughness_get(closure_center);
float mix_fac = saturate(roughness * uniform_buf.raytrace.roughness_mask_scale -
uniform_buf.raytrace.roughness_mask_bias);
bool use_raytrace = mix_fac < 1.0;
bool use_horizon = mix_fac > 0.0;
if (use_horizon == false) {
return;
}
vec3 accum_radiance = vec3(0.0);
float accum_occlusion = 0.0;
SphericalHarmonicL1 accum_sh = spherical_harmonics_L1_new();
float accum_weight = 0.0;
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
ivec2 offset = ivec2(x, y);
ivec2 sample_texel = texel + ivec2(x, y);
ivec2 sample_texel_fullres = sample_texel * uniform_buf.raytrace.resolution_scale +
uniform_buf.raytrace.resolution_bias;
ivec3 sample_tile = ivec3(sample_texel_fullres / RAYTRACE_GROUP_SIZE, closure_index);
/* Make sure the sample has been processed and do not contain garbage data. */
if (imageLoad(tile_mask_img, sample_tile).r == 0u) {
continue;
/* 3x3 filter. */
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
ivec2 sample_offset = ivec2(x, y);
ivec2 sample_texel = texel + sample_offset;
vec2 sample_uv = (vec2(sample_texel) + 0.5) * texel_size;
float sample_weight = sample_weight_get(
center_N, center_P, sample_texel, sample_uv, sample_offset);
/* We need to avoid sampling if there no weight as the texture values could be undefined
* (is_valid is false). */
if (sample_weight > 0.0) {
SphericalHarmonicL1 sample_sh = load_spherical_harmonic(sample_texel);
accum_sh = spherical_harmonics_madd(sample_sh, sample_weight, accum_sh);
accum_weight += sample_weight;
}
float sample_depth = texelFetch(depth_tx, sample_texel_fullres, 0).r;
vec2 sample_uv = (vec2(sample_texel_fullres) + 0.5) *
uniform_buf.raytrace.full_resolution_inv;
vec3 sample_P = drw_point_screen_to_world(vec3(sample_uv, sample_depth));
/* Background case. */
if (sample_depth == 0.0) {
continue;
}
vec3 sample_N = load_normal(sample_texel_fullres);
float depth_weight = bilateral_depth_weight(center_N, center_P, sample_P);
float spatial_weight = bilateral_spatial_weight(1.5, vec2(offset));
float normal_weight = bilateral_normal_weight(center_N, sample_N);
float weight = depth_weight * spatial_weight * normal_weight;
vec3 radiance = imageLoad(horizon_radiance_img, sample_texel).rgb;
/* Do not gather unprocessed pixels. */
if (all(equal(radiance, FLT_11_11_10_MAX))) {
continue;
}
float occlusion = imageLoad(horizon_occlusion_img, sample_texel).r;
accum_radiance += to_accumulation_space(radiance) * weight;
accum_occlusion += occlusion * weight;
accum_weight += weight;
}
}
float occlusion = accum_occlusion * safe_rcp(accum_weight);
vec3 radiance = from_accumulation_space(accum_radiance * safe_rcp(accum_weight));
accum_sh = spherical_harmonics_mul(accum_sh, safe_rcp(accum_weight));
vec3 P = center_P;
vec3 N = center_N;
vec3 Ng = center_N;
vec3 V = drw_world_incident_vector(P);
/* Fallback to nearest light-probe. */
LightProbeSample samp = lightprobe_load(P, Ng, V);
vec3 radiance_probe = spherical_harmonics_evaluate_lambert(N, samp.volume_irradiance);
/* Apply missing distant lighting. */
radiance += occlusion * radiance_probe;
accum_sh = spherical_harmonics_compress(accum_sh);
vec4 radiance_horizon = vec4(radiance, 0.0);
vec4 radiance_raytrace = use_raytrace ? imageLoad(radiance_img, texel_fullres) : vec4(0.0);
vec4 radiance_mixed = mix(radiance_raytrace, radiance_horizon, mix_fac);
imageStore(radiance_img, texel_fullres, radiance_mixed);
imageStore(out_sh_0_img, texel, accum_sh.L0.M0);
imageStore(out_sh_1_img, texel, accum_sh.L1.Mn1);
imageStore(out_sh_2_img, texel, accum_sh.L1.M0);
imageStore(out_sh_3_img, texel, accum_sh.L1.Mp1);
}

View File

@ -0,0 +1,205 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(draw_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_closure_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_filter_lib.glsl)
vec3 sample_normal_get(ivec2 texel, out bool is_processed)
{
vec4 normal = texelFetch(screen_normal_tx, texel, 0);
is_processed = (normal.w != 0.0);
return drw_normal_view_to_world(normal.xyz * 2.0 - 1.0);
}
float sample_weight_get(vec3 center_N, vec3 center_P, ivec2 center_texel, ivec2 sample_offset)
{
ivec2 sample_texel = center_texel + sample_offset;
ivec2 sample_texel_fullres = sample_texel * uniform_buf.raytrace.horizon_resolution_scale +
uniform_buf.raytrace.horizon_resolution_bias;
vec2 sample_uv = (vec2(sample_texel_fullres) + 0.5) * uniform_buf.raytrace.full_resolution_inv;
float sample_depth = texelFetch(depth_tx, sample_texel_fullres, 0).r;
bool is_valid;
vec3 sample_N = sample_normal_get(sample_texel, is_valid);
vec3 sample_P = drw_point_screen_to_world(vec3(sample_uv, sample_depth));
if (!is_valid) {
return 0.0;
}
/* TODO(fclem): Scene parameter. 10000.0 is dependent on scene scale. */
float depth_weight = filter_planar_weight(center_N, center_P, sample_P, 10000.0);
float normal_weight = filter_angle_weight(center_N, sample_N);
return depth_weight * normal_weight;
}
SphericalHarmonicL1 load_spherical_harmonic(ivec2 texel, bool valid)
{
if (!valid) {
/* We need to avoid sampling if there no weight as the texture values could be undefined
* (is_valid is false). */
return spherical_harmonics_L1_new();
}
SphericalHarmonicL1 sh;
sh.L0.M0 = texelFetch(horizon_radiance_0_tx, texel, 0);
sh.L1.Mn1 = texelFetch(horizon_radiance_1_tx, texel, 0);
sh.L1.M0 = texelFetch(horizon_radiance_2_tx, texel, 0);
sh.L1.Mp1 = texelFetch(horizon_radiance_3_tx, texel, 0);
return spherical_harmonics_decompress(sh);
}
void main()
{
const uint tile_size = RAYTRACE_GROUP_SIZE;
uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]);
ivec2 texel_fullres = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size);
ivec2 texel = max(ivec2(0), texel_fullres - uniform_buf.raytrace.horizon_resolution_bias) /
uniform_buf.raytrace.horizon_resolution_scale;
ivec2 extent = textureSize(gbuf_header_tx, 0).xy;
if (any(greaterThanEqual(texel_fullres, extent))) {
return;
}
GBufferReader gbuf = gbuffer_read(
gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel_fullres);
if (gbuf.header == 0u) {
return;
}
vec2 center_uv = (vec2(texel_fullres) + 0.5) * uniform_buf.raytrace.full_resolution_inv;
float center_depth = texelFetch(depth_tx, texel_fullres, 0).r;
vec3 center_P = drw_point_screen_to_world(vec3(center_uv, center_depth));
vec3 center_N = gbuf.surface_N;
SphericalHarmonicL1 accum_sh;
if (uniform_buf.raytrace.horizon_resolution_scale == 1) {
accum_sh = load_spherical_harmonic(texel, true);
}
else {
vec2 interp = vec2(texel_fullres - texel * uniform_buf.raytrace.horizon_resolution_scale -
uniform_buf.raytrace.horizon_resolution_bias) /
vec2(uniform_buf.raytrace.horizon_resolution_scale);
vec4 interp4 = vec4(interp, 1.0 - interp);
vec4 bilinear_weight = interp4.zxzx * interp4.wwyy;
vec4 bilateral_weights;
bilateral_weights.x = sample_weight_get(center_N, center_P, texel, ivec2(0, 0));
bilateral_weights.y = sample_weight_get(center_N, center_P, texel, ivec2(1, 0));
bilateral_weights.z = sample_weight_get(center_N, center_P, texel, ivec2(0, 1));
bilateral_weights.w = sample_weight_get(center_N, center_P, texel, ivec2(1, 1));
vec4 weights = bilateral_weights * bilinear_weight;
SphericalHarmonicL1 sh_00 = load_spherical_harmonic(texel + ivec2(0, 0), weights.x > 0.0);
SphericalHarmonicL1 sh_10 = load_spherical_harmonic(texel + ivec2(1, 0), weights.y > 0.0);
SphericalHarmonicL1 sh_01 = load_spherical_harmonic(texel + ivec2(0, 1), weights.z > 0.0);
SphericalHarmonicL1 sh_11 = load_spherical_harmonic(texel + ivec2(1, 1), weights.w > 0.0);
/* Avoid another division at the end. Normalize the weights upfront. */
weights *= safe_rcp(reduce_add(weights));
accum_sh = spherical_harmonics_mul(sh_00, weights.x);
accum_sh = spherical_harmonics_madd(sh_10, weights.y, accum_sh);
accum_sh = spherical_harmonics_madd(sh_01, weights.z, accum_sh);
accum_sh = spherical_harmonics_madd(sh_11, weights.w, accum_sh);
}
vec3 P = center_P;
vec3 Ng = center_N;
vec3 V = drw_world_incident_vector(P);
LightProbeSample samp = lightprobe_load(P, Ng, V);
for (int i = 0; i < GBUFFER_LAYER_MAX && i < gbuf.closure_count; i++) {
ClosureUndetermined cl = gbuffer_closure_get(gbuf, i);
float roughness = closure_apparent_roughness_get(cl);
float mix_fac = saturate(roughness * uniform_buf.raytrace.roughness_mask_scale -
uniform_buf.raytrace.roughness_mask_bias);
bool use_raytrace = mix_fac < 1.0;
bool use_horizon = mix_fac > 0.0;
if (!use_horizon) {
continue;
}
vec3 N = cl.N;
vec3 L;
switch (cl.type) {
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID:
L = lightprobe_reflection_dominant_dir(cl.N, V, roughness);
break;
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
L = lightprobe_refraction_dominant_dir(cl.N, V, to_closure_refraction(cl).ior, roughness);
break;
case CLOSURE_BSDF_TRANSLUCENT_ID:
L = -N;
break;
default:
L = N;
break;
}
vec3 vL = drw_normal_world_to_view(L);
/* Evaluate lighting from horizon scan. */
/* TODO(fclem): Evaluate depending on BSDF. */
vec3 radiance = spherical_harmonics_evaluate_lambert(vL, accum_sh);
/* Evaluate visibility from horizon scan. */
SphericalHarmonicL1 sh_visibility = spherical_harmonics_swizzle_wwww(accum_sh);
float occlusion = spherical_harmonics_evaluate_lambert(vL, sh_visibility).x;
/* FIXME(fclem): Tried to match the old occlusion look. I don't know why it's needed. */
occlusion *= 0.5;
/* TODO(fclem): Ideally, we should just combine both local and distant irradiance and evaluate
* once. Unfortunately, I couldn't find a way to do the same (1.0 - occlusion) with the
* spherical harmonic coefficients. */
float visibility = saturate(1.0 - occlusion);
/* Apply missing distant lighting. */
vec3 radiance_probe = spherical_harmonics_evaluate_lambert(L, samp.volume_irradiance);
radiance += visibility * radiance_probe;
int layer_index = gbuffer_closure_get_bin_index(gbuf, i);
vec4 radiance_horizon = vec4(radiance, 0.0);
vec4 radiance_raytrace = vec4(0.0);
if (use_raytrace) {
/* TODO(fclem): Layered texture. */
if (layer_index == 0) {
radiance_raytrace = imageLoad(closure0_img, texel_fullres);
}
else if (layer_index == 1) {
radiance_raytrace = imageLoad(closure1_img, texel_fullres);
}
else if (layer_index == 2) {
radiance_raytrace = imageLoad(closure2_img, texel_fullres);
}
}
vec4 radiance_mixed = mix(radiance_raytrace, radiance_horizon, mix_fac);
/* TODO(fclem): Layered texture. */
if (layer_index == 0) {
imageStore(closure0_img, texel_fullres, radiance_mixed);
}
else if (layer_index == 1) {
imageStore(closure1_img, texel_fullres, radiance_mixed);
}
else if (layer_index == 2) {
imageStore(closure2_img, texel_fullres, radiance_mixed);
}
}
}

View File

@ -6,7 +6,6 @@
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_horizon_scan_eval_lib.glsl)
void main()
@ -15,48 +14,52 @@ void main()
uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]);
ivec2 texel = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size);
ivec2 texel_fullres = texel * uniform_buf.raytrace.resolution_scale +
uniform_buf.raytrace.resolution_bias;
ivec2 texel_fullres = texel * uniform_buf.raytrace.horizon_resolution_scale +
uniform_buf.raytrace.horizon_resolution_bias;
/* Avoid tracing the outside border if dispatch is too big. */
ivec2 extent = textureSize(gbuf_header_tx, 0).xy;
if (any(greaterThanEqual(texel_fullres, extent))) {
if (any(greaterThanEqual(texel * uniform_buf.raytrace.horizon_resolution_scale, extent))) {
return;
}
/* Avoid loading texels outside texture range.
* This can happen even after the check above in non-power-of-2 textures. */
texel_fullres = min(texel_fullres, extent - 1);
/* Do not trace where nothing was rendered. */
if (texelFetch(gbuf_header_tx, texel_fullres, 0).r == 0u) {
#if 0 /* This is not needed as the next stage doesn't do bilinear filtering. */
imageStore(horizon_radiance_0_img, texel, vec4(0.0));
imageStore(horizon_radiance_1_img, texel, vec4(0.0));
imageStore(horizon_radiance_2_img, texel, vec4(0.0));
imageStore(horizon_radiance_3_img, texel, vec4(0.0));
#endif
return;
}
vec2 uv = (vec2(texel_fullres) + 0.5) * uniform_buf.raytrace.full_resolution_inv;
float depth = texelFetch(hiz_tx, texel_fullres, 0).r;
if (depth == 1.0) {
/* Do not trace for background */
imageStore(horizon_radiance_img, texel, vec4(FLT_11_11_10_MAX, 0.0));
return;
}
HorizonScanContext ctx;
ctx.closure = gbuffer_read_bin(
gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel_fullres, closure_index);
ctx.closure.N = drw_normal_world_to_view(ctx.closure.N);
if (ctx.closure.type == CLOSURE_NONE_ID) {
imageStore(horizon_radiance_img, texel, vec4(FLT_11_11_10_MAX, 0.0));
return;
}
vec3 vP = drw_point_screen_to_view(vec3(uv, depth));
vec3 vN = horizon_scan_sample_normal(uv);
vec2 noise = utility_tx_fetch(utility_tx, vec2(texel), UTIL_BLUE_NOISE_LAYER).rg;
noise = fract(noise + sampling_rng_2D_get(SAMPLING_AO_U));
horizon_scan_eval(vP,
ctx,
noise,
uniform_buf.ao.pixel_size,
1.0e16,
uniform_buf.ao.thickness,
uniform_buf.ao.angle_bias,
8,
false);
HorizonScanResult scan = horizon_scan_eval(vP,
vN,
noise,
uniform_buf.ao.pixel_size,
1.0e16,
uniform_buf.ao.thickness,
uniform_buf.ao.angle_bias,
8,
false);
imageStore(horizon_radiance_img, texel, ctx.closure_result);
imageStore(horizon_occlusion_img, texel, ctx.closure_result.wwww);
scan.result = spherical_harmonics_compress(scan.result);
imageStore(horizon_radiance_0_img, texel, scan.result.L0.M0);
imageStore(horizon_radiance_1_img, texel, scan.result.L1.Mn1);
imageStore(horizon_radiance_2_img, texel, scan.result.L1.M0);
imageStore(horizon_radiance_3_img, texel, scan.result.L1.Mp1);
}

View File

@ -22,7 +22,9 @@
#pragma BLENDER_REQUIRE(eevee_bxdf_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#if defined(MAT_DEFERRED) || defined(MAT_FORWARD)
#ifdef HORIZON_OCCLUSION
/* Do nothing. */
#elif defined(MAT_DEFERRED) || defined(MAT_FORWARD)
/* Enable AO node computation for material shaders. */
# define HORIZON_OCCLUSION
#else
@ -47,138 +49,6 @@ vec3 horizon_scan_sample_normal(vec2 uv)
#endif
}
/* Note: Expects all normals to be in view-space. */
struct HorizonScanContextCommon {
float N_angle;
float N_length;
uint bitmask;
float weight_slice;
float weight_accum;
vec3 light_slice;
vec4 light_accum;
};
struct HorizonScanContext {
#ifdef HORIZON_OCCLUSION
ClosureOcclusion occlusion;
HorizonScanContextCommon occlusion_common;
vec4 occlusion_result;
#endif
#ifdef HORIZON_CLOSURE
ClosureUndetermined closure;
HorizonScanContextCommon closure_common;
vec4 closure_result;
#endif
};
void horizon_scan_context_accumulation_reset(inout HorizonScanContext context)
{
#ifdef HORIZON_OCCLUSION
context.occlusion_common.light_accum = vec4(0.0);
context.occlusion_common.weight_accum = 0.0;
#endif
#ifdef HORIZON_CLOSURE
context.closure_common.light_accum = vec4(0.0);
context.closure_common.weight_accum = 0.0;
#endif
}
void horizon_scan_context_slice_start(
inout HorizonScanContextCommon context, vec3 vN, vec3 vV, vec3 vT, vec3 vB)
{
context.bitmask = 0u;
context.weight_slice = 0.0;
context.light_slice = vec3(0.0);
horizon_scan_projected_normal_to_plane_angle_and_length(
vN, vV, vT, vB, context.N_length, context.N_angle);
}
void horizon_scan_context_slice_start(inout HorizonScanContext context, vec3 vV, vec3 vT, vec3 vB)
{
#ifdef HORIZON_OCCLUSION
horizon_scan_context_slice_start(context.occlusion_common, context.occlusion.N, vV, vT, vB);
#endif
#ifdef HORIZON_CLOSURE
horizon_scan_context_slice_start(context.closure_common, context.closure.N, vV, vT, vB);
#endif
}
void horizon_scan_context_sample_finish(inout HorizonScanContextCommon context,
vec3 sample_radiance,
float sample_weight,
vec2 sample_theta,
float angle_bias)
{
/* Angular bias shrinks the visibility bitmask around the projected normal. */
sample_theta = (sample_theta - context.N_angle) * angle_bias;
uint sample_bitmask = horizon_scan_angles_to_bitmask(sample_theta);
sample_weight *= horizon_scan_bitmask_to_visibility_uniform(sample_bitmask & ~context.bitmask);
context.weight_slice += sample_weight;
context.light_slice += sample_radiance * sample_weight;
context.bitmask |= sample_bitmask;
}
void horizon_scan_context_sample_finish(
inout HorizonScanContext ctx, vec3 L, vec3 V, vec2 sample_uv, vec2 theta, float bias)
{
vec3 sample_radiance = horizon_scan_sample_radiance(sample_uv);
/* Take emitter surface normal into consideration. */
vec3 sample_normal = horizon_scan_sample_normal(sample_uv);
/* Discard back-facing samples.
* The paper suggests a smooth test which is not physically correct since we
* already consider the sample reflected radiance.
* Set the weight to allow energy conservation. If we modulate the radiance, we loose energy. */
float weight = step(dot(sample_normal, L), 0.0);
#ifdef HORIZON_OCCLUSION
horizon_scan_context_sample_finish(ctx.occlusion_common, sample_radiance, 1.0, theta, bias);
#endif
#ifdef HORIZON_CLOSURE
weight *= bsdf_lambert(ctx.closure.N, L);
horizon_scan_context_sample_finish(ctx.closure_common, sample_radiance, weight, theta, bias);
#endif
}
void horizon_scan_context_slice_finish(inout HorizonScanContextCommon context)
{
/* Use uniform visibility since this is what we use for near field lighting.
* Also the lighting we are going to mask is already containing the cosine lobe. */
float slice_occlusion = horizon_scan_bitmask_to_visibility_uniform(~context.bitmask);
/* Normalize radiance since BxDF is applied when merging direct and indirect light. */
context.light_slice *= safe_rcp(context.weight_slice) * (1.0 - slice_occlusion);
/* Correct normal not on plane (Eq. 8 of GTAO paper). */
context.light_accum += vec4(context.light_slice, slice_occlusion) * context.N_length;
context.weight_accum += context.N_length;
}
void horizon_scan_context_slice_finish(inout HorizonScanContext context)
{
#ifdef HORIZON_OCCLUSION
float occlusion = horizon_scan_bitmask_to_occlusion_cosine(context.occlusion_common.bitmask);
context.occlusion_common.light_accum += vec4(occlusion) * context.occlusion_common.N_length;
context.occlusion_common.weight_accum += context.occlusion_common.N_length;
#endif
#ifdef HORIZON_CLOSURE
horizon_scan_context_slice_finish(context.closure_common);
#endif
}
void horizon_scan_context_accumulation_finish(HorizonScanContextCommon context, out vec4 result)
{
result = context.light_accum * safe_rcp(context.weight_accum);
}
void horizon_scan_context_accumulation_finish(inout HorizonScanContext context)
{
#ifdef HORIZON_OCCLUSION
horizon_scan_context_accumulation_finish(context.occlusion_common, context.occlusion_result);
#endif
#ifdef HORIZON_CLOSURE
horizon_scan_context_accumulation_finish(context.closure_common, context.closure_result);
#endif
}
/**
* Returns the start and end point of a ray clipped to its intersection
* with a sphere.
@ -209,27 +79,38 @@ void horizon_scan_occluder_intersection_ray_sphere_clip(Ray ray,
P_exit = ray.origin + ray.direction * t_exit;
}
struct HorizonScanResult {
#ifdef HORIZON_OCCLUSION
float result;
#endif
#ifdef HORIZON_CLOSURE
SphericalHarmonicL1 result;
#endif
};
/**
* Scans the horizon in many directions and returns the indirect lighting radiance.
* Returned lighting is stored inside the context in `_accum` members already normalized.
* If `reversed` is set to true, the input normal must be negated.
*/
void horizon_scan_eval(vec3 vP,
inout HorizonScanContext context,
vec2 noise,
vec2 pixel_size,
float search_distance,
float global_thickness,
float angle_bias,
const int sample_count,
const bool reversed)
HorizonScanResult horizon_scan_eval(vec3 vP,
vec3 vN,
vec2 noise,
vec2 pixel_size,
float search_distance,
float global_thickness,
float angle_bias,
const int sample_count,
const bool reversed)
{
vec3 vV = drw_view_incident_vector(vP);
const int slice_len = 2;
vec2 v_dir = sample_circle(noise.x * (0.5 / float(slice_len)));
horizon_scan_context_accumulation_reset(context);
float weight_accum = 0.0;
float occlusion_accum = 0.0;
SphericalHarmonicL1 sh_accum = spherical_harmonics_L1_new();
for (int slice = 0; slice < slice_len; slice++) {
#if 0 /* For debug purpose. For when slice_len is greater than 2. */
@ -240,7 +121,18 @@ void horizon_scan_eval(vec3 vP,
vec3 vB = normalize(cross(vV, vec3(v_dir, 0.0)));
vec3 vT = cross(vB, vV);
horizon_scan_context_slice_start(context, vV, vT, vB);
/* Bitmask representing the occluded sectors on the slice. */
uint slice_bitmask = 0u;
/* Angle between vN and the horizon slice plane. */
float vN_angle;
/* Length of vN projected onto the horizon slice plane. */
float vN_length;
horizon_scan_projected_normal_to_plane_angle_and_length(vN, vV, vT, vB, vN_length, vN_angle);
SphericalHarmonicL1 sh_slice = spherical_harmonics_L1_new();
float weight_slice;
/* For both sides of the view vector. */
for (int side = 0; side < 2; side++) {
@ -306,15 +198,54 @@ void horizon_scan_eval(vec3 vP,
/* If we are tracing backward, the angles are negative. Swizzle to keep correct order. */
theta = (side == 0) ? theta.xy : -theta.yx;
horizon_scan_context_sample_finish(context, vL_front, vV, sample_uv, theta, angle_bias);
vec3 sample_radiance = horizon_scan_sample_radiance(sample_uv);
/* Take emitter surface normal into consideration. */
vec3 sample_normal = horizon_scan_sample_normal(sample_uv);
/* Discard back-facing samples.
* The 2 factor is to avoid loosing too much energy (which is something not
* explained in the paper...). Likely to be wrong, but we need a soft falloff. */
float facing_weight = saturate(-dot(sample_normal, vL_front) * 2.0);
float sample_weight = facing_weight * bsdf_lambert(vN, vL_front);
/* Angular bias shrinks the visibility bitmask around the projected normal. */
vec2 biased_theta = (theta - vN_angle) * angle_bias;
uint sample_bitmask = horizon_scan_angles_to_bitmask(biased_theta);
float weight_bitmask = horizon_scan_bitmask_to_visibility_uniform(sample_bitmask &
~slice_bitmask);
sample_radiance *= facing_weight * weight_bitmask;
/* Encoding using front sample direction gives better result than
* `normalize(vL_front + vL_back)` */
spherical_harmonics_encode_signal_sample(
vL_front, vec4(sample_radiance, weight_bitmask), sh_slice);
slice_bitmask |= sample_bitmask;
}
}
horizon_scan_context_slice_finish(context);
float occlusion_slice = horizon_scan_bitmask_to_occlusion_cosine(slice_bitmask);
/* Correct normal not on plane (Eq. 8 of GTAO paper). */
occlusion_accum += occlusion_slice * vN_length;
/* Use uniform visibility since this is what we use for near field lighting. */
sh_accum = spherical_harmonics_madd(sh_slice, vN_length, sh_accum);
weight_accum += vN_length;
/* Rotate 90 degrees. */
v_dir = orthogonal(v_dir);
}
horizon_scan_context_accumulation_finish(context);
float weight_rcp = safe_rcp(weight_accum);
HorizonScanResult res;
#ifdef HORIZON_OCCLUSION
res.result = occlusion_accum * weight_rcp;
#endif
#ifdef HORIZON_CLOSURE
/* Weight by area of the sphere. This is expected for correct SH evaluation. */
res.result = spherical_harmonics_mul(sh_accum, weight_rcp * 4.0 * M_PI);
#endif
return res;
}

View File

@ -16,8 +16,17 @@
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 texel_fullres = texel * uniform_buf.raytrace.resolution_scale +
uniform_buf.raytrace.resolution_bias;
ivec2 texel_fullres = texel * uniform_buf.raytrace.horizon_resolution_scale +
uniform_buf.raytrace.horizon_resolution_bias;
/* Return early for padding threads so we can use imageStoreFast. */
if (any(greaterThanEqual(texel, imageSize(out_radiance_img).xy))) {
return;
}
/* Avoid loading texels outside texture range. */
ivec2 extent = textureSize(gbuf_header_tx, 0).xy;
texel_fullres = min(texel_fullres, extent - 1);
/* Load Gbuffer. */
GBufferReader gbuf = gbuffer_read(
@ -25,12 +34,16 @@ void main()
/* Export normal. */
vec3 N = gbuf.surface_N;
/* Background has invalid data. */
/* FIXME: This is zero for opaque layer when we are processing the refraction layer. */
if (is_zero(N)) {
/* Avoid NaN. But should be fixed in any case. */
N = vec3(1.0, 0.0, 0.0);
}
vec3 vN = drw_normal_world_to_view(N);
imageStore(out_normal_img, texel, vec4(vN * 0.5 + 0.5, 0.0));
/* Tag processed pixel in the normal buffer for denoising speed. */
bool is_processed = gbuf.header != 0u;
imageStore(out_normal_img, texel, vec4(vN * 0.5 + 0.5, float(is_processed)));
/* Re-project radiance. */
vec2 uv = (vec2(texel_fullres) + 0.5) / vec2(textureSize(depth_tx, 0).xy);

View File

@ -22,34 +22,7 @@
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_closure_lib.glsl)
float bilateral_depth_weight(vec3 center_N, vec3 center_P, vec3 sample_P)
{
vec4 center_plane_eq = vec4(center_N, -dot(center_N, center_P));
/* Only compare distance to the center plane formed by the normal. */
float depth_delta = dot(center_plane_eq, vec4(sample_P, 1.0));
/* TODO(fclem): Scene parameter. This is dependent on scene scale. */
const float scale = 10000.0;
float weight = exp2(-scale * square(depth_delta));
return weight;
}
float bilateral_spatial_weight(float sigma, vec2 offset_from_center)
{
/* From https://github.com/tranvansang/bilateral-filter/blob/master/fshader.frag */
float fac = -1.0 / square(sigma);
/* Take two standard deviation. */
fac *= 2.0;
float weight = exp2(fac * length_squared(offset_from_center));
return weight;
}
float bilateral_normal_weight(vec3 center_N, vec3 sample_N)
{
float facing_ratio = dot(center_N, sample_N);
float weight = saturate(pow8f(facing_ratio));
return weight;
}
#pragma BLENDER_REQUIRE(eevee_filter_lib.glsl)
/* In order to remove some more fireflies, "tone-map" the color samples during the accumulation. */
vec3 to_accumulation_space(vec3 color)
@ -141,9 +114,12 @@ void main()
continue;
}
float depth_weight = bilateral_depth_weight(center_closure.N, center_P, sample_P);
float spatial_weight = bilateral_spatial_weight(filter_size, vec2(offset));
float normal_weight = bilateral_normal_weight(center_closure.N, sample_closure.N);
float gauss = filter_gaussian_factor(filter_size, 1.5);
/* TODO(fclem): Scene parameter. 10000.0 is dependent on scene scale. */
float depth_weight = filter_planar_weight(center_closure.N, center_P, sample_P, 10000.0);
float spatial_weight = filter_gaussian_weight(gauss, length_squared(vec2(offset)));
float normal_weight = filter_angle_weight(center_closure.N, sample_closure.N);
float weight = depth_weight * spatial_weight * normal_weight;
accum_radiance += to_accumulation_space(radiance) * weight;

View File

@ -14,7 +14,7 @@
#pragma BLENDER_REQUIRE(eevee_closure_lib.glsl)
shared uint tile_contains_ray_tracing[GBUFFER_LAYER_MAX];
shared uint tile_contains_horizon_scan[GBUFFER_LAYER_MAX];
shared uint tile_contains_horizon_scan;
/* Returns a blend factor between different tracing method. */
float ray_roughness_factor(RayTraceData raytrace, float roughness)
@ -26,9 +26,9 @@ void main()
{
if (gl_LocalInvocationIndex == 0u) {
/* Init shared variables. */
tile_contains_horizon_scan = 0;
for (int i = 0; i < GBUFFER_LAYER_MAX; i++) {
tile_contains_ray_tracing[i] = 0;
tile_contains_horizon_scan[i] = 0;
}
}
@ -51,7 +51,7 @@ void main()
/* We don't care about race condition here. */
if (ray_roughness_fac > 0.0) {
tile_contains_horizon_scan[i] = 1;
tile_contains_horizon_scan = 1;
}
if (ray_roughness_fac < 1.0) {
tile_contains_ray_tracing[i] = 1;
@ -70,11 +70,12 @@ void main()
imageStore(tile_raytrace_denoise_img, ivec3(denoise_tile_co, i), uvec4(1));
imageStore(tile_raytrace_tracing_img, ivec3(tracing_tile_co, i), uvec4(1));
}
}
if (tile_contains_horizon_scan[i] > 0) {
imageStore(tile_horizon_denoise_img, ivec3(denoise_tile_co, i), uvec4(1));
imageStore(tile_horizon_tracing_img, ivec3(tracing_tile_co, i), uvec4(1));
}
if (tile_contains_horizon_scan > 0) {
ivec2 tracing_tile_co = denoise_tile_co / uniform_buf.raytrace.horizon_resolution_scale;
imageStore(tile_horizon_denoise_img, ivec3(denoise_tile_co, 0), uvec4(1));
imageStore(tile_horizon_tracing_img, ivec3(tracing_tile_co, 0), uvec4(1));
}
}
}

View File

@ -19,13 +19,9 @@ void main()
if (all(equal(tile, ivec2(0)))) {
raytrace_tracing_dispatch_buf.num_groups_y = 1;
raytrace_denoise_dispatch_buf.num_groups_y = 1;
horizon_tracing_dispatch_buf.num_groups_y = 1;
horizon_denoise_dispatch_buf.num_groups_y = 1;
raytrace_tracing_dispatch_buf.num_groups_z = 1;
raytrace_denoise_dispatch_buf.num_groups_z = 1;
horizon_tracing_dispatch_buf.num_groups_z = 1;
horizon_denoise_dispatch_buf.num_groups_z = 1;
}
if (!in_image_range(tile, tile_raytrace_tracing_img)) {
@ -34,8 +30,6 @@ void main()
/* True if this tile is shooting and tracing rays. */
bool is_ray_tracing = imageLoad(tile_raytrace_tracing_img, ivec3(tile, closure_index)).r != 0;
/* True if this tile is using horizon scan. */
bool is_horizon_tracing = imageLoad(tile_horizon_tracing_img, ivec3(tile, closure_index)).r != 0;
/* True if an adjacent tile is ray tracing and will need this tile data for denoising. */
bool tile_is_ray_sampled = false;
@ -54,23 +48,6 @@ void main()
}
}
/* True if an adjacent tile is horizon tracing and will need this tile data for denoising. */
bool tile_is_horizon_sampled = false;
/* Could be optimized if that becomes an issue (3x3 cross gather + 3x3 "X" shape scatter). */
for (int x_tile = -1; x_tile <= 1; x_tile++) {
for (int y_tile = -1; y_tile <= 1; y_tile++) {
ivec2 tile_adj = tile + ivec2(x_tile, y_tile);
bool is_center_tile = (x_tile == 0 && y_tile == 0);
if (in_image_range(tile_adj, tile_horizon_tracing_img) && !is_center_tile) {
if (imageLoad(tile_horizon_tracing_img, ivec3(tile_adj, closure_index)).r != 0) {
/* This tile will sample the target tracing tile. Make sure it is cleared. */
tile_is_horizon_sampled = true;
break;
}
}
}
}
/* TODO(fclem): we might want to dispatch another type of shader only for clearing. */
if (is_ray_tracing || tile_is_ray_sampled) {
/* Dispatch trace resolution tracing tile. */
@ -78,15 +55,7 @@ void main()
raytrace_tracing_tiles_buf[tile_index] = packUvec2x16(uvec2(tile));
}
/* TODO(fclem): we might want to dispatch another type of shader only for clearing. */
if (is_horizon_tracing || tile_is_horizon_sampled) {
/* Dispatch trace resolution tracing tile. */
uint tile_index = atomicAdd(horizon_tracing_dispatch_buf.num_groups_x, 1u);
horizon_tracing_tiles_buf[tile_index] = packUvec2x16(uvec2(tile));
}
/* Dispatch denoise tiles. */
int resolution_scale = uniform_buf.raytrace.resolution_scale;
if (is_ray_tracing) {
for (int x_tile = 0; x_tile < resolution_scale; x_tile++) {
for (int y_tile = 0; y_tile < resolution_scale; y_tile++) {
@ -100,17 +69,4 @@ void main()
}
}
}
if (is_horizon_tracing) {
for (int x_tile = 0; x_tile < resolution_scale; x_tile++) {
for (int y_tile = 0; y_tile < resolution_scale; y_tile++) {
ivec2 tile_adj = tile * resolution_scale + ivec2(x_tile, y_tile);
if (in_image_range(tile_adj, tile_horizon_denoise_img)) {
if (imageLoad(tile_horizon_denoise_img, ivec3(tile_adj, closure_index)).r != 0) {
uint tile_index = atomicAdd(horizon_denoise_dispatch_buf.num_groups_x, 1u);
horizon_denoise_tiles_buf[tile_index] = packUvec2x16(uvec2(tile_adj));
}
}
}
}
}
}

View File

@ -102,6 +102,102 @@ struct SphericalHarmonicL2 {
SphericalHarmonicBandL2 L2;
};
SphericalHarmonicBandL0 spherical_harmonics_band_L0_new()
{
SphericalHarmonicBandL0 L0;
L0.M0 = vec4(0.0);
return L0;
}
SphericalHarmonicBandL1 spherical_harmonics_band_L1_new()
{
SphericalHarmonicBandL1 L1;
L1.Mn1 = vec4(0.0);
L1.M0 = vec4(0.0);
L1.Mp1 = vec4(0.0);
return L1;
}
SphericalHarmonicBandL2 spherical_harmonics_band_L2_new()
{
SphericalHarmonicBandL2 L2;
L2.Mn2 = vec4(0.0);
L2.Mn1 = vec4(0.0);
L2.M0 = vec4(0.0);
L2.Mp1 = vec4(0.0);
L2.Mp2 = vec4(0.0);
return L2;
}
SphericalHarmonicL0 spherical_harmonics_L0_new()
{
SphericalHarmonicL0 sh;
sh.L0 = spherical_harmonics_band_L0_new();
return sh;
}
SphericalHarmonicL1 spherical_harmonics_L1_new()
{
SphericalHarmonicL1 sh;
sh.L0 = spherical_harmonics_band_L0_new();
sh.L1 = spherical_harmonics_band_L1_new();
return sh;
}
SphericalHarmonicL2 spherical_harmonics_L2_new()
{
SphericalHarmonicL2 sh;
sh.L0 = spherical_harmonics_band_L0_new();
sh.L1 = spherical_harmonics_band_L1_new();
sh.L2 = spherical_harmonics_band_L2_new();
return sh;
}
SphericalHarmonicBandL0 spherical_harmonics_band_L0_swizzle_wwww(SphericalHarmonicBandL0 L0)
{
L0.M0 = L0.M0.wwww;
return L0;
}
SphericalHarmonicBandL1 spherical_harmonics_band_L1_swizzle_wwww(SphericalHarmonicBandL1 L1)
{
L1.Mn1 = L1.Mn1.wwww;
L1.M0 = L1.M0.wwww;
L1.Mp1 = L1.Mp1.wwww;
return L1;
}
SphericalHarmonicBandL2 spherical_harmonics_band_L2_swizzle_wwww(SphericalHarmonicBandL2 L2)
{
L2.Mn2 = L2.Mn2.wwww;
L2.Mn1 = L2.Mn1.wwww;
L2.M0 = L2.M0.wwww;
L2.Mp1 = L2.Mp1.wwww;
L2.Mp2 = L2.Mp2.wwww;
return L2;
}
SphericalHarmonicL0 spherical_harmonics_swizzle_wwww(SphericalHarmonicL0 sh)
{
sh.L0 = spherical_harmonics_band_L0_swizzle_wwww(sh.L0);
return sh;
}
SphericalHarmonicL1 spherical_harmonics_swizzle_wwww(SphericalHarmonicL1 sh)
{
sh.L0 = spherical_harmonics_band_L0_swizzle_wwww(sh.L0);
sh.L1 = spherical_harmonics_band_L1_swizzle_wwww(sh.L1);
return sh;
}
SphericalHarmonicL2 spherical_harmonics_swizzle_wwww(SphericalHarmonicL2 sh)
{
sh.L0 = spherical_harmonics_band_L0_swizzle_wwww(sh.L0);
sh.L1 = spherical_harmonics_band_L1_swizzle_wwww(sh.L1);
sh.L2 = spherical_harmonics_band_L2_swizzle_wwww(sh.L2);
return sh;
}
/** \} */
/* -------------------------------------------------------------------- */
@ -581,3 +677,33 @@ vec4 spherical_harmonics_dot(SphericalHarmonicL1 a, SphericalHarmonicL1 b)
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Compression
*
* Described by Josh Hobson in "The indirect Lighting Pipeline of God of War" p. 120
* \{ */
SphericalHarmonicL1 spherical_harmonics_compress(SphericalHarmonicL1 in_sh)
{
SphericalHarmonicL1 out_sh;
out_sh.L0 = in_sh.L0;
vec4 fac = safe_rcp(in_sh.L0.M0 * M_SQRT3);
out_sh.L1.Mn1 = (in_sh.L1.Mn1 * fac) * 0.5 + 0.5;
out_sh.L1.M0 = (in_sh.L1.M0 * fac) * 0.5 + 0.5;
out_sh.L1.Mp1 = (in_sh.L1.Mp1 * fac) * 0.5 + 0.5;
return out_sh;
}
SphericalHarmonicL1 spherical_harmonics_decompress(SphericalHarmonicL1 in_sh)
{
SphericalHarmonicL1 out_sh;
out_sh.L0 = in_sh.L0;
vec4 fac = in_sh.L0.M0 * M_SQRT3;
out_sh.L1.Mn1 = (in_sh.L1.Mn1 * 2.0 - 1.0) * fac;
out_sh.L1.M0 = (in_sh.L1.M0 * 2.0 - 1.0) * fac;
out_sh.L1.Mp1 = (in_sh.L1.Mp1 * 2.0 - 1.0) * fac;
return out_sh;
}
/** \} */

View File

@ -32,17 +32,12 @@ GPU_SHADER_CREATE_INFO(eevee_ray_tile_compact)
.typedef_source("draw_shader_shared.h")
.image_in(0, RAYTRACE_TILEMASK_FORMAT, ImageType::UINT_2D_ARRAY, "tile_raytrace_denoise_img")
.image_in(1, RAYTRACE_TILEMASK_FORMAT, ImageType::UINT_2D_ARRAY, "tile_raytrace_tracing_img")
.image_in(2, RAYTRACE_TILEMASK_FORMAT, ImageType::UINT_2D_ARRAY, "tile_horizon_denoise_img")
.image_in(3, RAYTRACE_TILEMASK_FORMAT, ImageType::UINT_2D_ARRAY, "tile_horizon_tracing_img")
.storage_buf(0, Qualifier::READ_WRITE, "DispatchCommand", "raytrace_tracing_dispatch_buf")
.storage_buf(1, Qualifier::READ_WRITE, "DispatchCommand", "raytrace_denoise_dispatch_buf")
.storage_buf(2, Qualifier::READ_WRITE, "DispatchCommand", "horizon_tracing_dispatch_buf")
.storage_buf(3, Qualifier::READ_WRITE, "DispatchCommand", "horizon_denoise_dispatch_buf")
.storage_buf(4, Qualifier::WRITE, "uint", "raytrace_tracing_tiles_buf[]")
.storage_buf(5, Qualifier::WRITE, "uint", "raytrace_denoise_tiles_buf[]")
.storage_buf(6, Qualifier::WRITE, "uint", "horizon_tracing_tiles_buf[]")
.storage_buf(7, Qualifier::WRITE, "uint", "horizon_denoise_tiles_buf[]")
.specialization_constant(Type::INT, "closure_index", 0)
.specialization_constant(Type::INT, "resolution_scale", 2)
.compute_source("eevee_ray_tile_compact_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_ray_generate)
@ -188,7 +183,7 @@ GPU_SHADER_CREATE_INFO(eevee_horizon_setup)
.sampler(0, ImageType::DEPTH_2D, "depth_tx")
.sampler(1, ImageType::FLOAT_2D, "in_radiance_tx")
.image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "out_radiance_img")
.image(3, GPU_RGBA8, Qualifier::WRITE, ImageType::FLOAT_2D, "out_normal_img")
.image(3, GPU_RGB10_A2, Qualifier::WRITE, ImageType::FLOAT_2D, "out_normal_img")
.compute_source("eevee_horizon_setup_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_horizon_scan)
@ -203,14 +198,31 @@ GPU_SHADER_CREATE_INFO(eevee_horizon_scan)
"draw_view")
.sampler(0, ImageType::FLOAT_2D, "screen_radiance_tx")
.sampler(1, ImageType::FLOAT_2D, "screen_normal_tx")
.image(
2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "horizon_radiance_img")
.image(3, GPU_R8, Qualifier::WRITE, ImageType::FLOAT_2D, "horizon_occlusion_img")
.image(2, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "horizon_radiance_0_img")
.image(3, GPU_RGBA8, Qualifier::WRITE, ImageType::FLOAT_2D, "horizon_radiance_1_img")
.image(4, GPU_RGBA8, Qualifier::WRITE, ImageType::FLOAT_2D, "horizon_radiance_2_img")
.image(5, GPU_RGBA8, Qualifier::WRITE, ImageType::FLOAT_2D, "horizon_radiance_3_img")
.storage_buf(7, Qualifier::READ, "uint", "tiles_coord_buf[]")
.specialization_constant(Type::INT, "closure_index", 0)
.compute_source("eevee_horizon_scan_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_horizon_denoise)
.do_static_compilation(true)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info(
"eevee_shared", "eevee_global_ubo", "eevee_sampling_data", "eevee_hiz_data", "draw_view")
.sampler(2, ImageType::FLOAT_2D, "in_sh_0_tx")
.sampler(4, ImageType::FLOAT_2D, "in_sh_1_tx")
.sampler(5, ImageType::FLOAT_2D, "in_sh_2_tx")
.sampler(6, ImageType::FLOAT_2D, "in_sh_3_tx")
.sampler(7, ImageType::FLOAT_2D, "screen_normal_tx")
.image(2, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_sh_0_img")
.image(3, GPU_RGBA8, Qualifier::WRITE, ImageType::FLOAT_2D, "out_sh_1_img")
.image(4, GPU_RGBA8, Qualifier::WRITE, ImageType::FLOAT_2D, "out_sh_2_img")
.image(5, GPU_RGBA8, Qualifier::WRITE, ImageType::FLOAT_2D, "out_sh_3_img")
.storage_buf(7, Qualifier::READ, "uint", "tiles_coord_buf[]")
.compute_source("eevee_horizon_denoise_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_horizon_resolve)
.do_static_compilation(true)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info("eevee_shared",
@ -220,14 +232,16 @@ GPU_SHADER_CREATE_INFO(eevee_horizon_denoise)
"eevee_lightprobe_data",
"draw_view")
.sampler(1, ImageType::DEPTH_2D, "depth_tx")
.image(
2, RAYTRACE_RADIANCE_FORMAT, Qualifier::READ, ImageType::FLOAT_2D, "horizon_radiance_img")
.image(3, GPU_R8, Qualifier::READ, ImageType::FLOAT_2D, "horizon_occlusion_img")
.image(4, RAYTRACE_RADIANCE_FORMAT, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "radiance_img")
.image(6, RAYTRACE_TILEMASK_FORMAT, Qualifier::READ, ImageType::UINT_2D_ARRAY, "tile_mask_img")
.sampler(2, ImageType::FLOAT_2D, "horizon_radiance_0_tx")
.sampler(3, ImageType::FLOAT_2D, "horizon_radiance_1_tx")
.sampler(4, ImageType::FLOAT_2D, "horizon_radiance_2_tx")
.sampler(5, ImageType::FLOAT_2D, "horizon_radiance_3_tx")
.sampler(8, ImageType::FLOAT_2D, "screen_normal_tx")
.image(3, RAYTRACE_RADIANCE_FORMAT, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "closure0_img")
.image(4, RAYTRACE_RADIANCE_FORMAT, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "closure1_img")
.image(5, RAYTRACE_RADIANCE_FORMAT, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "closure2_img")
.storage_buf(7, Qualifier::READ, "uint", "tiles_coord_buf[]")
.specialization_constant(Type::INT, "closure_index", 0)
.compute_source("eevee_horizon_denoise_comp.glsl");
.compute_source("eevee_horizon_resolve_comp.glsl");
#undef image_out
#undef image_in

View File

@ -176,6 +176,7 @@ vec3 orthogonal(vec3 v);
* \note Returned vector is always rotated 90 degrees counter clock wise.
*/
vec2 orthogonal(vec2 v);
ivec2 orthogonal(ivec2 v);
/**
* Return true if the difference between`a` and `b` is below the `epsilon` value.
@ -613,6 +614,10 @@ vec2 orthogonal(vec2 v)
{
return vec2(-v.y, v.x);
}
ivec2 orthogonal(ivec2 v)
{
return ivec2(-v.y, v.x);
}
bool is_equal(vec2 a, vec2 b, const float epsilon)
{

View File

@ -212,6 +212,7 @@
.gtao_quality = 0.25f, \
.gtao_thickness = 0.5f, \
.gtao_focus = 0.05f, \
.gtao_resolution = 2, \
\
.bokeh_overblur = 5.0f, \
.bokeh_max_size = 100.0f, \

View File

@ -1895,6 +1895,7 @@ typedef struct SceneEEVEE {
float gtao_quality;
float gtao_thickness;
float gtao_focus;
int gtao_resolution;
float bokeh_overblur;
float bokeh_max_size;
@ -1923,7 +1924,6 @@ typedef struct SceneEEVEE {
int shadow_ray_count;
int shadow_step_count;
float shadow_normal_bias;
float _pad0;
int ray_tracing_method;

View File

@ -7818,6 +7818,13 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem eevee_horizon_pixel_rate_items[] = {
{1, "1", 0, "1 px", ""},
{2, "2", 0, "4 px", ""},
{4, "4", 0, "16 px", ""},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem ray_tracing_method_items[] = {
{RAYTRACE_EEVEE_METHOD_NONE, "NONE", 0, "None", "No intersection with scene geometry"},
{RAYTRACE_EEVEE_METHOD_SCREEN,
@ -8173,6 +8180,16 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
prop = RNA_def_property(srna, "horizon_resolution", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "gtao_resolution");
RNA_def_property_enum_items(prop, eevee_horizon_pixel_rate_items);
RNA_def_property_ui_text(prop,
"Resolution",
"Control the quality of the horizon scan lighting "
"(lower size increase vram usage and quality)");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
/* Depth of Field */
prop = RNA_def_property(srna, "bokeh_max_size", PROP_FLOAT, PROP_PIXEL);