Skip to content

Commit 64163e6

Browse files
committed
feat: add ClearlyBugShaderBuilder and corresponding shader file for a "Happy Accident" effect
1 parent 4f005c9 commit 64163e6

File tree

5 files changed

+170
-5
lines changed

5 files changed

+170
-5
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'package:flutter/material.dart';
2+
import 'dart:ui';
3+
import 'shader_builder.dart';
4+
5+
class ClearlyBugShaderBuilder extends CustomShaderBuilder {
6+
const ClearlyBugShaderBuilder();
7+
8+
@override
9+
bool get requiresImageSampler => false;
10+
11+
@override
12+
Duration? get animationDuration => null; // Unbounded animation
13+
14+
@override
15+
void setUniforms(FragmentShader shader, Size size, double time) {
16+
shader
17+
..setFloat(0, size.width)
18+
..setFloat(1, size.height)
19+
..setFloat(2, time);
20+
}
21+
22+
@override
23+
Widget buildShader(
24+
ShaderMetadata metadata,
25+
FragmentShader shader,
26+
Size size,
27+
double time,
28+
Widget? child,
29+
) {
30+
return ColoredBox(
31+
color: Colors.black,
32+
child: CustomPaint(
33+
size: Size.infinite,
34+
painter: _ClearlyBugShaderPainter(shader),
35+
),
36+
);
37+
}
38+
39+
@override
40+
Widget? childBuilder(BuildContext context) {
41+
return null;
42+
}
43+
}
44+
45+
class _ClearlyBugShaderPainter extends CustomPainter {
46+
final FragmentShader shader;
47+
48+
_ClearlyBugShaderPainter(this.shader);
49+
50+
@override
51+
void paint(Canvas canvas, Size size) {
52+
canvas.drawRect(
53+
Rect.fromLTWH(0, 0, size.width, size.height),
54+
Paint()..shader = shader,
55+
);
56+
}
57+
58+
@override
59+
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
60+
}

lib/main.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:shaders/widgets/widgets.dart';
88

99
import 'shader_builder.dart';
1010
import 'crt_shader_builder.dart';
11+
import 'clearly_bug_shader_builder.dart';
1112
import 'noise_overlay_shader_builder.dart';
1213
import 'noise_shader_builder.dart';
1314
import 'ntsc_shader_builder.dart';
@@ -101,6 +102,16 @@ final shaders = [
101102
builder: const RingsShaderBuilder(),
102103
path: 'rings-shader',
103104
),
105+
ShaderInfo(
106+
name: 'Clearly a Bug',
107+
assetKey: 'shaders/clearly_bug_shader.frag',
108+
description: 'A "Happy Accident" raymarching shader with fractal patterns and beautiful lighting.',
109+
sourceUrl: 'https://www.shadertoy.com/view/33cGDj',
110+
author: 'Various (see comments)',
111+
dateAdded: DateTime(2025, 7, 29),
112+
builder: const ClearlyBugShaderBuilder(),
113+
path: 'clearly-bug-shader',
114+
),
104115
];
105116

106117
// Helper function to create URL-safe shader names

lib/widgets/shader_animation_view.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'package:shaders/tv_test_screen.dart';
44
import '../main.dart';
55

66
/// A widget that handles the animation and rendering of a shader.
7-
///
7+
///
88
/// This widget manages the animation controller and coordinates with the shader builder
99
/// to render the shader effect with proper timing and animation handling.
1010
class ShaderAnimationView extends StatefulWidget {
@@ -19,8 +19,7 @@ class ShaderAnimationView extends StatefulWidget {
1919
State<ShaderAnimationView> createState() => _ShaderAnimationViewState();
2020
}
2121

22-
class _ShaderAnimationViewState extends State<ShaderAnimationView>
23-
with SingleTickerProviderStateMixin {
22+
class _ShaderAnimationViewState extends State<ShaderAnimationView> with SingleTickerProviderStateMixin {
2423
late final AnimationController _controller;
2524

2625
@override
@@ -64,7 +63,7 @@ class _ShaderAnimationViewState extends State<ShaderAnimationView>
6463
(context, shader, _) {
6564
final duration = widget.shaderInfo.builder.animationDuration;
6665
double timeValue;
67-
66+
6867
if (duration != null) {
6968
// Bounded animation - use animated value between 0-1
7069
final animation = TweenSequence<double>([
@@ -103,7 +102,7 @@ class _ShaderAnimationViewState extends State<ShaderAnimationView>
103102
);
104103
},
105104
);
106-
}
105+
},
107106
);
108107
}
109108
}

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ flutter:
4343
- shaders/noise_shader.frag
4444
- shaders/ntsc_shader.frag
4545
- shaders/rings_shader.frag
46+
- shaders/clearly_bug_shader.frag

shaders/clearly_bug_shader.frag

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// CC0: Clearly a bug
2+
// A "Happy Accident" Shader
3+
4+
// Twigl: https://twigl.app?ol=true&ss=-OUOudmBPJ57CIb7rAxS
5+
// Modified to compile for Flutter
6+
// https://www.shadertoy.com/view/33cGDj
7+
8+
#version 460 core
9+
precision mediump float;
10+
11+
#include <flutter/runtime_effect.glsl>
12+
13+
uniform vec2 iResolution;
14+
uniform float iTime;
15+
16+
out vec4 fragColor;
17+
18+
// This shader uses a technique called "raymarching" to render 3D
19+
// Think of it like casting rays from your eye through each pixel into a 3D world,
20+
// then stepping along each ray until we hit something interesting.
21+
//
22+
// Key concepts for C developers:
23+
// - vec4/vec3/vec2: Like structs with x,y,z,w components (SIMD-style)
24+
// - Swizzling: p.xy means "give me just the x,y parts of vector p"
25+
// - mat2(): Creates a 2x2 rotation matrix from an angle
26+
// - All math operations work on vectors component-wise
27+
//
28+
// ATTRIBUTION: Shader techniques inspired by (alphabetical):
29+
// @byt3_m3chanic
30+
// @FabriceNeyrat2
31+
// @iq
32+
// @shane
33+
// @XorDev
34+
// + many more
35+
36+
void main() {
37+
vec2 C = FlutterFragCoord().xy;
38+
vec4 O;
39+
40+
float i = 0.0; // Loop counter (starts at 0)
41+
float d = 0.0; // Distance to nearest surface
42+
float z = fract(dot(C,sin(C)))-0.5; // Ray distance + noise for anti-banding
43+
44+
vec4 o = vec4(0.0); // Accumulated color/lighting
45+
vec4 p = vec4(0.0); // Current 3D position along ray
46+
47+
vec2 r = iResolution.xy; // Screen resolution
48+
49+
for(int iter = 0; iter < 77; iter++) {
50+
i = float(iter);
51+
52+
// Convert 2D pixel to 3D ray direction
53+
p = vec4(z*normalize(vec3(C-0.5*r,r.y)), 0.1*iTime);
54+
55+
// Move through 3D space over time
56+
p.z += iTime;
57+
58+
// Save position for lighting calculations
59+
O = p;
60+
61+
// Apply rotation matrices to create fractal patterns
62+
// (These transform the 3D coordinates in interesting ways)
63+
p.xy *= mat2(cos(2.0+O.z+vec4(0,11,33,0)));
64+
65+
// This was originally a bug in the matrix calculation
66+
// The incorrect transformation created an unexpectedly interesting pattern
67+
// Bob Ross would call this a "happy little accident"
68+
p.xy *= mat2(cos(O+vec4(0,11,33,0)));
69+
70+
// Calculate color based on position and space distortion
71+
// The sin() creates a nice looking palette, division by dot() creates falloff
72+
O = (1.0+sin(0.5*O.z+length(p-O)+vec4(0,4,3,6)))
73+
/ (0.5+2.0*dot(O.xy,O.xy));
74+
75+
// Domain repetition, repeats the single line and the 2 planes infinitely
76+
p = abs(fract(p)-0.5);
77+
78+
// Calculate distance to nearest surface
79+
// This combines a cylinder (length(p.xy)-.125) with 2 planes (min(p.x,p.y))
80+
d = abs(min(length(p.xy)-0.125,min(p.x,p.y)+1e-3))+1e-3;
81+
82+
// Add lighting contribution (brighter when closer to surfaces)
83+
o += O.w/d*O;
84+
85+
// Step forward (larger steps when far from surfaces)
86+
z += 0.6*d;
87+
}
88+
89+
// tanh() compresses the accumulated brightness to 0-1 range
90+
// (Like HDR tone mapping in photography)
91+
O = tanh(o/2e4);
92+
93+
fragColor = O;
94+
}

0 commit comments

Comments
 (0)