Shader - Architektur
Das ist so nach und nach komplexer geworden.
Die Grafikpipeline
Shader
- Vertex
- Fragment / Pixel
- Compute
State
Wir benutzen: const, uniform
Es gibt auch noch:
attribute, varying, buffer
Shader Compilezeit
Shader werden gebaut.
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:
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);
}
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));
}
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);
}
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);
}
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);
}
.. 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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
Raymarching
Raymarching
Raymarching
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
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);
}