feat(graphics): add configurable stylized water
This commit is contained in:
1 parent
e58bf96a6f
commit
7e45cdaebd
20 files changed
+2259
-1237
No files matched your search
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://evengine.dev/schema/eve.graphics.stylized-water-v1.schema.json",
|
||||
"title": "EVEngine Stylized Water",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["schema", "schemaVersion", "unknownFields", "colors", "waves", "depth", "foam", "lighting", "refraction", "caustics"],
|
||||
"properties": {
|
||||
"schema": { "const": "eve.graphics.stylized-water" },
|
||||
"schemaVersion": { "const": 1 },
|
||||
"unknownFields": { "const": "reject" },
|
||||
"colors": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["deep", "shallow", "foam", "reflectionTint"],
|
||||
"properties": {
|
||||
"deep": { "$ref": "#/$defs/color" },
|
||||
"shallow": { "$ref": "#/$defs/color" },
|
||||
"foam": { "$ref": "#/$defs/color" },
|
||||
"reflectionTint": { "$ref": "#/$defs/color" }
|
||||
}
|
||||
},
|
||||
"waves": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["speed", "amplitude", "scale", "sharpness", "rippleAmplitude", "rippleCount", "rippleInterval"],
|
||||
"properties": {
|
||||
"speed": { "type": "number", "minimum": -20, "maximum": 20 },
|
||||
"amplitude": { "type": "number", "minimum": 0, "maximum": 4 },
|
||||
"scale": { "type": "number", "minimum": 0.01, "maximum": 256 },
|
||||
"sharpness": { "type": "number", "minimum": 0.1, "maximum": 8 },
|
||||
"rippleAmplitude": { "type": "number", "minimum": 0, "maximum": 4 },
|
||||
"rippleCount": { "type": "integer", "minimum": 0, "maximum": 8 },
|
||||
"rippleInterval": { "type": "number", "minimum": 0.05, "maximum": 60 }
|
||||
}
|
||||
},
|
||||
"depth": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["distance", "opacity"],
|
||||
"properties": {
|
||||
"distance": { "type": "number", "exclusiveMinimum": 0, "maximum": 10000 },
|
||||
"opacity": { "type": "number", "minimum": 0, "maximum": 1 }
|
||||
}
|
||||
},
|
||||
"foam": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["width", "softness", "strength"],
|
||||
"properties": {
|
||||
"width": { "type": "number", "exclusiveMinimum": 0, "maximum": 1000 },
|
||||
"softness": { "type": "number", "minimum": 0, "maximum": 1 },
|
||||
"strength": { "type": "number", "minimum": 0, "maximum": 4 }
|
||||
}
|
||||
},
|
||||
"lighting": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["fresnelPower", "reflectionIntensity", "sunIntensity", "screenSpaceReflection", "screenSpaceReflectionStrength"],
|
||||
"properties": {
|
||||
"fresnelPower": { "type": "number", "minimum": 0.01, "maximum": 32 },
|
||||
"reflectionIntensity": { "type": "number", "minimum": 0, "maximum": 4 },
|
||||
"sunIntensity": { "type": "number", "minimum": 0, "maximum": 8 },
|
||||
"screenSpaceReflection": { "type": "boolean" },
|
||||
"screenSpaceReflectionStrength": { "type": "number", "minimum": 0, "maximum": 4 }
|
||||
}
|
||||
},
|
||||
"refraction": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["strength"],
|
||||
"properties": {
|
||||
"strength": { "type": "number", "minimum": 0, "maximum": 0.25 }
|
||||
}
|
||||
},
|
||||
"caustics": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["strength", "scale"],
|
||||
"properties": {
|
||||
"strength": { "type": "number", "minimum": 0, "maximum": 4, "default": 0.35 },
|
||||
"scale": { "type": "number", "minimum": 0.01, "maximum": 256, "default": 3.5 }
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"color": {
|
||||
"type": "array",
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"items": { "type": "number", "minimum": 0, "maximum": 8 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,6 +213,17 @@ local fall = gfx.newWaterfall();
|
||||
fall.createCurvedSheet(3.0, 7.0, 28, 48, 0.75, 0.85);
|
||||
```
|
||||
|
||||
### 风格化水体配置
|
||||
|
||||
`Water.applyConfigJson(json)` 使用 `eve.graphics.stylized-water` 版本化 schema 一次性校验并应用深浅水色、波浪、泡沫、透明度、折射、反射和焦散参数;失败时保留原配置。`Water.configJson()` 返回当前配置的规范 JSON,可用于编辑器属性面板、预设保存和运行时复制。
|
||||
|
||||
```squirrel
|
||||
local water = gfx.newWater();
|
||||
water.createPlane(14.0, 14.0, 64, 64);
|
||||
local current = water.configJson();
|
||||
water.applyConfigJson(current);
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
- 忘记每帧 `clear()`,保留未定义的旧帧内容。
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SHADER_DIR = ROOT / "src" / "modules" / "graphics" / "shaders"
|
||||
|
||||
FRAGS = ["water.frag"]
|
||||
SHADERS = [("water.vert", "vert"), ("water.frag", "frag")]
|
||||
|
||||
|
||||
def spv_to_inc(spv_path: Path, array_name: str, out_path: Path) -> None:
|
||||
@@ -37,17 +37,17 @@ def spv_to_inc(spv_path: Path, array_name: str, out_path: Path) -> None:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
for name in FRAGS:
|
||||
for name, stage in SHADERS:
|
||||
src = SHADER_DIR / name
|
||||
if not src.is_file():
|
||||
raise SystemExit(f"missing {src}")
|
||||
out_spv = src.with_suffix(".spv")
|
||||
r = subprocess.run(["glslc", "-fshader-stage=frag", str(src), "-o", str(out_spv)],
|
||||
r = subprocess.run(["glslc", f"-fshader-stage={stage}", str(src), "-o", str(out_spv)],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise SystemExit(f"glslc failed for {name}:\n{r.stderr or r.stdout}")
|
||||
array = f"{src.stem}_frag_spv"
|
||||
spv_to_inc(out_spv, array, SHADER_DIR / f"{src.stem}_frag_spv.inc")
|
||||
array = f"{src.stem}_{stage}_spv"
|
||||
spv_to_inc(out_spv, array, SHADER_DIR / f"{src.stem}_{stage}_spv.inc")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "graphics/Exposure.h"
|
||||
#include "graphics/DepthPyramid.h"
|
||||
#include "common/Capability.h"
|
||||
#include "common/SquirrelBinding.h"
|
||||
#include "common/config.h"
|
||||
#include "font/FontData.h"
|
||||
#include "graphics/ArtifactProvider.h"
|
||||
@@ -892,6 +893,12 @@ void Graphics::expose(ssq::Table& table) {
|
||||
water.addFunc("setScreenSpaceReflection", &Water::setScreenSpaceReflection);
|
||||
water.addFunc("getScreenSpaceReflection", &Water::getScreenSpaceReflection);
|
||||
water.addFunc("getScreenSpaceReflectionStrength", &Water::getScreenSpaceReflectionStrength);
|
||||
water.addFunc("applyConfigJson", [vm = table.getHandle()](Water* value, const std::string& json) {
|
||||
return eve::script::projectResult(vm, value->applyConfigJson(json));
|
||||
});
|
||||
water.addFunc("configJson", [vm = table.getHandle()](Water* value) {
|
||||
return eve::script::projectResult(vm, value->configJson(), [](std::string json) { return json; });
|
||||
});
|
||||
water.addFunc("setViewport", &Water::setViewport);
|
||||
water.addFunc("getViewportWidth", &Water::getViewportWidth);
|
||||
water.addFunc("getViewportHeight", &Water::getViewportHeight);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#include "graphics/Water.h"
|
||||
|
||||
#include "graphics/Graphics.h"
|
||||
#include "graphics/GBuffer.h"
|
||||
#include "graphics/Mesh.h"
|
||||
#include "graphics/RenderControl.h"
|
||||
#include "graphics/RenderSystem3D.h"
|
||||
#include "graphics/ScreenSpaceReflection.h"
|
||||
#include "graphics/shaders/WaterWgsl.h"
|
||||
#include "graphics/shaders/water_frag_spv.inc"
|
||||
#include "graphics/shaders/water_vert_spv.inc"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
@@ -17,16 +19,20 @@ namespace eve::graphics {
|
||||
namespace {
|
||||
|
||||
// Push-constant layout (data[32]):
|
||||
// 0 time, 1 waveSpeed, 2 waveAmp, 3 rippleAmp, 4 edgeFalloff,
|
||||
// 5 reflIntensity, 6 rippleCount, 7 rippleInterval, 8 waveScale,
|
||||
// 9..11 waterColor, 12..14 reflTint, 15 sunIntensity,
|
||||
// 16 viewportW, 17 viewportH, 18 ssrEnabled, 19 ssrStrength.
|
||||
// 0 time, 1 waveSpeed, 2 waveAmp, 3 waveScale, 4 rippleAmp,
|
||||
// 5 rippleCount, 6 rippleInterval, 7 foamWidth, 8 foamSoftness,
|
||||
// 9 foamStrength, 10..12 deepColor, 13..15 shallowColor,
|
||||
// 16 depthDistance, 17..19 foamColor, 20 reflectionIntensity,
|
||||
// 21..23 reflectionTint, 24 sunIntensity, 25 fresnelPower,
|
||||
// 26 causticsStrength, 27 signedSsrStrength, 28 refractionStrength,
|
||||
// 29 signedOpacityAndDepthAvailability, 30 causticsScale, 31 waveSharpness.
|
||||
|
||||
const char *kUniformNames[] = {
|
||||
"time", "waveSpeed", "waveAmp", "rippleAmp", "edgeFalloff",
|
||||
"reflInten", "rippleCnt", "rippleInt", "waveScale", "waterCol",
|
||||
"reflTint", "sunInten", "viewportW", "viewportH", "ssrEnabled",
|
||||
"ssrStrength",
|
||||
"time", "waveSpeed", "waveAmp", "waveScale", "rippleAmp",
|
||||
"rippleCnt", "rippleInt", "foamWidth", "foamSoftness", "foamStrength",
|
||||
"deepColor", "shallowColor", "depthDistance", "foamColor", "reflInten",
|
||||
"reflTint", "sunInten", "fresnelPower", "causticsStrength", "ssrStrength",
|
||||
"refractionStrength", "opacityAndDepth", "causticsScale", "waveSharpness",
|
||||
};
|
||||
const int kUniformCount = int(sizeof(kUniformNames) / sizeof(kUniformNames[0]));
|
||||
|
||||
@@ -37,15 +43,17 @@ Shader *newWaterShader(Graphics *gfx) {
|
||||
// on platforms without a runtime compiler (e.g. Windows).
|
||||
Shader *sh = nullptr;
|
||||
if (gfx->getBackendName() == "webgpu") {
|
||||
sh = gfx->newMeshShaderFromWgsl({}, shaders::kWaterFragWgsl);
|
||||
sh = gfx->newMeshShaderFromWgsl(shaders::kWaterVertWgsl, shaders::kWaterFragWgsl);
|
||||
} else {
|
||||
std::vector<uint32_t> vert(water_vert_spv, water_vert_spv + water_vert_spv_count);
|
||||
std::vector<uint32_t> frag(water_frag_spv, water_frag_spv + water_frag_spv_count);
|
||||
sh = gfx->newMeshShaderFromSpv({}, frag);
|
||||
sh = gfx->newMeshShaderFromSpv(vert, frag);
|
||||
}
|
||||
gfx->configureMeshShaderSurface(*sh, BlendMode::Alpha, false, false)
|
||||
.expect("water requires alpha blending with depth writes disabled");
|
||||
for (int i = 0; i < kUniformCount; ++i) {
|
||||
if (std::string(kUniformNames[i]) == "waterCol")
|
||||
sh->declareVec3(kUniformNames[i]);
|
||||
else if (std::string(kUniformNames[i]) == "reflTint")
|
||||
const std::string name(kUniformNames[i]);
|
||||
if (name == "deepColor" || name == "shallowColor" || name == "foamColor" || name == "reflTint")
|
||||
sh->declareVec3(kUniformNames[i]);
|
||||
else
|
||||
sh->declareFloat(kUniformNames[i]);
|
||||
@@ -118,28 +126,24 @@ void Water::setTime(float seconds) {
|
||||
bindParams();
|
||||
}
|
||||
|
||||
void Water::setWaveSpeed(float v) { waveSpeed_ = v; }
|
||||
void Water::setWaveAmplitude(float v) { waveAmplitude_ = v; }
|
||||
void Water::setRippleAmplitude(float v) { rippleAmplitude_ = v; }
|
||||
void Water::setEdgeFalloff(float v) { edgeFalloff_ = std::max(0.001f, v); }
|
||||
void Water::setRippleCount(int v) { rippleCount_ = std::max(0, v); }
|
||||
void Water::setRippleInterval(float v) { rippleInterval_ = std::max(0.05f, v); }
|
||||
void Water::setWaveScale(float v) { waveScale_ = std::max(0.1f, v); }
|
||||
void Water::setWaveSpeed(float v) { config_.waveSpeed = std::clamp(v, -20.0F, 20.0F); }
|
||||
void Water::setWaveAmplitude(float v) { config_.waveAmplitude = std::clamp(v, 0.0F, 4.0F); }
|
||||
void Water::setRippleAmplitude(float v) { config_.rippleAmplitude = std::clamp(v, 0.0F, 4.0F); }
|
||||
void Water::setEdgeFalloff(float v) { config_.foamWidth = std::clamp(v, 0.001F, 1000.0F); }
|
||||
void Water::setRippleCount(int v) { config_.rippleCount = std::clamp(v, 0, 8); }
|
||||
void Water::setRippleInterval(float v) { config_.rippleInterval = std::clamp(v, 0.05F, 60.0F); }
|
||||
void Water::setWaveScale(float v) { config_.waveScale = std::clamp(v, 0.01F, 256.0F); }
|
||||
void Water::setWaterColor(float r, float g, float b) {
|
||||
waterColor_[0] = r;
|
||||
waterColor_[1] = g;
|
||||
waterColor_[2] = b;
|
||||
config_.deepColor = glm::max(glm::vec3(r, g, b), glm::vec3(0.0F));
|
||||
}
|
||||
void Water::setReflectionTint(float r, float g, float b) {
|
||||
reflectionTint_[0] = r;
|
||||
reflectionTint_[1] = g;
|
||||
reflectionTint_[2] = b;
|
||||
config_.reflectionTint = glm::max(glm::vec3(r, g, b), glm::vec3(0.0F));
|
||||
}
|
||||
void Water::setReflectionIntensity(float v) { reflectionIntensity_ = std::max(0.f, v); }
|
||||
void Water::setSunIntensity(float v) { sunIntensity_ = std::max(0.f, v); }
|
||||
void Water::setReflectionIntensity(float v) { config_.reflectionIntensity = std::clamp(v, 0.0F, 4.0F); }
|
||||
void Water::setSunIntensity(float v) { config_.sunIntensity = std::clamp(v, 0.0F, 8.0F); }
|
||||
void Water::setScreenSpaceReflection(bool enabled, float strength) {
|
||||
ssrEnabled_ = enabled;
|
||||
ssrStrength_ = std::max(0.f, strength);
|
||||
config_.screenSpaceReflection = enabled;
|
||||
config_.screenSpaceReflectionStrength = std::clamp(strength, 0.0F, 4.0F);
|
||||
}
|
||||
void Water::setViewport(float w, float h) {
|
||||
viewportW_ = std::max(0.f, w);
|
||||
@@ -149,43 +153,70 @@ void Water::setViewport(float w, float h) {
|
||||
void Water::bindParams() {
|
||||
if (!shader_) return;
|
||||
shader_->sendFloat("time", time_);
|
||||
shader_->sendFloat("waveSpeed", waveSpeed_);
|
||||
shader_->sendFloat("waveAmp", waveAmplitude_);
|
||||
shader_->sendFloat("rippleAmp", rippleAmplitude_);
|
||||
shader_->sendFloat("edgeFalloff", edgeFalloff_);
|
||||
shader_->sendFloat("reflInten", reflectionIntensity_);
|
||||
shader_->sendFloat("rippleCnt", float(rippleCount_));
|
||||
shader_->sendFloat("rippleInt", rippleInterval_);
|
||||
shader_->sendFloat("waveScale", waveScale_);
|
||||
shader_->sendVec3("waterCol", waterColor_[0], waterColor_[1], waterColor_[2]);
|
||||
shader_->sendVec3("reflTint", reflectionTint_[0], reflectionTint_[1], reflectionTint_[2]);
|
||||
shader_->sendFloat("sunInten", sunIntensity_);
|
||||
shader_->sendFloat("viewportW", viewportW_);
|
||||
shader_->sendFloat("viewportH", viewportH_);
|
||||
shader_->sendFloat("ssrEnabled", ssrEnabled_ ? 1.f : 0.f);
|
||||
shader_->sendFloat("ssrStrength", ssrStrength_);
|
||||
shader_->sendFloat("waveSpeed", config_.waveSpeed);
|
||||
shader_->sendFloat("waveAmp", config_.waveAmplitude);
|
||||
shader_->sendFloat("waveScale", config_.waveScale);
|
||||
shader_->sendFloat("rippleAmp", config_.rippleAmplitude);
|
||||
shader_->sendFloat("rippleCnt", float(config_.rippleCount));
|
||||
shader_->sendFloat("rippleInt", config_.rippleInterval);
|
||||
shader_->sendFloat("foamWidth", config_.foamWidth);
|
||||
shader_->sendFloat("foamSoftness", config_.foamSoftness);
|
||||
shader_->sendFloat("foamStrength", config_.foamStrength);
|
||||
shader_->sendVec3("deepColor", config_.deepColor.x, config_.deepColor.y, config_.deepColor.z);
|
||||
shader_->sendVec3("shallowColor", config_.shallowColor.x, config_.shallowColor.y, config_.shallowColor.z);
|
||||
shader_->sendFloat("depthDistance", config_.depthDistance);
|
||||
shader_->sendVec3("foamColor", config_.foamColor.x, config_.foamColor.y, config_.foamColor.z);
|
||||
shader_->sendFloat("reflInten", config_.reflectionIntensity);
|
||||
shader_->sendVec3("reflTint", config_.reflectionTint.x, config_.reflectionTint.y, config_.reflectionTint.z);
|
||||
shader_->sendFloat("sunInten", config_.sunIntensity);
|
||||
shader_->sendFloat("fresnelPower", config_.fresnelPower);
|
||||
shader_->sendFloat("causticsStrength", config_.causticsStrength);
|
||||
shader_->sendFloat("ssrStrength", config_.screenSpaceReflection ? config_.screenSpaceReflectionStrength : -1.0F);
|
||||
shader_->sendFloat("refractionStrength", config_.refractionStrength);
|
||||
shader_->sendFloat("opacityAndDepth", -(config_.opacity + 1.0F));
|
||||
shader_->sendFloat("causticsScale", config_.causticsScale);
|
||||
shader_->sendFloat("waveSharpness", config_.waveSharpness);
|
||||
}
|
||||
|
||||
void Water::draw() {
|
||||
Result<void> Water::applyConfigJson(const std::string& json) {
|
||||
auto candidate = WaterStyleConfig::fromJson(json);
|
||||
if (!candidate) return Result<void>::failure(candidate.status());
|
||||
config_ = std::move(candidate).takeValue();
|
||||
bindParams();
|
||||
return Result<void>::success();
|
||||
}
|
||||
|
||||
Result<std::string> Water::configJson() const { return config_.toJson(); }
|
||||
|
||||
void Water::draw() { drawWithReflection(nullptr, 0.0F); }
|
||||
|
||||
void Water::drawWithPlanarReflection(Texture* planarReflection, float strength) {
|
||||
drawWithReflection(planarReflection, std::clamp(strength, 0.0F, 4.0F));
|
||||
}
|
||||
|
||||
void Water::drawWithReflection(Texture* requestedReflection, float requestedStrength) {
|
||||
if (!gfx_ || !mesh_ || !shader_) return;
|
||||
bindParams();
|
||||
const int drawableW = gfx_->getPixelWidth() > 0 ? gfx_->getPixelWidth() : gfx_->getWidth();
|
||||
const int drawableH = gfx_->getPixelHeight() > 0 ? gfx_->getPixelHeight() : gfx_->getHeight();
|
||||
const float viewportW = viewportW_ > 0.f ? viewportW_ : float(drawableW);
|
||||
const float viewportH = viewportH_ > 0.f ? viewportH_ : float(drawableH);
|
||||
shader_->sendFloat("viewportW", std::max(viewportW, 1.f));
|
||||
shader_->sendFloat("viewportH", std::max(viewportH, 1.f));
|
||||
|
||||
Texture *reflection = nullptr;
|
||||
Texture *reflection = requestedReflection;
|
||||
float reflectionStrength = requestedReflection ? requestedStrength : -1.0F;
|
||||
RenderControl *rc = gfx_->getRenderControl();
|
||||
if (ssrEnabled_ && rc && rc->isEnabled("ssr")) {
|
||||
if (!reflection && config_.screenSpaceReflection && rc && rc->isEnabled("ssr")) {
|
||||
ScreenSpaceReflection *ssr = gfx_->pipelineScreenSpaceReflection();
|
||||
if (ssr->hasValidHistory()) reflection = ssr->getReflectionTexture();
|
||||
if (ssr->hasValidHistory()) {
|
||||
reflection = ssr->getReflectionTexture();
|
||||
reflectionStrength = config_.screenSpaceReflectionStrength;
|
||||
}
|
||||
}
|
||||
shader_->sendFloat("ssrEnabled", reflection ? 1.f : 0.f);
|
||||
shader_->sendFloat("ssrStrength", reflection ? reflectionStrength : -1.0F);
|
||||
Texture *depth = rc && rc->getGBuffer() ? rc->getGBuffer()->getDepthTexture() : nullptr;
|
||||
Texture *sceneColor = gfx_->getSceneColorTexture();
|
||||
shader_->sendFloat("opacityAndDepth", depth ? config_.opacity : -(config_.opacity + 1.0F));
|
||||
shader_->sendFloat("refractionStrength", sceneColor ? config_.refractionStrength : 0.0F);
|
||||
gfx_->setMesh3DHeightTexture(reflection);
|
||||
gfx_->drawMeshShader(mesh_, glm::mat4(1.f), nullptr, glm::vec4(1.f), shader_);
|
||||
gfx_->setMesh3DSceneDepth(depth);
|
||||
gfx_->drawMeshShader(mesh_, glm::mat4(1.f), sceneColor, glm::vec4(1.f), shader_);
|
||||
gfx_->setMesh3DHeightTexture(nullptr);
|
||||
gfx_->setMesh3DSceneDepth(nullptr);
|
||||
}
|
||||
|
||||
void Water::drawReflectionCapture() {
|
||||
@@ -193,12 +224,14 @@ void Water::drawReflectionCapture() {
|
||||
bindParams();
|
||||
// Probe captures must never sample the main-view SSR history: that creates
|
||||
// view-dependent feedback and recursively bakes an old reflection into the cube.
|
||||
shader_->sendFloat("ssrEnabled", 0.f);
|
||||
shader_->sendFloat("ssrStrength", -1.0F);
|
||||
shader_->sendFloat("opacityAndDepth", -(config_.opacity + 1.0F));
|
||||
shader_->sendFloat("refractionStrength", 0.0F);
|
||||
gfx_->setMesh3DHeightTexture(nullptr);
|
||||
gfx_->drawMeshShader(mesh_, glm::mat4(1.f), nullptr, glm::vec4(1.f), shader_);
|
||||
// Restore the authored state for a subsequent main-view draw in the same frame.
|
||||
shader_->sendFloat("ssrEnabled", ssrEnabled_ ? 1.f : 0.f);
|
||||
shader_->sendFloat("ssrStrength", ssrStrength_);
|
||||
shader_->sendFloat("ssrStrength", config_.screenSpaceReflection ? config_.screenSpaceReflectionStrength : -1.0F);
|
||||
shader_->sendFloat("refractionStrength", config_.refractionStrength);
|
||||
}
|
||||
|
||||
} // namespace eve::graphics
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/Shader.h"
|
||||
#include "graphics/WaterStyleConfig.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -48,38 +49,38 @@ public:
|
||||
|
||||
// --- Animation / material knobs ---
|
||||
void setWaveSpeed(float speed);
|
||||
float getWaveSpeed() const { return waveSpeed_; }
|
||||
float getWaveSpeed() const { return config_.waveSpeed; }
|
||||
|
||||
/** @brief Amplitude of the shore-edge waves. */
|
||||
void setWaveAmplitude(float amp);
|
||||
float getWaveAmplitude() const { return waveAmplitude_; }
|
||||
float getWaveAmplitude() const { return config_.waveAmplitude; }
|
||||
|
||||
/** @brief Amplitude of the occasional middle drop ripples. */
|
||||
void setRippleAmplitude(float amp);
|
||||
float getRippleAmplitude() const { return rippleAmplitude_; }
|
||||
float getRippleAmplitude() const { return config_.rippleAmplitude; }
|
||||
|
||||
/** @brief Width (in UV, 0..1) of the edge wave band. */
|
||||
/** @brief Compatibility setter for the world-space shoreline foam width. */
|
||||
void setEdgeFalloff(float edge);
|
||||
float getEdgeFalloff() const { return edgeFalloff_; }
|
||||
float getEdgeFalloff() const { return config_.foamWidth; }
|
||||
|
||||
/** @brief How many expanding drop ripples exist. */
|
||||
void setRippleCount(int count);
|
||||
int getRippleCount() const { return rippleCount_; }
|
||||
int getRippleCount() const { return config_.rippleCount; }
|
||||
|
||||
/** @brief Seconds between drop ripples. */
|
||||
void setRippleInterval(float seconds);
|
||||
float getRippleInterval() const { return rippleInterval_; }
|
||||
float getRippleInterval() const { return config_.rippleInterval; }
|
||||
|
||||
void setWaveScale(float scale);
|
||||
float getWaveScale() const { return waveScale_; }
|
||||
float getWaveScale() const { return config_.waveScale; }
|
||||
|
||||
void setWaterColor(float r, float g, float b);
|
||||
void setReflectionTint(float r, float g, float b);
|
||||
void setReflectionIntensity(float intensity);
|
||||
float getReflectionIntensity() const { return reflectionIntensity_; }
|
||||
float getReflectionIntensity() const { return config_.reflectionIntensity; }
|
||||
|
||||
void setSunIntensity(float intensity);
|
||||
float getSunIntensity() const { return sunIntensity_; }
|
||||
float getSunIntensity() const { return config_.sunIntensity; }
|
||||
|
||||
/**
|
||||
* @brief Optional screen-space reflection overlay. When enabled, the shader
|
||||
@@ -89,8 +90,20 @@ public:
|
||||
* not called. SSR must also be enabled on RenderControl.
|
||||
*/
|
||||
void setScreenSpaceReflection(bool enabled, float strength = 0.85f);
|
||||
bool getScreenSpaceReflection() const { return ssrEnabled_; }
|
||||
float getScreenSpaceReflectionStrength() const { return ssrStrength_; }
|
||||
bool getScreenSpaceReflection() const { return config_.screenSpaceReflection; }
|
||||
float getScreenSpaceReflectionStrength() const { return config_.screenSpaceReflectionStrength; }
|
||||
|
||||
/**
|
||||
* @brief Atomically replace all authored parameters from `eve.graphics.stylized-water/1` JSON.
|
||||
* @param json Borrowed UTF-8 JSON used only for this call.
|
||||
* @return Success after validation and publication; failure preserves the previous configuration.
|
||||
* @thread Main/render thread only. Does not invoke callbacks or allocate GPU resources.
|
||||
*/
|
||||
[[nodiscard]] Result<void> applyConfigJson(const std::string& json);
|
||||
/** @brief Return the canonical current `eve.graphics.stylized-water/1` JSON snapshot. */
|
||||
[[nodiscard]] Result<std::string> configJson() const;
|
||||
/** @brief Return the immutable current configuration snapshot by value. */
|
||||
WaterStyleConfig config() const { return config_; }
|
||||
|
||||
/** @brief Window / target size in pixels, used to compute screen-space UVs. */
|
||||
void setViewport(float width, float height);
|
||||
@@ -103,6 +116,16 @@ public:
|
||||
/** @brief Draw the water plane (uses default mesh3d camera / lighting state). */
|
||||
void draw();
|
||||
|
||||
/**
|
||||
* @brief Draw with an optional caller-rendered planar reflection.
|
||||
* @param planarReflection Borrowed texture sampled only during this synchronous draw; may be null.
|
||||
* The caller retains ownership and may release it after this call returns.
|
||||
* @param strength Reflection contribution in [0,4]. A supplied planar reflection takes priority
|
||||
* over the configured screen-space reflection history for this draw only.
|
||||
* @thread Main/render thread only. The call does not retain pointers or invoke callbacks.
|
||||
*/
|
||||
void drawWithPlanarReflection(Texture* planarReflection, float strength = 1.0F);
|
||||
|
||||
/** @brief Enable or disable inclusion in reflection-probe captures. */
|
||||
void setReflectionCaptureEnabled(bool enabled) { reflectionCaptureEnabled_ = enabled; }
|
||||
/** @brief Return whether this water surface is included in reflection-probe captures. */
|
||||
@@ -120,6 +143,7 @@ public:
|
||||
static std::string paramName(int index);
|
||||
|
||||
private:
|
||||
void drawWithReflection(Texture* reflection, float strength);
|
||||
void drawReflectionCapture();
|
||||
|
||||
Graphics *gfx_ = nullptr;
|
||||
@@ -127,19 +151,7 @@ private:
|
||||
Mesh *mesh_ = nullptr;
|
||||
|
||||
float time_ = 0.f;
|
||||
float waveSpeed_ = 1.2f;
|
||||
float waveAmplitude_ = 0.35f;
|
||||
float rippleAmplitude_ = 0.6f;
|
||||
float edgeFalloff_ = 0.18f;
|
||||
int rippleCount_ = 6;
|
||||
float rippleInterval_ = 1.6f;
|
||||
float waveScale_ = 14.f;
|
||||
float waterColor_[3] = {0.02f, 0.16f, 0.24f};
|
||||
float reflectionTint_[3] = {0.7f, 0.85f, 1.0f};
|
||||
float reflectionIntensity_ = 0.6f;
|
||||
float sunIntensity_ = 0.9f;
|
||||
bool ssrEnabled_ = false;
|
||||
float ssrStrength_ = 0.85f;
|
||||
WaterStyleConfig config_;
|
||||
float viewportW_ = 0.f;
|
||||
float viewportH_ = 0.f;
|
||||
uint64_t captureDrawerToken_ = 0;
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
#include "graphics/WaterStyleConfig.h"
|
||||
|
||||
#include "common/Diagnostic.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <initializer_list>
|
||||
#include <set>
|
||||
|
||||
namespace eve::graphics {
|
||||
namespace {
|
||||
|
||||
template <class T>
|
||||
Result<T> invalid(std::string message, std::string path, DiagnosticCode code = DiagnosticCode::InvalidArgument) {
|
||||
return Result<T>::failure(Diagnostic::error(code, std::move(message), std::move(path), {}, "graphics.water"));
|
||||
}
|
||||
|
||||
Result<void> invalid(std::string message, std::string path) {
|
||||
return Result<void>::failure(
|
||||
Diagnostic::error(DiagnosticCode::InvalidArgument, std::move(message), std::move(path), {}, "graphics.water"));
|
||||
}
|
||||
|
||||
const Value::Object* objectAt(const Value& value, const char* name) {
|
||||
const Value* field = value.find(name);
|
||||
return field ? field->getIf<Value::Object>() : nullptr;
|
||||
}
|
||||
|
||||
bool exactFields(const Value::Object& object, std::initializer_list<const char*> fields) {
|
||||
if (object.size() != fields.size()) return false;
|
||||
for (const char* field : fields)
|
||||
if (!object.contains(field)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool numberAt(const Value::Object& object, const char* name, float& output) {
|
||||
const auto found = object.find(name);
|
||||
if (found == object.end() || (!found->second.isDouble() && !found->second.isInt64())) return false;
|
||||
output = static_cast<float>(found->second.isDouble() ? found->second.asDouble() : found->second.asInt());
|
||||
return std::isfinite(output);
|
||||
}
|
||||
|
||||
bool integerAt(const Value::Object& object, const char* name, int& output) {
|
||||
const auto found = object.find(name);
|
||||
if (found == object.end() || !found->second.isInt64()) return false;
|
||||
output = static_cast<int>(found->second.asInt());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool boolAt(const Value::Object& object, const char* name, bool& output) {
|
||||
const auto found = object.find(name);
|
||||
if (found == object.end() || !found->second.isBool()) return false;
|
||||
output = found->second.asBool();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vec3At(const Value::Object& object, const char* name, glm::vec3& output) {
|
||||
const auto found = object.find(name);
|
||||
if (found == object.end()) return false;
|
||||
const auto* values = found->second.getIf<Value::Array>();
|
||||
if (!values || values->size() != 3) return false;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!(*values)[i].isDouble() && !(*values)[i].isInt64()) return false;
|
||||
output[i] = static_cast<float>((*values)[i].isDouble() ? (*values)[i].asDouble() : (*values)[i].asInt());
|
||||
if (!std::isfinite(output[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Value vec3Value(const glm::vec3& value) {
|
||||
return Value::array({value.x, value.y, value.z});
|
||||
}
|
||||
|
||||
bool color(const glm::vec3& value) {
|
||||
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) &&
|
||||
glm::all(glm::greaterThanEqual(value, glm::vec3(0.0F))) &&
|
||||
glm::all(glm::lessThanEqual(value, glm::vec3(8.0F)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<void> WaterStyleConfig::validate() const {
|
||||
if (!color(deepColor) || !color(shallowColor) || !color(foamColor) || !color(reflectionTint))
|
||||
return invalid("water colors must be finite and in [0, 8]", "$.colors");
|
||||
if (!std::isfinite(waveSpeed) || waveSpeed < -20.0F || waveSpeed > 20.0F ||
|
||||
!std::isfinite(waveAmplitude) || waveAmplitude < 0.0F || waveAmplitude > 4.0F ||
|
||||
!std::isfinite(waveScale) || waveScale < 0.01F || waveScale > 256.0F ||
|
||||
!std::isfinite(waveSharpness) || waveSharpness < 0.1F || waveSharpness > 8.0F ||
|
||||
!std::isfinite(rippleAmplitude) || rippleAmplitude < 0.0F || rippleAmplitude > 4.0F ||
|
||||
rippleCount < 0 || rippleCount > 8 || !std::isfinite(rippleInterval) || rippleInterval < 0.05F ||
|
||||
rippleInterval > 60.0F)
|
||||
return invalid("invalid wave or ripple parameter", "$.waves");
|
||||
if (!std::isfinite(depthDistance) || depthDistance <= 0.0F || depthDistance > 10000.0F ||
|
||||
!std::isfinite(opacity) || opacity < 0.0F || opacity > 1.0F)
|
||||
return invalid("invalid depth parameter", "$.depth");
|
||||
if (!std::isfinite(foamWidth) || foamWidth <= 0.0F || foamWidth > 1000.0F ||
|
||||
!std::isfinite(foamSoftness) || foamSoftness < 0.0F || foamSoftness > 1.0F ||
|
||||
!std::isfinite(foamStrength) || foamStrength < 0.0F || foamStrength > 4.0F)
|
||||
return invalid("invalid foam parameter", "$.foam");
|
||||
if (!std::isfinite(fresnelPower) || fresnelPower < 0.01F || fresnelPower > 32.0F ||
|
||||
!std::isfinite(reflectionIntensity) || reflectionIntensity < 0.0F || reflectionIntensity > 4.0F ||
|
||||
!std::isfinite(sunIntensity) || sunIntensity < 0.0F || sunIntensity > 8.0F ||
|
||||
!std::isfinite(screenSpaceReflectionStrength) || screenSpaceReflectionStrength < 0.0F ||
|
||||
screenSpaceReflectionStrength > 4.0F)
|
||||
return invalid("invalid lighting parameter", "$.lighting");
|
||||
if (!std::isfinite(refractionStrength) || refractionStrength < 0.0F || refractionStrength > 0.25F)
|
||||
return invalid("invalid refraction strength", "$.refraction.strength");
|
||||
if (!std::isfinite(causticsStrength) || causticsStrength < 0.0F || causticsStrength > 4.0F ||
|
||||
!std::isfinite(causticsScale) || causticsScale < 0.01F || causticsScale > 256.0F)
|
||||
return invalid("invalid caustics parameter", "$.caustics");
|
||||
return Result<void>::success();
|
||||
}
|
||||
|
||||
Result<Value> WaterStyleConfig::toValue() const {
|
||||
auto valid = validate();
|
||||
if (!valid) return Result<Value>::failure(valid.status());
|
||||
Value::Object colors{{"deep", vec3Value(deepColor)}, {"shallow", vec3Value(shallowColor)},
|
||||
{"foam", vec3Value(foamColor)}, {"reflectionTint", vec3Value(reflectionTint)}};
|
||||
Value::Object waves{{"speed", waveSpeed}, {"amplitude", waveAmplitude}, {"scale", waveScale},
|
||||
{"sharpness", waveSharpness}, {"rippleAmplitude", rippleAmplitude},
|
||||
{"rippleCount", static_cast<std::int64_t>(rippleCount)}, {"rippleInterval", rippleInterval}};
|
||||
Value::Object depth{{"distance", depthDistance}, {"opacity", opacity}};
|
||||
Value::Object foam{{"width", foamWidth}, {"softness", foamSoftness}, {"strength", foamStrength}};
|
||||
Value::Object lighting{{"fresnelPower", fresnelPower}, {"reflectionIntensity", reflectionIntensity},
|
||||
{"sunIntensity", sunIntensity}, {"screenSpaceReflection", screenSpaceReflection},
|
||||
{"screenSpaceReflectionStrength", screenSpaceReflectionStrength}};
|
||||
Value::Object refraction{{"strength", refractionStrength}};
|
||||
Value::Object caustics{{"strength", causticsStrength}, {"scale", causticsScale}};
|
||||
return Result<Value>::success(Value::object({
|
||||
{"schema", std::string(SchemaId)}, {"schemaVersion", static_cast<std::int64_t>(SchemaVersion)},
|
||||
{"unknownFields", "reject"}, {"colors", Value(std::move(colors))}, {"waves", Value(std::move(waves))},
|
||||
{"depth", Value(std::move(depth))}, {"foam", Value(std::move(foam))},
|
||||
{"lighting", Value(std::move(lighting))}, {"refraction", Value(std::move(refraction))},
|
||||
{"caustics", Value(std::move(caustics))},
|
||||
}));
|
||||
}
|
||||
|
||||
Result<WaterStyleConfig> WaterStyleConfig::fromValue(const Value& value) {
|
||||
const auto* root = value.getIf<Value::Object>();
|
||||
if (!root || !exactFields(*root, {"schema", "schemaVersion", "unknownFields", "colors", "waves", "depth",
|
||||
"foam", "lighting", "refraction", "caustics"}))
|
||||
return invalid<WaterStyleConfig>("water definition requires exactly ten known fields", "$",
|
||||
DiagnosticCode::InvalidArgument);
|
||||
if (!root->at("schema").isString() || root->at("schema").asString() != SchemaId ||
|
||||
!root->at("schemaVersion").isInt64() || root->at("schemaVersion").asInt() != SchemaVersion)
|
||||
return invalid<WaterStyleConfig>("unsupported water schema/version", "$.schemaVersion", DiagnosticCode::UnknownVersion);
|
||||
if (!root->at("unknownFields").isString() || root->at("unknownFields").asString() != "reject")
|
||||
return invalid<WaterStyleConfig>("water v1 requires unknownFields=reject", "$.unknownFields");
|
||||
|
||||
const auto* colors = objectAt(value, "colors");
|
||||
const auto* waves = objectAt(value, "waves");
|
||||
const auto* depth = objectAt(value, "depth");
|
||||
const auto* foam = objectAt(value, "foam");
|
||||
const auto* lighting = objectAt(value, "lighting");
|
||||
const auto* refraction = objectAt(value, "refraction");
|
||||
const auto* caustics = objectAt(value, "caustics");
|
||||
if (!colors || !waves || !depth || !foam || !lighting || !refraction || !caustics ||
|
||||
!exactFields(*colors, {"deep", "shallow", "foam", "reflectionTint"}) ||
|
||||
!exactFields(*waves, {"speed", "amplitude", "scale", "sharpness", "rippleAmplitude", "rippleCount", "rippleInterval"}) ||
|
||||
!exactFields(*depth, {"distance", "opacity"}) ||
|
||||
!exactFields(*foam, {"width", "softness", "strength"}) ||
|
||||
!exactFields(*lighting, {"fresnelPower", "reflectionIntensity", "sunIntensity", "screenSpaceReflection", "screenSpaceReflectionStrength"}) ||
|
||||
!exactFields(*refraction, {"strength"}) || !exactFields(*caustics, {"strength", "scale"}))
|
||||
return invalid<WaterStyleConfig>("invalid or unknown nested water field", "$", DiagnosticCode::InvalidArgument);
|
||||
|
||||
WaterStyleConfig output;
|
||||
if (!vec3At(*colors, "deep", output.deepColor) || !vec3At(*colors, "shallow", output.shallowColor) ||
|
||||
!vec3At(*colors, "foam", output.foamColor) || !vec3At(*colors, "reflectionTint", output.reflectionTint) ||
|
||||
!numberAt(*waves, "speed", output.waveSpeed) || !numberAt(*waves, "amplitude", output.waveAmplitude) ||
|
||||
!numberAt(*waves, "scale", output.waveScale) || !numberAt(*waves, "sharpness", output.waveSharpness) ||
|
||||
!numberAt(*waves, "rippleAmplitude", output.rippleAmplitude) || !integerAt(*waves, "rippleCount", output.rippleCount) ||
|
||||
!numberAt(*waves, "rippleInterval", output.rippleInterval) || !numberAt(*depth, "distance", output.depthDistance) ||
|
||||
!numberAt(*depth, "opacity", output.opacity) || !numberAt(*foam, "width", output.foamWidth) ||
|
||||
!numberAt(*foam, "softness", output.foamSoftness) || !numberAt(*foam, "strength", output.foamStrength) ||
|
||||
!numberAt(*lighting, "fresnelPower", output.fresnelPower) ||
|
||||
!numberAt(*lighting, "reflectionIntensity", output.reflectionIntensity) ||
|
||||
!numberAt(*lighting, "sunIntensity", output.sunIntensity) ||
|
||||
!boolAt(*lighting, "screenSpaceReflection", output.screenSpaceReflection) ||
|
||||
!numberAt(*lighting, "screenSpaceReflectionStrength", output.screenSpaceReflectionStrength) ||
|
||||
!numberAt(*refraction, "strength", output.refractionStrength) ||
|
||||
!numberAt(*caustics, "strength", output.causticsStrength) ||
|
||||
!numberAt(*caustics, "scale", output.causticsScale))
|
||||
return invalid<WaterStyleConfig>("missing or invalid water value", "$", DiagnosticCode::InvalidArgument);
|
||||
auto valid = output.validate();
|
||||
if (!valid) return Result<WaterStyleConfig>::failure(valid.status());
|
||||
return Result<WaterStyleConfig>::success(std::move(output));
|
||||
}
|
||||
|
||||
Result<WaterStyleConfig> WaterStyleConfig::fromJson(std::string_view json) {
|
||||
auto value = Value::fromJson(json);
|
||||
if (!value) return Result<WaterStyleConfig>::failure(value.status());
|
||||
return fromValue(value.value());
|
||||
}
|
||||
|
||||
Result<std::string> WaterStyleConfig::toJson() const {
|
||||
auto value = toValue();
|
||||
if (!value) return Result<std::string>::failure(value.status());
|
||||
return value.value().toJson();
|
||||
}
|
||||
|
||||
} // namespace eve::graphics
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Result.h"
|
||||
#include "common/Value.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace eve::graphics {
|
||||
|
||||
/** @brief Versioned, backend-neutral configuration for the built-in stylized water surface. */
|
||||
struct WaterStyleConfig {
|
||||
static constexpr std::string_view SchemaId = "eve.graphics.stylized-water";
|
||||
static constexpr std::uint32_t SchemaVersion = 1;
|
||||
|
||||
glm::vec3 deepColor{0.02F, 0.16F, 0.24F};
|
||||
glm::vec3 shallowColor{0.10F, 0.48F, 0.58F};
|
||||
glm::vec3 foamColor{0.85F, 0.93F, 1.0F};
|
||||
glm::vec3 reflectionTint{0.70F, 0.85F, 1.0F};
|
||||
|
||||
float waveSpeed = 1.2F;
|
||||
float waveAmplitude = 0.35F;
|
||||
float waveScale = 14.0F;
|
||||
float waveSharpness = 1.0F;
|
||||
float rippleAmplitude = 0.60F;
|
||||
int rippleCount = 6;
|
||||
float rippleInterval = 1.6F;
|
||||
|
||||
float depthDistance = 5.0F;
|
||||
float opacity = 0.88F;
|
||||
|
||||
float foamWidth = 0.45F;
|
||||
float foamSoftness = 0.08F;
|
||||
float foamStrength = 0.85F;
|
||||
|
||||
float fresnelPower = 5.0F;
|
||||
float reflectionIntensity = 0.60F;
|
||||
float sunIntensity = 0.90F;
|
||||
bool screenSpaceReflection = false;
|
||||
float screenSpaceReflectionStrength = 0.85F;
|
||||
|
||||
float refractionStrength = 0.025F;
|
||||
|
||||
float causticsStrength = 0.35F;
|
||||
float causticsScale = 3.5F;
|
||||
|
||||
/** @brief Validate finite values and authored ranges without mutating runtime state. */
|
||||
[[nodiscard]] Result<void> validate() const;
|
||||
/** @brief Encode the canonical version-one representation. Unknown fields are rejected. */
|
||||
[[nodiscard]] Result<Value> toValue() const;
|
||||
/** @brief Decode version one transactionally; unknown fields and future versions are rejected. */
|
||||
[[nodiscard]] static Result<WaterStyleConfig> fromValue(const Value& value);
|
||||
/** @brief Parse JSON and decode version one transactionally. */
|
||||
[[nodiscard]] static Result<WaterStyleConfig> fromJson(std::string_view json);
|
||||
/** @brief Serialize canonical JSON after validation. */
|
||||
[[nodiscard]] Result<std::string> toJson() const;
|
||||
};
|
||||
|
||||
} // namespace eve::graphics
|
||||
@@ -2,156 +2,61 @@
|
||||
|
||||
namespace eve::graphics::shaders {
|
||||
|
||||
/** @brief Water fragment shader WGSL source.
|
||||
* @ownership Borrowed immutable string literal; callers must not free it.
|
||||
* @lifetime Static storage duration, valid for the entire process lifetime.
|
||||
* @thread Safe for concurrent reads.
|
||||
*/
|
||||
inline constexpr const char* kWaterFragWgsl = R"wgsl(
|
||||
struct Light3D { posRadius: vec4f, color: vec4f };
|
||||
struct Frame {
|
||||
mvp: mat4x4f, model: mat4x4f, lightDir: vec4f, lightColor: vec4f,
|
||||
tint: vec4f, cameraPos: vec4f, ambient: vec4f,
|
||||
lights: array<Light3D, 8>, texBomb: vec4f, parallax: vec4f,
|
||||
surface: vec4f, view: mat4x4f, clipInfo: vec4f, cloud: vec4f, cloudWind: vec4f,
|
||||
envProbeCenter: vec4f, envProbeExtent: vec4f, skinInfo: vec4f,
|
||||
reflectionProbeCenter: array<vec4f, 2>,
|
||||
reflectionProbeExtent: array<vec4f, 2>,
|
||||
};
|
||||
struct FSIn {
|
||||
@builtin(position) pos: vec4f,
|
||||
@location(0) normal: vec3f, @location(1) uv: vec2f, @location(2) tint: vec4f,
|
||||
@location(3) worldPos: vec3f, @location(4) cameraPos: vec3f,
|
||||
@location(5) viewPos: vec3f,
|
||||
};
|
||||
struct Externals { data: array<vec4f, 8> };
|
||||
@group(0) @binding(0) var<uniform> frame: Frame;
|
||||
@group(0) @binding(3) var environment: texture_cube<f32>;
|
||||
@group(0) @binding(6) var ssrTex: texture_2d<f32>;
|
||||
@group(0) @binding(7) var mainSampler: sampler;
|
||||
@group(0) @binding(15) var<uniform> params: Externals;
|
||||
@group(0) @binding(16) var reflectionProbe0: texture_cube<f32>;
|
||||
@group(0) @binding(17) var reflectionProbe1: texture_cube<f32>;
|
||||
fn p(i: u32) -> f32 { return params.data[i / 4u][i % 4u]; }
|
||||
fn hash(n: f32) -> f32 { return fract(sin(n) * 43758.5453123); }
|
||||
fn hash2(i: i32) -> vec2f {
|
||||
return vec2f(hash(f32(i) * 7.31), hash(f32(i) * 13.17 + 1.0));
|
||||
/** @brief Vertex-displaced low-frequency water layers for the WebGPU backend. */
|
||||
inline constexpr char kWaterVertWgsl[] = R"wgsl(
|
||||
struct Light3D{posRadius:vec4f,color:vec4f};
|
||||
struct Frame{mvp:mat4x4f,model:mat4x4f,lightDir:vec4f,lightColor:vec4f,tint:vec4f,cameraPos:vec4f,ambient:vec4f,lights:array<Light3D,8>,texBomb:vec4f,parallax:vec4f,surface:vec4f,view:mat4x4f,clipInfo:vec4f,cloud:vec4f,cloudWind:vec4f,envProbeCenter:vec4f,envProbeExtent:vec4f,skinInfo:vec4f,reflectionProbeCenter:array<vec4f,2>,reflectionProbeExtent:array<vec4f,2>};
|
||||
struct Params{data:array<vec4f,8>};
|
||||
struct VSIn{@location(0)pos:vec3f,@location(1)normal:vec3f,@location(2)uv:vec2f};
|
||||
struct VSOut{@builtin(position)pos:vec4f,@location(0)normal:vec3f,@location(1)uv:vec2f,@location(2)tint:vec4f,@location(3)worldPos:vec3f,@location(4)cameraPos:vec3f,@location(5)viewPos:vec3f};
|
||||
@group(0)@binding(0)var<uniform>frame:Frame;
|
||||
@group(0)@binding(15)var<uniform>params:Params;
|
||||
fn p(i:u32)->f32{return params.data[i/4u][i%4u];}
|
||||
fn waveHeight(worldXZ:vec2f)->f32{
|
||||
let scale=max(p(3u),0.01)*0.72;let time=p(0u)*p(1u);
|
||||
let primary=sin(dot(worldXZ,vec2f(0.91,0.31))*scale+time*1.45);
|
||||
let crossWave=sin(dot(worldXZ,vec2f(-0.36,0.93))*scale*0.58-time*0.92);
|
||||
let swell=sin(dot(worldXZ,vec2f(0.22,0.98))*scale*0.24+time*0.37);
|
||||
return p(2u)*(primary*0.48+crossWave*0.34+swell*0.18);
|
||||
}
|
||||
fn probeWeight(index: u32, worldPos: vec3f) -> f32 {
|
||||
let edge = frame.reflectionProbeExtent[index].xyz -
|
||||
abs(worldPos - frame.reflectionProbeCenter[index].xyz);
|
||||
let inside = min(edge.x, min(edge.y, edge.z));
|
||||
return select(0.0,
|
||||
clamp(inside / max(frame.reflectionProbeExtent[index].w, 0.0001), 0.0, 1.0),
|
||||
inside > 0.0 && frame.reflectionProbeCenter[index].w > 0.0);
|
||||
@vertex fn vs_main(in:VSIn)->VSOut{
|
||||
let baseWorld=frame.model*vec4f(in.pos,1.0);let eps=0.025;let height=waveHeight(baseWorld.xz);
|
||||
let dx=(waveHeight(baseWorld.xz+vec2f(eps,0))-waveHeight(baseWorld.xz-vec2f(eps,0)))/(2.0*eps);
|
||||
let dz=(waveHeight(baseWorld.xz+vec2f(0,eps))-waveHeight(baseWorld.xz-vec2f(0,eps)))/(2.0*eps);
|
||||
let localPos=in.pos+vec3f(0,height,0);let world=frame.model*vec4f(localPos,1.0);
|
||||
var out:VSOut;out.pos=frame.mvp*vec4f(localPos,1.0);out.pos.y=-out.pos.y;
|
||||
out.normal=normalize(mat3x3f(frame.model[0].xyz,frame.model[1].xyz,frame.model[2].xyz)*normalize(vec3f(-dx,1,-dz)));
|
||||
out.uv=in.uv;out.tint=frame.tint;out.worldPos=world.xyz;out.cameraPos=frame.cameraPos.xyz;out.viewPos=(frame.view*world).xyz;return out;
|
||||
}
|
||||
fn probeDirection(index: u32, direction: vec3f, worldPos: vec3f) -> vec3f {
|
||||
let center = frame.reflectionProbeCenter[index].xyz;
|
||||
let extent = frame.reflectionProbeExtent[index].xyz;
|
||||
let safeDir = select(vec3f(0.00001), direction, abs(direction) > vec3f(0.00001));
|
||||
let exitT = max((center - extent - worldPos) / safeDir,
|
||||
(center + extent - worldPos) / safeDir);
|
||||
let distanceToBox = min(exitT.x, min(exitT.y, exitT.z));
|
||||
return select(normalize(direction),
|
||||
normalize(worldPos + direction * distanceToBox - center), distanceToBox > 0.0);
|
||||
}
|
||||
fn rippleRing(uv: vec2f, i: i32) -> f32 {
|
||||
let period = max(p(7u), 0.001);
|
||||
let local = p(0u) - floor(p(0u) / period) * period;
|
||||
let age = local - hash(f32(i) * 3.7) * period;
|
||||
if (age < 0.0 || age > period * 0.8) { return 0.0; }
|
||||
let center = mix(vec2f(0.5), hash2(i), vec2f(0.72));
|
||||
let x = (length(uv - center) - age * 0.22) / 0.10;
|
||||
return p(3u) * exp(-(x * x) * 0.9) * cos(x * 6.28318) * exp(-age * 1.6);
|
||||
}
|
||||
fn waterHeight(uv: vec2f) -> f32 {
|
||||
let edgeDist = min(uv, vec2f(1.0) - uv);
|
||||
let edge = 1.0 - clamp(min(edgeDist.x, edgeDist.y) / max(p(4u), 0.0001), 0.0, 1.0);
|
||||
var w = edge * p(2u) *
|
||||
(sin((uv.x * p(8u) + p(0u) * p(1u)) * 6.2831853) +
|
||||
0.5 * sin((uv.y * p(8u) * 0.7 - p(0u) * p(1u) * 1.3) * 6.2831853));
|
||||
w += p(2u) * 0.10 *
|
||||
sin((uv.x * 31.0 + uv.y * 17.0 + p(0u) * p(1u) * 2.0) * 6.2831853);
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (f32(i) >= p(6u) + 0.5) { break; }
|
||||
w += rippleRing(uv, i);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
@fragment
|
||||
fn fs_main(in: FSIn) -> @location(0) vec4f {
|
||||
let eps = 0.001;
|
||||
let grad = vec2f(waterHeight(in.uv + vec2f(eps, 0.0)) -
|
||||
waterHeight(in.uv - vec2f(eps, 0.0)),
|
||||
waterHeight(in.uv + vec2f(0.0, eps)) -
|
||||
waterHeight(in.uv - vec2f(0.0, eps))) / (2.0 * eps);
|
||||
let n = normalize(vec3f(-grad.x, 1.0, -grad.y));
|
||||
let v = normalize(frame.cameraPos.xyz - in.worldPos);
|
||||
let reflected = reflect(-v, n);
|
||||
let ndv = max(dot(v, n), 0.0);
|
||||
let roughness = clamp(0.08 + length(grad) * 0.06, 0.04, 0.35);
|
||||
let maxLod = f32(max(i32(textureNumLevels(environment)) - 1, 0));
|
||||
let specMaxLod = select(maxLod, maxLod - 1.0, maxLod >= 2.0);
|
||||
let lod = roughness * specMaxLod;
|
||||
var probeWeight0 = probeWeight(0u, in.worldPos);
|
||||
var probeWeight1 = probeWeight(1u, in.worldPos);
|
||||
var probeWeightSum = probeWeight0 + probeWeight1;
|
||||
if (probeWeightSum > 1.0) {
|
||||
probeWeight0 /= probeWeightSum;
|
||||
probeWeight1 /= probeWeightSum;
|
||||
probeWeightSum = 1.0;
|
||||
}
|
||||
var reflection = textureSampleLevel(environment, mainSampler, reflected, lod).rgb *
|
||||
frame.lightColor.w * (1.0 - probeWeightSum);
|
||||
// @lifetime Shader math below does not declare host pointers.
|
||||
if (probeWeight0 > 0.0) {
|
||||
let probeLod = f32(max(i32(textureNumLevels(reflectionProbe0)) - 1, 0));
|
||||
reflection += textureSampleLevel(reflectionProbe0, mainSampler,
|
||||
probeDirection(0u, reflected, in.worldPos),
|
||||
roughness * max(probeLod - 1.0, 0.0)).rgb *
|
||||
frame.reflectionProbeCenter[0].w * probeWeight0;
|
||||
}
|
||||
if (probeWeight1 > 0.0) {
|
||||
let probeLod = f32(max(i32(textureNumLevels(reflectionProbe1)) - 1, 0));
|
||||
reflection += textureSampleLevel(reflectionProbe1, mainSampler,
|
||||
probeDirection(1u, reflected, in.worldPos),
|
||||
roughness * max(probeLod - 1.0, 0.0)).rgb *
|
||||
frame.reflectionProbeCenter[1].w * probeWeight1;
|
||||
}
|
||||
reflection *= vec3f(p(12u), p(13u), p(14u));
|
||||
let fresnel = 0.02 + 0.98 * pow(1.0 - ndv, 5.0);
|
||||
let brdf = roughness * vec4f(-1.0, -0.0275, -0.572, 0.022) +
|
||||
vec4f(1.0, 0.0425, 1.04, -0.04);
|
||||
let a004 = min(brdf.x * brdf.x, exp2(-9.28 * ndv)) * brdf.x + brdf.y;
|
||||
let dfg = vec2f(-1.04, 1.04) * a004 + brdf.zw;
|
||||
let envWeight = max(0.02 * dfg.x + dfg.y, 0.0);
|
||||
var color = mix(vec3f(p(9u), p(10u), p(11u)), reflection,
|
||||
clamp(max(fresnel, envWeight) * p(5u), 0.0, 1.0));
|
||||
let l = normalize(frame.lightDir.xyz);
|
||||
let h = normalize(v + l);
|
||||
let noL = max(dot(n, l), 0.0);
|
||||
let noH = max(dot(n, h), 0.0);
|
||||
let voH = max(dot(v, h), 0.0);
|
||||
let alpha = max(roughness * roughness, 0.002);
|
||||
let alpha2 = alpha * alpha;
|
||||
let denom = noH * noH * (alpha2 - 1.0) + 1.0;
|
||||
let d = alpha2 / max(3.14159265 * denom * denom, 0.0001);
|
||||
let k = (roughness + 1.0) * (roughness + 1.0) * 0.125;
|
||||
let gv = ndv / max(ndv * (1.0 - k) + k, 0.0001);
|
||||
let gl = noL / max(noL * (1.0 - k) + k, 0.0001);
|
||||
let fsun = 0.02 + 0.98 * pow(1.0 - voH, 5.0);
|
||||
let spec = d * gv * gl * fsun / max(4.0 * ndv * max(noL, 0.001), 0.001);
|
||||
color += frame.lightColor.rgb * spec * noL * p(15u);
|
||||
let edgeDist = min(in.uv, vec2f(1.0) - in.uv);
|
||||
let edge = 1.0 - clamp(min(edgeDist.x, edgeDist.y) / max(p(4u), 0.0001), 0.0, 1.0);
|
||||
color = mix(color, vec3f(0.85, 0.93, 1.0), edge * 0.22);
|
||||
if (p(18u) > 0.5 && p(16u) > 1.0 && p(17u) > 1.0) {
|
||||
let suv = in.pos.xy / vec2f(p(16u), p(17u));
|
||||
let ssr = textureSample(ssrTex, mainSampler, suv);
|
||||
let ssrWeight = clamp(ssr.a * p(19u), 0.0, 1.0);
|
||||
color = ssr.rgb * p(19u) + color * (1.0 - ssrWeight);
|
||||
}
|
||||
return vec4f(color, 1.0);
|
||||
)wgsl";
|
||||
|
||||
/** @brief Backend-parity stylized-water WGSL source with depth foam and scene-color refraction. */
|
||||
inline constexpr char kWaterFragWgsl[] = R"wgsl(
|
||||
struct Light3D{posRadius:vec4f,color:vec4f};
|
||||
struct Frame{mvp:mat4x4f,model:mat4x4f,lightDir:vec4f,lightColor:vec4f,tint:vec4f,cameraPos:vec4f,ambient:vec4f,lights:array<Light3D,8>,texBomb:vec4f,parallax:vec4f,surface:vec4f,view:mat4x4f,clipInfo:vec4f,cloud:vec4f,cloudWind:vec4f,envProbeCenter:vec4f,envProbeExtent:vec4f,skinInfo:vec4f,reflectionProbeCenter:array<vec4f,2>,reflectionProbeExtent:array<vec4f,2>};
|
||||
struct FSIn{@builtin(position)pos:vec4f,@location(0)normal:vec3f,@location(1)uv:vec2f,@location(2)tint:vec4f,@location(3)worldPos:vec3f,@location(4)cameraPos:vec3f,@location(5)viewPos:vec3f};
|
||||
struct Params{data:array<vec4f,8>};
|
||||
@group(0)@binding(0)var<uniform>frame:Frame;@group(0)@binding(1)var sceneColorTex:texture_2d<f32>;@group(0)@binding(3)var environment:texture_cube<f32>;@group(0)@binding(6)var ssrTex:texture_2d<f32>;@group(0)@binding(7)var samp:sampler;@group(0)@binding(9)var sceneDepthTex:texture_2d<f32>;@group(0)@binding(15)var<uniform>params:Params;@group(0)@binding(16)var reflectionProbe0:texture_cube<f32>;@group(0)@binding(17)var reflectionProbe1:texture_cube<f32>;
|
||||
fn p(i:u32)->f32{return params.data[i/4u][i%4u];}fn hash(n:f32)->f32{return fract(sin(n)*43758.5453123);}fn hash2(i:i32)->vec2f{return vec2f(hash(f32(i)*7.31),hash(f32(i)*13.17+1.0));}
|
||||
fn shapedWave(phase:f32)->f32{let w=sin(phase);return sign(w)*pow(abs(w),max(p(31u),0.1));}
|
||||
fn rippleRing(uv:vec2f,i:i32)->f32{let period=max(p(6u),0.05);let age=p(0u)-floor(p(0u)/period)*period-hash(f32(i)*3.7)*period;if(age<0.0||age>period*0.8){return 0.0;}let center=mix(vec2f(0.5),hash2(i),vec2f(0.72));let x=(length(uv-center)-age*0.22)/0.10;return p(4u)*exp(-(x*x)*0.9)*cos(x*6.2831853)*exp(-age*1.6);}
|
||||
fn waterHeight(uv:vec2f)->f32{let scale=p(3u);let time=p(0u)*p(1u);let q=uv+vec2f(sin(uv.y*scale*0.31+time*0.37),sin(uv.x*scale*0.27-time*0.29))*0.018;let phase0=dot(q,vec2f(1.0,0.23))*scale*6.2831853+time*6.2831853;let phase1=dot(q,vec2f(-0.48,0.88))*scale*0.73*6.2831853-time*1.17*6.2831853;let phase2=dot(q,vec2f(0.37,0.93))*scale*1.31*6.2831853+time*0.61*6.2831853;var h=p(2u)*(shapedWave(phase0)*0.42+shapedWave(phase1)*0.34+shapedWave(phase2)*0.24);for(var i=0;i<8;i++){if(f32(i)>=p(5u)+0.5){break;}h+=rippleRing(q,i);}return h;}
|
||||
fn caustics(pos:vec2f)->f32{let q=pos*max(p(30u),0.01);let t=p(0u)*p(1u);let a=sin(q.x*1.37+q.y*0.73+t*1.2);let b=sin(q.x*-0.62+q.y*1.51-t*0.93);let c=sin((q.x+q.y)*1.11+t*0.57);let ridge=1.0-abs((a+b+c)/3.0);return pow(clamp(ridge,0.0,1.0),5.0);}
|
||||
fn foamCells(pos:vec2f)->f32{let cell=floor(pos);let f=fract(pos);var first=10.0;var second=10.0;for(var y=-1;y<=1;y++){for(var x=-1;x<=1;x++){let g=vec2f(f32(x),f32(y));let point=hash2(i32(cell.x+g.x)*131+i32(cell.y+g.y)*197);let distance=length(g+point-f);if(distance<first){second=first;first=distance;}else if(distance<second){second=distance;}}}return 1.0-smoothstep(0.025,0.18,second-first);}
|
||||
fn flowNoise(pos:vec2f)->f32{let t=p(0u)*p(1u);let a=sin(pos.x*1.31+pos.y*0.73+t*1.17);let b=sin(pos.x*-0.67+pos.y*1.57-t*0.83);return 0.5+0.25*(a+b);}
|
||||
fn crestLines(pos:vec2f)->f32{let t=p(0u)*p(1u);let scale=max(p(3u),0.01)*0.43;let phaseA=dot(pos,vec2f(0.96,0.28))*scale+sin(pos.y*0.72-t*0.31)*1.35+t*1.35;let phaseB=dot(pos,vec2f(-0.38,0.92))*scale*0.67+sin(pos.x*0.58+t*0.27)*1.15-t*0.82;let a=smoothstep(0.91,0.985,sin(phaseA));let b=smoothstep(0.935,0.992,sin(phaseB));let broken=smoothstep(0.52,0.70,flowNoise(pos*0.66+vec2f(4.7)));return max(a*0.58,b*0.38)*broken;}
|
||||
fn probeWeight(i:u32,pos:vec3f)->f32{let edge=frame.reflectionProbeExtent[i].xyz-abs(pos-frame.reflectionProbeCenter[i].xyz);let inside=min(edge.x,min(edge.y,edge.z));return select(0.0,clamp(inside/max(frame.reflectionProbeExtent[i].w,0.0001),0.0,1.0),inside>0.0&&frame.reflectionProbeCenter[i].w>0.0);}
|
||||
fn probeDirection(i:u32,direction:vec3f,pos:vec3f)->vec3f{let center=frame.reflectionProbeCenter[i].xyz;let extent=frame.reflectionProbeExtent[i].xyz;let safe=select(vec3f(0.00001),direction,abs(direction)>vec3f(0.00001));let exitT=max((center-extent-pos)/safe,(center+extent-pos)/safe);let distance=min(exitT.x,min(exitT.y,exitT.z));return select(normalize(direction),normalize(pos+direction*distance-center),distance>0.0);}
|
||||
@fragment fn fs_main(in:FSIn)->@location(0)vec4f{
|
||||
let eps=0.001;let grad=vec2f(waterHeight(in.uv+vec2f(eps,0))-waterHeight(in.uv-vec2f(eps,0)),waterHeight(in.uv+vec2f(0,eps))-waterHeight(in.uv-vec2f(0,eps)))/(2.0*eps);let flowTime=p(0u)*p(1u);let normalLayerA=vec2f(sin(in.worldPos.x*1.7+in.worldPos.z*0.8+flowTime),cos(in.worldPos.z*1.5-in.worldPos.x*0.6-flowTime*0.83));let normalLayerB=vec2f(cos(in.worldPos.x*3.1-in.worldPos.z*1.2-flowTime*1.31),sin(in.worldPos.z*2.7+in.worldPos.x*0.9+flowTime*0.57));let layeredNormal=grad*0.11+normalLayerA*0.055+normalLayerB*0.025;let normal=normalize(in.normal+vec3f(-layeredNormal.x,0,-layeredNormal.y));let view=normalize(frame.cameraPos.xyz-in.worldPos);let reflected=reflect(-view,normal);let ndv=max(dot(view,normal),0.0);
|
||||
let size=vec2f(textureDimensions(sceneColorTex));let suv=in.pos.xy/max(size,vec2f(1));let nearZ=max(frame.clipInfo.x,0.0001);let farZ=max(frame.clipInfo.y,nearZ+0.001);let surface=clamp((-in.viewPos.z-nearZ)/(farZ-nearZ),0.0,1.0);let hasDepth=p(29u)>=0.0;let opacity=select(-p(29u)-1.0,p(29u),hasDepth);let sceneSample=textureSample(sceneColorTex,samp,suv);var scene=surface+1.0;if(hasDepth){scene=textureSample(sceneDepthTex,samp,suv).r;}if(sceneSample.a>surface+0.00001&&sceneSample.a<0.9999){scene=sceneSample.a;}let depth=max((scene-surface)*(farZ-nearZ),0.0);let shallow=1.0-smoothstep(0.0,max(p(16u),0.0001),depth);let shallowColor=vec3f(p(13u),p(14u),p(15u));let water=mix(vec3f(p(10u),p(11u),p(12u)),shallowColor,vec3f(shallow));
|
||||
let flow=vec2f(flowNoise(in.worldPos.xz*1.8),flowNoise(in.worldPos.zx*2.3+vec2f(17.0)))-vec2f(0.5);let ruv=clamp(suv+(layeredNormal*0.75+flow*0.35)*p(28u)*(0.25+shallow*0.75),vec2f(0.001),vec2f(0.999));let behind=textureSample(sceneColorTex,samp,ruv).rgb;let absorption=select(1.0,1.0-exp(-depth/max(p(16u),0.001)*3.2),hasDepth);var waterAlpha=clamp(opacity*mix(0.35,1.0,absorption),0.0,1.0);var color=water;let causticMask=select(0.0,smoothstep(0.02,max(p(16u),0.03),depth)*shallow,hasDepth);color+=shallowColor*caustics(in.worldPos.xz)*causticMask*p(26u);
|
||||
let rough=clamp(0.08+length(layeredNormal)*0.35,0.05,0.34);let envLod=rough*f32(max(i32(textureNumLevels(environment))-2,0));var w0=probeWeight(0u,in.worldPos);var w1=probeWeight(1u,in.worldPos);var sum=w0+w1;if(sum>1.0){w0/=sum;w1/=sum;sum=1.0;}var reflection=textureSampleLevel(environment,samp,reflected,envLod).rgb*frame.lightColor.w*(1.0-sum);if(w0>0.0){reflection+=textureSampleLevel(reflectionProbe0,samp,probeDirection(0u,reflected,in.worldPos),rough*4.0).rgb*frame.reflectionProbeCenter[0].w*w0;}if(w1>0.0){reflection+=textureSampleLevel(reflectionProbe1,samp,probeDirection(1u,reflected,in.worldPos),rough*4.0).rgb*frame.reflectionProbeCenter[1].w*w1;}reflection*=vec3f(p(21u),p(22u),p(23u));let fresnel=pow(1.0-ndv,max(p(25u),0.01));waterAlpha=max(waterAlpha,opacity*mix(0.35,1.0,fresnel));let reflectionWeight=clamp((0.08+0.92*fresnel)*p(20u),0.0,0.78);color=mix(color,reflection,vec3f(reflectionWeight));
|
||||
let light=normalize(frame.lightDir.xyz);let glint=pow(max(dot(normal,normalize(view+light)),0.0),mix(24.0,160.0,1.0-rough));color+=frame.lightColor.rgb*glint*max(dot(normal,light),0.0)*p(24u);
|
||||
let foamUv=in.worldPos.xz*0.80;let cells=foamCells(foamUv+vec2f(p(0u)*0.08,-p(0u)*0.05));let cells2=foamCells(foamUv*0.61+vec2f(-p(0u)*0.04,p(0u)*0.06));let curvedLines=smoothstep(0.72,0.96,max(cells,cells2*0.72));let crest=smoothstep(p(2u)*0.34,p(2u)*0.80,waterHeight(in.uv));let surfaceTint=mix(shallowColor,vec3f(p(17u),p(18u),p(19u)),vec3f(0.28));let surfacePattern=curvedLines*(0.07+crest*0.11)*p(9u);color=mix(color,surfaceTint,vec3f(surfacePattern));waterAlpha=max(waterAlpha,surfacePattern*0.42);
|
||||
let normalizedDepth=clamp(depth/max(p(7u),0.0001),0.0,1.0);let edge=1.0-smoothstep(max(p(7u)-p(8u),0.0),p(7u)+p(8u),depth);let bandCoord=normalizedDepth*3.4+flowNoise(foamUv*2.1)*0.34;let shoreBands=(1.0-smoothstep(0.035,0.12,abs(fract(bandCoord)-0.5)))*(1.0-normalizedDepth);let shoreFoam=max(edge*(0.52+0.48*cells),shoreBands*0.82)*p(9u);color=mix(color,vec3f(p(17u),p(18u),p(19u)),vec3f(clamp(shoreFoam,0.0,1.0)));waterAlpha=max(waterAlpha,clamp(shoreFoam,0.0,1.0));
|
||||
if(p(27u)>0.0){let externalReflection=textureSample(ssrTex,samp,suv);let externalWeight=clamp(externalReflection.a*p(27u),0.0,1.0);color=mix(color,externalReflection.rgb,vec3f(externalWeight));waterAlpha=max(waterAlpha,externalWeight);}
|
||||
waterAlpha=max(waterAlpha,reflectionWeight*0.72);let transmission=(1.0-waterAlpha)*0.38;color=mix(color,behind,vec3f(transmission));return vec4f(color*in.tint.rgb,waterAlpha);
|
||||
}
|
||||
)wgsl";
|
||||
|
||||
|
||||
@@ -1,197 +1,30 @@
|
||||
#version 450
|
||||
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
#include "tonemap.glsl"
|
||||
layout(location = 0) in vec3 vNormal;
|
||||
layout(location = 1) in vec2 vUV;
|
||||
layout(location = 2) in vec4 vTint;
|
||||
layout(location = 3) in vec3 vWorldPos;
|
||||
layout(location = 4) in vec3 vCameraPos;
|
||||
layout(location = 5) in vec3 vViewPos;
|
||||
|
||||
struct Light3D { vec4 posRadius; vec4 color; };
|
||||
layout(set = 0, binding = 0, std140) uniform Frame {
|
||||
mat4 mvp;
|
||||
mat4 model;
|
||||
vec4 lightDirIntensity;
|
||||
vec4 lightColor;
|
||||
vec4 tint;
|
||||
vec4 cameraPos;
|
||||
vec4 ambient;
|
||||
Light3D lights[8];
|
||||
vec4 texBomb;
|
||||
vec4 parallax;
|
||||
mat4 view;
|
||||
vec4 clipInfo;
|
||||
vec4 cloud;
|
||||
vec4 cloudWind;
|
||||
vec4 bindlessEnv;
|
||||
vec4 envProbeCenter;
|
||||
vec4 envProbeExtent;
|
||||
vec4 skinInfo;
|
||||
vec4 reflectionProbeCenter[2];
|
||||
vec4 reflectionProbeExtent[2];
|
||||
} ubo;
|
||||
|
||||
layout(set = 0, binding = 1) uniform sampler2D albedo;
|
||||
layout(set = 0, binding = 3) uniform samplerCube env;
|
||||
layout(set = 0, binding = 6) uniform sampler2D ssrTex; // screen-space reflection (optional)
|
||||
layout(set = 0, binding = 16) uniform samplerCube reflectionProbe0;
|
||||
layout(set = 0, binding = 17) uniform samplerCube reflectionProbe1;
|
||||
|
||||
layout(push_constant) uniform Externals { float data[32]; } u;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
const float PI = 3.14159265;
|
||||
|
||||
float hash(float n) { return fract(sin(n) * 43758.5453123); }
|
||||
vec2 hash2(int i) { return vec2(hash(float(i) * 7.31), hash(float(i) * 13.17 + 1.0)); }
|
||||
|
||||
float probeWeight(int index, vec3 worldPos) {
|
||||
vec3 edge = ubo.reflectionProbeExtent[index].xyz -
|
||||
abs(worldPos - ubo.reflectionProbeCenter[index].xyz);
|
||||
float inside = min(edge.x, min(edge.y, edge.z));
|
||||
if (inside <= 0.0 || ubo.reflectionProbeCenter[index].w <= 0.0) return 0.0;
|
||||
return clamp(inside / max(ubo.reflectionProbeExtent[index].w, 1e-4), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 probeDirection(int index, vec3 direction, vec3 worldPos) {
|
||||
vec3 center = ubo.reflectionProbeCenter[index].xyz;
|
||||
vec3 extent = ubo.reflectionProbeExtent[index].xyz;
|
||||
vec3 safeDir = mix(vec3(1e-5), direction, greaterThan(abs(direction), vec3(1e-5)));
|
||||
vec3 exitT = max((center - extent - worldPos) / safeDir,
|
||||
(center + extent - worldPos) / safeDir);
|
||||
float distanceToBox = min(exitT.x, min(exitT.y, exitT.z));
|
||||
return distanceToBox > 0.0
|
||||
? normalize(worldPos + direction * distanceToBox - center)
|
||||
: normalize(direction);
|
||||
}
|
||||
|
||||
// Expanding damped-wavelet ripple from a periodic drop (smooth, water-like).
|
||||
float rippleRing(vec2 uv, int i) {
|
||||
float period = max(u.data[7], 1e-3); // rippleInterval
|
||||
float local = mod(u.data[0], period);
|
||||
float startPhase = hash(float(i) * 3.7) * period;
|
||||
float age = local - startPhase;
|
||||
if (age < 0.0) return 0.0;
|
||||
float life = period * 0.8;
|
||||
if (age > life) return 0.0;
|
||||
vec2 center = hash2(i);
|
||||
center = mix(vec2(0.5), center, 0.72); // keep drops near the middle
|
||||
float r = length(uv - center);
|
||||
float radius = age * 0.22; // expanding ring
|
||||
float wavelength = 0.10; // spacing between crests
|
||||
float x = (r - radius) / wavelength;
|
||||
// Soft damped wavelet: smooth gaussian envelope, a crest + trough pair.
|
||||
float envelope = exp(-(x * x) * 0.9);
|
||||
float wave = cos(x * 6.28318);
|
||||
float fade = exp(-age * 1.6); // fade out as it grows
|
||||
return u.data[3] * envelope * wave * fade; // rippleAmp
|
||||
}
|
||||
|
||||
// Water height displacement over UV.
|
||||
float waterHeight(vec2 uv) {
|
||||
float t = u.data[0];
|
||||
float ws = u.data[8];
|
||||
vec2 edgeDist = min(uv, vec2(1.0) - uv);
|
||||
float edgeFactor = 1.0 - clamp(min(edgeDist.x, edgeDist.y) / max(u.data[4], 1e-4), 0.0, 1.0);
|
||||
float w = 0.0;
|
||||
// Shore-edge waves, strongest at the border and fading inward.
|
||||
w += edgeFactor * u.data[2] * (sin((uv.x * ws + t * u.data[1]) * PI * 2.0) +
|
||||
0.5 * sin((uv.y * ws * 0.7 - t * u.data[1] * 1.3) * PI * 2.0));
|
||||
// Fine detail everywhere.
|
||||
w += u.data[2] * 0.10 * sin((uv.x * 31.0 + uv.y * 17.0 + t * u.data[1] * 2.0) * PI * 2.0);
|
||||
// Occasional middle drop ripples.
|
||||
int n = int(u.data[6] + 0.5);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (i >= n) break;
|
||||
w += rippleRing(uv, i);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Surface normal from the analytic displacement (finite differences).
|
||||
float eps = 1e-3;
|
||||
float hL = waterHeight(vUV - vec2(eps, 0.0));
|
||||
float hR = waterHeight(vUV + vec2(eps, 0.0));
|
||||
float hD = waterHeight(vUV - vec2(0.0, eps));
|
||||
float hU = waterHeight(vUV + vec2(0.0, eps));
|
||||
vec2 grad = vec2((hR - hL) / (2.0 * eps), (hU - hD) / (2.0 * eps));
|
||||
vec3 N = normalize(vec3(-grad.x, 1.0, -grad.y));
|
||||
|
||||
vec3 V = normalize(ubo.cameraPos.xyz - vWorldPos);
|
||||
vec3 R = reflect(-V, N);
|
||||
|
||||
// Microfacet sky reflection; wave slope drives surface roughness.
|
||||
float ndv = max(dot(V, N), 0.0);
|
||||
float roughness = clamp(0.08 + length(grad) * 0.06, 0.04, 0.35);
|
||||
float maxLod = float(max(textureQueryLevels(env) - 1, 0));
|
||||
float specMaxLod = maxLod >= 2.0 ? maxLod - 1.0 : maxLod;
|
||||
float lod = roughness * specMaxLod;
|
||||
float probeWeight0 = probeWeight(0, vWorldPos);
|
||||
float probeWeight1 = probeWeight(1, vWorldPos);
|
||||
float probeWeightSum = probeWeight0 + probeWeight1;
|
||||
if (probeWeightSum > 1.0) {
|
||||
probeWeight0 /= probeWeightSum;
|
||||
probeWeight1 /= probeWeightSum;
|
||||
probeWeightSum = 1.0;
|
||||
}
|
||||
vec3 refl = textureLod(env, R, lod).rgb * ubo.lightColor.w * (1.0 - probeWeightSum);
|
||||
if (probeWeight0 > 0.0) {
|
||||
float probeLod = float(max(textureQueryLevels(reflectionProbe0) - 1, 0));
|
||||
refl += textureLod(reflectionProbe0, probeDirection(0, R, vWorldPos),
|
||||
roughness * max(probeLod - 1.0, 0.0)).rgb *
|
||||
ubo.reflectionProbeCenter[0].w * probeWeight0;
|
||||
}
|
||||
if (probeWeight1 > 0.0) {
|
||||
float probeLod = float(max(textureQueryLevels(reflectionProbe1) - 1, 0));
|
||||
refl += textureLod(reflectionProbe1, probeDirection(1, R, vWorldPos),
|
||||
roughness * max(probeLod - 1.0, 0.0)).rgb *
|
||||
ubo.reflectionProbeCenter[1].w * probeWeight1;
|
||||
}
|
||||
refl *= vec3(u.data[12], u.data[13], u.data[14]);
|
||||
float fresnel = 0.02 + 0.98 * pow(1.0 - ndv, 5.0);
|
||||
vec4 brdf = roughness * vec4(-1.0, -0.0275, -0.572, 0.022) +
|
||||
vec4(1.0, 0.0425, 1.04, -0.04);
|
||||
float a004 = min(brdf.x * brdf.x, exp2(-9.28 * ndv)) * brdf.x + brdf.y;
|
||||
vec2 dfg = vec2(-1.04, 1.04) * a004 + brdf.zw;
|
||||
float envWeight = max(0.02 * dfg.x + dfg.y, 0.0);
|
||||
|
||||
vec3 waterCol = vec3(u.data[9], u.data[10], u.data[11]);
|
||||
float reflectAmt = clamp(max(fresnel, envWeight) * u.data[5], 0.0, 1.0);
|
||||
vec3 color = mix(waterCol, refl, reflectAmt);
|
||||
|
||||
// GGX sun glint, sharing the same wave-driven roughness as environment IBL.
|
||||
vec3 L = normalize(ubo.lightDirIntensity.xyz);
|
||||
vec3 H = normalize(V + L);
|
||||
float NoL = max(dot(N, L), 0.0);
|
||||
float NoH = max(dot(N, H), 0.0);
|
||||
float VoH = max(dot(V, H), 0.0);
|
||||
float alpha = max(roughness * roughness, 0.002);
|
||||
float alpha2 = alpha * alpha;
|
||||
float denom = NoH * NoH * (alpha2 - 1.0) + 1.0;
|
||||
float D = alpha2 / max(3.14159265 * denom * denom, 1e-4);
|
||||
float k = (roughness + 1.0) * (roughness + 1.0) * 0.125;
|
||||
float Gv = ndv / max(ndv * (1.0 - k) + k, 1e-4);
|
||||
float Gl = NoL / max(NoL * (1.0 - k) + k, 1e-4);
|
||||
float Fsun = 0.02 + 0.98 * pow(1.0 - VoH, 5.0);
|
||||
float spec = D * Gv * Gl * Fsun / max(4.0 * ndv * max(NoL, 0.001), 1e-3);
|
||||
color += ubo.lightColor.rgb * spec * NoL * u.data[15];
|
||||
|
||||
// Soft foam where waves meet the edge.
|
||||
vec2 edgeDist = min(vUV, vec2(1.0) - vUV);
|
||||
float edgeFactor = 1.0 - clamp(min(edgeDist.x, edgeDist.y) / max(u.data[4], 1e-4), 0.0, 1.0);
|
||||
color = mix(color, vec3(0.85, 0.93, 1.0), edgeFactor * 0.22);
|
||||
// Optional screen-space reflection overlay (bound via the height slot,
|
||||
// binding 6). Sample the SSR pass result at this fragment's screen UV and
|
||||
// blend it over the env reflection where SSR found a hit (ssr.a > 0).
|
||||
if (u.data[18] > 0.5 && u.data[16] > 1.0 && u.data[17] > 1.0) {
|
||||
vec2 sUV = vec2(gl_FragCoord.x / u.data[16], gl_FragCoord.y / u.data[17]);
|
||||
vec4 ssr = texture(ssrTex, sUV);
|
||||
float ssrWeight = clamp(ssr.a * u.data[19], 0.0, 1.0);
|
||||
color = ssr.rgb * u.data[19] + color * (1.0 - ssrWeight);
|
||||
}
|
||||
|
||||
outColor = vec4(color, 1.0);
|
||||
layout(location=0) in vec3 vNormal; layout(location=1) in vec2 vUV; layout(location=2) in vec4 vTint;
|
||||
layout(location=3) in vec3 vWorldPos; layout(location=4) in vec3 vCameraPos; layout(location=5) in vec3 vViewPos;
|
||||
struct Light3D{vec4 posRadius;vec4 color;};
|
||||
layout(set=0,binding=0,std140) uniform Frame{mat4 mvp;mat4 model;vec4 lightDirIntensity;vec4 lightColor;vec4 tint;vec4 cameraPos;vec4 ambient;Light3D lights[8];vec4 texBomb;vec4 parallax;mat4 view;vec4 clipInfo;vec4 cloud;vec4 cloudWind;vec4 bindlessEnv;vec4 envProbeCenter;vec4 envProbeExtent;vec4 skinInfo;vec4 reflectionProbeCenter[2];vec4 reflectionProbeExtent[2];}ubo;
|
||||
layout(set=0,binding=1)uniform sampler2D sceneColorTex; layout(set=0,binding=3)uniform samplerCube env;
|
||||
layout(set=0,binding=6)uniform sampler2D ssrTex; layout(set=0,binding=7)uniform sampler2D sceneDepthTex;
|
||||
layout(set=0,binding=16)uniform samplerCube reflectionProbe0; layout(set=0,binding=17)uniform samplerCube reflectionProbe1;
|
||||
layout(push_constant)uniform Externals{float data[32];}u; layout(location=0)out vec4 outColor;
|
||||
const float PI=3.14159265;
|
||||
float hash(float n){return fract(sin(n)*43758.5453123);} vec2 hash2(int i){return vec2(hash(float(i)*7.31),hash(float(i)*13.17+1.0));}
|
||||
float shapedWave(float phase){float w=sin(phase);return sign(w)*pow(abs(w),max(u.data[31],0.1));}
|
||||
float rippleRing(vec2 uv,int i){float period=max(u.data[6],0.05);float age=mod(u.data[0],period)-hash(float(i)*3.7)*period;if(age<0.0||age>period*0.8)return 0.0;vec2 center=mix(vec2(0.5),hash2(i),0.72);float x=(length(uv-center)-age*0.22)/0.10;return u.data[4]*exp(-(x*x)*0.9)*cos(x*2.0*PI)*exp(-age*1.6);}
|
||||
float waterHeight(vec2 uv){float s=u.data[3],t=u.data[0]*u.data[1];vec2 q=uv+vec2(sin(uv.y*s*.31+t*.37),sin(uv.x*s*.27-t*.29))*.018;float p0=dot(q,vec2(1,.23))*s*2.0*PI+t*2.0*PI;float p1=dot(q,vec2(-.48,.88))*s*.73*2.0*PI-t*1.17*2.0*PI;float p2=dot(q,vec2(.37,.93))*s*1.31*2.0*PI+t*.61*2.0*PI;float h=u.data[2]*(shapedWave(p0)*.42+shapedWave(p1)*.34+shapedWave(p2)*.24);int count=int(u.data[5]+.5);for(int i=0;i<8;++i){if(i>=count)break;h+=rippleRing(q,i);}return h;}
|
||||
float caustics(vec2 p){float s=max(u.data[30],.01);p*=s;float t=u.data[0]*u.data[1];float a=sin(p.x*1.37+p.y*.73+t*1.2);float b=sin(p.x*-.62+p.y*1.51-t*.93);float c=sin((p.x+p.y)*1.11+t*.57);float ridge=1.0-abs((a+b+c)/3.0);return pow(clamp(ridge,0,1),5.0);}
|
||||
float foamCells(vec2 p){vec2 cell=floor(p),f=fract(p);float first=10.0,second=10.0;for(int y=-1;y<=1;++y)for(int x=-1;x<=1;++x){vec2 g=vec2(x,y),point=hash2(int(cell.x+g.x)*131+int(cell.y+g.y)*197);float d=length(g+point-f);if(d<first){second=first;first=d;}else if(d<second)second=d;}return 1.0-smoothstep(.025,.18,second-first);}
|
||||
float flowNoise(vec2 p){float t=u.data[0]*u.data[1];float a=sin(p.x*1.31+p.y*.73+t*1.17);float b=sin(p.x*-.67+p.y*1.57-t*.83);return .5+.25*(a+b);}
|
||||
float crestLines(vec2 p){float t=u.data[0]*u.data[1],s=max(u.data[3],.01)*.43;float phaseA=dot(p,vec2(.96,.28))*s+sin(p.y*.72-t*.31)*1.35+t*1.35;float phaseB=dot(p,vec2(-.38,.92))*s*.67+sin(p.x*.58+t*.27)*1.15-t*.82;float a=smoothstep(.91,.985,sin(phaseA));float b=smoothstep(.935,.992,sin(phaseB));float broken=smoothstep(.52,.70,flowNoise(p*.66+4.7));return max(a*.58,b*.38)*broken;}
|
||||
float probeWeight(int i,vec3 p){vec3 edge=ubo.reflectionProbeExtent[i].xyz-abs(p-ubo.reflectionProbeCenter[i].xyz);float inside=min(edge.x,min(edge.y,edge.z));if(inside<=0.0||ubo.reflectionProbeCenter[i].w<=0.0)return 0.0;return clamp(inside/max(ubo.reflectionProbeExtent[i].w,1e-4),0.0,1.0);}
|
||||
vec3 probeDirection(int i,vec3 d,vec3 p){vec3 c=ubo.reflectionProbeCenter[i].xyz,e=ubo.reflectionProbeExtent[i].xyz;vec3 sd=mix(vec3(1e-5),d,greaterThan(abs(d),vec3(1e-5)));vec3 t=max((c-e-p)/sd,(c+e-p)/sd);float exitDistance=min(t.x,min(t.y,t.z));return exitDistance>0.0?normalize(p+d*exitDistance-c):normalize(d);}
|
||||
void main(){
|
||||
float eps=1e-3;vec2 grad=vec2(waterHeight(vUV+vec2(eps,0))-waterHeight(vUV-vec2(eps,0)),waterHeight(vUV+vec2(0,eps))-waterHeight(vUV-vec2(0,eps)))/(2.0*eps);float ft=u.data[0]*u.data[1];vec2 normalLayerA=vec2(sin(vWorldPos.x*1.7+vWorldPos.z*.8+ft),cos(vWorldPos.z*1.5-vWorldPos.x*.6-ft*.83));vec2 normalLayerB=vec2(cos(vWorldPos.x*3.1-vWorldPos.z*1.2-ft*1.31),sin(vWorldPos.z*2.7+vWorldPos.x*.9+ft*.57));vec2 layeredNormal=grad*.11+normalLayerA*.055+normalLayerB*.025;vec3 N=normalize(vNormal+vec3(-layeredNormal.x,0,-layeredNormal.y));vec3 V=normalize(ubo.cameraPos.xyz-vWorldPos),R=reflect(-V,N);float ndv=max(dot(V,N),0.0);
|
||||
ivec2 ds=textureSize(sceneColorTex,0);vec2 suv=gl_FragCoord.xy/max(vec2(ds),vec2(1));float nearZ=max(ubo.clipInfo.x,1e-4),farZ=max(ubo.clipInfo.y,nearZ+1e-3);float surface=clamp((-vViewPos.z-nearZ)/(farZ-nearZ),0,1);bool hasDepth=u.data[29]>=0.0;float opacity=hasDepth?u.data[29]:-u.data[29]-1.0;vec4 sceneSample=texture(sceneColorTex,suv);float scene=hasDepth?texture(sceneDepthTex,suv).r:surface+1.0;if(sceneSample.a>surface+1e-5&&sceneSample.a<.9999)scene=sceneSample.a;float depth=max((scene-surface)*(farZ-nearZ),0.0);float shallow=1.0-smoothstep(0.0,max(u.data[16],1e-4),depth);vec3 deepColor=vec3(u.data[10],u.data[11],u.data[12]),shallowColor=vec3(u.data[13],u.data[14],u.data[15]);vec3 water=mix(deepColor,shallowColor,shallow);
|
||||
vec2 flow=vec2(flowNoise(vWorldPos.xz*1.8),flowNoise(vWorldPos.zx*2.3+17.0))-.5;vec2 ruv=clamp(suv+(layeredNormal*.75+flow*.35)*u.data[28]*(0.25+shallow*.75),vec2(.001),vec2(.999));vec3 behind=texture(sceneColorTex,ruv).rgb;float absorption=hasDepth?1-exp(-depth/max(u.data[16],.001)*3.2):1.0;float waterAlpha=clamp(opacity*mix(.35,1.0,absorption),0,1);vec3 color=water;float causticMask=hasDepth?smoothstep(0.02,max(u.data[16],.03),depth)*shallow:0.0;color+=shallowColor*caustics(vWorldPos.xz)*causticMask*u.data[26];
|
||||
float rough=clamp(.08+length(layeredNormal)*.35,.05,.34),lod=rough*float(max(textureQueryLevels(env)-2,0));float w0=probeWeight(0,vWorldPos),w1=probeWeight(1,vWorldPos),sum=w0+w1;if(sum>1){w0/=sum;w1/=sum;sum=1;}vec3 refl=textureLod(env,R,lod).rgb*ubo.lightColor.w*(1-sum);if(w0>0)refl+=textureLod(reflectionProbe0,probeDirection(0,R,vWorldPos),rough*4).rgb*ubo.reflectionProbeCenter[0].w*w0;if(w1>0)refl+=textureLod(reflectionProbe1,probeDirection(1,R,vWorldPos),rough*4).rgb*ubo.reflectionProbeCenter[1].w*w1;refl*=vec3(u.data[21],u.data[22],u.data[23]);float fresnel=pow(1-ndv,max(u.data[25],.01));waterAlpha=max(waterAlpha,opacity*mix(.35,1.0,fresnel));float reflectionWeight=clamp((.08+.92*fresnel)*u.data[20],0,.78);color=mix(color,refl,reflectionWeight);
|
||||
vec3 L=normalize(ubo.lightDirIntensity.xyz);float glint=pow(max(dot(N,normalize(V+L)),0),mix(24,160,1-rough));color+=ubo.lightColor.rgb*glint*max(dot(N,L),0)*u.data[24];
|
||||
vec2 foamUv=vWorldPos.xz*.80;float cells=foamCells(foamUv+vec2(u.data[0]*.08,-u.data[0]*.05));float cells2=foamCells(foamUv*.61+vec2(-u.data[0]*.04,u.data[0]*.06));float curvedLines=smoothstep(.72,.96,max(cells,cells2*.72));float crest=smoothstep(u.data[2]*.34,u.data[2]*.80,waterHeight(vUV));vec3 surfaceTint=mix(shallowColor,vec3(u.data[17],u.data[18],u.data[19]),.28);float surfacePattern=curvedLines*(.07+crest*.11)*u.data[9];color=mix(color,surfaceTint,surfacePattern);waterAlpha=max(waterAlpha,surfacePattern*.42);float normalizedDepth=clamp(depth/max(u.data[7],1e-4),0,1);float edge=1-smoothstep(max(u.data[7]-u.data[8],0),u.data[7]+u.data[8],depth);float bandCoord=normalizedDepth*3.4+flowNoise(foamUv*2.1)*.34;float shoreBands=(1-smoothstep(.035,.12,abs(fract(bandCoord)-.5)))*(1-normalizedDepth);float shoreFoam=max(edge*(.52+.48*cells),shoreBands*.82)*u.data[9];color=mix(color,vec3(u.data[17],u.data[18],u.data[19]),clamp(shoreFoam,0,1));waterAlpha=max(waterAlpha,clamp(shoreFoam,0,1));
|
||||
if(u.data[27]>0.0){vec4 externalReflection=texture(ssrTex,suv);float externalWeight=clamp(externalReflection.a*u.data[27],0,1);color=mix(color,externalReflection.rgb,externalWeight);waterAlpha=max(waterAlpha,externalWeight);}waterAlpha=max(waterAlpha,reflectionWeight*.72);float transmission=(1.0-waterAlpha)*.38;color=mix(color,behind,transmission);outColor=vec4(color*vTint.rgb,waterAlpha);
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 inPos;
|
||||
layout(location=1) in vec3 inNormal;
|
||||
layout(location=2) in vec2 inUV;
|
||||
|
||||
struct Light3D{vec4 posRadius;vec4 color;};
|
||||
layout(set=0,binding=0,std140) uniform Frame{mat4 mvp;mat4 model;vec4 lightDirIntensity;vec4 lightColor;vec4 tint;vec4 cameraPos;vec4 ambient;Light3D lights[8];vec4 texBomb;vec4 parallax;mat4 view;vec4 clipInfo;vec4 cloud;vec4 cloudWind;vec4 bindlessEnv;vec4 envProbeCenter;vec4 envProbeExtent;vec4 skinInfo;vec4 reflectionProbeCenter[2];vec4 reflectionProbeExtent[2];}ubo;
|
||||
layout(push_constant)uniform Externals{float data[32];}u;
|
||||
|
||||
layout(location=0) out vec3 vNormal;
|
||||
layout(location=1) out vec2 vUV;
|
||||
layout(location=2) out vec4 vTint;
|
||||
layout(location=3) out vec3 vWorldPos;
|
||||
layout(location=4) out vec3 vCameraPos;
|
||||
layout(location=5) out vec3 vViewPos;
|
||||
|
||||
float waveHeight(vec2 worldXZ){
|
||||
float scale=max(u.data[3],.01)*.72;
|
||||
float time=u.data[0]*u.data[1];
|
||||
float primary=sin(dot(worldXZ,vec2(.91,.31))*scale+time*1.45);
|
||||
float crossWave=sin(dot(worldXZ,vec2(-.36,.93))*scale*.58-time*.92);
|
||||
float swell=sin(dot(worldXZ,vec2(.22,.98))*scale*.24+time*.37);
|
||||
return u.data[2]*(primary*.48+crossWave*.34+swell*.18);
|
||||
}
|
||||
|
||||
void main(){
|
||||
vec4 baseWorld=ubo.model*vec4(inPos,1);
|
||||
float eps=.025;
|
||||
float height=waveHeight(baseWorld.xz);
|
||||
float dx=(waveHeight(baseWorld.xz+vec2(eps,0))-waveHeight(baseWorld.xz-vec2(eps,0)))/(2*eps);
|
||||
float dz=(waveHeight(baseWorld.xz+vec2(0,eps))-waveHeight(baseWorld.xz-vec2(0,eps)))/(2*eps);
|
||||
vec3 localPos=inPos;
|
||||
localPos.y+=height;
|
||||
vec4 world=ubo.model*vec4(localPos,1);
|
||||
gl_Position=ubo.mvp*vec4(localPos,1);
|
||||
vWorldPos=world.xyz;
|
||||
vViewPos=(ubo.view*world).xyz;
|
||||
vNormal=normalize(transpose(inverse(mat3(ubo.model)))*normalize(vec3(-dx,1,-dz)));
|
||||
vUV=inUV;
|
||||
vTint=ubo.tint;
|
||||
vCameraPos=ubo.cameraPos.xyz;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,231 @@
|
||||
static const uint32_t water_vert_spv[] = {
|
||||
0x07230203, 0x00010000, 0x000d000b, 0x000000fb, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
|
||||
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
|
||||
0x000f000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x00000074, 0x000000c0, 0x000000cc,
|
||||
0x000000cf, 0x000000d6, 0x000000ec, 0x000000ee, 0x000000f0, 0x000000f5, 0x000000fa, 0x00030003,
|
||||
0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79,
|
||||
0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45,
|
||||
0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000,
|
||||
0x00060005, 0x0000000b, 0x65766177, 0x67696548, 0x76287468, 0x003b3266, 0x00040005, 0x0000000a,
|
||||
0x6c726f77, 0x005a5864, 0x00040005, 0x0000000e, 0x6c616373, 0x00000065, 0x00050005, 0x00000012,
|
||||
0x65747845, 0x6c616e72, 0x00000073, 0x00050006, 0x00000012, 0x00000000, 0x61746164, 0x00000000,
|
||||
0x00030005, 0x00000014, 0x00000075, 0x00040005, 0x0000001f, 0x656d6974, 0x00000000, 0x00040005,
|
||||
0x00000026, 0x6d697270, 0x00797261, 0x00050005, 0x00000033, 0x736f7263, 0x76615773, 0x00000065,
|
||||
0x00040005, 0x00000042, 0x6c657773, 0x0000006c, 0x00050005, 0x00000064, 0x65736162, 0x6c726f57,
|
||||
0x00000064, 0x00040005, 0x00000066, 0x6867694c, 0x00443374, 0x00060006, 0x00000066, 0x00000000,
|
||||
0x52736f70, 0x75696461, 0x00000073, 0x00050006, 0x00000066, 0x00000001, 0x6f6c6f63, 0x00000072,
|
||||
0x00040005, 0x0000006c, 0x6d617246, 0x00000065, 0x00040006, 0x0000006c, 0x00000000, 0x0070766d,
|
||||
0x00050006, 0x0000006c, 0x00000001, 0x65646f6d, 0x0000006c, 0x00080006, 0x0000006c, 0x00000002,
|
||||
0x6867696c, 0x72694474, 0x65746e49, 0x7469736e, 0x00000079, 0x00060006, 0x0000006c, 0x00000003,
|
||||
0x6867696c, 0x6c6f4374, 0x0000726f, 0x00050006, 0x0000006c, 0x00000004, 0x746e6974, 0x00000000,
|
||||
0x00060006, 0x0000006c, 0x00000005, 0x656d6163, 0x6f506172, 0x00000073, 0x00050006, 0x0000006c,
|
||||
0x00000006, 0x69626d61, 0x00746e65, 0x00050006, 0x0000006c, 0x00000007, 0x6867696c, 0x00007374,
|
||||
0x00050006, 0x0000006c, 0x00000008, 0x42786574, 0x00626d6f, 0x00060006, 0x0000006c, 0x00000009,
|
||||
0x61726170, 0x78616c6c, 0x00000000, 0x00050006, 0x0000006c, 0x0000000a, 0x77656976, 0x00000000,
|
||||
0x00060006, 0x0000006c, 0x0000000b, 0x70696c63, 0x6f666e49, 0x00000000, 0x00050006, 0x0000006c,
|
||||
0x0000000c, 0x756f6c63, 0x00000064, 0x00060006, 0x0000006c, 0x0000000d, 0x756f6c63, 0x6e695764,
|
||||
0x00000064, 0x00060006, 0x0000006c, 0x0000000e, 0x646e6962, 0x7373656c, 0x00766e45, 0x00070006,
|
||||
0x0000006c, 0x0000000f, 0x50766e65, 0x65626f72, 0x746e6543, 0x00007265, 0x00070006, 0x0000006c,
|
||||
0x00000010, 0x50766e65, 0x65626f72, 0x65747845, 0x0000746e, 0x00060006, 0x0000006c, 0x00000011,
|
||||
0x6e696b73, 0x6f666e49, 0x00000000, 0x00090006, 0x0000006c, 0x00000012, 0x6c666572, 0x69746365,
|
||||
0x72506e6f, 0x4365626f, 0x65746e65, 0x00000072, 0x00090006, 0x0000006c, 0x00000013, 0x6c666572,
|
||||
0x69746365, 0x72506e6f, 0x4565626f, 0x6e657478, 0x00000074, 0x00030005, 0x0000006e, 0x006f6275,
|
||||
0x00040005, 0x00000074, 0x6f506e69, 0x00000073, 0x00030005, 0x0000007c, 0x00737065, 0x00040005,
|
||||
0x0000007e, 0x67696568, 0x00007468, 0x00040005, 0x0000007f, 0x61726170, 0x0000006d, 0x00030005,
|
||||
0x00000083, 0x00007864, 0x00040005, 0x0000008a, 0x61726170, 0x0000006d, 0x00040005, 0x00000091,
|
||||
0x61726170, 0x0000006d, 0x00030005, 0x00000098, 0x00007a64, 0x00040005, 0x0000009e, 0x61726170,
|
||||
0x0000006d, 0x00040005, 0x000000a5, 0x61726170, 0x0000006d, 0x00050005, 0x000000ac, 0x61636f6c,
|
||||
0x736f506c, 0x00000000, 0x00040005, 0x000000b4, 0x6c726f77, 0x00000064, 0x00060005, 0x000000be,
|
||||
0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x000000be, 0x00000000, 0x505f6c67,
|
||||
0x7469736f, 0x006e6f69, 0x00070006, 0x000000be, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953,
|
||||
0x00000000, 0x00070006, 0x000000be, 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e,
|
||||
0x00070006, 0x000000be, 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, 0x00030005,
|
||||
0x000000c0, 0x00000000, 0x00050005, 0x000000cc, 0x726f5776, 0x6f50646c, 0x00000073, 0x00050005,
|
||||
0x000000cf, 0x65695676, 0x736f5077, 0x00000000, 0x00040005, 0x000000d6, 0x726f4e76, 0x006c616d,
|
||||
0x00030005, 0x000000ec, 0x00565576, 0x00040005, 0x000000ee, 0x56556e69, 0x00000000, 0x00040005,
|
||||
0x000000f0, 0x6e695476, 0x00000074, 0x00050005, 0x000000f5, 0x6d614376, 0x50617265, 0x0000736f,
|
||||
0x00050005, 0x000000fa, 0x6f4e6e69, 0x6c616d72, 0x00000000, 0x00040047, 0x00000011, 0x00000006,
|
||||
0x00000004, 0x00030047, 0x00000012, 0x00000002, 0x00050048, 0x00000012, 0x00000000, 0x00000023,
|
||||
0x00000000, 0x00050048, 0x00000066, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000066,
|
||||
0x00000001, 0x00000023, 0x00000010, 0x00040047, 0x00000068, 0x00000006, 0x00000020, 0x00040047,
|
||||
0x0000006a, 0x00000006, 0x00000010, 0x00040047, 0x0000006b, 0x00000006, 0x00000010, 0x00030047,
|
||||
0x0000006c, 0x00000002, 0x00040048, 0x0000006c, 0x00000000, 0x00000005, 0x00050048, 0x0000006c,
|
||||
0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x0000006c, 0x00000000, 0x00000023, 0x00000000,
|
||||
0x00040048, 0x0000006c, 0x00000001, 0x00000005, 0x00050048, 0x0000006c, 0x00000001, 0x00000007,
|
||||
0x00000010, 0x00050048, 0x0000006c, 0x00000001, 0x00000023, 0x00000040, 0x00050048, 0x0000006c,
|
||||
0x00000002, 0x00000023, 0x00000080, 0x00050048, 0x0000006c, 0x00000003, 0x00000023, 0x00000090,
|
||||
0x00050048, 0x0000006c, 0x00000004, 0x00000023, 0x000000a0, 0x00050048, 0x0000006c, 0x00000005,
|
||||
0x00000023, 0x000000b0, 0x00050048, 0x0000006c, 0x00000006, 0x00000023, 0x000000c0, 0x00050048,
|
||||
0x0000006c, 0x00000007, 0x00000023, 0x000000d0, 0x00050048, 0x0000006c, 0x00000008, 0x00000023,
|
||||
0x000001d0, 0x00050048, 0x0000006c, 0x00000009, 0x00000023, 0x000001e0, 0x00040048, 0x0000006c,
|
||||
0x0000000a, 0x00000005, 0x00050048, 0x0000006c, 0x0000000a, 0x00000007, 0x00000010, 0x00050048,
|
||||
0x0000006c, 0x0000000a, 0x00000023, 0x000001f0, 0x00050048, 0x0000006c, 0x0000000b, 0x00000023,
|
||||
0x00000230, 0x00050048, 0x0000006c, 0x0000000c, 0x00000023, 0x00000240, 0x00050048, 0x0000006c,
|
||||
0x0000000d, 0x00000023, 0x00000250, 0x00050048, 0x0000006c, 0x0000000e, 0x00000023, 0x00000260,
|
||||
0x00050048, 0x0000006c, 0x0000000f, 0x00000023, 0x00000270, 0x00050048, 0x0000006c, 0x00000010,
|
||||
0x00000023, 0x00000280, 0x00050048, 0x0000006c, 0x00000011, 0x00000023, 0x00000290, 0x00050048,
|
||||
0x0000006c, 0x00000012, 0x00000023, 0x000002a0, 0x00050048, 0x0000006c, 0x00000013, 0x00000023,
|
||||
0x000002c0, 0x00040047, 0x0000006e, 0x00000021, 0x00000000, 0x00040047, 0x0000006e, 0x00000022,
|
||||
0x00000000, 0x00040047, 0x00000074, 0x0000001e, 0x00000000, 0x00030047, 0x000000be, 0x00000002,
|
||||
0x00050048, 0x000000be, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x000000be, 0x00000001,
|
||||
0x0000000b, 0x00000001, 0x00050048, 0x000000be, 0x00000002, 0x0000000b, 0x00000003, 0x00050048,
|
||||
0x000000be, 0x00000003, 0x0000000b, 0x00000004, 0x00040047, 0x000000cc, 0x0000001e, 0x00000003,
|
||||
0x00040047, 0x000000cf, 0x0000001e, 0x00000005, 0x00040047, 0x000000d6, 0x0000001e, 0x00000000,
|
||||
0x00040047, 0x000000ec, 0x0000001e, 0x00000001, 0x00040047, 0x000000ee, 0x0000001e, 0x00000002,
|
||||
0x00040047, 0x000000f0, 0x0000001e, 0x00000002, 0x00040047, 0x000000f5, 0x0000001e, 0x00000004,
|
||||
0x00040047, 0x000000fa, 0x0000001e, 0x00000001, 0x00020013, 0x00000002, 0x00030021, 0x00000003,
|
||||
0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000002,
|
||||
0x00040020, 0x00000008, 0x00000007, 0x00000007, 0x00040021, 0x00000009, 0x00000006, 0x00000008,
|
||||
0x00040020, 0x0000000d, 0x00000007, 0x00000006, 0x00040015, 0x0000000f, 0x00000020, 0x00000000,
|
||||
0x0004002b, 0x0000000f, 0x00000010, 0x00000020, 0x0004001c, 0x00000011, 0x00000006, 0x00000010,
|
||||
0x0003001e, 0x00000012, 0x00000011, 0x00040020, 0x00000013, 0x00000009, 0x00000012, 0x0004003b,
|
||||
0x00000013, 0x00000014, 0x00000009, 0x00040015, 0x00000015, 0x00000020, 0x00000001, 0x0004002b,
|
||||
0x00000015, 0x00000016, 0x00000000, 0x0004002b, 0x00000015, 0x00000017, 0x00000003, 0x00040020,
|
||||
0x00000018, 0x00000009, 0x00000006, 0x0004002b, 0x00000006, 0x0000001b, 0x3c23d70a, 0x0004002b,
|
||||
0x00000006, 0x0000001d, 0x3f3851ec, 0x0004002b, 0x00000015, 0x00000022, 0x00000001, 0x0004002b,
|
||||
0x00000006, 0x00000028, 0x3f68f5c3, 0x0004002b, 0x00000006, 0x00000029, 0x3e9eb852, 0x0005002c,
|
||||
0x00000007, 0x0000002a, 0x00000028, 0x00000029, 0x0004002b, 0x00000006, 0x0000002f, 0x3fb9999a,
|
||||
0x0004002b, 0x00000006, 0x00000035, 0xbeb851ec, 0x0004002b, 0x00000006, 0x00000036, 0x3f6e147b,
|
||||
0x0005002c, 0x00000007, 0x00000037, 0x00000035, 0x00000036, 0x0004002b, 0x00000006, 0x0000003b,
|
||||
0x3f147ae1, 0x0004002b, 0x00000006, 0x0000003e, 0x3f6b851f, 0x0004002b, 0x00000006, 0x00000044,
|
||||
0x3e6147ae, 0x0004002b, 0x00000006, 0x00000045, 0x3f7ae148, 0x0005002c, 0x00000007, 0x00000046,
|
||||
0x00000044, 0x00000045, 0x0004002b, 0x00000006, 0x0000004a, 0x3e75c28f, 0x0004002b, 0x00000006,
|
||||
0x0000004d, 0x3ebd70a4, 0x0004002b, 0x00000015, 0x00000051, 0x00000002, 0x0004002b, 0x00000006,
|
||||
0x00000055, 0x3ef5c28f, 0x0004002b, 0x00000006, 0x00000058, 0x3eae147b, 0x0004002b, 0x00000006,
|
||||
0x0000005c, 0x3e3851ec, 0x00040017, 0x00000062, 0x00000006, 0x00000004, 0x00040020, 0x00000063,
|
||||
0x00000007, 0x00000062, 0x00040018, 0x00000065, 0x00000062, 0x00000004, 0x0004001e, 0x00000066,
|
||||
0x00000062, 0x00000062, 0x0004002b, 0x0000000f, 0x00000067, 0x00000008, 0x0004001c, 0x00000068,
|
||||
0x00000066, 0x00000067, 0x0004002b, 0x0000000f, 0x00000069, 0x00000002, 0x0004001c, 0x0000006a,
|
||||
0x00000062, 0x00000069, 0x0004001c, 0x0000006b, 0x00000062, 0x00000069, 0x0016001e, 0x0000006c,
|
||||
0x00000065, 0x00000065, 0x00000062, 0x00000062, 0x00000062, 0x00000062, 0x00000062, 0x00000068,
|
||||
0x00000062, 0x00000062, 0x00000065, 0x00000062, 0x00000062, 0x00000062, 0x00000062, 0x00000062,
|
||||
0x00000062, 0x00000062, 0x0000006a, 0x0000006b, 0x00040020, 0x0000006d, 0x00000002, 0x0000006c,
|
||||
0x0004003b, 0x0000006d, 0x0000006e, 0x00000002, 0x00040020, 0x0000006f, 0x00000002, 0x00000065,
|
||||
0x00040017, 0x00000072, 0x00000006, 0x00000003, 0x00040020, 0x00000073, 0x00000001, 0x00000072,
|
||||
0x0004003b, 0x00000073, 0x00000074, 0x00000001, 0x0004002b, 0x00000006, 0x00000076, 0x3f800000,
|
||||
0x0004002b, 0x00000006, 0x0000007d, 0x3ccccccd, 0x0004002b, 0x00000006, 0x00000087, 0x00000000,
|
||||
0x0004002b, 0x00000006, 0x00000094, 0x40000000, 0x00040020, 0x000000ab, 0x00000007, 0x00000072,
|
||||
0x0004002b, 0x0000000f, 0x000000af, 0x00000001, 0x0004001c, 0x000000bd, 0x00000006, 0x000000af,
|
||||
0x0006001e, 0x000000be, 0x00000062, 0x00000006, 0x000000bd, 0x000000bd, 0x00040020, 0x000000bf,
|
||||
0x00000003, 0x000000be, 0x0004003b, 0x000000bf, 0x000000c0, 0x00000003, 0x00040020, 0x000000c9,
|
||||
0x00000003, 0x00000062, 0x00040020, 0x000000cb, 0x00000003, 0x00000072, 0x0004003b, 0x000000cb,
|
||||
0x000000cc, 0x00000003, 0x0004003b, 0x000000cb, 0x000000cf, 0x00000003, 0x0004002b, 0x00000015,
|
||||
0x000000d0, 0x0000000a, 0x0004003b, 0x000000cb, 0x000000d6, 0x00000003, 0x00040018, 0x000000d9,
|
||||
0x00000072, 0x00000003, 0x00040020, 0x000000eb, 0x00000003, 0x00000007, 0x0004003b, 0x000000eb,
|
||||
0x000000ec, 0x00000003, 0x00040020, 0x000000ed, 0x00000001, 0x00000007, 0x0004003b, 0x000000ed,
|
||||
0x000000ee, 0x00000001, 0x0004003b, 0x000000c9, 0x000000f0, 0x00000003, 0x0004002b, 0x00000015,
|
||||
0x000000f1, 0x00000004, 0x00040020, 0x000000f2, 0x00000002, 0x00000062, 0x0004003b, 0x000000cb,
|
||||
0x000000f5, 0x00000003, 0x0004002b, 0x00000015, 0x000000f6, 0x00000005, 0x0004003b, 0x00000073,
|
||||
0x000000fa, 0x00000001, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8,
|
||||
0x00000005, 0x0004003b, 0x00000063, 0x00000064, 0x00000007, 0x0004003b, 0x0000000d, 0x0000007c,
|
||||
0x00000007, 0x0004003b, 0x0000000d, 0x0000007e, 0x00000007, 0x0004003b, 0x00000008, 0x0000007f,
|
||||
0x00000007, 0x0004003b, 0x0000000d, 0x00000083, 0x00000007, 0x0004003b, 0x00000008, 0x0000008a,
|
||||
0x00000007, 0x0004003b, 0x00000008, 0x00000091, 0x00000007, 0x0004003b, 0x0000000d, 0x00000098,
|
||||
0x00000007, 0x0004003b, 0x00000008, 0x0000009e, 0x00000007, 0x0004003b, 0x00000008, 0x000000a5,
|
||||
0x00000007, 0x0004003b, 0x000000ab, 0x000000ac, 0x00000007, 0x0004003b, 0x00000063, 0x000000b4,
|
||||
0x00000007, 0x00050041, 0x0000006f, 0x00000070, 0x0000006e, 0x00000022, 0x0004003d, 0x00000065,
|
||||
0x00000071, 0x00000070, 0x0004003d, 0x00000072, 0x00000075, 0x00000074, 0x00050051, 0x00000006,
|
||||
0x00000077, 0x00000075, 0x00000000, 0x00050051, 0x00000006, 0x00000078, 0x00000075, 0x00000001,
|
||||
0x00050051, 0x00000006, 0x00000079, 0x00000075, 0x00000002, 0x00070050, 0x00000062, 0x0000007a,
|
||||
0x00000077, 0x00000078, 0x00000079, 0x00000076, 0x00050091, 0x00000062, 0x0000007b, 0x00000071,
|
||||
0x0000007a, 0x0003003e, 0x00000064, 0x0000007b, 0x0003003e, 0x0000007c, 0x0000007d, 0x0004003d,
|
||||
0x00000062, 0x00000080, 0x00000064, 0x0007004f, 0x00000007, 0x00000081, 0x00000080, 0x00000080,
|
||||
0x00000000, 0x00000002, 0x0003003e, 0x0000007f, 0x00000081, 0x00050039, 0x00000006, 0x00000082,
|
||||
0x0000000b, 0x0000007f, 0x0003003e, 0x0000007e, 0x00000082, 0x0004003d, 0x00000062, 0x00000084,
|
||||
0x00000064, 0x0007004f, 0x00000007, 0x00000085, 0x00000084, 0x00000084, 0x00000000, 0x00000002,
|
||||
0x0004003d, 0x00000006, 0x00000086, 0x0000007c, 0x00050050, 0x00000007, 0x00000088, 0x00000086,
|
||||
0x00000087, 0x00050081, 0x00000007, 0x00000089, 0x00000085, 0x00000088, 0x0003003e, 0x0000008a,
|
||||
0x00000089, 0x00050039, 0x00000006, 0x0000008b, 0x0000000b, 0x0000008a, 0x0004003d, 0x00000062,
|
||||
0x0000008c, 0x00000064, 0x0007004f, 0x00000007, 0x0000008d, 0x0000008c, 0x0000008c, 0x00000000,
|
||||
0x00000002, 0x0004003d, 0x00000006, 0x0000008e, 0x0000007c, 0x00050050, 0x00000007, 0x0000008f,
|
||||
0x0000008e, 0x00000087, 0x00050083, 0x00000007, 0x00000090, 0x0000008d, 0x0000008f, 0x0003003e,
|
||||
0x00000091, 0x00000090, 0x00050039, 0x00000006, 0x00000092, 0x0000000b, 0x00000091, 0x00050083,
|
||||
0x00000006, 0x00000093, 0x0000008b, 0x00000092, 0x0004003d, 0x00000006, 0x00000095, 0x0000007c,
|
||||
0x00050085, 0x00000006, 0x00000096, 0x00000094, 0x00000095, 0x00050088, 0x00000006, 0x00000097,
|
||||
0x00000093, 0x00000096, 0x0003003e, 0x00000083, 0x00000097, 0x0004003d, 0x00000062, 0x00000099,
|
||||
0x00000064, 0x0007004f, 0x00000007, 0x0000009a, 0x00000099, 0x00000099, 0x00000000, 0x00000002,
|
||||
0x0004003d, 0x00000006, 0x0000009b, 0x0000007c, 0x00050050, 0x00000007, 0x0000009c, 0x00000087,
|
||||
0x0000009b, 0x00050081, 0x00000007, 0x0000009d, 0x0000009a, 0x0000009c, 0x0003003e, 0x0000009e,
|
||||
0x0000009d, 0x00050039, 0x00000006, 0x0000009f, 0x0000000b, 0x0000009e, 0x0004003d, 0x00000062,
|
||||
0x000000a0, 0x00000064, 0x0007004f, 0x00000007, 0x000000a1, 0x000000a0, 0x000000a0, 0x00000000,
|
||||
0x00000002, 0x0004003d, 0x00000006, 0x000000a2, 0x0000007c, 0x00050050, 0x00000007, 0x000000a3,
|
||||
0x00000087, 0x000000a2, 0x00050083, 0x00000007, 0x000000a4, 0x000000a1, 0x000000a3, 0x0003003e,
|
||||
0x000000a5, 0x000000a4, 0x00050039, 0x00000006, 0x000000a6, 0x0000000b, 0x000000a5, 0x00050083,
|
||||
0x00000006, 0x000000a7, 0x0000009f, 0x000000a6, 0x0004003d, 0x00000006, 0x000000a8, 0x0000007c,
|
||||
0x00050085, 0x00000006, 0x000000a9, 0x00000094, 0x000000a8, 0x00050088, 0x00000006, 0x000000aa,
|
||||
0x000000a7, 0x000000a9, 0x0003003e, 0x00000098, 0x000000aa, 0x0004003d, 0x00000072, 0x000000ad,
|
||||
0x00000074, 0x0003003e, 0x000000ac, 0x000000ad, 0x0004003d, 0x00000006, 0x000000ae, 0x0000007e,
|
||||
0x00050041, 0x0000000d, 0x000000b0, 0x000000ac, 0x000000af, 0x0004003d, 0x00000006, 0x000000b1,
|
||||
0x000000b0, 0x00050081, 0x00000006, 0x000000b2, 0x000000b1, 0x000000ae, 0x00050041, 0x0000000d,
|
||||
0x000000b3, 0x000000ac, 0x000000af, 0x0003003e, 0x000000b3, 0x000000b2, 0x00050041, 0x0000006f,
|
||||
0x000000b5, 0x0000006e, 0x00000022, 0x0004003d, 0x00000065, 0x000000b6, 0x000000b5, 0x0004003d,
|
||||
0x00000072, 0x000000b7, 0x000000ac, 0x00050051, 0x00000006, 0x000000b8, 0x000000b7, 0x00000000,
|
||||
0x00050051, 0x00000006, 0x000000b9, 0x000000b7, 0x00000001, 0x00050051, 0x00000006, 0x000000ba,
|
||||
0x000000b7, 0x00000002, 0x00070050, 0x00000062, 0x000000bb, 0x000000b8, 0x000000b9, 0x000000ba,
|
||||
0x00000076, 0x00050091, 0x00000062, 0x000000bc, 0x000000b6, 0x000000bb, 0x0003003e, 0x000000b4,
|
||||
0x000000bc, 0x00050041, 0x0000006f, 0x000000c1, 0x0000006e, 0x00000016, 0x0004003d, 0x00000065,
|
||||
0x000000c2, 0x000000c1, 0x0004003d, 0x00000072, 0x000000c3, 0x000000ac, 0x00050051, 0x00000006,
|
||||
0x000000c4, 0x000000c3, 0x00000000, 0x00050051, 0x00000006, 0x000000c5, 0x000000c3, 0x00000001,
|
||||
0x00050051, 0x00000006, 0x000000c6, 0x000000c3, 0x00000002, 0x00070050, 0x00000062, 0x000000c7,
|
||||
0x000000c4, 0x000000c5, 0x000000c6, 0x00000076, 0x00050091, 0x00000062, 0x000000c8, 0x000000c2,
|
||||
0x000000c7, 0x00050041, 0x000000c9, 0x000000ca, 0x000000c0, 0x00000016, 0x0003003e, 0x000000ca,
|
||||
0x000000c8, 0x0004003d, 0x00000062, 0x000000cd, 0x000000b4, 0x0008004f, 0x00000072, 0x000000ce,
|
||||
0x000000cd, 0x000000cd, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x000000cc, 0x000000ce,
|
||||
0x00050041, 0x0000006f, 0x000000d1, 0x0000006e, 0x000000d0, 0x0004003d, 0x00000065, 0x000000d2,
|
||||
0x000000d1, 0x0004003d, 0x00000062, 0x000000d3, 0x000000b4, 0x00050091, 0x00000062, 0x000000d4,
|
||||
0x000000d2, 0x000000d3, 0x0008004f, 0x00000072, 0x000000d5, 0x000000d4, 0x000000d4, 0x00000000,
|
||||
0x00000001, 0x00000002, 0x0003003e, 0x000000cf, 0x000000d5, 0x00050041, 0x0000006f, 0x000000d7,
|
||||
0x0000006e, 0x00000022, 0x0004003d, 0x00000065, 0x000000d8, 0x000000d7, 0x00050051, 0x00000062,
|
||||
0x000000da, 0x000000d8, 0x00000000, 0x0008004f, 0x00000072, 0x000000db, 0x000000da, 0x000000da,
|
||||
0x00000000, 0x00000001, 0x00000002, 0x00050051, 0x00000062, 0x000000dc, 0x000000d8, 0x00000001,
|
||||
0x0008004f, 0x00000072, 0x000000dd, 0x000000dc, 0x000000dc, 0x00000000, 0x00000001, 0x00000002,
|
||||
0x00050051, 0x00000062, 0x000000de, 0x000000d8, 0x00000002, 0x0008004f, 0x00000072, 0x000000df,
|
||||
0x000000de, 0x000000de, 0x00000000, 0x00000001, 0x00000002, 0x00060050, 0x000000d9, 0x000000e0,
|
||||
0x000000db, 0x000000dd, 0x000000df, 0x0006000c, 0x000000d9, 0x000000e1, 0x00000001, 0x00000022,
|
||||
0x000000e0, 0x00040054, 0x000000d9, 0x000000e2, 0x000000e1, 0x0004003d, 0x00000006, 0x000000e3,
|
||||
0x00000083, 0x0004007f, 0x00000006, 0x000000e4, 0x000000e3, 0x0004003d, 0x00000006, 0x000000e5,
|
||||
0x00000098, 0x0004007f, 0x00000006, 0x000000e6, 0x000000e5, 0x00060050, 0x00000072, 0x000000e7,
|
||||
0x000000e4, 0x00000076, 0x000000e6, 0x0006000c, 0x00000072, 0x000000e8, 0x00000001, 0x00000045,
|
||||
0x000000e7, 0x00050091, 0x00000072, 0x000000e9, 0x000000e2, 0x000000e8, 0x0006000c, 0x00000072,
|
||||
0x000000ea, 0x00000001, 0x00000045, 0x000000e9, 0x0003003e, 0x000000d6, 0x000000ea, 0x0004003d,
|
||||
0x00000007, 0x000000ef, 0x000000ee, 0x0003003e, 0x000000ec, 0x000000ef, 0x00050041, 0x000000f2,
|
||||
0x000000f3, 0x0000006e, 0x000000f1, 0x0004003d, 0x00000062, 0x000000f4, 0x000000f3, 0x0003003e,
|
||||
0x000000f0, 0x000000f4, 0x00050041, 0x000000f2, 0x000000f7, 0x0000006e, 0x000000f6, 0x0004003d,
|
||||
0x00000062, 0x000000f8, 0x000000f7, 0x0008004f, 0x00000072, 0x000000f9, 0x000000f8, 0x000000f8,
|
||||
0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x000000f5, 0x000000f9, 0x000100fd, 0x00010038,
|
||||
0x00050036, 0x00000006, 0x0000000b, 0x00000000, 0x00000009, 0x00030037, 0x00000008, 0x0000000a,
|
||||
0x000200f8, 0x0000000c, 0x0004003b, 0x0000000d, 0x0000000e, 0x00000007, 0x0004003b, 0x0000000d,
|
||||
0x0000001f, 0x00000007, 0x0004003b, 0x0000000d, 0x00000026, 0x00000007, 0x0004003b, 0x0000000d,
|
||||
0x00000033, 0x00000007, 0x0004003b, 0x0000000d, 0x00000042, 0x00000007, 0x00060041, 0x00000018,
|
||||
0x00000019, 0x00000014, 0x00000016, 0x00000017, 0x0004003d, 0x00000006, 0x0000001a, 0x00000019,
|
||||
0x0007000c, 0x00000006, 0x0000001c, 0x00000001, 0x00000028, 0x0000001a, 0x0000001b, 0x00050085,
|
||||
0x00000006, 0x0000001e, 0x0000001c, 0x0000001d, 0x0003003e, 0x0000000e, 0x0000001e, 0x00060041,
|
||||
0x00000018, 0x00000020, 0x00000014, 0x00000016, 0x00000016, 0x0004003d, 0x00000006, 0x00000021,
|
||||
0x00000020, 0x00060041, 0x00000018, 0x00000023, 0x00000014, 0x00000016, 0x00000022, 0x0004003d,
|
||||
0x00000006, 0x00000024, 0x00000023, 0x00050085, 0x00000006, 0x00000025, 0x00000021, 0x00000024,
|
||||
0x0003003e, 0x0000001f, 0x00000025, 0x0004003d, 0x00000007, 0x00000027, 0x0000000a, 0x00050094,
|
||||
0x00000006, 0x0000002b, 0x00000027, 0x0000002a, 0x0004003d, 0x00000006, 0x0000002c, 0x0000000e,
|
||||
0x00050085, 0x00000006, 0x0000002d, 0x0000002b, 0x0000002c, 0x0004003d, 0x00000006, 0x0000002e,
|
||||
0x0000001f, 0x00050085, 0x00000006, 0x00000030, 0x0000002e, 0x0000002f, 0x00050081, 0x00000006,
|
||||
0x00000031, 0x0000002d, 0x00000030, 0x0006000c, 0x00000006, 0x00000032, 0x00000001, 0x0000000d,
|
||||
0x00000031, 0x0003003e, 0x00000026, 0x00000032, 0x0004003d, 0x00000007, 0x00000034, 0x0000000a,
|
||||
0x00050094, 0x00000006, 0x00000038, 0x00000034, 0x00000037, 0x0004003d, 0x00000006, 0x00000039,
|
||||
0x0000000e, 0x00050085, 0x00000006, 0x0000003a, 0x00000038, 0x00000039, 0x00050085, 0x00000006,
|
||||
0x0000003c, 0x0000003a, 0x0000003b, 0x0004003d, 0x00000006, 0x0000003d, 0x0000001f, 0x00050085,
|
||||
0x00000006, 0x0000003f, 0x0000003d, 0x0000003e, 0x00050083, 0x00000006, 0x00000040, 0x0000003c,
|
||||
0x0000003f, 0x0006000c, 0x00000006, 0x00000041, 0x00000001, 0x0000000d, 0x00000040, 0x0003003e,
|
||||
0x00000033, 0x00000041, 0x0004003d, 0x00000007, 0x00000043, 0x0000000a, 0x00050094, 0x00000006,
|
||||
0x00000047, 0x00000043, 0x00000046, 0x0004003d, 0x00000006, 0x00000048, 0x0000000e, 0x00050085,
|
||||
0x00000006, 0x00000049, 0x00000047, 0x00000048, 0x00050085, 0x00000006, 0x0000004b, 0x00000049,
|
||||
0x0000004a, 0x0004003d, 0x00000006, 0x0000004c, 0x0000001f, 0x00050085, 0x00000006, 0x0000004e,
|
||||
0x0000004c, 0x0000004d, 0x00050081, 0x00000006, 0x0000004f, 0x0000004b, 0x0000004e, 0x0006000c,
|
||||
0x00000006, 0x00000050, 0x00000001, 0x0000000d, 0x0000004f, 0x0003003e, 0x00000042, 0x00000050,
|
||||
0x00060041, 0x00000018, 0x00000052, 0x00000014, 0x00000016, 0x00000051, 0x0004003d, 0x00000006,
|
||||
0x00000053, 0x00000052, 0x0004003d, 0x00000006, 0x00000054, 0x00000026, 0x00050085, 0x00000006,
|
||||
0x00000056, 0x00000054, 0x00000055, 0x0004003d, 0x00000006, 0x00000057, 0x00000033, 0x00050085,
|
||||
0x00000006, 0x00000059, 0x00000057, 0x00000058, 0x00050081, 0x00000006, 0x0000005a, 0x00000056,
|
||||
0x00000059, 0x0004003d, 0x00000006, 0x0000005b, 0x00000042, 0x00050085, 0x00000006, 0x0000005d,
|
||||
0x0000005b, 0x0000005c, 0x00050081, 0x00000006, 0x0000005e, 0x0000005a, 0x0000005d, 0x00050085,
|
||||
0x00000006, 0x0000005f, 0x00000053, 0x0000005e, 0x000200fe, 0x0000005f, 0x00010038,
|
||||
};
|
||||
static const size_t water_vert_spv_count = 1823;
|
||||
@@ -5768,7 +5768,7 @@ wgpu::RenderPipeline buildPipelineFromWgsl(wgpu::Device &dev, wgpu::PipelineLayo
|
||||
WGPUTextureFormat format, const std::string &vert,
|
||||
const std::string &frag, bool depth, bool blend,
|
||||
bool mesh3d, bool hair, bool shadow, bool gbuffer,
|
||||
uint32_t sampleCount) {
|
||||
uint32_t sampleCount, bool depthWrite = true) {
|
||||
WGPURenderPipelineDescriptor pd{};
|
||||
pd.label = sv("eve_custom_shader");
|
||||
pd.layout = layout.Get();
|
||||
@@ -5857,7 +5857,7 @@ wgpu::RenderPipeline buildPipelineFromWgsl(wgpu::Device &dev, wgpu::PipelineLayo
|
||||
if (depth && !shadow) {
|
||||
static WGPUDepthStencilState ds{};
|
||||
ds.format = WGPUTextureFormat_Depth32Float;
|
||||
ds.depthWriteEnabled = WGPUOptionalBool_True;
|
||||
ds.depthWriteEnabled = depthWrite ? WGPUOptionalBool_True : WGPUOptionalBool_False;
|
||||
ds.depthCompare = WGPUCompareFunction_Less;
|
||||
pd.depthStencil = &ds;
|
||||
}
|
||||
@@ -5958,6 +5958,24 @@ Shader *Graphics::newMeshShaderFromWgsl(const std::string &vertWgsl, const std::
|
||||
return raw;
|
||||
}
|
||||
|
||||
eve::Result<void> Graphics::configureMeshShaderSurface(Shader& shader, BlendMode blend,
|
||||
bool depthWrite, bool doubleSided) {
|
||||
auto found = std::find_if(ownedGpuShaders.begin(), ownedGpuShaders.end(),
|
||||
[&](const auto& gpu) { return gpu.get() == shader.gpuHandle; });
|
||||
if (found == ownedGpuShaders.end() || !(*found)->isMesh3D)
|
||||
return eve::Result<void>::failure(eve::Diagnostic::error(
|
||||
eve::DiagnosticCode::InvalidArgument, "Expected an owned WebGPU mesh shader"));
|
||||
auto& gpu = **found;
|
||||
const bool alphaBlend = blend == BlendMode::Alpha;
|
||||
gpu.mesh3dPipeline = buildPipelineFromWgsl(
|
||||
device, mesh3dPipelineLayout, sceneColorFormat, gpu.wgslVert, gpu.wgslFrag,
|
||||
true, alphaBlend, true, doubleSided, false, false, sceneColorSamples, depthWrite);
|
||||
shader.meshBlend = blend;
|
||||
shader.meshDepthWrite = depthWrite;
|
||||
shader.meshDoubleSided = doubleSided;
|
||||
return eve::Result<void>::success();
|
||||
}
|
||||
|
||||
Shader *Graphics::newMeshShader(const std::string &vertGlsl, const std::string &fragGlsl) {
|
||||
(void)vertGlsl;
|
||||
(void)fragGlsl;
|
||||
|
||||
@@ -325,7 +325,9 @@ public:
|
||||
Shader *newMeshShaderFromSpv(const std::vector<uint32_t> &vertSpv,
|
||||
const std::vector<uint32_t> &fragSpv) override;
|
||||
Shader *newMeshShaderFromWgsl(const std::string &vertWgsl,
|
||||
const std::string &fragWgsl) override;
|
||||
const std::string &fragWgsl) override;
|
||||
eve::Result<void> configureMeshShaderSurface(Shader& shader, BlendMode blend,
|
||||
bool depthWrite, bool doubleSided) override;
|
||||
Shader *newMeshShader(const std::string &vertGlsl, const std::string &fragGlsl) override;
|
||||
Shader *newHairShaderFromSpv(const std::vector<uint32_t> &vertSpv,
|
||||
const std::vector<uint32_t> &fragSpv) override;
|
||||
|
||||
+94
-97
@@ -20,6 +20,7 @@
|
||||
#include "procgen/heightmap/TerrainStreaming.h"
|
||||
#include "procgen/algorithms/CastleMesh.h"
|
||||
#include "procgen/texture/TextureRecipe.h"
|
||||
#include "water_scene_fixture.h"
|
||||
#include "procgen/texture/PbrMaterial.h"
|
||||
#include "procgen/texture/NoiseField.h"
|
||||
#include "procgen/texture/ColorRamp.h"
|
||||
@@ -3996,7 +3997,7 @@ TEST_CASE("graphics.water.render.plane") {
|
||||
[[maybe_unused]] auto *const imageModule = eve::image::Image::create();
|
||||
|
||||
// Gradient sky cubemap: deep blue at the zenith, pale near the horizon.
|
||||
const int fs = 16;
|
||||
const int fs = 64;
|
||||
std::vector<uint8_t> sky(size_t(fs * fs * 4 * 6));
|
||||
{
|
||||
auto dirFor = [&](int f, int x, int y, float &dx, float &dy, float &dz) {
|
||||
@@ -4021,10 +4022,21 @@ TEST_CASE("graphics.water.render.plane") {
|
||||
const float ny = dy / len;
|
||||
// t=1 at zenith, t=0 near/below horizon.
|
||||
const float t = std::pow(std::clamp(ny * 0.5f + 0.5f, 0.f, 1.f), 1.5f);
|
||||
// Pale blue horizon 鈫?deep blue zenith.
|
||||
const float cr = 0.72f + (0.12f - 0.72f) * t;
|
||||
const float cg = 0.80f + (0.32f - 0.80f) * t;
|
||||
const float cb = 0.90f + (0.72f - 0.90f) * t;
|
||||
// Pale blue horizon → deep blue zenith.
|
||||
float cr = 0.72f + (0.12f - 0.72f) * t;
|
||||
float cg = 0.80f + (0.32f - 0.80f) * t;
|
||||
float cb = 0.90f + (0.72f - 0.90f) * t;
|
||||
const float nx = dx / len;
|
||||
const float nz = dz / len;
|
||||
const float cloudNoise = std::sin(nx * 8.0f + nz * 3.1f) +
|
||||
std::sin(nz * 11.3f - nx * 2.7f) * 0.55f +
|
||||
std::sin((nx + nz) * 17.0f) * 0.22f;
|
||||
const float cloudAltitude = std::clamp((ny - 0.03f) * 4.0f, 0.0f, 1.0f) *
|
||||
std::clamp((0.82f - ny) * 3.2f, 0.0f, 1.0f);
|
||||
const float cloud = std::clamp((cloudNoise - 0.42f) * 2.6f, 0.0f, 1.0f) * cloudAltitude;
|
||||
cr = cr + (0.96f - cr) * cloud;
|
||||
cg = cg + (0.97f - cg) * cloud;
|
||||
cb = cb + (1.00f - cb) * cloud;
|
||||
const size_t o = (size_t(f) * fs * fs + size_t(y) * fs + size_t(x)) * 4u;
|
||||
sky[o + 0] = uint8_t(cr * 255.f);
|
||||
sky[o + 1] = uint8_t(cg * 255.f);
|
||||
@@ -4037,30 +4049,6 @@ TEST_CASE("graphics.water.render.plane") {
|
||||
Texture *skyTex = gfx->newCubemap(fs, sky.data());
|
||||
REQUIRE(skyTex != nullptr);
|
||||
|
||||
// Skybox: a huge sphere centered on the camera, shaded purely by the env
|
||||
// cubemap so the sky is visible behind the water.
|
||||
const char *kSkyFrag = R"GLSL(#version 450
|
||||
layout(location = 3) in vec3 vWorldPos;
|
||||
layout(location = 4) in vec3 vCameraPos;
|
||||
layout(set = 0, binding = 3) uniform samplerCube env;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
void main() {
|
||||
vec3 c = texture(env, normalize(vWorldPos - vCameraPos)).rgb;
|
||||
outColor = vec4(c, 1.0);
|
||||
}
|
||||
)GLSL";
|
||||
Shader *skyShader = gfx->newMeshShader("", kSkyFrag);
|
||||
REQUIRE(skyShader != nullptr);
|
||||
Mesh *skyMesh = gfx->newMeshSphere(24, 16);
|
||||
REQUIRE(skyMesh != nullptr);
|
||||
auto *skyEnt = Renderable3D::create();
|
||||
skyEnt->setMesh(skyMesh);
|
||||
skyEnt->setShader(skyShader);
|
||||
skyEnt->setTexture(nullptr);
|
||||
skyEnt->setScale(200.f, 200.f, 200.f);
|
||||
skyEnt->setReceiveShadow(false);
|
||||
skyEnt->setCastShadow(false);
|
||||
|
||||
auto *camera = Camera3D::createCamera();
|
||||
camera->setEye(6.f, 5.f, 8.f);
|
||||
camera->setTarget(0.f, 0.f, 0.f);
|
||||
@@ -4069,11 +4057,10 @@ void main() {
|
||||
camera->setEnvMap(skyTex);
|
||||
camera->setEnvIntensity(1.f);
|
||||
camera->data()->nearZ = 0.1f;
|
||||
camera->data()->farZ = 2000.f;
|
||||
skyEnt->setCamera(camera);
|
||||
|
||||
gfx->setBackgroundColor(Color(0.05f, 0.09f, 0.14f, 1.f));
|
||||
camera->data()->farZ = 100.f;
|
||||
gfx->setBackgroundColor(Color(0.36f, 0.55f, 0.72f, 1.f));
|
||||
gfx->setScreenReadbackEnabled(true);
|
||||
gfx->getRenderControl()->enable("gbuffer");
|
||||
RenderSystem3D::setDirectionalLight(0.45f, 1.f, 0.3f, 1.4f, 1.3f, 1.2f);
|
||||
|
||||
auto *present = Renderable2D::create();
|
||||
@@ -4083,35 +4070,58 @@ void main() {
|
||||
present->sprite()->height = 1.f;
|
||||
present->sprite()->a = 0.f;
|
||||
|
||||
// A single flat water plane carrying the water shader.
|
||||
WaterSceneFixture sceneFixture = createStylizedWaterScene(gfx, camera);
|
||||
|
||||
// The water is drawn explicitly after the opaque GBuffer so it can sample
|
||||
// scene color and linear depth without a read/write feedback hazard.
|
||||
Water *water = gfx->newWater();
|
||||
REQUIRE(water != nullptr);
|
||||
water->createPlane(14.f, 14.f, 64, 64);
|
||||
water->setWaterColor(0.06f, 0.30f, 0.48f);
|
||||
water->setWaveAmplitude(0.30f);
|
||||
water->setRippleAmplitude(0.55f);
|
||||
water->setRippleCount(8);
|
||||
water->setRippleInterval(1.4f);
|
||||
water->setWaveScale(14.f);
|
||||
water->setReflectionTint(0.9f, 0.95f, 1.0f);
|
||||
water->setReflectionIntensity(1.3f);
|
||||
water->setSunIntensity(1.6f);
|
||||
|
||||
auto *waterEnt = Renderable3D::create();
|
||||
waterEnt->setMesh(water->getMesh());
|
||||
waterEnt->setShader(water->getShader());
|
||||
waterEnt->setTexture(nullptr);
|
||||
waterEnt->setReceiveShadow(false);
|
||||
waterEnt->setCastShadow(false);
|
||||
waterEnt->setCamera(camera);
|
||||
WaterStyleConfig style = water->config();
|
||||
style.deepColor = {0.005f, 0.16f, 0.42f};
|
||||
style.shallowColor = {0.025f, 0.50f, 0.62f};
|
||||
style.waveAmplitude = 0.11f;
|
||||
style.waveSharpness = 1.65f;
|
||||
style.rippleAmplitude = 0.12f;
|
||||
style.rippleCount = 5;
|
||||
style.rippleInterval = 1.4f;
|
||||
style.waveScale = 1.35f;
|
||||
style.foamWidth = 0.58f;
|
||||
style.foamSoftness = 0.12f;
|
||||
style.foamStrength = 0.72f;
|
||||
style.reflectionIntensity = 0.82f;
|
||||
style.fresnelPower = 2.2f;
|
||||
style.sunIntensity = 0.65f;
|
||||
style.opacity = 0.68f;
|
||||
style.refractionStrength = 0.045f;
|
||||
style.causticsStrength = 0.55f;
|
||||
style.causticsScale = 3.8f;
|
||||
if (const char* layerMode = std::getenv("EVENGINE_WATER_LAYER_MODE")) {
|
||||
if (std::strcmp(layerMode, "transmission") == 0) {
|
||||
style.reflectionIntensity = 0.0f;
|
||||
style.sunIntensity = 0.0f;
|
||||
style.foamStrength = 0.12f;
|
||||
style.opacity = 0.52f;
|
||||
} else if (std::strcmp(layerMode, "reflection") == 0) {
|
||||
style.reflectionIntensity = 1.15f;
|
||||
style.sunIntensity = 0.75f;
|
||||
style.refractionStrength = 0.0f;
|
||||
style.causticsStrength = 0.0f;
|
||||
style.foamStrength = 0.08f;
|
||||
style.opacity = 0.82f;
|
||||
}
|
||||
}
|
||||
auto styleJson = style.toJson();
|
||||
REQUIRE(static_cast<bool>(styleJson));
|
||||
REQUIRE(static_cast<bool>(water->applyConfigJson(styleJson.value())));
|
||||
|
||||
// Animate a few seconds so ripples travel, then save a frame.
|
||||
for (int frame = 0; frame < 40; ++frame) {
|
||||
// Keep the skybox centered on the camera so it reads as a surrounding sky.
|
||||
skyEnt->setPosition(camera->data()->eyeX, camera->data()->eyeY, camera->data()->eyeZ);
|
||||
water->setTime(float(frame) * 0.06f);
|
||||
water->bindParams();
|
||||
const float time = float(frame) * 0.06f;
|
||||
water->setTime(time);
|
||||
sceneFixture.update(time);
|
||||
RenderSystem3D::render(*gfx);
|
||||
water->draw();
|
||||
RenderSystem::render(*gfx);
|
||||
}
|
||||
std::unique_ptr<eve::image::ImageData> image(gfx->newImageData());
|
||||
@@ -4384,23 +4394,6 @@ TEST_CASE("graphics.water.render.planar") {
|
||||
present->sprite()->height = 1.f;
|
||||
present->sprite()->a = 0.f;
|
||||
|
||||
// Skybox: big sphere shaded by the env cubemap.
|
||||
const char *kSky = R"GLSL(#version 450
|
||||
layout(location = 3) in vec3 vWorldPos;
|
||||
layout(location = 4) in vec3 vCameraPos;
|
||||
layout(set = 0, binding = 3) uniform samplerCube env;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
void main() { outColor = vec4(texture(env, normalize(vWorldPos - vCameraPos)).rgb, 1.0); }
|
||||
)GLSL";
|
||||
Shader *skyShader = gfx->newMeshShader("", kSky);
|
||||
auto *skyEnt = Renderable3D::create();
|
||||
skyEnt->setMesh(gfx->newMeshSphere(24, 16));
|
||||
skyEnt->setShader(skyShader);
|
||||
skyEnt->setScale(300.f, 300.f, 300.f);
|
||||
skyEnt->setReceiveShadow(false);
|
||||
skyEnt->setCastShadow(false);
|
||||
skyEnt->setCamera(camera);
|
||||
|
||||
// Boxes above the water to reflect.
|
||||
struct Box { float x, y, z, sx, sy, sz; uint8_t r, g, b; };
|
||||
const Box boxes[] = {
|
||||
@@ -4409,22 +4402,15 @@ void main() { outColor = vec4(texture(env, normalize(vWorldPos - vCameraPos)).rg
|
||||
{3.5f, 3.5f, -4.f, 2.5f, 2.5f, 2.5f, 70, 110, 230},
|
||||
};
|
||||
Mesh *cube = makeUnitCube(gfx);
|
||||
struct Ent { Renderable3D *e; glm::mat4 model; };
|
||||
struct Ent { Texture* texture; glm::mat4 model; };
|
||||
std::vector<Ent> ents;
|
||||
for (const Box &b : boxes) {
|
||||
auto *ent = Renderable3D::create();
|
||||
ent->setMesh(cube);
|
||||
const uint8_t px[4] = {b.r, b.g, b.b, 255};
|
||||
ent->setTexture(gfx->newTexture(1, 1, px));
|
||||
ent->setPosition(b.x, b.y, b.z);
|
||||
ent->setScale(b.sx, b.sy, b.sz);
|
||||
ent->setReceiveShadow(false);
|
||||
ent->setCastShadow(false);
|
||||
ent->setCamera(camera);
|
||||
Texture* texture = gfx->newTexture(1, 1, px);
|
||||
glm::mat4 m(1.f);
|
||||
m = glm::translate(m, glm::vec3(b.x, b.y, b.z));
|
||||
m = glm::scale(m, glm::vec3(b.sx, b.sy, b.sz));
|
||||
ents.push_back({ent, m});
|
||||
ents.push_back({texture, m});
|
||||
}
|
||||
|
||||
Water *water = gfx->newWater();
|
||||
@@ -4435,14 +4421,6 @@ void main() { outColor = vec4(texture(env, normalize(vWorldPos - vCameraPos)).rg
|
||||
water->setScreenSpaceReflection(true, 0.9f);
|
||||
water->setViewport(float(settings.width), float(settings.height));
|
||||
|
||||
auto *waterEnt = Renderable3D::create();
|
||||
waterEnt->setMesh(water->getMesh());
|
||||
waterEnt->setShader(water->getShader());
|
||||
waterEnt->setTexture(nullptr);
|
||||
waterEnt->setReceiveShadow(false);
|
||||
waterEnt->setCastShadow(false);
|
||||
waterEnt->setCamera(camera);
|
||||
|
||||
Canvas *refl = gfx->newCanvas(settings.width, settings.height);
|
||||
REQUIRE(refl != nullptr);
|
||||
|
||||
@@ -4462,23 +4440,30 @@ void main() { outColor = vec4(texture(env, normalize(vWorldPos - vCameraPos)).rg
|
||||
gfx->setBackgroundColor(Color(0.45f, 0.62f, 0.85f, 1.f));
|
||||
gfx->begin3DFrameToCanvas(refl);
|
||||
for (const Ent &en : ents) {
|
||||
gfx->drawMeshShader(cube, en.model,
|
||||
static_cast<Renderable3D *>(en.e)->meshRenderer()->texture,
|
||||
glm::vec4(1.f), nullptr);
|
||||
gfx->drawMeshShader(cube, en.model, en.texture, glm::vec4(1.f), nullptr);
|
||||
}
|
||||
gfx->end3DFrameToCanvas();
|
||||
gfx->setBackgroundColor(Color(0.05f, 0.09f, 0.14f, 1.f));
|
||||
|
||||
// Final frame: water sampling the planar reflection.
|
||||
waterEnt->setHeightTexture(refl->getTexture());
|
||||
// Final frame: the public Water path borrows and samples the planar reflection.
|
||||
const glm::mat4 mainView = glm::lookAtRH(eye, tgt, glm::vec3(0.f, 1.f, 0.f));
|
||||
const glm::mat4 mainProj = perspectiveVulkanRH_ZO(glm::radians(camera->data()->fovYDeg),
|
||||
settings.width / float(settings.height),
|
||||
camera->data()->nearZ, camera->data()->farZ);
|
||||
gfx->setMesh3DViewProj(mainProj * mainView);
|
||||
gfx->setMesh3DView(mainView);
|
||||
gfx->setMesh3DCameraPos(eye);
|
||||
gfx->setMesh3DClip(camera->data()->nearZ, camera->data()->farZ);
|
||||
gfx->setMesh3DEnv(skyTex, 1.f);
|
||||
water->setTime(0.5f);
|
||||
water->bindParams();
|
||||
RenderSystem3D::render(*gfx);
|
||||
water->drawWithPlanarReflection(refl->getTexture(), 0.9F);
|
||||
RenderSystem::render(*gfx);
|
||||
|
||||
// Read the reflection canvas once and check the red box was captured.
|
||||
std::unique_ptr<eve::image::ImageData> rim(refl->newImageData());
|
||||
REQUIRE(rim.get() != nullptr);
|
||||
REQUIRE(saveImagePng(*rim, std::string(outPath) + ".reflection.png"));
|
||||
int red = 0, total = 0;
|
||||
const uint8_t *pd = static_cast<const uint8_t *>(rim->getData());
|
||||
const int rw = rim->getWidth();
|
||||
@@ -4497,6 +4482,18 @@ void main() { outColor = vec4(texture(env, normalize(vWorldPos - vCameraPos)).rg
|
||||
|
||||
std::unique_ptr<eve::image::ImageData> img(gfx->newImageData());
|
||||
REQUIRE(img.get() != nullptr);
|
||||
int reflectedRed = 0;
|
||||
const auto* finalPixels = static_cast<const uint8_t*>(img->getData());
|
||||
for (int y = 0; y < img->getHeight(); ++y) {
|
||||
for (int x = 0; x < img->getWidth(); ++x) {
|
||||
const size_t offset = (size_t(y) * size_t(img->getWidth()) + size_t(x)) * 4;
|
||||
const int r = finalPixels[offset];
|
||||
const int g = finalPixels[offset + 1];
|
||||
const int b = finalPixels[offset + 2];
|
||||
if (r > 120 && r > g + 20 && r > b + 20) ++reflectedRed;
|
||||
}
|
||||
}
|
||||
CHECK(reflectedRed > 1000);
|
||||
REQUIRE(saveImagePng(*img, outPath));
|
||||
std::printf("planar water render saved: %s\n", outPath);
|
||||
win->close();
|
||||
@@ -4591,4 +4588,4 @@ TEST_CASE("procgen.terrain.file.decodeEvtrAndEvtrn") {
|
||||
CHECK_EQ(int(parseTerrainFileFormat("evtr")), int(TerrainFileFormat::Evtr));
|
||||
CHECK_EQ(int(parseTerrainFileFormat("")), int(TerrainFileFormat::Auto));
|
||||
CHECK_EQ(int(parseTerrainFileFormat("nonsense")), int(TerrainFileFormat::Auto));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
#include "water_scene_fixture.h"
|
||||
|
||||
#include "graphics/Graphics.h"
|
||||
#include "graphics/Mesh.h"
|
||||
#include "graphics/RenderSystem3D.h"
|
||||
#include "graphics/Texture.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
using eve::graphics::Camera3D;
|
||||
using eve::graphics::Graphics;
|
||||
using eve::graphics::Mesh;
|
||||
using eve::graphics::Renderable3D;
|
||||
using eve::graphics::Texture;
|
||||
|
||||
namespace {
|
||||
|
||||
Texture* solidTexture(Graphics* gfx, uint8_t r, uint8_t g, uint8_t b) {
|
||||
const uint8_t pixel[4] = {r, g, b, 255};
|
||||
return gfx->newTexture(1, 1, pixel);
|
||||
}
|
||||
|
||||
Renderable3D* prop(Mesh* mesh, Texture* texture, Camera3D* camera, float x, float y, float z, float sx, float sy,
|
||||
float sz) {
|
||||
auto* value = Renderable3D::create();
|
||||
value->setMesh(mesh);
|
||||
value->setTexture(texture);
|
||||
value->setPosition(x, y, z);
|
||||
value->setScale(sx, sy, sz);
|
||||
value->setCamera(camera);
|
||||
value->setCastShadow(false);
|
||||
value->setMetallic(0.0F);
|
||||
value->setRoughness(0.88F);
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WaterSceneFixture createStylizedWaterScene(Graphics* gfx, Camera3D* camera) {
|
||||
WaterSceneFixture result;
|
||||
Mesh* cube = gfx->newMeshCube(1.0F);
|
||||
Mesh* rock = gfx->newMeshSphere(12, 7);
|
||||
Mesh* cylinder = gfx->newMeshCylinder(10, 1, true);
|
||||
|
||||
Texture* sand = solidTexture(gfx, 112, 76, 37);
|
||||
Texture* wetSand = solidTexture(gfx, 62, 47, 28);
|
||||
Texture* grass = solidTexture(gfx, 28, 61, 27);
|
||||
Texture* stone = solidTexture(gfx, 48, 57, 55);
|
||||
Texture* darkStone = solidTexture(gfx, 25, 38, 41);
|
||||
Texture* wood = solidTexture(gfx, 58, 31, 16);
|
||||
Texture* woodTop = solidTexture(gfx, 96, 57, 27);
|
||||
Texture* reed = solidTexture(gfx, 42, 83, 30);
|
||||
Texture* reedTip = solidTexture(gfx, 73, 39, 20);
|
||||
Texture* cloud = solidTexture(gfx, 208, 224, 232);
|
||||
|
||||
// Low-poly cloud banks echo the cloud shapes baked into the reflection cubemap.
|
||||
const float cloudPuffs[][6] = {
|
||||
{-5.8F, 2.8F, -11.5F, 2.2F, 0.72F, 0.85F}, {-4.1F, 3.1F, -11.8F, 1.7F, 0.95F, 0.92F},
|
||||
{-2.5F, 2.75F, -11.3F, 2.0F, 0.68F, 0.82F}, {3.5F, 3.4F, -13.0F, 2.5F, 0.82F, 1.0F},
|
||||
{5.5F, 3.65F, -13.4F, 1.8F, 1.05F, 0.9F}, {7.0F, 3.3F, -12.8F, 2.1F, 0.70F, 0.85F},
|
||||
};
|
||||
for (const auto& puff : cloudPuffs)
|
||||
prop(rock, cloud, camera, puff[0], puff[1], puff[2], puff[3], puff[4], puff[5]);
|
||||
|
||||
// Layered floor and banks form a readable shallow-to-deep silhouette.
|
||||
prop(cube, sand, camera, 0.0F, -1.75F, 0.0F, 15.0F, 0.5F, 15.0F);
|
||||
prop(cube, wetSand, camera, -5.35F, -0.68F, -0.8F, 4.1F, 1.45F, 14.0F);
|
||||
prop(cube, grass, camera, -6.25F, 0.04F, -0.8F, 2.5F, 0.20F, 14.0F);
|
||||
prop(cube, wetSand, camera, 3.7F, -1.05F, -5.7F, 7.0F, 1.1F, 2.6F);
|
||||
|
||||
// Overlapping earth and stones soften the otherwise rectangular bank edge.
|
||||
const float bankLobes[][6] = {
|
||||
{-3.72F, -0.40F, 4.7F, 1.85F, 0.52F, 1.75F}, {-3.36F, -0.43F, 3.0F, 1.25F, 0.46F, 1.40F},
|
||||
{-3.68F, -0.38F, 1.0F, 1.70F, 0.52F, 1.55F}, {-3.30F, -0.46F, -0.8F, 1.18F, 0.42F, 1.30F},
|
||||
{-3.70F, -0.40F, -2.7F, 1.62F, 0.50F, 1.60F}, {-3.42F, -0.45F, -4.6F, 1.35F, 0.44F, 1.50F},
|
||||
};
|
||||
for (const auto& lobe : bankLobes)
|
||||
prop(rock, wetSand, camera, lobe[0], lobe[1], lobe[2], lobe[3], lobe[4], lobe[5]);
|
||||
|
||||
const float exposedRocks[][7] = {
|
||||
{-4.15F, 0.00F, 3.1F, 0.72F, 0.52F, 0.66F, 17.0F},
|
||||
{-3.55F, -0.15F, 1.1F, 0.46F, 0.32F, 0.56F, -11.0F},
|
||||
{-4.18F, 0.04F, -2.0F, 0.88F, 0.62F, 0.70F, 31.0F},
|
||||
{3.0F, -0.08F, -5.0F, 0.62F, 0.44F, 0.52F, -23.0F},
|
||||
};
|
||||
for (const auto& r : exposedRocks) {
|
||||
auto* value = prop(rock, stone, camera, r[0], r[1], r[2], r[3], r[4], r[5]);
|
||||
value->setRotation(r[6], r[6] * 0.37F, 0.0F);
|
||||
}
|
||||
|
||||
const float waterlinePebbles[][6] = {
|
||||
{-3.05F, -0.34F, 3.0F, 0.42F, 0.28F, 0.50F}, {-2.92F, -0.42F, 2.35F, 0.28F, 0.20F, 0.34F},
|
||||
{-3.15F, -0.38F, 0.05F, 0.36F, 0.24F, 0.42F}, {-2.88F, -0.48F, -1.8F, 0.52F, 0.30F, 0.38F},
|
||||
{-3.08F, -0.40F, -2.55F, 0.30F, 0.22F, 0.35F},
|
||||
};
|
||||
for (const auto& pebble : waterlinePebbles)
|
||||
prop(rock, stone, camera, pebble[0], pebble[1], pebble[2], pebble[3], pebble[4], pebble[5]);
|
||||
|
||||
const float submergedRocks[][6] = {
|
||||
{-1.7F, -0.28F, 1.7F, 0.70F, 0.30F, 0.56F}, {0.0F, -0.48F, 2.8F, 0.48F, 0.28F, 0.65F},
|
||||
{2.1F, -0.36F, 1.0F, 0.82F, 0.32F, 0.70F}, {3.2F, -1.22F, 3.5F, 0.55F, 0.38F, 0.46F},
|
||||
{-0.2F, -0.58F, -2.5F, 0.62F, 0.34F, 0.58F},
|
||||
};
|
||||
for (const auto& r : submergedRocks)
|
||||
prop(rock, darkStone, camera, r[0], r[1], r[2], r[3], r[4], r[5]);
|
||||
|
||||
// A broken jetty gives the posts a readable purpose and a strong diagonal.
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
const float x = -2.75F + static_cast<float>(i) * 0.62F;
|
||||
auto* plank = prop(cube, i % 3 == 0 ? woodTop : wood, camera, x, 0.18F + 0.025F * float(i % 2), -2.55F,
|
||||
0.54F, 0.13F, 1.25F);
|
||||
plank->setRotation(0.0F, float((i % 3) - 1) * 2.2F, float((i % 2) ? 1 : -1));
|
||||
}
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const float x = -2.55F + static_cast<float>(i) * 1.18F;
|
||||
const float z = -2.55F + (i % 2 ? 0.72F : -0.72F);
|
||||
auto* post = prop(cylinder, wood, camera, x, -0.12F, z, 0.14F, 0.86F + 0.08F * float(i % 2), 0.14F);
|
||||
post->setRotation(float(i - 2) * 1.8F, 0.0F, float(i % 2 ? 2 : -2));
|
||||
}
|
||||
|
||||
// Reeds grow in small irregular clumps along the wet bank.
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
const float cluster = static_cast<float>(i / 6);
|
||||
const float strand = static_cast<float>(i % 6);
|
||||
const float x = -3.48F - 0.12F * strand + 0.09F * std::sin(float(i) * 2.3F);
|
||||
const float z = -4.55F + cluster * 2.02F + 0.28F * std::sin(float(i) * 1.7F);
|
||||
const float h = 0.78F + 0.24F * std::sin(float(i) * 2.1F);
|
||||
auto* stem = prop(cylinder, reed, camera, x, h * 0.5F - 0.04F, z, 0.032F, h, 0.032F);
|
||||
stem->setRotation(float((i % 5) - 2) * 2.5F, 0.0F, float((i % 3) - 1) * 2.0F);
|
||||
prop(cylinder, reedTip, camera, x, h + 0.05F, z, 0.055F, 0.18F, 0.055F);
|
||||
}
|
||||
|
||||
auto* log = prop(cylinder, wood, camera, 0.55F, 0.05F, 1.15F, 0.17F, 0.92F, 0.17F);
|
||||
log->setRotation(2.0F, 0.0F, 88.0F);
|
||||
result.floaters.push_back({log, 0.55F, 0.05F, 1.15F, 0.2F, 88.0F});
|
||||
|
||||
auto* plank = prop(cube, woodTop, camera, 1.75F, 0.07F, 0.15F, 1.05F, 0.10F, 0.34F);
|
||||
plank->setRotation(-13.0F, 0.0F, 2.0F);
|
||||
result.floaters.push_back({plank, 1.75F, 0.07F, 0.15F, 1.6F, 2.0F});
|
||||
return result;
|
||||
}
|
||||
|
||||
void WaterSceneFixture::update(float time) {
|
||||
for (auto& floater : floaters) {
|
||||
const float bob = std::sin(time * 1.35F + floater.phase);
|
||||
floater.renderable->setPosition(floater.x, floater.y + bob * 0.045F, floater.z);
|
||||
floater.renderable->setRotation(std::sin(time * 0.42F + floater.phase) * 4.0F, 0.0F,
|
||||
floater.roll + bob * 2.2F);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace eve::graphics {
|
||||
class Camera3D;
|
||||
class Graphics;
|
||||
class Renderable3D;
|
||||
}
|
||||
|
||||
struct WaterSceneFloater {
|
||||
eve::graphics::Renderable3D* renderable = nullptr;
|
||||
float x = 0.0F;
|
||||
float y = 0.0F;
|
||||
float z = 0.0F;
|
||||
float phase = 0.0F;
|
||||
float roll = 0.0F;
|
||||
};
|
||||
|
||||
struct WaterSceneFixture {
|
||||
std::vector<WaterSceneFloater> floaters;
|
||||
|
||||
void update(float time);
|
||||
};
|
||||
|
||||
WaterSceneFixture createStylizedWaterScene(eve::graphics::Graphics* gfx, eve::graphics::Camera3D* camera);
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "zeroerr/assert.h"
|
||||
#include "zeroerr/unittest.h"
|
||||
|
||||
#include "graphics/WaterStyleConfig.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using eve::DiagnosticCode;
|
||||
using eve::graphics::WaterStyleConfig;
|
||||
|
||||
TEST_CASE("graphics.waterStyleConfig.roundTrip") {
|
||||
WaterStyleConfig authored;
|
||||
authored.deepColor = {0.01F, 0.08F, 0.18F};
|
||||
authored.shallowColor = {0.18F, 0.72F, 0.64F};
|
||||
authored.waveSharpness = 2.25F;
|
||||
authored.depthDistance = 7.5F;
|
||||
authored.foamStrength = 1.2F;
|
||||
authored.screenSpaceReflection = true;
|
||||
authored.screenSpaceReflectionStrength = 0.7F;
|
||||
authored.refractionStrength = 0.04F;
|
||||
authored.causticsStrength = 0.65F;
|
||||
authored.causticsScale = 4.5F;
|
||||
|
||||
auto json = authored.toJson();
|
||||
REQUIRE(static_cast<bool>(json));
|
||||
auto decoded = WaterStyleConfig::fromJson(json.value());
|
||||
REQUIRE(static_cast<bool>(decoded));
|
||||
|
||||
CHECK(std::abs(decoded.value().deepColor.z - 0.18F) < 0.0001F);
|
||||
CHECK(std::abs(decoded.value().shallowColor.y - 0.72F) < 0.0001F);
|
||||
CHECK(std::abs(decoded.value().waveSharpness - 2.25F) < 0.0001F);
|
||||
CHECK(std::abs(decoded.value().depthDistance - 7.5F) < 0.0001F);
|
||||
CHECK(std::abs(decoded.value().foamStrength - 1.2F) < 0.0001F);
|
||||
CHECK(decoded.value().screenSpaceReflection);
|
||||
CHECK(std::abs(decoded.value().refractionStrength - 0.04F) < 0.0001F);
|
||||
CHECK(std::abs(decoded.value().causticsStrength - 0.65F) < 0.0001F);
|
||||
CHECK(std::abs(decoded.value().causticsScale - 4.5F) < 0.0001F);
|
||||
}
|
||||
|
||||
TEST_CASE("graphics.waterStyleConfig.rejectsUnknownFields") {
|
||||
WaterStyleConfig config;
|
||||
auto encoded = config.toValue();
|
||||
REQUIRE(static_cast<bool>(encoded));
|
||||
|
||||
auto* root = encoded.value().getIf<eve::Value::Object>();
|
||||
auto* colors = root->at("colors").getIf<eve::Value::Object>();
|
||||
colors->emplace("surprise", 1.0);
|
||||
auto decoded = WaterStyleConfig::fromValue(encoded.value());
|
||||
CHECK(!decoded);
|
||||
REQUIRE(decoded.error() != nullptr);
|
||||
CHECK(decoded.error()->code() == DiagnosticCode::InvalidArgument);
|
||||
}
|
||||
|
||||
TEST_CASE("graphics.waterStyleConfig.rejectsFutureVersionsAndInvalidRanges") {
|
||||
WaterStyleConfig config;
|
||||
auto encoded = config.toValue();
|
||||
REQUIRE(static_cast<bool>(encoded));
|
||||
|
||||
auto* root = encoded.value().getIf<eve::Value::Object>();
|
||||
root->at("schemaVersion") = static_cast<std::int64_t>(WaterStyleConfig::SchemaVersion + 1);
|
||||
auto future = WaterStyleConfig::fromValue(encoded.value());
|
||||
CHECK(!future);
|
||||
REQUIRE(future.error() != nullptr);
|
||||
CHECK(future.error()->code() == DiagnosticCode::UnknownVersion);
|
||||
|
||||
config.depthDistance = 0.0F;
|
||||
auto invalid = config.validate();
|
||||
CHECK(!invalid);
|
||||
}
|
||||
Reference in new issue
Block a user