Eevee-Next: World Reflective Light #108149

Merged
Jeroen Bakker merged 33 commits from Jeroen-Bakker/blender:eevee-next-world-shader into main 2023-06-29 15:25:04 +02:00
17 changed files with 405 additions and 4 deletions

View File

@ -154,6 +154,7 @@ set(SRC
engines/eevee_next/eevee_material.cc
engines/eevee_next/eevee_motion_blur.cc
engines/eevee_next/eevee_pipeline.cc
engines/eevee_next/eevee_reflection_probes.cc
engines/eevee_next/eevee_renderbuffers.cc
engines/eevee_next/eevee_sampling.cc
engines/eevee_next/eevee_shader.cc
@ -296,6 +297,7 @@ set(SRC
engines/eevee_next/eevee_material.hh
engines/eevee_next/eevee_motion_blur.hh
engines/eevee_next/eevee_pipeline.hh
engines/eevee_next/eevee_reflection_probes.hh
engines/eevee_next/eevee_renderbuffers.hh
engines/eevee_next/eevee_sampling.hh
engines/eevee_next/eevee_shader.hh
@ -511,6 +513,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl
engines/eevee_next/shaders/eevee_motion_blur_lib.glsl
engines/eevee_next/shaders/eevee_nodetree_lib.glsl
engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl
engines/eevee_next/shaders/eevee_sampling_lib.glsl
engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl
engines/eevee_next/shaders/eevee_shadow_lib.glsl

View File

@ -108,6 +108,7 @@
#define SHADOW_ATLAS_TEX_SLOT 5
#define SSS_TRANSMITTANCE_TEX_SLOT 6
#define IRRADIANCE_ATLAS_TEX_SLOT 7
#define REFLECTION_PROBE_TEX_SLOT 8
/* Only during shadow rendering. */
#define SHADOW_RENDER_MAP_SLOT 4

View File

@ -70,6 +70,7 @@ void Instance::init(const int2 &output_res,
motion_blur.init();
main_view.init();
irradiance_cache.init();
reflection_probes.init();
}
void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
@ -290,6 +291,7 @@ void Instance::render_sample()
sampling.step();
capture_view.render();
main_view.render();
motion_blur.step();

View File

@ -27,6 +27,7 @@
#include "eevee_material.hh"
#include "eevee_motion_blur.hh"
#include "eevee_pipeline.hh"
#include "eevee_reflection_probes.hh"
#include "eevee_renderbuffers.hh"
#include "eevee_sampling.hh"
#include "eevee_shader.hh"
@ -54,6 +55,7 @@ class Instance {
PipelineModule pipelines;
ShadowModule shadows;
LightModule lights;
ReflectionProbeModule reflection_probes;
VelocityModule velocity;
MotionBlurModule motion_blur;
DepthOfField depth_of_field;
@ -65,6 +67,7 @@ class Instance {
Film film;
RenderBuffers render_buffers;
MainView main_view;
CaptureView capture_view;
World world;
LightProbeModule light_probes;
IrradianceCache irradiance_cache;
@ -105,6 +108,7 @@ class Instance {
pipelines(*this),
shadows(*this),
lights(*this),
reflection_probes(*this),
velocity(*this),
motion_blur(*this),
depth_of_field(*this),
@ -115,6 +119,7 @@ class Instance {
film(*this),
render_buffers(*this),
main_view(*this),
capture_view(*this),
world(*this),
light_probes(*this),
irradiance_cache(*this){};

View File

@ -56,6 +56,56 @@ void BackgroundPipeline::render(View &view)
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Probe Pipeline
* \{ */
void WorldPipeline::sync(GPUMaterial *gpumat)
{
const int2 extent(1);
constexpr eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_WRITE;
dummy_cryptomatte_tx_.ensure_2d(GPU_RGBA32F, extent, usage);
dummy_renderpass_tx_.ensure_2d(GPU_RGBA16F, extent, usage);
dummy_aov_color_tx_.ensure_2d_array(GPU_RGBA16F, extent, 1, usage);
dummy_aov_value_tx_.ensure_2d_array(GPU_R16F, extent, 1, usage);
PassSimple &pass = cubemap_face_ps_;
pass.init();
pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS);
Manager &manager = *inst_.manager;
ResourceHandle handle = manager.resource_handle(float4x4::identity());
Jeroen-Bakker marked this conversation as resolved Outdated

Can we use GPU_TEXTURE_USAGE_MEMORYLESS with GPU_TEXTURE_USAGE_SHADER_WRITE here? sounds like potential issue.

Can we use GPU_TEXTURE_USAGE_MEMORYLESS with GPU_TEXTURE_USAGE_SHADER_WRITE here? sounds like potential issue.
pass.material_set(manager, gpumat);
pass.push_constant("world_opacity_fade", 1.0f);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_ubo(CAMERA_BUF_SLOT, inst_.camera.ubo_get());
pass.bind_ubo(RBUFS_BUF_SLOT, &inst_.render_buffers.data);
pass.bind_image("rp_normal_img", dummy_renderpass_tx_);
pass.bind_image("rp_light_img", dummy_renderpass_tx_);
pass.bind_image("rp_diffuse_color_img", dummy_renderpass_tx_);
pass.bind_image("rp_specular_color_img", dummy_renderpass_tx_);
pass.bind_image("rp_emission_img", dummy_renderpass_tx_);
pass.bind_image("rp_cryptomatte_img", dummy_cryptomatte_tx_);
pass.bind_image("rp_color_img", dummy_aov_color_tx_);
pass.bind_image("rp_value_img", dummy_aov_value_tx_);
/* Required by validation layers. */
inst_.cryptomatte.bind_resources(&pass);
pass.bind_image("aov_color_img", dummy_aov_color_tx_);
pass.bind_image("aov_value_img", dummy_aov_value_tx_);
pass.bind_ssbo("aov_buf", &inst_.film.aovs_info);

Currently reuses previous resources, as the last one would be the world background pass this kinda works. But should be fixed.

Currently reuses previous resources, as the last one would be the world background pass this kinda works. But should be fixed.
pass.draw(DRW_cache_fullscreen_quad_get(), handle);
}
void WorldPipeline::render(View &view)
{
inst_.manager->submit(cubemap_face_ps_, view);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pipeline
*
@ -371,6 +421,7 @@ void DeferredLayer::end_sync()
inst_.shadows.bind_resources(&eval_light_ps_);
inst_.sampling.bind_resources(&eval_light_ps_);
inst_.hiz_buffer.bind_resources(&eval_light_ps_);
inst_.reflection_probes.bind_resources(&eval_light_ps_);
inst_.irradiance_cache.bind_resources(&eval_light_ps_);
eval_light_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);

View File

@ -43,6 +43,34 @@ class BackgroundPipeline {
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Probe Pipeline
*
* Renders a single side for the world reflection probe.
* \{ */
class WorldPipeline {
private:
Instance &inst_;
/* Dummy textures: required to reuse background shader and avoid another shader variation. */
Jeroen-Bakker marked this conversation as resolved Outdated

Add comment saying these are required in order to reuse the background shader and avoid another shader variation.

Add comment saying these are required in order to reuse the background shader and avoid another shader variation.
Texture dummy_renderpass_tx_;
Texture dummy_cryptomatte_tx_;
Jeroen-Bakker marked this conversation as resolved Outdated

Use a single View instance and fill with different view ids.

Use a single View instance and fill with different view ids.

As far as I have seen this requires other changes.

As far as I have seen this requires other changes.
Texture dummy_aov_color_tx_;
Texture dummy_aov_value_tx_;
Jeroen-Bakker marked this conversation as resolved Outdated

This struct shouldn't be contained by the pipeline. The pipeline should only contain the pass and be view agnostic.

So you should only have one pass that is being drawn for the 6 views.

This struct shouldn't be contained by the pipeline. The pipeline should only contain the pass and be view agnostic. So you should only have one pass that is being drawn for the 6 views.
PassSimple cubemap_face_ps_ = {"World.Probe"};
public:
WorldPipeline(Instance &inst) : inst_(inst){};
void sync(GPUMaterial *gpumat);
void render(View &view);
}; // namespace blender::eevee
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pass
*
@ -288,6 +316,7 @@ class UtilityTexture : public Texture {
class PipelineModule {
public:
BackgroundPipeline background;
WorldPipeline world;
DeferredPipeline deferred;
ForwardPipeline forward;
ShadowPipeline shadow;
@ -297,7 +326,12 @@ class PipelineModule {
public:
PipelineModule(Instance &inst)
: background(inst), deferred(inst), forward(inst), shadow(inst), capture(inst){};
: background(inst),
world(inst),
deferred(inst),
forward(inst),
shadow(inst),
capture(inst){};
void begin_sync()
{

View File

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
#include "eevee_reflection_probes.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
void ReflectionProbeModule::init()
{
if (!initialized_) {
cubemaps_tx_.ensure_cube_array(GPU_RGBA16F,
max_resolution_,
max_probes_,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT,
NULL,
max_mipmap_levels_);
GPU_texture_mipmap_mode(cubemaps_tx_, true, true);
initialized_ = true;
}
}
} // namespace blender::eevee

View File

@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup eevee
*/
#pragma once
#include "eevee_shader_shared.hh"
#include "BKE_cryptomatte.hh"
extern "C" {
struct Material;
}
namespace blender::eevee {
class Instance;
class CaptureView;
/* -------------------------------------------------------------------- */
/** \name Reflection Probes
* \{ */
class ReflectionProbeModule {
private:
/** The max number of probes to track. */
static constexpr int max_probes_ = 1;
/**
* The maximum resolution of a cubemap side.
*
* Must be a power of two; intension to be used as a cubemap atlas.
*/
static constexpr int max_resolution_ = 2048;
static constexpr int max_mipmap_levels_ = log(max_resolution_) + 1;
Jeroen-Bakker marked this conversation as resolved Outdated

Style: Uppercase enum members.

Style: Uppercase enum members.
Instance &instance_;
Texture cubemaps_tx_ = {"Probes"};
bool initialized_ = false;
bool do_world_update_ = false;
public:
ReflectionProbeModule(Instance &instance) : instance_(instance) {}
Jeroen-Bakker marked this conversation as resolved Outdated

Style: No uppercase

Style: No uppercase
void init();
template<typename T> void bind_resources(draw::detail::PassBase<T> *pass)
{
pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, cubemaps_tx_);
}
Jeroen-Bakker marked this conversation as resolved Outdated

I don't know if this is covered by the next patch, but this should become an option.

I don't know if this is covered by the next patch, but this should become an option.

Yes, that will be part of the reflection probe baking patch. Might even land earlier when specific parts of that patch are stable. Currently this part isn't stable to land in main yet.

Yes, that will be part of the reflection probe baking patch. Might even land earlier when specific parts of that patch are stable. Currently this part isn't stable to land in main yet.
Jeroen-Bakker marked this conversation as resolved Outdated

Maybe derive it from MAX_RESOLUTION ? log2(MAX_RESOLUTION) + 1

Maybe derive it from `MAX_RESOLUTION` ? `log2(MAX_RESOLUTION) + 1`
void do_world_update_set(bool value)
{
do_world_update_ = value;
}
private:
bool do_world_update_get() const
{
return do_world_update_;
}
/* Capture View requires access to the cubemaps texture for framebuffer configuration. */
friend class CaptureView;
};
} // namespace blender::eevee

View File

@ -125,8 +125,6 @@ void ShadingView::render()
inst_.pipelines.deferred.render(render_view_new_, prepass_fb_, combined_fb_, extent_);
// inst_.lightprobes.draw_cache_display();
// inst_.lookdev.render_overlay(view_fb_);
inst_.pipelines.forward.render(render_view_new_, prepass_fb_, combined_fb_, rbufs.combined_tx);
@ -191,4 +189,35 @@ void ShadingView::update_view()
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture View
* \{ */
void CaptureView::render()
{
if (!inst_.reflection_probes.do_world_update_get()) {

Replace by inst_.reflection_probes.do_world_update.

Replace by `inst_.reflection_probes.do_world_update`.
return;
}
inst_.reflection_probes.do_world_update_set(false);
GPU_debug_group_begin("World.Capture");
View view = {"World.Capture.View"};
for (int face : IndexRange(6)) {
capture_fb_.ensure(
GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.reflection_probes.cubemaps_tx_, face));
GPU_framebuffer_bind(capture_fb_);
float4x4 view_m4 = cubeface_mat(face);
float4x4 win_m4 = math::projection::perspective(-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 10.0f);
Jeroen-Bakker marked this conversation as resolved Outdated

use math::projection::perspective()

use `math::projection::perspective()`
view.sync(view_m4, win_m4);
inst_.pipelines.world.render(view);
}
GPU_texture_update_mipmap_chain(inst_.reflection_probes.cubemaps_tx_);
GPU_debug_group_end();
}
/** \} */
} // namespace blender::eevee

View File

@ -142,4 +142,22 @@ class MainView {
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture View
*
* View for capturing cubemap renders outside a ShadingView.
* \{ */
class CaptureView {
private:
Instance &inst_;
Framebuffer capture_fb_ = {"World.Capture"};
public:
CaptureView(Instance &inst) : inst_(inst) {}
void render();
};
/** \} */
} // namespace blender::eevee

View File

@ -90,7 +90,7 @@ void World::sync()
WorldHandle &wo_handle = inst_.sync.sync_world(bl_world);
if (wo_handle.recalc != 0) {
// inst_.lightprobes.set_world_dirty();
inst_.reflection_probes.do_world_update_set(true);
}
wo_handle.reset_recalc_flag();
@ -109,6 +109,7 @@ void World::sync()
inst_.manager->register_layer_attributes(gpumat);
inst_.pipelines.background.sync(gpumat);
inst_.pipelines.world.sync(gpumat);
}
/** \} */

View File

@ -9,6 +9,7 @@
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
void main()
@ -54,6 +55,7 @@ void main()
vec3 reflection_light = vec3(0.0);
float shadow = 1.0;
light_world_eval(reflection_data, P, V, reflection_light);
lightprobe_eval(diffuse_data, reflection_data, P, Ng, V, diffuse_light, reflection_light);
light_eval(diffuse_data,

View File

@ -0,0 +1,61 @@
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
Jeroen-Bakker marked this conversation as resolved Outdated

Do we also need to update out_diffuse

Do we also need to update out_diffuse
void light_world_eval(ClosureReflection reflection, vec3 P, vec3 V, inout vec3 out_specular)
{
ivec3 texture_size = textureSize(reflectionProbes, 0);
/* TODO: This should be based by actual resolution. Currently the resolution is fixed but
* eventually this should based on a user setting and part of the reflection probe data that will
* be introduced by the reflection probe patch. */
float lod_cube_max = 12.0;
Jeroen-Bakker marked this conversation as resolved Outdated

Check lightprobe_filter_glossy_frag

Check `lightprobe_filter_glossy_frag`
/* Pow2f to distributed across lod more evenly */
float roughness = clamp(pow2f(reflection.roughness), 1e-4f, 0.9999f);
#if defined(GPU_COMPUTE_SHADER)
vec2 frag_coord = vec2(gl_GlobalInvocationID.xy) + 0.5;
#else
vec2 frag_coord = gl_FragCoord.xy;
#endif
vec2 noise = utility_tx_fetch(utility_tx, frag_coord, UTIL_BLUE_NOISE_LAYER).gb;
vec2 rand = fract(noise + sampling_rng_2D_get(SAMPLING_RAYTRACE_U));
vec3 Xi = sample_cylinder(rand);
/* Microfacet normal */
vec3 T, B;
make_orthonormal_basis(reflection.N, T, B);
float pdf;
vec3 H = sample_ggx(Xi, roughness, V, reflection.N, T, B, pdf);
vec3 L = -reflect(V, H);
float NL = dot(reflection.N, L);
if (NL > 0.0) {
/* Coarse Approximation of the mapping distortion
* Unit Sphere -> Cubemap Face */
const float dist = 4.0 * M_PI / 6.0;
/* http://http.developer.nvidia.com/GPUGems3/gpugems3_ch20.html : Equation 13 */
/* TODO: lod_factor should be precalculated and stored inside the reflection probe data. */
const float bias = 0;
const float lod_factor = bias + 0.5 * log(float(square_i(texture_size.x))) / log(2);
/* -2: Don't use LOD levels that are smaller than 4x4 pixels. */
float lod = clamp(lod_factor - 0.5 * log2(pdf * dist), 0.0, lod_cube_max - 2.0);
vec3 l_col = textureLod_cubemapArray(reflectionProbes, vec4(L, 0.0), lod).rgb;
/* Clamped brightness. */
/* For artistic freedom this should be read from the scene/reflection probe.
* Note: Eevee-legacy read the firefly_factor from gi_glossy_clamp.
* Note: Firefly removal should be moved to a different shader and also take SSR into
* account.*/
float luma = max(1e-8, max_v3(l_col));
const float firefly_factor = 1e16;
l_col *= 1.0 - max(0.0, luma - firefly_factor) / luma;
/* TODO: for artistic freedom want to read this from the reflection probe. That will be part of
* the reflection probe patch. */
const float intensity_factor = 1.0;
out_specular += vec3(intensity_factor * l_col);
}
}

View File

@ -5,6 +5,7 @@
**/
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
/* -------------------------------------------------------------------- */
/** \name Sampling data.
@ -102,3 +103,86 @@ vec3 sample_cylinder(vec2 rand)
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Microfacet GGX distribution
* \{ */
#define USE_VISIBLE_NORMAL 1
float D_ggx_opti(float NH, float a2)
{
float tmp = (NH * a2 - NH) * NH + 1.0;
return M_PI * tmp * tmp; /* Doing RCP and mul a2 at the end */
}
float G1_Smith_GGX_opti(float NX, float a2)
{
/* Using Brian Karis approach and refactoring by NX/NX
* this way the (2*NL)*(2*NV) in G = G1(V) * G1(L) gets canceled by the brdf denominator 4*NL*NV
* Rcp is done on the whole G later
* Note that this is not convenient for the transmission formula */
return NX + sqrt(NX * (NX - NX * a2) + a2);
/* return 2 / (1 + sqrt(1 + a2 * (1 - NX*NX) / (NX*NX) ) ); /* Reference function */
}
float pdf_ggx_reflect(float NH, float NV, float VH, float alpha)
{
float a2 = sqr(alpha);
#if USE_VISIBLE_NORMAL
float D = a2 / D_ggx_opti(NH, a2);
float G1 = NV * 2.0 / G1_Smith_GGX_opti(NV, a2);
return G1 * VH * D / NV;
#else
return NH * a2 / D_ggx_opti(NH, a2);
#endif
}
vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt)
{
#if USE_VISIBLE_NORMAL
/* From:
* "A Simpler and Exact Sampling Routine for the GGXDistribution of Visible Normals"
* by Eric Heitz.
* http://jcgt.org/published/0007/04/01/slides.pdf
* View vector is expected to be in tangent space. */
/* Stretch view. */
vec3 Th, Bh, Vh = normalize(vec3(alpha * Vt.xy, Vt.z));
make_orthonormal_basis(Vh, Th, Bh);
/* Sample point with polar coordinates (r, phi). */
float r = sqrt(rand.x);
float x = r * rand.y;
float y = r * rand.z;
float s = 0.5 * (1.0 + Vh.z);
y = (1.0 - s) * sqrt(1.0 - x * x) + s * y;
float z = sqrt(saturate(1.0 - x * x - y * y));
/* Compute normal. */
vec3 Hh = x * Th + y * Bh + z * Vh;
/* Unstretch. */
vec3 Ht = normalize(vec3(alpha * Hh.xy, saturate(Hh.z)));
/* Microfacet Normal. */
return Ht;
#else
/* Theta is the cone angle. */
float z = sqrt((1.0 - rand.x) / (1.0 + sqr(alpha) * rand.x - rand.x)); /* cos theta */
float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */
float x = r * rand.y;
float y = r * rand.z;
/* Microfacet Normal */
return vec3(x, y, z);
#endif
}
vec3 sample_ggx(vec3 rand, float alpha, vec3 V, vec3 N, vec3 T, vec3 B, out float pdf)
{
vec3 Vt = world_to_tangent(V, N, T, B);
vec3 Ht = sample_ggx(rand, alpha, Vt);
float NH = saturate(Ht.z);
float NV = saturate(Vt.z);
float VH = saturate(dot(Vt, Ht));
pdf = pdf_ggx_reflect(NH, NV, VH, alpha);
return tangent_to_world(Ht, N, T, B);
}
/** \} */

View File

@ -36,7 +36,9 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_light)
.sampler(1, ImageType::FLOAT_2D_ARRAY, "gbuffer_color_tx")
.additional_info("eevee_shared",
"eevee_utility_texture",
"eevee_sampling_data",
"eevee_light_data",
"eevee_reflection_probe_data",
"eevee_lightprobe_data",
"eevee_shadow_data",
"eevee_deferred_base",

View File

@ -0,0 +1,11 @@
#include "eevee_defines.hh"
#include "gpu_shader_create_info.hh"
/* -------------------------------------------------------------------- */
/** \name Shared
* \{ */
GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data)
.sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_CUBE_ARRAY, "reflectionProbes");
/** \} */

View File

@ -651,6 +651,7 @@ set(SRC_SHADER_CREATE_INFOS
../draw/engines/eevee_next/shaders/infos/eevee_light_culling_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_material_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_motion_blur_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_shadow_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_subsurface_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh