struct CameraUniform { view_proj: mat4x4, model: mat4x4, camera_pos: vec3, _pad: f32, }; struct LightUniform { direction: vec3, _pad0: f32, color: vec3, ambient_strength: f32, }; @group(0) @binding(0) var camera: CameraUniform; @group(0) @binding(1) var light: LightUniform; @group(1) @binding(0) var t_diffuse: texture_2d; @group(1) @binding(1) var s_diffuse: sampler; struct VertexInput { @location(0) position: vec3, @location(1) normal: vec3, @location(2) uv: vec2, @location(3) tangent: vec4, }; struct InstanceInput { @location(4) model_0: vec4, @location(5) model_1: vec4, @location(6) model_2: vec4, @location(7) model_3: vec4, @location(8) color: vec4, }; struct VertexOutput { @builtin(position) clip_position: vec4, @location(0) world_normal: vec3, @location(1) world_pos: vec3, @location(2) uv: vec2, @location(3) inst_color: vec4, }; @vertex fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VertexOutput { let inst_model = mat4x4( instance.model_0, instance.model_1, instance.model_2, instance.model_3, ); var out: VertexOutput; let world_pos = inst_model * vec4(vertex.position, 1.0); out.world_pos = world_pos.xyz; out.world_normal = normalize((inst_model * vec4(vertex.normal, 0.0)).xyz); out.clip_position = camera.view_proj * world_pos; out.uv = vertex.uv; out.inst_color = instance.color; return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let tex_color = textureSample(t_diffuse, s_diffuse, in.uv); let normal = normalize(in.world_normal); let light_dir = normalize(-light.direction); let ndotl = max(dot(normal, light_dir), 0.0); let diffuse = light.color * ndotl; let view_dir = normalize(camera.camera_pos - in.world_pos); let half_dir = normalize(light_dir + view_dir); let spec = pow(max(dot(normal, half_dir), 0.0), 32.0); let specular = light.color * spec * 0.3; let ambient = light.color * light.ambient_strength; let lit = (ambient + diffuse + specular) * tex_color.rgb * in.inst_color.rgb; return vec4(lit, tex_color.a * in.inst_color.a); }