Eevee-next: Lookdev HDRI Switcher
This PR adds support to override the world with the HDRI of the viewport. Blurring of the background and camera based rotation isn't supported. Switching the HDRI would still use the world diffuse light as that is baked into the irradiance cache. These issues have been added to #109646  Pull Request: https://projects.blender.org/blender/blender/pulls/109548
This commit is contained in:
@@ -6145,14 +6145,17 @@ class VIEW3D_PT_shading_lighting(Panel):
|
||||
split = layout.split(factor=0.9)
|
||||
col = split.column()
|
||||
|
||||
engine = context.scene.render.engine
|
||||
row = col.row()
|
||||
row.prop(shading, "use_studiolight_view_rotation", text="", icon='WORLD', toggle=True)
|
||||
row = row.row()
|
||||
if engine != 'BLENDER_EEVEE_NEXT':
|
||||
row.prop(shading, "use_studiolight_view_rotation", text="", icon='WORLD', toggle=True)
|
||||
row = row.row()
|
||||
row.prop(shading, "studiolight_rotate_z", text="Rotation")
|
||||
|
||||
col.prop(shading, "studiolight_intensity")
|
||||
col.prop(shading, "studiolight_background_alpha")
|
||||
col.prop(shading, "studiolight_background_blur")
|
||||
if engine != 'BLENDER_EEVEE_NEXT':
|
||||
col.prop(shading, "studiolight_background_blur")
|
||||
col = split.column() # to align properly with above
|
||||
|
||||
elif shading.type == 'RENDERED':
|
||||
@@ -6176,7 +6179,9 @@ class VIEW3D_PT_shading_lighting(Panel):
|
||||
col.prop(shading, "studiolight_rotate_z", text="Rotation")
|
||||
col.prop(shading, "studiolight_intensity")
|
||||
col.prop(shading, "studiolight_background_alpha")
|
||||
col.prop(shading, "studiolight_background_blur")
|
||||
engine = context.scene.render.engine
|
||||
if engine != 'BLENDER_EEVEE_NEXT':
|
||||
col.prop(shading, "studiolight_background_blur")
|
||||
col = split.column() # to align properly with above
|
||||
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ set(SRC
|
||||
engines/eevee_next/eevee_light.cc
|
||||
engines/eevee_next/eevee_lightcache.cc
|
||||
engines/eevee_next/eevee_lightprobe.cc
|
||||
engines/eevee_next/eevee_lookdev.cc
|
||||
engines/eevee_next/eevee_material.cc
|
||||
engines/eevee_next/eevee_motion_blur.cc
|
||||
engines/eevee_next/eevee_pipeline.cc
|
||||
@@ -297,6 +298,7 @@ set(SRC
|
||||
engines/eevee_next/eevee_light.hh
|
||||
engines/eevee_next/eevee_lightcache.hh
|
||||
engines/eevee_next/eevee_lightprobe.hh
|
||||
engines/eevee_next/eevee_lookdev.hh
|
||||
engines/eevee_next/eevee_material.hh
|
||||
engines/eevee_next/eevee_motion_blur.hh
|
||||
engines/eevee_next/eevee_pipeline.hh
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "eevee_irradiance_cache.hh"
|
||||
#include "eevee_light.hh"
|
||||
#include "eevee_lightprobe.hh"
|
||||
#include "eevee_lookdev.hh"
|
||||
#include "eevee_material.hh"
|
||||
#include "eevee_motion_blur.hh"
|
||||
#include "eevee_pipeline.hh"
|
||||
@@ -71,6 +72,7 @@ class Instance {
|
||||
MainView main_view;
|
||||
CaptureView capture_view;
|
||||
World world;
|
||||
LookdevModule lookdev;
|
||||
LightProbeModule light_probes;
|
||||
IrradianceCache irradiance_cache;
|
||||
|
||||
@@ -124,6 +126,7 @@ class Instance {
|
||||
main_view(*this),
|
||||
capture_view(*this),
|
||||
world(*this),
|
||||
lookdev(*this),
|
||||
light_probes(*this),
|
||||
irradiance_cache(*this){};
|
||||
~Instance(){};
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup eevee
|
||||
*/
|
||||
|
||||
#include "BKE_image.h"
|
||||
#include "BKE_lib_id.h"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_studiolight.h"
|
||||
|
||||
#include "NOD_shader.h"
|
||||
|
||||
#include "eevee_instance.hh"
|
||||
|
||||
namespace blender::eevee {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Viewport Override Node-Tree
|
||||
* \{ */
|
||||
|
||||
LookdevWorldNodeTree::LookdevWorldNodeTree()
|
||||
{
|
||||
bNodeTree *ntree = ntreeAddTree(nullptr, "Lookdev World Nodetree", ntreeType_Shader->idname);
|
||||
ntree_ = ntree;
|
||||
|
||||
bNode *coordinate = nodeAddStaticNode(nullptr, ntree, SH_NODE_TEX_COORD);
|
||||
bNodeSocket *coordinate_out = nodeFindSocket(coordinate, SOCK_OUT, "Generated");
|
||||
|
||||
bNode *rotate = nodeAddStaticNode(nullptr, ntree, SH_NODE_VECTOR_ROTATE);
|
||||
rotate->custom1 = NODE_VECTOR_ROTATE_TYPE_AXIS_Z;
|
||||
bNodeSocket *rotate_vector_in = nodeFindSocket(rotate, SOCK_IN, "Vector");
|
||||
angle_socket_ = static_cast<bNodeSocketValueFloat *>(
|
||||
static_cast<void *>(nodeFindSocket(rotate, SOCK_IN, "Angle")->default_value));
|
||||
bNodeSocket *rotate_out = nodeFindSocket(rotate, SOCK_OUT, "Vector");
|
||||
|
||||
bNode *environment = nodeAddStaticNode(nullptr, ntree, SH_NODE_TEX_ENVIRONMENT);
|
||||
environment_node_ = environment;
|
||||
NodeTexImage *environment_storage = static_cast<NodeTexImage *>(environment->storage);
|
||||
bNodeSocket *environment_vector_in = nodeFindSocket(environment, SOCK_IN, "Vector");
|
||||
bNodeSocket *environment_out = nodeFindSocket(environment, SOCK_OUT, "Color");
|
||||
|
||||
bNode *background = nodeAddStaticNode(nullptr, ntree, SH_NODE_BACKGROUND);
|
||||
bNodeSocket *background_out = nodeFindSocket(background, SOCK_OUT, "Background");
|
||||
bNodeSocket *background_color_in = nodeFindSocket(background, SOCK_IN, "Color");
|
||||
intensity_socket_ = static_cast<bNodeSocketValueFloat *>(
|
||||
static_cast<void *>(nodeFindSocket(background, SOCK_IN, "Strength")->default_value));
|
||||
|
||||
bNode *output = nodeAddStaticNode(nullptr, ntree, SH_NODE_OUTPUT_WORLD);
|
||||
bNodeSocket *output_in = nodeFindSocket(output, SOCK_IN, "Surface");
|
||||
|
||||
nodeAddLink(ntree, coordinate, coordinate_out, rotate, rotate_vector_in);
|
||||
nodeAddLink(ntree, rotate, rotate_out, environment, environment_vector_in);
|
||||
nodeAddLink(ntree, environment, environment_out, background, background_color_in);
|
||||
nodeAddLink(ntree, background, background_out, output, output_in);
|
||||
nodeSetActive(ntree, output);
|
||||
|
||||
/* Create a dummy image data block to hold GPU textures generated by studiolights. */
|
||||
BLI_strncpy(image.id.name, "IMLookdev", sizeof(image.id.name));
|
||||
BKE_libblock_init_empty(&image.id);
|
||||
image.type = IMA_TYPE_IMAGE;
|
||||
image.source = IMA_SRC_GENERATED;
|
||||
ImageTile *base_tile = BKE_image_get_tile(&image, 0);
|
||||
base_tile->gen_x = 1;
|
||||
base_tile->gen_y = 1;
|
||||
base_tile->gen_type = IMA_GENTYPE_BLANK;
|
||||
copy_v4_fl(base_tile->gen_color, 0.0f);
|
||||
/*
|
||||
* TODO: This works around the issue that the first time the texture is accessed the image would
|
||||
* overwrite the set GPU texture. A better solution would be to use image data-blocks as part of
|
||||
* the studiolights, but that requires a larger refactoring.
|
||||
*/
|
||||
BKE_image_get_gpu_texture(&image, &environment_storage->iuser, nullptr);
|
||||
}
|
||||
|
||||
LookdevWorldNodeTree::~LookdevWorldNodeTree()
|
||||
{
|
||||
ntreeFreeEmbeddedTree(ntree_);
|
||||
MEM_SAFE_FREE(ntree_);
|
||||
BKE_libblock_free_datablock(&image.id, 0);
|
||||
}
|
||||
|
||||
bNodeTree *LookdevWorldNodeTree::nodetree_get(const LookdevParameters ¶meters)
|
||||
{
|
||||
intensity_socket_->value = parameters.intensity;
|
||||
angle_socket_->value = parameters.rot_z;
|
||||
|
||||
GPU_TEXTURE_FREE_SAFE(image.gputexture[TEXTARGET_2D][0]);
|
||||
environment_node_->id = nullptr;
|
||||
|
||||
StudioLight *sl = BKE_studiolight_find(parameters.hdri.c_str(),
|
||||
STUDIOLIGHT_ORIENTATIONS_MATERIAL_MODE);
|
||||
if (sl) {
|
||||
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EQUIRECT_RADIANCE_GPUTEXTURE);
|
||||
GPUTexture *texture = sl->equirect_radiance_gputexture;
|
||||
if (texture != nullptr) {
|
||||
GPU_texture_ref(texture);
|
||||
image.gputexture[TEXTARGET_2D][0] = texture;
|
||||
environment_node_->id = &image.id;
|
||||
}
|
||||
}
|
||||
|
||||
return ntree_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Lookdev
|
||||
*
|
||||
* \{ */
|
||||
|
||||
LookdevModule::~LookdevModule()
|
||||
{
|
||||
GPU_material_free(&gpu_materials_);
|
||||
gpu_material_ = nullptr;
|
||||
}
|
||||
|
||||
bool LookdevModule::sync_world()
|
||||
{
|
||||
/* Check based on the v3d if the world is overridden. */
|
||||
LookdevParameters new_parameters(inst_.v3d);
|
||||
if (parameters_ != new_parameters) {
|
||||
if (parameters_.gpu_parameters_changed(new_parameters)) {
|
||||
GPU_material_free(&gpu_materials_);
|
||||
gpu_material_ = nullptr;
|
||||
}
|
||||
|
||||
parameters_ = new_parameters;
|
||||
inst_.reflection_probes.do_world_update_set(true);
|
||||
inst_.sampling.reset();
|
||||
}
|
||||
|
||||
if (parameters_.show_scene_world) {
|
||||
return false;
|
||||
}
|
||||
|
||||
::bNodeTree *node_tree = world_override_tree.nodetree_get(parameters_);
|
||||
gpu_material_ = inst_.shaders.material_shader_get("EEVEE Lookdev Background",
|
||||
gpu_materials_,
|
||||
node_tree,
|
||||
MAT_PIPE_DEFERRED,
|
||||
MAT_GEOM_WORLD,
|
||||
true);
|
||||
inst_.pipelines.world.sync(gpu_material_);
|
||||
inst_.pipelines.background.sync(gpu_material_, parameters_.background_opacity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Parameters
|
||||
* \{ */
|
||||
|
||||
LookdevParameters::LookdevParameters() {}
|
||||
|
||||
LookdevParameters::LookdevParameters(const ::View3D *v3d)
|
||||
{
|
||||
if (v3d == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ::View3DShading &shading = v3d->shading;
|
||||
show_scene_world = shading.type == OB_RENDER ? shading.flag & V3D_SHADING_SCENE_WORLD_RENDER :
|
||||
shading.flag & V3D_SHADING_SCENE_WORLD;
|
||||
if (!show_scene_world) {
|
||||
rot_z = shading.studiolight_rot_z;
|
||||
background_opacity = shading.studiolight_background;
|
||||
blur = shading.studiolight_blur;
|
||||
intensity = shading.studiolight_intensity;
|
||||
hdri = StringRefNull(shading.lookdev_light);
|
||||
}
|
||||
}
|
||||
|
||||
bool LookdevParameters::operator==(const LookdevParameters &other) const
|
||||
{
|
||||
return hdri == other.hdri && rot_z == other.rot_z &&
|
||||
background_opacity == other.background_opacity && blur == other.blur &&
|
||||
intensity == other.intensity && show_scene_world == other.show_scene_world;
|
||||
}
|
||||
|
||||
bool LookdevParameters::gpu_parameters_changed(const LookdevParameters &other) const
|
||||
{
|
||||
return !(hdri == other.hdri && rot_z == other.rot_z && blur == other.blur &&
|
||||
intensity == other.intensity);
|
||||
}
|
||||
|
||||
bool LookdevParameters::operator!=(const LookdevParameters &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::eevee
|
||||
@@ -0,0 +1,96 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup eevee
|
||||
*
|
||||
* World rendering with material handling. Also take care of lookdev
|
||||
* HDRI and default material.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_world_types.h"
|
||||
|
||||
namespace blender::eevee {
|
||||
|
||||
class Instance;
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Parameters
|
||||
*
|
||||
* Parameters used to check changes and to configure the world shader node tree.
|
||||
*
|
||||
* \{ */
|
||||
struct LookdevParameters {
|
||||
std::string hdri;
|
||||
float rot_z = 0.0f;
|
||||
float background_opacity = 0.0f;
|
||||
float intensity = 1.0f;
|
||||
float blur = 0.0f;
|
||||
bool show_scene_world = true;
|
||||
|
||||
LookdevParameters();
|
||||
LookdevParameters(const ::View3D *v3d);
|
||||
bool operator==(const LookdevParameters &other) const;
|
||||
bool operator!=(const LookdevParameters &other) const;
|
||||
bool gpu_parameters_changed(const LookdevParameters &other) const;
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Viewport Override Node-Tree
|
||||
*
|
||||
* In a viewport the world can be overridden by a custom HDRI and some settings.
|
||||
* \{ */
|
||||
|
||||
class LookdevWorldNodeTree {
|
||||
private:
|
||||
bNodeTree *ntree_ = nullptr;
|
||||
bNode *environment_node_ = nullptr;
|
||||
bNodeSocketValueFloat *intensity_socket_ = nullptr;
|
||||
bNodeSocketValueFloat *angle_socket_ = nullptr;
|
||||
::Image image = {};
|
||||
|
||||
public:
|
||||
LookdevWorldNodeTree();
|
||||
~LookdevWorldNodeTree();
|
||||
|
||||
bNodeTree *nodetree_get(const LookdevParameters ¶meters);
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Lookdev
|
||||
*
|
||||
* Look Development can override the world.
|
||||
*
|
||||
* \{ */
|
||||
|
||||
class LookdevModule {
|
||||
public:
|
||||
private:
|
||||
Instance &inst_;
|
||||
|
||||
LookdevWorldNodeTree world_override_tree;
|
||||
|
||||
LookdevParameters parameters_;
|
||||
ListBase gpu_materials_ = {nullptr, nullptr};
|
||||
GPUMaterial *gpu_material_ = nullptr;
|
||||
|
||||
public:
|
||||
LookdevModule(Instance &inst) : inst_(inst){};
|
||||
~LookdevModule();
|
||||
|
||||
bool sync_world();
|
||||
|
||||
private:
|
||||
::World *get_world(::bNodeTree *node_tree);
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::eevee
|
||||
@@ -22,7 +22,7 @@ namespace blender::eevee {
|
||||
* Used to draw background.
|
||||
* \{ */
|
||||
|
||||
void BackgroundPipeline::sync(GPUMaterial *gpumat)
|
||||
void BackgroundPipeline::sync(GPUMaterial *gpumat, const float background_opacity)
|
||||
{
|
||||
Manager &manager = *inst_.manager;
|
||||
RenderBuffers &rbufs = inst_.render_buffers;
|
||||
@@ -32,7 +32,7 @@ void BackgroundPipeline::sync(GPUMaterial *gpumat)
|
||||
world_ps_.init();
|
||||
world_ps_.state_set(DRW_STATE_WRITE_COLOR);
|
||||
world_ps_.material_set(manager, gpumat);
|
||||
world_ps_.push_constant("world_opacity_fade", inst_.film.background_opacity_get());
|
||||
world_ps_.push_constant("world_opacity_fade", background_opacity);
|
||||
world_ps_.bind_texture("utility_tx", inst_.pipelines.utility_tx);
|
||||
/* RenderPasses & AOVs. Cleared by background (even if bad practice). */
|
||||
world_ps_.bind_image("rp_color_img", &rbufs.rp_color_tx);
|
||||
|
||||
@@ -37,7 +37,7 @@ class BackgroundPipeline {
|
||||
public:
|
||||
BackgroundPipeline(Instance &inst) : inst_(inst){};
|
||||
|
||||
void sync(GPUMaterial *gpumat);
|
||||
void sync(GPUMaterial *gpumat, float background_opacity);
|
||||
void render(View &view);
|
||||
};
|
||||
|
||||
|
||||
@@ -78,9 +78,9 @@ World::~World()
|
||||
|
||||
void World::sync()
|
||||
{
|
||||
// if (inst_.lookdev.sync_world()) {
|
||||
// return;
|
||||
// }
|
||||
if (inst_.lookdev.sync_world()) {
|
||||
return;
|
||||
}
|
||||
|
||||
::World *bl_world = inst_.scene->world;
|
||||
if (bl_world == nullptr) {
|
||||
@@ -108,7 +108,7 @@ void World::sync()
|
||||
|
||||
inst_.manager->register_layer_attributes(gpumat);
|
||||
|
||||
inst_.pipelines.background.sync(gpumat);
|
||||
inst_.pipelines.background.sync(gpumat, inst_.film.background_opacity_get());
|
||||
inst_.pipelines.world.sync(gpumat);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user