
[{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/3d/","section":"Tags","summary":"","title":"3d","type":"tags"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/en/posts/","section":"Homepage","summary":"","title":"All Posts","type":"en"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/categories/article/","section":"Categories","summary":"","title":"Article","type":"categories"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/blender/","section":"Tags","summary":"","title":"Blender","type":"tags"},{"content":"\rGoals\r#\rArtfight is an annual art gifting competition, where participants score points against each other by creating art of their opponent\u0026rsquo;s character designs. I had a go last year, but I chafed a bit against the restrictions of the website. They have strict file size limits, to keep server costs down, but it makes it hard to showcase turnaround renders of 3D models.\nMy solution to this (for this year, at least), was to create a blender extension focused towards making single high-quality renders. The demographic of the Artfight community tend to be fans of pins, keychains and the like, so I thought it would be a fun gift to make renders of hypothetical merch of the character.\nHopefully, I could then encourage people to have a go at using the tool themselves, and get some feedback that way!\nTechnical Implementation\r#\rThe script is an implementation of the Marching Squares algorithm, which I hoped would counteract some of the problems I had with a previous similar project, my spinning ornament generator. I was often frustrated with the edges of the models for that project, which came out very jagged. That wasn\u0026rsquo;t so much of a problem if you massively increase the resolution, but that naturally increases generation time.\nMarching squares smooths out those edges, although it still has some square-like artifacts at the edges.\nIt takes a bit of balance to make sure the model is high enough in resolution to match the image texture, but doesn\u0026rsquo;t create overly artifacted edges.\rConclusion - For now\r#\rWhen I uploaded it to the Blender Extensions site, I was rejected on the basis of poor code architecture and not following best practices.\nIt was a surprise at first, but on reflection, I hadn\u0026rsquo;t thought about or properly researched the standards and guidelines for the website. This isn\u0026rsquo;t just an itch.io game jam or a personal upload, these communities have a standard above the code simply functioning, and they don\u0026rsquo;t expect uploads to have been made under time limits. In my rush I had definitely let my own quality slip.\nI\u0026rsquo;m going to take a break from the project for a month or two, and then come back to it with fresh eyes to bring it up to snuff. Watch this space!\n","date":"17 August 2026","externalUrl":null,"permalink":"/en/posts/enamel_pin/","section":"Homepage","summary":"For the 2026 Artfight competition, I had a go at making a blender plugin that reads images and creates enamel pin models","title":"Blender Plugin For Making Enamel Pins","type":"en"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/","section":"HK Oxalis Portfolio Website","summary":"","title":"HK Oxalis Portfolio Website","type":"page"},{"content":"\rRecent articles\rBlender Plugin For Making Enamel Pins\r17 August 2026\u0026middot;381 words\u0026middot;2 mins\rPython\r3d\rMeshes\rImage Processing\rTools\rBlender\rFor the 2026 Artfight competition, I had a go at making a blender plugin that reads images and creates enamel pin models\rCosmic shaders for the GMTK game jam\r3 August 2025\u0026middot;499 words\u0026middot;3 mins\rGodot\rShaders\r3d\rGlsl\rC#\rGames\rGame Jam\rVisuals\rStylisation\rFor the GMTK game jam 2025, I made a gravity-themed billiards game in Godot, with all art made procedurally with shaders\rWood-cut markmaking for a turn-based rpg\r2 June 2025\u0026middot;391 words\u0026middot;2 mins\rUnity\rShaders\r3d\rHlsl\rC#\rGames\rVisuals\rAgain using the OpenUSD Python API, I created a script to generate 3d printable models from images\r","date":"17 August 2026","externalUrl":null,"permalink":"/en/","section":"Homepage","summary":"","title":"Homepage","type":"en"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/image-processing/","section":"Tags","summary":"","title":"Image Processing","type":"tags"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/meshes/","section":"Tags","summary":"","title":"Meshes","type":"tags"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/categories/portfolio/","section":"Categories","summary":"","title":"Portfolio","type":"categories"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/tools/","section":"Tags","summary":"","title":"Tools","type":"tags"},{"content":"","date":"3 August 2025","externalUrl":null,"permalink":"/tags/c%23/","section":"Tags","summary":"","title":"C#","type":"tags"},{"content":"\u003c!DOCTYPE html\u003e\rBlack Hole Billiards by indefinite_article\rIntroduction For the GMTK game jam 2025, after my failure to follow through last year, I decided to stick with something that felt achievable but had space for ambition.\nThe theme was 'loop', so my concept was Black Hole Billiards, a game where you try and control planets in orbit around you and land them in pockets to score points.\nAnd instead of fussing around with pixel art or 3d models, I was going to do all the art through shaders!\nHere's each of those shaders one by one!\nBlack Hole\rFor the titular player avatar, I kind of cheated to get the right effect. Of course, a real black hole's appearance is based on complex light physics in the void of space, but this one is basically a paper cut-out and a ping-pong ball.\nThe orange corona effect is made out of two pieces, pictured below, and I use polar coordinates to scroll noise over it, creating the spiralling effect. Ringed Planet\rFor these animated rings, I used polar coordinates on a square again. Polar coordinates are an alternative to grid-based coordinates adapted to circular motion or shapes.\rThe way they work is that the x coordinate is the radius, or the distance from the centre point of the coordinates and the y coordinate is the angle from that centrepoint, or you could think of it as the angle around the diameter of an outer circle.\rThe rings are created by applying noise along the x coordinate and then I add variation along the polar y to create an orbiting effect.\nAlien Planet\rThis one's surface is two pretty simple thresholded noise samples, one blended over the other to create the clouds. The rings are using the same rect shader as the previous planet, but I've used parameters to make the rings thinner and increase the strength of the orbiting noise to give it a different character of movement.\rJupiter-like Planet\rFor this one, I used two samples of noise, one that stays constant on the UV x to split the sphere into stripes and another that scrolls over it. The second noise sample scrolls mostly horizontally but a little bit vertically to create more variation. I add that value to the x axis of the primary stripe sample.\nThis is all black and white, which I multiply with a base orange colour, but that looks a bit flat, so I also added a bit of artistic colour adjustment to add some cooler shades to the clouds.\nLava Planet\rThis one uses a vertex shader as well as a fragment. It uses a smoothstep function on a noise sample to subtract depth along the normal vector and then passes the sample to the fragment function.\rThe fragment takes that to mask a surface and secondary colour and some scrolling noise the animate the secondary colour. I also give the secondary colour to the emmisive output, to create a glowing lava effect.\r","date":"3 August 2025","externalUrl":null,"permalink":"/en/posts/black_hole_billards/","section":"Homepage","summary":"For the GMTK game jam 2025, I made a gravity-themed billiards game in Godot, with all art made procedurally with shaders","title":"Cosmic shaders for the GMTK game jam","type":"en"},{"content":"","date":"3 August 2025","externalUrl":null,"permalink":"/tags/game-jam/","section":"Tags","summary":"","title":"Game Jam","type":"tags"},{"content":"","date":"3 August 2025","externalUrl":null,"permalink":"/tags/games/","section":"Tags","summary":"","title":"Games","type":"tags"},{"content":"","date":"3 August 2025","externalUrl":null,"permalink":"/tags/glsl/","section":"Tags","summary":"","title":"Glsl","type":"tags"},{"content":"","date":"3 August 2025","externalUrl":null,"permalink":"/tags/godot/","section":"Tags","summary":"","title":"Godot","type":"tags"},{"content":"","date":"3 August 2025","externalUrl":null,"permalink":"/tags/shaders/","section":"Tags","summary":"","title":"Shaders","type":"tags"},{"content":"","date":"3 August 2025","externalUrl":null,"permalink":"/tags/stylisation/","section":"Tags","summary":"","title":"Stylisation","type":"tags"},{"content":"","date":"3 August 2025","externalUrl":null,"permalink":"/tags/visuals/","section":"Tags","summary":"","title":"Visuals","type":"tags"},{"content":"","date":"2 June 2025","externalUrl":null,"permalink":"/tags/hlsl/","section":"Tags","summary":"","title":"Hlsl","type":"tags"},{"content":"","date":"2 June 2025","externalUrl":null,"permalink":"/tags/unity/","section":"Tags","summary":"","title":"Unity","type":"tags"},{"content":"\u003c!DOCTYPE html\u003e\rSummary\rFor our final major project in college, we were tasked with getting into groups and making a game. I was the group leader of 5 students and as my specific production role, I decided to focus on making shaders to bring the 3d and 2d specialist's work together.\nHere I'm going to talk through the work I'm most proud of from the project, the shader that recreates wood cut markmaking.\nSystem\rThe shader is based off basic 1-bit dithering principles, where we compare the darkness of the current pixel to a dithering texture to decide whether to use the dithered colour or the base colour. The difference here is that the 'darkness' the shader looks at is not the literal colour or lighting data, but instead a value computed based on the camera data. It's also done in the material colour, not as a post-process effect.\nOne of the influences on the 'darkness' value is the dot product between the camera direction and the normal vector, which is then increased by a nudge control. This creates shading on more sharply angled faces, dulling or even blacking them out, which helps show the contour of a model. The control is also very important so users can push this influence to be more or less significant on different materials.\nA similar thing is done with the distance between the camera and the section of the model. The controls here are in a remap node, which translates to an effect on the range of distances the shader reacts to. This is useful for objects of different sizes, after all, a difference of 1m for a chair is going to be much more significant than for a castle.\nControls\rThe controls exposed to artists look like this, and I was able to explain them to the rest of the team quite easily. One significant feature here is the auto-darken button, which is useful for more colourful scenes. We generally decided on using darker versions of the surface colour for the dithering in the coloured scenes, so the auto-darken generates that automatically. However, there's also cases where more control over the colour is desired, so you can set the dithering colour manually as well.\nI think it turned out well in the final game, though I'm still partial to the black-and-white style myself.\n","date":"2 June 2025","externalUrl":null,"permalink":"/en/posts/resonant_sceptre/","section":"Homepage","summary":"Again using the OpenUSD Python API, I created a script to generate 3d printable models from images","title":"Wood-cut markmaking for a turn-based rpg","type":"en"},{"content":"","date":"6 February 2025","externalUrl":null,"permalink":"/tags/2d/","section":"Tags","summary":"","title":"2d","type":"tags"},{"content":"","date":"6 February 2025","externalUrl":null,"permalink":"/tags/c++/","section":"Tags","summary":"","title":"C++","type":"tags"},{"content":"\u003c!DOCTYPE html\u003e\rIntroduction\rOne of my programming inspirations is the Youtube channel The Coding Train, which frequently features breakdowns and implementations of scholarly articles that can be used for generative art. While I started watching as a beginner, I’ve recently found some success looking into their source material for more information. I’ve also been trying to learn the processing-adjacent library OpenFrameworks, which isn’t covered by the Coding Train channel, so I decided to go searching out my own source to re-implement. This project is an implementation of the lichen pattern generation algorithm described by Robert Sumner in his thesis, Pattern Formation in Lichen. It’s written in C++ using the OpenFrameworks library, and I did it without any external resources beyond the OpenFrameworks documentation.\rThe Algorithm\rThe system essentially works by using random walkers to emulate the two forces that drive a lichen’s shape; there is the growth force that causes branches to expand quickly the less competition they have from other lobes, but also the limiting force that causes growth to slow the further away the branch is from the centre body of the mass. While randomly moving particles sticking to each other don't reflect the real biological mechanics of lichens, by creating rules about how they stick we can create visually similar patterns.\nThe rule in this case is that a particle is more likely to stick if there are more stuck particles around it. This mixes with the typical rule that the outer fringes of the mass will be more likely to be hit to create those two opposing forces mentioned above.\nImplementation\rMy first low resolution simulations went well, but when I started to make the cell size smaller and the cell count larger, that’s when things started to get interesting. As you can see here, it developed into a single large, spiralling arm, not a rounded lichen at all. I think this is because I’m spawning all of my particles at the same time, not one at a time as the paper does. I didn’t think it would make a difference, but the paper mentions that they should be spawned far enough away their behaviour is the same as if they originated infinitely far away. Maybe the little bits of interaction that happen when multiple particles stick at the same time is enough to throw off the mathematical equilibrium and create a single long arm like this? It happened every time with these small particles and large spawning radius, and it cleared up when I switched to sending them one at a time.\nEven then, there’s still a visible clockwise spiral. I think that one's because of one of the ‘sticking’ steps.\nThe paper doesn’t mention how this rotation is calculated, and my initial improvised approach was always rotating them clockwise, which creates these spirals. Instead, I used this line of code float clockwise_Adjust = ceil(ofRandom(2)) == 1? 0.1 : -0.1; to randomise which direction the random walker rotates in. This reduces the obvious spiralling pattern, although I still feel like there’s some bias.\rMaybe it’s just my imagination, but do you notice how the anticlockwise faces seem to be a bit smoother? I did some measurements of generation speed, and I found that one of the major delays was when a particle would get stuck moving at the slowest speed in an area without any settled clusters. One of the optimisations the paper suggests is to make the particles take larger steps when they are outside of the settled cluster’s range, which speeds up the random walks immensely, but it only tests the range with a radius, assuming it’s a perfect circle. In extremely not-round cases, this slows the particles down drastically without any chance of them meeting the settled cluster. Instead, by using a rectangular bounding box, we can speed up the steps without adding much more computational load. There’s still cases where the particle is wandering around in the middle of nowhere, but the worst-case scenarios are limited. This optimisation becomes less useful the more exactly circular the lichen becomes, but my generations seem to generally end up with an off-centre profile.\rConclusion\rIn its current state, my simulation's a bit slow, especially compared to the paper's speeds of 100,000 elements in 15 minutes. It's not putting any strain on my computer, so I think I need to more fully understand how to make use of the full computing power available to me. I've added a few visualisation options in the code file, but it's a bit of a hassle to change the variables manually.\nIf I was to come back to this, I'd like to speed it up and add a UI that could expose those options and let other people experiment with it. OpenFramworks and C++ aren't quite as easy to embed in a webpage as P5.js, but I've seen that there are ways, and it would be nice to add the tool to this website.\n","date":"6 February 2025","externalUrl":null,"permalink":"/en/posts/lichen/","section":"Homepage","summary":"I used the C++ OpenFrameworks library to create a lichen visual simulation, following a scientific paper","title":"Lichen Pattern Simulation","type":"en"},{"content":"","date":"6 February 2025","externalUrl":null,"permalink":"/tags/simulations/","section":"Tags","summary":"","title":"Simulations","type":"tags"},{"content":"","date":"28 January 2025","externalUrl":null,"permalink":"/en/game_jams/","section":"Homepage","summary":"","title":"All Game Jams","type":"en"},{"content":"","date":"28 January 2025","externalUrl":null,"permalink":"/categories/game-jams/","section":"Categories","summary":"","title":"Game Jams","type":"categories"},{"content":"","date":"28 January 2025","externalUrl":null,"permalink":"/tags/group-projects/","section":"Tags","summary":"","title":"Group Projects","type":"tags"},{"content":"","date":"28 January 2025","externalUrl":null,"permalink":"/tags/unreal-engine/","section":"Tags","summary":"","title":"Unreal Engine","type":"tags"},{"content":"","date":"16 January 2025","externalUrl":null,"permalink":"/tags/3d-printing/","section":"Tags","summary":"","title":"3d Printing","type":"tags"},{"content":"\rMy Goal\r#\rIn the autumn, I was walking through a market with my dad and I saw this lovely stall. The optical illusion they create is stunning, and I decided I’d try to write code to recreate them.\nYour browser cannot play this video. Download video.\nMy goal was to create code that would read a monochrome outline image and then create a set of fully 3D printable concentric rings, which I could then send to a printer without much post-processing. Currently, the code does just that, using only USD, the Pillow library to handle the image processing and numpy for some operations.\nThey went down well as Christmas presents too!\rCode Breakdown\r#\rThe code starts with the function to create the outline, which I read from an image using the Pillow library. To account for both greyscale and full-colour images, I calculate my threshold using the data PIL supplies me. I’m currently just halving the maximum colour, which could create problems for very light images, but black is the preferred input colour anyway for other reasons.\nI had thought to use a typical outline shader-style convolution filter to find my edges, but I found that the brute force approach worked better and was easier to debug, considering that this is running on the CPU. Here I’m just checking the immediate neighbours to see if they’re crossing the colour threshold I found above.\nIf we’re creating a debug image, I can mark the point directly, and otherwise I can map the point to normalised 3D coordinates.\nIt’s important that they’re normalised, so when I go through creating the rings I can just multiply their locations by a given radius. The expand value here is to create spacing between rings, so they remain separate in the print and also so they have clearance to spin. The offset value is for the second set of vertices that give the model depth, again so that the model is physically viable without any editing.\nOnce I have the vertices, I have to create the faces to attach them. We do this by creating a list of which points belong to which face, and then a list of how many points each face has. I’m only using quads for this, so that second list is just a bunch of fours and I can be sure any four indices added together will be connected.\nThis is pretty simple for the ring surface, I’m just alternating between the inner and outer rings and connecting neighbours. There’s a bit of an edge case when we reach the last vert where we need to wrap back around to 0, but we can solve that with a modulus.\nAt this point we’ve got a series of concentric planes that match the given outline and other settings. The side faces are very similar, although I do have to split them into two for the sake of the normals.\nThe normal values are automatically generated, but they assume that the face’s points were wound clockwise, facing the front. Any error can also be easily fixed in most 3D editing software, and it’s not strictly relevant for a 3D printable volume, except that the slicer will use them to figure out how to handle intersections, which caused a few problems with the stringing loops.\nThose loops being hollow cylinders which I make very similarly to the main rings, although I reposition them using this code to place them relative to the highest and lowest central points on the outline. It’s not super reliable with asymmetric shapes, and needs a bit of manual tweaking sometimes, but it works well enough for most cases.\nConclusion\r#\rThe biggest problem I still have is that it doesn’t handle fuzzy edges well, such as those created by anti-aliasing, and it can’t handle stray pixels in the source image either. I have to create solid black-and-white images using a specific brush in my digital art software, and I wonder if a different artist would be able to identify potential issues and avoid them in their own art.\nImage problems are pretty obvious when you look at the model, but chasing the source down isn’t always intuitive, and I’d rather be more error-tolerant from the outset. I’d also like to clean up the code to sort out my constants and parameters, to make configuration a bit easier.\nYour browser cannot play this video. Download video.\n","date":"16 January 2025","externalUrl":null,"permalink":"/en/posts/spinners/","section":"Homepage","summary":"Again using the OpenUSD Python API, I created a script to generate 3d printable models from images","title":"Programmatically Generating Spinning Ornaments","type":"en"},{"content":"\u003c!DOCTYPE html\u003e\rOpen Usd Mazes\rYour browser cannot play this video. Download video.\nOpen Usd is a platform aimed at being a universal file interchange system for 3d model and animations. It was originally developed by Pixar for their animated films, but was released as open source in 2016. In 2023, the ‘Alliance for OpenUsd’ was announced, including such founding members as Adobe, NVIDIA and Autodesk. Its purpose is to promote the standardisation and growth of the software, under the theory that the wide adoption of a standard and high-quality ecosystem will boost the ability of everyone who works with 3d models.\nAs part of that, NVIDIA has provided a series of free online courses for their Python Usd API, which allows for programmatic interaction with the system. After finishing the first half of their training, I decided to have a go at bringing an old maze generation project of mine into 3d. Basic Implementation\rThe maze generator is in Javascript and it’s producing a JSON file that looks like this: {\"0,0\":{\"x\":0,\"y\":0,\"links\":[[1,0]]}, \"1,0\":{\"x\":1,\"y\":0,\"links\":[[0,0],[2,0]]} Each object saves an X and Y grid location and then the X and Y of their linked cells. There’s some extra data, because I’m saving the cell’s coordinates as both the dictionary key and in the x and y values, but that’s mostly just for readability. I throw the keys away immediately, because I only need the values.\nWe then take that list of cells and, iterating through them, we find the corner locations, and use them plus half a cell to find the centre location, which I place a parent transform at.\nThen, we test if the coordinates for a neighbour cell are in the linked cells list, if they aren’t, we would like to place a wall. We only make the north or west walls if we're at the edge of the maze (as in, the x or y positions are 0), to avoid duplicate geometry.\nWhen I first tried to do this, I got far too many walls and a strange diagonal line through the middle. Using some print statements, I was able to find out that only connections with symmetrical coordinates ([0, 0], [1, 1], [5, 5]) were being correctly processed, and it was because I got X and Y mixed up earlier in the code. Once we’ve identified that we want to place a wall, it’s just a matter of creating a cube of the correct dimensions and moving it to the right place.\nI’ve had some issues with z-fighting on corners and there’s two ways I could solve them. On the one hand, I could change the system to be creating an entire solid mesh out of the maze by defining vertices. That would preclude arbitrary meshes though, and I think those are more useful for games. Instead, when I upgrade this to use arbitrary meshes, I’ll ask the user for a corner mesh, which will make the maze overall more interesting too.\n","date":"16 October 2024","externalUrl":null,"permalink":"/en/posts/3d_mazes/","section":"Homepage","summary":"Using the Python API for OpenUSD, I processed the JSON output of a Javascript maze project into 3D models","title":"3D Mazes with JSON and USD","type":"en"},{"content":"\u003c!DOCTYPE html\u003e\rLive shader\rWhat and Why?\rLet’s start by talking about posterisation. Posterisation is a way of snapping all colours in an image to some amount of limited values. This creates flat blocks of colour and strange ‘rainbow’ patterns in the edges of light and dark. You often see it in horror games like Buckshot Roulette and Lethal Company, but it’s not like horror has a monopoly in rainbows.\nThe problem is, if you want to use it for a different mood, you need more control of what colours appear. You need control of the palette. Palettes aren’t that relevant in most art, but pixel art historically sticks very closely to limited palettes, and pixel art specific programs like Aseprite do it automatically. I’ve spent some time working on pixel art shaders, and the typical ‘cheat’ way to use palettes is to just greyscale the image and have your palette in exact brightness order, but that doesn’t allow enough direct control to be useful in most games.\nTake these two windows with different coloured flowers. Mathematically, the flowers are very similar colours, with very close saturation and brightness levels, so the only way to tell them apart is their hue, which is thrown away if you use a greyscale method. Now, what if they were your health and stamina bar? Or variations of elemental enemies? Or a normal versus critical hit? Colour information is very important for player communication, but standard palettisation techniques almost always lose it. That’s what I want to fix here.\nExecution\rFor this one I’m mostly translating a shader I wrote in Unity for college. The key part is this section, which takes two colours and a target colour and returns the one closest to the target colour.\rApologies for the poor highlighting, the online p5js editor isn’t very good for shaders. I’ve tried to be as descriptive as I can with the variable names. To break it down, find the difference between each colour and the target colour and then return the one with the lowest difference.\nDefining the colour palettes is annoying if I just do it through a const array, and the GLSL arrays don’t seem to be behaving themselves.\nAh, I see. If I want to use p5js, then I’m stuck in GLSL 1.0. And GLSL 1 does not like arrays for some reason. ...\nI mean, if it works, it works. I have some ideas about getting palettes from images, like the ones the Lospec site exports. Loops and arrays are objectively better for this, but this’ll work for debugging, as long as I remember to normalise my rgb values. (White screen not pictured)\nThat’s pretty cool, isn’t it? It’s palettised to the colour scheme I use on this website. Check out the shadows in the scales! But my real favourite part of this is when we get HSV involved.\nHSV and settings\rOne quick set of borrowed conversion code later and we can implement some cool mixing. HSV stands for Hue, Saturation and Value, so by selectively keeping one or more of the attributes from the original image, we can retain some shading while changing the hue and other aspects.\nHere’s the same image with the value carried across from the photo, which is what I stuck with for my college project, but I want to let the users fiddle with things live on the website.\nA little bit of parameterisation and we’re ready to move into html. I’ve copied in my image uploading code from the dithering page and the rest of it should be pretty simple as well.\nThere were a few typos and the like, but now everything's live on the site! My favourite configuration is still hue and saturation but no value, but see what you think! There are a few image artifacts that appear in certain settings, the brightness is a bit low when the Value is palettised, and I'd like to let the user select a palette, but not today.\nPalette images\rRight. Image palette time. I grabbed this palette by Conker from lospec, and got to work. Assuming we’ll be using a fixed size of 8 colours for now, we can get an offset value of 1/8 to find each colour in the image, and ‘iterate’ through them to set the colours. Unfortunately, we're defining the colours as constants right now, but if we want to change the active palette, I don’t think we can make it a constant. We’re not exactly changing it every frame either, but I don’t know if there’s a better way to do it than just moving the colour definitions into main(). This is really mesy without a loop... And we get a black screen. I checked to see if the colours are being correctly identified, and they are, but, of course, the colour definitions are now in the 0-1 range, instead of the more human-readable 0-255 I had before.\nAnd here we have it. Cool right? And now all the logic is in the main() function, I can parameterise the palette size and make any arbitrary sized palette. With a real loop! Loops do have their performance issues in shaders, but it’ll only loop as many times as there are colours in the palette, which shouldn’t be too much anyway. More than 16 and you might as well be doing normal posterisation and colour correction.\n","date":"22 July 2024","externalUrl":null,"permalink":"/en/posts/palettisation/","section":"Homepage","summary":"I wrote a shader using GLSL to align the colours in an image to a palette","title":"Applying palettes to images with shaders","type":"en"},{"content":"","date":"22 July 2024","externalUrl":null,"permalink":"/tags/web/","section":"Tags","summary":"","title":"Web","type":"tags"},{"content":"\u003c!DOCTYPE html\u003e\rLive Shader\rWhat is dithering?\rDithering is, essentially, a way to break up shapes and create the illusion of more colours than actually exist within the image. You may be more familiar with its cousin, commonly known as halftoning.\nThat's a panel from a 1933 Tarzan comic strip, as sourced by the Legion Of Andy blog, in part 8 their fascinating writeup on comic book printing techniques\nYou can see how, while there are only three ink colours there, and they're not mixing at all, a gradient is created by increasing the density of the red marks.\nMy shader here does essentially the same thing, forcing the image to black and white and controlling the density of black pixels to match the shades of light and dark in the original image.\nHave a look and see how it works! Feel free to zoom in to see the detail or save the generated image as a file!\n","date":"13 June 2024","externalUrl":null,"permalink":"/en/posts/dithering/","section":"Homepage","summary":"A quick glsl dithering shader live in browser","title":"Dithering shader","type":"en"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]