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 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
Jeroen-Bakker marked this conversation as resolved Outdated

We should not bind on numbers higher than 15. As more changes do this we should check on the best solution. @fclem looking at open PR we need 4 new slots (SSS, Reflection Probes, Global Illumination).

We should not bind on numbers higher than 15. As more changes do this we should check on the best solution. @fclem looking at open PR we need 4 new slots (SSS, Reflection Probes, Global Illumination).
/* 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);
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.
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_;

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.
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 {
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.
PassSimple cubemap_face_ps;
View view;
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.
Framebuffer cubemap_face_fb;
void render(Instance &instance);
};
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.
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
Jeroen-Bakker marked this conversation as resolved Outdated

You shouldn't have 3 sync functions. Kind of related to my other comments: Prefer syncing one view agnostic render pass and always sync it from World::sync().

You shouldn't have 3 `sync` functions. Kind of related to my other comments: Prefer syncing one view agnostic render pass and always sync it from `World::sync()`.
*
@ -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);
Jeroen-Bakker marked this conversation as resolved Outdated

Prefer always syncing it inside World::sync() just like the other world pipeline is doing inst_.pipelines.world.sync(gpumat);.

Prefer always syncing it inside `World::sync()` just like the other world pipeline is doing `inst_.pipelines.world.sync(gpumat);`.
}
else {
BLI_assert_unreachable();
}
}
void ReflectionProbeModule::set_world_dirty()
{
cubemaps_[WORLD_SLOT].is_dirty = true;
Jeroen-Bakker marked this conversation as resolved Outdated

Reflection Probe

Reflection Probe
}
/* -------------------------------------------------------------------- */
/** \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 };
Jeroen-Bakker marked this conversation as resolved Outdated

Style: Uppercase enum members.

Style: Uppercase enum members.
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;
Jeroen-Bakker marked this conversation as resolved Outdated

Style: No uppercase

Style: No uppercase
/**
* 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;
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`
/**
* 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,
Jeroen-Bakker marked this conversation as resolved Outdated

Do we also need to update out_diffuse

Do we also need to update out_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;
Jeroen-Bakker marked this conversation as resolved Outdated

Check lightprobe_filter_glossy_frag

Check `lightprobe_filter_glossy_frag`
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