feat: Add GLSL renderer and implement FPS overlay across rendering backends
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -7,6 +7,13 @@ import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
|||||||
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
|
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
|
||||||
import 'package:wolf_3d_renderer/wolf_3d_ascii_renderer.dart';
|
import 'package:wolf_3d_renderer/wolf_3d_ascii_renderer.dart';
|
||||||
import 'package:wolf_3d_renderer/wolf_3d_flutter_renderer.dart';
|
import 'package:wolf_3d_renderer/wolf_3d_flutter_renderer.dart';
|
||||||
|
import 'package:wolf_3d_renderer/wolf_3d_glsl_renderer.dart';
|
||||||
|
|
||||||
|
enum _RendererMode {
|
||||||
|
software,
|
||||||
|
ascii,
|
||||||
|
glsl,
|
||||||
|
}
|
||||||
|
|
||||||
/// Launches a [WolfEngine] via [Wolf3d] and exposes renderer/input integrations.
|
/// Launches a [WolfEngine] via [Wolf3d] and exposes renderer/input integrations.
|
||||||
class GameScreen extends StatefulWidget {
|
class GameScreen extends StatefulWidget {
|
||||||
@@ -25,7 +32,7 @@ class GameScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _GameScreenState extends State<GameScreen> {
|
class _GameScreenState extends State<GameScreen> {
|
||||||
late final WolfEngine _engine;
|
late final WolfEngine _engine;
|
||||||
bool _useAsciiMode = false;
|
_RendererMode _rendererMode = _RendererMode.software;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -60,11 +67,7 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
onPointerHover: widget.wolf3d.input.onPointerMove,
|
onPointerHover: widget.wolf3d.input.onPointerMove,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
// Keep both renderers behind the same engine so mode switching does
|
_buildRenderer(),
|
||||||
// not reset level state or audio playback.
|
|
||||||
_useAsciiMode
|
|
||||||
? WolfAsciiRenderer(engine: _engine)
|
|
||||||
: WolfFlutterRenderer(engine: _engine),
|
|
||||||
|
|
||||||
if (!_engine.isInitialized)
|
if (!_engine.isInitialized)
|
||||||
Container(
|
Container(
|
||||||
@@ -93,7 +96,7 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
onKeyEvent: (node, event) {
|
onKeyEvent: (node, event) {
|
||||||
if (event is KeyDownEvent &&
|
if (event is KeyDownEvent &&
|
||||||
event.logicalKey == LogicalKeyboardKey.tab) {
|
event.logicalKey == LogicalKeyboardKey.tab) {
|
||||||
setState(() => _useAsciiMode = !_useAsciiMode);
|
setState(_cycleRendererMode);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
@@ -115,7 +118,7 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
top: 16,
|
top: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
child: Text(
|
child: Text(
|
||||||
'TAB: Swap Renderer',
|
'TAB: ${_modeLabel(_rendererMode)}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white.withValues(alpha: 0.5),
|
color: Colors.white.withValues(alpha: 0.5),
|
||||||
),
|
),
|
||||||
@@ -129,4 +132,54 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildRenderer() {
|
||||||
|
// Keep all renderers behind the same engine so mode switching does not
|
||||||
|
// reset level state or audio playback.
|
||||||
|
switch (_rendererMode) {
|
||||||
|
case _RendererMode.software:
|
||||||
|
return WolfFlutterRenderer(engine: _engine);
|
||||||
|
case _RendererMode.ascii:
|
||||||
|
return WolfAsciiRenderer(engine: _engine);
|
||||||
|
case _RendererMode.glsl:
|
||||||
|
return WolfGlslRenderer(
|
||||||
|
engine: _engine,
|
||||||
|
onUnavailable: _onGlslUnavailable,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cycleRendererMode() {
|
||||||
|
switch (_rendererMode) {
|
||||||
|
case _RendererMode.software:
|
||||||
|
_rendererMode = _RendererMode.ascii;
|
||||||
|
break;
|
||||||
|
case _RendererMode.ascii:
|
||||||
|
_rendererMode = _RendererMode.glsl;
|
||||||
|
break;
|
||||||
|
case _RendererMode.glsl:
|
||||||
|
_rendererMode = _RendererMode.software;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onGlslUnavailable() {
|
||||||
|
if (!mounted || _rendererMode != _RendererMode.glsl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_rendererMode = _RendererMode.software;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String _modeLabel(_RendererMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case _RendererMode.software:
|
||||||
|
return 'Software';
|
||||||
|
case _RendererMode.ascii:
|
||||||
|
return 'ASCII';
|
||||||
|
case _RendererMode.glsl:
|
||||||
|
return 'GLSL';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ class WolfEngine {
|
|||||||
/// Elapsed engine lifetime in milliseconds.
|
/// Elapsed engine lifetime in milliseconds.
|
||||||
int get timeAliveMs => _timeAliveMs;
|
int get timeAliveMs => _timeAliveMs;
|
||||||
|
|
||||||
|
/// Exponential moving average of rendered frames per second.
|
||||||
|
double _smoothedFps = 0.0;
|
||||||
|
|
||||||
|
/// Current smoothed FPS, suitable for lightweight on-screen diagnostics.
|
||||||
|
double get fps => _smoothedFps;
|
||||||
|
|
||||||
/// The episode index where the game session begins.
|
/// The episode index where the game session begins.
|
||||||
final int? startingEpisode;
|
final int? startingEpisode;
|
||||||
|
|
||||||
@@ -203,6 +209,14 @@ class WolfEngine {
|
|||||||
|
|
||||||
// Trust the incoming delta time natively
|
// Trust the incoming delta time natively
|
||||||
_timeAliveMs += delta.inMilliseconds;
|
_timeAliveMs += delta.inMilliseconds;
|
||||||
|
if (delta.inMicroseconds > 0) {
|
||||||
|
final double instantaneousFps = 1000000.0 / delta.inMicroseconds;
|
||||||
|
if (_smoothedFps <= 0.0) {
|
||||||
|
_smoothedFps = instantaneousFps;
|
||||||
|
} else {
|
||||||
|
_smoothedFps = (_smoothedFps * 0.88) + (instantaneousFps * 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Process User Input
|
// 1. Process User Input
|
||||||
input.update();
|
input.update();
|
||||||
|
|||||||
@@ -351,6 +351,13 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawFpsOverlay(WolfEngine engine) {
|
||||||
|
const int textColor = 0xFFFFFFFF;
|
||||||
|
const int bgColor = 0xFF000000;
|
||||||
|
_writeString(1, 0, ' ${fpsLabel(engine)} ', textColor, bgColor);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void drawMenu(WolfEngine engine) {
|
void drawMenu(WolfEngine engine) {
|
||||||
final int bgColor = _rgbToPaletteColor(engine.menuBackgroundRgb);
|
final int bgColor = _rgbToPaletteColor(engine.menuBackgroundRgb);
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ abstract class RendererBackend<T>
|
|||||||
|
|
||||||
if (engine.difficulty == null) {
|
if (engine.difficulty == null) {
|
||||||
drawMenu(engine);
|
drawMenu(engine);
|
||||||
|
drawFpsOverlay(engine);
|
||||||
return finalizeFrame();
|
return finalizeFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ abstract class RendererBackend<T>
|
|||||||
// 3. Draw 2D overlays.
|
// 3. Draw 2D overlays.
|
||||||
drawWeapon(engine);
|
drawWeapon(engine);
|
||||||
drawHud(engine);
|
drawHud(engine);
|
||||||
|
drawFpsOverlay(engine);
|
||||||
|
|
||||||
// 4. Finalize and return the frame data (Buffer or String/List).
|
// 4. Finalize and return the frame data (Buffer or String/List).
|
||||||
return finalizeFrame();
|
return finalizeFrame();
|
||||||
@@ -139,6 +141,14 @@ abstract class RendererBackend<T>
|
|||||||
/// Default implementation is a no-op for backends that don't support menus.
|
/// Default implementation is a no-op for backends that don't support menus.
|
||||||
void drawMenu(WolfEngine engine) {}
|
void drawMenu(WolfEngine engine) {}
|
||||||
|
|
||||||
|
/// Draws a small FPS diagnostic in the top-left corner.
|
||||||
|
///
|
||||||
|
/// Backends can override this to render text in their native format.
|
||||||
|
void drawFpsOverlay(WolfEngine engine) {}
|
||||||
|
|
||||||
|
/// Returns a compact FPS label used by renderer-specific overlays.
|
||||||
|
String fpsLabel(WolfEngine engine) => 'FPS ${engine.fps.round()}';
|
||||||
|
|
||||||
/// Plots a VGA image into this backend's HUD coordinate space.
|
/// Plots a VGA image into this backend's HUD coordinate space.
|
||||||
///
|
///
|
||||||
/// Coordinates are in the original 320x200 HUD space. Backends that support
|
/// Coordinates are in the original 320x200 HUD space. Backends that support
|
||||||
|
|||||||
@@ -333,6 +333,18 @@ class SixelRenderer extends CliRendererBackend<String> {
|
|||||||
drawStandardVgaHud(engine);
|
drawStandardVgaHud(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawFpsOverlay(WolfEngine engine) {
|
||||||
|
const int panelColor = 0;
|
||||||
|
const int textColor = 15;
|
||||||
|
final String label = fpsLabel(engine);
|
||||||
|
final int textWidth = WolfMenuFont.measureTextWidth(label, 1);
|
||||||
|
final int panelWidth = (textWidth + 8).clamp(16, 96);
|
||||||
|
|
||||||
|
_fillRect320(2, 2, panelWidth, 10, panelColor);
|
||||||
|
_drawMenuText(label, 4, 3, textColor, scale: 1);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
/// Blits a VGA image into the Sixel index buffer HUD space (320x200).
|
/// Blits a VGA image into the Sixel index buffer HUD space (320x200).
|
||||||
void blitHudVgaImage(VgaImage image, int startX320, int startY200) {
|
void blitHudVgaImage(VgaImage image, int startX320, int startY200) {
|
||||||
|
|||||||
@@ -121,6 +121,19 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
|||||||
drawStandardVgaHud(engine);
|
drawStandardVgaHud(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawFpsOverlay(WolfEngine engine) {
|
||||||
|
const int panelX = 2;
|
||||||
|
const int panelY = 2;
|
||||||
|
const int panelW = 72;
|
||||||
|
const int panelH = 10;
|
||||||
|
final int panelColor = ColorPalette.vga32Bit[0];
|
||||||
|
final int textColor = ColorPalette.vga32Bit[15];
|
||||||
|
|
||||||
|
_fillMenuPanel(panelX, panelY, panelW, panelH, panelColor);
|
||||||
|
_drawMenuText(fpsLabel(engine), panelX + 3, panelY + 1, textColor);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
/// Blits a VGA image into the software framebuffer HUD space (320x200).
|
/// Blits a VGA image into the software framebuffer HUD space (320x200).
|
||||||
void blitHudVgaImage(VgaImage image, int startX320, int startY200) {
|
void blitHudVgaImage(VgaImage image, int startX320, int startY200) {
|
||||||
|
|||||||
182
packages/wolf_3d_renderer/lib/wolf_3d_glsl_renderer.dart
Normal file
182
packages/wolf_3d_renderer/lib/wolf_3d_glsl_renderer.dart
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/// Flutter widget that applies a GLSL post-process over Wolf3D frames.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||||
|
import 'package:wolf_3d_dart/wolf_3d_renderer.dart';
|
||||||
|
import 'package:wolf_3d_renderer/base_renderer.dart';
|
||||||
|
import 'package:wolf_3d_renderer/wolf_3d_asset_painter.dart';
|
||||||
|
|
||||||
|
/// Displays software-rendered frames through a GLSL post-processing pass.
|
||||||
|
class WolfGlslRenderer extends BaseWolfRenderer {
|
||||||
|
/// Callback when shader loading fails and software fallback should be used.
|
||||||
|
final VoidCallback? onUnavailable;
|
||||||
|
|
||||||
|
/// Creates a GLSL renderer bound to [engine].
|
||||||
|
const WolfGlslRenderer({
|
||||||
|
required super.engine,
|
||||||
|
this.onUnavailable,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WolfGlslRenderer> createState() => _WolfGlslRendererState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WolfGlslRendererState extends BaseWolfRendererState<WolfGlslRenderer> {
|
||||||
|
static const int _renderWidth = 320;
|
||||||
|
static const int _renderHeight = 200;
|
||||||
|
|
||||||
|
final SoftwareRenderer _renderer = SoftwareRenderer();
|
||||||
|
|
||||||
|
ui.Image? _renderedFrame;
|
||||||
|
ui.FragmentProgram? _shaderProgram;
|
||||||
|
ui.FragmentShader? _shader;
|
||||||
|
bool _isRendering = false;
|
||||||
|
bool _isShaderUnavailable = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.engine.frameBuffer.width != _renderWidth ||
|
||||||
|
widget.engine.frameBuffer.height != _renderHeight) {
|
||||||
|
widget.engine.setFrameBuffer(_renderWidth, _renderHeight);
|
||||||
|
}
|
||||||
|
_loadShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get scaffoldColor => widget.engine.difficulty == null
|
||||||
|
? _colorFromRgb(widget.engine.menuBackgroundRgb)
|
||||||
|
: const Color.fromARGB(255, 4, 64, 64);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_renderedFrame?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performRender() {
|
||||||
|
if (_isRendering) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isRendering = true;
|
||||||
|
|
||||||
|
final FrameBuffer frameBuffer = widget.engine.frameBuffer;
|
||||||
|
_renderer.render(widget.engine);
|
||||||
|
|
||||||
|
ui.decodeImageFromPixels(
|
||||||
|
frameBuffer.pixels.buffer.asUint8List(),
|
||||||
|
frameBuffer.width,
|
||||||
|
frameBuffer.height,
|
||||||
|
ui.PixelFormat.rgba8888,
|
||||||
|
(ui.Image image) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_renderedFrame?.dispose();
|
||||||
|
_renderedFrame = image;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
image.dispose();
|
||||||
|
}
|
||||||
|
_isRendering = false;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildViewport(BuildContext context) {
|
||||||
|
if (_renderedFrame == null) {
|
||||||
|
return const CircularProgressIndicator(color: Colors.white24);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isShaderUnavailable || _shader == null) {
|
||||||
|
// Keep frames visible even if GLSL initialization failed.
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 4 / 3,
|
||||||
|
child: WolfAssetPainter.frame(_renderedFrame),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 4 / 3,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: _GlslFramePainter(
|
||||||
|
frame: _renderedFrame!,
|
||||||
|
shader: _shader!,
|
||||||
|
),
|
||||||
|
child: const SizedBox.expand(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadShader() async {
|
||||||
|
try {
|
||||||
|
final ui.FragmentProgram program = await ui.FragmentProgram.fromAsset(
|
||||||
|
'packages/wolf_3d_renderer/shaders/wolf_world.frag',
|
||||||
|
);
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_shaderProgram = program;
|
||||||
|
_shader = _shaderProgram!.fragmentShader();
|
||||||
|
_isShaderUnavailable = false;
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isShaderUnavailable = true;
|
||||||
|
});
|
||||||
|
widget.onUnavailable?.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _colorFromRgb(int rgb) {
|
||||||
|
return Color(0xFF000000 | (rgb & 0x00FFFFFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GlslFramePainter extends CustomPainter {
|
||||||
|
final ui.Image frame;
|
||||||
|
final ui.FragmentShader shader;
|
||||||
|
|
||||||
|
_GlslFramePainter({
|
||||||
|
required this.frame,
|
||||||
|
required this.shader,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final double texelX = frame.width > 0 ? 1.0 / frame.width : 1.0;
|
||||||
|
final double texelY = frame.height > 0 ? 1.0 / frame.height : 1.0;
|
||||||
|
shader
|
||||||
|
..setFloat(0, size.width)
|
||||||
|
..setFloat(1, size.height)
|
||||||
|
..setFloat(2, texelX)
|
||||||
|
..setFloat(3, texelY)
|
||||||
|
..setImageSampler(0, frame);
|
||||||
|
|
||||||
|
final Paint paint = Paint()
|
||||||
|
..shader = shader
|
||||||
|
..filterQuality = FilterQuality.none;
|
||||||
|
|
||||||
|
canvas.drawRect(Offset.zero & size, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant _GlslFramePainter oldDelegate) {
|
||||||
|
return oldDelegate.frame != frame || oldDelegate.shader != shader;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,8 @@ dev_dependencies:
|
|||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
flutter:
|
||||||
|
shaders:
|
||||||
|
- shaders/wolf_world.frag
|
||||||
|
|
||||||
# To add assets to your package, add an assets section, like this:
|
# To add assets to your package, add an assets section, like this:
|
||||||
# assets:
|
# assets:
|
||||||
|
|||||||
43
packages/wolf_3d_renderer/shaders/wolf_world.frag
Normal file
43
packages/wolf_3d_renderer/shaders/wolf_world.frag
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include <flutter/runtime_effect.glsl>
|
||||||
|
|
||||||
|
uniform vec2 uResolution;
|
||||||
|
uniform vec2 uTexel;
|
||||||
|
uniform sampler2D uTexture;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
float luma(vec3 color) {
|
||||||
|
return dot(color, vec3(0.299, 0.587, 0.114));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = FlutterFragCoord().xy / uResolution;
|
||||||
|
vec4 centerSample = texture(uTexture, uv);
|
||||||
|
|
||||||
|
vec3 sampleN = texture(uTexture, uv + vec2(0.0, -uTexel.y)).rgb;
|
||||||
|
vec3 sampleS = texture(uTexture, uv + vec2(0.0, uTexel.y)).rgb;
|
||||||
|
vec3 sampleE = texture(uTexture, uv + vec2(uTexel.x, 0.0)).rgb;
|
||||||
|
vec3 sampleW = texture(uTexture, uv + vec2(-uTexel.x, 0.0)).rgb;
|
||||||
|
|
||||||
|
float lumaCenter = luma(centerSample.rgb);
|
||||||
|
float lumaMin = min(
|
||||||
|
lumaCenter,
|
||||||
|
min(min(luma(sampleN), luma(sampleS)), min(luma(sampleE), luma(sampleW)))
|
||||||
|
);
|
||||||
|
float lumaMax = max(
|
||||||
|
lumaCenter,
|
||||||
|
max(max(luma(sampleN), luma(sampleS)), max(luma(sampleE), luma(sampleW)))
|
||||||
|
);
|
||||||
|
|
||||||
|
float edgeSpan = max(lumaMax - lumaMin, 0.0001);
|
||||||
|
float edgeAmount = smoothstep(0.03, 0.18, edgeSpan);
|
||||||
|
vec3 neighborhoodAvg = (sampleN + sampleS + sampleE + sampleW) * 0.25;
|
||||||
|
vec3 aaColor = mix(centerSample.rgb, neighborhoodAvg, edgeAmount * 0.45);
|
||||||
|
|
||||||
|
vec2 centered = uv - 0.5;
|
||||||
|
float vignette = 1.0 - dot(centered, centered) * 0.35;
|
||||||
|
vignette = clamp(vignette, 0.75, 1.0);
|
||||||
|
|
||||||
|
vec3 color = aaColor * vignette;
|
||||||
|
fragColor = vec4(color, centerSample.a);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user