From b05b0f413cad2609258d2e047dcf8314869ffe94 Mon Sep 17 00:00:00 2001 From: YimingWu Date: Mon, 28 Apr 2025 12:59:54 +0200 Subject: [PATCH] Grease Pencil: Add separate render pass This PR adds a separate "Grease Pencil" render pass. Once the "Grease Pencil" option is checked in the passes list, the Grease Pencil engine will render to a new render pass for various composition uses. Notes: - Occluded Grease Pencil geometry is not rendered. - In most cases, using an "Alpha Over" with the rest will result in the same render as the "Combined" output. The exception is when there are Grease Pencil layers that use a blending mode that changes the chromaticity of the alpha channel. Pull Request: https://projects.blender.org/blender/blender/pulls/137638 --- intern/cycles/blender/addon/ui.py | 1 + .../startup/bl_ui/properties_view_layer.py | 2 + .../draw/engines/gpencil/gpencil_render.cc | 89 ++++++++++++++----- source/blender/makesdna/DNA_layer_types.h | 7 +- source/blender/makesdna/DNA_scene_types.h | 2 + source/blender/makesrna/intern/rna_layer.cc | 8 ++ .../composite/nodes/node_composite_image.cc | 5 ++ 7 files changed, 91 insertions(+), 23 deletions(-) diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index c5cf98ea411..c49e7b6bd74 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -1028,6 +1028,7 @@ class CYCLES_RENDER_PT_passes_data(CyclesButtonsPanel, Panel): sub.active = not rd.use_motion_blur sub.prop(view_layer, "use_pass_vector") col.prop(view_layer, "use_pass_uv") + col.prop(view_layer, "use_pass_grease_pencil", text="Grease Pencil") col.prop(cycles_view_layer, "denoising_store_passes", text="Denoising Data") diff --git a/scripts/startup/bl_ui/properties_view_layer.py b/scripts/startup/bl_ui/properties_view_layer.py index 4364c398e30..3bca2185084 100644 --- a/scripts/startup/bl_ui/properties_view_layer.py +++ b/scripts/startup/bl_ui/properties_view_layer.py @@ -84,6 +84,7 @@ class VIEWLAYER_PT_eevee_next_layer_passes_data(ViewLayerButtonsPanel, Panel): sub = col.column() sub.active = not scene.render.use_motion_blur sub.prop(view_layer, "use_pass_vector") + col.prop(view_layer, "use_pass_grease_pencil", text="Grease Pencil") class VIEWLAYER_PT_workbench_layer_passes_data(ViewLayerButtonsPanel, Panel): @@ -102,6 +103,7 @@ class VIEWLAYER_PT_workbench_layer_passes_data(ViewLayerButtonsPanel, Panel): col = layout.column() col.prop(view_layer, "use_pass_combined") col.prop(view_layer, "use_pass_z") + col.prop(view_layer, "use_pass_grease_pencil", text="Grease Pencil") class VIEWLAYER_PT_eevee_next_layer_passes_light(ViewLayerButtonsPanel, Panel): diff --git a/source/blender/draw/engines/gpencil/gpencil_render.cc b/source/blender/draw/engines/gpencil/gpencil_render.cc index 94c05c8eda6..74f2be6c3d2 100644 --- a/source/blender/draw/engines/gpencil/gpencil_render.cc +++ b/source/blender/draw/engines/gpencil/gpencil_render.cc @@ -17,6 +17,7 @@ #include "RE_engine.h" #include "RE_pipeline.h" +#include "render_types.h" #include "IMB_imbuf_types.hh" @@ -65,7 +66,8 @@ static void render_init_buffers(const DRWContext *draw_ctx, Instance &inst, RenderEngine *engine, RenderLayer *render_layer, - const rcti *rect) + const rcti *rect, + const bool use_separated_pass) { const int2 size = int2(draw_ctx->viewport_size_get()); View &view = View::default_get(); @@ -89,10 +91,11 @@ static void render_init_buffers(const DRWContext *draw_ctx, remap_depth(view, {pix_z, rpass_z_src->rectx * rpass_z_src->recty}); } - const bool do_region = !(rect->xmin == 0 && rect->ymin == 0 && rect->xmax == size.x && - rect->ymax == size.y); + const bool do_region = (!use_separated_pass) && + (!(rect->xmin == 0 && rect->ymin == 0 && rect->xmax == size.x && + rect->ymax == size.y)); const bool do_clear_z = !pix_z || do_region; - const bool do_clear_col = !pix_col || do_region; + const bool do_clear_col = use_separated_pass || (!pix_col) || do_region; /* FIXME(fclem): we have a precision loss in the depth buffer because of this re-upload. * Find where it comes from! */ @@ -225,6 +228,51 @@ static void render_result_combined(RenderLayer *rl, rp->ibuf->float_buffer.data); } +static void render_result_separated_pass(float *data, Instance &instance, const rcti *rect) +{ + Framebuffer read_fb; + read_fb.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(instance.accumulation_tx)); + GPU_framebuffer_bind(read_fb); + GPU_framebuffer_read_color(read_fb, + rect->xmin, + rect->ymin, + BLI_rcti_size_x(rect), + BLI_rcti_size_y(rect), + 4, + 0, + GPU_DATA_FLOAT, + data); +} + +static void render_frame(RenderEngine *engine, + Depsgraph *depsgraph, + const DRWContext *draw_ctx, + RenderLayer *render_layer, + const rcti rect, + gpencil::Instance &inst, + Manager &manager, + const bool separated_pass) +{ + const float aa_radius = clamp_f(draw_ctx->scene->r.gauss, 0.0f, 100.0f); + const int sample_count = draw_ctx->scene->grease_pencil_settings.aa_samples; + for (const int sample_i : IndexRange(sample_count)) { + const float2 aa_sample = Instance::antialiasing_sample_get(sample_i, sample_count) * aa_radius; + const float2 aa_offset = 2.0f * aa_sample / float2(inst.render_color_tx.size()); + render_set_view(engine, depsgraph, aa_offset); + render_init_buffers(draw_ctx, inst, engine, render_layer, &rect, separated_pass); + + /* Render the gpencil object and merge the result to the underlying render. */ + inst.draw(manager); + + /* Weight of this render SSAA sample. The sum of previous samples is weighted by `1 - weight`. + * This diminishes after each new sample as we want all samples to be equally weighted inside + * the final result (inside the combined buffer). This weighting scheme allows to always store + * the resolved result making it ready for in-progress display or read-back. */ + const float weight = 1.0f / (1.0f + sample_i); + inst.antialiasing_accumulate(manager, weight); + } +} + void Engine::render_to_image(RenderEngine *engine, RenderLayer *render_layer, const rcti rect) { const char *viewname = RE_GetActiveRenderView(engine->re); @@ -232,12 +280,18 @@ void Engine::render_to_image(RenderEngine *engine, RenderLayer *render_layer, co const DRWContext *draw_ctx = DRW_context_get(); Depsgraph *depsgraph = draw_ctx->depsgraph; + if (draw_ctx->view_layer->grease_pencil_flags & GREASE_PENCIL_AS_SEPARATE_PASS) { + Render *re = engine->re; + RE_create_render_pass( + re->result, RE_PASSNAME_GREASE_PENCIL, 4, "RGBA", render_layer->name, viewname, true); + } + gpencil::Instance inst; Manager &manager = *DRW_manager_get(); render_set_view(engine, depsgraph); - render_init_buffers(draw_ctx, inst, engine, render_layer, &rect); + render_init_buffers(draw_ctx, inst, engine, render_layer, &rect, false); inst.init(); inst.camera = DEG_get_evaluated_object(depsgraph, RE_GetCamera(engine->re)); @@ -259,26 +313,17 @@ void Engine::render_to_image(RenderEngine *engine, RenderLayer *render_layer, co manager.end_sync(); - const float aa_radius = clamp_f(draw_ctx->scene->r.gauss, 0.0f, 100.0f); - const int sample_count = draw_ctx->scene->grease_pencil_settings.aa_samples; - for (const int sample_i : IndexRange(sample_count)) { - const float2 aa_sample = Instance::antialiasing_sample_get(sample_i, sample_count) * aa_radius; - const float2 aa_offset = 2.0f * aa_sample / float2(inst.render_color_tx.size()); - render_set_view(engine, depsgraph, aa_offset); - render_init_buffers(draw_ctx, inst, engine, render_layer, &rect); + render_frame(engine, depsgraph, draw_ctx, render_layer, rect, inst, manager, false); + render_result_combined(render_layer, viewname, inst, &rect); - /* Render the gpencil object and merge the result to the underlying render. */ - inst.draw(manager); - - /* Weight of this render SSAA sample. The sum of previous samples is weighted by `1 - weight`. - * This diminishes after each new sample as we want all samples to be equally weighted inside - * the final result (inside the combined buffer). This weighting scheme allows to always store - * the resolved result making it ready for in-progress display or read-back. */ - const float weight = 1.0f / (1.0f + sample_i); - inst.antialiasing_accumulate(manager, weight); + float *pass_data = RE_RenderLayerGetPass(render_layer, RE_PASSNAME_GREASE_PENCIL, viewname); + if (pass_data) { + render_frame(engine, depsgraph, draw_ctx, render_layer, rect, inst, manager, true); + render_result_separated_pass(pass_data, inst, &rect); } - render_result_combined(render_layer, viewname, inst, &rect); + /* Transfer depth in the last step, because if we need to render separate pass, we need original + * untouched depth buffer. */ render_result_z(draw_ctx, render_layer, viewname, inst, &rect); } diff --git a/source/blender/makesdna/DNA_layer_types.h b/source/blender/makesdna/DNA_layer_types.h index 1e2474f8da6..85deb4c1649 100644 --- a/source/blender/makesdna/DNA_layer_types.h +++ b/source/blender/makesdna/DNA_layer_types.h @@ -50,6 +50,11 @@ typedef enum eViewLayerEEVEEPassType { #define EEVEE_RENDER_PASS_MAX_BIT 21 ENUM_OPERATORS(eViewLayerEEVEEPassType, 1 << EEVEE_RENDER_PASS_MAX_BIT) +/* #ViewLayer::grease_pencil_flags */ +typedef enum eViewLayerGreasePencilFlags { + GREASE_PENCIL_AS_SEPARATE_PASS = (1 << 0), +} eViewLayerGreasePencilFlags; + /* #ViewLayerAOV.type */ typedef enum eViewLayerAOVType { AOV_TYPE_VALUE = 0, @@ -166,7 +171,7 @@ typedef struct ViewLayer { float pass_alpha_threshold; short cryptomatte_flag; short cryptomatte_levels; - char _pad1[4]; + int grease_pencil_flags; int samples; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 9bff148b2b5..dea0db2c739 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -304,6 +304,8 @@ typedef enum eScenePassType { #define RE_PASSNAME_CRYPTOMATTE_ASSET "CryptoAsset" #define RE_PASSNAME_CRYPTOMATTE_MATERIAL "CryptoMaterial" +#define RE_PASSNAME_GREASE_PENCIL "GreasePencil" + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/makesrna/intern/rna_layer.cc b/source/blender/makesrna/intern/rna_layer.cc index db3555e785f..ddcfbe7d916 100644 --- a/source/blender/makesrna/intern/rna_layer.cc +++ b/source/blender/makesrna/intern/rna_layer.cc @@ -682,6 +682,14 @@ void RNA_def_view_layer(BlenderRNA *brna) RNA_def_property_struct_type(prop, "FreestyleSettings"); RNA_def_property_ui_text(prop, "Freestyle Settings", ""); + /* Grease Pencil */ + prop = RNA_def_property(srna, "use_pass_grease_pencil", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "grease_pencil_flags", GREASE_PENCIL_AS_SEPARATE_PASS); + RNA_def_property_ui_text( + prop, "Grease Pencil", "Deliver Grease Pencil render result in a separate pass"); + RNA_def_property_update(prop, NC_SCENE | ND_LAYER, nullptr); + /* debug update routine */ func = RNA_def_function(srna, "update", "rna_ViewLayer_update_tagged"); RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); diff --git a/source/blender/nodes/composite/nodes/node_composite_image.cc b/source/blender/nodes/composite/nodes/node_composite_image.cc index 9df2e8b5e4b..0d779aefa6a 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.cc +++ b/source/blender/nodes/composite/nodes/node_composite_image.cc @@ -329,6 +329,11 @@ static void cmp_node_rlayer_create_outputs(bNodeTree *ntree, ntree, node, scene, view_layer, RE_PASSNAME_FREESTYLE, SOCK_RGBA); } + if (view_layer->grease_pencil_flags & GREASE_PENCIL_AS_SEPARATE_PASS) { + node_cmp_rlayers_register_pass( + ntree, node, scene, view_layer, RE_PASSNAME_GREASE_PENCIL, SOCK_RGBA); + } + MEM_freeN(data); node->storage = nullptr;