Add Chromium-only Blender WebEngine parity work
This commit is contained in:
928
blender-5.2.0/source/blender/draw/CMakeLists.txt
Normal file
928
blender-5.2.0/source/blender/draw/CMakeLists.txt
Normal 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()
|
||||
213
blender-5.2.0/source/blender/draw/DRW_engine.hh
Normal file
213
blender-5.2.0/source/blender/draw/DRW_engine.hh
Normal 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
|
||||
86
blender-5.2.0/source/blender/draw/DRW_pbvh.hh
Normal file
86
blender-5.2.0/source/blender/draw/DRW_pbvh.hh
Normal 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
|
||||
129
blender-5.2.0/source/blender/draw/DRW_select_buffer.hh
Normal file
129
blender-5.2.0/source/blender/draw/DRW_select_buffer.hh
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
296
blender-5.2.0/source/blender/draw/engines/eevee/eevee_camera.cc
Normal file
296
blender-5.2.0/source/blender/draw/engines/eevee/eevee_camera.cc
Normal 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
|
||||
185
blender-5.2.0/source/blender/draw/engines/eevee/eevee_camera.hh
Normal file
185
blender-5.2.0/source/blender/draw/engines/eevee/eevee_camera.hh
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
310
blender-5.2.0/source/blender/draw/engines/eevee/eevee_defines.hh
Normal file
310
blender-5.2.0/source/blender/draw/engines/eevee/eevee_defines.hh
Normal 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 */
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
104
blender-5.2.0/source/blender/draw/engines/eevee/eevee_engine.cc
Normal file
104
blender-5.2.0/source/blender/draw/engines/eevee/eevee_engine.cc
Normal 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
|
||||
@@ -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
|
||||
1064
blender-5.2.0/source/blender/draw/engines/eevee/eevee_film.cc
Normal file
1064
blender-5.2.0/source/blender/draw/engines/eevee/eevee_film.cc
Normal file
File diff suppressed because it is too large
Load Diff
367
blender-5.2.0/source/blender/draw/engines/eevee/eevee_film.hh
Normal file
367
blender-5.2.0/source/blender/draw/engines/eevee/eevee_film.hh
Normal 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
|
||||
@@ -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
|
||||
254
blender-5.2.0/source/blender/draw/engines/eevee/eevee_gbuffer.hh
Normal file
254
blender-5.2.0/source/blender/draw/engines/eevee/eevee_gbuffer.hh
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
1028
blender-5.2.0/source/blender/draw/engines/eevee/eevee_instance.cc
Normal file
1028
blender-5.2.0/source/blender/draw/engines/eevee/eevee_instance.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
685
blender-5.2.0/source/blender/draw/engines/eevee/eevee_light.cc
Normal file
685
blender-5.2.0/source/blender/draw/engines/eevee/eevee_light.cc
Normal 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
|
||||
219
blender-5.2.0/source/blender/draw/engines/eevee/eevee_light.hh
Normal file
219
blender-5.2.0/source/blender/draw/engines/eevee/eevee_light.hh
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
652
blender-5.2.0/source/blender/draw/engines/eevee/eevee_lookdev.cc
Normal file
652
blender-5.2.0/source/blender/draw/engines/eevee/eevee_lookdev.cc
Normal 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
|
||||
227
blender-5.2.0/source/blender/draw/engines/eevee/eevee_lookdev.hh
Normal file
227
blender-5.2.0/source/blender/draw/engines/eevee/eevee_lookdev.hh
Normal 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
|
||||
51545
blender-5.2.0/source/blender/draw/engines/eevee/eevee_lut.cc
Normal file
51545
blender-5.2.0/source/blender/draw/engines/eevee/eevee_lut.cc
Normal file
File diff suppressed because it is too large
Load Diff
41
blender-5.2.0/source/blender/draw/engines/eevee/eevee_lut.hh
Normal file
41
blender-5.2.0/source/blender/draw/engines/eevee/eevee_lut.hh
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
1738
blender-5.2.0/source/blender/draw/engines/eevee/eevee_pipeline.cc
Normal file
1738
blender-5.2.0/source/blender/draw/engines/eevee/eevee_pipeline.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
1888
blender-5.2.0/source/blender/draw/engines/eevee/eevee_shader.cc
Normal file
1888
blender-5.2.0/source/blender/draw/engines/eevee/eevee_shader.cc
Normal file
File diff suppressed because it is too large
Load Diff
306
blender-5.2.0/source/blender/draw/engines/eevee/eevee_shader.hh
Normal file
306
blender-5.2.0/source/blender/draw/engines/eevee/eevee_shader.hh
Normal 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
|
||||
1416
blender-5.2.0/source/blender/draw/engines/eevee/eevee_shadow.cc
Normal file
1416
blender-5.2.0/source/blender/draw/engines/eevee/eevee_shadow.cc
Normal file
File diff suppressed because it is too large
Load Diff
501
blender-5.2.0/source/blender/draw/engines/eevee/eevee_shadow.hh
Normal file
501
blender-5.2.0/source/blender/draw/engines/eevee/eevee_shadow.hh
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
544
blender-5.2.0/source/blender/draw/engines/eevee/eevee_sync.cc
Normal file
544
blender-5.2.0/source/blender/draw/engines/eevee/eevee_sync.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
465
blender-5.2.0/source/blender/draw/engines/eevee/eevee_view.cc
Normal file
465
blender-5.2.0/source/blender/draw/engines/eevee/eevee_view.cc
Normal 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
|
||||
189
blender-5.2.0/source/blender/draw/engines/eevee/eevee_view.hh
Normal file
189
blender-5.2.0/source/blender/draw/engines/eevee/eevee_view.hh
Normal 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
|
||||
498
blender-5.2.0/source/blender/draw/engines/eevee/eevee_volume.cc
Normal file
498
blender-5.2.0/source/blender/draw/engines/eevee/eevee_volume.cc
Normal 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
|
||||
229
blender-5.2.0/source/blender/draw/engines/eevee/eevee_volume.hh
Normal file
229
blender-5.2.0/source/blender/draw/engines/eevee/eevee_volume.hh
Normal 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
|
||||
@@ -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
|
||||
175
blender-5.2.0/source/blender/draw/engines/eevee/eevee_world.cc
Normal file
175
blender-5.2.0/source/blender/draw/engines/eevee/eevee_world.cc
Normal 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
|
||||
135
blender-5.2.0/source/blender/draw/engines/eevee/eevee_world.hh
Normal file
135
blender-5.2.0/source/blender/draw/engines/eevee/eevee_world.hh
Normal 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
|
||||
@@ -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()
|
||||
@@ -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() {}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user