aboutsummaryrefslogtreecommitdiff
path: root/adapters/fparkan-render-vulkan/shaders
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-10-11 12:12:50 +0300
committerValentin Popov <valentin@popov.link>2026-10-11 12:12:50 +0300
commita8faba8aad441e87f8a8b94c148c01f9c075d621 (patch)
tree97aa4972990edba9991ff2e125edac3a7883f50f /adapters/fparkan-render-vulkan/shaders
parentaa51f3574d3d4d4e0fa59653eedd718fceb5d1bd (diff)
downloadfparkan-a8faba8aad441e87f8a8b94c148c01f9c075d621.tar.xz
fparkan-a8faba8aad441e87f8a8b94c148c01f9c075d621.zip
feat: complete map viewer scene and static CTL pose preview
Complete the interactive mission viewer with environment rendering, audio events, dynamic shadows, free-flight camera controls, and per-component CTL pose sampling.
Diffstat (limited to 'adapters/fparkan-render-vulkan/shaders')
-rw-r--r--adapters/fparkan-render-vulkan/shaders/triangle.frag126
-rw-r--r--adapters/fparkan-render-vulkan/shaders/triangle.frag.spvbin1184 -> 10388 bytes
-rw-r--r--adapters/fparkan-render-vulkan/shaders/triangle.vert264
-rw-r--r--adapters/fparkan-render-vulkan/shaders/triangle.vert.spvbin1432 -> 18580 bytes
4 files changed, 380 insertions, 10 deletions
diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.frag b/adapters/fparkan-render-vulkan/shaders/triangle.frag
index bcc3d43..89896cf 100644
--- a/adapters/fparkan-render-vulkan/shaders/triangle.frag
+++ b/adapters/fparkan-render-vulkan/shaders/triangle.frag
@@ -2,18 +2,132 @@
layout(location = 0) in vec3 in_color;
layout(location = 1) in vec2 in_uv;
+layout(location = 2) in vec2 in_detail_uv;
+layout(location = 3) in float in_overlay_alpha;
+layout(location = 4) in float in_fog_factor;
+layout(location = 5) in vec3 in_base_diffuse;
+layout(location = 6) in vec3 in_base_specular;
+layout(location = 7) in vec3 in_overlay_diffuse;
+layout(location = 8) in vec3 in_overlay_specular;
layout(location = 0) out vec4 out_color;
layout(set = 0, binding = 0) uniform sampler2D base_color;
+layout(set = 0, binding = 1) uniform sampler2D base_detail;
+layout(set = 0, binding = 2) uniform sampler2D overlay_color;
+layout(set = 0, binding = 3) uniform sampler2D overlay_detail;
-layout(push_constant) uniform AlphaTestConstants {
- layout(offset = 64)
- float alpha_cutoff;
-} alpha_test;
+layout(std140, set = 0, binding = 4) uniform FrameConstants {
+ mat4 clip_from_world;
+ vec4 directional_direction[4];
+ vec4 directional_rgb[4];
+ vec4 directional_coefficients[4];
+ vec4 point_position_range;
+ vec4 point_rgb_active;
+ vec4 point_coefficients_active;
+ vec4 lighting_floor;
+ vec4 fog_color;
+ vec4 fog_distances;
+ vec4 camera_position;
+} frame;
+
+layout(push_constant) uniform MaterialConstants {
+ layout(offset = 0) float alpha_cutoff;
+ layout(offset = 4) float diffuse_alpha;
+ layout(offset = 8) float overlay_diffuse_alpha;
+ layout(offset = 12) float directional_r;
+ layout(offset = 16) float directional_g;
+ layout(offset = 20) float directional_b;
+ layout(offset = 24) float additive_r;
+ layout(offset = 28) float additive_g;
+ layout(offset = 32) float additive_b;
+ layout(offset = 36) float overlay_directional_r;
+ layout(offset = 40) float overlay_directional_g;
+ layout(offset = 44) float overlay_directional_b;
+ layout(offset = 48) float overlay_additive_r;
+ layout(offset = 52) float overlay_additive_g;
+ layout(offset = 56) float overlay_additive_b;
+ layout(offset = 60) float base_page_x;
+ layout(offset = 64) float base_page_y;
+ layout(offset = 68) float base_page_w;
+ layout(offset = 72) float base_page_h;
+ layout(offset = 76) float detail_page_x;
+ layout(offset = 80) float detail_page_y;
+ layout(offset = 84) float detail_page_w;
+ layout(offset = 88) float detail_page_h;
+ layout(offset = 92) float overlay_page_x;
+ layout(offset = 96) float overlay_page_y;
+ layout(offset = 100) float overlay_page_w;
+ layout(offset = 104) float overlay_page_h;
+ layout(offset = 108) float overlay_detail_page_x;
+ layout(offset = 112) float overlay_detail_page_y;
+ layout(offset = 116) float overlay_detail_page_w;
+ layout(offset = 120) float overlay_detail_page_h;
+ layout(offset = 124) float material_mode;
+} material;
void main() {
- out_color = texture(base_color, in_uv) * vec4(in_color, 1.0);
- if (out_color.a < alpha_test.alpha_cutoff) {
+ vec2 base_uv = vec2(material.base_page_x, material.base_page_y)
+ + in_uv * vec2(material.base_page_w, material.base_page_h);
+ vec2 detail_uv = vec2(material.detail_page_x, material.detail_page_y)
+ + in_detail_uv * vec2(material.detail_page_w, material.detail_page_h);
+ vec2 overlay_uv = vec2(material.overlay_page_x, material.overlay_page_y)
+ + in_uv * vec2(material.overlay_page_w, material.overlay_page_h);
+ vec2 overlay_detail_uv = vec2(material.overlay_detail_page_x, material.overlay_detail_page_y)
+ + in_detail_uv * vec2(material.overlay_detail_page_w, material.overlay_detail_page_h);
+ vec4 base = texture(base_color, base_uv);
+ vec4 detail = texture(base_detail, detail_uv);
+ vec4 overlay = texture(overlay_color, overlay_uv);
+ vec4 overlay_detail_sample = texture(overlay_detail, overlay_detail_uv);
+ int mode = int(round(material.material_mode));
+ int combiner_mode = mode & 3;
+
+ // Native sky mode 4 combines nebula and stars as
+ // mix(TEX0.rgb, TEX1.rgb, TEX1.a) * DIFFUSE.rgb and carries DIFFUSE.a
+ // through as the pass alpha.
+ if (combiner_mode == 2) {
+ vec3 sky_rgb = mix(base.rgb, detail.rgb, clamp(detail.a, 0.0, 1.0));
+ out_color = vec4(sky_rgb * in_color, clamp(in_overlay_alpha, 0.0, 1.0));
+ if (out_color.a < material.alpha_cutoff) {
+ discard;
+ }
+ return;
+ }
+
+ // Native terrain mode 3 combines the base texture and the lightmap
+ // without the regular terrain 2x combiner or NdotL calculation.
+ if (combiner_mode == 3) {
+ vec3 lightmap_lighting = max(
+ vec3(material.additive_r, material.additive_g, material.additive_b),
+ frame.lighting_floor.rgb
+ );
+ vec3 vertex_rgb = base.rgb * detail.rgb * lightmap_lighting * in_color;
+ vec3 final_rgb = mix(vertex_rgb, frame.fog_color.rgb, in_fog_factor);
+ out_color = vec4(final_rgb, base.a * material.diffuse_alpha);
+ if (out_color.a < material.alpha_cutoff) {
+ discard;
+ }
+ return;
+ }
+
+ float overlay_alpha = clamp(
+ in_overlay_alpha * material.overlay_diffuse_alpha,
+ 0.0,
+ 1.0
+ );
+ vec3 base_rgb = 2.0 * base.rgb * detail.rgb;
+ vec3 overlay_rgb = 2.0 * overlay.rgb * overlay_detail_sample.rgb;
+ vec3 base_lit_rgb = base_rgb * in_base_diffuse + in_base_specular;
+ vec3 overlay_lit_rgb = overlay_rgb * in_overlay_diffuse + in_overlay_specular;
+ vec3 lit_rgb = mix(base_lit_rgb, overlay_lit_rgb, overlay_alpha);
+ vec3 vertex_rgb = lit_rgb * in_color;
+ vec3 final_rgb = combiner_mode == 0
+ ? mix(vertex_rgb, frame.fog_color.rgb, in_fog_factor)
+ : vertex_rgb;
+ float final_alpha = combiner_mode == 1
+ ? base.a * material.diffuse_alpha * in_overlay_alpha
+ : base.a * material.diffuse_alpha;
+ out_color = vec4(final_rgb, final_alpha);
+ if (out_color.a < material.alpha_cutoff) {
discard;
}
}
diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv
index 46b1e6f..8b867bb 100644
--- a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv
+++ b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv
Binary files differ
diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert b/adapters/fparkan-render-vulkan/shaders/triangle.vert
index dfcdccb..201c31a 100644
--- a/adapters/fparkan-render-vulkan/shaders/triangle.vert
+++ b/adapters/fparkan-render-vulkan/shaders/triangle.vert
@@ -2,17 +2,273 @@
layout(location = 0) in vec3 in_position;
layout(location = 1) in vec3 in_color;
-layout(location = 2) in vec2 in_uv;
+layout(location = 2) in vec3 in_normal;
+layout(location = 3) in vec2 in_uv;
+layout(location = 4) in vec2 in_detail_uv;
+layout(location = 5) in float in_overlay_alpha;
layout(location = 0) out vec3 out_color;
layout(location = 1) out vec2 out_uv;
+layout(location = 2) out vec2 out_detail_uv;
+layout(location = 3) out float out_overlay_alpha;
+// Native D3D vertex fog is written to SPECULAR.a and interpolated by the
+// fixed-function rasterizer. Keep the existing varying slot, but carry only
+// that scalar instead of recomputing distance in the fragment stage.
+layout(location = 4) out float out_fog_factor;
+layout(location = 5) out vec3 out_base_diffuse;
+layout(location = 6) out vec3 out_base_specular;
+layout(location = 7) out vec3 out_overlay_diffuse;
+layout(location = 8) out vec3 out_overlay_specular;
-layout(push_constant) uniform CameraConstants {
+layout(std140, set = 0, binding = 4) uniform FrameConstants {
mat4 clip_from_world;
-} camera;
+ vec4 directional_direction[4];
+ vec4 directional_rgb[4];
+ vec4 directional_coefficients[4];
+ vec4 point_position_range;
+ vec4 point_rgb_active;
+ vec4 point_coefficients_active;
+ vec4 lighting_floor;
+ vec4 fog_color;
+ vec4 fog_distances;
+ vec4 camera_position;
+} frame;
+
+layout(std430, set = 0, binding = 5) readonly buffer MaterialSpecular {
+ vec4 base;
+ vec4 overlay;
+} material_specular;
+
+layout(push_constant) uniform MaterialConstants {
+ layout(offset = 0) float alpha_cutoff;
+ layout(offset = 4) float diffuse_alpha;
+ layout(offset = 8) float overlay_diffuse_alpha;
+ layout(offset = 12) float directional_r;
+ layout(offset = 16) float directional_g;
+ layout(offset = 20) float directional_b;
+ layout(offset = 24) float additive_r;
+ layout(offset = 28) float additive_g;
+ layout(offset = 32) float additive_b;
+ layout(offset = 36) float overlay_directional_r;
+ layout(offset = 40) float overlay_directional_g;
+ layout(offset = 44) float overlay_directional_b;
+ layout(offset = 48) float overlay_additive_r;
+ layout(offset = 52) float overlay_additive_g;
+ layout(offset = 56) float overlay_additive_b;
+ layout(offset = 60) float base_page_x;
+ layout(offset = 64) float base_page_y;
+ layout(offset = 68) float base_page_w;
+ layout(offset = 72) float base_page_h;
+ layout(offset = 76) float detail_page_x;
+ layout(offset = 80) float detail_page_y;
+ layout(offset = 84) float detail_page_w;
+ layout(offset = 88) float detail_page_h;
+ layout(offset = 92) float overlay_page_x;
+ layout(offset = 96) float overlay_page_y;
+ layout(offset = 100) float overlay_page_w;
+ layout(offset = 104) float overlay_page_h;
+ layout(offset = 108) float overlay_detail_page_x;
+ layout(offset = 112) float overlay_detail_page_y;
+ layout(offset = 116) float overlay_detail_page_w;
+ layout(offset = 120) float overlay_detail_page_h;
+ layout(offset = 124) float material_mode;
+} material;
+
+struct LightingTerms {
+ vec3 diffuse;
+ vec3 specular;
+};
+
+float native_range_compress(float value) {
+ if (value <= 1.0) {
+ return value;
+ }
+ if (value <= 7.0) {
+ return value / 6.0 + 5.0 / 6.0;
+ }
+ return 2.0;
+}
+
+float native_specular_curve(float value) {
+ if (value <= 1.0) {
+ return 0.8 * value;
+ }
+ if (value <= 3.0) {
+ return 0.1 * value + 0.7;
+ }
+ return 1.0;
+}
+
+// The original callback uses the reflected vector and squares the cosine for
+// power-1 iterations. A bounded loop keeps the native byte power while
+// remaining valid for every material record.
+float native_specular_power(float cosine, float power) {
+ // CShade enables specular only when the material power byte is non-zero.
+ if (power <= 0.0 || cosine <= 0.0) {
+ return 0.0;
+ }
+ float result = cosine;
+ int iterations = clamp(int(power) - 1, 0, 254);
+ for (int index = 0; index < 254; ++index) {
+ if (index >= iterations) {
+ break;
+ }
+ result *= result;
+ }
+ return result;
+}
+
+float native_fog_factor(vec3 world_position) {
+ float fog_span = frame.fog_distances.y - frame.fog_distances.x;
+ float distance_to_camera = distance(world_position, frame.camera_position.xyz);
+ if (fog_span <= 0.000001 || distance_to_camera <= frame.fog_distances.x) {
+ return 0.0;
+ }
+ return clamp((distance_to_camera - frame.fog_distances.x) / fog_span, 0.0, 1.0);
+}
+
+vec3 normalized_or(vec3 value, vec3 fallback) {
+ float length_squared = dot(value, value);
+ return length_squared > 0.0000001 ? value * inversesqrt(length_squared) : fallback;
+}
+
+vec3 specular_from_light(
+ vec3 normal,
+ vec3 view_direction,
+ vec3 light_direction,
+ vec3 light_rgb,
+ vec3 material_rgb,
+ float power,
+ float attenuation
+) {
+ float ndotl = dot(normal, light_direction);
+ if (ndotl <= 0.0 || attenuation <= 0.0) {
+ return vec3(0.0);
+ }
+ vec3 reflection = reflect(-light_direction, normal);
+ float cosine = max(dot(reflection, view_direction), 0.0);
+ return material_rgb * light_rgb * native_specular_power(cosine, power) * attenuation;
+}
+
+LightingTerms native_material_lighting(
+ vec3 world_position,
+ vec3 normal,
+ vec3 directional_rgb,
+ vec3 additive_rgb,
+ vec3 specular_rgb,
+ float specular_power
+) {
+ vec3 accumulated = additive_rgb;
+ vec3 specular = vec3(0.0);
+ vec3 view_direction = normalized_or(frame.camera_position.xyz - world_position, vec3(0.0, 0.0, 1.0));
+ for (int index = 0; index < 4; ++index) {
+ vec4 direction_record = frame.directional_direction[index];
+ vec4 rgb_record = frame.directional_rgb[index];
+ if (direction_record.w <= 0.5 || rgb_record.w <= 0.5) {
+ continue;
+ }
+ vec3 light_direction = normalized_or(-direction_record.xyz, vec3(0.0, 0.0, 1.0));
+ float ndotl = max(dot(normal, light_direction), 0.0);
+ accumulated += ndotl * directional_rgb * rgb_record.rgb;
+ specular += specular_from_light(
+ normal,
+ view_direction,
+ light_direction,
+ rgb_record.rgb,
+ specular_rgb,
+ specular_power,
+ 1.0
+ );
+ }
+ vec4 point_position_range = frame.point_position_range;
+ vec4 point_rgb_active = frame.point_rgb_active;
+ vec4 point_coefficients_active = frame.point_coefficients_active;
+ vec3 to_light = point_position_range.xyz - world_position;
+ float distance_squared = dot(to_light, to_light);
+ float range = point_position_range.w;
+ if (point_rgb_active.w > 0.5 && range > 0.0 && distance_squared <= range * range) {
+ float distance_to_light = sqrt(distance_squared);
+ if (distance_to_light > 0.000001) {
+ vec3 light_direction = to_light / distance_to_light;
+ float ndotl = dot(normal, light_direction);
+ if (ndotl > 0.0) {
+ float t = (range - distance_to_light) / range;
+ float attenuation = point_coefficients_active.x
+ + point_coefficients_active.y * t
+ + point_coefficients_active.z * t * t;
+ attenuation = max(attenuation, 0.0);
+ accumulated += ndotl * directional_rgb * point_rgb_active.rgb * attenuation;
+ specular += specular_from_light(
+ normal,
+ view_direction,
+ light_direction,
+ point_rgb_active.rgb,
+ specular_rgb,
+ specular_power,
+ attenuation
+ );
+ }
+ }
+ }
+ accumulated = max(accumulated, frame.lighting_floor.rgb);
+ vec3 compressed = vec3(
+ native_range_compress(accumulated.r),
+ native_range_compress(accumulated.g),
+ native_range_compress(accumulated.b)
+ );
+ LightingTerms terms;
+ terms.diffuse = min(compressed, vec3(1.0));
+ vec3 total_specular = specular + max(compressed - vec3(1.0), vec3(0.0));
+ terms.specular = vec3(
+ native_specular_curve(total_specular.r),
+ native_specular_curve(total_specular.g),
+ native_specular_curve(total_specular.b)
+ );
+ return terms;
+}
void main() {
out_color = in_color;
out_uv = in_uv;
- gl_Position = camera.clip_from_world * vec4(in_position, 1.0);
+ out_detail_uv = in_detail_uv;
+ out_overlay_alpha = in_overlay_alpha;
+ out_fog_factor = native_fog_factor(in_position);
+ int mode = int(round(material.material_mode));
+ int combiner_mode = mode & 3;
+ if (combiner_mode == 0) {
+ LightingTerms base_terms = native_material_lighting(
+ in_position,
+ in_normal,
+ vec3(material.directional_r, material.directional_g, material.directional_b),
+ vec3(material.additive_r, material.additive_g, material.additive_b),
+ material_specular.base.rgb,
+ material_specular.base.w
+ );
+ LightingTerms overlay_terms = native_material_lighting(
+ in_position,
+ in_normal,
+ vec3(material.overlay_directional_r, material.overlay_directional_g, material.overlay_directional_b),
+ vec3(material.overlay_additive_r, material.overlay_additive_g, material.overlay_additive_b),
+ material_specular.overlay.rgb,
+ material_specular.overlay.w
+ );
+ out_base_diffuse = base_terms.diffuse;
+ out_base_specular = base_terms.specular;
+ out_overlay_diffuse = overlay_terms.diffuse;
+ out_overlay_specular = overlay_terms.specular;
+ } else {
+ out_base_diffuse = vec3(1.0);
+ out_base_specular = vec3(0.0);
+ out_overlay_diffuse = vec3(1.0);
+ out_overlay_specular = vec3(0.0);
+ }
+ vec4 clip_position = frame.clip_from_world * vec4(in_position, 1.0);
+ // Sky far-depth is a Vulkan projection choice for layers authored around
+ // the camera. Keep world positions, normals, and UVs intact while placing
+ // the resulting primitive at the far depth; this does not assert native
+ // D3D depth-bit equivalence.
+ if ((mode & 4) != 0) {
+ clip_position.z = clip_position.w;
+ }
+ gl_Position = clip_position;
}
diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv
index 84d1d9f..9574e1c 100644
--- a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv
+++ b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv
Binary files differ