A shader that aims to *mostly recreate how Adobe Animate/Flash handles drop shadows, but its main use here is for rim lighting.

Has options for color, angle, distance, and a threshold to not cast the shadow on parts like outlines. Can also be supplied a secondary mask which can then have an alternate threshold, for when sprites have too many conflicting colors for the drop shadow to look right (e.g. the tankmen on GF's speakers).

Also has an Adjust Color shader in here so they can work together when needed.

Constructor

@:glFragmentSource("\n #pragma header\n\n // This shader aims to mostly recreate how Adobe Animate/Flash handles drop shadows, but its main use here is for rim lighting.\n\n // this shader also includes a recreation of the Animate/Flash \"Adjust Color\" filter,\n // which was kindly provided and written by Rozebud https://github.com/ThatRozebudDude ( thank u rozebud :) )\n // Adapted from Andrey-Postelzhuks shader found here: https://forum.unity.com/threads/hue-saturation-brightness-contrast-shader.260649/\n // Hue rotation stuff is from here: https://www.w3.org/TR/filter-effects/#feColorMatrixElement\n\n // equals (frame.left, frame.top, frame.right, frame.bottom)\n uniform vec4 uFrameBounds;\n\n uniform float ang;\n uniform float dist;\n uniform float str;\n uniform float thr;\n\n // need to account for rotated frames... oops\n uniform float angOffset;\n\n uniform sampler2D altMask;\n uniform bool useMask;\n uniform float thr2;\n\n uniform vec3 dropColor;\n\n uniform float hue;\n uniform float saturation;\n uniform float brightness;\n uniform float contrast;\n\n uniform float AA_STAGES;\n\n const vec3 grayscaleValues = vec3(0.3098039215686275, 0.607843137254902, 0.0823529411764706);\n\t\t const float e = 2.718281828459045;\n\n\t\t vec3 applyHueRotate(vec3 aColor, float aHue){\n\t\t\t float angle = radians(aHue);\n\n\t\t\t mat3 m1 = mat3(0.213, 0.213, 0.213, 0.715, 0.715, 0.715, 0.072, 0.072, 0.072);\n\t\t\t mat3 m2 = mat3(0.787, -0.213, -0.213, -0.715, 0.285, -0.715, -0.072, -0.072, 0.928);\n\t\t\t mat3 m3 = mat3(-0.213, 0.143, -0.787, -0.715, 0.140, 0.715, 0.928, -0.283, 0.072);\n\t\t\t mat3 m = m1 + cos(angle) * m2 + sin(angle) * m3;\n\n\t\t\t return m * aColor;\n\t\t }\n\n\t\t vec3 applySaturation(vec3 aColor, float value){\n\t\t\t if(value > 0.0){ value = value * 3.0; }\n\t\t\t value = (1.0 + (value / 100.0));\n\t\t\t vec3 grayscale = vec3(dot(aColor, grayscaleValues));\n return clamp(mix(grayscale, aColor, value), 0.0, 1.0);\n\t\t }\n\n\t\t vec3 applyContrast(vec3 aColor, float value){\n\t\t\t value = (1.0 + (value / 100.0));\n\t\t\t if(value > 1.0){\n\t\t\t\t value = (((0.00852259 * pow(e, 4.76454 * (value - 1.0))) * 1.01) - 0.0086078159) * 10.0; //Just roll with it...\n\t\t\t\t value += 1.0;\n\t\t\t }\n return clamp((aColor - 0.25) * value + 0.25, 0.0, 1.0);\n\t\t }\n\n vec3 applyHSBCEffect(vec3 color){\n\n\t\t\t //Brightness\n\t\t\t color = color + ((brightness) / 255.0);\n\n\t\t\t //Hue\n\t\t\t color = applyHueRotate(color, hue);\n\n\t\t\t //Contrast\n\t\t\t color = applyContrast(color, contrast);\n\n\t\t\t //Saturation\n color = applySaturation(color, saturation);\n\n return color;\n }\n\n vec2 hash22(vec2 p) {\n vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n }\n\n float intensityPass(vec2 fragCoord, float curThreshold, bool useMask) {\n vec4 col = texture2D(bitmap, fragCoord);\n\n float maskIntensity = 0.0;\n if(useMask == true){\n maskIntensity = mix(0.0, 1.0, texture2D(altMask, fragCoord).b);\n }\n\n if(col.a == 0.0){\n return 0.0;\n }\n\n float intensity = dot(col.rgb, vec3(0.3098, 0.6078, 0.0823));\n\n intensity = maskIntensity > 0.0 ? float(intensity > thr2) : float(intensity > thr);\n\n return intensity;\n }\n\n // essentially just stole this from the AngleMask shader but repurposed it to smooth\n // the threshold because without any sort of smoothing it produces horrible edges\n float antialias(vec2 fragCoord, float curThreshold, bool useMask) {\n if (AA_STAGES == 0.0) {\n return intensityPass(fragCoord, curThreshold, useMask);\n }\n\n // In GLSL 100, we need to use constant loop bounds\n // Well assume a reasonable maximum for AA_STAGES and use a fixed loop\n // The actual number of iterations will be controlled by a condition inside\n const int MAX_AA = 8; // This should be large enough for most uses\n\n float AA_TOTAL_PASSES = AA_STAGES * AA_STAGES + 1.0;\n const float AA_JITTER = 0.5;\n\n // Run the shader multiple times with a random subpixel offset each time and average the results\n float color = intensityPass(fragCoord, curThreshold, useMask);\n for (int i = 0; i < MAX_AA * MAX_AA; i++) {\n // Calculate x and y from i\n int x = i / MAX_AA;\n int y = i - (MAX_AA * int(i/MAX_AA)); // poor mans modulus\n\n // Skip iterations beyond our desired AA_STAGES\n if (float(x) >= AA_STAGES || float(y) >= AA_STAGES) {\n continue;\n }\n\n vec2 offset = AA_JITTER * (2.0 * hash22(vec2(float(x), float(y))) - 1.0) / openfl_TextureSize.xy;\n color += intensityPass(fragCoord + offset, curThreshold, useMask);\n }\n\n return color / AA_TOTAL_PASSES;\n }\n\n vec3 createDropShadow(vec3 col, float curThreshold, bool useMask) {\n\n // essentially a mask so that areas under the threshold dont show the rimlight (mainly the outlines)\n float intensity = antialias(openfl_TextureCoordv, curThreshold, useMask);\n\n // the distance the dropshadow moves needs to be correctly scaled based on the texture size\n vec2 imageRatio = vec2(1.0/openfl_TextureSize.x, 1.0/openfl_TextureSize.y);\n\n // check the pixel in the direction and distance specified\n vec2 checkedPixel = vec2(openfl_TextureCoordv.x + (dist * cos(ang + angOffset) * imageRatio.x),\n openfl_TextureCoordv.y - (dist * sin(ang + angOffset) * imageRatio.y));\n\n // multiplier for the intensity of the drop shadow\n float dropShadowAmount = 0.0;\n\n\t\t\t if(checkedPixel.x > uFrameBounds.x && checkedPixel.y > uFrameBounds.y && checkedPixel.x < uFrameBounds.z && checkedPixel.y < uFrameBounds.w){\n dropShadowAmount = texture2D(bitmap, checkedPixel).a;\n\t\t\t }\n\n // add the dropshadow color based on the amount, strength, and intensity\n col.rgb += dropColor.rgb * ((1.0 - (dropShadowAmount * str))*intensity);\n\n return col;\n }\n\n void main()\n {\n vec4 col = texture2D(bitmap, openfl_TextureCoordv);\n\n vec3 unpremultipliedColor = col.a > 0.0 ? col.rgb / col.a : col.rgb;\n\n vec3 outColor = applyHSBCEffect(unpremultipliedColor);\n\n outColor = createDropShadow(outColor, thr, useMask);\n\n gl_FragColor = vec4(outColor.rgb * col.a, col.a);\n }\n\n ")new()

Variables

@:keepAA_STAGES:ShaderParameter_Float

@:keepaltMask:ShaderInput_openfl_display_BitmapData

altMaskImage:BitmapData

The image for the alternate mask. At the moment, it uses the blue channel to specify what is or isnt going to use the alternate threshold. (its kinda sloppy rn i need to make it work a little nicer) TODO: maybe have a sort of "threshold intensity texture" as well? where higher/lower values indicate threshold strength..

@:keepang:ShaderParameter_Float

@:keepangOffset:ShaderParameter_Float

angle:Float

The angle of the drop shadow.

for reference, depending on the angle, the affected side will be: 0 = RIGHT 90 = UP 180 = LEFT 270 = DOWN

antialiasAmt:Float

The amount of antialias samples per-pixel, used to smooth out any hard edges the brightness thresholding creates. Defaults to 2, and 0 will remove any smoothing.

attachedSprite:FunkinSprite

The FunkinSprite that the shader should get the frame data from. Needed to keep the drop shadow shader in the correct bounds and rotation.

baseBrightness:Float

The brightness component of the Adjust Color part of the shader.

baseContrast:Float

The contrast component of the Adjust Color part of the shader.

baseHue:Float

The hue component of the Adjust Color part of the shader.

baseSaturation:Float

The saturation component of the Adjust Color part of the shader.

@:keepbrightness:ShaderParameter_Float

color:FlxColor

The color of the drop shadow.

@:keepcontrast:ShaderParameter_Float

@:keepdist:ShaderParameter_Float

distance:Float

The distance or size of the drop shadow, in pixels, relative to the texture itself... NOT the camera.

@:keepdropColor:ShaderParameter_Float

@:keephue:ShaderParameter_Float

maskThreshold:Float

An alternate brightness threshold for the drop shadow. Anything below this number will NOT be affected by the drop shadow shader, but ONLY when the pixel is within the mask.

@:keepsaturation:ShaderParameter_Float

@:keepstr:ShaderParameter_Float

strength:Float

The strength of the drop shadow. Effectively just an alpha multiplier.

@:keepthr:ShaderParameter_Float

@:keepthr2:ShaderParameter_Float

threshold:Float

The brightness threshold for the drop shadow. Anything below this number will NOT be affected by the drop shadow shader. A value of 0 effectively means theres no threshold, and vice versa.

@:keepuFrameBounds:ShaderParameter_Float

useAltMask:Bool

Whether the shader should try and use the alternate mask. False by default.

@:keepuseMask:ShaderParameter_Bool

Methods

loadAltMask(path:String):Void

Loads an image for the mask. While you could directly set the value of the mask, this function works for both HTML5 and native targets.

Parameters:

path

The path to the image to load

onAttachedFrame(name:String, frameNum:Int, frameIndex:Int):Void

Should be called on the animation.callback of the attached sprite. TODO: figure out why the reference to the attachedSprite breaks on web??

Parameters:

name

The name of the animation

frameNum

The current frame number

frameIndex

The current frame index

setAdjustColor(b:Float, h:Float, c:Float, s:Float):Void

Sets all 4 adjust color values.

Parameters:

b

The brightness value

h

The hue value

c

The contrast value

s

The saturation value

updateFrameInfo(frame:FlxFrame):Void

Updates the frame bounds and angle offset of the sprite for the shader

Parameters:

frame

The frame to retrieve the information from

Inherited Variables

Defined by FlxGraphicsShader

@:keepalpha:ShaderParameter_Float

@:keepcolorMultiplier:ShaderParameter_Float

@:keepcolorOffset:ShaderParameter_Float

@:keephasColorTransform:ShaderParameter_Bool

@:keephasTransform:ShaderParameter_Bool

@:keeppremultiplyAlpha:ShaderParameter_Bool

Defined by GraphicsShader

@:keepbitmap:ShaderInput_openfl_display_BitmapData

Defined by Shader

write onlybyteCode:ByteArray

The raw shader bytecode for this Shader instance.

data:ShaderData

Provides access to parameters, input images, and metadata for the Shader instance. ShaderParameter objects representing parameters for the shader, ShaderInput objects representing the input images for the shader, and other values representing the shader's metadata are dynamically added as properties of the data property object when the Shader instance is created. Those properties can be used to introspect the shader and to set parameter and input values. For information about accessing and manipulating the dynamic properties of the data object, see the ShaderData class description.

read onlyglFragmentBodyRaw:String

The default GLSL vertex body, before being applied to the vertex source.

glFragmentExtensions:Array<{name:String, behavior:String}>

read onlyglFragmentHeaderRaw:String

The default GLSL vertex header, before being applied to the vertex source.

glFragmentSource:String

Get or set the fragment source used when compiling with GLSL.

This property is not available on the Flash target.

read onlyglFragmentSourceRaw:String

The default GLSL fragment source, before #pragma values are replaced.

@SuppressWarnings("checkstyle:Dynamic")read onlyglProgram:GLProgram

The compiled GLProgram if available.

This property is not available on the Flash target.

glVersion:String

Get or set the GLSL version used in the header when compiling with GLSL.

  • 120 is required for initialization (i.e. providing a default value for) uniform variables

read onlyglVertexBodyRaw:String

The default GLSL vertex body, before being applied to the vertex source.

glVertexExtensions:Array<{name:String, behavior:String}>

Provides additional #extension directives to insert in the vertex and fragment shaders.

Example:

@:glExtensions([{name: "OES_standard_derivatives", behavior: "require"}])
@:glVertexExtensions([{name: "OES_standard_derivatives", behavior: "require"}])
@:glFragmentExtensions([{name: "OES_standard_derivatives", behavior: "require"}])

read onlyglVertexHeaderRaw:String

The default GLSL vertex header, before being applied to the vertex source.

glVertexSource:String

Get or set the vertex source used when compiling with GLSL.

This property is not available on the Flash target.

read onlyglVertexSourceRaw:String

The default GLSL vertex source, before #pragma values are replaced.

precisionHint:ShaderPrecision

The precision of math operations performed by the shader. The set of possible values for the precisionHint property is defined by the constants in the ShaderPrecision class.

The default value is ShaderPrecision.FULL. Setting the precision to ShaderPrecision.FAST can speed up math operations at the expense of precision.

Full precision mode (ShaderPrecision.FULL) computes all math operations to the full width of the IEEE 32-bit floating standard and provides consistent behavior on all platforms. In this mode, some math operations such as trigonometric and exponential functions can be slow.

Fast precision mode (ShaderPrecision.FAST) is designed for maximum performance but does not work consistently on different platforms and individual CPU configurations. In many cases, this level of precision is sufficient to create graphic effects without visible artifacts.

The precision mode selection affects the following shader operations. These operations are faster on an Intel processor with the SSE instruction set:

  • sin(x)
  • cos(x)
  • tan(x)
  • asin(x)
  • acos(x)
  • atan(x)
  • atan(x, y)
  • exp(x)
  • exp2(x)
  • log(x)
  • log2(x)
  • pow(x, y)
  • reciprocal(x)
  • sqrt(x)

program:Program3D

The compiled Program3D if available.

This property is not available on the Flash target.

Inherited Methods