Metal Tutorial with Swift 3 Part 4: Lighting
In this fourth part of our Metal tutorial series, learn how to light 3D objects using the Phong lighting model. By Andrew Kharchyshyn.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Metal Tutorial with Swift 3 Part 4: Lighting
35 mins
- Getting Started
- Phong Lighting Model
- Project Setup
- Ambient Lighting Overview
- Adding Ambient Lighting
- Creating a Light Structure
- Passing the Light Data to the GPU
- Modifying the Shaders to Accept the Light Data
- Adding the Ambient Light Calculation
- Left in the Dark
- Diffuse Lighting Overview
- Introducing Normals
- Introducing Dot Products
- Introducing Diffuse Lighting
- Adding Diffuse Lighting
- Adding Normal Data
- Passing the Normal Data to the GPU
- Adding Diffuse Lighting Data
- Adding the Diffuse Lighting Calculation
- Specular Lighting Overview
- Adding Specular Lighting
- Byte Alignment
- Adding the Specular Lighting Calculation
- Where to Go From Here?
Byte Alignment
The problem you faced is a bit complicated. In your Light
structure, size
returns MemoryLayout
In Shaders.metal
, your Light
structure should also be 40 bytes, because that’s exactly the same structure. Right?
Yes — but that’s not how the GPU works. The GPU operates with memory chunks 16 bytes in size..
Replace the Light
structure in Shaders.metal with this:
struct Light{
packed_float3 color; // 0 - 2
float ambientIntensity; // 3
packed_float3 direction; // 4 - 6
float diffuseIntensity; // 7
float shininess; // 8
float specularIntensity; // 9
/*
_______________________
|0 1 2 3|4 5 6 7|8 9 |
-----------------------
| | | |
| chunk0| chunk1| chunk2|
*/
};
Even though you have 10 floats, the GPU is still allocating memory for 12 floats — which gives you a mismatch error.
To fix this crash, you need to increase the Light
structure size to match those 3 chunks (12 floats).
Open Light.swift and change size()
to return 12 instead of 10:
static func size() -> Int {
return MemoryLayout<Float>.size * 12
}
Build and run. Everything should work as expected:
Adding the Specular Lighting Calculation
Now that you’re passing the data through, it’s time for the calculation itself.
Open Shaders.metal, and add the following value to the VertexOut
struct, right below position
:
float3 fragmentPosition;
In the vertex shader, find this line:
VertexOut.position = proj_Matrix * mv_Matrix * float4(VertexIn.position,1);
…and add this line right below it:
VertexOut.fragmentPosition = (mv_Matrix * float4(VertexIn.position,1)).xyz;
This new “fragment position” value does just what it says: it’s a fragment position related to a camera. You’ll use this value to get the eye vector.
Now add the following under the diffuse calculations in the fragment shader:
//Specular
float3 eye = normalize(interpolated.fragmentPosition); //1
float3 reflection = reflect(light.direction, interpolated.normal); // 2
float specularFactor = pow(max(0.0, dot(reflection, eye)), light.shininess); //3
float4 specularColor = float4(light.color * light.specularIntensity * specularFactor ,1.0);//4
This is the same algorithm you learned about earlier:
- Get the eye vector.
- Calculate the reflection vector of the light across the current fragment.
- Calculate the specular factor.
- Combine all the values above to get the specular color.
Now with modify the return
line in the fragment shader to match the following:
return color * (ambientColor + diffuseColor + specularColor);
Build and run.
Enjoy your new shiny object!
Where to Go From Here?
Here is the final example project from this iOS Metal Tutorial.
Nicely done! Take a moment to review what you’ve done in this tutorial:
- You created a Light structure to send with matrices in a uniform buffer to the GPU.
- You modified the BufferProvider class to handle Light data.
- You implemented ambient lighting, diffuse lighting, and specular lighting.
- You learned how the GPU handles memory, and fixed your crash.
Go for a walk, take a nap or play around with your app a little — you totally deserve some rest! :]
Don’t feel tired? Then feel free to check out some of these great resources:
- Apple’s Metal for Developers page, which has tons of links to documentation, videos and sample code
- Apple’s Metal Programming Guide
- Apple’s Metal Shading Language Guide
- The Metal videos from WWDC 2014
- The Metal videos from WWDC 2015
- MetalByExample.com.
You also might enjoy the Beginning Metal course on our site, where we explain these same concepts in video form, but with even more detail.
Thank you for joining me for this tour through Metal. As you can see, it’s a powerful technology that’s relatively easy to implement — once you understand how it works!
If you have questions, comments or Metal discoveries to share, please leave them in the comments below!