158 lines
5.7 KiB
Metal
158 lines
5.7 KiB
Metal
|
|
// Shaders.metal
|
||
|
|
// BlenderiOS viewport shaders — cube, infinite grid, axis lines.
|
||
|
|
|
||
|
|
#include <metal_stdlib>
|
||
|
|
#include "ShaderTypes.h"
|
||
|
|
|
||
|
|
using namespace metal;
|
||
|
|
|
||
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
|
// MARK: - Mesh (default cube)
|
||
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
|
|
||
|
|
struct MeshVertexOut {
|
||
|
|
float4 position [[position]];
|
||
|
|
float3 worldPos;
|
||
|
|
float3 worldNormal;
|
||
|
|
float4 color;
|
||
|
|
};
|
||
|
|
|
||
|
|
vertex MeshVertexOut mesh_vertex(
|
||
|
|
const device VertexIn *vertices [[buffer(0)]],
|
||
|
|
constant Uniforms &uniforms [[buffer(1)]],
|
||
|
|
uint vid [[vertex_id]]
|
||
|
|
) {
|
||
|
|
MeshVertexOut out;
|
||
|
|
float4 worldPos = uniforms.modelMatrix * float4(vertices[vid].position, 1.0);
|
||
|
|
out.worldPos = worldPos.xyz;
|
||
|
|
out.position = uniforms.projectionMatrix * uniforms.viewMatrix * worldPos;
|
||
|
|
out.worldNormal = (uniforms.normalMatrix * float4(vertices[vid].normal, 0.0)).xyz;
|
||
|
|
out.color = vertices[vid].color;
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
fragment float4 mesh_fragment(
|
||
|
|
MeshVertexOut in [[stage_in]],
|
||
|
|
constant SceneConstants &scene [[buffer(0)]]
|
||
|
|
) {
|
||
|
|
float3 N = normalize(in.worldNormal);
|
||
|
|
float3 L = normalize(-scene.lightDirection);
|
||
|
|
|
||
|
|
float ambient = scene.ambientIntensity;
|
||
|
|
float diffuse = scene.diffuseIntensity * max(dot(N, L), 0.0);
|
||
|
|
|
||
|
|
// Rim light for that Blender viewport feel
|
||
|
|
float3 V = normalize(scene.cameraPosition - in.worldPos);
|
||
|
|
float rim = pow(1.0 - max(dot(N, V), 0.0), 3.0) * 0.15;
|
||
|
|
|
||
|
|
float3 lit = in.color.rgb * (ambient + diffuse) + rim;
|
||
|
|
return float4(lit, in.color.a);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
|
// MARK: - Wireframe overlay
|
||
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
|
|
||
|
|
struct WireVertexOut {
|
||
|
|
float4 position [[position]];
|
||
|
|
float4 color;
|
||
|
|
};
|
||
|
|
|
||
|
|
vertex WireVertexOut wire_vertex(
|
||
|
|
const device VertexIn *vertices [[buffer(0)]],
|
||
|
|
constant Uniforms &uniforms [[buffer(1)]],
|
||
|
|
uint vid [[vertex_id]]
|
||
|
|
) {
|
||
|
|
WireVertexOut out;
|
||
|
|
// Slight bias toward camera to sit on top of solid faces
|
||
|
|
float4 worldPos = uniforms.modelMatrix * float4(vertices[vid].position, 1.0);
|
||
|
|
float4 clipPos = uniforms.projectionMatrix * uniforms.viewMatrix * worldPos;
|
||
|
|
clipPos.z -= 0.0001;
|
||
|
|
out.position = clipPos;
|
||
|
|
out.color = float4(0.0, 0.0, 0.0, 0.35);
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
fragment float4 wire_fragment(WireVertexOut in [[stage_in]]) {
|
||
|
|
return in.color;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
|
// MARK: - Infinite grid
|
||
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
|
|
||
|
|
struct GridVertexOut {
|
||
|
|
float4 position [[position]];
|
||
|
|
float3 worldPos;
|
||
|
|
};
|
||
|
|
|
||
|
|
vertex GridVertexOut grid_vertex(
|
||
|
|
uint vid [[vertex_id]],
|
||
|
|
constant Uniforms &uniforms [[buffer(0)]]
|
||
|
|
) {
|
||
|
|
// Fullscreen quad on the XZ plane
|
||
|
|
float2 corners[6] = {
|
||
|
|
{-1, -1}, { 1, -1}, { 1, 1},
|
||
|
|
{-1, -1}, { 1, 1}, {-1, 1}
|
||
|
|
};
|
||
|
|
float scale = 50.0;
|
||
|
|
float3 worldPos = float3(corners[vid].x * scale, 0.0, corners[vid].y * scale);
|
||
|
|
|
||
|
|
GridVertexOut out;
|
||
|
|
out.worldPos = worldPos;
|
||
|
|
out.position = uniforms.projectionMatrix * uniforms.viewMatrix * float4(worldPos, 1.0);
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
fragment float4 grid_fragment(
|
||
|
|
GridVertexOut in [[stage_in]],
|
||
|
|
constant GridConstants &grid [[buffer(0)]],
|
||
|
|
constant SceneConstants &scene [[buffer(1)]]
|
||
|
|
) {
|
||
|
|
float2 coord = in.worldPos.xz / grid.gridSpacing;
|
||
|
|
float2 deriv = fwidth(coord);
|
||
|
|
float2 gridLine = abs(fract(coord - 0.5) - 0.5) / deriv;
|
||
|
|
float line = min(gridLine.x, gridLine.y);
|
||
|
|
float alpha = 1.0 - min(line, 1.0);
|
||
|
|
|
||
|
|
// Fade with distance from camera
|
||
|
|
float dist = length(in.worldPos - scene.cameraPosition);
|
||
|
|
float fade = 1.0 - smoothstep(grid.gridFadeDistance * 0.5,
|
||
|
|
grid.gridFadeDistance, dist);
|
||
|
|
alpha *= fade * 0.3;
|
||
|
|
|
||
|
|
// Highlight X axis (red) and Z axis (green-ish blue)
|
||
|
|
float4 color = grid.color;
|
||
|
|
if (abs(in.worldPos.z) < deriv.y * grid.gridSpacing * 0.5) {
|
||
|
|
color = float4(0.78, 0.14, 0.22, 1.0); // X axis red
|
||
|
|
alpha = fade * 0.8;
|
||
|
|
}
|
||
|
|
if (abs(in.worldPos.x) < deriv.x * grid.gridSpacing * 0.5) {
|
||
|
|
color = float4(0.22, 0.45, 0.78, 1.0); // Z axis blue
|
||
|
|
alpha = fade * 0.8;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (alpha < 0.005) discard_fragment();
|
||
|
|
return float4(color.rgb, alpha);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
|
// MARK: - Origin axis gizmo (3 lines)
|
||
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
|
|
||
|
|
vertex WireVertexOut axis_vertex(
|
||
|
|
const device VertexIn *vertices [[buffer(0)]],
|
||
|
|
constant Uniforms &uniforms [[buffer(1)]],
|
||
|
|
uint vid [[vertex_id]]
|
||
|
|
) {
|
||
|
|
WireVertexOut out;
|
||
|
|
float4 worldPos = float4(vertices[vid].position, 1.0);
|
||
|
|
out.position = uniforms.projectionMatrix * uniforms.viewMatrix * worldPos;
|
||
|
|
out.color = vertices[vid].color;
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
fragment float4 axis_fragment(WireVertexOut in [[stage_in]]) {
|
||
|
|
return in.color;
|
||
|
|
}
|