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
This commit is contained in:
YimingWu
2025-04-28 12:59:54 +02:00
committed by Falk David
parent 0dc4754da4
commit b05b0f413c
7 changed files with 91 additions and 23 deletions

View File

@@ -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")

View File

@@ -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):

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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"
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -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);

View File

@@ -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;