summaryrefslogtreecommitdiff
path: root/themes/src/summer
diff options
context:
space:
mode:
authorMatt Kosarek <matt.kosarek@canonical.com>2026-07-08 09:07:55 -0400
committerMatt Kosarek <matt.kosarek@canonical.com>2026-07-08 09:07:55 -0400
commitc8938fe29132a11126013ed1b010c3092f5645e4 (patch)
treef6a6cd0603f25d51c08e302cc4342c28f9e10806 /themes/src/summer
parent418396ee5100eda8f6bc036ea35dbbe44db2f91a (diff)
feature: improved summer scene
Diffstat (limited to 'themes/src/summer')
-rw-r--r--themes/src/summer/sky_model.cpp64
-rw-r--r--themes/src/summer/sky_model.h43
-rw-r--r--themes/src/summer/summer_theme.cpp30
-rw-r--r--themes/src/summer/summer_theme.h4
-rw-r--r--themes/src/summer/water_model.cpp127
-rw-r--r--themes/src/summer/water_model.h70
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