EEVEE Next: Volumes #107176
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
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
|
||||
* \{ */
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
Clément Foucault
commented
Having the default case be the compute one is a bit weird. Maybe have 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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
Clément Foucault
commented
Use 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
Clément Foucault
commented
Rename to Rename to `volume_view_z_to_froxel_z`, fits module prefix.
Miguel Pozo
commented
I think this would be misleading since the result is in the 0-1 range. Since this is pretty much 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>;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
Clément Foucault
commented
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?
Miguel Pozo
commented
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.
Clément Foucault
commented
I would say the projection matrix should be part of 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
Clément Foucault
commented
You can simplify to 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
Clément Foucault
commented
Code-style: Avoid 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
|
||||
|
||||
/** \} */
|
|
@ -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
Clément Foucault
commented
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
|
|
@ -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
|
||||
|
|
|
@ -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
Clément Foucault
commented
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();
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
Clément Foucault
commented
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Clément Foucault
commented
Code style. Use Code style. Use `bool is_persp` and use `else` clause for ortho.
Miguel Pozo
commented
Done. But could we maybe add a 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.
Clément Foucault
commented
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
Clément Foucault
commented
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));
|
||||
}
|
||||
}
|
|
@ -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
Clément Foucault
commented
Move 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
Clément Foucault
commented
If a file requires a specific resource (here 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
Clément Foucault
commented
This one doesn't make much sense since it is only called once. Merge it into This one doesn't make much sense since it is only called once. Merge it into `ndc_to_volume`.
Miguel Pozo
commented
Since this is the reverse of 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
Clément Foucault
commented
I would call it 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
Clément Foucault
commented
`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
Clément Foucault
commented
`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
Clément Foucault
commented
Document this function. The name isn't clear by itself. Document this function. The name isn't clear by itself.
Miguel Pozo
commented
Actually, could this be merged into Actually, could this be merged into `eevee_light_eval_lib`?
There's a `light_vector_get` there, but it doesn't handle area lights.
Clément Foucault
commented
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;
|
||||
}
|
|
@ -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
Clément Foucault
commented
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)));
|
||||
}
|
|
@ -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
Clément Foucault
commented
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.
Miguel Pozo
commented
This would have some implications though:
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?
Clément Foucault
commented
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));
|
||||
}
|
||||
}
|
|
@ -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
Clément Foucault
commented
This need a comment explaining why we divide by 2nd component. This need a comment explaining why we divide by 2nd component.
Miguel Pozo
commented
To be honest I have no idea. Currently, only the first channel is ever written to. 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.
Clément Foucault
commented
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.
Miguel Pozo
commented
Oops! This should be solved now. Oops! This should be solved now.
|
||||
#ifdef VOLUME_LIGHTING
|
||||
|
||||
fclem marked this conversation as resolved
Outdated
Clément Foucault
commented
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));
|
||||
}
|
|
@ -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
Clément Foucault
commented
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")
|
||||
|
|
|
@ -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
Clément Foucault
commented
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
Clément Foucault
commented
This should also output to the volume renderpasses. This should also output to the volume renderpasses.
Miguel Pozo
commented
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?
Clément Foucault
commented
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);
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Clément Foucault
commented
Update the comment. It should be half size. Update the comment. It should be half size.
|
||||
break;
|
||||
}
|
||||
case ID_ME: {
|
||||
|
|
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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
|
||||
|
|
Why do you need light and shadows here? the computation is not done at this stage.