SwiftUI Shader Rainbow Ring
With SwiftUI it is easy to add Metal shaders and with this example a simple rainbow ring is created. The shader is implemented in a `.metal` file. The function name is defined as `rainbowRing`. To use it in SwiftUI, the modifier `.visualEffect` is used. `rainbowRing` is automatically available as part of the `ShaderLibrary` type.
The ring is determined by computing the distance of the points to be drawn to the center. If that distance is within a small interval, a rainbow color is returned, otherwise the original color is used. To build the rainbow the hue component of a color is determined by calculating the angle of the vector pointing from the center to the position of a ring pixel. An offset allows to shift the angle, which effectively rotates the rainbow ring.
import SwiftUI
struct ShaderRainbowRingView: View {
@State private var offset: Double = 0.5
var body: some View {
VStack {
Text("Rainbow Ring")
.font(.largeTitle)
VStack {
Spacer()
ZStack {
Color.white
Image(systemName: "cloud.sun")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(32)
.foregroundStyle(Color.gray)
}
.frame(width: 200, height: 200)
.visualEffect({ [offset] content, geo in
content
.colorEffect(
ShaderLibrary.rainbowRing(.float2(geo.size), .float(offset))
)
})
Spacer()
Slider(value: $offset, label: { Text("Rainbow Offset") })
.padding()
Text("\(offset)")
}
}
}
}
#Preview {
ShaderRainbowRingView()
}
#include
using namespace metal;
#include
using namespace metal;
half3 hsv2rgb(half3 c) {
half4 K = half4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
half3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
[[ stitchable ]]
half4 rainbowRing(
float2 position,
half4 color,
float2 size,
float offset
) {
float2 center = size * 0.5;
float2 uv2 = (position - center) / size * 2;
float dist = length(uv2);
if (0.9 < dist && dist < 1.0) {
float angle = atan2(uv2.y, uv2.x);
float norm = (angle / M_PI_F + 1) / 2.0;
float hue = fract(norm + offset);
half3 rgb = hsv2rgb(half3(hue, 1.0, 1.0));
return half4(rgb.rgb, 1.0);
} else {
return color;
}
}