Interactive intro to shaders(mayerowitz.io) |
Interactive intro to shaders(mayerowitz.io) |
Honest blogging is dying in the shadows, unseen of the god almighty algorithm. It's sad times. We're deep in the Star Wars episode IV of the Internet.
My only feedback is, merci and more of it please!
If you want to see what the Masters can do with shaders, let me introduce you to Inigo Quilez and his shader art: https://www.youtube.com/watch?v=BFld4EBO2RE
EDIT: I did not notice you are the author of this article. It's very well done, and I've been looking for more approachable and interactive tutorials on the arts of shader coding.
IQ's personal page on Shadertoy: https://www.shadertoy.com/user/iq/sort=newest&from=576&num=8
Shadertoy is created by Beautypi (Inigo Quilez and Pol Jeremias).
And all of IQ's introductory articles on using Shadertoy.
// Input - Keyboard : https://www.shadertoy.com/view/lsXGzf
// Input - Microphone : https://www.shadertoy.com/view/llSGDh
// Input - Mouse : https://www.shadertoy.com/view/Mss3zH
// Input - Sound : https://www.shadertoy.com/view/Xds3Rr
// Input - SoundCloud : https://www.shadertoy.com/view/MsdGzn
// Input - Time : https://www.shadertoy.com/view/lsXGz8
// Input - TimeDelta : https://www.shadertoy.com/view/lsKGWV
// Inout - 3D Texture : https://www.shadertoy.com/view/4llcR4
Thanks a lot for making this, keep doing what you do!
I found a couple myself (@D_VISION7 @lv374 @beesandbombs @HAL09999), but ran into roadblocks trying to find more. There are a few fractals here and there, etc. Shadertoy seemed mostly like math demos/challenges rather than art, or at least it doesn't have an easy way to find the artsy ones.
I do find a lot of NFT pollution in these tags so I've got words related to that filtered out.
I follow some actual people too and you can find good follows from who they RT but following tags made mastodon much better for me than t**ter ever was, and actually makes it worthwhile posting with tags.
Personally I really like @KilledByAPixel and @piterpasma simply from a technical perspective. If you're into vector / plotting work @zancan is definitely worth checking out.
Also your website is very sleek, minimal and your projects are tasteful.
You might want to correct that. Now reading on :)
A shader is a pain in the ass that most programs and applications don't want.
3D stuff likes triangles and the GPUs are happy to slot into that abstraction. Shaders are useful to interpolate over those triangles.
Triangles are mostly garbage for everybody else. 2D rendering wants paths. Font rendering wants paths or pixmaps. GUI's would work much better with paths and pixmaps. Compositors really want pixmaps. Video decoders really want pixmaps and parallel rendering.
What everybody non-3D wants is rectangular pixmaps and access to computation directly to those pixmaps. And GPUs don't like this very much, and shaders don't map very well to this.
I feel compelled to link to his "happy bouncing" shader, which is phenomenal IMO: https://www.shadertoy.com/view/3lsSzf
(with associated 6 hour (!) youtube video on its creation)
That's a juicy ~500 lines of code (:
Anyways, well done, love the article.
Or do we have basic shapes like triangles, squares, circles, etc and the shaders go on top of it, drawing shadows, smoothing edges, etc?
From the example, it seems like you can create a shader to draw any object in a scene, and then I imagine you compose other shaders to get shadows and lightning and all of that. In the very limiting experience I had with drawing, I drew shapes but never through shaders. I always thought they didn't draw the objects themselves.
Link to imgur. The first image is screenshot of what I see in the browser. The rest are actual images after pasting to imgur.
Drawing a line on the CPU: a function looping through each pixel between point A and B and draw one pixel at a time sequentially. Runs once with exactly as many steps as there are pixels in the line.
Drawing a line on the GPU: a function checking if the pixel is on the line or not and draw if it's a match. Runs on all pixels on the screen at the same time, even ones that are far from the line.
Is this correct?
This really hits the sweet spot for me.
Would love to see tutorials on more topics. In particular, a very basic lighting model but with a detailed breakdown on how normals and dot product work together.
Yes, there's plenty of info out there, but I think your teaching style would still make it worthwhile
Normals point away from faces, straight out from faces. A surface normal is the direction the surface is facing. The dot product of 2 vectors measures how "aligned" they are, indeed, one definition of the dot product is defined in terms of the angle between the two vectors.
Also, remember that the vector opperation (A - B) results in a vector going from B to A. Thus (light_direction - face_center) is a vector from the face to the light, and (light_direction - face_center).dot(face_normal) gives us a numeric value that is higher when the face is pointing towards the light source.
You'll have to look to linear algebra for a deep understanding: https://youtu.be/LyGKycYT2v0?si=lWr38mH34yGRSJpv
I also recommend an informal (but rigorous enough) textbook called "Linear Algebra: Theory, Intuition, Code" which teaches through conversational written explanations, mathematical notation, and code. GPT4 is also quite competent at teaching linear algebra and, in my experience, is able to distinguish between correct and incorrect mathematical proofs; a good textbook is still a better primary source though.
I've always wanted to setup an interactive blog like this or Bartosz Ciechanowski's. Are you willing to describe your stack? I put something together with django & bootstrap once, but even that was more overhead than I liked.
This kind of constraint is so liberating for me for some reason. It just narrows the space a lot I guess.
I also kind of love not having imports and libraries as that also really simplifies things.
if you like this kind of a thing, erlang is a kind of thing you would like :o)
Edit: upon a reread, I don't really understand what your problem is with gpus. You can ignore the vertex processing pipeline entirely, drawing just a single fullscreen quad (or use a compute shader); the gpu will handle this with aplomb, and this is the sort of thing the linked article is talking about too.
As a devil's advocate: most programs don't necessarily require the GPU to achieve what they want. 3d stuff nigh requires such parallelism once you go past a very small scale.
That was the mindset in the 00's at least. Software has gotten more complex to the point where the GPU may be a desired optimization to make, but the GPU pipeline has always been strict and closed down, and the paradigms from single to multi-core programming often require different algorithms. GPGPU programming can liberate you from the need to work in triangles, but you still need to take a completely different approach if you want to enjoy that parallelism.
You can work around the limitations in vertex or fragment shaders, but, in many cases, you're having to unwind a lot of what the vertex and fragment processing does.
On of my favorite examples is drawing a screen/pixel-space rectangle and then drawing a border of the rectangle in a different color and specifying the number of pixels that border should be wide. Oof. You have to specify 4 triangles, make sure they don't overlap, specify them in a particular vertex order or annotate them with extra data so you only draw one of the three edges, make sure that you only draw each pixel just once or your blending goes all to crap, etc. Whereas, you can divide the rectangle into chunks, send each chunk through the compute pipeline, and it's stupidly straightforward.
Or, if you want simpler, just draw a line n pixels wide. If you don't have an explicit extension to do this, it's really a pain.
2D graphics simply wants very different graphic and computational primitives compared to 3D.
A GPU turns an abstract vector shape like a triangle, defined by three vertices and data such as a normal associated with each vertex, into a stream of fragments, one (or more if multisampling) for each pixel in the output buffer that’s covered by the shape. This part is all done in hardware.
A fragment is a pixel coordinate plus user-supplied data that’s either constant, called uniform, or the aforementioned vertex data interpolated across the triangle face, called varying. This interpolation business is again done in hardware and not programmable.
The fragment shader takes a fragment as input and based on the data computes a color, which is (after a couple more stages) output on the screen (or offscreen buffer) as the color of the respective pixel. This could be anything from a constant color to complex lighting calculations. In GPU rendering, this is all massively parallel, with countless fragments being processed simultaneously at any moment. Shaders are pure, stateless functions: the only data they can access is the input, and the only effect they can have is to return a color (and a few other things like a depth value).
So in a nutshell, the GPU hardware is responsible for computing which pixels should be filled to draw each triangle, but the fragment shader’s responsibility is to determine the color value of each of those pixels.
there are other stages (vertex, tesselation) that draw those basic shapes before the fragment shader draws "on top" of the scene.
(there is also a lot more to what I described for fragment shaders. e.g. deferred rendering[2]. But that's an equally large topic to get into).
1: https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipe...
2: https://learnopengl.com/Advanced-Lighting/Deferred-Shading
There is what is referred to as a graphics pipeline consisting of a mix of fixed-function hardware stages and programmable stages. At a high level, it does the following: 1) the GPU accepts a set of 3D triangles from the CPU, 2) a 'vertex' shader program transforms (flattens) the 3D triangle vertices into 2D triangle vertices with pixel coordinates, 3) the GPU rasterizes the 2D triangles to determine exactly which pixels the triangles cover, 4) a 'pixel' shader program is run for each covered pixel to determine the color of the pixel, 5) the resulting pixel color is stored in a frame buffer (which may involve blending it with the existing color). This 'pipeline' is then repeated many times (with different triangle meshes and shaders) until the whole frame is drawn.
Hope that helps!
For example, to get light and shadows, your shader should have access to some (probably global) variable about the position and direction of eg. a spotlight. Very often composite lighthing is achieved by combining multiple shader passes (a base pass for global ilumination, and one for each light for example), each literally adding more light (additive pass). Now, in order to avoid adding light for pixels where the light source is blocked (ie. shadow) the most common technique is using what's called a Z-buffer (just a floating point texture). You want to know for each light in the scene where their light reaches, so (before all lighting is applied) you set up a single shader pass that combines all solid geometry on the scene and using the light position and direction as the camera transform, and use a special shader whose only purpose is writing the objects distance to the Z-buffer. Now, every time you want to know whether a point in space is reached by your light, you go about sampling this Z-buffer (after doing some geometry) and compare the point's distance to the saved value in that direction. Yes, it can be very buggy and precision errors abound, and every engine worth their salt already does this for you, but lets you get in there and modify the process.
Everything else are variations on this theme. Deferred rendering is rendering data instead of colors into an intermediate texture which is later processed to get the colors. Blur effects are 2D convolutions of the rendertexture (eg by a Gaussing kernel). Tesseletion shaders are about generation new geometry in the vertext shader. Even drawing text is achieved through font atlasing and small rectangles.
So some talented artists are pushing the bounds and wrestling with performance trade-offs in the fragment shader.
Fragment shaders are more commonly used for making full screen filter effects (color correction, ect).
Shaders are also used to make textures and materials on basic objects. Material artists often generate textures with shader math.
Many visual effects are made by using shaders in creative ways.
Shaders are run on the GPU in a parallel, wave-like fashion. Many, many, many threads run across the same data in one wave.
In some cases shaders are much faster than CPU branching code. Shaders also have easier access to some rendering data.
So they are a good space for creative special effects.
Any object in a game with a high level of surface detail is a common target, to shift that detail onto a shader.
Ocean surfaces, tesselating meshes, ect.
There's many other uses, because GPUs are powerful and flexible.
Generally this - the SDF mechanism is very clever, but that's not what game engines tend to do, their geometry comes from triangle-based tools used by artists.
The fractional pixel mappings do get you - but I think I recall an integer mode somewhere in GLSL 3.2.
Your example steps outside the shader boundary, though. If you try to use triangles to draw pixmap, of course you'll have trouble. That's why pixel shaders are the right tool for the job.
The only disadvantage they have is that the pixmaps are immutable within a single pass, so you can't (easily) draw the border together with the inner rectangle in one pass. But if you're used to functional programming, you won't even notice this.
I agree that shaders are not the right thing when you need mutability, as you seem to, but working with 2D graphics doesn't necessarily require mutability.
Likely culprit?