๊ธฐ๋ณธ ๋ํ ํจ์ ยท ์ ์ง์ ยท ๋ํ์ ๋ง์คํฌ๋ก ์ฐ๋ ๋ฐฉ๋ฒ ยท ๋ํ ์์ GLSL ์ ฐ์ด๋ ๋ฃ๊ธฐ๋ฅผ ๋ค๋ฃน๋๋ค.
| ๋ณ๊ฒฝ ์ฌํญ | ๋ํ ์์ ๊ณผ์ ๊ด๊ณ |
|---|---|
| SDL3 ๊ธฐ๋ฐ ๋ฐฑ์๋ ์ถ๊ฐ, GLFW๋ deprecated | ์ฐฝ ์์ฑยท์ ๋ ฅ ๊ณ์ธต์ ๋ณ๊ฒฝ์ ๋๋ค. ๊ทธ๋ฆฌ๊ธฐ API์๋ ์ํฅ์ด ์์ต๋๋ค. |
| macOS์ Angle ๊ธฐ๋ฐ GLES ๋ฐฑ์๋์์ shape/contour fill ๋ ๋๋ง ์์ | macOS์์ ์ฑ์์ง shape๊ฐ ์ด์ํ๊ฒ ๋ณด์ด๋ ๋ฌธ์ ๊ฐ ์์ ๋์์ต๋๋ค. |
Segment2D/3D์ ์ด์ฐจ(quadratic) ๊ทผ์ฌ ์ถ๊ฐ | openrndr-math ๋ณ๊ฒฝ์
๋๋ค. |
| Kotlin 2.4.20, Gradle 9.7.1, LWJGL 3.4.3 | ๋น๋ ํ๊ฒฝ ์๊ฑด์ ๋๋ค. |
openrndr-module-catalog, openrndr-dependency-catalog ์ถ๊ฐ | ์์กด์ฑ ์ ์ธ ๋ฐฉ์์ด ๋จ์ํด์ก์ต๋๋ค. |
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.math.Vector2
import org.openrndr.shape.*
import kotlin.math.cos
import kotlin.math.sin
orx-compositor, orx-fx, orx-shade-styles, orx-shapes)์ ํด๋์ค๋ ํจํค์ง๊ฐ ๋ชจ๋๋ณ๋ก ๋๋์ด ์์ต๋๋ค. ๋ณธ๋ฌธ ์ฝ๋์๋ ORX import๋ฅผ ์ ์ง ์์์ผ๋ฏ๋ก IDE์ ์๋ import๋ฅผ ์ฌ์ฉํ์๊ธฐ ๋ฐ๋๋๋ค.openrndr-template์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ gradle/libs.versions.toml์์ ๋ฒ์ ์ ์ง์ ํฉ๋๋ค.openrndr = "0.5.0"
orx = "0.5.0"
orx-compositor, orx-shade-styles ๋ฑ์ด ํฌํจ๋์ด ์๋ค๊ณ ๊ณต์ ๊ฐ์ด๋์ ๋ช
์๋์ด ์์ต๋๋ค.fun main() = application {
configure {
width = 800
height = 600
}
program {
extend {
drawer.clear(ColorRGBa.PINK)
// ๊ทธ๋ฆฌ๊ธฐ ์ฝ๋
}
}
}
Double์
๋๋ค. width, height๋ Int์ด๋ฏ๋ก width / 2.0, height * 1.0์ฒ๋ผ Double๋ก ๋ณํํด์ ์ฌ์ฉํฉ๋๋ค.ShadeStyle, ShapeContour, Shape, RenderTarget)๋ extend ๋ฐ๊นฅ์ program { } ์์์ ํ ๋ฒ๋ง ๋ง๋๋ ๊ฒ์ด ์์น์
๋๋ค.| ํจ์ | ์ ๋ ฅ | ์ ์ฉ๋๋ ์คํ์ผ |
|---|---|---|
circle(x, y, radius) / circle(Vector2, radius) / circle(Circle) | ์ค์ฌ ๊ธฐ์ค | fill, stroke, strokeWeight |
circles(List<Circle>) / circles(List<Vector2>, radius) | ๋ฐฐ์น ๊ทธ๋ฆฌ๊ธฐ | ๋์ผ |
rectangle(x, y, width, height) / rectangle(Rectangle) | ์ผ์ชฝ ์ ๊ธฐ์ค | fill, stroke, strokeWeight |
rectangles { } | ๋ฐฐ์น ๋น๋ | ๋์ผ |
lineSegment(x0, y0, x1, y1) | ์ ๋ถ | stroke, strokeWeight, lineCap |
lineStrip(List<Vector2>) | ๊บพ์์ | stroke, strokeWeight, lineCap, lineJoin |
segment(Segment2D) / segments(List) | ๋ฒ ์ง์ ์กฐ๊ฐ | stroke |
contour(ShapeContour) / contours(List) | ์ค๊ณฝ์ | ๋ซํ contour๋ fill ์ฌ์ฉ, stroke ์ฌ์ฉ |
shape(Shape) / shapes(List) | ๊ตฌ๋ฉ์ ํฌํจํ ์ ์๋ ๋ํ | fill, stroke |
(x, y)๋ฅผ ์ค์ฌ์ผ๋ก ๊ทธ๋ ค์ง๋๋ค. ์ฑ์์ drawer.fill, ํ
๋๋ฆฌ๋ drawer.stroke์ drawer.strokeWeight๋ฅผ ๋ฐ๋ฆ
๋๋ค.fill ๋๋ stroke์ null์ ์ง์ ํ๋ฉด ํด๋น ๋ถ๋ถ์ ๊ทธ๋ฆฌ์ง ์์ต๋๋ค.fun main() = application {
configure { height = 300 }
program {
extend {
drawer.clear(ColorRGBa.PINK)
// ํฐ ์ฑ์ + ๊ฒ์ ํ
๋๋ฆฌ
drawer.fill = ColorRGBa.WHITE
drawer.stroke = ColorRGBa.BLACK
drawer.strokeWeight = 1.0
drawer.circle(width / 6.0, height / 2.0, width / 8.0)
// ์ฑ์ ์์, ํ
๋๋ฆฌ๋ง
drawer.fill = null
drawer.stroke = ColorRGBa.BLACK
drawer.circle(width / 6.0 + width / 3.0, height / 2.0, width / 8.0)
// ์ฑ์๋ง, ํ
๋๋ฆฌ ์์
drawer.fill = ColorRGBa.WHITE
drawer.stroke = null
drawer.circle(width / 6.0 + 2 * width / 3.0, height / 2.0, width / 8.0)
}
}
}
Vector2๋ Circle ๊ฐ์ ์ด๋ฏธ ๊ฐ์ง๊ณ ์๋ค๋ฉด ์ค๋ฒ๋ก๋๋ฅผ ์ฌ์ฉํ๋ฉด ํธํฉ๋๋ค.drawer.circle(mouse.position, 50.0) // Vector2 + ๋ฐ์ง๋ฆ
drawer.circle(Circle(200.0, 200.0, 40.0)) // Circle ๊ฐ์ฒด
rectangle์ ์ผ์ชฝ ์ ๋ชจ์๋ฆฌ ์ขํ์ ๋๋นยท๋์ด๋ฅผ ๋ฐ์ต๋๋ค. ์ค์ฌ ๊ธฐ์ค์ผ๋ก ๊ทธ๋ฆฌ๋ ค๋ฉด Rectangle.fromCenter๋ฅผ ์ฌ์ฉํฉ๋๋ค.extend {
drawer.clear(ColorRGBa.PINK)
drawer.fill = ColorRGBa.WHITE
drawer.stroke = ColorRGBa.BLACK
// ์ผ์ชฝ ์ ๊ธฐ์ค
drawer.rectangle(50.0, 50.0, 200.0, 120.0)
// ์ค์ฌ ๊ธฐ์ค: Rectangle ๊ฐ์ฒด๋ฅผ ๋ง๋ ๋ค ์ ๋ฌ
val r = Rectangle.fromCenter(Vector2(width / 2.0, height / 2.0), 200.0, 120.0)
drawer.rectangle(r)
}
stroke์ strokeWeight๋ก ๊ทธ๋ ค์ง๋ฉฐ fill์ ์ํฅ์ ์ฃผ์ง ์์ต๋๋ค.lineCap(BUTT, ROUND, SQUARE), ๊บพ์ด๋ ๋ถ๋ถ์ lineJoin์ผ๋ก ์ง์ ํฉ๋๋ค.extend {
drawer.clear(ColorRGBa.PINK)
drawer.stroke = ColorRGBa.BLACK
drawer.strokeWeight = 5.0
drawer.lineCap = LineCap.ROUND
drawer.lineSegment(10.0, height / 2.0 - 20.0, width - 10.0, height / 2.0 - 20.0)
drawer.lineCap = LineCap.BUTT
drawer.lineSegment(10.0, height / 2.0, width - 10.0, height / 2.0)
drawer.lineCap = LineCap.SQUARE
drawer.lineSegment(10.0, height / 2.0 + 20.0, width - 10.0, height / 2.0 + 20.0)
// ๊บพ์์
drawer.lineJoin = LineJoin.ROUND
drawer.lineStrip(
listOf(
Vector2(10.0, height - 10.0),
Vector2(width / 2.0, 10.0),
Vector2(width - 10.0, height - 10.0)
)
)
}
translate๋ฅผ ๋จผ์ ์ฐ๊ณ rotate๋ฅผ ๋์ค์ ์ฐ๋ฉด ์ค์ ๋ก๋ ํ์ ์ด ๋จผ์ ์ ์ฉ๋ ๋ค ์ด๋๋ฉ๋๋ค.isolated { }๋ ์คํ์ผ๊ณผ ๋ณํ์ ์ ์ฅํ๋ค๊ฐ ๋ธ๋ก์ด ๋๋๋ฉด ๋ณต์ํฉ๋๋ค.extend {
drawer.clear(ColorRGBa.PINK)
drawer.stroke = null
for (i in 0 until 5) {
drawer.isolated {
translate(120.0 + i * 140.0, height / 2.0)
rotate(seconds * 30.0 + i * 15.0)
scale(1.0 + i * 0.15)
fill = ColorRGBa.WHITE.shade(1.0 - i * 0.15)
rectangle(-40.0, -40.0, 80.0, 80.0) // ์์ ๊ธฐ์ค์ผ๋ก ๋ฐฐ์น
}
}
}
OPENRNDR์ ๋ํ ํํ์ ์ธ ๋จ๊ณ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
| ๋จ๊ณ | ํด๋์ค | ์ค๋ช |
|---|---|---|
| 1 | Segment2D | ์์์ , ๋์ , ์ ์ด์ 0~2๊ฐ๋ก ์ด๋ฃจ์ด์ง ๋ฒ ์ง์ ์กฐ๊ฐ(์ง์ ยท2์ฐจยท3์ฐจ) |
| 2 | ShapeContour | ๋๊ณผ ์์์ด ์ด์ด์ง Segment ๋ชจ์. ๋ซํ ๊ฒ(O)๊ณผ ์ด๋ฆฐ ๊ฒ(S)์ด ์์ |
| 3 | Shape | ShapeContour์ ๋ชจ์. ๊ตฌ๋ฉ์ด ์๋ ๋ํ์ ํํ ๊ฐ๋ฅ |
Circle, Rectangle, LineSegment ๊ฐ์ ์ํ ๊ฐ์ฒด๋ ํ๋ฉด์ ๊ทธ๋ฆฌ์ง ์์๋ ์กด์ฌํ ์ ์์ผ๋ฉฐ, .contour ๋๋ .shape๋ก ๋ณํ๋ฉ๋๋ค.
val c = Circle(200.0, 200.0, 50.0).contour // ShapeContour
val s = Circle(200.0, 200.0, 50.0).shape // Shape
moveTo, lineTo, curveTo, arcTo, close)๋ก contour๋ฅผ ๋ง๋ญ๋๋ค.cursor๋ ํ์ฌ ์์น, anchor๋ ํ์ฌ ์๋ธํจ์ค์ ์์์ ์
๋๋ค.fun main() = application {
program {
// ํ๋ ์๋ง๋ค ๋ฐ๋์ง ์์ผ๋ฏ๋ก ํ ๋ฒ๋ง ์์ฑ
val triangle = contour {
moveTo(Vector2(width / 2.0 - 120.0, height / 2.0 - 120.0))
lineTo(cursor + Vector2(240.0, 0.0))
lineTo(cursor + Vector2(0.0, 240.0))
lineTo(anchor)
close()
}
extend {
drawer.clear(ColorRGBa.WHITE)
drawer.fill = ColorRGBa.PINK
drawer.stroke = null
drawer.contour(triangle)
}
}
}
ShapeContour.fromPoints(points, closed)๋ ์ ๋ค์ ์ง์ ์ผ๋ก ์ฐ๊ฒฐํฉ๋๋ค. ์ ๋ค๊ฐํ๊ณผ ๋ณ์ ์ด ํจ์๋ก ๋ง๋ค ์ ์์ต๋๋ค.
fun regularPolygon(center: Vector2, radius: Double, sides: Int, rotationDeg: Double = -90.0): ShapeContour {
val points = List(sides) {
val a = Math.toRadians(rotationDeg) + it * 2.0 * Math.PI / sides
center + Vector2(cos(a), sin(a)) * radius
}
return ShapeContour.fromPoints(points, closed = true)
}
fun star(center: Vector2, outer: Double, inner: Double, tips: Int, rotationDeg: Double = -90.0): ShapeContour {
val points = List(tips * 2) {
val r = if (it % 2 == 0) outer else inner
val a = Math.toRadians(rotationDeg) + it * Math.PI / tips
center + Vector2(cos(a), sin(a)) * r
}
return ShapeContour.fromPoints(points, closed = true)
}
fun main() = application {
configure { width = 800; height = 400 }
program {
val hexagon = regularPolygon(Vector2(200.0, 200.0), 120.0, 6)
val starContour = star(Vector2(600.0, 200.0), 140.0, 60.0, 5)
extend {
drawer.clear(ColorRGBa.WHITE)
drawer.fill = ColorRGBa.PINK
drawer.stroke = ColorRGBa.BLACK
drawer.strokeWeight = 3.0
drawer.contour(hexagon)
drawer.contour(starContour)
}
}
}
orx-shapes์ hobbyCurve(points, closed)๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ณต์ ๊ฐ์ด๋๋ ์ด ํจ์๋ฅผ ShapeContour.fromPoints์ ๊ณก์ ๋ฒ์ ์ผ๋ก ์๊ฐํฉ๋๋ค.๊ฐ Segment๋ ์ด์ ์กฐ๊ฐ์ด ๋๋๋ ์ง์ ์์ ์์ํด์ผ ํฉ๋๋ค.
val segments = listOf(
Segment2D(Vector2(10.0, 100.0), Vector2(200.0, 80.0)), // ์ง์
Segment2D(Vector2(200.0, 80.0), Vector2(250.0, 280.0), Vector2(400.0, 80.0)), // 2์ฐจ
Segment2D(Vector2(400.0, 80.0), Vector2(450.0, 180.0), Vector2(500.0, 0.0), Vector2(630.0, 80.0)) // 3์ฐจ
)
val open = ShapeContour.fromSegments(segments, closed = false)
val pos = contour.position(0.1) // ์์ ๊ทผ์ฒ์ ์
val normal = contour.normal(0.9) // ๋ ๊ทผ์ฒ์ ๋ฒ์
val bounds = contour.bounds // Rectangle
val length = contour.length // ๊ธธ์ด
val points = contour.equidistantPositions(20) // ๋ฑ๊ฐ๊ฒฉ ์ 20๊ฐ
val part = contour.sub(0.0, 0.5) // ์ ์ ๋ฐ๋ง ์๋ผ๋ธ contour
val grown = contour.offset(10.0) // ๋ฐ๊นฅ์ผ๋ก ์คํ์
position(ut)์ ut๋ ๊ธธ์ด์ ๋น๋กํ์ง ์์ต๋๋ค. ๋ฑ์ ์ด๋์ด ํ์ํ๋ฉด orx-shapes์ rectified()๋ฅผ ์ฌ์ฉํฉ๋๋ค.shape { contour { ... } contour { ... } } ๋น๋๋ก ์ฌ๋ฌ contour๋ฅผ ๋ฌถ์ต๋๋ค. ๊ณต์ ์์ ์์๋ ๋ฐ๊นฅ ์ค๊ณฝ๊ณผ ์์ชฝ ์ค๊ณฝ์ ํจ๊ป ๋ฃ์ด ๊ตฌ๋ฉ์ด ์๋ ๋ํ์ ๋ง๋ญ๋๋ค.val s = shape {
contour {
moveTo(Vector2(width / 2.0 - 120.0, height / 2.0 - 120.0))
lineTo(cursor + Vector2(240.0, 0.0))
lineTo(cursor + Vector2(0.0, 240.0))
lineTo(anchor)
close()
}
contour {
moveTo(Vector2(width / 2.0 - 80.0, height / 2.0 - 100.0))
lineTo(cursor + Vector2(190.0, 0.0))
lineTo(cursor + Vector2(0.0, 190.0))
lineTo(anchor)
close()
}
}
drawer.shape(s)
compound { } ๋น๋๋ก ํฉ์งํฉ(union), ์ฐจ์งํฉ(difference), ๊ต์งํฉ(intersection)์ ๊ณ์ฐํฉ๋๋ค. ๊ฒฐ๊ณผ๋ List<Shape>์ด๋ฉฐ drawer.shapes(...)๋ก ๊ทธ๋ฆฝ๋๋ค.fun main() = application {
configure { width = 800; height = 400 }
program {
val ring = compound {
difference {
shape(Circle(200.0, 200.0, 140.0).shape)
shape(Circle(200.0, 200.0, 90.0).shape)
}
}
val lens = compound {
intersection {
shape(Circle(560.0, 160.0, 120.0).shape)
shape(Circle(560.0, 240.0, 120.0).shape)
}
}
extend {
drawer.clear(ColorRGBa.WHITE)
drawer.fill = ColorRGBa.PINK
drawer.stroke = ColorRGBa.PINK.shade(0.7)
drawer.shapes(ring)
drawer.shapes(lens)
}
}
}
compound๋ ํธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ง์ํ๋ฏ๋ก union { intersection { ... } intersection { ... } }์ฒ๋ผ ์ค์ฒฉํ ์ ์์ต๋๋ค.extend ๋ฐ๊นฅ์์ ํ ๋ฒ๋ง ๊ณ์ฐํฉ๋๋ค.drawer.circle()์ ๋ฐ๋ณตํ๋ ๊ฒ๋ณด๋ค drawer.circles(list)๊ฐ ํจ์ฌ ๋น ๋ฆ
๋๋ค.fun main() = application {
program {
extend {
drawer.clear(ColorRGBa.PINK)
drawer.fill = ColorRGBa.WHITE
drawer.stroke = ColorRGBa.BLACK
drawer.strokeWeight = 1.0
val circles = List(5000) {
Circle(Math.random() * width, Math.random() * height, Math.random() * 10.0 + 5.0)
}
drawer.circles(circles)
}
}
}
drawer.rectangleBatch { }๋ก ํ ๋ฒ๋ง ๋ง๋ค์ด ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค.extend {
drawer.clear(ColorRGBa.GRAY)
drawer.rectangles {
repeat(100) {
fill = ColorRGBa.PINK.opacify(0.2)
stroke = null
rectangle(Rectangle((it * 37.0) % width, 0.0, 30.0, 5.0 + it * 3.0))
}
}
}
ShapeContour์ Shape๋ฅผ RenderTarget์ ๊ทธ๋ฆด ๋๋ ์ปฌ๋ฌ ๋ฒํผ์ ํจ๊ป ๊น์ด(์คํ
์ค) ๋ฒํผ๊ฐ ํ์ํฉ๋๋ค. ์์ผ๋ฉด drawing ... requires a render target with a stencil attachment ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.val rt = renderTarget(width, height) {
colorBuffer()
depthBuffer() // contour/shape๋ฅผ ๊ทธ๋ฆด ๋ ํ์
}
ortho(rt)๋ก ํฌ์ ํ๋ ฌ์ ๋ง์ถฐ์ผ ํฉ๋๋ค.ColorRGBa์ด๋ฉฐ ์ฑ๋ ๊ฐ์ 0.0~1.0์
๋๋ค.BLACK, WHITE, RED, GREEN, BLUE, YELLOW, GRAY, PINK. ์ด ๋ฐ์ ORANGE, PURPLE, TRANSPARENT๋ ๊ณต์ ์์ ์ ์ฌ์ฉ๋ฉ๋๋ค.// ์์ฑ์: R, G, B, (A)
val red = ColorRGBa(1.0, 0.0, 0.0)
val halfBlue = ColorRGBa(0.0, 0.0, 1.0, 0.5)
// rgb / rgba ํจ์
val magenta = rgb(1.0, 0.0, 1.0)
val magentaHalf = rgb(1.0, 0.0, 1.0, 0.5)
// 16์ง์ ์ฝ๋ (์ ์ ๋๋ ๋ฌธ์์ด, ๋ฌธ์์ด์ # ์ ์๋ต ๊ฐ๋ฅ)
val c1 = ColorRGBa.fromHex(0xffc0cb)
val c2 = ColorRGBa.fromHex("#ffc0cb")
Drawer๊ฐ ์ ์งํ๋ DrawStyle ์ค ๋ํ๊ณผ ๊ด๋ จ๋ ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค(๊ณต์ ๊ฐ์ด๋์ ํ).
| ์์ฑ | ํ์ | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
|---|---|---|---|
fill | ColorRGBa? | WHITE | ์ฑ์ ์. null์ด๋ฉด ์ฑ์ฐ์ง ์์ |
stroke | ColorRGBa? | BLACK | ์ ์. null์ด๋ฉด ์ ์ ๊ทธ๋ฆฌ์ง ์์ |
strokeWeight | Double | 1.0 | ์ ๊ตต๊ธฐ |
smooth | Boolean | true | ์ค๊ณฝ์ ๋ถ๋๋ฝ๊ฒ ํ์ํ ์ง ์ฌ๋ถ |
lineCap | LineCap | BUTT | ์ ๋ ๋ชจ์ |
lineJoin | LineJoin | MITER | ๊บพ์ด๋ ๋ถ๋ถ ๋ชจ์ |
miterLimit | Double | 4.0 | miter ์ต๋ ๊ธธ์ด |
shadeStyle | ShadeStyle? | null | ์ ฐ์ด๋ ์คํ์ผ (5์ฅ) |
blendMode | BlendMode | OVER(๊ฐ์ด๋ ํ ๊ธฐ์ค) | ํฉ์ฑ ๋ฐฉ์ |
clip | Rectangle? | null | ์ง์ฌ๊ฐํ ํด๋ฆฌํ ์์ญ (4์ฅ) |
extend {
drawer.clear(ColorRGBa.fromHex("#1E2A78"))
drawer.fill = ColorRGBa.fromHex("#FFC0CB")
drawer.stroke = ColorRGBa.WHITE
drawer.strokeWeight = 6.0
drawer.lineJoin = LineJoin.ROUND
drawer.rectangle(100.0, 100.0, 300.0, 200.0)
}
shade(f): 1.0 ๋ฏธ๋ง์ด๋ฉด ์ด๋ก๊ฒ, 1.0 ์ด๊ณผ์ด๋ฉด ๋ฐ๊ฒ ๋ง๋ญ๋๋ค(๊ณต์ ์์ ์์ shade(1.0 + i / 15.0)๋ก ๋ฐ์ ํค์ ๋ง๋ญ๋๋ค).opacify(f): ๋ถํฌ๋ช
๋๋ฅผ ์กฐ์ ํฉ๋๋ค.mix(a, b, t): ๋ ์์ t(0~1) ๋น์จ๋ก ์์ต๋๋ค.extend {
drawer.stroke = null
val base = ColorRGBa.PINK
val target = ColorRGBa.fromHex("#FC3549")
for (i in 0..15) {
val x = 35.0 + (700 / 16.0) * i
drawer.fill = base.shade(i / 15.0) // ์ด๋์ด ํค
drawer.rectangle(x, 32.0, 700 / 16.0, 64.0)
drawer.fill = base.shade(1.0 + i / 15.0) // ๋ฐ์ ํค
drawer.rectangle(x, 96.0, 700 / 16.0, 64.0)
drawer.fill = base.opacify(i / 15.0) // ํฌ๋ช
๋ ๋ณํ
drawer.rectangle(x, 160.0, 700 / 16.0, 64.0)
drawer.fill = mix(base, target, i / 15.0) // ํผํฉ
drawer.rectangle(x, 224.0, 700 / 16.0, 64.0)
}
}
ColorRGBa ์ธ์ ๋ค์ ์ ๊ณต๊ฐ ํด๋์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
| ํด๋์ค | ์ค๋ช |
|---|---|
ColorHSVa / ColorHSLa | ์์ยท์ฑ๋ยท๋ช ๋(๋ฐ๊ธฐ) |
ColorXSVa / ColorXSLa | Kuler ๋ฐฉ์์ผ๋ก ์์ ์ถ์ ๋ณด์ ํ HSV/HSL |
ColorXYZa, ColorYxya | CIE ์ ๊ณต๊ฐ |
ColorLABa, ColorLCHABa, ColorLSHABa | LAB ๊ณ์ด |
ColorLUVa, ColorLCHUVa, ColorLSHUVa | LUV ๊ณ์ด |
ColorATVa | Coloroid (๋ถ๋ถ ๊ตฌํ) |
ColorOKLABa | OKLab. ColorRGBa.toOKLABa()๋ก ๋ณํํ๋ฉฐ ๊ทธ๋ผ๋์ธํธ ํ๋ฆฌ์
์์ ์ฌ์ฉ๋จ |
extend {
drawer.stroke = null
for (i in 0 until 12) {
drawer.fill = ColorHSVa(i * 30.0, 0.7, 0.9).toRGBa() // hue 30๋์ฉ ์ฆ๊ฐ
drawer.circle(70.0 + i * 55.0, height / 2.0, 24.0)
}
}
XSV/XSL์ "ํ๊ฐ์๊ฒ ๋ ์ ํฉํ ์ ๊ณต๊ฐ"์ผ๋ก ์ค๋ช
ํฉ๋๋ค. ๋นจ๊ฐ-์ด๋ก, ํ๋-๋
ธ๋ ๋ณด์์ถ์ด ์์ฐ์ค๋ฝ๊ฒ ๋ฐฐ์น๋๊ธฐ ๋๋ฌธ์
๋๋ค.drawer.pushStyle()
drawer.fill = ColorRGBa.PINK
drawer.rectangle(100.0, 100.0, 100.0, 100.0)
drawer.popStyle()
// ์คํ์ผ๊ณผ ๋ณํ์ ํจ๊ป ์ ์ฅยท๋ณต์
drawer.isolated {
fill = ColorRGBa.PINK
rectangle(100.0, 100.0, 100.0, 100.0)
}
BlendMode ํญ๋ชฉ์ OVER, BLEND, ADD, SUBTRACT, MULTIPLY, REPLACE, REMOVE, MIN, MAX, SCREEN, OVERLAY, DARKEN, LIGHTEN, COLOR_DODGE, COLOR_BURN, HARD_LIGHT, SOFT_LIGHT, DIFFERENCE, EXCLUSION, HSL_HUE, HSL_SATURATION, HSL_COLOR, HSL_LUMINOSITY์
๋๋ค.ADD)์ ๊ฒน์น ๋ถ๋ถ์ด ๋ฐ์์ง๋ฏ๋ก ๋น ํํ์ ์ ํฉํฉ๋๋ค.extend {
drawer.clear(ColorRGBa.BLACK)
drawer.stroke = null
drawer.isolated {
drawStyle.blendMode = BlendMode.ADD
val colors = listOf(ColorRGBa.RED, ColorRGBa.GREEN, ColorRGBa.BLUE)
colors.forEachIndexed { i, c ->
fill = c.opacify(0.8)
circle(width / 2.0 + cos(seconds + i * 2.094) * 80.0,
height / 2.0 + sin(seconds + i * 2.094) * 80.0, 120.0)
}
}
}
DrawStyle.blendMode๋ฅผ BlendMode?(๊ธฐ๋ณธ๊ฐ null)๋ก ํ๊ธฐํ๊ณ , ๊ฐ์ด๋ ํ๋ ๊ธฐ๋ณธ๊ฐ์ OVER๋ก ํ๊ธฐํฉ๋๋ค. ํ๊ธฐ๊ฐ ๋ค๋ฅด๋ฏ๋ก ํฉ์ฑ ๋ฐฉ์์ด ์ค์ํ ์ฝ๋์์๋ ๋ช
์์ ์ผ๋ก ์ง์ ํ์๊ธฐ ๋ฐ๋๋๋ค.orx-shade-styles์ gradient ์์ฑ๊ธฐ๋ก ์ ํยท๋ฐฉ์ฌํยท์๋ฟํ ๊ทธ๋ผ๋์ธํธ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ๋ด๋ถ์ ์ผ๋ก GLSL ์ฝ๋๋ฅผ ์์ฑํ์ฌ GPU์์ ์คํํฉ๋๋ค.// ์ ํ: ํ์ ํ๋ ๋ฐฉํฅ
drawer.shadeStyle = gradient<ColorRGBa> {
stops[0.0] = ColorRGBa.PINK
stops[0.8] = ColorRGBa.ORANGE
stops[1.0] = ColorRGBa.RED
linear {
start = Polar(seconds * 60.0, 0.5).cartesian + 0.5
end = Polar(seconds * 60.0 + 180.0, 0.5).cartesian + 0.5
}
}
drawer.rectangle(80.0, 40.0, 200.0, 200.0)
drawer.circle(180.0, 340.0, 90.0)
// ๋ฐฉ์ฌํ
drawer.shadeStyle = gradient<ColorRGBa> {
stops[0.0] = ColorRGBa.PINK
stops[1.0] = ColorRGBa.RED
radial {
radius = 0.5
center = Vector2(cos(seconds), sin(seconds * 2.0)) * 0.5 + 0.5
}
}
// ์๋ฟํ + OKLab ๋ณด๊ฐ
drawer.shadeStyle = gradient<ColorOKLABa> {
stops[0.0] = ColorRGBa.PINK.toOKLABa()
stops[1.0] = ColorRGBa.PURPLE.toOKLABa()
conic { rotation = seconds * 60.0 }
}
quantization = 16์ผ๋ก ์์ ๋จ๊ณํํ ์ ์๊ณ , levelWarpFunction, domainWarpFunction์ GLSL ํจ์ ๋ฌธ์์ด์ ๋ฃ์ด ๊ทธ๋ผ๋์ธํธ๋ฅผ ๋ณํํ ์ ์์ต๋๋ค.Polar๋ org.openrndr.math์ ์์ต๋๋ค. gradient์ import ๊ฒฝ๋ก๋ 12์ฅ์ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.drawer.fill/drawer.stroke ์ํ๋ก ๊ฒฐ์ ๋ฉ๋๋ค. ๋ฐ๋ผ์ ๋ํ๋ง๋ค ์์ด ๋ค๋ฅด๋ฉด ๊ทธ๋ฆด ๋๋ง๋ค ์์ ๋ฐ๊ฟ ์ค๋๋ค.program {
val palette = listOf("#F94144", "#F3722C", "#F9C74F", "#90BE6D", "#43AA8B", "#577590")
.map { ColorRGBa.fromHex(it) }
extend {
drawer.clear(ColorRGBa.WHITE)
drawer.stroke = null
palette.forEachIndexed { i, color ->
drawer.fill = color
drawer.circle(90.0 + i * 120.0, height / 2.0, 50.0)
}
}
}
OPENRNDR์ Drawer๋ ์ง์ฌ๊ฐํ ํด๋ฆฌํ ํ๋๋ง ์ง์ ์ง์ํฉ๋๋ค. ์์์ ๋ํ์ ๋ง์คํฌ๋ก ์ฐ๋ ค๋ฉด ์๋ ๋ฐฉ์ ์ค ํ๋๋ฅผ ์ ํํฉ๋๋ค.
| ๋ฐฉ์ | ๋ง์คํฌ ํํ | ๊ฒฐ๊ณผ | ์ฅ์ | ํ๊ณ |
|---|---|---|---|---|
A. drawStyle.clip | ์ง์ฌ๊ฐํ 1๊ฐ | ๋์คํฐ | ๊ฐ์ฅ ๋จ์ํจ | ์ง์ฌ๊ฐํ๋ง ๊ฐ๋ฅ |
B. compound { intersection } | ์์์ ๋ฒกํฐ ๋ํ | ๋ฒกํฐ | ๊ฒฐ๊ณผ๊ฐ ๋ํ์ด๋ฏ๋ก strokeยท์ ฐ์ด๋ ์ ์ฉ ๊ฐ๋ฅ, ๊ฒฝ๊ณ๊ฐ ์ ๋ช ํจ | ์ ๋ ฅ์ด ํ๋ ์๋ง๋ค ๋ฐ๋๋ฉด CPU ๋น์ฉ ์ฆ๊ฐ |
C. orx-compositor ๋ ์ด์ด + clip | ์์์ ๊ทธ๋ฆฌ๊ธฐ ๊ฒฐ๊ณผ | ๋์คํฐ | ์ด๋ฏธ์งยทํ ์คํธยท๋ํ ๋ชจ๋ ๋ง์คํฌ๋ก ์ฌ์ฉ ๊ฐ๋ฅ, ๋ธ๋ฌ ๋ฑ ํ์ฒ๋ฆฌ ๊ฒฐํฉ ์ฉ์ด | ORX ์์กด, ๋ ์ด์ด๋ง๋ค ์คํ์คํฌ๋ฆฐ ๋ฒํผ ์ฌ์ฉ |
| D. RenderTarget 2์ฅ + ShadeStyle ํฉ์ฑ | ์์์ ๊ทธ๋ฆฌ๊ธฐ ๊ฒฐ๊ณผ | ๋์คํฐ | ํฉ์ฑ์์ ์ง์ ์ ์ด(๋ฐ์ , ๋ถ๋๋ฌ์ด ๊ฒฝ๊ณ, ๊ทธ๋ ์ด์ค์ผ์ผ ๋ง์คํฌ) | ์ฝ๋๋์ด ๋ง์ |
| E. ํ๋๊ทธ๋จผํธ ์ ฐ์ด๋ SDF ๋ง์คํฌ | ์์์ผ๋ก ์ ์๋ ๋ํ | ๋์คํฐ | ๋ฒํผ๊ฐ ํ์ ์๊ณ GPU์์ ์ฒ๋ฆฌ๋๋ฉฐ ๊ฒฝ๊ณ ์ํฐ์จ๋ฆฌ์ด์ฑ์ด ์ข์ | ์์์ผ๋ก ํํ๋๋ ๋ํ์ ํ์ |
DrawStyle์๋ stencil ๊ด๋ จ ์์ฑ(StencilStyle)์ด ์์ผ๋ ๊ฐ์ด๋์ ์ฌ์ฉ ์๊ฐ ์๊ณ , contour/shape ์ฑ์์ด ์คํ
์ค ๋ฒํผ๋ฅผ ์ฌ์ฉํ๋ค๋ ์ ์ด ์ค๋ฅ ๋ฉ์์ง์์ ํ์ธ๋๋ฏ๋ก ์ด ๋ฌธ์์์๋ ๋ค๋ฃจ์ง ์์์ต๋๋ค.fun main() = application {
program {
extend {
drawer.stroke = null
drawer.fill = ColorRGBa.PINK
// ํด๋ฆฌํ ์์ญ ์ค์
drawer.drawStyle.clip = Rectangle(100.0, 100.0, width - 200.0, height - 200.0)
drawer.circle(
cos(seconds) * width / 2.0 + width / 2.0,
sin(seconds) * height / 2.0 + height / 2.0,
200.0
)
// ํด๋ฆฌํ ํด์
drawer.drawStyle.clip = null
}
}
}
drawer.isolated { } ์์์ ์ค์ ํ๋ฉด ์๋์ผ๋ก ๋ณต์๋ฉ๋๋ค.๋ง์คํฌ ๋ํ๊ณผ ์ฝํ ์ธ ๋ํ์ ๊ต์งํฉ์ ๊ตฌํ๋ฉด "๋ง์คํฌ ์์ชฝ์ ์๋ ๋ถ๋ถ๋ง ๋จ์ ๋ํ"์ด ๋ง๋ค์ด์ง๋๋ค. ๊ฒฐ๊ณผ๊ฐ ๋ฒกํฐ์ด๋ฏ๋ก ์ดํ์ stroke๋ฅผ ๊ทธ๋ฆฌ๊ฑฐ๋ ์ ฐ์ด๋๋ฅผ ์ ์ฉํ ์ ์์ต๋๋ค.
fun main() = application {
configure { width = 800; height = 600 }
program {
val mask = star(Vector2(400.0, 300.0), 260.0, 110.0, 5).shape
// ์ฝํ
์ธ : ์ธ๋ก ์ค๋ฌด๋ฌ 12๊ฐ
val stripes = List(12) { i ->
Rectangle(40.0 + i * 60.0, 0.0, 30.0, height * 1.0).shape
}
// ์ค๋ฌด๋ฌ๋ง๋ค ๋ง์คํฌ์์ ๊ต์งํฉ์ ๊ณ์ฐ (์
๋ ฅ์ด ๊ณ ์ ์ด๋ฏ๋ก ํ ๋ฒ๋ง)
val clipped = stripes.map { stripe ->
compound {
intersection {
shape(mask)
shape(stripe)
}
}
}
extend {
drawer.clear(ColorRGBa.WHITE)
drawer.stroke = null
clipped.forEachIndexed { i, pieces ->
drawer.fill = mix(ColorRGBa.PINK, ColorRGBa.fromHex("#FC3549"), i / 11.0)
drawer.shapes(pieces)
}
// ๋ง์คํฌ ์ค๊ณฝ์ ํ์
drawer.fill = null
drawer.stroke = ColorRGBa.BLACK
drawer.strokeWeight = 3.0
drawer.shape(mask)
}
}
}
extend ์์์ compound๋ฅผ ๋ค์ ๊ณ์ฐํด์ผ ํฉ๋๋ค. ์ค๋ฌด๋ฌ ์๊ฐ ๋ง์ผ๋ฉด ํ๋ ์ ์๊ฐ์ด ๋์ด๋๋ฏ๋ก, ์์ง์ด๋ ๋ง์คํฌ์๋ C~E ๋ฐฉ์์ด ์ ํฉํฉ๋๋ค.orx-compositor๋ ๋ ์ด์ด ๊ธฐ๋ฐ ํฉ์ฑ DSL์
๋๋ค. ๊ณต์ ๊ฐ์ด๋์ ๋ง์คํน ํจํด์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
blend(Normal()) { clip = true }๋ฅผ ์ง์ ํฉ๋๋ค.layer { }๋ก ๋ฌถ์ด ๋ฐฐ๊ฒฝ๊ณผ ๋จผ์ ํฉ์ฑ๋์ง ์๋๋ก ํฉ๋๋ค.fun main() = application {
configure { width = 800; height = 600 }
program {
val composite = compose {
// ๋ฐฐ๊ฒฝ
draw { drawer.clear(ColorRGBa.PINK) }
layer {
// ๋ง์คํฌ ๋ ์ด์ด: ์์ง์ด๋ ์
layer {
draw {
drawer.fill = ColorRGBa.WHITE
drawer.stroke = null
drawer.circle(width / 2.0 + cos(seconds) * 150.0, height / 2.0, 200.0)
}
}
// ์ฝํ
์ธ ๋ ์ด์ด: ๋ง์คํฌ(ํ์ ๋ ์ด์ด)๊ฐ ์๋ ๋ถ๋ถ์๋ง ํ์๋จ
layer {
blend(Normal()) { clip = true }
draw {
drawer.stroke = null
for (i in 0 until 20) {
drawer.fill = if (i % 2 == 0) ColorRGBa.BLACK else ColorRGBa.WHITE
drawer.rectangle(i * 40.0, 0.0, 40.0, height * 1.0)
}
}
}
}
}
extend {
composite.draw(drawer)
}
}
}
draw { }๋ ๋งค ํ๋ ์ ์คํ๋๊ณ , compose { } ๋ณธ๋ฌธ๊ณผ layer { } ๋ณธ๋ฌธ(๋ฆฌ์์ค ๋ก๋ฉ ๋ฑ)์ ํ ๋ฒ๋ง ์คํ๋ฉ๋๋ค.post(ApproximateGaussianBlur()) { ... }์ ์ถ๊ฐํ๋ฉด ๋ถ๋๋ฌ์ด ๊ฒฝ๊ณ์ ๋ง์คํฌ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ๊ฐ์ด๋๊ฐ ํ์ฒ๋ฆฌ๋ฅผ ๋ ์ด์ด ๋จ์๋ก ์ ์ฉํ๋ ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฅํ ๊ตฌ์ฑ์ด๋ฉฐ, ์ ์ฉ ์์(๋ง์คํฌ ํ์ฒ๋ฆฌ โ ์ฝํ
์ธ ํฉ์ฑ)๋ ์คํํ์ฌ ํ์ธํ์๊ธฐ ๋ฐ๋๋๋ค.layer(multisample = BufferMultisample.SampleCount(8)) { ... }๋ก ๋ฉํฐ์ํ๋ง์ ์ง์ ํฉ๋๋ค.๋ง์คํฌ ๋ํ๊ณผ ์ฝํ ์ธ ๋ฅผ ๊ฐ๊ฐ ์คํ์คํฌ๋ฆฐ ๋ฒํผ์ ๊ทธ๋ฆฐ ๋ค, ์ ฐ์ด๋์์ ์ฝํ ์ธ ์ RGB + ๋ง์คํฌ์ ์ํ๋ก ํฉ์ฑํฉ๋๋ค. ํฉ์ฑ์์ ์ง์ ์ฐ๋ฏ๋ก ๋ง์คํฌ ๋ฐ์ , ๋ถ๋๋ฌ์ด ๊ฒฝ๊ณ, ๋ค์ค ๋ง์คํฌ ๊ณฑ ๋ฑ ์ํ๋ ๋ฐฉ์์ผ๋ก ํ์ฅํ ์ ์์ต๋๋ค.
fun main() = application {
configure { width = 800; height = 600 }
program {
// contour๋ฅผ ๊ทธ๋ฆฌ๋ฏ๋ก depthBuffer ํ์
val maskTarget = renderTarget(width, height) {
colorBuffer()
depthBuffer()
}
val contentTarget = renderTarget(width, height) {
colorBuffer()
depthBuffer()
}
val starMask = star(Vector2(400.0, 300.0), 260.0, 110.0, 5)
val compositeStyle = shadeStyle {
fragmentTransform = """
vec2 uv = c_boundsPosition.xy;
uv.y = 1.0 - uv.y;
vec3 content = texture(p_content, uv).rgb;
float m = texture(p_mask, uv).a;
x_fill = vec4(content, m);
""".trimIndent()
}
extend {
// 1. ๋ง์คํฌ ๋ฒํผ: ํฌ๋ช
๋ฐฐ๊ฒฝ์ ํฐ์ ๋ํ
drawer.isolatedWithTarget(maskTarget) {
drawer.clear(ColorRGBa.TRANSPARENT)
drawer.fill = ColorRGBa.WHITE
drawer.stroke = null
drawer.contour(starMask)
}
// 2. ์ฝํ
์ธ ๋ฒํผ: ๋ฐ๋์ ๋ถํฌ๋ช
ํ๊ฒ ์ฑ์
drawer.isolatedWithTarget(contentTarget) {
drawer.clear(ColorRGBa.BLACK)
drawer.stroke = null
for (i in 0 until 40) {
drawer.fill = ColorRGBa.PINK.shade(0.4 + 0.6 * (0.5 + 0.5 * sin(seconds + i * 0.3)))
drawer.rectangle(i * 20.0, 0.0, 20.0, height * 1.0)
}
}
// 3. ํฉ์ฑ: ํ๋ฉด ํฌ๊ธฐ์ ์ฌ๊ฐํ์ ํฉ์ฑ ์
ฐ์ด๋ ์ ์ฉ
compositeStyle.parameter("content", contentTarget.colorBuffer(0))
compositeStyle.parameter("mask", maskTarget.colorBuffer(0))
drawer.clear(ColorRGBa.fromHex("#222222"))
drawer.isolated {
drawer.shadeStyle = compositeStyle
drawer.fill = ColorRGBa.WHITE
drawer.stroke = null
drawer.rectangle(drawer.bounds)
}
}
}
}
์ฃผ์ํ ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
clear(BLACK)). ๋ฐํฌ๋ช
์ฝํ
์ธ ๋ ์ํ ์ฒ๋ฆฌ ๋ฐฉ์์ ๋ฐ๋ผ ์์ด ์ด๋์์ง ์ ์์ต๋๋ค. ๋ฐํฌ๋ช
์ฝํ
์ธ ๊ฐ ํ์ํ๋ฉด C ๋ฐฉ์์ ๊ถ์ฅํฉ๋๋ค.uv.y = 1.0 - uv.y;๋ ๊ณต์ ๊ฐ์ด๋์ ์ด๋ฏธ์ง ๋งคํ ์์ (c_boundsPosition์ผ๋ก ํ
์ค์ฒ ์ขํ๋ฅผ ๋ง๋ค ๋ y๋ฅผ ๋ค์ง๋ ๋ฐฉ์)๋ฅผ ๋ฐ๋ฅธ ๊ฒ์
๋๋ค. ๊ฒฐ๊ณผ๊ฐ ์์๋๋ก ๋ค์งํ ๋ณด์ด๋ฉด ์ด ์ค์ ์ ๊ฑฐํ์๊ธฐ ๋ฐ๋๋๋ค.isolatedWithTarget ๋ธ๋ก ์์์ ortho(rt)๋ฅผ ํธ์ถํด์ผ ํฉ๋๋ค.ShadeStyle์ program { } ์์์ ํ ๋ฒ๋ง ์์ฑํ๊ณ ์ฌ์ฌ์ฉํฉ๋๋ค.๋ฒํผ ์์ด ์ ฐ์ด๋ ์์์ ๋ํ์ ๋ถํธ ์๋ ๊ฑฐ๋ฆฌ ํจ์(SDF)๋ฅผ ๊ณ์ฐํ์ฌ ์ํ๋ฅผ ์กฐ์ ํฉ๋๋ค. ์ฌ๊ฐํ ํ๋๋ฅผ ๊ทธ๋ฆฌ๋ฉด์ ๊ทธ ์์ ์โ๋ฅ๊ทผ ์ฌ๊ฐํ์ด ๋ณํ๋ ๋ชจ์์ ๋ง์คํฌ๋ฅผ ์ ์ฉํ๋ ์์ ๋๋ค.
fun main() = application {
configure { width = 800; height = 600 }
program {
val sdfMask = shadeStyle {
fragmentPreamble = """
float rn_sdRoundBox(vec2 p, vec2 b, float r) {
vec2 q = abs(p) - b + r;
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
}
""".trimIndent()
fragmentTransform = """
vec2 p = c_boundsPosition.xy - 0.5; // ์ค์ฌ์ด (0, 0)์ธ -0.5 ~ 0.5 ์ขํ
float circleD = length(p) - 0.4;
float boxD = rn_sdRoundBox(p, vec2(0.4), 0.08);
float d = mix(circleD, boxD, 0.5 + 0.5 * sin(p_time)); // ์ โ ๋ฅ๊ทผ ์ฌ๊ฐํ
float aa = fwidth(d); // ํ๋ฉด ํฝ์
๊ธฐ์ค ๊ฒฝ๊ณ ํญ
float m = 1.0 - smoothstep(-aa, aa, d); // ์์ชฝ 1, ๋ฐ๊นฅ์ชฝ 0
// ๋ง์คํฌ ์์ชฝ์ ๊ทธ๋ฆด ๋ด์ฉ
x_fill.rgb = mix(vec3(0.99, 0.75, 0.80), vec3(0.12, 0.16, 0.47), c_boundsPosition.y);
x_fill.a *= m; // ์ํ๋ก ๋ง์คํน
""".trimIndent()
}
extend {
sdfMask.parameter("time", seconds)
drawer.clear(ColorRGBa.WHITE)
drawer.shadeStyle = sdfMask
drawer.stroke = null
drawer.rectangle(200.0, 100.0, 400.0, 400.0)
}
}
}
discard๋ฅผ ์ฌ์ฉํ๋ฉด ์ํฐ์จ๋ฆฌ์ด์ฑ ์์ด ์๋ผ๋ผ ์ ์์ผ๋, ์ ์์ ์ฒ๋ผ smoothstep์ผ๋ก ์ํ๋ฅผ ์กฐ์ ํ๋ ๋ฐฉ์์ด ๊ฒฝ๊ณ ํ์ง ๋ฉด์์ ์ ๋ฆฌํฉ๋๋ค.ShadeStyle์ OPENRNDR๊ฐ ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋ ์
ฐ์ด๋ ํ
ํ๋ฆฟ์ GLSL ์ฝ๋ ์กฐ๊ฐ์ ๋ผ์ ๋ฃ๋ ๋ฐฉ์์
๋๋ค. ์์ ํ ์
ฐ์ด๋ ํ๋ก๊ทธ๋จ์ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ main() ๋ด๋ถ์ ๊ทธ ์๋ถ๋ถ์ ์ฝ๋๋ฅผ ์ฝ์
ํฉ๋๋ค.| ํ๋กํผํฐ | ์คํ ๋จ๊ณ | ์ฉ๋ |
|---|---|---|
vertexTransform | ์ ์ ์ ฐ์ด๋ | ์ง์ค๋ฉํธ๋ฆฌ ๋ณํ |
fragmentTransform | ํ๋๊ทธ๋จผํธ ์ ฐ์ด๋ | ํฝ์ ์ ๋ณ๊ฒฝ (๋ํ ์ฑ์์์ ๊ฐ์ฅ ์์ฃผ ์ฌ์ฉ) |
vertexPreamble / fragmentPreamble | main() ์ | ์ฌ์ฉ์ ํจ์, in/out ๋ณ์ ์ ์ธ |
drawer.shadeStyle = ...๋ก ํฉ๋๋ค. ์ดํ ๊ทธ๋ ค์ง๋ ๋ชจ๋ ๋ํ(circle, rectangle, contour, shape, ์ด๋ฏธ์ง, ํ
์คํธ)์ ์ ์ฉ๋๋ฉฐ drawer.shadeStyle = null๋ก ํด์ ํฉ๋๋ค.extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = "x_fill.rgb = vec3(1.0, 0.0, 0.0);"
}
drawer.fill = ColorRGBa.PINK
drawer.stroke = null
drawer.rectangle(width / 2.0 - 200.0, height / 2.0 - 200.0, 400.0, 400.0)
}
x_fill์ ์ฑ์ ์, x_stroke๋ ์ ์์ ๋ด์ vec4์
๋๋ค. ๊ฐ์ ๋ณ๊ฒฝํ๋ฉด ๊ทธ ๊ฒฐ๊ณผ๊ฐ ์ต์ข
์ถ๋ ฅ์ ๋ฐ์๋ฉ๋๋ค.drawer.fill์ด PINK์ฌ๋ ์
ฐ์ด๋๊ฐ RGB๋ฅผ ๋ฎ์ด์ฐ๋ฏ๋ก ๋นจ๊ฐ์์ด ์ถ๋ ฅ๋ฉ๋๋ค. x_fill.rgb๋ง ๋ฐ๊พธ๋ฉด ์ํ(๊ฐ์ฅ์๋ฆฌ ์ฒ๋ฆฌ ๋ฑ)๋ ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค.| ์ ๋์ฌ | ๋ฒ์ | ์๋ฏธ |
|---|---|---|
u_ | ์ ์ฒด | Drawer๊ฐ ์ ๋ฌํ๋ ์์คํ
์ ๋ํผ (u_fill, u_stroke, u_strokeWeight, u_viewDimensions ๋ฑ) |
a_ | vertex | ์ ์ ์์ฑ (a_position, a_normal, a_color) |
va_ | fragment | ๋ณด๊ฐ๋ ์ ์ ์์ฑ (va_position, va_normal, va_color, va_texCoord0) |
v_ | fragment | ๋ณด๊ฐ๋ ๊ฐ (v_worldPosition, v_viewPosition, v_clipPosition ๋ฑ) |
i_, vi_ | vertex / fragment | ์ธ์คํด์ค ์์ฑ |
x_ | ์ ์ฒด | ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๊ฐ (x_fill, x_stroke, x_position ๋ฑ) |
p_ | ์ ์ฒด | ์ฌ์ฉ์๊ฐ parameter()๋ก ์ ๋ฌํ ๊ฐ |
o_ | fragment | ์ถ๋ ฅ ๊ฐ (ํญ์ vec4) |
d_ | ์ ์ฒด | ์ ฐ์ด๋ ์ ์ |
c_ | ์ ์ฒด | ์์ (c_screenPosition, c_boundsPosition, c_boundsSize, c_contourPosition, c_element, c_instance) |
x_, p_, u_ ๋ฑ)๋ก ์์ํ์ง ์๊ฒ ํ๊ณ , ํ๋ ์์ํฌ๊ฐ ์์ฑํ๋ ์ด๋ฆ๊ณผ ๊ฒน์น์ง ์๋๋ก rn_ ๊ฐ์ ๊ณ ์ ์ ๋์ฌ๋ฅผ ๋ถ์ด๋ ๊ฒ์ด ์์ ํฉ๋๋ค.๋ํ ์์ชฝ์ ํจํด์ ๊ทธ๋ฆฌ๋ ค๋ฉด ์ด๋ค ์ขํ๋ฅผ ์ธ์ง ๋จผ์ ์ ํด์ผ ํฉ๋๋ค.
| ์ขํ | ๋ฒ์ | ํน์ง |
|---|---|---|
c_boundsPosition.xy | ๋ํ์ ๊ฒฝ๊ณ ์์ ๊ธฐ์ค 0~1 (y=0์ด ์์ชฝ) | ๋ํ์ด ์ด๋์ ์๋ , ์ผ๋ง๋ ํฌ๋ ๊ฐ์ ํจํด์ด ๋ํ์ ๋ง์ถฐ ๋ฐฐ์น๋ฉ๋๋ค. ๋ํ ์์ชฝ ๋ฌด๋ฌ์ ๊ฐ์ฅ ์ ํฉํฉ๋๋ค. |
c_boundsSize.xy | ๊ฒฝ๊ณ ์์ ํฌ๊ธฐ(px) | c_boundsPosition๊ณผ ๊ณฑํ๋ฉด ๋ํ ๋ด ํฝ์
์ขํ๊ฐ ๋ฉ๋๋ค. ๋ฌด๋ฌ์ ๋ฌผ๋ฆฌ์ ํฌ๊ธฐ๋ฅผ ๊ณ ์ ํ ๋ ์ฌ์ฉํฉ๋๋ค. |
c_screenPosition | ํ๋ฉด ์ขํ(์ฅ์น ์ขํ) | ๋ํ์ด ์์ง์ฌ๋ ํจํด์ด ํ๋ฉด์ ๊ณ ์ ๋ฉ๋๋ค. ๋ํ์ด ๋ง์น "์ฐฝ"์ฒ๋ผ ํ๋ฉด์ ํจํด์ ๋ณด์ฌ ์ฃผ๋ ํจ๊ณผ๋ฅผ ๋ผ ์ ์์ต๋๋ค. |
va_texCoord0 | ์ ์ ์์ฑ ๋ณด๊ฐ | contour ์ ์์๋ .x๊ฐ ์ ํญ ๋ฐฉํฅ์ผ๋ก 0โ1 ๋ณํฉ๋๋ค(๊ณต์ ๊ฐ์ด๋). |
c_contourPosition | 0 ~ contour ๊ธธ์ด | ์ /contour๋ฅผ ๊ทธ๋ฆด ๋๋ง 0์ด ์๋๋๋ค. ์ ์ ๋ฐ๋ผ ํ๋ฅด๋ ํจ๊ณผ์ ์ฌ์ฉํฉ๋๋ค. |
๊ฐ์ ์คํ์ผ์ ์๋ก ๋ค๋ฅธ ๋ํ์ ์ ์ฉํ๋ฉด c_boundsPosition์ด ๋ํ๋ง๋ค 0~1๋ก ์ ๊ทํ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
fun main() = application {
configure { width = 960; height = 400 }
program {
val starShape = star(Vector2(780.0, 200.0), 140.0, 60.0, 5).shape
val uvStyle = shadeStyle {
fragmentTransform = """
vec2 uv = c_boundsPosition.xy;
x_fill.rgb = vec3(uv, 0.5); // x โ ๋นจ๊ฐ, y โ ์ด๋ก
""".trimIndent()
}
extend {
drawer.clear(ColorRGBa.BLACK)
drawer.shadeStyle = uvStyle
drawer.stroke = null
drawer.circle(170.0, 200.0, 130.0)
drawer.rectangle(360.0, 80.0, 240.0, 240.0)
drawer.shape(starShape)
}
}
}
parameter("์ด๋ฆ", ๊ฐ)์ผ๋ก ์ ๋ฌํ ๊ฐ์ GLSL์์ p_์ด๋ฆ์ผ๋ก ์ ๊ทผํฉ๋๋ค.| Kotlin ํ์ | GLSL ํ์ |
|---|---|
Double(float) | float |
Vector2 / Vector3 / Vector4 | vec2 / vec3 / vec4 |
ColorRGBa | vec4 |
Matrix44 | mat4 |
ColorBuffer / DepthBuffer | sampler2D |
BufferTexture | samplerBuffer |
ShadeStyle์ program { } ์์์ ํ ๋ฒ ๋ง๋ค๊ณ , ๋งค ํ๋ ์ style.parameter(...)๋ก ๊ฐ๋ง ๊ฐฑ์ ํ๋ ๋ฐฉ์์ด ํจ์จ์ ์
๋๋ค. ๊ณต์ ๊ฐ์ด๋์ 3D ์์ ๋ ์ด ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.fun main() = application {
configure { width = 800; height = 500 }
program {
val waveStyle = shadeStyle {
fragmentTransform = """
float w = 0.5 + 0.5 * sin(p_frequency * c_boundsPosition.x * 6.2831853 + p_time);
x_fill.rgb = mix(p_colorA.rgb, p_colorB.rgb, w);
""".trimIndent()
}
extend {
waveStyle.parameter("time", seconds)
waveStyle.parameter("frequency", 4.0)
waveStyle.parameter("colorA", ColorRGBa.PINK)
waveStyle.parameter("colorB", ColorRGBa.fromHex("#1E2A78"))
drawer.clear(ColorRGBa.WHITE)
drawer.shadeStyle = waveStyle
drawer.stroke = null
drawer.circle(width / 2.0, height / 2.0, 200.0)
}
}
}
fragmentTransform์ main() ์์ชฝ์ ๋ค์ด๊ฐ๋ฏ๋ก ํจ์๋ฅผ ์ ์ธํ ์ ์์ต๋๋ค. ํจ์, ์์, in ๋ณ์๋ fragmentPreamble์ ์์ฑํฉ๋๋ค.fun main() = application {
configure { width = 800; height = 600 }
program {
val starShape = star(Vector2(400.0, 300.0), 260.0, 120.0, 5).shape
val noiseStyle = shadeStyle {
fragmentPreamble = """
float rn_hash21(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float rn_vnoise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
float a = rn_hash21(i);
float b = rn_hash21(i + vec2(1.0, 0.0));
float c = rn_hash21(i + vec2(0.0, 1.0));
float d = rn_hash21(i + vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
float rn_fbm(vec2 p) {
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 5; i++) {
v += a * rn_vnoise(p);
p = p * 2.0 + vec2(17.0, 9.0);
a *= 0.5;
}
return v;
}
""".trimIndent()
fragmentTransform = """
vec2 uv = c_boundsPosition.xy;
float t = p_time * 0.2;
float n = rn_fbm(uv * 4.0 + vec2(t, -t));
x_fill.rgb = mix(p_colorA.rgb, p_colorB.rgb, smoothstep(0.2, 0.8, n));
""".trimIndent()
parameter("colorA", ColorRGBa.fromHex("#1E2A78"))
parameter("colorB", ColorRGBa.fromHex("#FFC0CB"))
}
extend {
noiseStyle.parameter("time", seconds)
drawer.clear(ColorRGBa.BLACK)
drawer.shadeStyle = noiseStyle
drawer.stroke = null
drawer.shape(starShape)
}
}
}
x_stroke๋ฅผ ์์ ํ๊ณ c_contourPosition์ ์ฌ์ฉํฉ๋๋ค. ์๋ ์์ ๋ ๊ณก์ ์ ๋ฐ๋ผ ์ด๋ํ๋ ์ ์ ์
๋๋ค.c_contourPosition์ 0 ~ contour ๊ธธ์ด ๋ฒ์์ด๋ฏ๋ก, 0~1๋ก ์ ๊ทํํ๋ ค๋ฉด contour์ ๊ธธ์ด๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ ๋๋๋๋ค.fun main() = application {
configure { width = 960; height = 600 }
program {
val wave = contour {
moveTo(Vector2(80.0, 300.0))
curveTo(Vector2(300.0, 40.0), Vector2(600.0, 560.0), Vector2(880.0, 300.0))
}
val dashStyle = shadeStyle {
fragmentTransform = """
float s = c_contourPosition / p_len; // 0 ~ 1
float dash = step(0.5, fract(s * 20.0 - p_time * 0.5));
x_stroke.rgb = mix(p_colorA.rgb, p_colorB.rgb, s);
x_stroke.a *= dash;
""".trimIndent()
parameter("len", wave.length)
parameter("colorA", ColorRGBa.PINK)
parameter("colorB", ColorRGBa.fromHex("#4CC9F0"))
}
extend {
dashStyle.parameter("time", seconds)
drawer.clear(ColorRGBa.fromHex("#111111"))
drawer.shadeStyle = dashStyle
drawer.fill = null
drawer.stroke = ColorRGBa.WHITE
drawer.strokeWeight = 14.0
drawer.contour(wave)
}
}
}
c_element๊ฐ ๋ฐฐ์น ๋ด ์์์ ์ธ๋ฑ์ค์
๋๋ค(๊ณต์ ๊ฐ์ด๋์ ์์ ์ ์). ์์๋ง๋ค ์์์ด ๋ค๋ฅธ ์ ๋ณํ๋ฅผ ๋ง๋ค ๋ ์ฌ์ฉํฉ๋๋ค.fun main() = application {
configure { width = 800; height = 600 }
program {
val circles = List(60) {
Circle(80.0 + (it % 10) * 70.0, 100.0 + (it / 10) * 80.0, 28.0)
}
val elementStyle = shadeStyle {
fragmentTransform = """
float phase = float(c_element) * 0.35;
x_fill.rgb = 0.5 + 0.5 * cos(vec3(0.0, 2.0, 4.0) + p_time + phase);
""".trimIndent()
}
extend {
elementStyle.parameter("time", seconds)
drawer.clear(ColorRGBa.BLACK)
drawer.shadeStyle = elementStyle
drawer.stroke = null
drawer.circles(circles)
}
}
}
ColorBuffer ํ๋ผ๋ฏธํฐ๋ฅผ ํ
์ค์ฒ๋ก ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋ํ ์์ ์ฑ์ธ ์ ์์ต๋๋ค. ๊ณต์ ๊ฐ์ด๋ ์์ ์
๋๋ค.program {
val image = loadImage("data/images/cheeta.jpg")
image.filter(MinifyingFilter.LINEAR_MIPMAP_NEAREST, MagnifyingFilter.LINEAR)
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
vec2 texCoord = c_boundsPosition.xy;
texCoord.y = 1.0 - texCoord.y;
vec2 size = textureSize(p_image, 0);
texCoord.x /= size.x/size.y;
x_fill = texture(p_image, texCoord);
""".trimIndent()
parameter("image", image)
}
val shape = Circle(width / 2.0, height / 2.0, 110.0).shape
drawer.translate(cos(seconds) * 100.0, sin(seconds) * 100.0)
drawer.shape(shape)
}
}
gradient๋ฅผ ํฌํจํ์ฌ orx-shade-styles์๋ ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ง ์คํ์ผ์ด ์์ต๋๋ค. ์ง์ GLSL์ ์์ฑํ๊ธฐ ์ ์ ์ํ๋ ํจ๊ณผ๊ฐ ํ๋ฆฌ์
์ผ๋ก ์กด์ฌํ๋์ง ๋จผ์ ํ์ธํ๋ ๊ฒ์ด ํจ์จ์ ์
๋๋ค.| ์ฆ์ ยท ์์ธ | ๋์ |
|---|---|
| ์ ฐ์ด๋ ์ปดํ์ผ ์ค๋ฅ๋ก ํ๋ก๊ทธ๋จ์ด ์์๋์ง ์์ | ์ค๋ฅ๊ฐ ๋๋ฉด ํ๋ก์ ํธ ๋ฃจํธ์ ShaderError.glsl์ด ๊ธฐ๋ก๋ฉ๋๋ค. ์ด ํ์ผ์์ OPENRNDR๊ฐ ์์ฑํ ์ต์ข
์
ฐ์ด๋์ ์ฌ์ฉ ๊ฐ๋ฅํ ๋ณ์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ์๋์ ์ผ๋ก ๊ตฌ๋ฌธ ์ค๋ฅ๋ฅผ ๋ฃ์ด ์ต์ข
์
ฐ์ด๋๋ฅผ ํ์ธํ๋ ๋ฐฉ๋ฒ๋ ๊ณต์ ๊ฐ์ด๋์ ์๊ฐ๋์ด ์์ต๋๋ค. |
float์ ์ ์ ๋ฆฌํฐ๋ด ์ฌ์ฉ (1) | GLSL์์๋ 1.0์ฒ๋ผ ์์์ ์ ๋ถ์
๋๋ค. |
p_์ด๋ฆ์ ์ฐพ์ ์ ์์ | Kotlin์ parameter("์ด๋ฆ", โฆ)๊ณผ GLSL์ p_์ด๋ฆ์ด ์ผ์นํด์ผ ํ๋ฉฐ, ์ฒซ ๊ทธ๋ฆฌ๊ธฐ ์ ์ ๊ฐ์ด ์ค์ ๋์ด ์์ด์ผ ํฉ๋๋ค. |
๋ฌธ์์ด์ $๊ฐ ํฌํจ๋จ | Kotlin์ """ ๋ฌธ์์ด์ $๋ฅผ ํ
ํ๋ฆฟ์ผ๋ก ํด์ํฉ๋๋ค. GLSL์ $๋ฅผ ์ฐ์ง ์๋๋ก ํ๊ณ , ๊ฐ์ ์ฝ์
ํ๋ ค๋ฉด ${๊ฐ} ํ
ํ๋ฆฟ์ ์๋์ ์ผ๋ก ์ฌ์ฉํฉ๋๋ค(๊ณต์ gradient ์์ ์ levelWarpFunction์ด ์ด ๋ฐฉ์์ ์ฌ์ฉ). |
๋งค ํ๋ ์ shadeStyle { }๋ฅผ ์๋ก ๋ง๋ค์ด ๋๋ฆผ | ์คํ์ผ์ ํ ๋ฒ๋ง ๋ง๋ค๊ณ parameter()๋ก ๊ฐฑ์ ํฉ๋๋ค. |
| ์ ฐ์ด๋๋ฅผ ์ ์ฉํ ๋ค ๋ค๋ฅธ ๋ํ๋ ์ํฅ์ ๋ฐ์ | ๊ทธ๋ฆฐ ๋ค drawer.shadeStyle = null๋ก ํด์ ํ๊ฑฐ๋ drawer.isolated { } ์์์ ์ฌ์ฉํฉ๋๋ค. |
| ๋ง์คํฌ/ํฉ์ฑ ๊ฒฐ๊ณผ๊ฐ ๋ค์งํ | 4.5์ ์ uv.y = 1.0 - uv.y; ์ค์ ์ ๊ฑฐํ๊ฑฐ๋ ์ถ๊ฐํฉ๋๋ค. |
์ธ ์ข ๋ฅ์ ๋ํ์ ์๋ก ๋ค๋ฅธ ShadeStyle์ ์ ์ฉํ๋ ์์ ์ ๋๋ค.
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.math.Vector2
import org.openrndr.shape.*
import kotlin.math.cos
import kotlin.math.sin
fun star(center: Vector2, outer: Double, inner: Double, tips: Int, rotationDeg: Double = -90.0): ShapeContour {
val points = List(tips * 2) {
val r = if (it % 2 == 0) outer else inner
val a = Math.toRadians(rotationDeg) + it * Math.PI / tips
center + Vector2(cos(a), sin(a)) * r
}
return ShapeContour.fromPoints(points, closed = true)
}
fun main() = application {
configure {
width = 960
height = 400
}
program {
val starShape = star(Vector2(480.0, 200.0), 150.0, 65.0, 5).shape
// 1) ์ค๋ฌด๋ฌ (์)
val stripeStyle = shadeStyle {
fragmentTransform = """
float s = c_boundsPosition.x + c_boundsPosition.y;
float w = step(0.5, fract(s * p_count - p_time * 0.3));
x_fill.rgb = mix(p_colorA.rgb, p_colorB.rgb, w);
""".trimIndent()
parameter("count", 8.0)
parameter("colorA", ColorRGBa.fromHex("#FFC0CB"))
parameter("colorB", ColorRGBa.fromHex("#1E2A78"))
}
// 2) ๋
ธ์ด์ฆ (๋ณ Shape)
val noiseStyle = shadeStyle {
fragmentPreamble = """
float rn_hash21(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float rn_vnoise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(rn_hash21(i), rn_hash21(i + vec2(1.0, 0.0)), u.x),
mix(rn_hash21(i + vec2(0.0, 1.0)), rn_hash21(i + vec2(1.0, 1.0)), u.x),
u.y);
}
float rn_fbm(vec2 p) {
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 5; i++) {
v += a * rn_vnoise(p);
p = p * 2.0 + vec2(17.0, 9.0);
a *= 0.5;
}
return v;
}
""".trimIndent()
fragmentTransform = """
float t = p_time * 0.2;
float n = rn_fbm(c_boundsPosition.xy * 4.0 + vec2(t, -t));
x_fill.rgb = mix(p_colorA.rgb, p_colorB.rgb, smoothstep(0.2, 0.8, n));
""".trimIndent()
parameter("colorA", ColorRGBa.fromHex("#F94144"))
parameter("colorB", ColorRGBa.fromHex("#F9C74F"))
}
// 3) SDF ๋ง์คํฌ + ๊ทธ๋ผ๋์ธํธ (์ฌ๊ฐํ)
val sdfStyle = shadeStyle {
fragmentPreamble = """
float rn_sdRoundBox(vec2 p, vec2 b, float r) {
vec2 q = abs(p) - b + r;
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
}
""".trimIndent()
fragmentTransform = """
vec2 p = c_boundsPosition.xy - 0.5;
float d = mix(length(p) - 0.4, rn_sdRoundBox(p, vec2(0.4), 0.08), 0.5 + 0.5 * sin(p_time));
float aa = fwidth(d);
x_fill.rgb = mix(vec3(0.26, 0.67, 0.55), vec3(0.34, 0.46, 0.56), c_boundsPosition.y);
x_fill.a *= 1.0 - smoothstep(-aa, aa, d);
""".trimIndent()
}
extend {
val styles = listOf(stripeStyle, noiseStyle, sdfStyle)
styles.forEach { it.parameter("time", seconds) }
drawer.clear(ColorRGBa.fromHex("#111111"))
drawer.stroke = null
drawer.isolated {
drawer.shadeStyle = stripeStyle
drawer.circle(170.0, 200.0, 130.0)
}
drawer.isolated {
drawer.shadeStyle = noiseStyle
drawer.shape(starShape)
}
drawer.isolated {
drawer.shadeStyle = sdfStyle
drawer.rectangle(680.0, 70.0, 260.0, 260.0)
}
}
}
}
| ํ๊ณ ์ถ์ ์ผ | ์ฌ์ฉํ ๊ธฐ๋ฅ |
|---|---|
| ์ฑ์๋ง / ์ ๋ง ๊ทธ๋ฆฌ๊ธฐ | fill = null ๋๋ stroke = null |
| ๋ํ๋ณ๋ก ๋ค๋ฅธ ์ | ๊ทธ๋ฆฌ๊ธฐ ์ง์ ์ drawer.fill ๋ณ๊ฒฝ |
| ์์์ ๋ค๊ฐํยท๋ณ | ShapeContour.fromPoints(points, closed = true) |
| ๊ตฌ๋ฉ์ด ์๋ ๋ํ | shape { contour { } contour { } } ๋๋ compound { difference { } } |
| ๋ํ๋ผ๋ฆฌ ์๋ฅด๊ธฐยทํฉ์น๊ธฐ | compound { union / difference / intersection } |
| ์์ฒ ๊ฐ์ ๋ํ | drawer.circles(list), drawer.rectangles { } |
| ์ง์ฌ๊ฐํ์ผ๋ก ์๋ผ๋ด๊ธฐ | drawer.drawStyle.clip |
| ์์ ๋ํ/์ด๋ฏธ์ง๋ก ๋ง์คํน | orx-compositor์ blend(Normal()) { clip = true } |
| ๋ง์คํฌ ํฉ์ฑ์์ ์ง์ ์ ์ด | RenderTarget 2์ฅ + ShadeStyle |
| ์์์ผ๋ก ์ ์๋ ๋ง์คํฌ | ํ๋๊ทธ๋จผํธ ์ ฐ์ด๋ SDF |
| ๋ํ ์ ์ ๋๋ฉ์ด์ ๋ฌด๋ฌ | fragmentTransform + p_time |
| ์ ์ ๋ฐ๋ผ ํ๋ฅด๋ ํจ๊ณผ | c_contourPosition + x_stroke |
| ๋ฐฐ์น ๋ํ๋ง๋ค ๋ค๋ฅธ ์ | c_element |
| ๊ทธ๋ผ๋์ธํธ | orx-shade-styles์ gradient<ColorRGBa> { } |
ShapeContour, Shape, compound ๊ฒฐ๊ณผ, ShadeStyle, RenderTarget์ ์
๋ ฅ์ด ๋ฐ๋์ง ์๋ ํ extend ๋ฐ๊นฅ์์ ํ ๋ฒ๋ง ์์ฑํฉ๋๋ค.circles/rectangles ๋ฐฐ์น API๋ฅผ ์ฌ์ฉํฉ๋๋ค.shadeStyle, clip, blendMode๋ ๋ค์ ๊ทธ๋ฆฌ๊ธฐ์๋ ์ ์ง๋ฉ๋๋ค. isolated { }๋ก ๋ฒ์๋ฅผ ์ ํํฉ๋๋ค.vertexTransform ๊ธฐ๋ฐ ์ง์ค๋ฉํธ๋ฆฌ ๋ณํStencilStyle์ ์ด์ฉํ ์คํ
์ค ๋ง์คํน (๊ฐ์ด๋์ ์์ ๊ฐ ์์ด ์ ์ธ)orx-fx์ ๋ธ๋ฌ ๋ฑ ํ์ฒ๋ฆฌ ์์ธ)๊ณต์ ๊ฐ์ด๋ (guide.openrndr.org)
๋ฆด๋ฆฌ์ฆยทAPI
์ด ๋ฌธ์๋ฅผ ๋ฐํํ๊ธฐ ์ ์ ์คํ ํ๊ฒฝ์์ ํ์ธ์ด ํ์ํ ํญ๋ชฉ์ ๋๋ค.
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ์ฝ๋ ์คํ ์ฌ๋ถ | ๊ณต์ ๊ฐ์ด๋์ ์์ (2.2, 2.6.1, 2.6.3, 2.6.5, 3.3, 3.7, 4.2, 5.2, 5.9 ๋ฑ)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋, ๋ณธ๋ฌธ์ ์กฐํฉยทํ์ฅ ์ฝ๋(์ ๋ค๊ฐํ/๋ณ ํฌํผ, 4.3, 4.4, 4.5, 4.6, 5.6~5.8, 6์ฅ)๋ 0.5.0์์ ์ง์ ์ปดํ์ผยท์คํํ์ง ์์์ต๋๋ค. |
| ORX import ๊ฒฝ๋ก | gradient, compose, Normal, ApproximateGaussianBlur, hobbyCurve์ ํจํค์ง๋ IDE ์๋ import๋ก ํ์ธํ์๊ธฐ ๋ฐ๋๋๋ค. |
blendMode ๊ธฐ๋ณธ๊ฐ | ๊ฐ์ด๋ ํ๋ OVER, 0.5.0 API ๋ฌธ์๋ BlendMode? = null์
๋๋ค. ๋ช
์์ ์ผ๋ก ์ง์ ํ๋ฉด ๋ฌธ์ ๊ฐ ์์ต๋๋ค. |
| ๋ ๋ ํ๊น y ๋ฐฉํฅ | 4.5์ ์ uv.y = 1.0 - uv.y;๋ ์ด๋ฏธ์ง ๋งคํ ์์ ์ ๋ฐฉ์์ ์ ์ฉํ ๊ฒ์
๋๋ค. ๊ฒฐ๊ณผ๊ฐ ๋ค์งํ๋ฉด ์ ๊ฑฐํฉ๋๋ค. |
| compositor ํ์ฒ๋ฆฌ ์์ | 4.4์ ์์ ๋ง์คํฌ ๋ ์ด์ด์ ๋ธ๋ฌ๋ฅผ ๊ฒฐํฉํ์ ๋์ ๊ฒฐ๊ณผ๋ ์คํํ์ฌ ํ์ธํด์ผ ํฉ๋๋ค. |
c_element | ๋ฐฐ์น ๊ทธ๋ฆฌ๊ธฐ ์ ์ธ๋ฑ์ค ๋์์ ๊ฐ์ด๋์ ์์ ์ ์์ ๊ทผ๊ฑฐํฉ๋๋ค. circles(list)์์์ ๋์์ ์คํ์ผ๋ก ํ์ธํด์ผ ํฉ๋๋ค. |
| API ๋ฌธ์ ๋ฒ์ ํ๊ธฐ | API ๋ฌธ์ ์ฌ์ดํธ๋ 0.5.0-dev.2+6c5525b๋ก ํ์๋๋ฉฐ, ์ด ์๋ณ์๋ v0.5.0 ๋ฆด๋ฆฌ์ฆ ์ปค๋ฐ(6c5525b)๊ณผ ์ผ์นํฉ๋๋ค. |