Skip to content

Instantly share code, notes, and snippets.

@ylegall
Created October 13, 2020 13:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ylegall/7d1c269bb825df87711c0f3f43fb9438 to your computer and use it in GitHub Desktop.
Save ylegall/7d1c269bb825df87711c0f3f43fb9438 to your computer and use it in GitHub Desktop.
singularity full code
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.color.mix
import org.openrndr.color.rgb
import org.openrndr.draw.BlendMode
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.LineCap
import org.openrndr.draw.VertexElementType
import org.openrndr.draw.isolatedWithTarget
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.transforms.transform
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
import kotlin.random.Random
import org.openrndr.draw.ColorBuffer
import org.openrndr.draw.DepthBuffer
import org.openrndr.draw.Filter
import org.openrndr.draw.colorBuffer
import org.openrndr.draw.filterShaderFromCode
import org.openrndr.extra.parameters.Vector2Parameter
private const val WIDTH = 920
private const val HEIGHT = 920
private const val TOTAL_FRAMES = 360 * 4
private const val LOOPS = 1
private const val DELAY_FRAMES = 180
private const val RECORDING = false
private class ColorRamp(
val colorIntervals: List<ColorRGBa>
) {
constructor(
hexStrings: Iterable<String>,
opacity: Double = 1.0
): this(hexStrings.map { rgb(it).opacify(opacity) })
init {
check(colorIntervals.isNotEmpty()) { "colorIntervals must not be empty" }
}
val size; get() = colorIntervals.size
fun random(random: Random = Random) = get(random.nextDouble())
operator fun get(percent: Double): ColorRGBa {
return when {
percent <= 0.0 -> colorIntervals.first()
percent >= 1.0 -> colorIntervals.last()
else -> {
val scaledPercent = (colorIntervals.size - 1) * percent
val low = (scaledPercent).toInt()
val high = low + 1
return mix(colorIntervals[low], colorIntervals[high], scaledPercent % 1.0)
}
}
}
}
// http://tuxedolabs.blogspot.com/2018/05/bokeh-depth-of-field-in-single-pass.html
@Description("bokeh depth of field")
class BokehDepthBlur: Filter(
//filterShaderFromUrl(Application::class.java.getResource("/shaders/bokehDepthBlur.frag").toExternalForm())
filterShaderFromCode("""
#version 330
in vec2 v_texCoord0;
uniform sampler2D tex0;
uniform sampler2D depthBuffer;
uniform vec2 pixelScale;
uniform float near;
uniform float far;
uniform float focusPoint;
uniform float focusScale;
out vec4 o_color;
const float GOLDEN_ANGLE = 2.39996323;
const float MAX_BLUR_SIZE = 20.0;
const float RAD_SCALE = 0.5; // Smaller = nicer blur, larger = faster
// from http://tuxedolabs.blogspot.com/2018/05/bokeh-depth-of-field-in-single-pass.html
float linearDepth(vec2 uv) {
float z = texture(depthBuffer, uv).x;
return (2.0 * near) / (far + near - z * (far - near));
}
float getBlurSize(float depth, float focusPoint, float focusScale) {
float coc = clamp((1.0 / focusPoint - 1.0 / depth) * focusScale, -1.0, 1.0);
return abs(coc) * MAX_BLUR_SIZE;
}
void main() {
float centerDepth = linearDepth(v_texCoord0);
float centerSize = getBlurSize(centerDepth, focusPoint, focusScale);
vec3 color = texture(tex0, v_texCoord0).rgb;
float total = 1.0;
float radius = RAD_SCALE;
for (float angle = 0.0; radius < MAX_BLUR_SIZE; angle += GOLDEN_ANGLE) {
vec2 tc = v_texCoord0 + vec2(cos(angle), sin(angle)) * pixelScale * radius;
vec3 sampleColor = texture(tex0, tc).rgb;
float sampleDepth = linearDepth(tc);
float sampleSize = getBlurSize(sampleDepth, focusPoint, focusScale);
if (sampleDepth > centerDepth) {
sampleSize = clamp(sampleSize, 0.0, centerSize * 2.0);
}
float m = smoothstep(radius-0.5, radius+0.5, sampleSize);
color += mix(color/total, sampleColor, m);
total += 1.0;
radius += RAD_SCALE/radius;
}
color /= total;
o_color.rgb = color; o_color.a = 1.0;
//test render depth buffer
// float z = linearDepth(v_texCoord0);
// o_color.rgb = vec3(z);
// o_color.a = 1.0;
}
""".trimIndent(), "bokehDepthBlur")
) {
@DoubleParameter("near", 0.0, 10.0)
var near: Double by parameters
@DoubleParameter("far", 0.0, 9000.0, precision = 1)
var far: Double by parameters
@DoubleParameter("focus scale", 0.0, 1.0)
var focusScale: Double by parameters
@DoubleParameter("focus point", 0.0, 1.0, precision = 2)
var focusPoint: Double by parameters
@Vector2Parameter("pixel scale")
var pixelScale: Vector2 by parameters
var depthBuffer: DepthBuffer by parameters
private var intermediate: ColorBuffer? = null
init {
far = 1000.0
near = 0.1
focusScale = 0.5
focusPoint = 0.5
pixelScale = Vector2.ZERO
}
override fun apply(source: Array<ColorBuffer>, target: Array<ColorBuffer>) {
intermediate?.let {
if (it.width != target[0].width || it.height != target[0].height) {
intermediate = null
}
}
if (intermediate == null) {
intermediate = colorBuffer(target[0].width, target[0].height, target[0].contentScale, target[0].format, target[0].type)
}
intermediate?.let {
//depthBufferOut = depthBuffer
pixelScale = Vector2(1.0 / intermediate!!.width, 1.0 / intermediate!!.height)
super.apply(source, arrayOf(it))
it.copyTo(target[0])
}
}
}
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 = ColorRamp(listOf(
"5d0c5e","eb4738","fa9a6a","fad8b0",
))
val bgStars = List(512) {
Vector2(rng.nextDouble(0.0, width.toDouble()), rng.nextDouble(0.0, height.toDouble()))
}
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 cubicPulse(center: Double, halfwidth: Double, x: Double): Double {
var x1 = abs(x - center)
if (x1 > halfwidth) return 0.0
x1 /= halfwidth
return 1.0 - x1 * x1 * (3.0 - 2.0 * x1)
}
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