diff options
Diffstat (limited to 'themes/src/spring')
| -rw-r--r-- | themes/src/spring/grass_renderer.cpp | 145 | ||||
| -rw-r--r-- | themes/src/spring/grass_renderer.hpp | 58 | ||||
| -rw-r--r-- | themes/src/spring/spring_theme.cpp | 41 | ||||
| -rw-r--r-- | themes/src/spring/spring_theme.hpp | 64 |
4 files changed, 246 insertions, 62 deletions
diff --git a/themes/src/spring/grass_renderer.cpp b/themes/src/spring/grass_renderer.cpp index 685f733..e4a210c 100644 --- a/themes/src/spring/grass_renderer.cpp +++ b/themes/src/spring/grass_renderer.cpp @@ -1,29 +1,138 @@ #include "grass_renderer.hpp" #include "../renderer_3d.h" +#include "../shader.h" +#include "../shaders/grass_frag.h" +#include "../shaders/grass_vert.h" +#include "mathlib.h" +#include <cmath> +#include <cstddef> -void GrassRenderer::load(GrassRendererLoadData params, Renderer3d* renderer) { - const f32 COLUMN_INCREMENT = GRASS_BLADES_PER_COL / params.area.x; - const f32 ROW_INCREMENT = GRASS_BLADES_PER_ROW / params.area.y; - for (i32 r = 0; r < GRASS_BLADES_PER_ROW; r++) { - i32 indexOffset = r * GRASS_BLADES_PER_ROW; - f32 y = ROW_INCREMENT * r; - for (i32 c = 0; c < GRASS_BLADES_PER_COL; c++) { - f32 x = COLUMN_INCREMENT * c; - i32 index = indexOffset + c; - grassBlades[index].position = Vector3(x, y, 0); - grassBlades[index].top_offset = Vector2(0, 0); - } - } -} +void GrassRenderer::load(GrassRendererLoadData params, Renderer3d *renderer) { + bladeHeight = params.grassHeight; + + // Place blades randomly within a circle. Using r = R*sqrt(u) with a + // uniform u in [0,1] gives uniform areal density (no center clustering). + const f32 radius = fminf(params.area.x, params.area.y) * 0.5f; + for (i32 i = 0; i < NUM_GRASS_BLADES; i++) { + f32 r = radius * sqrtf(randomFloatBetween(0.f, 1.f)); + f32 theta = randomFloatBetween(0.f, 2.f * PI); + f32 x = params.origin.x + r * cosf(theta); + f32 z = params.origin.y + r * sinf(theta); + grassBlades[i].position = Vector3(x, 0, z); + grassBlades[i].top_offset = + Vector2(randomFloatBetween(0.f, 2.f * PI), // sway phase + randomFloatBetween(0.5f, 1.5f) // height scale + ); + } + + // Compile grass shader + shader = loadShader(shader_grass_vert, shader_grass_frag); + useShader(shader); + + // Attribute locations + attributes.position = getShaderAttribute(shader, "position"); + attributes.instancePos = getShaderAttribute(shader, "instancePos"); + attributes.instancePhase = getShaderAttribute(shader, "instancePhase"); + attributes.instanceHeight = getShaderAttribute(shader, "instanceHeight"); + + // Uniform locations + uniforms.projection = getShaderUniform(shader, "projection"); + uniforms.view = getShaderUniform(shader, "view"); + uniforms.time = getShaderUniform(shader, "time"); + uniforms.bladeWidth = getShaderUniform(shader, "bladeWidth"); + uniforms.bladeHeight = getShaderUniform(shader, "bladeHeight"); + uniforms.swayAmount = getShaderUniform(shader, "swayAmount"); + + // Base quad: two triangles forming a unit quad + // x in [-0.5, 0.5], y in [0, 1] + Vector2 quadVertices[] = {Vector2(-0.5f, 0.0f), Vector2(0.5f, 0.0f), + Vector2(0.5f, 1.0f), Vector2(-0.5f, 0.0f), + Vector2(0.5f, 1.0f), Vector2(-0.5f, 1.0f)}; + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + // Static quad VBO + 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); -void GrassRenderer::update(f32 seconds) { - + // Dynamic instance VBO + glGenBuffers(1, &instanceVbo); + glBindBuffer(GL_ARRAY_BUFFER, instanceVbo); + glBufferData(GL_ARRAY_BUFFER, NUM_GRASS_BLADES * sizeof(GrassInstanceData), + NULL, GL_DYNAMIC_DRAW); + + // instancePos: vec3 (x, y, z) at offset 0 + glEnableVertexAttribArray(attributes.instancePos); + glVertexAttribPointer(attributes.instancePos, 3, GL_FLOAT, GL_FALSE, + sizeof(GrassInstanceData), + (GLvoid *)offsetof(GrassInstanceData, x)); + glVertexAttribDivisor(attributes.instancePos, 1); + + // instancePhase: float at offset 12 (after 3 floats) + glEnableVertexAttribArray(attributes.instancePhase); + glVertexAttribPointer(attributes.instancePhase, 1, GL_FLOAT, GL_FALSE, + sizeof(GrassInstanceData), + (GLvoid *)offsetof(GrassInstanceData, phaseOffset)); + glVertexAttribDivisor(attributes.instancePhase, 1); + + // instanceHeight: float at offset 16 (after phaseOffset) + glEnableVertexAttribArray(attributes.instanceHeight); + glVertexAttribPointer(attributes.instanceHeight, 1, GL_FLOAT, GL_FALSE, + sizeof(GrassInstanceData), + (GLvoid *)offsetof(GrassInstanceData, heightScale)); + glVertexAttribDivisor(attributes.instanceHeight, 1); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); } -void GrassRenderer::render(Renderer3d* renderer) { +void GrassRenderer::update(f32 dtSeconds) { time += dtSeconds; } +void GrassRenderer::render(Renderer3d *renderer) { + useShader(shader); + setShaderMat4(uniforms.projection, renderer->projection); + setShaderMat4(uniforms.view, renderer->view); + setShaderFloat(uniforms.time, time); + setShaderFloat(uniforms.bladeWidth, bladeWidth); + setShaderFloat(uniforms.bladeHeight, bladeHeight); + setShaderFloat(uniforms.swayAmount, swayAmount); + + // Build and upload instance data + GrassInstanceData instanceData[NUM_GRASS_BLADES]; + for (i32 i = 0; i < NUM_GRASS_BLADES; i++) { + instanceData[i].x = grassBlades[i].position.x; + instanceData[i].y = grassBlades[i].position.y; + instanceData[i].z = grassBlades[i].position.z; + instanceData[i].phaseOffset = grassBlades[i].top_offset.x; + instanceData[i].heightScale = grassBlades[i].top_offset.y; + } + + glBindBuffer(GL_ARRAY_BUFFER, instanceVbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, + NUM_GRASS_BLADES * sizeof(GrassInstanceData), instanceData); + + glBindVertexArray(vao); + glDrawArraysInstanced(GL_TRIANGLES, 0, 6, NUM_GRASS_BLADES); + glBindVertexArray(0); } void GrassRenderer::unload() { - + if (vao) + glDeleteVertexArrays(1, &vao); + if (quadVbo) + glDeleteBuffers(1, &quadVbo); + if (instanceVbo) + glDeleteBuffers(1, &instanceVbo); + if (shader) + glDeleteProgram(shader); + vao = 0; + quadVbo = 0; + instanceVbo = 0; + shader = 0; } diff --git a/themes/src/spring/grass_renderer.hpp b/themes/src/spring/grass_renderer.hpp index 88879f3..14ef067 100644 --- a/themes/src/spring/grass_renderer.hpp +++ b/themes/src/spring/grass_renderer.hpp @@ -5,29 +5,59 @@ #include "mathlib.h" #include "types.h" -const i32 GRASS_BLADES_PER_ROW = 24; -const i32 GRASS_BLADES_PER_COL = 24; +const i32 GRASS_BLADES_PER_ROW = 48; +const i32 GRASS_BLADES_PER_COL = 48; const i32 NUM_GRASS_BLADES = GRASS_BLADES_PER_ROW * GRASS_BLADES_PER_COL; struct GrassRendererLoadData { - Vector2 origin = Vector2(0, 0); - Vector2 area = Vector2(480, 480); - f32 grassHeight = 12.f; + Vector2 origin = Vector2(0, 0); + Vector2 area = Vector2(480, 480); + f32 grassHeight = 4.f; }; struct GrassUpdateData { - Vector3 position; - Vector2 top_offset; + Vector3 position; + Vector2 top_offset; // top_offset.x stores per-blade sway phase offset +}; + +struct GrassInstanceData { + float x, y, z; + float phaseOffset; + float heightScale; }; struct GrassRenderer { - - GrassUpdateData grassBlades[NUM_GRASS_BLADES]; - - void load(GrassRendererLoadData params, Renderer3d* renderer); - void update(f32 dtSeconds); - void render(Renderer3d* renderer); - void unload(); + GrassUpdateData grassBlades[NUM_GRASS_BLADES]; + + u32 vao = 0; + u32 quadVbo = 0; + u32 instanceVbo = 0; + u32 shader = 0; + f32 time = 0.f; + f32 bladeWidth = 1.5f; + f32 bladeHeight = 6.f; + f32 swayAmount = 0.3f; + + struct { + i32 position; + i32 instancePos; + i32 instancePhase; + i32 instanceHeight; + } attributes; + + struct { + i32 projection; + i32 view; + i32 time; + i32 bladeWidth; + i32 bladeHeight; + i32 swayAmount; + } uniforms; + + void load(GrassRendererLoadData params, Renderer3d *renderer); + void update(f32 dtSeconds); + void render(Renderer3d *renderer); + void unload(); }; #endif diff --git a/themes/src/spring/spring_theme.cpp b/themes/src/spring/spring_theme.cpp index 8b09366..4b62795 100644 --- a/themes/src/spring/spring_theme.cpp +++ b/themes/src/spring/spring_theme.cpp @@ -1,6 +1,8 @@ #include "spring_theme.hpp" #include "../renderer_3d.h" +#include "../shader.h" #include "../shader_fetcher.hpp" +#include "../shapes_2d.h" #include <cstdio> #include <emscripten/fetch.h> @@ -49,7 +51,15 @@ SpringTheme::~SpringTheme() { unload(); } void SpringTheme::load(WebglContext *context) { state = SpringThemeState::Loading; renderer.context = context; - renderer.clearColor = Vector4(160, 231, 160, 255.f).toNormalizedColor(); + renderer.clearColor = Vector4(174, 216, 230, 255.f).toNormalizedColor(); + + renderer2d.load(context); + background = new RectangularGradient( + renderer2d, Vector4(174, 216, 230, 255).toNormalizedColor(), + Vector4(144, 238, 144, 255).toNormalizedColor(), renderer2d.get_width(), + renderer2d.get_height(), {0, 0}); + + grassRenderer.load({Vector2(0, -20), Vector2(96, 96), 3.f}, &renderer); fetch_shader({"themes/src/_shaders/renderer3d.vert", "themes/src/_shaders/renderer3d.frag"}, @@ -74,6 +84,9 @@ inline f32 rotationLerp(f32 start, f32 target, f32 t) { } void SpringTheme::update(f32 dtSeconds) { + if (state != SpringThemeState::Loading) { + grassRenderer.update(dtSeconds); + } switch (state) { case SpringThemeState::Loading: return; @@ -98,6 +111,15 @@ void SpringTheme::update(f32 dtSeconds) { yDir = -1; bunnyTarget = bunnyPosition + Vector3(randomFloatBetween(0, xDir * 25), 0, randomFloatBetween(0, yDir * 25)); + // Clamp bunnyTarget to within the grass circle (origin=(0,-20), radius=48) + const Vector3 grassCenter(0, 0, -20); + const f32 grassRadius = 48.f; + Vector3 toTarget = bunnyTarget - grassCenter; + toTarget.y = 0; + if (toTarget.length() > grassRadius) { + toTarget = toTarget.normalize() * grassRadius; + bunnyTarget = Vector3(grassCenter.x + toTarget.x, 0, grassCenter.z + toTarget.z); + } auto direction = (bunnyTarget - bunnyPosition); auto distance = direction.length(); direction = direction.normalize(); @@ -197,12 +219,29 @@ void SpringTheme::update(f32 dtSeconds) { void SpringTheme::render() { renderer.render(); + + // Draw the 2D gradient background without writing to the depth buffer so + // the 3D content rendered afterwards is unobstructed. + glDepthMask(GL_FALSE); + useShader(renderer2d.shader); + setShaderMat4(renderer2d.uniforms.projection, renderer2d.projection); + background->render(); + glDepthMask(GL_TRUE); + if (state != SpringThemeState::Loading) { + grassRenderer.render(&renderer); + // Restore the 3D renderer's shader after the grass shader took over + useShader(renderer.shader); + setShaderMat4(renderer.uniforms.projection, renderer.projection); + setShaderMat4(renderer.uniforms.view, renderer.view); bunnyMesh.render(&renderer); } } void SpringTheme::unload() { renderer.unload(); + renderer2d.unload(); + delete background; bunnyMesh.unload(); + grassRenderer.unload(); } diff --git a/themes/src/spring/spring_theme.hpp b/themes/src/spring/spring_theme.hpp index 6079958..4ee5684 100644 --- a/themes/src/spring/spring_theme.hpp +++ b/themes/src/spring/spring_theme.hpp @@ -2,44 +2,50 @@ #define SPRING_THEME_HPP #include "../mathlib.h" -#include "../types.h" +#include "../renderer_2d.h" #include "../renderer_3d.h" #include "../theme.h" +#include "../types.h" +#include "grass_renderer.hpp" +class RectangularGradient; enum class SpringThemeState { - Loading = 0, - LoadedShader, - LoadedBunny, - PreHop, - Hopping, - Idle + Loading = 0, + LoadedShader, + LoadedBunny, + PreHop, + Hopping, + Idle }; class SpringTheme : public Theme { public: - SpringTheme(WebglContext*); - ~SpringTheme(); - Renderer3d renderer; - SpringThemeState state; - f32 bunnySpeed = 5.f; - Vector3 bunnyPosition = Vector3(0, 0, 0); - Vector3 bunnyTarget = Vector3(0, 0, 0); - Vector3 hopIncrement = Vector3(0, 0, 0); - - f32 numHops = 0; - f32 hopCount = 0; - f32 bunnyHopAnimationTimer = 0.f; - f32 stateTimer = 0.f; - f32 bunnyRotation = 0.f; - f32 targetRotation = 0.f; - - Mesh3d bunnyMesh; - - void load(WebglContext*); - void update(f32 dtSeconds); - void render(); - void unload(); + SpringTheme(WebglContext *); + ~SpringTheme(); + Renderer3d renderer; + SpringThemeState state; + f32 bunnySpeed = 5.f; + Vector3 bunnyPosition = Vector3(0, 0, 0); + Vector3 bunnyTarget = Vector3(0, 0, 0); + Vector3 hopIncrement = Vector3(0, 0, 0); + + f32 numHops = 0; + f32 hopCount = 0; + f32 bunnyHopAnimationTimer = 0.f; + f32 stateTimer = 0.f; + f32 bunnyRotation = 0.f; + f32 targetRotation = 0.f; + + Mesh3d bunnyMesh; + GrassRenderer grassRenderer; + Renderer2d renderer2d; + RectangularGradient *background; + + void load(WebglContext *); + void update(f32 dtSeconds); + void render(); + void unload(); }; #endif |
