summaryrefslogtreecommitdiff
path: root/shaders
diff options
context:
space:
mode:
Diffstat (limited to 'shaders')
-rw-r--r--shaders/model.frag.slang65
-rw-r--r--shaders/model.vert.slang25
-rw-r--r--shaders/model_wireframe.frag.slang2
-rw-r--r--shaders/shadow.frag.slang20
-rw-r--r--shaders/shadow.vert.slang38
5 files changed, 129 insertions, 21 deletions
diff --git a/shaders/model.frag.slang b/shaders/model.frag.slang
index 4684db0..ac12d0e 100644
--- a/shaders/model.frag.slang
+++ b/shaders/model.frag.slang
@@ -16,10 +16,29 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+struct light_uniforms {
+ float3 direction;
+ float shadow_texel_size;
+ float shadow_minimum_bias;
+ float shadow_maximum_bias;
+ float shadow_strength;
+ float padding;
+};
+
+[[vk::binding(0, 3)]]
+ConstantBuffer<light_uniforms> light : register(b0, space3);
+
+[[vk::binding(0, 2)]]
+Texture2D<float> shadow_map : register(t0, space2);
+
+[[vk::binding(1, 2)]]
+SamplerComparisonState shadow_sampler : register(s0, space2);
+
struct FSInput {
- float3 normal : TEXCOORD0;
- float3 world_position : TEXCOORD1;
- float3 camera_position : TEXCOORD2;
+ float3 normal : TEXCOORD0;
+ float3 world_position : TEXCOORD1;
+ float3 camera_position : TEXCOORD2;
+ float4 light_space_position : TEXCOORD3;
};
struct FSOutput {
@@ -30,17 +49,45 @@ static const float ambient_strength = 0.22;
static const float diffuse_strength = 0.78;
static const float specular_strength = 0.6;
static const float shininess = 32.0;
+static const int shadow_pcf_radius = 1;
+
+float shadow_visibility(float4 light_space_position, float normal_dot_light) {
+ float3 projected = light_space_position.xyz / light_space_position.w;
+ if (projected.z < 0.0 || projected.z > 1.0) {
+ return 1.0;
+ }
+
+ float2 uv = float2(0.5 + 0.5 * projected.x, 0.5 - 0.5 * projected.y);
+ if (any(uv < 0.0) || any(uv > 1.0)) {
+ return 1.0;
+ }
+
+ float bias = max(light.shadow_maximum_bias * (1.0 - normal_dot_light), light.shadow_minimum_bias);
+ float reference = projected.z - bias;
+ float visibility = 0.0;
+ float taps = 0.0;
+ for (int y = -shadow_pcf_radius; y <= shadow_pcf_radius; y++) {
+ for (int x = -shadow_pcf_radius; x <= shadow_pcf_radius; x++) {
+ float2 offset = float2(x, y) * light.shadow_texel_size;
+ visibility += shadow_map.SampleCmpLevelZero(shadow_sampler, uv + offset, reference);
+ taps += 1.0;
+ }
+ }
+
+ return visibility / taps;
+}
FSOutput main(FSInput input) {
- float3 light_direction = normalize(float3(-0.45, 0.75, 0.55));
+ float3 light_direction = normalize(light.direction);
float3 normal = normalize(input.normal);
float3 view_direction = normalize(input.camera_position - input.world_position);
float3 halfway_direction = normalize(light_direction + view_direction);
-
- float diffuse = max(dot(normal, light_direction), 0.0);
- float specular = diffuse > 0.0 ? pow(max(dot(normal, halfway_direction), 0.0), shininess) : 0.0;
-
- float shade = saturate(ambient_strength + diffuse * diffuse_strength + specular * specular_strength);
+ float normal_dot_light = dot(normal, light_direction);
+ float diffuse = max(normal_dot_light, 0.0);
+ float specular = diffuse > 0.0 ? pow(max(dot(normal, halfway_direction), 0.0), shininess) : 0.0;
+ float visibility = shadow_visibility(input.light_space_position, normal_dot_light);
+ visibility = lerp(1.0, visibility, light.shadow_strength);
+ float shade = saturate(ambient_strength + visibility * (diffuse * diffuse_strength + specular * specular_strength));
FSOutput output;
output.out_color = float4(shade, shade, shade, 1.0);
return output;
diff --git a/shaders/model.vert.slang b/shaders/model.vert.slang
index 380ff37..3ddb39d 100644
--- a/shaders/model.vert.slang
+++ b/shaders/model.vert.slang
@@ -19,8 +19,9 @@
struct model_uniforms {
float4x4 mvp;
float4x4 model;
+ float4x4 light_view_projection;
float3 camera_position;
- float padding;
+ float shadow_normal_offset;
};
[[vk::binding(0, 1)]]
@@ -32,18 +33,22 @@ struct VSInput {
};
struct VSOutput {
- float4 position : SV_Position;
- float3 normal : TEXCOORD0;
- float3 world_position : TEXCOORD1;
- float3 camera_position : TEXCOORD2;
+ float4 position : SV_Position;
+ float3 normal : TEXCOORD0;
+ float3 world_position : TEXCOORD1;
+ float3 camera_position : TEXCOORD2;
+ float4 light_space_position : TEXCOORD3;
};
VSOutput main(VSInput input) {
VSOutput output;
- float4 world_position = mul(uniforms.model, float4(input.position, 1.0));
- output.position = mul(uniforms.mvp, float4(input.position, 1.0));
- output.normal = normalize(mul((float3x3)uniforms.model, input.normal));
- output.world_position = world_position.xyz;
- output.camera_position = uniforms.camera_position;
+ float4 world_position = mul(uniforms.model, float4(input.position, 1.0));
+ float3 world_normal = normalize(mul((float3x3)uniforms.model, input.normal));
+ output.position = mul(uniforms.mvp, float4(input.position, 1.0));
+ output.normal = world_normal;
+ output.world_position = world_position.xyz;
+ output.camera_position = uniforms.camera_position;
+ float3 offset_position = world_position.xyz + world_normal * uniforms.shadow_normal_offset;
+ output.light_space_position = mul(uniforms.light_view_projection, float4(offset_position, 1.0));
return output;
} \ No newline at end of file
diff --git a/shaders/model_wireframe.frag.slang b/shaders/model_wireframe.frag.slang
index bac0966..10c2a52 100644
--- a/shaders/model_wireframe.frag.slang
+++ b/shaders/model_wireframe.frag.slang
@@ -20,8 +20,6 @@ struct FSOutput {
float4 out_color : SV_Target0;
};
-// Ignores geometry normals/lighting entirely; used with the LINE fill mode
-// pipeline so only the mesh edges are drawn, in pure white.
FSOutput main() {
FSOutput output;
output.out_color = float4(1.0, 1.0, 1.0, 1.0);
diff --git a/shaders/shadow.frag.slang b/shaders/shadow.frag.slang
new file mode 100644
index 0000000..4f895f1
--- /dev/null
+++ b/shaders/shadow.frag.slang
@@ -0,0 +1,20 @@
+/*
+ * Tuxánci 2 - A first person shooter
+ * Copyright (C) 2025-2026 Connor Thomson
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+void main() {
+} \ No newline at end of file
diff --git a/shaders/shadow.vert.slang b/shaders/shadow.vert.slang
new file mode 100644
index 0000000..1e56f46
--- /dev/null
+++ b/shaders/shadow.vert.slang
@@ -0,0 +1,38 @@
+/*
+ * Tuxánci 2 - A first person shooter
+ * Copyright (C) 2025-2026 Connor Thomson
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+struct shadow_uniforms {
+ float4x4 light_model_view_projection;
+};
+
+[[vk::binding(0, 1)]]
+ConstantBuffer<shadow_uniforms> uniforms : register(b0, space1);
+
+struct VSInput {
+ float3 position : POSITION0;
+};
+
+struct VSOutput {
+ float4 position : SV_Position;
+};
+
+VSOutput main(VSInput input) {
+ VSOutput output;
+ output.position = mul(uniforms.light_model_view_projection, float4(input.position, 1.0));
+ return output;
+} \ No newline at end of file