Blender V5.0
eevee_lookdev.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_rect.h"
10
11#include "BKE_image.hh"
12#include "BKE_lib_id.hh"
13#include "BKE_node.hh"
15#include "BKE_studiolight.h"
16
17#include "NOD_shader.h"
18
19#include "GPU_material.hh"
20
21#include "draw_cache.hh"
22#include "draw_view_data.hh"
23
24#include "eevee_instance.hh"
25
26namespace blender::eevee {
27
28/* -------------------------------------------------------------------- */
31
33{
34 /* Create a dummy World data block to hold the nodetree generated for studio-lights. */
35 world = BKE_id_new_nomain<::World>("Lookdev");
36
38 nullptr, &world->id, "Lookdev World Nodetree", ntreeType_Shader->idname);
39
40 bNode *coordinate = bke::node_add_static_node(nullptr, *ntree, SH_NODE_TEX_COORD);
41 bNodeSocket *coordinate_out = bke::node_find_socket(*coordinate, SOCK_OUT, "Generated");
42
45 bNodeSocket *rotate_vector_in = bke::node_find_socket(*rotate, SOCK_IN, "Vector");
46 angle_socket_ = static_cast<bNodeSocketValueFloat *>(
48 bNodeSocket *rotate_out = bke::node_find_socket(*rotate, SOCK_OUT, "Vector");
49
50 bNode *environment = bke::node_add_static_node(nullptr, *ntree, SH_NODE_TEX_ENVIRONMENT);
51 environment_node_ = environment;
52 NodeTexImage *environment_storage = static_cast<NodeTexImage *>(environment->storage);
53 bNodeSocket *environment_vector_in = bke::node_find_socket(*environment, SOCK_IN, "Vector");
54 bNodeSocket *environment_out = bke::node_find_socket(*environment, SOCK_OUT, "Color");
55
56 bNode *background = bke::node_add_static_node(nullptr, *ntree, SH_NODE_BACKGROUND);
57 bNodeSocket *background_out = bke::node_find_socket(*background, SOCK_OUT, "Background");
58 bNodeSocket *background_color_in = bke::node_find_socket(*background, SOCK_IN, "Color");
59 intensity_socket_ = static_cast<bNodeSocketValueFloat *>(
60 bke::node_find_socket(*background, SOCK_IN, "Strength")->default_value);
61
63 bNodeSocket *output_in = bke::node_find_socket(*output, SOCK_IN, "Surface");
64
65 bke::node_add_link(*ntree, *coordinate, *coordinate_out, *rotate, *rotate_vector_in);
66 bke::node_add_link(*ntree, *rotate, *rotate_out, *environment, *environment_vector_in);
67 bke::node_add_link(*ntree, *environment, *environment_out, *background, *background_color_in);
68 bke::node_add_link(*ntree, *background, *background_out, *output, *output_in);
70
71 world->nodetree = ntree;
72
73 /* Create a dummy image data block to hold GPU textures generated by studio-lights. */
74 image = BKE_id_new_nomain<::Image>("Lookdev");
75 image->type = IMA_TYPE_IMAGE;
76 image->source = IMA_SRC_GENERATED;
77 ImageTile *base_tile = BKE_image_get_tile(image, 0);
78 base_tile->gen_x = 1;
79 base_tile->gen_y = 1;
80 base_tile->gen_type = IMA_GENTYPE_BLANK;
81 copy_v4_fl(base_tile->gen_color, 0.0f);
82 /* TODO: This works around the issue that the first time the texture is accessed the image would
83 * overwrite the set GPU texture. A better solution would be to use image data-blocks as part of
84 * the studio-lights, but that requires a larger refactoring. */
85 BKE_image_get_gpu_texture(image, &environment_storage->iuser);
86}
87
89{
90 BKE_id_free(nullptr, &image->id);
91 BKE_id_free(nullptr, &world->id);
92}
93
94bool LookdevWorld::sync(const LookdevParameters &new_parameters)
95{
96 const bool parameters_changed = assign_if_different(parameters_, new_parameters);
97
98 if (parameters_changed) {
99 intensity_socket_->value = parameters_.intensity;
100 angle_socket_->value = parameters_.rot_z;
101
102 GPU_TEXTURE_FREE_SAFE(image->gputexture[TEXTARGET_2D][0]);
103 environment_node_->id = nullptr;
104
105 StudioLight *sl = BKE_studiolight_find(parameters_.hdri.c_str(),
107 if (sl) {
110 if (texture != nullptr) {
112 image->gputexture[TEXTARGET_2D][0] = texture;
113 environment_node_->id = &image->id;
114 }
115 }
116
117 GPU_material_free(&world->gpumaterial);
118 }
119 return parameters_changed;
120}
121
123
124/* -------------------------------------------------------------------- */
128
130
132{
133 for (gpu::Batch *batch : sphere_lod_) {
135 }
136}
137
138blender::gpu::Batch *LookdevModule::sphere_get(const SphereLOD level_of_detail)
139{
140 BLI_assert(level_of_detail >= SphereLOD::LOW && level_of_detail < SphereLOD::MAX);
141
142 /* GCC 15.x triggers an array-bounds warning without this. */
143#if (defined(__GNUC__) && (__GNUC__ >= 15) && !defined(__clang__))
144 [[assume((level_of_detail >= 0) && (level_of_detail < SphereLOD::MAX))]];
145#endif
146
147 if (sphere_lod_[level_of_detail] != nullptr) {
148 return sphere_lod_[level_of_detail];
149 }
150
151 int lat_res;
152 int lon_res;
153 switch (level_of_detail) {
154 case 2:
155 lat_res = 80;
156 lon_res = 60;
157 break;
158 case 1:
159 lat_res = 64;
160 lon_res = 48;
161 break;
162 default:
163 case 0:
164 lat_res = 32;
165 lon_res = 24;
166 break;
167 }
168
169 GPUVertFormat format = {0};
170 GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
171 GPU_vertformat_attr_add(&format, "nor", gpu::VertAttrType::SFLOAT_32_32_32);
172 struct Vert {
173 float x, y, z;
174 float nor_x, nor_y, nor_z;
175 };
176
177 blender::gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
178 int v_len = (lat_res - 1) * lon_res * 6;
179 GPU_vertbuf_data_alloc(*vbo, v_len);
180
181 const float lon_inc = 2 * M_PI / lon_res;
182 const float lat_inc = M_PI / lat_res;
183 float lon, lat;
184
185 int v = 0;
186 lon = 0.0f;
187
188 auto sphere_lat_lon_vert = [&](float lat, float lon) {
189 Vert vert;
190 vert.nor_x = vert.x = sinf(lat) * cosf(lon);
191 vert.nor_y = vert.y = cosf(lat);
192 vert.nor_z = vert.z = sinf(lat) * sinf(lon);
193 GPU_vertbuf_vert_set(vbo, v, &vert);
194 v++;
195 };
196
197 for (int i = 0; i < lon_res; i++, lon += lon_inc) {
198 lat = 0.0f;
199 for (int j = 0; j < lat_res; j++, lat += lat_inc) {
200 if (j != lat_res - 1) { /* Pole */
201 sphere_lat_lon_vert(lat + lat_inc, lon + lon_inc);
202 sphere_lat_lon_vert(lat + lat_inc, lon);
203 sphere_lat_lon_vert(lat, lon);
204 }
205 if (j != 0) { /* Pole */
206 sphere_lat_lon_vert(lat, lon + lon_inc);
207 sphere_lat_lon_vert(lat + lat_inc, lon + lon_inc);
208 sphere_lat_lon_vert(lat, lon);
209 }
210 }
211 }
212
213 sphere_lod_[level_of_detail] = GPU_batch_create_ex(
214 GPU_PRIM_TRIS, vbo, nullptr, GPU_BATCH_OWNS_VBO);
215 return sphere_lod_[level_of_detail];
216}
217
218void LookdevModule::init(const rcti *visible_rect)
219{
220 visible_rect_ = *visible_rect;
221 enabled_ = inst_.is_viewport() && inst_.overlays_enabled() && inst_.use_lookdev_overlay();
222
223 if (enabled_) {
224 const int2 extent_dummy(1);
227 dummy_cryptomatte_tx_.ensure_2d(gpu::TextureFormat::SFLOAT_32_32_32_32, extent_dummy, usage);
228 dummy_aov_color_tx_.ensure_2d_array(
229 gpu::TextureFormat::SFLOAT_16_16_16_16, extent_dummy, 1, usage);
230 dummy_aov_value_tx_.ensure_2d_array(gpu::TextureFormat::SFLOAT_16, extent_dummy, 1, usage);
231 }
232}
233
234float LookdevModule::calc_viewport_scale()
235{
236 const float viewport_scale = clamp_f(
237 BLI_rcti_size_x(&visible_rect_) / (2000.0f * UI_SCALE_FAC), 0.5f, 1.0f);
238 return viewport_scale;
239}
240
241LookdevModule::SphereLOD LookdevModule::calc_level_of_detail(const float viewport_scale)
242{
243 float res_scale = clamp_f(
244 (U.lookdev_sphere_size / 400.0f) * viewport_scale * UI_SCALE_FAC, 0.1f, 1.0f);
245
246 if (res_scale > 0.7f) {
247 return LookdevModule::SphereLOD::HIGH;
248 }
249 if (res_scale > 0.25f) {
250 return LookdevModule::SphereLOD::MEDIUM;
251 }
252 return LookdevModule::SphereLOD::LOW;
253}
254
255static int calc_sphere_extent(const float viewport_scale)
256{
257 const int sphere_radius = U.lookdev_sphere_size * UI_SCALE_FAC * viewport_scale;
258 return sphere_radius * 2;
259}
260
262{
263 if (!enabled_) {
264 return;
265 }
266 const float viewport_scale = calc_viewport_scale();
267 const int2 extent = int2(calc_sphere_extent(viewport_scale));
268
269 const gpu::TextureFormat color_format = gpu::TextureFormat::SFLOAT_16_16_16_16;
270
271 for (int index : IndexRange(num_spheres)) {
272 if (spheres_[index].color_tx_.ensure_2d(color_format, extent)) {
273 /* Request redraw if the light-probe were off and the sampling was already finished. */
274 if (inst_.is_viewport() && inst_.sampling.finished_viewport()) {
275 inst_.sampling.reset();
276 }
277 }
278
279 spheres_[index].framebuffer.ensure(GPU_ATTACHMENT_NONE,
280 GPU_ATTACHMENT_TEXTURE(spheres_[index].color_tx_));
281 }
282
283 const Camera &cam = inst_.camera;
284 float sphere_distance = cam.data_get().clip_near;
285 int2 display_extent = inst_.film.display_extent_get();
286 float pixel_radius = ShadowModule::screen_pixel_radius(
287 cam.data_get().wininv, cam.is_perspective(), display_extent);
288
289 if (cam.is_perspective()) {
290 pixel_radius *= sphere_distance;
291 }
292
293 this->sphere_radius_ = (extent.x / 2) * pixel_radius;
294 this->sphere_position_ = cam.position() -
295 cam.forward() * (sphere_distance + this->sphere_radius_);
296
297 float4x4 model_m4 = float4x4(float3x3(cam.data_get().viewmat));
298 model_m4.location() = this->sphere_position_;
299 model_m4 = math::scale(model_m4, float3(this->sphere_radius_));
300
301 ResourceHandleRange handle = inst_.manager->resource_handle(model_m4);
302 gpu::Batch *geom = sphere_get(calc_level_of_detail(viewport_scale));
303
304 sync_pass(spheres_[0].pass, geom, inst_.materials.metallic_mat, handle);
305 sync_pass(spheres_[1].pass, geom, inst_.materials.diffuse_mat, handle);
306 sync_display();
307}
308
309void LookdevModule::sync_pass(PassSimple &pass,
310 gpu::Batch *geom,
311 ::Material *mat,
312 ResourceHandleRange res_handle)
313{
314 pass.init();
315 pass.clear_color_depth_stencil(float4(0.0, 0.0, 0.0, 1.0), inst_.film.depth.clear_value, 0);
316
318
321 pass.state_set(state);
322 pass.material_set(*inst_.manager, gpumat);
324 pass.bind_resources(inst_.uniform_data);
325 pass.bind_resources(inst_.lights);
326 pass.bind_resources(inst_.shadows);
327 pass.bind_resources(inst_.volume.result);
328 pass.bind_resources(inst_.sampling);
329 pass.bind_resources(inst_.hiz_buffer.front);
330 pass.bind_resources(inst_.volume_probes);
331 pass.bind_resources(inst_.sphere_probes);
332 pass.draw(geom, res_handle, 0);
333}
334
335void LookdevModule::sync_display()
336{
337 PassSimple &pass = display_ps_;
338
339 const float2 viewport_size = inst_.draw_ctx->viewport_size_get();
342 pass.init();
343 pass.state_set(state);
344 pass.shader_set(inst_.shaders.static_shader_get(LOOKDEV_DISPLAY));
345 pass.push_constant("viewportSize", viewport_size);
346 pass.push_constant("invertedViewportSize", 1.0f / viewport_size);
347 pass.push_constant("anchor", int2(visible_rect_.xmax, visible_rect_.ymin));
348 pass.bind_texture("metallic_tx", &spheres_[0].color_tx_);
349 pass.bind_texture("diffuse_tx", &spheres_[1].color_tx_);
350
351 pass.draw_procedural(GPU_PRIM_TRIS, 2, 6);
352}
353
355{
356 if (!enabled_) {
357 return;
358 }
359
360 inst_.volume_probes.set_view(view);
361 inst_.sphere_probes.set_view(view);
362
363 for (Sphere &sphere : spheres_) {
364 sphere.framebuffer.bind();
365 inst_.manager->submit(sphere.pass, view);
366 }
367}
368
370{
371 if (!enabled_) {
372 return;
373 }
374
375 BLI_assert(inst_.is_viewport());
376
377 DefaultFramebufferList *dfbl = inst_.draw_ctx->viewport_framebuffer_list_get();
378 /* The viewport of the framebuffer can be modified when border rendering is enabled. */
381 inst_.manager->submit(display_ps_);
382}
383
385
386/* -------------------------------------------------------------------- */
389
391
393{
394 if (v3d == nullptr) {
395 return;
396 }
397
398 const ::View3DShading &shading = v3d->shading;
399 show_scene_world = shading.type == OB_RENDER ? shading.flag & V3D_SHADING_SCENE_WORLD_RENDER :
400 shading.flag & V3D_SHADING_SCENE_WORLD;
401 if (!show_scene_world) {
402 rot_z = shading.studiolight_rot_z;
403 background_opacity = shading.studiolight_background;
404 blur = shading.studiolight_blur;
405 intensity = shading.studiolight_intensity;
406 hdri = StringRefNull(shading.lookdev_light);
407 }
408}
409
411{
412 return hdri == other.hdri && rot_z == other.rot_z &&
413 background_opacity == other.background_opacity && blur == other.blur &&
415}
416
418{
419 return !(*this == other);
420}
421
423
424} // namespace blender::eevee
blender::gpu::Texture * BKE_image_get_gpu_texture(Image *image, ImageUser *iuser)
Definition image_gpu.cc:496
void BKE_id_free(Main *bmain, void *idv)
void * BKE_id_new_nomain(short type, const char *name)
Definition lib_id.cc:1519
#define SH_NODE_OUTPUT_WORLD
#define SH_NODE_VECTOR_ROTATE
#define SH_NODE_TEX_COORD
#define SH_NODE_TEX_ENVIRONMENT
#define SH_NODE_BACKGROUND
@ STUDIOLIGHT_EQUIRECT_RADIANCE_GPUTEXTURE
struct StudioLight * BKE_studiolight_find(const char *name, int flag)
void BKE_studiolight_ensure_flag(StudioLight *sl, int flag)
#define STUDIOLIGHT_ORIENTATIONS_MATERIAL_MODE
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE float clamp_f(float value, float min, float max)
#define M_PI
MINLINE void copy_v4_fl(float r[4], float f)
BLI_INLINE int BLI_rcti_size_x(const struct rcti *rct)
Definition BLI_rect.h:194
@ IMA_SRC_GENERATED
@ IMA_GENTYPE_BLANK
@ IMA_TYPE_IMAGE
@ TEXTARGET_2D
@ NODE_VECTOR_ROTATE_TYPE_AXIS_Z
@ SOCK_OUT
@ SOCK_IN
@ OB_RENDER
#define UI_SCALE_FAC
@ V3D_SHADING_SCENE_WORLD_RENDER
@ V3D_SHADING_SCENE_WORLD
static AppView * view
#define GPU_BATCH_DISCARD_SAFE(batch)
Definition GPU_batch.hh:197
blender::gpu::Batch * GPU_batch_create_ex(GPUPrimType primitive_type, blender::gpu::VertBuf *vertex_buf, blender::gpu::IndexBuf *index_buf, GPUBatchFlag owns_flag)
Definition gpu_batch.cc:51
@ GPU_BATCH_OWNS_VBO
Definition GPU_batch.hh:42
void GPU_framebuffer_viewport_reset(blender::gpu::FrameBuffer *fb)
#define GPU_ATTACHMENT_TEXTURE(_texture)
#define GPU_ATTACHMENT_NONE
void GPU_framebuffer_bind(blender::gpu::FrameBuffer *fb)
void GPU_material_free(ListBase *gpumaterial)
@ GPU_PRIM_TRIS
void GPU_texture_ref(blender::gpu::Texture *texture)
eGPUTextureUsage
@ GPU_TEXTURE_USAGE_SHADER_READ
@ GPU_TEXTURE_USAGE_SHADER_WRITE
#define GPU_TEXTURE_FREE_SAFE(texture)
void GPU_vertbuf_vert_set(blender::gpu::VertBuf *verts, uint v_idx, const void *data)
static blender::gpu::VertBuf * GPU_vertbuf_create_with_format(const GPUVertFormat &format)
void GPU_vertbuf_data_alloc(blender::gpu::VertBuf &verts, uint v_len)
uint GPU_vertformat_attr_add(GPUVertFormat *format, blender::StringRef name, blender::gpu::VertAttrType type)
struct blender::bke::bNodeTreeType * ntreeType_Shader
#define U
ATTR_WARN_UNUSED_RESULT const BMVert * v
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
void bind_resources(U &resources)
Definition draw_pass.hh:449
void bind_texture(const char *name, gpu::Texture *texture, GPUSamplerState state=sampler_auto)
void state_set(DRWState state, int clip_plane_count=0)
void draw(gpu::Batch *batch, uint instance_len=-1, uint vertex_len=-1, uint vertex_first=-1, ResourceIndexRange res_index={}, uint custom_id=0)
Definition draw_pass.hh:893
void clear_color_depth_stencil(float4 color, float depth, uint8_t stencil)
void material_set(Manager &manager, GPUMaterial *material, bool deferred_texture_loading=false)
const float3 & forward() const
const float3 & position() const
const CameraData & data_get() const
bool is_perspective() const
struct blender::eevee::Film::DepthState depth
struct blender::eevee::HiZBuffer::@312272073133116306117315271010160007344264367302 front
A running instance of the engine.
VolumeProbeModule volume_probes
SphereProbeModule sphere_probes
const DRWContext * draw_ctx
UniformDataModule uniform_data
void init(const rcti *visible_rect)
bool sync(const LookdevParameters &new_parameters)
gpu::Shader * static_shader_get(eShaderType shader_type)
GPUMaterial * material_shader_get(::Material *blender_mat, bNodeTree *nodetree, eMaterialPipeline pipeline_type, eMaterialGeometry geometry_type, bool deferred_compilation, ::Material *default_mat)
static float screen_pixel_radius(const float4x4 &wininv, bool is_perspective, const int2 &extent)
struct blender::eevee::VolumeModule::@250013332365273170227003077320346304121216231272 result
DRWState
Definition draw_state.hh:25
@ DRW_STATE_BLEND_ALPHA
Definition draw_state.hh:55
@ DRW_STATE_WRITE_DEPTH
Definition draw_state.hh:29
@ DRW_STATE_WRITE_COLOR
Definition draw_state.hh:30
@ DRW_STATE_CULL_BACK
Definition draw_state.hh:43
@ DRW_STATE_DEPTH_ALWAYS
Definition draw_state.hh:36
#define RBUFS_UTILITY_TEX_SLOT
struct @021025263243242147216143265077100330027142264337::@225245033123204053237120173316075113304004012000 batch
#define output
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
format
static ulong state[N]
bNodeTree * node_tree_add_tree_embedded(Main *bmain, ID *owner_id, StringRefNull name, StringRefNull idname)
Definition node.cc:4098
bNodeSocket * node_find_socket(bNode &node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:2532
bNode * node_add_static_node(const bContext *C, bNodeTree &ntree, int type)
Definition node.cc:3500
bNodeLink & node_add_link(bNodeTree &ntree, bNode &fromnode, bNodeSocket &fromsock, bNode &tonode, bNodeSocket &tosock)
Definition node.cc:3810
void node_set_active(bNodeTree &ntree, bNode &node)
Definition node.cc:4724
detail::Pass< command::DrawCommandBuf > PassSimple
static int calc_sphere_extent(const float viewport_scale)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
MatBase< T, NumCol, NumRow > rotate(const MatBase< T, NumCol, NumRow > &mat, const RotationT &rotation)
bool assign_if_different(T &old_value, T new_value)
MatBase< float, 4, 4 > float4x4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
#define sinf
#define cosf
blender::float2 viewport_size_get() const
blender::gpu::FrameBuffer * default_fb
float gen_color[4]
struct bNodeTree * nodetree
blender::gpu::Texture * equirect_radiance_gputexture
void * default_value
void * storage
bool operator==(const LookdevParameters &other) const
bool operator!=(const LookdevParameters &other) const
int ymin
int xmax
void * BKE_image_get_tile
Definition stubs.c:36
i
Definition text_draw.cc:230