Created
October 13, 2020 13:20
-
-
Save ylegall/7d1c269bb825df87711c0f3f43fb9438 to your computer and use it in GitHub Desktop.
singularity full code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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