diff options
| author | Matt Kosarek <matt.kosarek@canonical.com> | 2026-07-08 09:07:55 -0400 |
|---|---|---|
| committer | Matt Kosarek <matt.kosarek@canonical.com> | 2026-07-08 09:07:55 -0400 |
| commit | c8938fe29132a11126013ed1b010c3092f5645e4 (patch) | |
| tree | f6a6cd0603f25d51c08e302cc4342c28f9e10806 /themes/src/summer | |
| parent | 418396ee5100eda8f6bc036ea35dbbe44db2f91a (diff) | |
feature: improved summer scene
Diffstat (limited to 'themes/src/summer')
| -rw-r--r-- | themes/src/summer/sky_model.cpp | 64 | ||||
| -rw-r--r-- | themes/src/summer/sky_model.h | 43 | ||||
| -rw-r--r-- | themes/src/summer/summer_theme.cpp | 30 | ||||
| -rw-r--r-- | themes/src/summer/summer_theme.h | 4 | ||||
| -rw-r--r-- | themes/src/summer/water_model.cpp | 127 | ||||
| -rw-r--r-- | themes/src/summer/water_model.h | 70 |
6 files changed, 335 insertions, 3 deletions
diff --git a/themes/src/summer/sky_model.cpp b/themes/src/summer/sky_model.cpp new file mode 100644 index 0000000..d700f34 --- /dev/null +++ b/themes/src/summer/sky_model.cpp @@ -0,0 +1,64 @@ +#include "sky_model.h" +#include "../webgl_context.h" +#include "../mathlib.h" +#include "../shaders/sky_vert.h" +#include "../shaders/sky_frag.h" + +SkyModel::SkyModel() {} + +SkyModel::~SkyModel() { unload(); } + +void SkyModel::load(WebglContext* inContext) { + context = inContext; + shader = loadShader(shader_sky_vert, shader_sky_frag); + useShader(shader); + + attributes.position = getShaderAttribute(shader, "position"); + uniforms.time = getShaderUniform(shader, "time"); + uniforms.horizon = getShaderUniform(shader, "horizon"); + + // Full-screen quad in clip space (two triangles). + Vector2 verts[6] = { + Vector2(-1.f, -1.f), Vector2(1.f, -1.f), Vector2(-1.f, 1.f), + Vector2(-1.f, 1.f), Vector2(1.f, -1.f), Vector2( 1.f, 1.f) + }; + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); + + glEnableVertexAttribArray(attributes.position); + glVertexAttribPointer(attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), (GLvoid*)0); + + glBindVertexArray(0); +} + +void SkyModel::update(f32 dtSeconds) { + elapsedTime += dtSeconds; +} + +void SkyModel::render() { + useShader(shader); + + // Background: paint every pixel first, without touching the depth buffer so + // it can neither reject nor be rejected by the sun/water drawn afterward. + glDepthMask(GL_FALSE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + setShaderFloat(uniforms.time, elapsedTime); + setShaderFloat(uniforms.horizon, horizon); + + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLES, 0, 6); + glBindVertexArray(0); +} + +void SkyModel::unload() { + if (vao) { glDeleteVertexArrays(1, &vao); vao = 0; } + if (vbo) { glDeleteBuffers(1, &vbo); vbo = 0; } + if (shader) { glDeleteProgram(shader); shader = 0; } +} diff --git a/themes/src/summer/sky_model.h b/themes/src/summer/sky_model.h new file mode 100644 index 0000000..d957f05 --- /dev/null +++ b/themes/src/summer/sky_model.h @@ -0,0 +1,43 @@ +#ifndef SKY_MODEL_H +#define SKY_MODEL_H + +#include "../shader.h" +#include "../types.h" + +struct WebglContext; + +/// A full-screen sky background. Renders a mid-afternoon gradient (from the +/// water horizon up to the top of the screen) and sparse, wispy procedural +/// clouds that shrink toward the horizon. Standalone: owns its own shader and +/// a two-triangle full-screen quad, drawn before the sun and water. +class SkyModel { +public: + SkyModel(); + ~SkyModel(); + + void load(WebglContext* context); + void update(f32 dtSeconds); + void render(); + void unload(); + + /// Screen-space y (0..1) of the water horizon, where the gradient starts. + f32 horizon = 0.5f; + +private: + WebglContext* context = nullptr; + Shader shader = 0; + u32 vao = 0; + u32 vbo = 0; + f32 elapsedTime = 0.f; + + struct { + i32 position = -1; + } attributes; + + struct { + i32 time = -1; + i32 horizon = -1; + } uniforms; +}; + +#endif // SKY_MODEL_H diff --git a/themes/src/summer/summer_theme.cpp b/themes/src/summer/summer_theme.cpp index 6d2cfec..3b237e2 100644 --- a/themes/src/summer/summer_theme.cpp +++ b/themes/src/summer/summer_theme.cpp @@ -4,7 +4,6 @@ #include "../mathlib.h" #include "../shaders/sun_frag.h" #include "../shaders/sun_vert.h" -#include <vector> SummerTheme::SummerTheme(WebglContext* context) { @@ -21,20 +20,45 @@ void SummerTheme::load(WebglContext* context) { renderer.load(context, shader_sun_vert, shader_sun_frag); renderer.clearColor = Vector4(0, 181, 286, 255.f).toNormalizedColor(); sun.sectors = 180; - sun.radius = renderer.context->width / 4.f; + sun.radius = renderer.context->width / 6.f; sun.load(&renderer); + + sky.load(context); + water.load(context); + // Align the fake reflection with the sun disk: pointing up and into the + // scene (x = 0 keeps the glitter column centered under the sun). + water.setSun(Vector3(0.f, 0.4f, -1.f), Vector4(249, 215, 28, 255).toNormalizedColor()); } void SummerTheme::update(f32 dtSeconds) { sun.update(dtSeconds); + sky.update(dtSeconds); + water.update(dtSeconds); } void SummerTheme::render() { renderer.render(); + + // Gradient sky + clouds behind everything. It uses its own shader and + // disables depth writes, so restore the sun shader + projection and + // re-enable depth writes before drawing the sun. + sky.render(); + useShader(renderer.shader); + setShaderMat4(renderer.uniforms.projection, renderer.projection); + glDepthMask(GL_TRUE); + // sky.render() disabled blending; the sun's soft glowing edge needs it back, + // otherwise its semi-transparent rim writes opaque as a hard white border. + glEnable(GL_BLEND); + sun.render(&renderer); + // Drawn last: the opaque water paints over the lower half of the sun, + // hiding the part below the waterline while reflecting the part above. + water.render(); } void SummerTheme::unload() { + water.unload(); + sky.unload(); sun.unload(); } @@ -61,7 +85,7 @@ void Sun::load(Renderer2d* renderer) { } mesh.load(&vertices.data[0], vertices.numElements, &indices.data[0], indices.numElements, renderer); - mesh.model = Mat4x4().translateByVec2(Vector2(renderer->context->width / 2.f, renderer->context->height / 2.f)); + mesh.model = Mat4x4().translateByVec2(Vector2(renderer->context->width / 2.f, renderer->context->height * (3.f/ 4.f))); timeUniform = getShaderUniform(renderer->shader, "time"); diff --git a/themes/src/summer/summer_theme.h b/themes/src/summer/summer_theme.h index eb404fd..e048a7a 100644 --- a/themes/src/summer/summer_theme.h +++ b/themes/src/summer/summer_theme.h @@ -2,6 +2,8 @@ #include "../types.h" #include "../renderer_2d.h" #include "../theme.h" +#include "sky_model.h" +#include "water_model.h" #include <vector> struct Sun { @@ -22,6 +24,8 @@ public: SummerTheme(WebglContext*); ~SummerTheme(); Sun sun; + SkyModel sky; + WaterModel water{ Vector3(0.f, 0.f, -150.f), Vector3(600.f, 0.6f, 400.f) }; void load(WebglContext*); void update(f32 dtSeconds); void render(); diff --git a/themes/src/summer/water_model.cpp b/themes/src/summer/water_model.cpp new file mode 100644 index 0000000..3ad02bb --- /dev/null +++ b/themes/src/summer/water_model.cpp @@ -0,0 +1,127 @@ +#include "water_model.h" +#include "../webgl_context.h" +#include "../list.h" +#include "../shaders/water_vert.h" +#include "../shaders/water_frag.h" + +WaterModel::WaterModel(Vector3 inPosition, Vector3 inSize) + : position(inPosition), size(inSize) {} + +WaterModel::~WaterModel() { unload(); } + +void WaterModel::setSun(Vector3 direction, Vector4 color) { + sunDir = direction.normalize(); + sunColor = color.toVector3(); +} + +void WaterModel::load(WebglContext* inContext) { + context = inContext; + shader = loadShader(shader_water_vert, shader_water_frag); + useShader(shader); + + attributes.position = getShaderAttribute(shader, "position"); + uniforms.projection = getShaderUniform(shader, "projection"); + uniforms.view = getShaderUniform(shader, "view"); + uniforms.model = getShaderUniform(shader, "model"); + uniforms.time = getShaderUniform(shader, "time"); + uniforms.amplitude = getShaderUniform(shader, "amplitude"); + uniforms.cameraPos = getShaderUniform(shader, "cameraPos"); + uniforms.sunDir = getShaderUniform(shader, "sunDir"); + uniforms.sunColor = getShaderUniform(shader, "sunColor"); + + // Camera: forward almost horizontal with a slight downward pitch so the + // horizon sits near the vertical center and the plane fills the lower half. + cameraPos = Vector3(0.f, 5.f, 20.f); + projection = Mat4x4().getPerspectiveProjection( + 0.1f, 1000.f, 0.9f, + static_cast<f32>(context->width) / static_cast<f32>(context->height)); + view = Mat4x4().getLookAt(cameraPos, Vector3(0.f, 4.f, -10.f), Vector3(0.f, 1.f, 0.f)); + model = Mat4x4().translate(position); + + // Sensible defaults; the theme overrides these with the actual sun via setSun(). + sunDir = Vector3(0.f, 0.4f, -1.f).normalize(); + sunColor = Vector3(1.0f, 0.95f, 0.75f); + + // Build the grid in local XZ, centered on the origin, extents from size. + matte::List<Vector2> verts; + matte::List<u32> idx; + verts.allocate((RES + 1) * (RES + 1)); + idx.allocate(RES * RES * 6); + + for (i32 z = 0; z <= RES; z++) { + for (i32 x = 0; x <= RES; x++) { + f32 fx = ((static_cast<f32>(x) / RES) - 0.5f) * size.x; + f32 fz = ((static_cast<f32>(z) / RES) - 0.5f) * size.z; + verts.add(Vector2(fx, fz)); + } + } + + const i32 stride = RES + 1; + for (i32 z = 0; z < RES; z++) { + for (i32 x = 0; x < RES; x++) { + u32 tl = z * stride + x; + u32 tr = tl + 1; + u32 bl = (z + 1) * stride + x; + u32 br = bl + 1; + idx.add(tl); idx.add(bl); idx.add(tr); + idx.add(tr); idx.add(bl); idx.add(br); + } + } + numIndices = static_cast<i32>(idx.numElements); + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, verts.numElements * sizeof(Vector2), &verts.data[0], GL_STATIC_DRAW); + + glGenBuffers(1, &ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.numElements * sizeof(u32), &idx.data[0], GL_STATIC_DRAW); + + glEnableVertexAttribArray(attributes.position); + glVertexAttribPointer(attributes.position, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), (GLvoid*)0); + + glBindVertexArray(0); + verts.deallocate(); + idx.deallocate(); +} + +void WaterModel::update(f32 dtSeconds) { + elapsedTime += dtSeconds; +} + +void WaterModel::render() { + useShader(shader); + + // Opaque, depth-tested. Wipe the depth buffer first: the 2D sun was drawn at + // z=0 (nearest) with depth writes on, so without this the LEQUAL test would + // reject every perspective water fragment. Clearing depth (not color) lets the + // opaque water paint over the lower half of the sun while still self-occluding. + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glClear(GL_DEPTH_BUFFER_BIT); + + setShaderMat4(uniforms.projection, projection); + setShaderMat4(uniforms.view, view); + setShaderMat4(uniforms.model, model); + setShaderFloat(uniforms.time, elapsedTime); + setShaderFloat(uniforms.amplitude, size.y); + glUniform3f(uniforms.cameraPos, cameraPos.x, cameraPos.y, cameraPos.z); + glUniform3f(uniforms.sunDir, sunDir.x, sunDir.y, sunDir.z); + glUniform3f(uniforms.sunColor, sunColor.x, sunColor.y, sunColor.z); + + glBindVertexArray(vao); + glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0); + glBindVertexArray(0); +} + +void WaterModel::unload() { + if (vao) { glDeleteVertexArrays(1, &vao); vao = 0; } + if (vbo) { glDeleteBuffers(1, &vbo); vbo = 0; } + if (ebo) { glDeleteBuffers(1, &ebo); ebo = 0; } + if (shader) { glDeleteProgram(shader); shader = 0; } +} diff --git a/themes/src/summer/water_model.h b/themes/src/summer/water_model.h new file mode 100644 index 0000000..a99f91d --- /dev/null +++ b/themes/src/summer/water_model.h @@ -0,0 +1,70 @@ +#ifndef WATER_MODEL_H +#define WATER_MODEL_H + +#include "../mathlib.h" +#include "../shader.h" +#include "../types.h" + +struct WebglContext; + +/// A water mesh is a 3D plane in the simulation world. +/// +/// The mesh reflects the sun above it and refracts whatever is +/// below its surface. It owns its own shader, geometry buffers, +/// and perspective camera, so it is fully standalone. +/// +/// The mesh can be rendered and updated. +class WaterModel { +public: + WaterModel(Vector3 position, Vector3 size); + ~WaterModel(); + + /// Compile the water shader, build the grid mesh, and set up the camera. + void load(WebglContext* context); + void update(f32 dtSeconds); + void render(); + void unload(); + + /// Direction pointing toward the sun (normalized internally) and its color, + /// used to fake the surface reflection. + void setSun(Vector3 direction, Vector4 color); + +private: + Vector3 position; + Vector3 size; + + WebglContext* context = nullptr; + Shader shader = 0; + u32 vao = 0; + u32 vbo = 0; + u32 ebo = 0; + i32 numIndices = 0; + f32 elapsedTime = 0.f; + + Mat4x4 projection; + Mat4x4 view; + Mat4x4 model; + + Vector3 cameraPos; + Vector3 sunDir; + Vector3 sunColor; + + static const i32 RES = 200; + + struct { + i32 position = -1; + } attributes; + + struct { + i32 projection = -1; + i32 view = -1; + i32 model = -1; + i32 time = -1; + i32 amplitude = -1; + i32 cameraPos = -1; + i32 sunDir = -1; + i32 sunColor = -1; + } uniforms; +}; + +#endif // WATER_MODEL_H |
