Files
retro3d/shaders/retro3d.vert.msl
2025-12-12 17:27:12 -06:00

102 lines
2.7 KiB
Plaintext

#include <metal_stdlib>
using namespace metal;
struct Uniforms {
float4x4 mvp;
float4x4 model;
float4x4 view;
float4x4 projection;
float4 ambient; // rgb, unused
float4 light_dir; // xyz normalized, unused
float4 light_color; // rgb, intensity
float4 fog_params; // near, far, unused, enabled
float4 fog_color; // rgb, unused
float4 tint; // rgba
float4 style_params; // style_id, vertex_snap, affine, dither
float4 resolution; // w, h, unused, unused
};
struct VertexIn {
float3 position [[attribute(0)]];
float3 normal [[attribute(1)]];
float2 uv [[attribute(2)]];
float4 color [[attribute(3)]];
};
struct VertexOut {
float4 position [[position]];
float3 world_normal;
float2 uv;
float4 color;
float fog_factor;
float3 noperspective_uv; // For affine texturing (PS1 style)
};
vertex VertexOut vertex_main(
VertexIn in [[stage_in]],
constant Uniforms &uniforms [[buffer(1)]]
) {
VertexOut out;
float4 world_pos = uniforms.model * float4(in.position, 1.0);
float4 clip_pos = uniforms.mvp * float4(in.position, 1.0);
// PS1-style vertex snapping
float style_id = uniforms.style_params.x;
float vertex_snap = uniforms.style_params.y;
if (vertex_snap > 0.5 && style_id < 0.5) {
// PS1 style: snap vertices to grid
float2 res = uniforms.resolution.xy;
float2 snap_res = res * 0.5;
// Snap in clip space after perspective divide
float4 snapped = clip_pos;
if (snapped.w > 0.0) {
float2 ndc = snapped.xy / snapped.w;
ndc = floor(ndc * snap_res + 0.5) / snap_res;
snapped.xy = ndc * snapped.w;
}
clip_pos = snapped;
}
out.position = clip_pos;
// Transform normal to world space
float3x3 normal_matrix = float3x3(
uniforms.model[0].xyz,
uniforms.model[1].xyz,
uniforms.model[2].xyz
);
out.world_normal = normalize(normal_matrix * in.normal);
// UV coordinates
out.uv = in.uv;
// For affine texturing (PS1), we pass UV * w to fragment shader
// and divide by interpolated w there
float affine = uniforms.style_params.z;
if (affine > 0.5) {
out.noperspective_uv = float3(in.uv * clip_pos.w, clip_pos.w);
} else {
out.noperspective_uv = float3(in.uv, 1.0);
}
// Vertex color
out.color = in.color;
// Fog calculation (linear fog)
float fog_enabled = uniforms.fog_params.w;
if (fog_enabled > 0.5) {
float4 view_pos = uniforms.view * world_pos;
float dist = length(view_pos.xyz);
float fog_near = uniforms.fog_params.x;
float fog_far = uniforms.fog_params.y;
out.fog_factor = clamp((fog_far - dist) / (fog_far - fog_near), 0.0, 1.0);
} else {
out.fog_factor = 1.0;
}
return out;
}