Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,7 @@ vcpkg/

# Compiled SPIR-V shaders (built from source by glslangValidator)
*.spv

# Vulkan SPIR-V is regenerated by CMake (glslangValidator required)
Resources/Shaders/Vulkan/**/*.spv
Resources/Shaders/Vulkan/*.spv
12 changes: 9 additions & 3 deletions Resources/Shaders/Vulkan/BasicMap.vert
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ layout(location = 0) in uvec3 positionAttribute;
layout(location = 1) in uvec2 aoCoordAttribute;
layout(location = 2) in uvec3 colorAttribute; // colorRed, colorGreen, colorBlue
layout(location = 3) in ivec3 normalAttribute;
layout(location = 4) in ivec3 fixedPositionAttribute; // face-center * 2 (chunk-local)

layout(location = 0) out vec4 color; // xyz = linearized vertex color, w = sun lambert
layout(location = 1) out vec3 ambientLight; // ambient lighting component (hemisphere fallback)
Expand Down Expand Up @@ -90,7 +91,12 @@ void main() {
// mapShadowCoord.y -= mapShadowCoord.z (diagonal sun projection)
// mapShadowCoord.z /= 255.0 (normalize height)
// mapShadowCoord.xy /= 512.0 (normalize to texture coords)
vec3 wPos = worldPos.xyz;
// Use the face-center "fixed position" (matches GL fixedPositionAttribute)
// instead of the raw vertex position. Vertices lie exactly on voxel
// boundaries; sampling the map-shadow texture there picks the neighboring
// column and leaks a lit sliver along face edges (e.g. top of north faces
// seen from the south). The face center is safely inside the voxel.
vec3 wPos = vec3(fixedPositionAttribute) * 0.5 + pushConstants.modelOrigin;
shadowCoord = vec3(wPos.x / 512.0, (wPos.y - wPos.z) / 512.0, wPos.z / 255.0);

// Fog density based on horizontal distance (matching SW/GL implementation)
Expand All @@ -102,15 +108,15 @@ void main() {
// AO 3D-texture coords. World position with z+1 (the 0-th slice is the
// "below ground" guard plane), divided by texture extent. Map dimensions
// are 512x512x64 in this game, so the texture is 512x512x65.
aoCoord = (worldPos.xyz + vec3(0.0, 0.0, 1.0)) / vec3(512.0, 512.0, 65.0);
aoCoord = (wPos + vec3(0.0, 0.0, 1.0)) / vec3(512.0, 512.0, 65.0);

// 2D AO atlas coords (matches GL BasicBlock.vs). The atlas is 256x256 with
// 16x16 precomputed AO tiles; aoCoordAttribute holds tile_x*16 + corner
// offset on each axis (range 0..255). +0.5 centres the sample.
ambientOcclusionCoord = (vec2(aoCoordAttribute) + 0.5) * (1.0 / 256.0);

// Radiosity 3D-texture coords (matches GL MapRadiosity.vs).
radiosityTextureCoord = worldPos.xyz / vec3(512.0, 512.0, 64.0);
radiosityTextureCoord = wPos / vec3(512.0, 512.0, 64.0);
normalVarying = normalFloat;

// Per-cascade light-clip coords for model-shadow sampling. Ortho => w == 1,
Expand Down
11 changes: 7 additions & 4 deletions Resources/Shaders/Vulkan/BasicMapPhys.vert
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ layout(location = 0) in uvec3 positionAttribute;
layout(location = 1) in uvec2 aoCoordAttribute;
layout(location = 2) in uvec3 colorAttribute;
layout(location = 3) in ivec3 normalAttribute;
layout(location = 4) in ivec3 fixedPositionAttribute; // face-center * 2 (chunk-local)

layout(location = 0) out vec4 color; // xyz = linearized vertex color, w = sun lambert (may be negative)
layout(location = 1) out vec3 ambientLight; // hemisphere ambient fallback
Expand Down Expand Up @@ -76,8 +77,10 @@ void main() {

color = vec4(vertexColor, lambert);

// Shadow coordinates
vec3 wPos = worldPos.xyz;
// Shadow coordinates — sample at the face center ("fixed position",
// matches GL fixedPositionAttribute) so voxel-boundary vertices don't
// leak the neighboring column's shadow value (lit sliver on face edges).
vec3 wPos = vec3(fixedPositionAttribute) * 0.5 + pushConstants.modelOrigin;
shadowCoord = vec3(wPos.x / 512.0, (wPos.y - wPos.z) / 512.0, wPos.z / 255.0);

// Fog
Expand All @@ -91,9 +94,9 @@ void main() {
viewSpaceNormal = normalize((pushConstants.viewMatrix * vec4(normalFloat, 0.0)).xyz);
reflectionDir = reflect(worldPos.xyz - pushConstants.viewOrigin, normalFloat);

aoCoord = (worldPos.xyz + vec3(0.0, 0.0, 1.0)) / vec3(512.0, 512.0, 65.0);
aoCoord = (wPos + vec3(0.0, 0.0, 1.0)) / vec3(512.0, 512.0, 65.0);

// Radiosity 3D-texture coords (matches GL MapRadiosity.vs).
radiosityTextureCoord = worldPos.xyz / vec3(512.0, 512.0, 64.0);
radiosityTextureCoord = wPos / vec3(512.0, 512.0, 64.0);
normalVarying = normalFloat;
}
32 changes: 32 additions & 0 deletions Resources/Shaders/Vulkan/BasicModelVertexColor.frag
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ layout(set = 0, binding = 4) uniform sampler3D radiosityTextureY;
layout(set = 0, binding = 5) uniform sampler3D radiosityTextureZ;
layout(set = 0, binding = 6) uniform sampler2D ambientOcclusionAtlas; // Gfx/AmbientOcclusion.png

layout(set = 1, binding = 0) uniform ShadowSampling {
mat4 cascadeMatrix[3];
int enabled;
} shadowSampling;
layout(set = 1, binding = 1) uniform sampler2D modelShadowMap0;
layout(set = 1, binding = 2) uniform sampler2D modelShadowMap1;
layout(set = 1, binding = 3) uniform sampler2D modelShadowMap2;

layout(location = 0) in vec4 color; // xyz = vertexColor, w = sun lambert
layout(location = 1) in vec3 ambientLight; // hemisphere ambient fallback (kept for VS↔FS compat)
layout(location = 2) in vec3 customColor;
Expand All @@ -44,9 +52,32 @@ layout(location = 7) in vec3 radiosityTextureCoord;
layout(location = 8) in vec3 normalVarying;
layout(location = 9) in vec2 ambientOcclusionCoord; // 2D coords into AO atlas
layout(location = 10) in float waterClip; // <0 = below the reflection plane
layout(location = 11) in vec3 modelShadowCoord0; // light-clip coords per cascade
layout(location = 12) in vec3 modelShadowCoord1;
layout(location = 13) in vec3 modelShadowCoord2;

layout(location = 0) out vec4 fragColor;

// Same cascade sampling as BasicMap.frag. Local Z 0 = sun side; a smaller
// stored depth means an occluder nearer the sun. Bias avoids self-shadow acne
// (models render into these cascades themselves).
float SampleModelCascade(sampler2D tex, vec3 c) {
vec2 uv = c.xy * 0.5 + 0.5;
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 || c.z < 0.0 || c.z > 1.0)
return -1.0;
float stored = texture(tex, uv).r;
return (stored < c.z - 0.0015) ? 0.0 : 1.0;
}

float EvaluteModelShadow() {
if (shadowSampling.enabled == 0)
return 1.0;
float v = SampleModelCascade(modelShadowMap0, modelShadowCoord0);
if (v < 0.0) v = SampleModelCascade(modelShadowMap1, modelShadowCoord1);
if (v < 0.0) v = SampleModelCascade(modelShadowMap2, modelShadowCoord2);
return (v < 0.0) ? 1.0 : v;
}

vec3 DecodeRadiosityValue(vec3 val) {
val *= 1023.0 / 1022.0;
val = (val * 2.0) - 1.0;
Expand All @@ -62,6 +93,7 @@ void main() {
// Evaluate map shadow (matching OpenGL Map.fs: EvaluateMapShadow)
float shadowVal = texture(mapShadowTexture, shadowCoord.xy).w;
float shadow = (shadowVal < shadowCoord.z - 0.0001) ? 0.0 : 1.0;
shadow *= EvaluteModelShadow(); // model-on-model cascades (set 1)

vec3 vertexColor = color.xyz;

Expand Down
18 changes: 17 additions & 1 deletion Resources/Shaders/Vulkan/BasicModelVertexColor.vert
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ layout(push_constant) uniform PushConstants {
vec3 sunDirection; // points toward the sun (renderer GetSunDirection)
} pushConstants;

layout(set = 1, binding = 0) uniform ShadowSampling {
mat4 cascadeMatrix[3];
int enabled;
} shadowSampling;

layout(location = 0) in uvec3 positionAttribute;
layout(location = 1) in uvec3 colorAttribute; // RGB color stored in u,v as (R, G, B)
layout(location = 2) in ivec3 normalAttribute;
Expand All @@ -49,6 +54,9 @@ layout(location = 7) out vec3 radiosityTextureCoord; // 3D coords into radiosity
layout(location = 8) out vec3 normalVarying; // world-space surface normal
layout(location = 9) out vec2 ambientOcclusionCoord; // 2D coords into AO atlas
layout(location = 10) out float waterClip; // >=0 keep, <0 clip below the reflection plane
layout(location = 11) out vec3 modelShadowCoord0; // light-clip coords per cascade
layout(location = 12) out vec3 modelShadowCoord1;
layout(location = 13) out vec3 modelShadowCoord2;

// Keep gl_Position bit-identical with ModelDynamicLit.vert so the additive
// dynamic-light pass (depth test EQUAL) matches this opaque pass's depth and
Expand Down Expand Up @@ -86,7 +94,9 @@ void main() {
customColorOut = pushConstants.customColor;

// Shadow map coordinates (sun projects diagonally along y-z)
shadowCoord = vec3(worldPos.x / 512.0, (worldPos.y - worldPos.z) / 512.0, worldPos.z / 255.0);
// Sample slightly inside the surface to avoid shadow bleed at voxel boundaries
vec3 shadowPos = worldPos - normalFloat * 0.05;
shadowCoord = vec3(shadowPos.x / 512.0, (shadowPos.y - shadowPos.z) / 512.0, shadowPos.z / 255.0);

// Fog density pre-computed on CPU from model world position
fogDensityOut = vec3(pushConstants.fogDensity);
Expand All @@ -103,6 +113,12 @@ void main() {
// 256-pixel-space tile + corner offsets baked per-face in EmitFace.
ambientOcclusionCoord = (vec2(aoXAttribute, aoYAttribute) + 0.5) * (1.0 / 256.0);

// Model-on-model shadow cascades (same set-1 sampling as BasicMap).
vec4 worldPos4 = vec4(worldPos, 1.0);
modelShadowCoord0 = (shadowSampling.cascadeMatrix[0] * worldPos4).xyz;
modelShadowCoord1 = (shadowSampling.cascadeMatrix[1] * worldPos4).xyz;
modelShadowCoord2 = (shadowSampling.cascadeMatrix[2] * worldPos4).xyz;

// Reflection-pass water clip: negative for fragments below the water plane.
// In the normal scene mirrorClipZ is +inf, so this stays positive (no clip).
waterClip = pushConstants.mirrorClipZ - worldPos.z;
Expand Down
4 changes: 3 additions & 1 deletion Resources/Shaders/Vulkan/BasicModelVertexColorPhys.vert
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ void main() {
customColorOut = pushConstants.customColor;

// Shadow coordinates
shadowCoord = vec3(worldPos.x / 512.0, (worldPos.y - worldPos.z) / 512.0, worldPos.z / 255.0);
// Sample slightly inside the surface to avoid shadow bleed at voxel boundaries
vec3 shadowPos = worldPos - worldNormal * 0.05;
shadowCoord = vec3(shadowPos.x / 512.0, (shadowPos.y - shadowPos.z) / 512.0, shadowPos.z / 255.0);

// Fog
fogDensityOut = vec3(pushConstants.fogDensity);
Expand Down
40 changes: 32 additions & 8 deletions Resources/Shaders/Vulkan/PostFilters/BloomComposite.vk.fs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,42 @@

#version 450

// Bloom final composite pass.
// Mirrors GLBloomFilter's GammaMix call with mix1=0.8, mix2=0.2 on a linear HDR framebuffer:
// outColor = scene * 0.8 + bloom * 0.2
// Bloom final composite — Vulkan port of GL's LensDust.fs (the real
// r_bloom composite). Linear framebuffer, so GL's linearize/sqrt round
// trip is dropped; the dust texture still gets squared to linearize it
// (it is a plain sRGB-ish JPG).

layout(binding = 0) uniform sampler2D sceneTexture;
layout(binding = 1) uniform sampler2D bloomTexture;
layout(binding = 0) uniform sampler2D inputTexture;
layout(binding = 1) uniform sampler2D blurTexture1;
layout(binding = 2) uniform sampler2D dustTexture;
layout(binding = 3) uniform sampler2D noiseTexture;

layout(push_constant) uniform LensDustPC {
vec4 noiseTexCoordFactor;
} pc;

layout(location = 0) in vec2 texCoord;
layout(location = 0) out vec4 outColor;

void main() {
vec3 scene = texture(sceneTexture, texCoord).rgb;
vec3 bloom = texture(bloomTexture, texCoord).rgb;
outColor = vec4(scene * 0.8 + bloom * 0.2, 1.0);
vec3 dust1 = texture(dustTexture, texCoord).xyz;
dust1 *= dust1; // linearize

vec3 blur1 = texture(blurTexture1, texCoord).xyz;

vec3 sum = dust1 * blur1;

vec3 final = texture(inputTexture, texCoord).xyz;

final *= 0.95;
final += sum * 2.0;

// film grain
vec4 noiseTexCoord = texCoord.xyxy * pc.noiseTexCoordFactor;
float grain = texture(noiseTexture, noiseTexCoord.xy).x;
grain += texture(noiseTexture, noiseTexCoord.zw).x;
grain = fract(grain) - 0.5;
final += grain * 0.003;

outColor = vec4(max(final, 0.0), 1.0);
}
81 changes: 81 additions & 0 deletions Resources/Shaders/Vulkan/PostFilters/CameraBlur.vk.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright (c) 2013 Fran6nd

This file is part of ZeroSpades, a fork of OpenSpades.

OpenSpades is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

OpenSpades is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenSpades. If not, see <http://www.gnu.org/licenses/>.

*/

#version 450

// Vulkan port of PostFilters/CameraBlur.fs.
// Offscreen buffer is linear (LINEAR_FRAMEBUFFER), so no
// linearize / sqrt round trip. Depth-weighted 5-tap smear along the
// per-pixel motion vector; depth^2 weighting keeps the view weapon
// (depth ~[0,0.1]) mostly unsmeared.

layout(binding = 0) uniform sampler2D mainTexture;
layout(binding = 1) uniform sampler2D depthTexture;

layout(push_constant) uniform CameraBlurPC {
mat4 reverseMatrix;
float shutterTimeScale;
} pc;

layout(location = 0) in vec2 newCoord;
layout(location = 1) in vec3 oldCoord;

layout(location = 0) out vec4 fragColor;

vec4 getSample(vec2 coord) {
vec3 color = texture(mainTexture, coord).xyz;
float depth = texture(depthTexture, coord).x;
float weight = depth * depth;
weight = min(weight, 1.0) + 0.0001;
return vec4(color * weight, weight);
}

void main() {
vec2 nextCoord = newCoord;
vec2 prevCoord = oldCoord.xy / oldCoord.z;
vec2 coord;

vec4 sum;

coord = mix(nextCoord, prevCoord, 0.0);
sum = getSample(coord);

// use latest sample's weight for camera blur strength
float allWeight = sum.w;
vec4 sum2;

sum /= sum.w;

coord = mix(nextCoord, prevCoord, pc.shutterTimeScale * 0.2);
sum2 = getSample(coord);

coord = mix(nextCoord, prevCoord, pc.shutterTimeScale * 0.4);
sum2 += getSample(coord);

coord = mix(nextCoord, prevCoord, pc.shutterTimeScale * 0.6);
sum2 += getSample(coord);

coord = mix(nextCoord, prevCoord, pc.shutterTimeScale * 0.8);
sum2 += getSample(coord);

sum += sum2 * allWeight;

fragColor = vec4(sum.xyz / sum.w, 1.0);
}
46 changes: 46 additions & 0 deletions Resources/Shaders/Vulkan/PostFilters/CameraBlur.vk.vs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
Copyright (c) 2013 Fran6nd

This file is part of ZeroSpades, a fork of OpenSpades.

OpenSpades is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

OpenSpades is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenSpades. If not, see <http://www.gnu.org/licenses/>.

*/

#version 450

// Vulkan port of PostFilters/CameraBlur.vs.
// Fullscreen triangle; per-vertex reprojection of the current-frame
// texcoord into last frame's frame via reverseMatrix.

layout(push_constant) uniform CameraBlurPC {
mat4 reverseMatrix;
float shutterTimeScale;
} pc;

layout(location = 0) out vec2 newCoord;
layout(location = 1) out vec3 oldCoord;

void main() {
vec2 uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(uv * 2.0 - 1.0, 0.5, 1.0);

newCoord = uv;

// GL math runs in bottom-left-origin coords; our texcoords are
// top-left-origin, so flip Y going in and coming out.
vec4 cvt = vec4(uv.x - 0.5, 0.5 - uv.y, 0.0, 1.0);
vec3 o = (pc.reverseMatrix * cvt).xyz;
oldCoord = vec3(o.x + 0.5 * o.z, -o.y + 0.5 * o.z, o.z);
}
10 changes: 9 additions & 1 deletion Resources/Shaders/Vulkan/PostFilters/Fog.vk.fs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,15 @@ void main() {

vec3 startPos = shadowOrigin;
vec3 dir = shadowRayDirection;
if (length(dir.xy) < 0.0001) dir.xy = vec2(0.0001);

// Near-vertical rays: voxelDistanceFactor -> 0, so the fog integral is
// ~zero anyway. Snapping dir.xy (old GL-style guard) made adjacent
// fragments flip between the real and snapped direction, glitching the
// view straight down. Skip the march instead.
if (length(dir.xy) < 0.0001 * length(dir)) {
outColor = texture(colorTexture, texCoord);
return;
}
if (dir.x == 0.0) dir.x = 0.00001;
if (dir.y == 0.0) dir.y = 0.00001;
dir = normalize(dir);
Expand Down
Loading
Loading