multiple effects

This commit is contained in:
2025-12-24 14:36:54 -06:00
parent 03159c66d7
commit 971299f062

237
film2d.cm
View File

@@ -133,79 +133,48 @@ function process_scene_tree(node, camera, ctx, parent_tint, parent_opacity, pare
return collect_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx)
}
// Process a group with effects - creates an "island" if needed
// Process a group with effects - creates an "island" and applies effects sequentially
function process_effect_group(node, camera, ctx, parent_tint, parent_opacity, parent_scissor, parent_pos) {
var effects = node.effects
var drawables = []
var backend = ctx.backend
var target_size = ctx.target_size
// For each effect, determine if we need an island
// 1. Render all children to an initial content target
var current_target = backend.get_or_create_target(target_size.width, target_size.height, 'effect_start_' + ctx.target_counter++)
var child_drawables = collect_children_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx)
render_drawables_to_target(child_drawables, current_target, camera, ctx)
// 2. Apply effects sequentially, each one consuming the previous target and producing a new one
for (var effect of effects) {
var effect_type = effect.type
if (effect_type == 'bloom') {
// Bloom requires an island - render children to intermediate, apply bloom, return as single drawable
var island_result = render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos)
if (island_result) {
drawables.push(island_result)
}
return drawables
}
if (effect_type == 'mask') {
// Mask - check if soft (needs texture) or hard (can use stencil)
var soft = effect.soft || false
if (soft) {
// Soft mask needs island
var island_result = render_mask_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos)
if (island_result) {
drawables.push(island_result)
}
return drawables
} else {
// Hard mask - can potentially use stencil inline
// For now, still use island approach for simplicity
var island_result = render_mask_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos)
if (island_result) {
drawables.push(island_result)
}
return drawables
}
}
if (effect_type == 'blur') {
// Blur requires an island
var island_result = render_blur_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos)
if (island_result) {
drawables.push(island_result)
}
return drawables
current_target = apply_bloom_effect(current_target, effect, ctx)
} else if (effect_type == 'mask') {
current_target = apply_mask_effect(current_target, effect, ctx)
} else if (effect_type == 'blur') {
current_target = apply_blur_effect(current_target, effect, ctx)
}
}
// Unknown effects - just render children normally
return collect_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx)
// 3. Return a single drawable that blits the final result
return [{
type: 'blit_target',
layer: node.layer || 0,
world_y: 0,
target: current_target,
width: target_size.width,
height: target_size.height
}]
}
// Render bloom effect as an island
function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) {
var backend = ctx.backend
var target_size = ctx.target_size
// Allocate targets for the island
var content_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_content_' + ctx.target_counter++)
var threshold_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_threshold_' + ctx.target_counter++)
var blur_target_a = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_blur_a_' + ctx.target_counter++)
var blur_target_b = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_blur_b_' + ctx.target_counter++)
var output_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_output_' + ctx.target_counter++)
// Collect children drawables
var child_drawables = collect_children_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx)
// Render children to content target
ctx.commands.push({cmd: 'begin_render', target: content_target, clear: {r: 0, g: 0, b: 0, a: 0}})
// Helper: Render a list of drawables to a specific target
function render_drawables_to_target(drawables, target, camera, ctx) {
ctx.commands.push({cmd: 'begin_render', target: target, clear: {r: 0, g: 0, b: 0, a: 0}})
ctx.commands.push({cmd: 'set_camera', camera: camera})
var batches = batch_drawables(child_drawables)
var batches = batch_drawables(drawables)
for (var batch of batches) {
if (batch.type == 'sprite_batch') {
ctx.commands.push({
@@ -217,15 +186,42 @@ function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opac
})
} else if (batch.type == 'text') {
ctx.commands.push({cmd: 'draw_text', drawable: batch.drawable})
} else if (batch.type == 'particles') {
ctx.commands.push({
cmd: 'draw_batch',
batch_type: 'particles',
geometry: {sprites: batch.sprites},
texture: batch.texture,
material: batch.material
})
} else if (batch.type == 'blit_target') {
ctx.commands.push({
cmd: 'blit',
texture: batch.drawable.target,
target: target,
dst_rect: {x: 0, y: 0, width: batch.drawable.width, height: batch.drawable.height},
filter: 'linear'
})
}
}
ctx.commands.push({cmd: 'end_render'})
}
// Apply bloom effect to a source target, returning a new target
function apply_bloom_effect(src_target, effect, ctx) {
var backend = ctx.backend
var target_size = ctx.target_size
var threshold_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_threshold_' + ctx.target_counter++)
var blur_target_a = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_blur_a_' + ctx.target_counter++)
var blur_target_b = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_blur_b_' + ctx.target_counter++)
var output_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_output_' + ctx.target_counter++)
// Threshold pass
ctx.commands.push({
cmd: 'shader_pass',
shader: 'threshold',
input: content_target,
input: src_target,
output: threshold_target,
uniforms: {threshold: effect.threshold || 0.8, intensity: effect.intensity || 1.0}
})
@@ -236,7 +232,6 @@ function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opac
var blur_src = threshold_target
for (var i = 0; i < blur_passes; i++) {
// Horizontal blur
ctx.commands.push({
cmd: 'shader_pass',
shader: 'blur',
@@ -244,8 +239,6 @@ function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opac
output: blur_target_a,
uniforms: {direction: [2, 0], texel_size: texel_size}
})
// Vertical blur
ctx.commands.push({
cmd: 'shader_pass',
shader: 'blur',
@@ -253,146 +246,62 @@ function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opac
output: blur_target_b,
uniforms: {direction: [0, 2], texel_size: texel_size}
})
blur_src = blur_target_b
}
// Composite bloom back onto content
// Composite bloom back onto source
ctx.commands.push({
cmd: 'composite_textures',
base: content_target,
base: src_target,
overlay: blur_src,
output: output_target,
mode: 'add'
})
// Return a drawable that blits the result
return {
type: 'blit_target',
layer: node.layer || 0,
world_y: 0,
target: output_target,
width: target_size.width,
height: target_size.height
}
return output_target
}
// Render mask effect as an island
function render_mask_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) {
// Apply mask effect to a source target, returning a new target
function apply_mask_effect(src_target, effect, ctx) {
var backend = ctx.backend
var target_size = ctx.target_size
var camera = ctx.camera
// Allocate targets
var content_target = backend.get_or_create_target(target_size.width, target_size.height, 'mask_content_' + ctx.target_counter++)
var mask_target = backend.get_or_create_target(target_size.width, target_size.height, 'mask_source_' + ctx.target_counter++)
var output_target = backend.get_or_create_target(target_size.width, target_size.height, 'mask_output_' + ctx.target_counter++)
// Collect children drawables (the content to be masked)
var child_drawables = collect_children_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx)
// Render children to content target
ctx.commands.push({cmd: 'begin_render', target: content_target, clear: {r: 0, g: 0, b: 0, a: 0}})
ctx.commands.push({cmd: 'set_camera', camera: camera})
var batches = batch_drawables(child_drawables)
for (var batch of batches) {
if (batch.type == 'sprite_batch') {
ctx.commands.push({
cmd: 'draw_batch',
batch_type: 'sprites',
geometry: {sprites: batch.sprites},
texture: batch.texture,
material: batch.material
})
} else if (batch.type == 'text') {
ctx.commands.push({cmd: 'draw_text', drawable: batch.drawable})
}
}
ctx.commands.push({cmd: 'end_render'})
// Render mask source
var mask_source = effect.source
if (mask_source) {
var mask_drawables = collect_drawables(mask_source, camera, null, null, null, null, ctx)
ctx.commands.push({cmd: 'begin_render', target: mask_target, clear: {r: 0, g: 0, b: 0, a: 0}})
ctx.commands.push({cmd: 'set_camera', camera: camera})
var mask_batches = batch_drawables(mask_drawables)
for (var batch of mask_batches) {
if (batch.type == 'sprite_batch') {
ctx.commands.push({
cmd: 'draw_batch',
batch_type: 'sprites',
geometry: {sprites: batch.sprites},
texture: batch.texture,
material: batch.material
})
} else if (batch.type == 'text') {
ctx.commands.push({cmd: 'draw_text', drawable: batch.drawable})
}
}
ctx.commands.push({cmd: 'end_render'})
render_drawables_to_target(mask_drawables, mask_target, camera, ctx)
}
// Apply mask
ctx.commands.push({
cmd: 'apply_mask',
content_texture: content_target,
content_texture: src_target,
mask_texture: mask_target,
output: output_target,
mode: effect.mode || 'alpha',
invert: effect.invert || false
})
// Return a drawable that blits the result
return {
type: 'blit_target',
layer: node.layer || 0,
world_y: 0,
target: output_target,
width: target_size.width,
height: target_size.height
}
return output_target
}
// Render blur effect as an island
function render_blur_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) {
// Apply blur effect to a source target, returning a new target
function apply_blur_effect(src_target, effect, ctx) {
var backend = ctx.backend
var target_size = ctx.target_size
// Allocate targets
var content_target = backend.get_or_create_target(target_size.width, target_size.height, 'blur_content_' + ctx.target_counter++)
var blur_target_a = backend.get_or_create_target(target_size.width, target_size.height, 'blur_a_' + ctx.target_counter++)
var blur_target_b = backend.get_or_create_target(target_size.width, target_size.height, 'blur_b_' + ctx.target_counter++)
// Collect children drawables
var child_drawables = collect_children_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx)
// Render children to content target
ctx.commands.push({cmd: 'begin_render', target: content_target, clear: {r: 0, g: 0, b: 0, a: 0}})
ctx.commands.push({cmd: 'set_camera', camera: camera})
var batches = batch_drawables(child_drawables)
for (var batch of batches) {
if (batch.type == 'sprite_batch') {
ctx.commands.push({
cmd: 'draw_batch',
batch_type: 'sprites',
geometry: {sprites: batch.sprites},
texture: batch.texture,
material: batch.material
})
} else if (batch.type == 'text') {
ctx.commands.push({cmd: 'draw_text', drawable: batch.drawable})
}
}
ctx.commands.push({cmd: 'end_render'})
// Blur passes
var blur_passes = effect.passes || 2
var texel_size = [1/target_size.width, 1/target_size.height]
var blur_src = content_target
var blur_src = src_target
var blur_dst = blur_target_a
for (var i = 0; i < blur_passes; i++) {
@@ -424,15 +333,7 @@ function render_blur_island(node, camera, ctx, effect, parent_tint, parent_opaci
blur_dst = tmp
}
// Return a drawable that blits the result
return {
type: 'blit_target',
layer: node.layer || 0,
world_y: 0,
target: blur_src,
width: target_size.width,
height: target_size.height
}
return blur_src
}
// Collect drawables from children only (not the node itself)