Malen nach Zahlen

Moin, ich bins, Jakob 🙋‍♀️

Ihr findet die slides auch hier:

nook2024.runjak.codes

Was war gleich Malen nach Zahlen?

Ein Malen nach Zahlen set

1950 in Detroit:
Max S. Klein patentierte Malen nach Zahlen

Ein Foto des Mariner 4 Raumschiffs

1964-11-28 NASA:
Mariner 4 Spacecraft geht auf die Reise

Malen nach Zahlen bei der NASA

1965-07-15 NASA:
Malen nach Zahlen

Papierstreifen für Malen nach Zahlen bei der NASAFarbschlüssel für Malen nach Zahlen bei der NASA

Das erste Fernsehbild vom Mars

1965-07-15 NASA: Erstes Fernsehbild vom Mars

Und nu?

  • Pro Pixel die Frage: welche Farbe?
  • Die NASA, aber als Shader.

Was sind eigentlich Shader?

Programme, die auf der Grafikkarte laufen.

Warum Shader?

Rechenbeispiel analog zum Book of Shaders

  • Auflösung $2880\times 1800$
  • 60 fps
  • Pixel pro Sekunde: $311\ 040\ 000$
  • Bei 1Ghz: $\sim \frac13$

Malen, aber wie?

Rendern mit dem guten Dreieck

Shader - Architektur

Das ist so nach und nach komplexer geworden.

Die Grafikpipeline

Skizze zur Grafikpipeline

Shader

  • Vertex
  • Fragment / Pixel
  • Compute

State

Wir benutzen: const, uniform

Es gibt auch noch:
attribute, varying, buffer

Shader Compilezeit

Shader werden gebaut.

  • Bei Bedarf
  • Vom Treiber

Shader compilation

const makeShader = (
  gl: WebGLRenderingContext,
  src: string,
  type: WebGLRenderingContext["VERTEX_SHADER" | "FRAGMENT_SHADER"]
): WebGLShader => {
  const shader = gl.createShader(type);

  if (!shader) {
    throw new Error("Could not create shader");
  }

  gl.shaderSource(shader, src);
  gl.compileShader(shader);

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    throw new Error("Error compiling shader: " + gl.getShaderInfoLog(shader));
  }

  return shader;
};

Shader linking

const initShaders = (
  gl: WebGLRenderingContext,
  vs_source: string,
  fs_source: string
): WebGLProgram => {
  const vertexShader = makeShader(gl, vs_source, gl.VERTEX_SHADER);
  const fragmentShader = makeShader(gl, fs_source, gl.FRAGMENT_SHADER);

  const glProgram = gl.createProgram();
  if (!glProgram) {
    throw new Error("Failed to create program");
  }

  gl.attachShader(glProgram, vertexShader);
  gl.attachShader(glProgram, fragmentShader);

  gl.linkProgram(glProgram);
  if (!gl.getProgramParameter(glProgram, gl.LINK_STATUS)) {
    throw new Error("Unable to initialize the shader program");
  }

  gl.useProgram(glProgram);

  return glProgram;
};

Unser vertex shader

attribute vec4 a_Position;

void main() {
  gl_Position = a_Position;
}

Unsere vertices

  const vertices = new Float32Array(
    [
      // Triangle 1
      [
        [-1, 1],
        [1, 1],
        [1, -1],
      ].flat(),
      // Triangle 2
      [
        [-1, 1],
        [1, -1],
        [-1, -1],
      ].flat(),
    ].flat()
  );

Beispiele GLSL

Wir bereiten uns etwas vor.

Beispiel Hintergrund

precision mediump float;

// #002322 -> vec4(0, 35, 34, 255)
const vec4 background = normalize(vec4(0, 35, 34, 255));

void main() {
    gl_FragColor = background;
}

Das sieht dann so aus:

precision mediump float; // #002322 -> vec4(0, 35, 34, 255) const vec4 background = normalize(vec4(0, 35, 34, 255)); void main() { gl_FragColor = background; }

Beispiel Farbverlauf

precision mediump float;

uniform vec3 iResolution;

void main() {
  vec2 uv = gl_FragCoord.xy / iResolution.xy;

  gl_FragColor = vec4(uv.xy, 0.0, 1.0);
}
precision mediump float; uniform vec3 iResolution; void main() { vec2 uv = gl_FragCoord.xy / iResolution.xy; gl_FragColor = vec4(uv.xy, 0.0, 1.0); }

Beispiel Zeit

precision mediump float;

uniform vec3 iResolution;
uniform float iGlobalTime;

const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322
const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360
const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475

void main() {
  vec2 uv = gl_FragCoord.xy / iResolution.xy;

  float curve = 0.4 * sin((9.25 * uv.x) + (2.0 * iGlobalTime));
  float lineAShape = smoothstep(1.0 - clamp(distance(curve + uv.y, 0.5), 0.0, 1.0), 1.0, 0.99);

  gl_FragColor = (1.0 - lineAShape) * vec4(mix(color3, color2, lineAShape));
}
precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 void main() { vec2 uv = gl_FragCoord.xy / iResolution.xy; float curve = 0.4 * sin((9.25 * uv.x) + (2.0 * iGlobalTime)); float lineAShape = smoothstep(1.0 - clamp(distance(curve + uv.y, 0.5), 0.0, 1.0), 1.0, 0.99); gl_FragColor = (1.0 - lineAShape) * vec4(mix(color3, color2, lineAShape)); }

Signed Distance Functions (SDFs)

Was sind das?

  • Vorzeichenbehaftete Entfernungsfunktion
    • Wie weit ist etwas weg?
    • Sind wir drinnen oder draußen?

Beispiele in 1D

Entfernung vom Ursprung

Entfernung vom Ursprung

Entfernung von 2

Entfernung von 2

Eine Strecke

Eine Strecke

Skalieren

Skalieren einer Strecke

Vereinigung

Vereinigung zweier Strecken

Schnitt

Schnitt zweier Strecken

Differenz

Differenz zweier Strecken

Beispiele in 2D

Wie sieht das in 2D aus?

Beispiel Kreis

float sdfCircle(vec2 position, float radius) {
  return length(position) - radius;
}
vec4 chooseColor(float value, vec4 c1, vec4 c2) {
  if(value >= 0.0) {
    return c1;
  }

  return c2;
}
void main() {
  vec2 uv = (gl_FragCoord.xy / iResolution.xy);
  vec2 domain = (uv - vec2(0.5)) * 2.0;

  float inCircle = sdfCircle(domain, 0.5);

  gl_FragColor = chooseColor(inCircle, color1, color3);
}
precision mediump float; uniform vec3 iResolution; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, vec4 c1, vec4 c2) { if(value >= 0.0) { return c1; } return c2; } void main() { vec2 uv = (gl_FragCoord.xy / iResolution.xy); vec2 domain = (uv - vec2(0.5)) * 2.0; float inCircle = sdfCircle(domain, 0.5); gl_FragColor = chooseColor(inCircle, color1, color3); }

Besserer Kreis

  • Geht das Runder?
  • Geht das weniger ~treppig?
vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) {
  return mix(inside, outside, clamp(value * gradient, 0., 1.));
}
void main() {
  float minDim = min(iResolution.x, iResolution.y);
  vec2 margins = iResolution.xy - vec2(minDim);

  vec2 base = margins / 2.;
  vec2 size = iResolution.xy - margins;

  vec2 uv = ((gl_FragCoord.xy - base) / size);
  vec2 domain = (uv - vec2(0.5)) * 2.0;

  float outside = outsideCenter(domain);
  if(outside > 0.) {
    gl_FragColor = color2;
    return;
  }

  float inCircle = sdfCircle(domain, 0.5);

  gl_FragColor = chooseColor(inCircle, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float outsideCenter(vec2 position) { vec2 delta = abs(position) - vec2(1.); return max(delta.x, delta.y); } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float inCircle = sdfCircle(domain, 0.5); gl_FragColor = chooseColor(inCircle, 100., color3, color1); }

Beispiel Rechteck

float sdfRectangle(vec2 position, vec2 size) {
  vec2 delta = abs(position) - size * 0.5;
  return max(delta.x, delta.y);
}
float outsideCenter(vec2 position) {
  return sdfRectangle(position, vec2(2.));
}
  float inRectangle = sdfRectangle(domain, vec2(3.,2.) / 3.0);

  gl_FragColor = chooseColor(inRectangle, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float sdfRectangle(vec2 position, vec2 size) { vec2 delta = abs(position) - size * 0.5; return max(delta.x, delta.y); } float outsideCenter(vec2 position) { return sdfRectangle(position, vec2(2.)); } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float inRectangle = sdfRectangle(domain, vec2(3.,2.) / 3.0); gl_FragColor = chooseColor(inRectangle, 100., color3, color1); }

Transformationen

.. für wenn wir das anders wollen

Skalieren

  float t = 2.0 * iGlobalTime;
  float scale = mix(0.75, 1.5, sin(t) * 0.5 + 0.5);

  float inRectangle = sdfRectangle(
    domain * scale,
    vec2(3.,2.) / 3.0
  );

  gl_FragColor = chooseColor(inRectangle, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float sdfRectangle(vec2 position, vec2 size) { vec2 delta = abs(position) - size * 0.5; return max(delta.x, delta.y); } float outsideCenter(vec2 position) { return sdfRectangle(position, vec2(2.)); } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float t = 2.0 * iGlobalTime; float scale = mix(0.75, 1.5, sin(t) * 0.5 + 0.5); float inRectangle = sdfRectangle( domain * scale, vec2(3.,2.) / 3.0 ); gl_FragColor = chooseColor(inRectangle, 100., color3, color1); }

Skalieren 2

  float t = 2.0 * iGlobalTime;
  float scaleX = mix(0.75, 1.5, sin(t) * 0.5 + 0.5);
  float scaleY = mix(0.75, 1.5, cos(t) * 0.5 + 0.5);

  float inRectangle = sdfRectangle(
    domain * vec2(scaleX, scaleY),
    vec2(3., 2.) / 3.0
  );

  gl_FragColor = chooseColor(inRectangle, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float sdfRectangle(vec2 position, vec2 size) { vec2 delta = abs(position) - size * 0.5; return max(delta.x, delta.y); } float outsideCenter(vec2 position) { return sdfRectangle(position, vec2(2.)); } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float t = 2.0 * iGlobalTime; float scaleX = mix(0.75, 1.5, sin(t) * 0.5 + 0.5); float scaleY = mix(0.75, 1.5, cos(t) * 0.5 + 0.5); float inRectangle = sdfRectangle( domain * vec2(scaleX, scaleY), vec2(3., 2.) / 3.0 ); gl_FragColor = chooseColor(inRectangle, 100., color3, color1); }

Verschieben

  float t = 2.0 * iGlobalTime;
  float deltaX = sin(t) * 0.5;
  float deltaY = cos(t) * 0.5;

  float inRectangle = sdfRectangle(
    domain + vec2(deltaX, deltaY),
    vec2(1.0)
  );

  gl_FragColor = chooseColor(inRectangle, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float sdfRectangle(vec2 position, vec2 size) { vec2 delta = abs(position) - size * 0.5; return max(delta.x, delta.y); } float outsideCenter(vec2 position) { return sdfRectangle(position, vec2(2.)); } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float t = 2.0 * iGlobalTime; float deltaX = sin(t) * 0.5; float deltaY = cos(t) * 0.5; float inRectangle = sdfRectangle( domain + vec2(deltaX, deltaY), vec2(1.0) ); gl_FragColor = chooseColor(inRectangle, 100., color3, color1); }

Drehen

vec2 rotate(vec2 position, float angle) {
  float s = sin(angle);
  float c = cos(angle);

  mat2 m = mat2(c, s, -s, c);

  return m * position;
}
  float t = -2.0 * iGlobalTime;
  vec2 rotatedDomain = rotate(domain, t);

  float inRectangle = sdfRectangle(rotatedDomain, vec2(2.0) / 3.0);

  gl_FragColor = chooseColor(inRectangle, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float sdfRectangle(vec2 position, vec2 size) { vec2 delta = abs(position) - size * 0.5; return max(delta.x, delta.y); } float outsideCenter(vec2 position) { return sdfRectangle(position, vec2(2.)); } vec2 rotate(vec2 position, float angle) { float s = sin(angle); float c = cos(angle); mat2 m = mat2(c, s, -s, c); return m * position; } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float t = -2.0 * iGlobalTime; vec2 rotatedDomain = rotate(domain, t); float inRectangle = sdfRectangle(rotatedDomain, vec2(2.0) / 3.0); gl_FragColor = chooseColor(inRectangle, 100., color3, color1); }

Domain Repetition

Lasst nochmal Desmos besuchen.

Domain Repetition

vec2 tile(vec2 position, vec2 counts) {
  vec2 uv = position * 0.5 + vec2(0.5);
  vec2 tiled = fract(uv * counts);
  return (tiled - vec2(0.5)) * 2.0;
}
  float t = -2.0 * iGlobalTime;
  float scale = mix(0.75, 1.5, sin(t) * 0.5 + 0.5);
  vec2 newDomain = rotate(tile(domain, vec2(5.)) * scale, t);

  float inRectangle = sdfRectangle(newDomain, vec2(2.0) / 3.0);

  gl_FragColor = chooseColor(inRectangle, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float sdfRectangle(vec2 position, vec2 size) { vec2 delta = abs(position) - size * 0.5; return max(delta.x, delta.y); } float outsideCenter(vec2 position) { return sdfRectangle(position, vec2(2.)); } vec2 rotate(vec2 position, float angle) { float s = sin(angle); float c = cos(angle); mat2 m = mat2(c, s, -s, c); return m * position; } vec2 tile(vec2 position, vec2 counts) { vec2 uv = position * 0.5 + vec2(0.5); vec2 tiled = fract(uv * counts); return (tiled - vec2(0.5)) * 2.0; } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float t = -2.0 * iGlobalTime; float scale = mix(0.75, 1.5, sin(t) * 0.5 + 0.5); vec2 newDomain = rotate(tile(domain, vec2(5.)) * scale, t); float inRectangle = sdfRectangle(newDomain, vec2(2.0) / 3.0); gl_FragColor = chooseColor(inRectangle, 100., color3, color1); }

Boolean Operations

  • Vereinigung
  • Schnitt
  • Differenz

Vereinigung

  float rect = sdfRectangle(domain  + vec2(0.25, 0.0), vec2(1.0));
  float circ = sdfCircle(domain - vec2(0.25, 0.0), 0.5);
  float inEither = min(rect, circ);

  gl_FragColor = chooseColor(inEither, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float sdfRectangle(vec2 position, vec2 size) { vec2 delta = abs(position) - size * 0.5; return max(delta.x, delta.y); } float outsideCenter(vec2 position) { return sdfRectangle(position, vec2(2.)); } vec2 rotate(vec2 position, float angle) { float s = sin(angle); float c = cos(angle); mat2 m = mat2(c, s, -s, c); return m * position; } vec2 tile(vec2 position, vec2 counts) { vec2 uv = position * 0.5 + vec2(0.5); vec2 tiled = fract(uv * counts); return (tiled - vec2(0.5)) * 2.0; } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float rect = sdfRectangle(domain + vec2(0.25, 0.0), vec2(1.0)); float circ = sdfCircle(domain - vec2(0.25, 0.0), 0.5); float inEither = min(rect, circ); gl_FragColor = chooseColor(inEither, 100., color3, color1); }

Schnitt

  float rect = sdfRectangle(domain  + vec2(0.25, 0.0), vec2(1.0));
  float circ = sdfCircle(domain - vec2(0.25, 0.0), 0.5);
  float inBoth = max(rect, circ);

  gl_FragColor = chooseColor(inBoth, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float sdfRectangle(vec2 position, vec2 size) { vec2 delta = abs(position) - size * 0.5; return max(delta.x, delta.y); } float outsideCenter(vec2 position) { return sdfRectangle(position, vec2(2.)); } vec2 rotate(vec2 position, float angle) { float s = sin(angle); float c = cos(angle); mat2 m = mat2(c, s, -s, c); return m * position; } vec2 tile(vec2 position, vec2 counts) { vec2 uv = position * 0.5 + vec2(0.5); vec2 tiled = fract(uv * counts); return (tiled - vec2(0.5)) * 2.0; } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float rect = sdfRectangle(domain + vec2(0.25, 0.0), vec2(1.0)); float circ = sdfCircle(domain - vec2(0.25, 0.0), 0.5); float inBoth = max(rect, circ); gl_FragColor = chooseColor(inBoth, 100., color3, color1); }

Differenz

  float rect = sdfRectangle(domain  + vec2(0.25, 0.0), vec2(1.0));
  float circ = sdfCircle(domain - vec2(0.25, 0.0), 0.5);
  float diff = max(rect, -circ);

  gl_FragColor = chooseColor(diff, 100., color3, color1);
}
precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const vec4 color1 = normalize(vec4(0, 35, 34, 255)); // #002322 const vec4 color2 = normalize(vec4(0, 99, 96, 255)); // #006360 const vec4 color3 = normalize(vec4(255, 244, 117, 255)); // #fff475 float sdfCircle(vec2 position, float radius) { return length(position) - radius; } vec4 chooseColor(float value, float gradient, vec4 inside, vec4 outside) { return mix(inside, outside, clamp(value * gradient, 0., 1.)); } float sdfRectangle(vec2 position, vec2 size) { vec2 delta = abs(position) - size * 0.5; return max(delta.x, delta.y); } float outsideCenter(vec2 position) { return sdfRectangle(position, vec2(2.)); } vec2 rotate(vec2 position, float angle) { float s = sin(angle); float c = cos(angle); mat2 m = mat2(c, s, -s, c); return m * position; } vec2 tile(vec2 position, vec2 counts) { vec2 uv = position * 0.5 + vec2(0.5); vec2 tiled = fract(uv * counts); return (tiled - vec2(0.5)) * 2.0; } void main() { float minDim = min(iResolution.x, iResolution.y); vec2 margins = iResolution.xy - vec2(minDim); vec2 base = margins / 2.; vec2 size = iResolution.xy - margins; vec2 uv = ((gl_FragCoord.xy - base) / size); vec2 domain = (uv - vec2(0.5)) * 2.0; float outside = outsideCenter(domain); if(outside > 0.) { gl_FragColor = color2; return; } float rect = sdfRectangle(domain + vec2(0.25, 0.0), vec2(1.0)); float circ = sdfCircle(domain - vec2(0.25, 0.0), 0.5); float diff = max(rect, -circ); gl_FragColor = chooseColor(diff, 100., color3, color1); }

SDFs in 3D

Raymarching

Raymarching Konzept 1

Raymarching

Raymarching Konzept 2

Raymarching

Raymarching Konzept 3

Raymarching code

float rayMarch(vec3 start, vec3 direction) {
  const int NUMBER_OF_STEPS = 32;
  const float MINIMUM_HIT_DISTANCE = 0.001;
  const float MAXIMUM_TRACE_DISTANCE = 1000.0;
  
  float distance_traveled = 0.0;
  
  for (int i = 0; i < NUMBER_OF_STEPS; i++) {
    vec3 current_position = start + direction * distance_traveled;
    
    float distance_to_closest = sdf(current_position);
    distance_traveled += distance_to_closest;
    
    if (distance_to_closest < MINIMUM_HIT_DISTANCE) {
      return distance_traveled;
    }
    
    if (distance_traveled > MAXIMUM_TRACE_DISTANCE) {
      break;
    }
  }
  
  return -1.0;
}

Plus shape

float sdPlus(vec3 p, vec2 b, float r) {
  float b1 = sdRoundBox(p, b.xyy, r);
  float b2 = sdRoundBox(p, b.yxy, r);
  float b3 = sdRoundBox(p, b.yyx, r);
  
  return opSmoothUnion(b1, opSmoothUnion(b2, b3, 0.025), 0.025);
}

Beispiel plus

/* Sources: rotation matrices: https://thebookofshaders.com/08/ erot: https://suricrasia.online/blog/shader-functions/ normals: https://www.youtube.com/watch?v=BNZtUB7yhX4 ray marching: https://michaelwalczyk.com/blog-ray-marching.html reflections: https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector http://www.sunshine2k.de/articles/coding/vectorreflection/vectorreflection.html https://registry.khronos.org/OpenGL-Refpages/gl4/html/reflect.xhtml distance functions: https://iquilezles.org/articles/distfunctions/ */ precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; vec3 erot(vec3 p, vec3 ax, float ro) { return mix(dot(ax, p)*ax, p, cos(ro)) + cross(ax,p)*sin(ro); } float sdTorus(vec3 p, vec2 t){ vec2 q = vec2(length(p.xz)-t.x,p.y); return length(q)-t.y; } float torus(in vec3 p) { const float r1 = 0.25; const float r2 = 0.125; return sdTorus(p, vec2(r1, r2)); } float sdRoundBox(vec3 p, vec3 b, float r) { vec3 q = abs(p) - b + r; return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r; } float opSmoothUnion(float d1, float d2, float k) { float h = clamp(0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0); return mix(d2, d1, h) - k * h * (1.0 - h); } float sdPlus(vec3 p, vec2 b, float r) { float b1 = sdRoundBox(p, b.xyy, r); float b2 = sdRoundBox(p, b.yxy, r); float b3 = sdRoundBox(p, b.yyx, r); return opSmoothUnion(b1, opSmoothUnion(b2, b3, 0.025), 0.025); } float sdf(vec3 p) { p = vec3(p.xy - vec2(0.5), p.z); p = erot(p, normalize(vec3(1.0, 1.0, 0.0)), 0.5 * iGlobalTime); // p = vec3(fract(p.xy), p.z); return sdPlus(p, vec2(0.25, 0.25/3.0), 0.025); //return sdRoundBox(p, vec3(0.25),0.025); return torus(p); } vec3 sdfNormal(vec3 p) { const float EPS = 0.001; vec3 v1 = vec3(sdf(p + vec3(EPS, 0.0, 0.0)), sdf(p + vec3(0.0, EPS, 0.0)), sdf(p + vec3(0.0, 0.0, EPS))); vec3 v2 = vec3(sdf(p - vec3(EPS, 0.0, 0.0)), sdf(p - vec3(0.0, EPS, 0.0)), sdf(p - vec3(0.0, 0.0, EPS))); return normalize(v1 - v2); } float rayMarch(vec3 start, vec3 direction) { const int NUMBER_OF_STEPS = 32; const float MINIMUM_HIT_DISTANCE = 0.001; const float MAXIMUM_TRACE_DISTANCE = 1000.0; float distance_traveled = 0.0; for (int i = 0; i < NUMBER_OF_STEPS; i++) { vec3 current_position = start + direction * distance_traveled; float distance_to_closest = sdf(current_position); distance_traveled += distance_to_closest; if (distance_to_closest < MINIMUM_HIT_DISTANCE) { return distance_traveled; } if (distance_traveled > MAXIMUM_TRACE_DISTANCE) { break; } } return -1.0; } void main() { vec2 uv = gl_FragCoord.xy / iResolution.xy; vec3 start = vec3(uv, 1.0); const vec3 direction = vec3(0.0, 0.0, -1.0); float hitDistance = rayMarch(start, direction); if (hitDistance >= 0.0) { vec3 hitPoint = start + direction * hitDistance; vec3 normal = sdfNormal(hitPoint); vec3 color = normal * 0.5 + 0.5; vec3 reflectDirection = reflect(direction, normal); float reflectDistance = rayMarch(hitPoint + reflectDirection * 2.0, direction); if (reflectDistance >= 0.0) { vec3 reflectPoint = hitPoint + reflectDirection * reflectDistance; vec3 reflectNormal = sdfNormal(reflectPoint); vec3 reflectColor = reflectNormal * 0.5 + 0.5; gl_FragColor = vec4(reflectColor, 1.0); } else { gl_FragColor = vec4(color, 1.0); } } else { gl_FragColor = vec4(0.0); } }

Wieso ist das bunt?

  • Weil Bunt 💖
  • Weil Normalenvektoren hilfreich sind.

Bei den Normalenvektoren können wir tricksen:

vec3 sdfNormal(vec3 p) {
  const float EPS = 0.001;
  
  vec3 v1 = vec3(sdf(p + vec3(EPS, 0.0, 0.0)), sdf(p + vec3(0.0, EPS, 0.0)), sdf(p + vec3(0.0, 0.0, EPS)));
  vec3 v2 = vec3(sdf(p - vec3(EPS, 0.0, 0.0)), sdf(p - vec3(0.0, EPS, 0.0)), sdf(p - vec3(0.0, 0.0, EPS)));
  
  return normalize(v1 - v2);
}

Malen nach Zahlen

/* Sources: rotation matrices: https://thebookofshaders.com/08/ erot: https://suricrasia.online/blog/shader-functions/ normals: https://www.youtube.com/watch?v=BNZtUB7yhX4 ray marching: https://michaelwalczyk.com/blog-ray-marching.html reflections: https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector http://www.sunshine2k.de/articles/coding/vectorreflection/vectorreflection.html https://registry.khronos.org/OpenGL-Refpages/gl4/html/reflect.xhtml distance functions: https://iquilezles.org/articles/distfunctions/ */ precision mediump float; uniform vec3 iResolution; uniform float iGlobalTime; const float pi = 3.1415926535897932384626433832795; vec3 erot(vec3 p, vec3 ax, float ro) { return mix(dot(ax, p) * ax, p, cos(ro)) + cross(ax, p) * sin(ro); } float dot2(vec2 v) { return dot(v, v); } float sdTorus(vec3 p, vec2 t) { vec2 q = vec2(length(p.xz) - t.x, p.y); return length(q) - t.y; } float sdRoundBox(vec3 p, vec3 b, float r) { vec3 q = abs(p) - b + r; return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) - r; } float sdCappedCylinder(vec3 p, float h, float r) { vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(r, h); return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)); } float sdRoundedCylinder(vec3 p, float ra, float rb, float h) { vec2 d = vec2(length(p.xz) - 2.0 * ra + rb, abs(p.y) - h); return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - rb; } float sdCappedCone(vec3 p, float h, float r1, float r2) { vec2 q = vec2(length(p.xz), p.y); vec2 k1 = vec2(r2, h); vec2 k2 = vec2(r2 - r1, 2.0 * h); vec2 ca = vec2(q.x - min(q.x, (q.y < 0.0) ? r1 : r2), abs(q.y) - h); vec2 cb = q - k1 + k2 * clamp(dot(k1 - q, k2) / dot2(k2), 0.0, 1.0); float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0; return s * sqrt(min(dot2(ca), dot2(cb))); } float sdDisc(vec3 p, vec2 t) { float torus = sdTorus(p, t); float cylinder = sdCappedCylinder(p, t.y, t.x); return min(torus, cylinder); } float sdDoor(vec3 p, vec3 size, float r) { float box = sdRoundBox(p, size, r); float cylinder = sdCappedCylinder(erot(p - vec3(0., size.y - r, 0.), vec3(1., 0., 0.), pi * 0.5), size.z, size.x); return min(box, cylinder); } float sdDoors(vec3 p, vec3 size, float r) { float door1 = sdDoor(p, size, r); p = erot(p, vec3(0., 1., 0.), pi * 0.5); float door2 = sdDoor(p, size, r); return min(door1, door2); } float sdRailing(vec3 p, float ra, float rb, float h) { const int NUMBER_OF_POSTS = 16; const float postAngle = 2.0 * pi / float(NUMBER_OF_POSTS); float rail = sdTorus(p - vec3(0.0, h + 4.0 * rb, 0.0), vec2(ra, rb)); float posts = 1.0 / 0.0; for(int i = 0; i < NUMBER_OF_POSTS; i++) { float angle = (float(i) + 0.5) * postAngle; vec3 pDelta = erot(vec3(0.0, h, ra), vec3(0.0, 1.0, 0.0), angle); float post = sdCappedCylinder(p - pDelta, h, rb * 0.5); posts = min(posts, post); } return min(rail, posts); } vec3 up(float y) { return vec3(0.0, y, 0.0); } float sdLighthouse(vec3 p) { // Positives float disc1 = sdDisc(p - vec3(0.0), vec2(63.5, 9.0)); float disc2 = sdDisc(p - up(100.0), vec2(50.0, 4.5)); float disc3 = sdDisc(p - up(180.0), vec2(40.75, 4.5)); float cone1 = sdCappedCone(p - up(110.0), 110.0, 63.5, 35.5); float cone2 = sdCappedCone(p - up(220.0), 7.5, 35.5, 44.0); float tower = min(min(disc1, min(disc2, disc3)), min(cone1, cone2)); float disc4 = sdRoundedCylinder(p - up(245.0), 22.5, 4.5, 10.0); float disc5 = sdDisc(p - up(255.0), vec2(50.0, 4.5)); float railing = sdRailing(p - up(258.0), 50.0, 2.5, 10.0); float balcony = min(min(disc4, disc5), railing); float cabinBase = sdRoundedCylinder(p - up(305.0), 17.5, 4.5, 40.0); float disc6 = sdDisc(p - up(345.0), vec2(37.5, 4.5)); float disc7 = sdDisc(p - up(355.0), vec2(32.5, 4.5)); float cabin = min(cabinBase, min(disc6, disc7)); float cone3 = sdCappedCone(p - up(365.0), 9.0, 32.5, 15.0); float cone4 = sdCappedCone(p - up(383.0), 9.0, 15.0, 5.0); float cone5 = sdCappedCone(p - up(401.0), 9.0, 5.0, 2.5); float cone6 = sdCappedCone(p - up(410.0), 4.5, 5.0, 0.0); float roof = min(min(cone3, cone4), min(cone5, cone6)); float wanted = min(min(tower, balcony), min(cabin, roof)); // Negatives float doors1 = sdDoors(p - up(28.0), vec3(17.0, 19.0, 63.5), 5.0); float doors2 = sdDoors(p - up(120.0), vec3(13.5, 18.0, 63.5), 5.0); float doors3 = sdDoors(p - up(200.0), vec3(7.5, 11.0, 63.5), 5.0); float doors4 = sdDoors(p - up(295.0), vec3(15.0, 19.0, 37.5), 5.0); float unwanted = min(min(doors1, doors2), min(doors3, doors4)); return max(wanted, -unwanted); } float sdf(vec3 p) { p = vec3(p.xy - vec2(0.0, 18.0), p.z); p = erot(p, normalize(vec3(0.075, 1.0, 0.0)), -0.5 * iGlobalTime); return sdLighthouse(p); } vec3 sdfNormal(vec3 p) { const float EPS = 0.001; vec3 v1 = vec3(sdf(p + vec3(EPS, 0.0, 0.0)), sdf(p + vec3(0.0, EPS, 0.0)), sdf(p + vec3(0.0, 0.0, EPS))); vec3 v2 = vec3(sdf(p - vec3(EPS, 0.0, 0.0)), sdf(p - vec3(0.0, EPS, 0.0)), sdf(p - vec3(0.0, 0.0, EPS))); return normalize(v1 - v2); } float rayMarch(vec3 start, vec3 direction) { const int NUMBER_OF_STEPS = 128; const float MINIMUM_HIT_DISTANCE = 0.001; const float MAXIMUM_TRACE_DISTANCE = 1000.0; float distance_traveled = 0.0; for(int i = 0; i < NUMBER_OF_STEPS; i++) { vec3 current_position = start + direction * distance_traveled; float distance_to_closest = sdf(current_position); distance_traveled += distance_to_closest; if(distance_to_closest < MINIMUM_HIT_DISTANCE) { return distance_traveled; } if(distance_traveled > MAXIMUM_TRACE_DISTANCE) { break; } } return -1.0; } void main() { vec2 uv = gl_FragCoord.xy / iResolution.xy; vec3 start = vec3((uv - vec2(0.5, 0.0)) * 512.0, 127.0); const vec3 direction = vec3(0.0, 0.0, -1.0); float hitDistance = rayMarch(start, direction); if(hitDistance >= 0.0) { vec3 hitPoint = start + direction * hitDistance; vec3 normal = sdfNormal(hitPoint); vec3 color = normal * 0.5 + 0.5; gl_FragColor = vec4(color, 1.0); } else { gl_FragColor = vec4(0.0); } }

http://nook-luebeck.de/feedback#malen-nach-zahlen