Blog · · 5 min read
A year of sunlight in six seconds
The Sunward roof demo renders a year of sun in six seconds by treating each sun position as one shadow pass and adding every pass into a half-float lightmap. The map holds kilowatt hours per square meter for each roof texel. A false-color ramp makes it readable, and a single readback lets every panel you drop on the roof look up its own yearly energy. It is a clear-sky model with one weather factor, so it is a visual estimate, not an engineering report.
By the AnimatedWebs team
One sun position, one pass
Sunlight on a roof depends on where the sun is and what blocks it. The first part is astronomy. The second is shadow mapping, which a GPU does well. So the demo walks the sun through the year, renders a shadow map for each position, and for every point on the house asks one question: is it lit, and how hard is the sun hitting it? Add up the answers and you have a year.
The sun comes from a short function for Tucson at 32.22 degrees north. For each month it takes one representative day, computes the declination, finds the sunrise hour angle, and spreads 17 samples evenly across daylight. Twelve months of 17 samples is at most 204 passes; devices that report software rendering get 6 per day. Each sample carries two numbers, direct and diffuse irradiance, from a clear-sky air mass model, already multiplied by the hours the sample stands for, the days in its month and a weather factor of 0.86. The unit works out so that summing the samples gives kilowatt hours per square meter per year.
The shadow pass
An orthographic camera sits 45 units away along the sun direction and looks at the middle of the lot. It covers 46 units across, which fits the house, the tree, a back wall and the yard. The scene renders into a 2048 pixel depth texture (1024 on weak hardware) with a depth-only material, so the pass writes no color at all.
Lookups use a 3 by 3 filter with a small bias and a normal offset so flat roofs do not shadow themselves. The panels are hidden during these passes on purpose: a panel must never shade the roof it is about to measure.
Drawing into the lightmap
The house is unwrapped with a second set of UVs, one unique patch per face, into a 1024 pixel atlas (512 on weak hardware). To accumulate, we render the house again, but the vertex shader ignores the camera and places each vertex at its own atlas coordinate. Rasterizing the mesh now fills the atlas, one texel for each patch of surface, and the fragment shader runs once per texel with the world position and normal of that surface point.
// vertex: the lightmap UV is the position
gl_Position = vec4(lmuv * 2.0 - 1.0, 0.0, 1.0);
// fragment: energy this texel receives from this one sun position
float facing = max(dot(n, uSun), 0.0);
float lit = facing > 0.0 ? shadowAt(worldPos, n) : 0.0;
float e = uDNI * facing * lit + uDHI * (0.5 + 0.5 * n.y);
gl_FragColor = vec4(e * uScale, 0.0, 0.0, uUnit);Direct sun is the irradiance times the cosine of the angle to the surface times the shadow term. Sky light is a simple blend that favors upward-facing surfaces. The blend state is set to add source and destination, so each pass lands on top of the last in a half-float render target. Half float matters here: an 8-bit target cannot hold a year of sums without heavy scaling. When a device does not offer float targets, the demo falls back to bytes with a scale and accepts the lost precision.
Making it readable
Raw kilowatt hours are meaningless to look at. The display shader maps the stored energy through a six-stop ramp, from deep blue through teal and yellow to orange and near white. The input is squared after normalizing, which gives the low end more room, and the normalizer is the energy a south-facing 22 degree roof would collect under the same sample set, times 0.97. While the year runs, the maximum grows from 35 to 100 percent of that value, so the map appears to burn in rather than snap to its final scale.
The playback is driven by a clock, not by the GPU. Over 6,000 milliseconds the demo advances the pass count along a curve with the exponent 1.7, capped at 36 passes per frame, or 10 on weak hardware. The sun in the sky, the clock readout and a trail of dots for every sampled position all follow the latest completed pass.
From texels to kWh per panel
When the last pass finishes, the demo encodes the lightmap into two 8-bit channels (a coarse byte and a fine byte, which covers up to 4,000 kWh per square meter) and reads it back to the CPU once. After that, no GPU work is needed to answer a question about the roof.
Each panel footprint samples that array at five points, the center and four inner corners, converts the panel's roof coordinates to atlas coordinates, and averages the values. The estimate multiplies by the panel area, 1.7 square meters, by a module efficiency of 0.212 and by 0.8 for system losses. As arithmetic only: a spot receiving 2,000 kWh per square meter a year would give 2,000 times 1.7 times 0.212 times 0.8, about 577 kWh for that panel. A panel in the chimney's shadow reads lower because its texels really received less.
function panelKwh(face, lu, lv) {
let sum = 0, n = 0;
for (const [du, dv] of SAMPLE_POINTS) { // center + 4 corners
const v = insolationAt(faceUV(face, lu + du, lv + dv));
if (v !== null) { sum += v; n++; }
}
return n ? (sum / n) * AREA * EFFICIENCY * LOSSES : null;
}Placing a panel is a raycast against the roof mesh. The hit gives a face and a position, which snaps to a grid of 1.06 by 1.76 meter cells. A cell is valid only if all four corners are inside the face and clear of the chimney. The ghost panel turns green or red accordingly.
What the numbers are and are not
- The sky is a clear-sky model scaled by one constant for Tucson. It has no hourly weather, no temperature effect on the modules and no snow or soiling.
- One representative day stands for each month. Cloud days and heat waves are averaged away.
- The demo says "illustrative" next to its rate for a reason: use it to understand a roof, and get a site survey before you buy.
What it does well is show cause and effect. Take the mesquite tree out or switch from a gable to a hip roof, and the lightmap clears and refills in six seconds. Because the whole thing is a few hundred cheap passes, there is no precomputed data to ship and no server to ask.
Try it
Open the live Sunward demo, press "Run the year", and drag panels onto the brightest part of the roof. The build prompt lives on the template page. Related ideas in the catalog include Solar Roof Five-Level Zoom and Heat Pump Isometric House. For another way to drive a whole 3D scene from one number, read Take-apart 3D.

