Metal shaders for iOS and macOS
Metal Shading Language is based on C++, and it is what you use for graphics on iPhone, iPad and Mac. The pixel code reads almost exactly like HLSL. The export gives you a complete .metal file with a full-screen vertex function and a fragment function that calls yours.
Things to know about Metal Shading Language
- Types look like HLSL:
float2,float3,float4; blend withmix. - Fragment functions are marked
fragmentand get their inputs through[[stage_in]]and buffer bindings. - Your helper functions go inside a struct in the export, so they can read
timeandaspect. - Pass a small uniforms struct with
setFragmentBytesin your render encoder.
Cheat sheet
| Idea | Write |
|---|---|
| Vectors | float2 float3 float4 |
| Declare a value | float d = length(p); |
| A function | float circle(float2 p, float r) { ... } |
| Matrices | float2x2, multiply with M * v (this site writes v * M for lesson rotations) |
| Blend | mix(a, b, t) |
| Fractional part | fract(x) |
| Clamp to 0..1 | saturate(x) |
| Remainder | fmod(a, b) (keeps the sign of a) |
| Choose | c ? a : b |
| Angle of a vector | atan2(y, x) |
| Time in seconds | time |
| Pixel position | uv (0..1, bottom-left) |
| Width / height | aspect |
| Entry point | float4 frag(float2 uv) { return …; } |
Handy built-ins: sin cos abs sign floor ceil fract step smoothstep clamp min max pow exp sqrt length distance dot cross normalize reflect
Gotchas
- Every pixel runs on its own, with no memory of other pixels.
- Declare a function above the place you use it.
- Prefer 2.0 over 2 in maths. This site fixes most cases for you; real engines may not.
- Loops need a small fixed count: for (int i = 0; i < 5; i++).
- Metal is C++ based. The export puts your functions in a struct so they can read time and aspect.
Example: a glowing ring
The same shader as it is written for Apple (iOS, macOS):
float4 frag(float2 uv)
{
float2 p = (uv - 0.5) * float2(aspect, 1.0);
float d = length(p);
float m = 1.0 - smoothstep(0.0, 0.02, abs(d - 0.3));
float3 bg = float3(0.08, 0.08, 0.12);
float3 fg = float3(0.4, 0.9, 1.0);
return float4(mix(bg, fg, m), 1.0);
} Exporting to Metal
Add the file to your Xcode project. Draw 3 vertices with sb_vertex / sb_fragment and pass a Uniforms struct in buffer 0 (setFragmentBytes). Your functions live inside a struct so they can read time and aspect, the same trick as the Unreal export.
// ShaderBattle: Glowing ring
#include <metal_stdlib>
using namespace metal;
struct ShaderBattle
{
float time;
float aspect;
float4 frag(float2 uv)
{
float2 p = (uv - 0.5) * float2(aspect, 1.0);
float d = length(p);
float m = 1.0 - smoothstep(0.0, 0.02, abs(d - 0.3));
float3 bg = float3(0.08, 0.08, 0.12);
float3 fg = float3(0.4, 0.9, 1.0);
return float4(mix(bg, fg, m), 1.0);
}
};
struct Uniforms { float time; float aspect; };
struct VertexOut { float4 position [[position]]; float2 uv; };
// one triangle that covers the screen
vertex VertexOut sb_vertex(uint vid [[vertex_id]])
{
float2 p = float2((vid << 1) & 2, vid & 2);
VertexOut o;
o.position = float4(p * 2.0 - 1.0, 0.0, 1.0);
o.uv = p; // (0,0) bottom-left
return o;
}
fragment float4 sb_fragment(VertexOut v [[stage_in]], constant Uniforms& u [[buffer(0)]])
{
ShaderBattle sb { u.time, u.aspect };
return sb.frag(v.uv);
} Questions
Do I need a Mac to learn Metal shading?
Not to practise the shader code: it runs in your browser. You will need Xcode to build and run the exported file.
Other languages
Unity (Built-in) · Unity (URP) · Unreal · Shadertoy / WebGL · OpenGL (LWJGL, raylib) · three.js · GameMaker · LÖVE (Lua) · Godot · WebGPU / Bevy