Skip to content

Instantly share code, notes, and snippets.

@ylegall
Created October 9, 2020 22:18
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ylegall/9ceef714324eda270fb35c9b51058e78 to your computer and use it in GitHub Desktop.
Save ylegall/9ceef714324eda270fb35c9b51058e78 to your computer and use it in GitHub Desktop.
package org.ygl.openrndr.demos
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.color.mix
import org.openrndr.draw.BlendMode
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.LineCap
import org.openrndr.draw.VertexElementType
import org.openrndr.draw.renderTarget
import org.openrndr.draw.shadeStyle
import org.openrndr.draw.vertexBuffer
import org.openrndr.draw.vertexFormat
import org.openrndr.extra.compositor.compose
import org.openrndr.extra.compositor.draw
import org.openrndr.extra.compositor.layer
import org.openrndr.extra.compositor.post
import org.openrndr.extra.fx.blur.FrameBlur
import org.openrndr.extra.fx.blur.GaussianBloom
import org.openrndr.extra.gui.GUI
import org.openrndr.extra.parameters.Description
import org.openrndr.extra.parameters.DoubleParameter
import org.openrndr.extras.camera.OrbitalCamera
import org.openrndr.extras.camera.OrbitalControls
import org.openrndr.extras.camera.applyTo
import org.openrndr.extras.meshgenerators.sphereMesh
import org.openrndr.ffmpeg.VideoWriter
import org.openrndr.math.Polar
import org.openrndr.math.Spherical
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.linearstep
import org.openrndr.math.mix
import org.openrndr.math.smoothstep
import org.openrndr.math.transforms.transform
import org.ygl.openrndr.utils.BokehDepthBlur
import org.ygl.openrndr.utils.ColorMap
import org.ygl.openrndr.utils.cubicPulse
import org.ygl.openrndr.utils.isolatedWithTarget
import org.ygl.openrndr.utils.randomPoint
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
import kotlin.random.Random
private const val WIDTH = 920
private const val HEIGHT = 920
private const val TOTAL_FRAMES = 360 * 4
private const val LOOPS = 3
private const val DELAY_FRAMES = 180
private const val RECORDING = true
fun main() = application {
configure {
width = WIDTH
height = HEIGHT
}
program {
var time = 0.0
val rng = Random(4)
val minRadius = 60.0
val maxRadius = 320.0
val numParticles = 10000
val colorMap = ColorMap(listOf(
"5d0c5e","eb4738","fa9a6a","fad8b0",
))
val bgStars = List(512) { drawer.bounds.randomPoint(rng) }
val params = @Description("params") object {
@DoubleParameter("horizon radius", 0.0, 1.0, precision = 3)
var horizonRadius = 0.21
@DoubleParameter("distortion period", 0.0, 10.0, precision = 2)
var period = 4.0
@DoubleParameter("distortion shape", 0.0, 4.0, precision = 2)
var shape = 2.5
@DoubleParameter("ring size", 0.0, 200.0, precision = 1)
var ringSize = 85.0
}
val camera = OrbitalCamera(
eye = Vector3.fromSpherical(Spherical(0.0, 75.0, maxRadius)),
lookAt = Vector3.ZERO,
fov = 60.0
)
val blur = BokehDepthBlur()
val bloom = GaussianBloom()
val tempImage = renderTarget(width, height) { colorBuffer(); depthBuffer() }
val particle = sphereMesh(radius = 1.4)
val particleInstances = vertexBuffer(vertexFormat {
attribute("transform", VertexElementType.MATRIX44_FLOAT32)
color(4)
}, numParticles)
class Particle(
val timeOffset: Double,
val startAngleOffset: Double,
val stopAngleOffset: Double,
val radius: Double,
val axisRotation: Double
)
val particles = List(numParticles) { i ->
val timeOffset = rng.nextDouble()
val startAngleOffset = rng.nextDouble()
val radiusOffset = rng.nextDouble()
val axisRotation = rng.nextDouble(-0.02, 0.02)
val radius = mix(minRadius, maxRadius, radiusOffset)
val arcLength = mix(3.0, 0.1, 1 - (1 - radiusOffset).pow(3))
Particle(timeOffset, startAngleOffset, startAngleOffset + arcLength, radius, axisRotation)
}
fun particlePosition(particle: Particle, t: Double): Vector3 {
val angle = mix(particle.startAngleOffset, particle.stopAngleOffset, t)
val (x, z) = Vector2.fromPolar(Polar(angle * 360, particle.radius))
val y = particle.radius * sin(2 * PI * (particle.axisRotation)) * cos(2 * PI * (angle - particle.startAngleOffset))
return Vector3(x, y, z)
}
fun update() {
time = ((frameCount - 1) % TOTAL_FRAMES) / TOTAL_FRAMES.toDouble()
camera.update(deltaTime)
val cameraPosition = Spherical(0.0, 72 + 10 * sin(2 * PI * time), maxRadius)
camera.setView(camera.lookAt, cameraPosition, camera.fov)
particleInstances.put {
for (i in particles.indices) {
val particle = particles[i]
val timeOffset = (particle.timeOffset + time) % 1.0
val pos = particlePosition(particle, timeOffset)
val opacity = cubicPulse(0.5, 0.5, timeOffset)
val colorOffset = linearstep(minRadius, maxRadius, particle.radius)
var color = colorMap[(1 - colorOffset).pow(2)]
color = mix(ColorRGBa.BLACK, color, opacity)
write(transform {
translate(pos)
})
write(color)
}
}
}
val composite = compose {
layer {
draw {
drawer.isolatedWithTarget(tempImage) {
camera.applyTo(drawer)
drawer.lineCap = LineCap.ROUND
drawer.drawStyle.blendMode = BlendMode.ADD
drawer.clear(ColorRGBa.BLACK)
drawer.shadeStyle = shadeStyle {
vertexTransform = "x_modelMatrix *= i_transform;"
fragmentTransform = "x_fill = vi_color;"
}
drawer.vertexBufferInstances(listOf(particle), listOf(particleInstances), DrawPrimitive.TRIANGLES, numParticles)
}
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
const float PI = 3.14159265359;
vec2 pos = vec2(c_boundsPosition.x, 1 - c_boundsPosition.y);
vec2 delta = pos - p_center;
float dist = length(delta);
float factor = clamp((1 - dist / p_radius), 0, 1);
factor = pow(factor, p_shape);
vec2 offset = normalize(delta) * -factor;
//vec2 offset = normalize(delta) * factor * cos(p_period * (1 - factor));
x_fill = texture(p_image, pos + offset);
""".trimIndent()
parameter("image", tempImage.colorBuffer(0))
parameter("radius", params.horizonRadius)
parameter("center", Vector2(0.5, 0.5))
parameter("period", params.period)
parameter("shape", params.shape)
}
drawer.rectangle(drawer.bounds)
drawer.shadeStyle = null
drawer.fill = ColorRGBa.WHITE
drawer.stroke = ColorRGBa.BLUE
drawer.strokeWeight = 1.0
drawer.circles(bgStars, 2.3)
drawer.fill = ColorRGBa.BLACK
drawer.stroke = null
drawer.strokeWeight = 2.0
drawer.circle(drawer.bounds.center, params.ringSize)
}
}
layer {
draw {
camera.applyTo(drawer)
drawer.clear(ColorRGBa.TRANSPARENT)
drawer.lineCap = LineCap.ROUND
drawer.drawStyle.blendMode = BlendMode.ADD
drawer.shadeStyle = shadeStyle {
vertexTransform = "x_modelMatrix *= i_transform;"
fragmentTransform = """
vec4 color = vi_color;
color.a *= smoothstep(0.0, 30.0, v_worldPosition.z);
//if (v_worldPosition.z < 0.0) {
// color = vec4(0);
//}
x_fill = color;
""".trimIndent()
}
drawer.vertexBufferInstances(listOf(particle), listOf(particleInstances), DrawPrimitive.TRIANGLES, numParticles)
}
}
post(blur) {
depthBuffer = tempImage.depthBuffer!!
far = camera.far
focusScale = 0.1
focusPoint = 0.5
}
post(bloom) {
sigma = 0.1
shape = 0.1
gain = 1.2
}
post(FrameBlur()) {
blend = 0.4
}
}
val videoTarget = renderTarget(width, height) { colorBuffer() }
val videoWriter = VideoWriter.create()
.size(width, height)
.frameRate(60)
.output("video/singularity.mp4")
if (RECORDING) {
videoWriter.start()
} else {
//extend(OrbitalControls(camera))
extend(GUI()) {
add(params)
add(blur)
add(bloom)
}
}
extend {
update()
if (RECORDING) {
drawer.isolatedWithTarget(videoTarget) {
composite.draw(this)
}
drawer.image(videoTarget.colorBuffer(0))
if (frameCount > DELAY_FRAMES) {
videoWriter.frame(videoTarget.colorBuffer(0))
}
if (frameCount >= TOTAL_FRAMES * LOOPS + DELAY_FRAMES) {
videoWriter.stop()
application.exit()
}
} else {
composite.draw(drawer)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment