WIP: eevee-next-world-irradiance #108304

Closed
Jeroen Bakker wants to merge 79 commits from Jeroen-Bakker:eevee-next-world-irradiance into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
17 changed files with 338 additions and 7 deletions
Showing only changes of commit e3608d689d - Show all commits

View File

@ -148,6 +148,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
@ -286,6 +287,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
@ -478,6 +480,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

@ -95,6 +95,7 @@
/* Only during shadow rendering. */
#define SHADOW_RENDER_MAP_SLOT 13
#define RBUFS_UTILITY_TEX_SLOT 14
#define REFLECTION_PROBE_TEX_SLOT 16
/* Images. */
#define RBUFS_NORMAL_SLOT 0

View File

@ -71,6 +71,7 @@ void Instance::init(const int2 &output_res,
motion_blur.init();
main_view.init();
irradiance_cache.init();
reflection_probes.init();
}
void Instance::set_time(float time)
@ -117,6 +118,7 @@ void Instance::begin_sync()
hiz_buffer.sync();
main_view.sync();
world.sync();
reflection_probes.sync();
film.sync();
irradiance_cache.sync();
}

View File

@ -26,6 +26,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"
@ -51,6 +52,7 @@ class Instance {
PipelineModule pipelines;
ShadowModule shadows;
LightModule lights;
ReflectionProbeModule reflection_probes;
VelocityModule velocity;
MotionBlurModule motion_blur;
DepthOfField depth_of_field;
@ -97,6 +99,7 @@ class Instance {
pipelines(*this),
shadows(*this),
lights(*this),
reflection_probes(*this),
velocity(*this),
motion_blur(*this),
depth_of_field(*this),

View File

@ -62,6 +62,75 @@ void WorldPipeline::render(View &view)
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Probe Pipeline
* \{ */
void WorldProbePipeline::sync()
{
for (int face : IndexRange(6)) {
CubemapSide &side = sides_[face];
/* View */
float4x4 view_m4 = cubeface_mat(face);
float4x4 win_m4;
cubeface_winmat_get(win_m4, 1.0f, 10.0f);
side.view.sync(view_m4, win_m4);
side.cubemap_face_ps.init();
side.cubemap_face_ps.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS);
}
}
void WorldProbePipeline::sync(GPUMaterial *gpumat)
{
for (int face : IndexRange(6)) {
sync(gpumat, face);
}
}
void WorldProbePipeline::sync(GPUMaterial *gpumat, int face)
{
Manager &manager = *inst_.manager;
CubemapSide &side = sides_[face];
/* Framebuffer. */
Texture &cubemap = inst_.reflection_probes.cubemaps_tx_;
side.cubemap_face_fb.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE_CUBEFACE(cubemap, face));
ResourceHandle handle = manager.resource_handle(float4x4::identity());
side.cubemap_face_ps.framebuffer_set(&side.cubemap_face_fb);
side.cubemap_face_ps.material_set(manager, gpumat);
side.cubemap_face_ps.draw(DRW_cache_fullscreen_quad_get(), handle);
/* To allow opaque pass rendering over it. */
side.cubemap_face_ps.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
void WorldProbePipeline::render()
{
GPUFrameBuffer *previous_framebuffer = GPU_framebuffer_active_get();
GPU_debug_group_begin("World.Probe");
for (int face : IndexRange(6)) {
sides_[face].render(inst_);
}
GPU_debug_group_end();
GPU_texture_update_mipmap_chain(inst_.reflection_probes.cubemaps_tx_);
if (previous_framebuffer) {
GPU_framebuffer_bind(previous_framebuffer);
}
}
void WorldProbePipeline::CubemapSide::render(Instance &instance)
{
instance.manager->submit(cubemap_face_ps, view);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pipeline
*
@ -387,6 +456,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_);
eval_light_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
eval_light_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);

View File

@ -43,6 +43,45 @@ class WorldPipeline {
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Probe Pipeline
*
* Render reflection probe of the world background.
* \{ */
class WorldProbePipeline {
private:
Instance &inst_;
struct CubemapSide {
PassSimple cubemap_face_ps;
View view;
Framebuffer cubemap_face_fb;
void render(Instance &instance);
};
CubemapSide sides_[6] = {
{{"PosX"}, {"PosX"}},
{{"NegX"}, {"NegX"}},
{{"PosY"}, {"PosY"}},
{{"NegY"}, {"NegY"}},
{{"PosZ"}, {"PosZ"}},
{{"NegZ"}, {"NegZ"}},
};
public:
WorldProbePipeline(Instance &inst) : inst_(inst){};
void sync();
void sync(GPUMaterial *gpumat);
void render();
private:
void sync(GPUMaterial *gpumat, int face);
}; // namespace blender::eevee
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pass
*
@ -266,6 +305,7 @@ class UtilityTexture : public Texture {
class PipelineModule {
public:
WorldPipeline world;
WorldProbePipeline world_probe;
DeferredPipeline deferred;
ForwardPipeline forward;
ShadowPipeline shadow;
@ -273,13 +313,15 @@ class PipelineModule {
UtilityTexture utility_tx;
public:
PipelineModule(Instance &inst) : world(inst), deferred(inst), forward(inst), shadow(inst){};
PipelineModule(Instance &inst)
: world(inst), world_probe(inst), deferred(inst), forward(inst), shadow(inst){};
void begin_sync()
{
deferred.begin_sync();
forward.sync();
shadow.sync();
world_probe.sync();
}
void end_sync()

View File

@ -0,0 +1,70 @@
/* 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 (cubemaps_.is_empty()) {
cubemaps_.reserve(MAX_PROBES);
/* Initialize the world cubemap. */
ReflectionProbe world_cubemap;
world_cubemap.type = ReflectionProbe::Type::World;
world_cubemap.is_dirty = true;
cubemaps_.append(world_cubemap);
cubemaps_tx_.ensure_cube_array(GPU_RGBA16F,
MAX_RESOLUTION,
MAX_PROBES,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT,
NULL,
12);
GPU_texture_mipmap_mode(cubemaps_tx_, true, true);
}
}
void ReflectionProbeModule::sync()
{
for (int index : IndexRange(MAX_PROBES)) {
ReflectionProbe &cubemap = cubemaps_[index];
if (!cubemap.needs_update()) {
continue;
}
sync(cubemap);
cubemap.is_dirty = false;
}
}
void ReflectionProbeModule::sync(const ReflectionProbe &cubemap)
{
if (cubemap.type == ReflectionProbe::Type::World) {
GPUMaterial *world_material = instance_.world.get_world_material();
instance_.pipelines.world_probe.sync(world_material);
}
else {
BLI_assert_unreachable();
}
}
void ReflectionProbeModule::set_world_dirty()
{
cubemaps_[WORLD_SLOT].is_dirty = true;
}
/* -------------------------------------------------------------------- */
/** \name World
*
* \{ */
bool ReflectionProbe::needs_update() const
{
return type != Type::Unused && is_dirty;
}
/** \} */
} // namespace blender::eevee

View File

@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup eevee
*
* Cubemaps
*
* Cubemaps record light from different locations in the scene. These cubemaps are used to add
* environment and indirect lighting from light probes.
*
* - Although we have Global illumination light probes can still be used for situations that
* GI doesn't work. For example semi-transparent surfaces.
* - The first cubemap is always reserved for the world shading.
*
*/
#pragma once
#include "eevee_shader_shared.hh"
#include "BKE_cryptomatte.hh"
extern "C" {
struct Material;
}
namespace blender::eevee {
class Instance;
class WorldProbePipeline;
/* -------------------------------------------------------------------- */
/** \name Reflection Probes
* \{ */
class ReflectionProbe {
public:
enum Type { Unused, World };
Type type;
bool is_dirty = false;
bool needs_update() const;
};
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;
/**
* Index of the probe that is used for world background.
*
* NOTE: First probe always contains the world probe.
*/
static constexpr int WORLD_SLOT = 0;
Instance &instance_;
Vector<ReflectionProbe> cubemaps_;
Texture cubemaps_tx_ = {"Probes"};
public:
ReflectionProbeModule(Instance &instance) : instance_(instance) {}
void init();
void set_world_dirty();
void sync();
template<typename T> void bind_resources(draw::detail::PassBase<T> *pass)
{
pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, cubemaps_tx_);
}
private:
void sync(const ReflectionProbe &cubemap);
friend class WorldProbePipeline;
};
} // namespace blender::eevee

View File

@ -119,14 +119,13 @@ void ShadingView::render()
GPU_framebuffer_clear_color_depth(combined_fb_, clear_color, 1.0f);
inst_.pipelines.world.render(render_view_new_);
inst_.pipelines.world_probe.render();
/* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */
inst_.lights.set_view(render_view_new_, extent_);
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);

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.set_world_dirty();
}
wo_handle.reset_recalc_flag();
@ -111,6 +111,21 @@ void World::sync()
inst_.pipelines.world.sync(gpumat);
}
GPUMaterial *World::get_world_material()
{
::World *bl_world = inst_.scene->world;
if (bl_world == nullptr) {
bl_world = default_world_get();
}
bNodeTree *ntree = (bl_world->nodetree && bl_world->use_nodes) ?
bl_world->nodetree :
default_tree.nodetree_get(bl_world);
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree);
return gpumat;
}
/** \} */
} // namespace blender::eevee

View File

@ -62,6 +62,13 @@ class World {
~World();
void sync();
/**
* Get the world material.
*
* NOTE: this function should only be called after World::sync has been executed.
*/
GPUMaterial *get_world_material();
};
/** \} */

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)
void main()
{
@ -52,6 +53,7 @@ void main()
vec3 diffuse_light = vec3(0.0);
vec3 reflection_light = vec3(0.0);
light_world_eval(diffuse_data, reflection_data, P, V, diffuse_light, reflection_light);
light_eval(
diffuse_data, reflection_data, P, Ng, V, vP_z, thickness, diffuse_light, reflection_light);

View File

@ -0,0 +1,16 @@
void light_world_eval(ClosureDiffuse diffuse,
ClosureReflection reflection,
vec3 P,
vec3 V,
inout vec3 out_diffuse,
inout vec3 out_specular)
{
float linear_roughness = fast_sqrt(reflection.roughness);
/* TODO: This should be based by actual LOD?.*/
float lod_cube_max = 12.0;
float lod = linear_roughness * lod_cube_max;
vec3 R = -reflect(V, reflection.N);
vec3 world_light = textureLod_cubemapArray(reflectionProbes, vec4(R, 0.0), lod).rgb;
out_specular += world_light;
}

View File

@ -37,6 +37,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_light)
.additional_info("eevee_shared",
"eevee_utility_texture",
"eevee_light_data",
"eevee_reflection_probe_data",
"eevee_shadow_data",
"eevee_deferred_base",
"eevee_hiz_data",

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

@ -674,7 +674,7 @@ class Texture : NonCopyable {
float *data = nullptr,
int mip_len = 1)
{
return ensure_impl(extent, extent, layers, mip_len, format, usage, data, false, true);
return ensure_impl(extent, extent, layers, mip_len, format, usage, data, true, true);
}
/**
@ -1033,8 +1033,7 @@ class TextureRef : public Texture {
* Dummy type to bind texture as image.
* It is just a GPUTexture in disguise.
*/
class Image {
};
class Image {};
static inline Image *as_image(GPUTexture *tex)
{

View File

@ -648,6 +648,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_velocity_info.hh
../draw/engines/gpencil/shaders/infos/gpencil_info.hh