├── .gitignore ├── LICENSE ├── MBEBaseEffect.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── MBEEffectDemo2.xcscheme ├── MBEBaseEffect ├── MBEBaseEffect.h ├── MBEBaseEffect.m └── Shaders.metal ├── MBEEffectDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── Bridging.h ├── Document.swift ├── EffectInspectorView.swift ├── EffectRenderer.swift ├── Info.plist ├── Inspectors │ ├── FogParameterInspectorView.swift │ ├── FogParameterInspectorView.xib │ ├── InspectorView.swift │ ├── LightInspectorView.swift │ ├── LightInspectorView.xib │ ├── MaterialInspectorView.swift │ ├── MaterialInspectorView.xib │ ├── ShadingInspectorView.swift │ ├── ShadingInspectorView.xib │ ├── TextureInspectorView.swift │ ├── TextureInspectorView.xib │ └── TransformInspectorView.xib ├── MBEEffectDemo.entitlements ├── Math.swift ├── ModelIOExtensions.swift ├── ViewController.swift └── crate.jpg ├── README.md └── screenshots └── 1.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Warren Moore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MBEBaseEffect.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 832DABF0283A206C0038F794 /* TextureInspectorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 832DABEF283A206C0038F794 /* TextureInspectorView.xib */; }; 11 | 832DABF2283A21BA0038F794 /* TextureInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832DABF1283A21BA0038F794 /* TextureInspectorView.swift */; }; 12 | 83E7E20E283C090B00EFABCD /* ShadingInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E7E20D283C090B00EFABCD /* ShadingInspectorView.swift */; }; 13 | 83E7E210283C1DE500EFABCD /* TransformInspectorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83E7E20F283C1DE500EFABCD /* TransformInspectorView.xib */; }; 14 | 83ED2B7C2839D55100CD6D8B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83ED2B7B2839D55100CD6D8B /* AppDelegate.swift */; }; 15 | 83ED2B7E2839D55100CD6D8B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83ED2B7D2839D55100CD6D8B /* ViewController.swift */; }; 16 | 83ED2B802839D55100CD6D8B /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83ED2B7F2839D55100CD6D8B /* Document.swift */; }; 17 | 83ED2B822839D55200CD6D8B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 83ED2B812839D55200CD6D8B /* Assets.xcassets */; }; 18 | 83ED2B852839D55200CD6D8B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 83ED2B832839D55200CD6D8B /* Main.storyboard */; }; 19 | 83ED2B8C2839D61800CD6D8B /* EffectInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83ED2B8B2839D61800CD6D8B /* EffectInspectorView.swift */; }; 20 | 83ED2B8D2839D6AD00CD6D8B /* ModelIOExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834F883E2834522A00327697 /* ModelIOExtensions.swift */; }; 21 | 83ED2B8E2839D6AD00CD6D8B /* Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834F883D2834522A00327697 /* Math.swift */; }; 22 | 83ED2B8F2839D6AD00CD6D8B /* EffectRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834F885028346AD100327697 /* EffectRenderer.swift */; }; 23 | 83ED2B902839D6B200CD6D8B /* FogParameterInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83F1B9662837F6F10066F5FB /* FogParameterInspectorView.swift */; }; 24 | 83ED2B912839D8AE00CD6D8B /* FogParameterInspectorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83F1B9632837F6970066F5FB /* FogParameterInspectorView.xib */; }; 25 | 83ED2B922839D8B800CD6D8B /* crate.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 834F883C2834522A00327697 /* crate.jpg */; }; 26 | 83ED2B932839D94C00CD6D8B /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 836AB82828337DD0003155B8 /* Shaders.metal */; }; 27 | 83ED2B942839D94C00CD6D8B /* MBEBaseEffect.m in Sources */ = {isa = PBXBuildFile; fileRef = 836AB826283379A2003155B8 /* MBEBaseEffect.m */; }; 28 | 83ED2B972839E33900CD6D8B /* LightInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83ED2B952839E33900CD6D8B /* LightInspectorView.swift */; }; 29 | 83ED2B982839E33900CD6D8B /* LightInspectorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83ED2B962839E33900CD6D8B /* LightInspectorView.xib */; }; 30 | 83ED2B9A283A063800CD6D8B /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83ED2B99283A063800CD6D8B /* InspectorView.swift */; }; 31 | 83ED2B9D283A0CE800CD6D8B /* MaterialInspectorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83ED2B9C283A0CE800CD6D8B /* MaterialInspectorView.xib */; }; 32 | 83ED2B9F283A0E2200CD6D8B /* MaterialInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83ED2B9E283A0E2200CD6D8B /* MaterialInspectorView.swift */; }; 33 | 83ED2BA1283A116200CD6D8B /* ShadingInspectorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83ED2BA0283A116200CD6D8B /* ShadingInspectorView.xib */; }; 34 | /* End PBXBuildFile section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 832DABEF283A206C0038F794 /* TextureInspectorView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TextureInspectorView.xib; sourceTree = ""; }; 38 | 832DABF1283A21BA0038F794 /* TextureInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextureInspectorView.swift; sourceTree = ""; }; 39 | 834F883B2834522A00327697 /* Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Bridging.h; sourceTree = ""; }; 40 | 834F883C2834522A00327697 /* crate.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = crate.jpg; sourceTree = ""; }; 41 | 834F883D2834522A00327697 /* Math.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Math.swift; sourceTree = ""; }; 42 | 834F883E2834522A00327697 /* ModelIOExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelIOExtensions.swift; sourceTree = ""; }; 43 | 834F885028346AD100327697 /* EffectRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectRenderer.swift; sourceTree = ""; }; 44 | 836AB825283379A2003155B8 /* MBEBaseEffect.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MBEBaseEffect.h; sourceTree = ""; }; 45 | 836AB826283379A2003155B8 /* MBEBaseEffect.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MBEBaseEffect.m; sourceTree = ""; }; 46 | 836AB82828337DD0003155B8 /* Shaders.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Shaders.metal; sourceTree = ""; }; 47 | 83E7E20D283C090B00EFABCD /* ShadingInspectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadingInspectorView.swift; sourceTree = ""; }; 48 | 83E7E20F283C1DE500EFABCD /* TransformInspectorView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TransformInspectorView.xib; sourceTree = ""; }; 49 | 83ED2B792839D55100CD6D8B /* MBEBaseEffectDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MBEBaseEffectDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 83ED2B7B2839D55100CD6D8B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 51 | 83ED2B7D2839D55100CD6D8B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 52 | 83ED2B7F2839D55100CD6D8B /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = ""; }; 53 | 83ED2B812839D55200CD6D8B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | 83ED2B842839D55200CD6D8B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 83ED2B862839D55200CD6D8B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 83ED2B872839D55200CD6D8B /* MBEEffectDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MBEEffectDemo.entitlements; sourceTree = ""; }; 57 | 83ED2B8B2839D61800CD6D8B /* EffectInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectInspectorView.swift; sourceTree = ""; }; 58 | 83ED2B952839E33900CD6D8B /* LightInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightInspectorView.swift; sourceTree = ""; }; 59 | 83ED2B962839E33900CD6D8B /* LightInspectorView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LightInspectorView.xib; sourceTree = ""; }; 60 | 83ED2B99283A063800CD6D8B /* InspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorView.swift; sourceTree = ""; }; 61 | 83ED2B9C283A0CE800CD6D8B /* MaterialInspectorView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MaterialInspectorView.xib; sourceTree = ""; }; 62 | 83ED2B9E283A0E2200CD6D8B /* MaterialInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaterialInspectorView.swift; sourceTree = ""; }; 63 | 83ED2BA0283A116200CD6D8B /* ShadingInspectorView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShadingInspectorView.xib; sourceTree = ""; }; 64 | 83F1B9632837F6970066F5FB /* FogParameterInspectorView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FogParameterInspectorView.xib; sourceTree = ""; }; 65 | 83F1B9662837F6F10066F5FB /* FogParameterInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FogParameterInspectorView.swift; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 83ED2B762839D55100CD6D8B /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | 836AB80628337969003155B8 = { 80 | isa = PBXGroup; 81 | children = ( 82 | 836AB81128337969003155B8 /* MBEBaseEffect */, 83 | 83ED2B7A2839D55100CD6D8B /* MBEEffectDemo */, 84 | 836AB81028337969003155B8 /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | 836AB81028337969003155B8 /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 83ED2B792839D55100CD6D8B /* MBEBaseEffectDemo.app */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | 836AB81128337969003155B8 /* MBEBaseEffect */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 836AB825283379A2003155B8 /* MBEBaseEffect.h */, 100 | 836AB826283379A2003155B8 /* MBEBaseEffect.m */, 101 | 836AB82828337DD0003155B8 /* Shaders.metal */, 102 | ); 103 | path = MBEBaseEffect; 104 | sourceTree = ""; 105 | }; 106 | 83ED2B7A2839D55100CD6D8B /* MBEEffectDemo */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 83F1B9652837F6DE0066F5FB /* Inspectors */, 110 | 83ED2B8B2839D61800CD6D8B /* EffectInspectorView.swift */, 111 | 83ED2B7D2839D55100CD6D8B /* ViewController.swift */, 112 | 83ED2B7F2839D55100CD6D8B /* Document.swift */, 113 | 83ED2B7B2839D55100CD6D8B /* AppDelegate.swift */, 114 | 834F885028346AD100327697 /* EffectRenderer.swift */, 115 | 834F883E2834522A00327697 /* ModelIOExtensions.swift */, 116 | 834F883D2834522A00327697 /* Math.swift */, 117 | 834F883B2834522A00327697 /* Bridging.h */, 118 | 834F883C2834522A00327697 /* crate.jpg */, 119 | 83ED2B812839D55200CD6D8B /* Assets.xcassets */, 120 | 83ED2B832839D55200CD6D8B /* Main.storyboard */, 121 | 83ED2B862839D55200CD6D8B /* Info.plist */, 122 | 83ED2B872839D55200CD6D8B /* MBEEffectDemo.entitlements */, 123 | ); 124 | path = MBEEffectDemo; 125 | sourceTree = ""; 126 | }; 127 | 83F1B9652837F6DE0066F5FB /* Inspectors */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 83ED2B99283A063800CD6D8B /* InspectorView.swift */, 131 | 83F1B9662837F6F10066F5FB /* FogParameterInspectorView.swift */, 132 | 83F1B9632837F6970066F5FB /* FogParameterInspectorView.xib */, 133 | 83ED2B952839E33900CD6D8B /* LightInspectorView.swift */, 134 | 83ED2B962839E33900CD6D8B /* LightInspectorView.xib */, 135 | 83ED2B9E283A0E2200CD6D8B /* MaterialInspectorView.swift */, 136 | 83ED2B9C283A0CE800CD6D8B /* MaterialInspectorView.xib */, 137 | 83E7E20D283C090B00EFABCD /* ShadingInspectorView.swift */, 138 | 83ED2BA0283A116200CD6D8B /* ShadingInspectorView.xib */, 139 | 832DABF1283A21BA0038F794 /* TextureInspectorView.swift */, 140 | 832DABEF283A206C0038F794 /* TextureInspectorView.xib */, 141 | 83E7E20F283C1DE500EFABCD /* TransformInspectorView.xib */, 142 | ); 143 | path = Inspectors; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | 83ED2B782839D55100CD6D8B /* MBEBaseEffectDemo */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 83ED2B882839D55200CD6D8B /* Build configuration list for PBXNativeTarget "MBEBaseEffectDemo" */; 152 | buildPhases = ( 153 | 83ED2B752839D55100CD6D8B /* Sources */, 154 | 83ED2B762839D55100CD6D8B /* Frameworks */, 155 | 83ED2B772839D55100CD6D8B /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | ); 161 | name = MBEBaseEffectDemo; 162 | productName = MBEEffectDemo; 163 | productReference = 83ED2B792839D55100CD6D8B /* MBEBaseEffectDemo.app */; 164 | productType = "com.apple.product-type.application"; 165 | }; 166 | /* End PBXNativeTarget section */ 167 | 168 | /* Begin PBXProject section */ 169 | 836AB80728337969003155B8 /* Project object */ = { 170 | isa = PBXProject; 171 | attributes = { 172 | BuildIndependentTargetsInParallel = 1; 173 | LastSwiftUpdateCheck = 1330; 174 | LastUpgradeCheck = 1330; 175 | TargetAttributes = { 176 | 83ED2B782839D55100CD6D8B = { 177 | CreatedOnToolsVersion = 13.3; 178 | }; 179 | }; 180 | }; 181 | buildConfigurationList = 836AB80A28337969003155B8 /* Build configuration list for PBXProject "MBEBaseEffect" */; 182 | compatibilityVersion = "Xcode 13.0"; 183 | developmentRegion = en; 184 | hasScannedForEncodings = 0; 185 | knownRegions = ( 186 | en, 187 | Base, 188 | ); 189 | mainGroup = 836AB80628337969003155B8; 190 | productRefGroup = 836AB81028337969003155B8 /* Products */; 191 | projectDirPath = ""; 192 | projectRoot = ""; 193 | targets = ( 194 | 83ED2B782839D55100CD6D8B /* MBEBaseEffectDemo */, 195 | ); 196 | }; 197 | /* End PBXProject section */ 198 | 199 | /* Begin PBXResourcesBuildPhase section */ 200 | 83ED2B772839D55100CD6D8B /* Resources */ = { 201 | isa = PBXResourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | 83ED2BA1283A116200CD6D8B /* ShadingInspectorView.xib in Resources */, 205 | 83ED2B822839D55200CD6D8B /* Assets.xcassets in Resources */, 206 | 83ED2B9D283A0CE800CD6D8B /* MaterialInspectorView.xib in Resources */, 207 | 83E7E210283C1DE500EFABCD /* TransformInspectorView.xib in Resources */, 208 | 83ED2B982839E33900CD6D8B /* LightInspectorView.xib in Resources */, 209 | 832DABF0283A206C0038F794 /* TextureInspectorView.xib in Resources */, 210 | 83ED2B852839D55200CD6D8B /* Main.storyboard in Resources */, 211 | 83ED2B912839D8AE00CD6D8B /* FogParameterInspectorView.xib in Resources */, 212 | 83ED2B922839D8B800CD6D8B /* crate.jpg in Resources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXResourcesBuildPhase section */ 217 | 218 | /* Begin PBXSourcesBuildPhase section */ 219 | 83ED2B752839D55100CD6D8B /* Sources */ = { 220 | isa = PBXSourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | 83ED2B9F283A0E2200CD6D8B /* MaterialInspectorView.swift in Sources */, 224 | 83ED2B9A283A063800CD6D8B /* InspectorView.swift in Sources */, 225 | 83ED2B972839E33900CD6D8B /* LightInspectorView.swift in Sources */, 226 | 83ED2B8F2839D6AD00CD6D8B /* EffectRenderer.swift in Sources */, 227 | 83ED2B902839D6B200CD6D8B /* FogParameterInspectorView.swift in Sources */, 228 | 83E7E20E283C090B00EFABCD /* ShadingInspectorView.swift in Sources */, 229 | 83ED2B7E2839D55100CD6D8B /* ViewController.swift in Sources */, 230 | 83ED2B7C2839D55100CD6D8B /* AppDelegate.swift in Sources */, 231 | 83ED2B8C2839D61800CD6D8B /* EffectInspectorView.swift in Sources */, 232 | 83ED2B8D2839D6AD00CD6D8B /* ModelIOExtensions.swift in Sources */, 233 | 83ED2B942839D94C00CD6D8B /* MBEBaseEffect.m in Sources */, 234 | 83ED2B932839D94C00CD6D8B /* Shaders.metal in Sources */, 235 | 83ED2B802839D55100CD6D8B /* Document.swift in Sources */, 236 | 832DABF2283A21BA0038F794 /* TextureInspectorView.swift in Sources */, 237 | 83ED2B8E2839D6AD00CD6D8B /* Math.swift in Sources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | /* End PBXSourcesBuildPhase section */ 242 | 243 | /* Begin PBXVariantGroup section */ 244 | 83ED2B832839D55200CD6D8B /* Main.storyboard */ = { 245 | isa = PBXVariantGroup; 246 | children = ( 247 | 83ED2B842839D55200CD6D8B /* Base */, 248 | ); 249 | name = Main.storyboard; 250 | sourceTree = ""; 251 | }; 252 | /* End PBXVariantGroup section */ 253 | 254 | /* Begin XCBuildConfiguration section */ 255 | 836AB8202833796A003155B8 /* Debug */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | CLANG_ANALYZER_NONNULL = YES; 260 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 261 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_ENABLE_OBJC_WEAK = YES; 265 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 266 | CLANG_WARN_BOOL_CONVERSION = YES; 267 | CLANG_WARN_COMMA = YES; 268 | CLANG_WARN_CONSTANT_CONVERSION = YES; 269 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 270 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 271 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 272 | CLANG_WARN_EMPTY_BODY = YES; 273 | CLANG_WARN_ENUM_CONVERSION = YES; 274 | CLANG_WARN_INFINITE_RECURSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 278 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 279 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 280 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 281 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 282 | CLANG_WARN_STRICT_PROTOTYPES = YES; 283 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 284 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 285 | CLANG_WARN_UNREACHABLE_CODE = YES; 286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 287 | COPY_PHASE_STRIP = NO; 288 | DEBUG_INFORMATION_FORMAT = dwarf; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | ENABLE_TESTABILITY = YES; 291 | GCC_C_LANGUAGE_STANDARD = gnu11; 292 | GCC_DYNAMIC_NO_PIC = NO; 293 | GCC_NO_COMMON_BLOCKS = YES; 294 | GCC_OPTIMIZATION_LEVEL = 0; 295 | GCC_PREPROCESSOR_DEFINITIONS = ( 296 | "DEBUG=1", 297 | "$(inherited)", 298 | ); 299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 301 | GCC_WARN_UNDECLARED_SELECTOR = YES; 302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 303 | GCC_WARN_UNUSED_FUNCTION = YES; 304 | GCC_WARN_UNUSED_VARIABLE = YES; 305 | MACOSX_DEPLOYMENT_TARGET = 11.0; 306 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 307 | MTL_FAST_MATH = YES; 308 | ONLY_ACTIVE_ARCH = YES; 309 | SDKROOT = macosx; 310 | }; 311 | name = Debug; 312 | }; 313 | 836AB8212833796A003155B8 /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ALWAYS_SEARCH_USER_PATHS = NO; 317 | CLANG_ANALYZER_NONNULL = YES; 318 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 319 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 320 | CLANG_ENABLE_MODULES = YES; 321 | CLANG_ENABLE_OBJC_ARC = YES; 322 | CLANG_ENABLE_OBJC_WEAK = YES; 323 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 324 | CLANG_WARN_BOOL_CONVERSION = YES; 325 | CLANG_WARN_COMMA = YES; 326 | CLANG_WARN_CONSTANT_CONVERSION = YES; 327 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 328 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 329 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 330 | CLANG_WARN_EMPTY_BODY = YES; 331 | CLANG_WARN_ENUM_CONVERSION = YES; 332 | CLANG_WARN_INFINITE_RECURSION = YES; 333 | CLANG_WARN_INT_CONVERSION = YES; 334 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 335 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 336 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 338 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 339 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 340 | CLANG_WARN_STRICT_PROTOTYPES = YES; 341 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 342 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 343 | CLANG_WARN_UNREACHABLE_CODE = YES; 344 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 345 | COPY_PHASE_STRIP = NO; 346 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 347 | ENABLE_NS_ASSERTIONS = NO; 348 | ENABLE_STRICT_OBJC_MSGSEND = YES; 349 | GCC_C_LANGUAGE_STANDARD = gnu11; 350 | GCC_NO_COMMON_BLOCKS = YES; 351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 353 | GCC_WARN_UNDECLARED_SELECTOR = YES; 354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 355 | GCC_WARN_UNUSED_FUNCTION = YES; 356 | GCC_WARN_UNUSED_VARIABLE = YES; 357 | MACOSX_DEPLOYMENT_TARGET = 11.0; 358 | MTL_ENABLE_DEBUG_INFO = NO; 359 | MTL_FAST_MATH = YES; 360 | SDKROOT = macosx; 361 | }; 362 | name = Release; 363 | }; 364 | 83ED2B892839D55200CD6D8B /* Debug */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 368 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 369 | CODE_SIGN_ENTITLEMENTS = MBEEffectDemo/MBEEffectDemo.entitlements; 370 | CODE_SIGN_STYLE = Automatic; 371 | COMBINE_HIDPI_IMAGES = YES; 372 | CURRENT_PROJECT_VERSION = 1; 373 | DEVELOPMENT_TEAM = RHRJ88BAB5; 374 | ENABLE_HARDENED_RUNTIME = YES; 375 | GENERATE_INFOPLIST_FILE = YES; 376 | INFOPLIST_FILE = MBEEffectDemo/Info.plist; 377 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 378 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 379 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 380 | LD_RUNPATH_SEARCH_PATHS = ( 381 | "$(inherited)", 382 | "@executable_path/../Frameworks", 383 | ); 384 | MACOSX_DEPLOYMENT_TARGET = 12.3; 385 | MARKETING_VERSION = 1.0; 386 | PRODUCT_BUNDLE_IDENTIFIER = co.fourspace.MBEEffectDemo; 387 | PRODUCT_NAME = "$(TARGET_NAME)"; 388 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 389 | SWIFT_EMIT_LOC_STRINGS = YES; 390 | SWIFT_OBJC_BRIDGING_HEADER = MBEEffectDemo/Bridging.h; 391 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 392 | SWIFT_VERSION = 5.0; 393 | }; 394 | name = Debug; 395 | }; 396 | 83ED2B8A2839D55200CD6D8B /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 400 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 401 | CODE_SIGN_ENTITLEMENTS = MBEEffectDemo/MBEEffectDemo.entitlements; 402 | CODE_SIGN_STYLE = Automatic; 403 | COMBINE_HIDPI_IMAGES = YES; 404 | CURRENT_PROJECT_VERSION = 1; 405 | DEVELOPMENT_TEAM = RHRJ88BAB5; 406 | ENABLE_HARDENED_RUNTIME = YES; 407 | GENERATE_INFOPLIST_FILE = YES; 408 | INFOPLIST_FILE = MBEEffectDemo/Info.plist; 409 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 410 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 411 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 412 | LD_RUNPATH_SEARCH_PATHS = ( 413 | "$(inherited)", 414 | "@executable_path/../Frameworks", 415 | ); 416 | MACOSX_DEPLOYMENT_TARGET = 12.3; 417 | MARKETING_VERSION = 1.0; 418 | PRODUCT_BUNDLE_IDENTIFIER = co.fourspace.MBEEffectDemo; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_COMPILATION_MODE = wholemodule; 421 | SWIFT_EMIT_LOC_STRINGS = YES; 422 | SWIFT_OBJC_BRIDGING_HEADER = MBEEffectDemo/Bridging.h; 423 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 424 | SWIFT_VERSION = 5.0; 425 | }; 426 | name = Release; 427 | }; 428 | /* End XCBuildConfiguration section */ 429 | 430 | /* Begin XCConfigurationList section */ 431 | 836AB80A28337969003155B8 /* Build configuration list for PBXProject "MBEBaseEffect" */ = { 432 | isa = XCConfigurationList; 433 | buildConfigurations = ( 434 | 836AB8202833796A003155B8 /* Debug */, 435 | 836AB8212833796A003155B8 /* Release */, 436 | ); 437 | defaultConfigurationIsVisible = 0; 438 | defaultConfigurationName = Release; 439 | }; 440 | 83ED2B882839D55200CD6D8B /* Build configuration list for PBXNativeTarget "MBEBaseEffectDemo" */ = { 441 | isa = XCConfigurationList; 442 | buildConfigurations = ( 443 | 83ED2B892839D55200CD6D8B /* Debug */, 444 | 83ED2B8A2839D55200CD6D8B /* Release */, 445 | ); 446 | defaultConfigurationIsVisible = 0; 447 | defaultConfigurationName = Release; 448 | }; 449 | /* End XCConfigurationList section */ 450 | }; 451 | rootObject = 836AB80728337969003155B8 /* Project object */; 452 | } 453 | -------------------------------------------------------------------------------- /MBEBaseEffect.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MBEBaseEffect.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MBEBaseEffect.xcodeproj/xcshareddata/xcschemes/MBEEffectDemo2.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /MBEBaseEffect/MBEBaseEffect.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | #import 4 | #import 5 | 6 | NS_ASSUME_NONNULL_BEGIN 7 | 8 | typedef struct { float x, y, z; } MBEVector3; 9 | typedef simd_float4 MBEVector4; 10 | typedef simd_float3x3 MBEMatrix3; 11 | typedef simd_float4x4 MBEMatrix4; 12 | 13 | /*! 14 | @enum MBEEffectVertexAttribute 15 | @abstract Indices which indicate the ordering of vertex attributes in meshes and shaders 16 | */ 17 | typedef NS_ENUM(UInt32, MBEEffectVertexAttribute) { 18 | MBEEffectVertexAttributePosition, // float3 19 | MBEEffectVertexAttributeNormal, // float3 20 | MBEEffectVertexAttributeColor, // float4 21 | MBEEffectVertexAttributeTexCoord0, // float2 22 | MBEEffectVertexAttributeTexCoord1, // float2 23 | }; 24 | 25 | /*! 26 | @enum MBEEffectLightingMode 27 | @abstract Modes which determine in which stage lighting is calculated 28 | */ 29 | typedef NS_ENUM(UInt32, MBEEffectLightingMode) { 30 | MBEEffectLightingModePerVertex, 31 | MBEEffectLightingModePerFragment 32 | }; 33 | 34 | typedef NS_ENUM(UInt32, MBEEffectTextureType) { 35 | MBEEffectTextureType2D = 1, 36 | }; 37 | 38 | /*! 39 | @enum MBEEffectTextureMode 40 | @abstract Modes which determine how textures are applied. 41 | @constant MBEEffectTextureModeReplace The color sampled from the texture replaces any existing fragment color 42 | @constant MBEEffectTextureModeModulate The color sampled from the texture is multiplied by the existing fragment color 43 | @constant MBEEffectTextureModeDecal The color sampled from the texture is blended with the existing fragment color 44 | according to the sampled color's alpha value, and the resulting alpha is the existing color's alpha. 45 | */ 46 | typedef NS_ENUM(UInt32, MBEEffectTextureMode) { 47 | MBEEffectTextureModeReplace = 1, 48 | MBEEffectTextureModeModulate, 49 | MBEEffectTextureModeDecal, 50 | }; 51 | 52 | /*! 53 | @enum MBEFogMode 54 | @abstract Modes that determine how the fog factor is determined 55 | @constant MBEFogModeExp The fog factor is calculated as the exp of the negative view-space distance 56 | scaled by the density 57 | @constant MBEFogModeExp2 The fog factor is calculated as the exp of the negative view-space distance squared 58 | scaled by the density squared 59 | @constant MBEFogModeLinear The fog factor is calculated by unlerping the view-space distance 60 | between the start and end distances 61 | */ 62 | typedef NS_ENUM(UInt32, MBEFogMode) { 63 | MBEFogModeExp = 1, 64 | MBEFogModeExp2, 65 | MBEFogModeLinear, 66 | }; 67 | 68 | /*! 69 | @class MBEEffectTransform 70 | @abstract A collection of transform matrices used to move among coordinate spaces 71 | */ 72 | @interface MBEEffectTransform : NSObject 73 | @property (nonatomic, assign) MBEMatrix4 modelViewMatrix; 74 | @property (nonatomic, assign) MBEMatrix4 projectionMatrix; 75 | @property (nonatomic, readonly) MBEMatrix3 normalMatrix; 76 | @end 77 | 78 | /*! 79 | @class MBEEffectLight 80 | @abstract A light source 81 | */ 82 | @interface MBEEffectLight : NSObject 83 | /// Enables or disables the light. Default value is YES. 84 | @property (nonatomic, assign) BOOL enabled; 85 | /// The position (point and spot) or direction (directional) of the light. 86 | /// Expressed in model space. Lighting is calculated in eye space, so this 87 | /// vector is transformed by the light's model-view matrix before lighting 88 | /// is performed. Default value is { 0.0, 0.0, 0.0, 1.0 }. 89 | @property (nonatomic, assign) MBEVector4 position; 90 | /// The ambient intensity of the light. Default value is { 0.0, 0.0, 0.0, 1.0 }. 91 | @property (nonatomic, assign) MBEVector4 ambientColor; 92 | /// The diffuse intensity of the light. Default value is { 1.0, 1.0, 1.0, 1.0 }. 93 | @property (nonatomic, assign) MBEVector4 diffuseColor; 94 | /// The specular intensity of the light. Default value is { 1.0, 1.0, 1.0, 1.0 }. 95 | @property (nonatomic, assign) MBEVector4 specularColor; 96 | /// The direction of the spot light. Expressed in model space. Lighting is 97 | /// calculated in eye space, so this vector is transformed by the light's 98 | /// model-view matrix before lighting is performed. 99 | /// Default value is { 0.0, 0.0, -1.0 }. 100 | @property (nonatomic, assign) MBEVector3 spotDirection; 101 | /// The falloff exponent of the spot light. Default value is 0 (hard cutoff). 102 | @property (nonatomic, assign) float spotExponent; 103 | /// The cutoff angle of the spotlight, in degrees. Default value is 180, indicating a point light. 104 | @property (nonatomic, assign) float spotCutoff; 105 | /// The constant distance attenuation term of the light. Default value is 1. 106 | @property (nonatomic, assign) float constantAttenuation; 107 | /// The linear distance attenuation factor of the light. Default value is 0. 108 | @property (nonatomic, assign) float linearAttenuation; 109 | /// The quadratic distance attenuation factor of the light. Default value is 0. 110 | @property (nonatomic, assign) float quadraticAttenuation; 111 | /// A transform for moving lighting vectors from the light's model space 112 | /// into camera space for lighting calculations. 113 | @property (nonatomic, copy) MBEEffectTransform *transform; 114 | @end 115 | 116 | /*! 117 | @class MBEEffectMaterial 118 | @abstract A material description for surfaces 119 | */ 120 | @interface MBEEffectMaterial : NSObject 121 | /// The ambient reflectance of the material. Default value is { 0.2, 0.2, 0.2, 1.0 }. 122 | @property (nonatomic, assign) MBEVector4 ambientColor; 123 | /// The diffuse reflectance of the material. Default value is { 0.8, 0.8, 0.8, 1.0 }. 124 | @property (nonatomic, assign) MBEVector4 diffuseColor; 125 | /// The specular reflectance of the material. Default value is { 0.0, 0.0, 0.0, 1.0 }. 126 | @property (nonatomic, assign) MBEVector4 specularColor; 127 | /// The emissive intensity of the material. Default value is { 0.0, 0.0, 0.0, 1.0 }. 128 | @property (nonatomic, assign) MBEVector4 emissiveColor; 129 | /// The shininess of the material, between 0 and 1. This is remapped to a specular exponent 130 | /// between 1 and 1024. The default value is 0. 131 | @property (nonatomic, assign) float shininess; 132 | @end 133 | 134 | /*! 135 | @class MBEEffectTexture 136 | @abstract A texture map 137 | */ 138 | @interface MBEEffectTexture : NSObject 139 | /// Determines whether the texture is enabled. Default value is NO. Ignored if `texture` is nil. 140 | @property (nonatomic, assign) BOOL enabled; 141 | /// The texture resource to sample. Default value is nil. 142 | @property (nonatomic, nullable, strong) id texture; 143 | /// The type of the texture. Must be MBEEffectTextureType2D. 144 | @property (nonatomic, assign) MBEEffectTextureType type; 145 | /// The mode of the texture. Determines how the sampled color affects the final fragment color. 146 | @property (nonatomic, assign) MBEEffectTextureMode mode; 147 | @end 148 | 149 | /*! 150 | @class MBEEffectFog 151 | @abstract A collection of parameters affecting the calculation of fog 152 | */ 153 | @interface MBEEffectFog : NSObject 154 | /// Determines whether fog calculation occurs. Default value is NO. 155 | @property (nonatomic, assign) BOOL enabled; 156 | /// The calculation mode of the fog factor. Default value is MBEFogModeExp. 157 | @property (nonatomic, assign) MBEFogMode mode; 158 | /// The fog color. Default value is { 0.0, 0.0, 0.0, 0.0 }. 159 | @property (nonatomic, assign) MBEVector4 color; 160 | /// The fog density. Default value is 1. 161 | @property (nonatomic, assign) float density; 162 | /// The start distance of the fog in view space. Ignored unless the mode is MBEFogModeLinear. Default value is 0. 163 | @property (nonatomic, assign) float start; 164 | /// The end distance of the fog in view space. Ignored unless the mode is MBEFogModeLinear. Default value is 1. 165 | @property (nonatomic, assign) float end; 166 | @end 167 | 168 | /*! 169 | @class MBEBaseEffect 170 | @abstract An effect that implements features of the OpenGL ES fixed-function pipeline with Metal shaders. 171 | */ 172 | @interface MBEBaseEffect : NSObject 173 | 174 | - (instancetype)initWithDevice:(id)device; 175 | 176 | - (instancetype)initWithDevice:(id)device vertexDescriptor:(MTLVertexDescriptor *)vertexDescriptor; 177 | 178 | /// The default, preferred vertex descriptor of meshes rendered by an effect. 179 | @property (class, nonatomic, readonly) MTLVertexDescriptor *defaultVertexDescriptor; 180 | 181 | /// A Metal device used to allocate resources internally. 182 | @property (nonatomic, strong) id device; 183 | 184 | /// The vertex descriptor of meshes rendered by this effect. 185 | @property (nonatomic, readonly) MTLVertexDescriptor *vertexDescriptor; 186 | 187 | /// The color pixel format of the first color attachment. Default value is MTLPixelFormatBGRA8Unorm_sRGB. 188 | @property (nonatomic, assign) MTLPixelFormat colorPixelFormat; 189 | 190 | /// The pixel format of the depth attachment. Default value is MTLPixelFormatDepth32Float. 191 | @property (nonatomic, assign) MTLPixelFormat depthPixelFormat; 192 | 193 | /// If NO, colors specified in the material are ignored by lighting calculations. Default value is YES. 194 | @property (nonatomic, assign) BOOL colorMaterialEnabled; 195 | 196 | /// Determines whether lighting is evaluated for back-facing primitives (using a flipped normal). Default is NO. 197 | @property (nonatomic, assign) BOOL lightModelTwoSided; 198 | 199 | /// The model-view and projection transforms used to draw meshes 200 | @property (nonatomic, readonly) MBEEffectTransform *transform; 201 | 202 | /// A light source. Enabled by default. 203 | @property (nonatomic, readonly) MBEEffectLight *light0; 204 | 205 | /// A light source. Disabled by default. 206 | @property (nonatomic, readonly) MBEEffectLight *light1; 207 | 208 | /// A light source. Disabled by default. 209 | @property (nonatomic, readonly) MBEEffectLight *light2; 210 | 211 | /// The lighting mode of the effect. The default mode is MBELightingModePerVertex. 212 | @property (nonatomic, assign) MBEEffectLightingMode lightingMode; 213 | 214 | /// The global ambient light intensity of the scene. Default value is { 0.2, 0.2, 0.2, 1.0 }. 215 | @property (nonatomic, assign) MBEVector4 ambientLightColor; 216 | 217 | /// The material state of the effect. Can be updated between calls to `prepareToDraw:`. 218 | @property (nonatomic, readonly) MBEEffectMaterial *material; 219 | 220 | /// The texture properties for the first texture stage. Disabled by default. 221 | @property (nonatomic, readonly) MBEEffectTexture *texture0; 222 | 223 | /// The texture properties for the second texture stage. Disabled by default. 224 | @property (nonatomic, readonly) MBEEffectTexture *texture1; 225 | 226 | /// The order in which texture stages are evaluated. Must only contain `texture0` and `texture`. 227 | /// Default order is [ texture0, texture1 ]. 228 | @property (nonatomic, nullable, copy) NSArray *textureOrder; 229 | 230 | /// Whether to use the constant color in the absence of per-vertex colors. Default is YES. 231 | @property (nonatomic, assign) BOOL useConstantColor; 232 | 233 | /// The constant vertex color to use in the absence of per-vertex colors. Default is { 1.0, 1.0, 1.0, 1.0 }. 234 | @property (nonatomic, assign) MBEVector4 constantColor; 235 | 236 | /// The fog properties of the effect. Disabled by default. 237 | @property (nonatomic, readonly) MBEEffectFog *fog; 238 | 239 | /// A label used to identify the effect. Default value is @"MBEBaseEffect". 240 | @property (nonatomic, nullable, copy) NSString *label; 241 | 242 | /// Update values and bind resources in preparation of drawing a mesh with the effect. 243 | - (void)prepareToDraw:(id)renderCommandEncoder NS_SWIFT_NAME(prepareToDraw(in:)); 244 | 245 | @end 246 | 247 | NS_ASSUME_NONNULL_END 248 | -------------------------------------------------------------------------------- /MBEBaseEffect/MBEBaseEffect.m: -------------------------------------------------------------------------------- 1 | 2 | #import "MBEBaseEffect.h" 3 | 4 | #define MBELightCount 3 5 | 6 | enum { 7 | MBEVertexBufferIndexVertexAttributes = 0, 8 | MBEVertexBufferIndexInstanceConstants = 16, 9 | }; 10 | 11 | enum { 12 | MBEFragmentBufferIndexInstanceConstants = 16 13 | }; 14 | 15 | static float MBERadFromDeg(float x) { 16 | return x * (M_PI / 180); 17 | } 18 | 19 | static float MBEClamp(float lo, float hi, float x) { 20 | return fmax(lo, fmin(x, hi)); 21 | } 22 | 23 | static float MBERemap(float a, float b, float c, float d, float t) { 24 | float p = (t - a) / (b - a); 25 | return c + p * (d - c); 26 | } 27 | 28 | static MBEVector3 MBEMakeVector3(simd_float3 xyz) { 29 | return (MBEVector3){ xyz[0], xyz[1], xyz[2] }; 30 | } 31 | 32 | static MBEVector4 MBEMakeVector4(MBEVector3 xyz, float w) { 33 | return simd_make_float4(xyz.x, xyz.y, xyz.z, w); 34 | } 35 | 36 | static MBEMatrix3 MBEMatrix4UpperLeft(MBEMatrix4 mat) { 37 | return (MBEMatrix3){{ 38 | { mat.columns[0][0], mat.columns[0][1], mat.columns[0][2] }, 39 | { mat.columns[1][0], mat.columns[1][1], mat.columns[1][2] }, 40 | { mat.columns[2][0], mat.columns[2][1], mat.columns[2][2] }, 41 | }}; 42 | } 43 | 44 | typedef struct { // 96 bytes 45 | MBEVector4 position; 46 | MBEVector4 ambientColor; 47 | MBEVector4 diffuseColor; 48 | MBEVector4 specularColor; 49 | MBEVector3 spotDirection; 50 | float spotExponent; 51 | float spotCosCutoff; 52 | float constantAttenuation; 53 | float linearAttenuation; 54 | float quadraticAttenuation; 55 | } MBELight; 56 | 57 | typedef struct { // 80 bytes 58 | MBEVector4 ambientColor; 59 | MBEVector4 diffuseColor; 60 | MBEVector4 specularColor; 61 | MBEVector4 emissiveColor; 62 | float shininess; 63 | float _pad0, _pad1, _pad2; 64 | } MBEMaterial; 65 | 66 | typedef struct { // 32 bytes 67 | MBEVector4 color; 68 | MBEFogMode mode; 69 | float density; 70 | float start; 71 | float end; 72 | } MBEFogParams; 73 | 74 | typedef struct { // 640 bytes 75 | MBEMatrix4 modelViewMatrix; 76 | MBEMatrix4 projectionMatrix; 77 | MBEMatrix3 normalMatrix; 78 | MBEMaterial material; 79 | MBEFogParams fog; 80 | MBELight lights[MBELightCount]; 81 | MBEVector4 ambientLightColor; 82 | MBEVector4 constantVertexColor; 83 | MBEEffectLightingMode lightingMode; 84 | MBEEffectTextureMode textureModes[2]; 85 | uint32_t colorMaterialEnabled; 86 | uint32_t lightModelTwoSided; 87 | uint32_t useConstantColor; 88 | uint32_t activeLightCount; 89 | float _pad0; 90 | } MBEInstanceConstants; 91 | 92 | @interface MBEBaseEffect (Internal) 93 | - (void)didUpdatePropertyWithKeyPath:(NSString *)keyPath; 94 | @end 95 | 96 | @implementation MBEEffectTransform 97 | 98 | - (instancetype)init { 99 | if (self = [super init]) { 100 | _modelViewMatrix = matrix_identity_float4x4; 101 | _projectionMatrix = matrix_identity_float4x4; 102 | } 103 | return self; 104 | } 105 | 106 | - (MBEMatrix3)normalMatrix { 107 | // FIXME: Technically we want the inverse transpose here. 108 | return MBEMatrix4UpperLeft(self.modelViewMatrix); 109 | } 110 | 111 | @end 112 | 113 | @implementation MBEEffectLight : NSObject 114 | 115 | - (instancetype)init { 116 | if (self = [super init]) { 117 | _enabled = YES; 118 | _position = simd_make_float4(0.0, 0.0, 1.0, 0.0); 119 | _ambientColor = simd_make_float4(0.0, 0.0, 0.0, 1.0); 120 | _diffuseColor = simd_make_float4(1.0, 1.0, 1.0, 1.0); 121 | _specularColor = simd_make_float4(1.0, 1.0, 1.0, 1.0); 122 | _spotDirection = (MBEVector3){ 0.0, 0.0, -1.0 }; 123 | _spotExponent = 0.0; 124 | _spotCutoff = 180.0; 125 | _constantAttenuation = 1.0; 126 | _linearAttenuation = 0.0; 127 | _quadraticAttenuation = 0.0; 128 | _transform = [MBEEffectTransform new]; 129 | } 130 | return self; 131 | } 132 | 133 | @end 134 | 135 | @implementation MBEEffectMaterial 136 | 137 | - (instancetype)init { 138 | if (self = [super init]) { 139 | _ambientColor = simd_make_float4(0.2, 0.2, 0.2, 1.0); 140 | _diffuseColor = simd_make_float4(0.8, 0.8, 0.8, 1.0); 141 | _specularColor = simd_make_float4(0.0, 0.0, 0.0, 1.0); 142 | _emissiveColor = simd_make_float4(0.0, 0.0, 0.0, 1.0); 143 | _shininess = 0.0; 144 | } 145 | return self; 146 | } 147 | 148 | @end 149 | 150 | @implementation MBEEffectTexture 151 | 152 | - (instancetype)init { 153 | if (self = [super init]) { 154 | _enabled = NO; 155 | _type = MBEEffectTextureType2D; 156 | _mode = MBEEffectTextureModeReplace; 157 | } 158 | return self; 159 | } 160 | 161 | @end 162 | 163 | @implementation MBEEffectFog 164 | 165 | - (instancetype)init { 166 | if (self = [super init]) { 167 | _enabled = NO; 168 | _mode = MBEFogModeExp; 169 | _color = simd_make_float4(0.0, 0.0, 0.0, 0.0); 170 | _density = 1.0; 171 | _start = 0.0; 172 | _end = 1.0; 173 | } 174 | return self; 175 | } 176 | 177 | @end 178 | 179 | @interface MBEBaseEffect () 180 | @property (nonatomic, assign) BOOL needsPipelineUpdate; 181 | @property (nonatomic, strong) id renderPipelineState; 182 | @end 183 | 184 | @implementation MBEBaseEffect 185 | 186 | @synthesize colorPixelFormat=_colorPixelFormat; 187 | @synthesize depthPixelFormat=_depthPixelFormat; 188 | 189 | + (MTLVertexDescriptor *)defaultVertexDescriptor { 190 | MTLVertexDescriptor *vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; 191 | vertexDescriptor.attributes[MBEEffectVertexAttributePosition].format = MTLVertexFormatFloat3; 192 | vertexDescriptor.attributes[MBEEffectVertexAttributePosition].bufferIndex = MBEVertexBufferIndexVertexAttributes; 193 | vertexDescriptor.attributes[MBEEffectVertexAttributePosition].offset = 0; 194 | vertexDescriptor.attributes[MBEEffectVertexAttributeNormal].format = MTLVertexFormatFloat3; 195 | vertexDescriptor.attributes[MBEEffectVertexAttributeNormal].bufferIndex = MBEVertexBufferIndexVertexAttributes; 196 | vertexDescriptor.attributes[MBEEffectVertexAttributeNormal].offset = 12; 197 | vertexDescriptor.attributes[MBEEffectVertexAttributeColor].format = MTLVertexFormatFloat4; 198 | vertexDescriptor.attributes[MBEEffectVertexAttributeColor].bufferIndex = MBEVertexBufferIndexVertexAttributes; 199 | vertexDescriptor.attributes[MBEEffectVertexAttributeColor].offset = 24; 200 | vertexDescriptor.attributes[MBEEffectVertexAttributeTexCoord0].format = MTLVertexFormatFloat2; 201 | vertexDescriptor.attributes[MBEEffectVertexAttributeTexCoord0].bufferIndex = MBEVertexBufferIndexVertexAttributes; 202 | vertexDescriptor.attributes[MBEEffectVertexAttributeTexCoord0].offset = 40; 203 | vertexDescriptor.attributes[MBEEffectVertexAttributeTexCoord1].format = MTLVertexFormatFloat2; 204 | vertexDescriptor.attributes[MBEEffectVertexAttributeTexCoord1].bufferIndex = MBEVertexBufferIndexVertexAttributes; 205 | vertexDescriptor.attributes[MBEEffectVertexAttributeTexCoord1].offset = 48; 206 | vertexDescriptor.layouts[MBEVertexBufferIndexVertexAttributes].stride = 56; 207 | return vertexDescriptor; 208 | } 209 | 210 | - (instancetype)initWithDevice:(id)device { 211 | return [self initWithDevice:device vertexDescriptor:MBEBaseEffect.defaultVertexDescriptor]; 212 | } 213 | 214 | - (instancetype)initWithDevice:(id)device vertexDescriptor:(MTLVertexDescriptor *)vertexDescriptor { 215 | if (self = [super init]) { 216 | _device = device; 217 | _vertexDescriptor = vertexDescriptor; 218 | _colorMaterialEnabled = YES; 219 | _lightModelTwoSided = NO; 220 | _useConstantColor = YES; 221 | _transform = [MBEEffectTransform new]; 222 | _light0 = [MBEEffectLight new]; 223 | _light1 = [MBEEffectLight new]; 224 | _light1.enabled = NO; 225 | _light2 = [MBEEffectLight new]; 226 | _light2.enabled = NO; 227 | _lightingMode = MBEEffectLightingModePerVertex; 228 | _ambientLightColor = simd_make_float4(0.2, 0.2, 0.2, 1.0); 229 | _material = [MBEEffectMaterial new]; 230 | _texture0 = [MBEEffectTexture new]; 231 | _texture1 = [MBEEffectTexture new]; 232 | _textureOrder = @[_texture0, _texture1]; 233 | _constantColor = simd_make_float4(1.0, 1.0, 1.0, 1.0); 234 | _fog = [MBEEffectFog new]; 235 | _label = @"MBEBaseEffect"; 236 | _colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB; 237 | _depthPixelFormat = MTLPixelFormatDepth32Float; 238 | _needsPipelineUpdate = YES; 239 | } 240 | return self; 241 | } 242 | 243 | - (void)setColorPixelFormat:(MTLPixelFormat)colorPixelFormat { 244 | _colorPixelFormat = colorPixelFormat; 245 | self.needsPipelineUpdate = YES; 246 | } 247 | 248 | - (void)setDepthPixelFormat:(MTLPixelFormat)depthPixelFormat { 249 | _depthPixelFormat = depthPixelFormat; 250 | self.needsPipelineUpdate = YES; 251 | } 252 | 253 | - (void)makePipeline { 254 | NSError *error = nil; 255 | 256 | id library = [_device newDefaultLibrary]; 257 | id vertexFunction = [library newFunctionWithName:@"vertex_base_effect"]; 258 | id fragmentFunction = [library newFunctionWithName:@"fragment_base_effect"]; 259 | 260 | MTLRenderPipelineDescriptor *renderPipelineDescriptor = [MTLRenderPipelineDescriptor new]; 261 | renderPipelineDescriptor.vertexFunction = vertexFunction; 262 | renderPipelineDescriptor.fragmentFunction = fragmentFunction; 263 | renderPipelineDescriptor.vertexDescriptor = self.vertexDescriptor; 264 | renderPipelineDescriptor.colorAttachments[0].pixelFormat = self.colorPixelFormat; 265 | renderPipelineDescriptor.colorAttachments[0].blendingEnabled = YES; 266 | renderPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne; 267 | renderPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; 268 | renderPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 269 | renderPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; 270 | renderPipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; 271 | renderPipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; 272 | renderPipelineDescriptor.depthAttachmentPixelFormat = self.depthPixelFormat; 273 | 274 | self.renderPipelineState = [self.device newRenderPipelineStateWithDescriptor:renderPipelineDescriptor 275 | error:&error]; 276 | if (error != nil) { 277 | NSLog(@"%@", error); 278 | } 279 | 280 | _needsPipelineUpdate = NO; 281 | } 282 | 283 | - (void)prepareToDraw:(id)renderCommandEncoder { 284 | if (self.needsPipelineUpdate) { 285 | [self makePipeline]; 286 | } 287 | 288 | if (self.renderPipelineState == nil) { 289 | return; 290 | } 291 | 292 | [renderCommandEncoder pushDebugGroup:[NSString stringWithFormat:@"%@ prepareToDraw", self.label]]; 293 | 294 | [renderCommandEncoder setRenderPipelineState:self.renderPipelineState]; 295 | 296 | __block MBEInstanceConstants constants; 297 | memset(&constants, 0, sizeof(MBEInstanceConstants)); 298 | 299 | constants.projectionMatrix = self.transform.projectionMatrix; 300 | constants.modelViewMatrix = self.transform.modelViewMatrix; 301 | constants.normalMatrix = self.transform.normalMatrix; 302 | 303 | if (self.colorMaterialEnabled) { 304 | constants.material.ambientColor = self.material.ambientColor; 305 | constants.material.diffuseColor = self.material.diffuseColor; 306 | constants.material.specularColor = self.material.specularColor; 307 | constants.material.emissiveColor = self.material.emissiveColor; 308 | } else { 309 | constants.material.ambientColor = simd_make_float4(1, 1, 1, 1); 310 | constants.material.diffuseColor = simd_make_float4(1, 1, 1, 1); 311 | constants.material.specularColor = simd_make_float4(1, 1, 1, 1); 312 | constants.material.emissiveColor = simd_make_float4(0, 0, 0, 1); 313 | } 314 | constants.material.shininess = MBERemap(0, 1, 1, 1024, MBEClamp(0, 1, self.material.shininess)); 315 | 316 | if (self.fog.enabled) { 317 | constants.fog.color = self.fog.color; 318 | constants.fog.mode = self.fog.mode; 319 | constants.fog.density = self.fog.density; 320 | constants.fog.start = self.fog.start; 321 | constants.fog.end = self.fog.end; 322 | } 323 | 324 | NSArray *lights = @[_light0, _light1, _light2]; 325 | __block int activeLightIndex = 0; 326 | [lights enumerateObjectsUsingBlock:^(MBEEffectLight *light, NSUInteger lightIndex, BOOL *stop) { 327 | if (!light.enabled) { return; } 328 | MBEVector4 lightPosition = light.position.w == 0 ? 329 | simd_make_float4(simd_mul(light.transform.normalMatrix, light.position.xyz), 0) : 330 | simd_mul(light.transform.modelViewMatrix, light.position); 331 | constants.lights[activeLightIndex].position = lightPosition; 332 | constants.lights[activeLightIndex].ambientColor = light.ambientColor; 333 | constants.lights[activeLightIndex].diffuseColor = light.diffuseColor; 334 | constants.lights[activeLightIndex].specularColor = light.specularColor; 335 | MBEVector4 spotDirection = simd_mul(light.transform.modelViewMatrix, MBEMakeVector4(light.spotDirection, 0)); 336 | constants.lights[activeLightIndex].spotDirection = MBEMakeVector3(spotDirection.xyz); 337 | constants.lights[activeLightIndex].spotExponent = light.spotExponent; 338 | constants.lights[activeLightIndex].spotCosCutoff = cosf(MBERadFromDeg(light.spotCutoff)); 339 | constants.lights[activeLightIndex].constantAttenuation = light.constantAttenuation; 340 | constants.lights[activeLightIndex].linearAttenuation = light.linearAttenuation; 341 | constants.lights[activeLightIndex].quadraticAttenuation = light.quadraticAttenuation; 342 | activeLightIndex += 1; 343 | }]; 344 | 345 | [self.textureOrder enumerateObjectsUsingBlock:^(MBEEffectTexture *textureObj, NSUInteger index, BOOL *stop) { 346 | if (!textureObj.enabled) { return; } 347 | constants.textureModes[index] = textureObj.mode; 348 | [renderCommandEncoder setFragmentTexture:textureObj.texture atIndex:index]; 349 | }]; 350 | 351 | constants.ambientLightColor = self.ambientLightColor; 352 | constants.constantVertexColor = self.constantColor; 353 | constants.lightingMode = self.lightingMode; 354 | constants.colorMaterialEnabled = self.colorMaterialEnabled; 355 | constants.lightModelTwoSided = self.lightModelTwoSided; 356 | constants.useConstantColor = self.useConstantColor; 357 | constants.activeLightCount = activeLightIndex; 358 | 359 | [renderCommandEncoder setVertexBytes:&constants 360 | length:sizeof(MBEInstanceConstants) 361 | atIndex:MBEVertexBufferIndexInstanceConstants]; 362 | [renderCommandEncoder setFragmentBytes:&constants 363 | length:sizeof(MBEInstanceConstants) 364 | atIndex:MBEFragmentBufferIndexInstanceConstants]; 365 | 366 | [renderCommandEncoder popDebugGroup]; 367 | } 368 | 369 | @end 370 | -------------------------------------------------------------------------------- /MBEBaseEffect/Shaders.metal: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | using namespace metal; 4 | 5 | enum { 6 | VertexAttributePosition, 7 | VertexAttributeNormal, 8 | VertexAttributeColor, 9 | VertexAttributeTexCoord0, 10 | //VertexAttributeTexCoord1, 11 | }; 12 | 13 | enum { 14 | VertexBufferIndexInstanceConstants = 16, 15 | }; 16 | 17 | enum { 18 | FragmentBufferIndexInstanceConstants = 16 19 | }; 20 | 21 | constexpr constant int MBELightCount = 3; 22 | constexpr constant int MBETextureStageCount = 2; 23 | 24 | enum class TextureMode: uint32_t { 25 | Disabled, 26 | Replace, 27 | Modulate, 28 | Decal, 29 | }; 30 | 31 | enum class FogMode: uint32_t { 32 | Disabled, 33 | Exp, 34 | Exp2, 35 | Linear, 36 | }; 37 | 38 | enum class LightingMode: uint32_t { 39 | PerVertex, 40 | PerFragment 41 | }; 42 | 43 | struct Light { // 96 bytes 44 | float4 position; 45 | float4 ambientColor; 46 | float4 diffuseColor; 47 | float4 specularColor; 48 | packed_float3 spotDirection; 49 | float spotExponent; 50 | float spotCosCutoff; // cosine of cutoff angle 51 | float constantAttenuation; 52 | float linearAttenuation; 53 | float quadraticAttenuation; 54 | }; 55 | 56 | struct Material { // 80 bytes 57 | float4 ambientColor; 58 | float4 diffuseColor; 59 | float4 specularColor; 60 | float4 emissiveColor; 61 | float shininess; 62 | packed_float3 _pad; 63 | }; 64 | 65 | struct FogParams { // 32 bytes 66 | float4 color; 67 | FogMode mode; 68 | float density; 69 | float start; 70 | float end; 71 | }; 72 | 73 | struct InstanceConstants { // 640 bytes 74 | float4x4 modelViewMatrix; 75 | float4x4 projectionMatrix; 76 | float3x3 normalMatrix; 77 | Material material; 78 | FogParams fog; 79 | Light lights[MBELightCount]; 80 | float4 ambientLightColor; 81 | float4 constantVertexColor; 82 | LightingMode lightingMode; 83 | TextureMode textureModes[MBETextureStageCount]; 84 | uint32_t colorMaterialEnabled; 85 | uint32_t lightModelTwoSided; 86 | uint32_t useConstantColor; 87 | uint32_t activeLightCount; 88 | float _pad; 89 | }; 90 | 91 | 92 | struct VertexIn { 93 | float3 position [[attribute(VertexAttributePosition)]]; 94 | float3 normal [[attribute(VertexAttributeNormal)]]; 95 | float4 color [[attribute(VertexAttributeColor)]]; 96 | float2 texCoord0 [[attribute(VertexAttributeTexCoord0)]]; 97 | //float2 texCoord1 [[attribute(VertexAttributeTexCoord1)]]; 98 | }; 99 | 100 | struct VertexOut { 101 | float4 position [[position]]; 102 | float3 eyePosition; 103 | float3 normal; 104 | float4 color; 105 | float2 texCoord0; 106 | //float2 texCoord1; 107 | }; 108 | 109 | struct FragmentIn { 110 | float4 position [[position]]; 111 | float3 eyePosition; 112 | float3 normal; 113 | float4 color; 114 | float2 texCoord0; 115 | //float2 texCoord1; 116 | bool frontFacing [[front_facing]]; 117 | }; 118 | 119 | static float calculateFogFactor(constant FogParams &fog, float3 eyePosition) { 120 | const float dist = abs(eyePosition.z); 121 | const float fogScale = 1.0 / (fog.end - fog.start); 122 | float fogFactor = 1.0; 123 | switch (fog.mode) { 124 | case FogMode::Disabled: 125 | break; 126 | case FogMode::Exp: 127 | fogFactor = exp(-fog.density * dist); 128 | break; 129 | case FogMode::Exp2: 130 | fogFactor = exp(-fog.density * fog.density * dist * dist); 131 | break; 132 | case FogMode::Linear: 133 | fogFactor = (fog.end - dist) * fogScale; 134 | break; 135 | } 136 | fogFactor = saturate(fogFactor); 137 | return 1 - fogFactor; 138 | } 139 | 140 | void evaluateDirectionalLight(constant Light &light, float shininess, float3 N, float3 H, 141 | thread float3 &ambient, thread float3 &diffuse, thread float3 &specular) 142 | { 143 | float3 L = normalize(light.position.xyz); 144 | float NdotL = max(0.0, dot(N, L)); 145 | float NdotH = max(0.0, dot(N, H)); 146 | float specularFactor = (NdotL > 0) ? powr(NdotH, shininess) : 0; 147 | 148 | ambient += light.ambientColor.rgb; 149 | diffuse += light.diffuseColor.rgb * NdotL; 150 | specular += light.specularColor.rgb * specularFactor; 151 | } 152 | 153 | void evaluatePointLight(constant Light &light, float3 eye, float3 eyePosition, float shininess, float3 N, 154 | thread float3 &ambient, thread float3 &diffuse, thread float3 &specular) 155 | { 156 | float3 L = light.position.xyz - eyePosition; 157 | float d = length(L); 158 | L = normalize(L); 159 | float3 V = normalize(eye - eyePosition); 160 | 161 | float attenDenom = (light.constantAttenuation + light.linearAttenuation * d + light.quadraticAttenuation * d * d); 162 | float attenuation = 1.0 / max(1e-5, attenDenom); 163 | float3 H = normalize(L + V); 164 | float NdotL = max(0.0, dot(N, L)); 165 | float NdotH = max(0.0, dot(N, H)); 166 | float specularFactor = (NdotL > 0) ? powr(NdotH, shininess) : 0; 167 | 168 | ambient += light.ambientColor.rgb * attenuation; 169 | diffuse += light.diffuseColor.rgb * NdotL * attenuation; 170 | specular += light.specularColor.rgb * specularFactor * attenuation; 171 | } 172 | 173 | void evaluateSpotLight(constant Light &light, float3 eye, float3 eyePosition, float shininess, float3 N, 174 | thread float3 &ambient, thread float3 &diffuse, thread float3 &specular) 175 | { 176 | float3 L = light.position.xyz - eyePosition; 177 | float d = length(L); 178 | L = normalize(L); 179 | float3 V = normalize(eye - eyePosition); 180 | 181 | float attenDenom = (light.constantAttenuation + light.linearAttenuation * d + light.quadraticAttenuation * d * d); 182 | float attenuation = 1.0 / max(1e-5, attenDenom); 183 | 184 | float spotDot = max(0.0f, dot(-L, normalize(light.spotDirection))); 185 | float spotAttenuation; 186 | if (spotDot < light.spotCosCutoff) { 187 | spotAttenuation = 0.0; 188 | } else { 189 | spotAttenuation = powr(spotDot, light.spotExponent); 190 | } 191 | 192 | attenuation *= spotAttenuation; 193 | 194 | float3 H = normalize(L + V); 195 | float NdotL = max(0.0, dot(N, L)); 196 | float NdotH = max(0.0, dot(N, H)); 197 | float specularFactor = (NdotL > 0) ? powr(NdotH, shininess) : 0; 198 | 199 | ambient += light.ambientColor.rgb * attenuation; 200 | diffuse += light.diffuseColor.rgb * NdotL * attenuation; 201 | specular += light.specularColor.rgb * specularFactor * attenuation; 202 | } 203 | 204 | static float4 evaluateLighting(float3 N, float4 color, float3 eyePosition, constant InstanceConstants &instance) { 205 | constexpr float3 eyeOrigin { 0 }; 206 | float3 V = normalize(eyeOrigin - eyePosition); 207 | 208 | float3 surfaceAmbient = instance.material.ambientColor.rgb * color.rgb; 209 | float3 surfaceDiffuse = instance.material.diffuseColor.rgb * color.rgb; 210 | float3 surfaceSpecular = instance.material.specularColor.rgb * color.rgb; 211 | float3 surfaceEmissive = instance.material.emissiveColor.rgb; 212 | float shininess = instance.material.shininess; 213 | float opacity = instance.material.diffuseColor.a * color.a; 214 | 215 | float3 ambientIntensity { 0 }; 216 | float3 diffuseIntensity { 0 }; 217 | float3 specularIntensity { 0 }; 218 | for (unsigned i = 0; i < instance.activeLightCount; ++i) { 219 | constant Light &light = instance.lights[i]; 220 | if (light.position.w == 0.0) { 221 | float3 L = normalize(-light.position.xyz); 222 | float3 H = normalize(L + V); 223 | evaluateDirectionalLight(light, shininess, N, H, 224 | ambientIntensity, diffuseIntensity, specularIntensity); 225 | } else if (light.spotCosCutoff == -1.0f) { 226 | evaluatePointLight(light, eyeOrigin, eyePosition, shininess, N, 227 | ambientIntensity, diffuseIntensity, specularIntensity); 228 | } else { 229 | evaluateSpotLight(light, eyeOrigin, eyePosition, shininess, N, 230 | ambientIntensity, diffuseIntensity, specularIntensity); 231 | } 232 | } 233 | 234 | float3 sceneColor = surfaceEmissive + surfaceAmbient * instance.ambientLightColor.rgb; 235 | 236 | color = float4(sceneColor + 237 | ambientIntensity * surfaceAmbient + 238 | diffuseIntensity * surfaceDiffuse + 239 | specularIntensity * surfaceSpecular, 240 | opacity); 241 | return color; 242 | } 243 | 244 | vertex VertexOut vertex_base_effect(VertexIn in [[stage_in]], 245 | constant InstanceConstants &instance [[buffer(VertexBufferIndexInstanceConstants)]]) 246 | { 247 | float4 eyePosition = instance.modelViewMatrix * float4(in.position, 1); 248 | VertexOut out; 249 | out.position = instance.projectionMatrix * eyePosition; 250 | out.eyePosition = eyePosition.xyz; 251 | out.normal = normalize(instance.normalMatrix * in.normal); 252 | out.color = instance.useConstantColor ? instance.constantVertexColor : in.color; 253 | out.texCoord0 = in.texCoord0; 254 | //out.texCoord1 = in.texCoord1; 255 | 256 | // TODO: Implement two-sided lighting for vertex lighting 257 | 258 | if (instance.lightingMode == LightingMode::PerVertex) { 259 | out.color = evaluateLighting(out.normal, out.color, out.eyePosition, instance); 260 | } 261 | 262 | return out; 263 | } 264 | 265 | fragment float4 fragment_base_effect(FragmentIn in [[stage_in]], 266 | constant InstanceConstants &instance [[buffer(FragmentBufferIndexInstanceConstants)]], 267 | texture2d texture0 [[texture(0)]], 268 | texture2d texture1 [[texture(1)]]) 269 | { 270 | constexpr sampler bilinearSampler(coord::normalized, filter::linear, mip_filter::linear, address::repeat); 271 | 272 | float3 N = normalize(in.normal); 273 | if (!in.frontFacing && instance.lightModelTwoSided) { 274 | N = -N; 275 | } 276 | 277 | float4 color = in.color; 278 | float3 eyePosition = in.eyePosition; 279 | if (instance.lightingMode == LightingMode::PerFragment) { 280 | color = evaluateLighting(N, in.color, eyePosition, instance); 281 | } 282 | 283 | float4 texColors[MBETextureStageCount] { 0 }; 284 | if (!is_null_texture(texture0)) { 285 | texColors[0] = texture0.sample(bilinearSampler, in.texCoord0); 286 | } 287 | if (!is_null_texture(texture1)) { 288 | texColors[1] = texture1.sample(bilinearSampler, in.texCoord0); 289 | } 290 | for (int i = 0; i < MBETextureStageCount; ++i) { 291 | switch (instance.textureModes[i]) { 292 | case TextureMode::Disabled: 293 | break; 294 | case TextureMode::Replace: 295 | color = texColors[i]; 296 | break; 297 | case TextureMode::Modulate: 298 | color *= texColors[i]; 299 | break; 300 | case TextureMode::Decal: { 301 | float4 texColor = texColors[i]; 302 | float3 decalColor = mix(color.rgb, texColor.rgb, texColor.a); 303 | color = float4(decalColor, color.a); 304 | break; 305 | } 306 | } 307 | } 308 | 309 | float fogFactor = calculateFogFactor(instance.fog, eyePosition); 310 | color = mix(color, instance.fog.color, fogFactor); 311 | 312 | return color; 313 | } 314 | -------------------------------------------------------------------------------- /MBEEffectDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | 4 | @main 5 | class AppDelegate: NSObject, NSApplicationDelegate { 6 | 7 | func applicationDidFinishLaunching(_ notification: Notification) { 8 | NSColorPanel.shared.showsAlpha = true 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /MBEEffectDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MBEEffectDemo/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 | -------------------------------------------------------------------------------- /MBEEffectDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MBEEffectDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /MBEEffectDemo/Bridging.h: -------------------------------------------------------------------------------- 1 | 2 | #import "MBEBaseEffect.h" 3 | -------------------------------------------------------------------------------- /MBEEffectDemo/Document.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | import MetalKit 4 | import ModelIO 5 | 6 | class Document: NSDocument { 7 | let device: MTLDevice! 8 | let textureLoader: MTKTextureLoader 9 | let bufferAllocator: MTKMeshBufferAllocator 10 | let mesh: MTKMesh 11 | let effect: MBEBaseEffect 12 | 13 | static var lastCascadePoint = CGPoint.zero 14 | 15 | override init() { 16 | device = MTLCreateSystemDefaultDevice()! 17 | effect = MBEBaseEffect(device: device) 18 | textureLoader = MTKTextureLoader(device: device) 19 | bufferAllocator = MTKMeshBufferAllocator(device: device) 20 | 21 | let vertexDescriptor = MDLVertexDescriptor() 22 | vertexDescriptor.attributes_[0].name = MDLVertexAttributePosition 23 | vertexDescriptor.attributes_[0].format = .float3 24 | vertexDescriptor.attributes_[0].bufferIndex = 0 25 | vertexDescriptor.attributes_[0].offset = 0 26 | vertexDescriptor.attributes_[1].name = MDLVertexAttributeNormal 27 | vertexDescriptor.attributes_[1].format = .float3 28 | vertexDescriptor.attributes_[1].bufferIndex = 0 29 | vertexDescriptor.attributes_[1].offset = 12 30 | vertexDescriptor.attributes_[2].name = MDLVertexAttributeColor 31 | vertexDescriptor.attributes_[2].format = .float4 32 | vertexDescriptor.attributes_[2].bufferIndex = 0 33 | vertexDescriptor.attributes_[2].offset = 24 34 | vertexDescriptor.attributes_[3].name = MDLVertexAttributeTextureCoordinate 35 | vertexDescriptor.attributes_[3].format = .float2 36 | vertexDescriptor.attributes_[3].bufferIndex = 0 37 | vertexDescriptor.attributes_[3].offset = 40 38 | vertexDescriptor.attributes_[4].name = MDLVertexAttributeTextureCoordinate 39 | vertexDescriptor.attributes_[4].format = .float2 40 | vertexDescriptor.attributes_[4].bufferIndex = 0 41 | vertexDescriptor.attributes_[4].offset = 48 42 | vertexDescriptor.layouts_[0].stride = 56 43 | 44 | let mdlMesh = MDLMesh.newBox(withDimensions: SIMD3(1, 1, 1), 45 | segments: SIMD3(1, 1, 1), 46 | geometryType: .triangles, 47 | inwardNormals: false, 48 | allocator: bufferAllocator) 49 | mdlMesh.vertexDescriptor = vertexDescriptor 50 | 51 | mesh = try! MTKMesh(mesh: mdlMesh, device: device) 52 | 53 | let textureOptions: [MTKTextureLoader.Option : Any] = [ 54 | .generateMipmaps : true, 55 | ] 56 | let textureURL = Bundle.main.url(forResource: "crate", withExtension:"jpg")! 57 | let crateTexture = try? textureLoader.newTexture(URL: textureURL, options: textureOptions) 58 | 59 | effect.light0.position = float4(1, 1, 1, 0) 60 | effect.light0.diffuseColor = float4(0.8, 0.8, 0.8, 1) 61 | effect.light0.specularColor = float4(1, 1, 1, 1) 62 | effect.light0.ambientColor = float4(0.05, 0.05, 0.05, 1) 63 | 64 | effect.material.diffuseColor = float4(1, 1, 1, 1) 65 | effect.material.ambientColor = float4(1, 1, 1, 1) 66 | effect.material.specularColor = float4(1, 1, 1, 1) 67 | effect.material.shininess = 0.5 68 | 69 | effect.texture0.enabled = true 70 | effect.texture0.mode = .modulate 71 | effect.texture0.texture = crateTexture 72 | 73 | effect.lightingMode = .perFragment 74 | 75 | super.init() 76 | } 77 | 78 | override class var autosavesInPlace: Bool { 79 | return true 80 | } 81 | 82 | override func makeWindowControllers() { 83 | let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) 84 | let identifier = NSStoryboard.SceneIdentifier("Document Window Controller") 85 | let windowController = storyboard.instantiateController(withIdentifier: identifier) as! NSWindowController 86 | self.addWindowController(windowController) 87 | 88 | if let viewController = windowController.contentViewController as? ViewController { 89 | let effectRenderer = EffectRenderer(mesh: mesh, effect: effect) 90 | _ = viewController.view // force load 91 | viewController.effectRenderer = effectRenderer 92 | } 93 | 94 | if let cascadePoint = windowController.window?.cascadeTopLeft(from: Document.lastCascadePoint) { 95 | Document.lastCascadePoint = cascadePoint 96 | } 97 | } 98 | 99 | override func data(ofType typeName: String) throws -> Data { 100 | // Insert code here to write your document to data of the specified type, throwing an error in case of failure. 101 | // Alternatively, you could remove this method and override fileWrapper(ofType:), write(to:ofType:), or write(to:ofType:for:originalContentsURL:) instead. 102 | throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) 103 | } 104 | 105 | override func read(from data: Data, ofType typeName: String) throws { 106 | // Insert code here to read your document from the given data of the specified type, throwing an error in case of failure. 107 | // Alternatively, you could remove this method and override read(from:ofType:) instead. 108 | // If you do, you should also override isEntireFileLoaded to return false if the contents are lazily loaded. 109 | throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /MBEEffectDemo/EffectInspectorView.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | 4 | class FlippedView : NSView { 5 | override var isFlipped: Bool { return true } 6 | } 7 | 8 | class EffectInspectorView: NSView { 9 | var effect: MBEBaseEffect? { 10 | didSet { 11 | bindInspectors() 12 | } 13 | } 14 | 15 | var scrollView: NSScrollView! 16 | var inspectorHostingView: NSView! 17 | 18 | var materialInspectorView: MaterialInspectorView! 19 | var fogInspectorView: FogParameterInspectorView! 20 | var lightInspectorViews = [LightInspectorView]() 21 | var textureInspectorViews = [TextureInspectorView]() 22 | var shadingInspectorView: ShadingInspectorView! 23 | 24 | override init(frame frameRect: NSRect) { 25 | super.init(frame: frameRect) 26 | makeInspectorViews() 27 | } 28 | 29 | required init?(coder: NSCoder) { 30 | super.init(coder: coder) 31 | makeInspectorViews() 32 | } 33 | 34 | private func makeInspectorViews() { 35 | scrollView = NSScrollView(frame: self.bounds) 36 | scrollView.translatesAutoresizingMaskIntoConstraints = false 37 | scrollView.contentView.translatesAutoresizingMaskIntoConstraints = false 38 | self.addSubview(scrollView) 39 | 40 | inspectorHostingView = FlippedView(frame: self.bounds) 41 | inspectorHostingView.translatesAutoresizingMaskIntoConstraints = false 42 | scrollView.documentView = inspectorHostingView 43 | 44 | var topLevels: NSArray? 45 | 46 | let materialInspectorNib = NSNib(nibNamed: "MaterialInspectorView", bundle: nil) 47 | materialInspectorNib!.instantiate(withOwner: nil, topLevelObjects: &topLevels) 48 | if let materialInspectorView = topLevels?.first(where: { $0 is MaterialInspectorView }) as? MaterialInspectorView { 49 | inspectorHostingView.addSubview(materialInspectorView) 50 | self.materialInspectorView = materialInspectorView 51 | } 52 | 53 | let lightInspectorNib = NSNib(nibNamed: "LightInspectorView", bundle: nil) 54 | for lightIndex in 0..<3 { 55 | lightInspectorNib!.instantiate(withOwner: nil, topLevelObjects: &topLevels) 56 | if let lightInspectorView = topLevels?.first(where: { $0 is LightInspectorView }) as? LightInspectorView { 57 | inspectorHostingView.addSubview(lightInspectorView) 58 | lightInspectorView.title = "Light \(lightIndex + 1)" 59 | lightInspectorViews.append(lightInspectorView) 60 | } 61 | } 62 | 63 | let textureInspectorNib = NSNib(nibNamed: "TextureInspectorView", bundle: nil) 64 | for textureIndex in 0..<2 { 65 | textureInspectorNib!.instantiate(withOwner: nil, topLevelObjects: &topLevels) 66 | if let textureInspectorView = topLevels?.first(where: { $0 is TextureInspectorView }) as? TextureInspectorView { 67 | inspectorHostingView.addSubview(textureInspectorView) 68 | textureInspectorView.titleLabel.stringValue = "Texture \(textureIndex + 1)" 69 | textureInspectorViews.append(textureInspectorView) 70 | } 71 | } 72 | 73 | let fogInspectorNib = NSNib(nibNamed: "FogParameterInspectorView", bundle: nil) 74 | fogInspectorNib!.instantiate(withOwner: nil, topLevelObjects: &topLevels) 75 | if let fogInspectorView = topLevels?.first(where: { $0 is FogParameterInspectorView }) as? FogParameterInspectorView { 76 | inspectorHostingView.addSubview(fogInspectorView) 77 | self.fogInspectorView = fogInspectorView 78 | } 79 | 80 | let shadingInspectorNib = NSNib(nibNamed: "ShadingInspectorView", bundle: nil) 81 | shadingInspectorNib!.instantiate(withOwner: nil, topLevelObjects: &topLevels) 82 | if let shadingInspectorView = topLevels?.first(where: { $0 is ShadingInspectorView }) as? ShadingInspectorView { 83 | inspectorHostingView.addSubview(shadingInspectorView) 84 | self.shadingInspectorView = shadingInspectorView 85 | } 86 | 87 | let views: [String : Any] = [ 88 | "scroll" : scrollView!, 89 | "content" : scrollView.contentView, 90 | "document" : inspectorHostingView!, 91 | "material" : materialInspectorView!, 92 | "light0" : lightInspectorViews[0], 93 | "light1" : lightInspectorViews[1], 94 | "light2" : lightInspectorViews[2], 95 | "texture0" : textureInspectorViews[0], 96 | "texture1" : textureInspectorViews[1], 97 | "fog" : fogInspectorView!, 98 | "shading" : shadingInspectorView!, 99 | ] 100 | 101 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|[scroll]|", metrics: nil, views: views)) 102 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scroll]|", metrics: nil, views: views)) 103 | 104 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|[content]|", metrics: nil, views: views)) 105 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[content]|", metrics: nil, views: views)) 106 | 107 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|[document]|", metrics: nil, views: views)) 108 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[document]", metrics: nil, views: views)) 109 | 110 | let inspectorViews = [materialInspectorView!, fogInspectorView!] + lightInspectorViews + textureInspectorViews 111 | for inspector in inspectorViews { 112 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|[inspector]|", 113 | metrics: nil, 114 | views: ["inspector" : inspector])) 115 | } 116 | 117 | addConstraints(NSLayoutConstraint.constraints( 118 | withVisualFormat: "V:|[material][texture0][texture1][light0][light1][light2][fog][shading]|", 119 | metrics: nil, 120 | views: views)) 121 | 122 | lightInspectorViews[1].setDisclosed(false) 123 | lightInspectorViews[2].setDisclosed(false) 124 | textureInspectorViews[1].setDisclosed(false) 125 | fogInspectorView.setDisclosed(false) 126 | } 127 | 128 | func bindInspectors() { 129 | materialInspectorView.effect = effect 130 | lightInspectorViews[0].light = effect?.light0 131 | lightInspectorViews[1].light = effect?.light1 132 | lightInspectorViews[2].light = effect?.light2 133 | textureInspectorViews[0].device = effect?.device 134 | textureInspectorViews[0].texture = effect?.texture0 135 | textureInspectorViews[1].device = effect?.device 136 | textureInspectorViews[1].texture = effect?.texture1 137 | fogInspectorView.effect = effect 138 | shadingInspectorView.effect = effect 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /MBEEffectDemo/EffectRenderer.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import MetalKit 4 | 5 | import MetalKit 6 | 7 | class EffectRenderer: NSObject, MTKViewDelegate { 8 | let effect: MBEBaseEffect 9 | let mesh: MTKMesh 10 | let device: MTLDevice 11 | let commandQueue: MTLCommandQueue 12 | 13 | private let depthStencilState: MTLDepthStencilState! 14 | private var time: Float = 0 15 | 16 | init(mesh: MTKMesh, effect: MBEBaseEffect) { 17 | self.effect = effect 18 | self.mesh = mesh 19 | 20 | device = effect.device 21 | commandQueue = device.makeCommandQueue()! 22 | 23 | let depthStencilDescriptor = MTLDepthStencilDescriptor() 24 | depthStencilDescriptor.isDepthWriteEnabled = true 25 | depthStencilDescriptor.depthCompareFunction = .lessEqual 26 | depthStencilState = device.makeDepthStencilState(descriptor:depthStencilDescriptor)! 27 | 28 | super.init() 29 | } 30 | 31 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { 32 | let aspect = Float(size.width / size.height); 33 | effect.transform.projectionMatrix = simd_float4x4(perspectiveProjectionFoVY: 1.13, 34 | aspectRatio: aspect, 35 | near: 0.1, 36 | far: 150) 37 | } 38 | 39 | func draw(in view: MTKView) { 40 | guard let passDescriptor = view.currentRenderPassDescriptor else { return } 41 | 42 | time += 1 / Float(view.preferredFramesPerSecond) 43 | 44 | let viewMatrix = simd_float4x4(translate: float3(0, 0, -2)) 45 | let modelMatrix = simd_float4x4(rotateAbout: float3(1, 0.6, 0.4), byAngle: time) 46 | effect.transform.modelViewMatrix = viewMatrix * modelMatrix 47 | 48 | // Bit of a hack to ensure that light positions/directions provided via UI are in world space 49 | effect.light0.transform.modelViewMatrix = viewMatrix 50 | effect.light1.transform.modelViewMatrix = viewMatrix 51 | effect.light2.transform.modelViewMatrix = viewMatrix 52 | 53 | let commandBuffer = commandQueue.makeCommandBuffer()! 54 | 55 | let renderCommandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor:passDescriptor)! 56 | 57 | renderCommandEncoder.setFrontFacing(.counterClockwise) 58 | renderCommandEncoder.setCullMode(.back) 59 | renderCommandEncoder.setDepthStencilState(depthStencilState) 60 | 61 | renderCommandEncoder.pushDebugGroup("Draw mesh") 62 | 63 | effect.prepareToDraw(in: renderCommandEncoder) 64 | 65 | for (index, meshBuffer) in mesh.vertexBuffers.enumerated() { 66 | renderCommandEncoder.setVertexBuffer(meshBuffer.buffer, 67 | offset:meshBuffer.offset, 68 | index:index) 69 | } 70 | 71 | for submesh in mesh.submeshes { 72 | renderCommandEncoder.drawIndexedPrimitives(type: submesh.primitiveType, 73 | indexCount:submesh.indexCount, 74 | indexType:submesh.indexType, 75 | indexBuffer:submesh.indexBuffer.buffer, 76 | indexBufferOffset:submesh.indexBuffer.offset) 77 | } 78 | 79 | renderCommandEncoder.popDebugGroup() 80 | 81 | renderCommandEncoder.endEncoding() 82 | 83 | commandBuffer.present(view.currentDrawable!) 84 | 85 | commandBuffer.commit() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /MBEEffectDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDocumentTypes 6 | 7 | 8 | CFBundleTypeRole 9 | Editor 10 | LSItemContentTypes 11 | 12 | com.example.plain-text 13 | 14 | NSDocumentClass 15 | $(PRODUCT_MODULE_NAME).Document 16 | 17 | 18 | UTImportedTypeDeclarations 19 | 20 | 21 | UTTypeConformsTo 22 | 23 | public.plain-text 24 | 25 | UTTypeDescription 26 | Example Text 27 | UTTypeIdentifier 28 | com.example.plain-text 29 | UTTypeTagSpecification 30 | 31 | public.filename-extension 32 | 33 | exampletext 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/FogParameterInspectorView.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | import AppKit 4 | 5 | class FogParameterInspectorView : InspectorView { 6 | var effect: MBEBaseEffect? { 7 | didSet { 8 | bindToEffect() 9 | } 10 | } 11 | 12 | @IBOutlet weak var enabledButton: NSButton! 13 | @IBOutlet weak var modePopupButton: NSPopUpButton! 14 | @IBOutlet weak var fogColorWell: NSColorWell! 15 | @IBOutlet weak var fogDensityTextField: NSTextField! 16 | @IBOutlet weak var fogStartDistanceField: NSTextField! 17 | @IBOutlet weak var fogEndDistanceField: NSTextField! 18 | 19 | override var disclosedHeight: CGFloat { return 152 } 20 | 21 | func bindToEffect() { 22 | guard let fog = effect?.fog else { return } 23 | 24 | self.enabledButton.state = fog.enabled ? .on : .off 25 | modePopupButton.selectItem(at: Int(fog.mode.rawValue) - 1) 26 | self.fogColorWell.color = NSColor(red: CGFloat(fog.color.x), 27 | green: CGFloat(fog.color.y), 28 | blue: CGFloat(fog.color.z), 29 | alpha: CGFloat(fog.color.w)) 30 | self.fogDensityTextField.stringValue = NSString(format: "%0.1f", fog.density) as String 31 | self.fogStartDistanceField.stringValue = NSString(format: "%0.1f", fog.start) as String 32 | self.fogEndDistanceField.stringValue = NSString(format: "%0.1f", fog.end) as String 33 | } 34 | 35 | @IBAction func disclosureButtonValueDidChange(_ sender: Any) { 36 | setDisclosed(disclosureButton!.state == .on, animate: true) 37 | } 38 | 39 | @IBAction func enableButtonValueDidChange(_ sender: Any) { 40 | guard let fog = effect?.fog else { return } 41 | fog.enabled = (enabledButton.state == .on) 42 | } 43 | 44 | @IBAction func modeButtonValueDidChange(_ sender: Any) { 45 | guard let fog = effect?.fog else { return } 46 | fog.mode = MBEFogMode(rawValue: UInt32(modePopupButton.indexOfSelectedItem) + 1) ?? .exp 47 | } 48 | 49 | @IBAction func fogColorWellValueDidChange(_ sender: Any) { 50 | guard let fog = effect?.fog else { return } 51 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 52 | fogColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 53 | fog.color = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 54 | } 55 | 56 | @IBAction func fogDensityTextFieldValueDidChange(_ sender: Any) { 57 | guard let fog = effect?.fog else { return } 58 | fog.density = fogDensityTextField.floatValue 59 | } 60 | 61 | @IBAction func fogStartDistanceFieldValueDidChange(_ sender: Any) { 62 | guard let fog = effect?.fog else { return } 63 | fog.start = fogStartDistanceField.floatValue 64 | } 65 | 66 | @IBAction func fogEndDistanceFieldValueDidChange(_ sender: Any) { 67 | guard let fog = effect?.fog else { return } 68 | fog.end = fogEndDistanceField.floatValue 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/FogParameterInspectorView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/InspectorView.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import Cocoa 4 | 5 | class InspectorView : NSView { 6 | @IBOutlet weak var disclosureButton: NSButton? 7 | 8 | var heightConstraint: NSLayoutConstraint! 9 | var disclosedHeight: CGFloat { return 100 } 10 | var undisclosedHeight: CGFloat { return 21 } 11 | 12 | override init(frame frameRect: NSRect) { 13 | super.init(frame: frameRect) 14 | commonInspectorInit() 15 | } 16 | 17 | required init?(coder: NSCoder) { 18 | super.init(coder: coder) 19 | commonInspectorInit() 20 | } 21 | 22 | private func commonInspectorInit() { 23 | heightConstraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, 24 | toItem: nil, attribute: .notAnAttribute, multiplier: 0.0, 25 | constant: disclosedHeight) 26 | self.addConstraint(heightConstraint) 27 | } 28 | 29 | func setDisclosed(_ disclosed: Bool, animate: Bool = false) { 30 | let targetHeight = disclosed ? disclosedHeight : undisclosedHeight 31 | if animate { 32 | NSAnimationContext.runAnimationGroup { context in 33 | context.duration = 0.3 34 | context.allowsImplicitAnimation = true 35 | heightConstraint.constant = targetHeight 36 | disclosureButton?.state = disclosed ? .on : .off 37 | superview?.layoutSubtreeIfNeeded() 38 | } 39 | } else { 40 | heightConstraint.constant = targetHeight 41 | disclosureButton?.state = disclosed ? .on : .off 42 | superview?.layoutSubtreeIfNeeded() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/LightInspectorView.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | 4 | class LightInspectorView: InspectorView { 5 | var light: MBEEffectLight? { 6 | didSet { 7 | bindToEffect() 8 | } 9 | } 10 | 11 | var title: String { 12 | get { 13 | return titleLabel.stringValue 14 | } 15 | set { 16 | titleLabel.stringValue = newValue 17 | } 18 | } 19 | 20 | @IBOutlet weak var titleLabel: NSTextField! 21 | @IBOutlet weak var enabledButton: NSButton! 22 | @IBOutlet weak var lightTypePopupButton: NSPopUpButton! 23 | @IBOutlet weak var positionDirectionLabel: NSTextField! 24 | @IBOutlet weak var lightDirectionXTextField: NSTextField! 25 | @IBOutlet weak var lightDirectionYTextField: NSTextField! 26 | @IBOutlet weak var lightDirectionZTextField: NSTextField! 27 | @IBOutlet weak var ambientLightColorWell: NSColorWell! 28 | @IBOutlet weak var diffuseLightColorWell: NSColorWell! 29 | @IBOutlet weak var specularLightColorWell: NSColorWell! 30 | @IBOutlet weak var spotDirectionXTextField: NSTextField! 31 | @IBOutlet weak var spotDirectionYTextField: NSTextField! 32 | @IBOutlet weak var spotDirectionZTextField: NSTextField! 33 | @IBOutlet weak var spotCutoffTextField: NSTextField! 34 | @IBOutlet weak var spotExponentTextField: NSTextField! 35 | @IBOutlet weak var attenuationConstantTextField: NSTextField! 36 | @IBOutlet weak var attenuationLinearTextField: NSTextField! 37 | @IBOutlet weak var attenuationQuadraticTextField: NSTextField! 38 | 39 | override var disclosedHeight: CGFloat { return 284 } 40 | 41 | func bindToEffect() { 42 | guard let light = light else { return } 43 | 44 | enabledButton.state = light.enabled ? .on : .off 45 | 46 | if light.position.w == 0 { 47 | lightTypePopupButton.selectItem(at: 0) // directional 48 | } else if light.spotCutoff == 180 { 49 | lightTypePopupButton.selectItem(at: 1) // point 50 | } else { 51 | lightTypePopupButton.selectItem(at: 2) // spot 52 | } 53 | 54 | lightDirectionXTextField.stringValue = NSString(format: "%0.2f", light.position.x) as String 55 | lightDirectionYTextField.stringValue = NSString(format: "%0.2f", light.position.y) as String 56 | lightDirectionZTextField.stringValue = NSString(format: "%0.2f", light.position.z) as String 57 | 58 | ambientLightColorWell.color = NSColor(red: CGFloat(light.ambientColor.x), 59 | green: CGFloat(light.ambientColor.y), 60 | blue: CGFloat(light.ambientColor.z), 61 | alpha: CGFloat(light.ambientColor.w)) 62 | 63 | diffuseLightColorWell.color = NSColor(red: CGFloat(light.diffuseColor.x), 64 | green: CGFloat(light.diffuseColor.y), 65 | blue: CGFloat(light.diffuseColor.z), 66 | alpha: CGFloat(light.diffuseColor.w)) 67 | 68 | specularLightColorWell.color = NSColor(red: CGFloat(light.specularColor.x), 69 | green: CGFloat(light.specularColor.y), 70 | blue: CGFloat(light.specularColor.z), 71 | alpha: CGFloat(light.specularColor.w)) 72 | 73 | spotDirectionXTextField.stringValue = NSString(format: "%0.2f", light.spotDirection.x) as String 74 | spotDirectionYTextField.stringValue = NSString(format: "%0.2f", light.spotDirection.y) as String 75 | spotDirectionZTextField.stringValue = NSString(format: "%0.2f", light.spotDirection.z) as String 76 | spotCutoffTextField.stringValue = NSString(format: "%0.1f", light.spotCutoff) as String 77 | spotExponentTextField.stringValue = NSString(format: "%0.1f", light.spotExponent) as String 78 | 79 | attenuationConstantTextField.stringValue = NSString(format: "%0.2f", light.constantAttenuation) as String 80 | attenuationLinearTextField.stringValue = NSString(format: "%0.2f", light.linearAttenuation) as String 81 | attenuationQuadraticTextField.stringValue = NSString(format: "%0.2f", light.quadraticAttenuation) as String 82 | } 83 | 84 | @IBAction func disclosureButtonValueDidChange(_ sender: Any) { 85 | setDisclosed(disclosureButton!.state == .on, animate: true) 86 | } 87 | 88 | @IBAction func enabledButtonValueDidChange(_ sender: Any) { 89 | light?.enabled = (enabledButton.state == .on) 90 | } 91 | 92 | @IBAction func lightTypeValueDidChange(_ sender: Any) { 93 | if lightTypePopupButton.indexOfSelectedItem == 0 { 94 | light?.position.w = 0 95 | } else if lightTypePopupButton.indexOfSelectedItem == 1 { 96 | light?.position.w = 1 97 | light?.spotCutoff = 180 98 | } else { 99 | light?.position.w = 1 100 | } 101 | 102 | spotCutoffValueDidChange(sender) 103 | } 104 | 105 | @IBAction func directionXValueDidChange(_ sender: Any) { 106 | light?.position.x = lightDirectionXTextField.floatValue 107 | } 108 | 109 | @IBAction func directionYValueDidChange(_ sender: Any) { 110 | light?.position.y = lightDirectionYTextField.floatValue 111 | } 112 | 113 | @IBAction func directionZValueDidChange(_ sender: Any) { 114 | light?.position.z = lightDirectionZTextField.floatValue 115 | } 116 | 117 | @IBAction func ambientColorValueDidChange(_ sender: Any) { 118 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 119 | ambientLightColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 120 | light?.ambientColor = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 121 | } 122 | 123 | @IBAction func diffuseColorValueDidChange(_ sender: Any) { 124 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 125 | diffuseLightColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 126 | light?.diffuseColor = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 127 | } 128 | 129 | @IBAction func specularColorValueDidChange(_ sender: Any) { 130 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 131 | specularLightColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 132 | light?.specularColor = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 133 | } 134 | 135 | @IBAction func spotDirectionXValueDidChange(_ sender: Any) { 136 | light?.spotDirection.x = self.spotDirectionXTextField.floatValue 137 | } 138 | 139 | @IBAction func spotDirectionYValueDidChange(_ sender: Any) { 140 | light?.spotDirection.y = self.spotDirectionYTextField.floatValue 141 | } 142 | 143 | @IBAction func spotDirectionZValueDidChange(_ sender: Any) { 144 | light?.spotDirection.z = self.spotDirectionZTextField.floatValue 145 | } 146 | 147 | @IBAction func spotCutoffValueDidChange(_ sender: Any) { 148 | light?.spotCutoff = self.spotCutoffTextField.floatValue 149 | } 150 | 151 | @IBAction func spotExponentValueDidChange(_ sender: Any) { 152 | light?.spotExponent = self.spotExponentTextField.floatValue 153 | } 154 | 155 | @IBAction func attenuationConstantValueDidChange(_ sender: Any) { 156 | light?.constantAttenuation = attenuationConstantTextField.floatValue 157 | } 158 | 159 | @IBAction func attenuationLinearValueDidChange(_ sender: Any) { 160 | light?.linearAttenuation = attenuationLinearTextField.floatValue 161 | } 162 | 163 | @IBAction func attenuationQuadraticValueDidChange(_ sender: Any) { 164 | light?.quadraticAttenuation = attenuationQuadraticTextField.floatValue 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/MaterialInspectorView.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | import AppKit 4 | 5 | class MaterialInspectorView : InspectorView { 6 | var effect: MBEBaseEffect? { 7 | didSet { 8 | bindToEffect() 9 | } 10 | } 11 | 12 | @IBOutlet weak var ambientColorWell: NSColorWell! 13 | @IBOutlet weak var diffuseColorWell: NSColorWell! 14 | @IBOutlet weak var specularColorWell: NSColorWell! 15 | @IBOutlet weak var emissiveColorWell: NSColorWell! 16 | @IBOutlet weak var shininessSlider: NSSlider! 17 | 18 | override var disclosedHeight: CGFloat { return 152 } 19 | 20 | func bindToEffect() { 21 | guard let effect = effect else { return } 22 | let material = effect.material 23 | self.ambientColorWell.color = NSColor(red: CGFloat(material.ambientColor.x), 24 | green: CGFloat(material.ambientColor.y), 25 | blue: CGFloat(material.ambientColor.z), 26 | alpha: CGFloat(material.ambientColor.w)) 27 | self.diffuseColorWell.color = NSColor(red: CGFloat(material.diffuseColor.x), 28 | green: CGFloat(material.diffuseColor.y), 29 | blue: CGFloat(material.diffuseColor.z), 30 | alpha: CGFloat(material.diffuseColor.w)) 31 | self.specularColorWell.color = NSColor(red: CGFloat(material.specularColor.x), 32 | green: CGFloat(material.specularColor.y), 33 | blue: CGFloat(material.specularColor.z), 34 | alpha: CGFloat(material.specularColor.w)) 35 | self.emissiveColorWell.color = NSColor(red: CGFloat(material.emissiveColor.x), 36 | green: CGFloat(material.emissiveColor.y), 37 | blue: CGFloat(material.emissiveColor.z), 38 | alpha: CGFloat(material.emissiveColor.w)) 39 | self.shininessSlider.floatValue = material.shininess 40 | } 41 | 42 | @IBAction func disclosureButtonValueDidChange(_ sender: Any) { 43 | setDisclosed(disclosureButton!.state == .on, animate: true) 44 | } 45 | 46 | @IBAction func ambientColorWellValueDidChange(_ sender: Any) { 47 | guard let effect = effect else { return } 48 | let material = effect.material 49 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 50 | ambientColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 51 | material.ambientColor = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 52 | } 53 | 54 | @IBAction func diffuseColorWellValueDidChange(_ sender: Any) { 55 | guard let effect = effect else { return } 56 | let material = effect.material 57 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 58 | diffuseColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 59 | material.diffuseColor = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 60 | } 61 | 62 | @IBAction func specularColorWellValueDidChange(_ sender: Any) { 63 | guard let effect = effect else { return } 64 | let material = effect.material 65 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 66 | specularColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 67 | material.specularColor = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 68 | } 69 | 70 | @IBAction func emissiveColorWellValueDidChange(_ sender: Any) { 71 | guard let effect = effect else { return } 72 | let material = effect.material 73 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 74 | emissiveColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 75 | material.emissiveColor = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 76 | } 77 | 78 | @IBAction func shininessValueDidChange(_ sender: Any) { 79 | guard let effect = effect else { return } 80 | let material = effect.material 81 | material.shininess = shininessSlider.floatValue 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/MaterialInspectorView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/ShadingInspectorView.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | import AppKit 4 | 5 | class ShadingInspectorView : InspectorView { 6 | var effect: MBEBaseEffect? { 7 | didSet { 8 | bindToEffect() 9 | } 10 | } 11 | 12 | @IBOutlet weak var lightingModePopUpButton: NSPopUpButton! 13 | @IBOutlet weak var twoSidedLightingCheckbox: NSButton! 14 | @IBOutlet weak var ambientColorWell: NSColorWell! 15 | @IBOutlet weak var constantColorWell: NSColorWell! 16 | @IBOutlet weak var constantColorCheckbox: NSButton! 17 | 18 | override var disclosedHeight: CGFloat { return 125 } 19 | 20 | func bindToEffect() { 21 | guard let effect = effect else { return } 22 | self.lightingModePopUpButton.selectItem(at: Int(effect.lightingMode.rawValue)) 23 | self.twoSidedLightingCheckbox.state = effect.lightModelTwoSided ? .on : .off 24 | self.ambientColorWell.color = NSColor(red: CGFloat(effect.ambientLightColor.x), 25 | green: CGFloat(effect.ambientLightColor.y), 26 | blue: CGFloat(effect.ambientLightColor.z), 27 | alpha: CGFloat(effect.ambientLightColor.w)) 28 | self.constantColorWell.color = NSColor(red: CGFloat(effect.constantColor.x), 29 | green: CGFloat(effect.constantColor.y), 30 | blue: CGFloat(effect.constantColor.z), 31 | alpha: CGFloat(effect.constantColor.w)) 32 | self.constantColorCheckbox.state = effect.useConstantColor ? .on : .off 33 | } 34 | 35 | @IBAction func disclosureButtonValueDidChange(_ sender: Any) { 36 | setDisclosed(disclosureButton!.state == .on, animate: true) 37 | } 38 | 39 | @IBAction func lightingModeValueDidChange(_ sender: Any) { 40 | effect?.lightingMode = MBEEffectLightingMode(rawValue: UInt32(lightingModePopUpButton.indexOfSelectedItem)) ?? .perVertex 41 | } 42 | 43 | @IBAction func twoSidedLightingValueDidChange(_ sender: Any) { 44 | effect?.lightModelTwoSided = (twoSidedLightingCheckbox.state == .on) 45 | } 46 | 47 | @IBAction func ambientColorWellValueDidChange(_ sender: Any) { 48 | guard let effect = effect else { return } 49 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 50 | ambientColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 51 | effect.ambientLightColor = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 52 | } 53 | 54 | @IBAction func constantColorWellValueDidChange(_ sender: Any) { 55 | guard let effect = effect else { return } 56 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 57 | constantColorWell.color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 58 | effect.constantColor = MBEVector4(Float(red), Float(green), Float(blue), Float(alpha)) 59 | } 60 | 61 | @IBAction func useConstantColorValueDidChange(_ sender: Any) { 62 | effect?.useConstantColor = constantColorCheckbox.state == .on 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/ShadingInspectorView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/TextureInspectorView.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | import AppKit 4 | import MetalKit 5 | 6 | extension MTLTexture { 7 | var nsImage: NSImage? { 8 | let bytesPerRow = width * 4 9 | let imageBytes = UnsafeMutableRawPointer.allocate(byteCount: bytesPerRow * height, alignment: 4) 10 | defer { imageBytes.deallocate() } 11 | getBytes(imageBytes, bytesPerRow: bytesPerRow, from: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0) 12 | let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)! 13 | let bitmapInfo = CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue 14 | let context = CGContext(data: imageBytes, 15 | width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, 16 | space: colorSpace, 17 | bitmapInfo: bitmapInfo) 18 | if let cgImage = context?.makeImage() { 19 | return NSImage(cgImage: cgImage, size: CGSize(width: width, height: height)) 20 | } 21 | return nil 22 | } 23 | } 24 | 25 | class TextureInspectorView : InspectorView { 26 | var device: MTLDevice! 27 | 28 | var texture: MBEEffectTexture? { 29 | didSet { 30 | bindToEffect() 31 | } 32 | } 33 | 34 | var title: String { 35 | get { return titleLabel.stringValue } 36 | set { titleLabel.stringValue = newValue } 37 | } 38 | 39 | @IBOutlet weak var titleLabel: NSTextField! 40 | @IBOutlet weak var enabledButton: NSButton! 41 | @IBOutlet weak var textureModePopupButton: NSPopUpButton! 42 | @IBOutlet weak var textureImageWell: NSImageView! 43 | 44 | override var disclosedHeight: CGFloat { return 108 } 45 | 46 | func bindToEffect() { 47 | guard let texture = texture else { return } 48 | 49 | enabledButton.state = texture.enabled ? .on : .off 50 | textureModePopupButton.selectItem(at: Int(texture.mode.rawValue - 1)) 51 | textureImageWell.image = texture.texture?.nsImage 52 | } 53 | 54 | @IBAction func disclosureButtonValueDidChange(_ sender: Any) { 55 | setDisclosed(disclosureButton!.state == .on, animate: true) 56 | } 57 | 58 | @IBAction func enabledButtonDidChangeValue(_ sender: Any) { 59 | guard let texture = texture else { return } 60 | texture.enabled = enabledButton.state == .on 61 | } 62 | 63 | @IBAction func textureModeValueDidChange(_ sender: Any) { 64 | guard let texture = texture else { return } 65 | texture.mode = MBEEffectTextureMode(rawValue: UInt32(textureModePopupButton.indexOfSelectedItem) + 1) ?? .replace 66 | } 67 | 68 | @IBAction func textureImageValueDidChange(_ sender: Any) { 69 | guard let texture = texture else { return } 70 | 71 | if let image = textureImageWell.image, 72 | let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) 73 | { 74 | let textureOptions = [MTKTextureLoader.Option.generateMipmaps : true] 75 | let textureLoader = MTKTextureLoader(device: device) 76 | do { 77 | let newTexture = try textureLoader.newTexture(cgImage: cgImage, options: textureOptions) 78 | texture.texture = newTexture 79 | } catch { 80 | print("Could not load texture: \(error)") 81 | textureImageWell.image = nil 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /MBEEffectDemo/Inspectors/TextureInspectorView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /MBEEffectDemo/MBEEffectDemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MBEEffectDemo/Math.swift: -------------------------------------------------------------------------------- 1 | 2 | import simd 3 | import Accelerate 4 | 5 | typealias float3 = SIMD3 6 | typealias float4 = SIMD4 7 | 8 | extension SIMD4 { 9 | var xyz: SIMD3 { 10 | return SIMD3(x, y, z) 11 | } 12 | } 13 | 14 | extension float4x4 { 15 | init(scale2D s: SIMD2) { 16 | self.init(SIMD4(s.x, 0, 0, 0), 17 | SIMD4( 0, s.y, 0, 0), 18 | SIMD4( 0, 0, 1, 0), 19 | SIMD4( 0, 0, 0, 1)) 20 | } 21 | 22 | init(rotateZ zRadians: Float) { 23 | let s = sin(zRadians) 24 | let c = cos(zRadians) 25 | self.init(SIMD4( c, s, 0, 0), 26 | SIMD4(-s, c, 0, 0), 27 | SIMD4( 0, 0, 1, 0), 28 | SIMD4( 0, 0, 0, 1)) 29 | } 30 | 31 | init(translate2D t: SIMD2) { 32 | self.init(SIMD4( 1, 0, 0, 0), 33 | SIMD4( 0, 1, 0, 0), 34 | SIMD4( 0, 0, 1, 0), 35 | SIMD4(t.x, t.y, 0, 1)) 36 | } 37 | 38 | init(scale s: SIMD3) { 39 | self.init(SIMD4(s.x, 0, 0, 0), 40 | SIMD4( 0, s.y, 0, 0), 41 | SIMD4( 0, 0, s.z, 0), 42 | SIMD4( 0, 0, 0, 1)) 43 | } 44 | 45 | init(rotateAbout axis: SIMD3, byAngle radians: Float) { 46 | let a = normalize(axis) 47 | let x = a.x 48 | let y = a.y 49 | let z = a.z 50 | let s = sin(radians) 51 | let c = cos(radians) 52 | 53 | self.init( 54 | SIMD4(x * x + (1 - x * x) * c, x * y * (1 - c) - z * s, x * z * (1 - c) + y * s, 0), 55 | SIMD4(x * y * (1 - c) + z * s, y * y + (1 - y * y) * c, y * z * (1 - c) - x * s, 0), 56 | SIMD4(x * z * (1 - c) - y * s, y * z * (1 - c) + x * s, z * z + (1 - z * z) * c, 0), 57 | SIMD4( 0, 0, 0, 1) 58 | ) 59 | } 60 | 61 | init(translate t: SIMD3) { 62 | self.init(SIMD4( 1, 0, 0, 0), 63 | SIMD4( 0, 1, 0, 0), 64 | SIMD4( 0, 0, 1, 0), 65 | SIMD4(t.x, t.y, t.z, 1)) 66 | } 67 | 68 | init(lookAt at: SIMD3, from: SIMD3, up: SIMD3) { 69 | let zNeg = normalize(at - from) 70 | let x = normalize(cross(zNeg, up)) 71 | let y = normalize(cross(x, zNeg)) 72 | self.init(SIMD4(x, 0), 73 | SIMD4(y, 0), 74 | SIMD4(-zNeg, 0), 75 | SIMD4(from, 1)) 76 | } 77 | 78 | init(perspectiveProjectionFoVY fovYRadians: Float, 79 | aspectRatio: Float, 80 | near: Float, 81 | far: Float) 82 | { 83 | let sy = 1 / tan(fovYRadians * 0.5) 84 | let sx = sy / aspectRatio 85 | let zRange = far - near 86 | let sz = -(far + near) / zRange 87 | let tz = -2 * far * near / zRange 88 | self.init(SIMD4(sx, 0, 0, 0), 89 | SIMD4(0, sy, 0, 0), 90 | SIMD4(0, 0, sz, -1), 91 | SIMD4(0, 0, tz, 0)) 92 | } 93 | 94 | var upperLeft3x3: float3x3 { 95 | return float3x3(columns.0.xyz, columns.1.xyz, columns.2.xyz) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /MBEEffectDemo/ModelIOExtensions.swift: -------------------------------------------------------------------------------- 1 | 2 | import ModelIO 3 | 4 | extension MDLVertexDescriptor { 5 | var attributes_: [MDLVertexAttribute] { 6 | return attributes as! [MDLVertexAttribute] 7 | } 8 | 9 | var layouts_: [MDLVertexBufferLayout] { 10 | return layouts as! [MDLVertexBufferLayout] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MBEEffectDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | 2 | import Cocoa 3 | import MetalKit 4 | import SwiftUI 5 | 6 | class ViewController: NSViewController { 7 | var effectRenderer: EffectRenderer? { 8 | didSet { 9 | guard let effectRenderer = effectRenderer else { 10 | mtkView.delegate = nil 11 | return 12 | } 13 | 14 | let effect = effectRenderer.effect 15 | effect.colorPixelFormat = mtkView.colorPixelFormat 16 | effect.depthPixelFormat = mtkView.depthStencilPixelFormat 17 | mtkView.device = effectRenderer.device 18 | mtkView.delegate = effectRenderer 19 | 20 | effectRenderer.mtkView(mtkView, drawableSizeWillChange: mtkView.drawableSize) 21 | 22 | inspectorView.effect = effect 23 | } 24 | } 25 | 26 | @IBOutlet weak var mtkView: MTKView! 27 | @IBOutlet weak var inspectorView: EffectInspectorView! 28 | 29 | // Ordinarily we'd just let MTKView manage its own display link, 30 | // but on macOS, this causes a noticeable pause when a popup or 31 | // context menu closes, since the main run loop is only run in 32 | // the com.apple.hitoolbox.windows.flushmode mode during 33 | // menu animation. So, to avoid such hitches, we run our own 34 | // display link and force synchronous draw submission. 35 | private var displayLink: CVDisplayLink? 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | mtkView.colorPixelFormat = .bgra8Unorm_srgb 41 | mtkView.depthStencilPixelFormat = .depth32Float 42 | mtkView.isPaused = true 43 | mtkView.enableSetNeedsDisplay = false 44 | } 45 | 46 | func displayLinkDidFire() { 47 | // Draw immediately. We don't use the setNeedsDisplay mechanism 48 | // because we're on a background thread and setNeedsDisplay is 49 | // not thread-safe. Using setNeedsDisplay may also cause us to 50 | // wait until the next display update, whereas doing it synchronously 51 | // gets us to the glass as quickly as possible. 52 | // We might be able to get the same effect by merging into a 53 | // dispatch source configured with the appropriate run loop modes, 54 | // but we'll use the bigger hammer for now. 55 | mtkView.draw() 56 | } 57 | 58 | override func viewDidAppear() { 59 | CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) 60 | if let displayLink = displayLink { 61 | CVDisplayLinkSetOutputHandler(displayLink) { [unowned self] 62 | displayLink, callbackTimeStamp, presentationTimeStamp, options, outOptions in 63 | self.displayLinkDidFire() 64 | return kCVReturnSuccess 65 | } 66 | CVDisplayLinkStart(displayLink) 67 | } 68 | } 69 | 70 | override func viewDidDisappear() { 71 | if let displayLink = displayLink { 72 | CVDisplayLinkStop(displayLink) 73 | } 74 | displayLink = nil 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /MBEEffectDemo/crate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metal-by-example/MBEBaseEffect/78de679a30dc56c3f8abe61d5672b30d99a4a7e7/MBEEffectDemo/crate.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MBEBaseEffect: Antique Fixed-Function Features for Modern Apps 2 | 3 | When [GLKit](https://developer.apple.com/documentation/glkit) was introduced with iOS 5 (2011), it included the [`GLKBaseEffect`](https://developer.apple.com/documentation/glkit/glkbaseeffect) class to allow developers to easily configure lights, materials, fog, and other effects in OpenGL and [OpenGL ES](https://developer.apple.com/documentation/opengles). Shaders were a relatively new feature on the platform (having debuted alongside OpenGL ES 2 in iPhone OS 3 in 2009), and many graphics programmers were not yet up-to-speed on shader programming. `GLKBaseEffect` was a bridge that eased the transition away from fixed-function OpenGL by providing a fixed-function style interface to shaders that implemented portions of the OpenGL ES 1.1 graphics pipeline. 4 | 5 | When MetalKit debuted in 2015, it included no such feature. The world had moved well beyond the fixed-function paradigm and fully into the brave new shader-driven world. Developers were now responsible for implementing their own shaders, a task made only more onerous by the rising demand for physically-based rendering. 6 | 7 | The `MBEBaseEffect` class in this repository emulates the fixed-function features of `GLKBaseEffect` atop Metal. Whether such a thing even makes sense depends, in part, on how rose-tinted one's nostalgia glasses are. In any event, it's not meant to be taken too seriously. It makes a lot of assumptions, it's quaintly non-physically based, and it's unlikely to be of use in many apps. 8 | 9 | Instead, this project is a love letter to the fixed-function pipelines of yore. Simplistic and quaint as they seem now, they were instrumental in bringing a lot of graphics developers into the fold. And if the simplicity of this project is instrumental in illuminating the art of shader programming for any newcomers, that's more than I could ask for. 10 | 11 | ![Screenshot](screenshots/1.png) -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metal-by-example/MBEBaseEffect/78de679a30dc56c3f8abe61d5672b30d99a4a7e7/screenshots/1.png --------------------------------------------------------------------------------