diff options
| -rw-r--r-- | themes/dist/output.js | 6 | ||||
| -rwxr-xr-x | themes/dist/output.wasm | bin | 101714 -> 101674 bytes | |||
| -rw-r--r-- | themes/meson.build | 6 | ||||
| -rw-r--r-- | themes/src/_shaders/snowflake.frag | 73 | ||||
| -rw-r--r-- | themes/src/_shaders/snowflake.vert | 30 | ||||
| -rw-r--r-- | themes/src/shaders/snowflake_frag.cpp | 76 | ||||
| -rw-r--r-- | themes/src/shaders/snowflake_frag.h | 4 | ||||
| -rw-r--r-- | themes/src/shaders/snowflake_vert.cpp | 33 | ||||
| -rw-r--r-- | themes/src/shaders/snowflake_vert.h | 4 | ||||
| -rw-r--r-- | themes/src/winter/snowflake.cpp | 204 | ||||
| -rw-r--r-- | themes/src/winter/snowflake.h | 33 |
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 Binary files differindex 4c88053..671e7bb 100755 --- a/themes/dist/output.wasm +++ b/themes/dist/output.wasm 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); |
