summaryrefslogtreecommitdiff
path: root/themes
diff options
context:
space:
mode:
authorMatt Kosarek <matt.kosarek@canonical.com>2026-02-03 08:13:30 -0500
committerMatt Kosarek <matt.kosarek@canonical.com>2026-02-03 08:13:30 -0500
commitb4e8ae9731eca175cd4e6e75a20da87ff86eb91f (patch)
tree95b1156031ed70e6bb91f5c58a03c75ad3722593 /themes
parent1b0fbb1818d6a9cd721366909275aaefb7de4c64 (diff)
feature: improve snowflake rendererHEADmaster
Diffstat (limited to 'themes')
-rw-r--r--themes/dist/output.js6
-rwxr-xr-xthemes/dist/output.wasmbin101714 -> 101674 bytes
-rw-r--r--themes/meson.build6
-rw-r--r--themes/src/_shaders/snowflake.frag73
-rw-r--r--themes/src/_shaders/snowflake.vert30
-rw-r--r--themes/src/shaders/snowflake_frag.cpp76
-rw-r--r--themes/src/shaders/snowflake_frag.h4
-rw-r--r--themes/src/shaders/snowflake_vert.cpp33
-rw-r--r--themes/src/shaders/snowflake_vert.h4
-rw-r--r--themes/src/winter/snowflake.cpp204
-rw-r--r--themes/src/winter/snowflake.h33
11 files changed, 357 insertions, 112 deletions
diff --git a/themes/dist/output.js b/themes/dist/output.js
index 011ac68..b19a1a3 100644
--- a/themes/dist/output.js
+++ b/themes/dist/output.js
@@ -2875,6 +2875,10 @@ function dbg(...args) {
GL.postDrawHandleClientVertexAttribBindings();
};
+ var _glDrawArraysInstanced = (mode, first, count, primcount) => {
+ GLctx.drawArraysInstanced(mode, first, count, primcount);
+ };
+
var _glDrawElements = (mode, count, type, indices) => {
var buf;
var vertexes = 0;
@@ -3342,6 +3346,8 @@ var wasmImports = {
/** @export */
glDrawArrays: _glDrawArrays,
/** @export */
+ glDrawArraysInstanced: _glDrawArraysInstanced,
+ /** @export */
glDrawElements: _glDrawElements,
/** @export */
glEnable: _glEnable,
diff --git a/themes/dist/output.wasm b/themes/dist/output.wasm
index 4c88053..671e7bb 100755
--- a/themes/dist/output.wasm
+++ b/themes/dist/output.wasm
Binary files differ
diff --git a/themes/meson.build b/themes/meson.build
index 1e4c9f1..563ddc6 100644
--- a/themes/meson.build
+++ b/themes/meson.build
@@ -38,6 +38,8 @@ sources = files(
'src/shaders/renderer3d_vert.cpp',
'src/shaders/sun_frag.cpp',
'src/shaders/sun_vert.cpp',
+ 'src/shaders/snowflake_frag.cpp',
+ 'src/shaders/snowflake_vert.cpp',
# Autumn theme
'src/autumn/autumn_theme.cpp',
@@ -80,7 +82,9 @@ shader_inputs = files(
'src/_shaders/renderer3d.frag',
'src/_shaders/renderer3d.vert',
'src/_shaders/sun.frag',
- 'src/_shaders/sun.vert'
+ 'src/_shaders/sun.vert',
+ 'src/_shaders/snowflake.frag',
+ 'src/_shaders/snowflake.vert'
)
# Custom target that runs whenever shader files change
diff --git a/themes/src/_shaders/snowflake.frag b/themes/src/_shaders/snowflake.frag
new file mode 100644
index 0000000..d887cf5
--- /dev/null
+++ b/themes/src/_shaders/snowflake.frag
@@ -0,0 +1,73 @@
+// Procedural star fragment shader
+varying lowp vec2 vUV;
+varying lowp float vSeed;
+varying lowp float vScale;
+
+const lowp float PI = 3.14159265359;
+
+// Simple hash function for deterministic randomness
+lowp float hash(lowp float n) {
+ return fract(sin(n) * 43758.5453123);
+}
+
+// Generate star pattern procedurally
+lowp float starPattern(lowp vec2 uv) {
+ lowp float dist = length(uv);
+ lowp float angle = atan(uv.y, uv.x);
+
+ // Number of star points (4 or 5)
+ lowp float numPoints = 4.0 + floor(hash(vSeed) * 2.0);
+
+ // Create sharp star with triangular points
+ lowp float angleStep = 2.0 * PI / numPoints;
+ lowp float currentAngle = mod(angle + PI / numPoints, angleStep) - angleStep * 0.5;
+
+ // Create triangular points - distance from center to edge varies linearly with angle
+ lowp float normalizedAngle = abs(currentAngle) / (angleStep * 0.5);
+
+ // Outer tip radius and inner valley radius
+ lowp float tipRadius = 0.5;
+ lowp float valleyRadius = 0.15;
+
+ // Linear interpolation creates sharp triangular points
+ lowp float rayEdge = mix(tipRadius, valleyRadius, normalizedAngle);
+
+ // Hard cutoff for sharp edges
+ lowp float star = step(dist, rayEdge);
+
+ return star;
+}
+
+void main() {
+ lowp float pattern = starPattern(vUV);
+
+ // Color variation based on seed - white and blue tints
+ lowp float colorVar = hash(vSeed * 3.0);
+ lowp vec3 starColor;
+
+ if (colorVar < 0.5) {
+ // Pure white
+ starColor = vec3(1.0, 1.0, 1.0);
+ } else if (colorVar < 0.75) {
+ // Light blue tint
+ starColor = vec3(0.9, 0.95, 1.0);
+ } else {
+ // Stronger blue tint
+ starColor = vec3(0.85, 0.92, 1.0);
+ }
+
+ // Scale alpha based on size - smaller stars are more transparent
+ // Normalize scale from range [16, 48] to [0, 1]
+ lowp float sizeRatio = (vScale - 16.0) / (48.0 - 16.0);
+ // Map to alpha range [0.3, 1.0] - smaller stars at 30% opacity, larger at 100%
+ lowp float alpha = mix(0.3, 1.0, sizeRatio);
+
+ lowp vec4 color = vec4(starColor, pattern * alpha);
+
+ // Discard fully transparent pixels for performance
+ if (color.a < 0.01) {
+ discard;
+ }
+
+ gl_FragColor = color;
+}
diff --git a/themes/src/_shaders/snowflake.vert b/themes/src/_shaders/snowflake.vert
new file mode 100644
index 0000000..7cbfb99
--- /dev/null
+++ b/themes/src/_shaders/snowflake.vert
@@ -0,0 +1,30 @@
+// Instanced snowflake vertex shader
+attribute vec2 position; // Base quad vertex position
+attribute vec2 instancePos; // Per-instance: snowflake center position
+attribute float instanceRot; // Per-instance: rotation angle
+attribute float instanceScale; // Per-instance: size scale
+attribute float instanceSeed; // Per-instance: random seed for variation
+
+uniform mat4 projection;
+uniform mat4 model;
+
+varying lowp vec2 vUV; // UV coordinates for fragment shader
+varying lowp float vSeed; // Pass seed to fragment shader
+varying lowp float vScale; // Pass scale to fragment shader
+
+void main() {
+ // Rotate and scale the base quad
+ float c = cos(instanceRot);
+ float s = sin(instanceRot);
+ mat2 rotation = mat2(c, s, -s, c);
+
+ vec2 rotatedPos = rotation * (position * instanceScale);
+ vec2 worldPos = instancePos + rotatedPos;
+
+ gl_Position = projection * model * vec4(worldPos, 0.0, 1.0);
+
+ // Pass UV in range [-1, 1] for procedural generation
+ vUV = position;
+ vSeed = instanceSeed;
+ vScale = instanceScale;
+}
diff --git a/themes/src/shaders/snowflake_frag.cpp b/themes/src/shaders/snowflake_frag.cpp
new file mode 100644
index 0000000..52d535d
--- /dev/null
+++ b/themes/src/shaders/snowflake_frag.cpp
@@ -0,0 +1,76 @@
+#include "snowflake_frag.h"
+
+const char* shader_snowflake_frag = "// Procedural star fragment shader \n"
+"varying lowp vec2 vUV; \n"
+"varying lowp float vSeed; \n"
+"varying lowp float vScale; \n"
+" \n"
+"const lowp float PI = 3.14159265359; \n"
+" \n"
+"// Simple hash function for deterministic randomness \n"
+"lowp float hash(lowp float n) { \n"
+" return fract(sin(n) * 43758.5453123); \n"
+"} \n"
+" \n"
+"// Generate star pattern procedurally \n"
+"lowp float starPattern(lowp vec2 uv) { \n"
+" lowp float dist = length(uv); \n"
+" lowp float angle = atan(uv.y, uv.x); \n"
+" \n"
+" // Number of star points (4 or 5) \n"
+" lowp float numPoints = 4.0 + floor(hash(vSeed) * 2.0); \n"
+" \n"
+" // Create sharp star with triangular points \n"
+" lowp float angleStep = 2.0 * PI / numPoints; \n"
+" lowp float currentAngle = mod(angle + PI / numPoints, angleStep) - angleStep * 0.5; \n"
+" \n"
+" // Create triangular points - distance from center to edge varies linearly with angle \n"
+" lowp float normalizedAngle = abs(currentAngle) / (angleStep * 0.5); \n"
+" \n"
+" // Outer tip radius and inner valley radius \n"
+" lowp float tipRadius = 0.5; \n"
+" lowp float valleyRadius = 0.15; \n"
+" \n"
+" // Linear interpolation creates sharp triangular points \n"
+" lowp float rayEdge = mix(tipRadius, valleyRadius, normalizedAngle); \n"
+" \n"
+" // Hard cutoff for sharp edges \n"
+" lowp float star = step(dist, rayEdge); \n"
+" \n"
+" return star; \n"
+"} \n"
+" \n"
+"void main() { \n"
+" lowp float pattern = starPattern(vUV); \n"
+" \n"
+" // Color variation based on seed - white and blue tints \n"
+" lowp float colorVar = hash(vSeed * 3.0); \n"
+" lowp vec3 starColor; \n"
+" \n"
+" if (colorVar < 0.5) { \n"
+" // Pure white \n"
+" starColor = vec3(1.0, 1.0, 1.0); \n"
+" } else if (colorVar < 0.75) { \n"
+" // Light blue tint \n"
+" starColor = vec3(0.9, 0.95, 1.0); \n"
+" } else { \n"
+" // Stronger blue tint \n"
+" starColor = vec3(0.85, 0.92, 1.0); \n"
+" } \n"
+" \n"
+" // Scale alpha based on size - smaller stars are more transparent \n"
+" // Normalize scale from range [16, 48] to [0, 1] \n"
+" lowp float sizeRatio = (vScale - 16.0) / (48.0 - 16.0); \n"
+" // Map to alpha range [0.3, 1.0] - smaller stars at 30% opacity, larger at 100% \n"
+" lowp float alpha = mix(0.3, 1.0, sizeRatio); \n"
+" \n"
+" lowp vec4 color = vec4(starColor, pattern * alpha); \n"
+" \n"
+" // Discard fully transparent pixels for performance \n"
+" if (color.a < 0.01) { \n"
+" discard; \n"
+" } \n"
+" \n"
+" gl_FragColor = color; \n"
+"} \n"
+" \n";
diff --git a/themes/src/shaders/snowflake_frag.h b/themes/src/shaders/snowflake_frag.h
new file mode 100644
index 0000000..b34328f
--- /dev/null
+++ b/themes/src/shaders/snowflake_frag.h
@@ -0,0 +1,4 @@
+#ifndef SHADER_SNOWFLAKE_FRAG
+#define SHADER_SNOWFLAKE_FRAG
+extern const char* shader_snowflake_frag;
+#endif
diff --git a/themes/src/shaders/snowflake_vert.cpp b/themes/src/shaders/snowflake_vert.cpp
new file mode 100644
index 0000000..199009d
--- /dev/null
+++ b/themes/src/shaders/snowflake_vert.cpp
@@ -0,0 +1,33 @@
+#include "snowflake_vert.h"
+
+const char* shader_snowflake_vert = "// Instanced snowflake vertex shader \n"
+"attribute vec2 position; // Base quad vertex position \n"
+"attribute vec2 instancePos; // Per-instance: snowflake center position \n"
+"attribute float instanceRot; // Per-instance: rotation angle \n"
+"attribute float instanceScale; // Per-instance: size scale \n"
+"attribute float instanceSeed; // Per-instance: random seed for variation \n"
+" \n"
+"uniform mat4 projection; \n"
+"uniform mat4 model; \n"
+" \n"
+"varying lowp vec2 vUV; // UV coordinates for fragment shader \n"
+"varying lowp float vSeed; // Pass seed to fragment shader \n"
+"varying lowp float vScale; // Pass scale to fragment shader \n"
+" \n"
+"void main() { \n"
+" // Rotate and scale the base quad \n"
+" float c = cos(instanceRot); \n"
+" float s = sin(instanceRot); \n"
+" mat2 rotation = mat2(c, s, -s, c); \n"
+" \n"
+" vec2 rotatedPos = rotation * (position * instanceScale); \n"
+" vec2 worldPos = instancePos + rotatedPos; \n"
+" \n"
+" gl_Position = projection * model * vec4(worldPos, 0.0, 1.0); \n"
+" \n"
+" // Pass UV in range [-1, 1] for procedural generation \n"
+" vUV = position; \n"
+" vSeed = instanceSeed; \n"
+" vScale = instanceScale; \n"
+"} \n"
+" \n";
diff --git a/themes/src/shaders/snowflake_vert.h b/themes/src/shaders/snowflake_vert.h
new file mode 100644
index 0000000..36bf8b0
--- /dev/null
+++ b/themes/src/shaders/snowflake_vert.h
@@ -0,0 +1,4 @@
+#ifndef SHADER_SNOWFLAKE_VERT
+#define SHADER_SNOWFLAKE_VERT
+extern const char* shader_snowflake_vert;
+#endif
diff --git a/themes/src/winter/snowflake.cpp b/themes/src/winter/snowflake.cpp
index 4ce8f3a..26b6549 100644
--- a/themes/src/winter/snowflake.cpp
+++ b/themes/src/winter/snowflake.cpp
@@ -2,6 +2,9 @@
#include "../renderer_2d.h"
#include "../mathlib.h"
#include "../list.h"
+#include "../shader.h"
+#include "../shaders/snowflake_vert.h"
+#include "../shaders/snowflake_frag.h"
#include <cstdio>
/*
@@ -11,12 +14,10 @@
- Windstream that blows a certain selection of snowflakes in a loop-dee-loop pattern
- Snowflakes that land on the ground and melt
- Snowflakes that spin along the Y-axis for a three dimensional effect
-
+
*/
-const Vector4 snowColor = Vector4(1.0, 0.98, 0.98, 1);
-const Vector2 NUM_ARMS_RANGE = Vector2(6.f, 8.f);
-const Vector2 RADIUS_RANGE = Vector2(8.f, 32.f);
+const Vector2 SCALE_RANGE = Vector2(16.f, 48.f);
const Vector2 VELOCITY_RANGE_X = Vector2(-10.f, 10.f);
const Vector2 VELOCITY_RANGE_Y = Vector2(-100.f, -85.f);
const Vector2 ROTATION_VELOCITY_RANGE = Vector2(-PI / 8.f, PI / 8.f);
@@ -24,111 +25,88 @@ const Vector2 WIND_VELOCITY_RANGE_X = Vector2(-3.f, 3.f);
const Vector2 WIND_VELOCITY_RANGE_Y = Vector2(3.f, 10.f);
const f32 GRAVITY = 5.f;
-inline void generateSnowflakeArm(f32 width, f32 height, f32 angle, matte::List<Vertex2D>* vertices, Mat4x4 transform = Mat4x4()) {
- f32 halfWidth = width / 2.f;
- Vector2 leftStart = transform * Vector2(-halfWidth, 0).rotate(angle);
- Vector2 leftEnd = transform * Vector2(-halfWidth, height).rotate(angle);
- Vector2 rightStart = transform * Vector2(halfWidth, 0).rotate(angle);
- Vector2 rightEnd = transform * Vector2(halfWidth, height).rotate(angle);
-
- vertices->add({ leftStart, snowColor, Mat4x4() });
- vertices->add({ leftEnd, snowColor, Mat4x4() });
- vertices->add({ rightEnd, snowColor, Mat4x4() });
- vertices->add({ leftStart, snowColor, Mat4x4() });
- vertices->add({ rightEnd, snowColor, Mat4x4() });
- vertices->add({ rightStart, snowColor, Mat4x4() });
-}
-
-/**
- Fills in the vertices array vertices that represent a snowflake shape. The snowflake shape consists
- of numArms jutting out of the center radially. The center of the flake is connected. The radius is
- used to determine the length of the arms. The first third of each arm is barren, after which branches
- extends on either side of the arm at an angle of about 60 degrees. Each branch can itself have tiny
- sub branches jutting out of it, but these should be not nearly as large as the regular branches.
+inline void initFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) {
+ ud->scale = randomFloatBetween(SCALE_RANGE.x, SCALE_RANGE.y);
+ ud->seed = randomFloatBetween(0.f, 1000.f);
- With all of this in mind, we should be able to build a convincing snowflake.
+ // Scale velocity based on star size - larger stars fall faster
+ f32 sizeRatio = (ud->scale - SCALE_RANGE.x) / (SCALE_RANGE.y - SCALE_RANGE.x);
+ f32 velocityY = VELOCITY_RANGE_Y.x + (VELOCITY_RANGE_Y.y - VELOCITY_RANGE_Y.x) * (0.5f + sizeRatio * 0.5f);
- :param vertices List of vertices to be filled in
- :param numArms Number of arms radially sticking out of the snowflake
- :param radius Length of the snowflake arms
- */
-inline void generateSnowflakeShape(matte::List<Vertex2D>* vertices, i32 numArms, f32 radius, f32 armWidthRatio = 0.08f) {
- f32 innerRadius = 0;
- f32 outerRadius = 2 * radius;
- f32 dx = ((2 * PI) / numArms);
- for (i32 armIndex = 0; armIndex < numArms; armIndex++) {
- f32 armAngle = dx * armIndex;
- generateSnowflakeArm(armWidthRatio * radius, radius, armAngle, vertices);
- f32 armLeftAngle = DEG_TO_RAD(60.f);
- f32 armRightAngle = DEG_TO_RAD(-60.f);
-
- const i32 NUM_SUB_ARMS = 4;
- for (i32 subArmIndex = 0; subArmIndex < NUM_SUB_ARMS; subArmIndex++) {
- f32 height = (radius / static_cast<f32>(subArmIndex));
- f32 width = (armWidthRatio / (subArmIndex + 1)) * height;
- f32 transY = (radius / (NUM_SUB_ARMS + 1)) * (subArmIndex + 1);
- Vector2 translation = Vector2(0, transY).rotate(armAngle);
- generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armLeftAngle));
- generateSnowflakeArm(width, height, armAngle, vertices, Mat4x4().translateByVec2(translation).rotate2D(armRightAngle));
- }
- }
-}
-
-inline void initFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) {
- ud->radius = randomFloatBetween(RADIUS_RANGE.x, RADIUS_RANGE.y);
- ud->vtxIdx = renderer->vertices.numElements;
- generateSnowflakeShape(&renderer->vertices,
- randomFloatBetween(NUM_ARMS_RANGE.x, NUM_ARMS_RANGE.y),
- ud->radius);
-
- ud->numVertices = renderer->vertices.numElements - ud->vtxIdx;
- ud->velocity = Vector2(randomFloatBetween(VELOCITY_RANGE_X.x, VELOCITY_RANGE_X.y), randomFloatBetween(VELOCITY_RANGE_Y.x, VELOCITY_RANGE_Y.y));
+ ud->velocity = Vector2(randomFloatBetween(VELOCITY_RANGE_X.x, VELOCITY_RANGE_X.y), velocityY);
ud->position = Vector2(randomFloatBetween(0, renderer->xMax), randomFloatBetween(renderer->yMax, 4 * renderer->yMax));
ud->rotateVelocity = randomFloatBetween(ROTATION_VELOCITY_RANGE.x, ROTATION_VELOCITY_RANGE.y);
+ ud->rotation = 0.f;
}
void SnowflakeParticleRenderer::load(SnowflakeLoadParameters params, Renderer2d* renderer) {
numSnowflakes = params.numSnowflakes;
-
updateData = new SnowflakeUpdateData[params.numSnowflakes];
xMax = static_cast<f32>(renderer->context->width);
yMax = static_cast<f32>(renderer->context->height);
- vertices.deallocate();
- vertices.growDynamically = true;
-
- // Initialize each snow flake with its shape
+ // Initialize each snowflake
for (i32 s = 0; s < numSnowflakes; s++) {
auto ud = &updateData[s];
initFlake(this, ud);
}
- useShader(renderer->shader);
-
+ // Load custom snowflake shader
+ shader = loadShader(shader_snowflake_vert, shader_snowflake_frag);
+ useShader(shader);
+
+ // Get attribute and uniform locations
+ attributes.position = getShaderAttribute(shader, "position");
+ attributes.instancePos = getShaderAttribute(shader, "instancePos");
+ attributes.instanceRot = getShaderAttribute(shader, "instanceRot");
+ attributes.instanceScale = getShaderAttribute(shader, "instanceScale");
+ attributes.instanceSeed = getShaderAttribute(shader, "instanceSeed");
+ uniforms.projection = getShaderUniform(shader, "projection");
+ uniforms.model = getShaderUniform(shader, "model");
+
+ // Create base quad geometry (just a square from -1 to 1)
+ Vector2 quadVertices[] = {
+ Vector2(-1.0f, -1.0f),
+ Vector2( 1.0f, -1.0f),
+ Vector2( 1.0f, 1.0f),
+ Vector2(-1.0f, -1.0f),
+ Vector2( 1.0f, 1.0f),
+ Vector2(-1.0f, 1.0f)
+ };
+
+ // Setup VAO
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
- glGenBuffers(1, &vbo);
- glBindBuffer(GL_ARRAY_BUFFER, vbo);
- glBufferData(GL_ARRAY_BUFFER, vertices.numElements * sizeof(Vertex2D), &vertices.data[0], GL_DYNAMIC_DRAW);
-
- glEnableVertexAttribArray(renderer->attributes.position);
- glVertexAttribPointer(renderer->attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)0);
-
- glEnableVertexAttribArray(renderer->attributes.color);
- glVertexAttribPointer(renderer->attributes.color, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (GLvoid *)offsetof(Vertex2D, color));
-
- for (i32 idx = 0; idx < 4; idx++) {
- i32 offset = (4 * sizeof(f32)) * idx;
- glEnableVertexAttribArray(renderer->attributes.vMatrix + idx);
- glVertexAttribPointer(renderer->attributes.vMatrix + idx,
- 4,
- GL_FLOAT,
- GL_FALSE,
- sizeof(Vertex2D),
- (GLvoid *)(offsetof(Vertex2D, vMatrix) + offset));
- }
+ // Create and setup quad VBO (static geometry)
+ glGenBuffers(1, &quadVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, quadVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
+ glEnableVertexAttribArray(attributes.position);
+ glVertexAttribPointer(attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), (GLvoid*)0);
+
+ // Create instance VBO (dynamic data)
+ glGenBuffers(1, &instanceVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, instanceVbo);
+ glBufferData(GL_ARRAY_BUFFER, numSnowflakes * sizeof(SnowflakeInstanceData), NULL, GL_DYNAMIC_DRAW);
+
+ // Setup instance attributes
+ glEnableVertexAttribArray(attributes.instancePos);
+ glVertexAttribPointer(attributes.instancePos, 2, GL_FLOAT, GL_FALSE, sizeof(SnowflakeInstanceData), (GLvoid*)offsetof(SnowflakeInstanceData, position));
+ glVertexAttribDivisor(attributes.instancePos, 1);
+
+ glEnableVertexAttribArray(attributes.instanceRot);
+ glVertexAttribPointer(attributes.instanceRot, 1, GL_FLOAT, GL_FALSE, sizeof(SnowflakeInstanceData), (GLvoid*)offsetof(SnowflakeInstanceData, rotation));
+ glVertexAttribDivisor(attributes.instanceRot, 1);
+
+ glEnableVertexAttribArray(attributes.instanceScale);
+ glVertexAttribPointer(attributes.instanceScale, 1, GL_FLOAT, GL_FALSE, sizeof(SnowflakeInstanceData), (GLvoid*)offsetof(SnowflakeInstanceData, scale));
+ glVertexAttribDivisor(attributes.instanceScale, 1);
+
+ glEnableVertexAttribArray(attributes.instanceSeed);
+ glVertexAttribPointer(attributes.instanceSeed, 1, GL_FLOAT, GL_FALSE, sizeof(SnowflakeInstanceData), (GLvoid*)offsetof(SnowflakeInstanceData, seed));
+ glVertexAttribDivisor(attributes.instanceSeed, 1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
@@ -136,22 +114,22 @@ void SnowflakeParticleRenderer::load(SnowflakeLoadParameters params, Renderer2d*
inline void resetFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud) {
ud->position.y = 2 * renderer->yMax;
- ud->velocity = Vector2(randomFloatBetween(-10, 10), randomFloatBetween(-100, -85));
+
+ // Scale velocity based on star size - larger stars fall faster
+ f32 sizeRatio = (ud->scale - SCALE_RANGE.x) / (SCALE_RANGE.y - SCALE_RANGE.x);
+ f32 velocityY = VELOCITY_RANGE_Y.x + (VELOCITY_RANGE_Y.y - VELOCITY_RANGE_Y.x) * (0.5f + sizeRatio * 0.5f);
+
+ ud->velocity = Vector2(randomFloatBetween(VELOCITY_RANGE_X.x, VELOCITY_RANGE_X.y), velocityY);
ud->rotation = 0;
}
inline void updateFlake(SnowflakeParticleRenderer* renderer, SnowflakeUpdateData* ud, i32 s, f32 dtSeconds) {
ud->velocity = ud->velocity + Vector2(0, -(GRAVITY * dtSeconds));
- //if (addWind) ud->velocity += renderer->windSpeed;
+ //if (addWind) ud->velocity += renderer->windSpeed;
ud->position += ud->velocity * dtSeconds;
ud->rotation += ud->rotateVelocity * dtSeconds;
- Mat4x4 m = Mat4x4().translateByVec2(ud->position).rotate2D(ud->rotation);
- for (i32 v = ud->vtxIdx; v < (ud->vtxIdx + ud->numVertices); v++) {
- renderer->vertices.data[v].vMatrix = m;
- }
-
- if (ud->position.y <= -ud->radius) {
+ if (ud->position.y <= -ud->scale) {
resetFlake(renderer, ud);
}
}
@@ -169,21 +147,39 @@ void SnowflakeParticleRenderer::update(f32 dtSeconds) {
}
void SnowflakeParticleRenderer::render(Renderer2d* renderer) {
- setShaderMat4(renderer->uniforms.model, model);
+ useShader(shader);
+ setShaderMat4(uniforms.projection, renderer->projection);
+ setShaderMat4(uniforms.model, model);
+
+ // Prepare instance data
+ SnowflakeInstanceData* instanceData = new SnowflakeInstanceData[numSnowflakes];
+ for (i32 s = 0; s < numSnowflakes; s++) {
+ instanceData[s].position = updateData[s].position;
+ instanceData[s].rotation = updateData[s].rotation;
+ instanceData[s].scale = updateData[s].scale;
+ instanceData[s].seed = updateData[s].seed;
+ }
- glBindBuffer(GL_ARRAY_BUFFER, vbo);
- glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.numElements * sizeof(Vertex2D), &vertices.data[0]);
+ // Upload instance data
+ glBindBuffer(GL_ARRAY_BUFFER, instanceVbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, numSnowflakes * sizeof(SnowflakeInstanceData), instanceData);
+ // Draw instanced
glBindVertexArray(vao);
- glDrawArrays(GL_TRIANGLES, 0, vertices.numElements);
+ glDrawArraysInstanced(GL_TRIANGLES, 0, 6, numSnowflakes);
glBindVertexArray(0);
+
+ delete[] instanceData;
}
void SnowflakeParticleRenderer::unload() {
glDeleteVertexArrays(1, &vao);
- glDeleteBuffers(1, &vbo);
+ glDeleteBuffers(1, &quadVbo);
+ glDeleteBuffers(1, &instanceVbo);
+ glDeleteProgram(shader);
vao = 0;
- vbo = 0;
- vertices.deallocate();
- delete [] updateData;
+ quadVbo = 0;
+ instanceVbo = 0;
+ shader = 0;
+ delete[] updateData;
}
diff --git a/themes/src/winter/snowflake.h b/themes/src/winter/snowflake.h
index 11a1438..e4118f6 100644
--- a/themes/src/winter/snowflake.h
+++ b/themes/src/winter/snowflake.h
@@ -14,15 +14,20 @@ struct SnowflakeLoadParameters {
f32 windIntervalSeconds = 1.5f;
};
+struct SnowflakeInstanceData {
+ Vector2 position;
+ f32 rotation;
+ f32 scale;
+ f32 seed;
+};
+
struct SnowflakeUpdateData {
Vector2 velocity;
Vector2 position;
f32 rotateVelocity = 0.f;
f32 rotation = 0;
- f32 radius;
-
- i32 vtxIdx = 0;
- i32 numVertices = 0;
+ f32 scale;
+ f32 seed;
};
struct SnowflakeParticleRenderer {
@@ -35,10 +40,24 @@ struct SnowflakeParticleRenderer {
SnowflakeUpdateData* updateData;
u32 vao;
- u32 vbo;
+ u32 quadVbo; // Base quad geometry
+ u32 instanceVbo; // Instance data (position, rotation, scale, seed)
+ u32 shader; // Custom snowflake shader
Mat4x4 model;
- matte::List<Vertex2D> vertices;
-
+
+ struct {
+ i32 position;
+ i32 instancePos;
+ i32 instanceRot;
+ i32 instanceScale;
+ i32 instanceSeed;
+ } attributes;
+
+ struct {
+ i32 projection;
+ i32 model;
+ } uniforms;
+
void load(SnowflakeLoadParameters params, Renderer2d* renderer);
void update(f32 dtSeconds);
void render(Renderer2d* renderer);