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());
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);
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. */
Texture dummy_renderpass_tx_;
Texture dummy_cryptomatte_tx_;
Texture dummy_aov_color_tx_;
Texture dummy_aov_value_tx_;
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;
Instance &instance_;
Texture cubemaps_tx_ = {"Probes"};
bool initialized_ = false;
bool do_world_update_ = false;
public:
ReflectionProbeModule(Instance &instance) : instance_(instance) {}
void init();
template<typename T> void bind_resources(draw::detail::PassBase<T> *pass)
{
pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, cubemaps_tx_);
}
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()) {
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);
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)
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;
/* 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