{"author_link":"\/users\/metriko","author_name":"MetRiko","author_uid":"metriko","comments":[],"epoch":1729278787,"event":"LD56","format":"md","ldjam_node_id":406207,"likes":15,"metadata":{"p_key":"209270","p_author":"MetRiko","p_authorkey":"1250849","p_urlkey":"447645","p_title":"Want to learn 2D shaping with shaders? ","p_cat":"LDJam ","p_event":"LD56","p_time":"1729278787","p_likes":"15","p_comments":"0","p_status":"WAYBACK","us_key":"1250849","us_name":"MetRiko","us_username":"metriko","event_start":"1728000000","event_key":"115","event_name":"Ludum Dare 56"},"node":{"_collation":{"body_sanitizer":"TextUtils::SanitizeHTML via existing importer","event":"LD56","removed_author":false},"_superparent":395186,"_trust":1,"author":250849,"body":"## We have good news for you!\nRecently in **[Blob, to The Top!](https:\/\/ldjam.com\/events\/ludum-dare\/56\/blob-to-the-top)** we added a subtle circular indicator that shows max power while charging a shoot. \nWe mentioned it in our latest update post (check it out **[here](https:\/\/ldjam.com\/events\/ludum-dare\/56\/blob-to-the-top\/blobs-are-back-with-yet-another-patch)**; yes it has gifs :D)\n\nThis indicator was made using shaders and here is a quick **step by step guide** how we made it.\n\nTo start.. create a new Godot **Sprite2D**, put there a default `icon.svg` texture and create a new shader material with a code (not a shader graph!). And now it's time for code! \n\n### 1. First we need a function to draw a simple circle:\n```c\nfloat circle(float radius, vec2 uv) {\n    float len = length(uv);\n    return len < radius ? 1.0 : 0.0;\n}\n```\nWe can replace ternary operator with built-in `step` function to simplify it, like that:\n```c\nfloat circle(float radius, vec2 uv) {\n    float len = length(uv);\n    return step(len, radius);\n}\n```\n\n### 2. Now let's make a `ring` function to draw a ring.\nThe plan is simple.. we will draw 2 circles and remove inner circle from outer circle. To simplify some math later, instead of just subtraction operation we will use multiplication instead.\n```c\nfloat ring(float radius, float width, vec2 uv) {\n    float outer_circle = circle(radius, uv);\n    float inner_circle = 1.0 - circle(radius - width, uv);\n    return outer_circle * inner_circle;\n}\n```\nWe can optimize it a little bit by using code from the `circle` function directly. We can also invert result of `step` function just by swapping arguments inside.\n\n```c\nfloat ring(float radius, float width, vec2 uv) {\n    float len = length(uv); \n    float outer_circle = step(len, radius);\n    float inner_circle = step(radius - width, len);\n    return outer_circle * inner_circle;\n}\n```\n\n**Time to test this `ring` function!**\n\n![frament_1.png](\/\/\/raw\/1e3\/d3\/z\/68f45.png)\n\nWell.. it works! But you can see it's not very practical. We have to use normalized values and ring isn't even centered.\nLet's fix that **by modifying `UV` in the `vertex` function**.\n\nAlso for the future use, we will copy value of `COLOR` variable from the `vertex` function. It's a **Godot specific feature** which allows for using value of **`modulate` property** (the one from the node) directly inside a shader code within the `fragment` function. To \"transport\" value from the `vertex` function to `fragment` function we will define a special `varying` variable.\n\nSo in the end our `vertex` function will look like this:\n```c\nvarying vec4 modulate;\n\nvoid vertex() {\n\t\/\/ Copy value of `modulate` property to new variable\n\tmodulate = COLOR;\n\n\t\/\/ Read a node scale from built-in matrix\n    vec2 node_scale = vec2(MODEL_MATRIX[0][0], MODEL_MATRIX[1][1]);\n\n    \/\/ Read texture size used in the Sprite2D node (in this case we used default 128x128 Godot icon)\n    vec2 tex_size = 1.0 \/ TEXTURE_PIXEL_SIZE; \/\/ = 128x128\n    \n    \/\/ Let's change UV from range 0..1 to -64..64.\n    UV = (UV * 2.0 - 1.0) * tex_size * node_scale * 0.5; \/\/0..1 -> -1..1 -> -64..64\n}\n```\n\nAnd now we can finally use pixels as units! And also shader reacts to `modulate` property and circle is finally centered.\n\n![frament_2.png](\/\/\/raw\/1e3\/d3\/z\/68f46.png)\n\n### 3. Now it's time to make dashed ring instead of solid one.\nFirst, to make a rotating dashed ring we will need few utility functions:\n\n* A. `get_angle` to get angle of the vector in range -PI to PI.\n* B. `rotate` which will return 2d transformation matrix to rotate any vector by specific angle.\n\n### 3A. Let's start with `get_angle` function.\nThe proper formula would be like this:\n```c\nfloat get_angle(vec2 vec) {\n    vec2 base = vec2(1.0, 0.0); \/\/ Base vector from which we will calculate angle to `vec`\n    float a = dot(vec, base);\n    float b = determinant(mat2(vec, base));\n    return atan(a, b);\n}\n```\nThis might look like a dark magic but let's simplify some things. We can replace `dot` and `determinant` built-in functions with just plane formulas:\n\n```c\nfloat get_angle(vec2 vec) {\n    vec2 base = vec2(1.0, 0.0);\n    float a = vec.x * base.x + vec.y * base.y;\n    float b = vec.x * base.y - vec.y * base.x; \n    return atan(a, b);\n}\n```\n\nIsn't it more clear now? But that's not the end. We can replace `base.x` and `base.y` with numbers and in the end we will get this simple function:\n\n```c\nfloat get_angle(vec2 vec) {\n    return atan(vec.x, -vec.y);\n}\n```\n\nNow when it's finally done..\n\n### 3B. Let's write a `rotate` function!\n\nThis will return a basic rotation matrix. The theory works like this: if we will multiply any vector by such matrix it will be rotated by a specific angle.. simple enough, right? :D \n\nThe final `rotate` function will look like this:\n```c\nmat2 rotate(float angle) {\n    float s = sin(angle);\n    float c = cos(angle);\n    return mat2(vec2(c,-s),vec2(s,c));\n}\n```\nNow it's the hardest part. \n### 4. Time to make holes in the ring to create a dashed ring.\nThe idea is simple:\n1. Split space into N*2 squared pizza fragments. \n2. Make only each odd fragment visible.\n3. Let's combine our ring with pizza.\n\n```c\nfloat pizza(float radius, int n, vec2 uv) {\n    float delta_angle = TAU \/ (float(n) * 2.0);\n    float angle = get_angle(uv);\n    float i = floor(angle \/ delta_angle);\n    return float(int(i) % 2);\n}\n\nfloat dashed_ring(float radius, float width, int n, vec2 uv) {\n    float pizza_shape = pizza(radius, n, uv);\n    float ring_shape = ring(radius, width, uv);\n    return ring_shape * pizza_shape;\n}\n```\nAnd now to add rotation we can just rotate `UV` by a specific angle using `rotate` function we already prepared.\n```c\nfloat dashed_ring(float radius, float width, int n, float rotation, vec2 uv) {\n\tuv *= rotate(rotation);\n    float pizza_shape = pizza(radius, n, uv);\n    float ring_shape = ring(radius, width, uv);\n    return ring_shape * pizza_shape;\n}\n```\n\nLet's test this thing! \n\n![fragment_3.gif](\/\/\/raw\/1e3\/d3\/z\/68f47.gif)\n\nWe are almost there. The only thing left is to add fading.\nWe did it by combining two fades like so:\n\n![fragment_4.png](\/\/\/raw\/1e3\/d3\/z\/68f48.png)\n\nAaand that's it!\n\nI hope someone will find this helpful :D \n\n### Now you can check out the final result in our game **[here](https:\/\/ldjam.com\/events\/ludum-dare\/56\/blob-to-the-top)**!\n\nIf you are interested more in our game, we have good news for you. \n\nAfter such an amazing and positive feedback (again, thank you sooo much for that :heart:), we have decided to expand the game and release it as a full-fledged title! If there is something you would like to see in the final game, **share your thoughts\/ideas in the feedback section or under this post**. We will see what we can do :grin: ","comments":0,"created":"2024-10-18T18:03:08Z","files":[],"files-timestamp":0,"id":406207,"love":15,"love-timestamp":"2024-10-20T12:26:03Z","meta":[],"modified":"2024-10-20T12:26:03Z","name":"Want to learn 2D shaping with shaders? ","node-timestamp":"2024-10-18T19:28:48Z","parent":400477,"parents":[1,5,9,395186,400477],"path":"\/events\/ludum-dare\/56\/blob-to-the-top\/want-to-learn-2d-shaping-with-shaders","published":"2024-10-18T19:13:07Z","scope":"public","slug":"want-to-learn-2d-shaping-with-shaders","subsubtype":"","subtype":"","type":"post","version":1277498},"node_metadata":{"n_key":"406207","n_urlkey":"447645","n_parent":"400477","n_path":"\/events\/ludum-dare\/56\/blob-to-the-top\/want-to-learn-2d-shaping-with-shaders","n_slug":"want-to-learn-2d-shaping-with-sh","n_type":"post","n_subtype":"","n_subsubtype":"","n_author":"250849","n_created":"1729274588","n_modified":"1729427163","n_version":"1277498","n_status":"WAYBACK"},"source_url":"https:\/\/ldjam.com\/events\/ludum-dare\/56\/blob-to-the-top\/want-to-learn-2d-shaping-with-shaders","text":"## We have good news for you!\nRecently in **[Blob, to The Top!](https:\/\/ldjam.com\/events\/ludum-dare\/56\/blob-to-the-top)** we added a subtle circular indicator that shows max power while charging a shoot. \nWe mentioned it in our latest update post (check it out **[here](https:\/\/ldjam.com\/events\/ludum-dare\/56\/blob-to-the-top\/blobs-are-back-with-yet-another-patch)**; yes it has gifs :D)\n\nThis indicator was made using shaders and here is a quick **step by step guide** how we made it.\n\nTo start.. create a new Godot **Sprite2D**, put there a default `icon.svg` texture and create a new shader material with a code (not a shader graph!). And now it's time for code! \n\n### 1. First we need a function to draw a simple circle:\n```c\nfloat circle(float radius, vec2 uv) {\n    float len = length(uv);\n    return len < radius ? 1.0 : 0.0;\n}\n```\nWe can replace ternary operator with built-in `step` function to simplify it, like that:\n```c\nfloat circle(float radius, vec2 uv) {\n    float len = length(uv);\n    return step(len, radius);\n}\n```\n\n### 2. Now let's make a `ring` function to draw a ring.\nThe plan is simple.. we will draw 2 circles and remove inner circle from outer circle. To simplify some math later, instead of just subtraction operation we will use multiplication instead.\n```c\nfloat ring(float radius, float width, vec2 uv) {\n    float outer_circle = circle(radius, uv);\n    float inner_circle = 1.0 - circle(radius - width, uv);\n    return outer_circle * inner_circle;\n}\n```\nWe can optimize it a little bit by using code from the `circle` function directly. We can also invert result of `step` function just by swapping arguments inside.\n\n```c\nfloat ring(float radius, float width, vec2 uv) {\n    float len = length(uv); \n    float outer_circle = step(len, radius);\n    float inner_circle = step(radius - width, len);\n    return outer_circle * inner_circle;\n}\n```\n\n**Time to test this `ring` function!**\n\n![frament_1.png](\/\/\/raw\/1e3\/d3\/z\/68f45.png)\n\nWell.. it works! But you can see it's not very practical. We have to use normalized values and ring isn't even centered.\nLet's fix that **by modifying `UV` in the `vertex` function**.\n\nAlso for the future use, we will copy value of `COLOR` variable from the `vertex` function. It's a **Godot specific feature** which allows for using value of **`modulate` property** (the one from the node) directly inside a shader code within the `fragment` function. To \"transport\" value from the `vertex` function to `fragment` function we will define a special `varying` variable.\n\nSo in the end our `vertex` function will look like this:\n```c\nvarying vec4 modulate;\n\nvoid vertex() {\n\t\/\/ Copy value of `modulate` property to new variable\n\tmodulate = COLOR;\n\n\t\/\/ Read a node scale from built-in matrix\n    vec2 node_scale = vec2(MODEL_MATRIX[0][0], MODEL_MATRIX[1][1]);\n\n    \/\/ Read texture size used in the Sprite2D node (in this case we used default 128x128 Godot icon)\n    vec2 tex_size = 1.0 \/ TEXTURE_PIXEL_SIZE; \/\/ = 128x128\n    \n    \/\/ Let's change UV from range 0..1 to -64..64.\n    UV = (UV * 2.0 - 1.0) * tex_size * node_scale * 0.5; \/\/0..1 -> -1..1 -> -64..64\n}\n```\n\nAnd now we can finally use pixels as units! And also shader reacts to `modulate` property and circle is finally centered.\n\n![frament_2.png](\/\/\/raw\/1e3\/d3\/z\/68f46.png)\n\n### 3. Now it's time to make dashed ring instead of solid one.\nFirst, to make a rotating dashed ring we will need few utility functions:\n\n* A. `get_angle` to get angle of the vector in range -PI to PI.\n* B. `rotate` which will return 2d transformation matrix to rotate any vector by specific angle.\n\n### 3A. Let's start with `get_angle` function.\nThe proper formula would be like this:\n```c\nfloat get_angle(vec2 vec) {\n    vec2 base = vec2(1.0, 0.0); \/\/ Base vector from which we will calculate angle to `vec`\n    float a = dot(vec, base);\n    float b = determinant(mat2(vec, base));\n    return atan(a, b);\n}\n```\nThis might look like a dark magic but let's simplify some things. We can replace `dot` and `determinant` built-in functions with just plane formulas:\n\n```c\nfloat get_angle(vec2 vec) {\n    vec2 base = vec2(1.0, 0.0);\n    float a = vec.x * base.x + vec.y * base.y;\n    float b = vec.x * base.y - vec.y * base.x; \n    return atan(a, b);\n}\n```\n\nIsn't it more clear now? But that's not the end. We can replace `base.x` and `base.y` with numbers and in the end we will get this simple function:\n\n```c\nfloat get_angle(vec2 vec) {\n    return atan(vec.x, -vec.y);\n}\n```\n\nNow when it's finally done..\n\n### 3B. Let's write a `rotate` function!\n\nThis will return a basic rotation matrix. The theory works like this: if we will multiply any vector by such matrix it will be rotated by a specific angle.. simple enough, right? :D \n\nThe final `rotate` function will look like this:\n```c\nmat2 rotate(float angle) {\n    float s = sin(angle);\n    float c = cos(angle);\n    return mat2(vec2(c,-s),vec2(s,c));\n}\n```\nNow it's the hardest part. \n### 4. Time to make holes in the ring to create a dashed ring.\nThe idea is simple:\n1. Split space into N*2 squared pizza fragments. \n2. Make only each odd fragment visible.\n3. Let's combine our ring with pizza.\n\n```c\nfloat pizza(float radius, int n, vec2 uv) {\n    float delta_angle = TAU \/ (float(n) * 2.0);\n    float angle = get_angle(uv);\n    float i = floor(angle \/ delta_angle);\n    return float(int(i) % 2);\n}\n\nfloat dashed_ring(float radius, float width, int n, vec2 uv) {\n    float pizza_shape = pizza(radius, n, uv);\n    float ring_shape = ring(radius, width, uv);\n    return ring_shape * pizza_shape;\n}\n```\nAnd now to add rotation we can just rotate `UV` by a specific angle using `rotate` function we already prepared.\n```c\nfloat dashed_ring(float radius, float width, int n, float rotation, vec2 uv) {\n\tuv *= rotate(rotation);\n    float pizza_shape = pizza(radius, n, uv);\n    float ring_shape = ring(radius, width, uv);\n    return ring_shape * pizza_shape;\n}\n```\n\nLet's test this thing! \n\n![fragment_3.gif](\/\/\/raw\/1e3\/d3\/z\/68f47.gif)\n\nWe are almost there. The only thing left is to add fading.\nWe did it by combining two fades like so:\n\n![fragment_4.png](\/\/\/raw\/1e3\/d3\/z\/68f48.png)\n\nAaand that's it!\n\nI hope someone will find this helpful :D \n\n### Now you can check out the final result in our game **[here](https:\/\/ldjam.com\/events\/ludum-dare\/56\/blob-to-the-top)**!\n\nIf you are interested more in our game, we have good news for you. \n\nAfter such an amazing and positive feedback (again, thank you sooo much for that :heart:), we have decided to expand the game and release it as a full-fledged title! If there is something you would like to see in the final game, **share your thoughts\/ideas in the feedback section or under this post**. We will see what we can do :grin: ","title":"Want to learn 2D shaping with shaders? ","wayback_source":[]}