-- Basic vertex + fragment shader pair -- Demonstrates: structs, uniforms, entry points, @varying, auto-location struct VertexInput position: vec3 normal: vec3 uv: vec2 end -- @varying marks a struct shared between vertex output and fragment input. -- Fragment inputs automatically skip @builtin fields. @varying struct Interpolated @builtin(position) position: vec4 uv: vec2 normal: vec3 end struct Uniforms model: mat4 view_proj: mat4 normal_mat: mat4 end -- Binding attributes are optional: @group defaults to 0, @binding auto-increments uniform uniforms: Uniforms @entry(vertex) function vs_main(input: VertexInput) -> Interpolated let world_pos = uniforms.model * vec4(input.position, 1.0) let clip_pos = uniforms.view_proj * world_pos let world_normal = normalize((uniforms.normal_mat * vec4(input.normal, 0.0)).xyz) return Interpolated { position = clip_pos, uv = input.uv, normal = world_normal, } end struct FragmentOutput @location(0) color: vec4 end -- sampler2D is automatically split into texture + sampler bindings @group(1) @binding(0) uniform albedo_tex: sampler2D @entry(fragment) function fs_main(input: Interpolated) -> FragmentOutput let albedo = sample(albedo_tex, input.uv) let light = max(dot(input.normal, normalize(vec3(1.0, 1.0, 0.0))), 0.0) let color = albedo * (light * 0.8 + 0.2) return FragmentOutput { color = vec4(color.rgb, 1.0), } end