
{"id":2225,"date":"2026-10-11T21:47:22","date_gmt":"2026-10-11T19:47:22","guid":{"rendered":"https:\/\/www.pcjv.de\/?p=2225"},"modified":"2026-10-12T08:19:43","modified_gmt":"2026-10-12T06:19:43","slug":"the-other-side-and-imperfection-two-glsl-scenes-with-a-go-runner","status":"publish","type":"post","link":"https:\/\/www.pcjv.de\/?p=2225","title":{"rendered":"Some GLSL-Fun"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The visualizer video for <a href=\"https:\/\/www.youtube.com\/watch?v=WUqdN0OOYUQ\">Moonchild&#8217;s &#8220;The Other Side&#8221;<\/a> caught me the first time I saw it: calm music, and a scene just as calm, a boat on a night sea under an aurora. I was deep into Shadertoy back then, so I tried to rebuild that scene from techniques I picked up in other shaders. It got somewhere, but never to a state I wanted to publish.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">AI-assisted development got me past the point where I had given up, and I could finally finish what I had in mind for that shader. The workflow was fun enough that I went straight on to a second one: an animated version of &#8220;imperfection&#8221;, an image I once made with generative AI.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p class=\"wp-block-paragraph\">Both are GLSL scenes in Shadertoy form. <a href=\"https:\/\/github.com\/CWBudde\/the-other-side\">the-other-side<\/a> is a night sea: a starry sky, two aurora ribbons low over the horizon, and an empty rowing boat riding the swells. <a href=\"https:\/\/github.com\/CWBudde\/imperfection\">imperfection<\/a> recreates the abstract painting, a warm yellow and red glow that breaks up into brush blocks and streaks and fades into dark blue, and keeps its paint flowing. Both run in the browser with WebGL2: <a href=\"https:\/\/cwbudde.github.io\/the-other-side\/\">the night sea<\/a> and <a href=\"https:\/\/cwbudde.github.io\/imperfection\/\">the painting<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The other side: a night sea in layers<\/h2>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"720\" style=\"aspect-ratio: 1280 \/ 720;\" width=\"1280\" controls loop muted poster=\"\/wordpress\/wp-content\/uploads\/the-other-side-poster.jpg\" src=\"\/wordpress\/wp-content\/uploads\/the-other-side.mp4\"><\/video><figcaption class=\"wp-element-caption\">the-other-side, 12 s at 1280&#215;720. A meteor is forced into every 4 s slot here (<code>-D METEOR_FORCE=1<\/code>); normally they are rarer.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The scene is a stack of layers, each in its own include file. The sky gradient is calibrated against a reference image of the visualizer: a saturated blue-teal zenith, a greyer cyan haze near the horizon, and brightest a little right of centre, under the aurora. The stars are cell-hashed points drawn as small Gaussians whose width never drops below a pixel, so they don&#8217;t flicker under the slow camera drift. Occasional meteors cross from right to left on paths fixed to the sky.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The aurora is the one part I didn&#8217;t write: it is derived from <a href=\"https:\/\/www.shadertoy.com\/view\/XtGGRt\">&#8220;Auroras&#8221; by nimitz<\/a> and keeps its licence (CC BY-NC-SA 3.0). The rainbow palette became a cyan-green to teal ramp, and the volume density is multiplied by a band mask, so instead of filling the whole sky the aurora reads as two broad arcs that meet the horizon. It is ray-marched with 40 steps in the sky and 12 in the reflections on the water.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The sea is not ray-marched at all. It is an analytic hit on a plane, with long swells and stretched noise ripples whose slopes are computed analytically. Every component is filtered against the pixel footprint, so the detail fades out toward the horizon instead of aliasing. The slope variance that gets filtered away is kept as roughness: at grazing angles only the facets tilted toward the viewer are visible, so the water is a bit darker than the sky right above it, and the horizon stays a crisp line. The boat is the only real 3D object: a small distance field, sphere-traced inside its bounding box, with rowlocks and two oars resting in them. It samples the swell height under bow, stern and both sides to heave, pitch and roll, and it is cut off at the true swell height, so its waterline moves with the passing swells although the sea is drawn flat.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The scene renders into a half-float buffer, and a second pass blends in a soft glow built from that buffer&#8217;s mip chain, like the veiling glare of a lens, before it tonemaps. On an Intel Iris Xe the whole scene takes about 10 ms per frame at 1280&#215;720 and 22.5 ms at 1920&#215;1080.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A sea that loops<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For a square Instagram clip, <code>-D LOOP=30<\/code> makes the scene repeat exactly every 30 seconds. Oscillations such as the swells, the camera bob and the aurora waves are adjusted to turn a whole number of cycles per loop, which changes their speed by a few percent. Drifting noise can&#8217;t be adjusted that way, so it is sampled at t and at t minus the loop length and cross-faded over the loop: it moves at its usual speed and is back at the start when the loop closes. Even the meteors wrap: one that crosses the loop point continues after it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Letting the model look at its own output<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Both scenes come with a small Go runner (go-gl and GLFW) instead of living in a browser tab. For me it is a window that reloads whenever an include file changes. For the coding agent it was more important: it can render a deterministic screenshot at any time, one layer in isolation (<code>-D VIEW=2<\/code> shows only the aurora), a side-by-side comparison with a reference, or a frame sequence for a contact sheet. That closes the loop the agent needs: change the code, run Go, grab the screenshot, look at it, and go back to the start if it isn&#8217;t right yet. Without it, every iteration would have needed a browser. The same runner bundles the include files into one file per Shadertoy tab, and the GitHub Pages workflow serves that bundle with a small WebGL2 player.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">imperfection: a painting that keeps flowing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The second scene started from an image, not from a scene: a warm column rising to the top left, a red band running to the right edge, an ice-blue column of drips beside the yellow core, and everything else cool and dark. The goal was not a pixel copy but something that reads as the same painting, and then to make its paint move without moving the composition.<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"720\" style=\"aspect-ratio: 720 \/ 720;\" width=\"720\" autoplay controls loop muted poster=\"\/wordpress\/wp-content\/uploads\/imperfection-poster.jpg\" src=\"\/wordpress\/wp-content\/uploads\/imperfection-loop.mp4\" playsinline><\/video><figcaption class=\"wp-element-caption\">imperfection, the full 32 s loop at 720&#215;720. The big shapes stay put; blocks wipe in and out, drips run and the wet paint smears along its grain. The last frame runs straight into the first.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Reading the image first<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before any shader code, the image was taken apart in <code>docs\/analysis.md<\/code>: composition, colours, directions and building blocks, with the measured numbers next to them. A few observations shaped everything after:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Warm and cool never mix into grey.<\/strong> The transitions are hard: blocks overlap, or streaks of both colours interleave. Interpolating from orange to blue passes through mud, so the shader must never do it. Even the darks are split: blue-black in the cool half, red-black in the warm half, no neutral black.<\/li>\n\n\n\n<li><strong>The texture is anisotropic everywhere.<\/strong> There is almost no isotropic noise, so plain value noise or fbm looks wrong unless it is stretched hard along one axis.<\/li>\n\n\n\n<li><strong>Three stroke directions<\/strong>: vertical drips and pixel-sort streaks dominate, then diagonal hatching at about 35&#8211;50&#176;, then horizontal grain inside some blocks. Every rectangle edge is axis-aligned.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Scoring a painting<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For the night sea, judging by eye against the visualizer was enough. For the painting, &#8220;looks about right&#8221; wasn&#8217;t enough to decide between approaches, so the repository has <code>imgstat<\/code>, a stdlib-only tool that compares a render with the reference by style and composition rather than pixel by pixel. It combines eight components: colour differences of 16&#215;16 and 32&#215;32 downsampled images in OKLab (<code>col16<\/code>, <code>col32<\/code>), two palette distances (<code>palH<\/code>, <code>palW<\/code>), the stroke direction per cell and overall from a structure tensor (<code>dirC<\/code>, <code>dirH<\/code>), and the strength and direction of the fine brush texture (<code>texE<\/code>, <code>texA<\/code>). Each is normalised so that the reference scores 0 against itself and a flat image in its mean colour about 1. The mirrored reference scores 0.61: same palette and texture, everything on the wrong side. For the coding agent this turned &#8220;does it look right?&#8221; into a number it could check after every change, next to the screenshot.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With that, three rival shaders were prototyped from the analysis: a collage of jittered rectangle grids (A), a smear field that drags one coherent colour field along a direction field, in effect a procedural pixel sort (B), and classic brush-stroke splatting (C).<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1875\" height=\"615\" src=\"\/wordpress\/wp-content\/uploads\/imp-rivals.png\" alt=\"Four renders of the imperfection painting side by side: rival A, a rectangle collage; rival B, a smear field; rival C, stroke splatting; and the final shader\" class=\"wp-image-2234\" srcset=\"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-rivals.png 1875w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-rivals-300x98.png 300w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-rivals-1024x336.png 1024w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-rivals-768x252.png 768w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-rivals-1536x504.png 1536w\" sizes=\"auto, (max-width: 1875px) 100vw, 1875px\" \/><figcaption class=\"wp-element-caption\">The three rivals and the final shader, all at t = 0. Scores are <code>imgstat<\/code> distances to the reference (lower is better).<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"800\" src=\"\/wordpress\/wp-content\/uploads\/imp-components.png\" alt=\"Grouped bar chart of the eight imgstat components for rivals A, B and C. B is clearly best in the global direction histogram dirH, C is best in texture anisotropy texA, otherwise the bars are close\" class=\"wp-image-2235\" srcset=\"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-components.png 1600w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-components-300x150.png 300w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-components-1024x512.png 1024w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-components-768x384.png 768w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-components-1536x768.png 1536w\" sizes=\"auto, (max-width: 1600px) 100vw, 1600px\" \/><figcaption class=\"wp-element-caption\">The rivals per score component, from <code>docs\/analysis.md<\/code>. The total is a weighted mean (col16 0.20, col32 and dirC 0.15, texE 0.12, palH, palW and dirH 0.10, texA 0.08).<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The totals are close: 0.1827, 0.1688 and 0.1745. B is clearly ahead only in the global direction histogram (0.057 against 0.130 and 0.078), and C has the best brush texture anisotropy (0.409). The gap between B and C is about as large as the metric&#8217;s noise, so the eye decided. A read like a clean digital mosaic. C had convincing ink scratches, but its warm column looked woven. B had the most painterly gesture, a fanned hatch at the top left, ice drips with pointed tips, paint dragged from the column into the band, and all of it from smearing one field, which keeps the colours consistent. B became the base, with the stroke texture and seam ink scratches from C and the frayed wedge and soot strip from A merged in.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">One colour field, dragged around<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The idea behind B carries the final shader: there is exactly one colour source, a hand-authored field that follows the cross composition. All structure, blocks, drips, hatching and grain, comes from distorting the lookup into that field. The smear drags the lookup along a direction field, so a pixel shows the colour from further up its drip. A block collapses the lookup to a single field point, so it gets one flat colour that still belongs to its surroundings. Every direction is one of 0&#176;, 45&#176;, 90&#176; or 135&#176;.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1860\" height=\"645\" src=\"\/wordpress\/wp-content\/uploads\/imp-layers.png\" alt=\"Four stages of the imperfection shader: the blurry base colour field, the smeared field with drips and hatching, the block layer with living blocks marked in green, magenta and cyan, and the final image\" class=\"wp-image-2236\" srcset=\"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-layers.png 1860w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-layers-300x104.png 300w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-layers-1024x355.png 1024w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-layers-768x266.png 768w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-layers-1536x533.png 1536w\" sizes=\"auto, (max-width: 1860px) 100vw, 1860px\" \/><figcaption class=\"wp-element-caption\">The layers, rendered with the runner&#8217;s debug defines (<code>SHOW_FIELD<\/code>, <code>NO_BLOCKS<\/code>, <code>BLOCK_DEBUG<\/code>). The middle two panels already have the dark accents of the Image pass on top.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">That restriction to four angles was a deliberate choice, and the score shows what it cost. The reference&#8217;s diagonals spread over 46&#8211;66&#176; and 118&#8211;150&#176;, so snapping B&#8217;s strokes to 45&#176; and 135&#176; alone raised its score from 0.1688 to 0.1880. That is the fair baseline for everything after. The first full version scored 0.1961, worse again, but with thin hatching lines instead of wide planks, glaze bars and drips at the bottom left, which looked clearly better. From then on the metric was a check, not a judge: changes that cost a little on the score but looked clearly better stayed. The wet pass below is the biggest of them.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"800\" src=\"\/wordpress\/wp-content\/uploads\/imp-progression.png\" alt=\"Bar chart of imgstat scores from the rivals A, C and B over B snapped to four angles, the first main shader, single pass, two passes with wet smear, before and after polish. B is lowest at 0.1688, the final polished shader scores 0.1912\" class=\"wp-image-2237\" srcset=\"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-progression.png 1600w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-progression-300x150.png 300w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-progression-1024x512.png 1024w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-progression-768x384.png 768w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-progression-1536x768.png 1536w\" sizes=\"auto, (max-width: 1600px) 100vw, 1600px\" \/><figcaption class=\"wp-element-caption\">Score from the rivals to the final shader. Rows with * are means over t = 0, 8, 16 and 24 s. Note the axis starts at 0.15.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Wet paint<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The single-pass version looked pixel hard: crisp 1 px grit and binary pixel edges everywhere, where the painting has wet, blended runs next to a few dry marks. The fix was a second pass. The first pass paints crisp, and its alpha channel packs three facts per pixel into one integer: the grain direction of the topmost paint, the kind of paint (background, grid block, hero block, glaze) and a wetness from 0 to 1, as <code>dir + 4&#183;kind + 16&#183;round(63&#183;wet)<\/code>. That stays at most 1023, which a half float holds exactly. So the second pass knows each pixel&#8217;s true grain without recomputing the direction field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The wet pass then takes 12 bilinear taps along the grain, 72&#160;% of them upstream because paint is dragged downstream, over a run of 4 to 40 reference pixels times the wetness. The taps are weighted by how similar they are to the centre pixel, and taps of the other colour family are dropped entirely, except within 2 px, where that is anti-aliasing. That is the rule from the analysis again: warm and cool never blend into grey, strong edges survive, small grit melts. A true line integral convolution that follows the grain at every tap rendered practically the same image (mean absolute difference 2e-5) and cost about 0.9 ms more, so the simpler version stayed.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1608\" height=\"645\" src=\"\/wordpress\/wp-content\/uploads\/imp-wet.png\" alt=\"A 2x magnified crop of the yellow block next to the ice column, crisp on the left and after the wet smear in the middle, and the wetness and grain map of the whole image on the right\" class=\"wp-image-2238\" srcset=\"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-wet.png 1608w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-wet-300x120.png 300w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-wet-1024x411.png 1024w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-wet-768x308.png 768w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-wet-1536x616.png 1536w\" sizes=\"auto, (max-width: 1608px) 100vw, 1608px\" \/><figcaption class=\"wp-element-caption\">A 200&#215;200 px crop at 2&#215;: the crisp paint of the first pass, the same after the wet smear, and the map that controls it.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The score didn&#8217;t like it: +0.006. The fine texture energy got better (0.139 to 0.125), because the excess grit melted, but the anisotropy got worse, since a directional blur sharpens whatever direction a cell already has. It stayed anyway, because it is what makes the image read as paint. At 1024&#215;1024 on an Intel Iris Xe the paint pass takes about 9.4&#8211;9.7 ms and the wet pass with accents and grade about 2.35 ms.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Motion on a fixed composition<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The animation follows one rule: the painting is alive, but its composition is fixed. Drips run downward at different speeds, smears in the band drag to the right, grid blocks wipe in along their grain, hold and wipe out again, the dark wedge&#8217;s strokes slide along their lanes, and the yellow core flickers like a low fire, all along the same four angles. Nothing reads raw time: every layer takes it from one clock with a fixed 32 s loop, scrolling a whole number of pattern periods per loop or drawing a new event per time slot. The frames at 0 and 32 s are identical, and <code>-D ANIM=0<\/code> freezes everything.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"693\" src=\"\/wordpress\/wp-content\/uploads\/imp-loop.png\" alt=\"Line chart of the mean absolute RGB difference between consecutive frames over the 32 second loop, flat around 0.007 with a few small spikes; the step from the last frame back to the first is 0.0080, inside the normal range\" class=\"wp-image-2239\" srcset=\"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-loop.png 1600w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-loop-300x130.png 300w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-loop-1024x444.png 1024w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-loop-768x333.png 768w, https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/imp-loop-1536x665.png 1536w\" sizes=\"auto, (max-width: 1600px) 100vw, 1600px\" \/><figcaption class=\"wp-element-caption\">How much each frame of the video above differs from the next (960 frames, 30 fps). The median step is 0.0073, the range 0.0069&#8211;0.0083, and the step across the loop seam is 0.0080: the seam is just another frame.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The analysis doesn&#8217;t pretend it is done: the ice column is still more streaky than in the reference, and the lower warm column more uniform orange than the reference&#8217;s patchwork. But it reads as the painting, and the paint keeps flowing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Trying it<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The browser versions need WebGL2 (<code>Space<\/code> pauses, <code>F<\/code> toggles fullscreen, <code>?t=20<\/code> sets the start time): <a href=\"https:\/\/cwbudde.github.io\/the-other-side\/\">the-other-side<\/a> and <a href=\"https:\/\/cwbudde.github.io\/imperfection\/\">imperfection<\/a>. The runner needs Go and OpenGL 4.1:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git clone https:\/\/github.com\/CWBudde\/the-other-side &amp;&amp; cd the-other-side\ngo run . -D VIEW=2              # window with hot reload, aurora only\ngo run . -shot out\/shot.png -time 12\ngo run . -bundle out\/shadertoy.glsl<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The READMEs list the layer views and tunables, and imperfection&#8217;s <code>docs\/analysis.md<\/code> has the full scoring tables.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>The visualizer video for Moonchild&#8217;s &#8220;The Other Side&#8221; caught me the first time I saw it: calm music, and a scene just as calm, a boat on a night sea under an aurora. I was deep into Shadertoy back then, so I tried to rebuild that scene from techniques I picked up in other shaders. [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":2226,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[41],"tags":[],"class_list":["post-2225","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-go"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Some GLSL-Fun - Christian&#039;s private site<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.pcjv.de\/?p=2225\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Christian\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225\"},\"author\":{\"name\":\"Christian\",\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/#\\\/schema\\\/person\\\/79b654f8c2fb5bf9405ccc4cb1c11880\"},\"headline\":\"Some GLSL-Fun\",\"datePublished\":\"2026-10-11T19:47:22+00:00\",\"dateModified\":\"2026-10-12T06:19:43+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225\"},\"wordCount\":2193,\"image\":{\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.pcjv.de\\\/wordpress\\\/wp-content\\\/uploads\\\/teaser-shaders.jpg\",\"articleSection\":[\"Go\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225\",\"url\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225\",\"name\":\"Some GLSL-Fun - Christian&#039;s private site\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.pcjv.de\\\/wordpress\\\/wp-content\\\/uploads\\\/teaser-shaders.jpg\",\"datePublished\":\"2026-10-11T19:47:22+00:00\",\"dateModified\":\"2026-10-12T06:19:43+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/#\\\/schema\\\/person\\\/79b654f8c2fb5bf9405ccc4cb1c11880\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.pcjv.de\\\/?p=2225\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225#primaryimage\",\"url\":\"https:\\\/\\\/www.pcjv.de\\\/wordpress\\\/wp-content\\\/uploads\\\/teaser-shaders.jpg\",\"contentUrl\":\"https:\\\/\\\/www.pcjv.de\\\/wordpress\\\/wp-content\\\/uploads\\\/teaser-shaders.jpg\",\"width\":480,\"height\":360},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/?p=2225#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.pcjv.de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Some GLSL-Fun\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/#website\",\"url\":\"https:\\\/\\\/www.pcjv.de\\\/\",\"name\":\"Christian&#039;s private site\",\"description\":\"Private site of Christian-W. Budde\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.pcjv.de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.pcjv.de\\\/#\\\/schema\\\/person\\\/79b654f8c2fb5bf9405ccc4cb1c11880\",\"name\":\"Christian\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/009d545df1a458e74ec172fa5dc19428c1144c2ee8f1ec82d94c3b28f12a0c14?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/009d545df1a458e74ec172fa5dc19428c1144c2ee8f1ec82d94c3b28f12a0c14?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/009d545df1a458e74ec172fa5dc19428c1144c2ee8f1ec82d94c3b28f12a0c14?s=96&d=mm&r=g\",\"caption\":\"Christian\"},\"sameAs\":[\"http:\\\/\\\/www.savioursofsoul.de\\\/Christian\"],\"url\":\"https:\\\/\\\/www.pcjv.de\\\/?author=3\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Some GLSL-Fun - Christian&#039;s private site","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.pcjv.de\/?p=2225","twitter_misc":{"Written by":"Christian","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.pcjv.de\/?p=2225#article","isPartOf":{"@id":"https:\/\/www.pcjv.de\/?p=2225"},"author":{"name":"Christian","@id":"https:\/\/www.pcjv.de\/#\/schema\/person\/79b654f8c2fb5bf9405ccc4cb1c11880"},"headline":"Some GLSL-Fun","datePublished":"2026-10-11T19:47:22+00:00","dateModified":"2026-10-12T06:19:43+00:00","mainEntityOfPage":{"@id":"https:\/\/www.pcjv.de\/?p=2225"},"wordCount":2193,"image":{"@id":"https:\/\/www.pcjv.de\/?p=2225#primaryimage"},"thumbnailUrl":"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/teaser-shaders.jpg","articleSection":["Go"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.pcjv.de\/?p=2225","url":"https:\/\/www.pcjv.de\/?p=2225","name":"Some GLSL-Fun - Christian&#039;s private site","isPartOf":{"@id":"https:\/\/www.pcjv.de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.pcjv.de\/?p=2225#primaryimage"},"image":{"@id":"https:\/\/www.pcjv.de\/?p=2225#primaryimage"},"thumbnailUrl":"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/teaser-shaders.jpg","datePublished":"2026-10-11T19:47:22+00:00","dateModified":"2026-10-12T06:19:43+00:00","author":{"@id":"https:\/\/www.pcjv.de\/#\/schema\/person\/79b654f8c2fb5bf9405ccc4cb1c11880"},"breadcrumb":{"@id":"https:\/\/www.pcjv.de\/?p=2225#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.pcjv.de\/?p=2225"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.pcjv.de\/?p=2225#primaryimage","url":"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/teaser-shaders.jpg","contentUrl":"https:\/\/www.pcjv.de\/wordpress\/wp-content\/uploads\/teaser-shaders.jpg","width":480,"height":360},{"@type":"BreadcrumbList","@id":"https:\/\/www.pcjv.de\/?p=2225#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.pcjv.de\/"},{"@type":"ListItem","position":2,"name":"Some GLSL-Fun"}]},{"@type":"WebSite","@id":"https:\/\/www.pcjv.de\/#website","url":"https:\/\/www.pcjv.de\/","name":"Christian&#039;s private site","description":"Private site of Christian-W. Budde","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.pcjv.de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.pcjv.de\/#\/schema\/person\/79b654f8c2fb5bf9405ccc4cb1c11880","name":"Christian","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/009d545df1a458e74ec172fa5dc19428c1144c2ee8f1ec82d94c3b28f12a0c14?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/009d545df1a458e74ec172fa5dc19428c1144c2ee8f1ec82d94c3b28f12a0c14?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/009d545df1a458e74ec172fa5dc19428c1144c2ee8f1ec82d94c3b28f12a0c14?s=96&d=mm&r=g","caption":"Christian"},"sameAs":["http:\/\/www.savioursofsoul.de\/Christian"],"url":"https:\/\/www.pcjv.de\/?author=3"}]}},"_links":{"self":[{"href":"https:\/\/www.pcjv.de\/index.php?rest_route=\/wp\/v2\/posts\/2225","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pcjv.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.pcjv.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.pcjv.de\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pcjv.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2225"}],"version-history":[{"count":5,"href":"https:\/\/www.pcjv.de\/index.php?rest_route=\/wp\/v2\/posts\/2225\/revisions"}],"predecessor-version":[{"id":2240,"href":"https:\/\/www.pcjv.de\/index.php?rest_route=\/wp\/v2\/posts\/2225\/revisions\/2240"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.pcjv.de\/index.php?rest_route=\/wp\/v2\/media\/2226"}],"wp:attachment":[{"href":"https:\/\/www.pcjv.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2225"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pcjv.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2225"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pcjv.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2225"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}