mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 09:50:50 +02:00
visual improvements to the grass
This commit is contained in:
+3
-1
@@ -821,7 +821,9 @@ local function quadsMesh(quads, grass)
|
|||||||
local uv = q.uv and q.uv[i] or { q.u, q.v }
|
local uv = q.uv and q.uv[i] or { q.u, q.v }
|
||||||
local v = { c[1], c[2], c[3], uv[1], uv[2], q.shade }
|
local v = { c[1], c[2], c[3], uv[1], uv[2], q.shade }
|
||||||
if grass then
|
if grass then
|
||||||
v[7], v[8], v[9] = q.sway or 0, q.cx or 0, q.cz or 0
|
v[7], v[8], v[9], v[10] = q.sway or 0, q.cx or 0,
|
||||||
|
q.cz or 0,
|
||||||
|
q.firefly and 2 or (q.leaf and 1 or 0)
|
||||||
end
|
end
|
||||||
verts[#verts + 1] = v
|
verts[#verts + 1] = v
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3521,6 +3521,38 @@ function Structures.buildGrass(S, map, x0, x1, y0, y1, data)
|
|||||||
cx = wx + 4, cz = wz + 4,
|
cx = wx + 4, cz = wz + 4,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Sparse wind-borne leaf. It reuses one opaque grass texel and is
|
||||||
|
-- animated entirely on the GPU, so no per-frame Lua particles exist.
|
||||||
|
if #tpl > 0 and ((tx * 13 + ty * 7) % 11 == 0) then
|
||||||
|
local src = tpl[1]
|
||||||
|
local uv = src.uv and src.uv[1] or { src.u, src.v }
|
||||||
|
local lx = wx + 2 + ((tx * 5 + ty * 3) % 5)
|
||||||
|
local lz = wz + 4
|
||||||
|
local ly, size = 9 + ((tx + ty) % 3), 1.25
|
||||||
|
quads[#quads + 1] = {
|
||||||
|
{ lx - size, ly, lz }, { lx + size, ly, lz },
|
||||||
|
{ lx + size, ly + size, lz }, { lx - size, ly + size, lz },
|
||||||
|
uv = { uv, uv, uv, uv }, shade = 1, sway = sway + 0.73,
|
||||||
|
cx = lx, cz = lz, leaf = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Rarer one-pixel firefly. Geometry exists all day, but the shader
|
||||||
|
-- gives it zero glow outside outdoor night.
|
||||||
|
if #tpl > 0 and ((tx * 17 + ty * 11) % 29 == 0) then
|
||||||
|
local src = tpl[1]
|
||||||
|
local uv = src.uv and src.uv[1] or { src.u, src.v }
|
||||||
|
local fx = wx + 2 + ((tx * 3 + ty * 5) % 5)
|
||||||
|
local fz = wz + 4
|
||||||
|
local fy, half = 9 + ((tx + ty) % 4), 0.5
|
||||||
|
quads[#quads + 1] = {
|
||||||
|
{ fx - half, fy, fz }, { fx + half, fy, fz },
|
||||||
|
{ fx + half, fy + 1, fz }, { fx - half, fy + 1, fz },
|
||||||
|
uv = { uv, uv, uv, uv }, shade = 1, sway = sway + 1.37,
|
||||||
|
cx = fx, cz = fz, firefly = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+54
-19
@@ -55,7 +55,7 @@ Voxel3D.GRASS_FORMAT = {
|
|||||||
{ "VertexPosition", "float", 3 },
|
{ "VertexPosition", "float", 3 },
|
||||||
{ "VertexTexCoord", "float", 2 },
|
{ "VertexTexCoord", "float", 2 },
|
||||||
{ "VertexShade", "float", 1 },
|
{ "VertexShade", "float", 1 },
|
||||||
{ "VertexGrass", "float", 3 }, -- phase, clump centre x, clump centre z
|
{ "VertexGrass", "float", 4 }, -- phase, clump centre x/z, effect kind
|
||||||
}
|
}
|
||||||
|
|
||||||
Voxel3D.GRASS_WIND_PIXELS = 1.15
|
Voxel3D.GRASS_WIND_PIXELS = 1.15
|
||||||
@@ -88,6 +88,8 @@ local SHADER = [[
|
|||||||
varying float vShade;
|
varying float vShade;
|
||||||
varying vec3 vSun; // this fragment's place in the sun's view
|
varying vec3 vSun; // this fragment's place in the sun's view
|
||||||
varying float vFog; // how deep into the map's haze it stands
|
varying float vFog; // how deep into the map's haze it stands
|
||||||
|
varying float vFirefly; // zero normally, night glow on firefly cards
|
||||||
|
uniform float fireflyNight; // shared safely by vertex and pixel stages
|
||||||
#ifdef VOXEL_CULL
|
#ifdef VOXEL_CULL
|
||||||
// where this fragment stands in the FLAT world, for the diorama's
|
// where this fragment stands in the FLAT world, for the diorama's
|
||||||
// viewport to measure. Same precision reasoning as vGrid below: a
|
// viewport to measure. Same precision reasoning as vGrid below: a
|
||||||
@@ -115,9 +117,10 @@ local SHADER = [[
|
|||||||
uniform vec4 grassPlayer; // world x, world z, radius, push pixels
|
uniform vec4 grassPlayer; // world x, world z, radius, push pixels
|
||||||
uniform vec2 grassPrevious; // previous player world xz for swept contact
|
uniform vec2 grassPrevious; // previous player world xz for swept contact
|
||||||
attribute float VertexShade;
|
attribute float VertexShade;
|
||||||
attribute vec3 VertexGrass;
|
attribute vec4 VertexGrass;
|
||||||
vec4 position(mat4 transform_projection, vec4 vertex_position) {
|
vec4 position(mat4 transform_projection, vec4 vertex_position) {
|
||||||
vShade = VertexShade;
|
vShade = VertexShade;
|
||||||
|
vFirefly = 0.0;
|
||||||
#ifdef VOXEL_GRID
|
#ifdef VOXEL_GRID
|
||||||
// MODEL space, deliberately: every mesh here is built a unit per
|
// MODEL space, deliberately: every mesh here is built a unit per
|
||||||
// voxel in its own frame, so the seams ride the model however it is
|
// voxel in its own frame, so the seams ride the model however it is
|
||||||
@@ -144,23 +147,51 @@ local SHADER = [[
|
|||||||
float bend = clamp(vertex_position.y / 8.0, 0.0, 1.0);
|
float bend = clamp(vertex_position.y / 8.0, 0.0, 1.0);
|
||||||
bend *= bend;
|
bend *= bend;
|
||||||
float wave = sin(grassWind.y * grassWind.w + VertexGrass.x);
|
float wave = sin(grassWind.y * grassWind.w + VertexGrass.x);
|
||||||
vec2 offset = vec2(wave * grassWind.z, 0.0);
|
if (VertexGrass.w > 1.5) {
|
||||||
|
// One-pixel firefly with a full behaviour loop: a long rest on the
|
||||||
// Move each touched tuft rigidly a little to the left or right. The
|
// grass, smooth take-off, an irregular short flight, descent, landing
|
||||||
// baked clump centre is identical for all its vertices, preventing
|
// and another pause. The baked phase keeps every insect independent.
|
||||||
// stretching, twisting and the ugly crushed-grass silhouette.
|
float cycle = fract(grassWind.y * 0.052 + VertexGrass.x * 0.173);
|
||||||
vec2 clump = VertexGrass.yz;
|
float takeoff = smoothstep(0.30, 0.39, cycle);
|
||||||
float bodyDist = length(clump - grassPlayer.xy);
|
float landing = 1.0 - smoothstep(0.68, 0.79, cycle);
|
||||||
float touch = 1.0 - smoothstep(grassPlayer.z * 0.45,
|
float airborne = takeoff * landing;
|
||||||
grassPlayer.z, bodyDist);
|
float drift = grassWind.y * 0.83 + VertexGrass.x * 3.7;
|
||||||
float side = clump.x < grassPlayer.x ? -1.0 : 1.0;
|
float wander = sin(drift) * 2.8 + sin(drift * 0.37 + 1.3) * 1.5;
|
||||||
if (abs(clump.x - grassPlayer.x) < 0.5)
|
float lift = 2.5 + sin(drift * 1.19) * 1.1
|
||||||
side = sin(VertexGrass.x) < 0.0 ? -1.0 : 1.0;
|
+ sin(drift * 0.53 + 0.8) * 0.7;
|
||||||
|
w.x += airborne * wander;
|
||||||
// Preserve the existing wind bend. Player contact is only a small,
|
w.y += airborne * lift;
|
||||||
// uniform X translation, just enough to suggest the grass parting.
|
// Mostly dim while resting, visibly brighter in flight, with a soft
|
||||||
w.xz += offset * bend;
|
// asynchronous pulse rather than a hard on/off blink.
|
||||||
w.x += side * touch * grassPlayer.w;
|
float blink = 0.68 + 0.32 * (sin(drift * 2.11) * 0.5 + 0.5);
|
||||||
|
vFirefly = fireflyNight * blink * mix(0.14, 0.92, airborne);
|
||||||
|
} else if (VertexGrass.w > 0.5) {
|
||||||
|
// The first 72% of the cycle is airborne. A leaf gets an initial
|
||||||
|
// upward lift, travels with the wind, then gravity accelerates it
|
||||||
|
// down to the ground. It rests there for the remainder before a new
|
||||||
|
// leaf is emitted. Z remains fixed, preserving sprite depth order.
|
||||||
|
float life = fract(grassWind.y * 0.085 + VertexGrass.x * 0.159);
|
||||||
|
float fall = min(life / 0.72, 1.0);
|
||||||
|
float travel = fall * 44.0 - 6.0;
|
||||||
|
float flutter = grassWind.y * 3.0 + VertexGrass.x * 4.7;
|
||||||
|
float lift = sin(fall * 3.14159265) * 5.0;
|
||||||
|
float gravity = 9.0 * fall * fall;
|
||||||
|
float flutterFade = 1.0 - smoothstep(0.62, 1.0, fall);
|
||||||
|
w.x += travel + sin(flutter) * 1.2 * flutterFade;
|
||||||
|
w.y += lift - gravity
|
||||||
|
+ sin(flutter * 0.61) * 0.8 * flutterFade;
|
||||||
|
} else {
|
||||||
|
vec2 offset = vec2(wave * grassWind.z, 0.0);
|
||||||
|
vec2 clump = VertexGrass.yz;
|
||||||
|
float bodyDist = length(clump - grassPlayer.xy);
|
||||||
|
float touch = 1.0 - smoothstep(grassPlayer.z * 0.45,
|
||||||
|
grassPlayer.z, bodyDist);
|
||||||
|
float side = clump.x < grassPlayer.x ? -1.0 : 1.0;
|
||||||
|
if (abs(clump.x - grassPlayer.x) < 0.5)
|
||||||
|
side = sin(VertexGrass.x) < 0.0 ? -1.0 : 1.0;
|
||||||
|
w.xz += offset * bend;
|
||||||
|
w.x += side * touch * grassPlayer.w;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// THE MAP'S HAZE (see ForestAtmos): how much fog stands between the
|
// THE MAP'S HAZE (see ForestAtmos): how much fog stands between the
|
||||||
// eye and this vertex -- distance dissolves into it, altitude climbs
|
// eye and this vertex -- distance dissolves into it, altitude climbs
|
||||||
@@ -389,6 +420,9 @@ local SHADER = [[
|
|||||||
// solid silhouette. Last in the chain, so neither the sun nor a voxel
|
// solid silhouette. Last in the chain, so neither the sun nor a voxel
|
||||||
// seam can mottle it.
|
// seam can mottle it.
|
||||||
rgb = mix(rgb, ghostColor, ghost);
|
rgb = mix(rgb, ghostColor, ghost);
|
||||||
|
// Emissive but still coloured: increasingly visible as Lua raises the
|
||||||
|
// night factor, without adding more insects or washing the scene white.
|
||||||
|
rgb = mix(rgb, vec3(0.82, 1.00, 0.22), vFirefly);
|
||||||
// the viewport's rim is an ALPHA, so the last of the model blends into
|
// the viewport's rim is an ALPHA, so the last of the model blends into
|
||||||
// whatever the frame opened with -- the sky, or the chroma key. 1
|
// whatever the frame opened with -- the sky, or the chroma key. 1
|
||||||
// everywhere without the cut compiled in, which is every flat frame.
|
// everywhere without the cut compiled in, which is every flat frame.
|
||||||
@@ -1116,6 +1150,7 @@ function Voxel3D.beginScene(w, h, cx, cy, vw, vh, sky, slot)
|
|||||||
pcall(sh.send, sh, "ghostColor", Voxel3D.GHOST_COLOR)
|
pcall(sh.send, sh, "ghostColor", Voxel3D.GHOST_COLOR)
|
||||||
-- the hour's light, as the caller last set it (see Voxel3D.tint)
|
-- the hour's light, as the caller last set it (see Voxel3D.tint)
|
||||||
pcall(sh.send, sh, "dayTint", Voxel3D.tint or { 1, 1, 1 })
|
pcall(sh.send, sh, "dayTint", Voxel3D.tint or { 1, 1, 1 })
|
||||||
|
pcall(sh.send, sh, "fireflyNight", Voxel3D.fireflyNight or 0)
|
||||||
-- and the map's haze (see Voxel3D.fog), density 0 when there is none
|
-- and the map's haze (see Voxel3D.fog), density 0 when there is none
|
||||||
local fog = Voxel3D.fog
|
local fog = Voxel3D.fog
|
||||||
pcall(sh.send, sh, "fogColor", (fog and fog.color) or { 0, 0, 0 })
|
pcall(sh.send, sh, "fogColor", (fog and fog.color) or { 0, 0, 0 })
|
||||||
|
|||||||
@@ -913,6 +913,9 @@ function VoxelScene.render(state, w, h, vw, vh, paletteFor, eyes)
|
|||||||
local GlassMask = V.require("GlassMask")
|
local GlassMask = V.require("GlassMask")
|
||||||
Voxel3D.glassMask = outdoor and GlassMask.texture(state.map.tileset) or nil
|
Voxel3D.glassMask = outdoor and GlassMask.texture(state.map.tileset) or nil
|
||||||
Voxel3D.glassNight = outdoor and DayNight.windowLight() or 0
|
Voxel3D.glassNight = outdoor and DayNight.windowLight() or 0
|
||||||
|
-- The existing day/night ramp is also a darkness factor. Fireflies fade
|
||||||
|
-- in naturally at dusk and reach full contrast only at deepest night.
|
||||||
|
Voxel3D.fireflyNight = outdoor and DayNight.windowLight() or 0
|
||||||
local g = VoxelScene.glintStep(glint, cx, cy)
|
local g = VoxelScene.glintStep(glint, cx, cy)
|
||||||
Voxel3D.glassPhase, Voxel3D.glassGlint = g.phase, g.amp
|
Voxel3D.glassPhase, Voxel3D.glassGlint = g.phase, g.amp
|
||||||
-- and the map's atmosphere, if it has one (see ForestAtmos): the haze
|
-- and the map's atmosphere, if it has one (see ForestAtmos): the haze
|
||||||
|
|||||||
Reference in New Issue
Block a user