Sprite sheets, atlases, game UI, time of day, weather presets, headless captures
- BillboardInstance: per-instance uv sub-rect, feet anchor, turn, dithered alpha, flash; BillboardBatch::sharedTexture; Texture createEmpty/update/cellUV/cellAnchor/nearest - SpriteAtlas: fixed-cell runtime atlas with gutters - hd2d::ui: Octopath-style panels, fonts (Cinzel, EB Garamond), dialogue box with typewriter + portraits + choices, menus, nameplates, toasts, ImGui theme - DayCycle (dawn/day/dusk/night keyframes, sun + moon arcs), Weather overcast/mist/ lightning, night-aware presets, stars and moon in the sky - Hidden-window offscreen captures (Window visible=false, Renderer output FBO, OffscreenTarget); --screenshot never opens a window - Camera::worldToScreen; CMake usable via add_subdirectory (HD2D_BUILD_DEMO, HD2D_ENGINE_ASSET_DIR)
This commit is contained in:
@@ -8,6 +8,7 @@ in VS {
|
||||
vec3 pivot;
|
||||
vec2 local;
|
||||
float tint;
|
||||
flat vec2 fx; // alpha, flash
|
||||
} v;
|
||||
|
||||
layout(binding = 0) uniform sampler2D uTex;
|
||||
@@ -15,10 +16,19 @@ uniform int uPass;
|
||||
uniform float uEmissive;
|
||||
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
const float kFlashWhite = 4.0; // linear HDR white the flash blends toward (blooms)
|
||||
|
||||
// 4x4 ordered (Bayer) threshold in (0, 1): alpha 1 keeps every pixel, 0 none.
|
||||
float bayer4(vec2 p) {
|
||||
ivec2 i = ivec2(p) & 3;
|
||||
const float m[16] = float[](0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5);
|
||||
return (m[i.y * 4 + i.x] + 0.5) / 16.0;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 c = sharpSample(uTex, v.uv);
|
||||
if (c.a < 0.5) discard;
|
||||
if (v.fx.x < 1.0 && v.fx.x < bayer4(gl_FragCoord.xy)) discard; // dithered fade, shadows included
|
||||
if (uPass == 1) return;
|
||||
|
||||
vec3 up = vec3(0.0, 1.0, 0.0);
|
||||
@@ -48,5 +58,6 @@ void main() {
|
||||
float d = length(v.pivot.xz - uBlob.xz) / uBlob.w;
|
||||
col *= 1.0 - 0.35 * (1.0 - smoothstep(0.3, 1.0, d)) * (1.0 - clamp(v.local.y / 0.6, 0.0, 1.0));
|
||||
}
|
||||
col = mix(col, vec3(kFlashWhite), clamp(v.fx.y, 0.0, 1.0)); // hit / capture flash
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#version 450 core
|
||||
#include "common.glsl"
|
||||
|
||||
// Per-instance attributes; must match BillboardInstance (Billboard.h).
|
||||
layout(location = 0) in vec4 iPosSway; // pivot xyz, sway
|
||||
layout(location = 1) in vec4 iSizeFlipTint; // size xy, flip, tint
|
||||
layout(location = 2) in vec4 iRect; // uv sub-rect (minU, minV, maxU, maxV)
|
||||
layout(location = 3) in vec4 iAnchorFx; // anchor v (< 0 = uOpaque.y), turn, alpha, flash
|
||||
|
||||
uniform vec4 uOpaque; // opaque pixel bounds in uv (minX, minY, maxX, maxY)
|
||||
uniform int uPass; // 0 = color, 1 = shadow
|
||||
@@ -13,22 +16,34 @@ out VS {
|
||||
vec3 pivot;
|
||||
vec2 local; // (horizontal offset, height above pivot) in world units
|
||||
float tint;
|
||||
flat vec2 fx; // alpha, flash
|
||||
} o;
|
||||
|
||||
void main() {
|
||||
vec2 corner = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1));
|
||||
vec2 size = iSizeFlipTint.xy;
|
||||
vec3 pivot = iPosSway.xyz;
|
||||
vec4 rect = iRect;
|
||||
float rectH = max(rect.w - rect.y, 1e-6);
|
||||
float anchor = iAnchorFx.x < 0.0 ? uOpaque.y : iAnchorFx.x;
|
||||
float anchorLocal = (anchor - rect.y) / rectH;
|
||||
float lx = (corner.x - 0.5) * size.x;
|
||||
float ly = (corner.y - uOpaque.y) * size.y; // lowest opaque row sits on the pivot
|
||||
float ly = (corner.y - anchorLocal) * size.y; // the feet row sits on the pivot
|
||||
|
||||
// Shadow cards face the sun so silhouettes are never edge-on to the light.
|
||||
vec3 right = uPass == 1 ? uShadowRight.xyz : uBillboardRight.xyz;
|
||||
// Shadow cards face the sun so silhouettes are never edge-on to the light;
|
||||
// `turn` yaws color cards away from camera-facing (paper turn).
|
||||
vec3 right = uBillboardRight.xyz;
|
||||
if (iAnchorFx.y != 0.0) {
|
||||
float ct = cos(iAnchorFx.y), st = sin(iAnchorFx.y);
|
||||
right = vec3(right.x * ct - right.z * st, 0.0, right.x * st + right.z * ct);
|
||||
}
|
||||
if (uPass == 1) right = uShadowRight.xyz;
|
||||
vec3 wp = pivot + right * lx + vec3(0.0, ly, 0.0);
|
||||
|
||||
float sway = iPosSway.w;
|
||||
if (sway > 0.0) {
|
||||
float visibleHeight = max(size.y * (uOpaque.w - uOpaque.y), 1e-3);
|
||||
// Feet to the top of the rect (or of the opaque pixels, if lower).
|
||||
float visibleHeight = max(size.y * (min(uOpaque.w, rect.w) - anchor) / rectH, 1e-3);
|
||||
float k = clamp(ly / visibleHeight, 0.0, 1.0);
|
||||
k *= k;
|
||||
float ph = dot(pivot.xz, vec2(0.37, 0.61));
|
||||
@@ -46,13 +61,14 @@ void main() {
|
||||
wp.y -= push * k * sway * 0.3 * visibleHeight;
|
||||
}
|
||||
|
||||
vec2 uv = corner;
|
||||
if (iSizeFlipTint.z > 0.5) uv.x = 1.0 - uv.x;
|
||||
o.uv = uv;
|
||||
vec2 c = corner;
|
||||
if (iSizeFlipTint.z > 0.5) c.x = 1.0 - c.x; // mirror inside the sub-rect
|
||||
o.uv = mix(rect.xy, rect.zw, c);
|
||||
o.worldPos = wp;
|
||||
o.pivot = pivot;
|
||||
o.local = vec2(lx, ly);
|
||||
o.tint = iSizeFlipTint.w;
|
||||
o.fx = iAnchorFx.zw;
|
||||
gl_Position = uViewProj * vec4(wp, 1.0);
|
||||
gl_ClipDistance[0] = dot(vec4(wp, 1.0), uClipPlane);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ layout(std140, binding = 0) uniform Frame {
|
||||
vec4 uWaterParams; // level, caustics intensity, has water, unused
|
||||
vec4 uGroundAO; // world -> uv: minX, minZ, 1/sizeX, 1/sizeZ (z = 0: disabled)
|
||||
vec4 uWeather; // rain intensity, surface wetness, rain fall speed, unused
|
||||
vec4 uSkyParams; // night (stars + moon disc at uSunDir), extra cloud cover, unused, unused
|
||||
ivec4 uLightCount;
|
||||
vec4 uLightPos[16]; // xyz, w = radius
|
||||
vec4 uLightColor[16];
|
||||
@@ -168,18 +169,30 @@ vec3 wetSurface(vec3 albedo, vec3 n, vec3 V, float vis, out vec3 sheen) {
|
||||
return albedo * mix(1.0, 0.68, uWeather.y);
|
||||
}
|
||||
|
||||
// Cloud layer opacity toward `dir` (0 at and below the horizon). Rain and
|
||||
// uSkyParams.y (overcast) thicken it.
|
||||
float skyClouds(vec3 dir) {
|
||||
if (dir.y <= 0.0) return 0.0;
|
||||
float cover = max(uWeather.x, uSkyParams.y);
|
||||
vec2 cp = dir.xz / (dir.y + 0.12) * 1.3 + uTime * vec2(0.012, 0.004);
|
||||
return smoothstep(0.5 - 0.45 * cover, 0.85 - 0.3 * cover, fbm2(cp)) * 0.55 * smoothstep(0.0, 0.2, dir.y);
|
||||
}
|
||||
|
||||
vec3 skyColor(vec3 dir) {
|
||||
float h = dir.y;
|
||||
vec3 c = mix(uSkyHorizon.rgb, uSkyZenith.rgb, pow(clamp(h, 0.0, 1.0), 0.55));
|
||||
if (h < 0.0) c = mix(uSkyHorizon.rgb, uSkyGround.rgb, clamp(-h * 3.0, 0.0, 1.0));
|
||||
float sd = max(dot(dir, uSunDir.xyz), 0.0);
|
||||
float night = uSkyParams.x;
|
||||
if (h > 0.0) {
|
||||
vec2 cp = dir.xz / (h + 0.12) * 1.3 + uTime * vec2(0.012, 0.004);
|
||||
float cl = smoothstep(0.5 - 0.45 * uWeather.x, 0.85 - 0.3 * uWeather.x, fbm2(cp)); // overcast when raining
|
||||
c = mix(c, uSunColor.rgb * (0.75 + 0.6 * sd), cl * 0.55 * smoothstep(0.0, 0.2, h));
|
||||
// Clouds catch the sun by day; at night they are faintly moonlit.
|
||||
vec3 lit = mix(uSunColor.rgb * (0.75 + 0.6 * sd),
|
||||
uSkyHorizon.rgb * 1.6 + uSunColor.rgb * uSunDir.w * (0.02 + 0.3 * pow(sd, 8.0)), night);
|
||||
c = mix(c, lit, skyClouds(dir));
|
||||
}
|
||||
c += uSunColor.rgb * (pow(sd, 900.0) * 40.0 + pow(sd, 10.0) * 0.3);
|
||||
// Sun disc + halo (the moon replaces it at night, overcast hides it).
|
||||
float sunVis = (1.0 - night) * (1.0 - uSkyParams.y) * clamp(uSunDir.w * 2.0, 0.0, 1.0);
|
||||
c += uSunColor.rgb * (pow(sd, 900.0) * 40.0 + pow(sd, 10.0) * 0.3) * sunVis;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,65 @@
|
||||
in vec2 vUV;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Star field: at most one star per cell of a grid around the view sphere,
|
||||
// anti-aliased against the pixel footprint (also works in the half-res
|
||||
// reflection), twinkling, mostly faint with a few bright ones that bloom.
|
||||
// `px` = angular pixel footprint (radians), computed in uniform control flow.
|
||||
vec3 stars(vec3 dir, float px) {
|
||||
const float grid = 150.0;
|
||||
vec3 q = dir * grid;
|
||||
vec3 cell = floor(q);
|
||||
float h = hash13(cell);
|
||||
if (h < 0.955) return vec3(0.0);
|
||||
vec3 jitter = vec3(hash13(cell + 11.3), hash13(cell + 27.1), hash13(cell + 3.7)) - 0.5;
|
||||
vec3 center = normalize(cell + 0.5 + jitter * 0.7) * grid; // on the sphere
|
||||
float d = length(q - center);
|
||||
float size = 0.10 + 0.12 * hash13(cell + 5.9);
|
||||
float r = max(size, px * grid * 0.6);
|
||||
float s = exp(-d * d / (r * r)) * (size * size) / (r * r);
|
||||
float bright = 0.35 + 3.5 * pow(hash13(cell + 41.7), 6.0);
|
||||
float twinkle = 0.7 + 0.3 * sin(uTime * (1.5 + 4.0 * hash13(cell + 9.2)) + h * 400.0);
|
||||
vec3 tint = mix(vec3(0.70, 0.80, 1.0), vec3(1.0, 0.86, 0.70), hash13(cell + 17.5));
|
||||
return tint * s * bright * twinkle;
|
||||
}
|
||||
|
||||
// Moon disc at the light direction (the sun acts as the moon at night):
|
||||
// soft maria, limb darkening and a two-lobe halo. rgb = disc colour,
|
||||
// a = disc coverage.
|
||||
vec4 moon(vec3 dir, float px, out vec3 halo) {
|
||||
const float R = 0.04; // angular radius (stylised, larger than real)
|
||||
vec3 L = uSunDir.xyz;
|
||||
vec3 T = normalize(cross(L, abs(L.y) > 0.99 ? vec3(1, 0, 0) : vec3(0, 1, 0)));
|
||||
vec3 B = cross(T, L);
|
||||
vec3 rel = dir - L * dot(dir, L);
|
||||
float ang = length(rel); // sin of the angle to the moon centre
|
||||
float front = step(0.0, dot(dir, L));
|
||||
vec3 moonCol = mix(vec3(1.0, 0.98, 0.94), uSunColor.rgb, 0.25);
|
||||
halo = moonCol * front * (exp(-ang / R * 1.3) * 0.35 + exp(-ang / (R * 7.0)) * 0.07);
|
||||
vec2 uv = vec2(dot(rel, T), dot(rel, B)) / R;
|
||||
float r = length(uv);
|
||||
float aa = max(px / R, 1e-3) * 1.5;
|
||||
float disc = (1.0 - smoothstep(1.0 - aa, 1.0, r)) * front;
|
||||
if (disc <= 0.0) return vec4(0.0);
|
||||
float maria = vnoise(uv * 1.8 + 3.0) * 0.6 + vnoise(uv * 4.5 + 7.0) * 0.4;
|
||||
float shade = mix(0.68, 1.0, smoothstep(0.35, 0.65, maria)) * (1.0 - 0.25 * r * r);
|
||||
return vec4(moonCol * 1.5 * shade, disc);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 far = worldFromDepth(vUV, 1.0);
|
||||
vec3 dir = normalize(far - uCamPos.xyz);
|
||||
fragColor = vec4(skyColor(dir), 1.0);
|
||||
float px = length(fwidth(dir));
|
||||
vec3 c = skyColor(dir);
|
||||
float night = uSkyParams.x;
|
||||
if (night > 0.0 && dir.y > 0.0) {
|
||||
float cover = max(uWeather.x, uSkyParams.y);
|
||||
float open = (1.0 - cover) * (1.0 - skyClouds(dir) / 0.55);
|
||||
c += stars(dir, px) * night * open * smoothstep(0.0, 0.2, dir.y);
|
||||
vec3 halo;
|
||||
vec4 disc = moon(dir, px, halo);
|
||||
c = mix(c, disc.rgb, disc.a * night * open);
|
||||
c += halo * night * (1.0 - 0.7 * cover);
|
||||
}
|
||||
fragColor = vec4(c, 1.0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user