aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2026-09-18 12:46:58 +0530
committerAkshay Nair <phenax5@gmail.com>2026-09-18 12:46:58 +0530
commit8700f1b32f812104200e2c9041f35f501e0b67fa (patch)
treef270f36080485312bf4b2c4ee0feb0f5011c53d5 /src/main
parenta329bfdbaa518f717ca40115edb21f07a8ad57cf (diff)
downloadart-playground-openrndr-8700f1b32f812104200e2c9041f35f501e0b67fa.tar.gz
art-playground-openrndr-8700f1b32f812104200e2c9041f35f501e0b67fa.zip
Faces stuff + simple camera rotation
Diffstat (limited to 'src/main')
-rw-r--r--src/main/kotlin/faces/FacesProgram.kt50
-rw-r--r--src/main/kotlin/phenax/extensions/CameraManager.kt54
-rw-r--r--src/main/resources/shaders/faces.glsl181
-rw-r--r--src/main/resources/shaders/modules/sdf.glsl6
-rw-r--r--src/main/resources/shaders/modules/utils.glsl5
5 files changed, 284 insertions, 12 deletions
diff --git a/src/main/kotlin/faces/FacesProgram.kt b/src/main/kotlin/faces/FacesProgram.kt
new file mode 100644
index 0000000..b76f607
--- /dev/null
+++ b/src/main/kotlin/faces/FacesProgram.kt
@@ -0,0 +1,50 @@
+package faces
+
+import org.openrndr.application
+import org.openrndr.color.*
+import org.openrndr.draw.*
+import org.openrndr.extra.noise.filters.SimplexNoise3D
+import org.openrndr.math.IntVector2
+import org.openrndr.math.Vector3
+import phenax.extensions.CameraManager
+import phenax.utils.*
+
+fun main() = application {
+ recordDuration(5.seconds())
+
+ configure {
+ width = 640
+ height = 480
+ windowResizable = false
+ position = IntVector2(0, 0)
+ }
+
+ oliveProgram {
+ val cs by liveComputeShader("faces")
+
+ val outputBuffer = colorBuffer(width, height, type = ColorType.UINT8)
+
+ val size = Math.max(width, height) * 2
+ val noiseBuffer = colorBuffer(size, size, type = ColorType.UINT8)
+ val noiseFilter = SimplexNoise3D()
+ noiseFilter.scale = Vector3.ONE * 2.4
+ noiseFilter.octaves = 8
+ noiseFilter.premultipliedAlpha = true
+
+ val cameraManager = CameraManager(Vector3(1.0, 1.0, -1.0), Vector3(30.0, 0.0, 0.0))
+ cameraManager.lightPosition = Vector3(6.0, 8.0, -8.0)
+ extend(cameraManager)
+
+ extend {
+ noiseFilter.seed = Vector3(0.01, 0.01, 0.001)
+ noiseFilter.apply(emptyArray(), noiseBuffer)
+ cs.image("noise_texture", 1, noiseBuffer.imageBinding(0, ImageAccess.READ))
+
+ cs.uniform("seconds", seconds)
+ cs.image("output_image", 0, outputBuffer.imageBinding(0, ImageAccess.WRITE))
+ cameraManager.setUniforms(cs)
+ cs.execute(outputBuffer.width, outputBuffer.height, 1)
+ drawer.image(outputBuffer)
+ }
+ }
+}
diff --git a/src/main/kotlin/phenax/extensions/CameraManager.kt b/src/main/kotlin/phenax/extensions/CameraManager.kt
index ab2403c..0303a54 100644
--- a/src/main/kotlin/phenax/extensions/CameraManager.kt
+++ b/src/main/kotlin/phenax/extensions/CameraManager.kt
@@ -1,55 +1,79 @@
package phenax.extensions
+import kotlin.math.absoluteValue
import org.openrndr.*
import org.openrndr.color.*
import org.openrndr.draw.*
+import org.openrndr.math.Quaternion
+import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import phenax.utils.recordingEnabled
enum class ActionType {
PAN,
- LIGHT_MOVE
+ ROTATE
}
-class CameraManager : Extension {
+class CameraManager(public var cameraPosition: Vector3, public var cameraRotation: Vector3) :
+ Extension {
override var enabled: Boolean = true
- public var cameraPosition: Vector3 = Vector3(0.0, 1.0, 0.0)
+ public var cameraQuaternion: Quaternion = Quaternion.IDENTITY
public var lightPosition: Vector3 = Vector3(1.0, 2.0, 1.0)
var font: FontImageMap? = null
var actionType: ActionType? = null
var lastPos: Vector3? = null
+ constructor() : this(Vector3(0.0, 1.0, 0.0), Vector3.ZERO) {}
+
override fun setup(program: Program) {
if (recordingEnabled) return
+ cameraQuaternion = Quaternion.fromAngles(cameraRotation.x, cameraRotation.y, cameraRotation.z)
+
font = program.loadFont("data/fonts/default.otf", 16.0)
- program.keyboard.keyDown.listen { if (it.key == KEY_ESCAPE) program.application.exit() }
+ program.keyboard.keyDown.listen {
+ when (it.name) {
+ "escape" -> program.application.exit()
+ "x" -> cameraQuaternion = Quaternion.IDENTITY
+ "y" -> cameraQuaternion = Quaternion.fromAngles(0.0, -90.0, 0.0)
+ "z" -> cameraQuaternion = Quaternion.fromAngles(90.0, 0.0, 0.0)
+ }
+ }
program.mouse.moved.listen {
val dimens = Vector3(program.width.toDouble(), program.height.toDouble(), 1.0)
- val pos = (Vector3(it.position.x, -it.position.y, 0.0) - dimens / 2.0).div(dimens)
+ var pos = (Vector3(it.position.x, -it.position.y, 0.0) - dimens / 2.0).div(dimens)
+ pos = cameraQuaternion.inversed.matrix * pos
+ val increment = lastPos?.let { pos - it }
if (actionType == ActionType.PAN) {
- lastPos?.let { lastPos -> cameraPosition -= (pos - lastPos) }
- } else if (actionType == ActionType.LIGHT_MOVE) {
- lastPos?.let { lastPos -> lightPosition += (pos - lastPos) }
+ increment?.let { cameraPosition -= it }
+ } else if (actionType == ActionType.ROTATE) {
+ increment?.let {
+ val mag = 10.0
+ val rot =
+ if (it.x.absoluteValue >= it.y.absoluteValue) Vector2(it.x * mag, 0.0)
+ else Vector2(0.0, it.y * mag)
+ cameraQuaternion *= Quaternion.fromAngles(rot.x, rot.y, 0.0)
+ }
+ // lastPos?.let { lastPos -> lightPosition += (pos - lastPos) }
}
actionType?.let { lastPos = pos }
}
program.mouse.buttonDown.listen {
val isCtrl = KeyModifier.CTRL in it.modifiers
- actionType = if (isCtrl) ActionType.LIGHT_MOVE else ActionType.PAN
+ actionType = if (isCtrl) ActionType.ROTATE else ActionType.PAN
}
program.mouse.scrolled.listen {
val isCtrl = KeyModifier.CTRL in it.modifiers
if (isCtrl) {
- lightPosition += Vector3(0.0, 0.0, it.rotation.y * 0.1)
+ cameraQuaternion *= Quaternion.fromAngles(it.rotation.y, 0.0, 0.0)
} else {
- cameraPosition += Vector3(0.0, 0.0, it.rotation.y * 0.1)
+ cameraPosition += cameraQuaternion.inversed.matrix * Vector3(0.0, 0.0, it.rotation.y * 0.1)
}
}
@@ -61,7 +85,8 @@ class CameraManager : Extension {
fun setUniforms(cs: ComputeShader) {
cs.uniform("camera_position", cameraPosition)
- cs.uniform("primary_light_position", lightPosition)
+ cs.uniform("camera_transformation", cameraQuaternion.matrix)
+ cs.uniform("key_light_position", lightPosition)
}
override fun beforeDraw(drawer: Drawer, program: Program) {}
@@ -81,5 +106,10 @@ class CameraManager : Extension {
8.0,
32.0
)
+ drawer.text(
+ "L: %.2f %.2f %.2f".format(lightPosition.x, lightPosition.y, lightPosition.z),
+ 8.0,
+ 32.0
+ )
}
}
diff --git a/src/main/resources/shaders/faces.glsl b/src/main/resources/shaders/faces.glsl
new file mode 100644
index 0000000..01184aa
--- /dev/null
+++ b/src/main/resources/shaders/faces.glsl
@@ -0,0 +1,181 @@
+#version 430
+layout(local_size_x = 1, local_size_y = 1) in;
+
+#include <sdf>
+#include <utils>
+#include <noise>
+
+#define RM_MAX_ITERATIONS 400
+#define RM_SHADOW_MAX_ITERATIONS 60
+#define RM_MAX_DISTANCE 100.
+#define DEFAULT_BLEND_GAP 0.001
+
+uniform float seconds = 0;
+layout(rgba8) uniform readonly image2D noise_texture;
+layout(rgba8) uniform writeonly image2D output_image;
+uniform vec3 camera_position = vec3(0, 1, 0);
+uniform mat3 camera_transformation;
+uniform vec3 key_light_position = vec3(1, 2, 1);
+uniform vec3 fill1_light_position = vec3(-1, 1.7, -1);
+uniform vec3 fill2_light_position = vec3(1, 1.6, 1);
+
+struct Obj {
+ int object_id;
+ float dist;
+ vec3 material;
+ bool lighting;
+};
+
+Obj sky_object = Obj(-1, 0., vec3(0.35, 0.6, 0.9), false);
+
+Obj create_object(in int object_id, in float dist) {
+ return Obj(object_id, dist, vec3(0), true);
+}
+
+#define OBJ_FACE 1
+#define OBJ_EYES 2
+#define OBJ_HAIR 4
+
+float sample_noise(vec2 p) {
+ vec4 v = imageLoad(noise_texture, ivec2(p));
+ return v.x;
+}
+
+Obj scene(in vec3 p) {
+ float head = sdf_circle(p - vec3(-0.1, 1.1, 1.0), 0.42);
+ float jaw = sd_ellipsoid(p - vec3(-0.1, 0.86, 0.86), vec3(0.36, 0.36, 0.2));
+ float eyel_socket = sdf_circle(p - vec3(-0.3, 1.1, 0.57), 0.118);
+ float eyer_socket = sdf_circle(p - vec3(0.05, 1.1, 0.56), 0.118);
+
+ float mouth_rot = -0.5;
+ vec3 mouthp = p - vec3(-0.1, 0.96, 0.68);
+ mouthp.yz *= mat2x2(cos(mouth_rot), -sin(mouth_rot), sin(mouth_rot), cos(mouth_rot));
+ float mouth = sd_ellipsoid(mouthp, vec3(0.1, 0.08, 0.04));
+ Obj face = create_object(OBJ_FACE,
+ ssubtract(
+ ssubtract(
+ smin(head, jaw, 0.12),
+ min(eyel_socket, eyer_socket), 0.06),
+ mouth, 0.07));
+ face.material = vec3(0.99, 0.64, 0.61);
+
+ float eyel = sdf_circle(p - vec3(-0.26, 1.1, 0.68), 0.08);
+ float eyer = sdf_circle(p - vec3(0.03, 1.1, 0.67), 0.08);
+ float dirx = length(normalize(p.xz + vec2(0.5, 0.0)));
+ Obj eyes = create_object(OBJ_EYES, min(eyel, eyer));
+
+ Obj teeth = create_object(3,
+ min(sdf_box(p - vec3(-0.08, 0.95, 0.61), vec3(0.01, 0.02, 0.001), 0),
+ sdf_box(p - vec3(-0.1, 0.955, 0.61), vec3(0.01, 0.02, 0.001), 0)));
+ teeth.material = vec3(1);
+
+ Obj hair = create_object(OBJ_HAIR,
+ ssubtract(sdf_circle(p - vec3(-0.1, 1.16, 1.036) * (1.0 + (fbm(p.xy * 10) - 0.1) / 5 + sin(100 * p.x) / 200), 0.44),
+ sdf_circle(p - vec3(-0.1, 1.1, 1.), 0.4), 0.08));
+ hair.material = vec3(0.02, 0.01, 0.01);
+
+ Obj platform = create_object(999, sdf_box(p - vec3(0, 0.1, 5), vec3(10, 0.01, 10), 0.0));
+ platform.material = vec3(1);
+
+ float dist = platform.dist;
+ dist = min(dist, face.dist);
+ dist = min(dist, eyes.dist);
+ dist = min(dist, teeth.dist);
+ dist = min(dist, hair.dist);
+
+ if (dist == eyes.dist) return eyes;
+ if (dist == teeth.dist) return teeth;
+ if (dist == face.dist) return face;
+ if (dist == hair.dist) return hair;
+ if (dist == platform.dist) return platform;
+ return sky_object; // unreachable
+}
+
+Obj ray_march_scene(in vec3 ray_origin, in vec3 ray_direction) {
+ float dist = 0.;
+ for (int i = 0; i < RM_MAX_ITERATIONS && dist < RM_MAX_DISTANCE; i++) {
+ vec3 p = ray_origin + ray_direction * dist;
+ Obj obj = scene(p);
+ dist += obj.dist;
+ if (obj.dist <= 0.001) {
+ obj.dist = dist;
+ return obj;
+ }
+ }
+ return sky_object;
+}
+
+float shadow_scene(in vec3 ray_origin, in vec3 ray_direction, in float light_intensity) {
+ float dist = 0.015;
+ float res = 1.;
+ float prev_gap = 1e10;
+ for (int i = 0; i < RM_SHADOW_MAX_ITERATIONS && dist < RM_MAX_DISTANCE; i++) {
+ vec3 p = ray_origin + dist * ray_direction;
+ Obj obj = scene(p);
+ float gap = obj.dist;
+ if (!obj.lighting)
+ continue;
+ float y = gap * gap / (10000.0 * prev_gap);
+ float d = sqrt(abs(gap * gap - y * y));
+ res = min(res, light_intensity * d / max(0, dist - y));
+ prev_gap = gap;
+ dist += gap;
+
+ if (res < 0.0001)
+ break;
+ }
+
+ res = clamp(res, 0.08, 1.0);
+ return res * res * (3.0 - 2.0 * res);
+}
+
+vec3 surface_normal_scene(in vec3 p) {
+ vec2 inc = vec2(0.01, 0);
+ return normalize(vec3(scene(p + inc.xyy).dist - scene(p - inc.xyy).dist,
+ scene(p + inc.yxy).dist - scene(p - inc.yxy).dist,
+ scene(p + inc.yyx).dist - scene(p - inc.yyx).dist));
+}
+
+void main() {
+ vec2 resolution = vec2(imageSize(output_image).xy);
+ float aspect_ratio = resolution.y / resolution.x;
+ vec2 uv = (gl_GlobalInvocationID.xy - resolution * 0.5) * vec2(1.0, aspect_ratio) / resolution;
+
+ vec3 dir = vec3(uv.x, uv.y, 1);
+ vec3 origin = vec3(0);
+ vec3 uv_direction = normalize((dir - origin) * camera_transformation + origin);
+ vec3 light_position = key_light_position;
+
+ Obj obj = ray_march_scene(camera_position, uv_direction);
+ vec3 p = camera_position + uv_direction * obj.dist;
+ vec3 normal = surface_normal_scene(p);
+
+ vec3 color = obj.material;
+ if (obj.object_id == OBJ_EYES) {
+ float foo = -dot(normal, vec3(0, 0, 1));
+ vec3 iris = vec3(1);
+ if (foo > 0.7) iris = mix(vec3(0.5, 0.3, 0.3), vec3(0), foo * 1.05);
+ color = iris;
+ }
+
+ if (obj.lighting) {
+ vec3 light_direction = normalize(light_position - p);
+ float light_distance = length(light_position - p);
+ float light_intensity = 32.0 / max(light_distance, 0.01);
+
+ float contrast = 1.0;
+ contrast *= clamp(dot(normal, light_direction), 0., 1.);
+ contrast *= shadow_scene(p, light_direction, light_intensity);
+ contrast = clamp(contrast, 0., 1.);
+
+ color *= contrast;
+ if (obj.object_id != OBJ_EYES && obj.object_id != OBJ_HAIR) {
+ color += vec3(1.0, 0.5, 0.5) * shadow_scene(p, normalize(fill1_light_position - p), 0.4);
+ color += vec3(1.0, 0.6, 0.6) * shadow_scene(p, normalize(fill2_light_position - p), 0.4);
+ }
+ }
+
+ // color = vec3(pow(color.x, 2.2), pow(color.y, 2.2), pow(color.z, 2.2));
+ color = clamp(color, 0., 1.);
+ imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(color, 1));
+}
diff --git a/src/main/resources/shaders/modules/sdf.glsl b/src/main/resources/shaders/modules/sdf.glsl
index 8121b18..9051339 100644
--- a/src/main/resources/shaders/modules/sdf.glsl
+++ b/src/main/resources/shaders/modules/sdf.glsl
@@ -10,3 +10,9 @@ float sdf_box(in vec3 p, in vec3 dimens, in float rounding) {
vec3 q = abs(p) - dimens + rounding;
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) - rounding;
}
+
+float sd_ellipsoid(vec3 p, vec3 r) {
+ float k0 = length(p / r);
+ float k1 = length(p / (r * r));
+ return k0 * (k0 - 1.0) / k1;
+}
diff --git a/src/main/resources/shaders/modules/utils.glsl b/src/main/resources/shaders/modules/utils.glsl
index 1616e8c..6908f3b 100644
--- a/src/main/resources/shaders/modules/utils.glsl
+++ b/src/main/resources/shaders/modules/utils.glsl
@@ -2,3 +2,8 @@ float smin(float a, float b, float k) {
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
+
+float ssubtract(float a, float b, float k) {
+ return -smin(-a, b, k);
+}
+