Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,928 @@
# SPDX-FileCopyrightText: 2016 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
# The browser uses Three.js as its only renderer. Keep Blender's public draw
# headers available to data/versioning code without compiling a desktop draw
# manager or embedding its shaders in the data-engine WebAssembly module.
if(WITH_WEB)
add_library(bf_draw INTERFACE)
target_include_directories(bf_draw INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/intern
)
add_library(bf::draw ALIAS bf_draw)
return()
endif()
set(INC
PUBLIC .
intern
../editors/include
../editors/space_view3d
../gpu/intern
../makesrna
../render/intern
../compositor
../compositor/algorithms
../compositor/cached_resources
../compositor/derived_resources
../../../intern/opensubdiv
)
set(INC_SYS
)
set(SRC
intern/attribute_convert.cc
intern/draw_cache.cc
intern/draw_cache_extract_mesh.cc
intern/draw_cache_extract_mesh_render_data.cc
intern/mesh_extractors/extract_mesh.cc
intern/mesh_extractors/extract_mesh_ibo_edituv.cc
intern/mesh_extractors/extract_mesh_ibo_fdots.cc
intern/mesh_extractors/extract_mesh_ibo_lines.cc
intern/mesh_extractors/extract_mesh_ibo_lines_adjacency.cc
intern/mesh_extractors/extract_mesh_ibo_lines_paint_mask.cc
intern/mesh_extractors/extract_mesh_ibo_points.cc
intern/mesh_extractors/extract_mesh_ibo_tris.cc
intern/mesh_extractors/extract_mesh_vbo_attributes.cc
intern/mesh_extractors/extract_mesh_vbo_edge_fac.cc
intern/mesh_extractors/extract_mesh_vbo_edit_data.cc
intern/mesh_extractors/extract_mesh_vbo_edituv_data.cc
intern/mesh_extractors/extract_mesh_vbo_edituv_stretch_angle.cc
intern/mesh_extractors/extract_mesh_vbo_edituv_stretch_area.cc
intern/mesh_extractors/extract_mesh_vbo_fdots_edituv_data.cc
intern/mesh_extractors/extract_mesh_vbo_fdots_nor.cc
intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc
intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc
intern/mesh_extractors/extract_mesh_vbo_lnor.cc
intern/mesh_extractors/extract_mesh_vbo_mesh_analysis.cc
intern/mesh_extractors/extract_mesh_vbo_orco.cc
intern/mesh_extractors/extract_mesh_vbo_paint_overlay_flag.cc
intern/mesh_extractors/extract_mesh_vbo_pos.cc
intern/mesh_extractors/extract_mesh_vbo_sculpt_data.cc
intern/mesh_extractors/extract_mesh_vbo_select_idx.cc
intern/mesh_extractors/extract_mesh_vbo_skin_roots.cc
intern/mesh_extractors/extract_mesh_vbo_tan.cc
intern/mesh_extractors/extract_mesh_vbo_uv.cc
intern/mesh_extractors/extract_mesh_vbo_vnor.cc
intern/mesh_extractors/extract_mesh_vbo_weights.cc
intern/draw_attributes.cc
intern/draw_cache_impl_curve.cc
intern/draw_cache_impl_curves.cc
intern/draw_cache_impl_grease_pencil.cc
intern/draw_cache_impl_lattice.cc
intern/draw_cache_impl_mesh.cc
intern/draw_cache_impl_particles.cc
intern/draw_cache_impl_pointcloud.cc
intern/draw_cache_impl_subdivision.cc
intern/draw_cache_impl_volume.cc
intern/draw_color_management.cc
intern/draw_command.cc
intern/draw_context.cc
intern/draw_curves.cc
intern/draw_debug.cc
intern/draw_fluid.cc
intern/draw_gpu_context.cc
intern/draw_hair.cc
intern/draw_manager.cc
intern/draw_manager_text.cc
intern/draw_pbvh.cc
intern/draw_pointcloud.cc
intern/draw_resource.cc
intern/draw_sculpt.cc
intern/draw_select_buffer.cc
intern/draw_shader.cc
intern/draw_view.cc
intern/draw_view_c.cc
intern/draw_view_data.cc
intern/draw_volume.cc
engines/compositor/compositor_engine.cc
engines/image/image_drawing_mode_image_space.cc
engines/image/image_drawing_mode_screen_space.cc
engines/image/image_engine.cc
engines/image/image_shader.cc
engines/eevee/eevee_ambient_occlusion.cc
engines/eevee/eevee_camera.cc
engines/eevee/eevee_cryptomatte.cc
engines/eevee/eevee_depth_of_field.cc
engines/eevee/eevee_engine.cc
engines/eevee/eevee_film.cc
engines/eevee/eevee_hizbuffer.cc
engines/eevee/eevee_instance.cc
engines/eevee/eevee_light.cc
engines/eevee/eevee_lightcache.cc
engines/eevee/eevee_lightprobe.cc
engines/eevee/eevee_lightprobe_planar.cc
engines/eevee/eevee_lightprobe_sphere.cc
engines/eevee/eevee_lightprobe_volume.cc
engines/eevee/eevee_lookdev.cc
engines/eevee/eevee_lut.cc
engines/eevee/eevee_material.cc
engines/eevee/eevee_motion_blur.cc
engines/eevee/eevee_pipeline.cc
engines/eevee/eevee_precompute.cc
engines/eevee/eevee_raytrace.cc
engines/eevee/eevee_renderbuffers.cc
engines/eevee/eevee_sampling.cc
engines/eevee/eevee_shader.cc
engines/eevee/eevee_shadow.cc
engines/eevee/eevee_subsurface.cc
engines/eevee/eevee_sync.cc
engines/eevee/eevee_velocity.cc
engines/eevee/eevee_view.cc
engines/eevee/eevee_volume.cc
engines/eevee/eevee_world.cc
engines/workbench/workbench_effect_antialiasing.cc
engines/workbench/workbench_effect_cavity.cc
engines/workbench/workbench_effect_dof.cc
engines/workbench/workbench_effect_outline.cc
engines/workbench/workbench_engine.cc
engines/workbench/workbench_materials.cc
engines/workbench/workbench_mesh_passes.cc
engines/workbench/workbench_resources.cc
engines/workbench/workbench_shader_cache.cc
engines/workbench/workbench_shadow.cc
engines/workbench/workbench_state.cc
engines/workbench/workbench_volume.cc
engines/external/external_engine.cc
engines/gpencil/gpencil_antialiasing.cc
engines/gpencil/gpencil_cache_utils.cc
engines/gpencil/gpencil_draw_data.cc
engines/gpencil/gpencil_engine_c.cc
engines/gpencil/gpencil_render.cc
engines/gpencil/gpencil_shader_fx.cc
engines/select/select_engine.cc
engines/select/select_instance.cc
engines/overlay/overlay_armature.cc
engines/overlay/overlay_engine.cc
engines/overlay/overlay_instance.cc
engines/overlay/overlay_mode_transfer.cc
engines/overlay/overlay_shader.cc
engines/overlay/overlay_shape.cc
DRW_engine.hh
DRW_pbvh.hh
DRW_select_buffer.hh
intern/DRW_gpu_wrapper.hh
intern/DRW_render.hh
intern/attribute_convert.hh
intern/draw_attributes.hh
intern/draw_cache.hh
intern/draw_cache_extract.hh
intern/draw_cache_impl.hh
intern/draw_cache_inline.hh
intern/draw_color_management.hh
intern/draw_command.hh
intern/draw_common.hh
intern/draw_common_c.hh
intern/draw_context_private.hh
intern/draw_curves_private.hh
intern/draw_debug.hh
intern/draw_hair_private.hh
intern/draw_handle.hh
intern/draw_manager.hh
intern/draw_manager_text.hh
intern/draw_pass.hh
intern/draw_pointcloud_private.hh
intern/draw_resource.hh
intern/draw_sculpt.hh
intern/draw_shader.hh
intern/draw_shader_shared.hh
intern/draw_state.hh
intern/draw_subdiv_defines.hh
intern/draw_subdiv_shader_shared.hh
intern/draw_subdivision.hh
intern/draw_view.hh
intern/draw_view_c.hh
intern/draw_view_data.hh
intern/mesh_extractors/extract_mesh.hh
engines/compositor/compositor_engine.h
engines/eevee/eevee_ambient_occlusion.hh
engines/eevee/eevee_camera.hh
engines/eevee/eevee_cryptomatte.hh
engines/eevee/eevee_depth_of_field.hh
engines/eevee/eevee_engine.h
engines/eevee/eevee_film.hh
engines/eevee/eevee_gbuffer.hh
engines/eevee/eevee_hizbuffer.hh
engines/eevee/eevee_instance.hh
engines/eevee/eevee_light.hh
engines/eevee/eevee_lightcache.hh
engines/eevee/eevee_lightprobe.hh
engines/eevee/eevee_lightprobe_planar.hh
engines/eevee/eevee_lightprobe_sphere.hh
engines/eevee/eevee_lightprobe_volume.hh
engines/eevee/eevee_lookdev.hh
engines/eevee/eevee_lut.hh
engines/eevee/eevee_material.hh
engines/eevee/eevee_motion_blur.hh
engines/eevee/eevee_pipeline.hh
engines/eevee/eevee_precompute.hh
engines/eevee/eevee_raytrace.hh
engines/eevee/eevee_renderbuffers.hh
engines/eevee/eevee_sampling.hh
engines/eevee/eevee_shader.hh
engines/eevee/eevee_shadow.hh
engines/eevee/eevee_subsurface.hh
engines/eevee/eevee_sync.hh
engines/eevee/eevee_velocity.hh
engines/eevee/eevee_view.hh
engines/eevee/eevee_volume.hh
engines/eevee/eevee_world.hh
engines/external/external_engine.h
engines/gpencil/gpencil_engine.hh
engines/gpencil/gpencil_engine_private.hh
engines/gpencil/gpencil_shader.hh
engines/image/image_batches.hh
engines/image/image_buffer_cache.hh
engines/image/image_drawing_mode_image_space.hh
engines/image/image_drawing_mode_screen_space.hh
engines/image/image_engine.h
engines/image/image_instance.hh
engines/image/image_partial_updater.hh
engines/image/image_private.hh
engines/image/image_shader.hh
engines/image/image_shader_params.hh
engines/image/image_space.hh
engines/image/image_space_image.hh
engines/image/image_space_node.hh
engines/image/image_state.hh
engines/image/image_texture_info.hh
engines/image/image_usage.hh
engines/overlay/overlay_antialiasing.hh
engines/overlay/overlay_armature.hh
engines/overlay/overlay_attribute_text.hh
engines/overlay/overlay_attribute_viewer.hh
engines/overlay/overlay_axes.hh
engines/overlay/overlay_background.hh
engines/overlay/overlay_base.hh
engines/overlay/overlay_bounds.hh
engines/overlay/overlay_camera.hh
engines/overlay/overlay_cursor.hh
engines/overlay/overlay_curve.hh
engines/overlay/overlay_empty.hh
engines/overlay/overlay_engine.h
engines/overlay/overlay_facing.hh
engines/overlay/overlay_fade.hh
engines/overlay/overlay_fluid.hh
engines/overlay/overlay_force_field.hh
engines/overlay/overlay_grease_pencil.hh
engines/overlay/overlay_grid.hh
engines/overlay/overlay_image.hh
engines/overlay/overlay_instance.hh
engines/overlay/overlay_lattice.hh
engines/overlay/overlay_light.hh
engines/overlay/overlay_lightprobe.hh
engines/overlay/overlay_mesh.hh
engines/overlay/overlay_metaball.hh
engines/overlay/overlay_mode_transfer.hh
engines/overlay/overlay_motion_path.hh
engines/overlay/overlay_name.hh
engines/overlay/overlay_origin.hh
engines/overlay/overlay_outline.hh
engines/overlay/overlay_paint.hh
engines/overlay/overlay_particle.hh
engines/overlay/overlay_pointcloud.hh
engines/overlay/overlay_prepass.hh
engines/overlay/overlay_private.hh
engines/overlay/overlay_relation.hh
engines/overlay/overlay_sculpt.hh
engines/overlay/overlay_speaker.hh
engines/overlay/overlay_text.hh
engines/overlay/overlay_wireframe.hh
engines/overlay/overlay_xray_fade.hh
engines/select/select_defines.hh
engines/select/select_engine.hh
engines/select/select_instance.hh
engines/workbench/workbench_defines.hh
engines/workbench/workbench_engine.h
engines/workbench/workbench_enums.hh
engines/workbench/workbench_private.hh
engines/workbench/workbench_shader_shared.hh
)
set(LIB
PRIVATE bf::blenfont
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blenloader
PRIVATE bf::blentranslation
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::functions
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
bf_compositor
PRIVATE bf::nodes
PRIVATE bf::render
PRIVATE bf::windowmanager
PRIVATE bf::intern::atomic
PRIVATE bf::dependencies::optional::opensubdiv
)
set(GLSL_SRC
intern/shaders/draw_curves_infos.hh
intern/shaders/draw_debug_infos.hh
intern/shaders/draw_object_infos_infos.hh
intern/shaders/draw_view_infos.hh
intern/shaders/subdiv_infos.hh
intern/shaders/subdiv_patch_eval_infos.hh
engines/eevee/shaders/infos/eevee_common_infos.hh
engines/eevee/shaders/infos/eevee_geom_infos.hh
engines/eevee/shaders/infos/eevee_nodetree_infos.hh
engines/image/shaders/infos/engine_image_infos.hh
engines/gpencil/shaders/infos/gpencil_infos.hh
engines/gpencil/shaders/infos/gpencil_vfx_infos.hh
engines/overlay/shaders/infos/overlay_armature_infos.hh
engines/overlay/shaders/infos/overlay_background_infos.hh
engines/overlay/shaders/infos/overlay_common_infos.hh
engines/overlay/shaders/infos/overlay_edit_mode_infos.hh
engines/overlay/shaders/infos/overlay_extra_infos.hh
engines/overlay/shaders/infos/overlay_facing_infos.hh
engines/overlay/shaders/infos/overlay_fullscreen_infos.hh
engines/overlay/shaders/infos/overlay_grid_infos.hh
engines/overlay/shaders/infos/overlay_outline_infos.hh
engines/overlay/shaders/infos/overlay_paint_infos.hh
engines/overlay/shaders/infos/overlay_sculpt_curves_infos.hh
engines/overlay/shaders/infos/overlay_sculpt_infos.hh
engines/overlay/shaders/infos/overlay_viewer_attribute_infos.hh
engines/overlay/shaders/infos/overlay_volume_infos.hh
engines/overlay/shaders/infos/overlay_wireframe_infos.hh
engines/select/shaders/infos/select_id_infos.hh
engines/workbench/shaders/infos/workbench_depth_infos.hh
engines/workbench/shaders/infos/workbench_effect_antialiasing_infos.hh
# engines/workbench/shaders/infos/workbench_effect_dof_info.hh # Excluded (shared).
engines/workbench/shaders/infos/workbench_effect_dof_infos.hh
engines/workbench/shaders/infos/workbench_effect_outline_infos.hh
engines/workbench/shaders/infos/workbench_prepass_infos.hh
engines/workbench/shaders/infos/workbench_shadow_infos.hh
engines/workbench/shaders/infos/workbench_transparent_resolve_infos.hh
engines/eevee/shaders/eevee_attributes_curves_lib.glsl
engines/eevee/shaders/eevee_attributes_mesh_lib.glsl
engines/eevee/shaders/eevee_attributes_pointcloud_lib.glsl
engines/eevee/shaders/eevee_attributes_volume_lib.glsl
engines/eevee/shaders/eevee_attributes_world_lib.glsl
engines/eevee/shaders/eevee_bxdf.bsl.hh
engines/eevee/shaders/eevee_bxdf_diffuse.bsl.hh
engines/eevee/shaders/eevee_bxdf_lut.bsl.hh
engines/eevee/shaders/eevee_bxdf_lut_lib.bsl.hh
engines/eevee/shaders/eevee_bxdf_microfacet.bsl.hh
engines/eevee/shaders/eevee_bxdf_types.bsl.hh
engines/eevee/shaders/eevee_camera_lib.bsl.hh
engines/eevee/shaders/eevee_closure.bsl.hh
engines/eevee/shaders/eevee_colorspace_lib.bsl.hh
engines/eevee/shaders/eevee_cryptomatte.bsl.hh
engines/eevee/shaders/eevee_cryptomatte_sort.bsl.hh
engines/eevee/shaders/eevee_debug.bsl.hh
engines/eevee/shaders/eevee_deferred_eval.bsl.hh
engines/eevee/shaders/eevee_deferred_combine.bsl.hh
engines/eevee/shaders/eevee_deferred_thickness_amend.bsl.hh
engines/eevee/shaders/eevee_deferred_tile_classify.bsl.hh
engines/eevee/shaders/eevee_depth_of_field_accumulator.bsl.hh
engines/eevee/shaders/eevee_depth_of_field_bokeh_lut.bsl.hh
engines/eevee/shaders/eevee_depth_of_field_filter.bsl.hh
engines/eevee/shaders/eevee_depth_of_field_gather.bsl.hh
engines/eevee/shaders/eevee_depth_of_field_lib.bsl.hh
engines/eevee/shaders/eevee_depth_of_field_resolve.bsl.hh
engines/eevee/shaders/eevee_depth_of_field_scatter.bsl.hh
engines/eevee/shaders/eevee_depth_of_field_setup.bsl.hh
engines/eevee/shaders/eevee_depth_of_field_tiles.bsl.hh
engines/eevee/shaders/eevee_film.bsl.hh
engines/eevee/shaders/eevee_filter.bsl.hh
engines/eevee/shaders/eevee_forward_lib.bsl.hh
engines/eevee/shaders/eevee_forward_resolve.bsl.hh
engines/eevee/shaders/eevee_gbuffer_types.bsl.hh
engines/eevee/shaders/eevee_gbuffer_read.bsl.hh
engines/eevee/shaders/eevee_gbuffer_write.bsl.hh
engines/eevee/shaders/eevee_geom_curves.bsl.hh
engines/eevee/shaders/eevee_geom_mesh.bsl.hh
engines/eevee/shaders/eevee_geom_pointcloud.bsl.hh
engines/eevee/shaders/eevee_geom_types_lib.bsl.hh
engines/eevee/shaders/eevee_geom_volume.bsl.hh
engines/eevee/shaders/eevee_geom_world.bsl.hh
engines/eevee/shaders/eevee_hiz.bsl.hh
engines/eevee/shaders/eevee_hiz_update.bsl.hh
engines/eevee/shaders/eevee_fast_gi.bsl.hh
engines/eevee/shaders/eevee_light_culling.bsl.hh
engines/eevee/shaders/eevee_light_shape_display.bsl.hh
engines/eevee/shaders/eevee_light_shadow_setup.bsl.hh
engines/eevee/shaders/eevee_light_data.bsl.hh
engines/eevee/shaders/eevee_light_eval.bsl.hh
engines/eevee/shaders/eevee_light_iter.bsl.hh
engines/eevee/shaders/eevee_light_lib.bsl.hh
engines/eevee/shaders/eevee_lightprobe.bsl.hh
engines/eevee/shaders/eevee_lightprobe_display.bsl.hh
engines/eevee/shaders/eevee_lightprobe_plane.bsl.hh
engines/eevee/shaders/eevee_lightprobe_sphere.bsl.hh
engines/eevee/shaders/eevee_lightprobe_sphere_bake.bsl.hh
engines/eevee/shaders/eevee_lightprobe_sphere_culling.bsl.hh
engines/eevee/shaders/eevee_lightprobe_volume.bsl.hh
engines/eevee/shaders/eevee_lightprobe_volume_bake.bsl.hh
engines/eevee/shaders/eevee_lightprobe_volume_load.bsl.hh
engines/eevee/shaders/eevee_lookdev_copy_world.bsl.hh
engines/eevee/shaders/eevee_lookdev_display.bsl.hh
engines/eevee/shaders/eevee_ltc_lib.bsl.hh
engines/eevee/shaders/eevee_ltc_lut_lib.bsl.hh
engines/eevee/shaders/eevee_material_variants.bsl.hh
engines/eevee/shaders/eevee_motion_blur.bsl.hh
engines/eevee/shaders/eevee_nodetree_closures_lib.glsl
engines/eevee/shaders/eevee_nodetree_frag_lib.glsl
engines/eevee/shaders/eevee_nodetree_lib.bsl.hh
engines/eevee/shaders/eevee_nodetree_type_lib.glsl
engines/eevee/shaders/eevee_nodetree_vert_lib.glsl
engines/eevee/shaders/eevee_occupancy_convert.bsl.hh
engines/eevee/shaders/eevee_occupancy_lib.bsl.hh
engines/eevee/shaders/eevee_octahedron_lib.bsl.hh
engines/eevee/shaders/eevee_pipeline.bsl.hh
engines/eevee/shaders/eevee_ray_denoise.bsl.hh
engines/eevee/shaders/eevee_ray_generate.bsl.hh
engines/eevee/shaders/eevee_ray_tile.bsl.hh
engines/eevee/shaders/eevee_ray_trace.bsl.hh
engines/eevee/shaders/eevee_ray_trace_screen_lib.bsl.hh
engines/eevee/shaders/eevee_ray_types_lib.bsl.hh
engines/eevee/shaders/eevee_reverse_z_lib.bsl.hh
engines/eevee/shaders/eevee_renderpass_clear.bsl.hh
engines/eevee/shaders/eevee_renderpass.bsl.hh
engines/eevee/shaders/eevee_sampling_lib.bsl.hh
engines/eevee/shaders/eevee_shadow.bsl.hh
engines/eevee/shaders/eevee_shadow_tracing.bsl.hh
engines/eevee/shaders/eevee_shadow_page_allocate.bsl.hh
engines/eevee/shaders/eevee_shadow_page_clear.bsl.hh
engines/eevee/shaders/eevee_shadow_page_defrag.bsl.hh
engines/eevee/shaders/eevee_shadow_page_free.bsl.hh
engines/eevee/shaders/eevee_shadow_page_mask.bsl.hh
engines/eevee/shaders/eevee_shadow_page_ops.bsl.hh
engines/eevee/shaders/eevee_shadow_tag_update.bsl.hh
engines/eevee/shaders/eevee_shadow_tag_usage.bsl.hh
engines/eevee/shaders/eevee_shadow_tag_usage_transparent.bsl.hh
engines/eevee/shaders/eevee_shadow_tilemap_amend.bsl.hh
engines/eevee/shaders/eevee_shadow_tilemap_bounds.bsl.hh
engines/eevee/shaders/eevee_shadow_tilemap_finalize.bsl.hh
engines/eevee/shaders/eevee_shadow_tilemap_init.bsl.hh
engines/eevee/shaders/eevee_shadow_tilemap_lib.bsl.hh
engines/eevee/shaders/eevee_shadow_visibility.bsl.hh
engines/eevee/shaders/eevee_spherical_harmonics.bsl.hh
engines/eevee/shaders/eevee_subsurface_lib.bsl.hh
engines/eevee/shaders/eevee_subsurface.bsl.hh
engines/eevee/shaders/eevee_surf_capture.bsl.hh
engines/eevee/shaders/eevee_surf_deferred.bsl.hh
engines/eevee/shaders/eevee_surf_depth.bsl.hh
engines/eevee/shaders/eevee_surf_forward.bsl.hh
engines/eevee/shaders/eevee_surf_hybrid.bsl.hh
engines/eevee/shaders/eevee_surf_common.bsl.hh
engines/eevee/shaders/eevee_surf_occupancy.bsl.hh
engines/eevee/shaders/eevee_surf_shadow.bsl.hh
engines/eevee/shaders/eevee_surf_volume.bsl.hh
engines/eevee/shaders/eevee_surf_world.bsl.hh
engines/eevee/shaders/eevee_surfel.bsl.hh
engines/eevee/shaders/eevee_surfel_cluster.bsl.hh
engines/eevee/shaders/eevee_surfel_light.bsl.hh
engines/eevee/shaders/eevee_surfel_list.bsl.hh
engines/eevee/shaders/eevee_surfel_ray.bsl.hh
engines/eevee/shaders/eevee_thickness_lib.bsl.hh
engines/eevee/shaders/eevee_transparency.bsl.hh
engines/eevee/shaders/eevee_uniform.bsl.hh
engines/eevee/shaders/eevee_utility_tx.bsl.hh
engines/eevee/shaders/eevee_velocity.bsl.hh
engines/eevee/shaders/eevee_volume.bsl.hh
engines/eevee/shaders/eevee_volume_lib.bsl.hh
engines/eevee/eevee_camera_shared.hh
engines/eevee/eevee_debug_shared.hh
engines/eevee/eevee_defines.hh
engines/eevee/eevee_depth_of_field_shared.hh
engines/eevee/eevee_film_shared.hh
engines/eevee/eevee_hizbuffer_shared.hh
engines/eevee/eevee_light_shared.hh
engines/eevee/eevee_lightprobe_shared.hh
# engines/eevee/eevee_material_shared.hh # Excluded (shared).
engines/eevee/eevee_motion_blur_shared.hh
engines/eevee/eevee_precompute_shared.hh
engines/eevee/eevee_raytrace_shared.hh
engines/eevee/eevee_renderbuffers_shared.hh
engines/eevee/eevee_sampling_shared.hh
engines/eevee/eevee_shadow_shared.hh
engines/eevee/eevee_subsurface_shared.hh
engines/eevee/eevee_transform.hh
engines/eevee/eevee_uniform_shared.hh
engines/eevee/eevee_velocity_shared.hh
engines/eevee/eevee_volume_shared.hh
engines/workbench/shaders/workbench_cavity.bsl.hh
engines/workbench/shaders/workbench_common.bsl.hh
engines/workbench/shaders/workbench_common_lib.glsl
engines/workbench/shaders/workbench_composite.bsl.hh
engines/workbench/shaders/workbench_curvature.bsl.hh
engines/workbench/shaders/workbench_effect_dof_prepare_frag.glsl
engines/workbench/shaders/workbench_effect_dof_downsample_frag.glsl
engines/workbench/shaders/workbench_effect_dof_blur1_frag.glsl
engines/workbench/shaders/workbench_effect_dof_blur2_frag.glsl
engines/workbench/shaders/workbench_effect_dof_resolve_frag.glsl
engines/workbench/shaders/workbench_effect_dof_lib.glsl
engines/workbench/shaders/workbench_effect_outline_frag.glsl
engines/workbench/shaders/workbench_effect_smaa_frag.glsl
engines/workbench/shaders/workbench_effect_smaa_vert.glsl
engines/workbench/shaders/workbench_effect_taa_frag.glsl
engines/workbench/shaders/workbench_fullscreen_vert.glsl
engines/workbench/shaders/workbench_image_lib.glsl
engines/workbench/shaders/workbench_image.bsl.hh
engines/workbench/shaders/workbench_matcap_lib.glsl
engines/workbench/shaders/workbench_matcap.bsl.hh
engines/workbench/shaders/workbench_material.bsl.hh
engines/workbench/shaders/workbench_merge_depth_frag.glsl
engines/workbench/shaders/workbench_overlay_depth_frag.glsl
engines/workbench/shaders/workbench_prepass.bsl.hh
engines/workbench/shaders/workbench_shadow.bsl.hh
engines/workbench/shaders/workbench_shadow_visibility.bsl.hh
engines/workbench/shaders/workbench_transparent_accum_frag.glsl
engines/workbench/shaders/workbench_transparent_resolve_frag.glsl
engines/workbench/shaders/workbench_volume.bsl.hh
engines/workbench/shaders/workbench_world_light_lib.glsl
engines/workbench/shaders/workbench_world_light.bsl.hh
engines/workbench/workbench_defines.hh
engines/workbench/workbench_shader_shared.hh
intern/shaders/osd_patch_defines.glsl
intern/shaders/osd_patch_basis.glsl
intern/shaders/subdiv_custom_data_interp_comp.glsl
intern/shaders/subdiv_ibo_lines_comp.glsl
intern/shaders/subdiv_ibo_tris_comp.glsl
intern/shaders/subdiv_lib.glsl
intern/shaders/subdiv_normals_accumulate_comp.glsl
intern/shaders/subdiv_patch_evaluation_comp.glsl
intern/shaders/subdiv_vbo_edge_fac_comp.glsl
intern/shaders/subdiv_vbo_edituv_stretch_angle_comp.glsl
intern/shaders/subdiv_vbo_edituv_stretch_area_comp.glsl
intern/shaders/subdiv_vbo_lnor_comp.glsl
intern/shaders/subdiv_vbo_paint_overlay_flag_comp.glsl
intern/shaders/subdiv_vbo_sculpt_data_comp.glsl
intern/shaders/draw_aabb_lib.glsl
intern/shaders/draw_colormanagement_lib.glsl
intern/shaders/draw_debug_draw_lib.glsl
intern/shaders/draw_debug_shape_lib.glsl
intern/shaders/draw_fxaa_lib.glsl
intern/shaders/draw_curves_interpolation_comp.glsl
intern/shaders/draw_curves_length_intercept_comp.glsl
intern/shaders/draw_curves_lib.glsl
intern/shaders/draw_curves_topology_comp.glsl
intern/shaders/draw_shape_lib.glsl
intern/shaders/draw_command_generate_comp.glsl
intern/shaders/draw_debug_draw_display_frag.glsl
intern/shaders/draw_debug_draw_display_vert.glsl
intern/shaders/draw_grease_pencil_lib.glsl
intern/shaders/draw_intersect_lib.glsl
intern/shaders/draw_math_geom_lib.glsl
intern/shaders/draw_model_lib.glsl
intern/shaders/draw_model.bsl.hh
intern/shaders/draw_object_infos_lib.glsl
intern/shaders/draw_pointcloud_lib.glsl
intern/shaders/draw_resource_finalize_comp.glsl
intern/shaders/draw_view_clipping_lib.glsl
intern/shaders/draw_view_finalize_comp.glsl
intern/shaders/draw_view_lib.glsl
intern/shaders/draw_view.bsl.hh
intern/shaders/draw_view_reconstruction_lib.glsl
intern/shaders/draw_visibility_comp.glsl
intern/draw_attribute_shader_shared.hh
intern/draw_command_shared.hh
intern/draw_curves_defines.hh
intern/draw_defines.hh
intern/draw_shader_shared.hh
intern/draw_subdiv_defines.hh
intern/draw_subdiv_shader_shared.hh
engines/gpencil/shaders/gpencil_frag.glsl
engines/gpencil/shaders/gpencil_vert.glsl
engines/gpencil/shaders/gpencil_antialiasing_accumulation_frag.glsl
engines/gpencil/shaders/gpencil_antialiasing_frag.glsl
engines/gpencil/shaders/gpencil_antialiasing_vert.glsl
engines/gpencil/shaders/gpencil_common_lib.glsl
engines/gpencil/shaders/gpencil_fullscreen_vert.glsl
engines/gpencil/shaders/gpencil_layer_blend_frag.glsl
engines/gpencil/shaders/gpencil_mask_invert_frag.glsl
engines/gpencil/shaders/gpencil_depth_merge_frag.glsl
engines/gpencil/shaders/gpencil_depth_merge_vert.glsl
engines/gpencil/shaders/gpencil_depth_pass_merge_frag.glsl
engines/gpencil/shaders/gpencil_vfx_frag.glsl
engines/gpencil/gpencil_defines.hh
engines/gpencil/gpencil_shader_shared.hh
engines/select/shaders/select_id_vert.glsl
engines/select/shaders/select_id_frag.glsl
engines/select/shaders/select_lib.glsl
engines/select/shaders/select_debug_frag.glsl
engines/select/select_defines.hh
engines/select/select_shader_shared.hh
engines/overlay/shaders/overlay_antialiasing.bsl.hh
engines/overlay/shaders/overlay_armature_dof_solid_frag.glsl
engines/overlay/shaders/overlay_armature_dof_vert.glsl
engines/overlay/shaders/overlay_armature_envelope_outline_vert.glsl
engines/overlay/shaders/overlay_armature_envelope_solid_frag.glsl
engines/overlay/shaders/overlay_armature_envelope_solid_vert.glsl
engines/overlay/shaders/overlay_armature_shape_outline_vert.glsl
engines/overlay/shaders/overlay_armature_shape_solid_frag.glsl
engines/overlay/shaders/overlay_armature_shape_solid_vert.glsl
engines/overlay/shaders/overlay_armature_shape_wire_vert.glsl
engines/overlay/shaders/overlay_armature_shape_wire_frag.glsl
engines/overlay/shaders/overlay_armature_sphere_outline_vert.glsl
engines/overlay/shaders/overlay_armature_sphere_solid_frag.glsl
engines/overlay/shaders/overlay_armature_sphere_solid_vert.glsl
engines/overlay/shaders/overlay_armature_stick_frag.glsl
engines/overlay/shaders/overlay_armature_stick_vert.glsl
engines/overlay/shaders/overlay_armature_wire_frag.glsl
engines/overlay/shaders/overlay_armature_wire_vert.glsl
engines/overlay/shaders/overlay_background_frag.glsl
engines/overlay/shaders/overlay_clipbound_vert.glsl
engines/overlay/shaders/overlay_common_lib.glsl
engines/overlay/shaders/overlay_depth_only_curves_vert.glsl
engines/overlay/shaders/overlay_depth_only_frag.glsl
engines/overlay/shaders/overlay_depth_only_gpencil_frag.glsl
engines/overlay/shaders/overlay_depth_only_gpencil_vert.glsl
engines/overlay/shaders/overlay_depth_only_mesh_conservative_vert.glsl
engines/overlay/shaders/overlay_depth_only_pointcloud_vert.glsl
engines/overlay/shaders/overlay_depth_only_vert.glsl
engines/overlay/shaders/overlay_edit_curve_handle_vert.glsl
engines/overlay/shaders/overlay_edit_curve_normals_vert.glsl
engines/overlay/shaders/overlay_edit_curve_point_vert.glsl
engines/overlay/shaders/overlay_edit_curve_wire_vert.glsl
engines/overlay/shaders/overlay_edit_curves_handle_vert.glsl
engines/overlay/shaders/overlay_edit_gpencil_canvas_vert.glsl
engines/overlay/shaders/overlay_edit_lattice_point_vert.glsl
engines/overlay/shaders/overlay_edit_lattice_wire_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_analysis_frag.glsl
engines/overlay/shaders/overlay_edit_mesh_analysis_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_common_lib.glsl
engines/overlay/shaders/overlay_edit_mesh_depth_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_frag.glsl
engines/overlay/shaders/overlay_edit_mesh_normal_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_skin_root_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_lib.glsl
engines/overlay/shaders/overlay_edit_mesh_edge_vert.glsl
engines/overlay/shaders/overlay_edit_mesh_facedot_vert.glsl
engines/overlay/shaders/overlay_edit_particle_point_vert.glsl
engines/overlay/shaders/overlay_edit_particle_strand_vert.glsl
engines/overlay/shaders/overlay_edit_pointcloud_vert.glsl
engines/overlay/shaders/overlay_edit_uv_edges_frag.glsl
engines/overlay/shaders/overlay_edit_uv_edges_vert.glsl
engines/overlay/shaders/overlay_edit_uv_face_dots_vert.glsl
engines/overlay/shaders/overlay_edit_uv_faces_vert.glsl
engines/overlay/shaders/overlay_edit_uv_image_mask_frag.glsl
engines/overlay/shaders/overlay_edit_uv_image_vert.glsl
engines/overlay/shaders/overlay_edit_uv_stretching_vert.glsl
engines/overlay/shaders/overlay_edit_uv_tiled_image_borders_vert.glsl
engines/overlay/shaders/overlay_edit_uv_verts_frag.glsl
engines/overlay/shaders/overlay_edit_uv_verts_vert.glsl
engines/overlay/shaders/overlay_extra_frag.glsl
engines/overlay/shaders/overlay_extra_groundline_vert.glsl
engines/overlay/shaders/overlay_extra_lightprobe_grid_vert.glsl
engines/overlay/shaders/overlay_extra_loose_point_frag.glsl
engines/overlay/shaders/overlay_extra_loose_point_vert.glsl
engines/overlay/shaders/overlay_extra_point_vert.glsl
engines/overlay/shaders/overlay_extra_vert.glsl
engines/overlay/shaders/overlay_extra_wire_frag.glsl
engines/overlay/shaders/overlay_extra_wire_vert.glsl
engines/overlay/shaders/overlay_facing_frag.glsl
engines/overlay/shaders/overlay_facing_vert.glsl
engines/overlay/shaders/overlay_fullscreen_vert.glsl
engines/overlay/shaders/overlay_grid_frag.glsl
engines/overlay/shaders/overlay_grid_vert.glsl
engines/overlay/shaders/overlay_grid_common_lib.glsl
engines/overlay/shaders/overlay_image_frag.glsl
engines/overlay/shaders/overlay_image_vert.glsl
engines/overlay/shaders/overlay_motion_path_line_frag.glsl
engines/overlay/shaders/overlay_motion_path_line_vert.glsl
engines/overlay/shaders/overlay_motion_path_point_vert.glsl
engines/overlay/shaders/overlay_outline_detect_frag.glsl
engines/overlay/shaders/overlay_outline_prepass_curves_vert.glsl
engines/overlay/shaders/overlay_outline_prepass_frag.glsl
engines/overlay/shaders/overlay_outline_prepass_gpencil_frag.glsl
engines/overlay/shaders/overlay_outline_prepass_gpencil_vert.glsl
engines/overlay/shaders/overlay_outline_prepass_pointcloud_vert.glsl
engines/overlay/shaders/overlay_outline_prepass_vert.glsl
engines/overlay/shaders/overlay_outline_prepass_wire_vert.glsl
engines/overlay/shaders/overlay_paint_face_vert.glsl
engines/overlay/shaders/overlay_paint_point_vert.glsl
engines/overlay/shaders/overlay_paint_texture_frag.glsl
engines/overlay/shaders/overlay_paint_texture_vert.glsl
engines/overlay/shaders/overlay_paint_weight_frag.glsl
engines/overlay/shaders/overlay_paint_weight_vert.glsl
engines/overlay/shaders/overlay_paint_wire_vert.glsl
engines/overlay/shaders/overlay_particle_frag.glsl
engines/overlay/shaders/overlay_particle_vert.glsl
engines/overlay/shaders/overlay_particle_hair_vert.glsl
engines/overlay/shaders/overlay_particle_shape_vert.glsl
engines/overlay/shaders/overlay_particle_shape_frag.glsl
engines/overlay/shaders/overlay_point_varying_color_frag.glsl
engines/overlay/shaders/overlay_point_varying_color_varying_outline_aa_frag.glsl
engines/overlay/shaders/overlay_sculpt_curves_cage_vert.glsl
engines/overlay/shaders/overlay_sculpt_curves_selection_frag.glsl
engines/overlay/shaders/overlay_sculpt_curves_selection_vert.glsl
engines/overlay/shaders/overlay_sculpt_mask_frag.glsl
engines/overlay/shaders/overlay_sculpt_mask_vert.glsl
engines/overlay/shaders/overlay_uniform_color_frag.glsl
engines/overlay/shaders/overlay_varying_color.glsl
engines/overlay/shaders/overlay_viewer_attribute_curve_vert.glsl
engines/overlay/shaders/overlay_viewer_attribute_curves_vert.glsl
engines/overlay/shaders/overlay_viewer_attribute_frag.glsl
engines/overlay/shaders/overlay_viewer_attribute_mesh_vert.glsl
engines/overlay/shaders/overlay_viewer_attribute_pointcloud_vert.glsl
engines/overlay/shaders/overlay_volume_gridlines_vert.glsl
engines/overlay/shaders/overlay_volume_velocity_vert.glsl
engines/overlay/shaders/overlay_wireframe_frag.glsl
engines/overlay/shaders/overlay_wireframe_vert.glsl
engines/overlay/shaders/overlay_xray_fade.bsl.hh
engines/overlay/overlay_shader_shared.hh
engines/image/shaders/image_engine_color_frag.glsl
engines/image/shaders/image_engine_color_vert.glsl
engines/image/shaders/image_engine_depth_frag.glsl
engines/image/shaders/image_engine_depth_vert.glsl
engines/image/shaders/image_engine_image_frag.glsl
engines/image/shaders/image_engine_image_tiled_frag.glsl
engines/image/shaders/image_engine_lib.glsl
engines/image/image_shader_shared.hh
# This test doesn't depend on gpu_shader_test, so it's always included.
intern/shaders/draw_curves_test.glsl
)
# These tests depend on gpu_shader_test, so they're only included when WITH_GPU_BACKEND_TESTS is enabled.
set(GLSL_SRC_TEST
engines/eevee/shaders/eevee_test_gbuffer_closure.bsl.hh
engines/eevee/shaders/eevee_test_gbuffer_normal.bsl.hh
engines/eevee/shaders/eevee_test_fast_gi.bsl.hh
engines/eevee/shaders/eevee_test_shadow.bsl.hh
engines/eevee/shaders/eevee_test_occupancy.bsl.hh
)
if(WITH_GTESTS)
if(WITH_GPU_BACKEND_TESTS)
list(APPEND GLSL_SRC ${GLSL_SRC_TEST})
endif()
endif()
set(GLSL_INC
../gpu
../gpu/intern
../gpu/shaders
../gpu/shaders/common
../gpu/shaders/infos
engines/gpencil
engines/gpencil/shaders
engines/gpencil/shaders/infos
engines/workbench
engines/workbench/shaders
engines/workbench/shaders/infos
engines/eevee
engines/eevee/shaders
engines/eevee/shaders/infos
engines/overlay
engines/overlay/shaders
engines/overlay/shaders/infos
engines/select
engines/select/shaders
engines/select/shaders/infos
engines/image
engines/image/shaders
engines/image/shaders/infos
intern
intern/shaders
)
set(GLSL_C "")
foreach(GLSL_FILE ${GLSL_SRC})
glsl_to_c(${GLSL_FILE} GLSL_C GLSL_INC)
endforeach()
blender_add_lib(bf_draw_shaders "${GLSL_C}" "" "" "")
blender_set_target_unity_build(bf_draw_shaders 10)
list(APPEND LIB
PRIVATE bf::animrig
bf_draw_shaders
)
set(GLSL_SOURCE_CONTENT "")
set(GLSL_METADATA_CONTENT "")
set(GLSL_INFOS_CONTENT "")
foreach(GLSL_FILE ${GLSL_SRC})
get_filename_component(GLSL_FILE_NAME ${GLSL_FILE} NAME)
string(REPLACE "." "_" GLSL_FILE_NAME_UNDERSCORES ${GLSL_FILE_NAME})
string(APPEND GLSL_FILE_NAME_UNDERSCORES "_tmp")
string(APPEND GLSL_SOURCE_CONTENT "SHADER_SOURCE\(${GLSL_FILE_NAME_UNDERSCORES}, \"${GLSL_FILE_NAME}\", \"${GLSL_FILE}\"\)\n")
string(APPEND GLSL_METADATA_CONTENT "#include \"${GLSL_FILE}.hh\"\n")
string(APPEND GLSL_INFOS_CONTENT "#include \"${GLSL_FILE}.info\"\n")
endforeach()
set(glsl_source_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_draw_source_list.h")
file(GENERATE OUTPUT ${glsl_source_list_file} CONTENT "${GLSL_SOURCE_CONTENT}")
list(APPEND SRC ${glsl_source_list_file})
set(glsl_metadata_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_draw_metadata_list.hh")
file(GENERATE OUTPUT ${glsl_metadata_list_file} CONTENT "${GLSL_METADATA_CONTENT}")
list(APPEND SRC ${glsl_metadata_list_file})
set(glsl_infos_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_draw_infos_list.hh")
file(GENERATE OUTPUT ${glsl_infos_list_file} CONTENT "${GLSL_INFOS_CONTENT}")
list(APPEND SRC ${glsl_infos_list_file})
list(APPEND INC ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(bf_draw_shaders PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
if(WITH_DRAW_DEBUG)
list(APPEND SRC
engines/select/select_debug_engine.cc
)
add_definitions(-DWITH_DRAW_DEBUG)
endif()
if(WITH_MOD_FLUID)
list(APPEND INC
../../../intern/mantaflow/extern
)
add_definitions(-DWITH_FLUID)
endif()
if(WITH_FREESTYLE)
add_definitions(-DWITH_FREESTYLE)
endif()
if(WITH_XR_OPENXR)
add_definitions(-DWITH_XR_OPENXR)
endif()
if(WITH_GTESTS)
if(WITH_GPU_DRAW_TESTS)
add_definitions(-DWITH_GPU_DRAW_TESTS)
if(WITH_OPENGL_BACKEND)
add_definitions(-DWITH_OPENGL_BACKEND)
endif()
if(WITH_METAL_BACKEND)
add_definitions(-DWITH_METAL_BACKEND)
endif()
if(WITH_VULKAN_BACKEND)
add_definitions(-DWITH_VULKAN_BACKEND)
list(APPEND LIB
PRIVATE bf::dependencies::optional::vulkan
)
endif()
endif()
endif()
blender_add_lib(bf_draw "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_library(bf::draw ALIAS bf_draw)
if(WITH_GTESTS)
if(WITH_GPU_DRAW_TESTS)
set(TEST_SRC
tests/draw_curves_test.cc
tests/draw_pass_test.cc
tests/eevee_test.cc
tests/draw_testing.hh
)
set(TEST_INC
${INC}
../../../intern/ghost
../gpu/tests
)
set(TEST_LIB
${LIB}
)
set(TEST_COMMON_SRC
tests/draw_testing.cc
)
if(NOT WITH_TESTS_SINGLE_BINARY)
list(APPEND TEST_COMMON_SRC
../gpu/tests/gpu_testing.cc
../gpu/tests/gpu_testing.hh
)
else()
list(APPEND TEST_LIB gpu_tests)
endif()
blender_add_test_suite_lib(draw "${TEST_SRC}" "${TEST_INC}" "${INC_SYS}" "${TEST_LIB}" "${TEST_COMMON_SRC}")
endif()
endif()

View File

@@ -0,0 +1,213 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "BLI_string_ref.hh"
struct DRWInstanceDataList;
class GHOST_IContext;
namespace blender {
struct ARegion;
struct Depsgraph;
struct DRWData;
struct GPUMaterial;
struct GPUOffScreen;
struct GPUVertFormat;
struct GPUViewport;
struct ID;
struct Main;
struct Object;
struct RegionView3D;
struct Render;
struct RenderEngine;
struct RenderEngineType;
struct Scene;
struct View3D;
struct ViewLayer;
struct bContext;
struct rcti;
namespace bke {
enum class AttrType : int16_t;
}
void DRW_engines_register();
void DRW_engines_free();
void DRW_module_init();
void DRW_module_exit();
void DRW_engine_external_free(RegionView3D *rv3d);
enum eDRWSelectStage {
DRW_SELECT_PASS_PRE = 1,
DRW_SELECT_PASS_POST,
};
using DRW_SelectPassFn = bool (*)(eDRWSelectStage stage, void *user_data);
using DRW_ObjectFilterFn = bool (*)(Object *ob, void *user_data);
/**
* Everything starts here.
* This function takes care of calling all cache and rendering functions
* for each relevant engine / mode engine.
*/
void DRW_draw_view(const bContext *C);
/**
* Draw render engine info.
*/
void DRW_draw_region_engine_info(int xoffset, int *yoffset, int line_height);
/**
* \param context: can be nullptr, optionally passed to the DRWContext for draw handlers/callbacks.
* \param viewport: can be nullptr, in this case we create one.
*/
void DRW_draw_render_loop_offscreen(Depsgraph *depsgraph,
RenderEngineType *engine_type,
ARegion *region,
View3D *v3d,
bContext *context,
bool is_image_render,
bool draw_background,
bool do_color_management,
GPUOffScreen *ofs,
GPUViewport *viewport);
/**
* Object mode select-loop.
*/
void DRW_draw_select_loop(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
bool draw_surface,
bool use_nearest,
bool do_material_sub_selection,
const rcti *rect,
DRW_SelectPassFn select_pass_fn,
void *select_pass_user_data,
DRW_ObjectFilterFn object_filter_fn,
void *object_filter_user_data);
/**
* Used by auto-depth and other depth queries feature.
*/
void DRW_draw_depth_loop(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
GPUViewport *viewport,
const bool use_gpencil,
const bool use_only_selected,
const bool use_only_active_object);
void DRW_draw_select_id(Depsgraph *depsgraph, ARegion *region, View3D *v3d);
/**
* Query that drawing is in progress (use to prevent nested draw calls).
*/
bool DRW_draw_in_progress();
/* Grease pencil render. */
/**
* This function only does following things to check whether grease pencil drawing is needed:
* - Whether Grease Pencil objects are excluded in the viewport.
* - If any Grease Pencil typed ID exists inside the depsgraph.
* - If any of the objects has grease pencil geometries that are generated by geometry nodes.
*/
bool DRW_render_check_grease_pencil(Depsgraph *depsgraph, View3D *v3d = nullptr);
/**
* Render grease pencil on top of other render engine output.
* This function creates a DRWContext.
*/
void DRW_render_gpencil(RenderEngine *engine, Depsgraph *depsgraph);
void DRW_render_context_enable(Render *render);
void DRW_render_context_disable(Render *render);
void DRW_mutexes_init();
void DRW_mutexes_exit();
/* Mutex to lock the drw manager and avoid concurrent context usage.
* Equivalent to the old DST lock.
* Brought back to 4.5 due to unforeseen issues causing data races and race conditions with Images
* and GPUTextures. (See #141253) */
void DRW_lock_start();
void DRW_lock_end();
/* Critical section for gpu::Shader usage. Can be removed when we have threadsafe gpu::Shader
* class. */
void DRW_submission_start();
void DRW_submission_end();
void DRW_gpu_context_create();
void DRW_gpu_context_destroy();
/**
* Binds the draw GPU context to the active thread.
* In background mode, this will create the draw GPU context on first call.
*/
void DRW_gpu_context_enable();
/**
* Tries to bind the draw GPU context to the active thread.
* Returns true on success, false if the draw GPU context does not exists.
*/
bool DRW_gpu_context_try_enable();
/**
* Returns true if the DRW_gpu_context is enabled on the calling thread.
*/
bool DRW_gpu_context_is_enabled();
void DRW_gpu_context_disable();
#ifdef WITH_XR_OPENXR
/* XXX: see comment on #DRW_system_gpu_context_get() */
GHOST_IContext *DRW_system_gpu_context_get();
void *DRW_xr_blender_gpu_context_get();
void DRW_xr_drawing_begin();
void DRW_xr_drawing_end();
#endif
/** For garbage collection. */
void DRW_cache_free_old_batches(Main *bmain);
namespace draw {
/** Free garbage collected subdivision data. */
void DRW_cache_free_old_subdiv();
} // namespace draw
/** Never use this. Only for closing blender. */
void DRW_gpu_context_enable_ex(bool restore);
void DRW_gpu_context_disable_ex(bool restore);
/* Render pipeline GPU context control.
* Enable system context first, then enable blender context,
* then disable blender context, then disable system context. */
void DRW_system_gpu_render_context_enable(GHOST_IContext *re_system_gpu_context);
void DRW_system_gpu_render_context_disable(GHOST_IContext *re_system_gpu_context);
void DRW_blender_gpu_render_context_enable(void *re_gpu_context);
void DRW_blender_gpu_render_context_disable(void *re_gpu_context);
DRWData *DRW_viewport_data_create();
void DRW_viewport_data_free(DRWData *drw_data);
bool DRW_gpu_context_release();
void DRW_gpu_context_activate(bool drw_state);
namespace draw {
void DRW_cdlayer_attr_aliases_add(GPUVertFormat *format,
const char *base_name,
bke::AttrType data_type,
StringRef layer_name,
bool is_active_render,
bool is_active_layer);
}
} // namespace blender

View File

@@ -0,0 +1,86 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include <variant>
#include "BLI_index_mask_fwd.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "BKE_paint_bvh.hh"
#include "DNA_customdata_types.h"
namespace blender {
namespace gpu {
class Batch;
class IndexBuf;
class VertBuf;
} // namespace gpu
struct Object;
namespace bke {
enum class AttrDomain : int8_t;
namespace pbvh {
class Node;
class DrawCache;
class Tree;
} // namespace pbvh
} // namespace bke
namespace draw::pbvh {
using GenericRequest = std::string;
enum class CustomRequest : int8_t {
Position,
Normal,
Mask,
FaceSet,
};
using AttributeRequest = std::variant<CustomRequest, GenericRequest>;
struct ViewportRequest {
Vector<AttributeRequest> attributes;
bool use_coarse_grids;
friend bool operator==(const ViewportRequest &a, const ViewportRequest &b) = default;
uint64_t hash() const;
};
class DrawCache : public bke::pbvh::DrawCache {
public:
~DrawCache() override = default;
/**
* Recalculate and copy data as necessary to prepare batches for drawing triangles for a
* specific combination of attributes.
*/
virtual Span<gpu::Batch *> ensure_tris_batches(const Object &object,
const ViewportRequest &request,
const IndexMask &nodes_to_update) = 0;
/**
* Recalculate and copy data as necessary to prepare batches for drawing wireframe geometry for a
* specific combination of attributes.
*/
virtual Span<gpu::Batch *> ensure_lines_batches(const Object &object,
const ViewportRequest &request,
const IndexMask &nodes_to_update) = 0;
/**
* Return the material index for each node (all faces in a node should have the same material
* index, as ensured by the BVH building process).
*/
virtual Span<int> ensure_material_indices(const Object &object) = 0;
};
DrawCache &ensure_draw_data(std::unique_ptr<bke::pbvh::DrawCache> &ptr);
} // namespace draw::pbvh
} // namespace blender

View File

@@ -0,0 +1,129 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "DNA_ID.h"
#include "BLI_array.hh"
#include "BLI_map.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_set.hh"
#include "BLI_sys_types.h" /* for bool and uint */
namespace blender {
struct ARegion;
struct Base;
struct Depsgraph;
struct Object;
struct RegionView3D;
struct View3D;
struct rcti;
/**
* Indices inside the selection frame-buffer associated with the elements of a mesh.
*/
struct ElemIndexRanges {
/** Range for each element type. */
IndexRange face;
IndexRange edge;
IndexRange vert;
/** Combined range for the whole object. */
IndexRange total;
};
struct SELECTID_Context {
/** All selectable evaluated objects. */
Vector<Object *> objects;
/** Map of the selectable objects from `objects` to their indices ranges. */
Map<Object *, ElemIndexRanges> elem_ranges;
/**
* Maximum index value that can be contained inside the selection frame-buffer.
* Each object/element type has different range which are described inside `elem_ranges`.
*/
uint max_index_drawn_len;
short select_mode;
/** To check for updates. */
float4x4 persmat;
uint64_t depsgraph_last_update = 0;
bool is_dirty(Depsgraph *depsgraph, RegionView3D *rv3d);
};
/* `draw_select_buffer.cc` */
bool DRW_select_buffer_elem_get(uint sel_id, uint &r_elem, uint &r_base_index, char &r_elem_type);
/**
* Assume it is called right after `DRW_select_buffer_bitmap_from_*` so that the same evaluated
* object is used for drawing and as parameter to this function.
*/
uint DRW_select_buffer_context_offset_for_object_elem(Depsgraph *depsgraph,
Object *object,
char elem_type);
/**
* Main function to read a block of pixels from the select frame buffer.
*/
uint *DRW_select_buffer_read(
Depsgraph *depsgraph, ARegion *region, View3D *v3d, const rcti *rect, uint *r_buf_len);
/**
* \param rect: The rectangle to sample indices from (min/max inclusive).
* \returns a #BLI_bitmap the length of \a bitmap_len or NULL on failure.
*/
uint *DRW_select_buffer_bitmap_from_rect(
Depsgraph *depsgraph, ARegion *region, View3D *v3d, const rcti *rect, uint *r_bitmap_len);
/**
* \param center: Circle center.
* \param radius: Circle radius.
* \param r_bitmap_len: Number of indices in the selection id buffer.
* \returns a #BLI_bitmap the length of \a r_bitmap_len or NULL on failure.
*/
uint *DRW_select_buffer_bitmap_from_circle(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
const int center[2],
int radius,
uint *r_bitmap_len);
/**
* \param poly: The polygon array.
* \param rect: Polygon boundaries.
* \returns a #BLI_bitmap.
*/
uint *DRW_select_buffer_bitmap_from_poly(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
Span<int2> poly,
const rcti *rect,
uint *r_bitmap_len);
/**
* Samples a single pixel.
*/
uint DRW_select_buffer_sample_point(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
const int center[2]);
/**
* Find the selection id closest to \a center.
* \param dist: Use to initialize the distance,
* when found, this value is set to the distance of the selection that's returned.
*/
uint DRW_select_buffer_find_nearest_to_point(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
const int center[2],
uint id_min,
uint id_max,
uint *dist);
void DRW_select_buffer_context_create(Depsgraph *depsgraph, Span<Base *> bases, short select_mode);
} // namespace blender

View File

@@ -0,0 +1,474 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_bounds.hh"
#include "BLI_listbase.h"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "BLI_utildefines.h"
#include "DNA_layer_types.h"
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "DNA_vec_types.h"
#include "DNA_view3d_types.h"
#include "BKE_node.hh"
#include "BKE_node_runtime.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_view3d.hh"
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.hh"
#include "COM_context.hh"
#include "COM_domain.hh"
#include "COM_node_group_operation.hh"
#include "COM_realize_on_domain_operation.hh"
#include "COM_result.hh"
#include "COM_utilities.hh"
#include "GPU_context.hh"
#include "GPU_state.hh"
#include "GPU_texture.hh"
#include "draw_view_data.hh"
#include "compositor_engine.h" /* Own include. */
namespace blender::draw::compositor_engine {
class Context : public compositor::Context {
private:
const Main *main_;
const Scene *scene_;
/* A pointer to the info message of the compositor engine. This is a char array of size
* GPU_INFO_SIZE. The message is cleared prior to updating or evaluating the compositor. */
char *info_message_;
/* Identified if the output of the viewer was written. */
bool viewer_was_written_ = false;
public:
Context(compositor::StaticCacheManager &cache_manager,
const Main *main,
const Scene *scene,
char *info_message)
: compositor::Context(cache_manager), main_(main), scene_(scene), info_message_(info_message)
{
this->set_info_message("");
}
const Main &get_main() const override
{
return *main_;
}
const Scene &get_scene() const override
{
return *scene_;
}
bool use_gpu() const override
{
return true;
}
/* The viewport compositor does not support viewer outputs, so treat viewers as composite
* outputs. */
bool treat_viewer_as_group_output() const override
{
return true;
}
/* In case the viewport has no camera region or is an image render, the domain covers the entire
* viewport. But in case the camera region is not entirely visible in the viewport, the data size
* of the domain will only cover the intersection of the viewport and the camera regions, while
* the display size will cover the virtual extension of the camera region. */
compositor::Domain get_compositing_domain() const override
{
const DRWContext *draw_ctx = DRW_context_get();
/* No camera region or is a viewport render, the domain is the entire viewport. */
if (draw_ctx->rv3d->persp != RV3D_CAMOB || draw_ctx->is_viewport_image_render()) {
return compositor::Domain(int2(draw_ctx->viewport_size_get()));
}
rctf camera_border;
ED_view3d_calc_camera_border(draw_ctx->scene,
draw_ctx->depsgraph,
draw_ctx->region,
draw_ctx->v3d,
draw_ctx->rv3d,
false,
&camera_border);
const Bounds<int2> camera_region = Bounds<int2>(
int2(int(camera_border.xmin), int(camera_border.ymin)),
int2(int(camera_border.xmax), int(camera_border.ymax)));
const Bounds<int2> render_region = Bounds<int2>(int2(0), int2(draw_ctx->viewport_size_get()));
const Bounds<int2> border_region = bounds::intersect(render_region, camera_region).value();
compositor::Domain domain = compositor::Domain(camera_region.size());
domain.data_size = border_region.size();
domain.data_offset = border_region.min - camera_region.min;
return domain;
}
/* Get the bounds of the camera region in pixels relative to the viewport. In case the viewport
* has no camera region or is an image render, return the bounds of the entire viewport. */
Bounds<int2> get_camera_region() const
{
const DRWContext *draw_ctx = DRW_context_get();
const int2 viewport_size = int2(draw_ctx->viewport_size_get());
const Bounds<int2> render_region = Bounds<int2>(int2(0), viewport_size);
/* No camera region or is a viewport render, the domain is the entire viewport. */
if (draw_ctx->rv3d->persp != RV3D_CAMOB || draw_ctx->is_viewport_image_render()) {
return render_region;
}
rctf camera_border;
ED_view3d_calc_camera_border(draw_ctx->scene,
draw_ctx->depsgraph,
draw_ctx->region,
draw_ctx->v3d,
draw_ctx->rv3d,
false,
&camera_border);
const Bounds<int2> camera_region = Bounds<int2>(
int2(int(camera_border.xmin), int(camera_border.ymin)),
int2(int(camera_border.xmax), int(camera_border.ymax)));
return bounds::intersect(render_region, camera_region).value_or(Bounds<int2>(int2(0)));
}
void write_output(const compositor::Result &result)
{
/* Do not write the output if the viewer output was already written. */
if (viewer_was_written_) {
return;
}
gpu::Texture *output = DRW_context_get()->viewport_texture_list_get()->color;
if (result.is_single_value()) {
GPU_texture_clear(output, GPU_DATA_FLOAT, result.get_single_value<compositor::Color>());
return;
}
gpu::Shader *shader = this->get_shader("compositor_write_output",
compositor::ResultPrecision::Half);
GPU_shader_bind(shader);
const Bounds<int2> bounds = this->get_camera_region();
GPU_shader_uniform_2iv(shader, "lower_bound", bounds.min);
GPU_shader_uniform_2iv(shader, "upper_bound", bounds.max);
result.bind_as_texture(shader, "input_tx");
const int image_unit = GPU_shader_get_sampler_binding(shader, "output_img");
GPU_texture_image_bind(output, image_unit);
compositor::compute_dispatch_threads_at_least(shader, result.domain().data_size);
result.unbind_as_texture();
GPU_texture_image_unbind(output);
GPU_shader_unbind();
}
void write_viewer(compositor::Result &viewer_result) override
{
using namespace compositor;
/* Realize the on the compositing domain if needed. */
const Domain compositing_domain = this->get_compositing_domain();
const InputDescriptor input_descriptor = {ResultType::Color,
InputRealizationMode::OperationDomain};
SimpleOperation *realization_operation = RealizeOnDomainOperation::construct_if_needed(
*this, viewer_result, input_descriptor, compositing_domain);
if (realization_operation) {
Result realize_input = this->create_result(ResultType::Color, viewer_result.precision());
realize_input.share_data(viewer_result);
realization_operation->map_input_to_result(&realize_input);
realization_operation->evaluate();
Result &realized_viewer_result = realization_operation->get_result();
this->write_output(realized_viewer_result);
realized_viewer_result.release();
viewer_was_written_ = true;
delete realization_operation;
return;
}
this->write_output(viewer_result);
viewer_was_written_ = true;
}
compositor::Result get_invalid_pass()
{
compositor::Result invalid_pass = this->create_result(compositor::ResultType::Color);
invalid_pass.allocate_invalid();
return invalid_pass;
}
/* Get the pass that corresponds to the given pass name. If no pass with the given name exists,
* returns an unallocated result instead. */
compositor::Result get_pass_result(const char *pass_name)
{
/* Return the pass that was written by the engine if such pass was found. */
if (DRW_viewport_pass_texture_exists(pass_name)) {
gpu::Texture *pass_texture = DRW_viewport_pass_texture_get(pass_name).gpu_texture();
compositor::Result pass = compositor::Result(*this, GPU_texture_format(pass_texture));
pass.share_data(pass_texture);
return pass;
}
/* The combined pass is a special case where we return the viewport color texture if the engine
* didn't provide a combined pass. */
if (STREQ(pass_name, RE_PASSNAME_COMBINED)) {
gpu::Texture *combined_texture = DRW_context_get()->viewport_texture_list_get()->color;
compositor::Result pass = compositor::Result(*this, GPU_texture_format(combined_texture));
pass.share_data(combined_texture);
return pass;
}
return this->create_result(compositor::ResultType::Color);
}
compositor::Result crop_pass(const compositor::Result &pass)
{
const char *shader_name = pass.type() == compositor::ResultType::Float ?
"compositor_image_crop_float" :
"compositor_image_crop_float4";
gpu::Shader *shader = this->get_shader(shader_name, pass.precision());
GPU_shader_bind(shader);
/* The compositing space is limited to a subset of the pass texture, so only read that
* compositing region into an appropriately sized result. */
const int2 lower_bound = this->get_camera_region().min;
GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound);
pass.bind_as_texture(shader, "input_tx");
compositor::Result cropped_pass = this->create_result(pass.type(), pass.precision());
cropped_pass.allocate_texture(this->get_compositing_domain());
cropped_pass.bind_as_image(shader, "output_img");
compositor::compute_dispatch_threads_at_least(shader, cropped_pass.domain().data_size);
GPU_shader_unbind();
pass.unbind_as_texture();
cropped_pass.unbind_as_image();
return cropped_pass;
}
compositor::Result get_pass(const Scene *scene, int view_layer_index, const char *name) override
{
/* Blender aliases the Image pass name to be the Combined pass, so we return the combined pass
* in that case. */
const char *pass_name = StringRef(name) == "Image" ? "Combined" : name;
const Scene *original_scene = DEG_get_original(scene_);
if (DEG_get_original(scene) != original_scene) {
return this->get_invalid_pass();
}
ViewLayer *view_layer = static_cast<ViewLayer *>(
BLI_findlink(&original_scene->view_layers, view_layer_index));
if (StringRef(view_layer->name) != DRW_context_get()->view_layer->name) {
return this->get_invalid_pass();
}
compositor::Result pass = this->get_pass_result(pass_name);
if (!pass.is_allocated()) {
return this->get_invalid_pass();
}
/* The pass data window matches the compositing domain data window, so update its display
* window and return it as is. */
const compositor::Domain compositing_domain = this->get_compositing_domain();
if (this->get_camera_region().min == int2(0) &&
compositing_domain.data_size == pass.domain().data_size)
{
pass.domain() = compositing_domain;
return pass;
}
return this->crop_pass(pass);
}
StringRef get_view_name() const override
{
const SceneRenderView *view = static_cast<SceneRenderView *>(
BLI_findlink(&get_render_data().views, DRW_context_get()->v3d->multiview_eye));
return view->name;
}
compositor::ResultPrecision get_precision() const override
{
switch (get_scene().r.compositor_precision) {
case SCE_COMPOSITOR_PRECISION_AUTO:
return compositor::ResultPrecision::Half;
case SCE_COMPOSITOR_PRECISION_FULL:
return compositor::ResultPrecision::Full;
}
BLI_assert_unreachable();
return compositor::ResultPrecision::Half;
}
void set_info_message(StringRef message) const override
{
message.copy_utf8_truncated(info_message_, GPU_INFO_SIZE);
}
compositor::NodeGroupOutputTypes needed_outputs() const
{
return compositor::NodeGroupOutputTypes::GroupOutputNode |
compositor::NodeGroupOutputTypes::ViewerNode;
}
void evaluate()
{
using namespace compositor;
const bNodeTree &node_group = *DRW_context_get()->scene->compositing_node_group;
const bke::DataBlockComputeContext compute_context(nullptr, this->get_scene().id);
NodeGroupOperation node_group_operation(*this,
node_group,
this->needed_outputs(),
node_group.active_viewer_key,
bke::NODE_INSTANCE_KEY_BASE,
compute_context);
/* Set the reference count for the outputs, only the first color output is actually needed,
* while the rest are ignored. */
node_group.ensure_interface_cache();
for (const bNodeTreeInterfaceSocket *output_socket : node_group.interface_outputs()) {
const bool is_first_output = output_socket == node_group.interface_outputs().first();
Result &output_result = node_group_operation.get_result(output_socket->identifier);
const bool is_color = output_result.type() == ResultType::Color;
output_result.set_reference_count(is_first_output && is_color ? 1 : 0);
}
/* Map the inputs to the operation. */
Vector<std::unique_ptr<Result>> inputs;
for (const bNodeTreeInterfaceSocket *input_socket : node_group.interface_inputs()) {
Result *input_result = new Result(
this->create_result(ResultType::Color, ResultPrecision::Half));
if (input_socket == node_group.interface_inputs()[0]) {
/* First socket is the viewport combined pass. */
const int active_view_layer_index = BLI_findstringindex(
&scene_->view_layers, DRW_context_get()->view_layer->name, offsetof(ViewLayer, name));
Result combined_pass = this->get_pass(
scene_, active_view_layer_index, RE_PASSNAME_COMBINED);
input_result->share_data(combined_pass);
combined_pass.release();
}
else {
/* The rest of the sockets are not supported. */
input_result->allocate_invalid();
}
node_group_operation.map_input_to_result(input_socket->identifier, input_result);
inputs.append(std::unique_ptr<Result>(input_result));
}
node_group_operation.evaluate();
/* Write the outputs of the operation. */
for (const bNodeTreeInterfaceSocket *output_socket : node_group.interface_outputs()) {
Result &output_result = node_group_operation.get_result(output_socket->identifier);
if (!output_result.should_compute()) {
continue;
}
/* Realize the output on the compositing domain if needed. */
const Domain compositing_domain = this->get_compositing_domain();
const InputDescriptor input_descriptor = {ResultType::Color,
InputRealizationMode::OperationDomain};
SimpleOperation *realization_operation = RealizeOnDomainOperation::construct_if_needed(
*this, output_result, input_descriptor, compositing_domain);
if (realization_operation) {
realization_operation->map_input_to_result(&output_result);
realization_operation->evaluate();
Result &realized_output_result = realization_operation->get_result();
this->write_output(realized_output_result);
realized_output_result.release();
delete realization_operation;
continue;
}
this->write_output(output_result);
output_result.release();
}
}
};
class Instance : public DrawEngine {
private:
compositor::StaticCacheManager cache_manager_;
public:
StringRefNull name_get() final
{
return "Compositor";
}
void init() final {};
void begin_sync() final {};
void object_sync(draw::ObjectRef & /*ob_ref*/, draw::Manager & /*manager*/) final {};
void end_sync() final {};
void draw(Manager & /*manager*/) final
{
Context context(cache_manager_,
DEG_get_bmain(DRW_context_get()->depsgraph),
DRW_context_get()->scene,
this->info);
if (context.get_camera_region().is_empty()) {
return;
}
DRW_submission_start();
#if defined(__APPLE__)
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
/* NOTE(Metal): Isolate Compositor compute work in individual command buffer to improve
* workload scheduling. When expensive compositor nodes are in the graph, these can stall out
* the GPU for extended periods of time and sub-optimally schedule work for execution. */
GPU_flush();
}
#endif
context.evaluate();
context.cache_manager().reset();
#if defined(__APPLE__)
/* NOTE(Metal): Following previous flush to break command stream, with compositor command
* buffers potentially being heavy, we avoid issuing subsequent commands until compositor work
* has completed. If subsequent work is prematurely queued up, the subsequent command buffers
* will be blocked behind compositor work and may trigger a command buffer time-out error. As a
* result, we should wait for compositor work to complete.
*
* This is not an efficient approach for peak performance, but a catch-all to prevent command
* buffer failure, until the offending cases can be resolved. */
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
GPU_finish();
}
#endif
DRW_submission_end();
}
};
DrawEngine *Engine::create_instance()
{
return new Instance();
}
} // namespace blender::draw::compositor_engine

View File

@@ -0,0 +1,15 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DRW_render.hh"
namespace blender::draw::compositor_engine {
struct Engine : public DrawEngine::Pointer {
DrawEngine *create_instance() final;
};
} // namespace blender::draw::compositor_engine

View File

@@ -0,0 +1,107 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Ground Truth Ambient Occlusion
*
* Based on Practical Realtime Strategies for Accurate Indirect Occlusion
* http://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pdf
* http://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pptx
*
* Algorithm Overview:
*
* We separate the computation into 2 steps.
*
* - First we scan the neighborhood pixels to find the maximum horizon angle.
* We save this angle in a RG8 array texture.
*
* - Then we use this angle to compute occlusion with the shading normal at
* the shading stage. This let us do correct shadowing for each diffuse / specular
* lobe present in the shader using the correct normal.
*/
#include "eevee_ambient_occlusion.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name AmbientOcclusion
* \{ */
void AmbientOcclusion::init()
{
render_pass_enabled_ = inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_AO;
const SceneEEVEE &sce_eevee = inst_.scene->eevee;
/* AO node uses its own number of samples. */
data_.lod_factor_ao = 1.0f / (1.0f + sce_eevee.fast_gi_quality * 4.0f);
/* Scale up to LOD 5 at the end of the ray. */
data_.lod_factor = (1.0f - sce_eevee.fast_gi_quality) * 0.2f;
data_.angle_bias = 1.0 / max_ff(1e-8f, 1.0 - sce_eevee.fast_gi_bias);
data_.thickness_near = sce_eevee.fast_gi_thickness_near;
/* Size is multiplied by 2 because it is applied in NDC [-1..1] range. */
data_.pixel_size = float2(2.0f) / float2(inst_.film.render_extent_get());
ray_count_ = sce_eevee.fast_gi_ray_count;
step_count_ = sce_eevee.fast_gi_step_count;
dummy_tx_.ensure_2d(gpu::TextureFormat::SFLOAT_16, int2(1), GPU_TEXTURE_USAGE_SHADER_READ);
}
void AmbientOcclusion::sync()
{
const SceneEEVEE &sce_eevee = inst_.scene->eevee;
const ViewLayerEEVEE &view_layer_eevee = inst_.view_layer->eevee;
/* Needs camera to be synced first. */
float max_distance = inst_.camera.bound_radius() * 2.0f;
data_.distance = (view_layer_eevee.ambient_occlusion_distance > 0.0f) ?
view_layer_eevee.ambient_occlusion_distance :
max_distance;
data_.gi_distance = (sce_eevee.fast_gi_distance > 0.0f) ? sce_eevee.fast_gi_distance :
max_distance;
if (!render_pass_enabled_) {
return;
}
render_pass_ps_.init();
gpu::Shader *sh = inst_.shaders.static_shader_get(AMBIENT_OCCLUSION_PASS);
render_pass_ps_.specialize_constant(sh, "ao_slice_count", ray_count_);
render_pass_ps_.specialize_constant(sh, "ao_step_count", step_count_);
render_pass_ps_.shader_set(sh);
render_pass_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, &inst_.pipelines.utility_tx);
render_pass_ps_.bind_resources(inst_.uniform_data);
render_pass_ps_.bind_resources(inst_.sampling);
render_pass_ps_.bind_resources(inst_.hiz_buffer.front);
render_pass_ps_.bind_texture("dummy_tx", &dummy_tx_);
render_pass_ps_.bind_image("in_normal_img", &inst_.render_buffers.rp_color_tx);
render_pass_ps_.push_constant("in_normal_img_layer_index", &inst_.render_buffers.data.normal_id);
render_pass_ps_.bind_image("out_ao_img", &inst_.render_buffers.rp_value_tx);
render_pass_ps_.push_constant("out_ao_img_layer_index",
&inst_.render_buffers.data.ambient_occlusion_id);
render_pass_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
render_pass_ps_.dispatch(
math::divide_ceil(inst_.film.render_extent_get(), int2(AMBIENT_OCCLUSION_PASS_TILE_SIZE)));
}
void AmbientOcclusion::render_pass(View &view)
{
if (!render_pass_enabled_) {
return;
}
inst_.hiz_buffer.update();
inst_.manager->submit(render_pass_ps_, view);
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Ground Truth Ambient Occlusion
*
* Based on Practical Realtime Strategies for Accurate Indirect Occlusion
* http://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pdf
* http://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pptx
*/
#pragma once
#include "draw_pass.hh"
#include "eevee_raytrace_shared.hh"
namespace blender::eevee {
using namespace draw;
class Instance;
/* -------------------------------------------------------------------- */
/** \name AmbientOcclusion
* \{ */
class AmbientOcclusion {
private:
class Instance &inst_;
bool render_pass_enabled_ = false;
int ray_count_ = 0;
int step_count_ = 0;
AOData &data_;
PassSimple render_pass_ps_ = {"AO Render Pass"};
Texture dummy_tx_ = {"dummy_tx"};
public:
AmbientOcclusion(Instance &inst, AOData &data) : inst_(inst), data_(data) {};
~AmbientOcclusion() {};
void init();
void sync();
void render(View &view);
void render_pass(View &view);
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,296 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#include "BKE_screen.hh"
#include "BLI_math_matrix.hh"
#include "BLI_rect.h"
#include "DRW_render.hh"
#include "DNA_camera_types.h"
#include "DNA_view3d_types.h"
#include "RE_engine.h"
#include "RE_pipeline.h"
#include "render_types.h"
#include "eevee_camera.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Camera
* \{ */
void Camera::init()
{
const Object *camera_eval = inst_.camera_eval_object;
CameraData &data = data_;
if (camera_eval && camera_eval->type == OB_CAMERA) {
const blender::Camera *cam = reinterpret_cast<const blender::Camera *>(camera_eval->data);
switch (cam->type) {
default:
case CAM_PERSP:
data.type = CAMERA_PERSP;
break;
case CAM_ORTHO:
data.type = CAMERA_ORTHO;
break;
#if 0 /* TODO(fclem): Make fisheye properties inside blender. */
case CAM_PANO: {
switch (cam->panorama_type) {
default:
case CAM_PANO_EQUIRECTANGULAR:
data.type = CAMERA_PANO_EQUIRECT;
break;
case CAM_PANO_FISHEYE_EQUIDISTANT:
data.type = CAMERA_PANO_EQUIDISTANT;
break;
case CAM_PANO_FISHEYE_EQUISOLID:
data.type = CAMERA_PANO_EQUISOLID;
break;
case CAM_PANO_MIRRORBALL:
data.type = CAMERA_PANO_MIRROR;
break;
}
}
#endif
}
}
else if (inst_.drw_view) {
data.type = inst_.drw_view->is_persp() ? CAMERA_PERSP : CAMERA_ORTHO;
}
else {
/* Light-probe baking. */
data.type = CAMERA_PERSP;
}
float overscan = 0.0f;
if ((inst_.scene->eevee.flag & SCE_EEVEE_OVERSCAN) && (inst_.drw_view || inst_.render)) {
overscan = inst_.scene->eevee.overscan / 100.0f;
}
overscan_changed_ = assign_if_different(overscan_, overscan);
camera_changed_ = assign_if_different(last_camera_object_, inst_.camera_orig_object);
}
void Camera::sync()
{
const Object *camera_eval = inst_.camera_eval_object;
CameraData &data = data_;
int2 display_extent = inst_.film.display_extent_get();
int2 film_extent = inst_.film.film_extent_get();
int2 film_offset = inst_.film.film_offset_get();
/* Over-scan in film pixel. Not the same as `render_overscan_get`. */
int film_overscan = Film::overscan_pixels_get(overscan_, film_extent);
rcti film_rect;
BLI_rcti_init(&film_rect,
film_offset.x,
film_offset.x + film_extent.x,
film_offset.y,
film_offset.y + film_extent.y);
Bounds<float2> uv_region = {float2(0.0f), float2(display_extent)};
if (inst_.drw_view) {
float2 uv_scale = float4(inst_.rv3d->viewcamtexcofac).xy();
float2 uv_bias = float4(inst_.rv3d->viewcamtexcofac).zw();
/* UV region inside the display extent reference frame. */
uv_region.min = (-uv_bias * float2(display_extent)) / uv_scale;
uv_region.max = uv_region.min + (float2(display_extent) / uv_scale);
}
data.uv_scale = float2(film_extent + film_overscan * 2) / uv_region.size();
data.uv_bias = (float2(film_offset - film_overscan) - uv_region.min) / uv_region.size();
if (inst_.is_baking()) {
/* Any view so that shadows and light culling works during irradiance bake. */
draw::View &view = inst_.volume_probes.bake.view_z_;
data.viewmat = view.viewmat();
data.viewinv = view.viewinv();
data.winmat = view.winmat();
data.type = CAMERA_ORTHO;
/* \note Follow camera parameters where distances are positive in front of the camera. */
data.clip_near = -view.far_clip();
data.clip_far = -view.near_clip();
data.fisheye_fov = data.fisheye_lens = -1.0f;
data.equirect_bias = float2(0.0f);
data.equirect_scale = float2(0.0f);
data.uv_scale = float2(1.0f);
data.uv_bias = float2(0.0f);
}
else if (inst_.drw_view) {
data.viewmat = inst_.drw_view->viewmat();
data.viewinv = inst_.drw_view->viewinv();
data.winmat = inst_.drw_view->winmat();
if (film_offset != int2(0) || film_extent != display_extent) {
data.winmat = projection_crop_matrix(film_offset, film_extent, display_extent) * data.winmat;
}
if (overscan_ != 0.0f) {
data.winmat = projection_overscan_matrix(film_extent, int2(film_overscan)) * data.winmat;
}
}
else if (inst_.render) {
const Render *re = inst_.render->re;
RE_GetCameraWindow(inst_.render->re, camera_eval, data.winmat.ptr());
RE_GetCameraModelMatrix(re, camera_eval, data.viewinv.ptr());
data.viewmat = math::invert(data.viewinv);
rctf viewplane = re->viewplane;
BKE_camera_params_crop_viewplane(&viewplane, UNPACK2(display_extent), &film_rect);
RE_GetWindowMatrixWithOverscan(this->is_orthographic(),
re->clip_start,
re->clip_end,
viewplane,
overscan_,
data.winmat.ptr());
}
else {
data.viewmat = float4x4::identity();
data.viewinv = float4x4::identity();
data.winmat = math::projection::perspective(-0.1f, 0.1f, -0.1f, 0.1f, 0.1f, 1.0f);
}
data.wininv = math::invert(data.winmat);
data.persmat = data.winmat * data.viewmat;
data.persinv = math::invert(data.persmat);
is_camera_object_ = false;
if (camera_eval && camera_eval->type == OB_CAMERA) {
const blender::Camera *cam = reinterpret_cast<const blender::Camera *>(camera_eval->data);
data.clip_near = cam->clip_start;
data.clip_far = cam->clip_end;
#if 0 /* TODO(fclem): Make fisheye properties inside blender. */
data.fisheye_fov = cam->fisheye_fov;
data.fisheye_lens = cam->fisheye_lens;
data.equirect_bias.x = -cam->longitude_min + M_PI_2;
data.equirect_bias.y = -cam->latitude_min + M_PI_2;
data.equirect_scale.x = cam->longitude_min - cam->longitude_max;
data.equirect_scale.y = cam->latitude_min - cam->latitude_max;
/* Combine with uv_scale/bias to avoid doing extra computation. */
data.equirect_bias += data.uv_bias * data.equirect_scale;
data.equirect_scale *= data.uv_scale;
data.equirect_scale_inv = 1.0f / data.equirect_scale;
#else
data.fisheye_fov = data.fisheye_lens = -1.0f;
data.equirect_bias = float2(0.0f);
data.equirect_scale = float2(0.0f);
#endif
is_camera_object_ = true;
}
else if (inst_.drw_view) {
/* \note Follow camera parameters where distances are positive in front of the camera. */
data.clip_near = -inst_.drw_view->near_clip();
data.clip_far = -inst_.drw_view->far_clip();
data.fisheye_fov = data.fisheye_lens = -1.0f;
data.equirect_bias = float2(0.0f);
data.equirect_scale = float2(0.0f);
}
data_.initialized = true;
update_bounds();
}
void Camera::update_bounds()
{
float left, right, bottom, top, near, far;
projmat_dimensions(data_.winmat.ptr(), &left, &right, &bottom, &top, &near, &far);
BoundBox bbox;
bbox.vec[0][2] = bbox.vec[3][2] = bbox.vec[7][2] = bbox.vec[4][2] = -near;
bbox.vec[0][0] = bbox.vec[3][0] = left;
bbox.vec[4][0] = bbox.vec[7][0] = right;
bbox.vec[0][1] = bbox.vec[4][1] = bottom;
bbox.vec[7][1] = bbox.vec[3][1] = top;
/* Get the coordinates of the far plane. */
if (!this->is_orthographic()) {
float sca_far = far / near;
left *= sca_far;
right *= sca_far;
bottom *= sca_far;
top *= sca_far;
}
bbox.vec[1][2] = bbox.vec[2][2] = bbox.vec[6][2] = bbox.vec[5][2] = -far;
bbox.vec[1][0] = bbox.vec[2][0] = left;
bbox.vec[6][0] = bbox.vec[5][0] = right;
bbox.vec[1][1] = bbox.vec[5][1] = bottom;
bbox.vec[2][1] = bbox.vec[6][1] = top;
bound_sphere.center = {0.0f, 0.0f, 0.0f};
bound_sphere.radius = 0.0f;
for (auto i : IndexRange(8)) {
bound_sphere.center += float3(bbox.vec[i]);
}
bound_sphere.center /= 8.0f;
for (auto i : IndexRange(8)) {
float dist_sqr = math::distance_squared(bound_sphere.center, float3(bbox.vec[i]));
bound_sphere.radius = max_ff(bound_sphere.radius, dist_sqr);
}
bound_sphere.radius = sqrtf(bound_sphere.radius);
/* Transform into world space. */
bound_sphere.center = math::transform_point(data_.viewinv, bound_sphere.center);
/* Compute diagonal length. */
float2 p0 = float2(bbox.vec[0]) / (this->is_perspective() ? bbox.vec[0][2] : 1.0f);
float2 p1 = float2(bbox.vec[7]) / (this->is_perspective() ? bbox.vec[7][2] : 1.0f);
data_.screen_diagonal_length = math::distance(p0, p1);
}
float4x4 Camera::projection_crop_matrix(int2 film_offset, int2 film_extent, int2 display_extent)
{
float2 uv_min = float2(film_offset) / float2(display_extent);
float2 uv_max = float2(film_offset + film_extent) / float2(display_extent);
float2 ndc_min = uv_min * 2.0f - 1.0f;
float2 ndc_max = uv_max * 2.0f - 1.0f;
float2 ndc_size = ndc_max - ndc_min;
float2 ndc_center = (ndc_min + ndc_max) * 0.5f;
float2 scale = 2.0f / ndc_size;
float2 offset = -ndc_center * scale;
float4x4 crop_matrix = float4x4::identity();
crop_matrix[0][0] = scale.x;
crop_matrix[1][1] = scale.y;
crop_matrix[3][0] = offset.x;
crop_matrix[3][1] = offset.y;
return crop_matrix;
}
float4x4 Camera::projection_overscan_matrix(int2 film_extent, int2 film_overscan)
{
float2 overscan_scale = float2(film_extent) / float2(film_extent + film_overscan * 2);
float4x4 overscan_matrix = float4x4::identity();
overscan_matrix[0][0] = overscan_scale.x;
overscan_matrix[1][1] = overscan_scale.y;
return overscan_matrix;
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,185 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup eevee
*/
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BKE_camera.h"
#include "eevee_camera_shared.hh"
namespace blender::eevee {
class Instance;
inline float4x4 cubeface_mat(int face)
{
switch (face) {
default:
case 0:
/* Pos X */
return float4x4({0.0f, 0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 1.0f});
case 1:
/* Neg X */
return float4x4({0.0f, 0.0f, 1.0f, 0.0f},
{0.0f, -1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 1.0f});
case 2:
/* Pos Y */
return float4x4({1.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, -1.0f, 0.0f},
{0.0f, 1.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 1.0f});
case 3:
/* Neg Y */
return float4x4({1.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f, 0.0f},
{0.0f, -1.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 1.0f});
case 4:
/* Pos Z */
return float4x4({1.0f, 0.0f, 0.0f, 0.0f},
{0.0f, -1.0f, 0.0f, 0.0f},
{0.0f, 0.0f, -1.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 1.0f});
case 5:
/* Neg Z */
return float4x4({-1.0f, 0.0f, 0.0f, 0.0f},
{0.0f, -1.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 1.0f});
}
}
inline void cubeface_winmat_get(float4x4 &winmat, float near, float far)
{
/* Simple 90 degree FOV projection. */
perspective_m4(winmat.ptr(), -near, near, -near, near, near, far);
}
/* -------------------------------------------------------------------- */
/** \name CameraData operators
* \{ */
inline bool operator==(const CameraData &a, const CameraData &b)
{
return compare_m4m4(a.persmat.ptr(), b.persmat.ptr(), FLT_MIN) && (a.uv_scale == b.uv_scale) &&
(a.uv_bias == b.uv_bias) && (a.equirect_scale == b.equirect_scale) &&
(a.equirect_bias == b.equirect_bias) && (a.fisheye_fov == b.fisheye_fov) &&
(a.fisheye_lens == b.fisheye_lens) && (a.type == b.type);
}
inline bool operator!=(const CameraData &a, const CameraData &b)
{
return !(a == b);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Camera
* \{ */
/**
* Point of view in the scene. Can be init from viewport or camera object.
*/
class Camera {
private:
Instance &inst_;
CameraData &data_;
struct {
float3 center;
float radius;
} bound_sphere;
float overscan_ = -1.0f;
bool overscan_changed_;
/** Whether or not the camera was synced from a camera object. */
bool is_camera_object_ = false;
/** Just for tracking camera changes, use Instance::camera_orig_object for data access. */
Object *last_camera_object_ = nullptr;
bool camera_changed_ = false;
public:
Camera(Instance &inst, CameraData &data) : inst_(inst), data_(data) {};
~Camera() {};
void init();
void sync();
/**
* Getters
*/
const CameraData &data_get() const
{
BLI_assert(data_.initialized);
return data_;
}
bool is_panoramic() const
{
return eevee::is_panoramic(data_.type);
}
bool is_orthographic() const
{
return data_.type == CAMERA_ORTHO;
}
bool is_perspective() const
{
return data_.type == CAMERA_PERSP;
}
bool is_camera_object() const
{
return is_camera_object_;
}
const float3 &position() const
{
return data_.viewinv.location();
}
const float3 &forward() const
{
return data_.viewinv.z_axis();
}
const float3 &bound_center() const
{
return bound_sphere.center;
}
const float &bound_radius() const
{
return bound_sphere.radius;
}
float overscan() const
{
return overscan_;
}
bool overscan_changed() const
{
return overscan_changed_;
}
bool camera_changed() const
{
return camera_changed_;
}
private:
void update_bounds();
float4x4 projection_crop_matrix(int2 film_offset, int2 film_extent, int2 display_extent);
float4x4 projection_overscan_matrix(int2 film_extent, int2 film_overscan);
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
enum [[host_shared]] eCameraType : uint32_t {
CAMERA_PERSP = 0u,
CAMERA_ORTHO = 1u,
CAMERA_PANO_EQUIRECT = 2u,
CAMERA_PANO_EQUISOLID = 3u,
CAMERA_PANO_EQUIDISTANT = 4u,
CAMERA_PANO_MIRROR = 5u
};
static inline bool is_panoramic(eCameraType type)
{
return type > CAMERA_ORTHO;
}
struct [[host_shared]] CameraData {
/* View Matrices of the camera, not from any view! */
float4x4 persmat;
float4x4 persinv;
float4x4 viewmat;
float4x4 viewinv;
float4x4 winmat;
float4x4 wininv;
/** Camera UV scale and bias. */
float2 uv_scale;
float2 uv_bias;
/** Panorama parameters. */
float2 equirect_scale;
float2 equirect_scale_inv;
float2 equirect_bias;
float fisheye_fov;
float fisheye_lens;
/** Clipping distances. */
float clip_near;
float clip_far;
enum eCameraType type;
/** World space distance between view corners at unit distance from camera. */
float screen_diagonal_length;
float _pad0;
float _pad1;
float _pad2;
bool32_t initialized;
#ifndef GPU_SHADER
/* Small constructor to allow detecting new buffers. */
CameraData() : initialized(false) {};
#endif
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,137 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_cryptomatte.hh"
#include "eevee_cryptomatte.hh"
#include "eevee_instance.hh"
#include "eevee_renderbuffers.hh"
namespace blender::eevee {
void Cryptomatte::begin_sync()
{
const eViewLayerEEVEEPassType enabled_passes = (inst_.film.enabled_passes_get() &
(EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT |
EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET |
EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL));
session_.reset();
object_layer_ = nullptr;
asset_layer_ = nullptr;
material_layer_ = nullptr;
if (enabled_passes && !inst_.is_viewport()) {
session_.reset(BKE_cryptomatte_init_from_view_layer(inst_.view_layer));
for (const std::string &layer_name :
bke::cryptomatte::BKE_cryptomatte_layer_names_get(*session_))
{
StringRef layer_name_ref = layer_name;
bke::cryptomatte::CryptomatteLayer *layer = bke::cryptomatte::BKE_cryptomatte_layer_get(
*session_, layer_name);
if (layer_name_ref.endswith(RE_PASSNAME_CRYPTOMATTE_OBJECT)) {
object_layer_ = layer;
}
else if (layer_name_ref.endswith(RE_PASSNAME_CRYPTOMATTE_ASSET)) {
asset_layer_ = layer;
}
else if (layer_name_ref.endswith(RE_PASSNAME_CRYPTOMATTE_MATERIAL)) {
material_layer_ = layer;
}
}
}
if (!(enabled_passes &
(EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT | EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET)))
{
/* Ensure dummy buffer for API validation. */
cryptomatte_object_buf.resize(16);
}
}
void Cryptomatte::sync_object(const ObjectHandle &ob_handle)
{
const eViewLayerEEVEEPassType enabled_passes = inst_.film.enabled_passes_get();
if (!(enabled_passes &
(EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT | EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET)))
{
return;
}
float2 object_hashes(0.0f, 0.0f);
if (enabled_passes & EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT) {
object_hashes[0] = register_id(EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT, ob_handle.object->id);
}
if (enabled_passes & EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET) {
Object *asset = ob_handle.object;
while (asset->parent) {
asset = asset->parent;
}
object_hashes[1] = register_id(EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET, asset->id);
}
for (ResourceID resource_id : ob_handle.res_handle.id_range()) {
cryptomatte_object_buf.get_or_resize(resource_id.index()) = object_hashes;
}
}
void Cryptomatte::sync_material(const blender::Material *material)
{
/* Material crypto hashes are generated during shader codegen stage. We only need to register
* them to store inside the metadata. */
if (material_layer_ && material) {
material_layer_->add_ID(material->id);
}
}
void Cryptomatte::end_sync()
{
cryptomatte_object_buf.push_update();
object_layer_ = nullptr;
asset_layer_ = nullptr;
material_layer_ = nullptr;
}
float Cryptomatte::register_id(const eViewLayerEEVEEPassType layer, const ID &id) const
{
BLI_assert(ELEM(layer,
EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT,
EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET,
EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL));
uint32_t cryptomatte_hash = 0;
if (session_) {
if (layer == EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT) {
BLI_assert(object_layer_);
cryptomatte_hash = object_layer_->add_ID(id);
}
else if (layer == EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET) {
BLI_assert(asset_layer_);
cryptomatte_hash = asset_layer_->add_ID(id);
}
else if (layer == EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL) {
BLI_assert(material_layer_);
cryptomatte_hash = material_layer_->add_ID(id);
}
}
else {
const char *name = &id.name[2];
const int name_len = BLI_strnlen(name, MAX_NAME - 2);
cryptomatte_hash = BKE_cryptomatte_hash(name, name_len);
}
return BKE_cryptomatte_hash_to_float(cryptomatte_hash);
}
void Cryptomatte::store_metadata(RenderResult *render_result)
{
if (session_) {
BKE_cryptomatte_store_metadata(&*session_, render_result);
}
}
} // namespace blender::eevee

View File

@@ -0,0 +1,80 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Cryptomatte.
*
* During rasterization, cryptomatte hashes are stored into a single array texture.
* The film pass then resamples this texture using pixel filter weighting.
* Each cryptomatte layer can hold N samples. These are stored in sequential layers
* of the array texture. The samples are sorted and merged only for final rendering.
*/
#pragma once
#include "DRW_gpu_wrapper.hh"
#include "BKE_cryptomatte.hh"
#include "draw_handle.hh"
#include "eevee_defines.hh"
#include "eevee_sync.hh"
namespace blender {
extern "C" {
struct Material;
}
namespace eevee {
using namespace draw;
class Instance;
/* -------------------------------------------------------------------- */
/** \name Cryptomatte
* \{ */
class Cryptomatte {
private:
class Instance &inst_;
using CryptomatteObjectBuf = draw::StorageArrayBuffer<float2, 16>;
bke::cryptomatte::CryptomatteSessionPtr session_;
/* Cached pointer to the cryptomatte layer instances. */
bke::cryptomatte::CryptomatteLayer *object_layer_ = nullptr;
bke::cryptomatte::CryptomatteLayer *asset_layer_ = nullptr;
bke::cryptomatte::CryptomatteLayer *material_layer_ = nullptr;
/** Contains per object hashes (object and asset hash). Indexed by resource ID. */
CryptomatteObjectBuf cryptomatte_object_buf;
public:
Cryptomatte(Instance &inst) : inst_(inst) {};
void begin_sync();
void sync_object(const ObjectHandle &ob_handle);
void sync_material(const blender::Material *material);
void end_sync();
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_ssbo(CRYPTOMATTE_BUF_SLOT, &cryptomatte_object_buf);
}
/* Register ID to use inside cryptomatte layer and returns associated hash as float. */
float register_id(const eViewLayerEEVEEPassType layer, const ID &id) const;
void store_metadata(RenderResult *render_result);
};
/** \} */
} // namespace eevee
} // namespace blender

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
/** These are just to make more sense of G.debug_value's values. Reserved range is 1-30. */
enum [[host_shared]] eDebugMode : uint32_t {
DEBUG_NONE = 0u,
/**
* Gradient showing light evaluation hot-spots.
*/
DEBUG_LIGHT_CULLING = 1u,
/**
* Show incorrectly down-sample tiles in red.
*/
DEBUG_HIZ_VALIDATION = 2u,
/**
* Display IrradianceCache surfels.
*/
DEBUG_IRRADIANCE_CACHE_SURFELS_NORMAL = 3u,
DEBUG_IRRADIANCE_CACHE_SURFELS_IRRADIANCE = 4u,
DEBUG_IRRADIANCE_CACHE_SURFELS_VISIBILITY = 5u,
DEBUG_IRRADIANCE_CACHE_SURFELS_CLUSTER = 6u,
/**
* Display IrradianceCache virtual offset.
*/
DEBUG_IRRADIANCE_CACHE_VIRTUAL_OFFSET = 7u,
DEBUG_IRRADIANCE_CACHE_VALIDITY = 8u,
/**
* Show tiles depending on their status.
*/
DEBUG_SHADOW_TILEMAPS = 10u,
/**
* Show content of shadow map. Used to verify projection code.
*/
DEBUG_SHADOW_VALUES = 11u,
/**
* Show random color for each tile. Verify allocation and LOD assignment.
*/
DEBUG_SHADOW_TILE_RANDOM_COLOR = 12u,
/**
* Show random color for each tile. Verify distribution and LOD transitions.
*/
DEBUG_SHADOW_TILEMAP_RANDOM_COLOR = 13u,
/**
* Show random color for each tile. Verify distribution and LOD transitions.
*/
DEBUG_SHADOW_ATOMIC_COST = 17u,
/**
* Show storage cost of each pixel in the gbuffer.
*/
DEBUG_GBUFFER_STORAGE = 14u,
/**
* Show evaluation cost of each pixel.
*/
DEBUG_GBUFFER_EVALUATION = 15u,
/**
* Color different buffers of the depth of field.
*/
DEBUG_DOF_PLANES = 16u,
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,310 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* List of defines that are shared with the GPUShaderCreateInfos. We do this to avoid
* dragging larger headers into the createInfo pipeline which would cause problems.
*/
#pragma once
#ifndef SQUARE
# define SQUARE(x) ((x) * (x))
#endif
/* Look Up Tables. */
#define LUT_WORKGROUP_SIZE 16
/* Hierarchical Z down-sampling. */
#define HIZ_MIP_COUNT 7
/* NOTE: The shader is written to update 5 mipmaps using LDS.
* Each thread actually update 4 LOD_0 pixels, which means the number of MIPLVL we cover is:
* `(log2(HIZ_GROUP_SIZE) + 1) + LOD_0 + LAST_GROUP_LVL = HIZ_MIP_COUNT` */
#define HIZ_GROUP_SIZE 16
/* Avoid too much overhead caused by resizing the light buffers too many time. */
#define LIGHT_CHUNK 256
#define CULLING_SELECT_GROUP_SIZE 256
#define CULLING_SORT_GROUP_SIZE 256
#define CULLING_ZBIN_GROUP_SIZE 1024
#define CULLING_TILE_GROUP_SIZE 256
/* Reflection Probes. */
/* When changed update parallel sum loop in `eevee_lightprobe_sphere_remap_comp.glsl`. */
#define SPHERE_PROBE_REMAP_GROUP_SIZE 32
#define SPHERE_PROBE_GROUP_SIZE 16
#define SPHERE_PROBE_SELECT_GROUP_SIZE 64
#define SPHERE_PROBE_MIPMAP_LEVELS 5
#define SPHERE_PROBE_SH_GROUP_SIZE 256
#define SPHERE_PROBE_SH_SAMPLES_PER_GROUP 64
/* Must be power of two for correct partitioning. */
#define SPHERE_PROBE_ATLAS_MAX_SUBDIV 12
#define SPHERE_PROBE_ATLAS_RES (1 << SPHERE_PROBE_ATLAS_MAX_SUBDIV)
/* Maximum number of thread-groups dispatched for remapping a probe to octahedral mapping. */
#define SPHERE_PROBE_MAX_HARMONIC SQUARE(SPHERE_PROBE_ATLAS_RES / SPHERE_PROBE_REMAP_GROUP_SIZE)
/* Start and end value for mixing sphere probe and volume probes. */
#define SPHERE_PROBE_MIX_START_ROUGHNESS 0.7f
#define SPHERE_PROBE_MIX_END_ROUGHNESS 0.9f
/* Roughness of the last mip map for sphere probes. */
#define SPHERE_PROBE_MIP_MAX_ROUGHNESS 0.7f
#define SPHERE_PROBE_FORMAT SFLOAT_16_16_16_16
/**
* Limited by the UBO size limit `(16384 bytes / sizeof(SphereProbeData))`.
*/
#define SPHERE_PROBE_MAX 128
/** NOTE: Runtime format only. */
#define VOLUME_PROBE_FORMAT SFLOAT_16_16_16_16
/**
* Limited by the performance impact it can cause.
* Limited by the max layer count supported by a hardware (256).
* Limited by the UBO size limit `(16384 bytes / sizeof(PlanarProbeData))`.
*/
#define PLANAR_PROBE_MAX 16
/**
* IMPORTANT: Some data packing are tweaked for these values.
* Be sure to update them accordingly.
* SHADOW_TILEMAP_RES max is 32 because of the shared bitmaps used for LOD tagging.
* It is also limited by the maximum thread group size (1024).
*/
#if 0
/* Useful for debugging the tile-copy version of the shadow rendering without making debugging
* tools unresponsive. */
# define SHADOW_TILEMAP_RES 4
# define SHADOW_TILEMAP_LOD 2 /* LOG2(SHADOW_TILEMAP_RES) */
#else
# define SHADOW_TILEMAP_RES 32
# define SHADOW_TILEMAP_LOD 5 /* LOG2(SHADOW_TILEMAP_RES) */
#endif
#define SHADOW_TILEMAP_LOD0_LEN ((SHADOW_TILEMAP_RES / 1) * (SHADOW_TILEMAP_RES / 1))
#define SHADOW_TILEMAP_LOD1_LEN ((SHADOW_TILEMAP_RES / 2) * (SHADOW_TILEMAP_RES / 2))
#define SHADOW_TILEMAP_LOD2_LEN ((SHADOW_TILEMAP_RES / 4) * (SHADOW_TILEMAP_RES / 4))
#define SHADOW_TILEMAP_LOD3_LEN ((SHADOW_TILEMAP_RES / 8) * (SHADOW_TILEMAP_RES / 8))
#define SHADOW_TILEMAP_LOD4_LEN ((SHADOW_TILEMAP_RES / 16) * (SHADOW_TILEMAP_RES / 16))
#define SHADOW_TILEMAP_LOD5_LEN ((SHADOW_TILEMAP_RES / 32) * (SHADOW_TILEMAP_RES / 32))
#define SHADOW_TILEMAP_PER_ROW 64
#define SHADOW_TILEDATA_PER_TILEMAP \
(SHADOW_TILEMAP_LOD0_LEN + SHADOW_TILEMAP_LOD1_LEN + SHADOW_TILEMAP_LOD2_LEN + \
SHADOW_TILEMAP_LOD3_LEN + SHADOW_TILEMAP_LOD4_LEN + SHADOW_TILEMAP_LOD5_LEN)
/* Maximum number of relative LOD distance we can store. */
#define SHADOW_TILEMAP_MAX_CLIPMAP_LOD 8
#if 0
/* Useful for debugging the tile-copy version of the shadow rendering without making debugging
* tools unresponsive. */
# define SHADOW_PAGE_CLEAR_GROUP_SIZE 8
# define SHADOW_PAGE_RES 8
# define SHADOW_PAGE_LOD 3 /* LOG2(SHADOW_PAGE_RES) */
#else
# define SHADOW_PAGE_CLEAR_GROUP_SIZE 32
# define SHADOW_PAGE_RES 256
# define SHADOW_PAGE_LOD 8 /* LOG2(SHADOW_PAGE_RES) */
#endif
/* For testing only. */
// #define SHADOW_FORCE_LOD0
#define SHADOW_MAP_MAX_RES (SHADOW_PAGE_RES * SHADOW_TILEMAP_RES)
#define SHADOW_DEPTH_SCAN_GROUP_SIZE 8
#define SHADOW_AABB_TAG_GROUP_SIZE 64
#define SHADOW_MAX_TILEMAP 4096
#define SHADOW_MAX_TILE (SHADOW_MAX_TILEMAP * SHADOW_TILEDATA_PER_TILEMAP)
#define SHADOW_MAX_PAGE 8192
#define SHADOW_BOUNDS_GROUP_SIZE 64
#define SHADOW_CLIPMAP_GROUP_SIZE 64
#define SHADOW_VIEW_MAX 64 /* Must match DRW_VIEW_MAX. */
#define SHADOW_RENDER_MAP_SIZE (SHADOW_VIEW_MAX * SHADOW_TILEMAP_LOD0_LEN)
#define SHADOW_ATOMIC 1
#define SHADOW_PAGE_PER_ROW 8
#define SHADOW_PAGE_PER_COL 8
#define SHADOW_PAGE_MAX_LAYER 128
#define SHADOW_PAGE_PER_LAYER (SHADOW_PAGE_PER_ROW * SHADOW_PAGE_PER_COL)
#define SHADOW_MAX_STEP 16
#define SHADOW_MAX_RAY 4
#define SHADOW_ROG_ID 0
/* Gbuffer. */
/** IMPORTANT: Make sure all Gbuffer frame-buffer setup matches this. */
#define GBUF_HEADER_FB_LAYER_COUNT 1
#define GBUF_CLOSURE_FB_LAYER_COUNT 2
#define GBUF_NORMAL_FB_LAYER_COUNT 1
/* Deferred Lighting. */
#define DEFERRED_RADIANCE_FORMAT UINT_32
#define DEFERRED_GBUFFER_ROG_ID 0
/* Ray-tracing. */
#define RAYTRACE_GROUP_SIZE 8
/* Keep this as a define to avoid shader variations. */
#define RAYTRACE_RADIANCE_FORMAT UFLOAT_11_11_10
#define RAYTRACE_RAYTIME_FORMAT SFLOAT_32
#define RAYTRACE_VARIANCE_FORMAT SFLOAT_16
#define RAYTRACE_TILEMASK_FORMAT UINT_8
/* Sub-Surface Scattering. */
#define SUBSURFACE_GROUP_SIZE RAYTRACE_GROUP_SIZE
#define SUBSURFACE_RADIANCE_FORMAT UFLOAT_11_11_10
#define SUBSURFACE_OBJECT_ID_FORMAT UINT_16
/* Film. */
#define FILM_GROUP_SIZE 16
/* Motion Blur. */
#define MOTION_BLUR_GROUP_SIZE 32
#define MOTION_BLUR_DILATE_GROUP_SIZE 512
/* Irradiance Cache. */
/** Maximum number of entities inside the cache. */
#define IRRADIANCE_GRID_MAX 64
/* Depth Of Field. */
#define DOF_TILES_SIZE 8
#define DOF_TILES_FLATTEN_GROUP_SIZE DOF_TILES_SIZE
#define DOF_TILES_DILATE_GROUP_SIZE 8
#define DOF_BOKEH_LUT_SIZE 32
#define DOF_MAX_SLIGHT_FOCUS_RADIUS 5
#define DOF_SLIGHT_FOCUS_SAMPLE_MAX 16
#define DOF_MIP_COUNT 4
#define DOF_REDUCE_GROUP_SIZE (1 << (DOF_MIP_COUNT - 1))
#define DOF_DEFAULT_GROUP_SIZE 32
#define DOF_STABILIZE_GROUP_SIZE 16
#define DOF_FILTER_GROUP_SIZE 8
#define DOF_GATHER_GROUP_SIZE DOF_TILES_SIZE
#define DOF_RESOLVE_GROUP_SIZE (DOF_TILES_SIZE * 2)
/* Ambient Occlusion. */
#define AMBIENT_OCCLUSION_PASS_TILE_SIZE 16
/* IrradianceBake. */
#define SURFEL_GROUP_SIZE 256
#define SURFEL_LIST_GROUP_SIZE 256
#define IRRADIANCE_GRID_GROUP_SIZE 4 /* In each dimension, so 4x4x4 workgroup size. */
#define IRRADIANCE_GRID_BRICK_SIZE 4 /* In each dimension, so 4x4x4 brick size. */
#define IRRADIANCE_BOUNDS_GROUP_SIZE 64
/* Volumes. */
#define VOLUME_GROUP_SIZE 4
#define VOLUME_INTEGRATION_GROUP_SIZE 8
#define VOLUME_HIT_DEPTH_MAX 16
/* Velocity. */
#define VERTEX_COPY_GROUP_SIZE 64
/* Utility Texture. */
#define UTIL_TEX_SIZE 64
#define UTIL_BSDF_LAYER_COUNT 16
/* Scale and bias to avoid interpolation of the border pixel.
* Remap UVs to the border pixels centers. */
#define UTIL_TEX_UV_SCALE ((UTIL_TEX_SIZE - 1.0f) / UTIL_TEX_SIZE)
#define UTIL_TEX_UV_BIAS (0.5f / UTIL_TEX_SIZE)
#define UTIL_BLUE_NOISE_LAYER 0
#define UTIL_SSS_TRANSMITTANCE_PROFILE_LAYER 1
#define UTIL_LTC_MAT_LAYER 2
#define UTIL_BRDF_LAYER 3
#define UTIL_BSDF_LAYER 4
#define UTIL_DISK_INTEGRAL_LAYER UTIL_SSS_TRANSMITTANCE_PROFILE_LAYER
#define UTIL_DISK_INTEGRAL_COMP 3
#define PREPASS_FRAG_OUT_NORMAL 0
#define PREPASS_FRAG_OUT_OB_ID 1
#define PREPASS_FRAG_OUT_VELOCITY 2
/* Resource bindings. */
/* Textures. */
/** WARNING: Don't forget to update the reserved slots info. */
/* Used anywhere. (Starts at index 2, since 0 and 1 are used by draw_gpencil) */
#define RBUFS_UTILITY_TEX_SLOT 2
#define HIZ_TEX_SLOT 3
/* Only during surface shading (forward and deferred eval). */
#define SHADOW_TILEMAPS_TEX_SLOT 4
#define SHADOW_ATLAS_TEX_SLOT 5
#define VOLUME_PROBE_TEX_SLOT 6
#define SPHERE_PROBE_TEX_SLOT 7
#define VOLUME_SCATTERING_TEX_SLOT 8
#define VOLUME_TRANSMITTANCE_TEX_SLOT 9
#define HIZ_PREVIOUS_LAYER_TEX_SLOT 10
#define RADIANCE_PREVIOUS_LAYER_TEX_SLOT 11
#define RAYCAST_DEPTH_TEX_SLOT 12
#define OBJECT_ID_TEX_SLOT 13
#define PREPASS_NORMAL_TEX_SLOT 14
/* Currently only used by ray-tracing, but might become used by forward too. */
#define PLANAR_PROBE_DEPTH_TEX_SLOT 15
#define PLANAR_PROBE_RADIANCE_TEX_SLOT 16
#define GBUF_CLOSURE_TEX_SLOT 17
#define GBUF_NORMAL_TEX_SLOT 18
#define GBUF_HEADER_TEX_SLOT 19
/* Images. */
#define RBUFS_COLOR_SLOT 0
#define RBUFS_VALUE_SLOT 1
#define RBUFS_CRYPTOMATTE_SLOT 2
#define GBUF_CLOSURE_SLOT 3
#define GBUF_NORMAL_SLOT 4
#define GBUF_HEADER_SLOT 5
/* Volume properties pass do not write to `rbufs`. Reuse the same bind points. */
#define VOLUME_PROP_SCATTERING_IMG_SLOT 0
#define VOLUME_PROP_EXTINCTION_IMG_SLOT 1
#define VOLUME_PROP_EMISSION_IMG_SLOT 2
#define VOLUME_PROP_PHASE_IMG_SLOT 3
#define VOLUME_PROP_PHASE_WEIGHT_IMG_SLOT 4
#define VOLUME_OCCUPANCY_SLOT 5
/* Only during volume pre-pass. */
#define VOLUME_HIT_DEPTH_SLOT 0
#define VOLUME_HIT_COUNT_SLOT 1
/* Only during shadow rendering. */
#define SHADOW_ATLAS_IMG_SLOT 4
/* Uniform Buffers. */
/* Slot 0 is GPU_NODE_TREE_UBO_SLOT. */
#define UNIFORM_BUF_SLOT 1
/* Split from the main uniform buffer since they're updated multiple times per frame. */
#define PIPELINE_BUF_SLOT 2
#define RAYTRACE_BUF_SLOT 3
/* Only during surface shading (forward and deferred eval). */
#define IRRADIANCE_GRID_BUF_SLOT 4
#define SPHERE_PROBE_BUF_SLOT 5
#define PLANAR_PROBE_BUF_SLOT 6
/* Only during pre-pass. */
#define VELOCITY_CAMERA_PREV_BUF 4
#define VELOCITY_CAMERA_CURR_BUF 5
#define VELOCITY_CAMERA_NEXT_BUF 6
#define CLIP_PLANE_BUF 7
/* Only during subsurface scattering */
#define SUBSURFACE_BUF_SLOT 4
/* Storage Buffers. */
#define LIGHT_CULL_BUF_SLOT 0
#define LIGHT_BUF_SLOT 1
#define LIGHT_ZBIN_BUF_SLOT 2
#define LIGHT_TILE_BUF_SLOT 3
#define IRRADIANCE_BRICK_BUF_SLOT 4
#define SAMPLING_BUF_SLOT 6
#define CRYPTOMATTE_BUF_SLOT 7
/* Only during surface capture. */
#define SURFEL_BUF_SLOT 4
/* Only during surface capture. */
#define CAPTURE_BUF_SLOT 5
/* Only during shadow rendering. */
#define SHADOW_RENDER_MAP_BUF_SLOT 3
#define SHADOW_PAGE_INFO_SLOT 4
#define SHADOW_RENDER_VIEW_BUF_SLOT 5
/* Only during pre-pass. */
#define VELOCITY_OBJ_PREV_BUF_SLOT 0
#define VELOCITY_OBJ_NEXT_BUF_SLOT 1
#define VELOCITY_GEO_PREV_BUF_SLOT 2
#define VELOCITY_GEO_NEXT_BUF_SLOT 3
#define VELOCITY_INDIRECTION_BUF_SLOT 4
#define CLOSURE_WEIGHT_CUTOFF 1e-5f
/* Treat closure as singular if the roughness is below this threshold. */
#define BSDF_ROUGHNESS_THRESHOLD 2e-2f
/* Cannot use math libraries in shared headers yet. */
#define EEVEE_PI 3.14159265358979323846f /* pi */

View File

@@ -0,0 +1,811 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Depth of field post process effect.
*
* There are 2 methods to achieve this effect.
* - The first uses projection matrix offsetting and sample accumulation to give
* reference quality depth of field. But this needs many samples to hide the
* under-sampling.
* - The second one is a post-processing based one. It follows the
* implementation described in the presentation
* "Life of a Bokeh - SIGGRAPH 2018" from Guillaume Abadie.
* There are some difference with our actual implementation that prioritize quality.
*/
#include "DRW_render.hh"
#include "BKE_camera.h"
#include "DNA_camera_types.h"
#include "GPU_platform.hh"
#include "GPU_texture.hh"
#include "GPU_debug.hh"
#include "eevee_camera.hh"
#include "eevee_instance.hh"
#include "eevee_sampling.hh"
#include "eevee_shader.hh"
#include "eevee_velocity_shared.hh"
#include "eevee_depth_of_field.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Depth of field
* \{ */
void DepthOfField::init()
{
const SceneEEVEE &sce_eevee = inst_.scene->eevee;
const Object *camera_object_eval = inst_.camera_eval_object;
const blender::Camera *camera = (camera_object_eval && camera_object_eval->type == OB_CAMERA) ?
reinterpret_cast<const blender::Camera *>(
camera_object_eval->data) :
nullptr;
enabled_ = camera && (camera->dof.flag & CAM_DOF_ENABLED) != 0;
if (enabled_ == false) {
/* Set to invalid value for update detection */
data_.scatter_color_threshold = -1.0f;
return;
}
/* Reminder: These are parameters not interpolated by motion blur. */
int sce_flag = sce_eevee.flag;
do_jitter_ = (sce_flag & SCE_EEVEE_DOF_JITTER) != 0;
user_overblur_ = sce_eevee.bokeh_overblur / 100.0f;
fx_max_coc_ = sce_eevee.bokeh_max_size;
data_.scatter_color_threshold = sce_eevee.bokeh_threshold;
data_.scatter_neighbor_max_color = sce_eevee.bokeh_neighbor_max;
data_.bokeh_blades = float(camera->dof.aperture_blades);
}
void DepthOfField::sync()
{
const Camera &camera = inst_.camera;
const Object *camera_object_eval = inst_.camera_eval_object;
const blender::Camera *camera_data = (camera_object_eval &&
camera_object_eval->type == OB_CAMERA) ?
reinterpret_cast<const blender::Camera *>(
camera_object_eval->data) :
nullptr;
if (inst_.debug_mode == DEBUG_DOF_PLANES) {
/* Set debug message even if DOF is not enabled. */
inst_.info_append(
"Debug Mode: Depth Of Field Buffers\n"
" - Purple: Gap Fill\n"
" - Blue: Background\n"
" - Red: Slight Out Of Focus\n"
" - Yellow: In Focus\n"
" - Green: Foreground\n");
}
if (enabled_ == false) {
jitter_radius_ = 0.0f;
fx_radius_ = 0.0f;
return;
}
float2 anisotropic_scale = {clamp_f(1.0f / camera_data->dof.aperture_ratio, 1e-5f, 1.0f),
clamp_f(camera_data->dof.aperture_ratio, 1e-5f, 1.0f)};
data_.bokeh_anisotropic_scale = anisotropic_scale;
data_.bokeh_rotation = camera_data->dof.aperture_rotation;
focus_distance_ = BKE_camera_object_dof_distance(camera_object_eval);
data_.bokeh_anisotropic_scale_inv = 1.0f / data_.bokeh_anisotropic_scale;
float fstop = max_ff(camera_data->dof.aperture_fstop, 1e-5f);
float aperture = 1.0f / (2.0f * fstop);
if (camera.is_perspective()) {
aperture *= camera_data->lens * 1e-3f;
}
if (camera.is_orthographic()) {
/* FIXME: Why is this needed? Some kind of implicit unit conversion? */
aperture *= 0.04f;
}
if (camera.is_panoramic()) {
/* FIXME: Eyeballed. */
aperture *= 0.185f;
}
if (camera_data->dof.aperture_ratio < 1.0) {
/* If ratio is scaling the bokeh outwards, we scale the aperture so that
* the gather kernel size will encompass the maximum axis. */
aperture /= max_ff(camera_data->dof.aperture_ratio, 1e-5f);
}
float jitter_radius, fx_radius;
/* Balance blur radius between fx dof and jitter dof. */
if (do_jitter_ && (inst_.sampling.dof_ring_count_get() > 0) && !camera.is_panoramic() &&
!inst_.is_viewport())
{
/* Compute a minimal overblur radius to fill the gaps between the samples.
* This is just the simplified form of dividing the area of the bokeh by
* the number of samples. */
float minimal_overblur = 1.0f / sqrtf(inst_.sampling.dof_sample_count_get());
fx_radius = (minimal_overblur + user_overblur_) * aperture;
/* Avoid dilating the shape. Over-blur only soften. */
jitter_radius = max_ff(0.0f, aperture - fx_radius);
}
else {
jitter_radius = 0.0f;
fx_radius = aperture;
}
/* Disable post fx if result wouldn't be noticeable. */
if (fx_max_coc_ <= 0.5f) {
fx_radius = 0.0f;
}
jitter_radius_ = jitter_radius;
fx_radius_ = fx_radius;
if (fx_radius_ == 0.0f) {
return;
}
/* TODO(fclem): Once we render into multiple view, we will need to use the maximum resolution. */
int2 max_render_res = inst_.film.render_extent_get();
int2 half_render_res = math::divide_ceil(max_render_res, int2(2));
int2 reduce_size = math::ceil_to_multiple(half_render_res, int2(DOF_REDUCE_GROUP_SIZE));
data_.gather_uv_fac = 1.0f / float2(reduce_size);
/* Resize the scatter list to contain enough entry to cover half the screen with sprites (which
* is unlikely due to local contrast test). */
data_.scatter_max_rect = (reduce_size.x * reduce_size.y / 4) / 2;
scatter_fg_list_buf_.resize(data_.scatter_max_rect);
scatter_bg_list_buf_.resize(data_.scatter_max_rect);
bokeh_lut_pass_sync();
setup_pass_sync();
stabilize_pass_sync();
downsample_pass_sync();
reduce_pass_sync();
tiles_flatten_pass_sync();
tiles_dilate_pass_sync();
gather_pass_sync();
filter_pass_sync();
scatter_pass_sync();
hole_fill_pass_sync();
resolve_pass_sync();
}
void DepthOfField::jitter_apply(float4x4 &winmat, float4x4 &viewmat)
{
if (jitter_radius_ == 0.0f) {
return;
}
float radius, theta;
inst_.sampling.dof_disk_sample_get(&radius, &theta);
if (data_.bokeh_blades >= 3.0f) {
theta = circle_to_polygon_angle(data_.bokeh_blades, theta);
radius *= circle_to_polygon_radius(data_.bokeh_blades, theta);
}
radius *= jitter_radius_;
theta += data_.bokeh_rotation;
/* Sample in View Space. */
float2 sample = float2(radius * cosf(theta), radius * sinf(theta));
sample *= data_.bokeh_anisotropic_scale;
/* Convert to NDC Space. */
float3 jitter = float3(UNPACK2(sample), -focus_distance_);
float3 center = float3(0.0f, 0.0f, -focus_distance_);
mul_project_m4_v3(winmat.ptr(), jitter);
mul_project_m4_v3(winmat.ptr(), center);
const bool is_ortho = (winmat[2][3] != -1.0f);
if (is_ortho) {
sample *= focus_distance_;
}
/* Translate origin. */
sub_v2_v2(viewmat[3], sample);
/* Skew winmat Z axis. */
add_v2_v2(winmat[2], center - jitter);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Passes setup.
* \{ */
void DepthOfField::bokeh_lut_pass_sync()
{
const bool has_anisotropy = data_.bokeh_anisotropic_scale != float2(1.0f);
if (!has_anisotropy && (data_.bokeh_blades == 0.0)) {
/* No need for LUTs in these cases. */
use_bokeh_lut_ = false;
return;
}
use_bokeh_lut_ = true;
/* Precompute bokeh texture. */
bokeh_lut_ps_.init();
bokeh_lut_ps_.shader_set(inst_.shaders.static_shader_get(DOF_BOKEH_LUT));
bokeh_lut_ps_.bind_ubo("dof_buf", data_);
bokeh_lut_ps_.bind_image("out_gather_lut_img", &bokeh_gather_lut_tx_);
bokeh_lut_ps_.bind_image("out_scatter_lut_img", &bokeh_scatter_lut_tx_);
bokeh_lut_ps_.bind_image("out_resolve_lut_img", &bokeh_resolve_lut_tx_);
bokeh_lut_ps_.dispatch(int3(1, 1, 1));
}
void DepthOfField::setup_pass_sync()
{
RenderBuffers &render_buffers = inst_.render_buffers;
setup_ps_.init();
setup_ps_.shader_set(inst_.shaders.static_shader_get(DOF_SETUP));
setup_ps_.bind_texture("color_tx", &input_color_tx_, no_filter);
setup_ps_.bind_texture("depth_tx", &render_buffers.depth_tx, no_filter);
setup_ps_.bind_ubo("dof_buf", data_);
setup_ps_.bind_image("out_color_img", &setup_color_tx_);
setup_ps_.bind_image("out_coc_img", &setup_coc_tx_);
setup_ps_.dispatch(&dispatch_setup_size_);
setup_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
void DepthOfField::stabilize_pass_sync()
{
RenderBuffers &render_buffers = inst_.render_buffers;
VelocityModule &velocity = inst_.velocity;
stabilize_ps_.init();
stabilize_ps_.shader_set(inst_.shaders.static_shader_get(DOF_STABILIZE));
stabilize_ps_.bind_ubo("camera_prev", &(*velocity.camera_steps[STEP_PREVIOUS]));
stabilize_ps_.bind_ubo("camera_curr", &(*velocity.camera_steps[STEP_CURRENT]));
/* This is only for temporal stability. The next step is not needed. */
stabilize_ps_.bind_ubo("camera_next", &(*velocity.camera_steps[STEP_PREVIOUS]));
stabilize_ps_.bind_texture("coc_tx", &setup_coc_tx_, no_filter);
stabilize_ps_.bind_texture("color_tx", &setup_color_tx_, no_filter);
stabilize_ps_.bind_texture("velocity_tx", &render_buffers.vector_tx, no_filter);
stabilize_ps_.bind_texture("in_history_tx", &stabilize_input_, with_filter);
stabilize_ps_.bind_texture("depth_tx", &render_buffers.depth_tx, no_filter);
stabilize_ps_.bind_ubo("dof_buf", data_);
stabilize_ps_.push_constant("u_use_history", &stabilize_valid_history_, 1);
stabilize_ps_.bind_image("out_coc_img", &reduced_coc_mip_views_[0]);
stabilize_ps_.bind_image("out_color_img", &reduced_color_mip_views_[0]);
stabilize_ps_.bind_image("out_history_img", &stabilize_output_tx_);
stabilize_ps_.dispatch(&dispatch_stabilize_size_);
stabilize_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
void DepthOfField::downsample_pass_sync()
{
downsample_ps_.init();
downsample_ps_.shader_set(inst_.shaders.static_shader_get(DOF_DOWNSAMPLE));
downsample_ps_.bind_texture("color_tx", &reduced_color_mip_views_[0], no_filter);
downsample_ps_.bind_texture("coc_tx", &reduced_coc_mip_views_[0], no_filter);
downsample_ps_.bind_image("out_color_img", &downsample_tx_);
downsample_ps_.dispatch(&dispatch_downsample_size_);
downsample_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
void DepthOfField::reduce_pass_sync()
{
reduce_ps_.init();
reduce_ps_.shader_set(inst_.shaders.static_shader_get(DOF_REDUCE));
reduce_ps_.bind_ubo("dof_buf", data_);
reduce_ps_.bind_texture("downsample_tx", &downsample_tx_, no_filter);
reduce_ps_.bind_ssbo("scatter_fg_list_buf", scatter_fg_list_buf_);
reduce_ps_.bind_ssbo("scatter_bg_list_buf", scatter_bg_list_buf_);
reduce_ps_.bind_ssbo("scatter_fg_indirect_buf", scatter_fg_indirect_buf_);
reduce_ps_.bind_ssbo("scatter_bg_indirect_buf", scatter_bg_indirect_buf_);
reduce_ps_.bind_image("inout_color_lod0_img", &reduced_color_mip_views_[0]);
reduce_ps_.bind_image("out_color_lod1_img", &reduced_color_mip_views_[1]);
reduce_ps_.bind_image("out_color_lod2_img", &reduced_color_mip_views_[2]);
reduce_ps_.bind_image("out_color_lod3_img", &reduced_color_mip_views_[3]);
reduce_ps_.bind_image("in_coc_lod0_img", &reduced_coc_mip_views_[0]);
reduce_ps_.bind_image("out_coc_lod1_img", &reduced_coc_mip_views_[1]);
reduce_ps_.bind_image("out_coc_lod2_img", &reduced_coc_mip_views_[2]);
reduce_ps_.bind_image("out_coc_lod3_img", &reduced_coc_mip_views_[3]);
reduce_ps_.dispatch(&dispatch_reduce_size_);
/* NOTE: Command buffer barrier is done automatically by the GPU backend. */
reduce_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_STORAGE);
}
void DepthOfField::tiles_flatten_pass_sync()
{
tiles_flatten_ps_.init();
tiles_flatten_ps_.shader_set(inst_.shaders.static_shader_get(DOF_TILES_FLATTEN));
/* NOTE(fclem): We should use the reduced_coc_tx_ as it is stable, but we need the slight focus
* flag from the setup pass. A better way would be to do the brute-force in focus gather without
* this. */
tiles_flatten_ps_.bind_texture("coc_tx", &setup_coc_tx_, no_filter);
tiles_flatten_ps_.bind_image("out_tiles_fg_img", &tiles_fg_tx_.current());
tiles_flatten_ps_.bind_image("out_tiles_bg_img", &tiles_bg_tx_.current());
tiles_flatten_ps_.dispatch(&dispatch_tiles_flatten_size_);
tiles_flatten_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
void DepthOfField::tiles_dilate_pass_sync()
{
for (int pass = 0; pass < 2; pass++) {
PassSimple &drw_pass = (pass == 0) ? tiles_dilate_minmax_ps_ : tiles_dilate_minabs_ps_;
eShaderType sh_type = (pass == 0) ? DOF_TILES_DILATE_MINMAX : DOF_TILES_DILATE_MINABS;
drw_pass.init();
drw_pass.shader_set(inst_.shaders.static_shader_get(sh_type));
drw_pass.bind_image("in_tiles_fg_img", &tiles_fg_tx_.previous());
drw_pass.bind_image("in_tiles_bg_img", &tiles_bg_tx_.previous());
drw_pass.bind_image("out_tiles_fg_img", &tiles_fg_tx_.current());
drw_pass.bind_image("out_tiles_bg_img", &tiles_bg_tx_.current());
drw_pass.push_constant("ring_count", &tiles_dilate_ring_count_, 1);
drw_pass.push_constant("ring_width_multiplier", &tiles_dilate_ring_width_mul_, 1);
drw_pass.dispatch(&dispatch_tiles_dilate_size_);
drw_pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
}
void DepthOfField::gather_pass_sync()
{
const GPUSamplerState gather_bilinear = {GPU_SAMPLER_FILTERING_MIPMAP |
GPU_SAMPLER_FILTERING_LINEAR};
const GPUSamplerState gather_nearest = {GPU_SAMPLER_FILTERING_MIPMAP};
for (int pass = 0; pass < 2; pass++) {
PassSimple &drw_pass = (pass == 0) ? gather_fg_ps_ : gather_bg_ps_;
SwapChain<TextureFromPool, 2> &color_chain = (pass == 0) ? color_fg_tx_ : color_bg_tx_;
SwapChain<TextureFromPool, 2> &weight_chain = (pass == 0) ? weight_fg_tx_ : weight_bg_tx_;
eShaderType sh_type = (pass == 0) ?
(use_bokeh_lut_ ? DOF_GATHER_FOREGROUND_LUT :
DOF_GATHER_FOREGROUND) :
(use_bokeh_lut_ ? DOF_GATHER_BACKGROUND_LUT : DOF_GATHER_BACKGROUND);
drw_pass.init();
drw_pass.bind_resources(inst_.sampling);
drw_pass.shader_set(inst_.shaders.static_shader_get(sh_type));
drw_pass.bind_ubo("dof_buf", data_);
drw_pass.bind_texture("color_bilinear_tx", &reduced_color_tx_, gather_bilinear);
drw_pass.bind_texture("color_tx", &reduced_color_tx_, gather_nearest);
drw_pass.bind_texture("coc_tx", &reduced_coc_tx_, gather_nearest);
drw_pass.bind_image("in_tiles_fg_img", &tiles_fg_tx_.current());
drw_pass.bind_image("in_tiles_bg_img", &tiles_bg_tx_.current());
drw_pass.bind_image("out_color_img", &color_chain.current());
drw_pass.bind_image("out_weight_img", &weight_chain.current());
drw_pass.bind_image("out_occlusion_img", &occlusion_tx_);
drw_pass.bind_texture("bokeh_lut_tx", &bokeh_gather_lut_tx_);
drw_pass.dispatch(&dispatch_gather_size_);
drw_pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
}
void DepthOfField::filter_pass_sync()
{
for (int pass = 0; pass < 2; pass++) {
PassSimple &drw_pass = (pass == 0) ? filter_fg_ps_ : filter_bg_ps_;
SwapChain<TextureFromPool, 2> &color_chain = (pass == 0) ? color_fg_tx_ : color_bg_tx_;
SwapChain<TextureFromPool, 2> &weight_chain = (pass == 0) ? weight_fg_tx_ : weight_bg_tx_;
drw_pass.init();
drw_pass.shader_set(inst_.shaders.static_shader_get(DOF_FILTER));
drw_pass.bind_texture("color_tx", &color_chain.previous());
drw_pass.bind_texture("weight_tx", &weight_chain.previous());
drw_pass.bind_image("out_color_img", &color_chain.current());
drw_pass.bind_image("out_weight_img", &weight_chain.current());
drw_pass.dispatch(&dispatch_filter_size_);
drw_pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
}
void DepthOfField::scatter_pass_sync()
{
for (int pass = 0; pass < 2; pass++) {
PassSimple &drw_pass = (pass == 0) ? scatter_fg_ps_ : scatter_bg_ps_;
drw_pass.init();
drw_pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL);
drw_pass.shader_set(inst_.shaders.static_shader_get(DOF_SCATTER));
drw_pass.bind_ubo("dof_buf", data_);
drw_pass.push_constant("use_bokeh_lut", use_bokeh_lut_);
drw_pass.bind_texture("bokeh_lut_tx", &bokeh_scatter_lut_tx_);
drw_pass.bind_texture("occlusion_tx", &occlusion_tx_);
if (pass == 0) {
drw_pass.bind_ssbo("scatter_list_buf", scatter_fg_list_buf_);
drw_pass.draw_procedural_indirect(GPU_PRIM_TRI_STRIP, scatter_fg_indirect_buf_);
/* Avoid background gather pass writing to the occlusion_tx mid pass. */
drw_pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
else {
drw_pass.bind_ssbo("scatter_list_buf", scatter_bg_list_buf_);
drw_pass.draw_procedural_indirect(GPU_PRIM_TRI_STRIP, scatter_bg_indirect_buf_);
}
}
}
void DepthOfField::hole_fill_pass_sync()
{
const GPUSamplerState gather_bilinear = {GPU_SAMPLER_FILTERING_MIPMAP |
GPU_SAMPLER_FILTERING_LINEAR};
const GPUSamplerState gather_nearest = {GPU_SAMPLER_FILTERING_MIPMAP};
hole_fill_ps_.init();
hole_fill_ps_.bind_resources(inst_.sampling);
hole_fill_ps_.shader_set(inst_.shaders.static_shader_get(DOF_GATHER_HOLE_FILL));
hole_fill_ps_.bind_ubo("dof_buf", data_);
hole_fill_ps_.bind_texture("color_bilinear_tx", &reduced_color_tx_, gather_bilinear);
hole_fill_ps_.bind_texture("color_tx", &reduced_color_tx_, gather_nearest);
hole_fill_ps_.bind_texture("coc_tx", &reduced_coc_tx_, gather_nearest);
hole_fill_ps_.bind_image("in_tiles_fg_img", &tiles_fg_tx_.current());
hole_fill_ps_.bind_image("in_tiles_bg_img", &tiles_bg_tx_.current());
hole_fill_ps_.bind_image("out_color_img", &hole_fill_color_tx_);
hole_fill_ps_.bind_image("out_weight_img", &hole_fill_weight_tx_);
hole_fill_ps_.dispatch(&dispatch_gather_size_);
hole_fill_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
void DepthOfField::resolve_pass_sync()
{
GPUSamplerState with_filter = {GPU_SAMPLER_FILTERING_LINEAR};
RenderBuffers &render_buffers = inst_.render_buffers;
gpu::Shader *sh = inst_.shaders.static_shader_get(use_bokeh_lut_ ? DOF_RESOLVE_LUT :
DOF_RESOLVE);
resolve_ps_.init();
resolve_ps_.specialize_constant(sh, "do_debug_color", inst_.debug_mode == DEBUG_DOF_PLANES);
resolve_ps_.shader_set(sh);
resolve_ps_.bind_ubo("dof_buf", data_);
resolve_ps_.bind_texture("depth_tx", &render_buffers.depth_tx, no_filter);
resolve_ps_.bind_texture("color_tx", &input_color_tx_, no_filter);
resolve_ps_.bind_texture("stable_color_tx", &resolve_stable_color_tx_, no_filter);
resolve_ps_.bind_texture("color_bg_tx", &color_bg_tx_.current(), with_filter);
resolve_ps_.bind_texture("color_fg_tx", &color_fg_tx_.current(), with_filter);
resolve_ps_.bind_image("in_tiles_fg_img", &tiles_fg_tx_.current());
resolve_ps_.bind_image("in_tiles_bg_img", &tiles_bg_tx_.current());
resolve_ps_.bind_texture("weight_bg_tx", &weight_bg_tx_.current());
resolve_ps_.bind_texture("weight_fg_tx", &weight_fg_tx_.current());
resolve_ps_.bind_texture("color_hole_fill_tx", &hole_fill_color_tx_);
resolve_ps_.bind_texture("weight_hole_fill_tx", &hole_fill_weight_tx_);
resolve_ps_.bind_texture("bokeh_lut_tx", &bokeh_resolve_lut_tx_);
resolve_ps_.bind_image("out_color_img", &output_color_tx_);
resolve_ps_.bind_resources(inst_.sampling);
resolve_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH);
resolve_ps_.dispatch(&dispatch_resolve_size_);
resolve_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Post-FX Rendering.
* \{ */
void DepthOfField::update_sample_table()
{
float2 subpixel_offset = inst_.film.pixel_jitter_get();
/* Since the film jitter is in full-screen res, divide by 2 to get the jitter in half res. */
subpixel_offset *= 0.5;
/* Same offsets as in dof_spatial_filtering(). */
const std::array<int2, 4> plus_offsets = {int2(-1, 0), int2(0, -1), int2(1, 0), int2(0, 1)};
const float radius = 1.5f;
int i = 0;
for (int2 offset : plus_offsets) {
float2 pixel_ofs = float2(offset) - subpixel_offset;
data_.filter_samples_weight[i++] = film_filter_weight(radius, math::length_squared(pixel_ofs));
}
data_.filter_center_weight = film_filter_weight(radius, math::length_squared(subpixel_offset));
}
void DepthOfField::render(View &view,
gpu::Texture **input_tx,
gpu::Texture **output_tx,
DepthOfFieldBuffer &dof_buffer)
{
if (fx_radius_ == 0.0f) {
return;
}
input_color_tx_ = *input_tx;
output_color_tx_ = *output_tx;
extent_ = {GPU_texture_width(input_color_tx_), GPU_texture_height(input_color_tx_)};
{
const CameraData &cam_data = inst_.camera.data_get();
data_.camera_type = cam_data.type;
/* OPTI(fclem) Could be optimized. */
float3 jitter = float3(fx_radius_, 0.0f, -focus_distance_);
float3 center = float3(0.0f, 0.0f, -focus_distance_);
mul_project_m4_v3(cam_data.winmat.ptr(), jitter);
mul_project_m4_v3(cam_data.winmat.ptr(), center);
/* Simplify CoC calculation to a simple MADD. */
if (inst_.camera.is_orthographic()) {
data_.coc_mul = (center[0] - jitter[0]) * 0.5f * extent_[0];
data_.coc_bias = focus_distance_ * data_.coc_mul;
}
else {
data_.coc_bias = -(center[0] - jitter[0]) * 0.5f * extent_[0];
data_.coc_mul = focus_distance_ * data_.coc_bias;
}
float min_fg_coc = coc_radius_from_camera_depth(data_, -cam_data.clip_near);
float max_bg_coc = coc_radius_from_camera_depth(data_, -cam_data.clip_far);
if (data_.camera_type != CAMERA_ORTHO) {
/* Background is at infinity so maximum CoC is the limit of coc_radius_from_camera_depth
* at -inf. We only do this for perspective camera since orthographic coc limit is inf. */
max_bg_coc = data_.coc_bias;
}
/* Clamp with user defined max. */
data_.coc_abs_max = min_ff(max_ff(fabsf(min_fg_coc), fabsf(max_bg_coc)), fx_max_coc_);
/* TODO(fclem): Make this dependent of the quality of the gather pass. */
data_.scatter_coc_threshold = 4.0f;
update_sample_table();
data_.push_update();
}
/* Acquire reduce texture mip chains, and views for each mip level. */
{
/* TODO(fclem): Once we render into multiple view, we'll need to use the maximum resolution. */
int2 max_render_res = inst_.film.render_extent_get();
int2 half_render_res = math::divide_ceil(max_render_res, int2(2));
int2 reduce_size = math::ceil_to_multiple(half_render_res, int2(DOF_REDUCE_GROUP_SIZE));
/* Now we know the maximum render resolution of every view, using depth of field, allocate
* reduced buffers. Color needs to be signed format here. See note in shader for explanation */
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT |
GPU_TEXTURE_USAGE_SHADER_WRITE;
reduced_color_tx_.acquire_2d(
reduce_size, gpu::TextureFormat::SFLOAT_16_16_16_16, usage, DOF_MIP_COUNT);
reduced_coc_tx_.acquire_2d(reduce_size, gpu::TextureFormat::SFLOAT_16, usage, DOF_MIP_COUNT);
/* Set pointers to view textures as TextureFromPool is transient. */
reduced_color_tx_.ensure_mip_views();
reduced_coc_tx_.ensure_mip_views();
for (int i = 0; i < DOF_MIP_COUNT; ++i) {
reduced_color_mip_views_[i] = reduced_color_tx_.mip_view(i);
reduced_coc_mip_views_[i] = reduced_coc_tx_.mip_view(i);
}
}
int2 half_res = math::divide_ceil(extent_, int2(2));
int2 quarter_res = math::divide_ceil(extent_, int2(4));
int2 tile_res = math::divide_ceil(half_res, int2(DOF_TILES_SIZE));
dispatch_setup_size_ = int3(math::divide_ceil(half_res, int2(DOF_DEFAULT_GROUP_SIZE)), 1);
dispatch_stabilize_size_ = int3(math::divide_ceil(half_res, int2(DOF_STABILIZE_GROUP_SIZE)), 1);
dispatch_downsample_size_ = int3(math::divide_ceil(quarter_res, int2(DOF_DEFAULT_GROUP_SIZE)),
1);
dispatch_reduce_size_ = int3(math::divide_ceil(half_res, int2(DOF_REDUCE_GROUP_SIZE)), 1);
dispatch_tiles_flatten_size_ = int3(math::divide_ceil(half_res, int2(DOF_TILES_SIZE)), 1);
dispatch_tiles_dilate_size_ = int3(
math::divide_ceil(tile_res, int2(DOF_TILES_DILATE_GROUP_SIZE)), 1);
dispatch_gather_size_ = int3(math::divide_ceil(half_res, int2(DOF_GATHER_GROUP_SIZE)), 1);
dispatch_filter_size_ = int3(math::divide_ceil(half_res, int2(DOF_FILTER_GROUP_SIZE)), 1);
dispatch_resolve_size_ = int3(math::divide_ceil(extent_, int2(DOF_RESOLVE_GROUP_SIZE)), 1);
if (GPU_type_matches_ex(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL)) {
/* On Mesa, there is a sync bug which can make a portion of the main pass (usually one shader)
* leave blocks of un-initialized memory. Doing a flush seems to alleviate the issue. */
GPU_flush();
}
GPU_debug_group_begin("Depth of Field");
Manager &drw = *inst_.manager;
constexpr eGPUTextureUsage usage_readwrite = GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_SHADER_WRITE;
constexpr eGPUTextureUsage usage_readwrite_attach = usage_readwrite |
GPU_TEXTURE_USAGE_ATTACHMENT;
{
GPU_debug_group_begin("Setup");
{
bokeh_gather_lut_tx_.acquire_2d(int2(DOF_BOKEH_LUT_SIZE), gpu::TextureFormat::SFLOAT_16_16);
bokeh_scatter_lut_tx_.acquire_2d(int2(DOF_BOKEH_LUT_SIZE), gpu::TextureFormat::SFLOAT_16);
bokeh_resolve_lut_tx_.acquire_2d(int2(DOF_MAX_SLIGHT_FOCUS_RADIUS * 2 + 1),
gpu::TextureFormat::SFLOAT_16);
if (use_bokeh_lut_) {
drw.submit(bokeh_lut_ps_, view);
}
}
{
setup_color_tx_.acquire_2d(
half_res, gpu::TextureFormat::SFLOAT_16_16_16_16, usage_readwrite);
setup_coc_tx_.acquire_2d(half_res, gpu::TextureFormat::SFLOAT_16);
drw.submit(setup_ps_, view);
}
{
stabilize_output_tx_.acquire_2d(half_res, gpu::TextureFormat::SFLOAT_16_16_16_16);
stabilize_valid_history_ = !dof_buffer.stabilize_history_tx_.acquire_2d(
half_res, gpu::TextureFormat::SFLOAT_16_16_16_16);
if (stabilize_valid_history_ == false) {
/* Avoid uninitialized memory that can contain NaNs. */
dof_buffer.stabilize_history_tx_.clear(float4(0.0f));
}
stabilize_input_ = dof_buffer.stabilize_history_tx_;
/* Outputs to reduced_*_tx_ mip 0. */
drw.submit(stabilize_ps_, view);
/* Swap history and output buffers, and retain the "new" history buffer until
* next cycle so we can reuse it as input. */
TextureFromPool::swap(stabilize_output_tx_, dof_buffer.stabilize_history_tx_);
dof_buffer.stabilize_history_tx_.retain();
/* Used by stabilize pass. */
stabilize_output_tx_.release();
setup_color_tx_.release();
}
{
GPU_debug_group_begin("Tile Prepare");
/* WARNING: If format changes, make sure dof_tile_* GLSL constants are properly encoded. */
tiles_fg_tx_.previous().acquire_2d(
tile_res, gpu::TextureFormat::UFLOAT_11_11_10, usage_readwrite);
tiles_bg_tx_.previous().acquire_2d(
tile_res, gpu::TextureFormat::UFLOAT_11_11_10, usage_readwrite);
tiles_fg_tx_.current().acquire_2d(
tile_res, gpu::TextureFormat::UFLOAT_11_11_10, usage_readwrite);
tiles_bg_tx_.current().acquire_2d(
tile_res, gpu::TextureFormat::UFLOAT_11_11_10, usage_readwrite);
drw.submit(tiles_flatten_ps_, view);
/* Used by tile_flatten and stabilize_ps pass. */
setup_coc_tx_.release();
/* Error introduced by gather center jittering. */
const float error_multiplier = 1.0f + 1.0f / (DOF_GATHER_RING_COUNT + 0.5f);
int dilation_end_radius = ceilf((fx_max_coc_ * error_multiplier) / (DOF_TILES_SIZE * 2));
/* Run dilation twice. One for minmax and one for minabs. */
for (int pass = 0; pass < 2; pass++) {
/* This algorithm produce the exact dilation radius by dividing it in multiple passes. */
int dilation_radius = 0;
while (dilation_radius < dilation_end_radius) {
int remainder = dilation_end_radius - dilation_radius;
/* Do not step over any unvisited tile. */
int max_multiplier = dilation_radius + 1;
int ring_count = min_ii(DOF_DILATE_RING_COUNT, ceilf(remainder / float(max_multiplier)));
int multiplier = min_ii(max_multiplier, floorf(remainder / float(ring_count)));
dilation_radius += ring_count * multiplier;
tiles_dilate_ring_count_ = ring_count;
tiles_dilate_ring_width_mul_ = multiplier;
tiles_fg_tx_.swap();
tiles_bg_tx_.swap();
drw.submit((pass == 0) ? tiles_dilate_minmax_ps_ : tiles_dilate_minabs_ps_, view);
}
}
tiles_fg_tx_.previous().release();
tiles_bg_tx_.previous().release();
GPU_debug_group_end();
}
downsample_tx_.acquire_2d(
quarter_res, gpu::TextureFormat::SFLOAT_16_16_16_16, usage_readwrite);
drw.submit(downsample_ps_, view);
scatter_fg_indirect_buf_.clear_to_zero();
scatter_bg_indirect_buf_.clear_to_zero();
drw.submit(reduce_ps_, view);
/* Used by reduce pass. */
downsample_tx_.release();
GPU_debug_group_end();
}
for (int is_background = 0; is_background < 2; is_background++) {
GPU_debug_group_begin(is_background ? "Background Convolution" : "Foreground Convolution");
SwapChain<TextureFromPool, 2> &color_tx = is_background ? color_bg_tx_ : color_fg_tx_;
SwapChain<TextureFromPool, 2> &weight_tx = is_background ? weight_bg_tx_ : weight_fg_tx_;
Framebuffer &scatter_fb = is_background ? scatter_bg_fb_ : scatter_fg_fb_;
PassSimple &gather_ps = is_background ? gather_bg_ps_ : gather_fg_ps_;
PassSimple &filter_ps = is_background ? filter_bg_ps_ : filter_fg_ps_;
PassSimple &scatter_ps = is_background ? scatter_bg_ps_ : scatter_fg_ps_;
color_tx.current().acquire_2d(
half_res, gpu::TextureFormat::SFLOAT_16_16_16_16, usage_readwrite_attach);
weight_tx.current().acquire_2d(half_res, gpu::TextureFormat::SFLOAT_16, usage_readwrite);
occlusion_tx_.acquire_2d(half_res, gpu::TextureFormat::SFLOAT_16_16);
drw.submit(gather_ps, view);
{
/* Filtering pass. */
color_tx.swap();
weight_tx.swap();
color_tx.current().acquire_2d(
half_res, gpu::TextureFormat::SFLOAT_16_16_16_16, usage_readwrite_attach);
weight_tx.current().acquire_2d(half_res, gpu::TextureFormat::SFLOAT_16, usage_readwrite);
drw.submit(filter_ps, view);
color_tx.previous().release();
weight_tx.previous().release();
}
GPU_memory_barrier(GPU_BARRIER_FRAMEBUFFER);
scatter_fb.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(color_tx.current()));
if (GPU_type_matches_ex(
GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OPENSOURCE, GPU_BACKEND_OPENGL))
{
/* WORKAROUND(fclem): Mesa has some synchronization issues between the previous compute
* shader and the following graphic pass (see #141198). */
GPU_flush();
}
GPU_framebuffer_bind(scatter_fb);
drw.submit(scatter_ps, view);
/* Used by scatter pass. */
occlusion_tx_.release();
GPU_debug_group_end();
}
{
GPU_debug_group_begin("Hole Fill");
bokeh_gather_lut_tx_.release();
bokeh_scatter_lut_tx_.release();
hole_fill_color_tx_.acquire_2d(
half_res, gpu::TextureFormat::SFLOAT_16_16_16_16, usage_readwrite);
hole_fill_weight_tx_.acquire_2d(half_res, gpu::TextureFormat::SFLOAT_16, usage_readwrite);
drw.submit(hole_fill_ps_, view);
/* NOTE: We do not filter the hole-fill pass as effect is likely to not be noticeable. */
reduced_color_tx_.release();
reduced_coc_tx_.release();
GPU_debug_group_end();
}
{
GPU_debug_group_begin("Resolve");
resolve_stable_color_tx_ = dof_buffer.stabilize_history_tx_;
drw.submit(resolve_ps_, view);
color_bg_tx_.current().release();
color_fg_tx_.current().release();
weight_bg_tx_.current().release();
weight_fg_tx_.current().release();
tiles_fg_tx_.current().release();
tiles_bg_tx_.current().release();
hole_fill_color_tx_.release();
hole_fill_weight_tx_.release();
bokeh_resolve_lut_tx_.release();
GPU_debug_group_end();
}
GPU_debug_group_end();
/* Swap buffers so that next effect has the right input. */
std::swap(*input_tx, *output_tx);
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,220 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Depth of field post process effect.
*
* There are 2 methods to achieve this effect.
* - The first uses projection matrix offsetting and sample accumulation to give
* reference quality depth of field. But this needs many samples to hide the
* under-sampling.
* - The second one is a post-processing based one. It follows the
* implementation described in the presentation
* "Life of a Bokeh - SIGGRAPH 2018" from Guillaume Abadie.
* There are some difference with our actual implementation that prioritize quality.
*/
#pragma once
#include "eevee_depth_of_field_shared.hh"
#include "draw_pass.hh"
namespace blender::eevee {
using namespace draw;
class Instance;
/* -------------------------------------------------------------------- */
/** \name Depth of field
* \{ */
struct DepthOfFieldBuffer {
/**
* Per view history texture for stabilize pass.
* Swapped with stabilize_output_tx_ in order to reuse the previous history during DoF
* processing.
* Note this should be private as its inner working only concerns the Depth Of Field
* implementation. The view itself should not touch it.
*/
TextureFromPool stabilize_history_tx_ = {"dof_taa"};
};
using DepthOfFieldScatterListBuf = draw::StorageArrayBuffer<ScatterRect, 16, true>;
using DepthOfFieldDataBuf = draw::UniformBuffer<DepthOfFieldData>;
class DepthOfField {
private:
class Instance &inst_;
static constexpr GPUSamplerState no_filter = GPUSamplerState::default_sampler();
static constexpr GPUSamplerState with_filter = {GPU_SAMPLER_FILTERING_LINEAR};
/** Input/Output texture references. */
gpu::Texture *input_color_tx_ = nullptr;
gpu::Texture *output_color_tx_ = nullptr;
/** Bokeh LUT precompute pass. */
TextureFromPool bokeh_gather_lut_tx_ = {"dof_bokeh_gather_lut"};
TextureFromPool bokeh_resolve_lut_tx_ = {"dof_bokeh_resolve_lut"};
TextureFromPool bokeh_scatter_lut_tx_ = {"dof_bokeh_scatter_lut"};
PassSimple bokeh_lut_ps_ = {"BokehLut"};
/** Outputs half-resolution color and Circle Of Confusion. */
TextureFromPool setup_coc_tx_ = {"dof_setup_coc"};
TextureFromPool setup_color_tx_ = {"dof_setup_color"};
int3 dispatch_setup_size_ = int3(-1);
PassSimple setup_ps_ = {"Setup"};
/* Reduce buffer mip chains with view texture per level. */
TextureFromPool reduced_coc_tx_ = {"dof_reduced_coc"};
TextureFromPool reduced_color_tx_ = {"dof_reduced_color"};
std::array<gpu::Texture *, DOF_MIP_COUNT> reduced_coc_mip_views_;
std::array<gpu::Texture *, DOF_MIP_COUNT> reduced_color_mip_views_;
/** Stabilization (flicker attenuation) of Color and CoC output of the setup pass. */
TextureFromPool stabilize_output_tx_ = {"dof_taa"};
gpu::Texture *stabilize_input_ = nullptr;
bool32_t stabilize_valid_history_ = false;
int3 dispatch_stabilize_size_ = int3(-1);
PassSimple stabilize_ps_ = {"Stabilize"};
/** 1/4th res color buffer used to speedup the local contrast test in the first reduce pass. */
TextureFromPool downsample_tx_ = {"dof_downsample"};
int3 dispatch_downsample_size_ = int3(-1);
PassSimple downsample_ps_ = {"Downsample"};
/** Create mip-mapped color & COC textures for gather passes as well as scatter rect list. */
DepthOfFieldScatterListBuf scatter_fg_list_buf_;
DepthOfFieldScatterListBuf scatter_bg_list_buf_;
DrawIndirectBuf scatter_fg_indirect_buf_;
DrawIndirectBuf scatter_bg_indirect_buf_;
int3 dispatch_reduce_size_ = int3(-1);
PassSimple reduce_ps_ = {"Reduce"};
/** Outputs min & max COC in each 8x8 half res pixel tiles (so 1/16th of full resolution). */
SwapChain<TextureFromPool, 2> tiles_fg_tx_;
SwapChain<TextureFromPool, 2> tiles_bg_tx_;
int3 dispatch_tiles_flatten_size_ = int3(-1);
PassSimple tiles_flatten_ps_ = {"TilesFlatten"};
/** Dilates the min & max CoCs to cover maximum COC values. */
int tiles_dilate_ring_count_ = -1;
int tiles_dilate_ring_width_mul_ = -1;
int3 dispatch_tiles_dilate_size_ = int3(-1);
PassSimple tiles_dilate_minmax_ps_ = {"TilesDilateMinmax"};
PassSimple tiles_dilate_minabs_ps_ = {"TilesDilateMinabs"};
/** Gather convolution for low intensity pixels and low contrast areas. */
SwapChain<TextureFromPool, 2> color_bg_tx_;
SwapChain<TextureFromPool, 2> color_fg_tx_;
SwapChain<TextureFromPool, 2> weight_bg_tx_;
SwapChain<TextureFromPool, 2> weight_fg_tx_;
TextureFromPool occlusion_tx_ = {"dof_occlusion"};
int3 dispatch_gather_size_ = int3(-1);
PassSimple gather_fg_ps_ = {"GatherFg"};
PassSimple gather_bg_ps_ = {"GatherBg"};
/** Hole-fill convolution: Gather pass meant to fill areas of foreground dis-occlusion. */
TextureFromPool hole_fill_color_tx_ = {"dof_color_hole_fill"};
TextureFromPool hole_fill_weight_tx_ = {"dof_weight_hole_fill"};
PassSimple hole_fill_ps_ = {"HoleFill"};
/** Small Filter pass to reduce noise out of gather passes. */
int3 dispatch_filter_size_ = int3(-1);
PassSimple filter_fg_ps_ = {"FilterFg"};
PassSimple filter_bg_ps_ = {"FilterBg"};
/** Scatter convolution: A quad is emitted for every 4 bright enough half pixels. */
Framebuffer scatter_fg_fb_ = {"dof_scatter_fg"};
Framebuffer scatter_bg_fb_ = {"dof_scatter_bg"};
PassSimple scatter_fg_ps_ = {"ScatterFg"};
PassSimple scatter_bg_ps_ = {"ScatterBg"};
/** Recombine the results and also perform a slight out of focus gather. */
gpu::Texture *resolve_stable_color_tx_ = nullptr;
int3 dispatch_resolve_size_ = int3(-1);
PassSimple resolve_ps_ = {"Resolve"};
DepthOfFieldDataBuf data_;
/** Scene settings that are immutable. */
float user_overblur_ = 0.0f;
float fx_max_coc_ = 0.0f;
/** Use jittered depth of field where we randomize camera location. */
bool do_jitter_ = false;
/** Enable bokeh lookup texture. */
bool use_bokeh_lut_ = false;
/** Circle of Confusion radius for FX DoF passes. Is in view X direction in [0..1] range. */
float fx_radius_ = 0.0f;
/** Circle of Confusion radius for jittered DoF. Is in view X direction in [0..1] range. */
float jitter_radius_ = 0.0f;
/** Focus distance in view space. */
float focus_distance_ = 0.0f;
/** Extent of the input buffer. */
int2 extent_ = int2(0);
bool enabled_ = false;
public:
DepthOfField(Instance &inst) : inst_(inst) {};
~DepthOfField() {};
void init();
void sync();
/**
* Apply Depth Of Field jittering to the view and projection matrices..
*/
void jitter_apply(float4x4 &winmat, float4x4 &viewmat);
/**
* Will swap input and output texture if rendering happens. The actual output of this function
* is in input_tx.
*/
void render(View &view,
gpu::Texture **input_tx,
gpu::Texture **output_tx,
DepthOfFieldBuffer &dof_buffer);
bool postfx_enabled() const
{
return fx_radius_ > 0.0f;
}
bool enabled() const
{
return enabled_;
}
private:
void bokeh_lut_pass_sync();
void setup_pass_sync();
void stabilize_pass_sync();
void downsample_pass_sync();
void reduce_pass_sync();
void tiles_flatten_pass_sync();
void tiles_dilate_pass_sync();
void gather_pass_sync();
void filter_pass_sync();
void scatter_pass_sync();
void hole_fill_pass_sync();
void resolve_pass_sync();
/**
* Similar to #Film::update_sample_table()
* but with constant filter radius and constant sample count.
*/
void update_sample_table();
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,116 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#include "eevee_camera_shared.hh"
#include "eevee_defines.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
/* 5% error threshold. */
#define DOF_FAST_GATHER_COC_ERROR 0.05f
#define DOF_GATHER_RING_COUNT 5
#define DOF_DILATE_RING_COUNT 3
struct [[host_shared]] DepthOfFieldData {
/** Size of the render targets for gather & scatter passes. */
int2 extent;
/** Size of a pixel in uv space (1.0 / extent). */
float2 texel_size;
/** Scale factor for anisotropic bokeh. */
float2 bokeh_anisotropic_scale;
float2 bokeh_anisotropic_scale_inv;
/* Correction factor to align main target pixels with the filtered mipmap chain texture. */
float2 gather_uv_fac;
/** Scatter parameters. */
float scatter_coc_threshold;
float scatter_color_threshold;
float scatter_neighbor_max_color;
int scatter_sprite_per_row;
/** Number of side the bokeh shape has. */
float bokeh_blades;
/** Rotation of the bokeh shape. */
float bokeh_rotation;
/** Multiplier and bias to apply to linear depth to Circle of confusion (CoC). */
float coc_mul;
float coc_bias;
/** Maximum absolute allowed Circle of confusion (CoC). Min of computed max and user max. */
float coc_abs_max;
/** Copy of camera type. */
enum eCameraType camera_type;
/** Weights of spatial filtering in stabilize pass. Not array to avoid alignment restriction. */
float4 filter_samples_weight;
float filter_center_weight;
/** Max number of sprite in the scatter pass for each ground. */
uint scatter_max_rect;
int _pad0;
int _pad1;
};
struct [[host_shared]] ScatterRect {
/** Color and CoC of the 4 pixels the scatter sprite represents. */
float4 color_and_coc[4];
/** Rect center position in half pixel space. */
float2 offset;
/** Rect half extent in half pixel space. */
float2 half_extent;
};
static inline float coc_radius_from_camera_depth(DepthOfFieldData dof, float depth)
{
depth = (dof.camera_type != CAMERA_ORTHO) ? 1.0f / depth : depth;
return dof.coc_mul * depth + dof.coc_bias;
}
static inline float regular_polygon_side_length(float sides_count)
{
return 2.0f * sinf(EEVEE_PI / sides_count);
}
/* Returns intersection ratio between the radius edge at theta and the regular polygon edge.
* Start first corners at theta == 0. */
static inline float circle_to_polygon_radius(float sides_count, float theta)
{
/* From Graphics Gems from CryENGINE 3 (SIGGRAPH 2013) by Tiago Sousa (slide 36). */
float side_angle = (2.0f * EEVEE_PI) / sides_count;
return cosf(side_angle * 0.5f) /
cosf(theta - side_angle * floorf((sides_count * theta + EEVEE_PI) / (2.0f * EEVEE_PI)));
}
/* Remap input angle to have homogenous spacing of points along a polygon edge.
* Expects theta to be in [0..2pi] range. */
static inline float circle_to_polygon_angle(float sides_count, float theta)
{
float side_angle = (2.0f * EEVEE_PI) / sides_count;
float halfside_angle = side_angle * 0.5f;
float side = floorf(theta / side_angle);
/* Length of segment from center to the middle of polygon side. */
float adjacent = circle_to_polygon_radius(sides_count, 0.0f);
/* This is the relative position of the sample on the polygon half side. */
float local_theta = theta - side * side_angle;
float ratio = (local_theta - halfside_angle) / halfside_angle;
float halfside_len = regular_polygon_side_length(sides_count) * 0.5f;
float opposite = ratio * halfside_len;
/* NOTE: atan(y_over_x) has output range [-pi/2..pi/2]. */
float final_local_theta = atanf(opposite / adjacent);
return side * side_angle + final_local_theta;
}
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,104 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_rect.h"
#include "GPU_framebuffer.hh"
#include "ED_screen.hh"
#include "ED_view3d.hh"
#include "DRW_render.hh"
#include "RE_pipeline.h"
#include "eevee_engine.h" /* Own include. */
#include "draw_view_data.hh"
#include "eevee_instance.hh"
namespace blender {
namespace eevee {
DrawEngine *Engine::create_instance()
{
return new Instance();
}
void Engine::free_static()
{
ShaderModule::module_free();
}
} // namespace eevee
using namespace blender::eevee;
static void eevee_render(RenderEngine *engine, Depsgraph *depsgraph)
{
Instance *instance = nullptr;
auto eevee_render_to_image = [&](RenderEngine *engine, RenderLayer *layer, const rcti /*rect*/) {
Render *render = engine->re;
Object *camera_original_ob = RE_GetCamera(engine->re);
const char *viewname = RE_GetActiveRenderView(engine->re);
int size[2] = {engine->resolution_x, engine->resolution_y};
/* Avoid leaking in the case of multiview. (see #145743) */
delete instance;
/* WORKAROUND: Fails if created in the parent scope. Must be because of lack of active
* `DRWContext`. To be revisited. */
instance = new Instance();
rctf view_rect;
rcti rect;
RE_GetViewPlane(render, &view_rect, &rect);
rcti visible_rect = rect;
instance->init(size, &rect, &visible_rect, engine, depsgraph, camera_original_ob, layer);
instance->render_frame(engine, layer, viewname);
};
auto eevee_store_metadata = [&](RenderResult *render_result) {
instance->store_metadata(render_result);
};
DRW_render_to_image(engine, depsgraph, eevee_render_to_image, eevee_store_metadata);
delete instance;
}
static void eevee_render_update_passes(RenderEngine *engine, Scene *scene, ViewLayer *view_layer)
{
Instance::update_passes(engine, scene, view_layer);
}
RenderEngineType DRW_engine_viewport_eevee_type = {
/*next*/ nullptr,
/*prev*/ nullptr,
/*idname*/ "BLENDER_EEVEE",
/*name*/ N_("EEVEE"),
/*flag*/ RE_INTERNAL | RE_USE_PREVIEW | RE_USE_STEREO_VIEWPORT | RE_USE_GPU_CONTEXT,
/*update*/ nullptr,
/*render*/ &eevee_render,
/*render_frame_finish*/ nullptr,
/*draw*/ nullptr,
/*bake*/ nullptr,
/*view_update*/ nullptr,
/*view_draw*/ nullptr,
/*update_script_node*/ nullptr,
/*update_render_passes*/ &eevee_render_update_passes,
/*update_custom_camera*/ nullptr,
/*draw_engine*/ nullptr,
/*rna_ext*/
{
/*data*/ nullptr,
/*srna*/ nullptr,
/*call*/ nullptr,
},
};
} // namespace blender

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup DNA
*/
#pragma once
#include "DRW_render.hh"
#include "RE_engine.h"
namespace blender {
extern RenderEngineType DRW_engine_viewport_eevee_type;
namespace eevee {
struct Engine : public DrawEngine::Pointer {
DrawEngine *create_instance() final;
static void free_static();
};
} // namespace eevee
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,367 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* The film class handles accumulation of samples with any distorted camera_type
* using a pixel filter. Inputs needs to be jittered so that the filter converges to the right
* result.
*
* In viewport, we switch between 2 accumulation mode depending on the scene state.
* - For static scene, we use a classic weighted accumulation.
* - For dynamic scene (if an update is detected), we use a more temporally stable accumulation
* following the Temporal Anti-Aliasing method (a.k.a. Temporal Super-Sampling). This does
* history reprojection and rectification to avoid most of the flickering.
*
* The Film module uses the following terms to refer to different spaces/extents:
*
* - Display: The full output extent (matches the full viewport or the final image resolution).
*
* - Film: The same extent as display, or a subset of it when a Render Region is used.
*
* - Render: The extent used internally by the engine for rendering the main views.
* Equals to the full display extent + overscan (even when a Render Region is used)
* and its resolution can be scaled.
*/
#pragma once
#include <string>
#include "BLI_math_vector.hh"
#include "BLI_set.hh"
#include "DNA_scene_types.h"
#include "DRW_render.hh"
#include "draw_pass.hh"
#include "eevee_film_shared.hh"
#include "eevee_renderbuffers_shared.hh"
#include <sstream>
namespace blender::eevee {
using namespace draw;
class Instance;
/* -------------------------------------------------------------------- */
/** \name Film
* \{ */
class Film {
public:
/** Stores indirection table of AOVs based on their name hash and their type. */
StorageBuffer<AOVsInfoData> aovs_info;
/** For debugging purpose but could be a user option in the future. */
static constexpr bool use_box_filter = false;
struct DepthState {
/** Set to 0 if reverse Z is supported, 1 otherwise. */
float clear_value = 1.0f;
/** Set to DRW_STATE_DEPTH_GREATER_EQUAL if reverse Z is supported, DRW_STATE_DEPTH_LESS_EQUAL
* otherwise. */
DRWState test_state = DRW_STATE_DEPTH_LESS_EQUAL;
} depth;
private:
Instance &inst_;
/** Incoming combined buffer with post FX applied (motion blur + depth of field). */
gpu::Texture *combined_final_tx_ = nullptr;
/** Are we using the compute shader/pipeline. */
bool use_compute_ = false;
/** Copy of v3d->shading properties used to detect viewport settings update. */
eViewLayerEEVEEPassType ui_render_pass_ = eViewLayerEEVEEPassType(0);
std::string ui_aov_name_;
/**
* Main accumulation textures containing every render-pass except depth, cryptomatte and
* combined.
*/
Texture color_accum_tx_;
Texture value_accum_tx_;
/** Depth accumulation texture. Separated because using a different format. */
Texture depth_tx_;
/** Cryptomatte texture. Separated because it requires full floats. */
Texture cryptomatte_tx_;
/** Combined "Color" buffer. Double buffered to allow re-projection. */
SwapChain<Texture, 2> combined_tx_;
/** Weight buffers. Double buffered to allow updating it during accumulation. */
SwapChain<Texture, 2> weight_tx_;
PassSimple accumulate_ps_ = {"Film.Accumulate"};
PassSimple copy_ps_ = {"Film.Copy"};
PassSimple cryptomatte_post_ps_ = {"Film.Cryptomatte.Post"};
FilmData &data_;
bool32_t display_only_;
int2 display_extent = int2(-1);
eViewLayerEEVEEPassType enabled_passes_ = eViewLayerEEVEEPassType(0);
/* Store the pass types needed by the viewport compositor separately, because some passes might
* be enabled but not used by the viewport compositor, so they needn't be written. */
eViewLayerEEVEEPassType viewport_compositor_enabled_passes_ = eViewLayerEEVEEPassType(0);
PassCategory enabled_categories_ = PassCategory(0);
bool use_reprojection_ = false;
bool is_valid_render_extent_ = true;
public:
Film(Instance &inst, FilmData &data) : inst_(inst), data_(data) {};
~Film() {};
void init(const int2 &full_extent, const rcti *output_rect);
void sync();
void end_sync();
const FilmData &get_data()
{
return data_;
}
/** Accumulate the newly rendered sample contained in #RenderBuffers and blit to display. */
void accumulate(View &view, gpu::Texture *combined_final_tx);
/** Sort and normalize cryptomatte samples. */
void cryptomatte_sort();
/** Blit to display. No rendered sample needed. */
void display();
float *read_pass(eViewLayerEEVEEPassType pass_type, int layer_offset);
float *read_aov(ViewLayerAOV *aov);
gpu::Texture *get_pass_texture(eViewLayerEEVEEPassType pass_type, int layer_offset);
gpu::Texture *get_aov_texture(ViewLayerAOV *aov);
void write_viewport_compositor_passes();
/** Returns shading views internal resolution. Includes overscan pixels. */
int2 render_extent_get() const
{
return data_.render_extent;
}
bool is_valid_render_extent() const
{
return is_valid_render_extent_;
}
/** Size and offset of the film (taking into account render region). */
int2 film_extent_get() const
{
return data_.extent;
}
int2 film_offset_get() const
{
return data_.offset;
}
/** Size of the whole viewport or the render, disregarding the render region. */
int2 display_extent_get() const
{
return display_extent;
}
/** Number of padding pixels around the render target. Included inside `render_extent_get`. */
int render_overscan_get() const
{
return data_.overscan;
}
/** Returns number of overscan pixels for the given parameters. */
static int overscan_pixels_get(float overscan, int2 extent)
{
return math::ceil(max_ff(0.0f, overscan) * math::reduce_max(extent));
}
int scaling_factor_get() const
{
return data_.scaling_factor;
}
float2 pixel_jitter_get() const;
float background_opacity_get() const
{
return data_.background_opacity;
}
eViewLayerEEVEEPassType enabled_passes_get() const;
int cryptomatte_layer_len_get() const;
/** WARNING: Film and RenderBuffers use different storage types for AO and Shadow. */
static ePassStorageType pass_storage_type(eViewLayerEEVEEPassType pass_type)
{
switch (pass_type) {
case EEVEE_RENDER_PASS_DEPTH:
case EEVEE_RENDER_PASS_MIST:
return PASS_STORAGE_VALUE;
case EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT:
case EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET:
case EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL:
return PASS_STORAGE_CRYPTOMATTE;
default:
return PASS_STORAGE_COLOR;
}
}
static bool pass_is_float3(eViewLayerEEVEEPassType pass_type)
{
return pass_storage_type(pass_type) == PASS_STORAGE_COLOR &&
!ELEM(pass_type,
EEVEE_RENDER_PASS_COMBINED,
EEVEE_RENDER_PASS_VECTOR,
EEVEE_RENDER_PASS_TRANSPARENT);
}
/* Returns layer offset in the accumulation texture. -1 if the pass is not enabled. */
int pass_id_get(eViewLayerEEVEEPassType pass_type) const
{
switch (pass_type) {
case EEVEE_RENDER_PASS_COMBINED:
return data_.combined_id;
case EEVEE_RENDER_PASS_DEPTH:
return data_.depth_id;
case EEVEE_RENDER_PASS_MIST:
return data_.mist_id;
case EEVEE_RENDER_PASS_NORMAL:
return data_.normal_id;
case EEVEE_RENDER_PASS_POSITION:
return data_.position_id;
case EEVEE_RENDER_PASS_VECTOR:
return data_.vector_id;
case EEVEE_RENDER_PASS_DIFFUSE_LIGHT:
return data_.diffuse_light_id;
case EEVEE_RENDER_PASS_DIFFUSE_COLOR:
return data_.diffuse_color_id;
case EEVEE_RENDER_PASS_SPECULAR_LIGHT:
return data_.specular_light_id;
case EEVEE_RENDER_PASS_SPECULAR_COLOR:
return data_.specular_color_id;
case EEVEE_RENDER_PASS_VOLUME_LIGHT:
return data_.volume_light_id;
case EEVEE_RENDER_PASS_EMIT:
return data_.emission_id;
case EEVEE_RENDER_PASS_ENVIRONMENT:
return data_.environment_id;
case EEVEE_RENDER_PASS_SHADOW:
return data_.shadow_id;
case EEVEE_RENDER_PASS_AO:
return data_.ambient_occlusion_id;
case EEVEE_RENDER_PASS_TRANSPARENT:
return data_.transparent_id;
case EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT:
return data_.cryptomatte_object_id;
case EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET:
return data_.cryptomatte_asset_id;
case EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL:
return data_.cryptomatte_material_id;
default:
return -1;
}
}
static const Vector<std::string> pass_to_render_pass_names(eViewLayerEEVEEPassType pass_type,
const ViewLayer *view_layer)
{
Vector<std::string> result;
auto build_cryptomatte_passes = [&](const char *pass_name) {
const int num_cryptomatte_passes = (view_layer->cryptomatte_levels + 1) / 2;
for (int pass = 0; pass < num_cryptomatte_passes; pass++) {
std::stringstream ss;
ss.fill('0');
ss << pass_name;
ss.width(2);
ss << pass;
result.append(ss.str());
}
};
switch (pass_type) {
case EEVEE_RENDER_PASS_COMBINED:
result.append(RE_PASSNAME_COMBINED);
break;
case EEVEE_RENDER_PASS_DEPTH:
result.append(RE_PASSNAME_DEPTH);
break;
case EEVEE_RENDER_PASS_MIST:
result.append(RE_PASSNAME_MIST);
break;
case EEVEE_RENDER_PASS_NORMAL:
result.append(RE_PASSNAME_NORMAL);
break;
case EEVEE_RENDER_PASS_POSITION:
result.append(RE_PASSNAME_POSITION);
break;
case EEVEE_RENDER_PASS_VECTOR:
result.append(RE_PASSNAME_VECTOR);
break;
case EEVEE_RENDER_PASS_DIFFUSE_LIGHT:
result.append(RE_PASSNAME_DIFFUSE_DIRECT);
break;
case EEVEE_RENDER_PASS_DIFFUSE_COLOR:
result.append(RE_PASSNAME_DIFFUSE_COLOR);
break;
case EEVEE_RENDER_PASS_SPECULAR_LIGHT:
result.append(RE_PASSNAME_GLOSSY_DIRECT);
break;
case EEVEE_RENDER_PASS_SPECULAR_COLOR:
result.append(RE_PASSNAME_GLOSSY_COLOR);
break;
case EEVEE_RENDER_PASS_VOLUME_LIGHT:
result.append(RE_PASSNAME_VOLUME_LIGHT);
break;
case EEVEE_RENDER_PASS_EMIT:
result.append(RE_PASSNAME_EMIT);
break;
case EEVEE_RENDER_PASS_ENVIRONMENT:
result.append(RE_PASSNAME_ENVIRONMENT);
break;
case EEVEE_RENDER_PASS_SHADOW:
result.append(RE_PASSNAME_SHADOW);
break;
case EEVEE_RENDER_PASS_AO:
result.append(RE_PASSNAME_AO);
break;
case EEVEE_RENDER_PASS_TRANSPARENT:
result.append(RE_PASSNAME_TRANSPARENT);
break;
case EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT:
build_cryptomatte_passes(RE_PASSNAME_CRYPTOMATTE_OBJECT);
break;
case EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET:
build_cryptomatte_passes(RE_PASSNAME_CRYPTOMATTE_ASSET);
break;
case EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL:
build_cryptomatte_passes(RE_PASSNAME_CRYPTOMATTE_MATERIAL);
break;
default:
BLI_assert(0);
break;
}
return result;
}
/**
* Precompute sample weights if they are uniform across the whole film extent.
*/
void update_sample_table();
private:
void init_aovs(const Set<std::string> &passes_used_by_viewport_compositor);
void sync_mist();
void init_pass(PassSimple &pass, gpu::Shader *sh);
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,147 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
#define FILM_PRECOMP_SAMPLE_MAX 16
enum [[host_shared]] eFilmWeightLayerIndex : uint32_t {
FILM_WEIGHT_LAYER_ACCUMULATION,
FILM_WEIGHT_LAYER_DISTANCE,
};
enum [[host_shared]] ePassStorageType : uint32_t {
PASS_STORAGE_COLOR,
PASS_STORAGE_VALUE,
PASS_STORAGE_CRYPTOMATTE
};
enum [[host_shared]] PassCategory : uint32_t {
PASS_CATEGORY_DATA = 1u << 0,
PASS_CATEGORY_COLOR_1 = 1u << 1,
PASS_CATEGORY_COLOR_2 = 1u << 2,
PASS_CATEGORY_COLOR_3 = 1u << 3,
PASS_CATEGORY_AOV = 1u << 4,
PASS_CATEGORY_CRYPTOMATTE = 1u << 5,
};
ENUM_OPERATORS(PassCategory)
struct [[host_shared]] FilmSample {
int2 texel;
float weight;
/** Used for accumulation. */
float weight_sum_inv;
};
struct [[host_shared]] FilmData {
/** Size of the film in pixels. */
int2 extent;
/** Offset to convert from Display space to Film space, in pixels. */
int2 offset;
/** Size of the render buffers including overscan when rendering the main views, in pixels. */
int2 render_extent;
/**
* Sub-pixel offset applied to the window matrix.
* NOTE: In render target pixel unit.
* NOTE: Positive values makes the view translate in the negative axes direction.
* NOTE: The origin is the center of the lower left film pixel of the area covered by a render
* pixel if using scaled resolution rendering.
*/
float2 subpixel_offset;
/** Scaling factor to convert texel to uvs. */
float2 extent_inv;
/**
* Number of border pixels on all sides inside the render_extent that do not contribute to the
* final image.
*/
int overscan;
/** Is true if history is valid and can be sampled. Bypass history to resets accumulation. */
bool32_t use_history;
/** Controlled by user in lookdev mode or by render settings. */
float background_opacity;
/** Output counts per type. */
int color_len;
int value_len;
/** Index in color_accum_img or value_accum_img of each pass. -1 if pass is not enabled. */
int mist_id;
int normal_id;
int position_id;
int vector_id;
int diffuse_light_id;
int diffuse_color_id;
int specular_light_id;
int specular_color_id;
int volume_light_id;
int emission_id;
int environment_id;
int shadow_id;
int ambient_occlusion_id;
int transparent_id;
/** Not indexed but still not -1 if enabled. */
int depth_id;
int combined_id;
/** Id of the render-pass to be displayed. -1 for combined. */
int display_id;
/** Storage type of the render-pass to be displayed. */
enum ePassStorageType display_storage_type;
/** Start of AOVs and number of aov. */
int aov_color_id;
int aov_color_len;
int aov_value_id;
int aov_value_len;
/** Start of cryptomatte per layer (-1 if pass is not enabled). */
int cryptomatte_object_id;
int cryptomatte_asset_id;
int cryptomatte_material_id;
/** Max number of samples stored per layer (is even number). */
int cryptomatte_samples_len;
/** Settings to render mist pass */
float mist_scale;
float mist_bias;
float mist_exponent;
/** Scene exposure used for better noise reduction. */
float exposure_scale;
/** Scaling factor for scaled resolution rendering. */
int scaling_factor;
/** Software LOD bias to apply to when sampling texture inside the node-tree evaluation. */
float texture_lod_bias;
/** Film pixel filter radius. */
float filter_radius;
/** Precomputed samples. First in the table is the closest one. The rest is unordered. */
int samples_len;
/** Sum of the weights of all samples in the sample table. */
float samples_weight_total;
int _pad0, _pad1;
struct FilmSample samples[FILM_PRECOMP_SAMPLE_MAX];
};
static inline float film_filter_weight(float filter_radius, float sample_distance_sqr)
{
#if 1 /* Faster */
/* Gaussian fitted to Blackman-Harris. */
float r = sample_distance_sqr / (filter_radius * filter_radius);
const float sigma = 0.284f;
const float fac = -0.5f / (sigma * sigma);
float weight = expf(fac * r);
#else
/* Blackman-Harris filter. */
float r = M_TAU * saturate(0.5 + sqrtf(sample_distance_sqr) / (2.0 * filter_radius));
float weight = 0.35875 - 0.48829 * cosf(r) + 0.14128 * cosf(2.0 * r) - 0.01168 * cosf(3.0 * r);
#endif
return weight;
}
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,254 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Gbuffer layout used for deferred shading pipeline.
*/
#pragma once
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.hh"
#include "GPU_capabilities.hh"
#include "GPU_platform.hh"
#include "eevee_defines.hh"
namespace blender::eevee {
using namespace draw;
class Instance;
/**
* Full-screen textures containing geometric and surface data.
* Used by deferred shading passes. Only one g-buffer is allocated per view
* and is reused for each deferred layer. This is why there can only be temporary
* texture inside it.
*
* Everything is stored inside two array texture, one for each format. This is to fit the
* limitation of the number of images we can bind on a single shader.
*
* The content of the g-buffer is polymorphic. A 8bit header specify the layout of the data.
* The first layer is always written to while others are written only if needed using imageStore
* operations reducing the bandwidth needed.
* Except for some special configurations, the g-buffer holds up to 3 closures.
*
* For each output closure, we also output the color to apply after the lighting computation.
* The color is stored with a 2 exponent that allows input color with component higher than 1.
* Color degradation is expected to happen in this case.
*
* Here are special configurations:
*
* - Opaque Dielectric:
* - 1 Diffuse lobe and 1 Reflection lobe without anisotropy.
* - Share a single normal.
* - Reflection is not colored.
* - Layout:
* - Color 1 : Diffuse color
* - Closure 1 R : Normal packed X
* - Closure 1 G : Normal packed Y
* - Closure 1 B : Roughness (isotropic)
* - Closure 1 A : Reflection intensity
*
* - Simple Car-paint: (TODO)
* - 2 Reflection lobe without anisotropy.
* - Share a single normal.
* - Coat layer is not colored.
* - Layout:
* - Color 1 : Bottom layer color
* - Closure 1 R : Normal packed X
* - Closure 1 G : Normal packed Y
* - Closure 1 B : Roughness (isotropic)
* - Closure 1 A : Coat layer intensity
*
* - Simple Glass: (TODO)
* - 1 Refraction lobe and 1 Reflection lobe without anisotropy.
* - Share a single normal.
* - Reflection intensity is derived from IOR.
* - Layout:
* - Color 1 : Refraction color
* - Closure 1 R : Normal packed X
* - Closure 1 G : Normal packed Y
* - Closure 1 B : Roughness (isotropic)
* - Closure 1 A : IOR
*
* Here are Closure configurations:
*
* - Reflection (Isotropic):
* - Layout:
* - Color : Reflection color
* - Closure 1 R : Normal packed X
* - Closure 1 G : Normal packed Y
* - Closure 1 B : Roughness
* - Closure 1 A : Unused
*
* - Reflection (Anisotropic): (TODO)
* - Layout:
* - Color : Reflection color
* - Closure 1 R : Normal packed X
* - Closure 1 G : Normal packed Y
* - Closure 1 B : Tangent packed X
* - Closure 1 A : Tangent packed Y
* - Closure 2 R : Roughness X
* - Closure 2 G : Roughness Y
* - Closure 2 B : Unused
* - Closure 2 A : Unused
*
* - Refraction (Isotropic):
* - Layout:
* - Color : Refraction color
* - Closure 1 R : Normal packed X
* - Closure 1 G : Normal packed Y
* - Closure 1 B : Roughness
* - Closure 1 A : IOR
*
* - Diffuse:
* - Layout:
* - Color : Diffuse color
* - Closure 1 R : Normal packed X
* - Closure 1 G : Normal packed Y
* - Closure 1 B : Unused
* - Closure 1 A : Unused (Could be used for diffuse roughness)
*
* - Sub-Surface Scattering:
* - Layout:
* - Color : Diffuse color
* - Closure 1 R : Normal packed X
* - Closure 1 G : Normal packed Y
* - Closure 1 B : Thickness
* - Closure 1 A : Unused (Could be used for diffuse roughness)
* - Closure 2 R : Scattering radius R
* - Closure 2 G : Scattering radius G
* - Closure 2 B : Scattering radius B
* - Closure 2 A : Object ID
*
*/
struct GBuffer {
public:
TextureFromPool header_tx = {"GBufferHeader"};
TextureFromPool closure_tx = {"GBufferClosure"};
TextureFromPool normal_tx = {"GBufferNormal"};
/* Expected number of layer written through the framebuffer. */
const uint header_fb_layer_count = GBUF_HEADER_FB_LAYER_COUNT;
const uint closure_fb_layer_count = GBUF_CLOSURE_FB_LAYER_COUNT;
const uint normal_fb_layer_count = GBUF_NORMAL_FB_LAYER_COUNT;
private:
/* References to optional GBuffer layers that are not always required or written to.
* These will point to either the dummy textures bellow or to a layer range view of the above
* textures. In the later case, these layers are written with imageStore instead of being part
* of the #Framebuffer. */
gpu::Texture *closure_opt_layers_ = nullptr;
gpu::Texture *normal_opt_layers_ = nullptr;
gpu::Texture *header_opt_layers_ = nullptr;
/* Textures used to fulfill the GBuffer optional layers binding when textures do not have enough
* layers for the optional layers image views. The shader are then expected to never write to
* them. */
TextureFromPool dummy_header_tx_ = {"GBufferDummyHeader"};
TextureFromPool dummy_closure_tx_ = {"GBufferDummyClosure"};
TextureFromPool dummy_normal_tx_ = {"GBufferDummyNormal"};
public:
void acquire(int2 extent, int header_count, int data_count, int normal_count)
{
/* Always allocate enough layers so that the frame-buffer attachments are always valid. */
header_count = max_ii(header_fb_layer_count, header_count);
data_count = max_ii(closure_fb_layer_count, data_count);
normal_count = max_ii(normal_fb_layer_count, normal_count);
eGPUTextureUsage dummy_use = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
dummy_header_tx_.acquire_2d_array({1, 1}, 1, gpu::TextureFormat::UINT_32, dummy_use);
dummy_closure_tx_.acquire_2d_array({1, 1}, 1, gpu::TextureFormat::UNORM_10_10_10_2, dummy_use);
dummy_normal_tx_.acquire_2d_array({1, 1}, 1, gpu::TextureFormat::UNORM_16_16, dummy_use);
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE |
GPU_TEXTURE_USAGE_ATTACHMENT;
header_tx.acquire_2d_array(extent, header_count, gpu::TextureFormat::UINT_32, usage);
closure_tx.acquire_2d_array(extent, data_count, gpu::TextureFormat::UNORM_10_10_10_2, usage);
normal_tx.acquire_2d_array(extent, normal_count, gpu::TextureFormat::UNORM_16_16, usage);
/* Ensure layer view for frame-buffer attachment. */
header_tx.ensure_layer_views();
closure_tx.ensure_layer_views();
normal_tx.ensure_layer_views();
/* Ensure layer view for image store. */
auto range = [](int layer_count, int fb_layer_count, Texture &tx, Texture &dummy) {
return (layer_count > fb_layer_count) ?
tx.layer_range_view(fb_layer_count, layer_count - fb_layer_count) :
dummy;
};
header_opt_layers_ = range(header_count, header_fb_layer_count, header_tx, dummy_header_tx_);
closure_opt_layers_ = range(data_count, closure_fb_layer_count, closure_tx, dummy_closure_tx_);
normal_opt_layers_ = range(normal_count, normal_fb_layer_count, normal_tx, dummy_normal_tx_);
}
/* Bind the GBuffer frame-buffer correctly using the correct workarounds. */
void bind(Framebuffer &gbuffer_fb, bool clear_combined = false)
{
/* Workaround a Metal bug that is only showing up on ATI/Intel GPUs. */
if (GPU_type_matches(
GPU_DEVICE_ATI | GPU_DEVICE_INTEL | GPU_DEVICE_INTEL_UHD, GPU_OS_MAC, GPU_DRIVER_ANY))
{
header_tx.clear(uint4(0));
GPU_framebuffer_bind(gbuffer_fb);
return;
}
if (!GPU_stencil_export_support()) {
/* Clearing custom load-store frame-buffers is invalid,
* clear the stencil as a regular frame-buffer first. */
GPU_framebuffer_bind(gbuffer_fb);
GPU_framebuffer_clear_stencil(gbuffer_fb, 0x0u);
}
GPULoadOp combined_load_action = clear_combined ? GPU_LOADACTION_CLEAR : GPU_LOADACTION_LOAD;
GPU_framebuffer_bind_ex(
gbuffer_fb,
{
{GPU_LOADACTION_LOAD, GPU_STOREACTION_STORE}, /* Depth. */
{combined_load_action, GPU_STOREACTION_STORE, {0}}, /* Combined. */
{GPU_LOADACTION_CLEAR, GPU_STOREACTION_STORE, {0}}, /* GBuf Header. */
{GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_STORE}, /* GBuf Normal. */
{GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_STORE}, /* GBuf Closure. */
{GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_STORE}, /* GBuf Closure 2. */
});
}
void release()
{
header_tx.release();
closure_tx.release();
normal_tx.release();
dummy_header_tx_.release();
dummy_closure_tx_.release();
dummy_normal_tx_.release();
header_opt_layers_ = nullptr;
closure_opt_layers_ = nullptr;
normal_opt_layers_ = nullptr;
}
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_texture("gbuf_header_tx", &header_tx);
pass.bind_texture("gbuf_closure_tx", &closure_tx);
pass.bind_texture("gbuf_normal_tx", &normal_tx);
}
template<typename PassType> void bind_optional_layers(PassType &pass)
{
pass.bind_image(GBUF_NORMAL_SLOT, &normal_opt_layers_);
pass.bind_image(GBUF_CLOSURE_SLOT, &closure_opt_layers_);
pass.bind_image(GBUF_HEADER_SLOT, &header_opt_layers_);
}
};
} // namespace blender::eevee

View File

@@ -0,0 +1,129 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "eevee_instance.hh"
#include "eevee_hizbuffer.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Hierarchical-Z buffer
*
* \{ */
void HiZBuffer::sync()
{
int2 render_extent = inst_.film.render_extent_get();
int2 probe_extent = int2(inst_.sphere_probes.probe_render_extent());
/* Padding to avoid complexity during down-sampling and screen tracing. */
int2 hiz_extent = math::ceil_to_multiple(math::max(render_extent, probe_extent),
int2(1u << (HIZ_MIP_COUNT - 1)));
/* Each thread update 4 LOD0 pixels. So the actual extent of each group is double of it size. */
int2 dispatch_size = math::divide_ceil(hiz_extent, int2(HIZ_GROUP_SIZE) * 2);
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
for ([[maybe_unused]] const int i : IndexRange(hiz_tx_.size())) {
hiz_tx_.current().ensure_2d(
gpu::TextureFormat::SFLOAT_32, hiz_extent, usage, nullptr, HIZ_MIP_COUNT);
hiz_tx_.current().ensure_mip_views();
GPU_texture_mipmap_mode(hiz_tx_.current(), true, false);
hiz_tx_.swap();
}
data_.uv_scale = float2(render_extent) / float2(hiz_extent);
/* TODO(@fclem): There might be occasions where we might not want to
* copy mip 0 for performance reasons if there is no need for it. */
bool update_mip_0 = true;
{
PassSimple &pass = hiz_update_ps_;
gpu::Shader *sh = inst_.shaders.static_shader_get(HIZ_UPDATE);
pass.init();
pass.specialize_constant(sh, "update_mip_0", update_mip_0);
pass.shader_set(sh);
pass.bind_ssbo("finished_tile_counter", atomic_tile_counter_);
/* TODO(fclem): Should be a parameter to avoid confusion. */
pass.bind_texture("depth_tx", &src_tx_);
pass.bind_image("out_mip_0", &hiz_mip_ref_[0]);
pass.bind_image("out_mip_1", &hiz_mip_ref_[1]);
pass.bind_image("out_mip_2", &hiz_mip_ref_[2]);
pass.bind_image("out_mip_3", &hiz_mip_ref_[3]);
pass.bind_image("out_mip_4", &hiz_mip_ref_[4]);
pass.bind_image("out_mip_5", &hiz_mip_ref_[5]);
pass.bind_image("out_mip_6", &hiz_mip_ref_[6]);
pass.dispatch(int3(dispatch_size, 1));
pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
{
PassSimple &pass = hiz_update_layer_ps_;
gpu::Shader *sh = inst_.shaders.static_shader_get(HIZ_UPDATE_LAYER);
pass.init();
pass.specialize_constant(sh, "update_mip_0", update_mip_0);
pass.shader_set(sh);
pass.bind_ssbo("finished_tile_counter", atomic_tile_counter_);
/* TODO(fclem): Should be a parameter to avoid confusion. */
pass.bind_texture("depth_layered_tx", &src_tx_);
pass.bind_image("out_mip_0", &hiz_mip_ref_[0]);
pass.bind_image("out_mip_1", &hiz_mip_ref_[1]);
pass.bind_image("out_mip_2", &hiz_mip_ref_[2]);
pass.bind_image("out_mip_3", &hiz_mip_ref_[3]);
pass.bind_image("out_mip_4", &hiz_mip_ref_[4]);
pass.bind_image("out_mip_5", &hiz_mip_ref_[5]);
pass.bind_image("out_mip_6", &hiz_mip_ref_[6]);
pass.push_constant("layer_id", &layer_id_);
pass.dispatch(int3(dispatch_size, 1));
pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
if (inst_.debug_mode == eDebugMode::DEBUG_HIZ_VALIDATION) {
debug_draw_ps_.init();
debug_draw_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM);
debug_draw_ps_.shader_set(inst_.shaders.static_shader_get(HIZ_DEBUG));
debug_draw_ps_.bind_resources(this->front);
debug_draw_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
}
void HiZBuffer::update()
{
if (!is_dirty_) {
return;
}
/* After this, the front buffer become valid. */
front.ref_tx_ = hiz_tx_.current();
src_tx_ = *src_tx_ptr_;
for (const int i : IndexRange(HIZ_MIP_COUNT)) {
hiz_mip_ref_[i] = hiz_tx_.current().mip_view(i);
}
if (layer_id_ == -1) {
inst_.manager->submit(hiz_update_ps_);
}
else {
inst_.manager->submit(hiz_update_layer_ps_);
}
is_dirty_ = false;
}
void HiZBuffer::debug_draw(View &view, gpu::FrameBuffer *view_fb)
{
if (inst_.debug_mode == eDebugMode::DEBUG_HIZ_VALIDATION) {
inst_.info_append(
"Debug Mode: HiZ Validation\n"
" - Red: pixel in front of HiZ tile value.\n"
" - Blue: No error.");
inst_.hiz_buffer.update();
GPU_framebuffer_bind(view_fb);
inst_.manager->submit(debug_draw_ps_, view);
}
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,136 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* The Hierarchical-Z buffer is texture containing a copy of the depth buffer with mipmaps.
* Each mip contains the maximum depth of each 4 pixels on the upper level.
* The size of the texture is padded to avoid messing with the mipmap pixels alignments.
*/
#pragma once
#include "DRW_render.hh"
#include "DRW_gpu_wrapper.hh"
#include "draw_pass.hh"
#include "eevee_defines.hh"
#include "eevee_hizbuffer_shared.hh"
namespace blender::eevee {
using namespace draw;
class Instance;
/* -------------------------------------------------------------------- */
/** \name Hierarchical-Z buffer
* \{ */
class HiZBuffer {
private:
Instance &inst_;
/** Contains depth pyramid of the current pass and the previous pass. */
SwapChain<Texture, 2> hiz_tx_;
/** References to the mip views of the current (front) HiZ texture. */
std::array<gpu::Texture *, HIZ_MIP_COUNT> hiz_mip_ref_;
/** Dummy texture cleared to max depth to be used before the first hiz update. */
draw::Texture dummy_empty_hiz_tx_ = {"dummy_empty_hiz_tx_"};
/**
* Atomic counter counting the number of tile that have finished down-sampling.
* The last one will process the last few mip level.
*/
draw::StorageBuffer<uint4, true> atomic_tile_counter_ = {"atomic_tile_counter"};
/** Single pass recursive down-sample. */
PassSimple hiz_update_ps_ = {"HizUpdate"};
/** Single pass recursive down-sample for layered depth buffer. Only downsample 1 layer. */
PassSimple hiz_update_layer_ps_ = {"HizUpdate.Layer"};
int layer_id_ = -1;
/** Debug pass. */
PassSimple debug_draw_ps_ = {"HizUpdate.Debug"};
/** Dirty flag to check if the update is necessary. */
bool is_dirty_ = true;
/** Reference to the depth texture to downsample. */
gpu::Texture *src_tx_ = nullptr;
gpu::Texture **src_tx_ptr_ = nullptr;
HiZData &data_;
public:
HiZBuffer(Instance &inst, HiZData &data) : inst_(inst), data_(data)
{
atomic_tile_counter_.clear_to_zero();
float value = 1.0f;
dummy_empty_hiz_tx_.ensure_2d(
gpu::TextureFormat::SFLOAT_32, int2(1), GPU_TEXTURE_USAGE_SHADER_READ, &value);
};
void sync();
/**
* Set source texture for the hiz down-sampling.
* Need to be called once at the start of a pipeline or view.
* Tag the buffer as dirty.
*/
void set_source(gpu::Texture **texture, int layer = -1)
{
src_tx_ptr_ = texture;
layer_id_ = layer;
swap_layer();
/* Both layer becomes undefined. Replace by dummy texture. */
back.ref_tx_ = dummy_empty_hiz_tx_;
front.ref_tx_ = dummy_empty_hiz_tx_;
}
/**
* Swap front and back layer.
* Internally set front layer to be dirty.
* IMPORTANT: Before the second swap (and the second update)
* the content of the back hi-z buffer is undefined.
*/
void swap_layer()
{
hiz_tx_.swap();
back.ref_tx_ = front.ref_tx_;
front.ref_tx_ = hiz_tx_.current();
set_dirty();
}
/**
* Tag the front buffer for update if needed.
*/
void set_dirty()
{
is_dirty_ = true;
}
/**
* Update the content of the HiZ buffer with the source depth set by `set_source()`.
* Noop if the buffer has not been tagged as dirty.
* Should be called before each passes that needs to read the hiz buffer.
*/
void update();
void debug_draw(View &view, gpu::FrameBuffer *view_fb);
/* Back is Previous layer depth (ex: For refraction). Front for current layer depth. */
struct {
/** References to the textures in the swap-chain. */
gpu::Texture *ref_tx_ = nullptr;
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_texture(HIZ_TEX_SLOT, &ref_tx_);
}
} front, back;
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
struct [[host_shared]] HiZData {
/** Scale factor to remove HiZBuffer padding. */
float2 uv_scale;
float2 _pad0;
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,401 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* An renderer instance that contains all data to render a full frame.
*/
#pragma once
#include <fmt/format.h>
#include "CLG_log.h"
#include "BLI_string.h"
#include "BLT_translation.hh"
#include "BKE_object.hh"
#include "DEG_depsgraph.hh"
#include "DNA_lightprobe_types.h"
#include "DRW_render.hh"
#include "eevee_ambient_occlusion.hh"
#include "eevee_camera.hh"
#include "eevee_cryptomatte.hh"
#include "eevee_debug_shared.hh"
#include "eevee_depth_of_field.hh"
#include "eevee_film.hh"
#include "eevee_gbuffer.hh"
#include "eevee_hizbuffer.hh"
#include "eevee_light.hh"
#include "eevee_lightprobe.hh"
#include "eevee_lightprobe_planar.hh"
#include "eevee_lightprobe_sphere.hh"
#include "eevee_lightprobe_volume.hh"
#include "eevee_lookdev.hh"
#include "eevee_material.hh"
#include "eevee_motion_blur.hh"
#include "eevee_pipeline.hh"
#include "eevee_raytrace.hh"
#include "eevee_renderbuffers.hh"
#include "eevee_sampling.hh"
#include "eevee_shader.hh"
#include "eevee_shadow.hh"
#include "eevee_subsurface.hh"
#include "eevee_sync.hh"
#include "eevee_view.hh"
#include "eevee_volume.hh"
#include "eevee_world.hh"
namespace blender::eevee {
using UniformDataBuf = draw::UniformBuffer<UniformData>;
using PipelineInfoBuf = draw::UniformBuffer<PipelineInfoData>;
using RaytraceDataBuf = draw::UniformBuffer<RayTraceData>;
/* Combines data from several modules to avoid wasting binding slots. */
struct UniformDataModule {
UniformDataBuf data{"UniformDataBuf"};
PipelineInfoBuf pipeline{"PipelineInfoBuf"};
RaytraceDataBuf raytrace{"RaytraceDataBuf"};
void push_update()
{
data.push_update();
pipeline.push_update();
raytrace.push_update();
}
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_ubo(UNIFORM_BUF_SLOT, &data);
pass.bind_ubo(PIPELINE_BUF_SLOT, &pipeline);
pass.bind_ubo(RAYTRACE_BUF_SLOT, &raytrace);
}
};
/**
* \class Instance
* \brief A running instance of the engine.
*/
class Instance : public DrawEngine {
friend VelocityModule;
friend MotionBlurModule;
/** Debug scopes. */
static void *debug_scope_render_frame;
static void *debug_scope_render_sample;
static void *debug_scope_irradiance_setup;
static void *debug_scope_irradiance_sample;
uint64_t depsgraph_last_update_ = 0;
bool overlays_enabled_ = false;
bool skip_render_ = false;
/** Info string displayed at the top of the render / viewport, or the console when baking. */
std::string info_ = "";
public:
ShaderModule &shaders;
SyncModule sync;
UniformDataModule uniform_data;
MaterialModule materials;
SubsurfaceModule subsurface;
PipelineModule pipelines;
ShadowModule shadows;
LightModule lights;
AmbientOcclusion ambient_occlusion;
RayTraceModule raytracing;
VelocityModule velocity;
MotionBlurModule motion_blur;
DepthOfField depth_of_field;
Cryptomatte cryptomatte;
GBuffer gbuffer;
HiZBuffer hiz_buffer;
Sampling sampling;
Camera camera;
Film film;
RenderBuffers render_buffers;
MainView main_view;
CaptureView capture_view;
World world;
LookdevView lookdev_view;
LookdevModule lookdev;
SphereProbeModule sphere_probes;
PlanarProbeModule planar_probes;
VolumeProbeModule volume_probes;
LightProbeModule light_probes;
VolumeModule volume;
static CLG_LogRef log;
/** Input data. */
Depsgraph *depsgraph;
Manager *manager;
/** Evaluated IDs. */
Scene *scene;
ViewLayer *view_layer;
/** Camera object if rendering through a camera. nullptr otherwise. */
Object *camera_eval_object;
Object *camera_orig_object;
/** Only available when rendering for final render. */
const RenderLayer *render_layer;
RenderEngine *render;
/** Only available when rendering for viewport. */
const View *drw_view = nullptr;
const View3D *v3d;
const RegionView3D *rv3d;
const DRWContext *draw_ctx = nullptr;
/** True if the instance is created for light baking. */
bool is_light_bake = false;
/** True if the instance is created for either viewport image render or final image render. */
bool is_image_render = false;
/** True if the instance is created only for viewport image render. */
bool is_viewport_image_render = false;
/** True if current viewport is drawn during playback. */
bool is_playback = false;
/** True if current viewport is drawn during navigation operator. */
bool is_navigating = false;
/** True if current viewport is drawn during painting operator. */
bool is_painting = false;
/** True if current viewport is drawn during transforming operator. */
bool is_transforming = false;
/** True if viewport compositor is enabled when drawing with this instance. */
bool is_viewport_compositor_enabled = false;
/** True if overlays need to be displayed (only for viewport). */
bool draw_overlays = false;
ShaderGroups loaded_shaders = ShaderGroups(0);
ShaderGroups needed_shaders = ShaderGroups(0);
/** View-layer overrides. */
bool use_surfaces = true;
bool use_curves = true;
bool use_volumes = true;
GPUSamplerFiltering anisotropic_filtering = GPU_SAMPLER_FILTERING_DEFAULT;
/** Debug mode from debug value. */
eDebugMode debug_mode = eDebugMode::DEBUG_NONE;
public:
Instance()
: shaders(*ShaderModule::module_get()),
sync(*this),
materials(*this),
subsurface(*this),
pipelines(*this, uniform_data.pipeline),
shadows(*this, uniform_data.data.shadow),
lights(*this),
ambient_occlusion(*this, uniform_data.data.ao),
raytracing(*this, uniform_data.raytrace),
velocity(*this),
motion_blur(*this),
depth_of_field(*this),
cryptomatte(*this),
hiz_buffer(*this, uniform_data.data.hiz),
sampling(*this, uniform_data.data.clamp),
camera(*this, uniform_data.data.camera),
film(*this, uniform_data.data.film),
render_buffers(*this, uniform_data.data.render_pass),
main_view(*this),
capture_view(*this),
world(*this),
lookdev_view(*this),
lookdev(*this),
sphere_probes(*this),
planar_probes(*this),
volume_probes(*this),
light_probes(*this),
volume(*this, uniform_data.data.volumes) {};
~Instance() override {};
StringRefNull name_get() final
{
return "EEVEE";
}
/* Render & Viewport. */
/* TODO(fclem): Split for clarity. */
void init(const int2 &output_res,
const rcti *output_rect,
const rcti *visible_rect,
RenderEngine *render,
Depsgraph *depsgraph,
Object *camera_object = nullptr,
const RenderLayer *render_layer = nullptr,
View *drw_view_ = nullptr,
const View3D *v3d = nullptr,
const RegionView3D *rv3d = nullptr);
void init() final;
void begin_sync() final;
void object_sync(ObjectRef &ob_ref, Manager &manager) final;
void end_sync() final;
bool is_loaded(ShaderGroups groups) const
{
return (loaded_shaders & groups) == groups;
}
/**
* Return true when probe pipeline is used during this sample.
*/
bool do_lightprobe_sphere_sync() const;
bool do_planar_probe_sync() const;
/**
* Return true when probe passes should be loaded.
* It can be true even if do_<type>_probe_sync() is false due to shaders still being compiled.
*/
bool needs_lightprobe_sphere_passes() const;
bool needs_planar_probe_passes() const;
/* Render. */
void render_sync();
void render_frame(RenderEngine *engine, RenderLayer *render_layer, const char *view_name);
void store_metadata(RenderResult *render_result);
/* Viewport. */
void draw_viewport();
void draw_viewport_image_render();
void draw(Manager &manager) final;
/* Light bake. */
void init_light_bake(Depsgraph *depsgraph, draw::Manager *manager);
void light_bake_irradiance(
Object &probe,
FunctionRef<void()> context_enable,
FunctionRef<void()> context_disable,
FunctionRef<bool()> stop,
FunctionRef<void(LightProbeGridCacheFrame *, float progress)> result_update);
static void update_passes(RenderEngine *engine, Scene *scene, ViewLayer *view_layer);
/* Append a new line to the info string. */
template<typename... Args> void info_append(const char *msg, Args &&...args)
{
std::string fmt_msg = fmt::format(fmt::runtime(msg), args...) + "\n";
/* Don't print the same error twice. */
if (info_ != fmt_msg && !BLI_str_endswith(info_.c_str(), fmt_msg.c_str())) {
info_ += fmt_msg;
}
}
/* The same as `info_append`, but `msg` will be translated.
* NOTE: When calling this function, `msg` should be a string literal. */
template<typename... Args> void info_append_i18n(const char *msg, Args &&...args)
{
std::string fmt_msg = fmt::format(fmt::runtime(RPT_(msg)), args...) + "\n";
/* Don't print the same error twice. */
if (info_ != fmt_msg && !BLI_str_endswith(info_.c_str(), fmt_msg.c_str())) {
info_ += fmt_msg;
}
}
const char *info_get()
{
return info_.c_str();
}
bool is_viewport() const
{
return render == nullptr && !is_baking();
}
bool is_baking() const
{
return is_light_bake;
}
bool overlays_enabled() const
{
return overlays_enabled_;
}
/** True if the grease pencil engine might be running. */
bool gpencil_engine_enabled() const
{
return DEG_id_type_any_exists(depsgraph, ID_GP);
}
bool use_scene_lights() const
{
return (!v3d) ||
((v3d->shading.type == OB_MATERIAL) &&
(v3d->shading.flag & V3D_SHADING_SCENE_LIGHTS)) ||
((v3d->shading.type == OB_RENDER) &&
(v3d->shading.flag & V3D_SHADING_SCENE_LIGHTS_RENDER));
}
/* Light the scene using the selected HDRI in the viewport shading pop-over. */
bool use_studio_light() const
{
return (v3d) && (((v3d->shading.type == OB_MATERIAL) &&
((v3d->shading.flag & V3D_SHADING_SCENE_WORLD) == 0)) ||
((v3d->shading.type == OB_RENDER) &&
((v3d->shading.flag & V3D_SHADING_SCENE_WORLD_RENDER) == 0)));
}
bool use_lookdev_overlay() const
{
return (v3d) &&
((v3d->shading.type == OB_MATERIAL) && (v3d->overlay.flag & V3D_OVERLAY_LOOK_DEV));
}
uint get_recalc_flags(const ObjectRef &ob_ref)
{
return ob_ref.recalc_flags(depsgraph_last_update_);
}
uint get_recalc_flags(const blender::World &world)
{
return world.last_update > depsgraph_last_update_ ? uint(ID_RECALC_SHADING) : 0;
}
private:
/**
* Conceptually renders one sample per pixel.
* Everything based on random sampling should be done here (i.e: DRWViews jitter)
*/
void render_sample();
void render_read_result(RenderLayer *render_layer, const char *view_name);
void update_eval_members();
void set_time(float time);
struct DebugScope {
void *scope;
DebugScope(void *&scope_p, const char *name)
{
if (scope_p == nullptr) {
scope_p = GPU_debug_capture_scope_create(name);
}
scope = scope_p;
GPU_debug_capture_scope_begin(scope);
}
~DebugScope()
{
GPU_debug_capture_scope_end(scope);
}
};
};
} // namespace blender::eevee

View File

@@ -0,0 +1,685 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* The light module manages light data buffers and light culling system.
*/
#include "draw_debug.hh"
#include "eevee_instance.hh"
#include "eevee_light.hh"
#include "DNA_light_types.h"
#include "DNA_sdna_type_ids.hh"
#include "BKE_light.h"
namespace blender::eevee {
/* Convert by putting the least significant bits in the first component. */
static uint2 uint64_to_uint2(uint64_t data)
{
return {uint(data), uint(data >> uint64_t(32))};
}
/* -------------------------------------------------------------------- */
/** \name LightData
* \{ */
static eLightType to_light_type(short blender_light_type,
short blender_area_type,
bool use_soft_falloff)
{
switch (blender_light_type) {
default:
case LA_LOCAL:
return use_soft_falloff ? LIGHT_OMNI_DISK : LIGHT_OMNI_SPHERE;
case LA_SUN:
return LIGHT_SUN;
case LA_SPOT:
return use_soft_falloff ? LIGHT_SPOT_DISK : LIGHT_SPOT_SPHERE;
case LA_AREA:
return ELEM(blender_area_type, LA_AREA_DISK, LA_AREA_ELLIPSE) ? LIGHT_ELLIPSE : LIGHT_RECT;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Light Object
* \{ */
void Light::sync(ShadowModule &shadows,
float4x4 object_to_world,
char visibility_flag,
const blender::Light *la,
const LightLinking *light_linking /* = nullptr */,
float threshold)
{
using namespace blender::math;
eLightType new_type = to_light_type(la->type, la->area_shape, la->mode & LA_USE_SOFT_FALLOFF);
if (assign_if_different(this->type, new_type)) {
shadow_discard_safe(shadows);
}
this->color = BKE_light_power(*la) * BKE_light_color(*la);
if (la->mode & LA_UNNORMALIZED) {
this->color *= BKE_light_area(*la, object_to_world);
}
float3 scale;
object_to_world.view<3, 3>() = normalize_and_get_size(object_to_world.view<3, 3>(), scale);
/* Make sure we have consistent handedness (in case of negatively scaled Z axis). */
float3 back = cross(float3(object_to_world.x_axis()), float3(object_to_world.y_axis()));
if (dot(back, float3(object_to_world.z_axis())) < 0.0f) {
negate_v3(object_to_world.y_axis());
}
this->object_to_world = object_to_world;
shape_parameters_set(
la, scale, object_to_world.z_axis(), threshold, shadows.get_data().use_jitter);
const bool diffuse_visibility = (visibility_flag & OB_HIDE_DIFFUSE) == 0;
const bool glossy_visibility = (visibility_flag & OB_HIDE_GLOSSY) == 0;
const bool transmission_visibility = (visibility_flag & OB_HIDE_TRANSMISSION) == 0;
const bool volume_visibility = (visibility_flag & OB_HIDE_VOLUME_SCATTER) == 0;
float shape_power = shape_radiance_get();
float point_power = point_radiance_get();
this->power[LIGHT_DIFFUSE] = la->diff_fac * shape_power * diffuse_visibility;
this->power[LIGHT_SPECULAR] = la->spec_fac * shape_power * glossy_visibility;
this->power[LIGHT_TRANSMISSION] = la->transmission_fac * shape_power * transmission_visibility;
this->power[LIGHT_VOLUME] = la->volume_fac * point_power * volume_visibility;
this->lod_bias = shadows.global_lod_bias();
this->lod_min = shadow_lod_min_get(la);
this->filter_radius = la->shadow_filter_radius;
this->shadow_jitter = (la->mode & LA_SHADOW_JITTER) != 0;
this->visible_camera = (visibility_flag & OB_HIDE_CAMERA) == 0;
if (la->mode & LA_SHADOW) {
shadow_ensure(shadows);
}
else {
shadow_discard_safe(shadows);
}
if (light_linking) {
this->light_set_membership = uint64_to_uint2(light_linking->runtime.light_set_membership);
this->shadow_set_membership = uint64_to_uint2(light_linking->runtime.shadow_set_membership);
}
else {
/* Set all bits if light linking is not used. */
this->light_set_membership = uint64_to_uint2(~uint64_t(0));
this->shadow_set_membership = uint64_to_uint2(~uint64_t(0));
}
this->initialized = true;
}
float Light::shadow_lod_min_get(const blender::Light *la)
{
/* Property is in mm. Convert to unit. */
float max_res_unit = la->shadow_maximum_resolution;
if (is_sun_light(this->type)) {
return log2f(max_res_unit * SHADOW_MAP_MAX_RES) - 1.0f;
}
/* Store absolute mode as negative. */
return (la->mode & LA_SHAD_RES_ABSOLUTE) ? -max_res_unit : max_res_unit;
}
void Light::shadow_discard_safe(ShadowModule &shadows)
{
if (this->directional != nullptr) {
shadows.directional_pool.destruct(*directional);
this->directional = nullptr;
}
if (this->punctual != nullptr) {
shadows.punctual_pool.destruct(*punctual);
this->punctual = nullptr;
}
}
void Light::shadow_ensure(ShadowModule &shadows)
{
if (is_sun_light(this->type) && this->directional == nullptr) {
this->directional = &shadows.directional_pool.construct(shadows);
}
else if (this->punctual == nullptr) {
this->punctual = &shadows.punctual_pool.construct(shadows);
}
}
float Light::attenuation_radius_get(const blender::Light *la,
float light_threshold,
float light_power)
{
if (la->mode & LA_CUSTOM_ATTENUATION) {
return la->att_dist;
}
/* Compute the distance (using the inverse square law)
* at which the light power reaches the light_threshold. */
/* TODO take area light scale into account. */
return sqrtf(light_power / light_threshold);
}
void Light::shape_parameters_set(const blender::Light *la,
const float3 &scale,
const float3 &z_axis,
const float threshold,
const bool use_jitter)
{
using namespace blender::math;
/* Compute influence radius first. Can be amended by shape later. */
if (is_local_light(this->type)) {
LightLocalData &l_local = this->local();
const float max_power = reduce_max(BKE_light_color(*la)) *
fabsf(BKE_light_power(*la) / 100.0f);
const float surface_max_power = max(la->diff_fac, la->spec_fac) * max_power;
const float volume_max_power = la->volume_fac * max_power;
float influence_radius_surface = attenuation_radius_get(la, threshold, surface_max_power);
float influence_radius_volume = attenuation_radius_get(la, threshold, volume_max_power);
l_local.local.influence_radius_max = max(influence_radius_surface, influence_radius_volume);
l_local.local.influence_radius_invsqr_surface = safe_rcp(square(influence_radius_surface));
l_local.local.influence_radius_invsqr_volume = safe_rcp(square(influence_radius_volume));
/* TODO(fclem): This is just duplicating a member for local lights. */
this->clip_far = float_as_int(l_local.local.influence_radius_max);
this->clip_near = float_as_int(l_local.local.influence_radius_max / 4000.0f);
}
float trace_scaling_fac = (use_jitter && (la->mode & LA_SHADOW_JITTER)) ?
la->shadow_jitter_overblur / 100.0f :
1.0f;
if (is_sun_light(this->type)) {
LightSunData &l_sun = this->sun();
float sun_half_angle = min_ff(la->sun_angle, DEG2RADF(179.9f)) / 2.0f;
/* Use non-clamped radius for soft shadows. Avoid having a minimum blur. */
l_sun.shadow_angle = sun_half_angle * trace_scaling_fac;
/* Clamp to a minimum to distinguish between point lights and area light shadow. */
l_sun.shadow_angle = (sun_half_angle > 0.0f) ? max_ff(1e-8f, l_sun.shadow_angle) : 0.0f;
/* Precompute this cosine on CPU to avoid differences in shadow tracing between platforms. */
l_sun.shadow_angle_cos = cosf(l_sun.shadow_angle);
/* Clamp to minimum value before float imprecision artifacts appear. */
l_sun.shape_radius = clamp(tanf(sun_half_angle), 0.001f, 20.0f);
/* Stable shading direction. */
l_sun.direction = z_axis;
}
else if (is_area_light(this->type)) {
LightAreaData &l_area = this->area();
const bool is_irregular = ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE);
l_area.size = float2(la->area_size, is_irregular ? la->area_sizey : la->area_size);
/* Scale and clamp to minimum value before float imprecision artifacts appear. */
l_area.size *= scale.xy() / 2.0f;
l_area.shadow_scale = trace_scaling_fac;
l_area.local.shadow_radius = length(l_area.size) * trace_scaling_fac;
/* Set to default position. */
l_area.local.shadow_position = float3(0.0f);
/* Do not render lights that have no area. */
if (l_area.size.x * l_area.size.y < 0.00001f) {
/* Forces light to be culled. */
l_area.local.influence_radius_max = 0.0f;
}
/* Clamp to minimum value before float imprecision artifacts appear. */
l_area.size = max(float2(0.003f), l_area.size);
/* For volume point lighting. */
l_area.local.shape_radius = max(0.001f, length(l_area.size) / 2.0f);
}
else if (is_point_light(this->type)) {
LightSpotData &l_spot = this->spot();
LightLocalData &l_local = this->local();
/* Spot size & blend */
if (is_spot_light(this->type)) {
const float spot_size = cosf(la->spotsize * 0.5f);
const float spot_blend = (1.0f - spot_size) * la->spotblend;
l_spot.spot_size_inv = scale.z / max(scale.xy(), float2(1e-8f));
l_spot.spot_mul = 1.0f / max(1e-8f, spot_blend);
l_spot.spot_bias = -spot_size * l_spot.spot_mul;
l_spot.spot_tan = tanf(min(la->spotsize * 0.5f, float(M_PI_2 - 0.0001f)));
}
else {
/* Point light could access it. Make sure to avoid Undefined Behavior.
* In practice it is only ever used. */
l_spot.spot_size_inv = float2(1.0f);
l_spot.spot_mul = 0.0f;
l_spot.spot_bias = 1.0f;
l_spot.spot_tan = 0.0f;
}
/* Use unclamped radius for soft shadows. Avoid having a minimum blur. */
l_local.local.shadow_radius = max(0.0f, la->radius) * trace_scaling_fac;
/* Clamp to a minimum to distinguish between point lights and area light shadow. */
l_local.local.shadow_radius = (la->radius > 0.0f) ?
max_ff(1e-8f, local().local.shadow_radius) :
0.0f;
/* Set to default position. */
l_local.local.shadow_position = float3(0.0f);
l_local.local.shape_radius = la->radius;
/* Clamp to minimum value before float imprecision artifacts appear. */
l_local.local.shape_radius = max(0.001f, l_local.local.shape_radius);
}
}
float Light::shape_radiance_get()
{
using namespace blender::math;
/* Make illumination power constant. */
switch (this->type) {
case LIGHT_RECT:
case LIGHT_ELLIPSE: {
/* Rectangle area. */
float area = this->area().size.x * this->area().size.y * 4.0f;
/* Scale for the lower area of the ellipse compared to the surrounding rectangle. */
if (this->type == LIGHT_ELLIPSE) {
area *= M_PI / 4.0f;
}
/* Convert radiant flux to radiance. */
return float(M_1_PI) / area;
}
case LIGHT_OMNI_SPHERE:
case LIGHT_OMNI_DISK:
case LIGHT_SPOT_SPHERE:
case LIGHT_SPOT_DISK: {
/* Sphere area. */
float area = float(4.0f * M_PI) * square(this->local().local.shape_radius);
/* Convert radiant flux to radiance. */
return 1.0f / (area * float(M_PI));
}
case LIGHT_SUN_ORTHO:
case LIGHT_SUN: {
float inv_sin_sq = 1.0f + 1.0f / square(this->sun().shape_radius);
/* Convert irradiance to radiance. */
return float(M_1_PI) * inv_sin_sq;
}
}
BLI_assert_unreachable();
return 0.0f;
}
float Light::point_radiance_get()
{
/* Volume light is evaluated as point lights. */
switch (this->type) {
case LIGHT_RECT:
case LIGHT_ELLIPSE: {
/* This corrects for area light most representative point trick.
* The fit was found by reducing the average error compared to cycles. */
float area = this->area().size.x * this->area().size.y * 4.0f;
float tmp = M_PI_2 / (M_PI_2 + sqrtf(area));
/* Lerp between 1.0 and the limit (1 / pi). */
float mrp_scaling = tmp + (1.0f - tmp) * M_1_PI;
return float(M_1_PI) * mrp_scaling;
}
case LIGHT_OMNI_SPHERE:
case LIGHT_OMNI_DISK:
case LIGHT_SPOT_SPHERE:
case LIGHT_SPOT_DISK: {
/* Convert radiant flux to intensity. */
/* Inverse of sphere solid angle. */
return float(1.0 / (4.0 * M_PI));
}
case LIGHT_SUN_ORTHO:
case LIGHT_SUN: {
return 1.0f;
}
}
BLI_assert_unreachable();
return 0.0f;
}
void Light::debug_draw()
{
drw_debug_sphere(this->object_to_world.location(),
this->local().local.influence_radius_max,
float4(0.8f, 0.3f, 0.0f, 1.0f));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name LightModule
* \{ */
LightModule::~LightModule()
{
/* WATCH: Destructor order. Expect shadow module to be destructed later. */
for (Light &light : light_map_.values()) {
light.shadow_discard_safe(inst_.shadows);
}
};
void LightModule::add_world_sun_light(const ObjectKey &key, bool use_diffuse, bool use_glossy)
{
/* Create a placeholder light to be fed by the GPU after sunlight extraction.
* Sunlight is disabled if power is zero. */
blender::Light la = {};
la.type = LA_SUN;
/* Set on the GPU. */
la.r = la.g = la.b = -1.0f; /* Tag as world sun light. */
la.energy = 1.0f;
la.sun_angle = inst_.world.sun_angle();
la.shadow_filter_radius = inst_.world.sun_shadow_filter_radius();
la.shadow_jitter_overblur = inst_.world.sun_shadow_jitter_overblur();
la.shadow_maximum_resolution = inst_.world.sun_shadow_max_resolution();
SET_FLAG_FROM_TEST(la.mode, inst_.world.use_sun_shadow(), LA_SHADOW);
SET_FLAG_FROM_TEST(la.mode, inst_.world.use_sun_shadow_jitter(), LA_SHADOW_JITTER);
int visibility_flag = 0;
SET_FLAG_FROM_TEST(visibility_flag, !use_diffuse, OB_HIDE_DIFFUSE);
SET_FLAG_FROM_TEST(visibility_flag, !use_glossy, OB_HIDE_GLOSSY);
visibility_flag |= OB_HIDE_CAMERA;
Light &light = light_map_.lookup_or_add_default(key);
light.used = true;
light.sync(inst_.shadows, float4x4::identity(), visibility_flag, &la, nullptr, light_threshold_);
sun_lights_len_ += 1;
}
void LightModule::begin_sync()
{
if (assign_if_different(use_scene_lights_, inst_.use_scene_lights())) {
if (inst_.is_viewport()) {
/* Catch lookdev viewport properties updates. */
inst_.sampling.reset();
}
}
/* Disable sunlight if world has a volume shader as we consider the light cannot go through an
* infinite opaque medium. */
use_sun_lights_ = (inst_.world.has_volume_absorption() == false);
/* In begin_sync so it can be animated. */
if (assign_if_different(light_threshold_, max_ff(1e-16f, inst_.scene->eevee.light_threshold))) {
/* All local lights need to be re-sync. */
for (Light &light : light_map_.values()) {
if (!ELEM(light.type, LIGHT_SUN, LIGHT_SUN_ORTHO)) {
light.initialized = false;
}
}
}
sun_lights_len_ = 0;
local_lights_len_ = 0;
if (use_sun_lights_ && inst_.world.sun_threshold() > 0.0f) {
if (inst_.pipelines.world.use_lightpath_node()) {
add_world_sun_light(world_sunlight_key_[WORLD_SUN_DIFFUSE], true, false);
add_world_sun_light(world_sunlight_key_[WORLD_SUN_GLOSSY], false, true);
}
else {
add_world_sun_light(world_sunlight_key_[WORLD_SUN_COMBINED], true, true);
}
}
}
void LightModule::sync_light(const ObjectRef &ob_ref)
{
const blender::Light &la = DRW_object_get_data_for_drawing<const blender::Light>(*ob_ref.object);
if (use_scene_lights_ == false) {
return;
}
if (use_sun_lights_ == false) {
if (la.type == LA_SUN) {
return;
}
}
Light &light = light_map_.lookup_or_add_default(ObjectKey(ob_ref));
light.used = true;
if (inst_.get_recalc_flags(ob_ref) != 0 || !light.initialized) {
light.initialized = true;
light.sync(inst_.shadows,
ob_ref.object_to_world(),
ob_ref.object->visibility_flag,
&la,
ob_ref.light_linking(),
light_threshold_);
}
sun_lights_len_ += int(is_sun_light(light.type));
local_lights_len_ += int(!is_sun_light(light.type));
}
void LightModule::end_sync()
{
/** IMPORTANT: We cannot add new lights here since the shadow module already executed its
* `end_sync`. Doing so ends up in very bad data access since the shadow data of the new light
* will not exists on the GPU. */
/* NOTE: We resize this buffer before removing deleted lights. */
int lights_allocated = ceil_to_multiple_u(max_ii(light_map_.size(), 1), LIGHT_CHUNK);
light_buf_.resize(lights_allocated);
/* Track light deletion. */
/* Indices inside GPU data array. */
int sun_lights_idx = 0;
int local_lights_idx = sun_lights_len_;
/* Fill GPU data with scene data. */
auto it_end = light_map_.items().end();
for (auto it = light_map_.items().begin(); it != it_end; ++it) {
Light &light = (*it).value;
/* Do not discard casters in baking mode. See WORKAROUND in `surfels_create`. */
if (!light.used && !inst_.is_baking()) {
light_map_.remove(it);
continue;
}
int dst_idx = is_sun_light(light.type) ? sun_lights_idx++ : local_lights_idx++;
/* Put all light data into global data SSBO. */
light_buf_[dst_idx] = light;
/* Untag for next sync. */
light.used = false;
}
/* This scene data buffer is then immutable after this point. */
light_buf_.push_update();
/* If exceeding the limit, just trim off the excess to avoid glitchy rendering. */
if (sun_lights_len_ + local_lights_len_ > CULLING_MAX_ITEM) {
sun_lights_len_ = min_ii(sun_lights_len_, CULLING_MAX_ITEM);
local_lights_len_ = min_ii(local_lights_len_, CULLING_MAX_ITEM - sun_lights_len_);
inst_.info_append_i18n("Error: Too many lights in the scene.");
}
lights_len_ = sun_lights_len_ + local_lights_len_;
/* Resize to the actual number of lights after pruning. */
lights_allocated = ceil_to_multiple_u(max_ii(lights_len_, 1), LIGHT_CHUNK);
culling_key_buf_.resize(lights_allocated);
culling_zdist_buf_.resize(lights_allocated);
culling_light_buf_.resize(lights_allocated);
{
int2 render_extent = inst_.film.render_extent_get();
int2 probe_extent = int2(inst_.sphere_probes.probe_render_extent());
int2 max_extent = math::max(render_extent, probe_extent);
/* Compute tile size and total word count. */
uint word_per_tile = divide_ceil_u(max_ii(lights_len_, 1), 32);
int2 tiles_extent;
/* Default to 32 as this is likely to be the maximum
* tile size used by hardware or compute shading. */
uint tile_size = 16;
bool tile_size_valid = false;
do {
tile_size *= 2;
tiles_extent = math::divide_ceil(max_extent, int2(tile_size));
uint tile_count = tiles_extent.x * tiles_extent.y;
if (tile_count > max_tile_count_threshold) {
continue;
}
total_word_count_ = tile_count * word_per_tile;
tile_size_valid = true;
} while (total_word_count_ > max_word_count_threshold || !tile_size_valid);
/* Keep aligned with storage buffer requirements. */
total_word_count_ = ceil_to_multiple_u(total_word_count_, 32);
culling_data_buf_.tile_word_len = word_per_tile;
culling_data_buf_.tile_size = tile_size;
culling_data_buf_.tile_x_len = tiles_extent.x;
culling_data_buf_.tile_y_len = tiles_extent.y;
culling_data_buf_.items_count = lights_len_;
culling_data_buf_.local_lights_len = local_lights_len_;
culling_data_buf_.sun_lights_len = sun_lights_len_;
}
culling_tile_buf_.resize(total_word_count_);
culling_pass_sync();
update_pass_sync();
shape_display_pass_sync();
debug_pass_sync();
}
void LightModule::shape_display_pass_sync()
{
shape_display_ps_.init();
if (lights_len_ == 0) {
return;
}
shape_display_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD |
DRW_STATE_CLIP_CONTROL_UNIT_RANGE | inst_.film.depth.test_state);
shape_display_ps_.shader_set(inst_.shaders.static_shader_get(LIGHT_SHAPE_DISPLAY));
shape_display_ps_.bind_resources(inst_.uniform_data);
shape_display_ps_.bind_resources(inst_.volume.result);
shape_display_ps_.bind_resources(inst_.lights);
shape_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, lights_len_ * 6);
}
void LightModule::culling_pass_sync()
{
uint safe_lights_len = max_ii(lights_len_, 1);
uint culling_select_dispatch_size = divide_ceil_u(safe_lights_len, CULLING_SELECT_GROUP_SIZE);
uint culling_sort_dispatch_size = divide_ceil_u(safe_lights_len, CULLING_SORT_GROUP_SIZE);
uint culling_tile_dispatch_size = divide_ceil_u(total_word_count_, CULLING_TILE_GROUP_SIZE);
/* NOTE: We reference the buffers that may be resized or updated later. */
culling_ps_.init();
{
auto &sub = culling_ps_.sub("Select");
sub.shader_set(inst_.shaders.static_shader_get(LIGHT_CULLING_SELECT));
sub.bind_ubo("sunlight_buf", &inst_.world.sunlight);
sub.bind_ssbo("light_cull_buf", &culling_data_buf_);
sub.bind_ssbo("in_light_buf", light_buf_);
sub.bind_ssbo("out_light_buf", culling_light_buf_);
sub.bind_ssbo("out_zdist_buf", culling_zdist_buf_);
sub.bind_ssbo("out_key_buf", culling_key_buf_);
sub.dispatch(int3(culling_select_dispatch_size, 1, 1));
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
}
{
auto &sub = culling_ps_.sub("Sort");
sub.shader_set(inst_.shaders.static_shader_get(LIGHT_CULLING_SORT));
sub.bind_ssbo("light_cull_buf", &culling_data_buf_);
sub.bind_ssbo("in_light_buf", light_buf_);
sub.bind_ssbo("out_light_buf", culling_light_buf_);
sub.bind_ssbo("in_zdist_buf", culling_zdist_buf_);
sub.bind_ssbo("in_key_buf", culling_key_buf_);
sub.dispatch(int3(culling_sort_dispatch_size, 1, 1));
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
}
{
auto &sub = culling_ps_.sub("Zbin");
sub.shader_set(inst_.shaders.static_shader_get(LIGHT_CULLING_ZBIN));
sub.bind_ssbo("light_cull_buf", &culling_data_buf_);
sub.bind_ssbo("light_buf", culling_light_buf_);
sub.bind_ssbo("out_zbin_buf", culling_zbin_buf_);
sub.dispatch(int3(1, 1, 1));
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
}
{
auto &sub = culling_ps_.sub("Tiles");
sub.shader_set(inst_.shaders.static_shader_get(LIGHT_CULLING_TILE));
sub.bind_ssbo("light_cull_buf", &culling_data_buf_);
sub.bind_ssbo("light_buf", culling_light_buf_);
sub.bind_ssbo("out_light_tile_buf", culling_tile_buf_);
sub.dispatch(int3(culling_tile_dispatch_size, 1, 1));
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
}
}
void LightModule::update_pass_sync()
{
/* TODO(fclem): This dispatch for all light before culling. This could be made better by
* only running on lights that survive culling using an indirect dispatch. */
uint safe_lights_len = max_ii(lights_len_, 1);
uint shadow_setup_dispatch_size = divide_ceil_u(safe_lights_len, CULLING_SELECT_GROUP_SIZE);
auto &pass = update_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(LIGHT_SHADOW_SETUP));
pass.bind_ssbo("light_buf", &culling_light_buf_);
pass.bind_ssbo("light_cull_buf", &culling_data_buf_);
pass.bind_ssbo("tilemaps_buf", &inst_.shadows.tilemap_pool.tilemaps_data);
pass.bind_ssbo("tilemaps_clip_buf", &inst_.shadows.tilemap_pool.tilemaps_clip);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.dispatch(int3(shadow_setup_dispatch_size, 1, 1));
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
}
void LightModule::debug_pass_sync()
{
if (inst_.debug_mode == eDebugMode::DEBUG_LIGHT_CULLING) {
debug_draw_ps_.init();
debug_draw_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM);
debug_draw_ps_.shader_set(inst_.shaders.static_shader_get(LIGHT_CULLING_DEBUG));
debug_draw_ps_.bind_resources(inst_.uniform_data);
debug_draw_ps_.bind_resources(inst_.hiz_buffer.front);
debug_draw_ps_.bind_ssbo("light_buf", &culling_light_buf_);
debug_draw_ps_.bind_ssbo("light_cull_buf", &culling_data_buf_);
debug_draw_ps_.bind_ssbo("light_zbin_buf", &culling_zbin_buf_);
debug_draw_ps_.bind_ssbo("light_tile_buf", &culling_tile_buf_);
debug_draw_ps_.bind_texture("depth_tx", &inst_.render_buffers.depth_tx);
debug_draw_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
}
void LightModule::set_view(View &view, const int2 extent)
{
float far_z = view.far_clip();
float near_z = view.near_clip();
culling_data_buf_.zbin_scale = -CULLING_ZBIN_COUNT / fabsf(far_z - near_z);
culling_data_buf_.zbin_bias = -near_z * culling_data_buf_.zbin_scale;
culling_data_buf_.tile_to_uv_fac = (culling_data_buf_.tile_size / float2(extent));
culling_data_buf_.visible_count = 0;
culling_data_buf_.view_is_flipped = view.is_inverted();
culling_data_buf_.push_update();
inst_.manager->submit(culling_ps_, view);
inst_.manager->submit(update_ps_, view);
}
void LightModule::debug_draw(View &view, gpu::FrameBuffer *view_fb)
{
if (inst_.debug_mode == eDebugMode::DEBUG_LIGHT_CULLING) {
inst_.info_append("Debug Mode: Light Culling Validation");
inst_.hiz_buffer.update();
GPU_framebuffer_bind(view_fb);
inst_.manager->submit(debug_draw_ps_, view);
}
}
void LightModule::shape_display_draw(View &view, gpu::FrameBuffer *view_fb)
{
GPU_framebuffer_bind(view_fb);
inst_.manager->submit(shape_display_ps_, view);
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,219 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* The light module manages light data buffers and light culling system.
*
* The culling follows the principles of Tiled Culling + Z binning from:
* "Improved Culling for Tiled and Clustered Rendering"
* by Michal Drobot
* http://advances.realtimerendering.com/s2017/2017_Sig_Improved_Culling_final.pdf
*
* The culling is separated in 4 compute phases:
* - View Culling (select pass): Create a z distance and a index buffer of visible lights.
* - Light sorting: Outputs visible lights sorted by Z distance.
* - Z binning: Compute the Z bins min/max light indices.
* - Tile intersection: Fine grained 2D culling of each lights outputting a bitmap per tile.
*/
#pragma once
#include "DNA_light_types.h"
#include "DRW_gpu_wrapper.hh"
#include "eevee_camera.hh"
#include "eevee_light_shared.hh"
#include "eevee_sampling.hh"
#include "eevee_sync.hh"
namespace blender::eevee {
class Instance;
class ShadowModule;
class ShadowDirectional;
class ShadowPunctual;
/* -------------------------------------------------------------------- */
/** \name Light Object
* \{ */
using LightCullingDataBuf = draw::StorageBuffer<LightCullingData>;
using LightCullingKeyBuf = draw::StorageArrayBuffer<uint, LIGHT_CHUNK, true>;
using LightCullingTileBuf = draw::StorageArrayBuffer<uint, LIGHT_CHUNK, true>;
using LightCullingZbinBuf = draw::StorageArrayBuffer<uint, CULLING_ZBIN_COUNT, true>;
using LightCullingZdistBuf = draw::StorageArrayBuffer<float, LIGHT_CHUNK, true>;
using LightDataBuf = draw::StorageArrayBuffer<LightData, LIGHT_CHUNK>;
struct Light : public LightData, NonCopyable {
public:
bool initialized = false;
bool used = false;
/** Pointers to source Shadow. Type depends on `LightData::type`. */
ShadowDirectional *directional = nullptr;
ShadowPunctual *punctual = nullptr;
Light()
{
/* Avoid valgrind warning. */
this->type = LIGHT_SUN;
}
/* Only used for debugging. */
#ifndef NDEBUG
Light(Light &&other)
{
*static_cast<LightData *>(this) = other;
this->initialized = other.initialized;
this->used = other.used;
this->directional = other.directional;
this->punctual = other.punctual;
other.directional = nullptr;
other.punctual = nullptr;
}
~Light()
{
BLI_assert(directional == nullptr);
BLI_assert(punctual == nullptr);
}
#endif
void sync(ShadowModule &shadows,
float4x4 object_to_world,
char visibility_flag,
const blender::Light *la,
const LightLinking *light_linking,
float threshold);
void shadow_ensure(ShadowModule &shadows);
void shadow_discard_safe(ShadowModule &shadows);
void debug_draw();
private:
float shadow_lod_min_get(const blender::Light *la);
float shadow_shape_size_get(const blender::Light *la);
float attenuation_radius_get(const blender::Light *la, float light_threshold, float light_power);
void shape_parameters_set(const blender::Light *la,
const float3 &scale,
const float3 &z_axis,
float threshold,
bool use_jitter);
float shape_radiance_get();
float point_radiance_get();
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name LightModule
* \{ */
/**
* The light module manages light data buffers and light culling system.
*/
class LightModule {
friend ShadowModule;
private:
/* Keep tile count reasonable for memory usage and 2D culling performance. */
static constexpr uint max_memory_threshold = 32 * 1024 * 1024; /* 32 MiB */
static constexpr uint max_word_count_threshold = max_memory_threshold / sizeof(uint);
static constexpr uint max_tile_count_threshold = 8192;
Instance &inst_;
/** Map of light objects data. Converted to flat array each frame. */
Map<ObjectKey, Light> light_map_;
/**
* In order to treat the world sun lights the same way as regular lights,
* an #ObjectKey needs to be associated to each of them.
* */
ObjectKey world_sunlight_key_[WORLD_SUN_MAX] = {ObjectKey(WORLD_SUN_DIFFUSE),
ObjectKey(WORLD_SUN_GLOSSY)};
/** Flat array sent to GPU, populated from light_map_. Source buffer for light culling. */
LightDataBuf light_buf_ = {"Lights_no_cull"};
/** Luminous intensity to consider the light boundary at. Used for culling. */
float light_threshold_ = 0.01f;
/** If false, will prevent all scene lights from being synced. */
bool use_scene_lights_ = false;
/** If false, will prevent all sun lights from being synced. */
bool use_sun_lights_ = false;
/** Number of sun lights synced during the last sync. Used as offset. */
int sun_lights_len_ = 0;
int local_lights_len_ = 0;
/** Sun plus local lights count for convenience. */
int lights_len_ = 0;
/**
* Light Culling
*/
/** LightData buffer used for rendering. Filled by the culling pass. */
LightDataBuf culling_light_buf_ = {"Lights_culled"};
/** Culling information. */
LightCullingDataBuf culling_data_buf_ = {"LightCull_data"};
/** Z-distance matching the key for each visible lights. Used for sorting. */
LightCullingZdistBuf culling_zdist_buf_ = {"LightCull_zdist"};
/** Key buffer containing only visible lights indices. Used for sorting. */
LightCullingKeyBuf culling_key_buf_ = {"LightCull_key"};
/** Zbins containing min and max light index for each Z bin. */
LightCullingZbinBuf culling_zbin_buf_ = {"LightCull_zbin"};
/** Bitmap of lights touching each tiles. */
LightCullingTileBuf culling_tile_buf_ = {"LightCull_tile"};
/** Culling compute passes. */
PassSimple culling_ps_ = {"LightCulling"};
/** Total number of words the tile buffer needs to contain for the render resolution. */
uint total_word_count_ = 0;
/** Flipped state of the view being processed. True for planar probe views. */
bool view_is_flipped_ = false;
/** Update light on the GPU after culling. Ran for each sample. */
PassSimple update_ps_ = {"LightUpdate"};
/** Draw camera-visible light shapes. */
PassSimple shape_display_ps_ = {"Light.ShapeDisplay"};
/** Debug Culling visualization. */
PassSimple debug_draw_ps_ = {"LightCulling.Debug"};
public:
LightModule(Instance &inst) : inst_(inst) {};
~LightModule();
void begin_sync();
void sync_light(const ObjectRef &ob_ref);
void end_sync();
/**
* Update acceleration structure for the given view.
*/
void set_view(View &view, const int2 extent);
void shape_display_draw(View &view, gpu::FrameBuffer *view_fb);
void debug_draw(View &view, gpu::FrameBuffer *view_fb);
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_ssbo(LIGHT_CULL_BUF_SLOT, &culling_data_buf_);
pass.bind_ssbo(LIGHT_BUF_SLOT, &culling_light_buf_);
pass.bind_ssbo(LIGHT_ZBIN_BUF_SLOT, &culling_zbin_buf_);
pass.bind_ssbo(LIGHT_TILE_BUF_SLOT, &culling_tile_buf_);
}
private:
void culling_pass_sync();
void update_pass_sync();
void shape_display_pass_sync();
void debug_pass_sync();
void add_world_sun_light(const ObjectKey &key, bool use_diffuse, bool use_glossy);
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,301 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "eevee_defines.hh"
#include "eevee_transform.hh"
#ifndef GPU_SHADER
# include "BLI_math_bits.h"
namespace blender::eevee {
#endif
#define LIGHT_NO_SHADOW -1
/**
* Index inside the world sun buffer.
* In the case the world uses the light path node, multiple suns can be extracted from the world.
*/
enum [[host_shared]] WorldSunIndex : uint32_t {
/** When the world node-tree doesn't use the light path node, there is only 1 extracted. */
WORLD_SUN_COMBINED = 0u,
/* Index of each components. */
WORLD_SUN_DIFFUSE = 0u,
WORLD_SUN_GLOSSY = 1u,
WORLD_SUN_MAX = 2u,
};
enum [[host_shared]] eLightType : uint32_t {
LIGHT_SUN = 0u,
LIGHT_SUN_ORTHO = 1u,
/* Point light. */
LIGHT_OMNI_SPHERE = 10u,
LIGHT_OMNI_DISK = 11u,
/* Spot light. */
LIGHT_SPOT_SPHERE = 12u,
LIGHT_SPOT_DISK = 13u,
/* Area light. */
LIGHT_RECT = 20u,
LIGHT_ELLIPSE = 21u
};
static inline bool is_area_light(eLightType type)
{
return type >= LIGHT_RECT;
}
static inline bool is_point_light(eLightType type)
{
return type >= LIGHT_OMNI_SPHERE && type <= LIGHT_SPOT_DISK;
}
static inline bool is_spot_light(eLightType type)
{
return type == LIGHT_SPOT_SPHERE || type == LIGHT_SPOT_DISK;
}
static inline bool is_sphere_light(eLightType type)
{
return type == LIGHT_SPOT_SPHERE || type == LIGHT_OMNI_SPHERE;
}
static inline bool is_oriented_disk_light(eLightType type)
{
return type == LIGHT_SPOT_DISK || type == LIGHT_OMNI_DISK;
}
static inline bool is_sun_light(eLightType type)
{
return type < LIGHT_OMNI_SPHERE;
}
static inline bool is_local_light(eLightType type)
{
return type >= LIGHT_OMNI_SPHERE;
}
struct [[host_shared]] LightLocalCommon {
/** Shift to apply to the light origin to get the shadow projection origin. In light space. */
packed_float3 shadow_position;
/** Radius of the light for shadow ray casting. Simple scaling factor for rectangle lights. */
float shadow_radius;
/** Radius of the light for shading. Bounding radius for rectangle lights. */
float shape_radius;
/** Maximum influence radius. Used for culling. Equal to clip far distance. */
float influence_radius_max;
/** Influence radius (inverted and squared) adjusted for Surface Volume power. */
float influence_radius_invsqr_surface;
float influence_radius_invsqr_volume;
};
/* Untyped local light data. Gets reinterpreted to LightSpotData and LightAreaData.
* Allow access to local light common data without casting. */
struct [[host_shared]] LightLocalData {
struct LightLocalCommon local;
/** Number of allocated tilemap for this local light. */
int tilemaps_count; /* Leaked from LightLocalCommon because of alignment. */
float _pad0;
float _pad1;
float _pad2;
float2 _pad3;
float _pad4;
float _pad5;
};
/* Despite the name, is also used for omni light. */
struct [[host_shared]] LightSpotData {
struct LightLocalCommon local;
/** Number of allocated tilemap for this local light. */
int tilemaps_count; /* Leaked from LightLocalCommon because of alignment. */
float _pad0;
float _pad1;
/** Scale and bias to spot equation parameter. Used for adjusting the falloff. */
float spot_mul;
/** Inverse spot size (in X and Y axes). */
float2 spot_size_inv;
/** Spot angle tangent. */
float spot_tan;
float spot_bias;
};
struct [[host_shared]] LightAreaData {
struct LightLocalCommon local;
/** Number of allocated tilemap for this local light. */
int tilemaps_count; /* Leaked from LightLocalCommon because of alignment. */
float _pad0;
float _pad1;
float _pad2;
/** Shape size. */
float2 size;
/** Scale to apply on top of `size` to get shadow tracing shape size. */
float shadow_scale;
float _pad6;
};
struct [[host_shared]] LightSunData {
/* Sun direction for shading. Use object_to_world for getting into shadow space. */
packed_float3 direction;
/* Radius of the sun disk, one unit away from a shading point. */
float shape_radius;
/** --- Shadow Data --- */
/** Offset of the LOD min in LOD min tile units. Split positive and negative for bit-shift. */
int2 clipmap_base_offset_neg;
int2 clipmap_base_offset_pos;
/** Angle covered by the light shape for shadow ray casting. */
float shadow_angle;
float shadow_angle_cos;
float _pad3;
float _pad4;
/** Offset to convert from world units to tile space of the clipmap_lod_max. */
float2 clipmap_origin;
/** Clip-map LOD range to avoid sampling outside of valid range. */
int clipmap_lod_min;
int clipmap_lod_max;
};
struct [[host_shared]] LightData {
/**
* Normalized object to world matrix. Stored transposed for compactness.
* Used for shading and shadowing local lights, or shadowing sun lights.
* IMPORTANT: Not used for shading sun lights as this matrix is jittered.
*/
struct Transform object_to_world;
/** Power depending on shader type. Referenced by LightingType. */
float4 power;
/** Light Color. */
packed_float3 color;
/** Light Type. */
enum eLightType type;
/** --- Shadow Data --- */
/** Near clip distances. Float stored as orderedIntBitsToFloat for atomic operations. */
int clip_near;
int clip_far;
/** Index of the first tile-map. Set to LIGHT_NO_SHADOW if light is not casting shadow. */
int tilemap_index;
/* Radius in pixels for shadow filtering. */
float filter_radius;
/* Shadow Map resolution bias. */
float lod_bias;
/* Shadow Map resolution maximum resolution. */
float lod_min;
/* True if the light uses jittered soft shadows. */
bool32_t shadow_jitter;
/* True if the light shape should be visible to camera rays. */
bool32_t visible_camera;
uint2 light_set_membership;
/** Used by shadow sync. */
/* TODO(fclem): this should be part of #eevee::Light struct. But for some reason it gets cleared
* to zero after each sync cycle. */
uint2 shadow_set_membership;
union {
union_t<struct LightLocalData> local;
union_t<struct LightSpotData> spot;
union_t<struct LightAreaData> area;
union_t<struct LightSunData> sun;
};
float3 x_axis() const
{
return object_to_world.x_axis();
}
float3 y_axis() const
{
return object_to_world.y_axis();
}
float3 z_axis() const
{
return object_to_world.z_axis();
}
float3 position() const
{
return object_to_world.location();
}
int tilemap_max_get() const
{
/* This is not something we need in performance critical code. */
if (is_sun_light(this->type)) {
return this->tilemap_index + (this->sun().clipmap_lod_max - this->sun().clipmap_lod_min);
}
return this->tilemap_index + this->local().tilemaps_count - 1;
}
/* Return the number of tilemap needed for a local light. */
int local_tilemap_count() const
{
if (is_spot_light(this->type)) {
return (this->spot().spot_tan > tanf(EEVEE_PI / 4.0)) ? 5 : 1;
}
if (is_area_light(this->type)) {
return 5;
}
return 6;
}
};
/* -------------------------------------------------------------------- */
/** \name Light Culling
* \{ */
/* Number of items we can cull. Limited by how we store CullingZBin. */
#define CULLING_MAX_ITEM 65536
/* Fine grained subdivision in the Z direction. Limited by the LDS in z-binning compute shader. */
#define CULLING_ZBIN_COUNT 4096
/* Max tile map resolution per axes. */
#define CULLING_TILE_RES 16
struct [[host_shared]] LightCullingData {
/** Scale applied to tile pixel coordinates to get target UV coordinate. */
float2 tile_to_uv_fac;
/** Scale and bias applied to linear Z to get zbin. */
float zbin_scale;
float zbin_bias;
/** Valid item count in the source data array. */
uint items_count;
/** Items that are processed by the 2.5D culling. */
uint local_lights_len;
/** Items that are **NOT** processed by the 2.5D culling (i.e: Sun Lights). */
uint sun_lights_len;
/** Number of items that passes the first culling test. (local lights only) */
uint visible_count;
/** Extent of one square tile in pixels. */
float tile_size;
/** Number of tiles on the X/Y axis. */
uint tile_x_len;
uint tile_y_len;
/** Number of word per tile. Depends on the maximum number of lights. */
uint tile_word_len;
/** Is the view being processed by light culling flipped (true for light probe planes). */
bool32_t view_is_flipped;
uint _pad0;
uint _pad1;
uint _pad2;
};
/** \} */
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,367 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Contains everything about light baking.
*/
#include "DRW_engine.hh"
#include "DRW_render.hh"
#include "BKE_global.hh"
#include "BKE_lightprobe.h"
#include "DNA_lightprobe_types.h"
#include "BLI_mutex.hh"
#include "BLI_threads.h"
#include "BLI_time.h"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
#include "GPU_capabilities.hh"
#include "GPU_context.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "wm_window.hh"
#include "eevee_instance.hh"
#include "eevee_lightcache.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Light Probe Baking
* \{ */
namespace eevee {
class LightBake {
private:
Depsgraph *depsgraph_;
/** Scene frame to evaluate the depsgraph at. */
int frame_;
/** Milliseconds. Delay the start of the baking to not slowdown interactions (TODO: remove). */
int delay_ms_;
/**
* Reference to the operator report string to print messages to the UI.
* Should be threadsafe to write to as it gets read by the operator code only if the job is
* finished.
*/
std::string &report_;
/**
* If running in parallel (in a separate thread), use this context.
* Created on main thread but first bound in worker thread.
*/
GHOST_IContext *gl_context_ = nullptr;
/** Context associated to `gl_context_`. Created in the worker thread. */
GPUContext *gpu_context_ = nullptr;
/** Baking instance. Created and freed in the worker thread. */
Instance *instance_ = nullptr;
/** Manager used for command submission. Created and freed in the worker thread. */
draw::Manager *manager_ = nullptr;
/** Light-probe original objects to bake. */
Vector<Object *> original_probes_;
/** Frame to copy to original objects during update. This is needed to avoid race conditions. */
Vector<LightProbeGridCacheFrame *> bake_result_;
Mutex result_mutex_;
public:
LightBake(Main *bmain,
ViewLayer *view_layer,
Scene *scene,
Span<Object *> probes,
bool run_as_job,
std::string &report,
int frame,
int delay_ms = 0)
: depsgraph_(DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_RENDER)),
frame_(frame),
delay_ms_(delay_ms),
report_(report),
original_probes_(probes)
{
BLI_assert(BLI_thread_is_main());
bake_result_.resize(probes.size());
bake_result_.fill(nullptr);
if (run_as_job && !GPU_use_main_context_workaround()) {
/* This needs to happen in main thread. */
gl_context_ = WM_system_gpu_context_create();
wm_window_reset_drawable();
}
}
~LightBake()
{
BLI_assert(BLI_thread_is_main());
DEG_graph_free(depsgraph_);
}
/**
* Called from main thread.
* Copy result to original scene data.
* Note that since this is in the main thread, the viewport cannot be using the light cache.
* So there is no race condition here.
*/
void update()
{
BLI_assert(BLI_thread_is_main());
for (auto i : bake_result_.index_range()) {
if (bake_result_[i] == nullptr) {
continue;
}
Object *orig_ob = original_probes_[i];
{
std::scoped_lock lock(result_mutex_);
LightProbeObjectCache *cache = orig_ob->lightprobe_cache;
/* Delete any existing cache. */
if (cache->grid_static_cache != nullptr) {
BKE_lightprobe_grid_cache_frame_free(cache->grid_static_cache);
}
/* Pass ownership to original object. */
cache->grid_static_cache = bake_result_[i];
bake_result_[i] = nullptr;
}
/* Propagate the cache to evaluated object. */
DEG_id_tag_update(&orig_ob->id, ID_RECALC_SYNC_TO_EVAL | ID_RECALC_SHADING);
}
}
/**
* Called from worker thread.
*/
void run(const bool *stop = nullptr, bool *do_update = nullptr, float *progress = nullptr)
{
DEG_graph_relations_update(depsgraph_);
DEG_evaluate_on_framechange(depsgraph_, frame_);
if (delay_ms_ > 0) {
BLI_time_sleep_ms(delay_ms_);
}
context_enable();
manager_ = new draw::Manager();
instance_ = new eevee::Instance();
instance_->init_light_bake(depsgraph_, manager_);
context_disable();
for (auto i : original_probes_.index_range()) {
Object *eval_ob = DEG_get_evaluated(depsgraph_, original_probes_[i]);
instance_->light_bake_irradiance(
*eval_ob,
[this]() { context_enable(); },
[this]() { context_disable(); },
[&]() { return (G.is_break == true) || ((stop != nullptr) ? *stop : false); },
[&](LightProbeGridCacheFrame *cache_frame, float grid_progress) {
{
std::scoped_lock lock(result_mutex_);
/* Delete any existing cache that wasn't transferred to the original object. */
if (bake_result_[i] != nullptr) {
BKE_lightprobe_grid_cache_frame_free(bake_result_[i]);
}
bake_result_[i] = cache_frame;
}
if (do_update) {
*do_update = true;
}
if (progress) {
*progress = (i + grid_progress) / original_probes_.size();
}
});
if (StringRefNull(instance_->info_get()) != "") {
/* Pipe report to operator. */
report_ = instance_->info_get();
}
if ((G.is_break == true) || (stop != nullptr && *stop == true)) {
break;
}
}
delete_resources();
}
private:
void context_enable(bool render_begin = true)
{
if (GPU_use_main_context_workaround() && !BLI_thread_is_main()) {
/* Reuse main draw context. */
GPU_context_main_lock();
DRW_gpu_context_enable();
}
else if (gl_context_ == nullptr) {
/* Main thread case. */
DRW_gpu_context_enable();
}
else {
/* Worker thread case. */
DRW_system_gpu_render_context_enable(gl_context_);
if (gpu_context_ == nullptr) {
/* Create GPUContext in worker thread as it needs the correct gl context bound (which can
* only be bound in worker thread because of some GL driver requirements). */
gpu_context_ = GPU_context_create(nullptr, gl_context_);
}
DRW_blender_gpu_render_context_enable(gpu_context_);
}
if (render_begin) {
GPU_render_begin();
}
}
void context_disable()
{
if (GPU_use_main_context_workaround() && !BLI_thread_is_main()) {
/* Reuse main draw context. */
DRW_gpu_context_disable();
GPU_render_end();
GPU_context_main_unlock();
}
else if (gl_context_ == nullptr) {
/* Main thread case. */
DRW_gpu_context_disable();
GPU_render_end();
}
else {
/* Worker thread case. */
DRW_blender_gpu_render_context_disable(gpu_context_);
GPU_render_end();
DRW_system_gpu_render_context_disable(gl_context_);
}
}
/**
* Delete the engine instance and the optional contexts.
* This needs to run on the worker thread because the OpenGL context can only be ever bound to a
* single thread (because of some driver implementation), and the resources (textures,
* buffers,...) need to be freed with the right context bound.
*/
void delete_resources()
{
/* Bind context without GPU_render_begin(). */
context_enable(false);
/* Free GPU data (Textures, Frame-buffers, etc...). */
delete instance_;
delete manager_;
/* Delete / unbind the GL & GPU context. Assumes it is currently bound. */
if (GPU_use_main_context_workaround() && !BLI_thread_is_main()) {
/* Reuse main draw context. */
DRW_gpu_context_disable();
GPU_context_main_unlock();
}
else if (gl_context_ == nullptr) {
/* Main thread case. */
DRW_gpu_context_disable();
}
else {
/* Worker thread case. */
if (gpu_context_ != nullptr) {
GPU_context_discard(gpu_context_);
}
DRW_system_gpu_render_context_disable(gl_context_);
WM_system_gpu_context_dispose(gl_context_);
}
}
};
} // namespace eevee
/** \} */
/* -------------------------------------------------------------------- */
/** \name Light Bake Job
* \{ */
using namespace blender::eevee;
wmJob *EEVEE_lightbake_job_create(wmWindowManager *wm,
wmWindow *win,
Main *bmain,
ViewLayer *view_layer,
Scene *scene,
Vector<Object *> original_probes,
std::string &report,
int delay_ms,
int frame)
{
/* Do not bake if there is a render going on. */
if (WM_jobs_test(wm, scene, WM_JOB_TYPE_RENDER)) {
return nullptr;
}
/* Stop existing baking job. */
WM_jobs_stop_type(wm, nullptr, WM_JOB_TYPE_LIGHT_BAKE);
wmJob *wm_job = WM_jobs_get(wm,
win,
scene,
"Baking lighting...",
WM_JOB_EXCL_RENDER | WM_JOB_PRIORITY | WM_JOB_PROGRESS,
WM_JOB_TYPE_LIGHT_BAKE);
LightBake *bake = new LightBake(
bmain, view_layer, scene, std::move(original_probes), true, report, frame, delay_ms);
WM_jobs_customdata_set(wm_job, bake, EEVEE_lightbake_job_data_free);
WM_jobs_timer(wm_job, 0.4, NC_SCENE | NA_EDITED, 0);
WM_jobs_callbacks(
wm_job, EEVEE_lightbake_job, nullptr, EEVEE_lightbake_update, EEVEE_lightbake_update);
G.is_break = false;
return wm_job;
}
void *EEVEE_lightbake_job_data_alloc(Main *bmain,
ViewLayer *view_layer,
Scene *scene,
Vector<Object *> original_probes,
std::string &report,
int frame)
{
LightBake *bake = new LightBake(
bmain, view_layer, scene, std::move(original_probes), false, report, frame);
/* TODO(fclem): Can remove this cast once we remove the previous EEVEE light cache. */
return reinterpret_cast<void *>(bake);
}
void EEVEE_lightbake_job_data_free(void *job_data)
{
delete static_cast<LightBake *>(job_data);
}
void EEVEE_lightbake_update(void *job_data)
{
static_cast<LightBake *>(job_data)->update();
}
void EEVEE_lightbake_job(void *job_data, wmJobWorkerStatus *worker_status)
{
static_cast<LightBake *>(job_data)->run(
&worker_status->stop, &worker_status->do_update, &worker_status->progress);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,82 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#pragma once
#include "BLI_vector.hh"
#include <string>
struct EEVEE_LightBake;
namespace blender {
struct wmWindowManager;
struct wmWindow;
struct Main;
struct ViewLayer;
struct Scene;
struct Object;
struct wmJob;
struct wmJobWorkerStatus;
/** Opaque type hiding eevee::LightBake. */
/* -------------------------------------------------------------------- */
/** \name Light Bake Job
* \{ */
/**
* Create the job description.
* This is called for asynchronous (modal) bake operator.
* The actual work will be done by `EEVEE_lightbake_job()`.
* IMPORTANT: Must run on the main thread because of potential GPUContext creation.
*/
wmJob *EEVEE_lightbake_job_create(wmWindowManager *wm,
wmWindow *win,
Main *bmain,
ViewLayer *view_layer,
Scene *scene,
Vector<Object *> original_probes,
std::string &report,
int delay_ms,
int frame);
/**
* Allocate dependency graph and job description (EEVEE_LightBake).
* Dependency graph evaluation does *not* happen here. It is delayed until
* `EEVEE_lightbake_job` runs.
* IMPORTANT: Must run on the main thread because of potential GPUContext creation.
* Return `EEVEE_LightBake *` but cast to `void *` because of compatibility with existing
* EEVEE function.
*/
void *EEVEE_lightbake_job_data_alloc(Main *bmain,
ViewLayer *view_layer,
Scene *scene,
Vector<Object *> original_probes,
std::string &report,
int frame);
/**
* Free the job data.
* NOTE: Does not free the GPUContext. This is the responsibility of `EEVEE_lightbake_job()`
*/
void EEVEE_lightbake_job_data_free(/*EEVEE_LightBake*/ void *job_data);
/**
* Callback for updating original scene light cache with bake result.
* Run by the job system for each update step and the finish step.
* This is called manually by `EEVEE_lightbake_job()` if not run from a job.
*/
void EEVEE_lightbake_update(/*EEVEE_LightBake*/ void *job_data);
/**
* Do the full light baking for all samples.
* Will call `EEVEE_lightbake_update()` on finish.
*/
void EEVEE_lightbake_job(/*EEVEE_LightBake*/ void *job_data, wmJobWorkerStatus *worker_status);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,373 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Module that handles light probe update tagging.
* Lighting data is contained in their respective module `VolumeProbeModule`, `SphereProbeModule`
* and `PlanarProbeModule`.
*/
#include "DNA_lightprobe_types.h"
#include "eevee_instance.hh"
#include "eevee_lightprobe.hh"
#include <iostream>
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Light-Probe Module
* \{ */
LightProbeModule::LightProbeModule(Instance &inst) : inst_(inst)
{
/* Initialize the world probe. */
world_sphere_.clipping_distances = float2(1.0f, 10.0f);
world_sphere_.world_to_probe_transposed = float3x4::identity();
world_sphere_.influence_shape = SHAPE_ELIPSOID;
world_sphere_.parallax_shape = SHAPE_ELIPSOID;
/* Full influence. */
world_sphere_.influence_scale = 0.0f;
world_sphere_.influence_bias = 1.0f;
world_sphere_.parallax_distance = 1e10f;
/* In any case, the world must always be up to valid and used for render. */
world_sphere_.use_for_render = true;
}
static eLightProbeResolution resolution_to_probe_resolution_enum(int resolution)
{
switch (resolution) {
case 128:
return LIGHT_PROBE_RESOLUTION_128;
case 256:
return LIGHT_PROBE_RESOLUTION_256;
case 512:
return LIGHT_PROBE_RESOLUTION_512;
case 1024:
return LIGHT_PROBE_RESOLUTION_1024;
case 2048:
return LIGHT_PROBE_RESOLUTION_2048;
case 4096:
return LIGHT_PROBE_RESOLUTION_4096;
}
BLI_assert_unreachable();
return LIGHT_PROBE_RESOLUTION_2048;
}
void LightProbeModule::init()
{
const SceneEEVEE &sce_eevee = inst_.scene->eevee;
sphere_object_resolution_ = resolution_to_probe_resolution_enum(sce_eevee.gi_cubemap_resolution);
}
void LightProbeModule::begin_sync()
{
auto_bake_enabled_ = inst_.is_viewport() &&
(inst_.scene->eevee.flag & SCE_EEVEE_GI_AUTOBAKE) != 0;
}
void LightProbeModule::sync_volume(const ObjectRef &ob_ref)
{
VolumeProbe &grid = volume_map_.lookup_or_add_default(ObjectKey(ob_ref));
grid.used = true;
if (inst_.get_recalc_flags(ob_ref) != 0 || grid.initialized == false) {
const blender::LightProbe &lightprobe =
DRW_object_get_data_for_drawing<const blender::LightProbe>(*ob_ref.object);
grid.initialized = true;
grid.updated = true;
grid.surfel_density = lightprobe.grid_surfel_density;
grid.object_to_world = ob_ref.object_to_world();
grid.cache = ob_ref.object->lightprobe_cache;
grid.world_to_object = float4x4(
math::normalize(math::transpose(float3x3(grid.object_to_world))));
grid.normal_bias = lightprobe.grid_normal_bias;
grid.view_bias = lightprobe.grid_view_bias;
grid.facing_bias = lightprobe.grid_facing_bias;
grid.validity_threshold = lightprobe.grid_validity_threshold;
grid.dilation_threshold = lightprobe.grid_dilation_threshold;
grid.dilation_radius = lightprobe.grid_dilation_radius;
grid.intensity = lightprobe.intensity;
const bool has_valid_cache = grid.cache && grid.cache->grid_static_cache;
grid.viewport_display = has_valid_cache && (lightprobe.flag & LIGHTPROBE_FLAG_SHOW_DATA);
if (grid.viewport_display) {
int3 cache_size = grid.cache->grid_static_cache->size;
float3 scale = math::transform_direction(ob_ref.object_to_world(),
1.0f / float3(cache_size + 1));
grid.viewport_display_size = math::reduce_min(scale) * lightprobe.data_display_size;
}
/* Force reupload. */
inst_.volume_probes.bricks_free(grid.bricks);
}
}
void LightProbeModule::sync_sphere(const ObjectRef &ob_ref)
{
SphereProbe &cube = sphere_map_.lookup_or_add_default(ObjectKey(ob_ref));
cube.used = true;
if (inst_.get_recalc_flags(ob_ref) != 0 || cube.initialized == false) {
const blender::LightProbe &light_probe = DRW_object_get_data_for_drawing<blender::LightProbe>(
*ob_ref.object);
cube.initialized = true;
cube.updated = true;
cube.do_render = true;
SphereProbeModule &probe_module = inst_.sphere_probes;
eLightProbeResolution probe_resolution = sphere_object_resolution_;
int subdivision_lvl = probe_module.subdivision_level_get(probe_resolution);
if (cube.atlas_coord.subdivision_lvl != subdivision_lvl) {
cube.atlas_coord.free();
cube.atlas_coord = find_empty_atlas_region(subdivision_lvl);
SphereProbeData &cube_data = *static_cast<SphereProbeData *>(&cube);
/* Update gpu data sampling coordinates. */
cube_data.atlas_coord = cube.atlas_coord.as_sampling_coord();
/* Coordinates have changed. Area might contain random data. Do not use for rendering. */
cube.use_for_render = false;
}
bool use_custom_parallax = (light_probe.flag & LIGHTPROBE_FLAG_CUSTOM_PARALLAX) != 0;
float influence_distance = light_probe.distinf;
float influence_falloff = light_probe.falloff;
float parallax_distance = light_probe.distpar;
parallax_distance = use_custom_parallax ? max_ff(parallax_distance, influence_distance) :
influence_distance;
auto to_eevee_shape = [](int bl_shape_type) {
return (bl_shape_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID : SHAPE_ELIPSOID;
};
cube.influence_shape = to_eevee_shape(light_probe.attenuation_type);
cube.parallax_shape = to_eevee_shape(use_custom_parallax ? light_probe.parallax_type :
light_probe.attenuation_type);
float4x4 object_to_world = math::scale(ob_ref.object_to_world(), float3(influence_distance));
cube.location = object_to_world.location();
cube.volume = math::abs(math::determinant(object_to_world));
cube.world_to_probe_transposed = float3x4(math::transpose(math::invert(object_to_world)));
cube.influence_scale = 1.0 / max_ff(1e-8f, influence_falloff);
cube.influence_bias = cube.influence_scale;
cube.parallax_distance = parallax_distance / influence_distance;
cube.clipping_distances = float2(light_probe.clipsta, light_probe.clipend);
float3 scale = influence_distance * math::to_scale(ob_ref.object_to_world());
cube.viewport_display = light_probe.flag & LIGHTPROBE_FLAG_SHOW_DATA;
cube.viewport_display_size = light_probe.data_display_size * math::reduce_add(scale / 3.0f);
}
}
void LightProbeModule::sync_planar(const ObjectRef &ob_ref)
{
PlanarProbe &plane = planar_map_.lookup_or_add_default(ObjectKey(ob_ref));
plane.used = true;
if (inst_.get_recalc_flags(ob_ref) != 0 || plane.initialized == false) {
const blender::LightProbe &light_probe = DRW_object_get_data_for_drawing<blender::LightProbe>(
*ob_ref.object);
plane.initialized = true;
plane.updated = true;
plane.plane_to_world = ob_ref.object_to_world();
plane.plane_to_world.z_axis() = math::normalize(plane.plane_to_world.z_axis()) *
light_probe.distinf;
plane.world_to_plane = math::invert(plane.plane_to_world);
plane.clipping_offset = light_probe.clipsta;
plane.viewport_display = (light_probe.flag & LIGHTPROBE_FLAG_SHOW_DATA) != 0;
plane.parallax_distance = light_probe.distpar;
}
}
void LightProbeModule::sync_probe(const ObjectRef &ob_ref)
{
const blender::LightProbe &lightprobe =
DRW_object_get_data_for_drawing<const blender::LightProbe>(*ob_ref.object);
switch (lightprobe.type) {
case LIGHTPROBE_TYPE_SPHERE:
sync_sphere(ob_ref);
return;
case LIGHTPROBE_TYPE_PLANE:
sync_planar(ob_ref);
return;
case LIGHTPROBE_TYPE_VOLUME:
sync_volume(ob_ref);
return;
}
BLI_assert_unreachable();
}
void LightProbeModule::sync_world(const blender::World *world, bool has_update)
{
const eLightProbeResolution probe_resolution = static_cast<eLightProbeResolution>(
world->probe_resolution);
SphereProbeModule &sph_module = inst_.sphere_probes;
int subdivision_lvl = sph_module.subdivision_level_get(probe_resolution);
if (subdivision_lvl != world_sphere_.atlas_coord.subdivision_lvl) {
world_sphere_.atlas_coord.free();
world_sphere_.atlas_coord = find_empty_atlas_region(subdivision_lvl);
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&world_sphere_);
world_data.atlas_coord = world_sphere_.atlas_coord.as_sampling_coord();
has_update = true;
}
if (has_update) {
world_sphere_.do_render = true;
}
}
void LightProbeModule::end_sync()
{
/* Check for deleted or updated grid. */
volume_update_ = false;
volume_map_.remove_if([&](const Map<ObjectKey, VolumeProbe>::MutableItem &item) {
VolumeProbe &grid = item.value;
bool remove_grid = !grid.used;
if (grid.updated || remove_grid) {
volume_update_ = true;
}
grid.updated = false;
grid.used = false;
return remove_grid;
});
/* Check for deleted or updated cube. */
sphere_update_ = false;
sphere_map_.remove_if([&](const Map<ObjectKey, SphereProbe>::MutableItem &item) {
SphereProbe &cube = item.value;
bool remove_cube = !cube.used;
if (cube.updated || remove_cube) {
sphere_update_ = true;
}
cube.updated = false;
cube.used = false;
return remove_cube;
});
/* Check for deleted or updated plane. */
planar_update_ = false;
planar_map_.remove_if([&](const Map<ObjectKey, PlanarProbe>::MutableItem &item) {
PlanarProbe &plane = item.value;
bool remove_plane = !plane.used;
if (plane.updated || remove_plane) {
planar_update_ = true;
}
plane.updated = false;
plane.used = false;
return remove_plane;
});
}
SphereProbeAtlasCoord LightProbeModule::find_empty_atlas_region(int subdivision_level) const
{
int layer_count = sphere_layer_count();
SphereProbeAtlasCoord::LocationFinder location_finder(layer_count, subdivision_level);
location_finder.mark_space_used(world_sphere_.atlas_coord);
for (const SphereProbe &probe : sphere_map_.values()) {
location_finder.mark_space_used(probe.atlas_coord);
}
return location_finder.first_free_spot();
}
int LightProbeModule::sphere_layer_count() const
{
int max_layer = world_sphere_.atlas_coord.atlas_layer;
for (const SphereProbe &probe : sphere_map_.values()) {
max_layer = max_ii(max_layer, probe.atlas_coord.atlas_layer);
}
int layer_count = max_layer + 1;
return layer_count;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name SphereProbeAtlasCoord
* \{ */
SphereProbeAtlasCoord::LocationFinder::LocationFinder(int allocated_layer_count,
int subdivision_level)
{
subdivision_level_ = subdivision_level;
areas_per_dimension_ = 1 << subdivision_level_;
areas_per_layer_ = square_i(areas_per_dimension_);
/* Always add an additional layer to make sure that there is always a free area.
* If this area is chosen the atlas will grow. */
int area_len = (allocated_layer_count + 1) * areas_per_layer_;
areas_occupancy_.resize(area_len, false);
}
void SphereProbeAtlasCoord::LocationFinder::mark_space_used(const SphereProbeAtlasCoord &coord)
{
if (coord.atlas_layer == -1) {
/* Coordinate not allocated yet. */
return;
}
/* The input probe data can be stored in a different subdivision level and should tag all areas
* of the target subdivision level. Shift right if subdivision is higher, left if lower. */
const int shift_right = max_ii(coord.subdivision_lvl - subdivision_level_, 0);
const int shift_left = max_ii(subdivision_level_ - coord.subdivision_lvl, 0);
const int2 pos_in_location_finder = (coord.area_location() >> shift_right) << shift_left;
/* Tag all areas this probe overlaps. */
const int layer_offset = coord.atlas_layer * areas_per_layer_;
const int areas_overlapped_per_dim = 1 << shift_left;
for (const int y : IndexRange(areas_overlapped_per_dim)) {
for (const int x : IndexRange(areas_overlapped_per_dim)) {
const int2 pos = pos_in_location_finder + int2(x, y);
const int area_index = pos.x + pos.y * areas_per_dimension_;
areas_occupancy_[area_index + layer_offset].set();
}
}
}
SphereProbeAtlasCoord SphereProbeAtlasCoord::LocationFinder::first_free_spot() const
{
SphereProbeAtlasCoord result;
result.subdivision_lvl = subdivision_level_;
for (int index : areas_occupancy_.index_range()) {
if (!areas_occupancy_[index]) {
result.atlas_layer = index / areas_per_layer_;
result.area_index = index % areas_per_layer_;
return result;
}
}
/* There should always be a free area. See constructor. */
BLI_assert_unreachable();
return result;
}
void SphereProbeAtlasCoord::LocationFinder::print_debug() const
{
std::ostream &os = std::cout;
int layer = 0, row = 0, column = 0;
os << "subdivision " << subdivision_level_ << "\n";
for (bool spot_taken : areas_occupancy_) {
if (row == 0 && column == 0) {
os << "layer " << layer << "\n";
}
os << (spot_taken ? 'X' : '-');
column++;
if (column == areas_per_dimension_) {
os << "\n";
column = 0;
row++;
}
if (row == areas_per_dimension_) {
row = 0;
layer++;
}
}
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,255 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Module that handles light probe update tagging.
* Lighting data is contained in their respective module `VolumeProbeModule`, `SphereProbeModule`
* and `PlanarProbeModule`.
*/
#pragma once
#include "BLI_bit_vector.hh"
#include "BLI_map.hh"
#include "DNA_world_types.h"
#include "draw_view.hh"
#include "eevee_defines.hh"
#include "eevee_lightprobe_shared.hh"
#include "eevee_sync.hh"
namespace blender::eevee {
using namespace draw;
class Instance;
class VolumeProbeModule;
/* -------------------------------------------------------------------- */
/** \name SphereProbeAtlasCoord
* \{ */
struct SphereProbeAtlasCoord {
/** On which layer of the texture array is this reflection probe stored. */
int atlas_layer = -1;
/** Gives the extent of this probe relative to the atlas size. */
int subdivision_lvl = -1;
/** Area index within the layer with the according subdivision level. */
int area_index = -1;
/** Release the current atlas space held by this probe. */
void free()
{
atlas_layer = -1;
}
/* Return the area extent in pixel. */
int area_extent(int mip_lvl = 0) const
{
return SPHERE_PROBE_ATLAS_RES >> (subdivision_lvl + mip_lvl);
}
/* Coordinate of the area in [0..area_count_per_dimension) range. */
int2 area_location() const
{
const int area_count_per_dimension = 1 << subdivision_lvl;
return int2(area_index % area_count_per_dimension, area_index / area_count_per_dimension);
}
/* Coordinate of the bottom left corner of the area in [0..SPHERE_PROBE_ATLAS_RES) range. */
int2 area_offset(int mip_lvl = 0) const
{
return area_location() * area_extent(mip_lvl);
}
SphereProbeUvArea as_sampling_coord() const
{
SphereProbeUvArea coord;
coord.scale = float(area_extent()) / SPHERE_PROBE_ATLAS_RES;
coord.offset = float2(area_offset()) / SPHERE_PROBE_ATLAS_RES;
coord.layer = atlas_layer;
return coord;
}
SphereProbePixelArea as_write_coord(int mip_lvl) const
{
SphereProbePixelArea coord;
coord.extent = area_extent(mip_lvl);
coord.offset = area_offset(mip_lvl);
coord.layer = atlas_layer;
return coord;
}
/**
* Utility class to find a location in the probe atlas that can be used to store a new probe in
* a specified subdivision level.
*
* The allocation space is subdivided in target subdivision level and is multi layered.
* A layer has `(2 ^ subdivision_lvl) ^ 2` areas.
*
* All allocated probe areas are then process and the candidate areas containing allocated probes
* are marked as occupied. The location finder then return the first available area.
*/
class LocationFinder {
BitVector<> areas_occupancy_;
int subdivision_level_;
/* Area count for the given subdivision level. */
int areas_per_dimension_;
int areas_per_layer_;
public:
LocationFinder(int allocated_layer_count, int subdivision_level);
/* Mark space to be occupied by the given probe_data. */
void mark_space_used(const SphereProbeAtlasCoord &coord);
SphereProbeAtlasCoord first_free_spot() const;
void print_debug() const;
};
};
/** \} */
struct LightProbe {
bool used = false;
bool initialized = false;
/* NOTE: Might be not needed if depsgraph updates work as intended. */
bool updated = false;
/** Display debug visuals in the viewport. */
bool viewport_display = false;
float viewport_display_size = 0.0f;
};
struct VolumeProbe : public LightProbe, VolumeProbeData {
/** Copy of the transform matrix. */
float4x4 object_to_world;
/** Precomputed inverse transform with normalized axes. No position. Used for rotating SH. */
float4x4 world_to_object;
/**
* Reference to the light-cache data.
* Do not try to dereference it before LightProbeModule::end_sync() as the grid could
* already have been freed (along with its cache). It is only safe to dereference after the
* pruning have been done.
*/
const LightProbeObjectCache *cache = nullptr;
/** List of associated atlas bricks that are used by this grid. */
Vector<IrradianceBrickPacked> bricks;
/** True if the grid needs to be re-uploaded & re-composited with other light-grids. */
bool do_update;
/** Index of the grid inside the grid UBO. */
int grid_index;
/** Copy of surfel density for debugging purpose. */
float surfel_density;
/** Copy of DNA members. */
float validity_threshold;
float dilation_threshold;
float dilation_radius;
float intensity;
};
struct SphereProbe : public LightProbe, SphereProbeData {
/** Used to sort the probes by priority. */
float volume;
/** True if the area in the atlas needs to be updated. */
bool do_render = true;
/** False if the area in the atlas contains undefined data. */
bool use_for_render = false;
/** Far and near clipping distances for rendering. */
float2 clipping_distances;
/** Atlas region this probe is rendered at (or will be rendered at). */
SphereProbeAtlasCoord atlas_coord;
};
struct PlanarProbe : public LightProbe, PlanarProbeData {
/* Copy of object matrices. */
float4x4 plane_to_world;
float4x4 world_to_plane;
/* Offset to the clipping plane in the normal direction. */
float clipping_offset;
/* Index in the resource array. */
int resource_index;
public:
/**
* Update the PlanarProbeData part of the struct.
* `view` is the view we want to render this probe with.
*/
void set_view(const draw::View &view, int layer_id);
/**
* Create the reflection clip plane equation that clips along the XY plane of the given
* transform. The `clip_offset` will push the clip plane a bit further to avoid missing pixels in
* reflections. The transform does not need to be normalized but is expected to be orthogonal.
* \note Only works after `set_view` was called.
*/
float4 reflection_clip_plane_get()
{
return float4(-normal, math::dot(normal, plane_to_world.location()) - clipping_offset);
}
private:
/**
* Create the reflection matrix that reflect along the XY plane of the given transform.
* The transform does not need to be normalized but is expected to be orthogonal.
*/
float4x4 reflection_matrix_get()
{
return plane_to_world * math::from_scale<float4x4>(float3(1, 1, -1)) * world_to_plane;
}
};
class LightProbeModule {
friend class IrradianceBake;
friend class VolumeProbeModule;
friend class PlanarProbeModule;
friend class SphereProbeModule;
friend class BackgroundPipeline;
private:
Instance &inst_;
/** Light Probe map to detect deletion and store associated data. */
Map<ObjectKey, VolumeProbe> volume_map_;
Map<ObjectKey, SphereProbe> sphere_map_;
Map<ObjectKey, PlanarProbe> planar_map_;
/* World probe is stored separately. */
SphereProbe world_sphere_;
/** True if a light-probe update was detected. */
bool volume_update_;
bool sphere_update_;
bool planar_update_;
/** True if the auto bake feature is enabled & available in this context. */
bool auto_bake_enabled_;
eLightProbeResolution sphere_object_resolution_ = LIGHT_PROBE_RESOLUTION_128;
public:
LightProbeModule(Instance &inst);
~LightProbeModule() {};
void init();
void begin_sync();
void sync_probe(const ObjectRef &ob_ref);
void sync_world(const blender::World *world, bool has_update);
void end_sync();
private:
void sync_sphere(const ObjectRef &ob_ref);
void sync_volume(const ObjectRef &ob_ref);
void sync_planar(const ObjectRef &ob_ref);
/** Get the number of atlas layers needed to store light probe spheres. */
int sphere_layer_count() const;
/** Returns coordinates of an area in the atlas for a probe with the given subdivision level. */
SphereProbeAtlasCoord find_empty_atlas_region(int subdivision_level) const;
};
} // namespace blender::eevee

View File

@@ -0,0 +1,207 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "eevee_lightprobe_planar.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
using namespace blender::math;
/* -------------------------------------------------------------------- */
/** \name Planar Probe
* \{ */
void PlanarProbe::set_view(const draw::View &view, int layer_id)
{
/* Invert the up axis to avoid changing handedness (see #137022). */
this->viewmat = from_scale<float4x4>(float3(1, -1, 1)) * view.viewmat() *
reflection_matrix_get();
this->winmat = view.winmat();
/* Invert Y offset in the projection matrix to compensate the flip above (see #141112). */
this->winmat[2][1] = -this->winmat[2][1];
this->wininv = invert(this->winmat);
this->world_to_object_transposed = float3x4(transpose(world_to_plane));
this->normal = normalize(plane_to_world.z_axis());
float3 view_vec = view.is_persp() ? view.location() - plane_to_world.location() : view.forward();
bool view_is_below_plane = dot(view_vec, plane_to_world.z_axis()) < 0.0;
if (view_is_below_plane) {
this->normal = -this->normal;
}
this->layer_id = layer_id;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Planar Probe Module
* \{ */
void PlanarProbeModule::init()
{
/* This triggers the compilation of clipped shader only if we can detect light-probe planes. */
if (inst_.is_viewport() && !inst_.is_image_render) {
/* This check needs to happen upfront before sync, so we use the previous sync result. */
update_probes_ = !inst_.light_probes.planar_map_.is_empty();
}
else {
/* TODO(jbakker): should we check on the subtype as well? Now it also populates even when
* there are other light probes in the scene. */
update_probes_ = DEG_id_type_any_exists(inst_.depsgraph, ID_LP);
}
do_display_draw_ = false;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ;
/* Tag the end of the array. */
dummy_resources.dummy_probe_planar_buf_[0].layer_id = -1;
dummy_resources.dummy_probe_planar_buf_.push_update();
dummy_resources.dummy_radiance_tx_.ensure_2d_array(
gpu::TextureFormat::UFLOAT_11_11_10, int2(1), 1, usage);
dummy_resources.dummy_depth_tx_.ensure_2d_array(
gpu::TextureFormat::SFLOAT_32_DEPTH, int2(1), 1, usage);
}
void PlanarProbeModule::end_sync()
{
/* When first planar probes are enabled it can happen that the first sample is off. */
if (!update_probes_ && !inst_.light_probes.planar_map_.is_empty()) {
DRW_viewport_request_redraw();
}
}
void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_extent)
{
GBuffer &gbuf = inst_.gbuffer;
const int64_t num_probes = inst_.light_probes.planar_map_.size();
/* TODO resolution percentage. */
int2 extent = main_view_extent;
int layer_count = num_probes;
const gpu::TextureFormat depth_format = gpu::TextureFormat::SFLOAT_32_DEPTH;
if (num_probes == 0) {
/* Create valid dummy texture. */
extent = int2(1);
layer_count = 1;
}
else {
inst_.render_buffers.acquire(extent, depth_format);
}
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ;
radiance_tx_.ensure_2d_array(gpu::TextureFormat::UFLOAT_11_11_10, extent, layer_count, usage);
depth_tx_.ensure_2d_array(depth_format, extent, layer_count, usage);
depth_tx_.ensure_layer_views();
do_display_draw_ = inst_.draw_overlays && num_probes > 0;
int resource_index = 0;
int display_index = 0;
for (PlanarProbe &probe : inst_.light_probes.planar_map_.values()) {
if (resource_index == PLANAR_PROBE_MAX) {
break;
}
PlanarResources &res = resources_[resource_index];
/* TODO Cull out of view planars. */
probe.set_view(main_view, resource_index);
probe_planar_buf_[resource_index] = probe;
res.view.sync(probe.viewmat, probe.winmat);
world_clip_buf_.plane = probe.reflection_clip_plane_get();
world_clip_buf_.push_update();
RenderBuffers &rbufs = inst_.render_buffers;
const bool with_raycast = inst_.pipelines.has_raycast;
res.prepass_fb.ensure(
GPU_ATTACHMENT_TEXTURE_LAYER(depth_tx_, resource_index),
with_raycast ? GPU_ATTACHMENT_TEXTURE(rbufs.prepass_normal_tx) : GPU_ATTACHMENT_NONE,
with_raycast ? GPU_ATTACHMENT_TEXTURE(rbufs.object_id_tx) : GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_NONE /* motion vectors */);
if (with_raycast) {
rbufs.object_id_tx.clear(uint4(0));
rbufs.prepass_normal_tx.clear(float4(0.0f));
}
gbuf.acquire(extent,
inst_.pipelines.deferred.header_layer_count(),
inst_.pipelines.deferred.closure_layer_count(),
inst_.pipelines.deferred.normal_layer_count());
res.combined_fb.ensure(GPU_ATTACHMENT_TEXTURE_LAYER(depth_tx_, resource_index),
GPU_ATTACHMENT_TEXTURE_LAYER(radiance_tx_, resource_index));
res.gbuffer_fb.ensure(GPU_ATTACHMENT_TEXTURE_LAYER(depth_tx_, resource_index),
GPU_ATTACHMENT_TEXTURE_LAYER(radiance_tx_, resource_index),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.header_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.normal_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.closure_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.closure_tx.layer_view(1), 0));
inst_.pipelines.planar.render(res.view,
depth_tx_.layer_view(resource_index),
res.prepass_fb,
res.gbuffer_fb,
res.combined_fb,
extent);
if (do_display_draw_ && probe.viewport_display) {
display_data_buf_.get_or_resize(display_index++) = {probe.plane_to_world, resource_index};
}
resource_index++;
}
gbuf.release();
if (resource_index < PLANAR_PROBE_MAX) {
/* Tag the end of the array. */
probe_planar_buf_[resource_index].layer_id = -1;
}
probe_planar_buf_.push_update();
do_display_draw_ = display_index > 0;
if (do_display_draw_) {
display_data_buf_.resize(display_index);
display_data_buf_.push_update();
}
}
void PlanarProbeModule::viewport_draw(View &view, gpu::FrameBuffer *view_fb)
{
if (!do_display_draw_) {
return;
}
viewport_display_ps_.init();
viewport_display_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
DRW_STATE_CLIP_CONTROL_UNIT_RANGE | inst_.film.depth.test_state |
DRW_STATE_CULL_BACK);
viewport_display_ps_.framebuffer_set(&view_fb);
viewport_display_ps_.shader_set(inst_.shaders.static_shader_get(DISPLAY_PROBE_PLANAR));
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&inst_.light_probes.world_sphere_);
viewport_display_ps_.push_constant("world_coord_packed",
reinterpret_cast<int4 *>(&world_data.atlas_coord));
viewport_display_ps_.bind_resources(*this);
viewport_display_ps_.bind_resources(inst_.sphere_probes);
viewport_display_ps_.bind_ssbo("display_data_buf", display_data_buf_);
viewport_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, display_data_buf_.size() * 6);
inst_.manager->submit(viewport_display_ps_, view);
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,102 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#pragma once
#include "eevee_defines.hh"
#include "eevee_lightprobe_shared.hh"
#include "eevee_uniform_shared.hh"
#include "draw_pass.hh"
#include "draw_view.hh"
namespace blender::eevee {
using namespace draw;
class Instance;
class HiZBuffer;
/* -------------------------------------------------------------------- */
/** \name Planar Probe Module
* \{ */
using ClipPlaneBuf = draw::UniformBuffer<ClipPlaneData>;
using PlanarProbeDataBuf = draw::UniformArrayBuffer<PlanarProbeData, PLANAR_PROBE_MAX>;
using PlanarProbeDisplayDataBuf = draw::StorageArrayBuffer<PlanarProbeDisplayData>;
class PlanarProbeModule {
friend class Instance;
friend class HiZBuffer;
friend class PlanarProbePipeline;
private:
Instance &inst_;
struct PlanarResources : NonCopyable {
Framebuffer prepass_fb = {"planar.prepass_fb"};
Framebuffer combined_fb = {"planar.combined_fb"};
Framebuffer gbuffer_fb = {"planar.gbuffer_fb"};
draw::View view = {"planar.view"};
};
std::array<PlanarResources, PLANAR_PROBE_MAX> resources_;
Texture radiance_tx_ = {"planar.radiance_tx"};
Texture depth_tx_ = {"planar.depth_tx"};
ClipPlaneBuf world_clip_buf_ = {"world_clip_buf"};
PlanarProbeDataBuf probe_planar_buf_ = {"probe_planar_buf"};
bool update_probes_ = false;
/** Viewport data display drawing. */
bool do_display_draw_ = false;
PlanarProbeDisplayDataBuf display_data_buf_;
PassSimple viewport_display_ps_ = {"PlanarProbeModule.Viewport Display"};
public:
PlanarProbeModule(Instance &instance) : inst_(instance) {}
void init();
void end_sync();
void set_view(const draw::View &main_view, int2 main_view_extent);
void viewport_draw(View &view, gpu::FrameBuffer *view_fb);
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_ubo(PLANAR_PROBE_BUF_SLOT, &probe_planar_buf_);
pass.bind_texture(PLANAR_PROBE_RADIANCE_TEX_SLOT, &radiance_tx_);
pass.bind_texture(PLANAR_PROBE_DEPTH_TEX_SLOT, &depth_tx_);
}
/** Used when updating the planar probe. Needed to avoid feedback loop. */
struct Dummy {
PlanarProbeDataBuf dummy_probe_planar_buf_ = {"probe_planar_buf"};
Texture dummy_radiance_tx_ = {"planar.dummy_radiance_tx"};
Texture dummy_depth_tx_ = {"planar.dummy_depth_tx"};
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_ubo(PLANAR_PROBE_BUF_SLOT, dummy_probe_planar_buf_);
pass.bind_texture(PLANAR_PROBE_RADIANCE_TEX_SLOT, dummy_radiance_tx_);
pass.bind_texture(PLANAR_PROBE_DEPTH_TEX_SLOT, dummy_depth_tx_);
}
} dummy_resources;
bool enabled() const
{
return update_probes_;
}
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,285 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#include "eevee_camera_shared.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
/* -------------------------------------------------------------------- */
/** \name Probe Spheres
* \{ */
struct [[host_shared]] ReflectionProbeLowFreqLight {
packed_float3 direction;
float ambient;
};
enum [[host_shared]] LightProbeShape : uint32_t {
SHAPE_ELIPSOID,
SHAPE_CUBOID,
};
/* Sampling coordinates using UV space. */
struct [[host_shared]] SphereProbeUvArea {
/* Offset in UV space to the start of the sampling space of the octahedron map. */
float2 offset;
/* Scaling of the squared UV space of the octahedron map. */
float scale;
/* Layer of the atlas where the octahedron map is stored. */
float layer;
};
/* Pixel read/write coordinates using pixel space. */
struct [[host_shared]] SphereProbePixelArea {
/* Offset in pixel space to the start of the writing space of the octahedron map.
* Note that the writing space is not the same as the sampling space as we have borders. */
int2 offset;
/* Size of the area in pixel that is covered by this probe mip-map. */
int extent;
/* Layer of the atlas where the octahedron map is stored. */
int layer;
};
/** Mapping data to locate a reflection probe in texture. */
struct [[host_shared]] SphereProbeData {
/** Transform to probe local position with non-uniform scaling. */
float3x4 world_to_probe_transposed;
packed_float3 location;
/** Shape of the parallax projection. */
float parallax_distance;
enum LightProbeShape parallax_shape;
enum LightProbeShape influence_shape;
/** Influence factor based on the distance to the parallax shape. */
float influence_scale;
float influence_bias;
struct SphereProbeUvArea atlas_coord;
/**
* Irradiance at the probe location encoded as spherical harmonics.
* Only contain the average luminance. Used for cube-map normalization.
*/
struct ReflectionProbeLowFreqLight low_freq_light;
};
/** Viewport Display Pass. */
struct [[host_shared]] SphereProbeDisplayData {
int probe_index;
float display_size;
float _pad0;
float _pad1;
};
/* Used for sphere probe spherical harmonics extraction. Output one for each thread-group
* and do a sum afterward. Reduces bandwidth usage. */
struct [[host_shared]] SphereProbeHarmonic {
float4 L0_M0;
float4 L1_Mn1;
float4 L1_M0;
float4 L1_Mp1;
};
struct [[host_shared]] SphereProbeSunLight {
float4 direction;
packed_float3 radiance;
float _pad0;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Planar Volume
* \{ */
struct [[host_shared]] PlanarProbeData {
/** Matrices used to render the planar capture. */
float4x4 viewmat;
float4x4 winmat;
float4x4 wininv;
/** Transform world to local position with influence distance as Z scale. */
float3x4 world_to_object_transposed;
/** World space plane normal. */
packed_float3 normal;
/** Layer in the planar capture textures used by this probe. */
int layer_id;
/** Parallax effect for forward evaluation. */
float parallax_distance;
float _pad0;
float _pad1;
float _pad2;
};
/** Viewport Display Pass. */
struct [[host_shared]] PlanarProbeDisplayData {
float4x4 plane_to_world;
int probe_index;
float _pad0;
float _pad1;
float _pad2;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Probe Volume
* \{ */
struct [[host_shared]] VolumeProbeData {
/** World to non-normalized local grid space [0..size-1]. Stored transposed for compactness. */
float3x4 world_to_grid_transposed;
/** Number of bricks for this grid. */
packed_int3 grid_size_padded;
/** Index in brick descriptor list of the first brick of this grid. */
int brick_offset;
/** Biases to apply to the shading point in order to sample a valid probe. */
float normal_bias;
float view_bias;
float facing_bias;
int _pad1;
};
struct IrradianceBrick {
/* Offset in pixel to the start of the data inside the atlas texture. */
uint2 atlas_coord;
};
/** \note Stored packed as a uint. */
#define IrradianceBrickPacked uint
static inline IrradianceBrickPacked irradiance_brick_pack(IrradianceBrick brick)
{
uint2 data = (uint2(brick.atlas_coord) & 0xFFFFu) << uint2(0u, 16u);
IrradianceBrickPacked brick_packed = data.x | data.y;
return brick_packed;
}
static inline IrradianceBrick irradiance_brick_unpack(IrradianceBrickPacked brick_packed)
{
IrradianceBrick brick;
brick.atlas_coord = (uint2(brick_packed) >> uint2(0u, 16u)) & uint2(0xFFFFu);
return brick;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Baking structures
* \{ */
struct [[host_shared]] SurfelRadiance {
/* Actually stores radiance and world (sky) visibility. Stored normalized. */
float4 front;
float4 back;
/* Accumulated weights per face. */
float front_weight;
float back_weight;
float _pad0;
float _pad1;
};
struct [[host_shared]] Surfel {
/** World position of the surfel. */
packed_float3 position;
/** Previous surfel index in the ray link-list. Only valid after sorting. */
int prev;
/** World orientation of the surface. */
packed_float3 normal;
/** Next surfel index in the ray link-list. */
int next;
/** Surface albedo to apply to incoming radiance. */
packed_float3 albedo_front;
/** Distance along the ray direction for sorting. */
float ray_distance;
/** Surface albedo to apply to incoming radiance. */
packed_float3 albedo_back;
/** Cluster this surfel is assigned to. */
int cluster_id;
/** True if the light can bounce or be emitted by the surfel back face. */
bool32_t double_sided;
/** Surface receiver light set for light linking. */
uint receiver_light_set;
/** List index this surfel is in. */
int list_id;
/** Index of this surfel inside the sorted list. Allow access to previous and next surfel id. */
int index_in_sorted_list;
/** Surface radiance: Emission + Direct Lighting. */
struct SurfelRadiance radiance_direct;
/** Surface radiance: Indirect Lighting. Double buffered to avoid race conditions. */
struct SurfelRadiance radiance_indirect[2];
};
struct [[host_shared]] CaptureInfoData {
/** Grid size without padding. */
packed_int3 irradiance_grid_size;
/** True if the surface shader needs to write the surfel data. */
bool32_t do_surfel_output;
/** True if the surface shader needs to increment the surfel_len. */
bool32_t do_surfel_count;
/** Number of surfels inside the surfel buffer or the needed len. */
uint surfel_len;
/** Total number of a ray for light transportation. */
float sample_count;
/** 0 based sample index. */
float sample_index;
/** Transform of the light-probe object. */
float4x4 irradiance_grid_local_to_world;
/** Transform of the light-probe object. */
float4x4 irradiance_grid_world_to_local;
/** Transform vectors from world space to local space. Does not have location component. */
/** TODO(fclem): This could be a float3x4 or a float3x3 if padded correctly. */
float4x4 irradiance_grid_world_to_local_rotation;
/** Scene bounds. Stored as min & max and as int for atomic operations. */
int scene_bound_x_min;
int scene_bound_y_min;
int scene_bound_z_min;
int scene_bound_x_max;
int scene_bound_y_max;
int scene_bound_z_max;
/* Max intensity a ray can have. */
float clamp_direct;
float clamp_indirect;
float _pad1;
float _pad2;
/** Minimum distance between a grid sample and a surface. Used to compute virtual offset. */
float min_distance_to_surface;
/** Maximum world scale offset an irradiance grid sample can be baked with. */
float max_virtual_offset;
/** Radius of surfels. */
float surfel_radius;
/** Capture options. */
bool32_t capture_world_direct;
bool32_t capture_world_indirect;
bool32_t capture_visibility_direct;
bool32_t capture_visibility_indirect;
bool32_t capture_indirect;
bool32_t capture_emission;
int _pad0;
/* World light probe atlas coordinate. */
struct SphereProbeUvArea world_atlas_coord;
};
struct [[host_shared]] SurfelListInfoData {
/** Size of the grid used to project the surfels into linked lists. */
int2 ray_grid_size;
/** Maximum number of list. Is equal to `ray_grid_size.x * ray_grid_size.y`. */
int list_max;
int list_prefix_sum;
};
/** \} */
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,377 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "eevee_lightprobe_sphere.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Reflection Probe Module
* \{ */
int SphereProbeModule::probe_render_extent() const
{
return instance_.scene->eevee.gi_cubemap_resolution / 2;
}
void SphereProbeModule::init()
{
if (!instance_.is_viewport() || instance_.is_image_render) {
/* TODO(jbakker): should we check on the subtype as well? Now it also populates even when
* there are other light probes in the scene. */
update_probes_next_sample_ = DEG_id_type_any_exists(instance_.depsgraph, ID_LP);
}
update_probes_this_sample_ = update_probes_next_sample_;
do_display_draw_ = false;
}
void SphereProbeModule::begin_sync()
{
LightProbeModule &light_probes = instance_.light_probes;
SphereProbeData &world_data = *static_cast<SphereProbeData *>(&light_probes.world_sphere_);
{
gpu::Shader *shader = instance_.shaders.static_shader_get(SPHERE_PROBE_REMAP);
PassSimple &pass = remap_ps_;
pass.init();
pass.specialize_constant(shader, "extract_sh", &extract_sh_);
pass.specialize_constant(shader, "extract_sun", &extract_sun_);
pass.shader_set(shader);
pass.bind_texture("cubemap_tx", &cubemap_tx_);
pass.bind_texture("atlas_tx", &probes_tx_);
pass.bind_image("atlas_img", &probes_tx_);
pass.bind_ssbo("out_sh", &tmp_spherical_harmonics_);
pass.bind_ssbo("out_sun", &tmp_sunlight_);
pass.push_constant("do_remap_mip0", &do_remap_mip0_);
pass.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&probe_sampling_coord_));
pass.push_constant("write_coord_packed", reinterpret_cast<int4 *>(&probe_write_coord_));
pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_data.atlas_coord));
pass.bind_resources(instance_.uniform_data);
pass.dispatch(&dispatch_probe_pack_);
}
{
PassSimple &pass = convolve_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_CONVOLVE));
pass.bind_texture("cubemap_tx", &cubemap_tx_);
pass.bind_texture("in_atlas_mip_tx", &convolve_input_);
pass.bind_image("out_atlas_mip_img", &convolve_output_);
pass.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&probe_sampling_coord_));
pass.push_constant("write_coord_packed", reinterpret_cast<int4 *>(&probe_write_coord_));
pass.push_constant("read_coord_packed", reinterpret_cast<int4 *>(&probe_read_coord_));
pass.push_constant("read_lod", &convolve_lod_);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
pass.dispatch(&dispatch_probe_convolve_);
}
{
PassSimple &pass = sum_sh_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_IRRADIANCE));
pass.push_constant("probe_remap_dispatch_size", &dispatch_probe_pack_);
pass.bind_ssbo("in_sh", &tmp_spherical_harmonics_);
pass.bind_ssbo("out_sh", &spherical_harmonics_);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
pass.dispatch(1);
}
{
PassSimple &pass = sum_sun_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SUNLIGHT));
pass.push_constant("sun_id", &extract_sun_index_);
pass.push_constant("probe_remap_dispatch_size", &dispatch_probe_pack_);
pass.bind_ssbo("in_sun", &tmp_sunlight_);
pass.bind_ssbo("sunlight_buf", &instance_.world.sunlight);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
pass.dispatch(1);
pass.barrier(GPU_BARRIER_UNIFORM);
}
{
PassSimple &pass = select_ps_;
pass.init();
pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SELECT));
pass.push_constant("lightprobe_sphere_count", &lightprobe_sphere_count_);
pass.bind_ssbo("lightprobe_sphere_buf", &data_buf_);
instance_.volume_probes.bind_resources(pass);
instance_.sampling.bind_resources(pass);
pass.bind_resources(instance_.uniform_data);
pass.dispatch(&dispatch_probe_select_);
pass.barrier(GPU_BARRIER_UNIFORM);
}
}
bool SphereProbeModule::ensure_atlas()
{
/* Make sure the atlas is always initialized even if there is nothing to render to it to fulfill
* the resource bindings. */
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ;
if (probes_tx_.ensure_2d_array(gpu::TextureFormat::SPHERE_PROBE_FORMAT,
int2(SPHERE_PROBE_ATLAS_RES),
instance_.light_probes.sphere_layer_count(),
usage,
nullptr,
SPHERE_PROBE_MIPMAP_LEVELS))
{
probes_tx_.ensure_mip_views();
/* TODO(fclem): Clearing means that we need to render all probes again.
* If existing data exists, copy it using `CopyImageSubData`. */
for (auto i : IndexRange(SPHERE_PROBE_MIPMAP_LEVELS)) {
/* Avoid undefined pixel data. Clear all mips. */
float4 data(0.0f);
GPU_texture_clear(probes_tx_.mip_view(i), GPU_DATA_FLOAT, &data);
}
GPU_texture_mipmap_mode(probes_tx_, true, true);
return true;
}
return false;
}
void SphereProbeModule::end_sync()
{
const bool atlas_resized = ensure_atlas();
if (atlas_resized) {
instance_.light_probes.world_sphere_.do_render = true;
}
const bool world_updated = instance_.light_probes.world_sphere_.do_render;
/* Detect if we need to render probe objects. */
update_probes_next_sample_ = false;
for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
if (atlas_resized || world_updated) {
/* Last minute tagging. */
probe.do_render = true;
}
if (probe.do_render) {
/* Tag the next redraw to warm up the probe pipeline.
* Keep doing this until there is no update.
* This avoids stuttering when moving a light-probe. */
update_probes_next_sample_ = true;
}
}
if (instance_.is_viewport()) {
/* When reflection probes are synced the sampling must be reset.
*
* This fixes issues when using a single non-projected sample. Without resetting the
* previous rendered viewport will be drawn and reflection probes will not be updated.
* #Instance::render_sample */
if (instance_.do_lightprobe_sphere_sync()) {
instance_.sampling.reset();
}
/* If we cannot render probes this redraw make sure we request another redraw. */
if (update_probes_next_sample_ && (instance_.do_lightprobe_sphere_sync() == false)) {
DRW_viewport_request_redraw();
}
}
}
void SphereProbeModule::ensure_cubemap_render_target(int resolution)
{
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ;
cubemap_tx_.ensure_cube(gpu::TextureFormat::SFLOAT_16_16_16_16, resolution, usage);
/* TODO(fclem): deallocate it. */
}
SphereProbeModule::UpdateInfo SphereProbeModule::update_info_from_probe(SphereProbe &probe)
{
SphereProbeModule::UpdateInfo info = {};
info.atlas_coord = probe.atlas_coord;
info.cube_target_extent = probe.atlas_coord.area_extent() / 2;
info.clipping_distances = probe.clipping_distances;
info.probe_pos = probe.location;
info.do_render = probe.do_render;
probe.do_render = false;
probe.use_for_render = true;
ensure_cubemap_render_target(info.cube_target_extent);
return info;
}
const SphereProbe &SphereProbeModule::world_sphere_probe() const
{
return instance_.light_probes.world_sphere_;
}
std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::world_update_info_pop()
{
SphereProbe &world_probe = instance_.light_probes.world_sphere_;
if (world_probe.do_render) {
return update_info_from_probe(world_probe);
}
return std::nullopt;
}
std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::probe_update_info_pop()
{
if (!instance_.do_lightprobe_sphere_sync()) {
/* Do not update probes during this sample as we did not sync the draw::Passes. */
return std::nullopt;
}
for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
if (!probe.do_render) {
continue;
}
return update_info_from_probe(probe);
}
return std::nullopt;
}
void SphereProbeModule::remap_to_octahedral_projection(const SphereProbeAtlasCoord &atlas_coord,
bool convolve_octahedral,
bool extract_spherical_harmonics,
int extract_sun_index)
{
/* Update shader parameters that change per dispatch. */
probe_sampling_coord_ = atlas_coord.as_sampling_coord();
probe_write_coord_ = atlas_coord.as_write_coord(0);
int resolution = probe_write_coord_.extent;
dispatch_probe_pack_ = int3(
int2(math::divide_ceil(int2(resolution), int2(SPHERE_PROBE_REMAP_GROUP_SIZE))), 1);
extract_sh_ = extract_spherical_harmonics;
extract_sun_ = extract_sun_index != -1;
do_remap_mip0_ = convolve_octahedral;
instance_.manager->submit(remap_ps_);
if (convolve_octahedral) {
/* Populate the mip levels */
for (auto i : IndexRange(SPHERE_PROBE_MIPMAP_LEVELS - 1)) {
convolve_lod_ = i;
convolve_input_ = probes_tx_.mip_view(i);
convolve_output_ = probes_tx_.mip_view(i + 1);
probe_read_coord_ = atlas_coord.as_write_coord(i);
probe_write_coord_ = atlas_coord.as_write_coord(i + 1);
int out_mip_res = probe_write_coord_.extent;
dispatch_probe_convolve_ = int3(
math::divide_ceil(int2(out_mip_res), int2(SPHERE_PROBE_GROUP_SIZE)), 1);
instance_.manager->submit(convolve_ps_);
}
}
/* This is only true for the world probe. */
if (extract_spherical_harmonics) {
instance_.manager->submit(sum_sh_ps_);
}
if (extract_sun_) {
extract_sun_index_ = extract_sun_index;
instance_.manager->submit(sum_sun_ps_);
}
if (extract_spherical_harmonics || extract_sun_) {
instance_.lookdev.store_world_probe_data(
probes_tx_, atlas_coord, spherical_harmonics_, instance_.world.sunlight);
}
/* Sync with atlas usage for shading. */
GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH);
}
void SphereProbeModule::set_view(View & /*view*/)
{
Vector<SphereProbe *> probe_active;
for (auto &probe : instance_.light_probes.sphere_map_.values()) {
/* Last slot is reserved for the world probe. */
if (lightprobe_sphere_count_ >= SPHERE_PROBE_MAX - 1) {
break;
}
if (!probe.use_for_render) {
continue;
}
/* TODO(fclem): Culling. */
probe_active.append(&probe);
}
/* Stable sorting of probes. */
std::ranges::sort(probe_active, [](const SphereProbe *a, const SphereProbe *b) {
if (a->volume != b->volume) {
/* Smallest first. */
return a->volume < b->volume;
}
/* Volumes are identical. Any arbitrary criteria can be used to sort them.
* Use position to avoid unstable result caused by depsgraph non deterministic eval
* order. This could also become a priority parameter. */
float3 _a = a->location;
float3 _b = b->location;
if (_a.x != _b.x) {
return _a.x < _b.x;
}
if (_a.y != _b.y) {
return _a.y < _b.y;
}
if (_a.z != _b.z) {
return _a.z < _b.z;
}
/* Fallback to memory address, since there's no good alternative. */
return a < b;
});
/* Push all sorted data to the UBO. */
int probe_id = 0;
for (auto &probe : probe_active) {
data_buf_[probe_id++] = *probe;
}
/* Add world probe at the end. */
data_buf_[probe_id++] = instance_.light_probes.world_sphere_;
/* Tag the end of the array. */
if (probe_id < SPHERE_PROBE_MAX) {
data_buf_[probe_id].atlas_coord.layer = -1;
}
data_buf_.push_update();
lightprobe_sphere_count_ = probe_id;
dispatch_probe_select_.x = divide_ceil_u(lightprobe_sphere_count_,
SPHERE_PROBE_SELECT_GROUP_SIZE);
instance_.manager->submit(select_ps_);
sync_display(probe_active);
}
void SphereProbeModule::sync_display(Vector<SphereProbe *> &probe_active)
{
do_display_draw_ = false;
if (!instance_.draw_overlays) {
return;
}
int display_index = 0;
for (int i : probe_active.index_range()) {
if (probe_active[i]->viewport_display) {
SphereProbeDisplayData &sph_data = display_data_buf_.get_or_resize(display_index++);
sph_data.probe_index = i;
sph_data.display_size = probe_active[i]->viewport_display_size;
}
}
if (display_index == 0) {
return;
}
do_display_draw_ = true;
display_data_buf_.resize(display_index);
display_data_buf_.push_update();
}
void SphereProbeModule::viewport_draw(View &view, gpu::FrameBuffer *view_fb)
{
if (!do_display_draw_) {
return;
}
viewport_display_ps_.init();
viewport_display_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
DRW_STATE_CLIP_CONTROL_UNIT_RANGE |
instance_.film.depth.test_state | DRW_STATE_CULL_BACK);
viewport_display_ps_.framebuffer_set(&view_fb);
viewport_display_ps_.shader_set(instance_.shaders.static_shader_get(DISPLAY_PROBE_SPHERE));
viewport_display_ps_.bind_resources(*this);
viewport_display_ps_.bind_ssbo("display_data_buf", display_data_buf_);
viewport_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, display_data_buf_.size() * 6);
instance_.manager->submit(viewport_display_ps_, view);
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,224 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#pragma once
#include "DNA_world_types.h"
#include "draw_pass.hh"
#include "eevee_lightprobe.hh"
#include "eevee_lightprobe_shared.hh"
namespace blender::eevee {
using namespace draw;
class Instance;
class CaptureView;
class LookdevModule;
/* -------------------------------------------------------------------- */
/** \name Reflection Probe Module
* \{ */
using SphereProbeDataBuf = draw::UniformArrayBuffer<SphereProbeData, SPHERE_PROBE_MAX>;
using SphereProbeDisplayDataBuf = draw::StorageArrayBuffer<SphereProbeDisplayData>;
class SphereProbeModule {
friend LightProbeModule;
/* Capture View requires access to the probe texture for frame-buffer configuration. */
friend class CaptureView;
/* Instance requires access to #update_probes_this_sample_ */
friend class Instance;
private:
Instance &instance_;
SphereProbeDataBuf data_buf_;
/** Probes texture stored in octahedral mapping. */
Texture probes_tx_ = {"Probes"};
/** Copy the rendered cube-map to the atlas texture. */
PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"};
/** Sum irradiance information optionally extracted during `remap_ps_`. */
PassSimple sum_sh_ps_ = {"Probe.SumSphericalHarmonics"};
/** Sum sunlight information optionally extracted during `remap_ps_`. */
PassSimple sum_sun_ps_ = {"Probe.SumSunlight"};
/** Copy volume probe irradiance for the center of sphere probes. */
PassSimple select_ps_ = {"Probe.Select"};
/** Convolve the octahedral map to fill the Mip-map levels. */
PassSimple convolve_ps_ = {"Probe.Convolve"};
/** Input mip level for the convolution. */
gpu::Texture *convolve_input_ = nullptr;
/** Output mip level for the convolution. */
gpu::Texture *convolve_output_ = nullptr;
int convolve_lod_ = 0;
/* True if we extract spherical harmonic during `remap_ps_`. */
bool extract_sh_ = false;
/* True if we extract sun during `remap_ps_`. */
bool extract_sun_ = false;
/* True if we actually remap the data during `remap_ps_`. */
bool32_t do_remap_mip0_ = false;
int3 dispatch_probe_pack_ = int3(1);
int3 dispatch_probe_convolve_ = int3(1);
int3 dispatch_probe_select_ = int3(1);
/**
* Texture containing a cube-map where the probe should be rendering to.
*
* NOTE: TextureFromPool doesn't support cube-maps.
*/
Texture cubemap_tx_ = {"Probe.Cubemap"};
/** Index of the probe being updated. */
int probe_index_ = 0;
/** Updated Probe coordinates in the atlas. */
SphereProbeUvArea probe_sampling_coord_;
SphereProbePixelArea probe_write_coord_;
/** Source Probe coordinates in the atlas. */
SphereProbePixelArea probe_read_coord_;
/** World coordinates in the atlas. */
SphereProbeUvArea world_sampling_coord_;
/** Number of the probe to process in the select phase. */
int lightprobe_sphere_count_ = 0;
/** Intermediate buffer to store spherical harmonics. */
StorageArrayBuffer<SphereProbeHarmonic, SPHERE_PROBE_MAX_HARMONIC, true>
tmp_spherical_harmonics_ = {"tmp_spherical_harmonics_"};
/** Final buffer containing the spherical harmonics for the world. */
StorageBuffer<SphereProbeHarmonic, true> spherical_harmonics_ = {"spherical_harmonics_"};
/** Intermediate buffer to store sun light. */
StorageArrayBuffer<SphereProbeSunLight, SPHERE_PROBE_MAX_HARMONIC, true> tmp_sunlight_ = {
"tmp_sunlight_"};
/** Destination index for the final extracted sun. */
int extract_sun_index_ = -1;
/**
* True if the next redraw will trigger a light-probe sphere update.
* As syncing the draw passes for rendering has a significant overhead,
* we only trigger this sync path if we detect updates. But we only know
* this after `end_sync` which is too late to sync objects for light-probe
* rendering. So we tag the next redraw (or sample) to do the sync.
*/
bool update_probes_next_sample_ = false;
/** True if this redraw will trigger a light-probe sphere update. */
bool update_probes_this_sample_ = false;
/** Compute world irradiance coefficient and store them into the volume probe atlas. */
bool do_world_irradiance_update = true;
/** Viewport data display drawing. */
bool do_display_draw_ = false;
SphereProbeDisplayDataBuf display_data_buf_;
PassSimple viewport_display_ps_ = {"ProbeSphereModule.Viewport Display"};
public:
SphereProbeModule(Instance &instance) : instance_(instance) {};
void init();
void begin_sync();
void end_sync();
void viewport_draw(View &view, gpu::FrameBuffer *view_fb);
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_texture(SPHERE_PROBE_TEX_SLOT, &probes_tx_);
pass.bind_ubo(SPHERE_PROBE_BUF_SLOT, &data_buf_);
}
/**
* Select which probes are used for rendering.
* NOTE: Must run after `volume_probe.set_view` as it reads the volume probe data.
*/
void set_view(View &view);
/**
* Get the resolution of a single cube-map side when rendering probes.
*
* The cube-maps are rendered half size of the size of the octahedral texture.
*/
int probe_render_extent() const;
StorageBuffer<SphereProbeHarmonic, true> &spherical_harmonics_buf()
{
return spherical_harmonics_;
}
const SphereProbe &world_sphere_probe() const;
Texture &octahedral_probes_texture()
{
return probes_tx_;
}
private:
/* Return the subdivision level for the requested probe resolution.
* Result is safely clamped to max resolution. */
int subdivision_level_get(const eLightProbeResolution probe_resolution)
{
return max_ii(SPHERE_PROBE_ATLAS_MAX_SUBDIV - int(probe_resolution), 0);
}
/**
* Ensure atlas texture is the right size.
* Returns true if the texture has been cleared and all probes needs to be rendered again.
*/
bool ensure_atlas();
/**
* Ensure the cube-map target texture for rendering the probe is allocated.
*/
void ensure_cubemap_render_target(int resolution);
struct UpdateInfo {
float3 probe_pos;
/** Resolution of the cube-map to be rendered. */
int cube_target_extent;
float2 clipping_distances;
SphereProbeAtlasCoord atlas_coord;
bool do_render;
};
UpdateInfo update_info_from_probe(SphereProbe &probe);
/**
* Pop the next reflection probe that requires to be updated.
*/
std::optional<UpdateInfo> world_update_info_pop();
std::optional<UpdateInfo> probe_update_info_pop();
/**
* Remap the rendered cube-map `cubemap_tx_` to a octahedral map inside the atlas at the given
* coordinate.
*
* If `convolve_octahedral` is true, it will convolve the octahedral representation down the
* mip_chain.
*
* If `extract_spherical_harmonics` is true, it will extract the spherical harmonics into
* `spherical_harmonics_`.
*
* If `extract_sun` is different than -1, it will extract the spherical harmonics into
* `world.sunlight` at the specified index (only 2 suns are allowed). The 2 suns setup is only
* used if `world.use_lightpath_node()` is true.
*/
void remap_to_octahedral_projection(const SphereProbeAtlasCoord &atlas_coord,
bool convolve_octahedral,
bool extract_spherical_harmonics,
int extract_sun = -1);
void sync_display(Vector<SphereProbe *> &probe_active);
};
/** \} */
} // namespace blender::eevee

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,277 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#pragma once
#include "DNA_lightprobe_types.h"
#include "BLI_math_quaternion_types.hh"
#include "eevee_lightprobe.hh"
namespace blender::eevee {
using math::AxisSigned;
using math::CartesianBasis;
class Instance;
class CapturePipeline;
class ShadowModule;
class Camera;
class SphereProbeModule;
using CaptureInfoBuf = draw::StorageBuffer<CaptureInfoData>;
using IrradianceBrickBuf = draw::StorageVectorBuffer<IrradianceBrickPacked, 16>;
using SurfelBuf = draw::StorageArrayBuffer<Surfel, 64>;
using SurfelListInfoBuf = draw::StorageBuffer<SurfelListInfoData>;
using VolumeProbeDataBuf = draw::UniformArrayBuffer<VolumeProbeData, IRRADIANCE_GRID_MAX>;
/**
* Baking related pass and data. Not used at runtime.
*/
class IrradianceBake {
friend CapturePipeline;
friend ShadowModule;
friend Camera;
private:
Instance &inst_;
/** Light cache being baked. */
LightCache *light_cache_ = nullptr;
/** Surface elements that represent the scene. */
SurfelBuf surfels_buf_;
/** Capture state. */
CaptureInfoBuf capture_info_buf_;
/** Framebuffer. */
Framebuffer empty_raster_fb_ = {"empty_raster_fb_"};
/** Evaluate light object contribution and store result to surfel. */
PassSimple surfel_light_eval_ps_ = {"LightEval"};
/** Create linked list of surfel to emulated ray-cast. */
PassSimple surfel_ray_build_ps_ = {"RayBuild"};
/** Create linked list of surfel to cluster them in the 3D irradiance grid. */
PassSimple surfel_cluster_build_ps_ = {"RayBuild"};
/** Propagate light from surfel to surfel. */
PassSimple surfel_light_propagate_ps_ = {"LightPropagate"};
/** Capture surfel lighting to irradiance samples. */
PassSimple irradiance_capture_ps_ = {"IrradianceCapture"};
/** Compute virtual offset for each irradiance samples. */
PassSimple irradiance_offset_ps_ = {"IrradianceOffset"};
/** Compute scene bounding box. */
PassSimple irradiance_bounds_ps_ = {"IrradianceBounds"};
/** Index of source and destination radiance in radiance double-buffer. */
int radiance_src_ = 0, radiance_dst_ = 1;
/**
* Basis orientation for each baking projection.
* Note that this is the view orientation. The projection matrix will take the negative Z axis
* as forward and Y as up. */
CartesianBasis basis_x_ = {AxisSigned::Z_POS, AxisSigned::Y_POS, AxisSigned::X_NEG};
CartesianBasis basis_y_ = {AxisSigned::X_POS, AxisSigned::Z_POS, AxisSigned::Y_NEG};
CartesianBasis basis_z_ = {AxisSigned::Y_POS, AxisSigned::X_POS, AxisSigned::Z_NEG};
/** Views for each baking projection. */
View view_x_ = {"BakingViewX"};
View view_y_ = {"BakingViewY"};
View view_z_ = {"BakingViewZ"};
/** Pixel resolution in each of the projection axes. Match the target surfel density. */
int3 grid_pixel_extent_ = int3(0);
/** Information for surfel list building. */
SurfelListInfoBuf list_info_buf_ = {"list_info_buf_"};
/** List array containing list start surfel index. Cleared to -1. */
StorageArrayBuffer<int, 16, true> list_start_buf_ = {"list_start_buf_"};
/** Count number of surfel per surfel list. Cleared to 0. */
StorageArrayBuffer<int, 16, true> list_counter_buf_ = {"list_counter_buf_"};
/** IndexRange of sorting items for each surfel list. */
StorageArrayBuffer<int, 16, true> list_range_buf_ = {"list_range_buf_"};
/** Sorting items for fast sorting of surfels. */
StorageArrayBuffer<float, 16, true> list_item_distance_buf_ = {"list_item_distance_buf_"};
StorageArrayBuffer<int, 16, true> list_item_surfel_id_buf_ = {"list_item_surfel_id_buf_"};
/** Result of sorting. Needed to be duplicated to avoid race condition. */
StorageArrayBuffer<int, 16, true> sorted_surfel_id_buf_ = {"sorted_surfel_id_buf_"};
/* Dispatch size for per surfel workload. */
int3 dispatch_per_surfel_ = int3(1);
/* Dispatch size for per surfel list workload. */
int3 dispatch_per_list_ = int3(1);
/* Dispatch size for per grid sample workload. */
int3 dispatch_per_grid_sample_ = int3(1);
/** View used to flatten the surfels into surfel lists representing rays. */
View ray_view_ = {"RayProjectionView"};
/** Irradiance textures for baking. Only represents one grid in there. */
Texture irradiance_L0_tx_ = {"irradiance_L0_tx_"};
Texture irradiance_L1_a_tx_ = {"irradiance_L1_a_tx_"};
Texture irradiance_L1_b_tx_ = {"irradiance_L1_b_tx_"};
Texture irradiance_L1_c_tx_ = {"irradiance_L1_c_tx_"};
/** Offset per irradiance point to apply to the baking location. */
Texture virtual_offset_tx_ = {"virtual_offset_tx_"};
/** List of closest surfels per irradiance sample. */
Texture cluster_list_tx_ = {"cluster_list_tx_"};
/** Contains ratio of back-face hits. Allows to get rid of invalid probes. */
Texture validity_tx_ = {"validity_tx_"};
/* Bounding sphere of the scene being baked. In world space. */
float4 scene_bound_sphere_;
/* Surfel per unit distance in world space. */
float surfel_density_ = 1.0f;
/**
* Minimum distance a grid sample point should have with a surface.
* In minimum grid sample spacing.
* Avoids samples to be too close to surface even if they are valid.
*/
float min_distance_to_surface_ = 0.05f;
/**
* Maximum distance from the grid sample point to the baking location.
* In minimum grid sample spacing.
* Avoids samples to be too far from their actual origin.
*/
float max_virtual_offset_ = 0.1f;
/**
* Surfaces outside the Grid won't generate surfels above this distance.
*/
float clip_distance_;
/** True if world lighting is recorded during irradiance capture. */
bool capture_world_ = false;
/** True if indirect lighting is recorded during the light propagation. */
bool capture_indirect_ = false;
/** True if emission is recorded during the light propagation. */
bool capture_emission_ = false;
/** True if the bake job should stop. */
bool do_break_ = false;
public:
IrradianceBake(Instance &inst) : inst_(inst) {};
void init(const Object &probe_object);
void sync();
/** True if the bake job should stop. */
bool should_break()
{
return do_break_;
}
/** Create the views used to rasterize the scene into surfel representation. */
void surfel_raster_views_sync(const float3 &scene_min,
const float3 &scene_max,
const float4x4 &probe_to_world);
/** Create a surfel representation of the scene from the probe using the capture pipeline. */
void surfels_create(const Object &probe_object);
/** Evaluate direct lighting (and also clear the surfels radiance). */
void surfels_lights_eval();
/**
* Create a surfel lists per irradiance probe in order to compute the virtual baking offset.
* NOTE: The resulting lists are only valid until `clusters_build()` or `raylists_build()` are
* called since they share the same links inside the Surfel struct.
*/
void clusters_build();
/**
* Create a surfel lists to emulate ray-casts for the current sample random direction.
* NOTE: The resulting lists are only valid until `clusters_build()` or `raylists_build()` are
* called since they share the same links inside the Surfel struct.
*/
void raylists_build();
/** Propagate light from surfel to surfel in a random direction over the sphere. */
void propagate_light();
/** Compute offset to bias irradiance capture location. */
void irradiance_offset();
/** Store surfel irradiance inside the irradiance grid samples. */
void irradiance_capture();
/** Read grid unpacked irradiance back to CPU and returns as a #LightProbeGridCacheFrame. */
LightProbeGridCacheFrame *read_result_unpacked();
/** Read grid packed irradiance back to CPU and returns as a #LightProbeGridCacheFrame. */
LightProbeGridCacheFrame *read_result_packed();
private:
/** Read surfel data back to CPU into \a cache_frame. */
void read_surfels(LightProbeGridCacheFrame *cache_frame);
/** Read virtual offset back to CPU into \a cache_frame. */
void read_virtual_offset(LightProbeGridCacheFrame *cache_frame);
};
/**
* Runtime container of diffuse indirect lighting.
* Also have debug and baking components.
*/
class VolumeProbeModule {
public:
IrradianceBake bake;
private:
Instance &inst_;
/** Atlas 3D texture containing all loaded grid data. */
Texture irradiance_atlas_tx_ = {"irradiance_atlas_tx_"};
/** Reserved atlas brick for world irradiance. */
int world_brick_index_ = 0;
/** Data structure used to index irradiance cache pages inside the atlas. */
VolumeProbeDataBuf grids_infos_buf_ = {"grids_infos_buf_"};
IrradianceBrickBuf bricks_infos_buf_ = {"bricks_infos_buf_"};
/** Pool of atlas regions to allocate to different grids. */
Vector<IrradianceBrickPacked> brick_pool_;
/** Stream data into the irradiance atlas texture. */
PassSimple grid_upload_ps_ = {"VolumeProbeModule.Upload"};
/** If true, will trigger the reupload of all grid data instead of just streaming new ones. */
bool do_full_update_ = true;
/**
* Last used pool size to identify if we can reuse previous irradiance atlas texture. Ref
* SceneEEVEE.gi_irradiance_pool_size */
uint irradiance_pool_size_ = 0;
/** Actual pool size allocated on device. Can be different due to limits. */
uint irradiance_pool_size_alloc_ = 0;
/** Display debug data. */
PassSimple debug_ps_ = {"VolumeProbeModule.Debug"};
/** Debug surfel elements copied from the light cache. */
draw::StorageArrayBuffer<Surfel> debug_surfels_buf_;
/** Display grid cache data. */
bool display_grids_enabled_ = false;
PassSimple display_grids_ps_ = {"VolumeProbeModule.Display Grids"};
/** True if world irradiance need to be updated. */
bool do_update_world_ = true;
public:
VolumeProbeModule(Instance &inst) : bake(inst), inst_(inst) {};
~VolumeProbeModule() {};
void init();
void sync();
/* Tag all grids for reupload in set_view and composite them with the world irradiance. */
void update_world_irradiance()
{
do_update_world_ = true;
}
void set_view(View &view);
void viewport_draw(View &view, gpu::FrameBuffer *view_fb);
Vector<IrradianceBrickPacked> bricks_alloc(int brick_len);
void bricks_free(Vector<IrradianceBrickPacked> &bricks);
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_ubo(IRRADIANCE_GRID_BUF_SLOT, &grids_infos_buf_);
pass.bind_ssbo(IRRADIANCE_BRICK_BUF_SLOT, &bricks_infos_buf_);
pass.bind_texture(VOLUME_PROBE_TEX_SLOT, &irradiance_atlas_tx_);
}
private:
void debug_pass_draw(View &view, gpu::FrameBuffer *view_fb);
void display_pass_draw(View &view, gpu::FrameBuffer *view_fb);
friend class SphereProbeModule;
};
} // namespace blender::eevee

View File

@@ -0,0 +1,652 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#include "BLI_math_axis_angle.hh"
#include "BLI_rect.h"
#include "BKE_image.hh"
#include "BKE_lib_id.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_studiolight.h"
#include "NOD_shader.h"
#include "IMB_colormanagement.hh"
#include "GPU_material.hh"
#include "draw_cache.hh"
#include "draw_view_data.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Viewport Override Node-Tree
* \{ */
LookdevWorld::LookdevWorld()
{
/* Create a dummy World data block to hold the nodetree generated for studio-lights. */
world = BKE_id_new_nomain<blender::World>("Lookdev");
using namespace bke;
bNodeTree &ntree = *world->nodetree;
/* Note: We can rename directly safely because #world is not part of any bmain. */
BLI_strncpy(ntree.id.name + 2, "Lookdev World Nodetree", MAX_NAME - 2);
bNode &coordinate = *node_add_static_node(nullptr, ntree, SH_NODE_TEX_COORD);
bNodeSocket &generated_sock = *node_find_socket(coordinate, SOCK_OUT, "Generated"_ustr);
bNode &transform = *node_add_static_node(nullptr, ntree, SH_NODE_VECT_TRANSFORM);
bNodeSocket &transform_in = *node_find_socket(transform, SOCK_IN, "Vector"_ustr);
bNodeSocket &transform_out = *node_find_socket(transform, SOCK_OUT, "Vector"_ustr);
NodeShaderVectTransform &nodeprop = *static_cast<NodeShaderVectTransform *>(transform.storage);
nodeprop.convert_from = SHD_VECT_TRANSFORM_SPACE_WORLD;
xform_socket_ = &nodeprop.convert_to;
node_add_link(ntree, coordinate, generated_sock, transform, transform_in);
/* Flip Y axis because of compatibility axis flipping inside the vector transform node. */
bNode &flip_y_mul = *node_add_static_node(nullptr, ntree, SH_NODE_VECTOR_MATH);
flip_y_mul.custom1 = NODE_VECTOR_MATH_MULTIPLY;
auto &flip_y_value_out = *node_find_socket(flip_y_mul, SOCK_OUT, "Vector"_ustr);
auto &flip_y_value_in0 = *static_cast<bNodeSocket *>(BLI_findlink(&flip_y_mul.inputs, 0));
auto &flip_y_value_in1 = *static_cast<bNodeSocket *>(BLI_findlink(&flip_y_mul.inputs, 1));
flip_y_socket_ = static_cast<bNodeSocketValueVector *>(flip_y_value_in1.default_value);
flip_y_socket_->value[0] = 1.0f;
flip_y_socket_->value[1] = 1.0f;
flip_y_socket_->value[2] = 1.0f;
node_add_link(ntree, transform, transform_out, flip_y_mul, flip_y_value_in0);
bNode &rotate_x = *node_add_static_node(nullptr, ntree, SH_NODE_VECTOR_ROTATE);
rotate_x.custom1 = NODE_VECTOR_ROTATE_TYPE_AXIS_X;
auto &rotate_x_vector_in = *node_find_socket(rotate_x, SOCK_IN, "Vector"_ustr);
auto &rotate_x_vector_angle = *node_find_socket(rotate_x, SOCK_IN, "Angle"_ustr);
auto &rotate_x_out = *node_find_socket(rotate_x, SOCK_OUT, "Vector"_ustr);
rotation_x_socket_ =
&static_cast<bNodeSocketValueFloat *>(rotate_x_vector_angle.default_value)->value;
node_add_link(ntree, flip_y_mul, flip_y_value_out, rotate_x, rotate_x_vector_in);
bNode &rotate_z = *node_add_static_node(nullptr, ntree, SH_NODE_VECTOR_ROTATE);
rotate_z.custom1 = NODE_VECTOR_ROTATE_TYPE_AXIS_Z;
auto &rotate_z_vector_in = *node_find_socket(rotate_z, SOCK_IN, "Vector"_ustr);
auto &rotate_z_vector_angle = *node_find_socket(rotate_z, SOCK_IN, "Angle"_ustr);
auto &rotate_z_out = *node_find_socket(rotate_z, SOCK_OUT, "Vector"_ustr);
angle_socket_ = static_cast<bNodeSocketValueFloat *>(rotate_z_vector_angle.default_value);
node_add_link(ntree, rotate_x, rotate_x_out, rotate_z, rotate_z_vector_in);
/* Discard the previous processing if we are rendering light probes. */
bNode &light_path = *node_add_static_node(nullptr, ntree, SH_NODE_LIGHT_PATH);
bNodeSocket &is_camera_out = *node_find_socket(light_path, SOCK_OUT, "Is Camera Ray"_ustr);
bNode &path_mix = *node_add_static_node(nullptr, ntree, SH_NODE_MIX);
NodeShaderMix &path_mix_data = *static_cast<NodeShaderMix *>(path_mix.storage);
path_mix_data.data_type = SOCK_VECTOR;
path_mix_data.factor_mode = NODE_MIX_MODE_UNIFORM;
path_mix_data.clamp_factor = false;
auto &path_mix_out = *node_find_socket(path_mix, SOCK_OUT, "Result_Vector"_ustr);
auto &path_mix_fac = *static_cast<bNodeSocket *>(BLI_findlink(&path_mix.inputs, 0));
auto &path_mix_in0 = *static_cast<bNodeSocket *>(BLI_findlink(&path_mix.inputs, 4));
auto &path_mix_in1 = *static_cast<bNodeSocket *>(BLI_findlink(&path_mix.inputs, 5));
node_add_link(ntree, light_path, is_camera_out, path_mix, path_mix_fac);
node_add_link(ntree, coordinate, generated_sock, path_mix, path_mix_in0);
node_add_link(ntree, rotate_z, rotate_z_out, path_mix, path_mix_in1);
bNode &environment = *node_add_static_node(nullptr, ntree, SH_NODE_TEX_ENVIRONMENT);
environment_node_ = &environment;
NodeTexImage *environment_storage = static_cast<NodeTexImage *>(environment.storage);
auto &environment_vector_in = *node_find_socket(environment, SOCK_IN, "Vector"_ustr);
auto &environment_out = *node_find_socket(environment, SOCK_OUT, "Color"_ustr);
node_add_link(ntree, path_mix, path_mix_out, environment, environment_vector_in);
bNode &background = *node_add_static_node(nullptr, ntree, SH_NODE_BACKGROUND);
auto &background_out = *node_find_socket(background, SOCK_OUT, "Background"_ustr);
auto &background_color_in = *node_find_socket(background, SOCK_IN, "Color"_ustr);
intensity_socket_ = static_cast<bNodeSocketValueFloat *>(
node_find_socket(background, SOCK_IN, "Strength"_ustr)->default_value);
node_add_link(ntree, environment, environment_out, background, background_color_in);
bNode &output = *node_add_static_node(nullptr, ntree, SH_NODE_OUTPUT_WORLD);
auto &output_in = *node_find_socket(output, SOCK_IN, "Surface"_ustr);
node_add_link(ntree, background, background_out, output, output_in);
node_set_active(ntree, output);
/* Create a dummy image data block to hold GPU textures generated by studio-lights. */
image = BKE_id_new_nomain<blender::Image>("Lookdev");
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 studio-lights, but that requires a larger refactoring. */
BKE_image_get_gpu_texture(image, &environment_storage->iuser);
}
LookdevWorld::~LookdevWorld()
{
BKE_id_free(nullptr, &image->id);
BKE_id_free(nullptr, &world->id);
}
bool LookdevWorld::sync(const LookdevParameters &new_parameters)
{
const bool parameters_changed = assign_if_different(parameters_, new_parameters);
if (parameters_changed) {
intensity_socket_->value = parameters_.intensity;
GPU_TEXTURE_FREE_SAFE(image->runtime->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);
gpu::Texture *texture = sl->equirect_radiance_gputexture;
if (texture != nullptr) {
GPU_texture_ref(texture);
image->runtime->gputexture[TEXTARGET_2D][0] = texture;
environment_node_->id = &image->id;
}
}
if (parameters_.camera_space) {
*xform_socket_ = SHD_VECT_TRANSFORM_SPACE_CAMERA;
flip_y_socket_->value[0] = 1.0f;
flip_y_socket_->value[1] = -1.0f;
flip_y_socket_->value[2] = 1.0f;
*rotation_x_socket_ = -M_PI / 2.0f;
}
else {
*xform_socket_ = SHD_VECT_TRANSFORM_SPACE_WORLD;
flip_y_socket_->value[0] = 1.0f;
flip_y_socket_->value[1] = 1.0f;
flip_y_socket_->value[2] = 1.0f;
*rotation_x_socket_ = 0.0f;
}
}
/* This isn't part of the main update check to avoid updating the probe capture.
* This should only update the Nodetree UBO. */
const bool rotation_changed = assign_if_different(angle_socket_->value, new_parameters.rot_z);
if (rotation_changed || parameters_changed) {
/* Propagate changes to nodetree. */
GPU_material_free(&world->gpumaterial);
}
return parameters_changed;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lookdev
*
* \{ */
LookdevModule::LookdevModule(Instance &inst) : inst_(inst) {}
LookdevModule::~LookdevModule()
{
for (gpu::Batch *batch : sphere_lod_) {
GPU_BATCH_DISCARD_SAFE(batch);
}
}
gpu::Batch *LookdevModule::sphere_get(const SphereLOD level_of_detail)
{
BLI_assert(level_of_detail >= SphereLOD::LOW && level_of_detail < SphereLOD::MAX);
/* GCC 15.x triggers an array-bounds warning without this. */
#if (defined(__GNUC__) && (__GNUC__ >= 15) && !defined(__clang__))
[[assume((level_of_detail >= 0) && (level_of_detail < SphereLOD::MAX))]];
#endif
if (sphere_lod_[level_of_detail] != nullptr) {
return sphere_lod_[level_of_detail];
}
int lat_res;
int lon_res;
switch (level_of_detail) {
case 2:
lat_res = 80;
lon_res = 60;
break;
case 1:
lat_res = 64;
lon_res = 48;
break;
default:
case 0:
lat_res = 32;
lon_res = 24;
break;
}
GPUVertFormat format = {0};
GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
GPU_vertformat_attr_add(&format, "nor", gpu::VertAttrType::SFLOAT_32_32_32);
struct Vert {
float x, y, z;
float nor_x, nor_y, nor_z;
};
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
int v_len = (lat_res - 1) * lon_res * 6;
GPU_vertbuf_data_alloc(*vbo, v_len);
const float lon_inc = 2 * M_PI / lon_res;
const float lat_inc = M_PI / lat_res;
float lon, lat;
int v = 0;
lon = 0.0f;
auto sphere_lat_lon_vert = [&](float lat, float lon) {
Vert vert;
vert.nor_x = vert.x = sinf(lat) * cosf(lon);
vert.nor_y = vert.y = cosf(lat);
vert.nor_z = vert.z = sinf(lat) * sinf(lon);
GPU_vertbuf_vert_set(vbo, v, &vert);
v++;
};
for (int i = 0; i < lon_res; i++, lon += lon_inc) {
lat = 0.0f;
for (int j = 0; j < lat_res; j++, lat += lat_inc) {
if (j != lat_res - 1) { /* Pole */
sphere_lat_lon_vert(lat + lat_inc, lon + lon_inc);
sphere_lat_lon_vert(lat + lat_inc, lon);
sphere_lat_lon_vert(lat, lon);
}
if (j != 0) { /* Pole */
sphere_lat_lon_vert(lat, lon + lon_inc);
sphere_lat_lon_vert(lat + lat_inc, lon + lon_inc);
sphere_lat_lon_vert(lat, lon);
}
}
}
sphere_lod_[level_of_detail] = GPU_batch_create_ex(
GPU_PRIM_TRIS, vbo, nullptr, GPU_BATCH_OWNS_VBO);
return sphere_lod_[level_of_detail];
}
void LookdevModule::init(const rcti *visible_rect)
{
visible_rect_ = *visible_rect;
use_reference_spheres_ = inst_.is_viewport() && inst_.overlays_enabled() &&
inst_.use_lookdev_overlay();
if (use_reference_spheres_) {
const int2 extent_dummy(1);
constexpr eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_WRITE |
GPU_TEXTURE_USAGE_SHADER_READ;
dummy_cryptomatte_tx_.ensure_2d(gpu::TextureFormat::SFLOAT_32_32_32_32, extent_dummy, usage);
dummy_aov_color_tx_.ensure_2d_array(
gpu::TextureFormat::SFLOAT_16_16_16_16, extent_dummy, 1, usage);
dummy_aov_value_tx_.ensure_2d_array(gpu::TextureFormat::SFLOAT_16, extent_dummy, 1, usage);
}
if (inst_.is_viewport()) {
const blender::View3DShading &shading = inst_.v3d->shading;
bool use_viewspace_lighting = (shading.flag & V3D_SHADING_STUDIOLIGHT_VIEW_ROTATION) != 0;
if (assign_if_different(use_viewspace_lighting_, use_viewspace_lighting)) {
inst_.sampling.reset();
}
if (assign_if_different(studio_light_rotation_z_, shading.studiolight_rot_z)) {
inst_.sampling.reset();
}
}
}
float LookdevModule::calc_viewport_scale()
{
const float viewport_scale = clamp_f(
BLI_rcti_size_x(&visible_rect_) / (2000.0f * UI_SCALE_FAC), 0.5f, 1.0f);
return viewport_scale;
}
LookdevModule::SphereLOD LookdevModule::calc_level_of_detail(const float viewport_scale)
{
float res_scale = clamp_f(
(U.lookdev_sphere_size / 400.0f) * viewport_scale * UI_SCALE_FAC, 0.1f, 1.0f);
if (res_scale > 0.7f) {
return LookdevModule::SphereLOD::HIGH;
}
if (res_scale > 0.25f) {
return LookdevModule::SphereLOD::MEDIUM;
}
return LookdevModule::SphereLOD::LOW;
}
static int calc_sphere_extent(const float viewport_scale)
{
const int sphere_radius = U.lookdev_sphere_size * UI_SCALE_FAC * viewport_scale;
return sphere_radius * 2;
}
void LookdevModule::sync()
{
if (!use_reference_spheres_) {
return;
}
const float viewport_scale = calc_viewport_scale();
const int2 extent = int2(calc_sphere_extent(viewport_scale));
const gpu::TextureFormat color_format = gpu::TextureFormat::SFLOAT_16_16_16_16;
for (int index : IndexRange(num_spheres)) {
if (spheres_[index].color_tx_.ensure_2d(color_format, extent)) {
/* Request redraw if the light-probe were off and the sampling was already finished. */
if (inst_.is_viewport() && inst_.sampling.finished_viewport()) {
inst_.sampling.reset();
}
}
spheres_[index].framebuffer.ensure(GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE(spheres_[index].color_tx_));
}
const Camera &cam = inst_.camera;
float sphere_distance = cam.data_get().clip_near;
int2 display_extent = inst_.film.display_extent_get();
float pixel_radius = ShadowModule::screen_pixel_radius(
cam.data_get().wininv, cam.is_perspective(), display_extent);
if (cam.is_perspective()) {
pixel_radius *= sphere_distance;
}
this->sphere_radius_ = (extent.x / 2) * pixel_radius;
this->sphere_position_ = cam.position() -
cam.forward() * (sphere_distance + this->sphere_radius_);
float4x4 model_m4 = float4x4(float3x3(cam.data_get().viewmat));
model_m4.location() = this->sphere_position_;
model_m4 = math::scale(model_m4, float3(this->sphere_radius_));
ResourceHandleRange handle = inst_.manager->resource_handle(model_m4);
gpu::Batch *geom = sphere_get(calc_level_of_detail(viewport_scale));
sync_pass(spheres_[0].pass, geom, inst_.materials.metallic_mat, handle);
sync_pass(spheres_[1].pass, geom, inst_.materials.diffuse_mat, handle);
sync_display();
}
void LookdevModule::sync_pass(PassSimple &pass,
gpu::Batch *geom,
blender::Material *mat,
ResourceHandleRange res_handle)
{
pass.init();
pass.clear_color_depth_stencil(float4(0.0, 0.0, 0.0, 1.0), inst_.film.depth.clear_value, 0);
const DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_CULL_BACK;
GPUMaterial *gpumat = inst_.shaders.material_shader_get(
mat, mat->nodetree, MAT_PIPE_FORWARD, MAT_GEOM_MESH, false, inst_.materials.default_surface);
pass.state_set(state);
pass.material_set(*inst_.manager, gpumat, false, inst_.anisotropic_filtering);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.lights);
pass.bind_resources(inst_.shadows);
pass.bind_resources(inst_.volume.result);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.planar_probes);
pass.draw(geom, res_handle, 0);
}
void LookdevModule::sync_display()
{
PassSimple &pass = display_ps_;
const float2 viewport_size = inst_.draw_ctx->viewport_size_get();
const DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_ALWAYS |
DRW_STATE_BLEND_ALPHA;
pass.init();
pass.state_set(state);
pass.shader_set(inst_.shaders.static_shader_get(LOOKDEV_DISPLAY));
pass.push_constant("viewportSize", viewport_size);
pass.push_constant("invertedViewportSize", 1.0f / viewport_size);
pass.push_constant("anchor", int2(visible_rect_.xmax, visible_rect_.ymin));
pass.bind_texture("metallic_tx", &spheres_[0].color_tx_);
pass.bind_texture("diffuse_tx", &spheres_[1].color_tx_);
pass.draw_procedural(GPU_PRIM_TRIS, 2, 6);
}
void LookdevModule::draw(View &view)
{
if (!use_reference_spheres_) {
return;
}
inst_.volume_probes.set_view(view);
inst_.sphere_probes.set_view(view);
if (assign_if_different(inst_.pipelines.data.use_monochromatic_transmittance, bool32_t(true))) {
inst_.uniform_data.pipeline.push_update();
}
for (Sphere &sphere : spheres_) {
sphere.framebuffer.bind();
inst_.manager->submit(sphere.pass, view);
}
}
void LookdevModule::rotate_world()
{
if (!inst_.is_viewport() || !inst_.use_studio_light()) {
return;
}
AxisAngle axis_angle_rotation(AxisSigned::Z_NEG, studio_light_rotation_z_);
float4x4 rotation = math::from_rotation<float4x4>(axis_angle_rotation);
if (use_viewspace_lighting_) {
CartesianBasis target(AxisSigned::X_POS, AxisSigned::Z_NEG, AxisSigned::Y_POS);
rotation = inst_.camera.data_get().viewinv * math::from_rotation<float4x4>(target) * rotation;
}
if (assign_if_different(last_rotation_matrix_, rotation)) {
rotate_world_probe_data(inst_.sphere_probes.octahedral_probes_texture(),
inst_.sphere_probes.world_sphere_probe().atlas_coord,
inst_.sphere_probes.spherical_harmonics_buf(),
inst_.world.sunlight,
rotation);
}
}
void LookdevModule::display()
{
if (!use_reference_spheres_) {
return;
}
BLI_assert(inst_.is_viewport());
DefaultFramebufferList *dfbl = inst_.draw_ctx->viewport_framebuffer_list_get();
/* The viewport of the framebuffer can be modified when border rendering is enabled. */
GPU_framebuffer_viewport_reset(dfbl->default_fb);
GPU_framebuffer_bind(dfbl->default_fb);
inst_.manager->submit(display_ps_);
}
void LookdevModule::store_world_probe_data(
Texture &in_sphere_probe,
const SphereProbeAtlasCoord &atlas_coord,
StorageBuffer<SphereProbeHarmonic, true> &in_volume_probe,
UniformArrayBuffer<LightData, 2> &in_sunlight)
{
SphereProbeUvArea read_coord = atlas_coord.as_sampling_coord();
SphereProbePixelArea write_coord_mip0 = atlas_coord.as_write_coord(0);
SphereProbePixelArea write_coord_mip1 = atlas_coord.as_write_coord(1);
SphereProbePixelArea write_coord_mip2 = atlas_coord.as_write_coord(2);
SphereProbePixelArea write_coord_mip3 = atlas_coord.as_write_coord(3);
SphereProbePixelArea write_coord_mip4 = atlas_coord.as_write_coord(4);
if (world_sphere_probe_.ensure_2d_array(gpu::TextureFormat::SPHERE_PROBE_FORMAT,
in_sphere_probe.size().xy(),
1,
GPU_TEXTURE_USAGE_GENERAL,
nullptr,
5))
{
GPU_texture_mipmap_mode(world_sphere_probe_, true, true);
world_sphere_probe_.ensure_mip_views();
}
PassSimple pass = {__func__};
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(LOOKDEV_COPY_WORLD));
pass.push_constant("read_coord_packed", reinterpret_cast<int4 *>(&read_coord));
pass.push_constant("write_coord_mip0_packed", reinterpret_cast<int4 *>(&write_coord_mip0));
pass.push_constant("write_coord_mip1_packed", reinterpret_cast<int4 *>(&write_coord_mip1));
pass.push_constant("write_coord_mip2_packed", reinterpret_cast<int4 *>(&write_coord_mip2));
pass.push_constant("write_coord_mip3_packed", reinterpret_cast<int4 *>(&write_coord_mip3));
pass.push_constant("write_coord_mip4_packed", reinterpret_cast<int4 *>(&write_coord_mip4));
pass.push_constant("lookdev_rotation", float4x4::identity());
pass.bind_texture("in_sphere_tx", in_sphere_probe);
pass.bind_image("out_sphere_mip0", world_sphere_probe_.mip_view(0));
pass.bind_image("out_sphere_mip1", world_sphere_probe_.mip_view(1));
pass.bind_image("out_sphere_mip2", world_sphere_probe_.mip_view(2));
pass.bind_image("out_sphere_mip3", world_sphere_probe_.mip_view(3));
pass.bind_image("out_sphere_mip4", world_sphere_probe_.mip_view(4));
pass.bind_ssbo("in_sh", in_volume_probe);
pass.bind_ssbo("out_sh", world_volume_probe_);
pass.bind_ssbo("in_sun", in_sunlight); /* Note only the 1st member of the array is read. */
pass.bind_ssbo("out_sun", world_sunlight_);
int3 dispatch_size = int3(
int2(math::divide_ceil(int2(write_coord_mip0.extent), int2(SPHERE_PROBE_REMAP_GROUP_SIZE))),
1);
pass.dispatch(dispatch_size);
inst_.manager->submit(pass);
last_rotation_matrix_ = float4x4::identity();
}
/* This is called as soon as possible inside the frame drawing and tag world probe volume
* to update. Volume probe update is the only thing that needs to be triggered to make sure the SH
* are copied to all volume probes. Sphere probes are already updated by this function. */
void LookdevModule::rotate_world_probe_data(
Texture &dst_sphere_probe,
const SphereProbeAtlasCoord &atlas_coord,
StorageBuffer<SphereProbeHarmonic, true> &dst_volume_probe,
UniformArrayBuffer<LightData, 2> &dst_sunlight,
float4x4 &rotation)
{
SphereProbeUvArea read_coord = atlas_coord.as_sampling_coord();
SphereProbePixelArea write_coord_mip0 = atlas_coord.as_write_coord(0);
SphereProbePixelArea write_coord_mip1 = atlas_coord.as_write_coord(1);
SphereProbePixelArea write_coord_mip2 = atlas_coord.as_write_coord(2);
SphereProbePixelArea write_coord_mip3 = atlas_coord.as_write_coord(3);
SphereProbePixelArea write_coord_mip4 = atlas_coord.as_write_coord(4);
PassSimple pass = {__func__};
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(LOOKDEV_COPY_WORLD));
pass.push_constant("read_coord_packed", reinterpret_cast<int4 *>(&read_coord));
pass.push_constant("write_coord_mip0_packed", reinterpret_cast<int4 *>(&write_coord_mip0));
pass.push_constant("write_coord_mip1_packed", reinterpret_cast<int4 *>(&write_coord_mip1));
pass.push_constant("write_coord_mip2_packed", reinterpret_cast<int4 *>(&write_coord_mip2));
pass.push_constant("write_coord_mip3_packed", reinterpret_cast<int4 *>(&write_coord_mip3));
pass.push_constant("write_coord_mip4_packed", reinterpret_cast<int4 *>(&write_coord_mip4));
pass.push_constant("lookdev_rotation", rotation);
pass.bind_texture("in_sphere_tx", &world_sphere_probe_);
pass.bind_image("out_sphere_mip0", dst_sphere_probe.mip_view(0));
pass.bind_image("out_sphere_mip1", dst_sphere_probe.mip_view(1));
pass.bind_image("out_sphere_mip2", dst_sphere_probe.mip_view(2));
pass.bind_image("out_sphere_mip3", dst_sphere_probe.mip_view(3));
pass.bind_image("out_sphere_mip4", dst_sphere_probe.mip_view(4));
pass.bind_ssbo("in_sh", world_volume_probe_);
pass.bind_ssbo("out_sh", dst_volume_probe);
pass.bind_ssbo("in_sun", world_sunlight_);
pass.bind_ssbo("out_sun", dst_sunlight);
int3 dispatch_size = int3(
int2(math::divide_ceil(int2(write_coord_mip0.extent), int2(SPHERE_PROBE_REMAP_GROUP_SIZE))),
1);
pass.dispatch(dispatch_size);
inst_.manager->submit(pass);
/* Tag world to update the SH stored in the volume probe atlas.
* If any volume probe is visible, this will reupload the baked data.
* This is the costly part of this feature. */
inst_.volume_probes.update_world_irradiance();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Parameters
* \{ */
LookdevParameters::LookdevParameters()
{
working_space = IMB_colormanagement_working_space_get();
}
LookdevParameters::LookdevParameters(const blender::View3D *v3d)
{
working_space = IMB_colormanagement_working_space_get();
if (v3d == nullptr) {
return;
}
const blender::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);
camera_space = (shading.flag & V3D_SHADING_STUDIOLIGHT_VIEW_ROTATION) != 0;
}
}
bool LookdevParameters::operator==(const LookdevParameters &other) const
{
return hdri == other.hdri && working_space == other.working_space &&
background_opacity == other.background_opacity && blur == other.blur &&
intensity == other.intensity && show_scene_world == other.show_scene_world &&
camera_space == other.camera_space;
}
bool LookdevParameters::operator!=(const LookdevParameters &other) const
{
return !(*this == other);
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,227 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* 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 <string>
#include "DNA_image_types.h"
#include "DNA_vec_types.h"
#include "DNA_world_types.h"
#include "BLI_math_vector_types.hh"
#include "DRW_gpu_wrapper.hh"
#include "eevee_light_shared.hh"
#include "eevee_lightprobe.hh"
#include "eevee_lightprobe_shared.hh"
#include "draw_pass.hh"
namespace blender {
struct bNode;
struct bNodeSocketValueFloat;
struct bNodeSocketValueVector;
struct View3D;
namespace eevee {
class Instance;
class LookdevView;
using draw::Framebuffer;
using draw::PassSimple;
using draw::ResourceHandleRange;
using draw::Texture;
using draw::View;
/* -------------------------------------------------------------------- */
/** \name Parameters
*
* Parameters used to check changes and to configure the world shader node tree.
*
* \{ */
struct LookdevParameters {
std::string hdri;
std::string working_space;
float rot_z = 0.0f;
float background_opacity = 0.0f;
float intensity = 1.0f;
float blur = 0.0f;
bool show_scene_world = true;
bool camera_space = true;
LookdevParameters();
LookdevParameters(const blender::View3D *v3d);
bool operator==(const LookdevParameters &other) const;
bool operator!=(const LookdevParameters &other) const;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Viewport Override World
*
* In a viewport the world can be overridden by a custom HDRI and some settings.
* \{ */
class LookdevWorld {
private:
bNode *environment_node_ = nullptr;
bNodeSocketValueFloat *intensity_socket_ = nullptr;
bNodeSocketValueFloat *angle_socket_ = nullptr;
/* Vector multiply socket for flipping Y axes when transforming to camera space. */
bNodeSocketValueVector *flip_y_socket_ = nullptr;
/* Vector transform socket `convert_to`. */
int *xform_socket_ = nullptr;
/* Set to M_PI/2 for rotating the HDRI horizon line in camera space mode. */
float *rotation_x_socket_ = nullptr;
blender::Image *image = nullptr;
blender::World *world = nullptr;
LookdevParameters parameters_;
public:
LookdevWorld();
~LookdevWorld();
/* Returns true if an update was detected. */
bool sync(const LookdevParameters &new_parameters);
blender::World *world_get()
{
return world;
}
float background_opacity_get() const
{
return parameters_.background_opacity;
}
float background_blur_get() const
{
return parameters_.blur;
}
float intensity_get() const
{
return parameters_.intensity;
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lookdev
*
* \{ */
using namespace draw;
class LookdevModule {
private:
Instance &inst_;
bool use_reference_spheres_ = false;
bool use_viewspace_lighting_ = false;
/* Used for update detection. */
float4x4 last_rotation_matrix_ = float4x4::identity();
float studio_light_rotation_z_ = 0.0f;
static constexpr int num_spheres = 2;
/**
* Shape resolution level of detail.
*/
enum SphereLOD {
LOW = 0,
MEDIUM = 1,
HIGH = 2,
MAX, /* Max number of level of detail */
};
std::array<gpu::Batch *, MAX> sphere_lod_ = {};
/* Size and position of the look-dev spheres in world space. */
float sphere_radius_;
float3 sphere_position_;
rcti visible_rect_;
/* Dummy textures: required to reuse forward mesh shader and avoid another shader variation. */
Texture dummy_cryptomatte_tx_;
Texture dummy_aov_color_tx_;
Texture dummy_aov_value_tx_;
struct Sphere {
Framebuffer framebuffer = {"Lookdev.Framebuffer"};
Texture color_tx_ = {"Lookdev.Color"};
PassSimple pass = {"Lookdev.Sphere"};
};
Sphere spheres_[num_spheres];
PassSimple display_ps_ = {"Lookdev.Display"};
/**
* Copy of non-rotated world probe values when using the "view space" light overlay option.
* These probes are then rotated to match the view direction.
* This is faster than trying to recompute all these values for each frame.
*/
Texture world_sphere_probe_ = {"world_sphere_probe_"};
StorageBuffer<SphereProbeHarmonic, true> world_volume_probe_ = {"world_volume_probe_"};
UniformArrayBuffer<LightData, 2> world_sunlight_ = {"world_sunlight_"};
public:
LookdevModule(Instance &inst);
~LookdevModule();
void init(const rcti *visible_rect);
void sync();
void draw(View &view);
void display();
void rotate_world();
void store_world_probe_data(Texture &in_sphere_probe,
const SphereProbeAtlasCoord &atlas_coord,
StorageBuffer<SphereProbeHarmonic, true> &in_volume_probe,
UniformArrayBuffer<LightData, 2> &in_sunlight);
void rotate_world_probe_data(Texture &dst_sphere_probe,
const SphereProbeAtlasCoord &atlas_coord,
StorageBuffer<SphereProbeHarmonic, true> &dst_volume_probe,
UniformArrayBuffer<LightData, 2> &dst_sunlight,
float4x4 &rotation);
private:
void sync_pass(PassSimple &pass,
gpu::Batch *geom,
blender::Material *mat,
ResourceHandleRange res_handle);
void sync_display();
float calc_viewport_scale();
SphereLOD calc_level_of_detail(const float viewport_scale);
gpu::Batch *sphere_get(const SphereLOD level_of_detail);
friend class LookdevView;
};
/** \} */
} // namespace eevee
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_math_vector_types.hh"
namespace blender::eevee::lut {
/* LTC matrix components for isotropic GGX. */
extern const float ltc_mat_ggx[64][64][4];
/* LTC magnitude components for isotropic GGX. */
extern const float ltc_mag_ggx[64][64][2];
/* Precomputed Disk integral for different elevation angles and solid angle. */
extern const float ltc_disk_integral[64][64][1];
/* Precomputed integrated split fresnel term of the GGX BRDF. */
extern const float brdf_ggx[64][64][3];
/* Precomputed Schlick reflectance and transmittance factor of glass material with IOR < 1. */
extern const float bsdf_ggx[16][64][64][3];
/* Precomputed Schlick transmittance factor of glass material with IOR > 1. */
extern const float btdf_ggx[16][64][64][1];
/* 4 different blue noise, one per channel. */
extern const float blue_noise[64][64][4];
/* Precomputed translucency profile of a slab of material with varying thickness. */
extern const float burley_sss_profile[64][1];
/* Precomputed translucency profile of a slab of material with varying thickness. */
extern const float random_walk_sss_profile[64][1];
#ifdef CYCLES_REFERENCE_LUT
/* Precomputed translucency profile of a slab of material with varying thickness. */
extern const float cycles_burley_reference_sss_profile[256][3];
/* Precomputed translucency profile of a slab of material with varying thickness. */
extern const float cycles_random_walk_reference_sss_profile[256][3];
#endif
} // namespace blender::eevee::lut

View File

@@ -0,0 +1,490 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#include "BLI_time.h"
#include "DNA_material_types.h"
#include "BKE_lib_id.hh"
#include "BKE_material.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_scene.hh"
#include "NOD_shader.h"
#include "eevee_instance.hh"
#include "eevee_material.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Material
*
* \{ */
MaterialModule::MaterialModule(Instance &inst) : inst_(inst)
{
{
diffuse_mat = BKE_id_new_nomain<blender::Material>("EEVEE default diffuse");
bNodeTree *ntree = diffuse_mat->nodetree;
diffuse_mat->surface_render_method = MA_SURFACE_METHOD_FORWARD;
/* Use 0.18 as it is close to middle gray. Middle gray is typically defined as 18% reflectance
* of visible light and commonly used for VFX balls. */
bNode *bsdf = bke::node_add_static_node(nullptr, *ntree, SH_NODE_BSDF_DIFFUSE);
bNodeSocket *base_color = bke::node_find_socket(*bsdf, SOCK_IN, "Color"_ustr);
copy_v3_fl((static_cast<bNodeSocketValueRGBA *>(base_color->default_value))->value, 0.18f);
bNode *output = bke::node_add_static_node(nullptr, *ntree, SH_NODE_OUTPUT_MATERIAL);
bke::node_add_link(*ntree,
*bsdf,
*bke::node_find_socket(*bsdf, SOCK_OUT, "BSDF"_ustr),
*output,
*bke::node_find_socket(*output, SOCK_IN, "Surface"_ustr));
bke::node_set_active(*ntree, *output);
}
{
metallic_mat = BKE_id_new_nomain<blender::Material>("EEVEE default metal");
bNodeTree *ntree = metallic_mat->nodetree;
metallic_mat->surface_render_method = MA_SURFACE_METHOD_FORWARD;
bNode *bsdf = bke::node_add_static_node(nullptr, *ntree, SH_NODE_BSDF_GLOSSY);
bNodeSocket *base_color = bke::node_find_socket(*bsdf, SOCK_IN, "Color"_ustr);
copy_v3_fl((static_cast<bNodeSocketValueRGBA *>(base_color->default_value))->value, 1.0f);
bNodeSocket *roughness = bke::node_find_socket(*bsdf, SOCK_IN, "Roughness"_ustr);
(static_cast<bNodeSocketValueFloat *>(roughness->default_value))->value = 0.0f;
bNode *output = bke::node_add_static_node(nullptr, *ntree, SH_NODE_OUTPUT_MATERIAL);
bke::node_add_link(*ntree,
*bsdf,
*bke::node_find_socket(*bsdf, SOCK_OUT, "BSDF"_ustr),
*output,
*bke::node_find_socket(*output, SOCK_IN, "Surface"_ustr));
bke::node_set_active(*ntree, *output);
}
{
default_surface = reinterpret_cast<blender::Material *>(BKE_id_copy_ex(
nullptr, &BKE_material_default_surface()->id, nullptr, LIB_ID_COPY_LOCALIZE));
default_volume = reinterpret_cast<blender::Material *>(BKE_id_copy_ex(
nullptr, &BKE_material_default_volume()->id, nullptr, LIB_ID_COPY_LOCALIZE));
}
{
error_mat_ = BKE_id_new_nomain<blender::Material>("EEVEE default error");
bNodeTree *ntree = error_mat_->nodetree;
/* Use emission and output material to be compatible with both World and Material. */
bNode *bsdf = bke::node_add_static_node(nullptr, *ntree, SH_NODE_EMISSION);
bNodeSocket *color = bke::node_find_socket(*bsdf, SOCK_IN, "Color"_ustr);
copy_v3_fl3(
(static_cast<bNodeSocketValueRGBA *>(color->default_value))->value, 1.0f, 0.0f, 1.0f);
bNode *output = bke::node_add_static_node(nullptr, *ntree, SH_NODE_OUTPUT_MATERIAL);
bke::node_add_link(*ntree,
*bsdf,
*bke::node_find_socket(*bsdf, SOCK_OUT, "Emission"_ustr),
*output,
*bke::node_find_socket(*output, SOCK_IN, "Surface"_ustr));
bke::node_set_active(*ntree, *output);
}
}
MaterialModule::~MaterialModule()
{
BKE_id_free(nullptr, metallic_mat);
BKE_id_free(nullptr, diffuse_mat);
BKE_id_free(nullptr, default_surface);
BKE_id_free(nullptr, default_volume);
BKE_id_free(nullptr, error_mat_);
}
void MaterialModule::begin_sync()
{
float frame = BKE_scene_frame_get(inst_.scene);
Scene *scene = inst_.scene;
bool frame_change = assign_if_different(material_frame, frame);
bool time_change = assign_if_different(material_time, float(FRA2TIME(frame)));
material_time_changed = (time_change || frame_change);
inst_.uniform_data.data.scene.time = material_time;
inst_.uniform_data.data.scene.frame = material_frame;
queued_shaders_count = 0;
queued_textures_count = 0;
queued_optimize_shaders_count = 0;
material_override = DEG_get_evaluated(inst_.depsgraph, inst_.view_layer->mat_override);
uint64_t next_update = GPU_pass_global_compilation_count();
gpu_pass_last_update_ = gpu_pass_next_update_;
gpu_pass_next_update_ = next_update;
texture_loading_queue_.clear();
material_map_.clear();
shader_map_.clear();
}
void MaterialModule::queue_texture_loading(GPUMaterial *material)
{
ListBaseT<GPUMaterialTexture> textures = GPU_material_textures(material);
for (GPUMaterialTexture *tex : ListBaseWrapper<GPUMaterialTexture>(textures)) {
if (tex->ima) {
const bool use_tile_mapping = tex->tiled_mapping_name[0];
ImageUser *iuser = tex->iuser_available ? &tex->iuser : nullptr;
ImageGPUTextures gputex = BKE_image_get_gpu_material_texture_try(
tex->ima, iuser, use_tile_mapping);
if (*gputex.texture == nullptr) {
texture_loading_queue_.append(tex);
}
}
}
}
void MaterialModule::end_sync()
{
if (texture_loading_queue_.is_empty()) {
return;
}
if (inst_.is_viewport()) {
/* Avoid ghosting of textures. */
inst_.sampling.reset();
}
GPU_debug_group_begin("Texture Loading");
/* Load files from disk in a multithreaded manner. Allow better parallelism. */
threading::parallel_for(texture_loading_queue_.index_range(), 1, [&](const IndexRange range) {
for (auto i : range) {
GPUMaterialTexture *tex = texture_loading_queue_[i];
ImageUser *iuser = tex->iuser_available ? &tex->iuser : nullptr;
BKE_image_get_tile(tex->ima, 0);
threading::isolate_task([&]() {
ImBuf *imbuf = BKE_image_acquire_ibuf(tex->ima, iuser, nullptr);
BKE_image_release_ibuf(tex->ima, imbuf, nullptr);
});
}
});
/* Tag time is not thread-safe. */
for (GPUMaterialTexture *tex : texture_loading_queue_) {
BKE_image_tag_time(tex->ima);
}
/* Avoid any leftover bind before BKE_image_get_gpu_material_texture which could cause assert
* about missing specialization constants. */
GPU_shader_unbind();
/* Upload to the GPU (create gpu::Texture). This part still requires a valid GPU context and
* is not easily parallelized. */
for (GPUMaterialTexture *tex : texture_loading_queue_) {
BLI_assert(tex->ima);
GPU_debug_group_begin(tex->ima->id.name);
const bool use_tile_mapping = tex->tiled_mapping_name[0];
ImageUser *iuser = tex->iuser_available ? &tex->iuser : nullptr;
ImageGPUTextures gputex = BKE_image_get_gpu_material_texture(
tex->ima, iuser, use_tile_mapping);
/* Acquire the textures since they were not existing inside `PassBase::material_set()`. */
inst_.manager->acquire_texture(*gputex.texture);
if (gputex.tile_mapping) {
inst_.manager->acquire_texture(*gputex.tile_mapping);
}
GPU_debug_group_end();
}
GPU_debug_group_end();
texture_loading_queue_.clear();
}
MaterialPass MaterialModule::material_pass_get(Object *ob,
blender::Material *blender_mat,
eMaterialPipeline pipeline_type,
eMaterialGeometry geometry_type,
eMaterialProbe probe_capture)
{
bNodeTree *ntree = (blender_mat->nodetree != nullptr) ? blender_mat->nodetree :
default_surface->nodetree;
/* We can't defer compilation in viewport image render, since we can't re-sync.(See #130235) */
bool use_deferred_compilation = !inst_.is_viewport_image_render;
const bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_OCCUPANCY, MAT_PIPE_VOLUME_MATERIAL);
blender::Material *default_mat = is_volume ? default_volume : default_surface;
MaterialPass matpass = MaterialPass();
matpass.gpumat = inst_.shaders.material_shader_get(
blender_mat, ntree, pipeline_type, geometry_type, use_deferred_compilation, default_mat);
queue_texture_loading(matpass.gpumat);
const bool is_forward = ELEM(pipeline_type,
MAT_PIPE_FORWARD,
MAT_PIPE_PREPASS_FORWARD,
MAT_PIPE_PREPASS_FORWARD_VELOCITY,
MAT_PIPE_PREPASS_OVERLAP);
switch (GPU_material_status(matpass.gpumat)) {
case GPU_MAT_SUCCESS: {
/* Determine optimization status for remaining compilations counter. */
int optimization_status = GPU_material_optimization_status(matpass.gpumat);
if (optimization_status == GPU_MAT_OPTIMIZATION_QUEUED) {
queued_optimize_shaders_count++;
}
break;
}
case GPU_MAT_QUEUED:
queued_shaders_count++;
matpass.gpumat = inst_.shaders.material_shader_get(
default_mat, default_mat->nodetree, pipeline_type, geometry_type, false, nullptr);
break;
case GPU_MAT_FAILED:
default:
matpass.gpumat = inst_.shaders.material_shader_get(
error_mat_, error_mat_->nodetree, pipeline_type, geometry_type, false, nullptr);
break;
}
/* Returned material should be ready to be drawn. */
BLI_assert(GPU_material_status(matpass.gpumat) == GPU_MAT_SUCCESS);
inst_.manager->register_layer_attributes(matpass.gpumat);
const bool is_transparent = GPU_material_flag_get(matpass.gpumat, GPU_MATFLAG_TRANSPARENT);
bool pass_updated = GPU_material_compilation_timestamp(matpass.gpumat) > gpu_pass_last_update_;
if (inst_.is_viewport() && use_deferred_compilation && pass_updated) {
inst_.sampling.reset();
const bool has_displacement = GPU_material_has_displacement_output(matpass.gpumat) &&
(blender_mat->displacement_method != MA_DISPLACEMENT_BUMP);
const bool has_volume = GPU_material_has_volume_output(matpass.gpumat);
if (((pipeline_type == MAT_PIPE_SHADOW) && (is_transparent || has_displacement)) || has_volume)
{
/* WORKAROUND: This is to avoid lingering shadows from default material.
* Ideally, we should tag the caster object to update only the needed areas but that's a bit
* more involved. */
inst_.shadows.reset();
}
}
if (is_volume || (is_forward && is_transparent)) {
/* Sub pass is generated later. */
matpass.sub_pass = nullptr;
}
else {
const bool hide_from_raycast = ob->visibility_flag & OB_HIDE_RAYCAST;
ShaderKey shader_key(matpass.gpumat, blender_mat, probe_capture, hide_from_raycast);
PassMain::Sub *shader_sub = shader_map_.lookup_or_add_cb(shader_key, [&]() {
/* First time encountering this shader. Create a sub that will contain materials using it. */
return inst_.pipelines.material_add(
ob, blender_mat, matpass.gpumat, pipeline_type, probe_capture);
});
if (shader_sub != nullptr) {
/* Create a sub for this material as `shader_sub` is for sharing shader between materials. */
matpass.sub_pass = &shader_sub->sub(GPU_material_get_name(matpass.gpumat));
matpass.sub_pass->material_set(
*inst_.manager, matpass.gpumat, true, inst_.anisotropic_filtering);
}
else {
matpass.sub_pass = nullptr;
}
}
return matpass;
}
Material &MaterialModule::material_sync(const ObjectHandle &ob_handle,
blender::Material *blender_mat,
eMaterialGeometry geometry_type,
bool has_motion)
{
Object *ob = ob_handle.object;
bool hide_on_camera = ob->visibility_flag & OB_HIDE_CAMERA;
if (geometry_type == MAT_GEOM_VOLUME) {
MaterialKey material_key(
blender_mat, geometry_type, MAT_PIPE_VOLUME_MATERIAL, ob->visibility_flag);
Material &mat = material_map_.lookup_or_add_cb(material_key, [&]() {
Material mat = {};
mat.volume_occupancy = material_pass_get(
ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, MAT_GEOM_VOLUME);
mat.volume_material = material_pass_get(
ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME);
mat.has_volume = GPU_material_has_volume_output(mat.volume_material.gpumat);
return mat;
});
return mat;
}
const bool use_forward_pipeline = (blender_mat->surface_render_method ==
MA_SURFACE_METHOD_FORWARD);
eMaterialPipeline surface_pipe, prepass_pipe;
if (use_forward_pipeline) {
surface_pipe = MAT_PIPE_FORWARD;
prepass_pipe = has_motion ? MAT_PIPE_PREPASS_FORWARD_VELOCITY : MAT_PIPE_PREPASS_FORWARD;
}
else {
surface_pipe = MAT_PIPE_DEFERRED;
prepass_pipe = has_motion ? MAT_PIPE_PREPASS_DEFERRED_VELOCITY : MAT_PIPE_PREPASS_DEFERRED;
}
/**
* NOTE: Use prepass_pipe instead of surface_pipe, since surface_pipe doesn't take velocity
* variants into account, causing all users of the same material to use velocity or not based on
* the first object that was synced.
* Note that prepass already takes deferred vs forward into account.
*
* TODO: Find a cleaner solution.
*/
MaterialKey material_key(blender_mat, geometry_type, prepass_pipe, ob->visibility_flag);
Material &mat = material_map_.lookup_or_add_cb(material_key, [&]() {
Material mat;
if (inst_.is_baking()) {
if (!(ob->visibility_flag & OB_HIDE_PROBE_VOLUME)) {
mat.capture = material_pass_get(ob, blender_mat, MAT_PIPE_CAPTURE, geometry_type);
}
/* TODO(fclem): Still need the shading pass for correct attribute extraction. Would be better
* to avoid this shader compilation in another context. */
mat.shading = material_pass_get(ob, blender_mat, surface_pipe, geometry_type);
mat.has_volume = false; /* TODO */
mat.has_surface = GPU_material_has_surface_output(mat.shading.gpumat);
}
else {
if (!hide_on_camera) {
mat.prepass = material_pass_get(ob, blender_mat, prepass_pipe, geometry_type);
}
mat.shading = material_pass_get(ob, blender_mat, surface_pipe, geometry_type);
if (hide_on_camera) {
/* Only null the sub_pass.
* `mat.shading.gpumat` is always needed for using the GPU_material API. */
mat.shading.sub_pass = nullptr;
}
if (inst_.needs_lightprobe_sphere_passes() && !(ob->visibility_flag & OB_HIDE_PROBE_CUBEMAP))
{
mat.lightprobe_sphere_prepass = material_pass_get(
ob, blender_mat, MAT_PIPE_PREPASS_DEFERRED, geometry_type, MAT_PROBE_REFLECTION);
mat.lightprobe_sphere_shading = material_pass_get(
ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, MAT_PROBE_REFLECTION);
}
if (inst_.needs_planar_probe_passes() && !(ob->visibility_flag & OB_HIDE_PROBE_PLANAR)) {
mat.planar_probe_prepass = material_pass_get(
ob, blender_mat, MAT_PIPE_PREPASS_PLANAR, geometry_type, MAT_PROBE_PLANAR);
mat.planar_probe_shading = material_pass_get(
ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, MAT_PROBE_PLANAR);
}
mat.has_surface = GPU_material_has_surface_output(mat.shading.gpumat);
mat.has_volume = GPU_material_has_volume_output(mat.shading.gpumat);
if (mat.has_volume && !hide_on_camera) {
mat.volume_occupancy = material_pass_get(
ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, geometry_type);
mat.volume_material = material_pass_get(
ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, geometry_type);
}
}
if (!(ob->visibility_flag & OB_HIDE_SHADOW)) {
mat.shadow = material_pass_get(ob, blender_mat, MAT_PIPE_SHADOW, geometry_type);
}
mat.is_alpha_blend_transparent = use_forward_pipeline &&
GPU_material_flag_get(mat.shading.gpumat,
GPU_MATFLAG_TRANSPARENT);
mat.has_transparent_shadows = blender_mat->blend_flag & MA_BL_TRANSPARENT_SHADOW &&
GPU_material_flag_get(mat.shading.gpumat,
GPU_MATFLAG_TRANSPARENT);
return mat;
});
return mat;
}
blender::Material *MaterialModule::material_from_slot(Object *ob, int slot)
{
blender::Material *ma = BKE_object_material_get_eval(ob, slot + 1);
if (ma == nullptr) {
if (ob->type == OB_VOLUME) {
return BKE_material_default_volume();
}
return BKE_material_default_surface();
}
return ma;
}
MaterialArray &MaterialModule::material_array_get(const ObjectHandle &ob_handle, bool has_motion)
{
Object *ob = ob_handle.object;
material_array_.materials.clear();
material_array_.gpu_materials.clear();
const int materials_len = BKE_object_material_used_with_fallback_eval(*ob);
for (auto i : IndexRange(materials_len)) {
blender::Material *blender_mat = (material_override) ? material_override :
material_from_slot(ob, i);
Material &mat = material_sync(ob_handle, blender_mat, to_material_geometry(ob), has_motion);
/* \note Perform a whole copy since next material_sync() can move the Material memory location
* (i.e: because of its container growing) */
material_array_.materials.append(mat);
material_array_.gpu_materials.append(mat.shading.gpumat);
}
return material_array_;
}
Material MaterialModule::material_get(const ObjectHandle &ob_handle,
bool has_motion,
int mat_nr,
eMaterialGeometry geometry_type)
{
blender::Material *blender_mat = (material_override) ?
material_override :
material_from_slot(ob_handle.object, mat_nr);
return material_sync(ob_handle, blender_mat, geometry_type, has_motion);
}
ShaderGroups MaterialModule::default_materials_load(bool block_until_ready)
{
bool shaders_are_ready = true;
auto request_shader =
[&](blender::Material *mat, eMaterialPipeline pipeline, eMaterialGeometry geom) {
GPUMaterial *gpu_mat = inst_.shaders.material_shader_get(
mat, mat->nodetree, pipeline, geom, !block_until_ready, nullptr);
shaders_are_ready = shaders_are_ready && GPU_material_status(gpu_mat) == GPU_MAT_SUCCESS;
};
request_shader(default_surface, MAT_PIPE_PREPASS_DEFERRED, MAT_GEOM_MESH);
request_shader(default_surface, MAT_PIPE_PREPASS_DEFERRED_VELOCITY, MAT_GEOM_MESH);
request_shader(default_surface, MAT_PIPE_DEFERRED, MAT_GEOM_MESH);
request_shader(default_surface, MAT_PIPE_SHADOW, MAT_GEOM_MESH);
return shaders_are_ready ? DEFAULT_MATERIALS : NONE;
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,431 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#pragma once
#include "DNA_material_types.h"
#include "DRW_render.hh"
#include "BLI_enum_flags.hh"
#include "BLI_map.hh"
#include "BLI_vector.hh"
#include "GPU_material.hh"
#include "draw_pass.hh"
#include "eevee_material_shared.hh"
#include "eevee_shader.hh"
#include "eevee_sync.hh"
namespace blender {
struct bNodeSocketValueFloat;
struct bNodeSocketValueRGBA;
namespace eevee {
class Instance;
/* -------------------------------------------------------------------- */
/** \name MaterialKey
*
* \{ */
static inline bool geometry_type_has_surface(eMaterialGeometry geometry_type)
{
return geometry_type < MAT_GEOM_VOLUME;
}
enum eMaterialDisplacement {
MAT_DISPLACEMENT_BUMP = 0,
MAT_DISPLACEMENT_VERTEX_WITH_BUMP,
};
static inline eMaterialDisplacement to_displacement_type(int displacement_method)
{
switch (displacement_method) {
case MA_DISPLACEMENT_DISPLACE:
/* Currently unsupported. Revert to vertex displacement + bump. */
ATTR_FALLTHROUGH;
case MA_DISPLACEMENT_BOTH:
return MAT_DISPLACEMENT_VERTEX_WITH_BUMP;
default:
return MAT_DISPLACEMENT_BUMP;
}
}
enum eMaterialThickness {
/* These maps directly to thickness mode. */
MAT_THICKNESS_SPHERE = 0,
MAT_THICKNESS_SLAB,
};
static inline eMaterialThickness to_thickness_type(int thickness_mode)
{
switch (thickness_mode) {
case MA_THICKNESS_SLAB:
return MAT_THICKNESS_SLAB;
default:
return MAT_THICKNESS_SPHERE;
}
}
enum eMaterialProbe {
MAT_PROBE_NONE = 0,
MAT_PROBE_REFLECTION,
MAT_PROBE_PLANAR,
};
static inline void material_type_from_shader_uuid(uint64_t shader_uuid,
eMaterialPipeline &pipeline_type,
eMaterialGeometry &geometry_type,
eMaterialDisplacement &displacement_type,
eMaterialThickness &thickness_type,
bool &transparent_shadows)
{
const uint64_t geometry_mask = ((1u << 4u) - 1u);
const uint64_t pipeline_mask = ((1u << 4u) - 1u);
const uint64_t thickness_mask = ((1u << 1u) - 1u);
const uint64_t displacement_mask = ((1u << 1u) - 1u);
geometry_type = static_cast<eMaterialGeometry>(shader_uuid & geometry_mask);
pipeline_type = static_cast<eMaterialPipeline>((shader_uuid >> 4u) & pipeline_mask);
displacement_type = static_cast<eMaterialDisplacement>((shader_uuid >> 8u) & displacement_mask);
thickness_type = static_cast<eMaterialThickness>((shader_uuid >> 9u) & thickness_mask);
transparent_shadows = (shader_uuid >> 10u) & 1u;
}
static inline uint64_t shader_uuid_from_material_type(
eMaterialPipeline pipeline_type,
eMaterialGeometry geometry_type,
eMaterialDisplacement displacement_type = MAT_DISPLACEMENT_BUMP,
eMaterialThickness thickness_type = MAT_THICKNESS_SPHERE,
char blend_flags = 0)
{
BLI_assert(int64_t(displacement_type) < (1 << 1));
BLI_assert(int64_t(thickness_type) < (1 << 1));
BLI_assert(int64_t(geometry_type) < (1 << 4));
BLI_assert(int64_t(pipeline_type) < (1 << 4));
uint64_t transparent_shadows = blend_flags & MA_BL_TRANSPARENT_SHADOW ? 1 : 0;
uint64_t uuid;
uuid = geometry_type;
uuid |= pipeline_type << 4;
uuid |= displacement_type << 8;
uuid |= thickness_type << 9;
uuid |= transparent_shadows << 10;
return uuid;
}
enum eClosureBits : uint32_t {
CLOSURE_NONE = 0u,
CLOSURE_DIFFUSE = (1u << 0u),
CLOSURE_SSS = (1u << 1u),
CLOSURE_REFLECTION = (1u << 2u),
CLOSURE_REFRACTION = (1u << 3u),
CLOSURE_TRANSLUCENT = (1u << 4u),
CLOSURE_TRANSPARENCY = (1u << 8u),
CLOSURE_EMISSION = (1u << 9u),
CLOSURE_HOLDOUT = (1u << 10u),
CLOSURE_VOLUME = (1u << 11u),
CLOSURE_AMBIENT_OCCLUSION = (1u << 12u),
CLOSURE_SHADER_TO_RGBA = (1u << 13u),
CLOSURE_CLEARCOAT = (1u << 14u),
CLOSURE_TRANSMISSION = CLOSURE_SSS | CLOSURE_REFRACTION | CLOSURE_TRANSLUCENT,
};
ENUM_OPERATORS(eClosureBits)
static inline eClosureBits shader_closure_bits_from_flag(const GPUMaterial *gpumat)
{
eClosureBits closure_bits = eClosureBits(0);
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_DIFFUSE)) {
closure_bits |= CLOSURE_DIFFUSE;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) {
closure_bits |= CLOSURE_TRANSPARENCY;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSLUCENT)) {
closure_bits |= CLOSURE_TRANSLUCENT;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_EMISSION)) {
closure_bits |= CLOSURE_EMISSION;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_GLOSSY)) {
closure_bits |= CLOSURE_REFLECTION;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_COAT)) {
closure_bits |= CLOSURE_CLEARCOAT;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_SUBSURFACE)) {
closure_bits |= CLOSURE_SSS;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_REFRACT)) {
closure_bits |= CLOSURE_REFRACTION;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_HOLDOUT)) {
closure_bits |= CLOSURE_HOLDOUT;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_AO)) {
closure_bits |= CLOSURE_AMBIENT_OCCLUSION;
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_SHADER_TO_RGBA)) {
closure_bits |= CLOSURE_SHADER_TO_RGBA;
}
return closure_bits;
}
/* Count the number of closure bins required for the given combination of closure types. */
static inline int to_gbuffer_bin_count(const eClosureBits closure_bits)
{
int closure_data_slots = 0;
if (closure_bits & CLOSURE_DIFFUSE) {
if ((closure_bits & CLOSURE_TRANSLUCENT) && !(closure_bits & CLOSURE_CLEARCOAT)) {
/* Special case to allow translucent with diffuse without noise.
* Revert back to noise if clear coat is present. */
closure_data_slots |= (1 << 2);
}
else {
closure_data_slots |= (1 << 0);
}
}
if (closure_bits & CLOSURE_SSS) {
closure_data_slots |= (1 << 0);
}
if (closure_bits & CLOSURE_REFRACTION) {
closure_data_slots |= (1 << 0);
}
if (closure_bits & CLOSURE_TRANSLUCENT) {
closure_data_slots |= (1 << 0);
}
if (closure_bits & CLOSURE_REFLECTION) {
closure_data_slots |= (1 << 1);
}
if (closure_bits & CLOSURE_CLEARCOAT) {
closure_data_slots |= (1 << 2);
}
return count_bits_i(closure_data_slots);
};
static inline eMaterialGeometry to_material_geometry(const Object *ob)
{
switch (ob->type) {
case OB_CURVES:
return MAT_GEOM_CURVES;
case OB_VOLUME:
return MAT_GEOM_VOLUME;
case OB_POINTCLOUD:
return MAT_GEOM_POINTCLOUD;
default:
return MAT_GEOM_MESH;
}
}
/**
* Unique key to identify each material in the hash-map.
* This is above the shader binning.
*/
struct MaterialKey {
blender::Material *mat;
uint64_t options;
MaterialKey(blender::Material *mat_,
eMaterialGeometry geometry,
eMaterialPipeline pipeline,
short visibility_flags)
: mat(mat_)
{
options = shader_uuid_from_material_type(pipeline,
geometry,
to_displacement_type(mat_->displacement_method),
to_thickness_type(mat_->thickness_mode),
mat_->blend_flag);
options = (options << 1) | (visibility_flags & OB_HIDE_CAMERA ? 0 : 1);
options = (options << 1) | (visibility_flags & OB_HIDE_SHADOW ? 0 : 1);
options = (options << 1) | (visibility_flags & OB_HIDE_PROBE_CUBEMAP ? 0 : 1);
options = (options << 1) | (visibility_flags & OB_HIDE_PROBE_PLANAR ? 0 : 1);
options = (options << 1) | (visibility_flags & OB_HIDE_RAYCAST ? 0 : 1);
}
uint64_t hash() const
{
return uint64_t(mat) + options;
}
bool operator==(const MaterialKey &k) const
{
return (mat == k.mat) && (options == k.options);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name ShaderKey
*
* \{ */
/**
* Key used to find the sub-pass that already renders objects with the same shader.
* This avoids the cost associated with shader switching.
* This is below the material binning.
* Should only include pipeline options that are not baked in the shader itself.
*/
struct ShaderKey {
gpu::Shader *shader;
uint64_t options;
ShaderKey(GPUMaterial *gpumat,
blender::Material *blender_mat,
eMaterialProbe probe_capture,
bool hide_from_raycast)
{
shader = GPU_material_get_shader(gpumat);
options = uint64_t(shader_closure_bits_from_flag(gpumat));
options = (options << 8) | blender_mat->blend_flag;
options = (options << 2) | uint64_t(probe_capture);
options = (options << 1) | (hide_from_raycast ? 1 : 0);
}
uint64_t hash() const
{
return uint64_t(shader) + options;
}
bool operator==(const ShaderKey &k) const
{
return (shader == k.shader) && (options == k.options);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Material
*
* \{ */
struct MaterialPass {
GPUMaterial *gpumat = nullptr;
PassMain::Sub *sub_pass = nullptr;
};
struct Material {
bool is_alpha_blend_transparent;
bool has_transparent_shadows;
bool has_surface;
bool has_volume;
MaterialPass shadow;
MaterialPass shading;
MaterialPass prepass;
MaterialPass capture;
MaterialPass lightprobe_sphere_prepass;
MaterialPass lightprobe_sphere_shading;
MaterialPass planar_probe_prepass;
MaterialPass planar_probe_shading;
/* These pipelines need a sub-pass per object/instance, so the returned sub_pass for these are
* always null and the sub-pass creation is handled directly by the SyncModule.
* Note that this also applies to the shading MaterialPass in the case of alpha-blended
* materials. */
MaterialPass overlap_masking;
MaterialPass volume_occupancy;
MaterialPass volume_material;
};
struct MaterialArray {
Vector<Material> materials;
Vector<GPUMaterial *> gpu_materials;
};
class MaterialModule {
public:
blender::Material *diffuse_mat;
blender::Material *metallic_mat;
blender::Material *default_surface;
blender::Material *default_volume;
blender::Material *material_override = nullptr;
int64_t queued_shaders_count = 0;
int64_t queued_textures_count = 0;
int64_t queued_optimize_shaders_count = 0;
bool material_time_changed = true;
float material_frame = 0;
float material_time = 0;
private:
Instance &inst_;
Map<MaterialKey, Material> material_map_;
Map<ShaderKey, PassMain::Sub *> shader_map_;
MaterialArray material_array_;
blender::Material *error_mat_;
uint64_t gpu_pass_last_update_ = 0;
uint64_t gpu_pass_next_update_ = 0;
Vector<GPUMaterialTexture *> texture_loading_queue_;
public:
MaterialModule(Instance &inst);
~MaterialModule();
void begin_sync();
void end_sync();
/**
* Returned Material references are valid until the next call to this function or material_get().
*/
MaterialArray &material_array_get(const ObjectHandle &ob_handle, bool has_motion);
/**
* Returned Material references are valid until the next call to this function or
* material_array_get().
*/
Material material_get(const ObjectHandle &ob_handle,
bool has_motion,
int mat_nr,
eMaterialGeometry geometry_type);
/* Request default materials and return DEFAULT_MATERIALS if they are compiled. */
ShaderGroups default_materials_load_async()
{
return default_materials_load(false);
}
ShaderGroups default_materials_wait_ready()
{
return default_materials_load(true);
}
private:
Material &material_sync(const ObjectHandle &ob_handle,
blender::Material *blender_mat,
eMaterialGeometry geometry_type,
bool has_motion);
/** Return correct material or empty default material if slot is empty. */
blender::Material *material_from_slot(Object *ob, int slot);
MaterialPass material_pass_get(Object *ob,
blender::Material *blender_mat,
eMaterialPipeline pipeline_type,
eMaterialGeometry geometry_type,
eMaterialProbe probe_capture = MAT_PROBE_NONE);
/* Push unloaded texture used by this material to the texture loading queue. */
void queue_texture_loading(GPUMaterial *material);
ShaderGroups default_materials_load(bool block_until_ready = false);
};
/** \} */
} // namespace eevee
} // namespace blender

View File

@@ -0,0 +1,70 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
/**
* Defines the specific rendering pass or shading strategy for a material.
* This determines which shader variants are generated.
*/
enum eMaterialPipeline {
/* G-Buffer pass, lighting is calculated in a separate pass. */
MAT_PIPE_DEFERRED = 0,
/* Main shading pass where lighting is calculated per-pixel during geometry submission. */
MAT_PIPE_FORWARD,
/**
* Pre-pass shaders: These are used to populate the Depth buffer and Motion Vectors before
* the main shading pass.
*/
/* Standard depth-only pass for the deferred pipeline. */
MAT_PIPE_PREPASS_DEFERRED,
MAT_PIPE_PREPASS_DEFERRED_VELOCITY,
/* Standard depth-only pass for the forward pipeline (opaque only). */
MAT_PIPE_PREPASS_FORWARD,
MAT_PIPE_PREPASS_FORWARD_VELOCITY,
/* Per object prepass to handle the transparency overlap option. */
MAT_PIPE_PREPASS_OVERLAP,
/* Depth pre-pass specifically for planar reflection probes. */
MAT_PIPE_PREPASS_PLANAR,
/* Pipeline for baking meshes volume occupancy to the froxel grid. */
MAT_PIPE_VOLUME_OCCUPANCY,
/* Pipeline for baking volume material properties to the froxel grid. */
MAT_PIPE_VOLUME_MATERIAL,
/* Pipeline for shadow map rendering. */
MAT_PIPE_SHADOW,
/* Pipeline for surfel capture. */
MAT_PIPE_CAPTURE,
};
/**
* Defines the geometric primitive type the shader is intended to run on.
* This affects attribute fetching and attribute interpolation.
*/
enum eMaterialGeometry {
/* These maps directly to object types. */
MAT_GEOM_MESH = 0,
MAT_GEOM_POINTCLOUD,
MAT_GEOM_CURVES,
MAT_GEOM_VOLUME,
/* Special case: The world background / HDRI environment shader. */
MAT_GEOM_WORLD,
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,261 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#include "BKE_colortools.hh"
#include "RE_engine.h"
#include "GPU_debug.hh"
#include "eevee_instance.hh"
#include "eevee_motion_blur.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name MotionBlurModule
*
* \{ */
void MotionBlurModule::init()
{
const Scene *scene = inst_.scene;
const ViewLayer *view_layer = inst_.view_layer;
/* Disable on viewport outside of animation playback,
* since it can get distracting while editing the scene. */
enabled_ = (scene->r.mode & R_MBLUR) != 0 && (inst_.is_image_render || inst_.is_playback);
if (enabled_) {
enabled_ = (view_layer->layflag & SCE_LAY_MOTION_BLUR) != 0;
}
if (!enabled_) {
motion_blur_fx_enabled_ = false;
return;
}
/* Take into account the steps needed for fx motion blur. */
int steps_count = max_ii(1, scene->eevee.motion_blur_steps) * 2 + 1;
time_steps_.resize(steps_count);
initial_frame_ = scene->r.cfra;
initial_subframe_ = scene->r.subframe;
frame_time_ = initial_frame_ + initial_subframe_;
shutter_position_ = scene->r.motion_blur_position;
shutter_time_ = scene->r.motion_blur_shutter;
data_.depth_scale = scene->eevee.motion_blur_depth_scale;
motion_blur_fx_enabled_ = true; /* TODO(fclem): UI option. */
/* Viewport stops here. We only do Post-FX motion blur. */
if (inst_.is_viewport()) {
enabled_ = false;
return;
}
/* Without this there is the possibility of the curve table not being allocated. */
BKE_curvemapping_changed(const_cast<CurveMapping *>(&scene->r.mblur_shutter_curve), false);
Vector<float> cdf(CM_TABLE);
Sampling::cdf_from_curvemapping(scene->r.mblur_shutter_curve, cdf);
Sampling::cdf_invert(cdf, time_steps_);
for (float &time : time_steps_) {
time = this->shutter_time_to_scene_time(time);
}
step_id_ = 1;
if (motion_blur_fx_enabled_) {
/* A bit weird but we have to sync the first 2 steps here because the step()
* function is only called after rendering a sample. */
inst_.velocity.step_sync(STEP_PREVIOUS, time_steps_[0]);
inst_.velocity.step_sync(STEP_NEXT, time_steps_[2]);
/* Let the main sync loop handle the current step. */
}
inst_.set_time(time_steps_[1]);
}
void MotionBlurModule::step()
{
if (!enabled_) {
return;
}
if (inst_.sampling.finished()) {
/* Restore original frame number. This is because the render pipeline expects it. */
RE_engine_frame_set(inst_.render, initial_frame_, initial_subframe_);
}
else if (inst_.sampling.do_render_sync()) {
/* Time to change motion step. */
BLI_assert(time_steps_.size() > step_id_ + 2);
step_id_ += 2;
if (motion_blur_fx_enabled_) {
inst_.velocity.step_swap();
inst_.velocity.step_sync(eVelocityStep::STEP_NEXT, time_steps_[step_id_ + 1]);
}
inst_.set_time(time_steps_[step_id_]);
}
}
float MotionBlurModule::shutter_time_to_scene_time(float time)
{
switch (shutter_position_) {
case SCE_MB_START:
/* No offset. */
break;
case SCE_MB_CENTER:
time -= 0.5f;
break;
case SCE_MB_END:
time -= 1.0;
break;
default:
BLI_assert_msg(false, "Invalid motion blur position enum!");
break;
}
time *= shutter_time_;
time += frame_time_;
return time;
}
void MotionBlurModule::sync()
{
/* Disable motion blur in viewport when changing camera projection type.
* Avoids really high velocities. */
if (inst_.velocity.camera_changed_projection() ||
(inst_.is_viewport() && (inst_.camera.overscan_changed() || inst_.camera.camera_changed())))
{
motion_blur_fx_enabled_ = false;
}
if (!motion_blur_fx_enabled_) {
return;
}
GPUSamplerState no_filter = GPUSamplerState::default_sampler();
RenderBuffers &render_buffers = inst_.render_buffers;
motion_blur_ps_.init();
motion_blur_ps_.bind_resources(inst_.velocity);
motion_blur_ps_.bind_resources(inst_.sampling);
{
/* Create max velocity tiles. */
PassSimple::Sub &sub = motion_blur_ps_.sub("TilesFlatten");
gpu::TextureFormat vector_tx_format = inst_.render_buffers.vector_tx_format();
eShaderType shader = vector_tx_format == gpu::TextureFormat::SFLOAT_16_16 ?
MOTION_BLUR_TILE_FLATTEN_RG :
MOTION_BLUR_TILE_FLATTEN_RGBA;
sub.shader_set(inst_.shaders.static_shader_get(shader));
sub.bind_ubo("motion_blur_buf", data_);
sub.bind_texture("depth_tx", &render_buffers.depth_tx);
sub.bind_image("velocity_img", &render_buffers.vector_tx);
sub.bind_image("out_tiles_img", &tiles_tx_);
sub.dispatch(&dispatch_flatten_size_);
sub.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
}
{
/* Expand max velocity tiles by spreading them in their neighborhood. */
PassSimple::Sub &sub = motion_blur_ps_.sub("TilesDilate");
sub.shader_set(inst_.shaders.static_shader_get(MOTION_BLUR_TILE_DILATE));
sub.bind_ssbo("tile_indirection_buf", tile_indirection_buf_);
sub.bind_image("in_tiles_img", &tiles_tx_);
sub.dispatch(&dispatch_dilate_size_);
sub.barrier(GPU_BARRIER_SHADER_STORAGE);
}
{
/* Do the motion blur gather algorithm. */
PassSimple::Sub &sub = motion_blur_ps_.sub("ConvolveGather");
sub.shader_set(inst_.shaders.static_shader_get(MOTION_BLUR_GATHER));
sub.bind_ubo("motion_blur_buf", data_);
sub.bind_ssbo("tile_indirection_buf", tile_indirection_buf_);
sub.bind_texture("depth_tx", &render_buffers.depth_tx, no_filter);
sub.bind_texture("velocity_tx", &render_buffers.vector_tx, no_filter);
sub.bind_texture("in_color_tx", &input_color_tx_, no_filter);
sub.bind_image("in_tiles_img", &tiles_tx_);
sub.bind_image("out_color_img", &output_color_tx_);
sub.dispatch(&dispatch_gather_size_);
sub.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
}
void MotionBlurModule::render(View &view, gpu::Texture **input_tx, gpu::Texture **output_tx)
{
if (!motion_blur_fx_enabled_) {
return;
}
const Texture &depth_tx = inst_.render_buffers.depth_tx;
int2 extent = {depth_tx.width(), depth_tx.height()};
int2 tiles_extent = math::divide_ceil(extent, int2(MOTION_BLUR_TILE_SIZE));
if (inst_.is_viewport()) {
float frame_delta_between_steps = fabsf(
inst_.velocity.step_time_delta_get(STEP_PREVIOUS, STEP_CURRENT));
/* Avoid highly disturbing blurs, during navigation with high shutter time. */
if (frame_delta_between_steps > 0.0f && !inst_.is_navigating) {
/* Rescale motion blur intensity to be shutter time relative and avoid long streak when we
* have frame skipping. Always try to stick to what the render frame would look like.
* For viewport, only previous motion is supported and always uses "Center on Frame" shutter
* position. */
data_.motion_scale = float2(0.5f, 0.5f) * float2(shutter_time_ / frame_delta_between_steps);
}
else {
/* There is no time change. Motion only comes from viewport navigation and object transform.
* Apply motion blur as smoothing and only blur towards last frame. */
data_.motion_scale = float2(1.0f, 0.0f);
if (was_navigating_ != inst_.is_navigating) {
/* Special case for navigation events that only last for one frame (for instance mouse
* scroll for zooming). For this case we have to wait for the next frame before enabling
* the navigation motion blur. */
was_navigating_ = inst_.is_navigating;
return;
}
}
was_navigating_ = inst_.is_navigating;
}
else {
data_.motion_scale = float2(1.0f);
}
/* Second motion vector is stored inverted. */
data_.motion_scale.y = -data_.motion_scale.y;
data_.target_size_inv = 1.0f / float2(extent);
data_.push_update();
input_color_tx_ = *input_tx;
output_color_tx_ = *output_tx;
dispatch_flatten_size_ = int3(tiles_extent, 1);
dispatch_dilate_size_ = int3(math::divide_ceil(tiles_extent, int2(MOTION_BLUR_GROUP_SIZE)), 1);
dispatch_gather_size_ = int3(math::divide_ceil(extent, int2(MOTION_BLUR_GROUP_SIZE)), 1);
GPU_debug_group_begin("Motion Blur");
tiles_tx_.acquire_2d(tiles_extent, gpu::TextureFormat::SFLOAT_16_16_16_16);
tile_indirection_buf_.clear_to_zero();
inst_.manager->submit(motion_blur_ps_, view);
tiles_tx_.release();
GPU_debug_group_end();
/* Swap buffers so that next effect has the right input. */
*input_tx = output_color_tx_;
*output_tx = input_color_tx_;
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,136 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Motion blur is done by accumulating scene samples over shutter time.
* Since the number of step is discrete, quite low, and not per pixel randomized,
* we couple this with a post processing motion blur.
*
* The post-fx motion blur is done in two directions, from the previous step and to the next.
*
* For a scene with 3 motion steps, a flat shutter curve and shutter time of 2 frame
* centered on frame we have:
*
* |--------------------|--------------------|
* -1 0 1 Frames
*
* |-------------|-------------|-------------|
* 1 2 3 Motion steps
*
* |------|------|------|------|------|------|
* 0 1 2 4 5 6 7 Time Steps
*
* |-------------| One motion step blurs this range.
* -1 | +1 Objects and geometry steps are recorded here.
* 0 Scene is rendered here.
*
* Since motion step N and N+1 share one time step we reuse it to avoid an extra scene evaluation.
*
* Note that we have to evaluate -1 and +1 time steps before rendering so eval order is -1, +1, 0.
* This is because all GPUBatches from the DRWCache are being free when changing a frame.
*
* For viewport, we only have the current and previous step data to work with. So we center the
* blur on the current frame and extrapolate the motion.
*
* The Post-FX motion blur is based on:
* "A Fast and Stable Feature-Aware Motion Blur Filter"
* by Jean-Philippe Guertin, Morgan McGuire, Derek Nowrouzezahrai
*/
#pragma once
#include "DRW_gpu_wrapper.hh"
#include "eevee_motion_blur_shared.hh"
#include "eevee_sampling.hh"
#include "draw_pass.hh"
namespace blender::eevee {
using namespace draw;
/* -------------------------------------------------------------------- */
/** \name MotionBlur
*
* \{ */
using MotionBlurDataBuf = draw::UniformBuffer<MotionBlurData>;
using MotionBlurTileIndirectionBuf = draw::StorageBuffer<MotionBlurTileIndirection, true>;
/**
* Manages time-steps evaluations and accumulation Motion blur.
* Also handles Post process motion blur.
*/
class MotionBlurModule {
private:
Instance &inst_;
/**
* Array containing all steps (in scene time) we need to evaluate (not render).
* Only odd steps are rendered. The even ones are evaluated for fx motion blur.
*/
Vector<float> time_steps_;
/** Copy of input frame and sub-frame to restore after render. */
int initial_frame_;
float initial_subframe_;
/** Time of the frame we are rendering. */
float frame_time_;
/** Enum controlling when the shutter opens. See RenderData.motion_blur_position. */
int shutter_position_;
/** Time in scene frame the shutter is open. Controls the amount of blur. */
float shutter_time_;
/** True if motion blur is enabled as a module. */
bool enabled_ = false;
/** True if motion blur post-fx is enabled. */
bool motion_blur_fx_enabled_ = false;
/** True if last viewport redraw state was already in navigation state. */
bool was_navigating_ = false;
int step_id_ = 0;
/** Velocity tiles used to guide and speedup the gather pass. */
TextureFromPool tiles_tx_;
gpu::Texture *input_color_tx_ = nullptr;
gpu::Texture *output_color_tx_ = nullptr;
PassSimple motion_blur_ps_ = {"MotionBlur"};
MotionBlurTileIndirectionBuf tile_indirection_buf_;
MotionBlurDataBuf data_;
/** Dispatch size for full-screen passes. */
int3 dispatch_flatten_size_ = int3(0);
int3 dispatch_dilate_size_ = int3(0);
int3 dispatch_gather_size_ = int3(0);
public:
MotionBlurModule(Instance &inst) : inst_(inst) {};
~MotionBlurModule() {};
void init();
/* Runs after rendering a sample. */
void step();
void sync();
bool postfx_enabled() const
{
return motion_blur_fx_enabled_;
}
void render(View &view, gpu::Texture **input_tx, gpu::Texture **output_tx);
private:
float shutter_time_to_scene_time(float time);
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
#define MOTION_BLUR_TILE_SIZE 32
#define MOTION_BLUR_MAX_TILE 512 /* 16384 / MOTION_BLUR_TILE_SIZE */
struct [[host_shared]] MotionBlurData {
/** As the name suggests. Used to avoid a division in the sampling. */
float2 target_size_inv;
/** Viewport motion scaling factor. Make blur relative to frame time not render time. */
float2 motion_scale;
/** Depth scaling factor. Avoid blurring background behind moving objects. */
float depth_scale;
float _pad0;
float _pad1;
float _pad2;
};
/* For some reasons some GLSL compilers do not like this struct.
* So we declare it as a uint array instead and do indexing ourselves. */
#ifndef GPU_SHADER
struct MotionBlurTileIndirection {
/**
* Stores indirection to the tile with the highest velocity covering each tile.
* This is stored using velocity in the MSB to be able to use atomicMax operations.
*/
uint prev[MOTION_BLUR_MAX_TILE][MOTION_BLUR_MAX_TILE];
uint next[MOTION_BLUR_MAX_TILE][MOTION_BLUR_MAX_TILE];
};
#endif
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,966 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Shading passes contain draw-calls specific to shading pipelines.
* They are shared across views.
* This file is only for shading passes. Other passes are declared in their own module.
*/
#pragma once
#include "BLI_math_bits.h"
#include "DRW_render.hh"
#include "eevee_defines.hh"
#include "eevee_lut.hh"
#include "eevee_material.hh"
#include "eevee_raytrace.hh"
#include "eevee_subsurface.hh"
#include "eevee_uniform_shared.hh"
namespace blender {
struct Camera;
namespace eevee {
class Instance;
struct RayTraceBuffer;
/* -------------------------------------------------------------------- */
/** \name World Background Pipeline
*
* Render world background values.
* \{ */
class BackgroundPipeline {
private:
Instance &inst_;
PassSimple clear_ps_ = {"World.Background.Clear"};
PassSimple world_ps_ = {"World.Background"};
public:
BackgroundPipeline(Instance &inst) : inst_(inst) {};
void sync(GPUMaterial *gpumat, float background_opacity, float background_blur);
void clear(View &view);
void render(View &view, Framebuffer &combined_fb);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \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"};
bool use_lightpath_node_ = false;
public:
WorldPipeline(Instance &inst) : inst_(inst) {};
void sync(GPUMaterial *gpumat);
void render(View &view);
/* NOTE: Is valid after WorldPipeline::sync. */
bool use_lightpath_node() const
{
return use_lightpath_node_;
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Volume Pipeline
*
* \{ */
class WorldVolumePipeline {
private:
Instance &inst_;
bool is_valid_;
PassSimple world_ps_ = {"World.Volume"};
public:
WorldVolumePipeline(Instance &inst) : inst_(inst) {};
void sync(GPUMaterial *gpumat);
void render(View &view);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pass
*
* \{ */
class ShadowPipeline {
private:
Instance &inst_;
/* Shadow update pass. */
PassMain render_ps_ = {"Shadow.Surface"};
/* Shadow surface render sub-passes. */
PassMain::Sub *surface_double_sided_ps_ = nullptr;
PassMain::Sub *surface_single_sided_ps_ = nullptr;
public:
ShadowPipeline(Instance &inst) : inst_(inst) {};
PassMain::Sub *surface_material_add(blender::Material *material, GPUMaterial *gpumat);
void sync();
void render(View &view);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Prepass
*
* Helper class for handling prepasses in Forward and Deferred pipelines.
* \{ */
class Prepass {
Instance &inst_;
PassMain pass_{"Prepass"};
PassMain::Sub *subs_[2 /*hide from raycast*/][2 /*double sided*/][2 /*moving*/][2 /*write id*/] =
{{{{nullptr}}}};
PassMain::Sub *setup_subs_[2 /*hide from raycast*/][2 /*double sided*/][2 /*moving*/]
[2 /*write id*/] = {{{{nullptr}}}};
DRWState common_state_{};
bool supports_motion_vectors_ = false;
bool supports_raycast_visibility_ = false;
/* These are never read in practice,
* only needed for GPU API correctness without extra shader variants. */
Texture dummy_raycast_depth_tx_{"prepass.dummy_raycast_depth_tx_"};
Texture dummy_raycast_id_tx_{"prepass.dummy_raycast_id_tx_"};
Texture dummy_raycast_normal_tx_{"prepass.dummy_raycast_normal_tx_"};
/* Copies of UniformDataModule::pipeline with can_raycast overridden.
* Needed so we can render the whole Prepass in a single PassMain. */
draw::UniformBuffer<PipelineInfoData> pipeline_buf_copy_{"prepass.pipeline_buf"};
draw::UniformBuffer<PipelineInfoData> pipeline_buf_copy_hide_from_raycast_{
"prepass.pipeline_buf_hide_from_raycast"};
gpu::Texture *fb_depth_tx_;
public:
Prepass(Instance &inst) : inst_(inst) {};
void init(DRWState extra_state = DRW_STATE_NO_DRAW,
bool supports_motion_vectors = true,
bool supports_raycast_visibility = true,
FunctionRef<void(PassMain &pass)> pass_setup_cb = {});
PassMain::Sub *add(blender::Material *blender_mat,
GPUMaterial *gpumat,
bool has_motion,
bool hide_from_raycast);
void end_sync();
void render(View &view, gpu::Texture *fb_depth_tx, bool can_raycast);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Forward Pass
*
* Handles alpha blended surfaces and NPR materials (using Closure to RGBA).
* \{ */
class ForwardPipeline {
private:
Instance &inst_;
Prepass prepass_{inst_};
PassMain opaque_ps_ = {"Shading"};
PassMain::Sub *opaque_subpasses_[2 /*Raycast*/][2 /*Double-Sided*/] = {{nullptr}};
PassMain::Sub *get_opaque_subpass(blender::Material *blender_mat, GPUMaterial *gpumat)
{
const bool has_raycast = GPU_material_flag_get(gpumat, GPU_MATFLAG_RAYCAST);
const bool double_sided = !(blender_mat->blend_flag & MA_BL_CULL_BACKFACE);
return opaque_subpasses_[has_raycast][double_sided];
}
PassSortable transparent_ps_ = {"Forward.Transparent"};
float3 camera_forward_;
PassSimple resolve_ps_ = {"Forward.Resolve"};
bool has_opaque_ = false;
bool has_transparent_ = false;
bool has_colored_transparency_ = false;
bool has_holdout_ = false;
struct TransparencyBuffer {
/* Channels are packed separately for technical reason (see eevee_surf_forward_frag.glsl for
* explanation). In the case of monochromatic transparency, the #r_channel_tx actually
* contains the whole RGBA and the other textures are dummy texture not attached to the
* frame-buffer. The #a_channel_tx is only allocated if holdout or film transparency is
* enabled. */
TextureFromPool r_channel_tx;
TextureFromPool g_channel_tx;
TextureFromPool b_channel_tx;
TextureFromPool a_channel_tx;
void acquire(int2 extent, bool use_colored_transparency);
void release();
} transp_buffer_;
public:
ForwardPipeline(Instance &inst) : inst_(inst) {}
void sync();
void end_sync();
PassMain::Sub *prepass_opaque_add(blender::Material *blender_mat,
GPUMaterial *gpumat,
bool has_motion);
PassMain::Sub *material_opaque_add(const Object *ob,
blender::Material *blender_mat,
GPUMaterial *gpumat);
void transparent_add(const Object *ob,
const float3 &ob_location,
blender::Material *blender_mat,
GPUMaterial *gpumat,
PassMain::Sub *&r_prepass_subpass,
PassMain::Sub *&r_material_subpass);
bool use_colored_transparency() const;
void render(View &view,
gpu::Texture *depth_tx,
Framebuffer &prepass_fb,
Framebuffer &transparent_fb,
Framebuffer &combined_fb,
int2 extent);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Deferred lighting.
* \{ */
struct DeferredLayerBase {
Prepass prepass_;
PassSimple clear_aovs_ps_{"Clear AOVs"};
PassMain gbuffer_ps_ = {"Shading"};
PassMain::Sub *gbuffer_subpasses_[2 /*Hybrid*/][2 /*Raycast*/][2 /*Double-Sided*/] = {
{{nullptr}}};
DeferredLayerBase(Instance &inst) : prepass_(inst) {};
PassMain::Sub *get_gbuffer_subpass(blender::Material *blender_mat, GPUMaterial *gpumat)
{
const bool has_shader_to_rgba = GPU_material_flag_get(gpumat, GPU_MATFLAG_SHADER_TO_RGBA);
const bool has_raycast = GPU_material_flag_get(gpumat, GPU_MATFLAG_RAYCAST);
const bool double_sided = !(blender_mat->blend_flag & MA_BL_CULL_BACKFACE);
return gbuffer_subpasses_[has_shader_to_rgba][has_raycast][double_sided];
}
gpu::Texture *radiance_behind_tx_ = nullptr;
/* Closures bits from the materials in this pass. */
eClosureBits closure_bits_ = CLOSURE_NONE;
/* Maximum closure count considering all material in this pass. */
int closure_count_ = 0;
/* True if this is a planar probe deferred layer. To be set before sync. */
bool is_probe_ = false;
/* Stencil values used during the deferred pipeline. */
enum class StencilBits : uint8_t {
/* Bits 0 to 1 are reserved for closure count [0..3]. */
CLOSURE_COUNT_0 = (1u << 0u),
CLOSURE_COUNT_1 = (1u << 1u),
/* Set for pixels have a transmission closure. */
TRANSMISSION = (1u << 2u),
/** Bits set by the StencilClassify pass. Set per pixel from gbuffer header data. */
HEADER_BITS = CLOSURE_COUNT_0 | CLOSURE_COUNT_1 | TRANSMISSION,
/* Set for materials that uses the shadow amend pass. */
THICKNESS_FROM_SHADOW = (1u << 3u),
/** Bits set by the material gbuffer pass. Set per materials. */
MATERIAL_BITS = THICKNESS_FROM_SHADOW,
};
/* Return the amount of gbuffer layer needed. */
int header_layer_count() const
{
/* Default header. */
int count = 1;
/* SSS, light linking, shadow offset all require an additional layer to store the object ID.
* Since tracking these are not part of the closure bits and are rather common features,
* always require one layer for it. */
count += 1;
return count;
}
/* Return the amount of gbuffer layer needed. */
int closure_layer_count() const
{
/* Always allocate 2 layer per closure for interleaved closure data packing in the gbuffer. */
return 2 * to_gbuffer_bin_count(closure_bits_);
}
/* Return the amount of gbuffer layer needed. */
int normal_layer_count() const
{
/* TODO(fclem): We could count the number of different tangent frame in the shader and use
* min(tangent_frame_count, closure_count) once we have the normal reuse optimization.
* For now, allocate a custom normal layer for each Closure. */
int count = to_gbuffer_bin_count(closure_bits_);
/* Count the additional information layer needed by some closures. */
count += count_bits_i(closure_bits_ & CLOSURE_TRANSMISSION);
return count;
}
eClosureBits closure_bits_get() const
{
return closure_bits_;
}
void gbuffer_pass_sync(Instance &inst);
};
class DeferredPipeline;
class DeferredLayer : DeferredLayerBase {
friend DeferredPipeline;
private:
Instance &inst_;
static constexpr int max_lighting_tile_count_ = 128 * 128;
/* Evaluate all light objects contribution. */
PassSimple eval_light_ps_ = {"EvalLights"};
/* Combine direct and indirect light contributions and apply BSDF color. */
PassSimple combine_ps_ = {"Combine"};
/**
* Accumulation textures for all stages of lighting evaluation (Light, SSR, SSSS, SSGI ...).
* These are split and separate from the main radiance buffer in order to accumulate light for
* the render passes and avoid too much bandwidth waste. Otherwise, we would have to load the
* BSDF color and do additive blending for each of the lighting step.
*
* NOTE: Not to be confused with the render passes.
* NOTE: Using array of texture instead of texture array to allow to use TextureFromPool.
*/
TextureFromPool direct_radiance_txs_[3] = {
{"direct_radiance_1"}, {"direct_radiance_2"}, {"direct_radiance_3"}};
/* NOTE: Only used when `use_split_radiance` is true. */
TextureFromPool indirect_radiance_txs_[3] = {
{"indirect_radiance_1"}, {"indirect_radiance_2"}, {"indirect_radiance_3"}};
/* Used when there is no indirect radiance buffer. */
Texture dummy_black = {"dummy_black"};
/* Reference to ray-tracing results. */
gpu::Texture *radiance_feedback_tx_ = nullptr;
/**
* Tile texture containing several bool per tile indicating presence of feature.
* It is used to select specialized shader for each tile.
*/
Texture tile_mask_tx_ = {"tile_mask_tx_"};
RayTraceResult indirect_result_;
bool use_split_radiance_ = true;
/* Output radiance from the combine shader instead of copy. Allow passing unclamped result. */
bool use_feedback_output_ = false;
bool use_raytracing_ = false;
bool use_screen_transmission_ = false;
bool use_screen_reflection_ = false;
bool use_clamp_direct_ = false;
bool use_clamp_indirect_ = false;
public:
DeferredLayer(Instance &inst) : DeferredLayerBase(inst), inst_(inst)
{
float4 data(0.0f);
dummy_black.ensure_2d(gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT,
int2(1),
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE,
data);
}
void begin_sync();
void end_sync(bool is_first_pass, bool is_last_pass, bool next_layer_has_transmission);
PassMain::Sub *prepass_add(blender::Material *blender_mat,
GPUMaterial *gpumat,
bool has_motion,
bool hide_from_raycast);
PassMain::Sub *material_add(blender::Material *blender_mat, GPUMaterial *gpumat);
bool is_empty() const
{
return closure_count_ == 0;
}
bool has_transmission() const
{
return (closure_bits_ & CLOSURE_TRANSMISSION) ||
((closure_bits_ & CLOSURE_TRANSPARENCY) && (closure_bits_ & CLOSURE_SHADER_TO_RGBA));
}
/* Do we compute indirect lighting inside the light eval pass. */
static bool do_merge_direct_indirect_eval(const Instance &inst);
/* Is the radiance split for the lighting pass. */
static bool do_split_direct_indirect_radiance(const Instance &inst);
/* Returns the radiance buffer to feed the next layer. */
gpu::Texture *render(View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
Framebuffer &gbuffer_fb,
int2 extent,
RayTraceBuffer &rt_buffer,
gpu::Texture *radiance_behind_tx);
};
class DeferredPipeline {
friend DeferredLayer;
private:
/* Gbuffer filling passes. We could have an arbitrary number of them but for now we just have
* a hardcoded number of them. */
DeferredLayer opaque_layer_;
DeferredLayer refraction_layer_;
DeferredLayer volumetric_layer_;
PassSimple debug_draw_ps_ = {"debug_gbuffer"};
public:
DeferredPipeline(Instance &inst)
: opaque_layer_(inst), refraction_layer_(inst), volumetric_layer_(inst) {};
void begin_sync();
void end_sync();
PassMain::Sub *prepass_add(blender::Material *blender_mat,
GPUMaterial *gpumat,
bool has_motion,
bool hide_from_raycast);
PassMain::Sub *material_add(blender::Material *blender_mat, GPUMaterial *gpumat);
void render(View &main_view,
View &render_view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
Framebuffer &gbuffer_fb,
int2 extent,
RayTraceBuffer &rt_buffer_opaque_layer,
RayTraceBuffer &rt_buffer_refract_layer);
/* Return the maximum amount of gbuffer layer needed. */
int header_layer_count() const
{
return max_ii(opaque_layer_.header_layer_count(), refraction_layer_.header_layer_count());
}
/* Return the maximum amount of gbuffer layer needed. */
int closure_layer_count() const
{
return max_ii(opaque_layer_.closure_layer_count(), refraction_layer_.closure_layer_count());
}
/* Return the maximum amount of gbuffer layer needed. */
int normal_layer_count() const
{
return max_ii(opaque_layer_.normal_layer_count(), refraction_layer_.normal_layer_count());
}
void debug_draw(draw::View &view, gpu::FrameBuffer *combined_fb);
bool is_empty() const
{
return opaque_layer_.is_empty() && refraction_layer_.is_empty();
}
eClosureBits closure_bits_get() const
{
return opaque_layer_.closure_bits_get() | refraction_layer_.closure_bits_get();
}
private:
void debug_pass_sync();
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Volume Pass
*
* \{ */
struct VolumeObjectBounds {
/* Screen 2D bounds for layer intersection check. */
std::optional<Bounds<float2>> screen_bounds;
/* Combined bounds in Z. Allow tighter integration bounds. */
std::optional<Bounds<float>> z_range;
VolumeObjectBounds(const Camera &camera, const ObjectHandle &ob_handle, int instance_index);
};
/**
* A volume layer contains a list of non-overlapping volume objects.
*/
class VolumeLayer {
public:
bool use_hit_list = false;
bool is_empty = true;
bool finalized = false;
bool has_scatter = false;
bool has_absorption = false;
private:
Instance &inst_;
PassMain volume_layer_ps_ = {"Volume.Layer"};
/* Sub-passes of volume_layer_ps. */
PassMain::Sub *occupancy_ps_;
PassMain::Sub *material_ps_;
/* List of bounds from all objects contained inside this pass. */
Vector<std::optional<Bounds<float2>>> object_bounds_;
/* Combined bounds from object_bounds_. */
std::optional<Bounds<float2>> combined_screen_bounds_;
public:
VolumeLayer(Instance &inst) : inst_(inst)
{
this->sync();
}
PassMain::Sub *occupancy_add(const Object *ob,
const blender::Material *blender_mat,
GPUMaterial *gpumat);
PassMain::Sub *material_add(const Object *ob,
const blender::Material *blender_mat,
GPUMaterial *gpumat);
/* Return true if the given bounds overlaps any of the contained object in this layer. */
bool bounds_overlaps(const VolumeObjectBounds &object_bounds) const;
void add_object_bound(const VolumeObjectBounds &object_bounds);
void sync();
void render(View &view, Texture &occupancy_tx);
};
class VolumePipeline {
private:
Instance &inst_;
Vector<std::unique_ptr<VolumeLayer>> layers_;
/* Combined bounds in Z. Allow tighter integration bounds. */
std::optional<Bounds<float>> object_integration_range_;
/* Aggregated properties of all volume objects. */
bool has_scatter_ = false;
bool has_absorption_ = false;
public:
VolumePipeline(Instance &inst) : inst_(inst) {};
void sync();
void render(View &view, Texture &occupancy_tx);
VolumeLayer *register_and_get_layer(const VolumeObjectBounds &object_bounds);
std::optional<Bounds<float>> object_integration_range() const;
bool has_scatter() const
{
for (const auto &layer : layers_) {
if (layer->has_scatter) {
return true;
}
}
return false;
}
bool has_absorption() const
{
for (const auto &layer : layers_) {
if (layer->has_absorption) {
return true;
}
}
return false;
}
/* Returns true if any volume layer uses the hist list. */
bool use_hit_list() const;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Deferred Probe Capture.
* \{ */
class DeferredProbePipeline {
private:
Instance &inst_;
DeferredLayerBase opaque_layer_;
PassSimple eval_light_ps_ = {"EvalLights"};
/* Used when there is no feedback radiance buffer. */
Texture dummy_black = {"dummy_black"};
public:
DeferredProbePipeline(Instance &inst) : inst_(inst), opaque_layer_(inst)
{
float4 data(0.0f);
dummy_black.ensure_2d(
gpu::TextureFormat::SFLOAT_16_16_16_16, int2(1), GPU_TEXTURE_USAGE_SHADER_READ, data);
opaque_layer_.is_probe_ = true;
}
void begin_sync();
void end_sync();
PassMain::Sub *prepass_add(blender::Material *blender_mat,
GPUMaterial *gpumat,
bool hide_from_raycast);
PassMain::Sub *material_add(blender::Material *blender_mat, GPUMaterial *gpumat);
void render(View &view,
Framebuffer &prepass_fb,
Framebuffer &combined_fb,
Framebuffer &gbuffer_fb,
int2 extent);
/* Return the maximum amount of gbuffer layer needed. */
int header_layer_count() const
{
return opaque_layer_.header_layer_count();
}
/* Return the maximum amount of gbuffer layer needed. */
int closure_layer_count() const
{
return opaque_layer_.closure_layer_count();
}
/* Return the maximum amount of gbuffer layer needed. */
int normal_layer_count() const
{
return opaque_layer_.normal_layer_count();
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Deferred Planar Probe Capture.
* \{ */
class PlanarProbePipeline : DeferredLayerBase {
private:
Instance &inst_;
PassSimple eval_light_ps_ = {"EvalLights"};
/* Used when there is no indirect radiance buffer. */
Texture dummy_black_ = {"dummy_black"};
public:
PlanarProbePipeline(Instance &inst) : DeferredLayerBase(inst), inst_(inst)
{
float4 data(0.0f);
dummy_black_.ensure_2d(
gpu::TextureFormat::SFLOAT_16_16_16_16, int2(1), GPU_TEXTURE_USAGE_SHADER_READ, data);
is_probe_ = true;
};
void begin_sync();
void end_sync();
PassMain::Sub *prepass_add(blender::Material *blender_mat,
GPUMaterial *gpumat,
bool hide_from_raycast);
PassMain::Sub *material_add(blender::Material *blender_mat, GPUMaterial *gpumat);
void render(View &view,
gpu::Texture *depth_layer_tx,
Framebuffer &prepass_fb,
Framebuffer &gbuffer,
Framebuffer &combined_fb,
int2 extent);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture Pipeline
*
* \{ */
class CapturePipeline {
private:
Instance &inst_;
PassMain surface_ps_ = {"Capture.Surface"};
public:
CapturePipeline(Instance &inst) : inst_(inst) {};
PassMain::Sub *surface_material_add(blender::Material *blender_mat, GPUMaterial *gpumat);
void sync();
void render(View &view);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Utility texture
*
* 64x64 2D array texture containing LUT tables and blue noises.
* \{ */
class UtilityTexture : public Texture {
struct Layer {
float4 data[UTIL_TEX_SIZE][UTIL_TEX_SIZE];
};
static constexpr int lut_size = UTIL_TEX_SIZE;
static constexpr int lut_size_sqr = lut_size * lut_size;
static constexpr int layer_count = UTIL_BSDF_LAYER + UTIL_BSDF_LAYER_COUNT;
public:
UtilityTexture()
: Texture("UtilityTx",
gpu::TextureFormat::SFLOAT_16_16_16_16,
GPU_TEXTURE_USAGE_SHADER_READ,
int2(lut_size),
layer_count,
nullptr)
{
Vector<Layer> data(layer_count);
{
Layer &layer = data[UTIL_BLUE_NOISE_LAYER];
memcpy(layer.data, lut::blue_noise, sizeof(layer));
}
{
Layer &layer = data[UTIL_SSS_TRANSMITTANCE_PROFILE_LAYER];
for (auto y : IndexRange(lut_size)) {
for (auto x : IndexRange(lut_size)) {
/* Repeatedly stored on every row for correct interpolation. */
layer.data[y][x][0] = lut::burley_sss_profile[x][0];
layer.data[y][x][1] = lut::random_walk_sss_profile[x][0];
layer.data[y][x][2] = 0.0f;
layer.data[y][x][UTIL_DISK_INTEGRAL_COMP] = lut::ltc_disk_integral[y][x][0];
}
}
BLI_assert(UTIL_SSS_TRANSMITTANCE_PROFILE_LAYER == UTIL_DISK_INTEGRAL_LAYER);
}
{
Layer &layer = data[UTIL_LTC_MAT_LAYER];
memcpy(layer.data, lut::ltc_mat_ggx, sizeof(layer));
}
{
Layer &layer = data[UTIL_BRDF_LAYER];
for (auto x : IndexRange(lut_size)) {
for (auto y : IndexRange(lut_size)) {
layer.data[y][x][0] = lut::brdf_ggx[y][x][0];
layer.data[y][x][1] = lut::brdf_ggx[y][x][1];
layer.data[y][x][2] = lut::brdf_ggx[y][x][2];
layer.data[y][x][3] = 0.0f;
}
}
}
{
for (auto layer_id : IndexRange(16)) {
Layer &layer = data[UTIL_BSDF_LAYER + layer_id];
for (auto x : IndexRange(lut_size)) {
for (auto y : IndexRange(lut_size)) {
layer.data[y][x][0] = lut::bsdf_ggx[layer_id][y][x][0];
layer.data[y][x][1] = lut::bsdf_ggx[layer_id][y][x][1];
layer.data[y][x][2] = lut::bsdf_ggx[layer_id][y][x][2];
layer.data[y][x][3] = lut::btdf_ggx[layer_id][y][x][0];
}
}
}
}
GPU_texture_update_mipmap(*this, 0, GPU_DATA_FLOAT, data.data());
}
~UtilityTexture() = default;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Pipelines
*
* Contains Shading passes. Shared between views. Objects will subscribe to at least one of them.
* \{ */
class PipelineModule {
public:
BackgroundPipeline background;
WorldPipeline world;
WorldVolumePipeline world_volume;
DeferredProbePipeline probe;
PlanarProbePipeline planar;
DeferredPipeline deferred;
ForwardPipeline forward;
ShadowPipeline shadow;
VolumePipeline volume;
CapturePipeline capture;
UtilityTexture utility_tx;
PipelineInfoData &data;
bool has_raycast = false;
PipelineModule(Instance &inst, PipelineInfoData &data)
: background(inst),
world(inst),
world_volume(inst),
probe(inst),
planar(inst),
deferred(inst),
forward(inst),
shadow(inst),
volume(inst),
capture(inst),
data(data) {};
void begin_sync()
{
data.ray_type = RAY_TYPE_CAMERA;
data.can_raycast = true;
probe.begin_sync();
planar.begin_sync();
deferred.begin_sync();
forward.sync();
shadow.sync();
volume.sync();
capture.sync();
has_raycast = false;
}
void end_sync()
{
probe.end_sync();
planar.end_sync();
deferred.end_sync();
forward.end_sync();
}
PassMain::Sub *material_add(Object *ob,
blender::Material *blender_mat,
GPUMaterial *gpumat,
eMaterialPipeline pipeline_type,
eMaterialProbe probe_capture)
{
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_RAYCAST)) {
has_raycast = true;
}
const bool hide_from_raycast = ob->visibility_flag & OB_HIDE_RAYCAST;
if (probe_capture == MAT_PROBE_REFLECTION) {
switch (pipeline_type) {
case MAT_PIPE_PREPASS_DEFERRED:
return probe.prepass_add(blender_mat, gpumat, hide_from_raycast);
case MAT_PIPE_DEFERRED:
return probe.material_add(blender_mat, gpumat);
default:
BLI_assert_unreachable();
break;
}
}
if (probe_capture == MAT_PROBE_PLANAR) {
switch (pipeline_type) {
case MAT_PIPE_PREPASS_PLANAR:
return planar.prepass_add(blender_mat, gpumat, hide_from_raycast);
case MAT_PIPE_DEFERRED:
return planar.material_add(blender_mat, gpumat);
default:
BLI_assert_unreachable();
break;
}
}
switch (pipeline_type) {
case MAT_PIPE_PREPASS_DEFERRED:
return deferred.prepass_add(blender_mat, gpumat, false, hide_from_raycast);
case MAT_PIPE_PREPASS_FORWARD:
return forward.prepass_opaque_add(blender_mat, gpumat, false);
case MAT_PIPE_PREPASS_OVERLAP:
BLI_assert_msg(0, "Overlap prepass should register to the forward pipeline directly.");
return nullptr;
case MAT_PIPE_PREPASS_DEFERRED_VELOCITY:
return deferred.prepass_add(blender_mat, gpumat, true, hide_from_raycast);
case MAT_PIPE_PREPASS_FORWARD_VELOCITY:
return forward.prepass_opaque_add(blender_mat, gpumat, true);
case MAT_PIPE_DEFERRED:
return deferred.material_add(blender_mat, gpumat);
case MAT_PIPE_FORWARD:
return forward.material_opaque_add(ob, blender_mat, gpumat);
case MAT_PIPE_SHADOW:
return shadow.surface_material_add(blender_mat, gpumat);
case MAT_PIPE_CAPTURE:
return capture.surface_material_add(blender_mat, gpumat);
case MAT_PIPE_VOLUME_OCCUPANCY:
case MAT_PIPE_VOLUME_MATERIAL:
BLI_assert_msg(0, "Volume shaders must register to the volume pipeline directly.");
return nullptr;
case MAT_PIPE_PREPASS_PLANAR:
/* Should be handled by the `probe_capture == MAT_PROBE_PLANAR` case. */
BLI_assert_unreachable();
return nullptr;
}
return nullptr;
}
};
/** \} */
} // namespace eevee
} // namespace blender

View File

@@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* LUT generation module.
*/
#include "GPU_state.hh"
#include "draw_pass.hh"
#include "eevee_defines.hh"
#include "eevee_precompute.hh"
namespace blender::eevee {
Precompute::Precompute(draw::Manager &manager, PrecomputeType type, int3 table_extent)
{
table_extent_ = table_extent;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_HOST_READ;
Texture table_tx = {"Precompute"};
table_tx.ensure_3d(gpu::TextureFormat::SFLOAT_32_32_32_32, table_extent, usage);
gpu::Shader *shader = GPU_shader_create_from_info_name("eevee_lut_comp");
PassSimple lut_ps = {"Precompute"};
lut_ps.shader_set(shader);
lut_ps.push_constant("type", int(type));
lut_ps.push_constant("extent", table_extent);
lut_ps.bind_image("image", table_tx);
lut_ps.dispatch(math::divide_ceil(table_extent, int3(int2(LUT_WORKGROUP_SIZE), 1)));
lut_ps.barrier(GPU_BARRIER_TEXTURE_UPDATE);
manager.submit(lut_ps);
raw_data_ = table_tx.read<float4>(GPU_DATA_FLOAT);
GPU_shader_unbind();
GPU_shader_free(shader);
}
Precompute::~Precompute()
{
MEM_SAFE_DELETE(raw_data_);
}
} // namespace blender::eevee

View File

@@ -0,0 +1,173 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* LUT generation module.
*/
#pragma once
#include "BLI_math_vector_types.hh"
#include "eevee_precompute_shared.hh"
#include "draw_manager.hh"
#include <fstream>
namespace blender::eevee {
using namespace draw;
/**
* Create a look-up table of the specified type using GPU compute.
* Not to be used at runtime in final release.
* Usage example: `Precompute(manager, LUT_GGX_BRDF_SPLIT_SUM, {64, 64, 1}).data<float2>()`
*/
class Precompute {
private:
int3 table_extent_;
float4 *raw_data_ = nullptr;
public:
Precompute(draw::Manager &manager, PrecomputeType type, int3 table_extent);
~Precompute();
/* Cast each pixel data to type `T`. */
template<typename T> Vector<T> data()
{
int64_t table_len = table_extent_.x * table_extent_.y * table_extent_.z;
Vector<T> out_data(table_len);
for (auto i : IndexRange(table_len)) {
out_data[i] = T(raw_data_[i]);
}
return out_data;
}
/**
* Write the content of a texture to a PFM image file for inspection.
* OpenGL texture coordinate convention with Y up is respected.
*/
template<typename VecT>
static void write_to_pfm(StringRefNull name,
Span<VecT> pixels,
int64_t n_x,
int64_t n_y = 1,
int64_t n_z = 1,
int64_t n_w = 1)
{
BLI_STATIC_ASSERT(VecT::type_length < 4, "4 component PFM are not possible");
std::ofstream file;
/* Write PFM header. */
file.open(std::string(name) + ".pfm");
file << "PF\n";
file << n_x * n_z << " " << n_y * n_w << "\n";
/* NOTE: this is endianness-sensitive.
* Big endian system would have needed `1.0` value instead. */
file << "-1.0\n";
file.close();
/* Write binary float content. */
file.open(std::string(name) + ".pfm", std::ios_base::app | std::ios::out | std::ios::binary);
/* Iterate over destination pixels. */
for (int64_t y : IndexRange(n_y * n_w)) {
for (int64_t x : IndexRange(n_x * n_z)) {
int64_t src_w = y / n_y;
int64_t src_z = x / n_x;
int64_t src_y = y % n_y;
int64_t src_x = x % n_x;
int64_t src = (n_x * n_y * n_z * src_w) + (n_x * n_y * src_z) + (n_x * src_y) + src_x;
float3 data(0.0f);
for (int c : IndexRange(VecT::type_length)) {
data[c] = pixels[src][c];
}
file.write(reinterpret_cast<char *>(&data), sizeof(float3));
}
}
file.close();
}
/**
* Write the content of a texture as a C++ header file array.
* The content is to be copied to `eevee_lut.cc` and formatted with `make format`.
*/
template<typename VecT>
static void write_to_header(StringRefNull name,
Span<VecT> pixels,
int64_t n_x,
int64_t n_y = 1,
int64_t n_z = 1,
int64_t n_w = 1)
{
std::ofstream file;
file.open(std::string(name) + ".hh");
file << "const float " << name;
if (n_w > 1) {
file << "[" << n_w << "]";
}
if (n_z > 1) {
file << "[" << n_z << "]";
}
if (n_y > 1) {
file << "[" << n_y << "]";
}
if (n_x > 1) {
file << "[" << n_x << "]";
}
file << "[" << VecT::type_length << "]";
file << " = {\n";
/* Print data formatted as C++ array. */
for (auto w : IndexRange(n_w)) {
if (n_w > 1) {
file << "{\n";
}
for (auto z : IndexRange(n_z)) {
if (n_z > 1 || n_w > 1) {
file << "{\n";
}
for (auto y : IndexRange(n_y)) {
if (n_y > 1 || n_z > 1 || n_w > 1) {
file << "{\n";
}
for (auto x : IndexRange(n_x)) {
if (n_x > 1 || n_y > 1 || n_z > 1 || n_w > 1) {
file << "{";
}
int64_t pixel_index = (n_x * n_y * n_z * w) + (n_x * n_y * z) + (n_x * y) + x;
for (auto c : IndexRange(VecT::type_length)) {
file << std::to_string(pixels[pixel_index][c]);
if (c + 1 < VecT::type_length) {
file << "f, ";
}
else {
file << "f";
}
}
if (n_x > 1 || n_y > 1 || n_z > 1 || n_w > 1) {
file << (x + 1 < n_x ? "}, " : "}");
}
}
if (n_y > 1 || n_z > 1 || n_w > 1) {
file << (y + 1 < n_y ? "},\n" : "}\n");
}
}
if (n_z > 1 || n_w > 1) {
file << (z + 1 < n_z ? "},\n" : "}\n");
}
}
if (n_w > 1) {
file << (w + 1 < n_w ? "},\n" : "}\n");
}
}
file << "};\n";
file.close();
}
};
} // namespace blender::eevee

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
/* Look-Up Table Generation. */
enum [[host_shared]] PrecomputeType : uint32_t {
LUT_GGX_BRDF_SPLIT_SUM = 0u,
LUT_GGX_BTDF_IOR_GT_ONE = 1u,
LUT_GGX_BSDF_SPLIT_SUM = 2u,
LUT_BURLEY_SSS_PROFILE = 3u,
LUT_RANDOM_WALK_SSS_PROFILE = 4u,
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,839 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* The ray-tracing module class handles ray generation, scheduling, tracing and denoising.
*/
#include "GPU_debug.hh"
#include "eevee_instance.hh"
#include "eevee_raytrace.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Raytracing
*
* \{ */
/**
* From Fabian Giesen's article:
* https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
*/
static uint compact_bits(uint x)
{
x &= 0x55555555;
x = (x ^ (x >> 1)) & 0x33333333;
x = (x ^ (x >> 2)) & 0x0f0f0f0f;
x = (x ^ (x >> 4)) & 0x00ff00ff;
x = (x ^ (x >> 8)) & 0x0000ffff;
return x;
}
static uint2 morton2d_to_coordinate(uint code)
{
return uint2(compact_bits(code >> 0), compact_bits(code >> 1));
}
/* Return a pseudo random sequence visiting all pixels in a tile before wrapping.
* `tile_size` needs to be a power of 2. */
static uint2 random_in_tile(uint sample_id, uint tile_size)
{
if (tile_size == 1) {
return uint2(0);
}
uint hash = sample_id;
hash = hash ^ (hash >> 16);
hash *= 0x85ebca6b;
hash = hash ^ (hash >> 13);
hash *= 0xc2b2ae35;
hash = hash ^ (hash >> 16);
return morton2d_to_coordinate(hash % square(tile_size));
}
void RayTraceModule::init()
{
const SceneEEVEE &sce_eevee = inst_.scene->eevee;
ray_tracing_options_ = sce_eevee.ray_tracing_options;
if ((sce_eevee.flag & SCE_EEVEE_FAST_GI_ENABLED) == 0) {
ray_tracing_options_.trace_max_roughness = 1.0f;
}
/* Always initialize thickness, for the ray-cast node. */
data_.thickness = ray_tracing_options_.screen_trace_thickness;
tracing_method_ = RaytraceEEVEE_Method(sce_eevee.ray_tracing_method);
fast_gi_ray_count_ = sce_eevee.fast_gi_ray_count;
fast_gi_step_count_ = sce_eevee.fast_gi_step_count;
fast_gi_ao_only_ = (sce_eevee.fast_gi_method == FAST_GI_AO_ONLY);
use_raytracing_ = (sce_eevee.flag & SCE_EEVEE_SSR_ENABLED) != 0;
float4 data(0.0f);
radiance_dummy_black_tx_.ensure_2d(
gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT, int2(1), GPU_TEXTURE_USAGE_SHADER_READ, data);
}
void RayTraceModule::sync()
{
Texture &depth_tx = inst_.render_buffers.depth_tx;
if (!use_raytracing_) {
/* Do not request raytracing shaders if not needed. */
return;
}
#define PASS_VARIATION(_pass_name, _index, _suffix) \
((_index == 0) ? _pass_name##reflect##_suffix : \
(_index == 1) ? _pass_name##refract##_suffix : \
_pass_name##diffuse##_suffix)
/* Setup. */
{
PassSimple &pass = tile_classify_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(RAY_TILE_CLASSIFY));
pass.bind_image("tile_raytrace_denoise_img", &tile_raytrace_denoise_tx_);
pass.bind_image("tile_raytrace_tracing_img", &tile_raytrace_tracing_tx_);
pass.bind_image("tile_fast_gi_denoise_img", &tile_fast_gi_denoise_tx_);
pass.bind_image("tile_fast_gi_tracing_img", &tile_fast_gi_tracing_tx_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(&tile_classify_dispatch_size_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_SHADER_STORAGE);
}
{
PassSimple &pass = tile_compact_ps_;
gpu::Shader *sh = inst_.shaders.static_shader_get(RAY_TILE_COMPACT);
pass.init();
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.specialize_constant(sh, "resolution_scale", &data_.trace_pixel_scale);
pass.shader_set(sh);
pass.bind_image("tile_raytrace_denoise_img", &tile_raytrace_denoise_tx_);
pass.bind_image("tile_raytrace_tracing_img", &tile_raytrace_tracing_tx_);
pass.bind_ssbo("raytrace_tracing_dispatch_buf", &raytrace_tracing_dispatch_buf_);
pass.bind_ssbo("raytrace_denoise_dispatch_buf", &raytrace_denoise_dispatch_buf_);
pass.bind_ssbo("raytrace_tracing_tiles_buf", &raytrace_tracing_tiles_buf_);
pass.bind_ssbo("raytrace_denoise_tiles_buf", &raytrace_denoise_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.dispatch(&tile_compact_dispatch_size_);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
}
{
PassSimple &pass = generate_ps_;
pass.init();
gpu::Shader *sh = inst_.shaders.static_shader_get(RAY_GENERATE);
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.shader_set(sh);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_image("out_ray_data_img", &ray_data_tx_);
pass.bind_ssbo("tiles_coord_buf", &raytrace_tracing_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(raytrace_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_STORAGE | GPU_BARRIER_TEXTURE_FETCH |
GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
/* Tracing. */
{
PassSimple &pass = trace_planar_ps_;
pass.init();
gpu::Shader *sh = inst_.shaders.static_shader_get(RAY_TRACE_PLANAR);
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.shader_set(sh);
pass.bind_ssbo("tiles_coord_buf", &raytrace_tracing_tiles_buf_);
pass.bind_image("ray_data_img", &ray_data_tx_);
pass.bind_image("ray_time_img", &ray_time_tx_);
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.planar_probes);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.gbuffer);
/* TODO(@fclem): Use another dispatch with only tiles that touches planar captures. */
pass.dispatch(raytrace_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
{
PassSimple &pass = trace_screen_ps_;
pass.init();
gpu::Shader *sh = inst_.shaders.static_shader_get(RAY_TRACE_SCREEN);
pass.specialize_constant(
sh, "trace_refraction", reinterpret_cast<bool *>(&data_.trace_refraction));
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.shader_set(sh);
pass.bind_ssbo("tiles_coord_buf", &raytrace_tracing_tiles_buf_);
pass.bind_image("ray_data_img", &ray_data_tx_);
pass.bind_image("ray_time_img", &ray_time_tx_);
pass.bind_texture("radiance_front_tx", &screen_radiance_front_tx_);
pass.bind_texture("radiance_back_tx", &screen_radiance_back_tx_);
pass.bind_texture("hiz_front_tx", &inst_.hiz_buffer.front.ref_tx_);
pass.bind_texture("hiz_back_tx", &inst_.hiz_buffer.back.ref_tx_);
/* Still bind front to hiz_tx for validation layers. */
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(raytrace_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
{
PassSimple &pass = trace_fallback_ps_;
pass.init();
gpu::Shader *sh = inst_.shaders.static_shader_get(RAY_TRACE_FALLBACK);
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.shader_set(sh);
pass.bind_ssbo("tiles_coord_buf", &raytrace_tracing_tiles_buf_);
pass.bind_image("ray_data_img", &ray_data_tx_);
pass.bind_image("ray_time_img", &ray_time_tx_);
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(raytrace_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
/* Denoise. */
{
PassSimple &pass = denoise_spatial_ps_;
gpu::Shader *sh = inst_.shaders.static_shader_get(RAY_DENOISE_SPATIAL);
pass.init();
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.specialize_constant(sh, "raytrace_resolution_scale", &data_.trace_pixel_scale);
pass.specialize_constant(sh, "skip_denoise", reinterpret_cast<bool *>(&data_.skip_denoise));
pass.shader_set(sh);
pass.bind_ssbo("tiles_coord_buf", &raytrace_denoise_tiles_buf_);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_image("ray_data_img", &ray_data_tx_);
pass.bind_image("ray_time_img", &ray_time_tx_);
pass.bind_image("ray_radiance_img", &ray_radiance_tx_);
pass.bind_image("out_radiance_img", &denoised_spatial_tx_);
pass.bind_image("out_variance_img", &hit_variance_tx_);
pass.bind_image("out_hit_depth_img", &hit_depth_tx_);
pass.bind_image("tile_mask_img", &tile_raytrace_denoise_tx_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(raytrace_denoise_dispatch_buf_);
/* Can either be loaded by next denoise pass as image or by combined pass as texture if this is
* the lass stage. */
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
}
{
PassSimple &pass = denoise_temporal_ps_;
gpu::Shader *sh = inst_.shaders.static_shader_get(RAY_DENOISE_TEMPORAL);
pass.init();
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.shader_set(sh);
pass.bind_resources(inst_.uniform_data);
pass.bind_texture("radiance_history_tx", &radiance_history_tx_);
pass.bind_texture("variance_history_tx", &variance_history_tx_);
pass.bind_texture("tilemask_history_tx", &tilemask_history_tx_);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_image("hit_depth_img", &hit_depth_tx_);
pass.bind_image("in_radiance_img", &denoised_spatial_tx_);
pass.bind_image("out_radiance_img", &denoised_temporal_tx_);
pass.bind_image("in_variance_img", &hit_variance_tx_);
pass.bind_image("out_variance_img", &denoise_variance_tx_);
pass.bind_image("tile_mask_img", &tile_raytrace_denoise_tx_);
pass.bind_ssbo("tiles_coord_buf", &raytrace_denoise_tiles_buf_);
pass.bind_resources(inst_.sampling);
pass.dispatch(raytrace_denoise_dispatch_buf_);
/* Can either be loaded by next denoise pass as image or by combined pass as texture if this is
* the lass stage. */
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
}
{
PassSimple &pass = denoise_bilateral_ps_;
pass.init();
gpu::Shader *sh = inst_.shaders.static_shader_get(RAY_DENOISE_BILATERAL);
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.shader_set(sh);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_image("in_radiance_img", &denoised_temporal_tx_);
pass.bind_image("out_radiance_img", &denoised_bilateral_tx_);
pass.bind_image("in_variance_img", &denoise_variance_tx_);
pass.bind_image("tile_mask_img", &tile_raytrace_denoise_tx_);
pass.bind_ssbo("tiles_coord_buf", &raytrace_denoise_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(raytrace_denoise_dispatch_buf_);
/* Can either be loaded and written by horizon scan as image or by combined pass as texture. */
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
}
{
PassSimple &pass = fast_gi_schedule_ps_;
/* Reuse tile compaction shader but feed it with horizon scan specific buffers. */
gpu::Shader *sh = inst_.shaders.static_shader_get(RAY_TILE_COMPACT);
pass.init();
pass.specialize_constant(sh, "closure_index", 0);
pass.specialize_constant(sh, "resolution_scale", &data_.fast_gi_resolution_scale);
pass.shader_set(sh);
pass.bind_image("tile_raytrace_denoise_img", &tile_fast_gi_denoise_tx_);
pass.bind_image("tile_raytrace_tracing_img", &tile_fast_gi_tracing_tx_);
pass.bind_ssbo("raytrace_tracing_dispatch_buf", &fast_gi_tracing_dispatch_buf_);
pass.bind_ssbo("raytrace_denoise_dispatch_buf", &fast_gi_denoise_dispatch_buf_);
pass.bind_ssbo("raytrace_tracing_tiles_buf", &fast_gi_tracing_tiles_buf_);
pass.bind_ssbo("raytrace_denoise_tiles_buf", &fast_gi_denoise_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.dispatch(&fast_gi_schedule_dispatch_size_);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
}
{
PassSimple &pass = fast_gi_setup_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(FAST_GI_SETUP));
pass.bind_resources(inst_.uniform_data);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_texture(
"in_radiance_tx", &screen_radiance_front_tx_, GPUSamplerState::default_sampler());
pass.bind_image("out_radiance_mip0", &downsampled_in_radiance_tx_ptr_[0]);
pass.bind_image("out_radiance_mip1", &downsampled_in_radiance_tx_ptr_[1]);
pass.bind_image("out_radiance_mip2", &downsampled_in_radiance_tx_ptr_[2]);
pass.bind_image("out_radiance_mip3", &downsampled_in_radiance_tx_ptr_[3]);
pass.bind_image("out_normal_mip0", &downsampled_in_normal_tx_ptr_[0]);
pass.bind_image("out_normal_mip1", &downsampled_in_normal_tx_ptr_[1]);
pass.bind_image("out_normal_mip2", &downsampled_in_normal_tx_ptr_[2]);
pass.bind_image("out_normal_mip3", &downsampled_in_normal_tx_ptr_[3]);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(&fast_gi_tracing_dispatch_size_);
/* Result loaded by the next stage using samplers. */
pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
{
const GPUSamplerState mipmap_linear = {GPU_SAMPLER_FILTERING_MIPMAP |
GPU_SAMPLER_FILTERING_LINEAR};
PassSimple &pass = fast_gi_scan_ps_;
pass.init();
gpu::Shader *sh = inst_.shaders.static_shader_get(FAST_GI_SCAN);
pass.specialize_constant(sh, "slice_count", fast_gi_ray_count_);
pass.specialize_constant(sh, "step_count", fast_gi_step_count_);
pass.specialize_constant(sh, "ao_only", fast_gi_ao_only_);
pass.shader_set(sh);
pass.bind_texture("screen_radiance_tx", &downsampled_in_radiance_tx_, mipmap_linear);
pass.bind_texture("screen_normal_tx", &downsampled_in_normal_tx_, mipmap_linear);
pass.bind_image("sh_0_img", &fast_gi_radiance_tx_[0]);
pass.bind_image("sh_1_img", &fast_gi_radiance_tx_[1]);
pass.bind_image("sh_2_img", &fast_gi_radiance_tx_[2]);
pass.bind_image("sh_3_img", &fast_gi_radiance_tx_[3]);
pass.bind_ssbo("tiles_coord_buf", &fast_gi_tracing_tiles_buf_);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.hiz_buffer.front);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(fast_gi_tracing_dispatch_buf_);
/* Result loaded by the next stage using samplers. */
pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
{
PassSimple &pass = fast_gi_denoise_ps_;
pass.init();
gpu::Shader *sh = inst_.shaders.static_shader_get(FAST_GI_DENOISE);
pass.shader_set(sh);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_texture("fast_gi_radiance_0_tx", &fast_gi_radiance_tx_[0]);
pass.bind_texture("fast_gi_radiance_1_tx", &fast_gi_radiance_tx_[1]);
pass.bind_texture("fast_gi_radiance_2_tx", &fast_gi_radiance_tx_[2]);
pass.bind_texture("fast_gi_radiance_3_tx", &fast_gi_radiance_tx_[3]);
pass.bind_texture("screen_normal_tx", &downsampled_in_normal_tx_);
pass.bind_image("sh_0_img", &fast_gi_radiance_denoised_tx_[0]);
pass.bind_image("sh_1_img", &fast_gi_radiance_denoised_tx_[1]);
pass.bind_image("sh_2_img", &fast_gi_radiance_denoised_tx_[2]);
pass.bind_image("sh_3_img", &fast_gi_radiance_denoised_tx_[3]);
pass.bind_ssbo("tiles_coord_buf", &fast_gi_tracing_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.hiz_buffer.front);
pass.dispatch(fast_gi_tracing_dispatch_buf_);
/* Result loaded by the next stage using samplers. */
pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
}
{
PassSimple &pass = fast_gi_resolve_ps_;
pass.init();
gpu::Shader *sh = inst_.shaders.static_shader_get(FAST_GI_RESOLVE);
pass.shader_set(sh);
pass.bind_texture("depth_tx", &depth_tx);
pass.bind_texture("fast_gi_radiance_0_tx", &fast_gi_radiance_denoised_tx_[0]);
pass.bind_texture("fast_gi_radiance_1_tx", &fast_gi_radiance_denoised_tx_[1]);
pass.bind_texture("fast_gi_radiance_2_tx", &fast_gi_radiance_denoised_tx_[2]);
pass.bind_texture("fast_gi_radiance_3_tx", &fast_gi_radiance_denoised_tx_[3]);
pass.bind_texture("screen_normal_tx", &downsampled_in_normal_tx_);
pass.bind_image("closure0_img", &fast_gi_scan_output_tx_[0]);
pass.bind_image("closure1_img", &fast_gi_scan_output_tx_[1]);
pass.bind_image("closure2_img", &fast_gi_scan_output_tx_[2]);
pass.bind_ssbo("tiles_coord_buf", &fast_gi_denoise_tiles_buf_);
pass.bind_resources(inst_.uniform_data);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.dispatch(fast_gi_denoise_dispatch_buf_);
/* Can either be loaded by another denoising stage or by combined pass as texture. */
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
}
for (int i : IndexRange(3)) {
const bool use_denoise = (ray_tracing_options_.flag & RAYTRACE_EEVEE_USE_DENOISE);
const bool use_spatial_denoise = (ray_tracing_options_.denoise_stages &
RAYTRACE_EEVEE_DENOISE_SPATIAL) &&
use_denoise;
const bool use_temporal_denoise = (ray_tracing_options_.denoise_stages &
RAYTRACE_EEVEE_DENOISE_TEMPORAL) &&
use_spatial_denoise;
const bool use_bilateral_denoise = (ray_tracing_options_.denoise_stages &
RAYTRACE_EEVEE_DENOISE_BILATERAL) &&
use_temporal_denoise;
data_.closure_index = i;
data_.trace_pixel_scale = max_ii(1, power_of_2_max_i(ray_tracing_options_.resolution_scale));
data_.skip_denoise = !use_spatial_denoise;
inst_.manager->warm_shader_specialization(tile_classify_ps_);
inst_.manager->warm_shader_specialization(tile_compact_ps_);
inst_.manager->warm_shader_specialization(generate_ps_);
if (tracing_method_ == RAYTRACE_EEVEE_METHOD_SCREEN) {
if (inst_.planar_probes.enabled()) {
inst_.manager->warm_shader_specialization(trace_planar_ps_);
}
for (int j : IndexRange(2)) {
data_.trace_refraction = bool(j);
inst_.manager->warm_shader_specialization(trace_screen_ps_);
}
}
else {
inst_.manager->warm_shader_specialization(trace_fallback_ps_);
}
if (use_spatial_denoise) {
inst_.manager->warm_shader_specialization(denoise_spatial_ps_);
}
if (use_temporal_denoise) {
inst_.manager->warm_shader_specialization(denoise_temporal_ps_);
}
if (use_bilateral_denoise) {
inst_.manager->warm_shader_specialization(denoise_bilateral_ps_);
}
bool use_fast_gi_scan = ray_tracing_options_.trace_max_roughness < 1.0f;
if (use_fast_gi_scan) {
inst_.manager->warm_shader_specialization(fast_gi_schedule_ps_);
inst_.manager->warm_shader_specialization(fast_gi_setup_ps_);
inst_.manager->warm_shader_specialization(fast_gi_scan_ps_);
inst_.manager->warm_shader_specialization(fast_gi_denoise_ps_);
inst_.manager->warm_shader_specialization(fast_gi_resolve_ps_);
}
}
}
void RayTraceModule::debug_pass_sync() {}
void RayTraceModule::debug_draw(View & /*view*/, gpu::FrameBuffer * /*view_fb*/) {}
void RayTraceModule::thickness_parameters_setup(const float4x4 &winmat, const int2 extent)
{
bool is_persp = winmat[3][3] == 0.0f;
float left, right, bottom, top, near, far;
projmat_dimensions(winmat.ptr(), &left, &right, &bottom, &top, &near, &far);
float avg_pixel_radius_far = length(abs(float2(left - right, bottom - top)) / float2(extent));
/* Average pixel radius at unit Z plane from the camera. */
const float avg_pixel_radius_unit = is_persp ? avg_pixel_radius_far / near :
avg_pixel_radius_far;
data_.ray_thickness = ScreenThicknessParameters::build(
winmat, avg_pixel_radius_unit, 3.0f, data_.thickness);
data_.fast_gi_thickness = ScreenThicknessParameters::build(
winmat,
avg_pixel_radius_unit,
/* Eyeballed for 64 samples and scaled for lower step count. */
4.0f * sqrtf(64.0f / float(fast_gi_step_count_)),
inst_.uniform_data.data.ao.thickness_near);
}
RayTraceResult RayTraceModule::render(RayTraceBuffer &rt_buffer,
gpu::Texture *screen_radiance_back_tx,
eClosureBits active_closures,
View &render_view)
{
using namespace blender::math;
BLI_assert(use_raytracing_);
screen_radiance_front_tx_ = rt_buffer.radiance_feedback_tx.is_valid() ?
rt_buffer.radiance_feedback_tx :
radiance_dummy_black_tx_;
screen_radiance_back_tx_ = screen_radiance_back_tx ? screen_radiance_back_tx :
screen_radiance_front_tx_;
RaytraceEEVEE options = ray_tracing_options_;
bool use_fast_gi_scan = options.trace_max_roughness < 1.0f;
const int resolution_scale = max_ii(1, power_of_2_max_i(options.resolution_scale));
const int fast_gi_resolution_scale = max_ii(
1, power_of_2_max_i(inst_.scene->eevee.fast_gi_resolution));
const int2 group_size(RAYTRACE_GROUP_SIZE);
const int2 extent = inst_.film.render_extent_get();
const int2 tracing_res = math::divide_ceil(extent, int2(resolution_scale));
/* Just like HiZ, the FastGI needs a multiple of its dispatch size in order to use MipMaps. */
const int2 tracing_res_fast_gi = math::ceil_to_multiple(
math::divide_ceil(extent, int2(fast_gi_resolution_scale)), group_size);
const int2 denoise_tiles = divide_ceil(extent, group_size);
const int2 raytrace_tiles = divide_ceil(tracing_res, group_size);
const int2 raytrace_tiles_fast_gi = divide_ceil(tracing_res_fast_gi, group_size);
const int denoise_tile_count = denoise_tiles.x * denoise_tiles.y;
const int raytrace_tile_count = raytrace_tiles.x * raytrace_tiles.y;
const int raytrace_tile_count_fast_gi = raytrace_tiles_fast_gi.x * raytrace_tiles_fast_gi.y;
tile_classify_dispatch_size_ = int3(denoise_tiles, 1);
fast_gi_schedule_dispatch_size_ = int3(divide_ceil(raytrace_tiles_fast_gi, group_size), 1);
tile_compact_dispatch_size_ = int3(divide_ceil(raytrace_tiles, group_size), 1);
tracing_dispatch_size_ = int3(raytrace_tiles, 1);
fast_gi_tracing_dispatch_size_ = int3(raytrace_tiles_fast_gi, 1);
/* TODO(fclem): Use real max closure count from shader. */
const int closure_count = 3;
gpu::TextureFormat format = gpu::TextureFormat::RAYTRACE_TILEMASK_FORMAT;
eGPUTextureUsage usage_rw = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
tile_raytrace_denoise_tx_.ensure_2d_array(format, denoise_tiles, closure_count, usage_rw);
tile_raytrace_tracing_tx_.ensure_2d_array(format, raytrace_tiles, closure_count, usage_rw);
/* Kept as 2D array for compatibility with the tile compaction shader. */
tile_fast_gi_denoise_tx_.ensure_2d_array(format, denoise_tiles, 1, usage_rw);
tile_fast_gi_tracing_tx_.ensure_2d_array(format, raytrace_tiles_fast_gi, 1, usage_rw);
tile_raytrace_denoise_tx_.clear(uint4(0u));
tile_raytrace_tracing_tx_.clear(uint4(0u));
tile_fast_gi_denoise_tx_.clear(uint4(0u));
tile_fast_gi_tracing_tx_.clear(uint4(0u));
fast_gi_tracing_tiles_buf_.resize(ceil_to_multiple_u(raytrace_tile_count_fast_gi, 512));
fast_gi_denoise_tiles_buf_.resize(ceil_to_multiple_u(denoise_tile_count, 512));
raytrace_tracing_tiles_buf_.resize(ceil_to_multiple_u(raytrace_tile_count, 512));
raytrace_denoise_tiles_buf_.resize(ceil_to_multiple_u(denoise_tile_count, 512));
data_.use_backface_hit = (options.flag & RAYTRACE_EEVEE_USE_BACKFACE) != 0;
data_.backface_hit_scale = data_.use_backface_hit ? options.backface_radiance_scale : 0.0f;
/* Data for tile classification. */
float roughness_mask_start = options.trace_max_roughness;
float roughness_mask_fade = 0.2f;
data_.roughness_mask_scale = 1.0 / roughness_mask_fade;
data_.roughness_mask_bias = data_.roughness_mask_scale * roughness_mask_start;
/* Data for the radiance setup. */
data_.trace_pixel_scale = resolution_scale;
data_.trace_pixel_offset = int2(random_in_tile(inst_.sampling.sample_index(), resolution_scale));
data_.history_persmat = rt_buffer.history_persmat;
data_.full_resolution = extent;
data_.full_resolution_inv = 1.0f / float2(extent);
data_.fast_gi_uv_scale = float2(extent) / float2(tracing_res_fast_gi * fast_gi_resolution_scale);
data_.fast_gi_lod_bias = log2_floor_u(resolution_scale);
data_.fast_gi_resolution_scale = fast_gi_resolution_scale;
data_.fast_gi_resolution_bias = int2(
random_in_tile(inst_.sampling.sample_index(), fast_gi_resolution_scale));
/* TODO(fclem): Eventually all uniform data is setup here. */
inst_.uniform_data.raytrace.push_update();
RayTraceResult result;
GPU_debug_group_begin("Raytracing");
const bool has_active_closure = active_closures != CLOSURE_NONE;
if (has_active_closure) {
inst_.manager->submit(tile_classify_ps_);
}
data_.trace_refraction = screen_radiance_back_tx != nullptr;
for (int i = 0; i < 3; i++) {
result.closures[i] = trace(i, (closure_count > i), options, rt_buffer, render_view);
}
if (has_active_closure) {
if (use_fast_gi_scan) {
GPU_debug_group_begin("Fast GI");
downsampled_in_radiance_tx_.ensure_2d(
gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT, tracing_res_fast_gi, usage_rw, nullptr, 4);
downsampled_in_normal_tx_.ensure_2d(
gpu::TextureFormat::UNORM_10_10_10_2, tracing_res_fast_gi, usage_rw, nullptr, 4);
downsampled_in_radiance_tx_.ensure_mip_views();
downsampled_in_normal_tx_.ensure_mip_views();
for (int i : IndexRange(4)) {
downsampled_in_radiance_tx_ptr_[i] = downsampled_in_radiance_tx_.mip_view(i);
downsampled_in_normal_tx_ptr_[i] = downsampled_in_normal_tx_.mip_view(i);
}
fast_gi_radiance_tx_[0].acquire_2d(
tracing_res_fast_gi, gpu::TextureFormat::SFLOAT_16_16_16_16, usage_rw);
fast_gi_radiance_denoised_tx_[0].acquire_2d(
tracing_res_fast_gi, gpu::TextureFormat::SFLOAT_16_16_16_16, usage_rw);
for (int i : IndexRange(1, 3)) {
fast_gi_radiance_tx_[i].acquire_2d(
tracing_res_fast_gi, gpu::TextureFormat::UNORM_8_8_8_8, usage_rw);
fast_gi_radiance_denoised_tx_[i].acquire_2d(
tracing_res_fast_gi, gpu::TextureFormat::UNORM_8_8_8_8, usage_rw);
}
for (int i : IndexRange(3)) {
fast_gi_scan_output_tx_[i] = result.closures[i];
}
fast_gi_tracing_dispatch_buf_.clear_to_zero();
fast_gi_denoise_dispatch_buf_.clear_to_zero();
inst_.manager->submit(fast_gi_schedule_ps_);
inst_.manager->submit(fast_gi_setup_ps_, render_view);
inst_.manager->submit(fast_gi_scan_ps_, render_view);
inst_.manager->submit(fast_gi_denoise_ps_, render_view);
inst_.manager->submit(fast_gi_resolve_ps_, render_view);
for (int i : IndexRange(4)) {
fast_gi_radiance_tx_[i].release();
fast_gi_radiance_denoised_tx_[i].release();
}
GPU_debug_group_end();
}
}
GPU_debug_group_end();
rt_buffer.history_persmat = render_view.persmat();
return result;
}
RayTraceResultTexture RayTraceModule::trace(int closure_index,
bool active_layer,
RaytraceEEVEE options,
RayTraceBuffer &rt_buffer,
View &render_view)
{
RayTraceBuffer::DenoiseBuffer *denoise_buf = &rt_buffer.closures[closure_index];
if (!active_layer) {
/* Early out. Release persistent buffers. Still acquire one dummy resource for validation. */
denoise_buf->denoised_spatial_tx.acquire_2d(int2(1),
gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT);
denoise_buf->radiance_history_tx.release();
denoise_buf->variance_history_tx.release();
denoise_buf->tilemask_history_tx.free();
return {denoise_buf->denoised_spatial_tx};
}
const int resolution_scale = max_ii(1, power_of_2_max_i(options.resolution_scale));
const int2 extent = inst_.film.render_extent_get();
const int2 tracing_res = math::divide_ceil(extent, int2(resolution_scale));
renderbuf_depth_view_ = inst_.render_buffers.depth_tx;
const bool use_denoise = (options.flag & RAYTRACE_EEVEE_USE_DENOISE);
const bool use_spatial_denoise = (options.denoise_stages & RAYTRACE_EEVEE_DENOISE_SPATIAL) &&
use_denoise;
const bool use_temporal_denoise = (options.denoise_stages & RAYTRACE_EEVEE_DENOISE_TEMPORAL) &&
use_spatial_denoise;
const bool use_bilateral_denoise = (options.denoise_stages & RAYTRACE_EEVEE_DENOISE_BILATERAL) &&
use_temporal_denoise;
eGPUTextureUsage usage_rw = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
GPU_debug_group_begin("Raytracing");
data_.thickness = options.screen_trace_thickness;
data_.quality = 1.0f - 0.95f * options.screen_trace_quality;
float roughness_mask_start = options.trace_max_roughness;
float roughness_mask_fade = 0.2f;
data_.roughness_mask_scale = 1.0 / roughness_mask_fade;
data_.roughness_mask_bias = data_.roughness_mask_scale * roughness_mask_start;
data_.trace_pixel_scale = resolution_scale;
data_.trace_pixel_offset = int2(random_in_tile(inst_.sampling.sample_index(), resolution_scale));
data_.denoise_history_persmat = denoise_buf->history_persmat;
data_.full_resolution = extent;
data_.full_resolution_inv = 1.0f / float2(extent);
data_.skip_denoise = !use_spatial_denoise;
data_.closure_index = closure_index;
inst_.uniform_data.raytrace.push_update();
/* Ray setup. */
raytrace_tracing_dispatch_buf_.clear_to_zero();
raytrace_denoise_dispatch_buf_.clear_to_zero();
inst_.manager->submit(tile_compact_ps_);
{
/* Tracing rays. */
ray_data_tx_.acquire_2d(tracing_res, gpu::TextureFormat::SFLOAT_16_16_16_16);
ray_time_tx_.acquire_2d(tracing_res, gpu::TextureFormat::RAYTRACE_RAYTIME_FORMAT);
ray_radiance_tx_.acquire_2d(tracing_res, gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT);
inst_.manager->submit(generate_ps_, render_view);
if (tracing_method_ == RAYTRACE_EEVEE_METHOD_SCREEN) {
if (inst_.planar_probes.enabled()) {
inst_.manager->submit(trace_planar_ps_, render_view);
}
inst_.manager->submit(trace_screen_ps_, render_view);
}
else {
inst_.manager->submit(trace_fallback_ps_, render_view);
}
}
RayTraceResultTexture result;
/* Spatial denoise pass is required to resolve at least one ray per pixel. */
{
denoise_buf->denoised_spatial_tx.acquire_2d(extent,
gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT);
hit_variance_tx_.acquire_2d(use_temporal_denoise ? extent : int2(1),
gpu::TextureFormat::RAYTRACE_VARIANCE_FORMAT);
hit_depth_tx_.acquire_2d(use_temporal_denoise ? extent : int2(1),
gpu::TextureFormat::SFLOAT_32);
denoised_spatial_tx_ = denoise_buf->denoised_spatial_tx;
inst_.manager->submit(denoise_spatial_ps_, render_view);
result = {denoise_buf->denoised_spatial_tx};
}
ray_data_tx_.release();
ray_time_tx_.release();
ray_radiance_tx_.release();
if (use_temporal_denoise) {
denoise_buf->denoised_temporal_tx.acquire_2d(
extent, gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT, usage_rw);
int2 variance_size = use_bilateral_denoise ? extent : int2(1);
gpu::TextureFormat variance_format = gpu::TextureFormat::RAYTRACE_VARIANCE_FORMAT;
denoise_variance_tx_.acquire_2d(variance_size, variance_format, usage_rw);
denoise_buf->variance_history_tx.acquire_2d(variance_size, variance_format, usage_rw);
denoise_buf->tilemask_history_tx.ensure_2d_array(gpu::TextureFormat::RAYTRACE_TILEMASK_FORMAT,
tile_raytrace_denoise_tx_.size().xy(),
tile_raytrace_denoise_tx_.size().z,
usage_rw);
if (denoise_buf->radiance_history_tx.acquire_2d(
extent, gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT, usage_rw) ||
denoise_buf->valid_history == false)
{
/* If viewport resolution changes, do not try to use history. */
denoise_buf->tilemask_history_tx.clear(uint4(0u));
}
radiance_history_tx_ = denoise_buf->radiance_history_tx;
variance_history_tx_ = denoise_buf->variance_history_tx;
tilemask_history_tx_ = denoise_buf->tilemask_history_tx;
denoised_temporal_tx_ = denoise_buf->denoised_temporal_tx;
inst_.manager->submit(denoise_temporal_ps_, render_view);
/* Save view-projection matrix for next reprojection. */
denoise_buf->history_persmat = render_view.persmat();
/* Radiance will be swapped with history in #RayTraceResult::release().
* Variance is swapped with history after bilateral denoise.
* It keeps data-flow easier to follow. */
result = {denoise_buf->denoised_temporal_tx, denoise_buf->radiance_history_tx};
/* Not referenced by result anymore. */
denoise_buf->denoised_spatial_tx.release();
GPU_texture_copy(denoise_buf->tilemask_history_tx, tile_raytrace_denoise_tx_);
}
/* Only use history buffer for the next frame if temporal denoise was used by the current one. */
denoise_buf->valid_history = use_temporal_denoise;
hit_variance_tx_.release();
hit_depth_tx_.release();
if (use_bilateral_denoise) {
denoise_buf->denoised_bilateral_tx.acquire_2d(
extent, gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT, usage_rw);
denoised_bilateral_tx_ = denoise_buf->denoised_bilateral_tx;
inst_.manager->submit(denoise_bilateral_ps_, render_view);
/* Swap after last use, retain history buffers until next cycle. */
TextureFromPool::swap(denoise_buf->denoised_temporal_tx, denoise_buf->radiance_history_tx);
TextureFromPool::swap(denoise_variance_tx_, denoise_buf->variance_history_tx);
denoise_buf->radiance_history_tx.retain();
denoise_buf->variance_history_tx.retain();
result = {denoise_buf->denoised_bilateral_tx};
/* Not referenced by result anymore. */
denoise_buf->denoised_temporal_tx.release();
}
else if (use_temporal_denoise) {
/* Not referenced by result anymore. */
denoise_buf->variance_history_tx.retain();
}
denoise_variance_tx_.release();
GPU_debug_group_end();
return result;
}
RayTraceResult RayTraceModule::alloc_only(RayTraceBuffer &rt_buffer)
{
const int2 extent = inst_.film.render_extent_get();
eGPUTextureUsage usage_rw = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
RayTraceResult result;
for (int i = 0; i < 3; i++) {
RayTraceBuffer::DenoiseBuffer *denoise_buf = &rt_buffer.closures[i];
denoise_buf->denoised_bilateral_tx.acquire_2d(
extent, gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT, usage_rw);
result.closures[i] = {denoise_buf->denoised_bilateral_tx};
}
return result;
}
RayTraceResult RayTraceModule::alloc_dummy(RayTraceBuffer &rt_buffer)
{
eGPUTextureUsage usage_rw = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
RayTraceResult result;
for (int i = 0; i < 3; i++) {
RayTraceBuffer::DenoiseBuffer *denoise_buf = &rt_buffer.closures[i];
denoise_buf->denoised_bilateral_tx.acquire_2d(
int2(1), gpu::TextureFormat::RAYTRACE_RADIANCE_FORMAT, usage_rw);
result.closures[i] = {denoise_buf->denoised_bilateral_tx};
}
return result;
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,308 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* The ray-tracing module class handles ray generation, scheduling, tracing and denoising.
*/
#pragma once
#include "DNA_scene_types.h"
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.hh"
#include "eevee_raytrace_shared.hh"
namespace blender::eevee {
class Instance;
using RayTraceTileBuf = draw::StorageArrayBuffer<uint, 1024, true>;
/* -------------------------------------------------------------------- */
/** \name Ray-tracing Buffers
*
* Contain persistent data used for temporal denoising. Similar to \class GBuffer but only contains
* persistent data.
* \{ */
/**
* Contain persistent buffer that need to be stored per view, per deferred layer.
*/
struct RayTraceBuffer {
/** Set of buffers that need to be allocated for each ray type. */
struct DenoiseBuffer {
/* Persistent history buffers. */
TextureFromPool radiance_history_tx = {"radiance_tx"};
TextureFromPool variance_history_tx = {"variance_tx"};
/* Map of tiles that were processed inside the history buffer. */
Texture tilemask_history_tx = {"tilemask_tx"};
/** Perspective matrix for which the history buffers were recorded. */
float4x4 history_persmat;
/** True if history buffer was used last frame and can be re-projected. */
bool valid_history = false;
/**
* Textures containing the ray hit radiance denoised (full-res). One of them is result_tx.
* One might become result buffer so it need instantiation by closure type to avoid reuse.
*/
TextureFromPool denoised_spatial_tx = {"denoised_spatial_tx"};
TextureFromPool denoised_temporal_tx = {"denoised_temporal_tx"};
TextureFromPool denoised_bilateral_tx = {"denoised_bilateral_tx"};
};
/**
* One for each closure. Not to be mistaken with deferred layer type.
*/
DenoiseBuffer closures[3];
/**
* Radiance feedback of the deferred layer for next sample's reflection or next layer's
* transmission.
*/
Texture radiance_feedback_tx = {"radiance_feedback_tx"};
/**
* Perspective matrix for which the radiance feedback buffer was recorded.
* Can be different from de-noise buffer's history matrix.
*/
float4x4 history_persmat = float4x4::zero();
gpu::Texture *feedback_ensure(bool is_dummy, int2 extent)
{
eGPUTextureUsage usage_rw = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
if (radiance_feedback_tx.ensure_2d(
gpu::TextureFormat::SFLOAT_16_16_16_16, is_dummy ? int2(1) : extent, usage_rw))
{
radiance_feedback_tx.clear(float4(0.0f));
}
return radiance_feedback_tx;
}
};
/**
* Contains the result texture.
* The result buffer is usually short lived and is kept in a TextureFromPool managed by the mode.
* This structure contains a reference to it so that it can be freed after use by the caller.
*/
class RayTraceResultTexture {
private:
/** Result is in a temporary texture that needs to be released. */
TextureFromPool *result_ = nullptr;
/** Value of `result_->tx_` that can be referenced in advance. */
gpu::Texture *tx_ = nullptr;
/** History buffer to swap the temporary texture that does not need to be released. */
TextureFromPool *history_ = nullptr;
public:
RayTraceResultTexture() = default;
RayTraceResultTexture(TextureFromPool &result) : result_(result.ptr()), tx_(result) {};
RayTraceResultTexture(TextureFromPool &result, TextureFromPool &history)
: result_(result.ptr()), tx_(result), history_(history.ptr()) {};
operator gpu::Texture *() const
{
BLI_assert(tx_ != nullptr);
return tx_;
}
gpu::Texture **operator&()
{
return &tx_;
}
void release()
{
if (history_) {
/* Swap after last use, retain history until next cycle. */
TextureFromPool::swap(*result_, *history_);
history_->retain();
}
/* Release previous history. */
result_->release();
}
};
struct RayTraceResult {
RayTraceResultTexture closures[3];
void release()
{
for (int i = 0; i < 3; i++) {
closures[i].release();
}
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Ray-tracing
* \{ */
class RayTraceModule {
private:
Instance &inst_;
draw::PassSimple tile_classify_ps_ = {"TileClassify"};
draw::PassSimple tile_compact_ps_ = {"TileCompact"};
draw::PassSimple generate_ps_ = {"RayGenerate"};
draw::PassSimple trace_planar_ps_ = {"Trace.Planar"};
draw::PassSimple trace_screen_ps_ = {"Trace.Screen"};
draw::PassSimple trace_fallback_ps_ = {"Trace.Fallback"};
draw::PassSimple denoise_spatial_ps_ = {"DenoiseSpatial"};
draw::PassSimple denoise_temporal_ps_ = {"DenoiseTemporal"};
draw::PassSimple denoise_bilateral_ps_ = {"DenoiseBilateral"};
draw::PassSimple fast_gi_schedule_ps_ = {"FastGI.Schedule"};
draw::PassSimple fast_gi_setup_ps_ = {"FastGI.Setup"};
draw::PassSimple fast_gi_scan_ps_ = {"FastGI.Trace"};
draw::PassSimple fast_gi_denoise_ps_ = {"FastGI.Denoise"};
draw::PassSimple fast_gi_resolve_ps_ = {"FastGI.Resolve"};
/** Dispatch with enough tiles for the whole screen. */
int3 tile_classify_dispatch_size_ = int3(1);
/** Dispatch with enough tiles for the tile mask. */
int3 tile_compact_dispatch_size_ = int3(1);
int3 fast_gi_schedule_dispatch_size_ = int3(1);
/** Dispatch with enough tiles for the tracing resolution. */
int3 tracing_dispatch_size_ = int3(1);
int3 fast_gi_tracing_dispatch_size_ = int3(1);
/** 2D tile mask to check which unused adjacent tile we need to clear and which tile we need to
* dispatch for each work type. */
Texture tile_raytrace_denoise_tx_ = {"tile_raytrace_denoise_tx_"};
Texture tile_raytrace_tracing_tx_ = {"tile_raytrace_tracing_tx_"};
Texture tile_fast_gi_denoise_tx_ = {"tile_fast_gi_denoise_tx_"};
Texture tile_fast_gi_tracing_tx_ = {"tile_fast_gi_tracing_tx_"};
/** Indirect dispatch rays. Avoid dispatching work-groups that will not trace anything. */
DispatchIndirectBuf raytrace_tracing_dispatch_buf_ = {"raytrace_tracing_dispatch_buf_"};
/** Indirect dispatch denoise full-resolution tiles. */
DispatchIndirectBuf raytrace_denoise_dispatch_buf_ = {"raytrace_denoise_dispatch_buf_"};
/** Indirect dispatch fast GI scan. Avoid dispatching work-groups that will not scan anything. */
DispatchIndirectBuf fast_gi_tracing_dispatch_buf_ = {"fast_gi_tracing_dispatch_buf_"};
/** Indirect dispatch denoise full-resolution tiles. */
DispatchIndirectBuf fast_gi_denoise_dispatch_buf_ = {"fast_gi_denoise_dispatch_buf_"};
/** Pointer to the texture to store the result of fast GI scan in. */
gpu::Texture *fast_gi_scan_output_tx_[3] = {nullptr};
/** Tile buffer that contains tile coordinates. */
RayTraceTileBuf raytrace_tracing_tiles_buf_ = {"raytrace_tracing_tiles_buf_"};
RayTraceTileBuf raytrace_denoise_tiles_buf_ = {"raytrace_denoise_tiles_buf_"};
RayTraceTileBuf fast_gi_tracing_tiles_buf_ = {"fast_gi_tracing_tiles_buf_"};
RayTraceTileBuf fast_gi_denoise_tiles_buf_ = {"fast_gi_denoise_tiles_buf_"};
/** Texture containing the ray direction and PDF. */
TextureFromPool ray_data_tx_ = {"ray_data_tx"};
/** Texture containing the ray hit time. */
TextureFromPool ray_time_tx_ = {"ray_data_tx"};
/** Texture containing the ray hit radiance (tracing-res). */
TextureFromPool ray_radiance_tx_ = {"ray_radiance_tx"};
/** Texture containing the fast GI local radiance. */
TextureFromPool fast_gi_radiance_tx_[4] = {{"fast_gi_radiance_tx_"}};
TextureFromPool fast_gi_radiance_denoised_tx_[4] = {{"fast_gi_radiance_denoised_tx_"}};
/** Texture containing the input screen radiance but re-projected. */
Texture downsampled_in_radiance_tx_ = {"downsampled_in_radiance_tx_"};
/** Texture containing the view space normal. The BSDF normal is arbitrarily chosen. */
Texture downsampled_in_normal_tx_ = {"downsampled_in_normal_tx_"};
/** Pointers to the mip view of the above textures. Available after sync. */
gpu::Texture *downsampled_in_radiance_tx_ptr_[4] = {nullptr};
gpu::Texture *downsampled_in_normal_tx_ptr_[4] = {nullptr};
/** Textures containing the ray hit radiance denoised (full-res). One of them is result_tx. */
gpu::Texture *denoised_spatial_tx_ = nullptr;
gpu::Texture *denoised_temporal_tx_ = nullptr;
gpu::Texture *denoised_bilateral_tx_ = nullptr;
/** Ray hit depth for temporal denoising. Output of spatial denoise. */
TextureFromPool hit_depth_tx_ = {"hit_depth_tx_"};
/** Ray hit variance for temporal denoising. Output of spatial denoise. */
TextureFromPool hit_variance_tx_ = {"hit_variance_tx_"};
/** Temporally stable variance for temporal denoising. Output of temporal denoise. */
TextureFromPool denoise_variance_tx_ = {"denoise_variance_tx_"};
/** Persistent texture reference for temporal denoising input. */
gpu::Texture *radiance_history_tx_ = nullptr;
gpu::Texture *variance_history_tx_ = nullptr;
gpu::Texture *tilemask_history_tx_ = nullptr;
/** Radiance input for screen space tracing. */
gpu::Texture *screen_radiance_front_tx_ = nullptr;
gpu::Texture *screen_radiance_back_tx_ = nullptr;
Texture radiance_dummy_black_tx_ = {"radiance_dummy_black_tx"};
/** Dummy texture when the tracing is disabled. */
TextureFromPool dummy_result_tx_ = {"dummy_result_tx"};
/** Pointer to `inst_.render_buffers.depth_tx` updated before submission. */
gpu::Texture *renderbuf_depth_view_ = nullptr;
/** Copy of the scene options to avoid changing parameters during motion blur. */
RaytraceEEVEE ray_tracing_options_;
int fast_gi_ray_count_ = 0;
int fast_gi_step_count_ = 0;
bool fast_gi_ao_only_ = false;
bool use_raytracing_ = false;
RaytraceEEVEE_Method tracing_method_ = RAYTRACE_EEVEE_METHOD_PROBE;
RayTraceData &data_;
public:
RayTraceModule(Instance &inst, RayTraceData &data) : inst_(inst), data_(data) {};
void init();
void sync();
/**
* RayTrace the scene and resolve radiance buffer for the corresponding `closure_bit`.
*
* IMPORTANT: Should not be conditionally executed as it manages the RayTraceResult.
* IMPORTANT: The screen tracing will be using the front and back Hierarchical-Z Buffer in its
* current state.
*
* \arg rt_buffer is the layer's permanent storage.
* \arg active_closures is a mask of all active closures in a deferred layer.
* \arg render_view is the TAA jittered view.
* \arg force_no_tracing will run the pipeline without any tracing, relying only on local probes.
*/
RayTraceResult render(RayTraceBuffer &rt_buffer,
gpu::Texture *screen_radiance_back_tx,
eClosureBits active_closures,
View &render_view);
/**
* Only allocate the RayTraceResult results buffers to be used by other passes.
*/
RayTraceResult alloc_only(RayTraceBuffer &rt_buffer);
/**
* Only allocate the RayTraceResult results buffers as dummy texture to ensure correct bindings.
*/
RayTraceResult alloc_dummy(RayTraceBuffer &rt_buffer);
void debug_pass_sync();
void debug_draw(View &view, gpu::FrameBuffer *view_fb);
bool use_raytracing() const
{
return use_raytracing_;
}
bool use_fast_gi() const
{
return use_raytracing() && ray_tracing_options_.trace_max_roughness < 1.0f;
}
/**
* Update screen space thickness parameters.
* Needs to be called early in the frame to allow ray-cast node usage.
* Needs uniform_data.push_update() to be called afterward to take effect.
*/
void thickness_parameters_setup(const float4x4 &winmat, const int2 extent);
private:
RayTraceResultTexture trace(int closure_index,
bool active_layer,
RaytraceEEVEE options,
RayTraceBuffer &rt_buffer,
View &render_view);
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,124 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
struct [[host_shared]] ScreenThicknessParameters {
float thickness_ndc_scale;
float thickness_ndc_bias;
float thickness_vs_scale;
float thickness_vs_bias;
/* Return the depth buffer Z thickness of a pixel at a given view space Z depth. */
static ScreenThicknessParameters build(float4x4 winmat,
float avg_pixel_radius_unit,
float min_pixel_thickness,
float min_constant_thickness)
{
ScreenThicknessParameters params;
avg_pixel_radius_unit *= min_pixel_thickness;
if (winmat[3][3] == 0.0f) {
/* Perspective pixels increase footprint with the distance. */
params.thickness_vs_scale = avg_pixel_radius_unit;
params.thickness_vs_bias = -min_constant_thickness;
params.thickness_ndc_scale = winmat[3][2];
params.thickness_ndc_bias = 0.0f;
/* Convert from NDC to screen range. */
params.thickness_ndc_scale *= 0.5f;
}
else {
/* Orthographic pixels have fixed footprint. */
params.thickness_vs_scale = 0.0f;
params.thickness_vs_bias = 1.0f; /* Avoid NaN. */
params.thickness_ndc_scale = 0.0f;
params.thickness_ndc_bias = -(avg_pixel_radius_unit + min_constant_thickness) * winmat[2][2];
/* Convert from NDC to screen range. */
params.thickness_ndc_bias *= 0.5f;
}
return params;
}
/* Return the depth buffer Z thickness of a pixel at a given view space Z depth. */
float pixel_depth_thickness_at(float vs_z) const
{
float vs_thickness = vs_z * thickness_vs_scale + thickness_vs_bias;
/* NDC offset from view space offset.
* From http://terathon.com/gdc07_lengyel.pdf (slide 24) */
float ndc_thickness = vs_thickness / (vs_z * (vs_z + vs_thickness));
return ndc_thickness * thickness_ndc_scale + thickness_ndc_bias;
}
};
struct [[host_shared]] RayTraceData {
/** ViewProjection matrix used to render the previous frame. */
float4x4 history_persmat;
/** ViewProjection matrix used to denoise the previous frame. */
float4x4 denoise_history_persmat;
/** Input resolution. */
int2 full_resolution;
/** Inverse of input resolution to get screen UVs. */
float2 full_resolution_inv;
/** Scale and bias to go from ray-trace resolution to input resolution. */
int2 trace_pixel_offset;
int trace_pixel_scale;
/** View space thickness the objects. */
float thickness;
/** Scale and bias to go from fast GI resolution to input resolution. */
int2 fast_gi_resolution_bias;
int fast_gi_resolution_scale;
/** Bias to the full-screen buffer LOD to account for radiance buffer top down-scaling factor. */
float fast_gi_lod_bias;
/** Scale to apply to full-screen UVs to remove padding. */
float2 fast_gi_uv_scale;
/** Determine how fast the sample steps are getting bigger. */
float quality;
/** Maximum roughness for which we will trace a ray. */
float roughness_mask_scale;
float roughness_mask_bias;
/** If set to true will bypass spatial denoising. */
bool32_t skip_denoise;
/** If set to false will bypass tracing for refractive closures. */
bool32_t trace_refraction;
/** Closure being ray-traced. */
int closure_index;
/** If true, consider backface hit as valid. Otherwise, use ray miss pipeline. */
bool32_t use_backface_hit;
/** Amount of frontface lighting to use for backface hits. */
float backface_hit_scale;
uint _pad0;
uint _pad1;
struct ScreenThicknessParameters fast_gi_thickness;
struct ScreenThicknessParameters ray_thickness;
};
struct [[host_shared]] AOData {
float2 pixel_size;
float distance;
float lod_factor;
float thickness_near;
float thickness_far;
float angle_bias;
float gi_distance;
float lod_factor_ao;
float _pad0;
float _pad1;
float _pad2;
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,152 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* A film is a full-screen buffer (usually at output extent)
* that will be able to accumulate sample in any distorted camera_type
* using a pixel filter.
*
* Input needs to be jittered so that the filter converges to the right result.
*/
#include "GPU_texture.hh"
#include "DRW_render.hh"
#include "eevee_film.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
void RenderBuffers::init()
{
const eViewLayerEEVEEPassType enabled_passes = inst_.film.enabled_passes_get();
data.color_len = 0;
data.value_len = 0;
auto pass_index_get = [&](eViewLayerEEVEEPassType pass_type, int dependent_passes = 0) {
if (enabled_passes & (pass_type | dependent_passes)) {
return pass_storage_type(pass_type) == PASS_STORAGE_COLOR ? data.color_len++ :
data.value_len++;
}
return -1;
};
data.normal_id = pass_index_get(EEVEE_RENDER_PASS_NORMAL, EEVEE_RENDER_PASS_AO);
data.position_id = pass_index_get(EEVEE_RENDER_PASS_POSITION);
data.diffuse_light_id = pass_index_get(EEVEE_RENDER_PASS_DIFFUSE_LIGHT);
data.diffuse_color_id = pass_index_get(EEVEE_RENDER_PASS_DIFFUSE_COLOR);
data.specular_light_id = pass_index_get(EEVEE_RENDER_PASS_SPECULAR_LIGHT);
data.specular_color_id = pass_index_get(EEVEE_RENDER_PASS_SPECULAR_COLOR);
data.volume_light_id = pass_index_get(EEVEE_RENDER_PASS_VOLUME_LIGHT);
data.emission_id = pass_index_get(EEVEE_RENDER_PASS_EMIT);
data.environment_id = pass_index_get(EEVEE_RENDER_PASS_ENVIRONMENT);
data.shadow_id = pass_index_get(EEVEE_RENDER_PASS_SHADOW);
data.ambient_occlusion_id = pass_index_get(EEVEE_RENDER_PASS_AO);
data.transparent_id = pass_index_get(EEVEE_RENDER_PASS_TRANSPARENT);
data.aovs = inst_.film.aovs_info;
}
void RenderBuffers::acquire(int2 extent, gpu::TextureFormat raycast_depth_format)
{
const eViewLayerEEVEEPassType enabled_passes = inst_.film.enabled_passes_get();
extent_ = extent;
auto pass_extent = [&](eViewLayerEEVEEPassType pass_bit) -> int2 {
/* Use dummy texture for disabled passes. Allows correct bindings. */
return (enabled_passes & pass_bit) ? extent : int2(1);
};
eGPUTextureUsage usage_attachment_read = GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_ATTACHMENT;
/* Depth and combined are always needed. */
depth_tx.ensure_2d(depth_format, extent, usage_attachment_read);
/* TODO(fclem): depth_tx should ideally be a texture from pool but we need stencil_view
* which is currently unsupported by pool textures. */
// depth_tx.acquire(extent, depth_format);
combined_tx.acquire_2d(extent, color_format);
eGPUTextureUsage usage_attachment_read_write = GPU_TEXTURE_USAGE_ATTACHMENT |
GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_SHADER_WRITE;
/* TODO(fclem): Make vector pass allocation optional if no TAA or motion blur is needed. */
vector_tx.acquire_2d(extent, vector_tx_format(), usage_attachment_read_write);
if (inst_.pipelines.has_raycast) {
raycast_depth_tx.acquire_2d(extent, raycast_depth_format, GPU_TEXTURE_USAGE_SHADER_READ);
object_id_tx.acquire_2d(extent, object_id_format, usage_attachment_read);
prepass_normal_tx.acquire_2d(extent, prepass_normal_format, usage_attachment_read);
}
else {
/* Still acquire them, since the passes can't conditionally bind textures. */
raycast_depth_tx.acquire_2d(int2(1), raycast_depth_format, GPU_TEXTURE_USAGE_SHADER_READ);
object_id_tx.acquire_2d(int2(1), object_id_format, GPU_TEXTURE_USAGE_SHADER_READ);
prepass_normal_tx.acquire_2d(int2(1), prepass_normal_format, GPU_TEXTURE_USAGE_SHADER_READ);
}
const bool do_motion_vectors_swizzle = vector_tx_format() == gpu::TextureFormat::SFLOAT_16_16;
if (do_motion_vectors_swizzle) {
/* Change texture swizzling to avoid complexity in shaders. */
GPU_texture_swizzle_set(vector_tx, "rgrg");
}
int color_len = data.color_len + data.aovs.color_len;
int value_len = data.value_len + data.aovs.value_len;
rp_color_tx.ensure_2d_array(color_format,
(color_len > 0) ? extent : int2(1),
math::max(1, color_len),
usage_attachment_read_write);
rp_value_tx.ensure_2d_array(float_format,
(value_len > 0) ? extent : int2(1),
math::max(1, value_len),
usage_attachment_read_write);
const gpu::TextureFormat cryptomatte_format = gpu::TextureFormat::SFLOAT_32_32_32_32;
cryptomatte_tx.acquire_2d(pass_extent(EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT |
EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET |
EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL),
cryptomatte_format,
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE);
}
void RenderBuffers::release()
{
/* TODO(fclem): depth_tx should ideally be a texture from pool but we need stencil_view
* which is currently unsupported by pool textures. */
// depth_tx.release();
combined_tx.release();
const bool do_motion_vectors_swizzle = vector_tx_format() == gpu::TextureFormat::SFLOAT_16_16;
if (do_motion_vectors_swizzle) {
/* Reset swizzle since this texture might be reused in other places. */
GPU_texture_swizzle_set(vector_tx, "rgba");
}
vector_tx.release();
raycast_depth_tx.release();
object_id_tx.release();
prepass_normal_tx.release();
cryptomatte_tx.release();
}
gpu::TextureFormat RenderBuffers::vector_tx_format()
{
const eViewLayerEEVEEPassType enabled_passes = inst_.film.enabled_passes_get();
bool do_full_vector_render_pass = ((enabled_passes & EEVEE_RENDER_PASS_VECTOR) ||
inst_.motion_blur.postfx_enabled()) &&
!inst_.is_viewport();
/* Only RG16F (`motion.prev`) for the viewport. */
return do_full_vector_render_pass ? gpu::TextureFormat::SFLOAT_16_16_16_16 :
gpu::TextureFormat::SFLOAT_16_16;
}
} // namespace blender::eevee

View File

@@ -0,0 +1,87 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Render buffers are textures that are filled during a view rendering.
* Their content is then added to the accumulation buffers of the film class.
* They are short lived and can be reused when doing multi view rendering.
*/
#pragma once
#include "DRW_render.hh"
#include "eevee_renderbuffers_shared.hh"
namespace blender::eevee {
class Instance;
class RenderBuffers {
public:
RenderBuffersInfoData &data;
static constexpr gpu::TextureFormat color_format = gpu::TextureFormat::SFLOAT_16_16_16_16;
static constexpr gpu::TextureFormat float_format = gpu::TextureFormat::SFLOAT_16;
static constexpr gpu::TextureFormat depth_format = gpu::TextureFormat::SFLOAT_32_DEPTH_UINT_8;
static constexpr gpu::TextureFormat object_id_format = gpu::TextureFormat::UINT_16;
static constexpr gpu::TextureFormat prepass_normal_format = gpu::TextureFormat::UNORM_10_10_10_2;
Texture depth_tx;
TextureFromPool combined_tx;
// TextureFromPool mist_tx; /* Derived from depth_tx during accumulation. */
TextureFromPool vector_tx;
TextureFromPool raycast_depth_tx;
TextureFromPool object_id_tx;
TextureFromPool prepass_normal_tx;
TextureFromPool cryptomatte_tx;
/* TODO(fclem): Use texture from pool once they support texture array. */
Texture rp_color_tx;
Texture rp_value_tx;
private:
Instance &inst_;
int2 extent_;
public:
RenderBuffers(Instance &inst, RenderBuffersInfoData &data) : data(data), inst_(inst) {};
/** WARNING: RenderBuffers and Film use different storage types for AO and Shadow. */
static ePassStorageType pass_storage_type(eViewLayerEEVEEPassType pass_type)
{
switch (pass_type) {
case EEVEE_RENDER_PASS_DEPTH:
case EEVEE_RENDER_PASS_MIST:
case EEVEE_RENDER_PASS_SHADOW:
case EEVEE_RENDER_PASS_AO:
return PASS_STORAGE_VALUE;
case EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT:
case EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET:
case EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL:
return PASS_STORAGE_CRYPTOMATTE;
default:
return PASS_STORAGE_COLOR;
}
}
void init();
/* Acquires (also ensures) the render buffer before rendering to them. */
void acquire(int2 extent, gpu::TextureFormat raycast_depth_format = depth_format);
void release();
/* Return the size of the allocated render buffers. Undefined if called before `acquire()`. */
int2 extent_get() const
{
return extent_;
}
gpu::TextureFormat vector_tx_format();
};
} // namespace blender::eevee

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "eevee_camera_shared.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
/* Theoretical max is 256 across color and value AOVS with texture array restrictions.
* However, the `output_aov()` function performs a linear search inside all the hashes.
* If we can find a way to avoid this we can bump this number up. */
#define AOV_MAX 128
struct [[host_shared]] AOVsInfoData {
/* Pack 4 hashes per uint4, using std140 packing rules.
* Color AOV hashes are placed before value AOV hashes. */
uint4 hash[AOV_MAX / 4];
/* Number of AOVs stored. */
int color_len;
int value_len;
/** Id of the AOV to be displayed (from the start of the AOV array). -1 for combined. */
int display_id;
/** True if the AOV to be displayed is from the value accumulation buffer. */
bool32_t display_is_value;
};
struct [[host_shared]] RenderBuffersInfoData {
struct AOVsInfoData aovs;
/* Color. */
int color_len;
int normal_id;
int position_id;
int diffuse_light_id;
int diffuse_color_id;
int specular_light_id;
int specular_color_id;
int volume_light_id;
int emission_id;
int environment_id;
int transparent_id;
/* Value */
int value_len;
int shadow_id;
int ambient_occlusion_id;
int _pad0;
int _pad1;
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,420 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Random number generator, contains persistent state and sample count logic.
*/
#include "RNA_access.hh"
#include "BKE_colortools.hh"
#include "BKE_scene.hh"
#include "BLI_rand.h"
#include "BLI_math_base.hh"
#include "BLI_math_base_safe.h"
#include "eevee_instance.hh"
#include "eevee_sampling.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Sampling
* \{ */
void Sampling::init(const Scene *scene)
{
/* Note: Cycles have different option for view layers sample overrides. The current behavior
* matches the default `Use`, which simply override if non-zero. */
uint64_t render_sample_count = (inst_.view_layer->samples > 0) ? inst_.view_layer->samples :
scene->eevee.taa_render_samples;
sample_count_ = inst_.is_viewport() ? scene->eevee.taa_samples : render_sample_count;
if (inst_.is_image_render) {
sample_count_ = math::max(uint64_t(1), sample_count_);
}
if (sample_count_ == 0) {
BLI_assert(inst_.is_viewport());
sample_count_ = infinite_sample_count_;
}
if (inst_.is_viewport()) {
/* We can't rely on the film module as it is initialized later. */
int pixel_size = BKE_render_preview_pixel_size(&inst_.scene->r);
if (pixel_size > 1) {
/* Enforce to render at least all the film pixel once. */
sample_count_ = max_ii(sample_count_, square_i(pixel_size));
}
}
motion_blur_steps_ = !inst_.is_viewport() && ((scene->r.mode & R_MBLUR) != 0) ?
scene->eevee.motion_blur_steps :
1;
sample_count_ = divide_ceil_u(sample_count_, motion_blur_steps_);
if (scene->eevee.flag & SCE_EEVEE_DOF_JITTER) {
if (sample_count_ == infinite_sample_count_) {
/* Special case for viewport continuous rendering. We clamp to a max sample
* to avoid the jittered dof never converging. */
dof_ring_count_ = 6;
}
else {
dof_ring_count_ = sampling_web_ring_count_get(dof_web_density_, sample_count_);
}
dof_sample_count_ = sampling_web_sample_count_get(dof_web_density_, dof_ring_count_);
/* Change total sample count to fill the web pattern entirely. */
sample_count_ = divide_ceil_u(sample_count_, dof_sample_count_) * dof_sample_count_;
}
else {
dof_ring_count_ = 0;
dof_sample_count_ = 1;
}
/* Only multiply after to have full the full DoF web pattern for each time steps. */
sample_count_ *= motion_blur_steps_;
auto clamp_value_load = [](float value) { return (value > 0.0) ? value : 1e20; };
clamp_data_.sun_threshold = clamp_value_load(inst_.world.sun_threshold());
clamp_data_.surface_direct = clamp_value_load(scene->eevee.clamp_surface_direct);
clamp_data_.surface_indirect = clamp_value_load(scene->eevee.clamp_surface_indirect);
clamp_data_.volume_direct = clamp_value_load(scene->eevee.clamp_volume_direct);
clamp_data_.volume_indirect = clamp_value_load(scene->eevee.clamp_volume_indirect);
clamp_data_.direct_scale = scene->eevee.direct_light_intensity;
clamp_data_.indirect_scale = scene->eevee.indirect_light_intensity;
/* Options for overwriting pixel jitter sample position. */
PointerRNA prop_scene = RNA_id_pointer_create(const_cast<ID *>(&scene->id));
blender::PropertyRNA *override_pixel_jitter_sample_prop = RNA_struct_find_property(
&prop_scene, "[\"override_pixel_jitter_sample\"]");
use_custom_pixel_jitter_sample_ = false;
if (override_pixel_jitter_sample_prop) {
const int array_length = RNA_property_array_length(&prop_scene,
override_pixel_jitter_sample_prop);
if (array_length == 2) {
RNA_property_float_get_array(
&prop_scene, override_pixel_jitter_sample_prop, &custom_pixel_jitter_sample_[0]);
use_custom_pixel_jitter_sample_ = true;
}
else if (array_length != 0) {
printf("%s: scene.custom_pixel_jitter_sample length is not 0 or 2.\n", __func__);
}
}
}
void Sampling::init(const Object &probe_object)
{
BLI_assert(inst_.is_baking());
const blender::LightProbe &lightprobe = DRW_object_get_data_for_drawing<blender::LightProbe>(
probe_object);
sample_count_ = max_ii(1, lightprobe.grid_bake_samples);
sample_ = 0;
}
void Sampling::end_sync()
{
if (reset_) {
viewport_sample_ = 0;
}
if (inst_.is_viewport()) {
interactive_mode_ = viewport_sample_ < interactive_mode_threshold;
bool interactive_mode_disabled = (inst_.scene->eevee.flag & SCE_EEVEE_TAA_REPROJECTION) == 0 ||
inst_.is_viewport_image_render;
if (interactive_mode_disabled) {
interactive_mode_ = false;
sample_ = viewport_sample_;
}
else if (interactive_mode_) {
int interactive_sample_count = interactive_sample_max_;
if (viewport_sample_ < interactive_sample_count) {
/* Loop over the same starting samples. */
sample_ = sample_ % interactive_sample_count;
}
else {
/* Break out of the loop and resume normal pattern. */
sample_ = interactive_sample_count;
}
}
}
}
void Sampling::step()
{
{
/* Repeat the sequence for all pixels that are being up-scaled. */
uint64_t sample_filter = sample_ / square_i(inst_.film.scaling_factor_get());
if (interactive_mode()) {
sample_filter = sample_filter % interactive_sample_aa_;
}
/* TODO(fclem) we could use some persistent states to speedup the computation. */
double2 r, offset = {0, 0};
if (use_custom_pixel_jitter_sample_) {
r[0] = custom_pixel_jitter_sample_[0];
r[1] = custom_pixel_jitter_sample_[1];
data_.dimensions[SAMPLING_FILTER_U] = fractf(r[0] + 0.5f);
data_.dimensions[SAMPLING_FILTER_V] = fractf(r[1] + 0.5f);
}
else {
/* Using 2,3 primes as per UE4 Temporal AA presentation.
* http://advances.realtimerendering.com/s2014/epic/TemporalAA.pptx (slide 14) */
uint2 primes = {2, 3};
BLI_halton_2d(primes, offset, sample_filter + 1, r);
/* WORKAROUND: We offset the distribution to make the first sample (0,0). This way, we are
* assured that at least one of the samples inside the TAA rotation will match the one from
* the draw manager. This makes sure overlays are correctly composited in static scene. */
data_.dimensions[SAMPLING_FILTER_U] = fractf(r[0] + (1.0f / 2.0f));
data_.dimensions[SAMPLING_FILTER_V] = fractf(r[1] + (2.0f / 3.0f));
}
/* TODO de-correlate. */
data_.dimensions[SAMPLING_TIME] = r[0];
data_.dimensions[SAMPLING_CLOSURE] = r[1];
data_.dimensions[SAMPLING_RAYTRACE_X] = r[0];
}
{
double3 r, offset = {0, 0, 0};
uint3 primes = {5, 7, 3};
BLI_halton_3d(primes, offset, sample_ + 1, r);
data_.dimensions[SAMPLING_LENS_U] = r[0];
data_.dimensions[SAMPLING_LENS_V] = r[1];
/* TODO de-correlate. */
data_.dimensions[SAMPLING_LIGHTPROBE] = r[0];
data_.dimensions[SAMPLING_TRANSPARENCY] = r[1];
/* TODO de-correlate. */
data_.dimensions[SAMPLING_AO_U] = r[0];
data_.dimensions[SAMPLING_AO_V] = r[1];
data_.dimensions[SAMPLING_AO_W] = r[2];
/* TODO de-correlate. */
data_.dimensions[SAMPLING_CURVES_U] = r[0];
}
{
uint64_t sample_raytrace = sample_;
if (interactive_mode()) {
sample_raytrace = sample_raytrace % interactive_sample_raytrace_;
}
/* Using leaped Halton sequence so we can reused the same primes as lens. */
double3 r, offset = {0, 0, 0};
uint64_t leap = 13;
uint3 primes = {5, 7, 11};
BLI_halton_3d(primes, offset, sample_raytrace * leap + 1, r);
data_.dimensions[SAMPLING_SHADOW_U] = r[0];
data_.dimensions[SAMPLING_SHADOW_V] = r[1];
data_.dimensions[SAMPLING_SHADOW_W] = r[2];
/* TODO de-correlate. */
data_.dimensions[SAMPLING_RAYTRACE_U] = r[0];
data_.dimensions[SAMPLING_RAYTRACE_V] = r[1];
data_.dimensions[SAMPLING_RAYTRACE_W] = r[2];
}
{
double3 r, offset = {0, 0, 0};
uint3 primes = {2, 3, 5};
BLI_halton_3d(primes, offset, sample_ + 1, r);
/* WORKAROUND: We offset the distribution to make the first sample (0,0,0). */
/* TODO de-correlate. */
data_.dimensions[SAMPLING_SHADOW_I] = fractf(r[0] + (1.0 / 2.0));
data_.dimensions[SAMPLING_SHADOW_J] = fractf(r[1] + (2.0 / 3.0));
data_.dimensions[SAMPLING_SHADOW_K] = fractf(r[2] + (4.0 / 5.0));
}
{
uint64_t sample_volume = sample_;
if (interactive_mode()) {
sample_volume = sample_volume % interactive_sample_volume_;
}
double3 r, offset = {0, 0, 0};
uint3 primes = {2, 3, 5};
BLI_halton_3d(primes, offset, sample_volume + 1, r);
/* WORKAROUND: We offset the distribution to make the first sample (0,0,0). */
data_.dimensions[SAMPLING_VOLUME_U] = fractf(r[0] + (1.0 / 2.0));
data_.dimensions[SAMPLING_VOLUME_V] = fractf(r[1] + (2.0 / 3.0));
data_.dimensions[SAMPLING_VOLUME_W] = fractf(r[2] + (4.0 / 5.0));
}
{
/* Using leaped Halton sequence so we can reused the same primes. */
double2 r, offset = {0, 0};
uint64_t leap = 5;
uint2 primes = {2, 3};
BLI_halton_2d(primes, offset, sample_ * leap + 1, r);
data_.dimensions[SAMPLING_SHADOW_X] = r[0];
data_.dimensions[SAMPLING_SHADOW_Y] = r[1];
/* TODO de-correlate. */
data_.dimensions[SAMPLING_SSS_U] = r[0];
data_.dimensions[SAMPLING_SSS_V] = r[1];
}
{
/* Separate sequence for G-buffer quantization dithering. */
double3 r, offset = {0, 0, 0};
uint3 primes = {11, 13, 17};
BLI_halton_3d(primes, offset, sample_ + 1, r);
data_.dimensions[SAMPLING_GBUFFER_U] = r[0];
data_.dimensions[SAMPLING_GBUFFER_V] = r[1];
data_.dimensions[SAMPLING_GBUFFER_W] = r[2];
}
for (int i : IndexRange(SAMPLING_DIMENSION_COUNT)) {
/* These numbers are often fed to `sqrt`. Make sure their values are in the expected range. */
BLI_assert(data_.dimensions[i] >= 0.0f);
BLI_assert(data_.dimensions[i] < 1.0f);
UNUSED_VARS_NDEBUG(i);
}
data_.push_update();
viewport_sample_++;
sample_++;
reset_ = false;
}
void Sampling::reset()
{
BLI_assert(inst_.is_viewport());
reset_ = true;
}
bool Sampling::is_reset() const
{
BLI_assert(inst_.is_viewport());
return reset_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sampling patterns
* \{ */
float3 Sampling::sample_ball(const float3 &rand)
{
float3 sample;
sample.z = rand.x * 2.0f - 1.0f; /* cos theta */
float r = sqrtf(fmaxf(0.0f, 1.0f - square_f(sample.z))); /* sin theta */
float omega = rand.y * 2.0f * M_PI;
sample.x = r * cosf(omega);
sample.y = r * sinf(omega);
sample *= sqrtf(sqrtf(rand.z));
return sample;
}
float2 Sampling::sample_disk(const float2 &rand)
{
float omega = rand.y * 2.0f * M_PI;
return sqrtf(rand.x) * float2(cosf(omega), sinf(omega));
}
float3 Sampling::sample_hemisphere(const float2 &rand)
{
const float omega = rand.y * 2.0f * M_PI;
const float cos_theta = rand.x;
const float sin_theta = safe_sqrtf(1.0f - square_f(cos_theta));
return float3(sin_theta * float2(cosf(omega), sinf(omega)), cos_theta);
}
float3 Sampling::sample_sphere(const float2 &rand)
{
const float omega = rand.y * 2.0f * M_PI;
const float cos_theta = rand.x * 2.0f - 1.0f;
const float sin_theta = safe_sqrtf(1.0f - square_f(cos_theta));
return float3(sin_theta * float2(cosf(omega), sinf(omega)), cos_theta);
}
float2 Sampling::sample_spiral(const float2 &rand)
{
/* Fibonacci spiral. */
float omega = 4.0f * M_PI * (1.0f + sqrtf(5.0f)) * rand.x;
float r = sqrtf(rand.x);
/* Random rotation. */
omega += rand.y * 2.0f * M_PI;
return r * float2(cosf(omega), sinf(omega));
}
void Sampling::dof_disk_sample_get(float *r_radius, float *r_theta) const
{
if (dof_ring_count_ == 0) {
*r_radius = *r_theta = 0.0f;
return;
}
int s = sample_ - 1;
int ring = 0;
int ring_sample_count = 1;
int ring_sample = 1;
s = s * (dof_web_density_ - 1);
s = s % dof_sample_count_;
/* Choosing sample to we get faster convergence.
* The issue here is that we cannot map a low discrepancy sequence to this sampling pattern
* because the same sample could be chosen twice in relatively short intervals. */
/* For now just use an ascending sequence with an offset. This gives us relatively quick
* initial coverage and relatively high distance between samples. */
/* TODO(@fclem) We can try to order samples based on a LDS into a table to avoid duplicates.
* The drawback would be some memory consumption and initialize time. */
int samples_passed = 1;
while (s >= samples_passed) {
ring++;
ring_sample_count = ring * dof_web_density_;
ring_sample = s - samples_passed;
ring_sample = (ring_sample + 1) % ring_sample_count;
samples_passed += ring_sample_count;
}
*r_radius = ring / float(dof_ring_count_);
*r_theta = 2.0f * M_PI * ring_sample / float(ring_sample_count);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cumulative Distribution Function (CDF)
* \{ */
void Sampling::cdf_from_curvemapping(const CurveMapping &curve, Vector<float> &cdf)
{
BLI_assert(cdf.size() > 1);
cdf[0] = 0.0f;
/* Actual CDF evaluation. */
for (int u : IndexRange(cdf.size() - 1)) {
float x = float(u + 1) / float(cdf.size() - 1);
cdf[u + 1] = cdf[u] + BKE_curvemapping_evaluateF(&curve, 0, x);
}
/* Normalize the CDF. */
for (int u : cdf.index_range()) {
cdf[u] /= cdf.last();
}
/* Just to make sure. */
cdf.last() = 1.0f;
}
void Sampling::cdf_invert(Vector<float> &cdf, Vector<float> &inverted_cdf)
{
BLI_assert(cdf.first() == 0.0f && cdf.last() == 1.0f);
for (int u : inverted_cdf.index_range()) {
float x = clamp_f(u / float(inverted_cdf.size() - 1), 1e-5f, 1.0f - 1e-5f);
for (int i : cdf.index_range().drop_front(1)) {
if (cdf[i] >= x) {
float t = (x - cdf[i]) / (cdf[i] - cdf[i - 1]);
inverted_cdf[u] = (float(i) + t) / float(cdf.size() - 1);
break;
}
}
}
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,268 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Random number generator, contains persistent state and sample count logic.
*/
#pragma once
#include "BLI_vector.hh"
#include "DNA_scene_types.h"
#include "eevee_sampling_shared.hh"
#include "eevee_uniform_shared.hh"
namespace blender::eevee {
class Instance;
using SamplingDataBuf = draw::StorageBuffer<SamplingData>;
class Sampling {
private:
Instance &inst_;
/* Number of samples in the first ring of jittered depth of field. */
static constexpr uint64_t dof_web_density_ = 6;
/* High number of sample for viewport infinite rendering. */
static constexpr uint64_t infinite_sample_count_ = 0xFFFFFFu;
/* During interactive rendering, loop over the first few samples. */
static constexpr uint64_t interactive_sample_aa_ = 8;
static constexpr uint64_t interactive_sample_raytrace_ = 32;
static constexpr uint64_t interactive_sample_volume_ = 32;
static constexpr uint64_t interactive_sample_max_ = interactive_sample_aa_ *
interactive_sample_raytrace_ *
interactive_sample_volume_;
/** 0 based current sample. Might not increase sequentially in viewport. */
uint64_t sample_ = 0;
/** Target sample count. */
uint64_t sample_count_ = 64;
/** Number of ring in the web pattern of the jittered Depth of Field. */
uint64_t dof_ring_count_ = 0;
/** Number of samples in the web pattern of the jittered Depth of Field. */
uint64_t dof_sample_count_ = 1;
/** Motion blur steps. */
uint64_t motion_blur_steps_ = 1;
/** Increases if the view and the scene is static. Does increase sequentially. */
int64_t viewport_sample_ = 0;
/** Tag to reset sampling for the next sample. */
bool reset_ = false;
/**
* Switch between interactive and static accumulation.
* In interactive mode, image stability is prioritized over quality.
*/
bool interactive_mode_ = false;
/**
* Sample count after which we use the static accumulation.
* Interactive sampling from sample 0 to (interactive_mode_threshold - 1).
* Accumulation sampling from sample interactive_mode_threshold to sample_count_.
*/
static constexpr int interactive_mode_threshold = 3;
/**
* For overwriting pixel jitter sample position.
*/
bool use_custom_pixel_jitter_sample_ = false;
float2 custom_pixel_jitter_sample_ = {};
SamplingDataBuf data_ = {"SamplingDataBuf"};
ClampData &clamp_data_;
public:
Sampling(Instance &inst, ClampData &clamp_data) : inst_(inst), clamp_data_(clamp_data) {};
~Sampling() {};
void init(const Scene *scene);
void init(const Object &probe_object);
void end_sync();
void step();
/* Viewport Only: Function to call to notify something in the scene changed.
* This will reset accumulation. Do not call after end_sync() or during sample rendering. */
void reset();
/* Viewport Only: true if an update happened in the scene and accumulation needs reset. */
bool is_reset() const;
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_ssbo(SAMPLING_BUF_SLOT, &data_);
}
/* Returns a pseudo random number in [0..1] range. Each dimension are de-correlated.
* WARNING: Don't use during init or sync,
* results are only valid during render, after step() has been called. */
float rng_get(eSamplingDimension dimension) const
{
return data_.dimensions[dimension];
}
/* Returns a pseudo random number in [0..1] range. Each dimension are de-correlated.
* WARNING: Don't use during init or sync,
* results are only valid during render, after step() has been called. */
float2 rng_2d_get(eSamplingDimension starting_dimension) const
{
return *reinterpret_cast<const float2 *>(&data_.dimensions[starting_dimension]);
}
/* Returns a pseudo random number in [0..1] range. Each dimension are de-correlated.
* WARNING: Don't use during init or sync,
* results are only valid during render, after step() has been called. */
float3 rng_3d_get(eSamplingDimension starting_dimension) const
{
return *reinterpret_cast<const float3 *>(&data_.dimensions[starting_dimension]);
}
/* Returns true if rendering has finished. */
bool finished() const
{
return (sample_ >= sample_count_);
}
/* Returns true if viewport smoothing and sampling has finished. */
bool finished_viewport() const
{
return (viewport_sample_ >= sample_count_) && !interactive_mode_;
}
/* Returns true if viewport renderer is in interactive mode and should use TAA. */
bool interactive_mode() const
{
return interactive_mode_;
}
/* Target sample count. */
uint64_t sample_count() const
{
return sample_count_;
}
/* 0 based current sample. Might not increase sequentially in viewport. */
uint64_t sample_index() const
{
return sample_;
}
/* Returns true if a custom pixel jitter sample position is set. */
bool use_custom_pixel_jitter_sample() const
{
return use_custom_pixel_jitter_sample_;
}
bool use_clamp_direct() const
{
return clamp_data_.surface_direct != 0.0f;
}
bool use_clamp_indirect() const
{
return clamp_data_.surface_indirect != 0.0f;
}
bool use_direct_scale() const
{
return clamp_data_.direct_scale != 1.0f;
}
float direct_scale() const
{
return clamp_data_.direct_scale;
}
bool use_indirect_scale() const
{
return clamp_data_.indirect_scale != 1.0f;
}
float indirect_scale() const
{
return clamp_data_.indirect_scale;
}
/* Return true if we are starting a new motion blur step. We need to run sync again since
* depsgraph was updated by MotionBlur::step(). */
bool do_render_sync() const
{
return ((sample_ % (sample_count_ / motion_blur_steps_)) == 0);
}
/**
* Special ball distribution:
* Point are distributed in a way that when they are orthogonally
* projected into any plane, the resulting distribution is (close to)
* a uniform disc distribution.
* \a rand is 3 random float in the [0..1] range.
* Returns point in a ball of radius 1 and centered on the origin.
*/
static float3 sample_ball(const float3 &rand);
/**
* Uniform disc distribution.
* \a rand is 2 random float in the [0..1] range.
* Returns point in a disk of radius 1 and centered on the origin.
*/
static float2 sample_disk(const float2 &rand);
/**
* Uniform hemisphere distribution.
* \a rand is 2 random float in the [0..1] range.
* Returns point on a Z positive hemisphere of radius 1 and centered on the origin.
*/
static float3 sample_hemisphere(const float2 &rand);
/**
* Uniform sphere distribution.
* \a rand is 2 random float in the [0..1] range.
* Returns point on the sphere of radius 1 and centered on the origin.
*/
static float3 sample_sphere(const float2 &rand);
/**
* Uniform disc distribution using Fibonacci spiral sampling.
* \a rand is 2 random float in the [0..1] range.
* Returns point in a disk of radius 1 and centered on the origin.
*/
static float2 sample_spiral(const float2 &rand);
/**
* Special RNG for depth of field.
* Returns \a radius and \a theta angle offset to apply to the web sampling pattern.
*/
void dof_disk_sample_get(float *r_radius, float *r_theta) const;
/**
* Returns sample count inside the jittered depth of field web pattern.
*/
uint64_t dof_ring_count_get() const
{
return dof_ring_count_;
}
/**
* Returns sample count inside the jittered depth of field web pattern.
*/
uint64_t dof_sample_count_get() const
{
return dof_sample_count_;
}
/* Cumulative Distribution Function Utils. */
/**
* Creates a discrete cumulative distribution function table from a given curvemapping.
* Output cdf vector is expected to already be sized according to the wanted resolution.
*/
static void cdf_from_curvemapping(const CurveMapping &curve, Vector<float> &cdf);
/**
* Inverts a cumulative distribution function.
* Output vector is expected to already be sized according to the wanted resolution.
*/
static void cdf_invert(Vector<float> &cdf, Vector<float> &inverted_cdf);
};
} // namespace blender::eevee

View File

@@ -0,0 +1,82 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
enum [[host_shared]] eSamplingDimension : uint32_t {
SAMPLING_FILTER_U = 0u,
SAMPLING_FILTER_V = 1u,
SAMPLING_LENS_U = 2u,
SAMPLING_LENS_V = 3u,
SAMPLING_TIME = 4u,
SAMPLING_SHADOW_U = 5u,
SAMPLING_SHADOW_V = 6u,
SAMPLING_SHADOW_W = 7u,
SAMPLING_SHADOW_X = 8u,
SAMPLING_SHADOW_Y = 9u,
SAMPLING_CLOSURE = 10u,
SAMPLING_LIGHTPROBE = 11u,
SAMPLING_TRANSPARENCY = 12u,
SAMPLING_SSS_U = 13u,
SAMPLING_SSS_V = 14u,
SAMPLING_RAYTRACE_U = 15u,
SAMPLING_RAYTRACE_V = 16u,
SAMPLING_RAYTRACE_W = 17u,
SAMPLING_RAYTRACE_X = 18u,
SAMPLING_AO_U = 19u,
SAMPLING_AO_V = 20u,
SAMPLING_AO_W = 21u,
SAMPLING_CURVES_U = 22u,
SAMPLING_VOLUME_U = 23u,
SAMPLING_VOLUME_V = 24u,
SAMPLING_VOLUME_W = 25u,
SAMPLING_SHADOW_I = 26u,
SAMPLING_SHADOW_J = 27u,
SAMPLING_SHADOW_K = 28u,
SAMPLING_GBUFFER_U = 29u,
SAMPLING_GBUFFER_V = 30u,
SAMPLING_GBUFFER_W = 31u,
};
/**
* IMPORTANT: Make sure the array can contain all sampling dimensions.
* Also note that it needs to be multiple of 4.
*/
#define SAMPLING_DIMENSION_COUNT 32
/* NOTE(@fclem): Needs to be used in #StorageBuffer because of arrays of scalar. */
struct [[host_shared]] SamplingData {
/** Array containing random values from Low Discrepancy Sequence in [0..1) range. */
float dimensions[SAMPLING_DIMENSION_COUNT];
};
/* Returns total sample count in a web pattern of the given size. */
static inline int sampling_web_sample_count_get(int web_density, int in_ring_count)
{
return ((in_ring_count * in_ring_count + in_ring_count) / 2) * web_density + 1;
}
/* Returns lowest possible ring count that contains at least sample_count samples. */
static inline int sampling_web_ring_count_get(int web_density, int sample_count)
{
/* Inversion of web_sample_count_get(). */
float x = 2.0f * (float(sample_count) - 1.0f) / float(web_density);
/* Solving polynomial. We only search positive solution. */
float discriminant = 1.0f + 4.0f * x;
return int(ceilf(0.5f * (sqrtf(discriminant) - 1.0f)));
}
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,306 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Shader module that manage shader libraries, deferred compilation,
* and static shader usage.
*/
#pragma once
#include <array>
#include "BLI_enum_flags.hh"
#include "BLI_map.hh"
#include "BLI_mutex.hh"
#include "DRW_render.hh"
#include "GPU_material.hh"
#include "GPU_shader.hh"
#include "eevee_material_shared.hh"
namespace blender::eevee {
using StaticShader = gpu::StaticShader;
/* Keep alphabetical order and clean prefix. */
enum eShaderType {
AMBIENT_OCCLUSION_PASS = 0,
FILM_COPY,
FILM_COMP,
FILM_CRYPTOMATTE_POST,
FILM_FRAG,
FILM_PASS_CONVERT_COMBINED,
FILM_PASS_CONVERT_DEPTH,
FILM_PASS_CONVERT_VALUE,
FILM_PASS_CONVERT_COLOR,
FILM_PASS_CONVERT_CRYPTOMATTE,
DEFERRED_AOV_CLEAR,
DEFERRED_CAPTURE_EVAL,
DEFERRED_COMBINE,
DEFERRED_LIGHT_SINGLE,
DEFERRED_LIGHT_DOUBLE,
DEFERRED_LIGHT_TRIPLE,
DEFERRED_PLANAR_EVAL,
DEFERRED_THICKNESS_AMEND,
DEFERRED_TILE_CLASSIFY,
DEBUG_GBUFFER,
DEBUG_SURFELS,
DEBUG_IRRADIANCE_GRID,
DISPLAY_PROBE_VOLUME,
DISPLAY_PROBE_SPHERE,
DISPLAY_PROBE_PLANAR,
DOF_BOKEH_LUT,
DOF_DOWNSAMPLE,
DOF_FILTER,
DOF_GATHER_BACKGROUND_LUT,
DOF_GATHER_BACKGROUND,
DOF_GATHER_FOREGROUND_LUT,
DOF_GATHER_FOREGROUND,
DOF_GATHER_HOLE_FILL,
DOF_REDUCE,
DOF_RESOLVE_LUT,
DOF_RESOLVE,
DOF_SCATTER,
DOF_SETUP,
DOF_STABILIZE,
DOF_TILES_DILATE_MINABS,
DOF_TILES_DILATE_MINMAX,
DOF_TILES_FLATTEN,
HIZ_UPDATE,
HIZ_UPDATE_LAYER,
HIZ_DEBUG,
FAST_GI_DENOISE,
FAST_GI_RESOLVE,
FAST_GI_SCAN,
FAST_GI_SETUP,
LIGHT_CULLING_DEBUG,
LIGHT_CULLING_SELECT,
LIGHT_CULLING_SORT,
LIGHT_CULLING_TILE,
LIGHT_CULLING_ZBIN,
LIGHT_SHAPE_DISPLAY,
LIGHT_SHADOW_SETUP,
LIGHTPROBE_IRRADIANCE_BOUNDS,
LIGHTPROBE_IRRADIANCE_OFFSET,
LIGHTPROBE_IRRADIANCE_RAY,
LIGHTPROBE_IRRADIANCE_LOAD,
LIGHTPROBE_IRRADIANCE_WORLD,
LOOKDEV_COPY_WORLD,
LOOKDEV_DISPLAY,
MOTION_BLUR_GATHER,
MOTION_BLUR_TILE_DILATE,
MOTION_BLUR_TILE_FLATTEN_RGBA,
MOTION_BLUR_TILE_FLATTEN_RG,
RAY_DENOISE_BILATERAL,
RAY_DENOISE_SPATIAL,
RAY_DENOISE_TEMPORAL,
RAY_GENERATE,
RAY_TILE_CLASSIFY,
RAY_TILE_COMPACT,
RAY_TRACE_FALLBACK,
RAY_TRACE_PLANAR,
RAY_TRACE_SCREEN,
RENDERPASS_CLEAR,
SPHERE_PROBE_CONVOLVE,
SPHERE_PROBE_IRRADIANCE,
SPHERE_PROBE_REMAP,
SPHERE_PROBE_SELECT,
SPHERE_PROBE_SUNLIGHT,
SHADOW_CLIPMAP_CLEAR,
SHADOW_DEBUG,
SHADOW_PAGE_ALLOCATE,
SHADOW_PAGE_CLEAR,
SHADOW_PAGE_DEFRAG,
SHADOW_PAGE_FREE,
SHADOW_PAGE_MASK,
SHADOW_TILEMAP_AMEND,
SHADOW_TILEMAP_BOUNDS,
SHADOW_TILEMAP_FINALIZE,
SHADOW_TILEMAP_RENDERMAP,
SHADOW_TILEMAP_INIT,
SHADOW_TILEMAP_TAG_UPDATE,
SHADOW_TILEMAP_TAG_UPDATE_PROPAGATE,
SHADOW_TILEMAP_TAG_USAGE_OPAQUE,
SHADOW_TILEMAP_TAG_USAGE_SURFELS,
SHADOW_TILEMAP_TAG_USAGE_TRANSPARENT,
SHADOW_TILEMAP_TAG_USAGE_VOLUME,
SHADOW_VIEW_VISIBILITY,
SUBSURFACE_CONVOLVE,
SUBSURFACE_SETUP,
SURFEL_CLUSTER_BUILD,
SURFEL_LIGHT,
SURFEL_LIST_BUILD,
SURFEL_LIST_FLATTEN,
SURFEL_LIST_PREFIX,
SURFEL_LIST_PREPARE,
SURFEL_LIST_SORT,
SURFEL_RAY,
TRANSPARENCY_RESOLVE,
VERTEX_COPY,
VOLUME_INTEGRATION,
VOLUME_OCCUPANCY_CONVERT,
VOLUME_RESOLVE,
VOLUME_SCATTER,
VOLUME_SCATTER_WITH_LIGHTS,
MAX_SHADER_TYPE,
};
/**
* Bitmask representing the shader categories.
* This allows the loading of certain parts of the engine to kick-in as soon as the shaders that
* depends on it are compiled.
*/
enum ShaderGroups : uint32_t {
NONE = 0,
DEFERRED_LIGHTING_SHADERS = 1 << 0,
DEFERRED_CAPTURE_SHADERS = 1 << 1,
DEFERRED_PLANAR_SHADERS = 1 << 2,
DEPTH_OF_FIELD_SHADERS = 1 << 3,
HIZ_SHADERS = 1 << 4,
FAST_GI_SHADERS = 1 << 5,
LIGHT_CULLING_SHADERS = 1 << 6,
IRRADIANCE_BAKE_SHADERS = 1 << 7,
SPHERE_PROBE_SHADERS = 1 << 8,
SHADOW_SHADERS = 1 << 9,
AMBIENT_OCCLUSION_SHADERS = 1 << 10,
MOTION_BLUR_SHADERS = 1 << 11,
RAYTRACING_SHADERS = 1 << 12,
FILM_SHADERS = 1 << 13,
SUBSURFACE_SHADERS = 1 << 14,
SURFEL_SHADERS = 1 << 15,
VERTEX_COPY_SHADERS = 1 << 16,
VOLUME_EVAL_SHADERS = 1 << 17,
DEFAULT_MATERIALS = 1 << 18,
WORLD_SHADERS = 1 << 19,
MATERIAL_SHADERS = 1 << 20,
VOLUME_PROBE_SHADERS = 1 << 21,
};
ENUM_OPERATORS(ShaderGroups)
/**
* Shader module. shared between instances.
*/
class ShaderModule {
private:
std::array<StaticShader, MAX_SHADER_TYPE> shaders_;
Mutex mutex_;
class SpecializationsKey {
private:
uint64_t hash_value_;
public:
SpecializationsKey(int render_buffers_shadow_id,
int shadow_ray_count,
int shadow_ray_step_count,
bool use_split_indirect,
bool use_lightprobe_eval)
{
BLI_assert(render_buffers_shadow_id >= -1);
BLI_assert(shadow_ray_count >= 1 && shadow_ray_count <= 4);
BLI_assert(shadow_ray_step_count >= 1 && shadow_ray_step_count <= 16);
BLI_assert(uint64_t(use_split_indirect) >= 0 && uint64_t(use_split_indirect) <= 1);
hash_value_ = render_buffers_shadow_id + 1;
hash_value_ = (hash_value_ << 2) | (shadow_ray_count - 1);
hash_value_ = (hash_value_ << 4) | (shadow_ray_step_count - 1);
hash_value_ = (hash_value_ << 1) | uint64_t(use_split_indirect);
hash_value_ = (hash_value_ << 1) | uint64_t(use_lightprobe_eval);
}
uint64_t hash() const
{
return hash_value_;
}
bool operator==(const SpecializationsKey &k) const
{
return hash_value_ == k.hash_value_;
}
};
Map<SpecializationsKey, Vector<AsyncSpecializationHandle>> specialization_handles_;
static gpu::StaticShaderCache<ShaderModule> &get_static_cache()
{
/** Shared shader module across all engine instances. */
static gpu::StaticShaderCache<ShaderModule> static_cache;
return static_cache;
}
public:
ShaderModule();
~ShaderModule();
/* Trigger async compilation for the given shaders groups. */
ShaderGroups static_shaders_load_async(ShaderGroups request_bits)
{
return static_shaders_load(request_bits, false);
}
/* Wait for async compilation to finish for the given shaders groups.
* If shaders are not scheduled to async compile, this will do blocking compilation. */
ShaderGroups static_shaders_wait_ready(ShaderGroups request_bits)
{
return static_shaders_load(request_bits, true);
}
bool request_specializations(bool block_until_ready,
int render_buffers_shadow_id,
int shadow_ray_count,
int shadow_ray_step_count,
bool use_split_indirect,
bool use_lightprobe_eval);
gpu::Shader *static_shader_get(eShaderType shader_type);
GPUMaterial *material_shader_get(blender::Material *blender_mat,
bNodeTree *nodetree,
eMaterialPipeline pipeline_type,
eMaterialGeometry geometry_type,
bool deferred_compilation,
blender::Material *default_mat);
GPUMaterial *world_shader_get(blender::World *blender_world,
bNodeTree *nodetree,
eMaterialPipeline pipeline_type,
bool deferred_compilation);
void material_create_info_amend(GPUMaterial *mat, GPUCodegenOutput *codegen);
/** Only to be used by Instance constructor. */
static ShaderModule *module_get();
static void module_free();
private:
const char *static_shader_create_info_name_get(eShaderType shader_type);
ShaderGroups static_shaders_load(ShaderGroups request_bits, bool block_until_ready);
void material_create_info_pipelines_amend(eMaterialGeometry geometry_type,
eMaterialPipeline pipeline_type,
gpu::shader::ShaderCreateInfo &r_info);
};
} // namespace blender::eevee

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,501 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* The shadow module manages shadow update tagging & shadow rendering.
*/
#pragma once
#include "BLI_pool.hh"
#include "BLI_vector.hh"
#include "GPU_batch.hh"
#include "eevee_camera.hh"
#include "eevee_material.hh"
#include "eevee_shadow_shared.hh"
#include "eevee_sync.hh"
#include "eevee_uniform_shared.hh"
namespace blender::eevee {
class Instance;
class ShadowModule;
class ShadowPipeline;
struct Light;
/* To be applied after view matrix. Follow same order as eCubeFace. */
constexpr static const float shadow_face_mat[6][3][3] = {
{{+1, +0, +0}, {+0, +1, +0}, {+0, +0, +1}}, /* Z_NEG */
{{+0, +0, -1}, {-1, +0, +0}, {+0, +1, +0}}, /* X_POS */
{{+0, +0, +1}, {+1, +0, +0}, {+0, +1, +0}}, /* X_NEG */
{{+1, +0, +0}, {+0, +0, -1}, {+0, +1, +0}}, /* Y_POS */
{{-1, +0, +0}, {+0, +0, +1}, {+0, +1, +0}}, /* Y_NEG */
{{+1, +0, +0}, {+0, -1, +0}, {+0, +0, -1}}, /* Z_POS */
};
/* Converts to [-SHADOW_TILEMAP_RES / 2..SHADOW_TILEMAP_RES / 2] for XY and [0..1] for Z. */
constexpr static const float shadow_clipmap_scale_mat[4][4] = {{SHADOW_TILEMAP_RES / 2, 0, 0, 0},
{0, SHADOW_TILEMAP_RES / 2, 0, 0},
{0, 0, 0.5, 0},
{0, 0, 0.5, 1}};
using ShadowStatisticsBuf = draw::StorageBuffer<ShadowStatistics>;
using ShadowPagesInfoDataBuf = draw::StorageBuffer<ShadowPagesInfoData>;
using ShadowPageHeapBuf = draw::StorageVectorBuffer<uint, SHADOW_MAX_PAGE>;
using ShadowPageCacheBuf = draw::StorageArrayBuffer<uint2, SHADOW_MAX_PAGE, true>;
using ShadowTileMapDataBuf = draw::StorageVectorBuffer<ShadowTileMapData, SHADOW_MAX_TILEMAP>;
using ShadowTileMapClipBuf = draw::StorageArrayBuffer<ShadowTileMapClip, SHADOW_MAX_TILEMAP, true>;
using ShadowTileDataBuf = draw::StorageArrayBuffer<ShadowTileDataPacked, SHADOW_MAX_TILE, true>;
using ShadowRenderViewBuf = draw::StorageArrayBuffer<ShadowRenderView, SHADOW_VIEW_MAX, true>;
/* -------------------------------------------------------------------- */
/** \name Tile-Map
*
* Stores indirection table and states of each tile of a virtual shadow-map.
* One tile-map has the effective resolution of `pagesize * tile_map_resolution`.
* Each tile-map overhead is quite small if they do not have any pages allocated.
*
* \{ */
struct ShadowTileMap : public ShadowTileMapData {
static constexpr int64_t tile_map_resolution = SHADOW_TILEMAP_RES;
static constexpr int64_t tiles_count = tile_map_resolution * tile_map_resolution;
/** Level of detail for clipmap. */
int level = INT_MAX;
/** Cube face index. */
eCubeFace cubeface = Z_NEG;
/** Cached, used for detecting updates. */
float4x4 object_mat = float4x4::identity();
public:
ShadowTileMap(int tiles_index_) : ShadowTileMapData{}
{
tiles_index = tiles_index_;
/* For now just the same index. */
clip_data_index = tiles_index_ / SHADOW_TILEDATA_PER_TILEMAP;
/* Avoid uninitialized data. */
this->grid_offset = int2(0);
this->grid_shift = int2(0);
this->set_dirty();
}
void sync_orthographic(const float4x4 &object_mat_,
int2 origin_offset,
int clipmap_level,
eShadowProjectionType projection_type_,
uint2 shadow_set_membership_ = ~uint2(0));
void sync_cubeface(eLightType light_type_,
const float4x4 &object_mat,
float near,
float far,
eCubeFace face,
uint2 shadow_set_membership_ = ~uint2(0));
void debug_draw() const;
void set_dirty()
{
is_dirty = true;
}
void set_updated()
{
is_dirty = false;
}
};
/**
* The tile-maps are managed on CPU and associated with each light shadow object.
*
* The number of tile-maps & tiles is unbounded (to the limit of SSBOs), but the actual number
* used for rendering is caped to 4096. This is to simplify tile-maps management on CPU.
*
* At sync end, all tile-maps are grouped by light inside the ShadowTileMapDataBuf so that each
* light has a contiguous range of tile-maps to refer to.
*/
struct ShadowTileMapPool {
public:
/** Limit the width of the texture. */
static constexpr int64_t maps_per_row = SHADOW_TILEMAP_PER_ROW;
/** Vector containing available offset to tile range in the ShadowTileDataBuf. */
Vector<uint> free_indices;
/** Pool containing shadow tile structure on CPU. */
Pool<ShadowTileMap> tilemap_pool;
/** Sorted descriptions for each tile-map in the pool. Updated each frame. */
ShadowTileMapDataBuf tilemaps_data = {"tilemaps_data"};
/** Previously used tile-maps that needs to release their tiles/pages. Updated each frame. */
ShadowTileMapDataBuf tilemaps_unused = {"tilemaps_unused"};
/** All possible tiles. A range of tiles tile is referenced by a tile-map. */
ShadowTileDataBuf tiles_data = {"tiles_data"};
/** Clip range for directional shadows. Updated on GPU. Persistent. */
ShadowTileMapClipBuf tilemaps_clip = {"tilemaps_clip"};
/** Texture equivalent of ShadowTileDataBuf but grouped by light. */
Texture tilemap_tx = {"tilemap_tx"};
/** Number of free tile-maps at the end of the previous sync. */
int64_t last_free_len = 0;
public:
ShadowTileMapPool();
ShadowTileMap *acquire();
/**
* Push the given list of ShadowTileMap onto the free stack. Their pages will be free.
*/
void release(Span<ShadowTileMap *> free_list);
void end_sync(ShadowModule &module);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Casters & Receivers
*
* \{ */
/* Can be either a shadow caster or a shadow receiver. */
struct ShadowObject {
ResourceHandle resource_handle;
bool used = true;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name ShadowModule
*
* Manages shadow atlas and shadow region data.
* \{ */
class ShadowPunctual;
class ShadowDirectional;
class ShadowModule {
friend ShadowPunctual;
friend ShadowDirectional;
friend ShadowPipeline;
friend ShadowTileMapPool;
public:
/** Need to be first because of destructor order. */
ShadowTileMapPool tilemap_pool;
Pool<ShadowPunctual> punctual_pool;
Pool<ShadowDirectional> directional_pool;
private:
Instance &inst_;
ShadowSceneData &data_;
/** Map of shadow casters to track deletion & update of intersected shadows. */
Map<ObjectKey, ShadowObject> objects_;
/* Used to call caster_update_ps_ only once per sync (Initialized on begin_sync). */
bool update_casters_ = false;
/* -------------------------------------------------------------------- */
/** \name Tile-map Management
* \{ */
PassSimple tilemap_setup_ps_ = {"TilemapSetup"};
PassMain tilemap_usage_ps_ = {"TagUsage"};
PassSimple tilemap_update_ps_ = {"TilemapUpdate"};
PassMain::Sub *tilemap_usage_transparent_ps_ = nullptr;
gpu::Batch *box_batch_ = nullptr;
/* Source texture for depth buffer analysis. */
gpu::Texture *src_depth_tx_ = nullptr;
Framebuffer usage_tag_fb;
PassSimple caster_update_ps_ = {"CasterUpdate"};
PassSimple jittered_transparent_caster_update_ps_ = {"TransparentCasterUpdate"};
PassSimple update_propagate_ps_ = {"CasterUpdatePropagate"};
/** List of Resource IDs (to get bounds) for tagging passes. */
StorageVectorBuffer<uint, 128> past_casters_updated_ = {"PastCastersUpdated"};
StorageVectorBuffer<uint, 128> curr_casters_updated_ = {"CurrCastersUpdated"};
StorageVectorBuffer<uint, 128> jittered_transparent_casters_ = {"JitteredTransparentCasters"};
/** List of Resource IDs (to get bounds) for getting minimum clip-maps bounds. */
StorageVectorBuffer<uint, 128> curr_casters_ = {"CurrCasters"};
/** Empty framebuffer to rasterize bounding boxes for update tagging. */
Framebuffer update_tag_fb_ = {"update_tag_fb"};
/** Indirect arguments for page clearing. */
DispatchIndirectBuf clear_dispatch_buf_ = {"clear_dispatch_buf"};
/** Indirect arguments for TBDR Tile Page passes. */
DrawIndirectBuf tile_draw_buf_ = {"tile_draw_buf"};
/** A compact stream of rendered tile coordinates in the shadow atlas. */
StorageArrayBuffer<uint, SHADOW_RENDER_MAP_SIZE, true> dst_coord_buf_ = {"dst_coord_buf"};
/** A compact stream of rendered tile coordinates in the framebuffer. */
StorageArrayBuffer<uint, SHADOW_RENDER_MAP_SIZE, true> src_coord_buf_ = {"src_coord_buf"};
/** Same as dst_coord_buf_ but is not compact. More like a linear texture. */
StorageArrayBuffer<uint, SHADOW_RENDER_MAP_SIZE, true> render_map_buf_ = {"render_map_buf"};
/** View to viewport index mapping and other render-only related data. */
ShadowRenderViewBuf render_view_buf_ = {"render_view_buf"};
int3 dispatch_depth_scan_size_;
int2 usage_tag_fb_resolution_;
int usage_tag_fb_lod_ = 5;
int max_view_per_tilemap_ = 1;
int2 input_depth_extent_;
/* Statistics that are read back to CPU after a few frame (to avoid stall). */
SwapChain<ShadowStatisticsBuf, 5> statistics_buf_;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Page Management
* \{ */
static constexpr gpu::TextureFormat atlas_type = gpu::TextureFormat::UINT_32;
/** Atlas containing all physical pages. */
Texture atlas_tx_ = {"shadow_atlas_tx_"};
/** Pool of unallocated pages waiting to be assigned to specific tiles in the tile-map atlas. */
ShadowPageHeapBuf pages_free_data_ = {"PagesFreeBuf"};
/** Pool of cached tiles waiting to be reused. */
ShadowPageCacheBuf pages_cached_data_ = {"PagesCachedBuf"};
/** Information for book keeping and debug. */
ShadowPagesInfoDataBuf pages_infos_data_ = {"PagesInfosBuf"};
int3 copy_dispatch_size_;
int3 scan_dispatch_size_;
int rendering_tilemap_;
int rendering_lod_;
bool do_full_update_ = true;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Rendering
* \{ */
class ShadowView : public View {
Instance &inst_;
ShadowRenderViewBuf &render_view_buf_;
public:
ShadowView(const char *name, Instance &inst, ShadowRenderViewBuf &render_view_buf)
: View(name, SHADOW_VIEW_MAX, true), inst_(inst), render_view_buf_(render_view_buf)
{
}
protected:
/** Special culling pass to take shadow linking into consideration. */
void compute_visibility(ObjectBoundsBuf &bounds,
ObjectInfosBuf &infos,
uint resource_len,
bool debug_freeze) override;
};
/** Multi-View containing a maximum of 64 view to be rendered with the shadow pipeline. */
ShadowView shadow_multi_view_ = {"ShadowMultiView", inst_, render_view_buf_};
/** Framebuffer with the atlas_tx attached. */
Framebuffer render_fb_ = {"shadow_write_framebuffer"};
/** Arrays of viewports to rendering each tile to. */
std::array<int4, 16> multi_viewports_;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debugging
* \{ */
/** Display information about the virtual shadows. */
PassSimple debug_draw_ps_ = {"Shadow.Debug"};
/** \} */
/** Scene immutable parameters. */
/* Render setting that reduces the LOD for every light. */
float global_lod_bias_ = 0.0f;
/** For now, needs to be hardcoded. */
int shadow_page_size_ = SHADOW_PAGE_RES;
/** Maximum number of allocated pages. Maximum value is SHADOW_MAX_TILEMAP. */
int shadow_page_len_ = SHADOW_MAX_TILEMAP;
/** Global switch. */
bool enabled_ = true;
public:
ShadowModule(Instance &inst, ShadowSceneData &data);
~ShadowModule()
{
GPU_BATCH_DISCARD_SAFE(box_batch_);
}
void init();
void begin_sync();
/** Register a shadow caster or receiver. */
void sync_object(const ObjectHandle &ob_handle,
bool is_alpha_blend,
bool has_transparent_shadows,
bool has_time_dependent_shadows);
void end_sync();
void set_lights_data();
void set_view(View &view, int2 extent);
/* Update all shadow regions visible inside the view.
* If called multiple time for the same view, it will only do the depth buffer scanning
* to check any new opaque surfaces.
* Needs to be called after `LightModule::set_view();` and after `ShadowModule::set_view();`. */
void render(View &view, int2 extent);
void debug_end_sync();
void debug_draw(View &view, gpu::FrameBuffer *view_fb);
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_texture(SHADOW_ATLAS_TEX_SLOT, &atlas_tx_);
pass.bind_texture(SHADOW_TILEMAPS_TEX_SLOT, &tilemap_pool.tilemap_tx);
}
const ShadowSceneData &get_data()
{
return data_;
}
float global_lod_bias() const
{
return global_lod_bias_;
}
/* Set all shadows to update. To be called before `end_sync`. */
void reset()
{
do_full_update_ = true;
}
/** Compute approximate screen pixel space radius (as world space radius). */
static float screen_pixel_radius(const float4x4 &wininv,
bool is_perspective,
const int2 &extent);
private:
void remove_unused();
bool shadow_update_finished(int loop_count);
/** Compute approximate punctual shadow pixel world space radius, 1 unit away of the light. */
float tilemap_pixel_radius();
/* Returns the maximum number of view per shadow projection for a single update loop. */
int max_view_per_tilemap();
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow
*
* A shadow component is associated to a `eevee::Light` and manages its associated Tile-maps.
* \{ */
class ShadowPunctual : public NonCopyable, NonMovable {
private:
ShadowModule &shadows_;
/** Tile-map for each cube-face needed (in eCubeFace order). */
Vector<ShadowTileMap *> tilemaps_;
public:
ShadowPunctual(ShadowModule &module) : shadows_(module) {};
ShadowPunctual(ShadowPunctual &&other)
: shadows_(other.shadows_), tilemaps_(std::move(other.tilemaps_)) {};
~ShadowPunctual()
{
shadows_.tilemap_pool.release(tilemaps_);
}
/**
* Release the tile-maps that will not be used in the current frame.
*/
void release_excess_tilemaps(const Light &light);
/**
* Allocate shadow tile-maps and setup views for rendering.
*/
void end_sync(Light &light);
};
class ShadowDirectional : public NonCopyable, NonMovable {
private:
ShadowModule &shadows_;
/** Tile-map for each clip-map level. */
Vector<ShadowTileMap *> tilemaps_;
/** Current range of clip-map / cascades levels covered by this shadow. */
IndexRange levels_range = IndexRange(0);
public:
ShadowDirectional(ShadowModule &module) : shadows_(module) {};
ShadowDirectional(ShadowDirectional &&other)
: shadows_(other.shadows_), tilemaps_(std::move(other.tilemaps_)) {};
~ShadowDirectional()
{
shadows_.tilemap_pool.release(tilemaps_);
}
/**
* Release the tile-maps that will not be used in the current frame.
*/
void release_excess_tilemaps(const Light &light, const Camera &camera);
/**
* Allocate shadow tile-maps and setup views for rendering.
*/
void end_sync(Light &light, const Camera &camera);
/* Return coverage of the whole tile-map in world unit. */
static float coverage_get(int lvl)
{
/* This function should be kept in sync with shadow_directional_level(). */
/* \note If we would to introduce a global scaling option it would be here. */
return exp2(lvl);
}
/* Return coverage of a single tile for a tile-map of this LOD in world unit. */
static float tile_size_get(int lvl)
{
return coverage_get(lvl) / SHADOW_TILEMAP_RES;
}
private:
IndexRange clipmap_level_range(const Camera &camera);
IndexRange cascade_level_range(const Light &light, const Camera &camera);
/**
* Distribute tile-maps in a linear pattern along camera forward vector instead of a clipmap
* centered on camera position.
*/
void cascade_tilemaps_distribution(Light &light, const Camera &camera);
void clipmap_tilemaps_distribution(Light &light, const Camera &camera);
void cascade_tilemaps_distribution_near_far_points(const Camera &camera,
const Light &light,
float3 &near_point,
float3 &far_point);
/* Choose between clip-map and cascade distribution of shadow-map precision depending on the
* camera projection type and bounds. */
static eShadowProjectionType directional_distribution_type_get(const Camera &camera);
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,355 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "eevee_light_shared.hh"
#include "eevee_transform.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
/* -------------------------------------------------------------------- */
/** \name Shadows
*
* Shadow data for either a directional shadow or a punctual shadow.
*
* A punctual shadow is composed of 1, 5 or 6 shadow regions.
* Regions are sorted in this order -Z, +X, -X, +Y, -Y, +Z.
* Face index is computed from light's object space coordinates.
*
* A directional light shadow is composed of multiple clip-maps with each level
* covering twice as much area as the previous one.
* \{ */
enum [[host_shared]] eCubeFace : uint32_t {
/* Ordering by culling order. If cone aperture is shallow, we cull the later view. */
Z_NEG = 0u,
X_POS = 1u,
X_NEG = 2u,
Y_POS = 3u,
Y_NEG = 4u,
Z_POS = 5u,
};
enum [[host_shared]] eShadowProjectionType : uint32_t {
SHADOW_PROJECTION_CUBEFACE,
SHADOW_PROJECTION_CLIPMAP,
SHADOW_PROJECTION_CASCADE,
};
static inline int2 shadow_cascade_grid_offset(int2 base_offset, int level_relative)
{
return (base_offset * level_relative) / (1 << 16);
}
/**
* Small descriptor used for the tile update phase. Updated by CPU & uploaded to GPU each redraw.
*/
struct [[host_shared]] ShadowTileMapData {
/** Cached, used for rendering. */
float4x4 viewmat;
/** Precomputed matrix, not used for rendering but for tagging. */
float4x4 winmat;
/** Punctual : Corners of the frustum. (float3 padded to float4) */
float4 corners[4];
/** Integer offset of the center of the 16x16 tiles from the origin of the tile space. */
int2 grid_offset;
/** Shift between previous and current grid_offset. Allows update tagging. */
int2 grid_shift;
/** True for punctual lights. */
enum eShadowProjectionType projection_type;
/** Multiple of SHADOW_TILEDATA_PER_TILEMAP. Offset inside the tile buffer. */
int tiles_index;
/** Index of persistent data in the persistent data buffer. */
int clip_data_index;
/** Light type this tilemap is from. */
enum eLightType light_type;
/** Entire tilemap (all tiles) needs to be tagged as dirty. */
bool32_t is_dirty;
/** Effective minimum resolution after update throttle. */
int effective_lod_min;
float _pad2;
/** Near and far clip distances for punctual (positive). */
float clip_near;
float clip_far;
/** Half of the tilemap size in world units. Used to compute window matrix. */
float half_size;
/** Offset in local space to the tilemap center in world units. Used for directional winmat. */
float2 center_offset;
/** Shadow set bitmask of the light using this tilemap. */
uint2 shadow_set_membership;
uint2 _pad3;
};
/**
* Lightweight version of ShadowTileMapData that only contains data used for rendering the
* shadow.
*/
struct [[host_shared]] ShadowRenderView {
/**
* Is either:
* - positive radial distance for point lights.
* - zero if disabled.
*/
float clip_distance_inv;
/** Viewport to submit the geometry of this tile-map view to. */
uint viewport_index;
/** True if coming from a sun light shadow. */
bool32_t is_directional;
/** If directional, distance along the negative Z axis of the near clip in view space. */
float clip_near;
/** Copy of `ShadowTileMapData.tiles_index`. */
int tilemap_tiles_index;
/** The level of detail of the tilemap this view is rendering. */
int tilemap_lod;
/** Updated region of the tilemap. */
int2 rect_min;
/** Shadow set bitmask of the light generating this view. */
uint2 shadow_set_membership;
uint2 _pad0;
};
/**
* Per tilemap data persistent on GPU.
* Kept separately for easier clearing on GPU.
*/
struct [[host_shared]] ShadowTileMapClip {
/** Clip distances that were used to render the pages. */
float clip_near_stored;
float clip_far_stored;
/** Near and far clip distances for directional. Float stored as int for atomic operations. */
/** NOTE: These are positive just like camera parameters. */
int clip_near;
int clip_far;
/* Transform the shadow is rendered with. Used to detect updates on GPU. */
struct Transform object_to_world;
/* Integer offset of the center of the 16x16 tiles from the origin of the tile space. */
int2 grid_offset;
int _pad0;
int _pad1;
};
struct [[host_shared]] ShadowPagesInfoData {
/** Number of free pages in the free page buffer. */
int page_free_count;
/** Number of page allocations needed for this cycle. */
int page_alloc_count;
/** Index of the next cache page in the cached page buffer. */
uint page_cached_next;
/** Index of the first page in the buffer since the last defragment. */
uint page_cached_start;
/** Index of the last page in the buffer since the last defragment. */
uint page_cached_end;
int _pad0;
int _pad1;
int _pad2;
};
struct [[host_shared]] ShadowStatistics {
/** Statistics that are read back to CPU after a few frame (to avoid stall). */
/**
* WARNING: Excepting `view_needed_count` it is uncertain if these are accurate.
* This is because `eevee_shadow_page_allocate_comp` runs on all pages even for
* directional. There might be some lingering states somewhere as relying on
* `page_update_count` was causing non-deterministic infinite loop. Needs further
* investigation.
*/
int page_used_count;
int page_update_count;
int page_allocated_count;
int page_rendered_count;
int view_needed_count;
int _pad0;
int _pad1;
int _pad2;
};
/** Decoded tile data structure. */
struct ShadowTileData {
/** Page inside the virtual shadow map atlas. */
uint3 page;
/** Page index inside pages_cached_buf. Only valid if `is_cached` is true. */
uint cache_index;
/** If the tile is needed for rendering. */
bool is_used;
/** True if an update is needed. This persists even if the tile gets unused. */
bool do_update;
/** True if the tile owns the page (mutually exclusive with `is_cached`). */
bool is_allocated;
/** True if the tile has been staged for rendering. This will remove the `do_update` flag. */
bool is_rendered;
/** True if the tile is inside the pages_cached_buf (mutually exclusive with `is_allocated`).
*/
bool is_cached;
};
/** \note Stored packed as a uint. */
#define ShadowTileDataPacked uint
enum [[host_shared]] eShadowFlag : uint32_t {
SHADOW_NO_DATA = 0u,
SHADOW_IS_CACHED = (1u << 27u),
SHADOW_IS_ALLOCATED = (1u << 28u),
SHADOW_DO_UPDATE = (1u << 29u),
SHADOW_IS_RENDERED = (1u << 30u),
SHADOW_IS_USED = (1u << 31u),
/* Reuse the same flag for tagging update before LOD propagation.
* Assume usage tagging is done afterwards. */
SHADOW_TAG_UPDATE = (1u << 31u)
};
/* NOTE: Trust the input to be in valid range (max is [7,7,127]).
* If it is in valid range, it should pack to 14bits so that `shadow_tile_pack()` can use it.
* But sometime this is used to encode invalid pages uint3(-1) and it needs to output uint(-1).
*/
static inline uint shadow_page_pack(uint3 page)
{
return (page.x << 0u) | (page.y << 3u) | (page.z << 6u);
}
static inline uint3 shadow_page_unpack(uint data)
{
uint3 page;
/* clang-format off */ /* Multi-line macros breaks shader error lines. */
BLI_STATIC_ASSERT(SHADOW_PAGE_PER_ROW <= 8 && SHADOW_PAGE_PER_COL <= 8 && SHADOW_PAGE_MAX_LAYER <= 128, "Update page packing")
/* clang-format on */
page.x = (data >> 0u) & 7u;
page.y = (data >> 3u) & 7u;
page.z = (data >> 6u) & 127u;
/* clang-format off */ /* Multi-line macros breaks shader error lines. */
BLI_STATIC_ASSERT(SHADOW_MAX_PAGE == (SHADOW_PAGE_PER_ROW * SHADOW_PAGE_PER_COL * SHADOW_PAGE_MAX_LAYER), "Update page packing")
/* clang-format on */
return page;
}
static inline ShadowTileData shadow_tile_unpack(ShadowTileDataPacked data)
{
ShadowTileData tile;
tile.page = shadow_page_unpack(data);
/* -- 13 bits -- */
/* Unused bits. */
/* -- 14 bits -- */
BLI_STATIC_ASSERT(SHADOW_MAX_PAGE <= 8192, "Update page packing")
tile.cache_index = (data >> 14u) & 8191u;
/* -- 27 bits -- */
tile.is_used = (data & SHADOW_IS_USED) != 0;
tile.is_cached = (data & SHADOW_IS_CACHED) != 0;
tile.is_allocated = (data & SHADOW_IS_ALLOCATED) != 0;
tile.is_rendered = (data & SHADOW_IS_RENDERED) != 0;
tile.do_update = (data & SHADOW_DO_UPDATE) != 0;
return tile;
}
static inline ShadowTileDataPacked shadow_tile_pack(ShadowTileData tile)
{
uint data;
/* NOTE: Page might be set to invalid values for tracking invalid usages.
* So we have to mask the result. */
data = shadow_page_pack(tile.page) & uint(SHADOW_MAX_PAGE - 1);
BLI_STATIC_ASSERT(SHADOW_MAX_PAGE <= 8192, "Update page packing")
data |= (tile.cache_index & 8191u) << 14u;
data |= (tile.is_used ? uint(SHADOW_IS_USED) : 0);
data |= (tile.is_allocated ? uint(SHADOW_IS_ALLOCATED) : 0);
data |= (tile.is_cached ? uint(SHADOW_IS_CACHED) : 0);
data |= (tile.is_rendered ? uint(SHADOW_IS_RENDERED) : 0);
data |= (tile.do_update ? uint(SHADOW_DO_UPDATE) : 0);
return data;
}
/**
* Decoded tile data structure.
* Similar to ShadowTileData, this one is only used for rendering and packed into `tilemap_tx`.
* This allow to reuse some bits for other purpose.
*/
struct ShadowSamplingTile {
/** Page inside the virtual shadow map atlas. */
uint3 page;
/** LOD pointed by LOD 0 tile page. */
uint lod;
/** Offset to the texel position to align with the LOD page start. (directional only). */
uint2 lod_offset;
/** If the tile is needed for rendering. */
bool is_valid;
};
/** \note Stored packed as a uint. */
#define ShadowSamplingTilePacked uint
/* NOTE: Trust the input to be in valid range [0, (1 << SHADOW_TILEMAP_MAX_CLIPMAP_LOD) - 1].
* Maximum LOD level index we can store is SHADOW_TILEMAP_MAX_CLIPMAP_LOD,
* so we need SHADOW_TILEMAP_MAX_CLIPMAP_LOD bits to store the offset in each dimension.
* Result fits into SHADOW_TILEMAP_MAX_CLIPMAP_LOD * 2 bits. */
static inline uint shadow_lod_offset_pack(uint2 ofs)
{
BLI_STATIC_ASSERT(SHADOW_TILEMAP_MAX_CLIPMAP_LOD <= 8, "Update page packing")
return ofs.x | (ofs.y << SHADOW_TILEMAP_MAX_CLIPMAP_LOD);
}
static inline uint2 shadow_lod_offset_unpack(uint data)
{
return (uint2(data) >> uint2(0, SHADOW_TILEMAP_MAX_CLIPMAP_LOD)) &
uint2((1 << SHADOW_TILEMAP_MAX_CLIPMAP_LOD) - 1);
}
static inline ShadowSamplingTile shadow_sampling_tile_unpack(ShadowSamplingTilePacked data)
{
ShadowSamplingTile tile;
tile.page = shadow_page_unpack(data);
/* -- 13 bits -- */
/* Max value is actually SHADOW_TILEMAP_MAX_CLIPMAP_LOD but we mask the bits. */
BLI_STATIC_ASSERT(SHADOW_TILEMAP_MAX_CLIPMAP_LOD <= 8u, "Update lod packing")
tile.lod = (data >> 13u) & 7u;
/* -- 16 bits -- */
tile.lod_offset = shadow_lod_offset_unpack(data >> 16u);
/* -- 32 bits -- */
tile.is_valid = data != 0u;
#ifndef GPU_SHADER
/* Make tests pass on CPU but it is not required for proper rendering. */
if (tile.lod == 0) {
tile.lod_offset.x = 0;
}
#endif
return tile;
}
static inline ShadowSamplingTilePacked shadow_sampling_tile_pack(ShadowSamplingTile tile)
{
if (!tile.is_valid) {
return 0u;
}
/* Tag a valid tile of LOD0 valid by setting their offset to 1.
* This doesn't change the sampling and allows to use of all bits for data.
* This makes sure no valid packed tile is 0u. */
if (tile.lod == 0) {
tile.lod_offset.x = 1;
}
uint data = shadow_page_pack(tile.page);
BLI_STATIC_ASSERT(SHADOW_MAX_PAGE <= 8192, "Update page packing")
/* Max value is actually SHADOW_TILEMAP_MAX_CLIPMAP_LOD but we mask the bits. */
BLI_STATIC_ASSERT(SHADOW_TILEMAP_MAX_CLIPMAP_LOD <= 8u, "Update lod packing")
data |= (tile.lod & 7u) << 13u;
data |= shadow_lod_offset_pack(tile.lod_offset) << 16u;
return data;
}
static inline ShadowSamplingTile shadow_sampling_tile_create(ShadowTileData tile_data, uint lod)
{
ShadowSamplingTile tile;
tile.page = tile_data.page;
tile.lod = lod;
tile.lod_offset = uint2(0, 0); /* Computed during tilemap amend phase. */
/* At this point, it should be the case that all given tiles that have been tagged as used are
* ready for sampling. Otherwise tile_data should be SHADOW_NO_DATA. */
tile.is_valid = tile_data.is_used;
return tile;
}
/** \} */
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,212 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#include <algorithm>
#include "eevee_instance.hh"
#include "eevee_subsurface.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Subsurface
* \{ */
void SubsurfaceModule::end_sync()
{
data_.sample_len = 16;
if (!(inst_.pipelines.deferred.closure_bits_get() & CLOSURE_SSS)) {
return;
}
/* Process direct / indirect radiance separately for correct ligthpath intensity. */
/* Note that we do not branch on clamp.surface_indirect since it is applied on the spherical
* harmonics or rays before SSS evaluation. */
use_split_radiance_ = inst_.uniform_data.data.clamp.direct_scale != 1.0f ||
inst_.uniform_data.data.clamp.indirect_scale != 1.0f ||
inst_.scene->eevee.clamp_surface_direct != 0.0f;
{
gpu::Shader *sh = inst_.shaders.static_shader_get(SUBSURFACE_SETUP);
PassSimple &pass = setup_ps_;
pass.init();
pass.state_set(DRW_STATE_NO_DRAW);
pass.specialize_constant(sh, "use_split_radiance", use_split_radiance_);
pass.shader_set(sh);
pass.bind_resources(inst_.gbuffer);
pass.bind_texture("depth_tx", &inst_.render_buffers.depth_tx);
pass.bind_image("direct_light_img", &direct_light_tx_);
pass.bind_image("indirect_light_img", &indirect_light_tx_);
pass.bind_image("object_id_img", &object_id_tx_);
pass.bind_image("radiance_img", &radiance_tx_);
pass.bind_ssbo("convolve_tile_buf", &convolve_tile_buf_);
pass.bind_ssbo("convolve_dispatch_buf", &convolve_dispatch_buf_);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
pass.dispatch(&setup_dispatch_size_);
}
{
/* Clamping to border color allows to always load ID 0 for out of view samples and discard
* their influence. Also disable filtering to avoid light bleeding between different objects
* and loading invalid interpolated IDs. */
GPUSamplerState sampler = {GPU_SAMPLER_FILTERING_DEFAULT,
GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER,
GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER,
GPU_SAMPLER_CUSTOM_COMPARE,
GPU_SAMPLER_STATE_TYPE_PARAMETERS};
gpu::Shader *sh = inst_.shaders.static_shader_get(SUBSURFACE_CONVOLVE);
PassSimple &pass = convolve_ps_;
pass.init();
pass.state_set(DRW_STATE_NO_DRAW);
pass.specialize_constant(sh, "use_split_radiance", use_split_radiance_);
pass.shader_set(sh);
pass.bind_resources(inst_.uniform_data);
pass.bind_ubo(SUBSURFACE_BUF_SLOT, data_);
pass.bind_resources(inst_.gbuffer);
pass.bind_texture("radiance_tx", &radiance_tx_, sampler);
pass.bind_texture("depth_tx", &inst_.render_buffers.depth_tx, sampler);
pass.bind_texture("object_id_tx", &object_id_tx_, sampler);
pass.bind_image("out_direct_light_img", &direct_light_tx_);
pass.bind_image("out_indirect_light_img", &indirect_light_tx_);
pass.bind_ssbo("tiles_coord_buf", &convolve_tile_buf_);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_STORAGE);
pass.dispatch(convolve_dispatch_buf_);
}
}
void SubsurfaceModule::render(gpu::Texture *direct_diffuse_light_tx,
gpu::Texture *indirect_diffuse_light_tx,
eClosureBits active_closures,
View &view)
{
if (!(active_closures & CLOSURE_SSS)) {
return;
}
/* TODO: This only needs to be update once per render sample. */
precompute_samples_location();
data_.push_update();
int2 render_extent = inst_.film.render_extent_get();
setup_dispatch_size_ = int3(math::divide_ceil(render_extent, int2(SUBSURFACE_GROUP_SIZE)), 1);
const int convolve_tile_count = setup_dispatch_size_.x * setup_dispatch_size_.y;
convolve_tile_buf_.resize(ceil_to_multiple_u(convolve_tile_count, 512));
direct_light_tx_ = direct_diffuse_light_tx;
indirect_light_tx_ = indirect_diffuse_light_tx;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
object_id_tx_.acquire_2d(render_extent, gpu::TextureFormat::SUBSURFACE_OBJECT_ID_FORMAT, usage);
radiance_tx_.acquire_2d_array(render_extent,
use_split_radiance_ ? 2 : 1,
gpu::TextureFormat::SUBSURFACE_RADIANCE_FORMAT,
usage);
convolve_dispatch_buf_.clear_to_zero();
inst_.manager->submit(setup_ps_, view);
inst_.manager->submit(convolve_ps_, view);
object_id_tx_.release();
radiance_tx_.release();
}
void SubsurfaceModule::precompute_samples_location()
{
/* Precompute sample position with white albedo. */
float d = burley_setup(float3(1.0f), float3(1.0f)).x;
float rand_u = inst_.sampling.rng_get(SAMPLING_SSS_U);
float rand_v = inst_.sampling.rng_get(SAMPLING_SSS_V);
/* Find minimum radius that we can represent because we are only sampling the largest radius. */
data_.min_radius = 1.0f;
double golden_angle = M_PI * (3.0 - sqrt(5.0));
for (auto i : IndexRange(data_.sample_len)) {
float theta = golden_angle * i + M_PI * 2.0f * rand_u;
float x = (rand_v + i) / data_.sample_len;
float r = SubsurfaceModule::burley_sample(d, x);
data_.min_radius = min_ff(data_.min_radius, r);
data_.samples[i].x = cosf(theta) * r;
data_.samples[i].y = sinf(theta) * r;
data_.samples[i].z = 1.0f / burley_pdf(d, r);
}
/* Avoid float imprecision. */
data_.min_radius = max_ff(data_.min_radius, 1e-4f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Christensen-Burley SSS model
*
* Based on: "Approximate Reflectance Profiles for Efficient Subsurface Scattering"
* by Per Christensen
* https://graphics.pixar.com/library/ApproxBSSRDF/approxbssrdfslides.pdf
* \{ */
float SubsurfaceModule::burley_sample(float d, float x_rand)
{
x_rand *= SSS_BURLEY_TRUNCATE_CDF;
const float tolerance = 1e-6;
const int max_iteration_count = 10;
/* Do initial guess based on manual curve fitting, this allows us to reduce
* number of iterations to maximum 4 across the [0..1] range. We keep maximum
* number of iteration higher just to be sure we didn't miss root in some
* corner case.
*/
float r;
if (x_rand <= 0.9) {
r = exp(x_rand * x_rand * 2.4) - 1.0;
}
else {
/* TODO(sergey): Some nicer curve fit is possible here. */
r = 15.0;
}
/* Solve against scaled radius. */
for (int i = 0; i < max_iteration_count; i++) {
float exp_r_3 = exp(-r / 3.0);
float exp_r = exp_r_3 * exp_r_3 * exp_r_3;
float f = 1.0 - 0.25 * exp_r - 0.75 * exp_r_3 - x_rand;
float f_ = 0.25 * exp_r + 0.25 * exp_r_3;
if (abs(f) < tolerance || f_ == 0.0) {
break;
}
r = r - f / f_;
r = std::max<double>(r, 0.0);
}
return r * d;
}
float SubsurfaceModule::burley_eval(float d, float r)
{
if (r >= SSS_BURLEY_TRUNCATE * d) {
return 0.0;
}
/* Slide 33. */
float exp_r_3_d = expf(-r / (3.0f * d));
float exp_r_d = exp_r_3_d * exp_r_3_d * exp_r_3_d;
return (exp_r_d + exp_r_3_d) / (8.0f * float(M_PI) * d);
}
float SubsurfaceModule::burley_pdf(float d, float r)
{
return burley_eval(d, r) / SSS_BURLEY_TRUNCATE_CDF;
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,91 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Postprocess diffuse radiance output from the diffuse evaluation pass to mimic subsurface
* transmission.
*
* This implementation follows the technique described in the SIGGRAPH presentation:
* "Efficient screen space subsurface scattering SIGGRAPH 2018"
* by Evgenii Golubev
*
* But, instead of having all the precomputed weights for all three color primaries,
* we precompute a weight profile texture to be able to support per pixel AND per channel radius.
*/
#pragma once
#include "draw_pass.hh"
#include "eevee_subsurface_shared.hh"
namespace blender::eevee {
using namespace draw;
/* -------------------------------------------------------------------- */
/** \name Subsurface
*
* \{ */
class Instance;
using SubsurfaceDataBuf = draw::UniformBuffer<SubsurfaceData>;
using SubsurfaceTileBuf = draw::StorageArrayBuffer<uint, 1024, true>;
struct SubsurfaceModule {
private:
Instance &inst_;
/** Contains samples locations. */
SubsurfaceDataBuf data_;
/** Scene diffuse irradiance. Pointer bound at sync time, set at render time. */
gpu::Texture *direct_light_tx_;
gpu::Texture *indirect_light_tx_;
/** Input radiance packed with surface ID. */
TextureFromPool radiance_tx_;
TextureFromPool object_id_tx_;
/** Setup pass fill the radiance_id_tx_ for faster convolution. */
PassSimple setup_ps_ = {"Subsurface.Prepare"};
int3 setup_dispatch_size_ = {1, 1, 1};
/** Screen space convolution pass. */
PassSimple convolve_ps_ = {"Subsurface.Convolve"};
SubsurfaceTileBuf convolve_tile_buf_;
DispatchIndirectBuf convolve_dispatch_buf_;
/* Process direct and indirect radiance separately. */
bool use_split_radiance_ = false;
public:
SubsurfaceModule(Instance &inst) : inst_(inst)
{
/* Force first update. */
data_.sample_len = -1;
};
~SubsurfaceModule() {};
void end_sync();
/* Process the direct & indirect diffuse light buffers using screen space subsurface scattering.
* Result is stored in the direct light texture. */
void render(gpu::Texture *direct_diffuse_light_tx,
gpu::Texture *indirect_diffuse_light_tx,
eClosureBits active_closures,
View &view);
private:
void precompute_samples_location();
/** Christensen-Burley implementation. */
static float burley_sample(float d, float x_rand);
static float burley_eval(float d, float r);
static float burley_pdf(float d, float r);
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,73 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
#define SSS_SAMPLE_MAX 64
#define SSS_BURLEY_TRUNCATE 16.0f
#define SSS_BURLEY_TRUNCATE_CDF 0.9963790093708328
#define SSS_TRANSMIT_LUT_SIZE 64.0f
#define SSS_TRANSMIT_LUT_RADIUS 2.0f
#define SSS_TRANSMIT_LUT_SCALE ((SSS_TRANSMIT_LUT_SIZE - 1.0f) / float(SSS_TRANSMIT_LUT_SIZE))
#define SSS_TRANSMIT_LUT_BIAS (0.5f / float(SSS_TRANSMIT_LUT_SIZE))
#define SSS_TRANSMIT_LUT_STEP_RES 64.0f
struct [[host_shared]] SubsurfaceData {
/** xy: 2D sample position [-1..1], zw: sample_bounds. */
/* NOTE(fclem) Using float4 for alignment. */
float4 samples[SSS_SAMPLE_MAX];
/** Number of samples precomputed in the set. */
int sample_len;
/** WORKAROUND: To avoid invalid integral for components that have very small radius, we clamp
* the minimal radius. This add bias to the SSS effect but this is the simplest workaround I
* could find to ship this without visible artifact. */
float min_radius;
int _pad1;
int _pad2;
};
static inline float3 burley_setup(float3 radius, float3 albedo)
{
/* TODO(fclem): Avoid constant duplication. */
const float m_1_pi = 0.318309886183790671538f;
float3 A = albedo;
/* Diffuse surface transmission, equation (6). */
float3 s = 1.9f - A + 3.5f * ((A - 0.8f) * (A - 0.8f));
/* Mean free path length adapted to fit ancient Cubic and Gaussian models. */
float3 l = 0.25f * m_1_pi * radius;
return l / s;
}
static inline float3 burley_eval(float3 d, float r)
{
/* Slide 33. */
float3 exp_r_3_d;
/* TODO(fclem): Vectorize. */
exp_r_3_d.x = expf(-r / (3.0f * d.x));
exp_r_3_d.y = expf(-r / (3.0f * d.y));
exp_r_3_d.z = expf(-r / (3.0f * d.z));
float3 exp_r_d = exp_r_3_d * exp_r_3_d * exp_r_3_d;
/* NOTE:
* - Surface albedo is applied at the end.
* - This is normalized diffuse model, so the equation is multiplied
* by 2*pi, which also matches `cdf()`.
*/
return (exp_r_d + exp_r_3_d) / (4.0 * d);
}
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,544 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Converts the different renderable object types to draw-calls.
*/
#include "BKE_paint.hh"
#include "BKE_paint_bvh.hh"
#include "DNA_curves_types.h"
#include "DNA_modifier_types.h"
#include "DNA_particle_types.h"
#include "DNA_pointcloud_types.h"
#include "DNA_volume_types.h"
#include "draw_cache.hh"
#include "draw_common.hh"
#include "draw_sculpt.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Recalc
*
* \{ */
ObjectHandle SyncModule::sync_object(const ObjectRef &ob_ref,
const ResourceHandleRange &res_handle,
uint sub_key)
{
return ObjectHandle(ob_ref, res_handle, inst_.get_recalc_flags(ob_ref), sub_key);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Common
* \{ */
static inline void geometry_call(PassMain::Sub *sub_pass,
gpu::Batch *geom,
ResourceHandleRange resource_handle)
{
if (sub_pass != nullptr) {
sub_pass->draw(geom, resource_handle);
}
}
static inline void geometry_volume_call(PassMain::Sub *pass,
GPUMaterial *gpumat,
Scene *scene,
Object *ob,
gpu::Batch *geom,
ResourceHandle res_handle)
{
BLI_assert(ob->type != OB_VOLUME);
if (pass != nullptr) {
PassMain::Sub *object_pass = volume_sub_pass(*pass, scene, ob, gpumat);
if (object_pass != nullptr) {
object_pass->draw(geom, {ResourceID(res_handle)});
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Common Helpers
* \{ */
void SyncModule::sync_common_passes(const Material &material,
FunctionRef<void(const MaterialPass &)> sync_cb)
{
sync_cb(material.shadow);
sync_cb(material.shading);
sync_cb(material.prepass);
sync_cb(material.capture);
sync_cb(material.lightprobe_sphere_prepass);
sync_cb(material.lightprobe_sphere_shading);
sync_cb(material.planar_probe_prepass);
sync_cb(material.planar_probe_shading);
}
void SyncModule::sync_volume_passes(const ObjectHandle &ob_handle,
const Material &material,
FunctionRef<void(const MaterialPass &, int)> sync_cb)
{
if (material.volume_occupancy.gpumat == nullptr || material.volume_material.gpumat == nullptr) {
return;
}
blender::Material *blender_mat = GPU_material_get_material(material.volume_material.gpumat);
for (int instance : IndexRange(ob_handle.instances_count())) {
/* TODO(fclem): This is against design. Sync shouldn't depend on view properties (camera). */
VolumeLayer *layer = inst_.pipelines.volume.register_and_get_layer(
VolumeObjectBounds(inst_.camera, ob_handle, instance));
if (!layer) {
continue;
}
sync_cb(MaterialPass{material.volume_occupancy.gpumat,
layer->occupancy_add(
ob_handle.object, blender_mat, material.volume_occupancy.gpumat)},
instance);
sync_cb(MaterialPass{material.volume_material.gpumat,
layer->material_add(
ob_handle.object, blender_mat, material.volume_material.gpumat)},
instance);
}
}
void SyncModule::sync_alpha_blended_passes(const ObjectHandle &ob_handle,
const Material &material,
FunctionRef<void(const MaterialPass &, int)> sync_cb)
{
const bool hide_on_camera = ob_handle.object->visibility_flag & OB_HIDE_CAMERA;
if (hide_on_camera || !material.is_alpha_blend_transparent) {
return;
}
blender::Material *blender_mat = GPU_material_get_material(material.shading.gpumat);
for (int instance : IndexRange(ob_handle.instances_count())) {
PassMain::Sub *prepass = nullptr;
PassMain::Sub *matpass = nullptr;
inst_.pipelines.forward.transparent_add(ob_handle.object,
ob_handle.object_to_world(instance).location(),
blender_mat,
material.shading.gpumat,
prepass,
matpass);
sync_cb(MaterialPass{material.overlap_masking.gpumat, prepass}, instance);
sync_cb(MaterialPass{material.shading.gpumat, matpass}, instance);
}
}
void SyncModule::sync_common(const ObjectHandle &ob_handle,
Span<Material *> materials,
Span<GPUMaterial *> gpu_materials)
{
bool has_volume = false;
bool is_alpha_blend = false;
bool has_transparent_shadows = false;
bool has_time_dependent_shadows = false;
float inflate_bounds = 0.0f;
for (const Material *material : materials) {
has_volume |= material->has_volume;
if (material->has_volume && !material->has_surface) {
continue;
}
GPUMaterial *gpu_material = material->shading.gpumat;
blender::Material *bl_material = GPU_material_get_material(gpu_material);
const bool has_displacement = GPU_material_has_displacement_output(gpu_material) &&
(bl_material->displacement_method != MA_DISPLACEMENT_BUMP);
const bool has_time_node = GPU_material_flag_get(gpu_material, GPU_MATFLAG_SCENE_TIME);
is_alpha_blend |= material->is_alpha_blend_transparent;
has_transparent_shadows |= material->has_transparent_shadows;
has_time_dependent_shadows |= has_time_node &&
(material->has_transparent_shadows || has_displacement);
if (has_displacement) {
inflate_bounds = math::max(inflate_bounds, bl_material->inflate_bounds);
}
inst_.cryptomatte.sync_material(bl_material);
}
inst_.cryptomatte.sync_object(ob_handle);
inst_.shadows.sync_object(
ob_handle, is_alpha_blend, has_transparent_shadows, has_time_dependent_shadows);
if (has_volume) {
inst_.volume.object_sync(ob_handle);
}
if (inflate_bounds != 0.0f) {
inst_.manager->update_handle_bounds(ob_handle.res_handle, ob_handle, inflate_bounds);
}
inst_.manager->extract_object_attributes(ob_handle.res_handle, ob_handle, gpu_materials);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mesh
* \{ */
void SyncModule::sync_mesh(const ObjectRef &ob_ref)
{
if (!inst_.use_surfaces) {
return;
}
if ((ob_ref.object->dt < OB_SOLID) &&
(inst_.is_viewport() && inst_.v3d->shading.type != OB_RENDER))
{
/** Do not render objects with display type lower than solid when in material preview mode. */
return;
}
ObjectHandle ob_handle = sync_object(ob_ref, inst_.manager->unique_handle(ob_ref));
bool has_motion = inst_.velocity.step_object_sync(ob_handle);
MaterialArray &material_array = inst_.materials.material_array_get(ob_handle, has_motion);
Span<gpu::Batch *> mat_geom = DRW_cache_object_surface_material_get(
ob_handle.object, material_array.gpu_materials);
if (mat_geom.is_empty()) {
return;
}
Vector<Material *, 8> synced_materials;
for (auto i : material_array.gpu_materials.index_range()) {
gpu::Batch *geom = mat_geom[i];
if (geom == nullptr) {
continue;
}
Material &material = material_array.materials[i];
synced_materials.append(&material);
if (material.has_volume) {
sync_volume_passes(ob_handle, material, [&](const MaterialPass &pass, int instance) {
geometry_volume_call(pass.sub_pass,
pass.gpumat,
inst_.scene,
ob_handle.object,
geom,
ob_handle.res_handle.sub_handle(instance));
});
/* Do not render surface if we are rendering a volume object
* and do not have a surface closure. */
if (!material.has_surface) {
continue;
}
}
sync_common_passes(material, [&](const MaterialPass &pass) {
geometry_call(pass.sub_pass, geom, ob_handle.res_handle);
});
sync_alpha_blended_passes(ob_handle, material, [&](const MaterialPass &pass, int instance) {
geometry_call(pass.sub_pass, geom, ob_handle.res_handle.sub_handle(instance));
});
}
sync_common(ob_handle, synced_materials.as_span(), material_array.gpu_materials);
}
bool SyncModule::sync_sculpt(const ObjectRef &ob_ref)
{
if (!inst_.use_surfaces) {
return false;
}
bool pbvh_draw = BKE_sculptsession_use_pbvh_draw(ob_ref.object, inst_.rv3d) &&
!inst_.is_image_render;
if (!pbvh_draw) {
return false;
}
ObjectHandle ob_handle = sync_object(ob_ref, inst_.manager->unique_handle_for_sculpt(ob_ref));
bool has_motion = false;
MaterialArray &material_array = inst_.materials.material_array_get(ob_handle, has_motion);
Vector<Material *, 8> synced_materials;
for (SculptBatch &batch :
sculpt_batches_per_material_get(ob_ref.object, material_array.gpu_materials))
{
gpu::Batch *geom = batch.batch;
if (geom == nullptr) {
continue;
}
Material &material = material_array.materials[batch.material_slot];
synced_materials.append(&material);
if (material.has_volume) {
sync_volume_passes(ob_handle, material, [&](const MaterialPass &pass, int instance) {
geometry_volume_call(pass.sub_pass,
pass.gpumat,
inst_.scene,
ob_handle.object,
geom,
ob_handle.res_handle.sub_handle(instance));
});
/* Do not render surface if we are rendering a volume object
* and do not have a surface closure. */
if (!material.has_surface) {
continue;
}
}
sync_common_passes(material, [&](const MaterialPass &pass) {
geometry_call(pass.sub_pass, geom, ob_handle.res_handle);
});
sync_alpha_blended_passes(ob_handle, material, [&](const MaterialPass &pass, int instance) {
geometry_call(pass.sub_pass, geom, ob_handle.res_handle.sub_handle(instance));
});
}
sync_common(ob_handle, synced_materials.as_span(), material_array.gpu_materials);
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Point Cloud
* \{ */
void SyncModule::sync_pointcloud(const ObjectRef &ob_ref)
{
const int material_slot = POINTCLOUD_MATERIAL_NR;
ObjectHandle ob_handle = sync_object(ob_ref, inst_.manager->unique_handle(ob_ref));
bool has_motion = inst_.velocity.step_object_sync(ob_handle);
Material material = inst_.materials.material_get(
ob_handle, has_motion, material_slot - 1, MAT_GEOM_POINTCLOUD);
auto drawcall_add = [&](const MaterialPass &matpass, bool dual_sided = false) {
if (matpass.sub_pass == nullptr) {
return;
}
PassMain::Sub &object_pass = matpass.sub_pass->sub("Point Cloud Sub Pass");
gpu::Batch *geometry = pointcloud_sub_pass_setup(
object_pass, ob_handle.object, matpass.gpumat);
if (dual_sided) {
/* WORKAROUND: Hack to generate backfaces. Should also be baked into the Index Buf too at
* some point in the future. */
object_pass.push_constant("ptcloud_backface", false);
object_pass.draw(geometry, ob_handle.res_handle);
object_pass.push_constant("ptcloud_backface", true);
object_pass.draw(geometry, ob_handle.res_handle);
}
else {
object_pass.push_constant("ptcloud_backface", false);
object_pass.draw(geometry, ob_handle.res_handle);
}
};
if (material.has_volume) {
/* Volumes not supported for now. */
/* NOTE: Point Cloud volumes used to work at some point,
* but we didn't catch the regression due to the test being disabled. :( */
if (!material.has_surface) {
/* Do not render surface if we are rendering a volume object
* and do not have a surface closure. */
return;
}
}
sync_common_passes(material, [&](const MaterialPass &pass) { drawcall_add(pass); });
sync_alpha_blended_passes(ob_handle, material, [&](const MaterialPass &pass, int /*instance*/) {
drawcall_add(pass);
});
sync_common(ob_handle, {&material}, {material.shading.gpumat});
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Volume Objects
* \{ */
void SyncModule::sync_volume(const ObjectRef &ob_ref)
{
if (!inst_.use_volumes) {
return;
}
ObjectHandle ob_handle = sync_object(ob_ref, inst_.manager->unique_handle(ob_ref));
const int material_slot = VOLUME_MATERIAL_NR;
/* Motion is not supported on volumes yet. */
const bool has_motion = false;
Material material = inst_.materials.material_get(
ob_handle, has_motion, material_slot - 1, MAT_GEOM_VOLUME);
if (!material.has_volume) {
return;
}
/* Do not render the object if there is no attribute used in the volume.
* This mimic Cycles behavior (see #124061). */
ListBaseT<GPUMaterialAttribute> attr_list = GPU_material_attributes(
material.volume_material.gpumat);
if (attr_list.is_empty()) {
return;
}
/* Use bounding box tag empty spaces. */
gpu::Batch *geom = inst_.volume.unit_cube_batch_get();
bool is_rendered = false;
sync_volume_passes(ob_handle, material, [&](const MaterialPass &pass, int /*instance*/) {
if (pass.sub_pass == nullptr) {
return;
}
BLI_assert_msg(!ob_handle.is_range(),
"volume_object_grids_init pass setup is object/instance specific.");
PassMain::Sub *object_pass = volume_sub_pass(
*pass.sub_pass, inst_.scene, ob_handle.object, pass.gpumat);
if (object_pass != nullptr) {
object_pass->draw(geom, ob_handle.res_handle);
is_rendered = true;
}
});
if (!is_rendered) {
return;
}
inst_.volume.object_sync(ob_handle);
inst_.manager->extract_object_attributes(
ob_handle.res_handle, ob_handle, material.volume_material.gpumat);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Hair
* \{ */
void SyncModule::sync_curves(const ObjectRef &ob_ref, HairParticleInfo const *hair_particle)
{
if (!inst_.use_curves) {
return;
}
int mat_nr = CURVES_MATERIAL_NR;
if (hair_particle != nullptr) {
mat_nr = hair_particle->psys.part->omat;
}
ObjectHandle ob_handle = hair_particle ?
ObjectHandle(ob_ref,
inst_.manager->resource_handle_for_psys(
ob_ref, ob_ref.object_to_world()),
hair_particle->recalc_flags,
hair_particle->sub_key) :
sync_object(ob_ref, inst_.manager->unique_handle(ob_ref));
bool has_motion = inst_.velocity.step_object_sync(ob_handle, hair_particle);
Material material = inst_.materials.material_get(
ob_handle, has_motion, mat_nr - 1, MAT_GEOM_CURVES);
auto drawcall_add = [&](const MaterialPass &matpass) {
if (matpass.sub_pass == nullptr) {
return;
}
if (hair_particle != nullptr) {
PassMain::Sub &sub_pass = matpass.sub_pass->sub("Hair SubPass");
gpu::Batch *geometry = hair_sub_pass_setup(
sub_pass, inst_.scene, ob_ref, &hair_particle->psys, &hair_particle->md, matpass.gpumat);
sub_pass.draw(geometry, ob_handle.res_handle);
}
else {
PassMain::Sub &sub_pass = matpass.sub_pass->sub("Curves SubPass");
const char *error = nullptr;
gpu::Batch *geometry = curves_sub_pass_setup(
sub_pass, inst_.scene, ob_ref.object, error, matpass.gpumat);
if (error) {
inst_.info_append(error);
}
sub_pass.draw(geometry, ob_handle.res_handle);
}
};
if (material.has_volume) {
/* Volumes not supported for now. */
if (!material.has_surface) {
/* Do not render surface if we are rendering a volume object
* and do not have a surface closure. */
return;
}
}
sync_common_passes(material, [&](const MaterialPass &pass) { drawcall_add(pass); });
sync_alpha_blended_passes(ob_handle, material, [&](const MaterialPass &pass, int /*instance*/) {
drawcall_add(pass);
});
sync_common(ob_handle, {&material}, {material.shading.gpumat});
}
/** \} */
void foreach_hair_particle(Instance &inst, ObjectRef &ob_ref, HairHandleCallback callback)
{
uint sub_key = 1;
for (ModifierData &md : ob_ref.object->modifiers) {
if (md.type == eModifierType_ParticleSystem) {
ParticleSystem *particle_sys = reinterpret_cast<ParticleSystemModifierData *>(&md)->psys;
ParticleSettings *part_settings = particle_sys->part;
/* Only use the viewport drawing mode for material preview. */
const int draw_as = (part_settings->draw_as == PART_DRAW_REND || !inst.is_viewport()) ?
part_settings->ren_as :
part_settings->draw_as;
if (draw_as != PART_DRAW_PATH ||
!DRW_object_is_visible_psys_in_active_context(ob_ref.object, particle_sys))
{
continue;
}
callback({md, *particle_sys, uint(particle_sys->recalc), sub_key++});
}
}
}
} // namespace blender::eevee

View File

@@ -0,0 +1,99 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* Structures to identify unique data blocks. The keys are unique so we are able to
* match ids across frame updates.
*/
#pragma once
#include "BKE_duplilist.hh"
#include "BLI_function_ref.hh"
#include "BLI_map.hh"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DRW_render.hh"
#include "draw_handle.hh"
namespace blender::eevee {
using namespace draw;
class Instance;
/* -------------------------------------------------------------------- */
/** \name Sync Module
*
* \{ */
struct BaseHandle {
const uint recalc = 0;
};
struct ObjectHandle : ObjectRef, BaseHandle {
const ResourceHandleRange res_handle;
ObjectHandle(const ObjectRef &ob_ref,
const ResourceHandleRange &res_handle,
uint recalc,
uint sub_key = 0)
: ObjectRef(ob_ref, sub_key), BaseHandle{recalc}, res_handle(res_handle) {};
};
struct WorldHandle : public BaseHandle {};
struct Material;
struct MaterialPass;
class SyncModule {
private:
Instance &inst_;
public:
SyncModule(Instance &inst) : inst_(inst) {};
~SyncModule() {};
ObjectHandle sync_object(const ObjectRef &ob_ref,
const ResourceHandleRange &res_handle,
uint sub_key = 0);
void sync_mesh(const ObjectRef &ob_ref);
bool sync_sculpt(const ObjectRef &ob_ref);
void sync_pointcloud(const ObjectRef &ob_ref);
void sync_volume(const ObjectRef &ob_ref);
void sync_curves(const ObjectRef &ob_ref,
struct HairParticleInfo const *hair_particle = nullptr);
private:
void sync_common_passes(const Material &material,
FunctionRef<void(const MaterialPass &)> sync_cb);
void sync_volume_passes(const ObjectHandle &ob_handle,
const Material &material,
FunctionRef<void(const MaterialPass &, int)> sync_cb);
void sync_alpha_blended_passes(const ObjectHandle &ob_handle,
const Material &material,
FunctionRef<void(const MaterialPass &, int)> sync_cb);
void sync_common(const ObjectHandle &ob_handle,
Span<Material *> materials,
Span<GPUMaterial *> gpu_materials);
};
struct HairParticleInfo {
ModifierData &md;
ParticleSystem &psys;
uint recalc_flags;
uint sub_key;
};
using HairHandleCallback = FunctionRef<void(const HairParticleInfo &)>;
void foreach_hair_particle(Instance &inst, ObjectRef &ob_ref, HairHandleCallback callback);
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,113 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
/**
* The #Transform class is used to store object transforms in a compact manner (row major).
*/
struct [[host_shared]] Transform {
/* The transform is stored transposed for compactness. */
float4 x;
float4 y;
float4 z;
#ifndef GPU_SHADER
Transform() = default;
Transform(const float4x4 &tx)
: x(tx[0][0], tx[1][0], tx[2][0], tx[3][0]),
y(tx[0][1], tx[1][1], tx[2][1], tx[3][1]),
z(tx[0][2], tx[1][2], tx[2][2], tx[3][2])
{
}
operator float4x4() const
{
return float4x4(float4(x.x, y.x, z.x, 0.0f),
float4(x.y, y.y, z.y, 0.0f),
float4(x.z, y.z, z.z, 0.0f),
float4(x.w, y.w, z.w, 1.0f));
}
#endif
float3 x_axis() const
{
return float3(x.x, y.x, z.x);
}
float3 y_axis() const
{
return float3(x.y, y.y, z.y);
}
float3 z_axis() const
{
return float3(x.z, y.z, z.z);
}
float3 location() const
{
return float3(x.w, y.w, z.w);
}
};
static inline float4x4 transform_to_matrix(Transform t)
{
return float4x4(float4(t.x.x, t.y.x, t.z.x, 0.0f),
float4(t.x.y, t.y.y, t.z.y, 0.0f),
float4(t.x.z, t.y.z, t.z.z, 0.0f),
float4(t.x.w, t.y.w, t.z.w, 1.0f));
}
static inline Transform transform_from_matrix(float4x4 m)
{
Transform t;
t.x = float4(m[0][0], m[1][0], m[2][0], m[3][0]);
t.y = float4(m[0][1], m[1][1], m[2][1], m[3][1]);
t.z = float4(m[0][2], m[1][2], m[2][2], m[3][2]);
return t;
}
#ifdef GPU_SHADER
static inline bool transform_equal(Transform a, Transform b)
{
return all(equal(a.x, b.x)) && all(equal(a.y, b.y)) && all(equal(a.z, b.z));
}
#endif
static inline float3 transform_point(Transform t, float3 point)
{
return float4(point, 1.0f) * float3x4(t.x, t.y, t.z);
}
static inline float3 transform_direction(Transform t, float3 direction)
{
return direction * float3x3(float3(t.x.x, t.x.y, t.x.z),
float3(t.y.x, t.y.y, t.y.z),
float3(t.z.x, t.z.y, t.z.z));
}
static inline float3 transform_direction_transposed(Transform t, float3 direction)
{
return float3x3(float3(t.x.x, t.x.y, t.x.z),
float3(t.y.x, t.y.y, t.y.z),
float3(t.z.x, t.z.y, t.z.z)) *
direction;
}
/* Assumes the transform has unit scale. */
static inline float3 transform_point_inversed(Transform t, float3 point)
{
return float3x3(float3(t.x.x, t.x.y, t.x.z),
float3(t.y.x, t.y.y, t.y.z),
float3(t.z.x, t.z.y, t.z.z)) *
(point - t.location());
}
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,121 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "eevee_camera_shared.hh"
#include "eevee_film_shared.hh"
#include "eevee_hizbuffer_shared.hh"
#include "eevee_raytrace_shared.hh"
#include "eevee_renderbuffers_shared.hh"
#include "eevee_subsurface_shared.hh"
#include "eevee_volume_shared.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
/* This should be inside "eevee_light_shared.hh" but it would pull a huge header that is not
* essential for most shaders. This could be moved back if including "eevee_bxdf_lib.glsl" is used
* only for shading shaders. */
enum [[host_shared]] LightingType : uint32_t {
LIGHT_DIFFUSE = 0u,
LIGHT_SPECULAR = 1u,
LIGHT_TRANSMISSION = 2u,
LIGHT_VOLUME = 3u,
/* WORKAROUND: Special value used to tag translucent BSDF with thickness.
* Fall back to LIGHT_DIFFUSE. */
LIGHT_TRANSLUCENT_WITH_THICKNESS = 4u,
};
struct [[host_shared]] ShadowSceneData {
/* Number of shadow rays to shoot for each light. */
int ray_count;
/* Number of shadow samples to take for each shadow ray. */
int step_count;
/* Bounding radius for a film pixel at 1 unit from the camera. */
float film_pixel_radius;
/* Global switch for jittered shadows. */
bool32_t use_jitter;
/* Output atomic counter instead of depth. */
bool32_t use_debug_cost;
int _pad0;
int _pad1;
int _pad2;
};
struct [[host_shared]] SceneData {
float time;
float frame;
uint _pad0;
uint _pad1;
};
/* Light Clamping. */
struct [[host_shared]] ClampData {
float sun_threshold;
float surface_direct;
float surface_indirect;
float volume_direct;
float volume_indirect;
float direct_scale;
float indirect_scale;
int _pad0;
};
/* Emulation of the light path node. */
enum [[host_shared]] RayPipelineType : uint32_t {
RAY_TYPE_CAMERA,
RAY_TYPE_SHADOW,
RAY_TYPE_DIFFUSE,
RAY_TYPE_GLOSSY,
};
struct [[host_shared]] PipelineInfoData {
float alpha_hash_scale;
enum RayPipelineType ray_type;
/* True if the main camera view has inverted handedness.
* This is the case if the camera has negative scale on one axis. */
bool32_t is_main_view_inverted;
/* WORKAROUND: Usually we would use imageSize to get the number of layers and get this id.
* However, some implementation return the number of layers from the base texture instead of the
* texture view (see #146132). So we always pass the correct layer index manually to avoid any
* platform inconsistency. */
int gbuffer_additional_data_layer_id;
/* Use monochromatic transmittance for the forward pipeline. */
bool32_t use_monochromatic_transmittance;
/* True if the pipeline can use the raycast node.
* The value is valid only if `MAT_RAYCAST` is defined. */
bool32_t can_raycast;
bool32_t _pad1;
bool32_t _pad2;
};
/* Combines data from several modules to avoid wasting binding slots. */
struct [[host_shared]] UniformData {
struct AOData ao;
struct CameraData camera;
struct ClampData clamp;
struct FilmData film;
struct HiZData hiz;
struct RenderBuffersInfoData render_pass;
struct ShadowSceneData shadow;
struct VolumesInfoData volumes;
struct SceneData scene;
};
/**
* World space clip plane equation. Used to render planar light-probes.
* Moved here to avoid dependencies to light-probe just for this. */
struct [[host_shared]] ClipPlaneData {
float4 plane;
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,470 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* The velocity pass outputs motion vectors to use for either
* temporal re-projection or motion blur.
*
* It is the module that tracks the objects between frames updates.
*/
#include "BKE_duplilist.hh"
#include "BKE_object.hh"
#include "BLI_map.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_modifier_types.h"
#include "DNA_particle_types.h"
#include "DNA_rigidbody_types.h"
#include "DRW_engine.hh"
#include "GPU_capabilities.hh"
#include "draw_cache.hh"
#include "draw_cache_impl.hh"
#include "eevee_instance.hh"
// #include "eevee_renderpasses.hh"
#include "eevee_shader.hh"
#include "eevee_velocity.hh"
#include "eevee_velocity_shared.hh"
#include "draw_common.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name VelocityModule
*
* \{ */
void VelocityModule::init()
{
if (!inst_.is_viewport() && !inst_.is_baking() &&
(inst_.film.enabled_passes_get() & EEVEE_RENDER_PASS_VECTOR) &&
!inst_.motion_blur.postfx_enabled())
{
/* No motion blur and the vector pass was requested. Do the steps sync here. */
const Scene *scene = inst_.scene;
float initial_time = scene->r.cfra + scene->r.subframe;
step_sync(STEP_PREVIOUS, initial_time - 1.0f);
step_sync(STEP_NEXT, initial_time + 1.0f);
/* Let the main sync loop handle the current step. */
inst_.set_time(initial_time);
step_ = STEP_CURRENT;
}
/* For viewport, only previous motion is supported.
* Still bind previous step to avoid undefined behavior. */
next_step_ = (inst_.is_viewport() || inst_.is_baking()) ? STEP_PREVIOUS : STEP_NEXT;
}
/* Similar to Instance::object_sync, but only syncs velocity. */
static void step_object_sync_render(Instance &inst, ObjectRef &ob_ref)
{
Object *ob = ob_ref.object;
const bool is_velocity_type = ELEM(ob->type, OB_CURVES, OB_MESH, OB_POINTCLOUD);
const int ob_visibility = DRW_object_visibility_in_active_context(ob);
const bool partsys_is_visible = (ob_visibility & OB_VISIBLE_PARTICLES) != 0 &&
(ob->type == OB_MESH);
const bool object_is_visible = DRW_object_is_renderable(ob) &&
(ob_visibility & OB_VISIBLE_SELF) != 0;
if (!is_velocity_type || (!partsys_is_visible && !object_is_visible)) {
return;
}
/* NOTE: Dummy resource handle since this won't be used for drawing. */
ResourceHandleRange resource_handle = {};
if (partsys_is_visible) {
auto sync_hair = [&](const HairParticleInfo &info) {
ObjectHandle ob_handle(ob_ref, resource_handle, info.recalc_flags, info.sub_key);
inst.velocity.step_object_sync(ob_handle, &info);
};
foreach_hair_particle(inst, ob_ref, sync_hair);
};
if (object_is_visible) {
ObjectHandle ob_handle = inst.sync.sync_object(ob_ref, resource_handle);
inst.velocity.step_object_sync(ob_handle);
}
}
void VelocityModule::step_sync(eVelocityStep step, float time)
{
inst_.set_time(time);
step_ = step;
object_steps_usage[step_] = 0;
step_camera_sync();
DRW_render_object_iter(
inst_.render, inst_.depsgraph, [&](draw::ObjectRef &ob_ref, RenderEngine *, Depsgraph *) {
step_object_sync_render(inst_, ob_ref);
});
geometry_steps_fill();
}
void VelocityModule::step_camera_sync()
{
inst_.camera.sync();
*camera_steps[step_] = inst_.camera.data_get();
step_time[step_] = inst_.scene->r.cfra + inst_.scene->r.subframe;
/* Fix undefined camera steps when rendering is starting. */
if ((step_ == STEP_CURRENT) && (camera_steps[STEP_PREVIOUS]->initialized == false)) {
*camera_steps[STEP_PREVIOUS] = *static_cast<CameraData *>(camera_steps[step_]);
camera_steps[STEP_PREVIOUS]->initialized = true;
step_time[STEP_PREVIOUS] = step_time[step_];
}
}
bool VelocityModule::step_object_sync(const ObjectHandle &ob_handle,
HairParticleInfo const *hair_particle /*=nullptr*/)
{
bool has_motion = object_has_velocity(ob_handle.object) ||
(ob_handle.recalc & ID_RECALC_TRANSFORM);
/* NOTE: Fragile. This will only work with 1 frame of lag since we can't record every geometry
* just in case there might be an update the next frame. */
bool has_deform = object_is_deform(ob_handle.object) || (ob_handle.recalc & ID_RECALC_GEOMETRY);
if (!has_motion && !has_deform) {
return false;
}
/* While VelocityObjectData is unique for each object/instance, multiple VelocityObjectDatas
* can point to the same offset in VelocityGeometryData, since geometry is stored local space. */
uint64_t velocity_id = hair_particle ? uint64_t(&hair_particle->psys) :
uint64_t(ob_handle.object->data);
/* Geometry motion. */
if (has_deform) {
auto add_cb = [&]() {
VelocityGeometryData data;
if (hair_particle) {
data.pos_buf = draw::hair_pos_buffer_get(
inst_.scene, ob_handle.object, &hair_particle->psys, &hair_particle->md);
return data;
}
switch (ob_handle.object->type) {
case OB_CURVES:
data.pos_buf = draw::curves_pos_buffer_get(ob_handle.object);
break;
case OB_POINTCLOUD:
data.pos_buf = DRW_pointcloud_position_and_radius_buffer_get(ob_handle.object);
break;
case OB_MESH:
data.pos_buf = DRW_cache_mesh_surface_get(ob_handle.object);
break;
default:
break;
}
return data;
};
const VelocityGeometryData &data = geometry_map.lookup_or_add_cb(velocity_id, add_cb);
has_deform = data.has_data();
}
bool any_instance_has_motion = false;
for (int i : IndexRange(ob_handle.instances_count())) {
/* Object motion. */
/* FIXME(fclem) As we are using original objects pointers, there is a chance the previous
* object key matches a totally different object if the scene was changed by user or python
* callback. In this case, we cannot correctly match objects between updates.
* What this means is that there will be incorrect motion vectors for these objects.
* We live with that until we have a correct way of identifying new objects. */
VelocityObjectData &vel = velocity_map.lookup_or_add_default(ObjectKey(ob_handle, i));
vel.obj.ofs[step_] = object_steps_usage[step_]++;
if (ob_handle.res_handle.is_valid()) {
vel.obj.resource_index = ob_handle.res_handle.sub_handle(i).index();
}
else {
/* Called from `step_object_sync_render`. */
vel.obj.resource_index = 0;
}
vel.id = velocity_id;
object_steps[step_]->get_or_resize(vel.obj.ofs[step_]) = ob_handle.object_to_world(i);
if (step_ == STEP_CURRENT) {
/* Replace invalid steps. Can happen if object was hidden in one of those steps. */
if (vel.obj.ofs[STEP_PREVIOUS] == -1) {
vel.obj.ofs[STEP_PREVIOUS] = object_steps_usage[STEP_PREVIOUS]++;
object_steps[STEP_PREVIOUS]->get_or_resize(
vel.obj.ofs[STEP_PREVIOUS]) = ob_handle.object_to_world(i);
}
if (vel.obj.ofs[STEP_NEXT] == -1) {
if (inst_.is_viewport()) {
/* Just set it to 0. motion.next is not meant to be valid in the viewport. */
vel.obj.ofs[STEP_NEXT] = 0;
}
else {
vel.obj.ofs[STEP_NEXT] = object_steps_usage[STEP_NEXT]++;
object_steps[STEP_NEXT]->get_or_resize(
vel.obj.ofs[STEP_NEXT]) = ob_handle.object_to_world(i);
}
}
}
/* Avoid drawing object that has no motions but were tagged as such. */
if (step_ == STEP_CURRENT && has_motion == true && has_deform == false) {
const float4x4 &obmat_curr = (*object_steps[STEP_CURRENT])[vel.obj.ofs[STEP_CURRENT]];
const float4x4 &obmat_prev = (*object_steps[STEP_PREVIOUS])[vel.obj.ofs[STEP_PREVIOUS]];
if (inst_.is_viewport()) {
any_instance_has_motion |= obmat_curr != obmat_prev;
}
else {
const float4x4 &obmat_next = (*object_steps[STEP_NEXT])[vel.obj.ofs[STEP_NEXT]];
any_instance_has_motion |= (obmat_curr != obmat_prev) || (obmat_curr != obmat_next);
}
}
#if 0
if (!has_motion && !has_deform) {
printf("Detected no motion on %s (%d)\n", ob_handle.object->id.name, i);
}
if (has_deform) {
printf("Geometry Motion on %s (%d)\n", ob_handle.object->id.name, i);
}
if (has_motion) {
printf("Object Motion on %s (%d)\n", ob_handle.object->id.name, i);
}
#endif
}
return has_deform || any_instance_has_motion;
}
void VelocityModule::geometry_steps_fill()
{
uint dst_ofs = 0;
for (VelocityGeometryData &geom : geometry_map.values()) {
gpu::VertBuf *pos_buf = geom.pos_buf_get();
if (!pos_buf) {
continue;
}
uint src_len = GPU_vertbuf_get_vertex_len(pos_buf);
geom.len = src_len;
geom.ofs = dst_ofs;
dst_ofs += src_len;
}
/* Fallback when velocity buffer could not be allocated. */
if (GPU_max_storage_buffer_size() < dst_ofs * sizeof(float) * 4) {
inst_.info_append_i18n(
"Error: Could not allocate velocity geometry buffer. Only object and camera velocities "
"will be used.");
for (VelocityObjectData &vel : velocity_map.values()) {
vel.geo.len[step_] = -1;
vel.geo.ofs[step_] = -1;
vel.id = 0;
}
return;
}
geometry_steps[step_]->resize(max_ii(16, dst_ofs));
DRW_submission_start();
PassSimple copy_ps("Velocity Copy Pass");
copy_ps.init();
copy_ps.state_set(DRW_STATE_NO_DRAW);
copy_ps.shader_set(inst_.shaders.static_shader_get(VERTEX_COPY));
copy_ps.bind_ssbo("out_buf", *geometry_steps[step_]);
for (VelocityGeometryData &geom : geometry_map.values()) {
gpu::VertBuf *pos_buf = geom.pos_buf_get();
if (!pos_buf || geom.len == 0) {
continue;
}
const GPUVertFormat *format = GPU_vertbuf_get_format(pos_buf);
if (format->stride == 16) {
GPU_storagebuf_copy_sub_from_vertbuf(*geometry_steps[step_],
pos_buf,
geom.ofs * sizeof(float4),
0,
geom.len * sizeof(float4));
}
else {
BLI_assert(format->stride % 4 == 0);
copy_ps.bind_ssbo("in_buf", pos_buf);
copy_ps.push_constant("start_offset", geom.ofs);
copy_ps.push_constant("vertex_stride", int(format->stride / 4));
copy_ps.push_constant("vertex_count", geom.len);
uint group_len_x = divide_ceil_u(geom.len, VERTEX_COPY_GROUP_SIZE);
uint verts_per_thread = divide_ceil_u(group_len_x, GPU_max_work_group_count(0));
copy_ps.dispatch(int3(group_len_x / verts_per_thread, 1, 1));
}
}
copy_ps.barrier(GPU_BARRIER_SHADER_STORAGE);
inst_.manager->submit(copy_ps);
DRW_submission_end();
/* Copy back the #VelocityGeometryIndex into #VelocityObjectData which are
* indexed using persistent keys (unlike geometries which are indexed by volatile ID). */
for (VelocityObjectData &vel : velocity_map.values()) {
const VelocityGeometryData &geom = geometry_map.lookup_default(vel.id, VelocityGeometryData());
vel.geo.len[step_] = geom.len;
vel.geo.ofs[step_] = geom.ofs;
/* Avoid reuse. */
vel.id = 0;
}
geometry_map.clear();
}
void VelocityModule::step_swap()
{
auto swap_steps = [&](eVelocityStep step_a, eVelocityStep step_b) {
std::swap(object_steps[step_a], object_steps[step_b]);
std::swap(geometry_steps[step_a], geometry_steps[step_b]);
std::swap(camera_steps[step_a], camera_steps[step_b]);
std::swap(step_time[step_a], step_time[step_b]);
std::swap(object_steps_usage[step_a], object_steps_usage[step_b]);
for (VelocityObjectData &vel : velocity_map.values()) {
vel.obj.ofs[step_a] = vel.obj.ofs[step_b];
vel.obj.ofs[step_b] = uint(-1);
vel.geo.ofs[step_a] = vel.geo.ofs[step_b];
vel.geo.len[step_a] = vel.geo.len[step_b];
vel.geo.ofs[step_b] = uint(-1);
vel.geo.len[step_b] = uint(-1);
}
};
if (inst_.is_viewport()) {
geometry_steps_fill();
/* For viewport we only use the last rendered redraw as previous frame.
* We swap current with previous step at the end of a redraw.
* We do not support motion blur as it is rendered to avoid conflicting motions
* for temporal reprojection. */
swap_steps(eVelocityStep::STEP_PREVIOUS, eVelocityStep::STEP_CURRENT);
}
else {
/* Render case: The STEP_CURRENT is left untouched. */
swap_steps(eVelocityStep::STEP_PREVIOUS, eVelocityStep::STEP_NEXT);
}
}
void VelocityModule::begin_sync()
{
step_ = STEP_CURRENT;
step_camera_sync();
object_steps_usage[step_] = 0;
/* STEP_NEXT is not used for viewport. (See #131134) */
BLI_assert(!inst_.is_viewport() || object_steps_usage[STEP_NEXT] == 0);
}
void VelocityModule::end_sync()
{
Vector<ObjectKey, 0> deleted_obj;
uint32_t max_resource_index_ = 0u;
for (MapItem<ObjectKey, VelocityObjectData> item : velocity_map.items()) {
if (item.value.obj.resource_index == uint32_t(-1)) {
deleted_obj.append(item.key);
}
else {
max_resource_index_ = max_uu(max_resource_index_, item.value.obj.resource_index);
}
}
for (auto &key : deleted_obj) {
velocity_map.remove(key);
}
indirection_buf.resize(ceil_to_multiple_u(max_resource_index_ + 1, 128));
/* Avoid uploading more data to the GPU as well as an extra level of
* indirection on the GPU by copying back offsets the to VelocityIndex. */
for (VelocityObjectData &vel : velocity_map.values()) {
/* Disable deform if vertex count mismatch. */
if (inst_.is_viewport()) {
/* Current geometry step will be copied at the end of the frame.
* Thus vel.geo.len[STEP_CURRENT] is not yet valid and the current length is manually
* retrieved. */
gpu::VertBuf *pos_buf =
geometry_map.lookup_default(vel.id, VelocityGeometryData()).pos_buf_get();
vel.geo.do_deform = pos_buf != nullptr &&
(vel.geo.len[STEP_PREVIOUS] == GPU_vertbuf_get_vertex_len(pos_buf));
}
else {
vel.geo.do_deform = (vel.geo.len[STEP_CURRENT] != 0) &&
(vel.geo.len[STEP_CURRENT] == vel.geo.len[STEP_PREVIOUS]) &&
(vel.geo.len[STEP_CURRENT] == vel.geo.len[STEP_NEXT]);
}
indirection_buf[vel.obj.resource_index] = vel;
/* Reset for next sync. */
vel.obj.resource_index = uint(-1);
}
object_steps[STEP_PREVIOUS]->push_update();
object_steps[STEP_NEXT]->push_update();
camera_steps[STEP_PREVIOUS]->push_update();
camera_steps[STEP_CURRENT]->push_update();
camera_steps[STEP_NEXT]->push_update();
indirection_buf.push_update();
}
bool VelocityModule::object_has_velocity(const Object *ob)
{
#if 0
RigidBodyOb *rbo = ob->rigidbody_object;
/* Active rigidbody objects only, as only those are affected by sim. */
const bool has_rigidbody = (rbo && (rbo->type == RBO_TYPE_ACTIVE));
/* For now we assume dupli objects are moving. */
const bool is_dupli = (ob->base_flag & BASE_FROM_DUPLI) != 0;
const bool object_moves = is_dupli || has_rigidbody || BKE_object_moves_in_time(ob, true);
#else
UNUSED_VARS(ob);
/* BKE_object_moves_in_time does not work in some cases.
* Better detect non moving object after evaluation. */
const bool object_moves = true;
#endif
return object_moves;
}
bool VelocityModule::object_is_deform(const Object *ob)
{
RigidBodyOb *rbo = ob->rigidbody_object;
/* Active rigidbody objects only, as only those are affected by sim. */
const bool has_rigidbody = (rbo && (rbo->type == RBO_TYPE_ACTIVE));
const bool is_deform = BKE_object_is_deform_modified(inst_.scene, const_cast<Object *>(ob)) ||
(has_rigidbody && (rbo->flag & RBO_FLAG_USE_DEFORM) != 0);
return is_deform;
}
bool VelocityModule::camera_has_motion() const
{
/* Only valid after sync. */
if (inst_.is_viewport()) {
/* Viewport has no next step. */
return *camera_steps[STEP_PREVIOUS] != *camera_steps[STEP_CURRENT];
}
return *camera_steps[STEP_PREVIOUS] != *camera_steps[STEP_CURRENT] &&
*camera_steps[STEP_NEXT] != *camera_steps[STEP_CURRENT];
}
bool VelocityModule::camera_changed_projection() const
{
/* Only valid after sync. */
if (inst_.is_viewport()) {
return camera_steps[STEP_PREVIOUS]->type != camera_steps[STEP_CURRENT]->type;
}
/* Cannot happen in render mode since we set the type during the init phase. */
return false;
}
float VelocityModule::step_time_delta_get(eVelocityStep start, eVelocityStep end) const
{
return step_time[end] - step_time[start];
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,192 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* The velocity pass outputs motion vectors to use for either
* temporal re-projection or motion blur.
*
* It is the module that tracks the objects data between frames updates.
*/
#pragma once
#include <variant>
#include "BLI_map.hh"
#include "DRW_gpu_wrapper.hh"
#include "GPU_batch.hh"
#include "eevee_camera_shared.hh"
#include "eevee_sync.hh"
#include "eevee_velocity_shared.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name VelocityModule
*
* \{ */
using CameraDataBuf = draw::UniformBuffer<CameraData>;
using VelocityGeometryBuf = draw::StorageArrayBuffer<float4, 16, true>;
using VelocityIndexBuf = draw::StorageArrayBuffer<VelocityIndex, 16>;
using VelocityObjectBuf = draw::StorageArrayBuffer<float4x4, 16>;
/** Container for scene velocity data. */
class VelocityModule {
public:
struct VelocityObjectData : public VelocityIndex {
/** ID key to retrieve the corresponding #VelocityGeometryData after copy. */
uint64_t id;
};
struct VelocityGeometryData {
/** VertBuf not yet ready to be copied to the #VelocityGeometryBuf. */
std::variant<std::monostate, gpu::Batch *, gpu::VertBuf *> pos_buf;
/* Offset in the #VelocityGeometryBuf to the start of the data. In vertex. */
int ofs = 0;
/* Length of the vertex buffer. In vertex. */
int len = 0;
gpu::VertBuf *pos_buf_get() const
{
if (std::holds_alternative<gpu::VertBuf *>(this->pos_buf)) {
return std::get<gpu::VertBuf *>(this->pos_buf);
}
if (std::holds_alternative<gpu::Batch *>(this->pos_buf)) {
gpu::VertBuf *buf = std::get<gpu::Batch *>(this->pos_buf)->verts_(1);
if (!buf) {
return nullptr;
}
BLI_assert(STREQ(buf->format.names, "pos"));
return buf;
}
return nullptr;
}
/* Returns true if the data is or **will** be available after the end of sync. */
bool has_data() const
{
if (std::holds_alternative<gpu::VertBuf *>(this->pos_buf)) {
return true;
}
if (std::holds_alternative<gpu::Batch *>(this->pos_buf)) {
return true;
}
return false;
}
};
/**
* The map contains indirection indices to the obmat and geometry in each step buffer.
* Note that each object component gets its own resource id so one component correspond to one
* geometry offset.
*/
Map<ObjectKey, VelocityObjectData> velocity_map;
/** Geometry to be copied to VelocityGeometryBuf. Indexed by evaluated ID hash. Empty after */
Map<uint64_t, VelocityGeometryData> geometry_map;
/** Contains all objects matrices for each time step. */
std::array<VelocityObjectBuf *, 3> object_steps;
/** Contains all Geometry steps from deforming objects for each time step. */
std::array<VelocityGeometryBuf *, 3> geometry_steps;
/** Number of occupied slot in each `object_steps`. */
int3 object_steps_usage = int3(0);
/** Buffer of all #VelocityIndex used in this frame. Indexed by draw manager resource id. */
VelocityIndexBuf indirection_buf;
/** Frame time at which each steps were evaluated. */
float3 step_time;
/**
* Copies of camera data. One for previous and one for next time step.
*/
std::array<CameraDataBuf *, 3> camera_steps;
private:
Instance &inst_;
/** Step being synced. */
eVelocityStep step_ = STEP_CURRENT;
/** Step referenced as next step. */
eVelocityStep next_step_ = STEP_NEXT;
public:
VelocityModule(Instance &inst) : inst_(inst)
{
for (VelocityObjectBuf *&step_buf : object_steps) {
step_buf = new VelocityObjectBuf();
}
for (VelocityGeometryBuf *&step_buf : geometry_steps) {
step_buf = new VelocityGeometryBuf();
}
for (CameraDataBuf *&step_buf : camera_steps) {
step_buf = new CameraDataBuf();
}
};
~VelocityModule()
{
for (VelocityObjectBuf *step_buf : object_steps) {
delete step_buf;
}
for (VelocityGeometryBuf *step_buf : geometry_steps) {
delete step_buf;
}
for (CameraDataBuf *step_buf : camera_steps) {
delete step_buf;
}
}
void init();
void step_camera_sync();
void step_sync(eVelocityStep step, float time);
/* Gather motion data. Returns true if the object **can** have motion. */
bool step_object_sync(const ObjectHandle &ob_handle,
HairParticleInfo const *hair_particle = nullptr);
/**
* In Render, moves the next frame data to previous frame data. Nullify next frame data.
* In Viewport, the current frame data will be used as previous frame data in the next frame.
*/
void step_swap();
void begin_sync();
/** This is the end of the current frame sync. Not the step_sync. */
void end_sync();
template<typename PassType> void bind_resources(PassType &pass)
{
/* Storage Buffer. */
pass.bind_ssbo(VELOCITY_OBJ_PREV_BUF_SLOT, &(*object_steps[STEP_PREVIOUS]));
pass.bind_ssbo(VELOCITY_OBJ_NEXT_BUF_SLOT, &(*object_steps[next_step_]));
pass.bind_ssbo(VELOCITY_GEO_PREV_BUF_SLOT, &(*geometry_steps[STEP_PREVIOUS]));
pass.bind_ssbo(VELOCITY_GEO_NEXT_BUF_SLOT, &(*geometry_steps[next_step_]));
pass.bind_ssbo(VELOCITY_INDIRECTION_BUF_SLOT, &indirection_buf);
/* Uniform Buffer. */
pass.bind_ubo(VELOCITY_CAMERA_PREV_BUF, &(*camera_steps[STEP_PREVIOUS]));
pass.bind_ubo(VELOCITY_CAMERA_CURR_BUF, &(*camera_steps[STEP_CURRENT]));
pass.bind_ubo(VELOCITY_CAMERA_NEXT_BUF, &(*camera_steps[next_step_]));
}
bool camera_has_motion() const;
bool camera_changed_projection() const;
/* Returns frame time difference between two steps. */
float step_time_delta_get(eVelocityStep start, eVelocityStep end) const;
/* Perform VelocityGeometryData offset computation and copy into the geometry step buffer.
* Should be called after all the vertex buffers have been updated by batch cache extraction. */
void geometry_steps_fill();
private:
bool object_has_velocity(const Object *ob);
bool object_is_deform(const Object *ob);
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,61 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
#define VELOCITY_INVALID 512.0
enum [[host_shared]] eVelocityStep : uint32_t {
STEP_PREVIOUS,
STEP_NEXT,
STEP_CURRENT,
};
struct [[host_shared]] VelocityObjectIndex {
/** Offset inside #VelocityObjectBuf for each time-step. Indexed using eVelocityStep. */
packed_int3 ofs;
/** Temporary index to copy this to the #VelocityIndexBuf. */
uint resource_index;
#ifndef GPU_SHADER
VelocityObjectIndex() : ofs(-1, -1, -1), resource_index(-1) {};
#endif
};
struct [[host_shared]] VelocityGeometryIndex {
/** Offset inside #VelocityGeometryBuf for each time-step. Indexed using eVelocityStep. */
packed_int3 ofs;
/** If true, compute deformation motion blur. */
bool32_t do_deform;
/**
* Length of data inside #VelocityGeometryBuf for each time-step.
* Indexed using eVelocityStep.
*/
packed_int3 len;
int _pad0;
#ifndef GPU_SHADER
VelocityGeometryIndex() : ofs(-1, -1, -1), do_deform(false), len(-1, -1, -1), _pad0(1) {};
#endif
};
struct [[host_shared]] VelocityIndex {
struct VelocityObjectIndex obj;
struct VelocityGeometryIndex geo;
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,465 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* A view is either:
* - The entire main view.
* - A fragment of the main view (for panoramic projections).
* - A shadow map view.
* - A light-probe view (either planar, cube-map, irradiance grid).
*
* A pass is a container for scene data. It is view agnostic but has specific logic depending on
* its type. Passes are shared between views.
*/
#include "DRW_render.hh"
#include "GPU_debug.hh"
#include "eevee_instance.hh"
#include "eevee_view.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name ShadingView
* \{ */
void ShadingView::init() {}
void ShadingView::sync()
{
int2 render_extent = inst_.film.render_extent_get();
if (false /* inst_.camera.is_panoramic() */) {
int64_t render_pixel_count = render_extent.x * int64_t(render_extent.y);
/* Divide pixel count between the 6 views. Rendering to a square target. */
extent_[0] = extent_[1] = ceilf(sqrtf(1 + (render_pixel_count / 6)));
/* TODO(@fclem): Clip unused views here. */
is_enabled_ = true;
}
else {
extent_ = render_extent;
/* Only enable -Z view. */
is_enabled_ = (StringRefNull(name_) == "negZ_view");
}
if (!is_enabled_) {
return;
}
/* Create views. */
const CameraData &cam = inst_.camera.data_get();
float4x4 viewmat, winmat;
if (false /* inst_.camera.is_panoramic() */) {
/* TODO(@fclem) Over-scans. */
/* For now a mandatory 5% over-scan for DoF. */
float side = cam.clip_near * 1.05f;
float near = cam.clip_near;
float far = cam.clip_far;
winmat = math::projection::perspective(-side, side, -side, side, near, far);
viewmat = face_matrix_ * cam.viewmat;
}
else {
viewmat = cam.viewmat;
winmat = cam.winmat;
}
main_view_.sync(viewmat, winmat);
inst_.uniform_data.pipeline.is_main_view_inverted = main_view_.is_inverted();
}
void ShadingView::render()
{
if (!is_enabled_) {
return;
}
update_view();
inst_.shadows.set_view(render_view_, extent_);
inst_.volume.set_view(main_view_);
inst_.uniform_data.data.push_update();
/* Need to be set early for planar probe rendering (if using ray-cast node) and ray-cast nodes in
* deferred / forward pipelines. */
inst_.raytracing.thickness_parameters_setup(render_view_.winmat(), extent_);
inst_.uniform_data.raytrace.push_update();
GPU_debug_group_begin(name_);
/* Needs to be before anything else because it query its own gbuffer. */
inst_.planar_probes.set_view(render_view_, extent_);
RenderBuffers &rbufs = inst_.render_buffers;
rbufs.acquire(extent_);
combined_fb_.ensure(GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx),
GPU_ATTACHMENT_TEXTURE(rbufs.combined_tx));
const bool with_raycast = inst_.pipelines.has_raycast;
prepass_fb_.ensure(
GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx),
with_raycast ? GPU_ATTACHMENT_TEXTURE(rbufs.prepass_normal_tx) : GPU_ATTACHMENT_NONE,
with_raycast ? GPU_ATTACHMENT_TEXTURE(rbufs.object_id_tx) : GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE(rbufs.vector_tx));
GBuffer &gbuf = inst_.gbuffer;
gbuf.acquire(extent_,
inst_.pipelines.deferred.header_layer_count(),
inst_.pipelines.deferred.closure_layer_count(),
inst_.pipelines.deferred.normal_layer_count());
gbuffer_fb_.ensure(GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx),
GPU_ATTACHMENT_TEXTURE(rbufs.combined_tx),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.header_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.normal_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.closure_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(gbuf.closure_tx.layer_view(1), 0));
/* If camera has any motion, compute motion vector in the film pass. Otherwise, we avoid float
* precision issue by setting the motion of all static geometry to 0. */
/* TODO: Clear using GPU_framebuffer. */
float4 clear_velocity = float4(inst_.velocity.camera_has_motion() ? VELOCITY_INVALID : 0.0f);
GPU_texture_clear(rbufs.vector_tx, GPU_DATA_FLOAT, &clear_velocity);
if (with_raycast) {
rbufs.object_id_tx.clear(uint4(0));
rbufs.prepass_normal_tx.clear(float4(0.0f));
}
GPU_framebuffer_bind(combined_fb_);
/* Alpha stores transmittance. So start at 1. */
GPU_framebuffer_clear_color_depth(
combined_fb_, {0.0, 0.0, 0.0, 1.0}, inst_.film.depth.clear_value);
inst_.pipelines.background.clear(render_view_);
/* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */
inst_.lights.set_view(render_view_, extent_);
inst_.hiz_buffer.set_source(&inst_.render_buffers.depth_tx);
inst_.volume.draw_prepass(main_view_);
inst_.pipelines.deferred.render(main_view_,
render_view_,
prepass_fb_,
combined_fb_,
gbuffer_fb_,
extent_,
rt_buffer_opaque_,
rt_buffer_refract_);
inst_.pipelines.background.render(render_view_, combined_fb_);
inst_.gbuffer.release();
inst_.volume.draw_compute(main_view_, extent_);
inst_.volume.draw_resolve(main_view_);
inst_.ambient_occlusion.render_pass(render_view_);
inst_.pipelines.forward.render(
render_view_, rbufs.depth_tx, prepass_fb_, transparent_fb_, combined_fb_, extent_);
inst_.lights.shape_display_draw(render_view_, combined_fb_);
inst_.lights.debug_draw(render_view_, combined_fb_);
inst_.hiz_buffer.debug_draw(render_view_, combined_fb_);
inst_.shadows.debug_draw(render_view_, combined_fb_);
inst_.volume_probes.viewport_draw(render_view_, combined_fb_);
inst_.sphere_probes.viewport_draw(render_view_, combined_fb_);
inst_.planar_probes.viewport_draw(render_view_, combined_fb_);
gpu::Texture *combined_final_tx = render_postfx(rbufs.combined_tx);
inst_.film.accumulate(jitter_view_, combined_final_tx);
rbufs.release();
postfx_tx_.release();
GPU_debug_group_end();
}
gpu::Texture *ShadingView::render_postfx(gpu::Texture *input_tx)
{
if (!inst_.depth_of_field.postfx_enabled() && !inst_.motion_blur.postfx_enabled()) {
return input_tx;
}
postfx_tx_.acquire_2d(extent_, gpu::TextureFormat::SFLOAT_16_16_16_16);
/* Fix a sync bug on AMD + Mesa when volume + motion blur create artifacts
* except if there is a clear event between them. */
if (inst_.volume.enabled() && inst_.motion_blur.postfx_enabled() &&
!inst_.depth_of_field.postfx_enabled() &&
GPU_type_matches_ex(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OFFICIAL, GPU_BACKEND_OPENGL))
{
postfx_tx_.clear(float4(0.0f));
}
gpu::Texture *output_tx = postfx_tx_;
/* Swapping is done internally. Actual output is set to the next input. */
inst_.motion_blur.render(render_view_, &input_tx, &output_tx);
inst_.depth_of_field.render(render_view_, &input_tx, &output_tx, dof_buffer_);
return input_tx;
}
void ShadingView::update_view()
{
const Film &film = inst_.film;
float4x4 viewmat = main_view_.viewmat();
float4x4 winmat = main_view_.winmat();
if (film.scaling_factor_get() > 1) {
/* This whole section ensures that the render target pixel grid will match the film pixel grid.
* Otherwise the weight computation inside the film accumulation will be wrong. */
float left, right, bottom, top, near, far;
projmat_dimensions(winmat.ptr(), &left, &right, &bottom, &top, &near, &far);
const float2 bottom_left_with_overscan = float2(left, bottom);
const float2 top_right_with_overscan = float2(right, top);
const float2 render_size_with_overscan = top_right_with_overscan - bottom_left_with_overscan;
float2 bottom_left = bottom_left_with_overscan;
float2 top_right = top_right_with_overscan;
float2 render_size = render_size_with_overscan;
float overscan = inst_.camera.overscan();
if (overscan > 0.0f) {
/* Size of overscan on the screen. */
const float max_size_with_overscan = math::reduce_max(render_size);
const float max_size_original = max_size_with_overscan / (1.0f + 2.0f * overscan);
const float overscan_size = (max_size_with_overscan - max_size_original) / 2.0f;
/* Undo overscan to get the initial dimension of the screen. */
bottom_left = bottom_left_with_overscan + overscan_size;
top_right = top_right_with_overscan - overscan_size;
/* Render target size on the screen (without overscan). */
render_size = top_right - bottom_left;
}
/* Final pixel size on the screen. */
const float2 pixel_size = render_size / float2(film.film_extent_get());
/* Render extent in final film pixel unit. */
const int2 render_extent = film.render_extent_get() * film.scaling_factor_get();
const int overscan_pixels = film.render_overscan_get() * film.scaling_factor_get();
const float2 render_bottom_left = bottom_left - pixel_size * float(overscan_pixels);
const float2 render_top_right = render_bottom_left + pixel_size * float2(render_extent);
if (main_view_.is_persp()) {
winmat = math::projection::perspective(render_bottom_left.x,
render_top_right.x,
render_bottom_left.y,
render_top_right.y,
near,
far);
}
else {
winmat = math::projection::orthographic(render_bottom_left.x,
render_top_right.x,
render_bottom_left.y,
render_top_right.y,
near,
far);
}
}
/* Anti-Aliasing / Super-Sampling jitter. */
float2 jitter = inst_.film.pixel_jitter_get() / float2(extent_);
/* Transform to NDC space. */
jitter *= 2.0f;
window_translate_m4(winmat.ptr(), winmat.ptr(), UNPACK2(jitter));
jitter_view_.sync(viewmat, winmat);
/* FIXME(fclem): The offset may be noticeably large and the culling might make object pop
* out of the blurring radius. To fix this, use custom enlarged culling matrix. */
inst_.depth_of_field.jitter_apply(winmat, viewmat);
render_view_.sync(viewmat, winmat);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture View
* \{ */
void CaptureView::render_world()
{
const auto update_info = inst_.sphere_probes.world_update_info_pop();
if (!update_info.has_value()) {
return;
}
View view = {"Capture.View"};
GPU_debug_group_begin("World.Capture");
if (update_info->do_render) {
auto render_cubemap = [&](RayPipelineType ray_type) {
if (assign_if_different(inst_.pipelines.data.ray_type, ray_type)) {
inst_.uniform_data.pipeline.push_update();
}
for (int face : IndexRange(6)) {
float4x4 view_m4 = cubeface_mat(face);
float4x4 win_m4 = math::projection::perspective(-update_info->clipping_distances.x,
update_info->clipping_distances.x,
-update_info->clipping_distances.x,
update_info->clipping_distances.x,
update_info->clipping_distances.x,
update_info->clipping_distances.y);
view.sync(view_m4, win_m4);
combined_fb_.ensure(
GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.sphere_probes.cubemap_tx_, face));
GPU_framebuffer_bind(combined_fb_);
inst_.pipelines.world.render(view);
}
};
if (inst_.pipelines.world.use_lightpath_node()) {
render_cubemap(RAY_TYPE_DIFFUSE);
inst_.sphere_probes.remap_to_octahedral_projection(
update_info->atlas_coord, false, true, WORLD_SUN_DIFFUSE);
render_cubemap(RAY_TYPE_GLOSSY);
inst_.sphere_probes.remap_to_octahedral_projection(
update_info->atlas_coord, true, false, WORLD_SUN_GLOSSY);
}
else {
render_cubemap(RAY_TYPE_GLOSSY);
inst_.sphere_probes.remap_to_octahedral_projection(
update_info->atlas_coord, true, true, WORLD_SUN_COMBINED);
}
/* All volume probe that needs to composite the world probe need to be updated. */
inst_.volume_probes.update_world_irradiance();
}
if (assign_if_different(inst_.pipelines.data.ray_type, RAY_TYPE_CAMERA)) {
inst_.uniform_data.pipeline.push_update();
}
GPU_debug_group_end();
}
void CaptureView::render_probes()
{
Framebuffer prepass_fb;
View view = {"Capture.View"};
/* Any 90 degree FOV view will do it. */
float4x4 win_m4 = math::projection::perspective(-0.1f, 0.1f, -0.1f, 0.1f, 0.1f, 10.0f);
/* Check if uniform_data needs to be updated. */
int prev_extent = 0;
while (const auto update_info = inst_.sphere_probes.probe_update_info_pop()) {
GPU_debug_group_begin("Probe.Capture");
if (assign_if_different(inst_.pipelines.data.ray_type, RAY_TYPE_GLOSSY)) {
inst_.uniform_data.pipeline.push_update();
}
if (prev_extent != update_info->cube_target_extent) {
/* Set correct thickness for raycast node in probe pipelines. */
inst_.raytracing.thickness_parameters_setup(win_m4, int2(update_info->cube_target_extent));
inst_.uniform_data.raytrace.push_update();
}
prev_extent = update_info->cube_target_extent;
int2 extent = int2(update_info->cube_target_extent);
RenderBuffers &rbufs = inst_.render_buffers;
rbufs.acquire(extent);
const bool with_raycast = inst_.pipelines.has_raycast;
prepass_fb.ensure(
GPU_ATTACHMENT_TEXTURE(rbufs.depth_tx),
with_raycast ? GPU_ATTACHMENT_TEXTURE(rbufs.prepass_normal_tx) : GPU_ATTACHMENT_NONE,
with_raycast ? GPU_ATTACHMENT_TEXTURE(rbufs.object_id_tx) : GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_NONE /* Motion vectors not supported. */);
inst_.gbuffer.acquire(extent,
inst_.pipelines.probe.header_layer_count(),
inst_.pipelines.probe.closure_layer_count(),
inst_.pipelines.probe.normal_layer_count());
for (int face : IndexRange(6)) {
float4x4 view_m4 = cubeface_mat(face);
view_m4 = math::translate(view_m4, -update_info->probe_pos);
float4x4 win_m4 = math::projection::perspective(-update_info->clipping_distances.x,
update_info->clipping_distances.x,
-update_info->clipping_distances.x,
update_info->clipping_distances.x,
update_info->clipping_distances.x,
update_info->clipping_distances.y);
view.sync(view_m4, win_m4);
inst_.shadows.set_view(view, extent);
inst_.volume.set_view(view);
inst_.uniform_data.data.push_update();
combined_fb_.ensure(GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.sphere_probes.cubemap_tx_, face));
gbuffer_fb_.ensure(GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.depth_tx),
GPU_ATTACHMENT_TEXTURE_CUBEFACE(inst_.sphere_probes.cubemap_tx_, face),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.header_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.normal_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(0), 0),
GPU_ATTACHMENT_TEXTURE_LAYER(inst_.gbuffer.closure_tx.layer_view(1), 0));
/* TODO: Clear from gbuffer.bind ? */
GPU_framebuffer_bind(combined_fb_);
/* Alpha stores transmittance. So start at 1. */
GPU_framebuffer_clear_color(combined_fb_, {0.0, 0.0, 0.0, 1.0});
inst_.pipelines.probe.render(view, prepass_fb, combined_fb_, gbuffer_fb_, extent);
}
inst_.render_buffers.release();
inst_.gbuffer.release();
GPU_debug_group_end();
inst_.sphere_probes.remap_to_octahedral_projection(update_info->atlas_coord, true, false);
}
if (assign_if_different(inst_.pipelines.data.ray_type, RAY_TYPE_CAMERA)) {
inst_.uniform_data.pipeline.push_update();
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lookdev View
* \{ */
void LookdevView::render()
{
if (!inst_.lookdev.use_reference_spheres_) {
return;
}
GPU_debug_group_begin("Lookdev");
const float radius = inst_.lookdev.sphere_radius_;
const float clip = inst_.camera.data_get().clip_near;
const float4x4 win_m4 = math::projection::orthographic_infinite(
-radius, radius, -radius, radius, clip);
const float4x4 &view_m4 = inst_.camera.data_get().viewmat;
view_.sync(view_m4, win_m4);
inst_.lookdev.draw(view_);
inst_.lookdev.display();
GPU_debug_group_end();
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,189 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*
* A view is either:
* - The entire main view.
* - A portion of the main view (for panoramic projections).
* - A light-probe view (either planar, cube-map, irradiance grid).
*
* A pass is a container for scene data. It is view agnostic but has specific logic depending on
* its type. Passes are shared between views.
*/
#pragma once
#include "DRW_render.hh"
#include "eevee_camera.hh"
#include "eevee_depth_of_field.hh"
#include "eevee_pipeline.hh"
#include "eevee_renderbuffers.hh"
#include "eevee_shader.hh"
#include "eevee_velocity.hh"
namespace blender::eevee {
class Instance;
/* -------------------------------------------------------------------- */
/** \name ShadingView
*
* Render the scene and fill all render passes data.
* \{ */
class ShadingView {
private:
Instance &inst_;
/** Static string pointer. Used as debug name and as UUID for texture pool. */
const char *name_;
/** Matrix to apply to the viewmat. */
const float4x4 &face_matrix_;
/** Ray-tracing persistent buffers. Only opaque and refraction can have surface tracing. */
RayTraceBuffer rt_buffer_opaque_;
RayTraceBuffer rt_buffer_refract_;
DepthOfFieldBuffer dof_buffer_;
Framebuffer prepass_fb_ = {"prepass_fb_"};
Framebuffer combined_fb_ = {"combined_fb_"};
Framebuffer gbuffer_fb_ = {"gbuffer_fb_"};
Framebuffer transparent_fb_ = {"transparent"};
TextureFromPool postfx_tx_;
/** Main views is created from the camera (or is from the viewport). It is not jittered. */
View main_view_ = {"main_view"};
/** Sub views is jittered versions or the main views. This allows jitter updates without trashing
* the visibility culling cache. */
View jitter_view_ = {"jitter_view"};
/** Same as jitter_view_ but has Depth Of Field jitter applied. */
View render_view_;
/** Render size of the view. Can change between scene sample eval. */
int2 extent_ = {-1, -1};
bool is_enabled_ = false;
public:
ShadingView(Instance &inst, const char *name, const float4x4 &face_matrix)
: inst_(inst), name_(name), face_matrix_(face_matrix), render_view_(name) {};
~ShadingView() {};
void init();
void sync();
void render();
private:
void render_transparent_pass(RenderBuffers &rbufs);
gpu::Texture *render_postfx(gpu::Texture *input_tx);
void update_view();
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Main View
*
* Container for all views needed to render the final image.
* We might need up to 6 views for panoramic cameras.
* All views are always available but only enabled for if needed.
* \{ */
class MainView {
private:
/* WORKAROUND: Defining this as an array does not seems to work on GCC < 9.4.
* It tries to use the copy constructor and fails because ShadingView is non-copyable and
* non-movable. */
ShadingView shading_views_0;
ShadingView shading_views_1;
ShadingView shading_views_2;
ShadingView shading_views_3;
ShadingView shading_views_4;
ShadingView shading_views_5;
#define shading_views_ (&shading_views_0)
public:
MainView(Instance &inst)
: shading_views_0(inst, "posX_view", cubeface_mat(0)),
shading_views_1(inst, "negX_view", cubeface_mat(1)),
shading_views_2(inst, "posY_view", cubeface_mat(2)),
shading_views_3(inst, "negY_view", cubeface_mat(3)),
shading_views_4(inst, "posZ_view", cubeface_mat(4)),
shading_views_5(inst, "negZ_view", cubeface_mat(5))
{
}
void init()
{
for (auto i : IndexRange(6)) {
shading_views_[i].init();
}
}
void sync()
{
for (auto i : IndexRange(6)) {
shading_views_[i].sync();
}
}
void render()
{
for (auto i : IndexRange(6)) {
shading_views_[i].render();
}
}
#undef shading_views_
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture View
*
* View for capturing cube-map renders outside a ShadingView.
* \{ */
class CaptureView {
private:
Instance &inst_;
Framebuffer combined_fb_ = {"Capture.Combined"};
Framebuffer gbuffer_fb_ = {"Capture.Gbuffer"};
public:
CaptureView(Instance &inst) : inst_(inst) {}
void render_world();
void render_probes();
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lookdev View
*
* View for rendering the lookdev HDRI spheres.
* \{ */
class LookdevView {
private:
Instance &inst_;
View view_ = {"Lookdev.View"};
public:
LookdevView(Instance &inst) : inst_(inst) {}
void render();
};
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,498 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*
* Volumetric effects rendering using Frostbite's Physically-based & Unified Volumetric Rendering
* approach.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite
*/
#include "GPU_capabilities.hh"
#include "GPU_debug.hh"
#include "eevee_instance.hh"
#include "eevee_pipeline.hh"
#include "eevee_volume.hh"
namespace blender::eevee {
void VolumeModule::init()
{
enabled_ = false;
const Scene *scene_eval = inst_.scene;
const int2 extent = inst_.film.render_extent_get();
int tile_size = clamp_i(scene_eval->eevee.volumetric_tile_size, 1, 16);
int3 tex_size;
/* Try to match resolution setting but fall back to lower resolution
* if it doesn't fit the hardware limits. */
for (; tile_size <= 16; tile_size *= 2) {
/* Find Froxel Texture resolution. */
tex_size = int3(math::divide_ceil(extent, int2(tile_size)), 0);
tex_size.z = std::max(1, scene_eval->eevee.volumetric_samples);
if (math::reduce_max(tex_size) < GPU_max_texture_3d_size()) {
/* Fits hardware limits. */
break;
}
}
if (tile_size != scene_eval->eevee.volumetric_tile_size) {
inst_.info_append_i18n(
"Warning: Volume rendering data could not be allocated. Now using a resolution of 1:{} "
"instead of 1:{}.",
tile_size,
scene_eval->eevee.volumetric_tile_size);
}
data_.tile_size = tile_size;
data_.tile_size_lod = int(log2(tile_size));
data_.coord_scale = float2(extent) / float2(tile_size * tex_size);
data_.main_view_extent = float2(extent);
data_.main_view_extent_inv = 1.0f / float2(extent);
data_.tex_size = tex_size;
data_.inv_tex_size = 1.0f / float3(tex_size);
const bool shadow_enabled = (scene_eval->eevee.flag & SCE_EEVEE_VOLUMETRIC_SHADOWS) != 0;
data_.shadow_steps = (shadow_enabled) ? scene_eval->eevee.volumetric_shadow_samples : 0;
data_.light_clamp = scene_eval->eevee.volumetric_light_clamp;
data_.light_clamp = (data_.light_clamp > 0.0) ? data_.light_clamp : 1e20;
use_reprojection_ = (scene_eval->eevee.flag & SCE_EEVEE_TAA_REPROJECTION) != 0;
}
void VolumeModule::begin_sync()
{
previous_objects_ = current_objects_;
current_objects_.clear();
}
void VolumeModule::world_sync(const WorldHandle &world_handle)
{
if (!use_reprojection_) {
return;
}
if (world_handle.recalc && !inst_.is_playback) {
valid_history_ = false;
}
}
void VolumeModule::object_sync(const ObjectHandle &ob_handle)
{
for (int i : IndexRange(ob_handle.instances_count())) {
current_objects_.add(ObjectKey(ob_handle, i));
}
if (!use_reprojection_) {
return;
}
if (ob_handle.recalc && !inst_.is_playback) {
valid_history_ = false;
}
}
bool VolumeModule::will_enable() const
{
return inst_.world.has_volume() || !current_objects_.is_empty() ||
inst_.film.get_data().volume_light_id != -1;
}
void VolumeModule::end_sync()
{
enabled_ = will_enable();
/* Save reset state. */
viewport_sampling_is_reset_ = inst_.is_viewport() && inst_.sampling.is_reset();
const Scene *scene_eval = inst_.scene;
const bool custom_range = scene_eval->eevee.flag & SCE_EEVEE_VOLUME_CUSTOM_RANGE;
const float camera_clip_start = inst_.camera.data_get().clip_near;
const float camera_clip_end = inst_.camera.data_get().clip_far;
float integration_start = custom_range ? scene_eval->eevee.volumetric_start : camera_clip_start;
float integration_end = custom_range ? scene_eval->eevee.volumetric_end : camera_clip_end;
if (!inst_.camera.is_camera_object() && inst_.camera.is_orthographic()) {
integration_start = -integration_end;
}
std::optional<Bounds<float>> volume_bounds = inst_.pipelines.volume.object_integration_range();
if (volume_bounds && !inst_.world.has_volume()) {
/* Restrict integration range to the object volume range. This increases precision. */
integration_start = math::max(integration_start, -volume_bounds.value().max);
integration_end = math::min(integration_end, -volume_bounds.value().min);
}
/* Negate clip values (View matrix forward vector is -Z). */
float near = -math::max(integration_start, camera_clip_start - 1e-4f);
float far = -math::min(integration_end, camera_clip_end + 1e-4f);
if (assign_if_different(history_camera_is_perspective_, inst_.camera.is_perspective())) {
/* Currently, the re-projection uses the same path for volume_z_to_view_z conversion for both
* the current view and the history view. Moreover, re-projecting in this huge change is more
* detrimental than anything. */
valid_history_ = false;
}
if (valid_history_) {
/* Avoid the (potentially expensive) check if valid_history_ is already false. */
if (current_objects_ != previous_objects_) {
valid_history_ = false;
}
}
if (inst_.camera.is_perspective()) {
float sample_distribution = scene_eval->eevee.volumetric_sample_distribution;
sample_distribution = 4.0f * math::max(1.0f - sample_distribution, 1e-2f);
data_.depth_near = (far - near * exp2(1.0f / sample_distribution)) / (far - near);
data_.depth_far = (1.0f - data_.depth_near) / near;
data_.depth_distribution = sample_distribution;
}
else {
data_.depth_near = near;
data_.depth_far = far;
data_.depth_distribution = 0.0f; /* Unused. */
}
if (!enabled_) {
occupancy_tx_.free();
prop_scattering_tx_.free();
prop_extinction_tx_.free();
prop_emission_tx_.free();
prop_phase_tx_.free();
prop_phase_weight_tx_.free();
scatter_tx_.current().free();
scatter_tx_.previous().free();
extinction_tx_.current().free();
extinction_tx_.previous().free();
integrated_scatter_tx_.free();
integrated_transmit_tx_.free();
/* Update references for bindings. */
result.scattering_tx_ = dummy_scatter_tx_;
result.transmittance_tx_ = dummy_transmit_tx_;
/* These shouldn't be used. */
properties.scattering_tx_ = nullptr;
properties.extinction_tx_ = nullptr;
properties.emission_tx_ = nullptr;
properties.phase_tx_ = nullptr;
properties.phase_weight_tx_ = nullptr;
properties.occupancy_tx_ = nullptr;
occupancy.occupancy_tx_ = nullptr;
occupancy.hit_depth_tx_ = nullptr;
occupancy.hit_count_tx_ = nullptr;
/* Avoid undefined re-projection behavior. */
valid_history_ = false;
return;
}
bool has_scatter = inst_.world.has_volume_scatter() || inst_.pipelines.volume.has_scatter();
bool has_absorption = inst_.world.has_volume_absorption() ||
inst_.pipelines.volume.has_absorption();
use_lights_ = has_scatter;
/* TODO(fclem): Allocate extinction texture as dummy (1px^3) if has_absorption are false. */
UNUSED_VARS(has_absorption);
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE |
GPU_TEXTURE_USAGE_ATTACHMENT;
prop_scattering_tx_.ensure_3d(gpu::TextureFormat::UFLOAT_11_11_10, data_.tex_size, usage);
prop_extinction_tx_.ensure_3d(gpu::TextureFormat::UFLOAT_11_11_10, data_.tex_size, usage);
prop_emission_tx_.ensure_3d(gpu::TextureFormat::UFLOAT_11_11_10, data_.tex_size, usage);
/* We need 2 separate images to prevent bugs in Nvidia drivers (See #122454). */
prop_phase_tx_.ensure_3d(gpu::TextureFormat::SFLOAT_16, data_.tex_size, usage);
prop_phase_weight_tx_.ensure_3d(gpu::TextureFormat::SFLOAT_16, data_.tex_size, usage);
int occupancy_layers = divide_ceil_u(data_.tex_size.z, 32u);
eGPUTextureUsage occupancy_usage = GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC;
occupancy_tx_.ensure_3d(
gpu::TextureFormat::UINT_32, int3(data_.tex_size.xy(), occupancy_layers), occupancy_usage);
{
eGPUTextureUsage hit_count_usage = GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC;
eGPUTextureUsage hit_depth_usage = GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_SHADER_WRITE;
int2 hit_list_size = int2(1);
int hit_list_layer = 1;
if (inst_.pipelines.volume.use_hit_list()) {
hit_list_layer = clamp_i(inst_.scene->eevee.volumetric_ray_depth, 1, 16);
hit_list_size = data_.tex_size.xy();
}
hit_depth_tx_.ensure_3d(
gpu::TextureFormat::SFLOAT_32, int3(hit_list_size, hit_list_layer), hit_depth_usage);
if (hit_count_tx_.ensure_2d(gpu::TextureFormat::UINT_32, hit_list_size, hit_count_usage)) {
hit_count_tx_.clear(uint4(0u));
}
}
eGPUTextureUsage front_depth_usage = GPU_TEXTURE_USAGE_SHADER_READ |
GPU_TEXTURE_USAGE_ATTACHMENT;
front_depth_tx_.ensure_2d(
gpu::TextureFormat::SFLOAT_32_DEPTH_UINT_8, data_.tex_size.xy(), front_depth_usage);
occupancy_fb_.ensure(GPU_ATTACHMENT_TEXTURE(front_depth_tx_));
bool created = false;
created |= scatter_tx_.current().ensure_3d(
gpu::TextureFormat::UFLOAT_11_11_10, data_.tex_size, usage);
created |= extinction_tx_.current().ensure_3d(
gpu::TextureFormat::UFLOAT_11_11_10, data_.tex_size, usage);
created |= scatter_tx_.previous().ensure_3d(
gpu::TextureFormat::UFLOAT_11_11_10, data_.tex_size, usage);
created |= extinction_tx_.previous().ensure_3d(
gpu::TextureFormat::UFLOAT_11_11_10, data_.tex_size, usage);
if (created) {
valid_history_ = false;
}
integrated_scatter_tx_.ensure_3d(gpu::TextureFormat::UFLOAT_11_11_10, data_.tex_size, usage);
integrated_transmit_tx_.ensure_3d(gpu::TextureFormat::UFLOAT_11_11_10, data_.tex_size, usage);
/* Update references for bindings. */
result.scattering_tx_ = integrated_scatter_tx_;
result.transmittance_tx_ = integrated_transmit_tx_;
properties.scattering_tx_ = prop_scattering_tx_;
properties.extinction_tx_ = prop_extinction_tx_;
properties.emission_tx_ = prop_emission_tx_;
properties.phase_tx_ = prop_phase_tx_;
properties.phase_weight_tx_ = prop_phase_weight_tx_;
properties.occupancy_tx_ = occupancy_tx_;
occupancy.occupancy_tx_ = occupancy_tx_;
occupancy.hit_depth_tx_ = hit_depth_tx_;
occupancy.hit_count_tx_ = hit_count_tx_;
/* Set extend mode to extend and reject invalid samples in the shader.
* This avoids some black rim artifacts near the edge of the re-projected volume.
* Filter linear to avoid sharp artifacts during re-projection. */
const GPUSamplerState history_sampler = {GPU_SAMPLER_FILTERING_LINEAR,
GPU_SAMPLER_EXTEND_MODE_EXTEND,
GPU_SAMPLER_EXTEND_MODE_EXTEND};
scatter_ps_.init();
scatter_ps_.shader_set(
inst_.shaders.static_shader_get(use_lights_ ? VOLUME_SCATTER_WITH_LIGHTS : VOLUME_SCATTER));
scatter_ps_.bind_resources(inst_.hiz_buffer.front);
scatter_ps_.bind_resources(inst_.lights);
scatter_ps_.bind_resources(inst_.sphere_probes);
scatter_ps_.bind_resources(inst_.volume_probes);
scatter_ps_.bind_resources(inst_.shadows);
scatter_ps_.bind_resources(inst_.uniform_data);
scatter_ps_.bind_resources(inst_.sampling);
scatter_ps_.bind_image("in_scattering_img", &prop_scattering_tx_);
scatter_ps_.bind_image("in_extinction_img", &prop_extinction_tx_);
scatter_ps_.bind_texture("extinction_tx", &prop_extinction_tx_);
scatter_ps_.bind_image("in_emission_img", &prop_emission_tx_);
scatter_ps_.bind_image("in_phase_img", &prop_phase_tx_);
scatter_ps_.bind_image("in_phase_weight_img", &prop_phase_weight_tx_);
scatter_ps_.bind_texture("scattering_history_tx", &scatter_tx_.previous(), history_sampler);
scatter_ps_.bind_texture("extinction_history_tx", &extinction_tx_.previous(), history_sampler);
scatter_ps_.bind_image("out_scattering_img", &scatter_tx_.current());
scatter_ps_.bind_image("out_extinction_img", &extinction_tx_.current());
scatter_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
/* Sync with the property pass. */
scatter_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
scatter_ps_.dispatch(math::divide_ceil(data_.tex_size, int3(VOLUME_GROUP_SIZE)));
integration_ps_.init();
integration_ps_.shader_set(inst_.shaders.static_shader_get(VOLUME_INTEGRATION));
integration_ps_.bind_resources(inst_.uniform_data);
integration_ps_.bind_resources(inst_.sampling);
integration_ps_.bind_texture("in_scattering_tx", &scatter_tx_.current());
integration_ps_.bind_texture("in_extinction_tx", &extinction_tx_.current());
integration_ps_.bind_image("out_scattering_img", &integrated_scatter_tx_);
integration_ps_.bind_image("out_transmittance_img", &integrated_transmit_tx_);
/* Sync with the scatter pass. */
integration_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH);
integration_ps_.dispatch(
math::divide_ceil(int2(data_.tex_size), int2(VOLUME_INTEGRATION_GROUP_SIZE)));
resolve_ps_.init();
resolve_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM);
resolve_ps_.shader_set(inst_.shaders.static_shader_get(VOLUME_RESOLVE));
resolve_ps_.bind_resources(inst_.uniform_data);
resolve_ps_.bind_resources(this->result);
resolve_ps_.bind_resources(inst_.hiz_buffer.front);
resolve_ps_.bind_image(RBUFS_COLOR_SLOT, &inst_.render_buffers.rp_color_tx);
resolve_ps_.bind_image(RBUFS_VALUE_SLOT, &inst_.render_buffers.rp_value_tx);
/* Sync with the integration pass. */
resolve_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH);
resolve_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
void VolumeModule::set_view(View &main_view)
{
/* Number of frame to consider for blending with exponential (infinite) average. */
int exponential_frame_count = 16;
if (inst_.is_image_render) {
/* Disable reprojection for rendering. */
exponential_frame_count = 0;
}
else if (!use_reprojection_) {
/* No re-projection if TAA is disabled. */
exponential_frame_count = 0;
}
else if (inst_.is_playback) {
/* For now, we assume we want responsiveness for volume animation.
* But this makes general animation inside uniform volumes less stable.
* When we introduce updated volume tagging, we will be able to increase this for general
* playback. */
exponential_frame_count = 3;
}
else if (inst_.is_transforming) {
/* Improve responsiveness of volume if we are transforming objects. */
/* TODO(fclem): This is too general as it will be triggered even for non volume object.
* Instead, we should tag which areas of the volume that need increased responsiveness. */
exponential_frame_count = 3;
}
else if (inst_.is_navigating) {
/* Navigation is usually smooth because of the re-projection but we can get ghosting
* artifacts on lights because of voxels stretched in Z or anisotropy. */
exponential_frame_count = 8;
}
else if (viewport_sampling_is_reset_) {
/* If we are not falling in any cases above, this usually means there is a scene or object
* parameter update. Reset accumulation completely. */
exponential_frame_count = 0;
}
if (!valid_history_) {
history_frame_count_ = 0;
}
/* Interactive mode accumulate samples using exponential average.
* We still reuse the history when we go into static mode.
* However, using re-projection for static mode will show the precision limit of RG11B10 format.
* So we clamp it to the exponential frame count in any case. */
history_frame_count_ = math::min(history_frame_count_, exponential_frame_count);
/* In interactive mode, use exponential average (fixed ratio).
* For static / render mode use simple average (moving ratio). */
float history_opacity = history_frame_count_ / (history_frame_count_ + 1.0f);
/* Setting opacity to 0.0 will bypass any sampling of history buffer.
* Allowing us to skip the 3D texture clear. */
data_.history_opacity = (valid_history_) ? history_opacity : 0.0f;
float left, right, bottom, top, near, far;
projmat_dimensions(main_view.winmat().ptr(), &left, &right, &bottom, &top, &near, &far);
/* Just like up-sampling matrix computation, we have to be careful to where to put the bounds of
* our froxel volume so that a 2D pixel covers exactly the number of pixel in a tile. */
float2 render_size = float2(right - left, top - bottom);
float2 volume_size = render_size * float2(data_.tex_size.xy() * data_.tile_size) /
float2(inst_.film.render_extent_get());
/* Change to the padded extends. */
right = left + volume_size.x;
top = bottom + volume_size.y;
float4x4 winmat_infinite, winmat_finite;
/* Create an infinite projection matrix to avoid far clipping plane clipping the object. This
* way, surfaces that are further away than the far clip plane will still be voxelized. */
winmat_infinite = main_view.is_persp() ?
math::projection::perspective_infinite(left, right, bottom, top, near) :
math::projection::orthographic_infinite(left, right, bottom, top, near);
/* We still need a bounded projection matrix to get correct froxel location. */
winmat_finite = main_view.is_persp() ?
math::projection::perspective(left, right, bottom, top, near, far) :
math::projection::orthographic(left, right, bottom, top, near, far);
/* Save non-jittered finite matrix for re-projection. */
data_.winmat_stable = winmat_finite;
data_.wininv_stable = math::invert(winmat_finite);
/* Anti-Aliasing / Super-Sampling jitter. */
float2 jitter = inst_.sampling.rng_2d_get(SAMPLING_VOLUME_U);
/* Wrap to keep first sample centered (0,0) and scale to convert to NDC. */
jitter = math::fract(jitter + 0.5f) * 2.0f - 1.0f;
/* Convert to pixel size. */
jitter *= data_.inv_tex_size.xy();
/* Apply jitter to both matrices. */
winmat_infinite = math::projection::translate(winmat_infinite, jitter);
winmat_finite = math::projection::translate(winmat_finite, jitter);
data_.winmat_finite = winmat_finite;
data_.wininv_finite = math::invert(winmat_finite);
/* Compute re-projection matrix. */
data_.curr_view_to_past_view = history_viewmat_ * main_view.viewinv();
volume_view.sync(main_view.viewmat(), winmat_infinite);
}
void VolumeModule::draw_prepass(View &main_view)
{
if (!enabled_) {
return;
}
GPU_debug_group_begin("Volumes");
occupancy_fb_.bind();
inst_.pipelines.world_volume.render(main_view);
/* TODO(fclem): The infinite projection matrix makes the culling test unreliable (see #115595).
* We need custom culling for these but that's not implemented yet. */
volume_view.visibility_test(false);
if (!current_objects_.is_empty()) {
inst_.pipelines.volume.render(volume_view, occupancy_tx_);
}
GPU_debug_group_end();
}
void VolumeModule::draw_compute(View &main_view, int2 extent)
{
if (!enabled_) {
return;
}
if (inst_.pipelines.deferred.is_empty()) {
/* This assume the volume are computed after deferred passes. This is needed to avoid broken
* lighting and shadowing as the lights are not setup otherwise (see #121971). */
inst_.hiz_buffer.swap_layer();
inst_.hiz_buffer.update();
inst_.volume_probes.set_view(main_view);
inst_.sphere_probes.set_view(main_view);
inst_.shadows.render(main_view, extent);
}
scatter_tx_.swap();
extinction_tx_.swap();
inst_.manager->submit(scatter_ps_, main_view);
inst_.manager->submit(integration_ps_, main_view);
/* Copy history data. */
history_viewmat_ = main_view.viewmat();
data_.history_depth_near = data_.depth_near;
data_.history_depth_far = data_.depth_far;
data_.history_depth_distribution = data_.depth_distribution;
data_.history_winmat_stable = data_.winmat_stable;
valid_history_ = true;
history_frame_count_ += 1;
}
void VolumeModule::draw_resolve(View &view)
{
if (!enabled_) {
return;
}
inst_.hiz_buffer.update();
resolve_fb_.ensure(GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.combined_tx));
resolve_fb_.bind();
inst_.manager->submit(resolve_ps_, view);
}
} // namespace blender::eevee

View File

@@ -0,0 +1,229 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*
* Volumetric effects rendering using Frostbite's Physically-based & Unified Volumetric Rendering
* approach.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite
*
* The rendering is separated in 4 stages:
*
* - Material Parameters : we collect volume properties of
* all participating media in the scene and store them in
* a 3D texture aligned with the 3D frustum.
* This is done in 2 passes, one that clear the texture
* and/or evaluate the world volumes, and the 2nd one that
* additively render object volumes.
*
* - Light Scattering : the volume properties then are sampled
* and light scattering is evaluated for each froxel of the
* volume texture. Temporal super-sampling (if enabled) occurs here.
*
* - Volume Integration : the scattered light and extinction is
* integrated (accumulated) along the view-rays. The result is stored
* for every froxel in another texture.
*
* - Full-screen Resolve : From the previous stage, we get two
* 3D textures that contains integrated scattered light and extinction
* for "every" positions in the frustum. We only need to sample
* them and blend the scene color with those factors. This also
* work for alpha blended materials.
*/
#pragma once
#include "BLI_set.hh"
#include "DRW_gpu_wrapper.hh"
#include "GPU_batch_utils.hh"
#include "eevee_sync.hh"
#include "eevee_volume_shared.hh"
namespace blender::eevee {
class Instance;
class VolumePipeline;
class WorldVolumePipeline;
class VolumeModule {
friend VolumePipeline;
friend WorldVolumePipeline;
private:
Instance &inst_;
bool enabled_;
bool viewport_sampling_is_reset_ = false;
bool use_reprojection_;
bool use_lights_;
/* Track added/removed volume objects to reset the accumulation history. */
Set<ObjectKey> previous_objects_;
Set<ObjectKey> current_objects_;
VolumesInfoData &data_;
/**
* Occupancy map that allows to fill froxels that are inside the geometry.
* It is filled during a pre-pass using atomic operations.
* Using a 3D bit-field, we only allocate one bit per froxel.
*/
Texture occupancy_tx_ = {"occupancy_tx"};
/**
* List of surface hit for correct occupancy determination.
* One texture holds the number of hit count and the other the depth and
* the facing of each hit.
*/
Texture hit_count_tx_ = {"hit_count_tx"};
Texture hit_depth_tx_ = {"hit_depth_tx"};
Texture front_depth_tx_ = {"front_depth_tx"};
Framebuffer occupancy_fb_ = {"occupancy_fb"};
/* Material Parameters */
Texture prop_scattering_tx_;
Texture prop_extinction_tx_;
Texture prop_emission_tx_;
Texture prop_phase_tx_;
Texture prop_phase_weight_tx_;
/* Light Scattering. */
PassSimple scatter_ps_ = {"Volumes.Scatter"};
SwapChain<Texture, 2> scatter_tx_;
SwapChain<Texture, 2> extinction_tx_;
/* Volume Integration */
PassSimple integration_ps_ = {"Volumes.Integration"};
Texture integrated_scatter_tx_;
Texture integrated_transmit_tx_;
/* Full-screen Resolve */
PassSimple resolve_ps_ = {"Volumes.Resolve"};
Framebuffer resolve_fb_;
Texture dummy_scatter_tx_;
Texture dummy_transmit_tx_;
View volume_view = {"Volume View"};
float4x4 history_viewmat_ = float4x4::zero();
/* Number of re-projected frame into the volume history.
* Allows continuous integration between interactive and static mode. */
int history_frame_count_ = 0;
/* Used to detect change in camera projection type. */
bool history_camera_is_perspective_ = false;
/* Must be set to false on every event that makes the history invalid to sample. */
bool valid_history_ = false;
gpu::Batch *cube_batch_ = GPU_batch_unit_cube();
public:
VolumeModule(Instance &inst, VolumesInfoData &data) : inst_(inst), data_(data)
{
dummy_scatter_tx_.ensure_3d(
gpu::TextureFormat::UNORM_8_8_8_8, int3(1), GPU_TEXTURE_USAGE_SHADER_READ, float4(0.0f));
dummy_transmit_tx_.ensure_3d(
gpu::TextureFormat::UNORM_8_8_8_8, int3(1), GPU_TEXTURE_USAGE_SHADER_READ, float4(1.0f));
};
~VolumeModule()
{
GPU_BATCH_DISCARD_SAFE(cube_batch_);
}
bool needs_shadow_tagging() const
{
return enabled_ && use_lights_;
}
/* Return the future value of enabled() that will only be available after end_sync(). */
bool will_enable() const;
/* Returns the state of the module. */
bool enabled() const
{
return enabled_;
}
int3 grid_size()
{
return data_.tex_size;
}
gpu::Batch *unit_cube_batch_get()
{
return cube_batch_;
}
void init();
void begin_sync();
void world_sync(const WorldHandle &world_handle);
void object_sync(const ObjectHandle &ob_handle);
void end_sync();
void set_view(View &main_view);
/* Render material properties. Needs to be called after `set_view`. */
void draw_prepass(View &main_view);
/* Compute scattering and integration. */
void draw_compute(View &main_view, int2 extent);
/* Final image compositing. */
void draw_resolve(View &view);
/* Final occupancy after resolve. Used by object volume material evaluation. */
struct {
/** References to the textures in the module. */
gpu::Texture *scattering_tx_ = nullptr;
gpu::Texture *transmittance_tx_ = nullptr;
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_texture(VOLUME_SCATTERING_TEX_SLOT, &scattering_tx_);
pass.bind_texture(VOLUME_TRANSMITTANCE_TEX_SLOT, &transmittance_tx_);
}
} result;
/* Volume property buffers that are populated by objects or world volume shaders. */
struct {
/** References to the textures in the module. */
gpu::Texture *scattering_tx_ = nullptr;
gpu::Texture *extinction_tx_ = nullptr;
gpu::Texture *emission_tx_ = nullptr;
gpu::Texture *phase_tx_ = nullptr;
gpu::Texture *phase_weight_tx_ = nullptr;
gpu::Texture *occupancy_tx_ = nullptr;
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_image(VOLUME_PROP_SCATTERING_IMG_SLOT, &scattering_tx_);
pass.bind_image(VOLUME_PROP_EXTINCTION_IMG_SLOT, &extinction_tx_);
pass.bind_image(VOLUME_PROP_EMISSION_IMG_SLOT, &emission_tx_);
pass.bind_image(VOLUME_PROP_PHASE_IMG_SLOT, &phase_tx_);
pass.bind_image(VOLUME_PROP_PHASE_WEIGHT_IMG_SLOT, &phase_weight_tx_);
pass.bind_image(VOLUME_OCCUPANCY_SLOT, &occupancy_tx_);
}
} properties;
/* Textures used for object volume occupancy computation. */
struct {
/** References to the textures in the module. */
gpu::Texture *occupancy_tx_ = nullptr;
gpu::Texture *hit_depth_tx_ = nullptr;
gpu::Texture *hit_count_tx_ = nullptr;
template<typename PassType> void bind_resources(PassType &pass)
{
pass.bind_image(VOLUME_OCCUPANCY_SLOT, &occupancy_tx_);
pass.bind_image(VOLUME_HIT_DEPTH_SLOT, &hit_depth_tx_);
pass.bind_image(VOLUME_HIT_COUNT_SLOT, &hit_count_tx_);
}
} occupancy;
};
} // namespace blender::eevee

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Shared code between host and client code-bases.
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::eevee {
#endif
struct [[host_shared]] VolumesInfoData {
/* During object voxelization, we need to use an infinite projection matrix to avoid clipping
* faces. But they cannot be used for recovering the view position from froxel position as they
* are not invertible. We store the finite projection matrix and use it for this purpose. */
float4x4 winmat_finite;
float4x4 wininv_finite;
/* Copies of the matrices above but without jittering. Used for re-projection. */
float4x4 wininv_stable;
float4x4 winmat_stable;
/* Previous render sample copy of winmat_stable. */
float4x4 history_winmat_stable;
/* Transform from current view space to previous render sample view space. */
float4x4 curr_view_to_past_view;
/* Size of the froxel grid texture. */
packed_int3 tex_size;
/* Maximum light intensity during volume lighting evaluation. */
float light_clamp;
/* Inverse of size of the froxel grid. */
packed_float3 inv_tex_size;
/* Maximum light intensity during volume lighting evaluation. */
float shadow_steps;
/* 2D scaling factor to make froxel squared. */
float2 coord_scale;
/* Extent and inverse extent of the main shading view (render extent, not film extent). */
float2 main_view_extent;
float2 main_view_extent_inv;
/* Size in main view pixels of one froxel in XY. */
int tile_size;
/* Hi-Z LOD to use during volume shadow tagging. */
int tile_size_lod;
/* Depth to froxel mapping. */
float depth_near;
float depth_far;
float depth_distribution;
/* Previous render sample copy of the depth mapping parameters. */
float history_depth_near;
float history_depth_far;
float history_depth_distribution;
/* Amount of history to blend during the scatter phase. */
float history_opacity;
float _pad1;
};
#ifndef GPU_SHADER
} // namespace blender::eevee
#endif

View File

@@ -0,0 +1,175 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#include "BKE_lib_id.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "DEG_depsgraph_query.hh"
#include "NOD_shader.h"
#include "eevee_instance.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name World
*
* \{ */
World::~World()
{
if (default_world_ != nullptr) {
BKE_id_free(nullptr, default_world_);
}
}
blender::World *World::default_world_get()
{
if (default_world_ == nullptr) {
default_world_ = BKE_id_new_nomain<blender::World>("EEVEE default world");
default_world_->gpumaterial.clear_no_delete();
}
return default_world_;
}
blender::World *World::scene_world_get()
{
return (inst_.scene->world != nullptr) ? inst_.scene->world : default_world_get();
}
float World::sun_threshold()
{
/* No sun extraction during baking. */
if (inst_.is_baking()) {
return 0.0;
}
float sun_threshold = scene_world_get()->sun_threshold;
if (inst_.use_studio_light()) {
/* Do not call `lookdev_world_.intensity_get()` as it might not be initialized yet. */
sun_threshold *= inst_.v3d->shading.studiolight_intensity;
}
return sun_threshold;
}
void World::sync()
{
bool has_update = false;
WorldHandle wo_handle = {inst_.scene->world ? inst_.get_recalc_flags(*inst_.scene->world) : 0};
/* Detect world update before overriding it. */
has_update = wo_handle.recalc != 0;
bool wait_ready = true; // TODO !inst_.is_image_render;
/* Sync volume first since its result can override the surface world. */
sync_volume(wo_handle, wait_ready);
blender::World *bl_world;
if (inst_.use_studio_light()) {
has_update |= lookdev_world_.sync(LookdevParameters(inst_.v3d));
bl_world = lookdev_world_.world_get();
}
else if ((inst_.view_layer->layflag & SCE_LAY_SKY) == 0) {
bl_world = default_world_get();
}
else if (has_volume_absorption_) {
bl_world = default_world_get();
}
else {
bl_world = scene_world_get();
}
blender::World *world_override = DEG_get_evaluated(inst_.depsgraph,
inst_.view_layer->world_override);
if (world_override) {
bl_world = world_override;
}
bNodeTree *ntree = (bl_world->nodetree) ? bl_world->nodetree : default_world_get()->nodetree;
{
if (has_volume_absorption_) {
/* Replace world by black world. */
bl_world = default_world_get();
}
}
/* We have to manually test here because we have overrides. */
blender::World *orig_world = DEG_get_original(bl_world);
if (assign_if_different(prev_original_world, orig_world)) {
has_update = true;
}
inst_.light_probes.sync_world(bl_world, has_update);
if (inst_.is_viewport() && has_update) {
/* Catch lookdev viewport properties updates. */
inst_.sampling.reset();
}
GPUMaterial *gpumat = inst_.shaders.world_shader_get(
bl_world, ntree, MAT_PIPE_DEFERRED, !wait_ready);
if (GPU_material_status(gpumat) == GPU_MAT_FAILED) {
bl_world = default_world_get();
ntree = bl_world->nodetree;
gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_DEFERRED, !wait_ready);
}
if (GPU_material_status(gpumat) == GPU_MAT_QUEUED) {
is_ready_ = false;
return;
}
is_ready_ = true;
inst_.manager->register_layer_attributes(gpumat);
float opacity = inst_.use_studio_light() ? lookdev_world_.background_opacity_get() :
inst_.film.background_opacity_get();
float background_blur = inst_.use_studio_light() ? lookdev_world_.background_blur_get() : 0.0;
inst_.pipelines.background.sync(gpumat, opacity, background_blur);
inst_.pipelines.world.sync(gpumat);
}
void World::sync_volume(const WorldHandle &world_handle, bool wait_ready)
{
/* Studio lights have no volume shader. */
blender::World *world = inst_.use_studio_light() ? nullptr : inst_.scene->world;
GPUMaterial *gpumat = nullptr;
/* Only the scene world nodetree can have volume shader. */
if (world && world->nodetree) {
gpumat = inst_.shaders.world_shader_get(
world, world->nodetree, MAT_PIPE_VOLUME_MATERIAL, !wait_ready);
}
bool had_volume = has_volume_;
if (gpumat && (GPU_material_status(gpumat) == GPU_MAT_SUCCESS)) {
has_volume_ = GPU_material_has_volume_output(gpumat);
has_volume_scatter_ = GPU_material_flag_get(gpumat, GPU_MATFLAG_VOLUME_SCATTER);
has_volume_absorption_ = GPU_material_flag_get(gpumat, GPU_MATFLAG_VOLUME_ABSORPTION);
}
else {
has_volume_ = has_volume_absorption_ = has_volume_scatter_ = false;
}
/* World volume needs to be always synced for correct clearing of parameter buffers. */
inst_.pipelines.world_volume.sync(gpumat);
if (has_volume_ || had_volume) {
inst_.volume.world_sync(world_handle);
}
}
/** \} */
} // namespace blender::eevee

View File

@@ -0,0 +1,135 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* 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"
#include "eevee_lookdev.hh"
#include "eevee_sync.hh"
namespace blender {
struct bNodeTree;
struct bNodeSocketValueRGBA;
struct UniformBuffer;
namespace eevee {
class Instance;
/* -------------------------------------------------------------------- */
/** \name World
*
* \{ */
class World {
public:
/**
* Buffer containing the sun light for the world.
* Filled by #LightProbeModule and read by #LightModule.
*/
UniformArrayBuffer<LightData, 2> sunlight = {"sunlight"};
private:
Instance &inst_;
/* Used to detect if world change. */
blender::World *prev_original_world = nullptr;
/* Used when the scene doesn't have a world. */
blender::World *default_world_ = nullptr;
/* Is true if world as a valid volume shader compiled. */
bool has_volume_ = false;
/* Is true if the volume shader has absorption. Disables distant lights. */
bool has_volume_absorption_ = false;
/* Is true if the volume shader has scattering. */
bool has_volume_scatter_ = false;
/* Is true if the surface shader is compiled and ready. */
bool is_ready_ = false;
LookdevWorld lookdev_world_;
public:
World(Instance &inst) : inst_(inst) {};
~World();
/* Setup and request the background shader. */
void sync();
bool has_volume() const
{
return has_volume_;
}
bool has_volume_absorption() const
{
return has_volume_absorption_;
}
bool has_volume_scatter() const
{
return has_volume_scatter_;
}
bool is_ready() const
{
return is_ready_;
}
float sun_threshold();
float sun_angle()
{
return scene_world_get()->sun_angle;
}
float sun_shadow_max_resolution()
{
return scene_world_get()->sun_shadow_maximum_resolution;
}
float sun_shadow_filter_radius()
{
return scene_world_get()->sun_shadow_filter_radius;
}
float sun_shadow_jitter_overblur()
{
return scene_world_get()->sun_shadow_jitter_overblur;
}
bool use_sun_shadow()
{
return scene_world_get()->flag & WO_USE_SUN_SHADOW;
}
bool use_sun_shadow_jitter()
{
return scene_world_get()->flag & WO_USE_SUN_SHADOW_JITTER;
}
private:
void sync_volume(const WorldHandle &world_handle, bool wait_ready);
/* Returns a dummy black world for when a valid world isn't present or when we want to suppress
* any light coming from the world. */
blender::World *default_world_get();
/* Returns either the scene world or the default world if scene has no world. */
blender::World *scene_world_get();
};
/** \} */
} // namespace eevee
} // namespace blender

View File

@@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: 2024 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC_GLSL
.
..
# For variadic macros
../../../../blenlib
../../../intern
../../../intern/shaders
../../../../gpu
../../../../gpu/intern
../../../../gpu/shaders
../../../../gpu/shaders/common
../../../../gpu/shaders/infos
# For grease pencil.
../../gpencil
)
set(SRC_GLSL_LIB
eevee_attributes_curves_lib.glsl
eevee_attributes_mesh_lib.glsl
eevee_attributes_pointcloud_lib.glsl
eevee_attributes_volume_lib.glsl
eevee_attributes_world_lib.glsl
)
set(SRC_BSL
bsl_shader_linting.cc
)
# Compile shaders with shader code.
if(WITH_GPU_SHADER_CPP_COMPILATION)
# TODO Remove
add_definitions(-DUSE_GPU_SHADER_CREATE_INFO)
compile_sources_as_cpp(eevee_cpp_shaders_bsl "${SRC_BSL}" "")
# Only enable to make sure they compile on their own.
# Otherwise it creates a warning about `pragma once`.
# compile_sources_as_cpp(eevee_cpp_shaders_lib "${SRC_GLSL_LIB}" "GPU_LIBRARY_SHADER")
endif()

View File

@@ -0,0 +1,139 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Compile shader files as C++ inside one compilation unit to lint syntax and get IDE integration.
*/
#include "eevee_bxdf.bsl.hh" /* IWYU pragma: export */
#include "eevee_bxdf_diffuse.bsl.hh" /* IWYU pragma: export */
#include "eevee_bxdf_lut.bsl.hh" /* IWYU pragma: export */
#include "eevee_bxdf_lut_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_bxdf_microfacet.bsl.hh" /* IWYU pragma: export */
#include "eevee_bxdf_types.bsl.hh" /* IWYU pragma: export */
#include "eevee_camera_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_closure.bsl.hh" /* IWYU pragma: export */
#include "eevee_colorspace_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_cryptomatte.bsl.hh" /* IWYU pragma: export */
#include "eevee_cryptomatte_sort.bsl.hh" /* IWYU pragma: export */
#include "eevee_debug.bsl.hh" /* IWYU pragma: export */
#include "eevee_deferred_combine.bsl.hh" /* IWYU pragma: export */
#include "eevee_deferred_eval.bsl.hh" /* IWYU pragma: export */
#include "eevee_deferred_thickness_amend.bsl.hh" /* IWYU pragma: export */
#include "eevee_deferred_tile_classify.bsl.hh" /* IWYU pragma: export */
#include "eevee_depth_of_field_accumulator.bsl.hh" /* IWYU pragma: export */
#include "eevee_depth_of_field_bokeh_lut.bsl.hh" /* IWYU pragma: export */
#include "eevee_depth_of_field_filter.bsl.hh" /* IWYU pragma: export */
#include "eevee_depth_of_field_gather.bsl.hh" /* IWYU pragma: export */
#include "eevee_depth_of_field_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_depth_of_field_resolve.bsl.hh" /* IWYU pragma: export */
#include "eevee_depth_of_field_scatter.bsl.hh" /* IWYU pragma: export */
#include "eevee_depth_of_field_setup.bsl.hh" /* IWYU pragma: export */
#include "eevee_depth_of_field_tiles.bsl.hh" /* IWYU pragma: export */
#include "eevee_fast_gi.bsl.hh" /* IWYU pragma: export */
#include "eevee_film.bsl.hh" /* IWYU pragma: export */
#include "eevee_filter.bsl.hh" /* IWYU pragma: export */
#include "eevee_forward_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_forward_resolve.bsl.hh" /* IWYU pragma: export */
#include "eevee_gbuffer_read.bsl.hh" /* IWYU pragma: export */
#include "eevee_gbuffer_types.bsl.hh" /* IWYU pragma: export */
#include "eevee_gbuffer_write.bsl.hh" /* IWYU pragma: export */
#include "eevee_geom_curves.bsl.hh" /* IWYU pragma: export */
#include "eevee_geom_mesh.bsl.hh" /* IWYU pragma: export */
#include "eevee_geom_pointcloud.bsl.hh" /* IWYU pragma: export */
#include "eevee_geom_types_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_geom_volume.bsl.hh" /* IWYU pragma: export */
#include "eevee_geom_world.bsl.hh" /* IWYU pragma: export */
#include "eevee_hiz.bsl.hh" /* IWYU pragma: export */
#include "eevee_hiz_update.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_culling.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_data.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_eval.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_iter.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_shadow_setup.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_shape_display.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe_display.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe_plane.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe_sphere.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe_sphere_bake.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe_sphere_culling.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe_volume.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe_volume_bake.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe_volume_load.bsl.hh" /* IWYU pragma: export */
#include "eevee_lookdev_copy_world.bsl.hh" /* IWYU pragma: export */
#include "eevee_lookdev_display.bsl.hh" /* IWYU pragma: export */
#include "eevee_ltc_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_ltc_lut_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_material_variants.bsl.hh" /* IWYU pragma: export */
#include "eevee_motion_blur.bsl.hh" /* IWYU pragma: export */
#include "eevee_nodetree_closures_lib.glsl" /* IWYU pragma: export */
#include "eevee_nodetree_frag_lib.glsl" /* IWYU pragma: export */
#include "eevee_nodetree_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_nodetree_type_lib.glsl" /* IWYU pragma: export */
#include "eevee_nodetree_vert_lib.glsl" /* IWYU pragma: export */
#include "eevee_occupancy_convert.bsl.hh" /* IWYU pragma: export */
#include "eevee_occupancy_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_octahedron_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_pipeline.bsl.hh" /* IWYU pragma: export */
#include "eevee_ray_denoise.bsl.hh" /* IWYU pragma: export */
#include "eevee_ray_generate.bsl.hh" /* IWYU pragma: export */
#include "eevee_ray_tile.bsl.hh" /* IWYU pragma: export */
#include "eevee_ray_trace.bsl.hh" /* IWYU pragma: export */
#include "eevee_ray_trace_screen_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_ray_types_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_renderpass.bsl.hh" /* IWYU pragma: export */
#include "eevee_renderpass_clear.bsl.hh" /* IWYU pragma: export */
#include "eevee_reverse_z_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_sampling_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_page_allocate.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_page_clear.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_page_defrag.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_page_free.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_page_mask.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_page_ops.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_tag_update.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_tag_usage.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_tag_usage_transparent.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_tilemap_amend.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_tilemap_bounds.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_tilemap_finalize.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_tilemap_init.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_tilemap_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_tracing.bsl.hh" /* IWYU pragma: export */
#include "eevee_shadow_visibility.bsl.hh" /* IWYU pragma: export */
#include "eevee_spherical_harmonics.bsl.hh" /* IWYU pragma: export */
#include "eevee_subsurface.bsl.hh" /* IWYU pragma: export */
#include "eevee_subsurface_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_capture.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_common.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_deferred.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_depth.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_forward.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_hybrid.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_occupancy.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_shadow.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_volume.bsl.hh" /* IWYU pragma: export */
#include "eevee_surf_world.bsl.hh" /* IWYU pragma: export */
#include "eevee_surfel.bsl.hh" /* IWYU pragma: export */
#include "eevee_surfel_cluster.bsl.hh" /* IWYU pragma: export */
#include "eevee_surfel_light.bsl.hh" /* IWYU pragma: export */
#include "eevee_surfel_list.bsl.hh" /* IWYU pragma: export */
#include "eevee_surfel_ray.bsl.hh" /* IWYU pragma: export */
#include "eevee_thickness_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_transparency.bsl.hh" /* IWYU pragma: export */
#include "eevee_uniform.bsl.hh" /* IWYU pragma: export */
#include "eevee_utility_tx.bsl.hh" /* IWYU pragma: export */
#include "eevee_velocity.bsl.hh" /* IWYU pragma: export */
#include "eevee_volume.bsl.hh" /* IWYU pragma: export */
#include "eevee_volume_lib.bsl.hh" /* IWYU pragma: export */
// #include "eevee_test_fast_gi.bsl.hh" /* IWYU pragma: export */
// #include "eevee_test_gbuffer_closure.bsl.hh" /* IWYU pragma: export */
// #include "eevee_test_gbuffer_normal.bsl.hh" /* IWYU pragma: export */
// #include "eevee_test_occupancy.bsl.hh" /* IWYU pragma: export */
// #include "eevee_test_shadow.bsl.hh" /* IWYU pragma: export */
void main() {}

View File

@@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "draw_curves_lib.glsl"
#include "eevee_geom_types_lib.bsl.hh"
#include "gpu_shader_codegen_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Curve
*
* Curve objects loads attributes from buffers through sampler buffers.
* Per attribute scope follows loading order.
* \{ */
float3 attr_load_orco(CurvesPoint point, float4 /*orco*/, int /*index*/)
{
/* NOTE: Doesn't support ORCO attribute. */
return point.orco_default;
}
/* Return the index to use for looking up the attribute value in the sampler
* based on the attribute scope (point or spline). */
int curves_attribute_element_id(CurvesPoint point, int index)
{
const auto &curves_buf = buffer_get(draw_curves_infos, drw_curves);
if (curves_buf.is_point_attribute[index][0] != 0u) {
return point.point_id;
}
return point.curve_id;
}
float4 attr_load_tangent(CurvesPoint /*point*/, samplerBuffer /*cd_buf*/, int /*index*/)
{
/* Not supported for the moment. */
return float4(0.0f, 0.0f, 0.0f, 1.0f);
}
float3 attr_load_uv(CurvesPoint point, samplerBuffer cd_buf, int /*index*/)
{
return texelFetch(cd_buf, point.curve_id).rgb;
}
float4 attr_load_color(CurvesPoint point, samplerBuffer cd_buf, int /*index*/)
{
return texelFetch(cd_buf, point.curve_id).rgba;
}
float4 attr_load_float4(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curves_attribute_element_id(point, index)).rgba;
}
float3 attr_load_float3(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curves_attribute_element_id(point, index)).rgb;
}
float2 attr_load_float2(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curves_attribute_element_id(point, index)).rg;
}
float attr_load_float(CurvesPoint point, samplerBuffer cd_buf, int index)
{
return texelFetch(cd_buf, curves_attribute_element_id(point, index)).r;
}
/** \} */

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "eevee_geom_types_lib.bsl.hh"
#include "gpu_shader_codegen_lib.glsl"
#include "gpu_shader_math_vector_safe_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Mesh
*
* Mesh objects attributes are loaded using vertex input attributes.
* \{ */
float3 attr_load_orco(MeshVertex vert, float4 orco, int /*index*/)
{
/* We know when there is no orco layer when orco.w is 1.0 because it uses the generic vertex
* attribute (which is [0,0,0,1]). */
if (orco.w == 1.0f) {
/* If the object does not have any deformation, the orco layer calculation is done on the fly
* using the orco_madd factors. */
return vert.orco_default;
}
return orco.xyz * 0.5f + 0.5f;
}
float4 attr_load_tangent(MeshVertex vert, float4 tangent, int /*index*/)
{
/* Same as normal_object_to_world. */
tangent.xyz = safe_normalize(tangent.xyz * vert.world_to_object);
return tangent;
}
float4 attr_load_float4(MeshVertex /*vert*/, float4 attr, int /*index*/)
{
return attr;
}
float3 attr_load_float3(MeshVertex /*vert*/, float3 attr, int /*index*/)
{
return attr;
}
float2 attr_load_float2(MeshVertex /*vert*/, float2 attr, int /*index*/)
{
return attr;
}
float attr_load_float(MeshVertex /*vert*/, float attr, int /*index*/)
{
return attr;
}
float4 attr_load_color(MeshVertex /*vert*/, float4 attr, int /*index*/)
{
return attr;
}
float3 attr_load_uv(MeshVertex /*vert*/, float3 attr, int /*index*/)
{
return attr;
}
/** \} */

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "draw_pointcloud_lib.glsl"
#include "eevee_geom_types_lib.bsl.hh"
#include "gpu_shader_codegen_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Point Cloud
*
* Point Cloud objects loads attributes from buffers through sampler buffers.
* \{ */
float3 attr_load_orco(PointCloudPoint point, float4 /*orco*/, int /*index*/)
{
/* NOTE: Doesn't support ORCO attribute. */
return point.orco_default;
}
float4 attr_load_tangent(PointCloudPoint point, samplerBuffer cd_buf, int /*index*/)
{
return pointcloud::get_customdata_vec4(point.point_id, cd_buf);
}
float3 attr_load_uv(PointCloudPoint point, samplerBuffer cd_buf, int /*index*/)
{
return pointcloud::get_customdata_vec3(point.point_id, cd_buf);
}
float4 attr_load_color(PointCloudPoint point, samplerBuffer cd_buf, int /*index*/)
{
return pointcloud::get_customdata_vec4(point.point_id, cd_buf);
}
float4 attr_load_float4(PointCloudPoint point, samplerBuffer cd_buf, int /*index*/)
{
return pointcloud::get_customdata_vec4(point.point_id, cd_buf);
}
float3 attr_load_float3(PointCloudPoint point, samplerBuffer cd_buf, int /*index*/)
{
return pointcloud::get_customdata_vec3(point.point_id, cd_buf);
}
float2 attr_load_float2(PointCloudPoint point, samplerBuffer cd_buf, int /*index*/)
{
return pointcloud::get_customdata_vec2(point.point_id, cd_buf);
}
float attr_load_float(PointCloudPoint point, samplerBuffer cd_buf, int /*index*/)
{
return pointcloud::get_customdata_float(point.point_id, cd_buf);
}
/** \} */

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "eevee_geom_types_lib.bsl.hh"
#include "gpu_shader_codegen_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Volume
*
* Volume objects loads attributes from "grids" in the form of 3D textures.
* Per grid transform order is following loading order.
* \{ */
float3 grid_coordinates(float3 lP, int index)
{
const auto &drw_volume = buffer_get(draw_volume_infos, drw_volume);
return (drw_volume.grids_xform[index] * float4(lP, 1.0f)).xyz;
}
float3 attr_load_orco(VolumePoint point, sampler3D /*tex*/, int /*index*/)
{
/* NOTE: Doesn't support ORCO attribute. */
return point.orco_default;
}
float4 attr_load_tangent(VolumePoint /*point*/, sampler3D /*tex*/, int /*index*/)
{
return float4(0);
}
float4 attr_load_float4(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates(point.lP, index));
}
float3 attr_load_float3(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates(point.lP, index)).rgb;
}
float2 attr_load_float2(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates(point.lP, index)).rg;
}
float attr_load_float(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates(point.lP, index)).r;
}
float4 attr_load_color(VolumePoint point, sampler3D tex, int index)
{
return texture(tex, grid_coordinates(point.lP, index));
}
float3 attr_load_uv(VolumePoint /*point*/, sampler3D /*attr*/, int /*index*/)
{
return float3(0);
}
/** \} */

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "eevee_geom_types_lib.bsl.hh"
#include "gpu_shader_codegen_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name World
*
* World has no attributes other than orco.
* \{ */
float3 attr_load_orco(WorldPoint point, float4 /*orco*/, int /*index*/)
{
return point.lP;
}
float4 attr_load_tangent(WorldPoint /*point*/, float4 /*tangent*/, int /*index*/)
{
return float4(0);
}
float4 attr_load_float4(WorldPoint /*point*/, float4 /*attr*/, int /*index*/)
{
return float4(0);
}
float3 attr_load_float3(WorldPoint /*point*/, float3 /*attr*/, int /*index*/)
{
return float3(0);
}
float2 attr_load_float2(WorldPoint /*point*/, float2 /*attr*/, int /*index*/)
{
return float2(0);
}
float attr_load_float(WorldPoint /*point*/, float /*attr*/, int /*index*/)
{
return 0.0f;
}
float4 attr_load_color(WorldPoint /*point*/, float4 /*attr*/, int /*index*/)
{
return float4(0);
}
float3 attr_load_uv(WorldPoint /*point*/, float3 /*attr*/, int /*index*/)
{
return float3(0);
}
/** \} */

View File

@@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/**
* Sampling of Normal Distribution Function for various BxDF.
*/
#include "eevee_bxdf_diffuse.bsl.hh" // IWYU pragma: export
#include "eevee_bxdf_microfacet.bsl.hh" // IWYU pragma: export
#include "eevee_bxdf_types.bsl.hh" // IWYU pragma: export

View File

@@ -0,0 +1,154 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "eevee_bxdf_types.bsl.hh"
#include "eevee_ltc_lut_lib.bsl.hh"
#include "eevee_thickness_lib.bsl.hh"
#include "gpu_shader_codegen_lib.glsl"
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_math_safe_lib.glsl"
#include "gpu_shader_ray_lib.glsl"
#include "gpu_shader_utildefines_lib.glsl"
/* -------------------------------------------------------------------- */
/** \name Diffuse BSDF
* \{ */
/**
* Returns a tangent space diffuse direction following the Lambertian distribution.
*
* \param rand: random point on the unit cylinder (result of sample_cylinder).
* The Z component can be biased towards 1.
* \return pdf: the pdf of sampling the reflected/refracted ray. 0 if ray is invalid.
*/
BsdfSample bxdf_diffuse_sample(float3 rand)
{
float cos_theta = safe_sqrt(rand.x);
BsdfSample samp;
samp.direction = float3(rand.yz * sin_from_cos(cos_theta), cos_theta);
samp.pdf = cos_theta * M_1_PI;
return samp;
}
BsdfEval bxdf_diffuse_eval(float3 N, float3 L)
{
BsdfEval eval;
eval.throughput = eval.pdf = saturate(dot(N, L));
return eval;
}
float bxdf_diffuse_perceived_roughness()
{
return 1.0f;
}
LightProbeRay bxdf_diffuse_lightprobe(float3 N)
{
LightProbeRay probe;
probe.perceptual_roughness = bxdf_diffuse_perceived_roughness();
probe.dominant_direction = N;
return probe;
}
ClosureLight bxdf_diffuse_light(ClosureUndetermined cl)
{
ClosureLight light;
light.ltc_mat = eevee::lut::ltc::identity(); /* No transform, just plain cosine distribution. */
light.N = cl.N;
light.type = LIGHT_DIFFUSE;
return light;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Translucent BSDF
* \{ */
/**
* Returns a tangent space diffuse direction following and inverted Lambertian distribution.
*
* \param rand: random point on the unit cylinder (result of sample_cylinder).
* The Z component can be biased towards 1.
* \param thickness: Thickness of the object. 0 is considered thin.
* \return pdf: the pdf of sampling the reflected/refracted ray. 0 if ray is invalid.
*/
BsdfSample bxdf_translucent_sample(float3 rand, Thickness thickness)
{
if (thickness.mode() == ThicknessMode::Sphere && thickness.value() != 0.0f) {
/* Two transmission events inside a sphere is a uniform sphere distribution. */
float cos_theta = rand.x * 2.0f - 1.0f;
BsdfSample samp;
samp.direction = float3(rand.yz * sin_from_cos(cos_theta), -cos_theta);
samp.pdf = 0.25f * M_1_PI;
return samp;
}
/* Inverted cosine distribution. */
BsdfSample samp = bxdf_diffuse_sample(rand);
samp.direction = -samp.direction;
return samp;
}
BsdfEval bxdf_translucent_eval(float3 N, float3 L, Thickness thickness)
{
if (thickness.mode() == ThicknessMode::Sphere && thickness.value() != 0.0f) {
/* Two transmission events inside a sphere is a uniform sphere distribution. */
BsdfEval eval;
eval.throughput = eval.pdf = 0.25f * M_1_PI;
return eval;
}
BsdfEval eval;
eval.throughput = eval.pdf = saturate(dot(-N, L));
return eval;
}
float bxdf_translucent_perceived_roughness()
{
return 1.0f;
}
LightProbeRay bxdf_translucent_lightprobe(float3 N, Thickness thickness)
{
LightProbeRay probe;
probe.perceptual_roughness = bxdf_translucent_perceived_roughness();
/* If using the spherical assumption, discard any directionality from the lighting. */
const bool sphere_with_thickness = thickness.mode() == ThicknessMode::Sphere &&
thickness.value() != 0.0f;
probe.dominant_direction = sphere_with_thickness ? float3(0.0f) : -N;
return probe;
}
Ray bxdf_translucent_ray_amend(ClosureUndetermined cl, float3 /*V*/, Ray ray, Thickness thickness)
{
if (thickness.mode() == ThicknessMode::Sphere) {
/* Ray direction is distributed on the whole sphere.
* Move the ray origin to the sphere surface (with bias to avoid self-intersection). */
ray.origin += (ray.direction - cl.N) * thickness.value() * 0.505f;
}
return ray;
}
ClosureLight bxdf_translucent_light(ClosureUndetermined cl, float3 /*V*/, Thickness thickness)
{
/* A translucent sphere lit by a light outside the sphere transmits the
* light uniformly over the sphere. To mimic this phenomenon, we use the light vector
* as normal. This is done inside `light_eval_single`.
*
* For slab model, the approximation has little to no impact on the lighting in practice,
* only focusing the light a tiny bit. Using the flipped normal is good enough approximation.
*/
ClosureLight light;
light.ltc_mat = eevee::lut::ltc::identity(); /* No transform, just plain cosine distribution. */
light.N = -cl.N;
const bool sphere_with_thickness = thickness.mode() == ThicknessMode::Sphere &&
thickness.value() != 0.0f;
light.type = sphere_with_thickness ? LIGHT_TRANSLUCENT_WITH_THICKNESS : LIGHT_DIFFUSE;
return light;
}
/** \} */

View File

@@ -0,0 +1,370 @@
/* SPDX-FileCopyrightText: 2017-2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Pipelines to generate Look Up Tables for BxDFs. Not used in the default configuration as the
* tables are stored in the blender executable. This is used for reference or to update them.
*/
#pragma once
#include "eevee_bxdf.bsl.hh"
#include "eevee_bxdf_lut_lib.bsl.hh"
#include "eevee_precompute_shared.hh"
#include "eevee_sampling_lib.bsl.hh"
#include "eevee_subsurface_shared.hh"
#include "gpu_shader_compat.hh"
#include "gpu_shader_math_base_lib.glsl"
namespace eevee::lut {
/* -------------------------------------------------------------------- */
/** \name LUT models
* \{ */
/**
* Generate 2D GGX BRDF LUT.
*
* Follows the split sum approximation used in [Real shading in unreal engine 4]
* (https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf).
*
* The 2D LUT is parameterized on:
* - `x = roughness`,
* - `y = sqrt(1 - cos(theta))`,
*
* and the result is interpreted as:
* - `integral = F0 * scale + F90 * bias - F82_tint * metal_bias`,
* where `F82_tint = mix(F0, float3(1), pow5f(6/7) * 7 / pow6f(6/7)) * (1 - F82)`.
*/
struct GGXBrdfClosure {
float roughness;
float3 V;
static GGXBrdfClosure from_params(float3 params)
{
/* We use squared roughness for approximate perceptual linearity
* following [Physically Based Shading at Disney]
* (https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf)
* Section 5.4. */
float roughness = square(params.x);
float NV = clamp(1.0f - square(params.y), 1e-4f, 0.9999f);
float3 V = float3(sqrt(1.0f - square(NV)), 0.0f, NV);
return {roughness, V};
}
GGXBrdfData eval(float3 Xi) const
{
/* Geometric normal. */
constexpr float3 N = float3(0.0f, 0.0f, 1.0f);
/* Return values. */
GGXBrdfData brdf = {};
/* Sample light vector, recover half vector as microfacet normal. */
float3 L = bxdf_ggx_sample_reflection(Xi, V, roughness, false).direction;
float3 H = normalize(V + L);
/* Restrict light vector to positive hemisphere. */
float NL = L.z;
if (NL > 0.0f) {
float weight = bxdf_ggx_eval_reflection(N, L, V, roughness, false).weight;
float VH = saturate(dot(V, H));
/* Schlick's Fresnel. */
float s = saturate(pow5f(1.0f - VH));
/* F82 tint effect. */
float b = VH * saturate(pow6f(1.0f - VH));
brdf.scale = (1.0f - s) * weight;
brdf.bias = s * weight;
brdf.metal_bias = b * weight;
}
return brdf;
}
};
/**
* Generate 3D GGX BSDF LUT.
*
* Follows the split sum approximation used in [Real shading in unreal engine 4]
* (https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf).
* and uses Schlick's approximation to weight R, T components.
*
* The 3D LUT is parameterized on:
* - `x = sqrt((ior - 1) / (ior + 1))`,
* - `y = sqrt(1 - cos(theta))`,
* - `z = roughness`,
*
* and output is interpreted as:
* - `reflectance = F0 * scale + F90 * bias`,
* - `transmittance = (1 - F0) * transmission_factor`.
*/
struct GGXBsdfClosure {
float roughness;
float ior;
float3 V;
static GGXBsdfClosure from_params(float3 params)
{
/* We use squared roughness for approximate perceptual linearity
* following [Physically Based Shading at Disney]
* (https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf)
* Section 5.4. */
float roughness = square(params.z);
/* ior is sin of critical angle. */
float ior = clamp(sqrt(params.x), 1e-4f, 0.9999f);
float critical_cos = sqrt(1.0f - saturate(square(ior)));
/* Maximize texture usage on both sides of the critical angle. */
params.y = params.y * 2.0f - 1.0f;
params.y *= (params.y > 0.0f) ? (1.0f - critical_cos) : critical_cos;
/* Center LUT around critical angle to avoid strange interpolation
* issues when the critical angle is changing. */
params.y += critical_cos;
float NV = clamp(params.y, 1e-4f, 0.9999f);
float3 V = float3(sqrt(1.0f - square(NV)), 0.0f, NV);
return {roughness, ior, V};
}
GGXBsdfData eval(float3 Xi) const
{
/* Geometric normal. */
constexpr float3 N = float3(0.0f, 0.0f, 1.0f);
GGXBsdfData bsdf = {};
/* Reflection, restricted to positive hemisphere. */
float3 R = bxdf_ggx_sample_reflection(Xi, V, roughness, false).direction;
float NR = R.z;
if (NR > 0.0f) {
/* Recover half vector as GGX normal. */
float3 H = normalize(V + R);
float3 L = refract(-V, H, 1.0f / ior);
float HL = abs(dot(H, L));
/* Schlick's Fresnel. */
float s = saturate(pow5f(1.0f - saturate(HL)));
float weight = bxdf_ggx_eval_reflection(N, R, V, roughness, false).weight;
bsdf.scale = (1.0f - s) * weight;
bsdf.bias = s * weight;
}
/* Refraction, restricted to negative hemisphere. */
float3 T = bxdf_ggx_sample_refraction(Xi, V, roughness, ior, Thickness{}, false).direction;
float NT = T.z;
/* In the case of TIR, `T == float3(0)`. */
if (NT < 0.0f) {
/* Recover half vector as GGX normal. */
float3 H = normalize(ior * T + V);
float HL = abs(dot(H, T));
/* Schlick's Fresnel. */
float s = saturate(pow5f(1.0f - saturate(HL)));
float weight = bxdf_ggx_eval_refraction(N, T, V, roughness, ior, Thickness{}, false).weight;
bsdf.transmission_factor = (1.0f - s) * weight;
}
return bsdf;
}
};
/**
* Generate 3D GGX BTDF LUT, for IOR > 1.
*
* Using Schlick's approximation; only the transmittance is needed because scale and
* bias do not depend on the IOR, and can be obtained independently from the BRDF LUT.
*
* The 3D LUT is parameterized on:
* - `x = sqrt((ior - 1) / (ior + 1))` for higher precision in the range `1 < IOR < 2`
* - `y = sqrt(1.0f - cos(theta))`
* - `z = roughness`
*
* and output is interpreted as:
* - `transmittance = (1 - F0) * transmission_factor`.
*/
struct GGXBtdfGt1Closure {
float roughness;
float ior;
float3 V;
static GGXBtdfGt1Closure from_params(float3 params)
{
/* We use squared roughness for approximate perceptual linearity
* following [Physically Based Shading at Disney]
* (https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf)
* Section 5.4. */
float roughness = square(params.z);
float F0 = clamp(square(params.x), 1e-4f, 0.9999f);
float ior = (1.0f + F0) / (1.0f - F0);
float NV = clamp(1.0f - square(params.y), 1e-4f, 0.9999f);
float3 V = float3(sqrt(1.0f - square(NV)), 0.0f, NV);
return {roughness, ior, V};
}
GGXBtdfGt1Data eval(float3 Xi) const
{
/* Geometric normal. */
constexpr float3 N = float3(0.0f, 0.0f, 1.0f);
GGXBtdfGt1Data btdf = {};
/* Refraction, restricted to negative hemisphere. */
float3 L = bxdf_ggx_sample_refraction(Xi, V, roughness, ior, Thickness{}, false).direction;
float NL = L.z;
if (NL < 0.0f) {
/* Recover half vector as GGX normal. */
float3 H = normalize(ior * L + V);
float HV = abs(dot(H, V));
/* Schlick's Fresnel. */
float s = saturate(pow5f(1.0f - saturate(HV)));
float weight = bxdf_ggx_eval_refraction(N, L, V, roughness, ior, Thickness{}, false).weight;
btdf.transmission_factor = (1.0f - s) * weight;
}
return btdf;
}
};
/**
* Generate Christensen-Burley translucency profile.
*
* Generally follows:
* [Extending the Disney BRDF to a BSDF with Integrated Subsurface Scattering]
* (https://blog.selfshadow.com/publications/s2015-shading-course/burley/s2015_pbs_disney_bsdf_notes.pdf)
* with empirical fitting to match cycles. Precomputes exit radiance for a slab of homogeneous
* material, back-lit by a directional light. We only care about a single color primary, as the
* profile is applied to each primary independently. The other components are for debugging.
*
* Params: `x = distance`, while output is interpreted as exit radiance.
*/
float4 burley_sss_translucency(float3 params)
{
/* Note that we only store the 1st (radius == 1) component.
* The others are here for debugging overall appearance. */
float3 radii = float3(1.0f, 0.2f, 0.1f);
float thickness = params.x * SSS_TRANSMIT_LUT_RADIUS;
float3 r = thickness / radii;
/* Manual fit based on cycles render of a backlit slab of varying thickness.
* Mean Error: 0.003
* Max Error: 0.015 */
float3 exponential = exp(-3.6f * pow(r, float3(1.11f)));
float3 gaussian = exp(-pow(3.4f * r, float3(1.6f)));
float3 fac = square(saturate(0.5f + r / 0.6f));
float3 profile = saturate(mix(gaussian, exponential, fac));
/* Mask off the end progressively to 0. */
profile *= saturate(1.0f - pow5f(params.x));
return float4(profile, 0.0f);
}
/* Note: unused. */
float4 random_walk_sss_translucency(float3 params)
{
/* Note that we only store the 1st (radius == 1) component.
* The others are here for debugging overall appearance. */
float3 radii = float3(1.0f, 0.2f, 0.1f);
float thickness = params.x * SSS_TRANSMIT_LUT_RADIUS;
float3 r = thickness / radii;
/* Manual fit based on cycles render of a backlit slab of varying thickness.
* Mean Error: 0.003
* Max Error: 0.016 */
float3 scale = float3(0.31f, 0.47f, 0.32f);
float3 exponent = float3(-22.0f, -5.8f, -0.5f);
float3 profile = float3(dot(scale, exp(exponent * r.r)),
dot(scale, exp(exponent * r.g)),
dot(scale, exp(exponent * r.b)));
profile = saturate(profile - 0.1f);
/* Mask off the end progressively to 0. */
profile *= saturate(1.0f - pow5f(params.x));
return float4(profile, 0.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name LUT computation, integration
* \{ */
template<typename F> float4 integrate(const F &func)
{
constexpr uint sample_count = 512u * 512u;
/* TODO(not_mark): Remove workaround for BSL-spec #4. */
// F func = F::from_params(params);
/* Measure func using N samples. */
float4 measure = float4(0.0f);
for (uint i = 1u; i <= sample_count; i++) {
/* Warp sequence to a point on the unit cylinder. */
float2 rand = hammersley_2d(i, sample_count);
float3 Xi = sample_cylinder(rand);
/* Add sample to measure. */
measure += func.eval(Xi).pack();
}
return measure / float(sample_count);
}
template float4 integrate<GGXBrdfClosure>(const GGXBrdfClosure &);
template float4 integrate<GGXBsdfClosure>(const GGXBsdfClosure &);
template float4 integrate<GGXBtdfGt1Closure>(const GGXBtdfGt1Closure &);
struct LUT {
[[image(0, read_write, SFLOAT_32_32_32_32)]] image3D image;
[[push_constant]] const int type;
[[push_constant]] const int3 extent;
};
[[compute]] [[local_size(LUT_WORKGROUP_SIZE, LUT_WORKGROUP_SIZE)]]
void comp_main([[global_invocation_id]] const uint3 global_id, [[resource_table]] LUT &lut)
{
/* Make sure coordinates are covering the whole [0..1] range at texel center. */
float3 lut_normalized_coordinate = float3(global_id) / float3(lut.extent - 1);
/* Make sure missing cases are noticeable. */
float4 result = float4(-1);
switch (uint(lut.type)) {
case LUT_GGX_BRDF_SPLIT_SUM:
result = integrate(GGXBrdfClosure::from_params(lut_normalized_coordinate));
break;
case LUT_GGX_BSDF_SPLIT_SUM:
result = integrate(GGXBsdfClosure::from_params(lut_normalized_coordinate));
break;
case LUT_GGX_BTDF_IOR_GT_ONE:
result = integrate(GGXBtdfGt1Closure::from_params(lut_normalized_coordinate));
break;
case LUT_BURLEY_SSS_PROFILE:
result = burley_sss_translucency(lut_normalized_coordinate);
break;
case LUT_RANDOM_WALK_SSS_PROFILE:
result = random_walk_sss_translucency(lut_normalized_coordinate);
break;
}
imageStore(lut.image, int3(global_id), result);
}
/** \} */
PipelineCompute comp(comp_main);
} // namespace eevee::lut

View File

@@ -0,0 +1,141 @@
/* SPDX-FileCopyrightText: 2017-2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Wrapper objects for data from Look Up Tables for BxDFs. Refer to
* `eevee_bxdf_lut.bsl.hh` for table computation.
*/
#pragma once
#include "eevee_defines.hh"
#include "eevee_utility_tx.bsl.hh"
#include "gpu_shader_compat.hh"
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_utildefines_lib.glsl"
namespace eevee::lut {
/**
* Output from the 2D GGX BRDF LUT.
*
* The result is interpreted as:
* - `integral = F0 * scale + F90 * bias - F82_tint * metal_bias`,
* where `F82_tint = mix(F0, float3(1), pow5f(6/7) * 7 / pow6f(6/7)) * (1 - F82)`.
*/
struct GGXBrdfData {
float scale;
float bias;
float metal_bias;
float4 pack() const
{
return float4(scale, bias, metal_bias, 0.0f);
}
static GGXBrdfData unpack(float4 data)
{
return {.scale = data.x, .bias = data.y, .metal_bias = data.z};
}
static float2 lut_coords_get(float cos_theta, float roughness)
{
return float2(roughness, sqrt(saturate(1.0f - cos_theta)));
}
static GGXBrdfData sample_utility_tx([[resource_table]] const UtilityTexture &util_tx,
float cos_theta,
float roughness)
{
const float2 coords = lut_coords_get(cos_theta, roughness);
const float4 data = util_tx.sample_lut(coords, UTIL_BRDF_LAYER);
return unpack(data);
}
};
/**
* Output from the 3D GGX BSDF LUT.
*
* The result is interpreted as:
* - `reflectance = F0 * scale + F90 * bias`,
* - `transmittance = (1 - F0) * transmission_factor`.
*/
struct GGXBsdfData {
float scale;
float bias;
float transmission_factor;
float4 pack() const
{
return float4(scale, bias, transmission_factor, 0.0f);
}
static GGXBsdfData unpack(float4 data)
{
return {.scale = data.x, .bias = data.y, .transmission_factor = data.z};
}
static float3 lut_coords_get(float cos_theta, float roughness, float ior)
{
/* IOR is the sine of the critical angle. */
float critical_cos = sqrt(1.0f - ior * ior);
float3 coords;
coords.x = square(ior);
coords.y = cos_theta;
coords.y -= critical_cos;
coords.y /= (coords.y > 0.0f) ? (1.0f - critical_cos) : critical_cos;
coords.y = coords.y * 0.5f + 0.5f;
coords.z = roughness;
return saturate(coords);
}
static GGXBsdfData sample_utility_tx([[resource_table]] const UtilityTexture &util_tx,
float cos_theta,
float roughness,
float ior)
{
const float3 coords = lut_coords_get(cos_theta, roughness, ior);
const float4 data = util_tx.sample_bsdf_lut(coords.xy, coords.z);
return unpack(data);
}
};
/**
* Output from the 3D GGX BTDF LUT, for IOR > 1.
*
* The result is interpreted as:
* - `transmittance = (1 - F0) * transmission_factor`.
*/
struct GGXBtdfGt1Data {
float transmission_factor;
float4 pack() const
{
return float4(transmission_factor, 0.0f, 0.0f, 0.0f);
}
static GGXBtdfGt1Data unpack(float4 data)
{
return {.transmission_factor = data.w};
}
static float3 lut_coords_get(float cos_theta, float roughness, float f0)
{
return float3(sqrt(f0), sqrt(1.0f - cos_theta), roughness);
}
static GGXBtdfGt1Data sample_utility_tx([[resource_table]] const UtilityTexture &util_tx,
float cos_theta,
float roughness,
float f0)
{
const float3 coords = lut_coords_get(cos_theta, roughness, f0);
const float4 data = util_tx.sample_bsdf_lut(coords.xy, coords.z);
return unpack(data);
}
};
} // namespace eevee::lut

Some files were not shown because too many files have changed in this diff Show More