3D viewport, milestone 1
This commit is contained in:
parent
38156dd570
commit
e277663ba0
27 changed files with 7389 additions and 8 deletions
2
src/3d/shaders/.gitkeep
Normal file
2
src/3d/shaders/.gitkeep
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Placeholder for shader files
|
||||
# PS1-style shaders will be added in Milestone 1
|
||||
90
src/3d/shaders/ps1_fragment.glsl
Normal file
90
src/3d/shaders/ps1_fragment.glsl
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// PS1-style fragment shader for OpenGL 3.2+
|
||||
// Implements affine texture mapping, fog, color quantization, and dithering
|
||||
|
||||
#version 150 core
|
||||
|
||||
// Uniforms - texturing
|
||||
uniform sampler2D u_texture;
|
||||
uniform bool u_has_texture; // Whether to use texture or just vertex color
|
||||
|
||||
// Uniforms - PS1 effects
|
||||
uniform bool u_enable_dither; // Enable ordered dithering
|
||||
uniform vec3 u_fog_color; // Fog color (usually matches background)
|
||||
|
||||
// Varyings from vertex shader
|
||||
in vec4 v_color; // Gouraud-shaded vertex color
|
||||
noperspective in vec2 v_texcoord; // Texture coordinates (affine interpolation!)
|
||||
in float v_fog; // Fog factor
|
||||
|
||||
// Output
|
||||
out vec4 fragColor;
|
||||
|
||||
// =========================================================================
|
||||
// 4x4 Bayer Dithering Matrix
|
||||
// Used to add ordered noise for color quantization, reducing banding
|
||||
// =========================================================================
|
||||
const int bayerMatrix[16] = int[16](
|
||||
0, 8, 2, 10,
|
||||
12, 4, 14, 6,
|
||||
3, 11, 1, 9,
|
||||
15, 7, 13, 5
|
||||
);
|
||||
|
||||
float getBayerValue(vec2 fragCoord) {
|
||||
int x = int(mod(fragCoord.x, 4.0));
|
||||
int y = int(mod(fragCoord.y, 4.0));
|
||||
return float(bayerMatrix[y * 4 + x]) / 16.0;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 15-bit Color Quantization
|
||||
// PS1 had 15-bit color (5 bits per channel), creating visible color banding
|
||||
// =========================================================================
|
||||
vec3 quantize15bit(vec3 color) {
|
||||
// Quantize to 5 bits per channel (32 levels)
|
||||
return floor(color * 31.0 + 0.5) / 31.0;
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Sample texture or use vertex color
|
||||
vec4 color;
|
||||
if (u_has_texture) {
|
||||
vec4 texColor = texture(u_texture, v_texcoord);
|
||||
|
||||
// Binary alpha test (PS1 style - no alpha blending)
|
||||
if (texColor.a < 0.5) {
|
||||
discard;
|
||||
}
|
||||
|
||||
color = texColor * v_color;
|
||||
} else {
|
||||
color = v_color;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Color Quantization with Dithering
|
||||
// Reduce color depth to 15-bit, using dithering to reduce banding
|
||||
// =========================================================================
|
||||
if (u_enable_dither) {
|
||||
// Get Bayer dither threshold for this pixel
|
||||
float threshold = getBayerValue(gl_FragCoord.xy);
|
||||
|
||||
// Apply dither before quantization
|
||||
// Threshold is in range [0, 1), we scale it to affect quantization
|
||||
vec3 dithered = color.rgb + (threshold - 0.5) / 31.0;
|
||||
|
||||
// Quantize to 15-bit
|
||||
color.rgb = quantize15bit(dithered);
|
||||
} else {
|
||||
// Just quantize without dithering
|
||||
color.rgb = quantize15bit(color.rgb);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Fog Application
|
||||
// Linear fog blending based on depth
|
||||
// =========================================================================
|
||||
color.rgb = mix(color.rgb, u_fog_color, v_fog);
|
||||
|
||||
fragColor = color;
|
||||
}
|
||||
120
src/3d/shaders/ps1_fragment_es2.glsl
Normal file
120
src/3d/shaders/ps1_fragment_es2.glsl
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// PS1-style fragment shader for OpenGL ES 2.0 / WebGL 1.0
|
||||
// Implements affine texture mapping, fog, color quantization, and dithering
|
||||
|
||||
precision mediump float;
|
||||
|
||||
// Uniforms - texturing
|
||||
uniform sampler2D u_texture;
|
||||
uniform bool u_has_texture; // Whether to use texture or just vertex color
|
||||
|
||||
// Uniforms - PS1 effects
|
||||
uniform bool u_enable_dither; // Enable ordered dithering
|
||||
uniform vec3 u_fog_color; // Fog color (usually matches background)
|
||||
|
||||
// Varyings from vertex shader
|
||||
varying vec4 v_color; // Gouraud-shaded vertex color
|
||||
varying vec2 v_texcoord; // Texture coordinates (pre-multiplied by w)
|
||||
varying float v_w; // Clip space w for affine restoration
|
||||
varying float v_fog; // Fog factor
|
||||
|
||||
// =========================================================================
|
||||
// 4x4 Bayer Dithering Matrix
|
||||
// Used to add ordered noise for color quantization, reducing banding
|
||||
// =========================================================================
|
||||
const mat4 bayerMatrix = mat4(
|
||||
0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0,
|
||||
12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0,
|
||||
3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0,
|
||||
15.0/16.0, 7.0/16.0, 13.0/16.0, 5.0/16.0
|
||||
);
|
||||
|
||||
float getBayerValue(vec2 fragCoord) {
|
||||
int x = int(mod(fragCoord.x, 4.0));
|
||||
int y = int(mod(fragCoord.y, 4.0));
|
||||
|
||||
// Manual matrix lookup (GLES2 doesn't support integer indexing of mat4)
|
||||
if (y == 0) {
|
||||
if (x == 0) return 0.0/16.0;
|
||||
if (x == 1) return 8.0/16.0;
|
||||
if (x == 2) return 2.0/16.0;
|
||||
return 10.0/16.0;
|
||||
}
|
||||
if (y == 1) {
|
||||
if (x == 0) return 12.0/16.0;
|
||||
if (x == 1) return 4.0/16.0;
|
||||
if (x == 2) return 14.0/16.0;
|
||||
return 6.0/16.0;
|
||||
}
|
||||
if (y == 2) {
|
||||
if (x == 0) return 3.0/16.0;
|
||||
if (x == 1) return 11.0/16.0;
|
||||
if (x == 2) return 1.0/16.0;
|
||||
return 9.0/16.0;
|
||||
}
|
||||
// y == 3
|
||||
if (x == 0) return 15.0/16.0;
|
||||
if (x == 1) return 7.0/16.0;
|
||||
if (x == 2) return 13.0/16.0;
|
||||
return 5.0/16.0;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 15-bit Color Quantization
|
||||
// PS1 had 15-bit color (5 bits per channel), creating visible color banding
|
||||
// =========================================================================
|
||||
vec3 quantize15bit(vec3 color) {
|
||||
// Quantize to 5 bits per channel (32 levels)
|
||||
return floor(color * 31.0 + 0.5) / 31.0;
|
||||
}
|
||||
|
||||
void main() {
|
||||
// =========================================================================
|
||||
// PS1 Effect: Affine Texture Mapping
|
||||
// Divide by interpolated w to restore texture coordinates.
|
||||
// Because w was interpolated linearly (not perspectively), this creates
|
||||
// the characteristic texture warping on PS1.
|
||||
// =========================================================================
|
||||
vec2 uv = v_texcoord / v_w;
|
||||
|
||||
// Sample texture or use vertex color
|
||||
vec4 color;
|
||||
if (u_has_texture) {
|
||||
vec4 texColor = texture2D(u_texture, uv);
|
||||
|
||||
// Binary alpha test (PS1 style - no alpha blending)
|
||||
if (texColor.a < 0.5) {
|
||||
discard;
|
||||
}
|
||||
|
||||
color = texColor * v_color;
|
||||
} else {
|
||||
color = v_color;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Color Quantization with Dithering
|
||||
// Reduce color depth to 15-bit, using dithering to reduce banding
|
||||
// =========================================================================
|
||||
if (u_enable_dither) {
|
||||
// Get Bayer dither threshold for this pixel
|
||||
float threshold = getBayerValue(gl_FragCoord.xy);
|
||||
|
||||
// Apply dither before quantization
|
||||
// Threshold is in range [0, 1), we scale it to affect quantization
|
||||
vec3 dithered = color.rgb + (threshold - 0.5) / 31.0;
|
||||
|
||||
// Quantize to 15-bit
|
||||
color.rgb = quantize15bit(dithered);
|
||||
} else {
|
||||
// Just quantize without dithering
|
||||
color.rgb = quantize15bit(color.rgb);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Fog Application
|
||||
// Linear fog blending based on depth
|
||||
// =========================================================================
|
||||
color.rgb = mix(color.rgb, u_fog_color, v_fog);
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
||||
87
src/3d/shaders/ps1_vertex.glsl
Normal file
87
src/3d/shaders/ps1_vertex.glsl
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// PS1-style vertex shader for OpenGL 3.2+
|
||||
// Implements vertex snapping, Gouraud shading, and fog distance calculation
|
||||
|
||||
#version 150 core
|
||||
|
||||
// Uniforms - transform matrices
|
||||
uniform mat4 u_model;
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_projection;
|
||||
|
||||
// Uniforms - PS1 effects
|
||||
uniform vec2 u_resolution; // Internal render resolution for vertex snapping
|
||||
uniform bool u_enable_snap; // Enable vertex snapping to pixel grid
|
||||
uniform float u_fog_start; // Fog start distance
|
||||
uniform float u_fog_end; // Fog end distance
|
||||
|
||||
// Uniforms - lighting
|
||||
uniform vec3 u_light_dir; // Directional light direction (normalized)
|
||||
uniform vec3 u_ambient; // Ambient light color
|
||||
|
||||
// Attributes
|
||||
in vec3 a_position;
|
||||
in vec2 a_texcoord;
|
||||
in vec3 a_normal;
|
||||
in vec4 a_color;
|
||||
|
||||
// Varyings - passed to fragment shader
|
||||
out vec4 v_color; // Gouraud-shaded vertex color
|
||||
noperspective out vec2 v_texcoord; // Texture coordinates (affine interpolation!)
|
||||
out float v_fog; // Fog factor (0 = no fog, 1 = full fog)
|
||||
|
||||
void main() {
|
||||
// Transform vertex to clip space
|
||||
vec4 worldPos = u_model * vec4(a_position, 1.0);
|
||||
vec4 viewPos = u_view * worldPos;
|
||||
vec4 clipPos = u_projection * viewPos;
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Vertex Snapping
|
||||
// The PS1 had limited precision for vertex positions, causing vertices
|
||||
// to "snap" to a grid, creating the characteristic jittery look.
|
||||
// =========================================================================
|
||||
if (u_enable_snap) {
|
||||
// Convert to NDC
|
||||
vec4 ndc = clipPos;
|
||||
ndc.xyz /= ndc.w;
|
||||
|
||||
// Snap to pixel grid based on render resolution
|
||||
vec2 grid = u_resolution * 0.5;
|
||||
ndc.xy = floor(ndc.xy * grid + 0.5) / grid;
|
||||
|
||||
// Convert back to clip space
|
||||
ndc.xyz *= clipPos.w;
|
||||
clipPos = ndc;
|
||||
}
|
||||
|
||||
gl_Position = clipPos;
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Gouraud Shading
|
||||
// Per-vertex lighting was used on PS1 due to hardware limitations.
|
||||
// This creates characteristic flat-shaded polygons.
|
||||
// =========================================================================
|
||||
vec3 worldNormal = mat3(u_model) * a_normal;
|
||||
worldNormal = normalize(worldNormal);
|
||||
|
||||
// Simple directional light + ambient
|
||||
float diffuse = max(dot(worldNormal, -u_light_dir), 0.0);
|
||||
vec3 lighting = u_ambient + vec3(diffuse);
|
||||
|
||||
// Apply lighting to vertex color
|
||||
v_color = vec4(a_color.rgb * lighting, a_color.a);
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Affine Texture Mapping
|
||||
// Using 'noperspective' qualifier disables perspective-correct interpolation
|
||||
// This creates the characteristic texture warping on large polygons
|
||||
// =========================================================================
|
||||
v_texcoord = a_texcoord;
|
||||
|
||||
// =========================================================================
|
||||
// Fog Distance Calculation
|
||||
// Calculate linear fog factor based on view-space depth
|
||||
// =========================================================================
|
||||
float depth = -viewPos.z; // View space depth (positive)
|
||||
v_fog = clamp((depth - u_fog_start) / (u_fog_end - u_fog_start), 0.0, 1.0);
|
||||
}
|
||||
90
src/3d/shaders/ps1_vertex_es2.glsl
Normal file
90
src/3d/shaders/ps1_vertex_es2.glsl
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// PS1-style vertex shader for OpenGL ES 2.0 / WebGL 1.0
|
||||
// Implements vertex snapping, Gouraud shading, and fog distance calculation
|
||||
|
||||
precision mediump float;
|
||||
|
||||
// Uniforms - transform matrices
|
||||
uniform mat4 u_model;
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_projection;
|
||||
|
||||
// Uniforms - PS1 effects
|
||||
uniform vec2 u_resolution; // Internal render resolution for vertex snapping
|
||||
uniform bool u_enable_snap; // Enable vertex snapping to pixel grid
|
||||
uniform float u_fog_start; // Fog start distance
|
||||
uniform float u_fog_end; // Fog end distance
|
||||
|
||||
// Uniforms - lighting
|
||||
uniform vec3 u_light_dir; // Directional light direction (normalized)
|
||||
uniform vec3 u_ambient; // Ambient light color
|
||||
|
||||
// Attributes
|
||||
attribute vec3 a_position;
|
||||
attribute vec2 a_texcoord;
|
||||
attribute vec3 a_normal;
|
||||
attribute vec4 a_color;
|
||||
|
||||
// Varyings - passed to fragment shader
|
||||
varying vec4 v_color; // Gouraud-shaded vertex color
|
||||
varying vec2 v_texcoord; // Texture coordinates (multiplied by w for affine trick)
|
||||
varying float v_w; // Clip space w for affine mapping restoration
|
||||
varying float v_fog; // Fog factor (0 = no fog, 1 = full fog)
|
||||
|
||||
void main() {
|
||||
// Transform vertex to clip space
|
||||
vec4 worldPos = u_model * vec4(a_position, 1.0);
|
||||
vec4 viewPos = u_view * worldPos;
|
||||
vec4 clipPos = u_projection * viewPos;
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Vertex Snapping
|
||||
// The PS1 had limited precision for vertex positions, causing vertices
|
||||
// to "snap" to a grid, creating the characteristic jittery look.
|
||||
// =========================================================================
|
||||
if (u_enable_snap) {
|
||||
// Convert to NDC
|
||||
vec4 ndc = clipPos;
|
||||
ndc.xyz /= ndc.w;
|
||||
|
||||
// Snap to pixel grid based on render resolution
|
||||
vec2 grid = u_resolution * 0.5;
|
||||
ndc.xy = floor(ndc.xy * grid + 0.5) / grid;
|
||||
|
||||
// Convert back to clip space
|
||||
ndc.xyz *= clipPos.w;
|
||||
clipPos = ndc;
|
||||
}
|
||||
|
||||
gl_Position = clipPos;
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Gouraud Shading
|
||||
// Per-vertex lighting was used on PS1 due to hardware limitations.
|
||||
// This creates characteristic flat-shaded polygons.
|
||||
// =========================================================================
|
||||
vec3 worldNormal = mat3(u_model) * a_normal;
|
||||
worldNormal = normalize(worldNormal);
|
||||
|
||||
// Simple directional light + ambient
|
||||
float diffuse = max(dot(worldNormal, -u_light_dir), 0.0);
|
||||
vec3 lighting = u_ambient + vec3(diffuse);
|
||||
|
||||
// Apply lighting to vertex color
|
||||
v_color = vec4(a_color.rgb * lighting, a_color.a);
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Affine Texture Mapping Trick
|
||||
// GLES2 doesn't have 'noperspective' interpolation, so we manually
|
||||
// multiply texcoords by w here and divide by w in fragment shader.
|
||||
// This creates the characteristic texture warping on large polygons.
|
||||
// =========================================================================
|
||||
v_texcoord = a_texcoord * clipPos.w;
|
||||
v_w = clipPos.w;
|
||||
|
||||
// =========================================================================
|
||||
// Fog Distance Calculation
|
||||
// Calculate linear fog factor based on view-space depth
|
||||
// =========================================================================
|
||||
float depth = -viewPos.z; // View space depth (positive)
|
||||
v_fog = clamp((depth - u_fog_start) / (u_fog_end - u_fog_start), 0.0, 1.0);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue