이 문서는 “그려진 이미지를 어떻게 다루는가”에 초점을 맞췄습니다. 기본은 RenderTarget에 그리기 → Filter.apply → 화면 출력이고, 레이어·블렌드·다중 post가 필요해지면 orx-compositor, 전체 화면 체인만 필요하면 Post 확장, 픽셀 단위 제어가 필요하면 커스텀 Filter + GLSL로 확장하면 됩니다.
RenderTarget · Filter · orx-fx · Blur · Color · Blend · Distortion · Custom GLSL · Feedback · Ping-Pong · Compositor의 개념을 다룹니다.
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.fx.blur.*
import org.openrndr.extra.fx.color.*
import org.openrndr.extra.fx.blend.*
import org.openrndr.extra.fx.distort.*
import org.openrndr.extra.compositor.*
import org.openrndr.math.Vector2
import kotlin.math.cos
import kotlin.math.sin
orx-fx, orx-compositor는 ORX 모듈입니다. 템플릿에서 해당 기능을 활성화한 뒤 IDE 자동 import를 사용하시기 바랍니다.drawer.circle, drawer.image, drawer.vertexBuffer 등으로 지오메트리·이미지를 그리는 단계입니다. 결과가 바로 화면(백버퍼)에 쓰이거나, 오프스크린 버퍼에 쓰입니다.즉, “무엇을 그릴지”가 렌더링이고, “그린 결과를 어떻게 보이게 할지”가 후처리입니다.
전형적인 흐름은 다음과 같습니다.
장면 그리기 → (오프스크린 ColorBuffer) → Filter 적용 → (결과 ColorBuffer) → drawer.image로 화면 출력
화면(백버퍼)에 직접 그린 뒤에도 후처리를 할 수 있지만, OPENRNDR에서는 RenderTarget에 그린 뒤 Filter를 적용하는 패턴이 기본입니다.
RenderTarget은 “그릴 수 있는 오프스크린 버퍼”입니다. colorBuffer() 첨부가 있어야 이미지가 저장됩니다.ColorBuffer를 입력·출력으로 받습니다. 따라서:renderTarget { colorBuffer() }로 장면을 그림filter.apply(sourceColorBuffer, targetColorBuffer)로 후처리drawer.image(target)으로 화면에 표시orx-compositor와 Post 확장은 이 버퍼 관리와 체인 구성을 자동화합니다.
val offscreen = renderTarget(width, height) {
colorBuffer()
depthBuffer() // Shape/contour 또는 3D를 그릴 때 필요
}
drawer.isolatedWithTarget(offscreen) {
drawer.clear(ColorRGBa.BLACK)
drawer.fill = ColorRGBa.PINK
drawer.stroke = null
drawer.circle(width / 2.0, height / 2.0, 100.0)
}
isolatedWithTarget은 타깃을 바인딩하고, 블록이 끝나면 이전 타깃으로 복원합니다.ortho(rt) 등으로 투영을 맞춥니다.colorBuffer(width, height) 또는 renderTarget의 colorBuffer(0)으로 얻습니다.ColorFormat), 타입(ColorType.UINT8, FLOAT16 등)을 지정할 수 있습니다.drawer.image(cb)로 화면에 그릴 수 있습니다.공식 가이드 기본 패턴:
fun main() = application {
configure { width = 770; height = 578 }
program {
val offscreen = renderTarget(width, height) {
colorBuffer()
depthBuffer()
}
val blur = BoxBlur()
val blurred = colorBuffer(width, height)
extend {
drawer.isolatedWithTarget(offscreen) {
clear(ColorRGBa.BLACK)
fill = ColorRGBa.PINK
stroke = null
circle(
cos(seconds) * 100.0 + width / 2,
sin(seconds) * 100.0 + height / 2.0,
100.0 + 100.0 * cos(seconds * 2.0)
)
}
blur.window = 30
blur.apply(offscreen.colorBuffer(0), blurred)
drawer.image(blurred)
}
}
}
ColorRGBa.TRANSPARENT로 clear한 뒤 여러 타깃을 순서대로 drawer.image로 올립니다.Filter는 프래그먼트 셰이더 기반의 이미지 처리 단위입니다.Filter1to1, Filter2to1 등으로 나뉩니다.apply(source, target) 또는 apply(arrayOf(src0, src1), target) 형태로 호출합니다.val blur = BoxBlur()
blur.window = 30
blur.apply(sourceBuffer, targetBuffer)
targetBuffer에 쓰이며, 이후 drawer.image(targetBuffer)로 표시합니다.window, sigma, gain 등 프로퍼티로 파라미터를 노출합니다.var name: Double by parameters로 유니폼을 연결합니다(공식 Noise 예제).여러 Filter를 순차 적용할 때 중간 버퍼가 필요합니다.
blur.apply(input, temp)
colorCorr.apply(temp, output)
Post 확장이나 orx-compositor의 post { }를 쓰면 중간 버퍼 할당·리사이즈를 자동화할 수 있습니다.
extend(Post()) {
val blur = ApproximateGaussianBlur()
val add = Add()
post { input, output ->
blur.window = 50
blur.sigma = 50.0
blur.apply(input, intermediate[0])
add.apply(arrayOf(input, intermediate[0]), output)
}
}
window (커널 반경 관련).val blur = BoxBlur()
blur.window = 30
blur.apply(src, dst)
| 클래스 | 특징 |
|---|---|
BoxBlur | 빠름, 품질 낮음 |
ApproximateGaussianBlur | 중간 속도·품질 (실무에서 자주 사용) |
GaussianBlur | 정밀, 상대적으로 느림 |
HashBlur | 노이즈 기반, 예술적 |
ZoomBlur | 중심 방향 줌 블러 |
FrameBlur | 시간축 평균(트레일 성격) |
val blur = ApproximateGaussianBlur()
blur.window = 25
blur.sigma = 10.0
blur.apply(src, dst)
window: 샘플 범위(픽셀 단위에 가까운 정수).sigma: 가우시안 표준편차. 클수록 더 부드럽게 퍼집니다.sigma = cos(seconds) * 10.0 + 10.01처럼 시간에 묶을 수 있습니다.Bloom, MipBloom 등이 있습니다.// 1) 밝은 부분만 남기는 처리(또는 원본 복사)
// 2) blur.apply(bright, bloomBuf)
// 3) Add().apply(arrayOf(original, bloomBuf), output)
Post 확장의 Add + ApproximateGaussianBlur 조합이 공식 데모에 자주 등장합니다.
ColorCorrection, ChromaticAberration, 톤맵(Uncharted2Tonemap) 등이 제공됩니다.drawer.drawStyle.colorMatrix에 행렬을 넣어 그리는 시점에 색을 바꿀 수 있습니다.drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.opacify(0.5))
// 또는 커스텀 행렬
drawer.clear(ColorRGBa.TRANSPARENT) 후 레이어를 쌓습니다.drawer.drawStyle.blendMode = BlendMode.ADD 등.Add(), Multiply(), Normal() 등은 두 입력을 받는 Filter2to1이며, compositor의 blend { }에 쓰입니다.공식 compositor 패턴:
val composite = compose {
draw {
drawer.clear(ColorRGBa.PINK)
drawer.circle(width / 2.0, height / 2.0, 175.0)
}
layer {
blend(Add()) { clip = true }
draw {
drawer.circle(width / 2.0, height / 2.0 + 100.0, 100.0)
}
post(ApproximateGaussianBlur()) {
window = 25
sigma = 10.0
}
}
}
extend {
composite.draw(drawer)
}
blend는 “아래 레이어와 어떻게 합칠지”, post는 “이 레이어 그린 뒤 어떤 필터를 적용할지”입니다.texture(tex0, distortedUV) 형태로 구현합니다.HorizontalWave, VerticalWave: 수평/수직 물결.ChromaticAberration: RGB 채널 분리.post(HorizontalWave()) {
amplitude = cos(seconds * 3) * 0.1
frequency = sin(seconds * 2) * 4
segments = 10
phase = seconds
}
post(VerticalWave()) { /* ... */ }
post(ApproximateGaussianBlur()) {
sigma = cos(seconds * 2) * 5.0 + 5.01
window = 25
}
적용 순서가 결과에 크게 영향을 줍니다(왜곡 후 블러 vs 블러 후 왜곡).
Filter가 정점·투영·상태 설정을 담당하므로, 사용자는 주로 fragment 셰이더만 작성합니다.표준 인터페이스(공식 Noise 필터):
in vec2 v_texCoord0;
uniform sampler2D tex0;
out vec4 o_color;
v_texCoord0: 0~1 UV.texture(tex0, uv)로 입력 픽셀을 읽습니다.uv + vec2(1.0/width, 0.0)처럼 오프셋을 더해 샘플합니다.val noiseShader = """
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0;
out vec4 o_color;
uniform float gain;
uniform float time;
#define HASHSCALE 443.8975
vec2 hash22(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * HASHSCALE);
p3 += dot(p3, p3.yzx + 19.19);
return fract(vec2((p3.x + p3.y) * p3.z, (p3.x + p3.z) * p3.y));
}
void main() {
float n = hash22(v_texCoord0 + vec2(time)).x;
vec4 color = texture(tex0, v_texCoord0) + vec4(vec3(n), 0.0) * gain;
o_color = color;
}
"""
class Noise : Filter(filterShaderFromCode(noiseShader, "noise-shader")) {
var gain: Double by parameters
var time: Double by parameters
init {
gain = 1.0
time = 0.0
}
}
사용:
noise.time = seconds
noise.gain = 1.0
noise.apply(offscreen.colorBuffer(0), offscreen.colorBuffer(0))
src == dst)가 안전합니다. 블러처럼 주변을 읽으면 별도 출력 버퍼를 쓰세요.uniform vec2 textureSize0 또는 textureSize(tex0, 0) (버전에 따라 제공 방식이 다를 수 있음).v_texCoord0.parameters로 넘긴 뒤 uniform float time 등으로 사용.개념적 패턴:
// persistent: 누적 버퍼
drawer.isolatedWithTarget(persistent) {
// 1) 전체를 약간 투명한 검정으로 덮어 fade
drawer.fill = ColorRGBa.BLACK.opacify(0.05)
drawer.stroke = null
drawer.rectangle(drawer.bounds)
// 2) 새 내용 그리기
drawer.fill = ColorRGBa.PINK
drawer.circle(...)
}
drawer.image(persistent.colorBuffer(0))
orx-no-clear 확장으로 “화면을 지우지 않고” 그리는 방법도 있습니다(캔버스형 트레일).FrameBlur는 시간축 블러에 가깝습니다.이웃 픽셀을 읽거나, 시뮬레이션을 반복할 때 읽기 버퍼와 쓰기 버퍼를 분리합니다.
A (read) → Filter → B (write)
swap: B가 다음 read, A가 다음 write
var read = colorBuffer(w, h)
var write = colorBuffer(w, h)
// 매 프레임 또는 반복 루프
filter.apply(read, write)
val tmp = read; read = write; write = tmp
FLOAT16/FLOAT32로 올리면 정밀도가 좋아질 수 있습니다.compose { } DSL로 레이어 트리, blend, post 체인을 선언합니다.orx-fx와 함께 쓰는 것이 일반적입니다.layer {
draw { /* ... */ }
post(HorizontalWave()) { /* params */ }
post(VerticalWave()) { /* params */ }
post(ApproximateGaussianBlur()) { /* params */ }
}
post 호출 순서 = 적용 순서입니다.| 구성 | 효과 |
|---|---|
blend(Add()) { clip = true } | 가산 + 아래 레이어로 클리핑 |
blend(Normal()) { clip = true } | 일반 알파 + 마스크처럼 사용 |
중첩 layer { layer { } } | 마스크·그림자를 분리해 합성 |
post 다중 호출 | 필터 체인 |
마스킹 예(텍스트가 있는 곳에만 이미지):
blend(Normal()) { clip = true }드롭 섀도:
post(DropShadow()) { window = 10; gain = 1.0; ... }| 방식 | 적합한 경우 |
|---|---|
| 수동 RenderTarget + Filter | 단순 1~2단 후처리, 버퍼를 직접 제어할 때 |
Post() 확장 | 전체 화면 후처리, 중간 버퍼 자동 관리 |
orx-compositor | 다중 레이어, 블렌드, 레이어별 post, 마스크 |
| 항목 | 내용 |
|---|---|
| 코드 실행 여부 | 공식 Filters / Compositor 예제 기반. Ping-pong·커스텀 체인 조합은 0.5.0 직접 실행 미검증 |
| ORX import | BoxBlur, ApproximateGaussianBlur, Add, compose 등 IDE 자동 import |
| in-place apply | 이웃 샘플 Filter는 src ≠ dst 권장 |
| premultiplied alpha | orx-fx는 premultiplied 입력 가정 |
| content scale | 고DPI에서 블러 window/sigma 체감이 달라질 수 있음 (0.5.0에서 일부 수정) |
| 파라미터 이름 | ColorCorrection 등 세부 프로퍼티는 orx-fx 소스·데모 확인 |
| 하고 싶은 일 | 사용할 기능 |
|---|---|
| 장면 블러 후 표시 | RT에 그리기 → BoxBlur/ApproximateGaussianBlur.apply → drawer.image |
| 블룸 | 블러 + Add (또는 Bloom) |
| 전체 화면 후처리 체인 | extend(Post()) { post { ... } } |
| 레이어별 효과·마스크 | orx-compositor의 layer / blend / post |
| 물결·수차 | HorizontalWave, VerticalWave, ChromaticAberration |
| 커스텀 픽셀 효과 | Filter 상속 + GLSL fragment |
| 잔상·트레일 | 누적 RT + 매 프레임 fade, 또는 no-clear |
| 안정적 피드백/시뮬 | Ping-pong 두 버퍼 |
program { }에서 한 번만 만들고 재사용합니다. 매 프레임 colorBuffer() 생성은 피합니다.ApproximateGaussianBlur가 실용적입니다.orx-fx 전체 필터 목록의 파라미터 상세(버전별로 소스 확인)orx-temporal-blur (비실시간 지향)공식 가이드
저장소
릴리즈