├── .DS_Store
├── .gitignore
├── MetalPlayground.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ └── MetalPlayground.xcscheme
└── xcuserdata
│ └── raheel.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── xcschememanagement.plist
├── MetalPlayground
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ └── reset.imageset
│ │ ├── Contents.json
│ │ └── reset.pdf
├── Base.lproj
│ └── Main.storyboard
├── Info.plist
├── MetalPlayground.entitlements
├── MetalPlaygroundApp.swift
├── R.png
├── Rendering
│ ├── MathUtils.swift
│ ├── MetalView.swift
│ └── Renderer.swift
├── Root
│ ├── ConfigView.swift
│ ├── MetalSwiftUIView.swift
│ ├── RootView.swift
│ └── ViewModel.swift
├── Scenes
│ ├── BookShaders
│ │ ├── 06Colors.metal
│ │ ├── 07ShapePrimitives.metal
│ │ ├── 07Shapes.metal
│ │ ├── 08LeftRightTiler.metal
│ │ ├── BoSShapes.swift
│ │ └── Shaping.metal
│ ├── Explorations
│ │ ├── 05Algorithmic.metal
│ │ ├── 07FuturisticUI.metal
│ │ ├── AudioViz.metal
│ │ ├── AudioViz.swift
│ │ ├── CellularNoise.metal
│ │ ├── Girih.metal
│ │ ├── Girih.swift
│ │ ├── HappyJumping.metal
│ │ ├── PolarExperiments.metal
│ │ ├── RaysConfig.swift
│ │ ├── ShaderToyDistortions.metal
│ │ ├── ShaderToySmiley.metal
│ │ ├── ShaderToyStarField.metal
│ │ ├── Simplest3D.metal
│ │ └── Starfield.swift
│ ├── Helpers.metal
│ ├── JumpingBalls
│ │ ├── JumpingBalls.metal
│ │ └── JumpingBalls.swift
│ ├── MetalHeaders.h
│ ├── Playground.swift
│ ├── ShaderHeaders.h
│ └── SimonShaders
│ │ ├── FractAndFriends.metal
│ │ ├── SimonCloudyDay.metal
│ │ ├── SimonNoiseIntro.metal
│ │ └── SimonSDFs.metal
├── UI
│ └── TitledSlider.swift
├── raga.mp3
└── wallpaper.png
├── README.md
└── images
├── circles.png
├── colors.png
├── futuristic-UI.gif
├── futuristic-UI.mov
├── leftright-tiler.gif
├── leftright-tiler.mov
└── starfield.png
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | MetalPlayground.xcodeproj/project.xcworkspace/xcuserdata/raheel.xcuserdatad/UserInterfaceState.xcuserstate
2 |
--------------------------------------------------------------------------------
/MetalPlayground.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MetalPlayground.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MetalPlayground.xcodeproj/xcshareddata/xcschemes/MetalPlayground.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
58 |
59 |
63 |
64 |
68 |
69 |
73 |
74 |
75 |
76 |
82 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/MetalPlayground.xcodeproj/xcuserdata/raheel.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
21 |
22 |
23 |
25 |
37 |
38 |
39 |
41 |
53 |
54 |
55 |
57 |
69 |
70 |
71 |
73 |
85 |
86 |
87 |
89 |
101 |
102 |
103 |
105 |
117 |
118 |
119 |
121 |
133 |
134 |
135 |
137 |
149 |
150 |
151 |
153 |
165 |
166 |
167 |
169 |
181 |
182 |
183 |
185 |
197 |
198 |
199 |
201 |
213 |
214 |
215 |
217 |
229 |
230 |
231 |
233 |
245 |
246 |
247 |
249 |
261 |
262 |
263 |
265 |
277 |
278 |
279 |
281 |
293 |
294 |
295 |
297 |
309 |
310 |
311 |
313 |
325 |
326 |
327 |
329 |
341 |
342 |
343 |
345 |
357 |
358 |
359 |
361 |
373 |
374 |
375 |
377 |
389 |
390 |
391 |
393 |
405 |
406 |
407 |
408 |
409 |
--------------------------------------------------------------------------------
/MetalPlayground.xcodeproj/xcuserdata/raheel.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Glitter.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 1
11 |
12 | MetalPlayground.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | 59C2BD20246A709D00335E59
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/MetalPlayground/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/MetalPlayground/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/MetalPlayground/Assets.xcassets/reset.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "reset.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "template"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MetalPlayground/Assets.xcassets/reset.imageset/reset.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/MetalPlayground/Assets.xcassets/reset.imageset/reset.pdf
--------------------------------------------------------------------------------
/MetalPlayground/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2020 Raheel Ahmad. All rights reserved.
27 | NSSupportsAutomaticTermination
28 |
29 | NSSupportsSuddenTermination
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/MetalPlayground/MetalPlayground.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/MetalPlayground/MetalPlaygroundApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 5/11/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SwiftUI
11 |
12 |
13 | @main
14 | struct MetalPlaygroundApp: App {
15 | var body: some Scene {
16 | WindowGroup {
17 | RootView()
18 | .frame(minWidth: 420)
19 | }
20 | .windowStyle(.hiddenTitleBar)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/MetalPlayground/R.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/MetalPlayground/R.png
--------------------------------------------------------------------------------
/MetalPlayground/Rendering/MathUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MathUtils.swift
3 | // MetalByTutorialsScratch
4 | //
5 | // Created by Raheel Ahmad on 8/23/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import simd
11 | typealias float3 = SIMD3
12 | typealias float4 = SIMD4
13 |
14 | extension float4 {
15 | init(_ v: float3, _ w: Float) {
16 | self.init(x: v.x, y: v.y, z: v.z, w: w)
17 | }
18 |
19 | // RGB color from HSV color (all parameters in range [0, 1])
20 | init(hue: Float, saturation: Float, brightness: Float) {
21 | let c = brightness * saturation
22 | let x = c * (1 - fabsf(fmodf(hue * 6, 2) - 1))
23 | let m = brightness - saturation
24 |
25 | var r: Float = 0
26 | var g: Float = 0
27 | var b: Float = 0
28 | switch hue {
29 | case _ where hue < 0.16667:
30 | r = c; g = x; b = 0
31 | case _ where hue < 0.33333:
32 | r = x; g = c; b = 0
33 | case _ where hue < 0.5:
34 | r = 0; g = c; b = x
35 | case _ where hue < 0.66667:
36 | r = 0; g = x; b = c
37 | case _ where hue < 0.83333:
38 | r = x; g = 0; b = c
39 | case _ where hue <= 1.0:
40 | r = c; g = 0; b = x
41 | default:
42 | break
43 | }
44 |
45 | r += m; g += m; b += m
46 | self.init(x: r, y: g, z: b, w: 1)
47 | }
48 |
49 | var xyz: float3 {
50 | return float3(x, y, z)
51 | }
52 | }
53 |
54 | extension float4x4 {
55 | // MARK:- Translate
56 | init(translation: float3) {
57 | let matrix = float4x4(
58 | [ 1, 0, 0, 0],
59 | [ 0, 1, 0, 0],
60 | [ 0, 0, 1, 0],
61 | [translation.x, translation.y, translation.z, 1]
62 | )
63 | self = matrix
64 | }
65 |
66 | // MARK:- Scale
67 | init(scaling: float3) {
68 | let matrix = float4x4(
69 | [scaling.x, 0, 0, 0],
70 | [ 0, scaling.y, 0, 0],
71 | [ 0, 0, scaling.z, 0],
72 | [ 0, 0, 0, 1]
73 | )
74 | self = matrix
75 | }
76 |
77 | init(scaling: Float) {
78 | self = matrix_identity_float4x4
79 | columns.3.w = 1 / scaling
80 | }
81 |
82 | // MARK:- Rotate
83 | init(rotationX angle: Float) {
84 | let matrix = float4x4(
85 | [1, 0, 0, 0],
86 | [0, cos(angle), sin(angle), 0],
87 | [0, -sin(angle), cos(angle), 0],
88 | [0, 0, 0, 1]
89 | )
90 | self = matrix
91 | }
92 |
93 | init(rotationY angle: Float) {
94 | let matrix = float4x4(
95 | [cos(angle), 0, -sin(angle), 0],
96 | [ 0, 1, 0, 0],
97 | [sin(angle), 0, cos(angle), 0],
98 | [ 0, 0, 0, 1]
99 | )
100 | self = matrix
101 | }
102 |
103 | init(rotationZ angle: Float) {
104 | let matrix = float4x4(
105 | [ cos(angle), sin(angle), 0, 0],
106 | [-sin(angle), cos(angle), 0, 0],
107 | [ 0, 0, 1, 0],
108 | [ 0, 0, 0, 1]
109 | )
110 | self = matrix
111 | }
112 |
113 | init(rotation angle: float3) {
114 | let rotationX = float4x4(rotationX: angle.x)
115 | let rotationY = float4x4(rotationY: angle.y)
116 | let rotationZ = float4x4(rotationZ: angle.z)
117 | self = rotationX * rotationY * rotationZ
118 | }
119 |
120 |
121 | init(rotationAroundAxis axis: float3, by angle: Float) {
122 | let unitAxis = normalize(axis)
123 | let ct = cosf(angle)
124 | let st = sinf(angle)
125 | let ci = 1 - ct
126 | let x = unitAxis.x, y = unitAxis.y, z = unitAxis.z
127 | self.init(columns:(float4( ct + x * x * ci, y * x * ci + z * st, z * x * ci - y * st, 0),
128 | float4(x * y * ci - z * st, ct + y * y * ci, z * y * ci + x * st, 0),
129 | float4(x * z * ci + y * st, y * z * ci - x * st, ct + z * z * ci, 0),
130 | float4( 0, 0, 0, 1)))
131 | }
132 |
133 | init(translationBy v: float3) {
134 | self.init(columns:(float4(1, 0, 0, 0),
135 | float4(0, 1, 0, 0),
136 | float4(0, 0, 1, 0),
137 | float4(v.x, v.y, v.z, 1)))
138 | }
139 |
140 | var upperLeft: float3x3 {
141 | let x = columns.0.xyz
142 | let y = columns.1.xyz
143 | let z = columns.2.xyz
144 | return float3x3(columns: (x, y, z))
145 | }
146 |
147 | init(perspectiveProjectionRHFovY fovy: Float, aspectRatio: Float, nearZ: Float, farZ: Float, lhs: Bool = true) {
148 | let ys = 1 / tanf(fovy * 0.5)
149 | let xs = ys / aspectRatio
150 | let zs = lhs ? farZ / (farZ - nearZ) : farZ / (nearZ - farZ)
151 | let X = float4( xs, 0, 0, 0)
152 | let Y = float4( 0, ys, 0, 0)
153 | let Z = lhs ? float4( 0, 0, zs, 1) : float4( 0, 0, zs, -1)
154 | let W = lhs ? float4( 0, 0, zs * -nearZ, 0) : float4( 0, 0, zs * nearZ, 0)
155 | self.init()
156 | columns = (X,Y,Z,W)
157 | }
158 | }
159 |
160 | func radians_from_degrees(_ degrees: Float) -> Float {
161 | return (degrees / 180) * .pi
162 | }
163 |
--------------------------------------------------------------------------------
/MetalPlayground/Rendering/MetalView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetalView.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/6/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import MetalKit
10 |
11 | final class MetalView: MTKView {
12 | var renderer: Renderer?
13 |
14 | override func viewDidMoveToWindow() {
15 | super.viewDidMoveToWindow()
16 | }
17 |
18 | override func mouseMoved(with event: NSEvent) {
19 | let x = Float(event.locationInWindow.x)
20 | let y = Float(event.locationInWindow.y)
21 | renderer?.mouseLocation = .init(x,y)
22 | }
23 |
24 | override func updateTrackingAreas() {
25 | let area = NSTrackingArea(rect: self.bounds,
26 | options: [NSTrackingArea.Options.activeAlways,
27 | NSTrackingArea.Options.mouseMoved,
28 | NSTrackingArea.Options.enabledDuringMouseDrag],
29 | owner: self,
30 | userInfo: nil)
31 | self.addTrackingArea(area)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/MetalPlayground/Rendering/Renderer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Renderer.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 5/11/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import Metal
10 | import MetalKit
11 |
12 | struct Vertex {
13 | var position: vector_float2;
14 | }
15 |
16 | struct FragmentUniforms {
17 | var time: Float
18 | var screen_width: Float
19 | var screen_height: Float
20 | var screen_scale: Float
21 | var mouseLocation: vector_float2
22 | }
23 |
24 | final class Renderer: NSObject, MTKViewDelegate {
25 | var mouseLocation: vector_float2 = .init(repeating: 0) {
26 | didSet {
27 | let x = mouseLocation.x / uniforms.screen_width * 2 - 0.5
28 | let y = (0.5 - 2 * mouseLocation.y / uniforms.screen_height)
29 | uniforms.mouseLocation = vector_float2(x, y)
30 | }
31 | }
32 | let device: MTLDevice
33 | let queue: MTLCommandQueue
34 | let pixelFormat: MTLPixelFormat = .bgra8Unorm
35 |
36 | static var aspectRatio: Float = 1.0
37 |
38 | private let compileQueue = DispatchQueue.init(label: "Shader compile queue")
39 |
40 | var pipelineState: MTLRenderPipelineState!
41 | private var uniforms: FragmentUniforms = .init(time: 0, screen_width: 0, screen_height: 0, screen_scale: 0, mouseLocation: .init(0,0))
42 |
43 | override init() {
44 | device = MTLCreateSystemDefaultDevice()!
45 | queue = device.makeCommandQueue()!
46 |
47 | super.init()
48 | }
49 |
50 | func setup(_ view: MTKView) {
51 | view.device = device
52 | view.colorPixelFormat = pixelFormat
53 | view.preferredFramesPerSecond = 60
54 | view.delegate = self
55 | uniforms.screen_scale = 2
56 | setupPipeline()
57 | }
58 |
59 | var lastRenderTime: CFTimeInterval? = nil
60 | var currentTime: Double = 0
61 | let gpuLock = DispatchSemaphore(value: 1)
62 |
63 | var scene: Playground = SceneKind.allCases[0].scene {
64 | didSet {
65 | setupPipeline()
66 | }
67 | }
68 |
69 | var vertexBuffer: MTLBuffer?
70 | var isPaused = false
71 |
72 | private func setupPipeline() {
73 | isPaused = true
74 | scene.buildPipeline(device: device, pixelFormat: pixelFormat) { [weak self] pipelineState,vertexBuffer in
75 | self?.isPaused = false
76 | self?.pipelineState = pipelineState
77 | self?.vertexBuffer = vertexBuffer
78 | }
79 | }
80 |
81 | func draw(in view: MTKView) {
82 | guard !isPaused else { return }
83 | guard let commandBuffer = queue.makeCommandBuffer() else { return }
84 | guard let passDescriptor = view.currentRenderPassDescriptor else { return }
85 | guard let pipelineState = self.pipelineState else { return }
86 |
87 | if lastRenderTime == nil, var frame = view.window?.frame {
88 | frame.size = .init(width: 500, height: 500)
89 | view.window?.setFrame(frame, display: true, animate: false)
90 | }
91 |
92 | // update time
93 | let systemTime = CACurrentMediaTime()
94 | let timeDiff = lastRenderTime.map { systemTime - $0 } ?? 0
95 | currentTime += timeDiff
96 | lastRenderTime = systemTime
97 |
98 | uniforms.time = Float(currentTime)
99 |
100 | passDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.3, green: 0.3, blue: 0.4, alpha: 1)
101 | guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) else { return }
102 |
103 | compileQueue.async {
104 | self.compileScenePipeline()
105 | }
106 |
107 | encoder.setRenderPipelineState(pipelineState)
108 |
109 | encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
110 |
111 | let uniformsBuffer = device.makeBuffer(bytes: &uniforms, length: MemoryLayout.size, options: [])
112 | encoder.setFragmentBuffer(uniformsBuffer, offset: 0, index: 0)
113 | scene.setUniforms(device: device, encoder: encoder)
114 |
115 | guard scene.ready else { return }
116 | scene.draw(encoder: encoder)
117 |
118 | encoder.endEncoding()
119 | commandBuffer.present(view.currentDrawable!)
120 | commandBuffer.commit()
121 | }
122 |
123 | var shaderContents = ""
124 | func compileScenePipeline() {
125 | guard scene.liveReloads else { return }
126 | let fm = FileManager()
127 | let filePath = scene.filePath as NSString
128 | let shaderPath: String = filePath.deletingLastPathComponent.appending("/\(scene.fileName).metal")
129 | let helpersPath = (filePath.deletingLastPathComponent) + "/Helpers.metal"
130 | guard
131 | let shaderContentsData = fm.contents(atPath: shaderPath),
132 | let helpersData = fm.contents(atPath: helpersPath),
133 | var shaderContents = String(data: shaderContentsData, encoding: .utf8),
134 | let helperContents = String(data: helpersData, encoding: .utf8)
135 | else {
136 | assertionFailure()
137 | return
138 | }
139 | var shaderContentLines = shaderContents.split(separator: "\n")
140 | if let headerIndex = shaderContentLines.firstIndex(where: { $0 == "#include \"../ShaderHeaders.h\"" }) {
141 | shaderContentLines.remove(at: headerIndex)
142 |
143 | var headerLines = helperContents.split(separator: "\n")
144 | if let helperHeaderIndex = headerLines.firstIndex(where: { String($0) == "#include \"ShaderHeaders.h\"" }) {
145 | headerLines.remove(at: helperHeaderIndex)
146 | }
147 | shaderContentLines.insert(contentsOf: headerLines, at: headerIndex)
148 | }
149 | shaderContents = shaderContentLines.joined(separator: "\n")
150 |
151 | let oldValue = self.shaderContents
152 | self.shaderContents = shaderContents
153 |
154 | guard shaderContents != oldValue else {
155 | return
156 | }
157 |
158 | do {
159 | let pipelineDesc = MTLRenderPipelineDescriptor()
160 | let library = try device.makeLibrary(source: shaderContents, options: nil)
161 | pipelineDesc.vertexFunction = library.makeFunction(name: scene.vertexFuncName)
162 | pipelineDesc.fragmentFunction = library.makeFunction(name: scene.fragmentFuncName)
163 | pipelineDesc.colorAttachments[0].pixelFormat = pixelFormat
164 |
165 | let pipeline = (try? device.makeRenderPipelineState(descriptor: pipelineDesc))!
166 |
167 | let vertexBuffer = device.makeBuffer(bytes: scene.basicVertices, length: MemoryLayout.stride * scene.basicVertices.count, options: [])
168 | DispatchQueue.main.async {
169 | self.pipelineState = pipeline
170 | self.vertexBuffer = vertexBuffer
171 | }
172 | } catch {
173 | print(error.localizedDescription)
174 | }
175 |
176 | }
177 |
178 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
179 | uniforms.screen_width = Float(size.width)
180 | uniforms.screen_height = Float(size.height)
181 | Self.aspectRatio = Float(uniforms.screen_width/uniforms.screen_height)
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/MetalPlayground/Root/ConfigView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigView.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 9/22/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ConfigView: NSViewRepresentable {
12 | @Environment(ViewModel.self) var viewModel
13 |
14 | func makeNSView(context: Context) -> NSView {
15 | NSView()
16 | }
17 |
18 | func updateNSView(_ nsView: NSView, context: Context) {
19 | nsView.subviews.forEach { $0.removeFromSuperview() }
20 | if let view = viewModel.scene.view {
21 | nsView.addSubview(view)
22 | nsView.translatesAutoresizingMaskIntoConstraints = false
23 | view.translatesAutoresizingMaskIntoConstraints = false
24 | NSLayoutConstraint.activate([
25 | view.leadingAnchor.constraint(equalTo: nsView.leadingAnchor),
26 | view.trailingAnchor.constraint(equalTo: nsView.trailingAnchor),
27 | view.topAnchor.constraint(equalTo: nsView.topAnchor),
28 | view.bottomAnchor.constraint(equalTo: nsView.bottomAnchor),
29 | ])
30 | }
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/MetalPlayground/Root/MetalSwiftUIView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetalSwiftUIView.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 9/22/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct MetalSwiftView: NSViewRepresentable {
12 | @Environment(ViewModel.self) var viewModel
13 |
14 | init() {
15 | }
16 |
17 | func makeNSView(context: Context) -> MetalView {
18 | MetalView()
19 | }
20 |
21 | func makeCoordinator() -> ViewModel {
22 | viewModel
23 | }
24 |
25 | func updateNSView(_ nsView: MetalView, context: Context) {
26 | nsView.delegate = context.coordinator.renderer
27 | nsView.renderer = context.coordinator.renderer
28 | context.coordinator.update(view: nsView)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/MetalPlayground/Root/RootView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootView.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 9/20/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct PlaygroundListItemView: View {
12 | let group: PlaygroundGroup
13 | var viewModel: ViewModel
14 | @State private var isExpanded = false
15 |
16 |
17 | var body: some View {
18 | DisclosureGroup(isExpanded: $isExpanded) {
19 | ForEach(group.scenes) { sceneKind in
20 | HStack {
21 | Text(sceneKind.name)
22 | .foregroundColor(viewModel.sceneKind == sceneKind ? .primary : .secondary)
23 | }
24 | .frame(maxWidth: .infinity, alignment: .leading)
25 | .tag(sceneKind)
26 | .contentShape(.rect)
27 | .onTapGesture {
28 | viewModel.sceneKind = sceneKind
29 | }
30 | }
31 | } label: {
32 | Text(group.rawValue)
33 | .font(.headline)
34 | .foregroundStyle(.secondary)
35 | .contentShape(.rect)
36 | }.onAppear {
37 | isExpanded = group.scenes.contains(viewModel.sceneKind)
38 | }.onChange(of: viewModel.sceneKind) { (_, newValue) in
39 | withAnimation {
40 | isExpanded = group.scenes.contains(newValue)
41 | }
42 | }
43 | }
44 | }
45 |
46 | struct RootView: View {
47 | @State var viewModel = ViewModel()
48 | @State var isOptionsOpen = false
49 | @State var isSideBarOPen = false
50 |
51 | init() {
52 | }
53 |
54 | private var sidebar: some View {
55 | List {
56 | ForEach(PlaygroundGroup.allCases) { sceneGroup in
57 | PlaygroundListItemView(group: sceneGroup, viewModel: viewModel)
58 | }
59 | }
60 | .listStyle(.inset)
61 | .frame(minWidth: 180)
62 | }
63 |
64 | @ViewBuilder
65 | private var options: some View {
66 | if !viewModel.hasConfig {
67 | EmptyView()
68 | } else if !isOptionsOpen {
69 | Text("Options")
70 | .padding(EdgeInsets(top: 5, leading: 8, bottom: 5, trailing: 8))
71 | .background(Color(NSColor.windowBackgroundColor))
72 | .cornerRadius(14)
73 | .padding()
74 | .onTapGesture {
75 | withAnimation {
76 | self.isOptionsOpen.toggle()
77 | }
78 | }.zIndex(1)
79 | } else {
80 | VStack(spacing: 28) {
81 | ConfigView()
82 | .environment(viewModel)
83 | }
84 | .frame(maxWidth: 210)
85 | .padding()
86 | .background(Color(NSColor.windowBackgroundColor))
87 | .cornerRadius(12)
88 | .padding()
89 | .transition(.move(edge: .trailing))
90 | .zIndex(2)
91 | .opacity(0.96)
92 | }
93 | }
94 |
95 | @State private var columnVisibility = NavigationSplitViewVisibility.all
96 |
97 | var body: some View {
98 | NavigationSplitView(columnVisibility: $columnVisibility) {
99 | sidebar
100 | } detail: {
101 | MetalSwiftView()
102 | .aspectRatio(1.0, contentMode: .fill)
103 | .environment(viewModel)
104 | .onTapGesture {
105 | withAnimation {
106 | if self.isOptionsOpen {
107 | self.isOptionsOpen = false
108 | }
109 | }
110 | }
111 | .zIndex(0)
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/MetalPlayground/Root/ViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModel.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 9/20/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import Combine
11 |
12 | @Observable
13 | final class ViewModel {
14 | var sceneKind: SceneKind = .simonNoiseIntro {
15 | didSet {
16 | updateSceneSelection(kind: sceneKind)
17 | }
18 | }
19 |
20 | let renderer: Renderer
21 |
22 | var scene: Playground {
23 | sceneKind.scene
24 | }
25 | var hasConfig: Bool {
26 | sceneKind.scene.view != nil
27 | }
28 | @ObservationIgnored
29 | private var cancellables: [AnyCancellable] = []
30 | private var view: MetalView?
31 |
32 | init() {
33 | self.renderer = Renderer()
34 | }
35 |
36 | func update(view: MetalView) {
37 |
38 | view.delegate = renderer
39 | view.renderer = renderer
40 | renderer.setup(view)
41 |
42 | self.view = view
43 | view.delegate = renderer
44 |
45 | updateSceneSelection(kind: sceneKind)
46 | }
47 |
48 |
49 | private func updateSceneSelection(kind: SceneKind) {
50 | renderer.scene = scene
51 | view?.enableSetNeedsDisplay = !scene.isPaused
52 | view?.isPaused = scene.isPaused
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/BookShaders/06Colors.metal:
--------------------------------------------------------------------------------
1 | //
2 | // 06Colors.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/9/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | struct VertexIn {
13 | vector_float2 pos;
14 | };
15 |
16 | struct FragmentUniforms {
17 | float time;
18 | float screen_width;
19 | float screen_height;
20 | float screen_scale;
21 | float2 mousePos;
22 | };
23 |
24 | struct VertexOut {
25 | float4 pos [[position]];
26 | float4 color;
27 | };
28 | // Function from Iñigo Quiles
29 | // https://www.shadertoy.com/view/MsS3Wc
30 | vector_float3 hsb2rgbf(vector_float3 c ){
31 | vector_float3 rgb = clamp(
32 | abs(
33 | fmod(
34 | c.x*6.0+vector_float3(0.0,4.0,2.0), 6.0
35 | )
36 | -3.0)-1.0,
37 | 0.0,
38 | 1.0 );
39 | rgb = rgb*rgb*(3.0-2.0*rgb);
40 | return c.z * mix(vector_float3(1.0), rgb, c.y);
41 | }
42 |
43 |
44 |
45 | vertex VertexOut bos_colors_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
46 | VertexIn in = vertexArray[vid];
47 | VertexOut out;
48 | out.pos = float4(in.pos, 0, 1);
49 | return out;
50 | }
51 |
52 | fragment float4 bos_colors_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
53 | float2 st = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y / uniforms.screen_height};
54 |
55 | st = float2(0.5) - st;
56 | st.x *= uniforms.screen_width/uniforms.screen_height;
57 | // st.y /= 2;
58 | float2 toCenter = st;
59 |
60 | float angleTimeOffset = float(int(uniforms.time * 100) % 360);
61 | angleTimeOffset = (angleTimeOffset / 360) * 2 * 3.14;
62 |
63 | float angle = atan2(toCenter.y, toCenter.x); // between -pi → +pi
64 |
65 | float angle2PI = (angle + angleTimeOffset)/(2 * 3.141);
66 |
67 | float hue = angle2PI;
68 | float saturation = length(toCenter) * 2.0; // extend length from max 0.5 to max 1.0
69 |
70 | hue = smoothstep(0.4, 0.8, hue);
71 |
72 | vector_float3 hsb = {hue, saturation, 1.0};
73 | vector_float3 col = hsb2rgbf(hsb);
74 |
75 | float4 color = float4(col, 1);
76 | return color;
77 | }
78 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/BookShaders/07ShapePrimitives.metal:
--------------------------------------------------------------------------------
1 | //
2 | // 07ShapePrimitives.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/11/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | float3 blurRect(float2 st, float time, float4 margins) {
13 | float blur = (1 + sin(time * 10))/2 * 0.04;
14 | float2 left_bottom = smoothstep({margins[1], margins[2]}, {margins[1] + blur, margins[2] + blur}, st);
15 | float2 right_top = smoothstep({margins[3], margins[0]}, {margins[3] + blur, margins[0] + blur}, 1 - st);
16 |
17 | float visibility = left_bottom.x * left_bottom.y * right_top.x * right_top.y;
18 |
19 | float3 color = visibility * float3(0.5, 0.1, 0.9);
20 | return color;
21 | }
22 |
23 | float3 rect(float2 st, float time, float4 margins, float3 mainColor) {
24 | float2 left_bottom = step({margins[1], margins[2]}, st);
25 | float2 right_top = step({margins[3], margins[0]}, 1 - st);
26 |
27 | float visibility = left_bottom.x * left_bottom.y * right_top.x * right_top.y;
28 |
29 | float3 color = visibility * mainColor;
30 | return color;
31 | }
32 |
33 | float3 outlineRect(float2 st, float4 margins, float time) {
34 | float thickness = 0.01;
35 | float4 innerMargins = margins + thickness;
36 |
37 | float2 left_bottom = step({margins[1], margins[2]}, st);
38 | float2 right_top = step({margins[3], margins[0]}, 1 - st);
39 |
40 | float2 left_bottom_inner = step({innerMargins[1], innerMargins[2]}, st);
41 | float2 right_top_inner = step({innerMargins[3], innerMargins[0]}, 1 - st);
42 |
43 | float visibility_outer = left_bottom.x * left_bottom.y * right_top.x * right_top.y;
44 | float visibility_inner = left_bottom_inner.x * left_bottom_inner.y * right_top_inner.x * right_top_inner.y;
45 |
46 | float visibility = visibility_outer * (1 - visibility_inner);
47 |
48 | float3 color = visibility * float3(0.5, 0.1, 0.9);
49 | return color;
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/BookShaders/07Shapes.metal:
--------------------------------------------------------------------------------
1 | //
2 | // 06Colors.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/9/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 | #include "../ShaderHeaders.h"
12 |
13 | struct VertexIn {
14 | vector_float2 pos;
15 | };
16 |
17 | struct FragmentUniforms {
18 | float time;
19 | float screen_width;
20 | float screen_height;
21 | float screen_scale;
22 | float2 mousePos;
23 | };
24 |
25 | struct VertexOut {
26 | float4 pos [[position]];
27 | float4 color;
28 | };
29 |
30 |
31 | vertex VertexOut bos_shapes_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
32 | VertexIn in = vertexArray[vid];
33 | VertexOut out;
34 | out.pos = float4(in.pos, 0, 1);
35 | return out;
36 | }
37 |
38 | float3 hash32(float2 p) {
39 | float3 p3 = fract(float3(p.xyx) * float3(.1031, .1030, .0973));
40 | p3 += dot(p3, p3.yxz+33.33);
41 | return fract((p3.xxy+p3.yzz)*p3.zyx);
42 | }
43 |
44 | float rectangle(float2 st, float width) {
45 | float bl = step(width, st.x) * step(width, st.y);
46 | float tr = (1 - step((1 - width), st.x)) * (1 - step((1 - width), st.y));
47 | return bl * tr;
48 | }
49 |
50 | float3 circle(float2 st, float time) {
51 | // st = 4 * (st + 0.5);
52 | // st = fract(st) - 0.5;
53 | float t = min(length(st), length(st - 0.3));
54 | float r = 0.5 * absSin(time);
55 | return smoothstep(r - 0.02, r, t);
56 | }
57 |
58 | fragment float4 bos_shapes_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
59 | float3 green = float3(0.4, 0.7, 0.1);
60 | float2 st = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y / uniforms.screen_height};
61 | // st = float2(0.5) - st;
62 |
63 | st.x *= uniforms.screen_width/uniforms.screen_height;
64 |
65 | st = 2 * st - 1;
66 |
67 | float l = length(float2(0.3) - abs(st));
68 | float pct = step(0.5, l);
69 | pct = fract(l * 10);
70 |
71 | float3 col = green * pct;
72 |
73 | float4 color = float4(col, 1);
74 | return color;
75 | }
76 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/BookShaders/08LeftRightTiler.metal:
--------------------------------------------------------------------------------
1 | //
2 | // 07FuturisticUI.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 12/31/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 |
13 | struct VertexOut {
14 | float4 pos [[position]];
15 | float4 color;
16 | };
17 |
18 | struct VertexIn {
19 | vector_float2 pos;
20 | };
21 |
22 |
23 | struct FragmentUniforms {
24 | float time;
25 | float screen_width;
26 | float screen_height;
27 | float screen_scale;
28 | float2 mousePos;
29 | };
30 |
31 |
32 | float3 bricks(float2 uv, float time) {
33 | float zoom = 20.0;
34 | uv *= zoom;
35 | time *= 1.3;
36 | float horizontal = step(fmod(time, 4.0), 2.0);
37 | float offset = step(fmod(horizontal > 0 ? uv.x : uv.y, 2.0), 1.0) * 2.0 - 1.0;
38 | uv = fract(uv);
39 |
40 | uv = 2.0*uv - 1.;
41 | uv.y = -uv.y;
42 |
43 | float t = 0;
44 |
45 | uv.y += fmod(time , 2.0) * offset * horizontal;
46 | uv.x += fmod(time , 2.0) * offset * (1- horizontal);
47 |
48 | float side = 0.52;
49 | t += smoothstep(side,side-0.001, length(uv));
50 |
51 | if (horizontal > 0) {
52 | uv.y -= 2.0;
53 | } else {
54 | uv.x -= 2.0;
55 | }
56 | t += smoothstep(side,side-0.001, length(uv));
57 | if (horizontal > 0) {
58 | uv.y += 4.0;
59 | } else {
60 | uv.x += 4.0;
61 | }
62 | t += smoothstep(side,side-0.001, length(uv));
63 | return (1-t) * float3(0.8);
64 | }
65 |
66 | vertex VertexOut leftright_vertex(const device VertexIn *vertices [[buffer(0)]], unsigned int vid [[vertex_id]]) {
67 | VertexOut v;
68 | v.pos = float4(vertices[vid].pos, 0, 1);
69 | return v;
70 | }
71 |
72 | fragment float4 leftright_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
73 | float2 uv = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y/uniforms.screen_height};
74 | uv.x *= uniforms.screen_width / uniforms.screen_height;
75 | float time = uniforms.time;
76 | float3 col = bricks(uv, time);
77 | return float4( col, 1.0 );
78 | }
79 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/BookShaders/BoSShapes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BoSShapes.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/17/23.
6 | // Copyright © 2023 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MetalKit
11 | import SwiftUI
12 |
13 | class BoSShapes07: Playground {
14 | var fileName: String {
15 | "BookShaders/07Shapes"
16 | }
17 | var vertexFuncName: String { "bos_shapes_vertex" }
18 | var fragmentFuncName: String { "bos_shapes_fragment" }
19 | required init() {}
20 |
21 | enum SketchKind: Int, CaseIterable, Identifiable {
22 | case bezierCurve
23 | case flowingCurves
24 |
25 | var id: Int { rawValue }
26 |
27 | var name: String {
28 | switch self {
29 | case .bezierCurve:
30 | return "Bezier Curve"
31 | case .flowingCurves:
32 | return "Flowing Curves"
33 | }
34 | }
35 | }
36 |
37 | class Config: ObservableObject {
38 | @Published var kind: SketchKind = .bezierCurve
39 | }
40 |
41 | struct Uniforms {
42 | let kind: Float
43 | }
44 |
45 | fileprivate var config: Config = .init()
46 | var fragmentUniforms: Uniforms {
47 | .init(kind: Float(config.kind.rawValue))
48 | }
49 |
50 | func setUniforms(device: MTLDevice, encoder: MTLRenderCommandEncoder) {
51 | var uniforms = fragmentUniforms
52 | let length = MemoryLayout.stride(ofValue: uniforms)
53 | encoder.setFragmentBytes(&uniforms, length: length, index: 1)
54 | }
55 |
56 | struct ConfigView: View {
57 | @EnvironmentObject private var config: Config
58 | @State fileprivate var kind = SketchKind.bezierCurve
59 |
60 | var body: some View {
61 | VStack(alignment: .leading, spacing: 19) {
62 | Picker(selection: $config.kind, label: Text("Kind")) {
63 | ForEach(SketchKind.allCases) {
64 | Text($0.name).tag($0)
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
71 | var view: NSView? {
72 | NSHostingView(
73 | rootView: ConfigView().environmentObject(config)
74 | )
75 | }
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/BookShaders/Shaping.metal:
--------------------------------------------------------------------------------
1 | //
2 | // Shaping.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/10/23.
6 | // Copyright © 2023 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 | #include "../ShaderHeaders.h"
12 |
13 | struct VertexOut {
14 | float4 pos [[position]];
15 | float4 color;
16 | };
17 |
18 | struct VertexIn {
19 | vector_float2 pos;
20 | };
21 |
22 | struct FragmentUniforms {
23 | float time;
24 | float screen_width;
25 | float screen_height;
26 | float screen_scale;
27 | float2 mousePos;
28 | };
29 |
30 | typedef enum {
31 | Bezier = 0,
32 | FlowingCurves = 1,
33 | } SketchKind;
34 |
35 | struct ShapingUniforms {
36 | float kind;
37 | };
38 |
39 |
40 | vertex VertexOut bos_shaping_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
41 | VertexIn in = vertexArray[vid];
42 | VertexOut out;
43 | out.pos = float4(in.pos, 0, 1);
44 | return out;
45 | }
46 |
47 | float bezier(float2 uv, float a, float b) {
48 | float epsilon = 0.00001;
49 | float x = uv.x;
50 | a = max(0.0, min(1.0, a));
51 | b = max(0.0, min(1.0, b));
52 | if (a == 0.5){
53 | a += epsilon;
54 | }
55 |
56 | // solve t from x (an inverse operation)
57 | float om2a = 1 - 2*a;
58 | float t = (sqrt(a*a + om2a*x) - a)/om2a;
59 | float y = (1.0-2*b)*(t*t) + (2*b)*t;
60 | return y;
61 | return 0.0;
62 | }
63 |
64 | fragment float4 bos_shaping_fragment(
65 | VertexOut interpolated [[stage_in]],
66 | constant FragmentUniforms &uniforms [[buffer(0)]],
67 | constant ShapingUniforms &shapingUniforms [[buffer(1)]]
68 | )
69 | {
70 | float t = uniforms.time;
71 | float2 st = {interpolated.pos.x / uniforms.screen_width, interpolated.pos.y / uniforms.screen_height};
72 |
73 | float3 color;
74 | float d = 0;
75 | if (shapingUniforms.kind == Bezier) {
76 | st.y = 1 - st.y;
77 | st.x *= uniforms.screen_width / uniforms.screen_height;
78 | st.x = fract(st.x * 12 + t);
79 | d = bezier(st, 0.082, 0.89);
80 | d = step(d, st.y);
81 | color = min(d, step(0.5, st.y));
82 | } else if (shapingUniforms.kind == FlowingCurves) {
83 | float l = 0.3 + 0.2 * (1. + sin(t))/4;
84 | for (int i=0; i<3; i++) {
85 | float2 uv = st;
86 | uv.x *= sin(uv.x + uv.y) + uv.y + sin(t * 1.1) / 15;
87 | uv.y *= sin(uv.y) + uv.x + cos(t * 1.4) / 4;
88 | l += sin(t * i) + 0.2 * length(uv);
89 | color[i] = l;
90 | }
91 | } else {
92 | color = 0.3;
93 | }
94 |
95 | return vector_float4(color, 1.0);
96 | }
97 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/05Algorithmic.metal:
--------------------------------------------------------------------------------
1 | //
2 | // 05Algorithmic.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/6/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | struct VertexIn {
13 | vector_float2 pos;
14 | };
15 |
16 | struct FragmentUniforms {
17 | float time;
18 | float screen_width;
19 | float screen_height;
20 | float screen_scale;
21 | float2 mousePos;
22 | };
23 |
24 | struct VertexOut {
25 | float4 pos [[position]];
26 | float4 color;
27 | };
28 |
29 | float plotColor(float2 st, float y) {
30 | if (abs(st.y - y) <= 0.01) {
31 | // it's a plot point
32 | return 1;
33 | } else {
34 | return 0;
35 | }
36 |
37 | }
38 |
39 | vertex VertexOut smoothing_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
40 | VertexIn in = vertexArray[vid];
41 | VertexOut out;
42 | out.pos = float4(in.pos, 0, 1);
43 | return out;
44 | }
45 |
46 | fragment float4 smoothing_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
47 | float2 st = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y / uniforms.screen_width};
48 | float x = st.x * 2*M_PI_F;
49 |
50 | float y = fract(4+sin(x))/8;
51 |
52 | float3 color = plotColor(st, y);
53 |
54 | return vector_float4(color, 1.0);
55 | }
56 |
57 |
58 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/07FuturisticUI.metal:
--------------------------------------------------------------------------------
1 | //
2 | // 07FuturisticUI.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 12/31/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | #include "../ShaderHeaders.h"
13 |
14 | struct VertexOut {
15 | float4 pos [[position]];
16 | float4 color;
17 | };
18 |
19 | struct VertexIn {
20 | vector_float2 pos;
21 | };
22 |
23 |
24 | struct FragmentUniforms {
25 | float time;
26 | float screen_width;
27 | float screen_height;
28 | float screen_scale;
29 | float2 mousePos;
30 | };
31 |
32 | // --- Primitives
33 |
34 | // box: left, top, right, bottom
35 | float3 rectangle(float2 uv, float4 box, float3 color) {
36 | float d = 0.001;
37 | float left = smoothstep(box.x, box.x+d, uv.x);
38 | float top = smoothstep(box.y+d, box.y, uv.y);
39 | float right = smoothstep(box.z+d, box.z, uv.x);
40 | float bottom = smoothstep(box.w, box.w+d, uv.y);
41 |
42 | return
43 | (left*top * right*bottom) * color;
44 | }
45 |
46 | float circleSmooth(float2 uv, float r) {
47 | return smoothstep(r+0.01, r, length(uv));
48 | }
49 |
50 | float triangle(float2 uv) {
51 | // Number of sides of your shape
52 | int N = 3;
53 |
54 | // angle progress for this pixel
55 | float a = atan2(uv.x,uv.y);
56 | // Angle / polygon's side
57 | float r = (M_PI_F*2.0)/float(N);
58 |
59 | // Shaping function that modulate the distance
60 | float d = cos(
61 | floor(.5 + a/r)
62 | *r
63 | -a
64 | )*
65 | length(uv);
66 |
67 | return 1.0-smoothstep(.4,.4001,d);
68 | // color = vec3(d);
69 | }
70 |
71 |
72 | // --- Transformation
73 |
74 | // --- Shapes
75 |
76 | float circleOutline(float2 uv, float r, float th) {
77 | return circleSmooth(uv, r) - circleSmooth(uv, r - th);
78 | }
79 |
80 | float wedge(float2 uv, float r, float s, float e) {
81 | float angle = atan2(uv.y, uv.x);
82 | float modulate = smoothstep(s, e, angle) * (1. - step(e, angle)) * 0.6;
83 | return circleSmooth(uv, r) * modulate;
84 | }
85 |
86 | float arc(float2 uv, float r, float angleSt, float angleEnd, float th) {
87 | uv = rotate(M_PI_F) * uv;
88 | float angle = atan2(uv.y, uv.x);
89 | angle = lerp(angle, -M_PI_F, M_PI_F, 0, M_PI_F * 2.);
90 | if (angle < angleSt || angle > angleEnd) { return 0.; }
91 | return circleOutline(uv, r, th);
92 | }
93 |
94 | float animatedArc(float2 uv, float r, float time) {
95 | uv = rotate(M_PI_F/2.) * uv;
96 | return arc(uv, r, -M_PI_F, lerp(sin(time), -1.,1., -M_PI_F, M_PI_F ), 0.01);
97 | }
98 |
99 | // --- Scene
100 |
101 | float3 fui(float2 uv, float time) {
102 | float anim = sin(time);
103 | float3 dark = 0.4;
104 | float3 light = 0.9;
105 | float originalR = 0.8;
106 | float r = originalR;
107 | float d = 0.07;
108 | d = d/2.;
109 | float arcSpan = M_PI_F/4.0;
110 | float outerTH = 0.01;
111 | float outerLightArc =
112 | arc(rotate(-M_PI_F/4.0-0.0)*uv, r, d, arcSpan - d, outerTH)
113 | +
114 | arc(uv, r, d, arcSpan - d, outerTH)
115 | +
116 | arc(rotate(M_PI_F*0.75)*uv, r, d, arcSpan - d, outerTH)
117 | +
118 | arc(rotate(M_PI_F*1.0)*uv, r, d, arcSpan - d, outerTH)
119 | ;
120 | float outerDarkArc =
121 | arc(rotate(M_PI_F*0.25)*uv, r, d, arcSpan - d, outerTH)
122 | +
123 | arc(rotate(M_PI_F*0.5)*uv, r, d, arcSpan - d, outerTH)
124 | +
125 | arc(rotate(M_PI_F*1.25)*uv, r, d, arcSpan - d, outerTH)
126 | +
127 | arc(rotate(M_PI_F*1.5)*uv, r, d, arcSpan - d, outerTH)
128 | ;
129 | float3 outerArc = outerLightArc * light
130 | + outerDarkArc * dark
131 | ;
132 |
133 | r -= 0.1;
134 | float smallArcAnimD = lerp(anim, -1., 1., 0., 0.4);
135 | float outerSmallArcs = 0;
136 | for (int i=0; i<2; i++) {
137 | float rotateAngle = M_PI_F * (i+0.25);
138 | outerSmallArcs +=
139 | arc(rotate(rotateAngle - smallArcAnimD)*uv,
140 | r,
141 | d - smallArcAnimD/2.0,
142 | arcSpan*2.0 - d + 2.*smallArcAnimD,
143 | outerTH/2.0
144 | );
145 | }
146 | // +
147 | // arc(rotate(M_PI_F*1.25)*uv, r, d + smallArcAnimD, arcSpan*2 - d, outerTH/2.0);
148 | float3 outerSmallArc = outerSmallArcs * dark;
149 |
150 | r -= 0.07;
151 | float3 middleCircle =
152 | circleOutline(uv, r, outerTH*0.8) * light;
153 |
154 | float grid =
155 | // smoothstep(0.0031, 0.003, abs(uv.y))
156 | // +
157 | // smoothstep(0.0031, 0.003, abs(uv.x))
158 | // +
159 | smoothstep(0.0031, 0.003, abs(uv.x - uv.y))
160 | +
161 | smoothstep(0.0031, 0.003, abs(uv.y + uv.x))
162 | ;
163 | grid *= 1.0 - step(r, length(uv));
164 |
165 | float3 innerCircles =
166 | (circleOutline(uv, r-0.2, outerTH*0.8)
167 | +
168 | circleOutline(uv, r-0.4, outerTH*0.8))
169 | * dark;
170 |
171 | float triOffset = originalR + lerp(anim, -1.,1., 0.06,-0.06);
172 | float2 triUV = abs(uv);
173 | triUV.y -= triOffset;
174 |
175 | triUV = scale(.021)*triUV;
176 | float triTop = triangle(triUV);
177 | triUV = abs(uv);
178 | triUV.x -= triOffset;
179 | triUV = rotate(-M_PI_F/2.) * scale(.021)*triUV;
180 | float triRight = triangle(triUV);
181 |
182 | // move in elliptical angles
183 | float redOffsetAngle = lerp(fract(time/42.), -1., 1., 0, -4.*M_PI_F);
184 | float redOffsetRad = -0.01 * redOffsetAngle + sin(redOffsetAngle)*cos(redOffsetAngle);
185 | float redOffsetY = redOffsetRad*sin(redOffsetAngle);
186 | float redOffsetX = redOffsetRad*cos(redOffsetAngle);
187 | float2 redOffset = {redOffsetX, redOffsetY};
188 | float redBigCircleRad = 0.2 * fract(time*1.2);
189 | float redBigCircleTH = 0.04;
190 | float2 redBigCircleUV = uv - redOffset;
191 | float redBigCircleT = circleOutline(redBigCircleUV, redBigCircleRad, redBigCircleTH);
192 | redBigCircleT *= smoothstep(redBigCircleRad - redBigCircleTH, redBigCircleRad, length(redBigCircleUV));
193 | float3 pulseColor = float3(0.76,0.34,0.03);
194 | float3 redBigCircle = redBigCircleT * pulseColor;
195 |
196 | float3 pulseSmallStaticCircle = circleOutline(redBigCircleUV, 0.015, 0.005) * pulseColor;
197 | float smallPulseDuration = step(0.0, (sin(anim*40.))/4.0);
198 | float3 pulseSmallPulsingCircle = circleSmooth(redBigCircleUV, 0.007) * smallPulseDuration * pulseColor;
199 |
200 | // float radarAngle = lerp(anim, -1.0, 1.0, 0, 2*M_PI_F);
201 | float radarSpan = 0.5;
202 | float radarRotate = time*4.2/(2*M_PI_F);
203 | float2 radarUV = rotate(radarRotate)*uv;
204 | float radarArcT = wedge(radarUV, r, 0.0, radarSpan);
205 | float3 radarArc = radarArcT * light;
206 |
207 | return outerArc + outerSmallArc + middleCircle + innerCircles
208 | + triTop
209 | + triRight
210 |
211 | + grid * dark/3.0
212 | + redBigCircle
213 | + pulseSmallStaticCircle
214 | + pulseSmallPulsingCircle
215 | + radarArc
216 | ;
217 | }
218 |
219 | float3 scene(float2 uv, float time) {
220 | return fui(uv, time);
221 |
222 | // return wedge(uv, 0.3, 0.0, 0.9);
223 | }
224 |
225 |
226 | vertex VertexOut futuristic_UI_vertex(const device VertexIn *vertices [[buffer(0)]], unsigned int vid [[vertex_id]]) {
227 | VertexOut v;
228 | v.pos = float4(vertices[vid].pos, 0, 1);
229 | return v;
230 | }
231 |
232 | fragment float4 futuristic_UI_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
233 | float2 uv = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y/uniforms.screen_height};
234 | uv -= 0.5;
235 | uv *= 2;
236 | uv.x *= uniforms.screen_width / uniforms.screen_height;
237 | float time = uniforms.time;
238 | float3 col = scene(uv, time);
239 | return float4( col, 1.0 );
240 | }
241 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/AudioViz.metal:
--------------------------------------------------------------------------------
1 | #include
2 | using namespace metal;
3 | #include "../ShaderHeaders.h"
4 |
5 | struct VertexIn {
6 | vector_float2 pos;
7 | };
8 |
9 | struct FragmentUniforms {
10 | float time;
11 | float screen_width;
12 | float screen_height;
13 | float screen_scale;
14 | };
15 |
16 | struct VertexOut {
17 | float4 pos [[position]];
18 | float4 color;
19 | };
20 |
21 | float3 palette01(float t) {
22 | float3 a = float3(0.738, 0.870, 0.870);
23 | float3 b = float3(0.228, 0.500, 0.500);
24 | float3 c = float3(1.0, 1.0, 1.0);
25 | float3 d = float3(0.000, 0.333, 0.667);
26 |
27 | return a + b * cos(6.28318 * (c * t + d));
28 | }
29 |
30 | vertex VertexOut audioVizVertexShader(const device VertexIn *vertices [[buffer(0)]], unsigned int vid [[vertex_id]]) {
31 | VertexOut in;
32 | in.pos = {vertices[vid].pos.x, vertices[vid].pos.y, 0, 1};
33 | return in;
34 | }
35 |
36 | struct LiveCodeUniforms {
37 | uint samplesCount;
38 | };
39 |
40 | float sin01(float v)
41 | {
42 | return 0.5 + 0.5 * sin(v);
43 | }
44 |
45 | float drawCircle(float r, float polarRadius, float thickness)
46 | {
47 | return smoothstep(r, r + thickness, polarRadius) -
48 | smoothstep(r + thickness, r + 2.0 * thickness, polarRadius);
49 | }
50 |
51 |
52 | fragment float4 audioVizFragmentShader(
53 | VertexOut interpolated [[stage_in]],
54 | constant FragmentUniforms &uniforms [[buffer(0)]],
55 | const constant float *loudnessBuffer [[buffer(1)]],
56 | const constant float *frequenciesBuffer [[buffer(2)]]
57 | ) {
58 |
59 | float2 uv = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y/uniforms.screen_height};
60 | uv = 2 * (uv - 0.5);
61 | if (uniforms.screen_width > uniforms.screen_height) {
62 | uv.x *= uniforms.screen_width/uniforms.screen_height;
63 | } else {
64 | uv.y *= uniforms.screen_height/uniforms.screen_width;
65 | }
66 |
67 | float p = length(uv);
68 | float pa = atan2(uv.y, uv.x);
69 |
70 | // Frequency:
71 | // map -1 → 1 to 0 → 361
72 | // int index = int(lerp(uv.x, -1.0, 1.0, 0, 361));
73 | float indexRad = (0.5 * pa / 3.1415 + 1) / 2.0; // 0 → 1
74 |
75 | int index = lerp(indexRad, 0, 1, 0, 361);
76 |
77 | float freq = frequenciesBuffer[index];
78 | freq = sin(indexRad ) * freq;
79 | float o = 0;
80 | float inc = 0;
81 |
82 | float numRings = 7;
83 | float ringSpacing = 0.01;
84 | float ringThickness = 0.10;
85 | float rotationDistortion = 0.3;
86 | for (float i = 0; i < numRings; i += 1.0) {
87 | float baseR;
88 | baseR = 0.2 * sin(freq * 0.8);
89 | float r = baseR + inc;
90 |
91 | r += rotationDistortion * (0.5 + 0.1 * sin(pa * i + 0.3 * uniforms.time * (i - 0.1)));
92 | // r += loudness/4;
93 | r = min(0.8, r);
94 | o += drawCircle(r, p, ringThickness * (1.0 + 0.12 * freq * (i - 1.0)));
95 |
96 | inc += ringSpacing;
97 | }
98 |
99 | float3 bgCol = float3(0.9 - cos(uniforms.time/2) * 0.2 * uv.y, 0.12, 0.3 - sin(uniforms.time/2) * 0.2 * uv.y);
100 | float3 col = mix(bgCol, float3(1, 0.7, 0.3), o);
101 |
102 | return float4(col, 1);
103 |
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/AudioViz.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LiveCode.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 1/9/21.
6 | // Copyright © 2021 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import MetalKit
10 | import SwiftUI
11 | import Accelerate
12 | import AVFoundation
13 |
14 | extension AVAudioPCMBuffer {
15 | /// Returns audio data as an `Array` of `Float` Arrays.
16 | ///
17 | /// If stereo:
18 | /// - `floatChannelData?[0]` will contain an Array of left channel samples as `Float`
19 | /// - `floatChannelData?[1]` will contains an Array of right channel samples as `Float`
20 | func toFloatChannelData() -> [[Float]]? {
21 | // Do we have PCM channel data?
22 | guard let pcmFloatChannelData = floatChannelData else {
23 | return nil
24 | }
25 |
26 | let channelCount = Int(format.channelCount)
27 | let frameLength = Int(self.frameLength)
28 | let stride = self.stride
29 |
30 | // Preallocate our Array so we're not constantly thrashing while resizing as we append.
31 | let zeroes: [Float] = Array(repeating: 0, count: frameLength)
32 | var result = Array(repeating: zeroes, count: channelCount)
33 |
34 | // Loop across our channels...
35 | for channel in 0 ..< channelCount {
36 | // Make sure we go through all of the frames...
37 | for sampleIndex in 0 ..< frameLength {
38 | result[channel][sampleIndex] = pcmFloatChannelData[channel][sampleIndex * stride]
39 | }
40 | }
41 |
42 | return result
43 | }
44 | }
45 |
46 | extension AVAudioFile {
47 | /// converts to a 32 bit PCM buffer
48 | func toAVAudioPCMBuffer() -> AVAudioPCMBuffer? {
49 | guard let buffer = AVAudioPCMBuffer(pcmFormat: processingFormat,
50 | frameCapacity: AVAudioFrameCount(length)) else { return nil }
51 |
52 | do {
53 | framePosition = 0
54 | try read(into: buffer)
55 | print("Created buffer with format")
56 |
57 | } catch let error as NSError {
58 | print("Cannot read into buffer " + error.localizedDescription)
59 | }
60 |
61 | return buffer
62 | }
63 |
64 | /// converts to Swift friendly Float array
65 | public func toFloatChannelData() -> [[Float]]? {
66 | guard let pcmBuffer = toAVAudioPCMBuffer(),
67 | let data = pcmBuffer.toFloatChannelData() else { return nil }
68 | return data
69 | }
70 | }
71 | /// Returns the minimums of chunks of binSize.
72 | func binMin(samples: [Float], binSize: Int) -> [Float] {
73 | var out: [Float] = .init(repeating: 0.0, count: samples.count / binSize)
74 |
75 | // Note: we have to use a dumb while loop to avoid swift's Range and have
76 | // decent perf in debug.
77 | var bin = 0
78 | while bin < out.count {
79 |
80 | // Note: we could do the following but it's too slow in debug
81 | // out[bin] = samples[(bin * binSize) ..< ((bin + 1) * binSize)].min()!
82 |
83 | var v = Float.greatestFiniteMagnitude
84 | let start: Int = bin * binSize
85 | let end: Int = (bin + 1) * binSize
86 | var i = start
87 | while i < end {
88 | v = min(samples[i], v)
89 | i += 1
90 | }
91 | out[bin] = v
92 | bin += 1
93 | }
94 | return out
95 | }
96 |
97 | /// Returns the maximums of chunks of binSize.
98 | func binMax(samples: [Float], binSize: Int) -> [Float] {
99 | var out: [Float] = .init(repeating: 0.0, count: samples.count / binSize)
100 |
101 | // Note: we have to use a dumb while loop to avoid swift's Range and have
102 | // decent perf in debug.
103 | var bin = 0
104 | while bin < out.count {
105 |
106 | // Note: we could do the following but it's too slow in debug
107 | // out[bin] = samples[(bin * binSize) ..< ((bin + 1) * binSize)].max()!
108 |
109 | var v = -Float.greatestFiniteMagnitude
110 | let start: Int = bin * binSize
111 | let end: Int = (bin + 1) * binSize
112 | var i = start
113 | while i < end {
114 | v = max(samples[i], v)
115 | i += 1
116 | }
117 | out[bin] = v
118 | bin += 1
119 | }
120 | return out
121 | }
122 |
123 | public final class SampleBuffer: Sendable {
124 | let samples: [Float]
125 |
126 | /// Initialize the buffer with samples
127 | public init(samples: [Float]) {
128 | self.samples = samples
129 | }
130 |
131 | /// Number of samples
132 | public var count: Int {
133 | samples.count
134 | }
135 | }
136 |
137 | final class AudioVizScene: Playground {
138 | var fileName: String {
139 | "Explorations/AudioViz"
140 | }
141 |
142 | let vertexFuncName = "audioVizVertexShader"
143 | let fragmentFuncName = "audioVizFragmentShader"
144 |
145 | private var shaderContents = ""
146 | var sample: SampleBuffer = .init(samples: [])
147 |
148 | let queue = DispatchQueue(label: "live-code")
149 | let engine: AVAudioEngine
150 |
151 | init() {
152 | engine = AVAudioEngine()
153 | _ = engine.mainMixerNode
154 | engine.prepare()
155 | do {
156 | try engine.start()
157 | } catch {
158 | print(error.localizedDescription)
159 | }
160 |
161 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
162 | let file = Bundle.main.url(forResource: "raga", withExtension: "mp3")!
163 | // let file = Bundle.main.url(forResource: "who", withExtension: "mp3")!
164 | // let file = Bundle.main.url(forResource: "laila", withExtension: "m4a")!
165 | // let file = Bundle.main.url(forResource: "malabar", withExtension: "mp3")!
166 | let audio = try! AVAudioFile(forReading: file)
167 | let format = audio.processingFormat
168 | let player = AVAudioPlayerNode()
169 | self.engine.mainMixerNode.installTap(onBus: 0, bufferSize: 1024, format: nil) { [weak self] buffer, time in
170 | self?.processAudioData(buffer: buffer)
171 | }
172 | self.engine.attach(player)
173 | self.engine.connect(player, to: self.engine.mainMixerNode, format: format)
174 | player.scheduleFile(audio, at: nil)
175 | player.play()
176 | }
177 | }
178 |
179 | var ready: Bool {
180 | self.loudnessBuffer != nil && self.freqeuencyBuffer != nil
181 | }
182 |
183 | func processAudioData(buffer: AVAudioPCMBuffer) {
184 | guard let channelData = buffer.floatChannelData?[0] else { return }
185 | let frames = buffer.frameLength
186 |
187 | //rms
188 | let rmsValue = rms(data: channelData, frameLength: UInt(frames))
189 | loudnessMagnitude = rmsValue
190 |
191 | //fft
192 | let fftMagnitudes = AudioVizScene.fft(data: channelData, setup: fftSetup!)
193 | frequencyVertices = fftMagnitudes
194 | // print(fftMagnitudes.max()!)
195 | }
196 |
197 | static func fft(data: UnsafeMutablePointer, setup: OpaquePointer) -> [Float]{
198 | //output setup
199 | var realIn = [Float](repeating: 0, count: 1024)
200 | var imagIn = [Float](repeating: 0, count: 1024)
201 | var realOut = [Float](repeating: 0, count: 1024)
202 | var imagOut = [Float](repeating: 0, count: 1024)
203 |
204 | //fill in real input part with audio samples
205 | for i in 0...1023 {
206 | realIn[i] = data[i]
207 | }
208 |
209 |
210 | vDSP_DFT_Execute(setup, &realIn, &imagIn, &realOut, &imagOut)
211 |
212 | //our results are now inside realOut and imagOut
213 |
214 | //package it inside a complex vector representation used in the vDSP framework
215 | var complex = DSPSplitComplex(realp: &realOut, imagp: &imagOut)
216 |
217 | //setup magnitude output
218 | var magnitudes = [Float](repeating: 0, count: 512)
219 |
220 | //calculate magnitude results
221 | vDSP_zvabs(&complex, 1, &magnitudes, 1, 512)
222 |
223 | //normalize
224 | var normalizedMagnitudes = [Float](repeating: 0.0, count: 512)
225 | var scalingFactor = Float(30.0/512)
226 | vDSP_vsmul(&magnitudes, 1, &scalingFactor, &normalizedMagnitudes, 1, 512)
227 |
228 | return normalizedMagnitudes
229 | }
230 |
231 | static func interpolate(current: Float, previous: Float) -> [Float]{
232 | var vals = [Float](repeating: 0, count: 11)
233 | vals[10] = current
234 | vals[5] = (current + previous)/2
235 | vals[2] = (vals[5] + previous)/2
236 | vals[1] = (vals[2] + previous)/2
237 | vals[8] = (vals[5] + current)/2
238 | vals[9] = (vals[10] + current)/2
239 | vals[7] = (vals[5] + vals[9])/2
240 | vals[6] = (vals[5] + vals[7])/2
241 | vals[3] = (vals[1] + vals[5])/2
242 | vals[4] = (vals[3] + vals[5])/2
243 | vals[0] = (previous + vals[1])/2
244 |
245 | return vals
246 | }
247 |
248 | var prevRMSValue : Float = 0.3
249 |
250 | //fft setup object for 1024 values going forward (time domain -> frequency domain)
251 | let fftSetup = vDSP_DFT_zop_CreateSetup(nil, 1024, vDSP_DFT_Direction.FORWARD)
252 |
253 |
254 | private func rms(data: UnsafeMutablePointer, frameLength: UInt) -> Float {
255 | var val : Float = 0
256 | vDSP_measqv(data, 1, &val, frameLength)
257 |
258 | var db = 10*log10f(val)
259 | //inverse dB to +ve range where 0(silent) -> 160(loudest)
260 | db = 160 + db;
261 | //Only take into account range from 120->160, so FSR = 40
262 | db = db - 120
263 |
264 | let dividor = Float(40/0.3)
265 | var adjustedVal = 0.3 + db/dividor
266 |
267 | //cutoff
268 | if (adjustedVal < 0.3) {
269 | adjustedVal = 0.3
270 | } else if (adjustedVal > 0.6) {
271 | adjustedVal = 0.6
272 | }
273 | return adjustedVal
274 | }
275 |
276 | func tearDown() {
277 | }
278 |
279 | private var device: MTLDevice? {
280 | didSet {
281 | if let device {
282 | loudnessBuffer = device.makeBuffer(bytes: &loudnessMagnitude, length: MemoryLayout.stride)
283 | freqeuencyBuffer = device.makeBuffer(bytes: frequencyVertices, length: frequencyVertices.count * MemoryLayout.stride, options: [])!
284 |
285 | }
286 | }
287 | }
288 | private var pixelFormat: MTLPixelFormat?
289 |
290 | private var loudnessMagnitude: Float = 0 {
291 | didSet {
292 | loudnessBuffer = device?.makeBuffer(bytes: &loudnessMagnitude, length: MemoryLayout.stride)
293 | }
294 | }
295 | private var loudnessBuffer: MTLBuffer?
296 | private var freqeuencyBuffer : MTLBuffer!
297 | public var frequencyVertices : [Float] = [Float](repeating: 0, count: 361) {
298 | didSet{
299 | let sliced = Array(frequencyVertices[0..<361])
300 | if let device {
301 | freqeuencyBuffer = device.makeBuffer(bytes: sliced, length: sliced.count * MemoryLayout.stride, options: [])!
302 | }
303 | }
304 | }
305 |
306 | func tick(time: Float) {
307 | }
308 |
309 | func setUniforms(device: MTLDevice, encoder: MTLRenderCommandEncoder) {
310 | if self.device !== device {
311 | self.device = device
312 | }
313 | guard
314 | let loudnessBuffer = loudnessBuffer,
315 | let frequencyBuffer = freqeuencyBuffer
316 | else {
317 | return
318 | }
319 | encoder.setFragmentBuffer(loudnessBuffer, offset: 0, index: 1)
320 | encoder.setFragmentBuffer(frequencyBuffer, offset: 0, index: 2)
321 | }
322 |
323 | }
324 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/CellularNoise.metal:
--------------------------------------------------------------------------------
1 | //
2 | // CellularNoise.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 5/16/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | //float randomized(float x) {
13 | // return fract(sin(x) * 10.0);
14 | //}
15 | //
16 |
17 | struct VertexIn {
18 | vector_float2 pos;
19 | };
20 |
21 | struct FragmentUniforms {
22 | float time;
23 | float screen_width;
24 | float screen_height;
25 | float screen_scale;
26 | };
27 |
28 | struct VertexOut {
29 | float4 pos [[position]];
30 | float4 color;
31 | };
32 |
33 | vertex VertexOut cellularVertexShader(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
34 | VertexIn in = vertexArray[vid];
35 | VertexOut out;
36 | out.pos = float4(in.pos, 0, 1);
37 | return out;
38 | }
39 |
40 |
41 | vector_float2 random2(vector_float2 p ) {
42 | return fract(sin(vector_float2(dot(p,vector_float2(127.1,311.7)),dot(p,vector_float2(269.5,183.3))))*43758.5453);
43 | }
44 |
45 | fragment float4 tileFragmentShader(VertexOut pixel [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
46 | float2 st = {pixel.pos.x / uniforms.screen_width, 1 - pixel.pos.y / uniforms.screen_height};
47 | st *= 5;
48 | float2 ist = floor(st);
49 | float2 fst = fract(st);
50 |
51 | float min_dist = 1;
52 |
53 | for(int i=-1; i<=1; i++) {
54 | for(int j=-1; j<=1; j++) {
55 | float2 neighbor_origin = {float(i), float(j)};
56 | float2 neighbor_point = random2(ist + neighbor_origin);
57 | neighbor_point = 0.5 + 0.5*sin(uniforms.time + 6.2831*neighbor_point);
58 | float neighbor_point_distance = length(neighbor_origin + neighbor_point - fst);
59 | min_dist = min(min_dist, neighbor_point_distance * min_dist);
60 | }
61 | }
62 |
63 | float3 color = float3(0) + step(0.06, min_dist);
64 | if (length(color) != 0) {
65 | color = float3(0.8, 0.7, 0.68);
66 | }
67 | // color[1] = min_dist;
68 |
69 | return float4(color, 1);
70 | }
71 |
72 | fragment float4 tileFragmentShader2(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
73 | float x = interpolated.pos.x / uniforms.screen_width;
74 | float y = 1 - interpolated.pos.y / uniforms.screen_height;
75 | float2 st = {x,y};
76 | float scale = 3;
77 | st *= scale;
78 |
79 | float2 ist = floor(st);
80 | float2 fst = fract(st);
81 |
82 | float min_dist = 100;
83 |
84 | for (int y= -1; y <= 1; y++) {
85 | for (int x= -1; x <= 1; x++) {
86 | // Neighbor place in the grid
87 | vector_float2 neighborOffset = vector_float2(float(x),float(y));
88 |
89 | // Random position from current + neighbor place in the grid
90 | vector_float2 pointInNeighbor = random2(ist + neighborOffset);
91 |
92 | // Animate the point
93 | pointInNeighbor = 0.5 + 0.5*sin(uniforms.time + 6.2831*pointInNeighbor);
94 |
95 | // Vector between the pixel and the point
96 | vector_float2 diff = neighborOffset + pointInNeighbor - fst;
97 |
98 | // Distance to the point
99 | float dist = length(diff);
100 |
101 | // Keep the closer distance
102 | min_dist = min(min_dist, dist);
103 | }
104 | }
105 |
106 | vector_float3 color = 0;
107 | // Draw the min distance (distance field)
108 | color += min_dist;
109 |
110 | // Draw cell center
111 | color += 1.-step(.02, min_dist);
112 |
113 | // Draw grid
114 | color.r += step(.98, fst.x) + step(.98, fst.y);
115 |
116 | return float4(float3(min_dist), 1);
117 | }
118 |
119 | fragment float4 cellularFragmentShader(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
120 | unsigned int pointsCounts = int(uniforms.time) % 5 + 1;
121 | float x = interpolated.pos.x / uniforms.screen_width;
122 | float y = 1 - interpolated.pos.y / uniforms.screen_height;
123 | float2 st = { x, y };
124 |
125 | float2 points[6];
126 | points[0] = {
127 | 0.1,
128 | 0.4
129 | };
130 | points[1] = {
131 | 0.3,
132 | 0.8
133 | };
134 | points[2] = {
135 | 0.7,
136 | 0.5
137 | };
138 | points[3] = {
139 | 0.9,
140 | 0.32
141 | };
142 | points[4] = {
143 | 0.81,
144 | 0.72
145 | };
146 | points[5] = {
147 | 0.51,
148 | 0.1
149 | };
150 |
151 | float min_dist = 100;
152 | float variation_max = 0.1;
153 | for (unsigned int i = 0; i < pointsCounts; i++) {
154 | float2 center = points[i];
155 | float variation = (0 + sin(uniforms.time)) * variation_max;
156 | if (i % 2 == 0) {
157 | center[0] += variation;
158 | } else {
159 | center[1] += variation;
160 | }
161 |
162 | float dist = distance(st, center);
163 | if (dist < 0.005) {
164 | return float4(0.3, 0.4, 0.6, 1);
165 | }
166 | min_dist = min(dist, min_dist);
167 | }
168 |
169 | if (pointsCounts > 3) {
170 | min_dist -= step(.7,abs(sin(50.0*min_dist)))*.3;
171 | }
172 |
173 | return float4(float3(min_dist), 1);
174 | }
175 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/Girih.metal:
--------------------------------------------------------------------------------
1 | //
2 | // RepeatingCircles.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 8/2/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | #include "../ShaderHeaders.h"
13 |
14 | struct VertexOut {
15 | float4 pos [[position]];
16 | float4 color;
17 | };
18 |
19 | struct VertexIn {
20 | vector_float2 pos;
21 | };
22 |
23 | typedef enum {
24 | GirihPatternFirstThingsFirst = 0,
25 | GirihPatternSixesInterpolated = 1
26 | } GirihPatternKind;
27 |
28 | struct FragmentUniforms {
29 | float time;
30 | float screen_width;
31 | float screen_height;
32 | float screen_scale;
33 | float2 mousePos;
34 | };
35 |
36 | struct GirihUniforms {
37 | GirihPatternKind kind;
38 | bool rotating;
39 | float num_rows;
40 | float num_polygons;
41 | float scale;
42 | };
43 |
44 | vertex VertexOut girih_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
45 | VertexIn in = vertexArray[vid];
46 | VertexOut out;
47 | out.pos = float4(in.pos, 0, 1);
48 | return out;
49 | }
50 |
51 | // ---
52 |
53 | float Band(float p, float start, float end, float blur) {
54 | float mask = smoothstep(start - blur, start + blur, p);
55 | mask *= smoothstep(end + blur, end - blur, p);
56 | return mask;
57 | }
58 |
59 |
60 | float CircleBand(float2 st, float2 pos, float r, float thickness, float blur) {
61 | float d = length(st - pos);
62 | float color = Band(d, r, r + thickness, blur);
63 | return color;
64 | }
65 |
66 |
67 | // ---
68 |
69 | float lineDistance(float2 p, float2 v, float2 w) {
70 | const float l2 = length_squared(w - v); // i.e. |w-v|^2 - avoid a sqrt
71 | if (l2 == 0.0) return distance(p, v); // v == w case
72 |
73 | // Consider the line extending the segment, parameterized as v + t (w - v).
74 | // We find projection of point p onto the line.
75 | // It falls where t = [(p-v) . (w-v)] / |w-v|^2
76 | // We clamp t from [0,1] to handle points outside the segment vw.
77 | const float t = max(0.0, min(1.0, dot(p - v, w - v) / l2));
78 | const float2 projection = v + t * (w - v); // Projection falls on the segment
79 | return distance(p, projection);
80 | }
81 |
82 | float onLine(float2 p, float2 v, float2 w) {
83 | float dist = lineDistance(p, v, w);
84 | // return smoothstep(0.004, 0.001, dist);
85 | return 1 - step(0.005, dist);
86 | }
87 |
88 | float2 lineIntersection(float2 p1, float2 p2, float2 p3, float2 p4) {
89 | float pxNum = (p1.x*p2.y - p1.y*p2.x)*(p3.x - p4.x) - (p1.x - p2.x)*(p3.x*p4.y - p3.y*p4.x);
90 | float pyNum = (p1.x*p2.y - p1.y*p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x*p4.y - p3.y*p4.x);
91 |
92 | float den = (p1.x - p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x - p4.x);
93 | float px = pxNum / den;
94 | float py = pyNum / den;
95 | return {px, py};
96 | }
97 |
98 | /// Intersection point between two circles of same radius r,
99 | /// and positioned at center0 and center1. top: whether we want the top or bottom intersection.
100 | /// From http://paulbourke.net/geometry/circlesphere/
101 | float2 circlesIntersctionPoint(float r, bool at_top, float2 center0, float2 center1) {
102 | float d = distance(center0, center1);
103 | float a = d/2;
104 | float2 p2 = center0 + a * (center1 - center0) / d;
105 | float h = sqrt(pow(r, 2) - pow(a, 2));
106 | float xPart = (h * (center1.y - center0.y) / d);
107 | float x3 = p2.x;
108 | if (at_top) {
109 | x3 -= xPart;
110 | } else {
111 | x3 += xPart;
112 | }
113 | float yPart = (h * (center1.x - center0.x) / d);
114 | float y3 = p2.y;
115 | if (at_top) {
116 | y3 += yPart;
117 | } else {
118 | y3 -= yPart;
119 | }
120 | return float2(x3, y3);
121 | }
122 |
123 | struct Mask {
124 | float circleMask;
125 | float polygonMask;
126 | // circle intersections from which we build new circles
127 | float2 intersections [6];
128 | };
129 |
130 | Mask girih_circles_mask(float2 st, float r, float rotating, float scaleFactor, float time) {
131 | if (rotating) {
132 | st *= rotate(sin(time/4) * M_PI_F);
133 | }
134 |
135 | // scale down a bit so we are away from the boundary
136 | st = scale(1/scaleFactor) * st;
137 |
138 | float2 centerPos = {0,0};
139 |
140 | int totalCircles = 6;
141 | float thickness = 0.004;
142 | float blur = 0.001;
143 |
144 | Mask mask;
145 | mask.circleMask = CircleBand(st, centerPos, r, thickness, blur); // start w/ middle circle
146 | mask.polygonMask = 0;
147 |
148 | // midpoints between consecutive intersections
149 | float2 midpoints[6];
150 |
151 | // for outer intersections
152 | float2 innerCircleCenters[6];
153 |
154 | // Build circles and intersections, and collect midpoints
155 | for (int idx = 0; idx < totalCircles; idx++) {
156 | float2 circle2Pos = (idx == 0) ?
157 | circle2Pos = {centerPos.x + r, 0} // to the right first
158 | :
159 | mask.intersections[idx - 1]; // or the last circle
160 |
161 | float2 intersection = circlesIntersctionPoint(r, true, centerPos, circle2Pos);
162 | mask.circleMask += CircleBand(st, intersection, r, thickness, blur);
163 |
164 | innerCircleCenters[idx] = circle2Pos;
165 |
166 | // if (idx > 0) {
167 | float2 outerIntersection = circlesIntersctionPoint(r, true, circle2Pos, intersection);
168 | mask.circleMask += CircleBand(st, outerIntersection, r, thickness, blur);
169 | // }
170 |
171 | float2 line_point1 = circle2Pos;
172 | float2 line_point2 = intersection;
173 |
174 | midpoints[idx] = float2((line_point1.x+line_point2.x)/2, (line_point1.y+line_point2.y)/2);
175 |
176 | mask.intersections[idx] = intersection;
177 | }
178 |
179 | // Lines between midpoints of the inner hexagon
180 | for (int idx = 0; idx < totalCircles; idx++) {
181 | int n = totalCircles;
182 | float2 p1 = lineIntersection(
183 | midpoints[idx], midpoints[(idx + 2)%n],
184 | midpoints[(idx + 1)%n],
185 | midpoints[(idx + (n - 1))%n]
186 | );
187 | float2 p2 = midpoints[idx];
188 | float fallsOnLine1 = onLine(st, p1, p2);
189 | mask.polygonMask = max(mask.polygonMask, fallsOnLine1);
190 |
191 | float2 p3 = lineIntersection(
192 | midpoints[idx], midpoints[(idx + n - 2)%n],
193 | midpoints[(idx + n - 1)%n],
194 | midpoints[(idx + 1)%n]
195 | );
196 | float2 p4 = midpoints[idx];
197 | float fallsOnLine2 = onLine(st, p3, p4);
198 | mask.polygonMask = max(mask.polygonMask, fallsOnLine2);
199 | }
200 |
201 | mask.circleMask = min(mask.circleMask, 1.0);
202 | return mask;
203 | }
204 |
205 | float3 colorForMask(Mask mask, float2 st, float scale) {
206 | float3 basePerimeterColor = float3(0.4, 0.2, 0.42);
207 | float3 baseLineColor = float3(0.9, 0.8, 0.1);
208 | float3 color = 0;
209 |
210 | // Circle outline
211 | color += (1 - mask.polygonMask) * mask.circleMask * basePerimeterColor;
212 | // Polygon
213 | color += mask.polygonMask * baseLineColor;
214 |
215 | // Intersection circles
216 | if (scale == 1) {
217 | float3 intersectionColor = 0;
218 | for (int i=0; i<6; i++) {
219 | float dist = distance(st, mask.intersections[i]);
220 | float thickness = 0.01;
221 | dist = smoothstep(thickness, thickness - 0.001, dist);
222 | intersectionColor += dist * basePerimeterColor;
223 | }
224 |
225 | if (length(intersectionColor) > 0) {
226 | color = intersectionColor;
227 | }
228 | }
229 |
230 | color = min(color, 1.0);
231 | return color;
232 | }
233 |
234 |
235 | float3 girih_color(float2 st, int rows_count, int polygons_count, bool rotating, float scale, float time) {
236 | float r = 0.5;
237 |
238 | st *= rows_count;
239 | st = fract(st);
240 |
241 | st -= 0.5;
242 | st *= 1.01; // inset a little bit from the edges
243 |
244 | float angleBetweenPoints = M_PI_F / 3.0; // 6 in 2_PI
245 | float rotationAngle = angleBetweenPoints / polygons_count;
246 |
247 | float3 color = 0;
248 | for (int i=0; i
10 | using namespace metal;
11 |
12 | struct VertexOut {
13 | float4 pos [[position]];
14 | float4 color;
15 | };
16 |
17 | typedef struct {
18 | float time;
19 | float3 appResolution;
20 | } CustomUniforms;
21 |
22 |
23 | struct VertexIn {
24 | vector_float2 pos;
25 | };
26 |
27 | struct FragmentUniforms {
28 | float time;
29 | float screen_width;
30 | float screen_height;
31 | float screen_scale;
32 | float2 mousePos;
33 | };
34 |
35 |
36 | vertex VertexOut happy_jumping_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
37 | VertexIn in = vertexArray[vid];
38 | VertexOut out;
39 | out.pos = float4(in.pos, 0, 1);
40 | return out;
41 | }
42 |
43 | float sdEllipsoid(float3 pos, float3 radii) {
44 | float k0 = length(pos/radii);
45 | float k1 = length(pos/radii/radii);
46 | return k0 * (k0 - 1.0) / k1;
47 | }
48 |
49 | float sdSphere(float3 sphere, float radius) {
50 | return length(sphere) - radius;
51 | }
52 |
53 | float smin(float a, float b, float k) {
54 | float h = max(k - abs(a-b), 0.0);
55 | float val = min(a, b) - h * h / (k * 4.0);
56 | return val;
57 | }
58 |
59 |
60 | float smax(float a, float b, float k) {
61 | float h = max(k - abs(a-b), 0.0);
62 | float val = max(a, b) + h * h / (k * 4.0);
63 | return val;
64 | }
65 |
66 | float2 sdGuy(float3 pos, float time, float3 center) {
67 | time = fract(time / 2.0);
68 | time = 0.5; // for modeling
69 | float y = 4.0 * (1 - time) * time;
70 | center.y = y;
71 |
72 | float sy = 0.5 + 0.5 * y; // deform in y
73 | float sz = 1.0 / sy; // inverse deform in z, so volume is preserved
74 | float3 radii = float3(0.25, 0.25 * sy, 0.25 * sz);
75 |
76 | float3 q = pos - center;
77 |
78 | // main body
79 | float d = sdEllipsoid(q, radii);
80 |
81 | float3 h = q;
82 |
83 | // head
84 | float d2 = sdEllipsoid(h - float3(0.,0.28,0.), float3(0.2));
85 | float d3 = sdEllipsoid(h - float3(0.,0.28,-0.1), float3(0.2)); // head's back
86 | float head = smin(d3, d2, 0.03);
87 | d = smin(d, head, 0.1);
88 |
89 | float3 heye = {abs(h.x), h.y, h.z};
90 |
91 | // eye brows
92 | float3 eyebrowCoordinates = heye - float3(0.12,0.34,0.15);
93 | // to rotate, can multiply by a pythagorean triplet;
94 | // {3, 4, 5} → gives us 40-ish angle
95 | float2x2 rotator = float2x2(3,4,-4,3);
96 | float denom = 5.0;
97 | eyebrowCoordinates.xy = ((rotator) * eyebrowCoordinates.xy) / denom;
98 | float dEyebrow = sdEllipsoid(eyebrowCoordinates, float3(0.06, 0.035, 0.05));
99 | d = smin(d, dEyebrow, 0.04);
100 |
101 | // mouth
102 | float dMouth = sdEllipsoid(h - float3(0.0, 0.1, 0.1), float3(0.1,0.035,0.3));
103 | // carve out the mouth using smax
104 | d =smax(d, -dMouth, 0.03);
105 |
106 | float2 result = float2(d, 2.0); // 1 is the body identifier, 2 is head
107 |
108 | // eye
109 | float eye = sdSphere(heye - float3(0.08, 0.28, 0.16), 0.05);
110 | if (eye < d) {
111 | result = float2(eye, 3.0); // 3 is the eye id
112 | }
113 | float pupil = sdSphere(heye - float3(0.09, 0.28, 0.19), 0.02);
114 | if (pupil < d) {
115 | result = float2(pupil, 4.0); // 4 is the pupil id
116 | }
117 |
118 | return result;
119 | }
120 |
121 | float2 RayMarch(float3 pos, float t) {
122 | float2 guy1 = sdGuy(pos, t, float3(0, 0, 0.0));
123 |
124 | float planeY = -0.25;
125 | float planeD = pos.y - planeY;
126 |
127 | return (planeD < guy1.x) ? float2(planeD, 1.0) : guy1; // 1 is plane-id
128 | }
129 |
130 | float3 calcNormal(float3 pos, float t) {
131 | float2 e = float2(0.001, 0.);
132 | return normalize(
133 | float3(
134 | RayMarch(pos+e.xyy, t).x - RayMarch(pos-e.xyy, t).x,
135 | RayMarch(pos+e.yxy, t).x - RayMarch(pos-e.yxy, t).x,
136 | RayMarch(pos+e.yyx, t).x - RayMarch(pos-e.yyx, t).x
137 | )
138 | );
139 | }
140 |
141 | #define MAX_ITER 200
142 | #define MAX_DIST 20
143 |
144 | /// ro → position of the point (probably on some surface) that we are checking if it's in shadow
145 | /// rd → direction to light source
146 | float castShadow(float3 ro, float3 rd, float current_material) {
147 | float res = 1.0;
148 |
149 | float t = 0.001;
150 | for (int i=0; i<100; i++) {
151 | // if we hit something, we are in shadow, since we can't reach the light
152 | // we'll also mark how close we are from hitting something, even if it didn't hit any objects
153 |
154 | float3 pos = ro + t * rd;
155 | float2 closest = RayMarch(pos, t);
156 | float h = closest.x;
157 |
158 |
159 | // t → how far we have come from pos
160 | // h → the distance to the closest thing at t
161 | // divide h by → closer things will have a stronger shadow
162 | if (closest.y != current_material) {
163 | res = min(res, 16.0 * h / t); // find the closest thing, so min
164 | }
165 |
166 | h += t;
167 |
168 | if (t > MAX_DIST) { break; }
169 | }
170 | return res;
171 | }
172 |
173 | float2 castRay(float3 ro, float3 rd, float time) {
174 | float material = -1.0 ; // unknown
175 | // distance from camera in to the scene
176 | float t = 0;
177 | for (int i = 0; i < MAX_ITER; i++) {
178 | float3 pos = ro + t * rd; // current sampling position in to the scene
179 | float2 h = RayMarch(pos, time); // distance and material of nearest item in scene from pos
180 | material = h.y;
181 | if (h.x < 0.001) { // we have hit something
182 | break;
183 | }
184 | t += h.x; // march further in to scene (by the safe distance h)
185 | if (t > MAX_DIST) {
186 | break; // we have gone too far without hitting
187 | }
188 | }
189 |
190 | if (t > MAX_DIST) {
191 | material = -1.0; // if not hit, send -1.0
192 | }
193 | return float2(t, material);
194 | }
195 |
196 | fragment float4 happy_jumping_fragment( VertexOut in [[stage_in]],
197 | constant FragmentUniforms &uniforms [[buffer( 0 )]] )
198 | {
199 | // float2 uv = in.pos.xy;
200 | float2 uv = {in.pos.x / uniforms.screen_width, in.pos.y / uniforms.screen_height};
201 | uv = 2 * uv - 1.0;
202 | uv.y = -uv.y;
203 |
204 | float time = uniforms.time;
205 | float mouseOffset = 30.0 * uniforms.mousePos.x;
206 | // mouseOffset = 0;
207 |
208 | float3 lookAt = float3(0,0.95,0); // camera target
209 | float3 ro = lookAt + float3(1.5 * sin(mouseOffset), 0 , 1.5*cos(mouseOffset));
210 | float3 forward = normalize(lookAt - ro);
211 | float3 right = normalize(cross(forward, float3(0,1,0)));
212 | float3 up = normalize(cross(right, forward));
213 | float screenZPos = 1.3;
214 | float3 rd = normalize( uv.x*right + uv.y*up + screenZPos*forward);
215 |
216 | float3 sky_col = float3(.7,.75,.89);
217 | float sky_gradient = uv.y * 0.4;
218 | float3 col = sky_col - sky_gradient;
219 |
220 | float2 tAndMaterial = castRay(ro, rd, time);
221 |
222 | float t = tAndMaterial.x;
223 | float tMaterial = tAndMaterial.y;
224 | if (tMaterial > 0) { // we did hit something (-1 for not hit)
225 | /// Calculate lighting
226 | float3 pos = ro + t * rd; // position at the param t
227 | float3 normal = calcNormal(pos, time);
228 |
229 | // grass has albedo 0.2. Can use that as base color for objects.
230 |
231 | float3 material = 0.18;
232 |
233 | if (tMaterial < 1.5) {
234 | material = {0.03, 0.13, 0.01};
235 | } else if (tMaterial < 2.5) {
236 | material = {0.1, 0.12, 0.01};
237 | } else if (tMaterial < 3.5) {
238 | material = {0.4, 0.52, 0.41};
239 | } else if (tMaterial < 4.5) {
240 | material = float3(0.001);
241 | }
242 |
243 | float3 sun_dir = {0.8,0.4,0.2};
244 | float sun_dif = clamp(dot(normal, sun_dir), 0.,1.0);
245 | float3 sun_col = material * float3(7.0,5.0,2.6);
246 | float sun_sha =
247 | // step(castShadow(pos + normal * 0.001, sun_dir, tMaterial), 0);
248 | step(castRay(pos + normal * 0.001, sun_dir, time).y, 0);
249 | col = sun_col * sun_dif * sun_sha;
250 |
251 | float3 sky_col = material * float3(0.5, 0.8, 0.9);
252 | float3 sky_dir = float3(0,1.0,0.0);
253 | float sky_dif = clamp(0.5 + 0.5 * dot(normal, sky_dir), 0.,1.);
254 | col += sky_col * sky_dif;
255 |
256 | // Bounce calculates light in "up" direction. Otherwise, similar to sky.
257 | float3 bounce_dir = float3(0,-1,0);
258 | float bounce_dif = clamp(0.5 + 0.5 * dot(normal, bounce_dir), 0.,1.);
259 | float3 bounce_col = float3(0.7,0.3,0.2);
260 | col += material * bounce_col * bounce_dif;
261 | }
262 |
263 | // gamma correction
264 | col = pow(col, 0.4545);
265 |
266 | return float4(col, 1.0 );
267 | }
268 |
269 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/PolarExperiments.metal:
--------------------------------------------------------------------------------
1 | //
2 | // PolarExperiments.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 8/4/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | struct VertexOut {
13 | float4 pos [[position]];
14 | float4 color;
15 | };
16 |
17 | struct VertexIn {
18 | vector_float2 pos;
19 | };
20 |
21 |
22 | struct FragmentUniforms {
23 | float time;
24 | float screen_width;
25 | float screen_height;
26 | float screen_scale;
27 | float2 mousePos;
28 | };
29 |
30 | vertex VertexOut polar_experiments_vertex(const device VertexIn *vertices [[buffer(0)]], unsigned int vid [[vertex_id]]) {
31 | VertexOut v;
32 | v.pos = float4(vertices[vid].pos, 0, 1);
33 | return v;
34 | }
35 |
36 | // ---
37 |
38 | float remaps(float a, float b, float c, float d, float t) {
39 | float val = (t - a) / (b - a) * (d - c) + c;
40 | return clamp(val, 0.0, 1.0);
41 | }
42 |
43 | float2 polar(float2 st) {
44 | float2 polSt = float2(atan2(st.y, st.x), length(st));
45 | return polSt;
46 | }
47 |
48 |
49 | fragment float4 polar_experiments_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
50 | float3 col = {0.7, 0.5, 0.1};
51 |
52 | float2 st = {
53 | interpolated.pos.x / uniforms.screen_width,
54 | 1 - interpolated.pos.y/uniforms.screen_height
55 | };
56 | st -= 0.5;
57 | st.x *= uniforms.screen_width / uniforms.screen_height;
58 | float2 polSt = polar(st);
59 | // polSt.x = remaps(-M_PI_F, M_PI_F, 0, 1, polSt.x);
60 |
61 | float c = polSt.y * abs(cos(polSt.x)*sin(polSt.x)*2);
62 |
63 | // float m = min(fract(x), fract(1-x));
64 | // float c = smoothstep(0., .01, m*.5 + .2 - st.y);
65 | col *= c;
66 |
67 | return float4(col, 1);
68 | }
69 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/RaysConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RaysConfig.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 9/5/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import simd
11 |
12 | class RaysConfig: ObservableObject {
13 | @Published var cameraPosX: Float = 0
14 | @Published var cameraPosY: Float = 0
15 | @Published var cameraPosZ: Float = -30
16 | @Published var cameraRotX: Float = 0
17 | @Published var cameraRotY: Float = 0
18 | @Published var cameraRotZ: Float = 0
19 |
20 | @Published var modelPosX: Float = 0
21 | @Published var modelPosY: Float = 0
22 | @Published var modelPosZ: Float = 0
23 | @Published var modelRotX: Float = 0
24 | @Published var modelRotY: Float = 0
25 | @Published var modelRotZ: Float = 0
26 |
27 | @Published var modelScale: Float = 1
28 |
29 |
30 | func resetModelPos() {
31 | modelPosX = 0
32 | modelPosY = 0
33 | modelPosZ = 0
34 | }
35 |
36 | func resetModelRot() {
37 | modelRotX = 0
38 | modelRotY = 0
39 | modelRotZ = 0
40 | }
41 |
42 | func resetModelScale() {
43 | modelScale = 1
44 | }
45 |
46 | func resetCameraPos() {
47 | cameraPosX = 0
48 | cameraPosY = 0
49 | cameraPosZ = -30
50 | }
51 |
52 | func resetCameraRot() {
53 | cameraRotX = 0
54 | cameraRotY = 0
55 | cameraRotZ = 0
56 | }
57 | }
58 |
59 | let raysConfig = RaysConfig()
60 |
61 | struct RaysConfigView: View {
62 | @EnvironmentObject fileprivate var config: RaysConfig
63 |
64 | private var modelPos: some View {
65 | VStack {
66 | Text("Model").font(.subheadline)
67 | Spacer().frame(height: 10)
68 | HStack {
69 | Text("Position")
70 | Button(action: {
71 | raysConfig.resetModelPos()
72 | }, label: { Text("Reset") })
73 | }
74 | VStack(spacing: 0) {
75 | numSlider($config.modelPosX, "x", extent: 40)
76 | numSlider($config.modelPosY, "y", extent: 40)
77 | numSlider($config.modelPosZ, "z", extent: 40)
78 | }
79 | }
80 | }
81 |
82 | var modelRot: some View {
83 | VStack {
84 | HStack {
85 | Text("Rotation")
86 | Button(action: {
87 | raysConfig.resetModelRot()
88 | }, label: { Text("Reset") })
89 | }
90 | VStack(spacing: 0) {
91 | numSlider($config.modelRotX, "x", extent: 3)
92 | numSlider($config.modelRotY, "y", extent: 3)
93 | numSlider($config.modelRotZ, "z", extent: 3)
94 | }
95 | }
96 | }
97 |
98 | var modelScale: some View {
99 | VStack {
100 | HStack {
101 | Text("Scale")
102 | Button(action: {
103 | raysConfig.resetModelScale()
104 | }, label: { Text("Reset") })
105 | }
106 | numSlider($config.modelScale, "", extent: 1.0, minExtent: 0)
107 | }
108 | }
109 |
110 | var model: some View {
111 | VStack(spacing: 10) {
112 | modelPos
113 | modelRot
114 | modelScale
115 | }
116 | }
117 |
118 | func numSlider(_ binding: Binding, _ label: String, extent: Float, minExtent: Float? = nil) -> some View {
119 | HStack {
120 | Text(label)
121 | Slider(value: binding, in: (minExtent ?? -extent)...extent)
122 | .frame(width: 120)
123 | HStack {
124 | Spacer()
125 | Text(String(format: "%.2f", binding.wrappedValue))
126 | }
127 | }
128 | }
129 |
130 | var camera: some View {
131 | VStack {
132 | Text("Camera").font(.subheadline)
133 | Spacer().frame(height: 10)
134 | HStack {
135 | Text("Position")
136 | Button(action: {
137 | raysConfig.resetCameraPos()
138 | }, label: { Text("Reset") })
139 | }
140 | VStack(spacing: 0) {
141 | numSlider($config.cameraPosX, "x", extent: 40)
142 | numSlider($config.cameraPosY, "y", extent: 40)
143 | numSlider($config.cameraPosZ, "z", extent: 40)
144 | }
145 | Spacer().frame(height: 10)
146 | HStack {
147 | Text("Rotation")
148 | Button(action: {
149 | raysConfig.resetCameraRot()
150 | }, label: { Text("Reset") })
151 | }
152 | VStack(spacing: 0) {
153 | numSlider($config.cameraRotX, "x", extent: 3.14)
154 | numSlider($config.cameraRotY, "y", extent: 3.14)
155 | numSlider($config.cameraRotZ, "z", extent: 3.14)
156 | }
157 | }
158 | }
159 |
160 | var body: some View {
161 | VStack(alignment: .leading, spacing: 0) {
162 | camera
163 | model
164 |
165 | Spacer()
166 | }
167 | }
168 | }
169 |
170 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/ShaderToyDistortions.metal:
--------------------------------------------------------------------------------
1 | //
2 | // ShaderToyDistortions.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 8/2/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 |
13 | struct VertexOut {
14 | float4 pos [[position]];
15 | float4 color;
16 | };
17 |
18 | struct VertexIn {
19 | vector_float2 pos;
20 | };
21 |
22 |
23 | struct FragmentUniforms {
24 | float time;
25 | float screen_width;
26 | float screen_height;
27 | float screen_scale;
28 | float2 mousePos;
29 | };
30 |
31 | vertex VertexOut domain_distortion_vertex(const device VertexIn *vertices [[buffer(0)]], unsigned int vid [[vertex_id]]) {
32 | VertexOut v;
33 | v.pos = float4(vertices[vid].pos, 0, 1);
34 | return v;
35 | }
36 |
37 | // ---
38 |
39 | float baand(float p, float start, float end, float endsBlur) {
40 | float m = smoothstep(start - endsBlur, start + endsBlur, p);
41 | m *= smoothstep(end + endsBlur, end - endsBlur, p);
42 | float2x2 ma = float2x2(0,1,1,0);
43 | float3 x = {0};
44 | float2 mas = x.xz * ma;
45 | x.xz = mas;
46 | return m;
47 | }
48 |
49 | float rectangle(float2 st, float left, float right, float bottom, float top, float blur) {
50 | return
51 | baand(st.x, left, right, blur)
52 | *
53 | baand(st.y, bottom, top, blur)
54 | ;
55 | }
56 |
57 | float remapper(float a, float b, float c, float d, float t) {
58 | float val = (t - a) / (b - a) * (d - c) + c;
59 | return clamp(val, 0.0, 1.0);
60 | }
61 |
62 |
63 | fragment float4 domain_distortion_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
64 | float t = uniforms.time;
65 | float2 st = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y/uniforms.screen_height};
66 | st -= 0.5;
67 | st.x *= uniforms.screen_width / uniforms.screen_height;
68 | float3 col = {0.7, 0.5, 0.1};
69 |
70 | float x = st.x;
71 | float m = sin(x*10 + t*2)/10;
72 | st.y = st.y - m;
73 |
74 | float blur = remapper(-0.5, 0.5, 0, .3, st.x);
75 | blur *= blur;
76 | float val = rectangle(st, -.4, .4, -.1, .1, blur);
77 | col = col * val;
78 | return float4(col, 1);
79 | }
80 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/ShaderToySmiley.metal:
--------------------------------------------------------------------------------
1 | //
2 | // ShaderToySmiley.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/14/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | struct VertexOut {
13 | float4 pos [[position]];
14 | float4 color;
15 | };
16 |
17 | struct VertexIn {
18 | vector_float2 pos;
19 | };
20 |
21 |
22 | struct FragmentUniforms {
23 | float time;
24 | float screen_width;
25 | float screen_height;
26 | float screen_scale;
27 | float2 mousePos;
28 | };
29 |
30 | float random1(float2 st) {
31 | return fract(sin(st.x * 0.122190) * 21001.);
32 | }
33 |
34 | float lerp2(float a, float b, float m, float n, float t) {
35 | return saturate(m + (t - a) / (b - a) * (n - m));
36 | }
37 |
38 | float2 within(float2 st, float4 rect) {
39 | // Like mapping but inside a rect
40 | return (st - rect.xy)/(rect.zw - rect.xy);
41 | }
42 |
43 | float Circle(float2 st, float2 pos, float r, float blur) {
44 | st -= pos;
45 | float c = smoothstep(r, r - blur, length(st));
46 | return c;
47 | }
48 |
49 | float4 Head(float2 st) {
50 | float d = length(st);
51 |
52 | float4 col = float4(0.9, 0.6, 0.1, 1.);
53 |
54 | // add shadow to the edge
55 | float edgeShadow = smoothstep(0.35, 0.5, d);
56 | edgeShadow *= edgeShadow;
57 | col.rgb *= 1.0 - edgeShadow * 0.2;
58 |
59 | // make everything go inside a circle of 0.5 radius
60 | col.a = smoothstep(0.5, 0.5 - 0.001, d);
61 |
62 | /// top highlight
63 | // white inner circle of r=0.41
64 | float highlight = smoothstep(0.41, 0.405, d);
65 | // circle is light white (0.75 at 0.41, vanishes at -0.1)
66 | highlight *= lerp2(0.41, -0.1, 0.75, 0., st.y);
67 | col.rgb = mix(col.rgb, float3(1), highlight);
68 |
69 | /// Cheek
70 | // on both sides
71 | st.x = abs(st.x);
72 | // new d for cheek position
73 | float2 cheekPos = {0.25, -0.2};
74 | d = length(st - cheekPos);
75 | // the gradient
76 | float cheek = smoothstep(0.2, 0.0, d);
77 | cheek *= cheek;
78 | col.rgb = mix(col.rgb, float3(1.0,0.3,0.1), cheek);
79 |
80 | return col;
81 | }
82 |
83 |
84 | float4 Eye(float2 st) {
85 | // receives in 0 → 1
86 | st -= 0.5;
87 |
88 | float d = length(st);
89 | float4 irisCol = float4(.3,.5,1,1);
90 | // goes from white at .1 to irisCol at .7 (whole effect is then halved)
91 | float4 col = mix(float4(1), irisCol, smoothstep(.1, .7, d) * 0.5);
92 |
93 | // inner shadow next to the nose
94 | col.rgb *= 1. - smoothstep(.45, .5, d) * saturate(-st.y-st.x);
95 |
96 | col.rgb = mix(float3(0), col.rgb, smoothstep(0.3, 0.31, d));
97 | col.rgb = mix(irisCol.rgb, col.rgb, smoothstep(0.25, 0.3, d));
98 | col.rgb = mix(float3(0), col.rgb, smoothstep(0.189, 0.19, d));
99 |
100 | /// small circles
101 | // positioned at .1,.1
102 | float highlight = smoothstep(0.1, 0.09, length(st - float2(0.14,0.1)));
103 | col.rgb = mix(col.rgb, float3(1), highlight);
104 | // positioned at -.1,-.1. Smaller (0.04)
105 | highlight += smoothstep(0.04, 0.03, length(st - float2(-0.1,-0.1)));
106 | col.rgb = mix(col.rgb, float3(1), highlight);
107 |
108 | // only have it go till half the length
109 | col.a = smoothstep(0.5, 0.48, d);
110 |
111 | return col;
112 | }
113 |
114 | float4 Mouth(float2 st) {
115 | st -= .5;
116 | st.y *= 1.5;
117 | st.y -= 2 * st.x * st.x;
118 | float4 col = float4(0.5, 0.18, 0.05, 1.);
119 | float d = length(st);
120 | col.a = smoothstep(0.5, 0.49, d);
121 |
122 |
123 | // teeth
124 | float td = length(st - float2(0, .6));
125 | float3 toothCol = float3(1) * smoothstep(0.6, 0.35, d);
126 | col.rgb = mix(toothCol, col.rgb, smoothstep(0.49, 0.5, td));
127 |
128 | return col;
129 | }
130 |
131 | float4 Smiley(float2 st) {
132 | float4 col = float4(0);
133 |
134 | // Duplicate the right to the left as well:
135 | st.x = abs(st.x);
136 |
137 | // the alpha of Eye, Head, etc. will be 0 outside their drawing,
138 | // so that doing the mix with col using the `alpha` would only draw "within"
139 |
140 | float4 eye = Eye(within(st, float4(0.03, -0.1, 0.37, 0.25)));
141 | float4 head = Head(st);
142 | float4 mouth = Mouth(within(st, float4(-.3,-.4,.3,-.1)));
143 |
144 | col = mix(col, head, head.a);
145 | col = mix(col, eye, eye.a);
146 | col = mix(col, mouth, mouth.a);
147 |
148 | return col;
149 | }
150 |
151 | vertex VertexOut shaderToySmileyVertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
152 | VertexIn in = vertexArray[vid];
153 | VertexOut out;
154 | out.pos = float4(in.pos, 0, 1);
155 | return out;
156 | }
157 |
158 |
159 | fragment float4 shaderToySmiley(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
160 | // float t = uniforms.time;
161 | float2 st = {interpolated.pos.x / uniforms.screen_width, interpolated.pos.y / uniforms.screen_height};
162 | st.y = 1. - st.y; // make it go from bottom to top
163 | st -= 0.5; // center
164 |
165 | float4 smiley = Smiley(st);
166 | return smiley;
167 | }
168 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/ShaderToyStarField.metal:
--------------------------------------------------------------------------------
1 | //
2 | // ShaderToyStarField.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/16/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 | struct VertexOut {
13 | float4 pos [[position]];
14 | float4 color;
15 | };
16 |
17 | struct VertexIn {
18 | vector_float2 pos;
19 | };
20 |
21 |
22 | struct FragmentUniforms {
23 | float time;
24 | float screen_width;
25 | float screen_height;
26 | float screen_scale;
27 | float2 mousePos;
28 | };
29 |
30 | struct StarfieldUniforms {
31 | bool rotating;
32 | bool flying;
33 | float num_layers;
34 | float num_density;
35 | };
36 |
37 | #define S(a, b, t) smoothstep(a, b, t)
38 |
39 | float map(float a, float b, float c, float d, float t) {
40 | float val = (t - a) / (b - a) * (d - c) + c;
41 | return clamp(val, 0.0, 1.0);
42 | }
43 |
44 | /// Normalize st within the box: if st.x == 0, it will be on left side; if st.y == 0, it will be on bottom side;
45 | float2 withinRect(float2 st, float4 rect) {
46 | return (st - rect.xy) / (rect.zw - rect.xy);
47 | }
48 |
49 | float2x2 Rot(float a) {
50 | float s = sin(a), c = cos(a);
51 | return float2x2(c, -s, s, c);
52 | }
53 |
54 | float Star(float2 st, float flare) {
55 | float d = length(st);
56 |
57 | float m = 0;
58 |
59 | float star = .05/d;
60 | m += star;
61 |
62 | // 2 rays:
63 | // 1st
64 | float rays = max(0., 1. - abs(st.y * st.x * 1000));
65 | m += rays * flare;
66 | // 2nd rotated
67 | st *= Rot(3.1415/4);
68 | rays = max(0., 1. - abs(st.y * st.x * 1000));
69 | m += rays * .3 * flare;
70 |
71 | // don't project indefinitely outside the grid
72 | // (just in the next neighbor, but not the neighbor's neighbor)
73 | m *= smoothstep(0.9, .2, d);
74 |
75 | return m;
76 | }
77 |
78 | float Hash12(float2 p) {
79 | p = fract(p * float2(123.34, 456.21));
80 | p += dot(p, p + 45.32);
81 | return fract(p.x * p.y);
82 | }
83 |
84 | float3 starField(float2 st) {
85 | float3 color = 0;
86 | float2 id = floor(st);
87 | float2 gv = fract(st)
88 | - 0.5 // move the coordinate-system so star is expected in the center
89 | ;
90 |
91 | for (int x=-1; x<=1; x++) {
92 | for (int y=-1; y<=1; y++) {
93 | float2 offset = float2(x, y);
94 | float contributingStarId = Hash12(id + offset);
95 | float2 gvPos = gv // place in the unit box for which we are asking for pixel value if star is centered in the grid
96 | - offset // consider the star to be in a different grid if we have an offset
97 | - (float2(
98 | contributingStarId, // add a random offset for x inside the grid
99 | fract(contributingStarId * 12) // same for y, but make it a different random
100 | )
101 | - .5
102 | ) ;
103 |
104 | float size = fract(contributingStarId * 219.34);
105 | float flare = smoothstep(0.4, .8, size); // only for bigger stars
106 | float star = Star(gvPos, flare);
107 | float3 col = sin(float3(0.6, 0.4, 0.8) * fract(contributingStarId * 210) * 212.) * .5 + .5;
108 | color += star * size * col;
109 | }
110 | }
111 | return color;
112 | }
113 |
114 | vertex VertexOut shadertoy_starfield_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
115 | VertexIn in = vertexArray[vid];
116 | VertexOut out;
117 | out.pos = float4(in.pos, 0, 1);
118 | return out;
119 | }
120 |
121 |
122 | fragment float4 shaderToyStarfield(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]],
123 | constant StarfieldUniforms &uniforms2 [[buffer(1)]] ) {
124 | float t = uniforms.time * 0.1;
125 | if (!uniforms2.flying) {
126 | t = 0;
127 | }
128 | float2 st = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y / uniforms.screen_height};
129 | st -= 0.5;
130 |
131 | if (uniforms2.rotating) {
132 | st *= Rot(t);
133 | }
134 | st *= uniforms2.num_density; // divides the screen in to a grid (rather, repeats the
135 |
136 | float numLayers = uniforms2.num_layers;
137 |
138 | float3 color = 0;
139 | for (float i = 0; i < 1; i+=1/numLayers) {
140 | float depth = fract(i + t); // increading depth, b/w 0 and 1
141 | float scale = mix(20, .5, depth); // smaller in the back
142 | float fade = depth; // don't count the color much if it's in the back (depth ~= 0)
143 | float layerOffset =
144 | floor(i + t);
145 | // i * 453.2;
146 | color += starField(st * scale + layerOffset) * fade;
147 | }
148 |
149 | // if (abs(gv.x) > 0.49 || abs(gv.y) > 0.49) color.r = 1;
150 |
151 | return float4(color, 1);
152 | }
153 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/Simplest3D.metal:
--------------------------------------------------------------------------------
1 | //
2 | // Simplest3D.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 9/17/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 |
12 |
13 | struct VertexOut {
14 | float4 pos [[position]];
15 | float4 color;
16 | };
17 |
18 | struct VertexIn {
19 | vector_float2 pos;
20 | };
21 |
22 |
23 | struct FragmentUniforms {
24 | float time;
25 | float screen_width;
26 | float screen_height;
27 | float screen_scale;
28 | float2 mousePos;
29 | };
30 |
31 | vertex VertexOut simplest_3d_vertex(const device VertexIn *vertices [[buffer(0)]], unsigned int vid [[vertex_id]]) {
32 | VertexOut v;
33 | v.pos = float4(vertices[vid].pos, 0, 1);
34 | return v;
35 | }
36 |
37 | float rayDist(float3 r0, float3 rd, float3 p) {
38 | if (length(rd) == 0) {
39 | return 0.2;
40 | }
41 | return length(cross(p - r0, rd)) / length(rd);
42 | }
43 |
44 | float drawPoint(float3 r0, float3 rd, float3 p) {
45 | float d = rayDist(r0, rd, p);
46 | d = smoothstep(0.06, 0.05, d);
47 | return d;
48 | }
49 |
50 | // ---
51 | fragment float4 simplest_3d_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
52 | float2 st = {
53 | interpolated.pos.x / uniforms.screen_width,
54 | 1 - interpolated.pos.y / uniforms.screen_height
55 | };
56 | st -= 0.5;
57 | st.x *= uniforms.screen_width / uniforms.screen_height;
58 |
59 | float3 rayOrigin = {2,0,-4}; // camera pos
60 |
61 |
62 | float zoom = 1;
63 |
64 | // ray from camera to the screen point (and then in to the screen to hit an object)
65 | float3 lookAt = {0.5};
66 | float3 forward = normalize(lookAt - rayOrigin);
67 | float3 right = cross( float3(0, 1, 0), forward);
68 | float3 up = cross(forward, right);
69 |
70 | float3 screenCenter = rayOrigin + forward * zoom;
71 |
72 | float3 screenPoint = screenCenter + st.x * right + st.y * up;
73 | float3 rayDirection = screenPoint - rayOrigin;
74 |
75 | float d = 0;
76 | for (int z=0; z<=1; z++) {
77 | for (int y=0; y<=1; y++) {
78 | for (int x=0; x<=1; x++) {
79 | float3 p = {float(x),float(y),float(z)};
80 | d += drawPoint(rayOrigin, rayDirection, p);
81 | }
82 | }
83 | }
84 |
85 | float3 col = d;
86 |
87 | return float4(col, 1);
88 | }
89 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Explorations/Starfield.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Starfield.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 9/22/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import MetalKit
10 | import SwiftUI
11 |
12 | class StarField: Playground {
13 | class StarFieldConfig: ObservableObject {
14 | @Published var rotating: Bool = false
15 | @Published var flying: Bool = true
16 | @Published var numDepthLayers: Float = 1
17 | @Published var numDensityLayers: Float = 1
18 | }
19 |
20 | let starfieldConfig = StarFieldConfig()
21 |
22 | struct Uniforms {
23 | var rotating: Bool
24 | var flying: Bool
25 | var numDepthLayers: Float
26 | var numDensityLayers: Float
27 | }
28 |
29 | static var name: String { "Star Field" }
30 | var fileName: String {
31 | "Explorations/ShaderToyStarField"
32 | }
33 | var vertexFuncName: String { "shadertoy_starfield_vertex" }
34 | var fragmentFuncName: String { "shaderToyStarfield" }
35 | var starFieldUniforms: Uniforms {
36 | Uniforms(rotating: starfieldConfig.rotating, flying: starfieldConfig.flying, numDepthLayers: Float(starfieldConfig.numDepthLayers), numDensityLayers: Float(starfieldConfig.numDensityLayers))
37 | }
38 |
39 | var fragmentUniforms: Any? {
40 | starFieldUniforms
41 | }
42 | struct ConfigView: View {
43 | @EnvironmentObject var config: StarFieldConfig
44 |
45 | var body: some View {
46 | VStack(alignment: .leading, spacing: 19) {
47 | Toggle("Rotating", isOn: $config.rotating)
48 | Toggle("Flying", isOn: $config.flying)
49 | TitledSlider(title: "Depth Layers", value: $config.numDepthLayers, in: 0...10, step: 1) {
50 | self.config.numDepthLayers = 1
51 | }
52 | TitledSlider(title: "Density Layers", value: $config.numDensityLayers, in: 1...10, step: 1) {
53 | self.config.numDensityLayers = 1
54 | }
55 | }
56 | }
57 | }
58 |
59 | func setUniforms(device: MTLDevice, encoder: MTLRenderCommandEncoder) {
60 | var uniform = self.starFieldUniforms
61 | let length = MemoryLayout.size(ofValue: uniform)
62 | encoder.setFragmentBytes(&uniform, length: length, index: 1)
63 | }
64 |
65 | var view: NSView? {
66 | NSHostingView(
67 | rootView: ConfigView().environmentObject(starfieldConfig)
68 | )
69 | }
70 | required init() {}
71 | }
72 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Helpers.metal:
--------------------------------------------------------------------------------
1 | //
2 | // Helpers.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 1/9/21.
6 | // Copyright © 2021 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 |
11 | using namespace metal;
12 | #include "ShaderHeaders.h"
13 |
14 | float2x2 rotate(float angle) {
15 | return float2x2(cos(angle),sin(-angle), sin(angle),cos(angle));
16 | }
17 |
18 | float2x2 scale(float2 sc) {
19 | sc = 1/sc;
20 | return float2x2(
21 | sc.x, 0.,
22 | 0., sc.y
23 | );
24 | }
25 |
26 | float absSin(float t) {
27 | return (sin(t) + 1)/2.0;
28 | }
29 |
30 | float lerp(float x, float u, float v, float m, float n) {
31 | float prog = (x - u) / (v - u);
32 | return m + (n - m) * prog;
33 | }
34 |
35 | float lerpU(float x, float u, float v) {
36 | return lerp(x, u, v, 0.0, 1.0);
37 | }
38 |
39 | float circle(float2 st, float time, float rad, float2 center) {
40 | float variation =
41 | (1 + cos(time)) / 2.0
42 | ;
43 | float pct = distance(st, center);
44 | float dist = 1 - step(rad * variation, pct);
45 | return dist;
46 | }
47 |
48 | // MARK: SDFs
49 |
50 | float getDistanceToCylinder(float3 point) {
51 | float spheresRadius = 0.4;
52 | float3 startAPosition = float3(-0, 0.5, 1);
53 | float3 endBPosition = float3(4, 0.5, 3);
54 | float3 aToB = endBPosition - startAPosition;
55 | float3 pointToA = point - startAPosition;
56 | float t = dot(pointToA, aToB) / dot(aToB, aToB);
57 | float3 center = startAPosition + t * aToB;
58 | float x = length(point - center) - spheresRadius;
59 | float y = (abs(t - 0.5) - 0.5) * length(aToB);
60 | float exterior = length(max(float2(x, y), 0));
61 | float interior = min(max(x, y), 0.);
62 | return exterior + interior;
63 | }
64 |
65 | float getDistanceToCapsule(float3 point) {
66 | float spheresRadius = 0.1;
67 | float3 startAPosition = float3(1, 0.5, 1);
68 | float3 endBPosition = float3(1.5, 0.5, 1);
69 | float3 aToB = endBPosition - startAPosition;
70 | float3 pointToA = point - startAPosition;
71 | float t = dot(pointToA, aToB) / dot(aToB, aToB);
72 | t = clamp(t, 0., 1.);
73 | float3 center = startAPosition + t * aToB;
74 | float d = length(point - center) - spheresRadius;
75 | return d;
76 | }
77 |
78 | float getDistanceToTorus(float3 point) {
79 | float innerRadius = 1.06;
80 | float thickness = 0.40;
81 | float x = length(point.xz) - innerRadius;
82 | float y = length(float2(x, point.y)) - thickness;
83 | return y;
84 | }
85 |
86 | float getDistanceToBox(float3 point, float size) {
87 | float3 boxSize = float3(size);
88 | float3 q = abs(point) - boxSize;
89 | return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
90 | }
91 |
92 | float getDistanceToPlane(float3 point) {
93 | float p = dot(point, normalize(float3(0, 1, 0)));
94 | return p;
95 | }
96 |
97 | float sdfCircle(float2 p, float r) {
98 | return length(p) - r;
99 | }
100 |
101 | float sdfLine(float2 p, float2 a, float2 b) {
102 | float2 pa = p - a;
103 | float2 ba = b - a;
104 |
105 | float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
106 | return length(pa - ba * h);
107 |
108 | }
109 |
110 |
111 | // MARK: SDF Operations
112 |
113 | float subtractSDFs(float a, float b) {
114 | return max(-a, b);
115 | }
116 |
117 | float intersectSDFs(float a, float b) {
118 | return max(a, b);
119 | }
120 | float unionSDFs(float a, float b) {
121 | return min(a, b);
122 | }
123 |
124 | float blendSDFs(float a, float b, float t) {
125 | return mix(a, b, t);
126 | }
127 |
128 | float smoothUnionSDFs( float a, float b, float k) {
129 | float h = clamp( 0.5+0.5* (b-a)/k, 0., 1. );
130 | return mix( b, a, h) - k*h* (1.0-h);
131 | }
132 |
133 | float softMax(float a, float b, float k) {
134 | return log(exp(k * a) + exp(k * b)) / k;
135 | }
136 |
137 | float softMin(float a, float b, float k) {
138 | k *= 1.0; // why?
139 | float r = exp2(-a/k) + exp2(-b/k);
140 | return -k * log2(r);
141 | }
142 |
143 | // MARK: Colors
144 | float3 red() { return float3(1.0, 0.0, 0.0); }
145 | float3 green() { return float3(0.0, 1.0, 0.0); }
146 | float3 blue() { return float3(0.4, 0.4, 1.0); }
147 | float3 yellow() { return float3(1.0, 1.0, 0.0); }
148 | float3 black() { return float3(0.0, 0.0, 0.0); }
149 | float3 gray() { return float3(0.8, 0.8, 0.8); }
150 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/JumpingBalls/JumpingBalls.metal:
--------------------------------------------------------------------------------
1 | #include
2 | using namespace metal;
3 | #include "../ShaderHeaders.h"
4 |
5 | struct VertexIn {
6 | vector_float2 pos;
7 | };
8 |
9 | struct FragmentUniforms {
10 | float time;
11 | float screen_width;
12 | float screen_height;
13 | float screen_scale;
14 | float2 mousePos;
15 | };
16 |
17 | struct VertexOut {
18 | float4 pos [[position]];
19 | float4 color;
20 | };
21 |
22 | vertex VertexOut jumpingVertexShader(const device VertexIn *vertices [[buffer(0)]], unsigned int vid [[vertex_id]]) {
23 | VertexOut in;
24 | in.pos = {vertices[vid].pos.x, vertices[vid].pos.y, 0, 1};
25 | return in;
26 | }
27 |
28 | float fmodal(float a, float b) {
29 | if(a<0.0) {
30 | return b - modf(abs(a), b);
31 | }
32 | return modf(a, b);
33 | }
34 |
35 | // MARK: - Ray Marching
36 |
37 | float sdfSphere(float3 p, float r) {
38 | return length(p) - r;
39 | }
40 |
41 | float sdPlane(float3 p) {
42 | return p.y - (-1.3);
43 | }
44 |
45 | float2 scene(float3 position, float time) {
46 | // sphere
47 | float3 spherePosition = position - float3(0, 0.5, 1.0);
48 | float distance = sdfSphere(spherePosition, 0.8);
49 | float materialID = 1.0;
50 |
51 | // Other objects
52 | float3 objPosition = position - float3(0, 0.5, 1); // starting point
53 | objPosition.x = fract(objPosition.x + 0.5) - 0.5;
54 | objPosition.z = fmod(objPosition.z + 1.0, 2.0) - 1.0;
55 | objPosition.y += sin(position.x + time) * 0.45;
56 | objPosition.y += cos(position.z + time * 3) * 0.45;
57 | float distanceObjs = sdfSphere(objPosition, 0.2 * fract(objPosition.z));
58 | if (distanceObjs < distance) {
59 | distance = distanceObjs;
60 | materialID = 2.0;
61 | }
62 |
63 | float distancePlane = sdPlane(position);
64 | if (distancePlane < distance) {
65 | distance = distancePlane;
66 | materialID = 3.0;
67 | }
68 |
69 | return float2(distance, materialID);
70 | }
71 |
72 | const constant float NEAR_CLIPPING_PLANE = 0.21;
73 | const constant float FAR_CLIPPING_PLANE = 20.01;
74 | const constant float MAX_MARCH_STEPS = 200;
75 | const constant float EPSILON = 0.01;
76 | const constant float DISTANCE_BIAS = 0.7;
77 |
78 | float2 rayMarchTo(float3 position, float3 direction, float time) {
79 | float total_distance = NEAR_CLIPPING_PLANE;
80 | float2 result;
81 | for(int i = 0; i < MAX_MARCH_STEPS; i++) {
82 | // result.x is the distance and result.y is the material
83 | result = scene(position + direction * total_distance, time);
84 | if (result.x < EPSILON) { break; }
85 | total_distance += result.x * DISTANCE_BIAS;
86 | if (total_distance > FAR_CLIPPING_PLANE) { break; }
87 | }
88 |
89 | return float2(total_distance, result.y);
90 | }
91 |
92 | float3 getNormal(float3 rayHitPosition, float smoothness, float time) {
93 | float3 n;
94 | float2 dn = float2(smoothness, 0);
95 | // here .x from the scene is the distance
96 | n.x = scene(rayHitPosition + dn.xyy, time).x - scene(rayHitPosition - dn.xyy, time).x;
97 | n.y = scene(rayHitPosition + dn.yxy, time).x - scene(rayHitPosition - dn.yxy, time).x;
98 | n.z = scene(rayHitPosition + dn.yyx, time).x - scene(rayHitPosition - dn.yyx, time).x;
99 | return normalize(n);
100 | }
101 |
102 | // MARK: - Fragment Shader
103 |
104 | fragment float4 jumpingFragmentShader(
105 | VertexOut interpolated [[stage_in]],
106 | constant FragmentUniforms &uniforms [[buffer(0)]]
107 | ) {
108 | float2 uv = 2 * (float2(
109 | interpolated.pos.x / uniforms.screen_width,
110 | 1 - interpolated.pos.y / uniforms.screen_height
111 | ) - 0.5);
112 | uv.x *= uniforms.screen_width / uniforms.screen_height;
113 |
114 | float time = uniforms.time;
115 |
116 | // -- Main image generation
117 |
118 | float3 direction = normalize(float3(uv.x, uv.y, 2.5));
119 | float3 cameraOrigin = float3(0, 0, -6.5);
120 | float2 result = rayMarchTo(cameraOrigin, direction, time);
121 |
122 | // TODO: use fog
123 | float fog = 1.0 - result.x / FAR_CLIPPING_PLANE;
124 |
125 | float3 materialColor = float3(0.0, 0.0, 0);
126 | if (result.y == 1.0) {
127 | // sphere
128 | materialColor = float3(1.0, 0.25, 1);
129 | } else if (result.y == 2.0) {
130 | // sphere
131 | materialColor = float3(1.0, 0.75, 0.2);
132 | } else if (result.y == 3.0) {
133 | // sphere
134 | materialColor = float3(0.0, 0.15, 0.1);
135 | }
136 |
137 | // Lighting
138 | float3 intersection = cameraOrigin + direction * result.x;
139 | float3 nrml = getNormal(intersection, 0.01, time);
140 | float3 lightDir = normalize(float3(0, 1, 0.0));
141 | float diffuse = dot(lightDir, nrml);
142 | diffuse = diffuse * 0.5 + 0.5;
143 | // Combine ambient and diffuse
144 | float3 lightColor = float3(1.0, 1.2, 0.7);
145 | float3 ambientColor = float3(0.2, 0.45, 0.6);
146 | float3 diffuseLit = materialColor * (diffuse * lightColor + ambientColor);
147 |
148 | float3 color = diffuseLit * fog;
149 |
150 | // -- Image generation DONE
151 |
152 | return float4(color, 1.0);
153 | }
154 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/JumpingBalls/JumpingBalls.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LiveCode.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 1/9/21.
6 | // Copyright © 2021 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import MetalKit
10 | import SwiftUI
11 | import Accelerate
12 | import AVFoundation
13 |
14 |
15 | extension MTLDevice {
16 | func makeBuffer(_ values: [Float]) -> MTLBuffer? {
17 | makeBuffer(bytes: values, length: MemoryLayout.size * values.count)
18 | }
19 | }
20 |
21 | public extension MTLRenderCommandEncoder {
22 | func setFragmentBytes(_ value: T, index: Int) {
23 | var copy = value
24 | setFragmentBytes(©, length: MemoryLayout.size, index: index)
25 | }
26 |
27 | func setFragmentBytes(_ value: T, index: Int32) {
28 | var copy = value
29 | setFragmentBytes(©, length: MemoryLayout.size, index: Int(index))
30 | }
31 | }
32 |
33 | extension Color {
34 | var components: SIMD4 {
35 |
36 | var r: CGFloat = 0
37 | var g: CGFloat = 0
38 | var b: CGFloat = 0
39 | var a: CGFloat = 0
40 |
41 | #if canImport(UIKit)
42 | UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a)
43 | #elseif canImport(AppKit)
44 | NSColor(self).usingColorSpace(.deviceRGB)!.getRed(&r, green: &g, blue: &b, alpha: &a)
45 | #endif
46 |
47 | return .init(Float(r), Float(g), Float(b), Float(a))
48 | }
49 | }
50 |
51 | final class JumpingBalls: Playground {
52 | static let name = "Jumping Balls"
53 | var fileName: String {
54 | "JumpingBalls/JumpingBalls"
55 | }
56 |
57 | let vertexFuncName = "jumpingVertexShader"
58 | let fragmentFuncName = "jumpingFragmentShader"
59 |
60 | init() {}
61 |
62 | func tearDown() { }
63 |
64 | private var device: MTLDevice?
65 | private var pixelFormat: MTLPixelFormat?
66 |
67 | func setUniforms(device: MTLDevice, encoder: MTLRenderCommandEncoder) { }
68 | }
69 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/MetalHeaders.h:
--------------------------------------------------------------------------------
1 | //
2 | // MetalHeaders.h
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/11/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 |
10 | #ifndef METAL_CONSTANTS
11 | #define METAL_CONSTANTS
12 |
13 | struct VertexIn {
14 | vector_float2 pos;
15 | };
16 |
17 | struct FragmentUniforms {
18 | float time;
19 | float screen_width;
20 | float screen_height;
21 | float screen_scale;
22 | float2 mousePos;
23 | };
24 |
25 | struct VertexOut {
26 | float4 pos [[position]];
27 | float4 color;
28 | };
29 |
30 | enum class CircleKind { single, multiple };
31 |
32 | float3 blurRect(float2 st, float time, float4 margins);
33 |
34 | float3 rect(float2 st, float time, float4 margins, float3 mainColor);
35 |
36 | float3 outlineRect(float2 st, float4 margins, float time);
37 |
38 | float circle(float2 st, float time, float rad, float2 center);
39 |
40 | float remap(float a, float b, float c, float d, float t);
41 |
42 | #endif
43 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/Playground.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Scene.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 7/26/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import MetalKit
10 | import SwiftUI
11 |
12 | protocol Playground {
13 | typealias Built = (MTLRenderPipelineState, MTLBuffer) -> Void
14 |
15 | /// Filename for the Shader
16 | var fileName: String { get }
17 | var vertexFuncName: String { get }
18 | var fragmentFuncName: String { get }
19 |
20 | // For mostly fragment-based
21 | var liveReloads: Bool { get }
22 | var ready: Bool { get }
23 |
24 | init()
25 |
26 | var view: NSView? { get }
27 | func tick(time: Float)
28 | func setUniforms(device: MTLDevice, encoder: MTLRenderCommandEncoder)
29 | func buildPipeline(device: MTLDevice, pixelFormat: MTLPixelFormat, built: @escaping Built)
30 | func draw(encoder: MTLRenderCommandEncoder)
31 | var isPaused: Bool { get }
32 | }
33 |
34 | extension Playground {
35 | var filePath: String { #filePath }
36 | func tick(time: Float) {}
37 | var isPaused: Bool { false }
38 | var liveReloads: Bool { true }
39 | var basicVertices: [Vertex] {
40 | [
41 | Vertex(position: [-1, -1]),
42 | Vertex(position: [-1, 1]),
43 | Vertex(position: [1, 1]),
44 |
45 | Vertex(position: [-1, -1]),
46 | Vertex(position: [1, 1]),
47 | Vertex(position: [1, -1]),
48 | ]
49 | }
50 |
51 | var ready: Bool { true }
52 |
53 | func buildPipeline(device: MTLDevice, pixelFormat: MTLPixelFormat, built: @escaping Built) {
54 | let descriptor = buildBasicPipelineDescriptor(device: device, pixelFormat: pixelFormat)
55 | let pipeline = (try? device.makeRenderPipelineState(descriptor: descriptor))!
56 |
57 | let vertexBuffer = device.makeBuffer(
58 | bytes: basicVertices, length: MemoryLayout.stride * basicVertices.count,
59 | options: [])
60 | built(pipeline, vertexBuffer!)
61 | }
62 |
63 | func buildBasicPipelineDescriptor(device: MTLDevice, pixelFormat: MTLPixelFormat)
64 | -> MTLRenderPipelineDescriptor
65 | {
66 | let pipelineDesc = MTLRenderPipelineDescriptor()
67 | let library = device.makeDefaultLibrary()
68 | pipelineDesc.vertexFunction = library?.makeFunction(name: vertexFuncName)
69 | pipelineDesc.fragmentFunction = library?.makeFunction(name: fragmentFuncName)
70 | pipelineDesc.colorAttachments[0].pixelFormat = pixelFormat
71 | return pipelineDesc
72 | }
73 |
74 | /// In most scenes where fragment shaders do the rendering themselves, basicVertices can be used for full screen Clip space coordinates.
75 | /// Hence this default implementation
76 | func draw(encoder: MTLRenderCommandEncoder) {
77 | encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: basicVertices.count)
78 | }
79 |
80 | func setUniforms(device: MTLDevice, encoder: MTLRenderCommandEncoder) {}
81 |
82 | var view: NSView? {
83 | nil
84 | }
85 | }
86 |
87 | enum PlaygroundGroup: String, CaseIterable, Identifiable {
88 | case bookOfShaders = "Book of Shaders"
89 | case artOfCode = "The Art of Code"
90 | case explorations = "Explorations"
91 | case simonDev = "Simon's Shaders Course"
92 |
93 | var id: String { rawValue }
94 | var scenes: [SceneKind] {
95 | switch self {
96 | case .bookOfShaders:
97 | return [
98 | .bookOfShaders07Shapes,
99 | .bookOfShaders05Shaping,
100 | .leftRightTiler,
101 | .futuristicUI,
102 | .domainDisortion,
103 | .bookOfShaders06Colors,
104 | ]
105 | case .artOfCode:
106 | return [
107 | .smiley, .starfield, .simplest3D, .polarScene,
108 | ]
109 | case .explorations:
110 | return [
111 | .audioVisualizer, .happyJumping, .girihPattern, .jumpingBalls,
112 | .cellularNoise
113 | ]
114 | case .simonDev:
115 | return [.simonNoiseIntro, .simonDevFractAndFriends, .simonDevSDFs, .simonDevCloudyDays]
116 | }
117 | }
118 | }
119 |
120 | enum SceneKind: Int, CaseIterable, Identifiable {
121 | case jumpingBalls
122 | case leftRightTiler
123 | case futuristicUI
124 | case domainDisortion
125 | case bookOfShaders05Shaping
126 | case bookOfShaders06Colors
127 | case bookOfShaders07Shapes
128 |
129 | case happyJumping
130 | case smiley
131 | case girihPattern
132 | case starfield
133 | case simplest3D
134 | case polarScene
135 | case audioVisualizer
136 | case cellularNoise
137 |
138 | case simonDevFractAndFriends
139 | case simonDevSDFs
140 | case simonDevCloudyDays
141 | case simonNoiseIntro
142 |
143 | var id: Int {
144 | rawValue
145 | }
146 |
147 | var name: String {
148 | switch self {
149 | case .jumpingBalls:
150 | return "Jumping Balls"
151 | case .audioVisualizer:
152 | return "Audio Visualizer"
153 | case .bookOfShaders05Shaping:
154 | return "Book Of Shaders - 05 - Shaping"
155 | case .bookOfShaders07Shapes:
156 | return "Book Of Shaders - 07 - Shapes"
157 | case .leftRightTiler:
158 | return "Book of Shaders - Left/Right Tiler"
159 | case .smiley:
160 | return "Smiley"
161 | case .girihPattern:
162 | return "Girih"
163 | case .happyJumping:
164 | return "Happy Jumping"
165 | case .starfield:
166 | return "Starfield"
167 | case .simplest3D:
168 | return "Simplest 3D"
169 | case .domainDisortion:
170 | return "Domain Distortion"
171 | case .polarScene:
172 | return "Polar"
173 | case .futuristicUI:
174 | return "Futuristic UI"
175 | case .cellularNoise:
176 | return "Cellular Noise"
177 | case .bookOfShaders06Colors:
178 | return "Colors Mixing"
179 | case .simonDevFractAndFriends:
180 | return "Simon Dev - Fract and Friends"
181 | case .simonDevSDFs:
182 | return "Simon Dev - SDFs"
183 | case .simonDevCloudyDays:
184 | return "Simon Dev - Cloudy Days"
185 | case .simonNoiseIntro:
186 | return "Simon Dev - Noise Intro"
187 | }
188 | }
189 |
190 | var scene: Playground {
191 | switch self {
192 | case .audioVisualizer: return AudioVizScene()
193 | case .jumpingBalls: return JumpingBalls()
194 | case .leftRightTiler: return BoSLeftRightTiler()
195 | case .bookOfShaders05Shaping: return BoSShaping()
196 | case .bookOfShaders06Colors: return BoSColors06()
197 | case .bookOfShaders07Shapes: return BoSShapes07()
198 | case .futuristicUI: return FuturisticUI()
199 | case .happyJumping: return HappyJumping()
200 | case .girihPattern: return Girih()
201 | case .starfield: return StarField()
202 | case .smiley: return Smiley()
203 | case .simplest3D: return Simplest3D()
204 | case .polarScene: return PolarScene()
205 | case .domainDisortion: return DomainDistortion()
206 | case .cellularNoise: return CellularNoise()
207 | case .simonDevFractAndFriends: return SimonFractAndFriends()
208 | case .simonDevSDFs: return SimonSDFs()
209 | case .simonDevCloudyDays: return SimonCloudyDay()
210 | case .simonNoiseIntro: return SimonNoiseIntro()
211 | }
212 | }
213 | }
214 |
215 | extension MDLVertexDescriptor {
216 | var vertexAttributes: [MDLVertexAttribute] {
217 | attributes as! [MDLVertexAttribute]
218 | }
219 |
220 | var bufferLayouts: [MDLVertexBufferLayout] {
221 | layouts as! [MDLVertexBufferLayout]
222 | }
223 |
224 | static var `default`: MDLVertexDescriptor = {
225 | let vd = MDLVertexDescriptor()
226 | // position
227 | vd.vertexAttributes[0].name = MDLVertexAttributePosition
228 | vd.vertexAttributes[0].format = .float3
229 | vd.vertexAttributes[0].offset = 0
230 | vd.vertexAttributes[0].bufferIndex = 0
231 | var nextOffset = MemoryLayout.size
232 |
233 | // normal
234 | // vd.vertexAttributes[1].name = MDLVertexAttributeNormal
235 | // vd.vertexAttributes[1].format = .float3
236 | // vd.vertexAttributes[1].offset = nextOffset
237 | // vd.vertexAttributes[1].bufferIndex = 0
238 | // nextOffset += MemoryLayout.size * 3
239 | vd.bufferLayouts[0].stride = nextOffset
240 |
241 | return vd
242 | }()
243 | }
244 |
245 | // MARK: Simon's Shaders Course
246 |
247 | class SimonFractAndFriends: Playground {
248 | var fileName: String {
249 | "SimonShaders/FractAndFriends"
250 | }
251 | var vertexFuncName: String { "fract_and_friends_vertex" }
252 | var fragmentFuncName: String { "fract_and_friends_fragment" }
253 | var texture: MTLTexture?
254 |
255 | required init() {}
256 | func setUniforms(device: any MTLDevice, encoder: any MTLRenderCommandEncoder) {
257 | if texture == nil {
258 | let imageURL = Bundle.main.url(forResource: "wallpaper.png", withExtension: nil)!
259 | let loader = MTKTextureLoader(device: device)
260 | self.texture = try! loader.newTexture(URL: imageURL)
261 | }
262 | if let texture {
263 | encoder.setFragmentTexture(texture, index: 10)
264 | }
265 | }
266 | static func makeTexture(
267 | size: CGSize,
268 | pixelFormat: MTLPixelFormat,
269 | label: String,
270 | device: any MTLDevice,
271 | storageMode: MTLStorageMode = .private,
272 | usage: MTLTextureUsage = [.shaderRead, .renderTarget]
273 | ) -> MTLTexture? {
274 | let width = Int(size.width)
275 | let height = Int(size.height)
276 |
277 | let textureDesc = MTLTextureDescriptor.texture2DDescriptor(
278 | pixelFormat: pixelFormat,
279 | width: width,
280 | height: height,
281 | mipmapped: false
282 | )
283 | textureDesc.usage = usage
284 | let texture = device.makeTexture(descriptor: textureDesc)
285 | texture?.label = label
286 | return texture
287 | }
288 | }
289 |
290 | class SimonNoiseIntro: Playground {
291 | var fileName: String {
292 | "SimonShaders/SimonNoiseIntro"
293 | }
294 | var vertexFuncName: String { "simon_noise_intro_vertex" }
295 | var fragmentFuncName: String { "simon_noise_intro_fragment" }
296 | var texture: MTLTexture?
297 |
298 | required init() {}
299 | }
300 | class SimonCloudyDay: Playground {
301 | var fileName: String {
302 | "SimonShaders/SimonCloudyDay"
303 | }
304 | var vertexFuncName: String { "simon_cloudy_day_vertex" }
305 | var fragmentFuncName: String { "simon_cloudy_day_fragment" }
306 | var texture: MTLTexture?
307 |
308 | required init() {}
309 | }
310 |
311 | class SimonSDFs: Playground {
312 | var fileName: String {
313 | "SimonShaders/SimonSDFs"
314 | }
315 | var vertexFuncName: String { "simon_sdfs_vertex" }
316 | var fragmentFuncName: String { "simon_sdfs_fragment" }
317 | var texture: MTLTexture?
318 |
319 | required init() {}
320 | }
321 |
322 | // MARK: Others
323 |
324 | class HappyJumping: Playground {
325 | var fileName: String {
326 | "Explorations/HappyJumping"
327 | }
328 | var vertexFuncName: String { "happy_jumping_vertex" }
329 | var fragmentFuncName: String { "happy_jumping_fragment" }
330 | required init() {}
331 | }
332 |
333 | class Simplest3D: Playground {
334 | var fileName: String {
335 | "Explorations/Simplest3D"
336 | }
337 | var vertexFuncName: String { "simplest_3d_vertex" }
338 | var fragmentFuncName: String { "simplest_3d_fragment" }
339 | required init() {}
340 | }
341 |
342 | class Smiley: Playground {
343 |
344 | var fileName: String {
345 | "Explorations/ShaderToySmiley"
346 | }
347 | var vertexFuncName: String { "shaderToySmileyVertex" }
348 | var fragmentFuncName: String { "shaderToySmiley" }
349 | required init() {}
350 | }
351 |
352 | class CellularNoise: Playground {
353 | var fileName: String {
354 | "Explorations/CellularNoise"
355 | }
356 |
357 | var vertexFuncName: String { "cellularVertexShader" }
358 | var fragmentFuncName: String { "tileFragmentShader2" }
359 | required init() {}
360 | }
361 |
362 | class FuturisticUI: Playground {
363 | var fileName: String {
364 | "Explorations/07FuturisticUI"
365 | }
366 |
367 | var vertexFuncName: String { "futuristic_UI_vertex" }
368 | var fragmentFuncName: String { "futuristic_UI_fragment" }
369 | required init() {}
370 | }
371 |
372 | class BoSLeftRightTiler: Playground {
373 | var fileName: String {
374 | "BookShaders/08LeftRightTiler"
375 | }
376 | var vertexFuncName: String { "leftright_vertex" }
377 | var fragmentFuncName: String { "leftright_fragment" }
378 | required init() {}
379 | }
380 |
381 | class BoSColors06: Playground {
382 | var fileName: String {
383 | "BookShaders/06Colors"
384 | }
385 | var vertexFuncName: String { "bos_colors_vertex" }
386 | var fragmentFuncName: String { "bos_colors_fragment" }
387 | required init() {}
388 |
389 | enum SketchKind: Int, CaseIterable, Identifiable {
390 | case bezierCurve
391 | case flowingCurves
392 |
393 | var id: Int { rawValue }
394 |
395 | var name: String {
396 | switch self {
397 | case .bezierCurve:
398 | return "Bezier Curve"
399 | case .flowingCurves:
400 | return "Flowing Curves"
401 | }
402 | }
403 | }
404 |
405 | class Config: ObservableObject {
406 | @Published var kind: SketchKind = .bezierCurve
407 | }
408 |
409 | struct Uniforms {
410 | let kind: Float
411 | }
412 |
413 | fileprivate var config: Config = .init()
414 | var fragmentUniforms: Uniforms {
415 | .init(kind: Float(config.kind.rawValue))
416 | }
417 |
418 | func setUniforms(device: MTLDevice, encoder: MTLRenderCommandEncoder) {
419 | var uniforms = fragmentUniforms
420 | let length = MemoryLayout.stride(ofValue: uniforms)
421 | encoder.setFragmentBytes(&uniforms, length: length, index: 1)
422 | }
423 |
424 | struct ConfigView: View {
425 | @EnvironmentObject private var config: Config
426 | @State fileprivate var kind = SketchKind.bezierCurve
427 |
428 | var body: some View {
429 | VStack(alignment: .leading, spacing: 19) {
430 | Picker(selection: $config.kind, label: Text("Kind")) {
431 | ForEach(SketchKind.allCases) {
432 | Text($0.name).tag($0)
433 | }
434 | }
435 | }
436 | }
437 | }
438 |
439 | var view: NSView? {
440 | NSHostingView(
441 | rootView: ConfigView().environmentObject(config)
442 | )
443 | }
444 | }
445 |
446 | class BoSShaping: Playground {
447 | var fileName: String {
448 | "BookShaders/Shaping"
449 | }
450 | var vertexFuncName: String { "bos_shaping_vertex" }
451 | var fragmentFuncName: String { "bos_shaping_fragment" }
452 | required init() {}
453 |
454 | enum SketchKind: Int, CaseIterable, Identifiable {
455 | case bezierCurve
456 | case flowingCurves
457 |
458 | var id: Int { rawValue }
459 |
460 | var name: String {
461 | switch self {
462 | case .bezierCurve:
463 | return "Bezier Curve"
464 | case .flowingCurves:
465 | return "Flowing Curves"
466 | }
467 | }
468 | }
469 |
470 | class Config: ObservableObject {
471 | @Published var kind: SketchKind = .bezierCurve
472 | }
473 |
474 | struct Uniforms {
475 | let kind: Float
476 | }
477 |
478 | fileprivate var config: Config = .init()
479 | var fragmentUniforms: Uniforms {
480 | .init(kind: Float(config.kind.rawValue))
481 | }
482 |
483 | func setUniforms(device: MTLDevice, encoder: MTLRenderCommandEncoder) {
484 | var uniforms = fragmentUniforms
485 | let length = MemoryLayout.stride(ofValue: uniforms)
486 | encoder.setFragmentBytes(&uniforms, length: length, index: 1)
487 | }
488 |
489 | struct ConfigView: View {
490 | @EnvironmentObject private var config: Config
491 | @State fileprivate var kind = SketchKind.bezierCurve
492 |
493 | var body: some View {
494 | VStack(alignment: .leading, spacing: 19) {
495 | Picker(selection: $config.kind, label: Text("Kind")) {
496 | ForEach(SketchKind.allCases) {
497 | Text($0.name).tag($0)
498 | }
499 | }
500 | }
501 | }
502 | }
503 |
504 | var view: NSView? {
505 | NSHostingView(
506 | rootView: ConfigView().environmentObject(config)
507 | )
508 | }
509 | }
510 |
511 | class PolarScene: Playground {
512 | var fileName: String {
513 |
514 | "Explorations/PolarExperiments"
515 | }
516 | var vertexFuncName: String { "polar_experiments_vertex" }
517 | var fragmentFuncName: String { "polar_experiments_fragment" }
518 | required init() {}
519 | }
520 |
521 | class DomainDistortion: Playground {
522 | var fileName: String {
523 | "Explorations/ShaderToyDistortions"
524 | }
525 |
526 | var vertexFuncName: String { "domain_distortion_vertex" }
527 | var fragmentFuncName: String { "domain_distortion_fragment" }
528 | required init() {}
529 | }
530 |
531 | class BookOfShaders05: Playground {
532 | var fileName: String {
533 | "Explorations/05Algorithmic"
534 | }
535 |
536 | var vertexFuncName: String { "smoothing_vertex" }
537 | var fragmentFuncName: String { "smoothing_fragment" }
538 | required init() {}
539 | }
540 |
541 | class BookOfShaders06: Playground {
542 | var fileName: String {
543 | "06Colors"
544 | }
545 |
546 | var vertexFuncName: String { "color_vertex" }
547 | var fragmentFuncName: String { "color_fragment" }
548 | required init() {}
549 | }
550 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/ShaderHeaders.h:
--------------------------------------------------------------------------------
1 | //
2 | // ShaderHeaders.h
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 1/9/21.
6 | // Copyright © 2021 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #ifndef MyDefs
10 | #define MyDefs
11 |
12 | float absSin(float t);
13 | float lerp(float x, float m, float n, float a, float b);
14 | float2x2 rotate(float angle);
15 | float2x2 scale(float2 _scale);
16 | float circle(float2 st, float time, float rad, float2 center);
17 | float getDistanceToCylinder(float3 point);
18 | float getDistanceToCapsule(float3 point);
19 | float getDistanceToTorus(float3 point);
20 | float getDistanceToBox(float3 point);
21 | float getDistanceToPlane(float3 point);
22 | float subtractSDFs(float a, float b);
23 | float sdfCircle(float2 p, float r);
24 | float sdfLine(float2 p, float2 a, float2 b);
25 | float intersectSDFs(float a, float b);
26 | float unionSDFs(float a, float b);
27 | float blendSDFs(float a, float b, float t);
28 | float smoothUnionSDFs( float a, float b, float k);
29 | float softMax(float a, float b, float k);
30 | float softMin(float a, float b, float k);
31 |
32 | float3 red();
33 | float3 green();
34 | float3 yellow();
35 | float3 black();
36 | float3 gray();
37 | float3 blue();
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/SimonShaders/FractAndFriends.metal:
--------------------------------------------------------------------------------
1 | //
2 | // FractAndFriends.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 12/6/24.
6 | // Copyright © 2024 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 | #include "../ShaderHeaders.h"
12 |
13 | struct VertexIn {
14 | vector_float2 pos;
15 | };
16 |
17 | struct FragmentUniforms {
18 | float time;
19 | float screen_width;
20 | float screen_height;
21 | float screen_scale;
22 | float2 mousePos;
23 | };
24 |
25 | struct VertexOut {
26 | float4 pos [[position]];
27 | float4 color;
28 | };
29 |
30 | vertex VertexOut fract_and_friends_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
31 | VertexIn in = vertexArray[vid];
32 | VertexOut out;
33 | out.pos = float4(in.pos, 0, 1);
34 | return out;
35 | }
36 |
37 | fragment float4 fract_and_friends_fragment(VertexOut interpolated [[stage_in]], texture2d baseColorTexture [[texture(10)]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
38 | float2 st = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y / uniforms.screen_width};
39 |
40 | constexpr sampler textureSampler(filter::nearest, mip_filter::linear, max_anisotropy(8), address::repeat);
41 | float2 uv = 1 - st;
42 | float3 baseColor = baseColorTexture.sample(textureSampler, uv).rgb;
43 |
44 | // Background
45 | float3 color = baseColor;
46 |
47 | float2 pst = (st + uniforms.time * .012) * 400;
48 | float val = lerp(sin(pst.y), -1,1, 0.2, 1);
49 | color = mix(blue(), green(), val) * color;
50 |
51 | return vector_float4(color, 1.0);
52 | }
53 |
54 |
55 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/SimonShaders/SimonCloudyDay.metal:
--------------------------------------------------------------------------------
1 | //
2 | // SimonCloudyDay.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 12/16/24.
6 | // Copyright © 2024 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 | #include "../ShaderHeaders.h"
12 |
13 |
14 | struct VertexIn {
15 | vector_float2 pos;
16 | };
17 |
18 | struct FragmentUniforms {
19 | float time;
20 | float screen_width;
21 | float screen_height;
22 | float screen_scale;
23 | float2 mousePos;
24 | };
25 |
26 | struct VertexOut {
27 | float4 pos [[position]];
28 | float4 color;
29 | };
30 |
31 | vertex VertexOut simon_cloudy_day_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
32 | VertexIn in = vertexArray[vid];
33 | VertexOut out;
34 | out.pos = float4(in.pos, 0, 1);
35 | return out;
36 | }
37 |
38 | float hash(float2 v) {
39 | return sin(dot(v, float2(3)));
40 | }
41 |
42 | // -- Fragment
43 |
44 | float dayLength() {
45 | return 20;
46 | }
47 |
48 | float dayTime(float time) {
49 | return fmod(time, dayLength());
50 | }
51 |
52 | float3 backgroundColor(float2 st, float time) {
53 | float3 morning = mix(
54 | float3(0.44, 0.65, 0.81),
55 | float3(0.36, 0.53, 0.91),
56 | smoothstep(0, 1, pow(st.x * st.y, 0.7))
57 | );
58 |
59 | float3 midday = mix(
60 | float3(0.71, 0.75, 0.81),
61 | float3(0.56, 0.53, 0.81),
62 | smoothstep(0, 1, pow(st.x * st.y, 0.7))
63 | );
64 | float3 evening = mix(
65 | float3(0.61, 0.55, 0.25),
66 | float3(0.66, 0.33, 0.31),
67 | smoothstep(0, 1, pow(st.x * st.y, 0.7))
68 | );
69 | float3 night = mix(
70 | float3(0.11, 0.35, 0.41),
71 | float3(0.05, 0.04, 0.07),
72 | smoothstep(0, 1, pow(st.x * st.y, 0.7))
73 | );
74 | float3 color;
75 | float dt = dayTime(time);
76 | float dl = dayLength();
77 | if (dt < dl * 0.25) {
78 | color = mix(morning, midday, smoothstep(0, dl * 0.25, dt));
79 | } else if (dt < dl * 0.5) {
80 | color = mix(midday, evening, smoothstep(dl * 0.25, dl * 0.50, dt));
81 | } else if (dt < dl * 0.75) {
82 | color = mix(evening, night, smoothstep(dl * 0.5, dl * 0.75, dt));
83 | } else {
84 | color = mix(night, morning, smoothstep(dl * 0.75, dl, dt));
85 | }
86 |
87 | return color;
88 | }
89 |
90 | float sdfCloud(float2 st) {
91 | float puff = sdfCircle(st, 0.1);
92 | float puffLeft = sdfCircle(st - float2(-0.1, 0), 0.07);
93 | float puffRight = sdfCircle(st + float2(-0.1, 0), 0.07);
94 | return unionSDFs(puff, unionSDFs(puffLeft, puffRight));
95 | }
96 |
97 | fragment float4 simon_cloudy_day_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
98 | float2 st = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y / uniforms.screen_height};
99 | st = st - 0.5;
100 |
101 | // Background
102 | float3 color = backgroundColor(st, uniforms.time);
103 |
104 | // Sun
105 | float _dayTime = dayTime(uniforms.time);
106 | float timePassed;
107 | if (_dayTime > 0.75 * dayLength()) {
108 | timePassed = lerp(_dayTime, 0, dayLength() * 0.4, 0.0, 0.99);
109 | } else {
110 | timePassed = lerp(_dayTime, dayLength() * 0.2, dayLength() * 0.4, 0.9, 0.0);
111 | }
112 | float2 sunPos = st - float2(-0.3, 0.3 * timePassed);
113 | float sun = sdfCircle(sunPos, 0.1);
114 | color = mix(yellow() * 0.92, color, smoothstep(0.0, 0.001, sun));
115 |
116 | // additive blending:
117 | float s = max(0.01, sun);
118 | float p = saturate(exp(-200.0 * s * s));
119 | color += 0.45 * mix(float(0.0),float3(0.8, 0.85, 0.37), p);
120 |
121 | float numClouds = 6;
122 | for(float i = numClouds; i >= 0; i--) {
123 | // float size = (i + 4.4)/4;
124 | float size = mix(2, 1, (i/numClouds) + 0.1 * hash(float2(i)));
125 | float2 stCloud = st * float(size) + float2(i * 0.4 + uniforms.time/12, 0);
126 | stCloud.x = fract(stCloud.x + 1) - 0.5;
127 | stCloud.y -= -0.1 + hash(float2(i) * 0.08);
128 | // cloud shadow
129 | float cloudOffset = 0.04;
130 | float cloudShadow = sdfCloud(stCloud * float2(2.2) - float2(-cloudOffset, -cloudOffset/2));
131 | // cloud
132 | float cloud = sdfCloud(stCloud);
133 |
134 | color = mix(float3(0.3), color, smoothstep(0, 0.22, cloudShadow));
135 | color = mix(float3(1), color, smoothstep(0, 0.001, cloud));
136 | }
137 |
138 |
139 | return vector_float4(color, 1.0);
140 | }
141 |
142 |
143 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/SimonShaders/SimonNoiseIntro.metal:
--------------------------------------------------------------------------------
1 | //
2 | // SimonCloudyDay.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 12/16/24.
6 | // Copyright © 2024 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 | #include "../ShaderHeaders.h"
12 |
13 |
14 | struct VertexIn {
15 | vector_float2 pos;
16 | };
17 |
18 | struct FragmentUniforms {
19 | float time;
20 | float screen_width;
21 | float screen_height;
22 | float screen_scale;
23 | float2 mousePos;
24 | };
25 |
26 | struct VertexOut {
27 | float4 pos [[position]];
28 | float4 color;
29 | };
30 |
31 | vertex VertexOut simon_noise_intro_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
32 | VertexIn in = vertexArray[vid];
33 | VertexOut out;
34 | out.pos = float4(in.pos, 0, 1);
35 | return out;
36 | }
37 |
38 | // -- Fragment
39 |
40 | float3 hash( float3 p ) // replace this by something better
41 | {
42 | p = float3( dot(p,float3(127.1,311.7, 74.7)),
43 | dot(p,float3(269.5,183.3,246.1)),
44 | dot(p,float3(113.5,271.9,124.6)));
45 |
46 | return -1.0 + 2.0*fract(sin(p)*43758.5453123);
47 | }
48 |
49 | float noise(float3 p )
50 | {
51 | float3 i = floor( p );
52 | float3 f = fract( p );
53 |
54 | float3 u = f*f*(3.0-2.0*f);
55 |
56 | return mix( mix( mix( dot( hash( i + float3(0.0,0.0,0.0) ), f - float3(0.0,0.0,0.0) ),
57 | dot( hash( i + float3(1.0,0.0,0.0) ), f - float3(1.0,0.0,0.0) ), u.x),
58 | mix( dot( hash( i + float3(0.0,1.0,0.0) ), f - float3(0.0,1.0,0.0) ),
59 | dot( hash( i + float3(1.0,1.0,0.0) ), f - float3(1.0,1.0,0.0) ), u.x), u.y),
60 | mix( mix( dot( hash( i + float3(0.0,0.0,1.0) ), f - float3(0.0,0.0,1.0) ),
61 | dot( hash( i + float3(1.0,0.0,1.0) ), f - float3(1.0,0.0,1.0) ), u.x),
62 | mix( dot( hash( i + float3(0.0,1.0,1.0) ), f - float3(0.0,1.0,1.0) ),
63 | dot( hash( i + float3(1.0,1.0,1.0) ), f - float3(1.0,1.0,1.0) ), u.x), u.y), u.z );
64 | }
65 |
66 | float fbm(float3 st, int octaves, float persistence, float lacunarity) {
67 | float amplitude = 0.5;
68 | float frequency = 1.0;
69 | float total = 0.0;
70 | float normalization = 0.0;
71 |
72 | for(int i = 0; i < octaves; i++) {
73 | float noiseValue = noise(st * frequency);
74 | total += noiseValue * amplitude;
75 | normalization += amplitude;
76 | amplitude *= persistence;
77 | frequency *= lacunarity;
78 | }
79 |
80 | total /= normalization;
81 | return total;
82 | }
83 |
84 | float ridgedFbm(float3 st, int octaves, float persistence, float lacunarity) {
85 | float amplitude = 0.5;
86 | float frequency = 1.0;
87 | float total = 0.0;
88 | float normalization = 0.0;
89 |
90 | for(int i = 0; i < octaves; i++) {
91 | float noiseValue = noise(st * frequency);
92 | noiseValue = 1.0 - abs(noiseValue);
93 |
94 | total += noiseValue * amplitude;
95 | normalization += amplitude;
96 | amplitude *= persistence;
97 | frequency *= lacunarity;
98 | }
99 |
100 | total /= normalization;
101 | total *= total;
102 | return total;
103 | }
104 |
105 | float turbulenceFbm(float3 st, int octaves, float persistence, float lacunarity) {
106 | float amplitude = 0.5;
107 | float frequency = 1.0;
108 | float total = 0.0;
109 | float normalization = 0.0;
110 |
111 | for(int i = 0; i < octaves; i++) {
112 | float noiseValue = noise(st * frequency);
113 | noiseValue = abs(noiseValue);
114 |
115 | total += noiseValue * amplitude;
116 | normalization += amplitude;
117 | amplitude *= persistence;
118 | frequency *= lacunarity;
119 | }
120 |
121 | total /= normalization;
122 | total *= total;
123 | return total;
124 | }
125 |
126 | float cellularNoise(float3 coords) {
127 | float2 gridBasePos = floor(coords.xy);
128 | float2 gridCoordOffset = fract(coords.xy);
129 | float closest = 1.0;
130 | for(float y = -2.0; y <= 2.0; y += 1) {
131 | for(float x = -2.0; x <= 2.0; x += 1) {
132 | float2 neighboringCellPos = float2(x,y);
133 | float2 cellWorldPos = gridBasePos + neighboringCellPos;
134 | float2 cellOffset = float2(
135 | noise(float3(cellWorldPos, coords.z) + float3(243.432, 324.235, 0.0)),
136 | noise(float3(cellWorldPos, coords.z))
137 | );
138 | float distanceToNeighbor = length(neighboringCellPos + cellOffset - gridCoordOffset);
139 | closest = min(closest, distanceToNeighbor);
140 | }
141 | }
142 | return closest;
143 | }
144 |
145 |
146 | /// Useful for creating wood or marble
147 | float stepped(float noiseSample) {
148 | float steppedSample = floor(noiseSample * 10.0) / 10.0;
149 | float remainder = fract(noiseSample * 10);
150 | // darken darks, lighten lights; creates a halo like effect
151 | steppedSample = (steppedSample - remainder);
152 | return steppedSample;
153 | }
154 |
155 | float domainWarping(float3 coords) {
156 | // Use the first sample to get the next
157 |
158 | float3 slightOffset = float3(43.21, 32.122, 0.0);
159 | float3 offset = float3(
160 | fbm(coords, 4, 0.5, 2.0),
161 | fbm(coords + slightOffset, 4, 0.5, 2),
162 | 0.0
163 | );
164 | // Do we need this?
165 | float noiseSample = fbm(coords + offset, 1, 0.5, 2);
166 |
167 | // second offset includes the 1st offset
168 | float3 slightOffset2 = float3(23.21, 12.122, 0.0);
169 | float3 offset2 = float3(
170 | fbm(coords + offset * slightOffset2, 4, 0.5, 2.0),
171 | fbm(coords + offset + slightOffset*1.1, 4, 0.5, 2),
172 | 0.0
173 | );
174 | noiseSample = fbm(coords + 4.0 * offset2, 1, 0.5, 2);
175 |
176 | return noiseSample;
177 |
178 | }
179 |
180 | fragment float4 simon_noise_intro_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
181 | float2 st = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y / uniforms.screen_height};
182 |
183 | float3 noiseCoords = float3(st * 2, uniforms.time * 0.1);
184 |
185 | float noiseSample = 0.0;
186 |
187 | // noiseSample = lerp(noise(noiseCoords), -1, 1, 0, 1);
188 | // noiseSample = lerp(ridgedFbm(noiseCoords, 10, 0.5, 2.0), -1, 1, 0, 1);
189 | // noiseSample = lerp(turbulenceFbm(noiseCoords, 10, 0.5, 2.0), -0.5, 1, 0, 1);
190 | // noiseSample = cellularNoise(noiseCoords);
191 |
192 | noiseSample = lerp(domainWarping(noiseCoords), -1,1, 0,1);
193 |
194 | // can reuse with above:
195 | noiseSample = stepped(noiseSample);
196 |
197 |
198 | float3 color = float3(noiseSample);
199 |
200 |
201 | return vector_float4(color, 1.0);
202 | }
203 |
204 |
205 |
--------------------------------------------------------------------------------
/MetalPlayground/Scenes/SimonShaders/SimonSDFs.metal:
--------------------------------------------------------------------------------
1 | //
2 | // FractAndFriends.metal
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 12/6/24.
6 | // Copyright © 2024 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | #include
10 | using namespace metal;
11 | #include "../ShaderHeaders.h"
12 |
13 | struct VertexIn {
14 | vector_float2 pos;
15 | };
16 |
17 | struct FragmentUniforms {
18 | float time;
19 | float screen_width;
20 | float screen_height;
21 | float screen_scale;
22 | float2 mousePos;
23 | };
24 |
25 | struct VertexOut {
26 | float4 pos [[position]];
27 | float4 color;
28 | };
29 |
30 | vertex VertexOut simon_sdfs_vertex(const device VertexIn *vertexArray [[buffer(0)]], unsigned int vid [[vertex_id]]) {
31 | VertexIn in = vertexArray[vid];
32 | VertexOut out;
33 | out.pos = float4(in.pos, 0, 1);
34 | return out;
35 | }
36 |
37 | // ---
38 |
39 | float3 skyBackgroundColor(float2 st) {
40 | float distanceFromCenter = length(st);
41 | float vignette = 1.0 - distanceFromCenter;
42 | vignette = smoothstep(0, 0.7, vignette);
43 | vignette = lerp(vignette, 0, 1, 0.3, 1);
44 | return float3(vignette);
45 | }
46 |
47 | float3 grid(float2 st, float aspect, float3 color, float3 backgroundColor, float cellSpacing, float lineWidth) {
48 | float2 cells = abs(fract(st * cellSpacing) - 0.5);
49 |
50 | float distanceToCenter = (0.5 - max(cells.x, cells.y));
51 | float t = smoothstep(0.00, lineWidth, distanceToCenter);
52 |
53 |
54 | color = mix(color, backgroundColor, t);
55 |
56 | return color;
57 | }
58 |
59 | float sdBox(float2 p, float2 b )
60 | {
61 | float2 d = abs(p)-b;
62 | return length(max(d,0.0)) + min(max(d.x,d.y),0.0);
63 | }
64 |
65 | fragment float4 simon_sdfs_fragment(VertexOut interpolated [[stage_in]], constant FragmentUniforms &uniforms [[buffer(0)]]) {
66 | float2 st = {interpolated.pos.x / uniforms.screen_width, 1 - interpolated.pos.y / uniforms.screen_height};
67 | st = st - 0.5;
68 |
69 | // Background
70 | float3 color = skyBackgroundColor(st);
71 |
72 | // Grid
73 | float aspect = uniforms.screen_width / uniforms.screen_height;
74 | color = grid(st, aspect, gray(), color, 100, 0.08);
75 | color = grid(st, aspect, black(), color, 20, 0.04);
76 |
77 | // Circles
78 | float radius = 1.0/9;
79 | // smooth the edges (antialias) with smoothstep; useful for curves
80 | float offset = 0.3;
81 | float circle1 = sdfCircle(st - float2(-offset, offset), radius);
82 | float circle2 = sdfCircle(st - float2(offset, offset), radius);
83 | float circle3 = sdfCircle(st - float2(0, -offset), radius);
84 |
85 | float circles = unionSDFs(unionSDFs(circle1, circle2), circle3);
86 |
87 | // Box
88 | float box = sdBox(rotate(uniforms.time) * st, float2(0.28, 0.28));
89 |
90 | float colorK = lerp(circles - box, -1, 1, 0, 1);
91 | float3 shapeCol = mix(blue(), red(), smoothstep(0, 1, colorK));
92 |
93 | // Final shape
94 | float shape = softMin(circles, box, 0.02);
95 |
96 | color = mix(shapeCol * 0.2, color, smoothstep(0.0, 0.003, shape));
97 | color = mix(shapeCol, color, smoothstep(0, 0.001, shape));
98 |
99 |
100 | // float line = sdfLine(rotate(uniforms.time) * st, float2(0.5, 0.0), float2(-0.5, 0.0));
101 | // line = step(0.01, line);
102 | //
103 | //
104 | // color = mix(yellow(), color, line);
105 | //
106 |
107 |
108 | return vector_float4(color, 1.0);
109 | }
110 |
111 |
112 |
--------------------------------------------------------------------------------
/MetalPlayground/UI/TitledSlider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TitledSlider.swift
3 | // MetalPlayground
4 | //
5 | // Created by Raheel Ahmad on 9/20/20.
6 | // Copyright © 2020 Raheel Ahmad. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct TitledSlider: View {
12 | let title: String
13 | var value: Binding
14 | let slider: AnyView
15 | let reset: (() -> ())
16 |
17 | static let nf: NumberFormatter = {
18 | let f = NumberFormatter()
19 | f.maximumFractionDigits = 1
20 | return f
21 | }()
22 |
23 | static func boundLabel(value: Float) -> some View {
24 | Text(nf.string(from: NSNumber(floatLiteral: Double(value)))!)
25 | .font(.caption).foregroundColor(.secondary)
26 | }
27 |
28 | static func valueLabel(value: Float) -> some View {
29 | Text(nf.string(from: NSNumber(floatLiteral: Double(value)))!)
30 | .bold()
31 | }
32 |
33 | init(title: String, value: Binding, in bounds: ClosedRange, step: Float? = nil, reset: @escaping (() -> ())) {
34 | if let step = step {
35 | self.slider = AnyView(
36 | Slider(
37 | value: value,
38 | in: bounds,
39 | step: step,
40 | minimumValueLabel: Self.boundLabel(value: Float(bounds.lowerBound)),
41 | maximumValueLabel: Self.boundLabel(value: Float(bounds.upperBound)),
42 | label: { EmptyView() }
43 | )
44 | )
45 | } else {
46 | self.slider = AnyView(
47 | Slider(
48 | value: value,
49 | in: bounds,
50 | minimumValueLabel: Self.boundLabel(value: Float(bounds.lowerBound)),
51 | maximumValueLabel: Self.boundLabel(value: Float(bounds.upperBound)),
52 | label: { EmptyView() }
53 | )
54 | )
55 | }
56 | self.title = title
57 | self.reset = reset
58 | self.value = value
59 | }
60 |
61 | public var body: some View {
62 | VStack(alignment: .leading, spacing: 0) {
63 | HStack(spacing: 12) {
64 | Text(title)
65 | Spacer()
66 | Self.valueLabel(value: value.wrappedValue)
67 | Button(action: {
68 | self.reset()
69 | }) {
70 | Image("reset")
71 | .resizable()
72 | .foregroundColor(Color(red: 185/255.0, green: 160/255.0, blue: 176/255.0))
73 | .aspectRatio(contentMode: .fit)
74 | .frame(width: 19)
75 | }
76 | .buttonStyle(PlainButtonStyle())
77 | }
78 | slider
79 | }
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/MetalPlayground/raga.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/MetalPlayground/raga.mp3
--------------------------------------------------------------------------------
/MetalPlayground/wallpaper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/MetalPlayground/wallpaper.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Metal Playground
2 |
3 | A macOS app to explore Metal, particularly Shaders.
4 |
5 | Allows sending vertex and fragment uniforms over to the shaders. Uses SwiftUI for uniform tweaks.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/images/circles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/images/circles.png
--------------------------------------------------------------------------------
/images/colors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/images/colors.png
--------------------------------------------------------------------------------
/images/futuristic-UI.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/images/futuristic-UI.gif
--------------------------------------------------------------------------------
/images/futuristic-UI.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/images/futuristic-UI.mov
--------------------------------------------------------------------------------
/images/leftright-tiler.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/images/leftright-tiler.gif
--------------------------------------------------------------------------------
/images/leftright-tiler.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/images/leftright-tiler.mov
--------------------------------------------------------------------------------
/images/starfield.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raheelahmad/metal-playground/131322ce5d01e1b448128dc2ba2827182860d1b4/images/starfield.png
--------------------------------------------------------------------------------