[orx-shade-styles] Add noise {} shade style builder

This commit is contained in:
Edwin Jakobs
2025-03-30 17:17:28 +02:00
parent 3ab5c528ca
commit 4901b22ef4
14 changed files with 1240 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
package org.openrndr.extra.shadestyles.fills.noise
import org.openrndr.draw.ShadeStyle
class NoiseBase(
override var parameterValues: MutableMap<String, Any>,
domainWarpFunction: String,
noiseFunction: String,
fbmFunction: String,
levelWarpFunction: String,
blendFunction: String
) : ShadeStyle() {
var fit: Int by Parameter("noiseFit", 0)
var units: Int by Parameter("noiseUnits", 0)
var scale: Double by Parameter("noiseScale", 1.0)
init {
fragmentPreamble = """
$noiseFunction
${domainWarpFunction.replace("domainWarp(", "noiseDomainWarp(")}
$fbmFunction
${levelWarpFunction.replace("levelWarp(", "noiseLevelWarp(")}
${blendFunction.replace("blend(", "noiseBlend(")}
""".trimIndent()
fragmentTransform = """
vec3 coord = vec3(0.0);
if (p_noiseUnits == 0) { // BOUNDS
coord.xy = c_boundsPosition.xy;
if (p_noiseFit == 0) {
if (p_noiseScaleToSize) {
coord.xy *= c_boundsSize.xy * 1.0;
}
} else
if (p_noiseFit == 1) { // COVER
float mx = max(c_boundsSize.x, c_boundsSize.y);
float ar = min(c_boundsSize.x, c_boundsSize.y) / mx;
if (c_boundsSize.x == mx) {
coord.y = (coord.y - 0.5) * ar + 0.5;
if (p_noiseScaleToSize) {
coord *= c_boundsSize.x;
}
} else {
coord.x = (coord.x - 0.5) * ar + 0.5;
if (p_noiseScaleToSize) {
coord *= c_boundsSize.y;
}
}
} else if (p_noiseFit == 2) { // CONTAIN
float mx = max(c_boundsSize.x, c_boundsSize.y);
float ar = mx / min(c_boundsSize.x, c_boundsSize.y);
if (c_boundsSize.y == mx) {
coord.y = (coord.y - 0.5) * ar + 0.5;
if (p_noiseScaleToSize) {
coord *= c_boundsSize.x;
}
} else {
coord.x = (coord.x - 0.5) * ar + 0.5;
if (p_noiseScaleToSize) {
coord *= c_boundsSize.y;
}
}
}
} else if (p_noiseUnits == 1) { // WORLD
coord.xy = v_worldPosition.xy;
} else if (p_noiseUnits == 2) { // VIEW
coord.xy = v_viewPosition.xy;
} else if (p_noiseUnits == 3) { // SCREEN
coord.xy = c_screenPosition.xy;
coord.y = u_viewDimensions.y - coord.y;
}
coord.z = p_noisePhase;
vec3 dx = dFdx(coord);
vec3 dy = dFdy(coord);
int w = p_noiseFilterWindow;
vec4 filtered = vec4(0.0);
float filterScale = 1.0 / max(float(w) - 1.0, 1.0);
for (int y = 0; y < w; y++) {
float fy = float(y) * filterScale - 0.5;
for (int x = 0; x < w; x++) {
float fx = float(x) * filterScale - 0.5;
vec3 scoord = noiseDomainWarp(coord + fx * dx + fy * dy );
float level = noiseLevelWarp(scoord, fbm(scoord * p_noiseScale));
vec4 blend = noiseBlend(x_fill, level);
filtered += blend;
}
}
filtered /= (float(w) * float(w));
x_fill = filtered;
""".trimIndent()
}
}

View File

@@ -0,0 +1,257 @@
package org.openrndr.extra.shadestyles.fills.noise
import org.openrndr.draw.ObservableHashmap
import org.openrndr.draw.ShadeStyle
import org.openrndr.draw.StyleParameters
import org.openrndr.extra.shaderphrases.noise.*
import org.openrndr.math.Matrix44
import org.openrndr.math.transforms.transform
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
class NoiseBuilder : StyleParameters {
override var parameterTypes: ObservableHashmap<String, String> = ObservableHashmap(mutableMapOf()) {}
override var parameterValues: MutableMap<String, Any> = mutableMapOf()
override var textureBaseIndex: Int = 2
var scaleToSize: Boolean by Parameter("noiseScaleToSize", false)
var filterWindow: Int by Parameter("noiseFilterWindow", 1)
var transform: Matrix44 by Parameter("noiseTransform", Matrix44.IDENTITY)
var domainWarpFunction = """vec3 domainWarp(vec3 p) { return p; }"""
var levelWarpFunction = """float levelWarp(vec3 p, float l) { return l; }"""
var noiseFunction = """float noise(vec3 p) { return fract(sin(dot(p.xy, vec2(12.9898,78.233))) * 43758.5453);}"""
var fbmFunction = """float fbm(vec3 p) { return noise(p); }"""
var blendFunction = """vec4 blend(vec4 o, float n) { return vec4(o.rgb * n, o.a); }"""
var phase: Double by Parameter("noisePhase", 0.0)
inner class WhiteNoiseBuilder {
init {
noiseFunction = """
$fhash12Phrase
float noise(vec3 p) { return fhash12(vec2(p.x,p.y)); }
""".trimIndent()
}
fun bilinear() {
scaleToSize = true
noiseFunction = """
$fhash12Phrase
float noise(vec3 p) {
uvec2 up00 = uvec2(p.xy * 1.0);
uvec2 up10 = uvec2(p.xy * 1.0) + uvec2(1u, 0u);
uvec2 up01 = uvec2(p.xy * 1.0) + uvec2(0u, 1u);
uvec2 up11 = uvec2(p.xy * 1.0) + uvec2(1u, 1u);
uint seed = uint(0);
float n00 = fhash12(vec2(up00));
float n10 = fhash12(vec2(up10));
float n01 = fhash12(vec2(up01));
float n11 = fhash12(vec2(up11));
vec2 f = fract(p.xy);
return (n00 * (1.0 -f.x) * (1.0 -f.y) +
n10 * f.x * (1.0 -f.y) +
n01 * (1.0 -f.x) * f.y +
n11 * f.x * f.y);
}
""".trimIndent()
}
}
@OptIn(ExperimentalContracts::class)
fun whiteNoise(builder: WhiteNoiseBuilder.() -> Unit) {
contract {
callsInPlace(builder, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
WhiteNoiseBuilder().builder()
}
inner class SimplexBuilder {}
@OptIn(ExperimentalContracts::class)
fun simplex(builder: SimplexBuilder.() -> Unit) {
contract {
callsInPlace(builder, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
noiseFunction = """
${simplex13}
float noise(vec3 p) { return simplex13(vec3(p)); }
""".trimIndent()
}
inner class FBMBuilder {
var octaves: Int by Parameter("noiseOctaves", 1)
var frequency: Double by Parameter("noiseFrequency", 1.0)
var lacunarity: Double by Parameter("noiseLacunarity", 2.0)
var gain: Double by Parameter("noiseGain", 0.5)
}
inner class BlueNoiseBuilder {
var scale: Double by Parameter("noiseScale", 1.0)
var bits: Int by Parameter("noiseBits", 17)
fun bilinear() {
noiseFunction = """
$hilbertR1BlueNoiseFloatPhrase
float noise(vec3 p) {
uvec2 up00 = uvec2(p.xy * 1.0);
uvec2 up10 = uvec2(p.xy * 1.0) + uvec2(1u, 0u);
uvec2 up01 = uvec2(p.xy * 1.0) + uvec2(0u, 1u);
uvec2 up11 = uvec2(p.xy * 1.0) + uvec2(1u, 1u);
uint seed = uint(0);
uint bits = uint(p_noiseBits);
float n00 = hilbertR1BlueNoiseFloat(up00, bits, seed);
float n10 = hilbertR1BlueNoiseFloat(up10, bits, seed);
float n01 = hilbertR1BlueNoiseFloat(up01, bits, seed);
float n11 = hilbertR1BlueNoiseFloat(up11, bits, seed);
vec2 f = fract(p.xy);
return (n00 * (1.0 -f.x) * (1.0 -f.y) +
n10 * f.x * (1.0 -f.y) +
n01 * (1.0 -f.x) * f.y +
n11 * f.x * f.y);
}
""".trimIndent()
}
fun trilinear() {
noiseFunction = """
$hilbertR1BlueNoiseFloatV3Phrase
float noise(vec3 p) {
uvec3 up000 = uvec3(p * 1.0);
uvec3 up100 = uvec3(p * 1.0) + uvec3(1u, 0u, 0u);
uvec3 up010 = uvec3(p * 1.0) + uvec3(0u, 1u, 0u);
uvec3 up110 = uvec3(p * 1.0) + uvec3(1u, 1u, 0u);
uvec3 up001 = uvec3(p * 1.0) + uvec3(0u, 0u, 1u);
uvec3 up101 = uvec3(p * 1.0) + uvec3(1u, 0u, 1u);
uvec3 up011 = uvec3(p * 1.0) + uvec3(0u, 1u, 1u);
uvec3 up111 = uvec3(p * 1.0) + uvec3(1u, 1u, 1u);
uint seed = 0u;
uint bits = uint(p_noiseBits);
float n000 = hilbertR1BlueNoiseFloat(up000, bits, seed);
float n100 = hilbertR1BlueNoiseFloat(up100, bits, seed);
float n010 = hilbertR1BlueNoiseFloat(up010, bits, seed);
float n110 = hilbertR1BlueNoiseFloat(up110, bits, seed);
float n001 = hilbertR1BlueNoiseFloat(up001, bits, seed);
float n101 = hilbertR1BlueNoiseFloat(up101, bits, seed);
float n011 = hilbertR1BlueNoiseFloat(up011, bits, seed);
float n111 = hilbertR1BlueNoiseFloat(up111, bits, seed);
vec3 f = fract(p);
f.z = 0.0;
return (n000 * (1.0 -f.x) * (1.0 -f.y) * (1.0 - f.z) +
n100 * f.x * (1.0 -f.y) * (1.0 - f.z)+
n010 * (1.0 -f.x) * f.y * (1.0 - f.z)+
n110 * f.x * f.y * (1.0 - f.z) +
n001 * (1.0 -f.x) * (1.0 -f.y) * f.z +
n101 * f.x * (1.0 -f.y) * f.z +
n011 * (1.0 -f.x) * f.y * f.z +
n111 * f.x * f.y * f.z);
}
""".trimIndent()
}
init {
noiseFunction = """
$hilbertR1BlueNoiseFloatPhrase
float noise(vec3 p) {
uvec2 up00 = uvec2(p.xy * 1.0);
uint seed = uint(p.z);
uint bits = uint(p_noiseBits);
float n00 = hilbertR1BlueNoiseFloat(up00, bits, seed);
return n00;
}
""".trimIndent()
}
}
@OptIn(ExperimentalContracts::class)
fun blueNoise(builder: BlueNoiseBuilder.() -> Unit) {
contract {
callsInPlace(builder, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
scaleToSize = true
BlueNoiseBuilder().apply { builder() }
}
@OptIn(ExperimentalContracts::class)
fun fbm(builder: FBMBuilder.() -> Unit) {
contract {
callsInPlace(builder, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
FBMBuilder().apply { builder() }
fbmFunction = """
float fbm(vec3 p) {
float f = 0.0;
float amp = 1.0;
for (int i = 0; i < p_noiseOctaves; i++) {
f += noise(p) * amp;
p *= p_noiseLacunarity;
amp *= p_noiseGain;
}
return f;
}""".trimIndent()
}
inner class AnisotropicFBMBuilder {
var octaves: Int by Parameter("noiseOctaves", 1)
var lacunarity: Matrix44 by Parameter("noiseLacunarity", transform { scale(2.0, 2.0, 1.0) })
var decay: Double by Parameter("noiseDecay", 0.5)
var warpFactor: Double by Parameter("warpFactor", 1.0)
}
@OptIn(ExperimentalContracts::class)
fun anisotropicFbm(builder: AnisotropicFBMBuilder.() -> Unit) {
contract {
callsInPlace(builder, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
AnisotropicFBMBuilder().apply { builder() }
fbmFunction = """float fbm(vec3 p) {
float f = 0.0;
float amp = 1.0;
for (int i = 0; i < p_noiseOctaves; i++) {
f += abs(noise(p) * amp);
p = (p_noiseLacunarity * vec4(p, 1.0)).xyz;
p = mix(p, noiseDomainWarp(p), p_warpFactor);
amp *= p_noiseDecay;
}
return f;
}"""
}
fun build(): ShadeStyle {
return NoiseBase(
parameterValues,
domainWarpFunction,
noiseFunction,
fbmFunction,
levelWarpFunction,
blendFunction
).apply {
this.parameterTypes.putAll(this@NoiseBuilder.parameterTypes)
}
}
}
@OptIn(ExperimentalContracts::class)
fun noise(builder: NoiseBuilder.() -> Unit): ShadeStyle {
contract {
callsInPlace(builder, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
val b = NoiseBuilder()
b.builder()
return b.build()
}

View File

@@ -0,0 +1,48 @@
package noise
import org.openrndr.application
import org.openrndr.draw.loadImage
import org.openrndr.extra.camera.Camera2D
import org.openrndr.extra.imageFit.imageFit
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.shaderphrases.noise.simplex13
import org.openrndr.extra.shadestyles.fills.noise.noise
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
import kotlin.math.cos
import kotlin.reflect.KMutableProperty0
fun main() {
application {
configure {
width = 720
height = 720
}
program {
val image = loadImage("demo-data/images/image-001.png")
extend(Camera2D())
extend {
drawer.shadeStyle = noise {
phase = seconds * 10.0
filterWindow = 5
domainWarpFunction =
"""$simplex13
vec3 domainWarp(vec3 p) { float px = simplex13(p*0.01); float py = simplex13(p.yxz*-0.01); return p + 10.25 * vec3(px, py, 0.0); }""".trimIndent()
blueNoise {
bits = 17
bilinear()
}
blendFunction = """vec4 blend(vec4 o, float n) { float luma = dot(o.rgb, vec3(1.0/3.0));
|return vec4(vec3(smoothstep(luma+0.01, luma-0.01, n)), 1.0);
|}""".trimMargin()
}
drawer.imageFit(image, drawer.bounds)
}
}
}
}

View File

@@ -0,0 +1,57 @@
package noise
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.camera.Camera2D
import org.openrndr.extra.color.presets.PEACH_PUFF
import org.openrndr.extra.color.spaces.RGB
import org.openrndr.extra.shadestyles.fills.gradients.gradient
import org.openrndr.extra.shadestyles.fills.noise.noise
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
import kotlin.math.cos
fun main() {
application {
configure {
width = 720
height = 720
}
program {
extend(Camera2D())
extend {
drawer.shadeStyle = noise {
phase = seconds * 0.01
simplex {
}
domainWarpFunction =
"""vec3 domainWarp(vec3 p) { float px = simplex13(p*4.0); float py = simplex13(p.yxz*-4.0); return p + 0.25 * vec3(px, py, px*py); }"""
anisotropicFbm {
octaves = 10
decay = 0.4
lacunarity = transform {
translate(0.1, cos(seconds) * 0.2, 0.0)
rotate(Vector3.UNIT_X, seconds)
scale(1.89, 6.32, 2.1)
rotate(Vector3.UNIT_X, seconds * 10.0)
}
warpFactor = cos(seconds) * 0.5 + 0.5
}
} + gradient<RGB> {
stops[0.0] = ColorRGBa.PINK
stops[0.25] = ColorRGBa.BLACK
stops[0.5] = ColorRGBa.CYAN.shade(0.5)
stops[0.75] = ColorRGBa.BLACK
stops[1.0] = ColorRGBa.PEACH_PUFF
luma {
}
}
drawer.circle(drawer.bounds.center, 300.0)
}
}
}
}

View File

@@ -0,0 +1,39 @@
package noise
import org.openrndr.application
import org.openrndr.draw.loadImage
import org.openrndr.extra.camera.Camera2D
import org.openrndr.extra.imageFit.imageFit
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.shaderphrases.noise.simplex13
import org.openrndr.extra.shadestyles.fills.noise.noise
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.transform
import kotlin.math.cos
import kotlin.reflect.KMutableProperty0
fun main() {
application {
configure {
width = 720
height = 720
}
program {
val image = loadImage("demo-data/images/image-001.png")
extend(Camera2D())
extend {
drawer.shadeStyle = noise {
whiteNoise {
bilinear()
}
blendFunction = """vec4 blend(vec4 o, float n) {
| float luma = dot(o.rgb, vec3(1.0/3.0));
| return vec4(vec3(smoothstep(luma+0.01, luma-0.01, n)), 1.0);
|}""".trimMargin()
}
drawer.imageFit(image, drawer.bounds)
}
}
}
}