diff options
| author | Akshay Nair <phenax5@gmail.com> | 2026-09-18 12:46:58 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2026-09-18 12:46:58 +0530 |
| commit | 8700f1b32f812104200e2c9041f35f501e0b67fa (patch) | |
| tree | f270f36080485312bf4b2c4ee0feb0f5011c53d5 | |
| parent | a329bfdbaa518f717ca40115edb21f07a8ad57cf (diff) | |
| download | art-playground-openrndr-8700f1b32f812104200e2c9041f35f501e0b67fa.tar.gz art-playground-openrndr-8700f1b32f812104200e2c9041f35f501e0b67fa.zip | |
Faces stuff + simple camera rotation
| -rw-r--r-- | src/main/kotlin/faces/FacesProgram.kt | 50 | ||||
| -rw-r--r-- | src/main/kotlin/phenax/extensions/CameraManager.kt | 54 | ||||
| -rw-r--r-- | src/main/resources/shaders/faces.glsl | 181 | ||||
| -rw-r--r-- | src/main/resources/shaders/modules/sdf.glsl | 6 | ||||
| -rw-r--r-- | src/main/resources/shaders/modules/utils.glsl | 5 |
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); +} + |
