EEVEE Next: Volumes #107176

Merged
Miguel Pozo merged 126 commits from pragma37/blender:pull-eevee-next-volumes into main 2023-08-04 16:47:22 +02:00
35 changed files with 1842 additions and 86 deletions

View File

@ -406,6 +406,53 @@ class RENDER_PT_eevee_volumetric_shadows(RenderButtonsPanel, Panel):
layout.prop(props, "volumetric_shadow_samples", text="Samples")
class RENDER_PT_eevee_next_volumetric(RenderButtonsPanel, Panel):
bl_label = "Volumetrics"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
@classmethod
def poll(cls, context):
return (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.eevee
col = layout.column(align=True)
col.prop(props, "volumetric_start")
col.prop(props, "volumetric_end")
col = layout.column()
col.prop(props, "volumetric_tile_size")
col.prop(props, "volumetric_samples")
col.prop(props, "volumetric_sample_distribution", text="Distribution")
class RENDER_PT_eevee_next_volumetric_lighting(RenderButtonsPanel, Panel):
bl_label = "Volumetric Lighting"
bl_parent_id = "RENDER_PT_eevee_next_volumetric"
COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'}
def draw_header(self, context):
scene = context.scene
props = scene.eevee
self.layout.prop(props, "use_volumetric_lights", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
scene = context.scene
props = scene.eevee
layout.active = props.use_volumetric_lights
layout.prop(props, "volumetric_light_clamp", text="Light Clamping")
class RENDER_PT_eevee_subsurface_scattering(RenderButtonsPanel, Panel):
bl_label = "Subsurface Scattering"
bl_options = {'DEFAULT_CLOSED'}
@ -1133,6 +1180,8 @@ classes = (
RENDER_PT_eevee_volumetric,
RENDER_PT_eevee_volumetric_lighting,
RENDER_PT_eevee_volumetric_shadows,
RENDER_PT_eevee_next_volumetric,
RENDER_PT_eevee_next_volumetric_lighting,
RENDER_PT_eevee_performance,
RENDER_PT_eevee_hair,
RENDER_PT_eevee_shadows,

View File

@ -161,6 +161,7 @@ set(SRC
engines/eevee_next/eevee_sync.cc
engines/eevee_next/eevee_velocity.cc
engines/eevee_next/eevee_view.cc
engines/eevee_next/eevee_volume.cc
engines/eevee_next/eevee_world.cc
engines/workbench/workbench_data.cc
engines/workbench/workbench_effect_antialiasing.cc
@ -307,6 +308,7 @@ set(SRC
engines/eevee_next/eevee_sync.hh
engines/eevee_next/eevee_velocity.hh
engines/eevee_next/eevee_view.hh
engines/eevee_next/eevee_volume.hh
engines/eevee_next/eevee_world.hh
engines/external/external_engine.h
engines/gpencil/gpencil_antialiasing.hh
@ -578,6 +580,11 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl
engines/eevee_next/shaders/eevee_transparency_lib.glsl
engines/eevee_next/shaders/eevee_velocity_lib.glsl
engines/eevee_next/shaders/eevee_volume_integration_comp.glsl
engines/eevee_next/shaders/eevee_volume_lib.glsl
engines/eevee_next/shaders/eevee_volume_material_comp.glsl
engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl
engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl
engines/eevee_next/eevee_defines.hh
engines/eevee_next/eevee_shader_shared.hh

View File

@ -113,6 +113,10 @@
#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
/* Resource bindings. */
/* Textures. */
@ -125,6 +129,8 @@
#define SSS_TRANSMITTANCE_TEX_SLOT 6
#define IRRADIANCE_ATLAS_TEX_SLOT 7
#define REFLECTION_PROBE_TEX_SLOT 8
#define VOLUME_SCATTERING_TEX_SLOT 9
#define VOLUME_TRANSMITTANCE_TEX_SLOT 10
/* Only during shadow rendering. */
#define SHADOW_RENDER_MAP_SLOT 4
@ -134,6 +140,11 @@
#define RBUFS_CRYPTOMATTE_SLOT 2
#define GBUF_CLOSURE_SLOT 3
#define GBUF_COLOR_SLOT 4
/* 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
/* Uniform Buffers. */
/* Slot 0 is GPU_NODE_TREE_UBO_SLOT. */
@ -143,6 +154,7 @@
#define HIZ_BUF_SLOT 3
#define IRRADIANCE_GRID_BUF_SLOT 4
#define AO_BUF_SLOT 5
#define VOLUMES_INFO_BUF_SLOT 6
/* SLOT 6 is used by render shaders (Film, DoF and Motion Blur). Need to check if it should be
* assigned a different slot. */
#define REFLECTION_PROBE_BUF_SLOT 7

View File

@ -77,6 +77,7 @@ void Instance::init(const int2 &output_res,
/* Irradiance Cache needs reflection probes to be initialized. */
reflection_probes.init();
irradiance_cache.init();
volume.init();
}
void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
@ -142,6 +143,7 @@ void Instance::begin_sync()
velocity.begin_sync(); /* NOTE: Also syncs camera. */
lights.begin_sync();
shadows.begin_sync();
volume.begin_sync();
pipelines.begin_sync();
cryptomatte.begin_sync();
reflection_probes.begin_sync();
@ -180,8 +182,14 @@ void Instance::scene_sync()
void Instance::object_sync(Object *ob)
{
const bool is_renderable_type = ELEM(
ob->type, OB_CURVES, OB_GPENCIL_LEGACY, OB_MESH, OB_POINTCLOUD, OB_LAMP, OB_LIGHTPROBE);
const bool is_renderable_type = ELEM(ob->type,
OB_CURVES,
OB_GPENCIL_LEGACY,
OB_MESH,
OB_POINTCLOUD,
OB_VOLUME,
OB_LAMP,
OB_LIGHTPROBE);
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);
@ -217,7 +225,9 @@ void Instance::object_sync(Object *ob)
break;
case OB_POINTCLOUD:
sync.sync_point_cloud(ob, ob_handle, res_handle, ob_ref);
break;
case OB_VOLUME:
volume.sync_object(ob, ob_handle, res_handle);
break;
case OB_CURVES:
sync.sync_curves(ob, ob_handle, res_handle);
@ -259,6 +269,7 @@ void Instance::end_sync()
pipelines.end_sync();
light_probes.end_sync();
reflection_probes.end_sync();
volume.end_sync();
}
void Instance::render_sync()

View File

@ -38,6 +38,7 @@
#include "eevee_subsurface.hh"
#include "eevee_sync.hh"
#include "eevee_view.hh"
#include "eevee_volume.hh"
#include "eevee_world.hh"
namespace blender::eevee {
@ -77,6 +78,7 @@ class Instance {
LookdevModule lookdev;
LightProbeModule light_probes;
IrradianceCache irradiance_cache;
VolumeModule volume;
/** Input data. */
Depsgraph *depsgraph;
@ -131,7 +133,8 @@ class Instance {
world(*this),
lookdev(*this),
light_probes(*this),
irradiance_cache(*this){};
irradiance_cache(*this),
volume(*this){};
~Instance(){};
/* Render & Viewport. */

View File

@ -176,8 +176,8 @@ MaterialPass MaterialModule::material_pass_get(Object *ob,
break;
case GPU_MAT_QUEUED:
queued_shaders_count++;
blender_mat = (geometry_type == MAT_GEOM_VOLUME) ? BKE_material_default_volume() :
BKE_material_default_surface();
blender_mat = (geometry_type == MAT_GEOM_VOLUME_OBJECT) ? BKE_material_default_volume() :
BKE_material_default_surface();
matpass.gpumat = inst_.shaders.material_shader_get(
blender_mat, blender_mat->nodetree, pipeline_type, geometry_type, false);
break;
@ -233,6 +233,15 @@ Material &MaterialModule::material_sync(Object *ob,
eMaterialGeometry geometry_type,
bool has_motion)
{
if (geometry_type == MAT_GEOM_VOLUME_OBJECT) {
MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME);
return material_map_.lookup_or_add_cb(material_key, [&]() {
Material mat = {};
mat.volume = material_pass_get(ob, blender_mat, MAT_PIPE_VOLUME, MAT_GEOM_VOLUME_OBJECT);
return mat;
});
}
eMaterialPipeline surface_pipe = (blender_mat->blend_method == MA_BM_BLEND) ? MAT_PIPE_FORWARD :
MAT_PIPE_DEFERRED;
eMaterialPipeline prepass_pipe = (blender_mat->blend_method == MA_BM_BLEND) ?
@ -253,6 +262,7 @@ Material &MaterialModule::material_sync(Object *ob,
mat.capture = material_pass_get(ob, blender_mat, MAT_PIPE_CAPTURE, geometry_type);
mat.probe_prepass = MaterialPass();
mat.probe_shading = MaterialPass();
mat.volume = MaterialPass();
}
else {
/* Order is important for transparent. */
@ -268,6 +278,13 @@ Material &MaterialModule::material_sync(Object *ob,
mat.probe_shading = material_pass_get(
ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, true);
}
if (GPU_material_has_volume_output(mat.shading.gpumat)) {
mat.volume = material_pass_get(ob, blender_mat, MAT_PIPE_VOLUME, MAT_GEOM_VOLUME_OBJECT);
}
else {
mat.volume = MaterialPass();
}
}
if (blender_mat->blend_shadow == MA_BS_NONE) {
@ -276,6 +293,7 @@ Material &MaterialModule::material_sync(Object *ob,
else {
mat.shadow = material_pass_get(ob, blender_mat, MAT_PIPE_SHADOW, geometry_type);
}
mat.is_alpha_blend_transparent = (blender_mat->blend_method == MA_BM_BLEND) &&
GPU_material_flag_get(mat.shading.gpumat,
GPU_MATFLAG_TRANSPARENT);

View File

@ -42,7 +42,8 @@ enum eMaterialGeometry {
MAT_GEOM_POINT_CLOUD,
MAT_GEOM_CURVES,
MAT_GEOM_GPENCIL,
MAT_GEOM_VOLUME,
MAT_GEOM_VOLUME_OBJECT,
MAT_GEOM_VOLUME_WORLD,
MAT_GEOM_WORLD,
};
@ -100,7 +101,7 @@ static inline eMaterialGeometry to_material_geometry(const Object *ob)
case OB_CURVES:
return MAT_GEOM_CURVES;
case OB_VOLUME:
return MAT_GEOM_VOLUME;
return MAT_GEOM_VOLUME_OBJECT;
case OB_GPENCIL_LEGACY:
return MAT_GEOM_GPENCIL;
case OB_POINTCLOUD:
@ -219,7 +220,7 @@ struct MaterialPass {
struct Material {
bool is_alpha_blend_transparent;
MaterialPass shadow, shading, prepass, capture, probe_prepass, probe_shading;
MaterialPass shadow, shading, prepass, capture, probe_prepass, probe_shading, volume;
};
struct MaterialArray {

View File

@ -14,6 +14,8 @@
#include "eevee_pipeline.hh"
#include "draw_common.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
@ -107,6 +109,38 @@ void WorldPipeline::render(View &view)
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Volume Pipeline
*
* \{ */
void WorldVolumePipeline::sync(GPUMaterial *gpumat)
{
world_ps_.init();
world_ps_.state_set(DRW_STATE_WRITE_COLOR);
inst_.volume.bind_properties_buffers(world_ps_);
inst_.sampling.bind_resources(&world_ps_);
pragma37 marked this conversation as resolved

Why do you need light and shadows here? the computation is not done at this stage.

Why do you need light and shadows here? the computation is not done at this stage.
if (GPU_material_status(gpumat) != GPU_MAT_SUCCESS) {
/* Skip if the material has not compiled yet. */
return;
}
world_ps_.material_set(*inst_.manager, gpumat);
volume_sub_pass(world_ps_, nullptr, nullptr, gpumat);
world_ps_.dispatch(math::divide_ceil(inst_.volume.grid_size(), int3(VOLUME_GROUP_SIZE)));
/* Sync with object property pass. */
world_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
void WorldVolumePipeline::render(View &view)
{
inst_.manager->submit(world_ps_, view);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pipeline
*
@ -229,6 +263,7 @@ void ForwardPipeline::sync()
inst_.lights.bind_resources(&sub);
inst_.shadows.bind_resources(&sub);
inst_.volume.bind_resources(sub);
inst_.sampling.bind_resources(&sub);
inst_.hiz_buffer.bind_resources(&sub);
inst_.ambient_occlusion.bind_resources(&sub);
@ -292,11 +327,9 @@ void ForwardPipeline::render(View &view,
Framebuffer &combined_fb,
GPUTexture * /*combined_tx*/)
{
UNUSED_VARS(view);
DRW_stats_group_start("Forward.Opaque");
GPU_framebuffer_bind(prepass_fb);
prepass_fb.bind();
inst_.manager->submit(prepass_ps_, view);
// if (!DRW_pass_is_empty(prepass_ps_)) {
@ -311,11 +344,13 @@ void ForwardPipeline::render(View &view,
inst_.shadows.set_view(view);
inst_.irradiance_cache.set_view(view);
GPU_framebuffer_bind(combined_fb);
combined_fb.bind();
inst_.manager->submit(opaque_ps_, view);
DRW_stats_group_end();
inst_.volume.draw_resolve(view);
inst_.manager->submit(transparent_ps_, view);
// if (inst_.raytracing.enabled()) {
@ -495,7 +530,8 @@ void DeferredLayer::render(View &main_view,
rt_buffer, closure_bits_, CLOSURE_REFLECTION, main_view, render_view);
indirect_reflection_tx_ = reflect_result.get();
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE |
GPU_TEXTURE_USAGE_ATTACHMENT;
diffuse_light_tx_.acquire(extent, GPU_RGBA16F, usage);
diffuse_light_tx_.clear(float4(0.0f));
specular_light_tx_.acquire(extent, GPU_RGBA16F, usage);
@ -579,6 +615,31 @@ void DeferredPipeline::render(View &main_view,
/** \} */
/* -------------------------------------------------------------------- */
/** \name Volume Pipeline
*
* \{ */
void VolumePipeline::sync()
{
volume_ps_.init();
volume_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
inst_.volume.bind_properties_buffers(volume_ps_);
inst_.sampling.bind_resources(&volume_ps_);
}
PassMain::Sub *VolumePipeline::volume_material_add(GPUMaterial *gpumat)
{
return &volume_ps_.sub(GPU_material_get_name(gpumat));
}
void VolumePipeline::render(View &view)
{
inst_.manager->submit(volume_ps_, view);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Deferred Probe Layer
* \{ */

View File

@ -72,6 +72,26 @@ class WorldPipeline {
/** \} */
/* -------------------------------------------------------------------- */
/** \name World Volume Pipeline
*
* \{ */
class WorldVolumePipeline {
private:
Instance &inst_;
PassSimple world_ps_ = {"World.Volume"};
public:
WorldVolumePipeline(Instance &inst) : inst_(inst){};
void sync(GPUMaterial *gpumat);
void render(View &view);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shadow Pass
*
@ -226,6 +246,28 @@ class DeferredPipeline {
/** \} */
/* -------------------------------------------------------------------- */
/** \name Volume Pass
*
* \{ */
class VolumePipeline {
private:
Instance &inst_;
PassMain volume_ps_ = {"Volume.Objects"};
public:
VolumePipeline(Instance &inst) : inst_(inst){};
PassMain::Sub *volume_material_add(GPUMaterial *gpumat);
void sync();
void render(View &view);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Deferred Probe Capture.
* \{ */
@ -385,10 +427,12 @@ class PipelineModule {
public:
BackgroundPipeline background;
WorldPipeline world;
WorldVolumePipeline world_volume;
DeferredProbePipeline probe;
DeferredPipeline deferred;
ForwardPipeline forward;
ShadowPipeline shadow;
VolumePipeline volume;
CapturePipeline capture;
UtilityTexture utility_tx;
@ -397,10 +441,12 @@ class PipelineModule {
PipelineModule(Instance &inst)
: background(inst),
world(inst),
world_volume(inst),
probe(inst),
deferred(inst),
forward(inst),
shadow(inst),
volume(inst),
capture(inst){};
void begin_sync()
@ -409,6 +455,7 @@ class PipelineModule {
deferred.begin_sync();
forward.sync();
shadow.sync();
volume.sync();
capture.sync();
}
@ -459,10 +506,8 @@ class PipelineModule {
return forward.material_transparent_add(ob, blender_mat, gpumat);
}
return forward.material_opaque_add(blender_mat, gpumat);
case MAT_PIPE_VOLUME:
/* TODO(fclem) volume pass. */
return nullptr;
return volume.volume_material_add(gpumat);
case MAT_PIPE_SHADOW:
return shadow.surface_material_add(gpumat);
case MAT_PIPE_CAPTURE:

View File

@ -150,6 +150,10 @@ void Sampling::step()
data_.dimensions[SAMPLING_RAYTRACE_U] = r[0];
data_.dimensions[SAMPLING_RAYTRACE_V] = r[1];
data_.dimensions[SAMPLING_RAYTRACE_W] = r[2];
/* TODO de-correlate. */
data_.dimensions[SAMPLING_VOLUME_U] = r[0];
data_.dimensions[SAMPLING_VOLUME_V] = r[1];
data_.dimensions[SAMPLING_VOLUME_W] = r[2];
}
{
/* Using leaped Halton sequence so we can reused the same primes. */

View File

@ -228,6 +228,14 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_surfel_list_sort";
case SURFEL_RAY:
return "eevee_surfel_ray";
case VOLUME_INTEGRATION:
return "eevee_volume_integration";
case VOLUME_RESOLVE:
return "eevee_volume_resolve";
case VOLUME_SCATTER:
return "eevee_volume_scatter";
case VOLUME_SCATTER_WITH_LIGHTS:
return "eevee_volume_scatter_with_lights";
/* To avoid compiler warning about missing case. */
case MAX_SHADER_TYPE:
return "";
@ -387,15 +395,20 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
}
info.vertex_inputs_.clear();
break;
case MAT_GEOM_VOLUME:
/** No attributes supported. */
case MAT_GEOM_VOLUME_OBJECT:
case MAT_GEOM_VOLUME_WORLD:
/** Volume grid attributes come from 3D textures. Transfer attributes to samplers. */
for (auto &input : info.vertex_inputs_) {
info.sampler(sampler_slot--, ImageType::FLOAT_3D, input.name, Frequency::BATCH);
}
info.vertex_inputs_.clear();
break;
}
const bool do_fragment_attrib_load = (geometry_type == MAT_GEOM_WORLD);
const bool do_vertex_attrib_load = !ELEM(
pragma37 marked this conversation as resolved

Having the default case be the compute one is a bit weird. Maybe have do_compute_attrib_load = (pipeline_type == MAT_PIPE_VOLUME) for clarity and test for this instead.

Having the default case be the compute one is a bit weird. Maybe have `do_compute_attrib_load = (pipeline_type == MAT_PIPE_VOLUME)` for clarity and test for this instead.
geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_VOLUME_OBJECT);
if (do_fragment_attrib_load && !info.vertex_out_interfaces_.is_empty()) {
if (!do_vertex_attrib_load && !info.vertex_out_interfaces_.is_empty()) {
/* Codegen outputs only one interface. */
const StageInterfaceInfo &iface = *info.vertex_out_interfaces_.first();
/* Globals the attrib_load() can write to when it is in the fragment shader. */
@ -415,17 +428,22 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
attr_load << ((codegen.attr_load) ? codegen.attr_load : "");
attr_load << "}\n\n";
std::stringstream vert_gen, frag_gen;
std::stringstream vert_gen, frag_gen, comp_gen;
if (do_fragment_attrib_load) {
bool is_compute = pipeline_type == MAT_PIPE_VOLUME;
if (do_vertex_attrib_load) {
vert_gen << global_vars.str() << attr_load.str();
}
else if (!is_compute) {
frag_gen << global_vars.str() << attr_load.str();
}
else {
vert_gen << global_vars.str() << attr_load.str();
comp_gen << global_vars.str() << attr_load.str();
}
{
if (!ELEM(geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME)) {
if (!is_compute) {
if (!ELEM(geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_VOLUME_OBJECT)) {
vert_gen << "vec3 nodetree_displacement()\n";
vert_gen << "{\n";
vert_gen << ((codegen.displacement) ? codegen.displacement : "return vec3(0);\n");
@ -435,7 +453,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
info.vertex_source_generated = vert_gen.str();
}
{
if (!is_compute) {
frag_gen << ((codegen.material_functions) ? codegen.material_functions : "\n");
if (codegen.displacement) {
@ -454,12 +472,6 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
frag_gen << ((codegen.surface) ? codegen.surface : "return Closure(0);\n");
frag_gen << "}\n\n";
frag_gen << "Closure nodetree_volume()\n";
frag_gen << "{\n";
frag_gen << " closure_weights_reset();\n";
frag_gen << ((codegen.volume) ? codegen.volume : "return Closure(0);\n");
frag_gen << "}\n\n";
frag_gen << "float nodetree_thickness()\n";
frag_gen << "{\n";
/* TODO(fclem): Better default. */
@ -469,13 +481,28 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
info.fragment_source_generated = frag_gen.str();
}
if (is_compute) {
comp_gen << ((codegen.material_functions) ? codegen.material_functions : "\n");
comp_gen << "Closure nodetree_volume()\n";
comp_gen << "{\n";
comp_gen << " closure_weights_reset();\n";
comp_gen << ((codegen.volume) ? codegen.volume : "return Closure(0);\n");
comp_gen << "}\n\n";
info.compute_source_generated = comp_gen.str();
}
/* Geometry Info. */
switch (geometry_type) {
case MAT_GEOM_WORLD:
info.additional_info("eevee_geom_world");
break;
case MAT_GEOM_VOLUME:
info.additional_info("eevee_geom_volume");
case MAT_GEOM_VOLUME_WORLD:
info.additional_info("eevee_volume_world");
break;
case MAT_GEOM_VOLUME_OBJECT:
info.additional_info("eevee_volume_object");
break;
case MAT_GEOM_GPENCIL:
info.additional_info("eevee_geom_gpencil");
@ -490,13 +517,13 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
info.additional_info("eevee_geom_point_cloud");
break;
}
/* Pipeline Info. */
switch (geometry_type) {
case MAT_GEOM_WORLD:
info.additional_info("eevee_surf_world");
break;
case MAT_GEOM_VOLUME:
case MAT_GEOM_VOLUME_OBJECT:
case MAT_GEOM_VOLUME_WORLD:
break;
default:
switch (pipeline_type) {
@ -541,31 +568,27 @@ GPUMaterial *ShaderModule::material_shader_get(::Material *blender_mat,
eMaterialGeometry geometry_type,
bool deferred_compilation)
{
uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type);
bool is_volume = (pipeline_type == MAT_PIPE_VOLUME);
uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type);
return DRW_shader_from_material(
blender_mat, nodetree, shader_uuid, is_volume, deferred_compilation, codegen_callback, this);
}
GPUMaterial *ShaderModule::world_shader_get(::World *blender_world, bNodeTree *nodetree)
GPUMaterial *ShaderModule::world_shader_get(::World *blender_world,
struct bNodeTree *nodetree,
eMaterialPipeline pipeline_type)
{
eMaterialPipeline pipeline_type = MAT_PIPE_DEFERRED; /* Unused. */
eMaterialGeometry geometry_type = MAT_GEOM_WORLD;
bool is_volume = (pipeline_type == MAT_PIPE_VOLUME);
bool defer_compilation = is_volume;
eMaterialGeometry geometry_type = is_volume ? MAT_GEOM_VOLUME_WORLD : MAT_GEOM_WORLD;
uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type);
bool is_volume = (pipeline_type == MAT_PIPE_VOLUME);
bool deferred_compilation = false;
return DRW_shader_from_world(blender_world,
nodetree,
shader_uuid,
is_volume,
deferred_compilation,
codegen_callback,
this);
return DRW_shader_from_world(
blender_world, nodetree, shader_uuid, is_volume, defer_compilation, codegen_callback, this);
}
/* Variation to compile a material only with a nodetree. Caller needs to maintain the list of

View File

@ -115,6 +115,11 @@ enum eShaderType {
SURFEL_LIST_SORT,
SURFEL_RAY,
VOLUME_INTEGRATION,
VOLUME_RESOLVE,
VOLUME_SCATTER,
VOLUME_SCATTER_WITH_LIGHTS,
MAX_SHADER_TYPE,
};
@ -138,7 +143,9 @@ class ShaderModule {
eMaterialPipeline pipeline_type,
eMaterialGeometry geometry_type,
bool deferred_compilation);
GPUMaterial *world_shader_get(::World *blender_world, bNodeTree *nodetree);
GPUMaterial *world_shader_get(::World *blender_world,
bNodeTree *nodetree,
eMaterialPipeline pipeline_type);
GPUMaterial *material_shader_get(const char *name,
ListBase &materials,
bNodeTree *nodetree,

View File

@ -107,13 +107,16 @@ enum eSamplingDimension : uint32_t {
SAMPLING_AO_U = 19u,
SAMPLING_AO_V = 20u,
SAMPLING_CURVES_U = 21u,
SAMPLING_VOLUME_U = 22u,
SAMPLING_VOLUME_V = 23u,
SAMPLING_VOLUME_W = 24u
};
/**
* IMPORTANT: Make sure the array can contain all sampling dimensions.
* Also note that it needs to be multiple of 4.
*/
#define SAMPLING_DIMENSION_COUNT 24
#define SAMPLING_DIMENSION_COUNT 28
/* NOTE(@fclem): Needs to be used in #StorageBuffer because of arrays of scalar. */
struct SamplingData {
@ -444,6 +447,81 @@ BLI_STATIC_ASSERT_ALIGN(MotionBlurTileIndirection, 16)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Volumes
* \{ */
struct VolumesInfoData {
float2 coord_scale;
float2 viewport_size_inv;
pragma37 marked this conversation as resolved Outdated

Use bool1 for booleans in shared GLSL structs.

Use `bool1` for booleans in shared GLSL structs.
packed_int3 tex_size;
float light_clamp;
packed_float3 inv_tex_size;
float shadow_steps;
bool1 use_lights;
bool1 use_soft_shadows;
float depth_near;
float depth_far;
float depth_distribution;
float _pad0;
float _pad1;
float _pad2;
};
BLI_STATIC_ASSERT_ALIGN(VolumesInfoData, 16)
/* Volume slice to view space depth. */
static inline float volume_z_to_view_z(
float near, float far, float distribution, bool is_persp, float z)
{
if (is_persp) {
fclem marked this conversation as resolved

Rename to volume_view_z_to_froxel_z, fits module prefix.

Rename to `volume_view_z_to_froxel_z`, fits module prefix.
Review

I think this would be misleading since the result is in the 0-1 range.
If you want to use the volume prefix here too, I think we should use a more accurate term.

Since this is pretty much NDC with a different distribution and range, maybe we could use NVC and add a comment explaining this stands for “Normalized Volume Coordinates” and explain what they are?

I think this would be misleading since the result is in the 0-1 range. If you want to use the volume prefix here too, I think we should use a more accurate term. Since this is pretty much `NDC` with a different distribution and range, maybe we could use `NVC` and add a comment explaining this stands for “Normalized Volume Coordinates” and explain what they are?
/* Exponential distribution. */
return (exp2(z / distribution) - near) / far;
}
else {
/* Linear distribution. */
return near + (far - near) * z;
}
}
static inline float view_z_to_volume_z(
float near, float far, float distribution, bool is_persp, float depth)
{
if (is_persp) {
/* Exponential distribution. */
return distribution * log2(depth * far + near);
}
else {
/* Linear distribution. */
return (depth - near) * distribution;
}
}
static inline float3 ndc_to_volume(float4x4 projection_matrix,
float near,
float far,
float distribution,
float2 coord_scale,
float3 coord)
{
bool is_persp = projection_matrix[3][3] == 0.0;
/* get_view_z_from_depth */
float d = 2.0 * coord.z - 1.0;
if (is_persp) {
coord.z = -projection_matrix[3][2] / (d + projection_matrix[2][2]);
}
else {
coord.z = (d - projection_matrix[3][2]) / projection_matrix[2][2];
}
coord.z = view_z_to_volume_z(near, far, distribution, is_persp, coord.z);
coord.x *= coord_scale.x;
coord.y *= coord_scale.y;
return coord;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Depth of field
* \{ */
@ -1189,6 +1267,7 @@ using SurfelListInfoBuf = draw::StorageBuffer<SurfelListInfoData>;
using VelocityGeometryBuf = draw::StorageArrayBuffer<float4, 16, true>;
using VelocityIndexBuf = draw::StorageArrayBuffer<VelocityIndex, 16>;
using VelocityObjectBuf = draw::StorageArrayBuffer<float4x4, 16>;
using VolumesInfoDataBuf = draw::UniformBuffer<VolumesInfoData>;
using CryptomatteObjectBuf = draw::StorageArrayBuffer<float2, 16>;
using AODataBuf = draw::UniformBuffer<AOData>;

View File

@ -50,6 +50,8 @@ ObjectHandle &SyncModule::sync_object(Object *ob)
const int recalc_flags = ID_RECALC_COPY_ON_WRITE | ID_RECALC_TRANSFORM | ID_RECALC_SHADING |
ID_RECALC_GEOMETRY;
if ((eevee_dd.recalc & recalc_flags) != 0) {
/** WARNING: Some objects are always created "on the fly" (ie. Geometry Nodes volumes),
* so this causes to redraw the sample 1 forever. */
inst_.sampling.reset();
}
@ -122,6 +124,13 @@ void SyncModule::sync_mesh(Object *ob,
return;
}
if ((ob->dt < OB_SOLID) && !DRW_state_is_scene_render()) {
/** NOTE:
* EEVEE doesn't render meshes with bounds or wire display type in the viewport,
* but Cycles does. */
return;
}
bool is_shadow_caster = false;
bool is_alpha_blend = false;
bool do_probe_sync = inst_.do_probe_sync();
@ -130,7 +139,20 @@ void SyncModule::sync_mesh(Object *ob,
if (geom == nullptr) {
continue;
}
Material &material = material_array.materials[i];
GPUMaterial *gpu_material = material_array.gpu_materials[i];
if (material.volume.gpumat && i == 0) {
/* Only support single volume material for now. */
inst_.volume.sync_object(ob, ob_handle, res_handle, &material.volume);
/* Do not render surface if we are rendering a volume object
* and do not have a surface closure. */
if (gpu_material && !GPU_material_has_surface_output(gpu_material)) {
continue;
}
}
geometry_call(material.shading.sub_pass, geom, res_handle);
geometry_call(material.prepass.sub_pass, geom, res_handle);
geometry_call(material.shadow.sub_pass, geom, res_handle);
@ -143,7 +165,6 @@ void SyncModule::sync_mesh(Object *ob,
is_shadow_caster = is_shadow_caster || material.shadow.sub_pass != nullptr;
is_alpha_blend = is_alpha_blend || material.is_alpha_blend_transparent;
GPUMaterial *gpu_material = material_array.gpu_materials[i];
::Material *mat = GPU_material_get_material(gpu_material);
inst_.cryptomatte.sync_material(mat);
}

View File

@ -122,6 +122,9 @@ void ShadingView::render()
/* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */
inst_.lights.set_view(render_view_new_, extent_);
inst_.volume.draw_compute(render_view_new_);
/* TODO: cleanup. */
View main_view_new("MainView", main_view_);
/* TODO(Miguel Pozo): Deferred and forward prepass should happen before the GBuffer pass. */

View File

@ -0,0 +1,323 @@
/* 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 "DNA_volume_types.h"
#include "GPU_capabilities.h"
#include "draw_common.hh"
#include "eevee_instance.hh"
#include "eevee_pipeline.hh"
#include "eevee_volume.hh"
namespace blender::eevee {
bool VolumeModule::GridAABB::init(Object *ob, const Camera &camera, const VolumesInfoDataBuf &data)
{
/* Returns the unified volume grid cell index of a world space coordinate. */
auto to_global_grid_coords = [&](float3 wP) -> int3 {
const float4x4 &view_matrix = camera.data_get().viewmat;
const float4x4 &projection_matrix = camera.data_get().winmat;
float3 ndc_coords = math::project_point(projection_matrix * view_matrix, wP);
ndc_coords = (ndc_coords * 0.5f) + float3(0.5f);
float3 grid_coords = ndc_to_volume(projection_matrix,
data.depth_near,
data.depth_far,
data.depth_distribution,
data.coord_scale,
ndc_coords);
return int3(grid_coords * float3(data.tex_size));
};
const BoundBox &bbox = *BKE_object_boundbox_get(ob);
min = int3(INT32_MAX);
max = int3(INT32_MIN);
for (float3 corner : bbox.vec) {
corner = math::transform_point(float4x4(ob->object_to_world), corner);
int3 grid_coord = to_global_grid_coords(corner);
min = math::min(min, grid_coord);
max = math::max(max, grid_coord);
}
bool is_visible = false;
for (int i : IndexRange(3)) {
is_visible = is_visible || (min[i] >= 0 && min[i] < data.tex_size[i]);
is_visible = is_visible || (max[i] >= 0 && max[i] < data.tex_size[i]);
}
min = math::clamp(min, int3(0), data.tex_size);
max = math::clamp(max, int3(0), data.tex_size);
pragma37 marked this conversation as resolved Outdated

We shouldn't need any of this with the shared GLSL/C++ code. Maybe it's a leftover comment?

We shouldn't need any of this with the shared GLSL/C++ code. Maybe it's a leftover comment?

The implementation is not fully shared since the way to retrieve the projection matrix and the coordinate scale is different.

The implementation is not fully shared since the way to retrieve the projection matrix and the coordinate scale is different.

I would say the projection matrix should be part of VolumesInfoData (as we will need one for TAA anyway) and maybe you can fold a bit of the math into it.

This way you can share more of the code.

I would say the projection matrix should be part of `VolumesInfoData` (as we will need one for TAA anyway) and maybe you can fold a bit of the math into it. This way you can share more of the code.
return is_visible;
}
bool VolumeModule::GridAABB::overlaps(const GridAABB &aabb)
{
for (int i : IndexRange(3)) {
if (min[i] > aabb.max[i] || max[i] < aabb.min[i]) {
return false;
}
}
return true;
}
void VolumeModule::init()
{
enabled_ = false;
subpass_aabbs_.clear();
const Scene *scene_eval = inst_.scene;
const float2 viewport_size = float2(inst_.film.render_extent_get());
const int tile_size = scene_eval->eevee.volumetric_tile_size;
/* Find Froxel Texture resolution. */
int3 tex_size = int3(math::ceil(math::max(float2(1.0f), viewport_size / float(tile_size))), 0);
tex_size.z = std::max(1, scene_eval->eevee.volumetric_samples);
/* Clamp 3D texture size based on device maximum. */
int3 max_size = int3(GPU_max_texture_3d_size());
BLI_assert(tex_size == math::min(tex_size, max_size));
tex_size = math::min(tex_size, max_size);
data_.coord_scale = viewport_size / float2(tile_size * tex_size);
data_.viewport_size_inv = 1.0f / viewport_size;
/* TODO: compute snap to maxZBuffer for clustered rendering. */
if (data_.tex_size != tex_size) {
data_.tex_size = tex_size;
data_.inv_tex_size = 1.0f / float3(tex_size);
}
if ((scene_eval->eevee.flag & SCE_EEVEE_VOLUMETRIC_SHADOWS) == 0) {
data_.shadow_steps = 0;
}
else {
data_.shadow_steps = float(scene_eval->eevee.volumetric_shadow_samples);
}
data_.use_lights = (scene_eval->eevee.flag & SCE_EEVEE_VOLUMETRIC_LIGHTS) != 0;
data_.use_soft_shadows = (scene_eval->eevee.flag & SCE_EEVEE_SHADOW_SOFT) != 0;
data_.light_clamp = scene_eval->eevee.volumetric_light_clamp;
}
void VolumeModule::begin_sync()
{
const Scene *scene_eval = inst_.scene;
/* Negate clip values (View matrix forward vector is -Z). */
const float clip_start = -inst_.camera.data_get().clip_near;
const float clip_end = -inst_.camera.data_get().clip_far;
float integration_start = scene_eval->eevee.volumetric_start;
float integration_end = scene_eval->eevee.volumetric_end;
if (inst_.camera.is_perspective()) {
float sample_distribution = scene_eval->eevee.volumetric_sample_distribution;
sample_distribution = 4.0f * std::max(1.0f - sample_distribution, 1e-2f);
float near = integration_start = std::min(-integration_start, clip_start - 1e-4f);
float far = integration_end = std::min(-integration_end, near - 1e-4f);
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 {
integration_start = std::min(integration_end, clip_start);
integration_end = std::max(-integration_end, clip_end);
data_.depth_near = integration_start;
data_.depth_far = integration_end;
data_.depth_distribution = 1.0f / (integration_end - integration_start);
}
data_.push_update();
enabled_ = inst_.world.has_volume();
}
void VolumeModule::sync_object(Object *ob,
ObjectHandle & /*ob_handle*/,
ResourceHandle res_handle,
MaterialPass *material_pass /*= nullptr*/)
{
float3 size = math::to_scale(float4x4(ob->object_to_world));
/* Check if any of the axes have 0 length. (see #69070) */
const float epsilon = 1e-8f;
if (size.x < epsilon || size.y < epsilon || size.z < epsilon) {
return;
}
if (material_pass == nullptr) {
Material material = inst_.materials.material_get(
ob, false, VOLUME_MATERIAL_NR, MAT_GEOM_VOLUME_OBJECT);
material_pass = &material.volume;
}
/* If shader failed to compile or is currently compiling. */
if (material_pass->gpumat == nullptr) {
return;
}
GPUShader *shader = GPU_material_get_shader(material_pass->gpumat);
if (shader == nullptr) {
return;
}
GridAABB aabb;
if (!aabb.init(ob, inst_.camera, data_)) {
return;
}
PassMain::Sub *object_pass = volume_sub_pass(
*material_pass->sub_pass, inst_.scene, ob, material_pass->gpumat);
if (object_pass) {
enabled_ = true;
/* Add a barrier at the start of a subpass or when 2 volumes overlaps. */
if (!subpass_aabbs_.contains_as(shader)) {
object_pass->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
subpass_aabbs_.add(shader, {aabb});
}
else {
Vector<GridAABB> &aabbs = subpass_aabbs_.lookup(shader);
for (GridAABB &_aabb : aabbs) {
if (aabb.overlaps(_aabb)) {
object_pass->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
aabbs.clear();
break;
}
}
aabbs.append(aabb);
}
int3 grid_size = aabb.max - aabb.min + int3(1);
pragma37 marked this conversation as resolved Outdated

You can simplify to math::to_scale(float4x4(ob->object_to_world)). This does exactly the same thing without the intermediate copy.

You can simplify to `math::to_scale(float4x4(ob->object_to_world))`. This does exactly the same thing without the intermediate copy.
object_pass->push_constant("drw_ResourceID", int(res_handle.resource_index()));
object_pass->push_constant("grid_coords_min", aabb.min);
object_pass->dispatch(math::divide_ceil(grid_size, int3(VOLUME_GROUP_SIZE)));
}
}
void VolumeModule::end_sync()
{
if (!enabled_) {
prop_scattering_tx_.free();
prop_extinction_tx_.free();
prop_emission_tx_.free();
prop_phase_tx_.free();
scatter_tx_.free();
extinction_tx_.free();
integrated_scatter_tx_.free();
integrated_transmit_tx_.free();
transparent_pass_scatter_tx_ = dummy_scatter_tx_;
transparent_pass_transmit_tx_ = dummy_transmit_tx_;
return;
}
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE |
GPU_TEXTURE_USAGE_ATTACHMENT;
pragma37 marked this conversation as resolved Outdated

Code-style: Avoid ps as name. Use more descriptive name like object_volume_pass.

Code-style: Avoid `ps` as name. Use more descriptive name like `object_volume_pass`.
prop_scattering_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage);
prop_extinction_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage);
prop_emission_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage);
prop_phase_tx_.ensure_3d(GPU_RG16F, data_.tex_size, usage);
scatter_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage);
extinction_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage);
integrated_scatter_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage);
integrated_transmit_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage);
transparent_pass_scatter_tx_ = integrated_scatter_tx_;
transparent_pass_transmit_tx_ = integrated_transmit_tx_;
scatter_ps_.init();
scatter_ps_.shader_set(inst_.shaders.static_shader_get(
data_.use_lights ? VOLUME_SCATTER_WITH_LIGHTS : VOLUME_SCATTER));
inst_.lights.bind_resources(&scatter_ps_);
inst_.shadows.bind_resources(&scatter_ps_);
inst_.sampling.bind_resources(&scatter_ps_);
scatter_ps_.bind_image("in_scattering_img", &prop_scattering_tx_);
scatter_ps_.bind_image("in_extinction_img", &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("out_scattering_img", &scatter_tx_);
scatter_ps_.bind_image("out_extinction_img", &extinction_tx_);
/* Sync with the property pass. */
scatter_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
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_ubo(VOLUMES_INFO_BUF_SLOT, data_);
integration_ps_.bind_texture("in_scattering_tx", &scatter_tx_);
integration_ps_.bind_texture("in_extinction_tx", &extinction_tx_);
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));
bind_resources(resolve_ps_);
resolve_ps_.bind_texture("depth_tx", &inst_.render_buffers.depth_tx);
resolve_ps_.bind_ubo(RBUFS_BUF_SLOT, &inst_.render_buffers.data);
resolve_ps_.bind_image(RBUFS_COLOR_SLOT, &inst_.render_buffers.rp_color_tx);
/* Sync with the integration pass. */
resolve_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH);
resolve_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
void VolumeModule::draw_compute(View &view)
{
if (!enabled_) {
return;
}
DRW_stats_group_start("Volumes");
inst_.pipelines.world_volume.render(view);
inst_.pipelines.volume.render(view);
inst_.manager->submit(scatter_ps_, view);
inst_.manager->submit(integration_ps_, view);
DRW_stats_group_end();
}
void VolumeModule::draw_resolve(View &view)
{
if (!enabled_) {
return;
}
resolve_fb_.ensure(GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE(inst_.render_buffers.combined_tx));
resolve_fb_.bind();
inst_.manager->submit(resolve_ps_, view);
}
} // namespace blender::eevee
/** \} */

View File

@ -0,0 +1,140 @@
/* 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 "eevee_shader_shared.hh"
namespace blender::eevee {
class Instance;
class VolumeModule {
private:
Instance &inst_;
bool enabled_;
VolumesInfoDataBuf data_;
/* Material Parameters */
Texture prop_scattering_tx_;
Texture prop_extinction_tx_;
Texture prop_emission_tx_;
Texture prop_phase_tx_;
/* Light Scattering. */
PassSimple scatter_ps_ = {"Volumes.Scatter"};
Texture scatter_tx_;
Texture extinction_tx_;
pragma37 marked this conversation as resolved Outdated

Uneeded like the others framebuffers in this file.

Uneeded like the others framebuffers in this file.
/* 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_;
/* Used in the forward transparent pass (ForwardPipeline).
* The forward transparent pass must perform its own resolve step for correct composition between
* volumes and transparent surfaces. */
GPUTexture *transparent_pass_scatter_tx_;
GPUTexture *transparent_pass_transmit_tx_;
Texture dummy_scatter_tx_;
Texture dummy_transmit_tx_;
/* Axis aligned bounding box in the volume grid.
* Used for frustum culling and volumes overlapping detection. */
struct GridAABB {
int3 min, max;
/* Returns true if visible. */
bool init(Object *ob, const Camera &camera, const VolumesInfoDataBuf &data);
bool overlaps(const GridAABB &aabb);
};
/* Stores a vector of volume AABBs for each material pass,
* so we can detect overlapping volumes and place GPU barriers where needed
* (Only stores the AABBs for the volumes rendered since the last barrier). */
Map<GPUShader *, Vector<GridAABB>> subpass_aabbs_;
public:
VolumeModule(Instance &inst) : inst_(inst)
{
dummy_scatter_tx_.ensure_3d(GPU_RGBA8, int3(1), GPU_TEXTURE_USAGE_SHADER_READ, float4(0.0f));
dummy_transmit_tx_.ensure_3d(GPU_RGBA8, int3(1), GPU_TEXTURE_USAGE_SHADER_READ, float4(1.0f));
};
~VolumeModule(){};
/* Bind resources needed by external passes to perform their own resolve. */
template<typename PassType> void bind_resources(PassType &ps)
{
ps.bind_ubo(VOLUMES_INFO_BUF_SLOT, data_);
ps.bind_texture(VOLUME_SCATTERING_TEX_SLOT, &transparent_pass_scatter_tx_);
ps.bind_texture(VOLUME_TRANSMITTANCE_TEX_SLOT, &transparent_pass_transmit_tx_);
}
/* Bind the common resources needed by all volumetric passes. */
template<typename PassType> void bind_properties_buffers(PassType &ps)
{
ps.bind_ubo(VOLUMES_INFO_BUF_SLOT, &data_);
ps.bind_image(VOLUME_PROP_SCATTERING_IMG_SLOT, &prop_scattering_tx_);
ps.bind_image(VOLUME_PROP_EXTINCTION_IMG_SLOT, &prop_extinction_tx_);
ps.bind_image(VOLUME_PROP_EMISSION_IMG_SLOT, &prop_emission_tx_);
ps.bind_image(VOLUME_PROP_PHASE_IMG_SLOT, &prop_phase_tx_);
}
int3 grid_size()
{
return data_.tex_size;
}
void init();
void begin_sync();
void sync_world();
void sync_object(Object *ob,
ObjectHandle &ob_handle,
ResourceHandle res_handle,
MaterialPass *material_pass = nullptr);
void end_sync();
void draw_compute(View &view);
void draw_resolve(View &view);
};
} // namespace blender::eevee

View File

@ -76,15 +76,29 @@ World::~World()
return default_world_;
}
void World::sync()
void World::world_and_ntree_get(::World *&world, bNodeTree *&ntree)
{
if (inst_.lookdev.sync_world()) {
return;
world = inst_.scene->world;
if (world == nullptr) {
world = default_world_get();
}
::World *bl_world = inst_.scene->world;
if (bl_world == nullptr) {
bl_world = default_world_get();
ntree = (world->nodetree && world->use_nodes && !inst_.use_studio_light()) ?
world->nodetree :
default_tree.nodetree_get(world);
}
void World::sync()
{
::World *bl_world;
bNodeTree *ntree;
world_and_ntree_get(bl_world, ntree);
GPUMaterial *volume_gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME);
inst_.pipelines.world_volume.sync(volume_gpumat);
if (inst_.lookdev.sync_world()) {
return;
}
WorldHandle &wo_handle = inst_.sync.sync_world(bl_world);
@ -100,11 +114,7 @@ void World::sync()
inst_.sampling.reset();
}
bNodeTree *ntree = (bl_world->nodetree && bl_world->use_nodes) ?
bl_world->nodetree :
default_tree.nodetree_get(bl_world);
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree);
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_DEFERRED);
inst_.manager->register_layer_attributes(gpumat);
@ -112,6 +122,16 @@ void World::sync()
inst_.pipelines.world.sync(gpumat);
}
bool World::has_volume()
{
::World *bl_world;
bNodeTree *ntree;
world_and_ntree_get(bl_world, ntree);
GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME);
return GPU_material_has_volume_output(gpumat);
}
/** \} */
} // namespace blender::eevee

View File

@ -49,19 +49,25 @@ class World {
Instance &inst_;
DefaultWorldNodeTree default_tree;
bool has_volume_ = false;
/* Used to detect if world change. */
::World *prev_original_world = nullptr;
/* Used when the scene doesn't have a world. */
::World *default_world_ = nullptr;
::World *default_world_get();
void world_and_ntree_get(::World *&world, bNodeTree *&ntree);
pragma37 marked this conversation as resolved Outdated

The world should call the volume module. not the other way around.

The world should call the volume module. not the other way around.
public:
World(Instance &inst) : inst_(inst){};
~World();
void sync();
bool has_volume();
};
/** \} */

View File

@ -227,7 +227,7 @@ float attr_load_float(samplerBuffer cd_buf)
/** \} */
#elif defined(MAT_GEOM_VOLUME)
#elif defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD)
/* -------------------------------------------------------------------- */
/** \name Volume
@ -236,20 +236,19 @@ float attr_load_float(samplerBuffer cd_buf)
* Per grid transform order is following loading order.
* \{ */
# ifndef OBINFO_LIB
# error draw_object_infos is mandatory for volume objects
# endif
vec3 g_orco;
vec3 g_lP = vec3(0.0);
vec3 g_orco = vec3(0.0);
int g_attr_id = 0;
vec3 grid_coordinates()
{
vec3 co = g_orco;
# ifdef MAT_GEOM_VOLUME_OBJECT
/* Optional per-grid transform. */
if (drw_volume.grids_xform[g_attr_id][3][3] != 0.0) {
co = (drw_volume.grids_xform[g_attr_id] * vec4(objectPosition, 1.0)).xyz;
co = (drw_volume.grids_xform[g_attr_id] * vec4(g_lP, 1.0)).xyz;
}
# endif
g_attr_id += 1;
return co;
}
@ -261,7 +260,7 @@ vec3 attr_load_orco(sampler3D tex)
}
vec4 attr_load_tangent(sampler3D tex)
{
attr_id += 1;
g_attr_id += 1;
return vec4(0);
}
vec4 attr_load_vec4(sampler3D tex)
@ -286,7 +285,7 @@ vec4 attr_load_color(sampler3D tex)
}
vec3 attr_load_uv(sampler3D attr)
{
attr_id += 1;
g_attr_id += 1;
return vec3(0);
}

View File

@ -22,6 +22,35 @@ void light_vector_get(LightData ld, vec3 P, out vec3 L, out float dist)
}
}
/* Light vector to the closest point in the light shape. */
void light_shape_vector_get(LightData ld, vec3 P, out vec3 L, out float dist)
{
if (ld.type == LIGHT_RECT || ld.type == LIGHT_ELLIPSE) {
L = P - ld._position;
vec2 closest_point = vec2(dot(ld._right, L), dot(ld._up, L));
vec2 max_pos = vec2(ld._area_size_x, ld._area_size_y);
closest_point /= max_pos;
if (ld.type == LIGHT_ELLIPSE) {
closest_point /= max(1.0, length(closest_point));
}
else {
closest_point = clamp(closest_point, -1.0, 1.0);
}
closest_point *= max_pos;
vec3 L_prime = ld._right * closest_point.x + ld._up * closest_point.y;
L = L_prime - L;
dist = inversesqrt(len_squared(L));
L *= dist;
dist = 1.0 / dist;
}
else {
light_vector_get(ld, P, L, dist);
}
}
/* Rotate vector to light's local space. Does not translate. */
vec3 light_world_to_local(LightData ld, vec3 L)
{

View File

@ -15,10 +15,14 @@ float g_holdout;
ClosureDiffuse g_diffuse_data;
ClosureReflection g_reflection_data;
ClosureRefraction g_refraction_data;
ClosureVolumeScatter g_volume_scatter_data;
ClosureVolumeAbsorption g_volume_absorption_data;
/* Random number per sampled closure type. */
float g_diffuse_rand;
float g_reflection_rand;
float g_refraction_rand;
float g_volume_scatter_rand;
float g_volume_absorption_rand;
/**
* Returns true if the closure is to be selected based on the input weight.
@ -62,12 +66,22 @@ void closure_weights_reset()
g_refraction_data.roughness = 0.0;
g_refraction_data.ior = 0.0;
g_volume_scatter_data.weight = 0.0;
g_volume_scatter_data.scattering = vec3(0.0);
g_volume_scatter_data.anisotropy = 0.0;
g_volume_absorption_data.weight = 0.0;
g_volume_absorption_data.absorption = vec3(0.0);
#if defined(GPU_FRAGMENT_SHADER)
g_diffuse_rand = g_reflection_rand = g_refraction_rand = g_closure_rand;
g_volume_scatter_rand = g_volume_absorption_rand = g_closure_rand;
#else
g_diffuse_rand = 0.0;
g_reflection_rand = 0.0;
g_refraction_rand = 0.0;
g_volume_scatter_rand = 0.0;
g_volume_absorption_rand = 0.0;
#endif
g_emission = vec3(0.0);
@ -115,13 +129,15 @@ Closure closure_eval(ClosureTransparency transparency)
Closure closure_eval(ClosureVolumeScatter volume_scatter)
{
/* TODO */
/* TODO: Combine instead of selecting. */
fclem marked this conversation as resolved Outdated

Add a TODO. These do not need to use closure select and could just be combined.

Add a TODO. These do not need to use closure select and could just be combined.
SELECT_CLOSURE(g_volume_scatter_data, g_volume_scatter_rand, volume_scatter);
return Closure(0);
}
Closure closure_eval(ClosureVolumeAbsorption volume_absorption)
{
/* TODO */
/* TODO: Combine instead of selecting. */
SELECT_CLOSURE(g_volume_absorption_data, g_volume_absorption_rand, volume_absorption);
return Closure(0);
}
@ -160,7 +176,9 @@ Closure closure_eval(ClosureVolumeScatter volume_scatter,
ClosureVolumeAbsorption volume_absorption,
ClosureEmission emission)
{
/* TODO */
closure_eval(volume_scatter);
closure_eval(volume_absorption);
closure_eval(emission);
return Closure(0);
}
@ -229,7 +247,7 @@ float ambient_occlusion_eval(vec3 normal,
#ifndef GPU_METAL
void attrib_load();
Closure nodetree_surface();
Closure nodetree_volume();
/* Closure nodetree_volume(); */
vec3 nodetree_displacement();
float nodetree_thickness();
vec4 closure_to_rgba(Closure cl);
@ -517,20 +535,24 @@ vec3 coordinate_incoming(vec3 P)
*
* \{ */
#if defined(MAT_GEOM_VOLUME)
#if defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD)
float attr_load_temperature_post(float attr)
{
# ifdef MAT_GEOM_VOLUME_OBJECT
/* Bring the into standard range without having to modify the grid values */
attr = (attr > 0.01) ? (attr * drw_volume.temperature_mul + drw_volume.temperature_bias) : 0.0;
# endif
return attr;
}
vec4 attr_load_color_post(vec4 attr)
{
# ifdef MAT_GEOM_VOLUME_OBJECT
/* Density is premultiplied for interpolation, divide it out here. */
attr.rgb *= safe_rcp(attr.a);
attr.rgb *= drw_volume.color_mul.rgb;
attr.a = 1.0;
# endif
return attr;
}

View File

@ -13,6 +13,7 @@
#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl)
vec4 closure_to_rgba(Closure cl)
{
@ -126,6 +127,20 @@ void main()
/** NOTE: AO is done on its own pass. */
#endif
#ifdef MAT_TRANSPARENT
/* Volumetric resolve and compositing. */
vec2 uvs = gl_FragCoord.xy * volumes_info_buf.viewport_size_inv;
VolumeResolveSample vol = volume_resolve(
vec3(uvs, gl_FragCoord.z), volume_transmittance_tx, volume_scattering_tx);
/* Removes the part of the volume scattering that has
* already been added to the destination pixels by the opaque resolve.
* Since we do that using the blending pipeline we need to account for material transmittance. */
vol.scattering -= vol.scattering * g_transmittance;
out_radiance.rgb = out_radiance.rgb * vol.transmittance + vol.scattering;
#endif
out_radiance.rgb *= 1.0 - g_holdout;
out_transmittance.rgb = g_transmittance;

View File

@ -0,0 +1,73 @@
#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl)
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
/* Step 3 : Integrate for each froxel the final amount of light
* scattered back to the viewer and the amount of transmittance. */
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec3 tex_size = volumes_info_buf.tex_size;
if (any(greaterThanEqual(texel, tex_size.xy))) {
return;
}
/* Start with full transmittance and no scattered light. */
vec3 scattering = vec3(0.0);
vec3 transmittance = vec3(1.0);
/* Compute view ray. */
vec2 uvs = (vec2(texel) + vec2(0.5)) / vec2(tex_size.xy);
vec3 ndc_cell = volume_to_ndc(vec3(uvs, 1e-5));
vec3 view_cell = get_view_space_from_depth(ndc_cell.xy, ndc_cell.z);
float prev_ray_len;
fclem marked this conversation as resolved Outdated

Code style. Use bool is_persp and use else clause for ortho.

Code style. Use `bool is_persp` and use `else` clause for ortho.

Done. But could we maybe add a is_perpective function to common_view_lib?
This is used in so many places that IMO it would make sense.

Done. But could we maybe add a `is_perpective` function to `common_view_lib`? This is used in so many places that IMO it would make sense.

It would make sense. But this would be a separate cleanup.

It would make sense. But this would be a separate cleanup.
float orig_ray_len;
bool is_persp = ProjectionMatrix[3][3] == 0.0;
if (is_persp) {
prev_ray_len = length(view_cell);
orig_ray_len = prev_ray_len / view_cell.z;
}
else {
prev_ray_len = view_cell.z;
orig_ray_len = 1.0;
}
for (int i = 0; i <= tex_size.z; i++) {
ivec3 froxel = ivec3(texel, i);
vec3 froxel_scattering = texelFetch(in_scattering_tx, froxel, 0).rgb;
vec3 extinction = texelFetch(in_extinction_tx, froxel, 0).rgb;
float cell_depth = volume_z_to_view_z((float(i) + 1.0) / tex_size.z);
float ray_len = orig_ray_len * cell_depth;
/* Emission does not work if there is no extinction because
* froxel_transmittance evaluates to 1.0 leading to froxel_scattering = 0.0. (See #65771) */
extinction = max(vec3(1e-7) * step(1e-5, froxel_scattering), extinction);
/* Evaluate Scattering. */
float step_len = abs(ray_len - prev_ray_len);
prev_ray_len = ray_len;
vec3 froxel_transmittance = exp(-extinction * step_len);
/* Integrate along the current step segment. */
/** NOTE: Original calculation carries precision issues when compiling for AMD GPUs
* and running Metal. This version of the equation retains precision well for all
* macOS HW configurations. */
froxel_scattering = (froxel_scattering * (1.0f - froxel_transmittance)) /
fclem marked this conversation as resolved Outdated

Comment style. This apply to the whole diff. Please fix existing comments when you copy them.

Comment style. This apply to the whole diff. Please fix existing comments when you copy them.
max(vec3(1e-8), extinction);
/* Accumulate and also take into account the transmittance from previous steps. */
scattering += transmittance * froxel_scattering;
transmittance *= froxel_transmittance;
imageStore(out_scattering_img, froxel, vec4(scattering, 1.0));
imageStore(out_transmittance_img, froxel, vec4(transmittance, 1.0));
}
}

View File

@ -0,0 +1,199 @@
/**
* The resources expected to be defined are:
* - volumes_info_buf
*/
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
pragma37 marked this conversation as resolved

Move view_z_to_volume_z & volume_z_to_view_z to shader shared file as the Bound box init use it.

Move `view_z_to_volume_z` & `volume_z_to_view_z` to shader shared file as the Bound box init use it.
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
pragma37 marked this conversation as resolved Outdated

If a file requires a specific resource (here volumes_info_buf), make it clear at the top the file. (see eevee_light_eval_lib.glsl for example)

If a file requires a specific resource (here `volumes_info_buf`), make it clear at the top the file. (see `eevee_light_eval_lib.glsl` for example)
/* Volume slice to view space depth. */
float volume_z_to_view_z(float z)
{
float near = volumes_info_buf.depth_near;
float far = volumes_info_buf.depth_far;
float distribution = volumes_info_buf.depth_distribution;
bool is_persp = ProjectionMatrix[3][3] == 0.0;
fclem marked this conversation as resolved Outdated

This one doesn't make much sense since it is only called once. Merge it into ndc_to_volume.

This one doesn't make much sense since it is only called once. Merge it into `ndc_to_volume`.

Since this is the reverse of volume_z_to_view_z, I think leaving it as a separate function makes it easier to understand the code.

Since this is the reverse of `volume_z_to_view_z`, I think leaving it as a separate function makes it easier to understand the code.
/* Implemented in eevee_shader_shared.cc */
return volume_z_to_view_z(near, far, distribution, is_persp, z);
}
float view_z_to_volume_z(float depth)
{
float near = volumes_info_buf.depth_near;
float far = volumes_info_buf.depth_far;
float distribution = volumes_info_buf.depth_distribution;
bool is_persp = ProjectionMatrix[3][3] == 0.0;
/* Implemented in eevee_shader_shared.cc */
return view_z_to_volume_z(near, far, distribution, is_persp, depth);
}
/* Volume texture normalized coordinates to NDC (special range [0, 1]). */
vec3 volume_to_ndc(vec3 coord)
{
coord.z = volume_z_to_view_z(coord.z);
coord.z = get_depth_from_view_z(coord.z);
fclem marked this conversation as resolved

I would call it volume_ndc_to_froxel.

I would call it `volume_ndc_to_froxel`.
coord.xy /= volumes_info_buf.coord_scale;
return coord;
}
vec3 ndc_to_volume(vec3 coord)
{
float near = volumes_info_buf.depth_near;
float far = volumes_info_buf.depth_far;
float distribution = volumes_info_buf.depth_distribution;
pragma37 marked this conversation as resolved Outdated

volume_phase_function_isotropic

`volume_phase_function_isotropic`
vec2 coord_scale = volumes_info_buf.coord_scale;
/* Implemented in eevee_shader_shared.cc */
return ndc_to_volume(ProjectionMatrix, near, far, distribution, coord_scale, coord);
}
pragma37 marked this conversation as resolved Outdated

volume_phase_function_henyey_greenstein

`volume_phase_function_henyey_greenstein`
float volume_phase_function_isotropic()
{
return 1.0 / (4.0 * M_PI);
}
float volume_phase_function(vec3 v, vec3 l, float g)
{
/* Henyey-Greenstein. */
float cos_theta = dot(v, l);
g = clamp(g, -1.0 + 1e-3, 1.0 - 1e-3);
float sqr_g = g * g;
return (1 - sqr_g) / max(1e-8, 4.0 * M_PI * pow(1 + sqr_g - 2 * g * cos_theta, 3.0 / 2.0));
}
vec3 volume_light(LightData ld, vec3 L, float l_dist)
{
float power = 1.0;
if (ld.type != LIGHT_SUN) {
float volume_radius_squared = ld.radius_squared;
float light_clamp = volumes_info_buf.light_clamp;
if (light_clamp != 0.0) {
/* 0.0 light clamp means it's disabled. */
float max_power = max_v3(ld.color) * ld.volume_power;
if (max_power > 0.0) {
/* The limit of the power attenuation function when the distance to the light goes to 0 is
* `2 / r^2` where r is the light radius. We need to find the right radius that emits at
* most the volume light upper bound. Inverting the function we get: */
float min_radius_squared = 1.0f / (0.5f * light_clamp / max_power);
/* Square it here to avoid a multiplication inside the shader. */
volume_radius_squared = max(volume_radius_squared, min_radius_squared);
}
}
/**
* Using "Point Light Attenuation Without Singularity" from Cem Yuksel
* http://www.cemyuksel.com/research/pointlightattenuation/pointlightattenuation.pdf
* http://www.cemyuksel.com/research/pointlightattenuation/
*/
float d = l_dist;
float d_sqr = sqr(d);
float r_sqr = volume_radius_squared;
/* Using reformulation that has better numerical precision. */
power = 2.0 / (d_sqr + r_sqr + d * sqrt(d_sqr + r_sqr));
if (ld.type == LIGHT_RECT || ld.type == LIGHT_ELLIPSE) {
/* Modulate by light plane orientation / solid angle. */
power *= saturate(dot(ld._back, L));
}
}
return ld.color * ld.volume_power * power;
}
#define VOLUMETRIC_SHADOW_MAX_STEP 128.0
vec3 volume_participating_media_extinction(vec3 wpos, sampler3D volume_extinction)
{
/* Waiting for proper volume shadowmaps and out of frustum shadow map. */
vec3 ndc = project_point(ProjectionMatrix, transform_point(ViewMatrix, wpos));
vec3 volume_co = ndc_to_volume(ndc * 0.5 + 0.5);
pragma37 marked this conversation as resolved Outdated

Document this function. The name isn't clear by itself.

Document this function. The name isn't clear by itself.

Actually, could this be merged into eevee_light_eval_lib?
There's a light_vector_get there, but it doesn't handle area lights.

Actually, could this be merged into `eevee_light_eval_lib`? There's a `light_vector_get` there, but it doesn't handle area lights.

Yes it can be moved. But this function returns the light vector to the closest point on the light shape and only for rects and ellipses as the other shapes use another method for area lighting. The function name and its documentation should reflect that.

Yes it can be moved. But this function returns the light vector to the closest point on the light shape and only for rects and ellipses as the other shapes use another method for area lighting. The function name and its documentation should reflect that.
/* Let the texture be clamped to edge. This reduce visual glitches. */
return texture(volume_extinction, volume_co).rgb;
}
vec3 volume_shadow(LightData ld, vec3 ray_wpos, vec3 L, float l_dist)
{
/* TODO (Miguel Pozo) */
#if 0 && defined(VOLUME_SHADOW)
vec4 l_vector = vec4(L * l_dist, l_dist);
/* If light is shadowed, use the shadow vector, if not, reuse the light vector. */
if (volumes_info_buf.use_soft_shadows && ld.shadowid >= 0.0) {
ShadowData sd = shadows_data[int(ld.shadowid)];
if (ld.type == LIGHT_SUN) {
l_vector.xyz = shadows_cascade_data[int(sd.sh_data_index)].sh_shadow_vec;
/* No need for length, it is recomputed later. */
}
else {
l_vector.xyz = shadows_cube_data[int(sd.sh_data_index)].position.xyz - ray_wpos;
l_vector.w = length(l_vector.xyz);
}
}
/* Heterogeneous volume shadows. */
float dd = l_vector.w / volumes_info_buf.shadow_steps;
vec3 L = l_vector.xyz / volumes_info_buf.shadow_steps;
if (ld.type == LIGHT_SUN) {
/* For sun light we scan the whole frustum. So we need to get the correct endpoints. */
vec3 ndcP = project_point(ProjectionMatrix, transform_point(ViewMatrix, ray_wpos));
vec3 ndcL = project_point(ProjectionMatrix,
transform_point(ViewMatrix, ray_wpos + l_vector.xyz)) -
ndcP;
vec3 frustum_isect = ndcP + ndcL * line_unit_box_intersect_dist_safe(ndcP, ndcL);
vec4 L_hom = ViewMatrixInverse * (ProjectionMatrixInverse * vec4(frustum_isect, 1.0));
L = (L_hom.xyz / L_hom.w) - ray_wpos;
L /= volumes_info_buf.shadow_steps;
dd = length(L);
}
# if 0 /* TODO use shadow maps instead. */
vec3 shadow = vec3(1.0);
for (float s = 1.0; s < VOLUMETRIC_SHADOW_MAX_STEP && s <= volumes_info_buf.shadow_steps; s += 1.0) {
vec3 pos = ray_wpos + L * s;
vec3 s_extinction = volume_participating_media_extinction(pos, volume_extinction);
shadow *= exp(-s_extinction * dd);
}
return shadow;
# endif
#else
return vec3(1.0);
#endif /* VOLUME_SHADOW */
}
vec3 volume_irradiance(vec3 wpos)
{
#ifdef IRRADIANCE_HL2
IrradianceData ir_data = load_irradiance_cell(0, vec3(1.0));
vec3 irradiance = ir_data.cubesides[0] + ir_data.cubesides[1] + ir_data.cubesides[2];
ir_data = load_irradiance_cell(0, vec3(-1.0));
irradiance += ir_data.cubesides[0] + ir_data.cubesides[1] + ir_data.cubesides[2];
irradiance *= 0.16666666; /* 1/6 */
return irradiance;
#else
return vec3(0.0);
#endif
}
struct VolumeResolveSample {
vec3 transmittance;
vec3 scattering;
};
VolumeResolveSample volume_resolve(vec3 ndc_P, sampler3D transmittance_tx, sampler3D scattering_tx)
{
vec3 coord = ndc_to_volume(ndc_P);
VolumeResolveSample volume;
volume.scattering = texture(scattering_tx, coord).rgb;
volume.transmittance = texture(transmittance_tx, coord).rgb;
return volume;
}

View File

@ -0,0 +1,102 @@
#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl)
/* Needed includes for shader nodes. */
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_attribute_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl)
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
/* Store volumetric properties into the froxel textures. */
GlobalData init_globals(vec3 wP)
{
GlobalData surf;
surf.P = wP;
surf.N = vec3(0.0);
surf.Ng = vec3(0.0);
surf.is_strand = false;
surf.hair_time = 0.0;
surf.hair_thickness = 0.0;
surf.hair_strand_id = 0;
surf.barycentric_coords = vec2(0.0);
surf.barycentric_dists = vec3(0.0);
surf.ray_type = RAY_TYPE_CAMERA;
surf.ray_depth = 0.0;
surf.ray_length = distance(surf.P, cameraPos);
return surf;
}
#ifndef GPU_METAL
Closure nodetree_volume();
void attrib_load();
#endif
void main()
{
ivec3 froxel = ivec3(gl_GlobalInvocationID);
#ifdef MAT_GEOM_VOLUME_OBJECT
froxel += grid_coords_min;
#endif
if (any(greaterThanEqual(froxel, volumes_info_buf.tex_size))) {
return;
}
vec3 jitter = sampling_rng_3D_get(SAMPLING_VOLUME_U);
vec3 ndc_cell = volume_to_ndc((vec3(froxel) + jitter) * volumes_info_buf.inv_tex_size);
vec3 vP = get_view_space_from_depth(ndc_cell.xy, ndc_cell.z);
vec3 wP = point_view_to_world(vP);
#ifdef MAT_GEOM_VOLUME_OBJECT
g_lP = point_world_to_object(wP);
g_orco = OrcoTexCoFactors[0].xyz + g_lP * OrcoTexCoFactors[1].xyz;
if (any(lessThan(g_orco, vec3(0.0))) || any(greaterThan(g_orco, vec3(1.0)))) {
return;
}
#else /* WORLD_SHADER */
g_orco = wP;
#endif
g_data = init_globals(wP);
attrib_load();
pragma37 marked this conversation as resolved

This is a legacy flag that is no inside EEVEE-Next.

This is a legacy flag that is no inside EEVEE-Next.
nodetree_volume();
vec3 scattering = g_volume_scatter_data.scattering;
vec3 absorption = g_volume_absorption_data.absorption;
vec3 emission = g_emission;
float anisotropy = g_volume_scatter_data.anisotropy;
vec2 phase = vec2(anisotropy, 1.0);
#ifdef MAT_GEOM_VOLUME_OBJECT
scattering *= drw_volume.density_scale;
absorption *= drw_volume.density_scale;
emission *= drw_volume.density_scale;
#endif
vec3 extinction = scattering + absorption;
/* Do not add phase weight if there's no scattering. */
if (all(equal(scattering, vec3(0.0)))) {
phase = vec2(0.0);
}
#ifdef MAT_GEOM_VOLUME_OBJECT
/* Additive Blending.
* No race condition since each invocation only handles its own froxel. */
scattering += imageLoad(out_scattering_img, froxel).rgb;
extinction += imageLoad(out_extinction_img, froxel).rgb;
emission += imageLoad(out_emissive_img, froxel).rgb;
phase += imageLoad(out_phase_img, froxel).rg;
#endif
imageStore(out_scattering_img, froxel, vec4(scattering, 1.0));
imageStore(out_extinction_img, froxel, vec4(extinction, 1.0));
imageStore(out_emissive_img, froxel, vec4(emission, 1.0));
imageStore(out_phase_img, froxel, vec4(phase, vec2(1.0)));
}

View File

@ -0,0 +1,25 @@
#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl)
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
/* Step 4 : Apply final integration on top of the scene color. */
fclem marked this conversation as resolved

This shader should be part of the deferred pipeline shader. We shouldn't load the scene depth only for this pass.

This shader should be part of the deferred pipeline shader. We shouldn't load the scene depth only for this pass.
Review

This would have some implications though:

  • The deferred shader uses a stencil mask (I guess it's for the background?), so this would conflict with the world volume rendering.
  • The opaque forward pass would need to perform the volume resolve as well.

I can give it a try, but I would expect this approach would be slower?

This would have some implications though: - The deferred shader uses a stencil mask (I guess it's for the background?), so this would conflict with the world volume rendering. - The opaque forward pass would need to perform the volume resolve as well. I can give it a try, but I would expect this approach would be slower?

Ok Leave it as is for now. It can be refactor after the deferred pipeline has matured.

Ok Leave it as is for now. It can be refactor after the deferred pipeline has matured.
void main()
{
vec2 uvs = gl_FragCoord.xy / vec2(textureSize(depth_tx, 0));
float scene_depth = texture(depth_tx, uvs).r;
VolumeResolveSample vol = volume_resolve(
vec3(uvs, scene_depth), volume_transmittance_tx, volume_scattering_tx);
out_radiance = vec4(vol.scattering, 0.0);
out_transmittance = vec4(vol.transmittance, saturate(avg(vol.transmittance)));
if (rp_buf.volume_light_id >= 0) {
imageStore(rp_color_img,
ivec3(ivec2(gl_FragCoord.xy), rp_buf.volume_light_id),
vec4(vol.scattering, 1.0));
}
}

View File

@ -0,0 +1,92 @@
#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
/* Based on Frosbite Unified Volumetric.
* https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */
/* Step 2 : Evaluate all light scattering for each froxels.
* Also do the temporal reprojection to fight aliasing artifacts. */
#ifdef VOLUME_LIGHTING
vec3 volume_scatter_light_eval(vec3 P, vec3 V, uint l_idx, float s_anisotropy)
{
LightData ld = light_buf[l_idx];
if (ld.volume_power == 0.0) {
return vec3(0);
}
vec3 L;
float l_dist;
light_shape_vector_get(ld, P, L, l_dist);
# if 0
/* TODO(Miguel Pozo): Shadows */
float vis = light_visibility(ld, P, l_vector);
# else
float vis = light_attenuation(ld, L, l_dist);
# endif
if (vis < 1e-4) {
return vec3(0);
}
vec3 Li = volume_light(ld, L, l_dist) * volume_shadow(ld, P, L, l_dist);
return Li * vis * volume_phase_function(-V, L, s_anisotropy);
}
#endif
void main()
{
ivec3 froxel = ivec3(gl_GlobalInvocationID);
if (any(greaterThanEqual(froxel, volumes_info_buf.tex_size))) {
return;
}
/* Emission. */
vec3 scattering = imageLoad(in_emission_img, froxel).rgb;
vec3 extinction = imageLoad(in_extinction_img, froxel).rgb;
vec3 s_scattering = imageLoad(in_scattering_img, froxel).rgb;
vec3 jitter = sampling_rng_3D_get(SAMPLING_VOLUME_U);
vec3 volume_ndc = volume_to_ndc((vec3(froxel) + jitter) * volumes_info_buf.inv_tex_size);
vec3 vP = get_view_space_from_depth(volume_ndc.xy, volume_ndc.z);
vec3 P = point_view_to_world(vP);
vec3 V = cameraVec(P);
vec2 phase = imageLoad(in_phase_img, froxel).rg;
/* Divide by phase total weight, to compute the mean anisotropy. */
float s_anisotropy = phase.x / max(1.0, phase.y);
scattering += volume_irradiance(P) * s_scattering * volume_phase_function_isotropic();
fclem marked this conversation as resolved Outdated

This need a comment explaining why we divide by 2nd component.

This need a comment explaining why we divide by 2nd component.

To be honest I have no idea. Currently, only the first channel is ever written to.
It was like this in the current implementation so I left it there.
I assumed the second channel was intended to be used eventually.

To be honest I have no idea. Currently, only the first channel is ever written to. It was like this in the current implementation so I left it there. I assumed the second channel was intended to be used eventually.

It is used to store the number or phase that were written to it. This way we take the mean phase still using addive blending.

It is used to store the number or phase that were written to it. This way we take the mean phase still using addive blending.

Oops! This should be solved now.

Oops! This should be solved now.
#ifdef VOLUME_LIGHTING
fclem marked this conversation as resolved Outdated

This comment is obsolete.

This comment is obsolete.
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
scattering += volume_scatter_light_eval(P, V, l_idx, s_anisotropy) * s_scattering;
}
LIGHT_FOREACH_END
vec2 pixel = (vec2(froxel.xy) + vec2(0.5)) / vec2(volumes_info_buf.tex_size.xy) /
volumes_info_buf.viewport_size_inv;
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, pixel, vP.z, l_idx) {
scattering += volume_scatter_light_eval(P, V, l_idx, s_anisotropy) * s_scattering;
}
LIGHT_FOREACH_END
#endif
/* Catch NaNs. */
if (any(isnan(scattering)) || any(isnan(extinction))) {
scattering = vec3(0.0);
extinction = vec3(0.0);
}
imageStore(out_scattering_img, froxel, vec4(scattering, 1.0));
imageStore(out_extinction_img, froxel, vec4(extinction, 1.0));
}

View File

@ -146,6 +146,7 @@ GPU_SHADER_CREATE_INFO(eevee_surf_forward)
"eevee_utility_texture",
"eevee_sampling_data",
"eevee_shadow_data",
"eevee_volume_lib",
"eevee_ambient_occlusion_data"
/* Optionally added depending on the material. */
// "eevee_render_pass_out",
@ -204,6 +205,70 @@ GPU_SHADER_CREATE_INFO(eevee_surf_shadow)
/** \name Volume
* \{ */
fclem marked this conversation as resolved Outdated

Remove the render passes they make no sense. in this context.

Remove the render passes they make no sense. in this context.
GPU_SHADER_CREATE_INFO(eevee_volume_material_common)
.compute_source("eevee_volume_material_comp.glsl")
.local_group_size(VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE)
.define("VOLUMETRICS")
.uniform_buf(VOLUMES_INFO_BUF_SLOT, "VolumesInfoData", "volumes_info_buf")
.additional_info("draw_resource_id_uniform",
"draw_view",
"eevee_shared",
"eevee_sampling_data",
"eevee_camera",
"eevee_utility_texture");
GPU_SHADER_CREATE_INFO(eevee_volume_object)
.define("MAT_GEOM_VOLUME_OBJECT")
.push_constant(Type::IVEC3, "grid_coords_min")
.image(VOLUME_PROP_SCATTERING_IMG_SLOT,
GPU_R11F_G11F_B10F,
Qualifier::READ_WRITE,
ImageType::FLOAT_3D,
"out_scattering_img")
.image(VOLUME_PROP_EXTINCTION_IMG_SLOT,
GPU_R11F_G11F_B10F,
Qualifier::READ_WRITE,
ImageType::FLOAT_3D,
"out_extinction_img")
.image(VOLUME_PROP_EMISSION_IMG_SLOT,
GPU_R11F_G11F_B10F,
Qualifier::READ_WRITE,
ImageType::FLOAT_3D,
"out_emissive_img")
.image(VOLUME_PROP_PHASE_IMG_SLOT,
GPU_RG16F,
Qualifier::READ_WRITE,
ImageType::FLOAT_3D,
"out_phase_img")
.additional_info("eevee_volume_material_common",
"draw_object_infos_new",
"draw_volume_infos",
"draw_modelmat_new_common");
GPU_SHADER_CREATE_INFO(eevee_volume_world)
.image(VOLUME_PROP_SCATTERING_IMG_SLOT,
GPU_R11F_G11F_B10F,
Qualifier::WRITE,
ImageType::FLOAT_3D,
"out_scattering_img")
.image(VOLUME_PROP_EXTINCTION_IMG_SLOT,
GPU_R11F_G11F_B10F,
Qualifier::WRITE,
ImageType::FLOAT_3D,
"out_extinction_img")
.image(VOLUME_PROP_EMISSION_IMG_SLOT,
GPU_R11F_G11F_B10F,
Qualifier::WRITE,
ImageType::FLOAT_3D,
"out_emissive_img")
.image(VOLUME_PROP_PHASE_IMG_SLOT,
GPU_RG16F,
Qualifier::WRITE,
ImageType::FLOAT_3D,
"out_phase_img")
.define("MAT_GEOM_VOLUME_WORLD")
.additional_info("eevee_volume_material_common");
#if 0 /* TODO */
GPU_SHADER_INTERFACE_INFO(eevee_volume_iface, "interp")
.smooth(Type::VEC3, "P_start")

View File

@ -0,0 +1,82 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
#pragma once
/* Used for shaders that need the final accumulated volume transmittance and scaterring. */
GPU_SHADER_CREATE_INFO(eevee_volume_lib)
.additional_info("eevee_shared")
.additional_info("draw_view")
.uniform_buf(VOLUMES_INFO_BUF_SLOT, "VolumesInfoData", "volumes_info_buf")
.sampler(VOLUME_SCATTERING_TEX_SLOT, ImageType::FLOAT_3D, "volume_scattering_tx")
.sampler(VOLUME_TRANSMITTANCE_TEX_SLOT, ImageType::FLOAT_3D, "volume_transmittance_tx");
GPU_SHADER_CREATE_INFO(eevee_volume_properties_data)
.image(VOLUME_PROP_SCATTERING_IMG_SLOT,
GPU_R11F_G11F_B10F,
Qualifier::READ,
ImageType::FLOAT_3D,
"in_scattering_img")
.image(VOLUME_PROP_EXTINCTION_IMG_SLOT,
GPU_R11F_G11F_B10F,
Qualifier::READ,
ImageType::FLOAT_3D,
"in_extinction_img")
.image(VOLUME_PROP_EMISSION_IMG_SLOT,
GPU_R11F_G11F_B10F,
Qualifier::READ,
ImageType::FLOAT_3D,
"in_emission_img")
.image(VOLUME_PROP_PHASE_IMG_SLOT,
GPU_RG16F,
Qualifier::READ,
ImageType::FLOAT_3D,
"in_phase_img");
GPU_SHADER_CREATE_INFO(eevee_volume_scatter)
.additional_info("eevee_shared")
.additional_info("draw_resource_id_varying")
.additional_info("draw_view")
.additional_info("eevee_light_data")
.additional_info("eevee_shadow_data")
.additional_info("eevee_sampling_data")
.compute_source("eevee_volume_scatter_comp.glsl")
.local_group_size(VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE)
.define("VOLUME_SHADOW")
.uniform_buf(VOLUMES_INFO_BUF_SLOT, "VolumesInfoData", "volumes_info_buf")
.additional_info("eevee_volume_properties_data")
.image(4, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_3D, "out_scattering_img")
.image(5, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_3D, "out_extinction_img")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_volume_scatter_with_lights)
.additional_info("eevee_volume_scatter")
.define("VOLUME_LIGHTING")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_volume_integration)
.additional_info("eevee_shared")
.additional_info("draw_view")
pragma37 marked this conversation as resolved Outdated

Doesnt seems to be necessary. Merge with eevee_volume_resolve.

Doesnt seems to be necessary. Merge with eevee_volume_resolve.
.compute_source("eevee_volume_integration_comp.glsl")
.local_group_size(VOLUME_INTEGRATION_GROUP_SIZE, VOLUME_INTEGRATION_GROUP_SIZE, 1)
.uniform_buf(VOLUMES_INFO_BUF_SLOT, "VolumesInfoData", "volumes_info_buf")
/* Inputs. */
.sampler(0, ImageType::FLOAT_3D, "in_scattering_tx")
.sampler(1, ImageType::FLOAT_3D, "in_extinction_tx")
/* Outputs. */
.image(0, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_3D, "out_scattering_img")
pragma37 marked this conversation as resolved Outdated

This should also output to the volume renderpasses.

This should also output to the volume renderpasses.

I think we should actually get final (f12) rendering working first before looking into this?

I think we should actually get final (f12) rendering working first before looking into this?

Ok but then add a TODO here.

Ok but then add a TODO here.
.image(1, GPU_R11F_G11F_B10F, Qualifier::WRITE, ImageType::FLOAT_3D, "out_transmittance_img")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(eevee_volume_resolve)
.additional_info("eevee_shared")
.additional_info("eevee_volume_lib")
.additional_info("draw_fullscreen")
.additional_info("eevee_render_pass_out")
.fragment_source("eevee_volume_resolve_frag.glsl")
.sampler(0, ImageType::DEPTH_2D, "depth_tx")
.fragment_out(0, Type::VEC4, "out_radiance", DualBlend::SRC_0)
.fragment_out(0, Type::VEC4, "out_transmittance", DualBlend::SRC_1)
/** TODO(Miguel Pozo): Volume RenderPasses. */
.do_static_compilation(true);

View File

@ -615,13 +615,14 @@ void DrawMultiBuf::bind(RecordingState &state,
group.start = resource_id_count_;
resource_id_count_ += group.len * view_len;
int batch_inst_len;
int batch_vert_len, batch_vert_first, batch_base_index, batch_inst_len;
/* Now that GPUBatches are guaranteed to be finished, extract their parameters. */
GPU_batch_draw_parameter_get(group.gpu_batch,
&group.vertex_len,
&group.vertex_first,
&group.base_index,
&batch_inst_len);
GPU_batch_draw_parameter_get(
group.gpu_batch, &batch_vert_len, &batch_vert_first, &batch_base_index, &batch_inst_len);
group.vertex_len = group.vertex_len == -1 ? batch_vert_len : group.vertex_len;
group.vertex_first = group.vertex_first == -1 ? batch_vert_first : group.vertex_first;
group.base_index = batch_base_index;
/* Instancing attributes are not supported using the new pipeline since we use the base
* instance to set the correct resource_id. Workaround is a storage_buf + gl_InstanceID. */

View File

@ -46,4 +46,23 @@ GPUBatch *point_cloud_sub_pass_setup(PassSimple::Sub &sub_ps,
Object *object,
GPUMaterial *gpu_material = nullptr);
/**
* Add attribute bindings of volume grids to an existing pass.
* No draw call is added so the caller can decide how to use the data.
* \return nullptr if there is nothing to draw.
*/
PassMain::Sub *volume_sub_pass(PassMain::Sub &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material);
/**
* Add attribute bindings of volume grids to an existing pass.
* No draw call is added so the caller can decide how to use the data.
* \return nullptr if there is nothing to draw.
*/
PassSimple::Sub *volume_sub_pass(PassSimple::Sub &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material);
} // namespace blender::draw

View File

@ -107,7 +107,7 @@ inline void ObjectInfos::sync(const blender::draw::ObjectRef ref, bool is_active
case ID_VO: {
BoundBox &bbox = *BKE_volume_boundbox_get(ref.object);
orco_add = (float3(bbox.vec[6]) + float3(bbox.vec[0])) * 0.5f; /* Center. */
orco_mul = float3(bbox.vec[6]) - float3(bbox.vec[0]); /* Size. */
orco_mul = (float3(bbox.vec[6]) - float3(bbox.vec[0])) * 0.5f; /* Half-Size. */
pragma37 marked this conversation as resolved Outdated

Update the comment. It should be half size.

Update the comment. It should be half size.
break;
}
case ID_ME: {

View File

@ -26,6 +26,8 @@
#include "draw_common.h"
#include "draw_manager.h"
#include "draw_common.hh"
using namespace blender;
using namespace blender::draw;
using VolumeInfosBuf = blender::draw::UniformBuffer<VolumeInfos>;
@ -274,3 +276,200 @@ DRWShadingGroup *DRW_shgroup_volume_create_sub(Scene *scene,
}
return drw_volume_object_mesh_init(scene, ob, &attrs, shgrp);
}
/* -------------------------------------------------------------------- */
/** \name New Draw Manager implementation
* \{ */
namespace blender::draw {
template<typename PassType>
PassType *volume_world_grids_init(PassType &ps, ListBaseWrapper<GPUMaterialAttribute> &attrs)
{
PassType *sub = &ps.sub("World Volume");
for (const GPUMaterialAttribute *attr : attrs) {
sub->bind_texture(attr->input_name, grid_default_texture(attr->default_value));
}
return sub;
}
template<typename PassType>
PassType *volume_object_grids_init(PassType &ps,
Object *ob,
ListBaseWrapper<GPUMaterialAttribute> &attrs)
{
VolumeUniformBufPool *pool = (VolumeUniformBufPool *)DST.vmempool->volume_grids_ubos;
VolumeInfosBuf &volume_infos = *pool->alloc();
Volume *volume = (Volume *)ob->data;
BKE_volume_load(volume, G.main);
volume_infos.density_scale = BKE_volume_density_scale(volume, ob->object_to_world);
volume_infos.color_mul = float4(1.0f);
volume_infos.temperature_mul = 1.0f;
volume_infos.temperature_bias = 0.0f;
bool has_grids = false;
for (const GPUMaterialAttribute *attr : attrs) {
if (BKE_volume_grid_find_for_read(volume, attr->name) != nullptr) {
has_grids = true;
break;
}
}
/* Render nothing if there is no attribute for the shader to render.
* This also avoids an assert caused by the bounding box being zero in size. */
if (!has_grids) {
return nullptr;
}
PassType *sub = &ps.sub("Volume Object SubPass");
/* Bind volume grid textures. */
int grid_id = 0;
for (const GPUMaterialAttribute *attr : attrs) {
const VolumeGrid *volume_grid = BKE_volume_grid_find_for_read(volume, attr->name);
const DRWVolumeGrid *drw_grid = (volume_grid) ?
DRW_volume_batch_cache_get_grid(volume, volume_grid) :
nullptr;
/* Handle 3 cases here:
* - Grid exists and texture was loaded -> use texture.
* - Grid exists but has zero size or failed to load -> use zero.
* - Grid does not exist -> use default value. */
const GPUTexture *grid_tex = (drw_grid) ? drw_grid->texture :
(volume_grid) ? g_data.dummy_zero :
grid_default_texture(attr->default_value);
/* TODO (Miguel Pozo): bind_texture const support ? */
sub->bind_texture(attr->input_name, (GPUTexture *)grid_tex);
volume_infos.grids_xform[grid_id++] = float4x4(drw_grid ? drw_grid->object_to_texture :
g_data.dummy_grid_mat);
}
volume_infos.push_update();
sub->bind_ubo("drw_volume", volume_infos);
return sub;
}
template<typename PassType>
PassType *drw_volume_object_mesh_init(PassType &ps,
Scene *scene,
Object *ob,
ListBaseWrapper<GPUMaterialAttribute> &attrs)
{
VolumeUniformBufPool *pool = (VolumeUniformBufPool *)DST.vmempool->volume_grids_ubos;
VolumeInfosBuf &volume_infos = *pool->alloc();
ModifierData *md = nullptr;
volume_infos.density_scale = 1.0f;
volume_infos.color_mul = float4(1.0f);
volume_infos.temperature_mul = 1.0f;
volume_infos.temperature_bias = 0.0f;
PassType *sub = nullptr;
/* Smoke Simulation. */
if ((md = BKE_modifiers_findby_type(ob, eModifierType_Fluid)) &&
BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime) &&
((FluidModifierData *)md)->domain != nullptr)
{
FluidModifierData *fmd = (FluidModifierData *)md;
FluidDomainSettings *fds = fmd->domain;
/* Don't try to show liquid domains here. */
if (!fds->fluid || !(fds->type == FLUID_DOMAIN_TYPE_GAS)) {
return nullptr;
}
if (fds->fluid && (fds->type == FLUID_DOMAIN_TYPE_GAS)) {
DRW_smoke_ensure(fmd, fds->flags & FLUID_DOMAIN_USE_NOISE);
}
sub = &ps.sub("Volume Modifier SubPass");
int grid_id = 0;
for (const GPUMaterialAttribute *attr : attrs) {
if (STREQ(attr->name, "density")) {
sub->bind_texture(attr->input_name,
fds->tex_density ? &fds->tex_density : &g_data.dummy_one);
}
else if (STREQ(attr->name, "color")) {
sub->bind_texture(attr->input_name, fds->tex_color ? &fds->tex_color : &g_data.dummy_one);
}
else if (STR_ELEM(attr->name, "flame", "temperature")) {
sub->bind_texture(attr->input_name, fds->tex_flame ? &fds->tex_flame : &g_data.dummy_zero);
}
else {
sub->bind_texture(attr->input_name, grid_default_texture(attr->default_value));
}
volume_infos.grids_xform[grid_id++] = float4x4(g_data.dummy_grid_mat);
}
bool use_constant_color = ((fds->active_fields & FLUID_DOMAIN_ACTIVE_COLORS) == 0 &&
(fds->active_fields & FLUID_DOMAIN_ACTIVE_COLOR_SET) != 0);
if (use_constant_color) {
volume_infos.color_mul = float4(UNPACK3(fds->active_color), 1.0f);
}
/* Output is such that 0..1 maps to 0..1000K */
volume_infos.temperature_mul = fds->flame_max_temp - fds->flame_ignition;
volume_infos.temperature_bias = fds->flame_ignition;
}
else {
sub = &ps.sub("Volume Mesh SubPass");
int grid_id = 0;
for (const GPUMaterialAttribute *attr : attrs) {
sub->bind_texture(attr->input_name, grid_default_texture(attr->default_value));
volume_infos.grids_xform[grid_id++] = float4x4(g_data.dummy_grid_mat);
}
}
if (sub) {
volume_infos.push_update();
sub->bind_ubo("drw_volume", volume_infos);
}
return sub;
}
template<typename PassType>
PassType *volume_sub_pass_implementation(PassType &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
ListBase attr_list = GPU_material_attributes(gpu_material);
ListBaseWrapper<GPUMaterialAttribute> attrs(attr_list);
if (ob == nullptr) {
return volume_world_grids_init(ps, attrs);
}
else if (ob->type == OB_VOLUME) {
return volume_object_grids_init(ps, ob, attrs);
}
else {
return drw_volume_object_mesh_init(ps, scene, ob, attrs);
}
}
PassMain::Sub *volume_sub_pass(PassMain::Sub &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
return volume_sub_pass_implementation(ps, scene, ob, gpu_material);
}
PassSimple::Sub *volume_sub_pass(PassSimple::Sub &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
return volume_sub_pass_implementation(ps, scene, ob, gpu_material);
}
} // namespace blender::draw
/** \} */

View File

@ -651,6 +651,7 @@ set(SRC_SHADER_CREATE_INFOS
../draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_subsurface_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_velocity_info.hh
../draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh
../draw/engines/gpencil/shaders/infos/gpencil_info.hh
../draw/engines/gpencil/shaders/infos/gpencil_vfx_info.hh
../draw/engines/overlay/shaders/infos/overlay_antialiasing_info.hh