5#include "graphics/shaders/water_frag_spv.inc"
20const char *kWtrShaderFrag = R
"GLSL(#version 450
21layout(location = 0) in vec3 vNormal;
22layout(location = 1) in vec2 vUV;
23layout(location = 2) in vec4 vTint;
24layout(location = 3) in vec3 vWorldPos;
25layout(location = 4) in vec3 vCameraPos;
26layout(location = 5) in vec3 vViewPos;
28struct Light3D { vec4 posRadius; vec4 color; };
29layout(set = 0, binding = 0, std140) uniform Frame {
32 vec4 lightDirIntensity;
44layout(set = 0, binding = 1) uniform sampler2D albedo;
45layout(set = 0, binding = 3) uniform samplerCube env;
46layout(set = 0, binding = 6) uniform sampler2D ssrTex; // screen-space reflection (optional)
48layout(push_constant) uniform Externals { float data[32]; } u;
49layout(location = 0) out vec4 outColor;
51const float PI = 3.14159265;
53float hash(float n) { return fract(sin(n) * 43758.5453123); }
54vec2 hash2(int i) { return vec2(hash(float(i) * 7.31), hash(float(i) * 13.17 + 1.0)); }
56// Expanding damped-wavelet ripple from a periodic drop (smooth, water-like).
57float rippleRing(vec2 uv, int i) {
58 float period = max(u.data[7], 1e-3); // rippleInterval
59 float local = mod(u.data[0], period);
60 float startPhase = hash(float(i) * 3.7) * period;
61 float age = local - startPhase;
62 if (age < 0.0) return 0.0;
63 float life = period * 0.8;
64 if (age > life) return 0.0;
65 vec2 center = hash2(i);
66 center = mix(vec2(0.5), center, 0.72); // keep drops near the middle
67 float r = length(uv - center);
68 float radius = age * 0.22; // expanding ring
69 float wavelength = 0.10; // spacing between crests
70 float x = (r - radius) / wavelength;
71 // Soft damped wavelet: smooth gaussian envelope, a crest + trough pair.
72 float envelope = exp(-(x * x) * 0.9);
73 float wave = cos(x * 6.28318);
74 float fade = exp(-age * 1.6); // fade out as it grows
75 return u.data[3] * envelope * wave * fade; // rippleAmp
78// Water height displacement over UV.
79float waterHeight(vec2 uv) {
82 vec2 edgeDist = min(uv, vec2(1.0) - uv);
83 float edgeFactor = 1.0 - clamp(min(edgeDist.x, edgeDist.y) / max(u.data[4], 1e-4), 0.0, 1.0);
85 // Shore-edge waves, strongest at the border and fading inward.
86 w += edgeFactor * u.data[2] * (sin((uv.x * ws + t * u.data[1]) * PI * 2.0) +
87 0.5 * sin((uv.y * ws * 0.7 - t * u.data[1] * 1.3) * PI * 2.0));
88 // Fine detail everywhere.
89 w += u.data[2] * 0.10 * sin((uv.x * 31.0 + uv.y * 17.0 + t * u.data[1] * 2.0) * PI * 2.0);
90 // Occasional middle drop ripples.
91 int n = int(u.data[6] + 0.5);
92 for (int i = 0; i < 8; ++i) {
94 w += rippleRing(uv, i);
100 // Surface normal from the analytic displacement (finite differences).
102 float hL = waterHeight(vUV - vec2(eps, 0.0));
103 float hR = waterHeight(vUV + vec2(eps, 0.0));
104 float hD = waterHeight(vUV - vec2(0.0, eps));
105 float hU = waterHeight(vUV + vec2(0.0, eps));
106 vec2 grad = vec2((hR - hL) / (2.0 * eps), (hU - hD) / (2.0 * eps));
107 vec3 N = normalize(vec3(-grad.x, 1.0, -grad.y));
109 vec3 V = normalize(ubo.cameraPos.xyz - vWorldPos);
110 vec3 R = reflect(-V, N);
112 // Sky reflection via the env cubemap, blurred more at grazing angles.
113 // Clamp LOD to available mip levels so a non-mipmapped env is still safe.
114 float ndv = max(dot(V, N), 0.0);
115 float maxLod = float(max(textureQueryLevels(env) - 1, 0));
116 float lod = min(1.0 + (1.0 - ndv) * 4.0, maxLod);
117 vec3 refl = textureLod(env, R, lod).rgb * vec3(u.data[12], u.data[13], u.data[14]);
118 float fresnel = 0.02 + 0.98 * pow(1.0 - ndv, 5.0);
120 vec3 waterCol = vec3(u.data[9], u.data[10], u.data[11]);
121 float reflectAmt = clamp(fresnel * u.data[5] + 0.30, 0.0, 1.0);
122 vec3 color = mix(waterCol, refl, reflectAmt);
124 // Sun glint highlight.
125 vec3 L = normalize(ubo.lightDirIntensity.xyz);
126 vec3 H = normalize(V + L);
127 float spec = pow(max(dot(N, H), 0.0), 96.0);
128 color += ubo.lightColor.rgb * spec * u.data[15];
130 // Soft foam where waves meet the edge.
131 vec2 edgeDist = min(vUV, vec2(1.0) - vUV);
132 float edgeFactor = 1.0 - clamp(min(edgeDist.x, edgeDist.y) / max(u.data[4], 1e-4), 0.0, 1.0);
133 color = mix(color, vec3(0.85, 0.93, 1.0), edgeFactor * 0.22);
135 // Optional screen-space reflection overlay (bound via the height slot,
136 // binding 6). Sample the SSR pass result at this fragment's screen UV and
137 // blend it over the env reflection where SSR found a hit (ssr.a > 0).
138 if (u.data[18] > 0.5 && u.data[16] > 1.0 && u.data[17] > 1.0) {
139 vec2 sUV = vec2(gl_FragCoord.x / u.data[16], gl_FragCoord.y / u.data[17]);
140 vec4 ssr = texture(ssrTex, sUV);
141 color = mix(color, ssr.rgb, clamp(ssr.a * u.data[19], 0.0, 1.0));
144 outColor = vec4(color, 1.0);
148const char *kUniformNames[] = {
149 "time",
"waveSpeed",
"waveAmp",
"rippleAmp",
"edgeFalloff",
150 "reflInten",
"rippleCnt",
"rippleInt",
"waveScale",
"waterCol",
151 "reflTint",
"sunInten",
"viewportW",
"viewportH",
"ssrEnabled",
154const int kUniformCount = int(
sizeof(kUniformNames) /
sizeof(kUniformNames[0]));
161 std::vector<uint32_t>
frag(water_frag_spv, water_frag_spv + water_frag_spv_count);
163 for (
int i = 0; i < kUniformCount; ++i) {
164 if (std::string(kUniformNames[i]) ==
"waterCol")
166 else if (std::string(kUniformNames[i]) ==
"reflTint")
177 if (index < 0 || index >= kUniformCount)
return {};
178 return kUniformNames[index];
187 segX = std::max(1, segX);
188 segZ = std::max(1, segZ);
189 std::vector<float>
pos, nrm, uv;
190 std::vector<uint32_t>
idx;
191 for (
int z = 0;
z <= segZ; ++
z) {
192 for (
int x = 0;
x <= segX; ++
x) {
193 pos.push_back(-sizeX * 0.5f + sizeX *
float(
x) / segX);
195 pos.push_back(-sizeZ * 0.5f + sizeZ *
float(
z) / segZ);
199 uv.push_back(
float(
x) / segX);
200 uv.push_back(
float(
z) / segZ);
203 for (
int z = 0;
z < segZ; ++
z) {
204 for (
int x = 0;
x < segX; ++
x) {
205 const uint32_t
a = uint32_t(
z * (segX + 1) +
x);
206 const uint32_t
b =
a + 1;
207 const uint32_t
c =
a + uint32_t(segX + 1);
208 const uint32_t
d =
c + 1;
218 idx.data(),
int(
idx.size()));
244 reflectionTint_[0] = r;
245 reflectionTint_[1] = g;
246 reflectionTint_[2] =
b;
252 ssrStrength_ = std::max(0.f, strength);
255 viewportW_ = std::max(0.f,
w);
256 viewportH_ = std::max(0.f,
h);
260 if (!shader_)
return;
262 shader_->
sendFloat(
"waveSpeed", waveSpeed_);
263 shader_->
sendFloat(
"waveAmp", waveAmplitude_);
264 shader_->
sendFloat(
"rippleAmp", rippleAmplitude_);
265 shader_->
sendFloat(
"edgeFalloff", edgeFalloff_);
266 shader_->
sendFloat(
"reflInten", reflectionIntensity_);
267 shader_->
sendFloat(
"rippleCnt",
float(rippleCount_));
268 shader_->
sendFloat(
"rippleInt", rippleInterval_);
269 shader_->
sendFloat(
"waveScale", waveScale_);
270 shader_->
sendVec3(
"waterCol", waterColor_[0], waterColor_[1], waterColor_[2]);
271 shader_->
sendVec3(
"reflTint", reflectionTint_[0], reflectionTint_[1], reflectionTint_[2]);
272 shader_->
sendFloat(
"sunInten", sunIntensity_);
273 shader_->
sendFloat(
"viewportW", viewportW_);
274 shader_->
sendFloat(
"viewportH", viewportH_);
275 shader_->
sendFloat(
"ssrEnabled", ssrEnabled_ ? 1.f : 0.f);
276 shader_->
sendFloat(
"ssrStrength", ssrStrength_);
280 if (!gfx_ || !mesh_ || !shader_)
return;
281 gfx_->
drawMeshShader(mesh_, glm::mat4(1.f),
nullptr, glm::vec4(1.f), shader_);
virtual Shader * newMeshShaderFromSpv(const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv)=0
Create a Mesh3D custom shader (MeshVertex + Frame UBO + albedo). Empty vert → default mesh3d....
virtual void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint, Shader *shader)=0
Draw mesh with an explicit Mesh3D Shader (nullptr = default PBR pipeline).
virtual Mesh * newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount)=0
Upload a triangle mesh from packed CPU arrays. Owned by Graphics. posXYZ required (vertexCount*3)....
void sendFloat(const std::string &name, float x)
void sendVec3(const std::string &name, float x, float y, float z)
int declareVec3(const std::string &name)
int declareFloat(const std::string &name)
Reserve sequential float slots in the push-constant block. Returns start index.
void setScreenSpaceReflection(bool enabled, float strength=0.85f)
Optional screen-space reflection overlay. When enabled, the shader samples the SSR pass result (bound...
void setWaveAmplitude(float amp)
Amplitude of the shore-edge waves.
static std::string paramName(int index)
void setRippleAmplitude(float amp)
Amplitude of the occasional middle drop ripples.
void setWaveSpeed(float speed)
void update(float dt)
Advance the animation clock by dt seconds.
void draw()
Draw the water plane (uses default mesh3d camera / lighting state).
void setEdgeFalloff(float edge)
Width (in UV, 0..1) of the edge wave band.
void createPlane(float sizeX, float sizeZ, int segX, int segZ)
Build a flat XZ plane (Y-up) sized sizeX × sizeZ with UVs in [0,1]².
void setSunIntensity(float intensity)
void setReflectionTint(float r, float g, float b)
void setTime(float seconds)
void setReflectionIntensity(float intensity)
static int paramCount()
Names of the push-constant parameters (for UI / inspection).
void setWaveScale(float scale)
void setRippleInterval(float seconds)
Seconds between drop ripples.
void bindParams()
Upload current params to the shader push constants.
void setViewport(float width, float height)
Window / target size in pixels, used to compute screen-space UVs.
void setRippleCount(int count)
How many expanding drop ripples exist.
void setWaterColor(float r, float g, float b)
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Shader * newWaterShader(Graphics *gfx)
Create the embedded water fragment shader (owned by Graphics).