├── LICENSE ├── README.md ├── explain_images └── outlines.gif ├── game ├── images │ └── girl.png └── outline_shader.rpy ├── outline_overview.md └── shader_overview.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Remix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Outline Shader 2 | 3 | #### All you need is the single file [outline_shader.rpy](https://github.com/RenpyRemix/outline-shader/blob/main/game/outline_shader.rpy). Just read through the comments, maybe delete the sample bits at the top, drop it in your game and use as advised. 4 | 5 | 6 | ![Image of Outline Shaders on a Sprite](explain_images/outlines.gif?raw=true "Thanks to: 7 | Pixabay for the girl image (link at end)") 8 | 9 | 10 | Using Ren'Py 7.4 shader to outline displayables. 11 | 12 | This is a pretty simple shader approach for adding an outline to any displayable. 13 | With the `mesh` boolean (set to True in the transform) it can work on LayeredImages, Composites and even Live2D models as well as normal images and text glyphs. 14 | It also allows setting a `mesh_pad` boolean which, if True, will pad out the surface adding extra room to draw the outline (so you do not have to create new images with extra transparency around them). 15 | 16 | It uses the GPU to do loads of calculations in parallel, one for each non opaque pixel and works out the distance to the nearest opaque pixel. Once it has that distance it can calculate the colour to draw the pixel by using a variety of settings. 17 | With all these being in parallel it only takes a fraction of the time that an approach like this would require if done on the CPU. This makes it pretty acceptable to use during run-time as it should not add lag spikes, especially if mostly used for smaller images such as buttons. 18 | 19 | The end of the rpy file has the base transform as well as a couple of example transforms that then use the base one inside them. 20 | You could use them when showing images (as in the example label) using the `at` keyword to dictate a transform or use the `At()` wrapper function when defining or declaring an image. 21 | ```py 22 | imagebutton: 23 | idle "images/button.png" 24 | hover At("images/button.png", coloured_outline) 25 | action NullAction() 26 | ``` 27 | Alternatively, you could even write transforms especially for the buttons and include event triggers within them such as `on hover:` that then used the shader. 28 | 29 | The notes in the rpy file should pretty much cover the basics of how the system is used. 30 | 31 | ### Navigation: 32 | 33 | [Shaders Overview](https://github.com/RenpyRemix/outline-shader/blob/main/shader_overview.md) 34 | (this briefly covers how shaders work internally as well as some hints for writing shader code in Ren'Py) 35 | 36 | [This Outline Shader](https://github.com/RenpyRemix/outline-shader/blob/main/outline_overview.md) 37 | (this goes into more depth to explain this shader in particular) 38 | 39 | 40 | [![Support me on Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/bePatron?u=19978585) 41 | 42 | 43 | Sprite used in example image: https://pixabay.com/vectors/girl-face-startled-portrait-312497/ 44 | 45 | ### Please note: 46 | 47 | The way that some parts of this approach work might not be suitable for complete beginners. As such it will likely require some knowledge of Ren'Py in order to extend it to your particular needs. 48 | 49 | Though I have tried to explain it as simply as possible, I will not be available to help extend it unless under a paid contract. 50 | Basically, if you want it to do more, you are expected to know enough Ren'Py to handle that yourself (or consider paying someone) 51 | -------------------------------------------------------------------------------- /explain_images/outlines.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RenpyRemix/outline-shader/c19868ae8e6f7c67893de724688ed0fd340ebde9/explain_images/outlines.gif -------------------------------------------------------------------------------- /game/images/girl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RenpyRemix/outline-shader/c19868ae8e6f7c67893de724688ed0fd340ebde9/game/images/girl.png -------------------------------------------------------------------------------- /game/outline_shader.rpy: -------------------------------------------------------------------------------- 1 | ## To view example in your game add 2 | ## 3 | ## call outline_example 4 | ## 5 | ## somewhere in your running label script 6 | 7 | 8 | 9 | ########################################### 10 | # # 11 | # To use in your game # 12 | # # 13 | # Make sure the Python stuff and the # 14 | # transform are available (basically, # 15 | # just do not delete them) # 16 | # Then just do similar to the example # 17 | # usage below # 18 | # # 19 | ########################################### 20 | 21 | # You may need to allow gl2 to operate by setting the config 22 | define config.gl2 = True 23 | 24 | label outline_example: 25 | 26 | scene expression "#456" 27 | 28 | ### girl.png is https://pixabay.com/vectors/girl-face-startled-portrait-312497/ 29 | 30 | show girl at outline(): 31 | xcenter 0.38 32 | ycenter 0.4 33 | pause 1.0 34 | 35 | show girl as girl2 at coloured_outline(): 36 | xcenter 0.5 37 | ycenter 0.4 38 | pause 1.0 39 | 40 | show girl as girl3 at animated_outline(): 41 | xcenter 0.64 42 | ycenter 0.4 43 | pause 44 | 45 | hide girl 46 | hide girl2 47 | hide girl3 48 | 49 | return 50 | 51 | 52 | init python: 53 | 54 | renpy.register_shader("remix.smoothstep_outline", 55 | variables=""" 56 | uniform vec2 u_model_size; 57 | uniform float u_lod_bias; 58 | uniform sampler2D tex0; 59 | varying vec2 v_tex_coord; 60 | 61 | uniform float u_threshold; 62 | uniform float u_width; 63 | uniform float u_step_start; 64 | uniform float u_step_end; 65 | uniform vec4 u_color; 66 | uniform vec4 u_far_color; 67 | uniform vec4 u_low_color; 68 | uniform float u_low_color_fade; 69 | uniform float u_mesh_pad; 70 | """, 71 | vertex_300=""" 72 | """, 73 | fragment_functions=""" 74 | # line 74 "outline_shader.rpy " 75 | 76 | // given a 0.0 to 1.0 distance, return alpha based on smoothstep values 77 | float get_step_alpha(float d, float s, float e) 78 | { 79 | return smoothstep(s, s + 1.0 - e, d) * (1.0 - smoothstep(e, 1.0, d)); 80 | } 81 | 82 | bool find_opaque(float x, float y, vec2 pos, vec2 pxo, float lod, sampler2D tex0, float threshold) { 83 | // x,y 84 | if (texture2D(tex0, clamp(pos + pxo, 0.0, 1.0), lod).a >= threshold) { 85 | return true; 86 | } 87 | 88 | // -x, y (if x is not 0) 89 | if (x > 0.1) { 90 | if (texture2D(tex0, clamp(pos + vec2(-pxo.x, pxo.y), 0.0, 1.0), lod).a >= threshold) { 91 | return true; 92 | } 93 | } 94 | 95 | // x, -y (if y is not 0) 96 | if (y > 0.1) { 97 | if (texture2D(tex0, clamp(pos + vec2(pxo.x, -pxo.y), 0.0, 1.0), lod).a >= threshold) { 98 | return true; 99 | } 100 | 101 | // -x, -y (if both x and y not 0) 102 | if (x > 0.1) { 103 | if (texture2D(tex0, clamp(pos - pxo, 0.0, 1.0), lod).a >= threshold) { 104 | return true; 105 | } 106 | } 107 | } 108 | return false; 109 | } 110 | 111 | float opaque_distance(vec2 pos, float lod, sampler2D tex0, float threshold, 112 | float u_width, vec2 pixel_size, float max_dist) { 113 | float nearest = 0.0; 114 | for (float x = 1.0; x <= u_width; x += 1.0) { 115 | float x_dist = pow(x, 2); 116 | for (float y = 0.0; y <= x; y += 1.0) { 117 | float d = pow(y, 2) + x_dist; 118 | if (nearest > 0.1) max_dist = nearest; 119 | if (d > max_dist) break; 120 | 121 | vec2 pxo = vec2(x, y) * pixel_size; 122 | // best of the four pixels (-x, y), (x, y), (-x, -y), (x, -y) 123 | if (find_opaque(x, y, pos, pxo, lod, tex0, threshold)) { 124 | nearest = d; 125 | } 126 | 127 | if (x > y) { 128 | vec2 pyo = vec2(y, x) * pixel_size; 129 | // best of the four pixels (-y, x), (y, x), (-y, -x), (y, -x) 130 | if (find_opaque(y, x, pos, pyo, lod, tex0, threshold)) { 131 | nearest = d; 132 | } 133 | } 134 | } 135 | } 136 | return nearest; 137 | } 138 | """, 139 | fragment_300=""" 140 | # line 140 "outline_shader.rpy " 141 | 142 | vec2 padded_size = u_model_size; 143 | if (u_mesh_pad > 0.5) padded_size += vec2(u_width) * 2; 144 | 145 | vec2 pos = v_tex_coord.xy; 146 | vec2 pixel_size = (vec2(1.) / padded_size); 147 | 148 | float max_dist = pow(u_width, 2); 149 | 150 | // only want outlines where the image is part or fully transparent 151 | if (gl_FragColor.a < 0.98) { 152 | // the square distance of nearest threshold alpha pixel 153 | float near = opaque_distance(pos, u_lod_bias, tex0, u_threshold, 154 | u_width, pixel_size, max_dist); 155 | if (near > 0.1) { 156 | // Now we can do the sqrt 157 | float dist = sqrt(near) / u_width; 158 | float color_dist = (dist - u_step_start) / (u_step_end - u_step_start); 159 | vec4 color = mix(u_color, u_far_color, color_dist); 160 | float alpha = get_step_alpha(dist, u_step_start, u_step_end); 161 | if (u_low_color != u_color && alpha < 0.99) { 162 | color = (u_low_color_fade > 0.5) ? 163 | mix(u_low_color, color, clamp(alpha * alpha, 0.0, 1.0)) : 164 | u_low_color; 165 | } 166 | alpha *= u_color.a; 167 | // this pixel should be altered 168 | if (gl_FragColor.a > 0.05 && alpha > 0.05) { 169 | // some edge pixel that has some opacity 170 | gl_FragColor = vec4( 171 | mix(color.rgb, 172 | gl_FragColor.rgb, 173 | gl_FragColor.a / (gl_FragColor.a + alpha)), 174 | 1.0) * (1.0, 1.0, 1.0, max(alpha, gl_FragColor.a)); 175 | } else { 176 | gl_FragColor = vec4(color * (1.0, 1.0, 1.0, alpha)); 177 | } 178 | } 179 | } 180 | """) 181 | 182 | ## This is the base transform 183 | ## You could either use this directly and pass in parameters as wanted 184 | ## or make transforms (with set parameters) to then call this (like below) 185 | # 186 | ## width: the width of the outline area 187 | ## threshold: the minimum alpha level to count as opaque when finding 188 | ## the nearest opaque pixel 189 | ## color: the primary colour of the outline 190 | ## far_color: secondary colour 191 | ## if set, this is the colour furthest from the image 192 | ## low_color: secondary colour 193 | ## if set, this is the colour through the inner/outer steps 194 | ## low_color_fade: whether to fade the primary colour into the low_color 195 | ## step_start: proportion of the width to begin a smooth up-step at 196 | ## (if this plus (1.0 - step_end) is less than 0.0 then no inner 197 | ## step will be shown) 198 | ## step_end: proportion of the width at which to begin a smooth step-down 199 | ## which will appear as a form of anti-aliasing 200 | ## mesh_pad: whether to pad out the image to make extra room for the outline 201 | transform outline( 202 | width=10.0, 203 | threshold=0.95, 204 | color="#FFF", 205 | far_color=None, 206 | low_color=None, 207 | low_color_fade=True, 208 | step_start=-0.25, 209 | step_end=0.75, 210 | mesh_pad=True): 211 | mesh True 212 | mesh_pad (False if not mesh_pad else (int(width),) * 4) 213 | shader "remix.smoothstep_outline" 214 | u_width float(width) 215 | u_threshold float(threshold) 216 | u_step_start float(step_start) 217 | u_step_end min(1.0, max(0.0, float(step_end))) 218 | u_color Color(color).rgba 219 | u_far_color (Color(far_color).rgba if far_color else Color(color).rgba) 220 | u_low_color (Color(low_color).rgba if low_color else Color(color).rgba) 221 | u_low_color_fade (1.0 if low_color_fade else 0.0) 222 | u_mesh_pad (1.0 if mesh_pad else 0.0) 223 | 224 | ## This one just uses other colour values 225 | transform coloured_outline(): 226 | outline( 227 | width=25.0, 228 | color="#0EC04D", 229 | far_color="#074621", 230 | low_color="#FFF", 231 | low_color_fade=False, 232 | step_start=0.1, 233 | step_end=0.7, 234 | mesh_pad=True) 235 | 236 | ## A little bit of animation on the width and step distances 237 | transform animated_outline(): 238 | outline( 239 | width=15.0, 240 | color="#FFF", 241 | step_start=0.4, 242 | step_end=0.8) 243 | linear 1.5 u_width 0.0 u_step_start -0.2 244 | linear 1.5 u_width 15.0 u_step_start 0.4 245 | repeat 246 | -------------------------------------------------------------------------------- /outline_overview.md: -------------------------------------------------------------------------------- 1 | # This Outline Shader in Depth 2 | 3 | So, this will be a rather vague `in Depth` overview because: 4 | 1. Those who are using GLSL can probably suss out what is happening there. 5 | 1. The main useful stuff is just the variables that are passed in to the transform. 6 | 1. I want to move on to new things rather than explain this. 7 | 8 | The Shader part of the code basically iterates outward from each transparent pixel to find the nearest threshold opaque one. It then uses the distance, along with the passed in variables, to calculate a colour and opacity to draw the pixel. The passed in values allow the outline to have an antialias effect, use different colours for set parts and have defined curve boundaries. 9 | 10 | The parameters for the transform are briefly explained as a comment block in the code... 11 | 12 | ```py 13 | 14 | ## This is the base transform 15 | ## You could either use this directly and pass in parameters as wanted 16 | ## or make transforms (with set parameters) to then call this (like below) 17 | # 18 | ## width: the width of the outline area 19 | ## threshold: the minimum alpha level to count as opaque when finding 20 | ## the nearest opaque pixel 21 | ## color: the primary colour of the outline 22 | ## far_color: secondary colour 23 | ## if set, this is the colour furthest from the image 24 | ## low_color: secondary colour 25 | ## if set, this is the colour through the inner/outer steps 26 | ## low_color_fade: whether to fade the primary colour into the low_color 27 | ## step_start: proportion of the width to begin a smooth up-step at 28 | ## (if this plus (1.0 - step_end) is less than 0.0 then no inner 29 | ## step will be shown) 30 | ## step_end: proportion of the width at which to begin a smooth step-down 31 | ## which will appear as a form of anti-aliasing 32 | ## mesh_pad: whether to pad out the image to make extra room for the outline 33 | transform outline( 34 | width=10.0, 35 | threshold=0.95, 36 | color="#FFF", 37 | far_color=None, 38 | low_color=None, 39 | low_color_fade=True, 40 | step_start=-0.25, 41 | step_end=0.75, 42 | mesh_pad=True): 43 | mesh True 44 | mesh_pad (False if not mesh_pad else (int(width),) * 4) 45 | shader "remix.smoothstep_outline" 46 | u_width float(width) 47 | u_threshold float(threshold) 48 | u_step_start float(step_start) 49 | u_step_end min(1.0, max(0.0, float(step_end))) 50 | u_color Color(color).rgba 51 | u_far_color (Color(far_color).rgba if far_color else Color(color).rgba) 52 | u_low_color (Color(low_color).rgba if low_color else Color(color).rgba) 53 | u_low_color_fade (1.0 if low_color_fade else 0.0) 54 | u_mesh_pad (1.0 if mesh_pad else 0.0) 55 | ``` 56 | Some of these are self-explanatory to some level. I will expand on the comment descriptions anyway. 57 | 58 | #### `width` - the width of the outline area 59 | As the name implies, this is the pixel width of the outline area. 60 | 61 | #### `threshold` - the minimum alpha level to count as opaque when finding the nearest opaque pixel 62 | When calculating the distance to nearest opaque pixel the system uses this value to determine if a found pixel is suitable. This would allow outlines on images that themselves were lower alpha (by setting this value to match). It also makes the internal logic easier as it only looks for ones above a certain level rather than trying to calculate whether opacity 0.88 at 11px distance is better or worse than 0.96 at 12px... 63 | Just set it to something a bit below the alpha level of the main image is best. 64 | 65 | #### `color` - the primary colour of the outline 66 | As the comment says, this is the outline colour. 67 | 68 | #### `far_color` - secondary colour - if set, this is the colour furthest from the image 69 | With this set (to something different than `color`) the outline will use a gradient between the two colours, with the `color` colour being closest to the image. 70 | 71 | #### `low_color` - secondary colour - if set, this is the colour through the inner/outer steps 72 | With this set (to something different than `color`) the outline will use a different colour for the inner and outer edges where the step-up/down anti-aliasing occurs. 73 | 74 | #### `low_color_fade` - whether to fade the primary colour into the low_color 75 | This True/False boolean controls how immediate the transition is between `color` and `low_color`. 76 | If you end up using these `low_` settings you might do best to tweak the actual shader itself and create a level that suits you best (as the setting is a bit clunky being just `on/off`) 77 | 78 | #### `step_end` - proportion of the width at which to begin a smooth step-down which will appear as a form of anti-aliasing 79 | To add a form of edge anti-aliasing, this value controls the point when smooth Hermite interpolation is used to reduce the alpha level of the outer edge of the outline. Pixels at a distance greater than this (as a 0.0 to 1.0 proportion of width) are reduced in opacity by using a smooth `S` curve. 80 | 81 | #### `step_start` - proportion of the width to begin a smooth up-step at (if this plus (1.0 - step_end) is less than 0.0 then no inner step will be shown) 82 | Taking the width of the `step_end` value (if step_end was 0.8 it would have a width of 0.2 as it runs from itself up to 1.0) this setting controls where to place a mirrored step-up at for the inner edge of the outline. This allows outlines to start a little away from the image edge if desired. 83 | 84 | #### `mesh_pad` - whether to pad out the image to make extra room for the outline 85 | Utility setting so you do not have to add transparent borders to images (to make room for the added outline). 86 | Note: Positional settings for the image will Not take the extra padding into account (if you set the left edge of the image at 20px and use a 15px outline the left edge of that outline would end up being at 5px) 87 | 88 | ### Navigation: 89 | 90 | Back to the main page [Home](README.md) 91 | 92 | [Shaders Overview](https://github.com/RenpyRemix/outline-shader/blob/main/shader_overview.md) 93 | (this briefly covers how shaders work internally as well as some hints for writing shader code in Ren'Py) 94 | -------------------------------------------------------------------------------- /shader_overview.md: -------------------------------------------------------------------------------- 1 | # Outline Shader - Shaders Overview 2 | 3 | If you have not encountered shaders in Ren'Py yet, there is documentation [Here](https://www.renpy.org/doc/html/model.html). 4 | This new features allows creation of GLSL code that is passed to the GPU to act upon a displayable. The main way of using them is to declare a shader by name within a normal transform. 5 | 6 | Creating your own shader is done through the `renpy.register_shader()` function which basically has parameters that can be used to pass in blocks of text... 7 | 8 | ```py 9 | init python: 10 | 11 | renpy.register_shader("remix.smoothstep_outline", 12 | variables=""" 13 | // this section is to set up variable names 14 | """, 15 | ## not using vertex code on this one 16 | vertex_300=""" 17 | """, 18 | fragment_functions=""" 19 | # line 74 "outline_shader.rpy " 20 | 21 | // this section can set up functions to use in the fragment section(s) 22 | """, 23 | fragment_300=""" 24 | # line 140 "outline_shader.rpy " 25 | 26 | // the main fragment shader section (the _300 part is generally what you would use to indicate main) 27 | """) 28 | ``` 29 | Just blocks of text... variables, then vertex stuff, then fragment stuff... All just text, written in GLSL (which is similar to C, not Python) 30 | 31 | ### Useful Little Trick 32 | Did you spot the 33 | ```py 34 | 35 | fragment_functions=""" 36 | # line 74 "outline_shader.rpy " 37 | ``` 38 | bit? 39 | Adding a line like that is really helpful for debugging as it can allow any reported error to also indicate the filename and line. Obviously you do have to write the actual line number there... Then if an error occurs 10 lines further down it would say `Error (outline_shader.rpy 84)` or somesuch, pointing you to where you need look. 40 | 41 | 42 | ## The Process (a simplified example) 43 | #### (Note: this does not reflect the approach used for the Outline Shader) 44 | 45 | In order to maintain some structure here, I will detail the standard process in a chronological order (that does not reflect the outline shader file). 46 | 47 | So, let's start with a displayable shown with a transform: 48 | ```py 49 | label x: 50 | show eileen at outline 51 | pause 52 | ``` 53 | All very straight-forward, so next our simplified transform: 54 | ```py 55 | transform outline(width=3.0, color="#FFF"): 56 | mesh True 57 | shader "remix.simple_shadow_example" 58 | u_width float(width) 59 | u_color Color(color).rgba 60 | ``` 61 | As you can see, it is very similar to normal transforms. We give it a name and pass in a couple of parameters by name with default values. 62 | The differences start once we are inside... 63 | Firstly we have `mesh True` which at a basic level flattens the displayable so our shader can work on it as a whole rather than running once per part of it. 64 | Then we tell it the shader to use with `shader "remix.simple_shadow_example"`. Convention in shader naming is to use prefix dot name. 65 | The next two lines both declare a variable to use inside the shader. Here we use the `u_` prefix to denote a `uniform` value. The values of each are coerced to a format usable inside a shader, float for the width (though it could easily be int) and a four part float for colour using the `Color().rgba` property. 66 | 67 | Those variables with the `u_` prefix are now passed into the shader defined as shown at the top... 68 | ```py 69 | init python: 70 | 71 | renpy.register_shader("remix.simple_shadow_example", 72 | variables=""" 73 | uniform float u_width; 74 | uniform vec4 u_color; 75 | ``` 76 | That names the shader and scopes the two uniforms for use inside other parts... 77 | 78 | There are also quite a few inbuilt variales that can be accessed within shaders. These cover things such as the current position, textures and texture sizes and others. These are detailed in the Ren'Py documentation. 79 | 80 | ... in that section we also declare the inbuilt variables we want to use... 81 | ```py 82 | uniform vec2 u_model_size; 83 | uniform sampler2D tex0; 84 | varying vec2 v_tex_coord; 85 | """, 86 | ``` 87 | 88 | Within the vertex or fragment sections we would now have access to those variables... 89 | ```py 90 | fragment_300=""" 91 | vec2 pixel_size = (vec2(1.) / u_model_size); 92 | vec2 offset_pos = v_tex_coord.xy - (vec2(u_width) * pixel_size); 93 | 94 | if (gl_FragColor.a < 0.98) { 95 | if (texture2D(tex0, offset_pos).a >= 0.02) { 96 | gl_FragColor = u_color; 97 | } 98 | } 99 | """) 100 | ``` 101 | (not really an example you would want to use in a game) 102 | 103 | Almost everything within a shader is based upon a float value between 0.0 and 1.0. 104 | This includes colours (0.2, 0.6, 0.8, 1.0) rgba values rather than say "#3399CCFF" and positions (0.5, 0.5) rather than pixel (100, 100). 105 | 106 | Our horrible little example script there wants to add a dropshadow of size `u_width` drawn in colour `u_color`. 107 | As we passed in the width as a pixel value we first want to determine what each pixel is as a float of the full image size. So we just divide 1.0 by how many pixels wide and divide 1.0 by how tall the image is. 108 | Now we can multiply those by the passed in width to get an offset (say (0.014, 0.018)) and minus that from the position of the current pixel. 109 | 110 | The fragment shader here is effectively running once for each pixel in the image, just is it doing loads of those similar calculations at the same time in parallel. 111 | Therefore, when we use `v_tex_coord` we are getting the position of just the pixel we are working on. Our line calculating offset_pos is taking that position and moving `width` pixels left and `width` pixels upwards, calculated as two floats relative to the texture. 112 | 113 | The conditional block after that is then doing: 114 | ```py 115 | if the current pixel is not opaque: 116 | test the alpha level of the pixel up-and-left from current pixel 117 | if that offset pixel is opaque: 118 | recolour the current pixel to be the colour we set 119 | ``` 120 | Very basic, rather simple and not at all like the approach used in the Outline Shader. 121 | 122 | There are a huge number of resources online for learning about GLSL. 123 | This page is just to give a few pointers about how they are done within Ren'Py. 124 | 125 | 126 | ### Navigation: 127 | 128 | Back to the main page [Home](README.md) 129 | 130 | [This Outline Shader](https://github.com/RenpyRemix/outline-shader/blob/main/outline_overview.md) 131 | (this goes into more depth to explain this shader in particular) 132 | --------------------------------------------------------------------------------