2023-12-19 01:49:01 +00:00
|
|
|
package funkin.graphics.shaders;
|
2023-09-11 15:10:08 +00:00
|
|
|
|
2023-09-13 21:28:59 +00:00
|
|
|
import flixel.FlxCamera;
|
2023-09-11 15:10:08 +00:00
|
|
|
import flixel.FlxG;
|
2024-07-18 05:04:19 +00:00
|
|
|
import flixel.graphics.frames.FlxFrame;
|
2023-09-11 15:10:08 +00:00
|
|
|
import flixel.addons.display.FlxRuntimeShader;
|
|
|
|
import lime.graphics.opengl.GLProgram;
|
|
|
|
import lime.utils.Log;
|
|
|
|
|
|
|
|
class RuntimePostEffectShader extends FlxRuntimeShader
|
|
|
|
{
|
2023-09-13 21:28:59 +00:00
|
|
|
@:glVertexHeader('
|
|
|
|
// normalized screen coord
|
|
|
|
// (0, 0) is the top left of the window
|
|
|
|
// (1, 1) is the bottom right of the window
|
|
|
|
varying vec2 screenCoord;
|
|
|
|
', true)
|
|
|
|
@:glVertexBody('
|
|
|
|
screenCoord = vec2(
|
2023-09-11 15:10:08 +00:00
|
|
|
openfl_TextureCoord.x > 0.0 ? 1.0 : 0.0,
|
|
|
|
openfl_TextureCoord.y > 0.0 ? 1.0 : 0.0
|
|
|
|
);
|
2023-09-13 21:28:59 +00:00
|
|
|
')
|
|
|
|
@:glFragmentHeader('
|
|
|
|
// normalized screen coord
|
|
|
|
// (0, 0) is the top left of the window
|
|
|
|
// (1, 1) is the bottom right of the window
|
|
|
|
varying vec2 screenCoord;
|
|
|
|
|
|
|
|
// equals (FlxG.width, FlxG.height)
|
|
|
|
uniform vec2 uScreenResolution;
|
|
|
|
|
|
|
|
// equals (camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom)
|
|
|
|
uniform vec4 uCameraBounds;
|
|
|
|
|
2024-07-18 05:04:19 +00:00
|
|
|
// equals (frame.left, frame.top, frame.right, frame.bottom)
|
|
|
|
uniform vec4 uFrameBounds;
|
|
|
|
|
2023-09-13 21:28:59 +00:00
|
|
|
// screen coord -> world coord conversion
|
|
|
|
// returns world coord in px
|
|
|
|
vec2 screenToWorld(vec2 screenCoord) {
|
|
|
|
float left = uCameraBounds.x;
|
|
|
|
float top = uCameraBounds.y;
|
|
|
|
float right = uCameraBounds.z;
|
|
|
|
float bottom = uCameraBounds.w;
|
|
|
|
vec2 scale = vec2(right - left, bottom - top);
|
|
|
|
vec2 offset = vec2(left, top);
|
|
|
|
return screenCoord * scale + offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
// world coord -> screen coord conversion
|
|
|
|
// returns normalized screen coord
|
|
|
|
vec2 worldToScreen(vec2 worldCoord) {
|
|
|
|
float left = uCameraBounds.x;
|
|
|
|
float top = uCameraBounds.y;
|
|
|
|
float right = uCameraBounds.z;
|
|
|
|
float bottom = uCameraBounds.w;
|
|
|
|
vec2 scale = vec2(right - left, bottom - top);
|
|
|
|
vec2 offset = vec2(left, top);
|
|
|
|
return (worldCoord - offset) / scale;
|
|
|
|
}
|
|
|
|
|
2024-07-18 05:04:19 +00:00
|
|
|
// screen coord -> frame coord conversion
|
|
|
|
// returns normalized frame coord
|
|
|
|
vec2 screenToFrame(vec2 screenCoord) {
|
|
|
|
float left = uFrameBounds.x;
|
|
|
|
float top = uFrameBounds.y;
|
|
|
|
float right = uFrameBounds.z;
|
|
|
|
float bottom = uFrameBounds.w;
|
|
|
|
float width = right - left;
|
|
|
|
float height = bottom - top;
|
|
|
|
|
|
|
|
float clampedX = clamp(screenCoord.x, left, right);
|
|
|
|
float clampedY = clamp(screenCoord.y, top, bottom);
|
|
|
|
|
|
|
|
return vec2(
|
|
|
|
(clampedX - left) / (width),
|
|
|
|
(clampedY - top) / (height)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-09-13 21:28:59 +00:00
|
|
|
// internally used to get the maximum `openfl_TextureCoordv`
|
|
|
|
vec2 bitmapCoordScale() {
|
|
|
|
return openfl_TextureCoordv / screenCoord;
|
|
|
|
}
|
|
|
|
|
|
|
|
// internally used to compute bitmap coord
|
|
|
|
vec2 screenToBitmap(vec2 screenCoord) {
|
|
|
|
return screenCoord * bitmapCoordScale();
|
|
|
|
}
|
|
|
|
|
|
|
|
// samples the frame buffer using a screen coord
|
|
|
|
vec4 sampleBitmapScreen(vec2 screenCoord) {
|
|
|
|
return texture2D(bitmap, screenToBitmap(screenCoord));
|
|
|
|
}
|
|
|
|
|
|
|
|
// samples the frame buffer using a world coord
|
|
|
|
vec4 sampleBitmapWorld(vec2 worldCoord) {
|
|
|
|
return sampleBitmapScreen(worldToScreen(worldCoord));
|
2023-09-11 15:10:08 +00:00
|
|
|
}
|
2023-09-13 21:28:59 +00:00
|
|
|
', true)
|
2023-09-11 15:10:08 +00:00
|
|
|
public function new(fragmentSource:String = null, glVersion:String = null)
|
|
|
|
{
|
|
|
|
super(fragmentSource, null, glVersion);
|
2023-09-13 21:28:59 +00:00
|
|
|
uScreenResolution.value = [FlxG.width, FlxG.height];
|
2024-07-18 05:04:19 +00:00
|
|
|
uCameraBounds.value = [0, 0, FlxG.width, FlxG.height];
|
|
|
|
uFrameBounds.value = [0, 0, FlxG.width, FlxG.height];
|
2023-09-13 21:28:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// basically `updateViewInfo(FlxG.width, FlxG.height, FlxG.camera)` is good
|
|
|
|
public function updateViewInfo(screenWidth:Float, screenHeight:Float, camera:FlxCamera):Void
|
|
|
|
{
|
|
|
|
uScreenResolution.value = [screenWidth, screenHeight];
|
|
|
|
uCameraBounds.value = [camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom];
|
2023-09-11 15:10:08 +00:00
|
|
|
}
|
|
|
|
|
2024-07-18 05:04:19 +00:00
|
|
|
public function updateFrameInfo(frame:FlxFrame)
|
|
|
|
{
|
|
|
|
// NOTE: uv.width is actually the right pos and uv.height is the bottom pos
|
|
|
|
uFrameBounds.value = [frame.uv.x, frame.uv.y, frame.uv.width, frame.uv.height];
|
|
|
|
}
|
|
|
|
|
2023-09-11 15:10:08 +00:00
|
|
|
override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
final res = super.__createGLProgram(vertexSource, fragmentSource);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
catch (error)
|
|
|
|
{
|
2023-09-13 21:28:59 +00:00
|
|
|
Log.warn(error); // prevent the app from dying immediately
|
2023-09-11 15:10:08 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|