Vertex and Fragment Shaders (Metal Part 3)

z4gon
z4gon

Modeling the vertices data as structs in the CPU side, then passing this data through a buffer to the GPU. Modeling the data structures for vertex and fragment functions in the GPU side. Accessing the interpolated values after rasterization to render corresponding colors for pixels on screen.

Cover Image for Vertex and Fragment Shaders (Metal Part 3)

Source Code

See Project in GitHub 👩‍💻

References


Table of Content


Vertex Struct in CPU

We will expand the data definition for the vertices, including not only the position in screen space, but also the color of the vertices.

1struct Vertex { 2 var position: SIMD3<Float> 3 var color: SIMD4<Float> 4}

Picture

Image Source 🔗

The vertices array now holds objects of this type, and we also need to account for this when calculating the memory stride.

1var vertices: [Vertex]! 2 3func createVertices() { 4 // counter clock wise to define the face 5 vertices = [ 6 Vertex(position: SIMD3<Float>(0, 1, 0), color: SIMD4<Float>(0, 0, 1, 1)), // top mid 7 Vertex(position: SIMD3<Float>(-1, -1, 0), color: SIMD4<Float>(0, 1, 0, 1)), // bot left 8 Vertex(position: SIMD3<Float>(1, -1, 0), color: SIMD4<Float>(1, 0, 0, 1)), // top right 9 ] 10} 11 12func createBuffers() { 13 let vertexMemSize = MemoryLayout<Vertex>.stride 14 15 vertexBuffer = device?.makeBuffer(bytes: vertices, length: vertexMemSize * vertices.count, options: []) 16}

Vertex Shader

Similarly, in the GPU side, we need to model the data with the closest data types. We will read elements off of the buffer, and will represent them with these structures.

The attribute [[ position ]] prevents the values interpolation from happening on it. Color will be interpolated per fragment, depending on its position in the triangle face, respect to the defining vertices.

The interpolation happens at the Rasterizer stage.

Picture

Image Source 🔗

1struct VertexData { 2 float3 position; 3 float4 color; 4}; 5 6struct FragmentData { 7 float4 position [[ position ]]; // use position attribute to prevent interpolation of the value 8 float4 color; 9};

Now the vertex funciton takes in an array of VertexData.

We also need to create a new FragmentData structure and populate it with the corresponding values.

1vertex FragmentData basic_vertex_shader( 2 device VertexData *vertices [[ buffer(0) ]], // access the vertices buffer at buffer with index 0 3 uint vertexID [[ vertex_id ]] // get the vertex id, which corresponds to the index of the vertex in the buffer 4){ 5 VertexData IN = vertices[vertexID]; 6 7 FragmentData OUT; 8 9 OUT.position = float4(IN.position, 1); // return the vertex position in homogeneous screen space 10 OUT.color = IN.color; 11 12 return OUT; // return the vertex position in homogeneous screen space 13}

Picture

Image Source 🔗


Fragment Shader

The fragment function now takes in a FragmentData structure.

We get an interpolated color coming in, which we can use to paint the corresponding pixel.

1fragment half4 basic_fragment_shader(FragmentData IN [[ stage_in ]]){ 2 float4 color = IN.color; 3 return half4(color.r, color.g, color.b, color.a); 4}

Picture

Image Source 🔗


Result

Picture