[HN Gopher] Interactive intro to shaders
       ___________________________________________________________________
        
       Interactive intro to shaders
        
       Author : superMayo
       Score  : 482 points
       Date   : 2023-10-26 22:03 UTC (1 days ago)
        
 (HTM) web link (www.mayerowitz.io)
 (TXT) w3m dump (www.mayerowitz.io)
        
       | superMayo wrote:
       | I finally found the courage to write and expose myself to the
       | internet. I've always wanted to learn shaders so I thought it
       | would be nice to document my learning and share it with others.
        
         | djmips wrote:
         | Thank you!
        
         | keyle wrote:
         | I just commend you for writing on your own blog and making an
         | interactive post; as opposed to some shitty Notion/Medium/Dev
         | feed the google-algorithm, AI copy pasta.
         | 
         | 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!
        
         | bt4u wrote:
         | Don't be shy, I guarantee that I'm worse at shaders than you
         | are. Thanks for your effort
        
         | rendaw wrote:
         | Tangential to the main topic, what generative art artists are
         | you following and/or where did you look to find them?
         | 
         | 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.
        
           | solarwindy wrote:
           | Here's a few artists you might like: @FEELSxart
           | @ilithya_rocks @generativelight @thresfold @Tezumies
           | @_nonfigurativ_ @Olga_f2727
        
           | Bewelge wrote:
           | Can only recommend checking out https://www.fxhash.xyz/. It's
           | an NFT platform for generative art. Think of NFTs what you
           | will but that platform really brought together lots of
           | artists and in my opinion the field has advanced a lot these
           | past two years due to that platform existing.
           | 
           | 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.
        
           | bazzargh wrote:
           | On mastodon you can follow hashtags, so follow #generative,
           | #procedural, #creativecoding etc, not just people; you get a
           | lot of serendipitous finds this way. What shows up on the
           | different tags varies a bit: #generativeart picks up a lot
           | more AI-generated guff, not interesting to me at all, while
           | #generative tends to get less of that and more hand-coded
           | art.
           | 
           | 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.
        
         | pininja wrote:
         | This article is super fun. Thanks! I was curious if you've
         | learned WGSL, the shader language for WebGPU? I'm struggling to
         | wrap my head around what's similar and different.. a fun and
         | interactive guide like you've done here with GLSL would be
         | amazing.
        
           | Buttons840 wrote:
           | https://webgpufundamentals.org/ is only a few months old and
           | is the best WebGPU resource I've found.
        
         | Jasper_ wrote:
         | You can improve your antialiasing, assuming you're willing to
         | use the well-supported OES_standard_derivatives extension (or
         | WebGL 2). Instead of doing smoothstep(0.0f, 0.01f, dist); with
         | the constants picks sort of at random, instead do
         | smoothstep(fwidth(dist), -fwidth(dist), dist);
        
           | superMayo wrote:
           | Didn't know that, thanks for the tip!
        
         | pjmlp wrote:
         | Looks like a good way to introduce newbies to the world of
         | shaders, many thanks for putting this out, I already have a
         | recipient in mind for this content.
        
         | helloplanets wrote:
         | This feels like the absolute best way to give an introduction
         | to shaders, given that they're completely graphical. The way
         | the interactive code is embedded within the article, instead of
         | being a link to an excercise works super well.
         | 
         | Thanks a lot for making this, keep doing what you do!
        
         | Ennea wrote:
         | Impeccable timing, I was just about to try and get into
         | shaders. Thank you so much for writing this, I will be reading
         | it on the weekend.
        
         | sph wrote:
         | Welcome to the Internet, superMayo!
         | 
         | 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.
        
           | flakes wrote:
           | That was an amazing video, thanks for sharing! I really
           | appreciate that there's a link to the shader code as well.
           | Makes me want to dive into graphics again!
        
         | kossTKR wrote:
         | This is very cool, just what i needed actually.
         | 
         | Also your website is very sleek, minimal and your projects are
         | tasteful.
        
         | pjc50 wrote:
         | The Mona Lisa paint cannon is an incredible GIF, do you know
         | where that's from?
        
           | TheMaskedCoder wrote:
           | Looks like it's from a talk the MythBusters guys did at
           | NVISION 08: https://www.youtube.com/watch?v=aa3OGgBkRiQ
        
         | haspok wrote:
         | First sentence: "What if I told you that it could takeS..."
         | 
         | You might want to correct that. Now reading on :)
        
         | gensym wrote:
         | Nice work - this is really cool!
        
         | davanger wrote:
         | This post got me to log in to thank you for taking the time to
         | write this, and to congratulate you on how enlightening it is.
        
       | sva_ wrote:
       | Great intro, hope it will be continued. Too often these kind of
       | things start with a great intro and are then dropped.
        
       | danielvaughn wrote:
       | This is really nice. I'm a former-artist-turned-programmer and
       | every now and then I get the itch to dig into graphics
       | programming. I've written a couple very basic shaders, but once
       | it gets into the math (which is...very early on), I hit a
       | ceiling. I went to college for art rather than comp-sci, so my
       | math skills are virtually non-existent.
       | 
       | Anyways, well done, love the article.
        
       | Waterluvian wrote:
       | "their parallel nature makes them memoryless and stateless. This
       | translates to: "You can't store or share data between pixels or
       | shader executions.""
       | 
       | 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.
        
         | prox wrote:
         | I might be wrong but there are compute shaders that can retake
         | inputs and well, do more computing on them afaik.
        
           | nwoli wrote:
           | You can set up shaders to access a shared global texture (and
           | write to it). It's more expensive and a bit to set up so it's
           | not totally true, but it's a good first mental model
        
           | superMayo wrote:
           | Indeed it's a simplification! You can use buffers to keep
           | information from previous states or share state between
           | pixels (for both fragment and compute shaders). Though it can
           | greatly reduce the performance sometimes. For an intro
           | however, I think it's simpler to assume no memory nor shared
           | state.
        
         | signa11 wrote:
         | > This kind of constraint is so liberating for me for some
         | reason.
         | 
         | if you like this kind of a thing, erlang is a kind of thing you
         | would like :o)
        
       | antoineMoPa wrote:
       | Beautiful, thanks for posting!
        
       | kevindamm wrote:
       | Minor nitpick: you mention cel shading but spelled it cell -- the
       | term comes from cels used in hand-drawn animations and the
       | quantized tones used in shading there.
        
         | persnickety wrote:
         | Meta-nitpick: cel stands for celluloid:
         | 
         | https://en.wikipedia.org/wiki/Cel
        
         | superMayo wrote:
         | Fixed-it, thanks ;)
        
       | kwoff wrote:
       | Good job on a really good intro
        
       | interroboink wrote:
       | This is neat! I've gone down the SDF rabbit hole a bit, recently.
       | I'm glad you added some links to iq's site; he's got great stuff.
       | 
       | 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 (:
        
         | superMayo wrote:
         | Inigo Quilez is a wizard, the shader community owes so much to
         | him!
        
       | bsder wrote:
       | The article is quite nice. However, it glosses over the primary
       | problem with shaders.
       | 
       | 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.
        
         | persnickety wrote:
         | What part of a pixel/fragment shader does not map very well to
         | pixmaps?
        
           | bsder wrote:
           | Only interpolating based on the edge? Needing to carefully
           | map float coordinates so you don't wind up with fractional
           | pixels mapping incorrectly? Inability to look at pixels to
           | each side to compute something?
           | 
           | 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.
        
             | persnickety wrote:
             | Interpolation works on neighboring pixels though? Not sure
             | what you mean there. Looking at neighboring pixels of the
             | input pixmap is trivial as well.
             | 
             | 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.
        
         | moonchild wrote:
         | Having written a 2-d curve renderer, what I want is parallel
         | compute and high bandwidth; gpus deliver. (Especially with
         | newer interfaces that support scatter-write; not sure how much
         | penetration these have in the browser yet.) It's true this is
         | not what you want at a higher level, but it serves as a fine
         | base to implement higher-level abstractions. You could support
         | them in hardware, but it's not at all obvious what the
         | advantages would be; no one complains that cpus don't have
         | architectural support for for-loops.
         | 
         | 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.
        
         | johnnyanmac wrote:
         | >A shader is a pain in the ass that most programs and
         | applications don't want.
         | 
         | 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.
        
       | juliebelz wrote:
       | I've often tried to get interested in the subject, but could
       | never find an accessible gateway: I found it with this
       | introduction! Super fun and playful, thanks, can't wait to read
       | the rest!
        
       | dakom wrote:
       | Very nice, I've tried looking at a couple other tutorials in the
       | past but they always assume more prior knowledge than I have.
       | 
       | 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
        
         | mjburgess wrote:
         | As a general rule, the third article/book/tutorial you read is
         | the one where you "get it".
        
         | superMayo wrote:
         | That's really nice to hear and it gives motivation to continue!
         | Next article will probably go 3D, but it adds a lot of
         | difficulties. I still need to find out a good small-scale but
         | playful shader project to cover!
        
         | Buttons840 wrote:
         | > a detailed breakdown on how normals and dot product work
         | together
         | 
         | 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.
        
       | Kiro wrote:
       | This is my understanding of shaders:
       | 
       | 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?
        
         | NZGumboot wrote:
         | Not exactly. First, a GPU can't run a pixel shader against
         | EVERY pixel simultaneously. A typical screen might have about
         | two million pixels, while GPUs max out at a few thousand
         | concurrent 'threads of execution' i.e. it effectively draws in
         | chunks of a few thousand pixels at a time. Second, the GPU
         | doesn't need to run pixel shaders against a whole screen, it's
         | capable of running shaders against any arbitrary shape you like
         | using triangles. So the efficient way to draw a line is to send
         | the GPU two triangles that match the line geometry that you
         | want, and then only run the pixel shader on the pixels that the
         | triangles overlap. Much more efficient.
        
       | Draiken wrote:
       | I have never dealt with shaders, so pardon me if it's a very
       | basic question. In a single frame from a game, are shaders
       | essentially all that are being used to draw it?
       | 
       | 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.
        
         | NZGumboot wrote:
         | Yes, the color of every pixel is ultimately determined by a
         | shader program, but as you might expect it's more complicated
         | then that.
         | 
         | 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!
        
         | johnnyanmac wrote:
         | Shaders do all the drawing, but it does so in different stages.
         | I won't explain the entire graphics pipline[1], but a lot (some
         | 90%+) of what people casually think of as "shaders" for doing
         | lighting effects are the fragment/pixel shader stage of the
         | renderer.
         | 
         | 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
        
         | EmilioMartinez wrote:
         | Yes, you got the right idea. AFAIK every type of code running
         | on the GPU is called a shader (eg. special data operations are
         | even called "compute shaders", although they are a different
         | beast). All the operations you mentioned (colors, shadows,
         | shading, image-effects, general image-processing) are achieved
         | through parallelized computing combining lots of data arrays
         | (vertices and their properties, source textures, pre-computed
         | functions, target textures, buffers, etc).
         | 
         | 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.
        
         | pjc50 wrote:
         | > Or do we have basic shapes like triangles
         | 
         | 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.
        
         | Sharlin wrote:
         | (Note the following is a simplified description of the classic
         | forward rendering process; the so-called _deferred rendering_
         | techniqure is a bit different.)
         | 
         | 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.
        
       | habibur wrote:
       | In case anyone else was seeing the images as flickering noise, my
       | fix was to copy the image from the browser and paste those
       | somewhere else. You can view the images correctly.
       | 
       | Link to imgur. The first image is screenshot of what I see in the
       | browser. The rest are actual images after pasting to imgur.
       | 
       | https://imgur.com/a/F4203rz
        
         | superMayo wrote:
         | That's strange, what browser are you using?
        
           | habibur wrote:
           | Chrome / Fedora 38 64 bit / Wayland / Radeon Vega 3 graphics.
        
             | ShamelessC wrote:
             | > Wayland
             | 
             | Likely culprit?
        
               | habibur wrote:
               | Unlikely. This is what gets installed and activated by
               | default on Fedora.
        
       | tomelders wrote:
       | This is ridiculously serendipitous. I was looking for a tutorial
       | for this exact shader for a game I'm working on.
        
       | sroussey wrote:
       | From an efficiency point of view, would this be better or worse
       | than the css animation? Just curious.
        
       | strontian wrote:
       | wow this is delightful! Is there anything you can share about how
       | you made the page? Are the graphics in webgl? What did you use to
       | embed the code?
        
         | strontian wrote:
         | ah I see it is webgl and codemirror. But if you have anything
         | else to share about the making of the site would love to hear!
        
       ___________________________________________________________________
       (page generated 2023-10-27 23:01 UTC)