Some Custom Shaders

rob1221

  • *
  • Posts: 9473
I've started getting into shaders recently and over time I'll be sharing some of the ones that I make.  For each one I'll include the Stencyl code and a global custom block that lets you easily use the shader and also sets some default values.  Some shaders will also have code for Shadertoy included.  Shadertoy is what I've been using to create my shaders and then I port them to Stencyl.

To use the Shadertoy versions of the shaders, go to https://www.shadertoy.com/new and paste the code there.  Also select an image by clicking on iChannel0 on the bottom of the screen.

To use the Stencyl code (if not using the custom block), paste it into the "shader from code" block and set your shader attribute to it.  Then set all of the shader properties which you must do before using as I can't set default values from within the shader.  For more details: http://www.stencyl.com/help/view/shaders/

To use the global custom block, just import the attached scene behavior and apply the shader using the custom block.  You don't need to modify the behavior or attach it to any scenes.  The shader is automatically created when using the block and default values are set.

« Last Edit: January 28, 2019, 03:04:27 pm by rob1221 »

rob1221

  • *
  • Posts: 9473
The first shader I have to share is a simple circle light shader.
https://gfycat.com/EsteemedRemarkableGreathornedowl

Here are the properties of the shader that you can set:
enabled (bool) -- This is used to control whether most of the shader code is run.  It's useful if you want to disable the shader without needing to reapply shaders.  If not using the custom block it defaults to false so you'll need to set it to true first.
centerX (float) -- This is the X center of the light circle.
centerY (float) -- This is the Y center of the light circle.
minDarkAmount (float) -- This is how dark the screen becomes just outside of the light radius (range: 0-1)
maxDarkAmount (float) -- This is the maximum darkness of the screen and is reached at the dark radius (range: 0-1)
lightRadius (float) -- This is the inner radius of the light that has no darkness at all.
darkRadius (float) -- This is the outer radius of the light at which point the max darkness is reached.
red (float) -- This is the red value of the light (range: 0-1)
green (float) -- This is the green value of the light (range: 0-1)
blue (float) -- This is the blue value of the light (range: 0-1)

Shadertoy Code
Code: [Select]
bool enabled = true;
float minDarkAmount = 0.4;
float maxDarkAmount = 0.8;
float lightRadius = 0.2;
float darkRadius = 0.5;
float red = 1.0;
float green = 1.0;
float blue = 1.0;

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{   
    float centerX = iMouse.x / iResolution.x;
float centerY = iMouse.y / iResolution.y;
   
    vec2 uv = vec2(fragCoord.xy / iResolution.xy);

if (!enabled)
{
fragColor = texture(iChannel0, uv);
return;
}
   
    vec2 res = iResolution.xy;
    float lightRadius2 = lightRadius * (res.x + res.y) * 0.5;
    float darkRadius2 = darkRadius * (res.x + res.y) * 0.5;
   
float xDiff = (centerX - uv.x) * res.x;
float yDiff = (centerY - uv.y) * res.y;
float dist = sqrt((xDiff * xDiff) + (yDiff * yDiff));
    float darkRange = maxDarkAmount - minDarkAmount;
    float darkAmount = minDarkAmount + min(darkRange * ((dist - lightRadius2) / (darkRadius2 - lightRadius2)), darkRange);
vec3 darkness = vec3(texture(iChannel0, uv).rgb * (1.0 - darkAmount));

if (dist < lightRadius2)
{
        vec3 colorMod = vec3(red, green, blue);
    fragColor = vec4(texture(iChannel0, uv).rgb * colorMod, 1.0);
}
else
    {
        if (red == 1.0 && green == 1.0 && blue == 1.0)
        {
            fragColor = vec4(darkness, 1.0);
        }
        else
        {
float darkRatio = (darkAmount - minDarkAmount) / darkRange;
    float newRed = red + ((1.0 - red) * darkRatio);
    float newGreen = green + ((1.0 - green) * darkRatio);
    float newBlue = blue + ((1.0 - blue) * darkRatio);
        vec3 colorMod = vec3(newRed, newGreen, newBlue);
        fragColor = vec4(darkness * colorMod, 1.0);
        }
    }
}

Stencyl Code
Code: [Select]
#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vTexCoord;
uniform vec2 uResolution;
uniform vec2 uResolutionUs;
uniform sampler2D uImage0;

uniform bool enabled;
uniform float centerX;
uniform float centerY;
uniform float minDarkAmount;
uniform float maxDarkAmount;
uniform float lightRadius;
uniform float darkRadius;
uniform float red;
uniform float green;
uniform float blue;

void main()
{   
vec2 uv = vTexCoord;
vec4 tex = texture2D(uImage0, uv);

if (!enabled)
{
gl_FragColor = tex;
return;
}
   
vec2 res = uResolution.xy;
float scale = res.x / uResolutionUs.x;
float xDiff = (centerX / uResolutionUs.x - uv.x) * res.x;
float yDiff = ((1.0 - centerY / uResolutionUs.y) - uv.y) * res.y;
float dist = sqrt((xDiff * xDiff) + (yDiff * yDiff));
float darkRange = maxDarkAmount - minDarkAmount;
float lightRad = lightRadius * scale;
float darkRad = darkRadius * scale;
float darkAmount = minDarkAmount + min(darkRange * ((dist - lightRad) / (darkRad - lightRad)), darkRange);
vec3 darkness = vec3(tex.rgb * (1.0 - darkAmount));

if (dist < lightRad)
{
vec3 colorMod = vec3(red, green, blue);
gl_FragColor = vec4(tex.rgb * colorMod, 1.0);
}
else
{
if (red == 1.0 && green == 1.0 && blue == 1.0)
{
gl_FragColor = vec4(darkness, 1.0);
}
else
{
float darkRatio = (darkAmount - minDarkAmount) / darkRange;
float newRed = red + ((1.0 - red) * darkRatio);
float newGreen = green + ((1.0 - green) * darkRatio);
float newBlue = blue + ((1.0 - blue) * darkRatio);
vec3 colorMod = vec3(newRed, newGreen, newBlue);
gl_FragColor = vec4(darkness * colorMod, 1.0);
}
}
}

« Last Edit: January 26, 2019, 06:18:36 pm by rob1221 »

rob1221

  • *
  • Posts: 9473
This shader wipes the RGB channels off or back on the screen from any direction.  It could be used with scene transitions.
https://gfycat.com/ShamelessSeparateIsopod

Here are the properties of the shader that you can set:
enabled (bool) -- This is used to control whether most of the shader code is run.  It's useful if you want to disable the shader without needing to reapply shaders.  If not using the custom block it defaults to false so you'll need to set it to true first.
counter (float) -- This controls the current position of the shader and it must be 0 or higher.  Set this under the always/updating event or in a timer.  Increase to add color channels and decrease to remove color channels.  The example GIF changes the counter by + or - 20 in the always/updating event.
direction (float) -- This is the direction (in degrees) that the shader will move when adding color channels.  The direction is reversed when removing color channels. (range: 0-360)

Shadertoy Code
Code: [Select]
bool enabled = true;
float counter = 0.0;
float direction = 0.0;

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord.xy / iResolution.xy;
    vec4 tex = texture(iChannel0, uv);
   
if (!enabled)
{
fragColor = tex;
return;
}

    vec2 res = vec2(iResolution.xy);
    float rad = radians(direction);
    float s = sin(rad + radians(180.0));
    float c = cos(rad);
    float start = res.x * abs(c) * 3.0 + res.y * abs(s) * 3.0;
   
    if (false) // for testing: true = transition out and false = transition in
    {
        counter = start - iTime * 500.0;
    }
    else
    {
        counter = iTime * 500.0;
    }
   
    float x = fragCoord.x;
    float y = fragCoord.y;
   
    if (direction >= 0.0 && direction < 90.0)
    {
        y = y - res.y;
    }
    else if (direction >= 90.0 && direction < 180.0)
    {
        x = x - res.x;
        y = y - res.y;
    }
else if (direction >= 180.0 && direction < 270.0)
    {
        x = x - res.x;
    }
    // no changes between 270.0 and 360.0

    float red = 0.0;
    float green = 0.0;
    float blue = 0.0;
   
    if (y * s + x * c < counter)
    {
        red = tex.r;
    }

    if (y * s + x * c < (counter - start / 3.0))
    {
        green = tex.g;
    }

if (y * s + x * c < (counter - start * 2.0 / 3.0))
    {
        blue = tex.b;
    }

     fragColor = vec4(red, green, blue, 1.0);
}

Stencyl Code
Code: [Select]
#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vTexCoord;
uniform vec2 uResolution;
uniform sampler2D uImage0;

uniform bool enabled;
uniform float counter;
uniform float direction;

void main()
{
vec2 uv = vTexCoord;
    vec4 tex = texture2D(uImage0, uv);
   
if (!enabled)
{
gl_FragColor = tex;
return;
}

    vec2 res = uResolution;
    float rad = radians(direction);
    float s = sin(rad + radians(180.0));
    float c = cos(rad);
    float start = res.x * abs(c) * 3.0 + res.y * abs(s) * 3.0;
    float x = uv.x * res.x;
    float y = uv.y * res.y;
   
    if (direction >= 0.0 && direction < 90.0)
    {
        y = y - res.y;
    }
    else if (direction >= 90.0 && direction < 180.0)
    {
        x = x - res.x;
        y = y - res.y;
    }
else if (direction >= 180.0 && direction < 270.0)
    {
        x = x - res.x;
    }
    // no changes between 270.0 and 360.0

    float red = 0.0;
    float green = 0.0;
    float blue = 0.0;
   
    if (y * s + x * c < counter)
    {
        red = tex.r;
    }

    if (y * s + x * c < (counter - start / 3.0))
    {
        green = tex.g;
    }

if (y * s + x * c < (counter - start * 2.0 / 3.0))
    {
        blue = tex.b;
    }

gl_FragColor = vec4(red, green, blue, 1.0);
}

« Last Edit: April 03, 2018, 04:29:51 pm by rob1221 »

Eriko

  • Posts: 162
Thanks, man. The collection keeps growing.  :D

rob1221

  • *
  • Posts: 9473
This shader creates colored rings that move away from a given center.  It's similar to the shockwave shader by laserhosen but with colors rather than distortion.
https://gfycat.com/SpryPhonyGossamerwingedbutterfly

Here are the properties of the shader that you can set:
enabled (bool) -- This is used to control whether most of the shader code is run.  It's useful if you want to disable the shader without needing to reapply shaders.  If not using the custom block it defaults to false so you'll need to set it to true first.
centerX (float) -- This is the X center of the rings.
centerY (float) -- This is the Y center of the rings.
counter (float) -- This controls the current position of the shader and it must be 0 or higher.  Set this under the always/updating event or in a timer.  Increase to expand the rings, but you can also decrease to reverse the ring movement.  The example GIF changes the counter by +6 in the always/updating event.
thickness (float) -- This is the thickness of each ring.
ringSpacing (float) -- This is the empty space between each ring if you have more than 1.
ringCount (float) -- This is the number of rings that will be created in total if you keep increasing the counter.
red (float) -- This is the red value and is either added to or multiplied with the current screen. (range: 0-2)
green (float) -- This is the red value and is either added to or multiplied with the current screen. (range: 0-2)
blue (float) -- This is the red value and is either added to or multiplied with the current screen. (range: 0-2)
multiply (bool) -- If true, the color values are multiplied with the screen values.  If false, the color values are added to the screen values.

Shadertoy Code
Code: [Select]
bool enabled = true;
float counter = 0.0;
float thickness = 25.0;
float ringSpacing = 100.0;
const float ringCount = 6.0;
float red = 1.6;
float green = 1.4;
float blue = 0.5;
bool multiply = false;

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float centerX = iMouse.x;
float centerY = iMouse.y;
   
vec2 uv = fragCoord.xy / iResolution.xy;
vec4 tex = texture(iChannel0, uv);
   
if (!enabled)
{
fragColor = tex;
return;
}
   
vec2 fragPt = fragCoord;
counter = iTime;
float outerRad = counter * 300.0;
float innerRad = outerRad - thickness;
float xDiff = centerX - fragPt.x;
float yDiff = centerY - fragPt.y;
float dist = sqrt((xDiff * xDiff) + (yDiff * yDiff));
   
fragColor = tex;
   
for(float i = 0.0; i < ringCount; i++)
{
if (dist < outerRad - i * ringSpacing && dist > innerRad - i * ringSpacing)
{
if (multiply)
{
fragColor = vec4(tex.r * red, tex.g * green, tex.b * blue, 1.0);
}
else
{
fragColor = vec4(tex.r + red, tex.g + green, tex.b + blue, 1.0);
}
}
}
}

Stencyl Code
Code: [Select]
#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vTexCoord;
uniform vec2 uResolution;
uniform vec2 uResolutionUs;
uniform sampler2D uImage0;

uniform bool enabled;
uniform float centerX;
uniform float centerY;
uniform float counter;
uniform float thickness;
uniform float ringSpacing;
uniform float ringCount;
uniform float red;
uniform float green;
uniform float blue;
uniform bool multiply;

void main()
{
vec2 uv = vTexCoord;
vec4 tex = texture2D(uImage0, uv);
   
if (!enabled)
{
gl_FragColor = tex;
return;
}
   
vec2 scale = uResolution.xy / uResolutionUs.xy;
float scaleAvg = (scale.x + scale.y) / 2.0;
vec2 fragPt = uv * uResolution;
float outerRad = counter * scaleAvg;
float innerRad = outerRad - thickness * scaleAvg;
float xDiff = centerX * scale.x - fragPt.x;
float yDiff = (uResolutionUs.y - centerY) * scale.y - fragPt.y;
float dist = sqrt((xDiff * xDiff) + (yDiff * yDiff));
   
gl_FragColor = tex;
   
for(float i = 0.0; i < 1000.0; i++)
{
if (i >= ringCount)
{
break;
}
if (dist < outerRad - i * ringSpacing * scaleAvg && dist > innerRad - i * ringSpacing * scaleAvg)
{
if (multiply)
{
gl_FragColor = vec4(tex.r * red, tex.g * green, tex.b * blue, 1.0);
}
else
{
gl_FragColor = vec4(tex.r + red, tex.g + green, tex.b + blue, 1.0);
}
}
}
}

« Last Edit: January 26, 2019, 06:41:41 pm by rob1221 »

rob1221

  • *
  • Posts: 9473
This shader displays two sets of bars that come in from opposite sides of the screen and cross each other to cover the screen.  It could be used as a scene transition.
https://gfycat.com/SimplePoliteHummingbird

Here are the properties of the shader that you can set:
enabled (bool) -- This is used to control whether most of the shader code is run.  It's useful if you want to disable the shader without needing to reapply shaders.  If not using the custom block it defaults to false so you'll need to set it to true first.
counter (float) -- This controls the current position of the shader and it must be 0 or higher.  Set this under the always/updating event or in a timer.  Increase to move the bars in and decrease to move the bars out.  The example GIF changes the counter by + or - 8 every 0.01 seconds.
direction (float) -- This is the direction (in degrees) that the first set of bars will move.  The second set moves in the opposite direction. (range: 0-360)
barWidth(float) -- This is the width of each bar.
red(float) -- This is the red value of the bars. (range: 0-1)
green(float) -- This is the green value of the bars. (range: 0-1)
blue(float) -- This is the blue value of the bars. (range: 0-1)

Shadertoy Code
Code: [Select]
bool enabled = true;
float counter = 0.0;
float direction = 45.0;
float barWidth = 20.0;
float red = 0.1;
float green = 0.2;
float blue = 0.4;

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
vec2 uv = fragCoord.xy / iResolution.xy;
    vec4 tex = texture(iChannel0, uv);
   
if (!enabled)
{
fragColor = tex;
return;
}

    vec2 res = vec2(iResolution.xy);
    float rad = radians(direction);
    float s = sin(rad + radians(180.0));
    float c = cos(rad);
    float start = res.x * abs(c) + res.y * abs(s);
   
    if (false) // for testing: false = transition out and true = transition in
    {
        counter = start - iTime * 500.0;
    }
    else
    {
        counter = iTime * 500.0;
    }
   
    float x = fragCoord.x;
    float y = fragCoord.y;
    float startX = x;
    float startY = y;
   
    if (direction >= 0.0 && direction < 90.0)
    {
        startY = y - res.y;
    }
    else if (direction >= 90.0 && direction < 180.0)
    {
        startX = x - res.x;
        startY = y - res.y;
    }
else if (direction >= 180.0 && direction < 270.0)
    {
        startX = x - res.x;
    }
    else
    {
        // no changes between 270.0 and 360.0
    }

    float offset = res.x * 2.0; // avoids inconsistent bars
    float row = floor(abs((x + offset) * s - y * c) / barWidth);
   
    if (counter > abs(startX * c) + abs(startY * s) && row * 0.5 == floor(row * 0.5))
    {
        fragColor = vec4(red, green, blue, 1.0);
    }
    else if (abs(res.x * c) + abs(res.y * s) - counter < abs(startX * c) + abs(startY * s) && row * 0.5 != floor(row * 0.5))
    {
        fragColor = vec4(red, green, blue, 1.0);
    }
    else
    {
        fragColor = vec4(tex);
    }
}

Stencyl Code
Code: [Select]
#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vTexCoord;
uniform vec2 uResolution;
uniform vec2 uResolutionUs;
uniform sampler2D uImage0;

uniform bool enabled;
uniform float counter;
uniform float direction;
uniform float barWidth;
uniform float red;
uniform float green;
uniform float blue;

void main()
{
vec2 uv = vTexCoord;
    vec4 tex = texture2D(uImage0, uv);
   
if (!enabled)
{
gl_FragColor = tex;
return;
}

    vec2 res = uResolution;
float scale = res.x / uResolutionUs.x;
    float rad = radians(direction);
    float s = sin(rad + radians(180.0));
    float c = cos(rad);
    float start = res.x * abs(c) + res.y * abs(s);
    float x = uv.x * res.x;
    float y = uv.y * res.y;
    float startX = x;
    float startY = y;
   
    if (direction >= 0.0 && direction < 90.0)
    {
        startY = y - res.y;
    }
    else if (direction >= 90.0 && direction < 180.0)
    {
        startX = x - res.x;
        startY = y - res.y;
    }
else if (direction >= 180.0 && direction < 270.0)
    {
        startX = x - res.x;
    }
    else
    {
        // no changes between 270.0 and 360.0
    }

    float offset = res.x * 2.0; // avoids inconsistent bars
    float row = floor(abs((x + offset) * s - y * c) / (barWidth * scale));
   
    if (counter * scale > abs(startX * c) + abs(startY * s) && row * 0.5 == floor(row * 0.5))
    {
        gl_FragColor = vec4(red, green, blue, 1.0);
    }
    else if (abs(res.x * c) + abs(res.y * s) - counter * scale < abs(startX * c) + abs(startY * s) && row * 0.5 != floor(row * 0.5))
    {
        gl_FragColor = vec4(red, green, blue, 1.0);
    }
    else
    {
        gl_FragColor = vec4(tex);
    }
}

« Last Edit: January 27, 2019, 05:36:49 pm by rob1221 »

rob1221

  • *
  • Posts: 9473
With Stencyl 4.0, shader arrays are supported which allows for more options for shaders including this multiple lights shader.
https://gfycat.com/ParallelColdFunnelweaverspider

It's similar to the circle light shader but modified to support multiple lights.  Some properties have been changed to use list attributes rather than number attributes.  After modifying the lists, set the shader properties to the lists to update the shader.  The array size is set at 100 for performance reasons but you can edit the shader code to change that if you need to.

In addition to this shader, I've also checked my other shaders and updated those as needed so they work without errors on both desktop and HTML5.

Here are the properties of the shader that you can set:
enabled (bool) -- This is used to control whether most of the shader code is run.  It's useful if you want to disable the shader without needing to reapply shaders.  If not using the custom block it defaults to false so you'll need to set it to true first.
centerX (float array) -- This is the X center of the light circle.
centerY (float array) -- This is the Y center of the light circle.
minDarkAmount (float) -- This is how dark the screen becomes just outside of the light radius (range: 0-1)
maxDarkAmount (float) -- This is the maximum darkness of the screen and is reached at the dark radius (range: 0-1)
lightRadius (float) -- This is the inner radius of the light that has no darkness at all.
darkRadius (float) -- This is the outer radius of the light at which point the max darkness is reached.
red (float array) -- This is the red value of the light (range: 0-1)
green (float array) -- This is the green value of the light (range: 0-1)
blue (float array) -- This is the blue value of the light (range: 0-1)

Stencyl Code
Code: [Select]
#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vTexCoord;
uniform vec2 uResolution;
uniform vec2 uResolutionUs;
uniform sampler2D uImage0;

uniform bool enabled;
uniform float centerX[100];
uniform float centerY[100];
uniform float minDarkAmount;
uniform float maxDarkAmount;
uniform float lightRadius;
uniform float darkRadius;
uniform float red[100];
uniform float green[100];
uniform float blue[100];

void main()
{
vec2 uv = vTexCoord;
vec4 tex = texture2D(uImage0, uv);

if (!enabled)
{
gl_FragColor = tex;
return;
}

float lightAmount = (1.0 - maxDarkAmount);
vec4 tex2 = vec4(lightAmount, lightAmount, lightAmount, 1.0);
vec2 res = uResolution.xy;
float scale = res.x / uResolutionUs.x;
float darkRange = maxDarkAmount - minDarkAmount;
float lightRad = lightRadius * scale;
float darkRad = darkRadius * scale;

for (int i = 0; i < 100; i++)
{
float xDiff = ((centerX[i] / uResolutionUs.x) - uv.x) * res.x;
float yDiff = ((1.0 - (centerY[i] / uResolutionUs.y)) - uv.y) * res.y;
float dist = sqrt((xDiff * xDiff) + (yDiff * yDiff));
float darkAmount = minDarkAmount + min(darkRange * ((dist - lightRad) / (darkRad - lightRad)), darkRange);
vec3 colorMod = vec3(red[i], green[i], blue[i]);

if (dist < lightRad)
{
tex2 = 1.0 - ((1.0 - tex2) * (1.0 - vec4(colorMod, 1.0)));
}
else if (dist < darkRad)
{
tex2 = 1.0 - ((1.0 - tex2) * (1.0 - vec4(colorMod * (1.0 - darkAmount), 1.0)));
}
}

gl_FragColor = tex * tex2;
}

« Last Edit: September 23, 2019, 12:50:02 pm by rob1221 »

squeeb

  • Posts: 1617
love these!!  no gif for the last one? :/

rob1221

  • *
  • Posts: 9473
I tried to record something with GifCam like I did with the other shaders but all those lights made the quality bad so I didn't bother uploading it.

Update: I made a video of the shader with OBS and added a link to it.

« Last Edit: January 29, 2019, 03:39:51 pm by rob1221 »

squeeb

  • Posts: 1617
Thanks!  That last one looks awesome!  Do these work on mobile now?!

rob1221

  • *
  • Posts: 9473
I tested the last one on Android and while it functions as expected, the fps is less than 1 on my device (about 5 fps if I lower the array size to 10).  It's a pretty weak device (lg optimus fuel) so maybe the shader would run fine on a high end one.

Update: I tested the crossing bars shader and it runs at 60fps so it really just depends on the complexity of the shader.  I'll assume the other shaders also work on Android unless someone posts otherwise.  I don't have an iOS device so I can't confirm if they work there.

« Last Edit: January 29, 2019, 05:04:29 pm by rob1221 »