├── README.md ├── SpriteKitShaders.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SpriteKitShaders ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── Particle Sprite Atlas.spriteatlas │ │ ├── Contents.json │ │ ├── bokeh.imageset │ │ ├── Contents.json │ │ └── bokeh.png │ │ └── spark.imageset │ │ ├── Contents.json │ │ └── spark.png ├── Base.lproj │ └── MainMenu.xib ├── Images │ ├── grid.png │ ├── leaf.png │ ├── lightburst.png │ ├── poolBackground.png │ ├── spritekitText.png │ └── water.png ├── Info.plist ├── LightSparkle.sks ├── PoolScene.swift ├── Shaders │ ├── algaeBloomShader.fsh │ ├── poolWaterShader.fsh │ └── simpleLiquidShader.fsh ├── SpriteKitShaders.entitlements └── UtilityExtensions.swift └── spriteKitShadersDemo.gif /README.md: -------------------------------------------------------------------------------- 1 | ![Shader Demo](/spriteKitShadersDemo.gif?raw=true "Demo Gif") 2 | 3 | # SpriteKit Shaders & Geometry Demo 4 | 5 | This demo app shows how **SKShader** and **SKWarpable** can be used to create a liquid effect for a simple 2D pool simulation. Particles from **SKEmitterNode** and falling leaves animated with SKAction are thrown in for fun. The purpose of this code is to provide an introduction to how fragment shaders can be easily used with SpriteKit. 6 | 7 | ## Related Article 8 | 9 | Check out the short [YouTube video](https://www.youtube.com/watch?v=4VO97nosweA) or the related blog post here: [http://sound-of-silence.com/?article=20180806](http://sound-of-silence.com/?article=201808016) 10 | 11 | ## Author 12 | 13 | **Matt Reagan** - Website: [http://sound-of-silence.com/](http://sound-of-silence.com/) - Twitter: [@hmblebee](https://twitter.com/hmblebee) 14 | 15 | ## License 16 | 17 | Source code and related resources are Copyright (C) Matthew Reagan 2016. The source code is released under the [MIT License](https://opensource.org/licenses/MIT). 18 | -------------------------------------------------------------------------------- /SpriteKitShaders.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C973A423211230FC0039E57A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C973A422211230FC0039E57A /* AppDelegate.swift */; }; 11 | C973A425211230FC0039E57A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C973A424211230FC0039E57A /* Assets.xcassets */; }; 12 | C973A428211230FC0039E57A /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = C973A426211230FC0039E57A /* MainMenu.xib */; }; 13 | C973A432211231160039E57A /* SpriteKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C973A431211231160039E57A /* SpriteKit.framework */; }; 14 | C973A434211232ED0039E57A /* water.png in Resources */ = {isa = PBXBuildFile; fileRef = C973A433211232ED0039E57A /* water.png */; }; 15 | C973A436211245D60039E57A /* poolWaterShader.fsh in Resources */ = {isa = PBXBuildFile; fileRef = C973A435211245D60039E57A /* poolWaterShader.fsh */; }; 16 | C973A43C2113F4FB0039E57A /* grid.png in Resources */ = {isa = PBXBuildFile; fileRef = C973A43B2113F4FB0039E57A /* grid.png */; }; 17 | C973A43E211400220039E57A /* poolBackground.png in Resources */ = {isa = PBXBuildFile; fileRef = C973A43D211400220039E57A /* poolBackground.png */; }; 18 | C973A440211409020039E57A /* lightburst.png in Resources */ = {isa = PBXBuildFile; fileRef = C973A43F211409020039E57A /* lightburst.png */; }; 19 | C973A4422114093E0039E57A /* LightSparkle.sks in Resources */ = {isa = PBXBuildFile; fileRef = C973A4412114093E0039E57A /* LightSparkle.sks */; }; 20 | C973A44421140F4F0039E57A /* spritekitText.png in Resources */ = {isa = PBXBuildFile; fileRef = C973A44321140F4F0039E57A /* spritekitText.png */; }; 21 | C973A44621140FCA0039E57A /* simpleLiquidShader.fsh in Resources */ = {isa = PBXBuildFile; fileRef = C973A44521140FCA0039E57A /* simpleLiquidShader.fsh */; }; 22 | C9A8A59121151FD800B2DFFB /* leaf.png in Resources */ = {isa = PBXBuildFile; fileRef = C9A8A59021151FD800B2DFFB /* leaf.png */; }; 23 | C9A8A5952115360400B2DFFB /* PoolScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A8A5942115360400B2DFFB /* PoolScene.swift */; }; 24 | C9A8A5972115497300B2DFFB /* UtilityExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A8A5962115497300B2DFFB /* UtilityExtensions.swift */; }; 25 | C9A8A59921162D3F00B2DFFB /* algaeBloomShader.fsh in Resources */ = {isa = PBXBuildFile; fileRef = C9A8A59821162D3F00B2DFFB /* algaeBloomShader.fsh */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | C973A41F211230FC0039E57A /* SpriteKitShaders.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SpriteKitShaders.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | C973A422211230FC0039E57A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 31 | C973A424211230FC0039E57A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | C973A427211230FC0039E57A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33 | C973A429211230FC0039E57A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | C973A42A211230FC0039E57A /* SpriteKitShaders.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SpriteKitShaders.entitlements; sourceTree = ""; }; 35 | C973A431211231160039E57A /* SpriteKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SpriteKit.framework; path = System/Library/Frameworks/SpriteKit.framework; sourceTree = SDKROOT; }; 36 | C973A433211232ED0039E57A /* water.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = water.png; sourceTree = ""; }; 37 | C973A435211245D60039E57A /* poolWaterShader.fsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = poolWaterShader.fsh; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.glsl; }; 38 | C973A43B2113F4FB0039E57A /* grid.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = grid.png; sourceTree = ""; }; 39 | C973A43D211400220039E57A /* poolBackground.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = poolBackground.png; sourceTree = ""; }; 40 | C973A43F211409020039E57A /* lightburst.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = lightburst.png; sourceTree = ""; }; 41 | C973A4412114093E0039E57A /* LightSparkle.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = LightSparkle.sks; sourceTree = ""; }; 42 | C973A44321140F4F0039E57A /* spritekitText.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = spritekitText.png; sourceTree = ""; }; 43 | C973A44521140FCA0039E57A /* simpleLiquidShader.fsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = simpleLiquidShader.fsh; sourceTree = ""; }; 44 | C9A8A59021151FD800B2DFFB /* leaf.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = leaf.png; sourceTree = ""; }; 45 | C9A8A5942115360400B2DFFB /* PoolScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoolScene.swift; sourceTree = ""; }; 46 | C9A8A5962115497300B2DFFB /* UtilityExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityExtensions.swift; sourceTree = ""; }; 47 | C9A8A59821162D3F00B2DFFB /* algaeBloomShader.fsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = algaeBloomShader.fsh; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | C973A41C211230FC0039E57A /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | C973A432211231160039E57A /* SpriteKit.framework in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | C973A416211230FC0039E57A = { 63 | isa = PBXGroup; 64 | children = ( 65 | C973A421211230FC0039E57A /* SpriteKitShaders */, 66 | C973A420211230FC0039E57A /* Products */, 67 | C973A430211231160039E57A /* Frameworks */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | C973A420211230FC0039E57A /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | C973A41F211230FC0039E57A /* SpriteKitShaders.app */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | C973A421211230FC0039E57A /* SpriteKitShaders */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | C9A8A592211535D000B2DFFB /* Images */, 83 | C9A8A593211535E200B2DFFB /* Shaders */, 84 | C973A422211230FC0039E57A /* AppDelegate.swift */, 85 | C9A8A5942115360400B2DFFB /* PoolScene.swift */, 86 | C9A8A5962115497300B2DFFB /* UtilityExtensions.swift */, 87 | C973A4412114093E0039E57A /* LightSparkle.sks */, 88 | C973A424211230FC0039E57A /* Assets.xcassets */, 89 | C973A426211230FC0039E57A /* MainMenu.xib */, 90 | C973A429211230FC0039E57A /* Info.plist */, 91 | C973A42A211230FC0039E57A /* SpriteKitShaders.entitlements */, 92 | ); 93 | path = SpriteKitShaders; 94 | sourceTree = ""; 95 | }; 96 | C973A430211231160039E57A /* Frameworks */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | C973A431211231160039E57A /* SpriteKit.framework */, 100 | ); 101 | name = Frameworks; 102 | sourceTree = ""; 103 | }; 104 | C9A8A592211535D000B2DFFB /* Images */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | C973A44321140F4F0039E57A /* spritekitText.png */, 108 | C973A43B2113F4FB0039E57A /* grid.png */, 109 | C973A43D211400220039E57A /* poolBackground.png */, 110 | C973A43F211409020039E57A /* lightburst.png */, 111 | C973A433211232ED0039E57A /* water.png */, 112 | C9A8A59021151FD800B2DFFB /* leaf.png */, 113 | ); 114 | path = Images; 115 | sourceTree = ""; 116 | }; 117 | C9A8A593211535E200B2DFFB /* Shaders */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | C9A8A59821162D3F00B2DFFB /* algaeBloomShader.fsh */, 121 | C973A44521140FCA0039E57A /* simpleLiquidShader.fsh */, 122 | C973A435211245D60039E57A /* poolWaterShader.fsh */, 123 | ); 124 | path = Shaders; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | C973A41E211230FC0039E57A /* SpriteKitShaders */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = C973A42D211230FC0039E57A /* Build configuration list for PBXNativeTarget "SpriteKitShaders" */; 133 | buildPhases = ( 134 | C973A41B211230FC0039E57A /* Sources */, 135 | C973A41C211230FC0039E57A /* Frameworks */, 136 | C973A41D211230FC0039E57A /* Resources */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = SpriteKitShaders; 143 | productName = SpriteKitShaders; 144 | productReference = C973A41F211230FC0039E57A /* SpriteKitShaders.app */; 145 | productType = "com.apple.product-type.application"; 146 | }; 147 | /* End PBXNativeTarget section */ 148 | 149 | /* Begin PBXProject section */ 150 | C973A417211230FC0039E57A /* Project object */ = { 151 | isa = PBXProject; 152 | attributes = { 153 | LastSwiftUpdateCheck = 0920; 154 | LastUpgradeCheck = 0920; 155 | ORGANIZATIONNAME = "Matt Reagan"; 156 | TargetAttributes = { 157 | C973A41E211230FC0039E57A = { 158 | CreatedOnToolsVersion = 9.2; 159 | ProvisioningStyle = Automatic; 160 | }; 161 | }; 162 | }; 163 | buildConfigurationList = C973A41A211230FC0039E57A /* Build configuration list for PBXProject "SpriteKitShaders" */; 164 | compatibilityVersion = "Xcode 8.0"; 165 | developmentRegion = en; 166 | hasScannedForEncodings = 0; 167 | knownRegions = ( 168 | en, 169 | Base, 170 | ); 171 | mainGroup = C973A416211230FC0039E57A; 172 | productRefGroup = C973A420211230FC0039E57A /* Products */; 173 | projectDirPath = ""; 174 | projectRoot = ""; 175 | targets = ( 176 | C973A41E211230FC0039E57A /* SpriteKitShaders */, 177 | ); 178 | }; 179 | /* End PBXProject section */ 180 | 181 | /* Begin PBXResourcesBuildPhase section */ 182 | C973A41D211230FC0039E57A /* Resources */ = { 183 | isa = PBXResourcesBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | C973A434211232ED0039E57A /* water.png in Resources */, 187 | C973A440211409020039E57A /* lightburst.png in Resources */, 188 | C973A425211230FC0039E57A /* Assets.xcassets in Resources */, 189 | C973A428211230FC0039E57A /* MainMenu.xib in Resources */, 190 | C973A43E211400220039E57A /* poolBackground.png in Resources */, 191 | C9A8A59921162D3F00B2DFFB /* algaeBloomShader.fsh in Resources */, 192 | C973A44421140F4F0039E57A /* spritekitText.png in Resources */, 193 | C973A436211245D60039E57A /* poolWaterShader.fsh in Resources */, 194 | C973A4422114093E0039E57A /* LightSparkle.sks in Resources */, 195 | C973A43C2113F4FB0039E57A /* grid.png in Resources */, 196 | C973A44621140FCA0039E57A /* simpleLiquidShader.fsh in Resources */, 197 | C9A8A59121151FD800B2DFFB /* leaf.png in Resources */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | /* End PBXResourcesBuildPhase section */ 202 | 203 | /* Begin PBXSourcesBuildPhase section */ 204 | C973A41B211230FC0039E57A /* Sources */ = { 205 | isa = PBXSourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | C9A8A5952115360400B2DFFB /* PoolScene.swift in Sources */, 209 | C973A423211230FC0039E57A /* AppDelegate.swift in Sources */, 210 | C9A8A5972115497300B2DFFB /* UtilityExtensions.swift in Sources */, 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | /* End PBXSourcesBuildPhase section */ 215 | 216 | /* Begin PBXVariantGroup section */ 217 | C973A426211230FC0039E57A /* MainMenu.xib */ = { 218 | isa = PBXVariantGroup; 219 | children = ( 220 | C973A427211230FC0039E57A /* Base */, 221 | ); 222 | name = MainMenu.xib; 223 | sourceTree = ""; 224 | }; 225 | /* End PBXVariantGroup section */ 226 | 227 | /* Begin XCBuildConfiguration section */ 228 | C973A42B211230FC0039E57A /* Debug */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | ALWAYS_SEARCH_USER_PATHS = NO; 232 | CLANG_ANALYZER_NONNULL = YES; 233 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 234 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 235 | CLANG_CXX_LIBRARY = "libc++"; 236 | CLANG_ENABLE_MODULES = YES; 237 | CLANG_ENABLE_OBJC_ARC = YES; 238 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_COMMA = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 244 | CLANG_WARN_EMPTY_BODY = YES; 245 | CLANG_WARN_ENUM_CONVERSION = YES; 246 | CLANG_WARN_INFINITE_RECURSION = YES; 247 | CLANG_WARN_INT_CONVERSION = YES; 248 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 249 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 251 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 252 | CLANG_WARN_STRICT_PROTOTYPES = YES; 253 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 254 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 255 | CLANG_WARN_UNREACHABLE_CODE = YES; 256 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 257 | CODE_SIGN_IDENTITY = "-"; 258 | COPY_PHASE_STRIP = NO; 259 | DEBUG_INFORMATION_FORMAT = dwarf; 260 | ENABLE_STRICT_OBJC_MSGSEND = YES; 261 | ENABLE_TESTABILITY = YES; 262 | GCC_C_LANGUAGE_STANDARD = gnu11; 263 | GCC_DYNAMIC_NO_PIC = NO; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_OPTIMIZATION_LEVEL = 0; 266 | GCC_PREPROCESSOR_DEFINITIONS = ( 267 | "DEBUG=1", 268 | "$(inherited)", 269 | ); 270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 272 | GCC_WARN_UNDECLARED_SELECTOR = YES; 273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 274 | GCC_WARN_UNUSED_FUNCTION = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | MACOSX_DEPLOYMENT_TARGET = 10.13; 277 | MTL_ENABLE_DEBUG_INFO = YES; 278 | ONLY_ACTIVE_ARCH = YES; 279 | SDKROOT = macosx; 280 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 281 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 282 | }; 283 | name = Debug; 284 | }; 285 | C973A42C211230FC0039E57A /* Release */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ALWAYS_SEARCH_USER_PATHS = NO; 289 | CLANG_ANALYZER_NONNULL = YES; 290 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 291 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 292 | CLANG_CXX_LIBRARY = "libc++"; 293 | CLANG_ENABLE_MODULES = YES; 294 | CLANG_ENABLE_OBJC_ARC = YES; 295 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 296 | CLANG_WARN_BOOL_CONVERSION = YES; 297 | CLANG_WARN_COMMA = YES; 298 | CLANG_WARN_CONSTANT_CONVERSION = YES; 299 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 300 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 301 | CLANG_WARN_EMPTY_BODY = YES; 302 | CLANG_WARN_ENUM_CONVERSION = YES; 303 | CLANG_WARN_INFINITE_RECURSION = YES; 304 | CLANG_WARN_INT_CONVERSION = YES; 305 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 306 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 307 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 308 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 309 | CLANG_WARN_STRICT_PROTOTYPES = YES; 310 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 311 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 312 | CLANG_WARN_UNREACHABLE_CODE = YES; 313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 314 | CODE_SIGN_IDENTITY = "-"; 315 | COPY_PHASE_STRIP = NO; 316 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 317 | ENABLE_NS_ASSERTIONS = NO; 318 | ENABLE_STRICT_OBJC_MSGSEND = YES; 319 | GCC_C_LANGUAGE_STANDARD = gnu11; 320 | GCC_NO_COMMON_BLOCKS = YES; 321 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 322 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 323 | GCC_WARN_UNDECLARED_SELECTOR = YES; 324 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 325 | GCC_WARN_UNUSED_FUNCTION = YES; 326 | GCC_WARN_UNUSED_VARIABLE = YES; 327 | MACOSX_DEPLOYMENT_TARGET = 10.13; 328 | MTL_ENABLE_DEBUG_INFO = NO; 329 | SDKROOT = macosx; 330 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 331 | }; 332 | name = Release; 333 | }; 334 | C973A42E211230FC0039E57A /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 338 | CODE_SIGN_ENTITLEMENTS = SpriteKitShaders/SpriteKitShaders.entitlements; 339 | CODE_SIGN_STYLE = Automatic; 340 | COMBINE_HIDPI_IMAGES = YES; 341 | INFOPLIST_FILE = SpriteKitShaders/Info.plist; 342 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 343 | PRODUCT_BUNDLE_IDENTIFIER = "Matt-Reagan.SpriteKitShaders"; 344 | PRODUCT_NAME = "$(TARGET_NAME)"; 345 | SWIFT_VERSION = 4.0; 346 | }; 347 | name = Debug; 348 | }; 349 | C973A42F211230FC0039E57A /* Release */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 353 | CODE_SIGN_ENTITLEMENTS = SpriteKitShaders/SpriteKitShaders.entitlements; 354 | CODE_SIGN_STYLE = Automatic; 355 | COMBINE_HIDPI_IMAGES = YES; 356 | INFOPLIST_FILE = SpriteKitShaders/Info.plist; 357 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 358 | PRODUCT_BUNDLE_IDENTIFIER = "Matt-Reagan.SpriteKitShaders"; 359 | PRODUCT_NAME = "$(TARGET_NAME)"; 360 | SWIFT_VERSION = 4.0; 361 | }; 362 | name = Release; 363 | }; 364 | /* End XCBuildConfiguration section */ 365 | 366 | /* Begin XCConfigurationList section */ 367 | C973A41A211230FC0039E57A /* Build configuration list for PBXProject "SpriteKitShaders" */ = { 368 | isa = XCConfigurationList; 369 | buildConfigurations = ( 370 | C973A42B211230FC0039E57A /* Debug */, 371 | C973A42C211230FC0039E57A /* Release */, 372 | ); 373 | defaultConfigurationIsVisible = 0; 374 | defaultConfigurationName = Release; 375 | }; 376 | C973A42D211230FC0039E57A /* Build configuration list for PBXNativeTarget "SpriteKitShaders" */ = { 377 | isa = XCConfigurationList; 378 | buildConfigurations = ( 379 | C973A42E211230FC0039E57A /* Debug */, 380 | C973A42F211230FC0039E57A /* Release */, 381 | ); 382 | defaultConfigurationIsVisible = 0; 383 | defaultConfigurationName = Release; 384 | }; 385 | /* End XCConfigurationList section */ 386 | }; 387 | rootObject = C973A417211230FC0039E57A /* Project object */; 388 | } 389 | -------------------------------------------------------------------------------- /SpriteKitShaders.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SpriteKitShaders/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SpriteKitShaders 4 | // 5 | // Created by Matthew Reagan on 8/1/18. 6 | // Copyright © 2018 Matt Reagan. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 9 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 10 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 11 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 17 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 18 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Cocoa 21 | import SpriteKit 22 | 23 | @NSApplicationMain 24 | class AppDelegate: NSObject, NSApplicationDelegate { 25 | 26 | @IBOutlet weak var window: NSWindow! 27 | var skView: SKView! 28 | 29 | func applicationDidFinishLaunching(_ aNotification: Notification) { 30 | Random.seed() 31 | DispatchQueue.main.async { 32 | self.setUpView() 33 | } 34 | } 35 | 36 | func setUpView() { 37 | guard let contentView = window.contentView else { return } 38 | 39 | let scene = PoolScene.init(size: contentView.bounds.size) 40 | scene.anchorPoint = CGPoint(x: 0.5, y: 0.5) 41 | scene.backgroundColor = SKColor.black 42 | scene.scaleMode = .aspectFit 43 | 44 | skView = SKView.init(frame: contentView.bounds) 45 | skView.showsFPS = true 46 | skView.showsNodeCount = true 47 | skView.autoresizingMask = [.width, .height] 48 | 49 | contentView.addSubview(skView) 50 | skView.presentScene(scene) 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /SpriteKitShaders/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /SpriteKitShaders/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SpriteKitShaders/Assets.xcassets/Particle Sprite Atlas.spriteatlas/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SpriteKitShaders/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bokeh.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SpriteKitShaders/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/bokeh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/SpriteKitShaders/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/bokeh.png -------------------------------------------------------------------------------- /SpriteKitShaders/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "spark.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SpriteKitShaders/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/SpriteKitShaders/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/spark.png -------------------------------------------------------------------------------- /SpriteKitShaders/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 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 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | Default 539 | 540 | 541 | 542 | 543 | 544 | 545 | Left to Right 546 | 547 | 548 | 549 | 550 | 551 | 552 | Right to Left 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | Default 564 | 565 | 566 | 567 | 568 | 569 | 570 | Left to Right 571 | 572 | 573 | 574 | 575 | 576 | 577 | Right to Left 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | -------------------------------------------------------------------------------- /SpriteKitShaders/Images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/SpriteKitShaders/Images/grid.png -------------------------------------------------------------------------------- /SpriteKitShaders/Images/leaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/SpriteKitShaders/Images/leaf.png -------------------------------------------------------------------------------- /SpriteKitShaders/Images/lightburst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/SpriteKitShaders/Images/lightburst.png -------------------------------------------------------------------------------- /SpriteKitShaders/Images/poolBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/SpriteKitShaders/Images/poolBackground.png -------------------------------------------------------------------------------- /SpriteKitShaders/Images/spritekitText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/SpriteKitShaders/Images/spritekitText.png -------------------------------------------------------------------------------- /SpriteKitShaders/Images/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/SpriteKitShaders/Images/water.png -------------------------------------------------------------------------------- /SpriteKitShaders/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2018 Matt Reagan. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | PrefersOpenGL 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /SpriteKitShaders/LightSparkle.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/SpriteKitShaders/LightSparkle.sks -------------------------------------------------------------------------------- /SpriteKitShaders/PoolScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PoolScene.swift 3 | // SpriteKitShaders 4 | // 5 | // Created by Matthew Reagan on 8/3/18. 6 | // Copyright © 2018 Matt Reagan. All rights reserved. 7 | // 8 | // More info: http://sound-of-silence.com 9 | // 10 | // For LICENSE information please see AppDelegate.swift. 11 | 12 | import SpriteKit 13 | 14 | class PoolScene: SKScene { 15 | 16 | // MARK: - Properties 17 | 18 | let waterNode = SKSpriteNode(imageNamed: "water") 19 | let emitterNode = SKEmitterNode(fileNamed: "LightSparkle")! 20 | var waterWarpPositions: [float2] = [] 21 | let waterWarpGridSize = 12 22 | 23 | // MARK: - Init 24 | 25 | override init(size: CGSize) { 26 | super.init(size: size) 27 | configureNodes() 28 | } 29 | 30 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 31 | 32 | // MARK: - Node Configuration 33 | 34 | func configureNodes() { 35 | waterNode.position = CGPoint(x: 0.0, y: 12.0); 36 | waterNode.shader = SKShader(fileNamed: "poolWaterShader") 37 | addChild(waterNode) 38 | 39 | emitterNode.position = waterNode.position 40 | addChild(emitterNode) 41 | 42 | let backgroundNode = SKSpriteNode(imageNamed: "poolBackground") 43 | backgroundNode.size = size 44 | backgroundNode.zPosition = -10.0 45 | addChild(backgroundNode) 46 | 47 | let spriteKitTextNode = SKSpriteNode(imageNamed: "spritekitText") 48 | spriteKitTextNode.zPosition = 50.0 49 | spriteKitTextNode.position = CGPoint(x: 0.0, y: 250.0) 50 | spriteKitTextNode.shader = SKShader(fileNamed: "simpleLiquidShader") 51 | addChild(spriteKitTextNode) 52 | 53 | waterWarpPositions = geometryGridPositions(byWarping: false) 54 | waterNode.warpGeometry = SKWarpGeometryGrid(columns: waterWarpGridSize, rows: waterWarpGridSize) 55 | randomizeWaterGeometry() 56 | } 57 | 58 | // MARK: - Animation Functions 59 | 60 | func geometryGridPositions(byWarping: Bool) -> [float2] { 61 | var points = [float2]() 62 | for y in 0...waterWarpGridSize { 63 | for x in 0...waterWarpGridSize { 64 | let shouldWarp = byWarping && x > 0 && y > 0 && x < waterWarpGridSize && y < waterWarpGridSize 65 | let warpAmount: Float = 1.0 / Float(waterWarpGridSize + 1) / 4.0 66 | func randomizedWarpAmount() -> Float { return (shouldWarp ? warpAmount * Random.between0And1() - (warpAmount / 2.0) : 0.0) } 67 | let nx = Float(x) * (1.0 / Float(waterWarpGridSize)) + randomizedWarpAmount() 68 | let ny = Float(y) * (1.0 / Float(waterWarpGridSize)) + randomizedWarpAmount() 69 | points.append(float2(nx,ny)) 70 | } 71 | } 72 | return points 73 | } 74 | 75 | func randomizeWaterGeometry() { 76 | let sourcePositions = waterWarpPositions 77 | waterWarpPositions = geometryGridPositions(byWarping: true) 78 | let warpGeometryGrid = SKWarpGeometryGrid.init(columns: waterWarpGridSize, rows: waterWarpGridSize, sourcePositions: sourcePositions, destinationPositions: waterWarpPositions) 79 | let warpDuration = TimeInterval(0.36) 80 | if let warpAction = SKAction.warp(to: warpGeometryGrid, duration: warpDuration) { 81 | waterNode.run(.sequence([ 82 | warpAction.byEasingInOut(), 83 | .run({[weak self] in self?.randomizeWaterGeometry()}) 84 | ])) 85 | } 86 | } 87 | 88 | // MARK: - Event Handlers 89 | 90 | override func mouseDown(with event: NSEvent) { 91 | super.mouseDown(with: event) 92 | 93 | let pointInPool = event.location(in: waterNode) 94 | 95 | if pointInPool.distance(to: .zero) < 160.0 { 96 | dropLeaf(at: pointInPool) 97 | } 98 | } 99 | 100 | // MARK: - Leaf Animation Utilities 101 | 102 | func dropLeaf(at pointInPool: CGPoint) { 103 | let leaf = SKSpriteNode(imageNamed: "leaf") 104 | leaf.zPosition = 5.0 105 | leaf.position = pointInPool 106 | leaf.alpha = 0.4 107 | leaf.zRotation = CGFloat(Random.between0And1() * (Float.pi * 2.0)) 108 | waterNode.addChild(leaf) 109 | 110 | let blueFadeColor = SKColor.init(red: 0.0, green: 0.0, blue: 0.7, alpha: 1.0) 111 | 112 | let randomRotationAngle = CGFloat(Random.between0And1() * (Float.pi * 2.0) - Float.pi) 113 | let randomDriftAngle = CGFloat(Random.between0And1() * (Float.pi * 2.0) - Float.pi) 114 | let randomDriftDistance = CGFloat(Random.between0And1() * 30.0) + 20.0 115 | let driftDestination = CGPoint(x: randomDriftDistance * cos(randomDriftAngle) * CGFloat(Random.between0And1()) + pointInPool.x, 116 | y: randomDriftDistance * sin(randomDriftAngle) * CGFloat(Random.between0And1()) + pointInPool.y) 117 | 118 | let totalAnimationDuration = TimeInterval(2.2) 119 | let leafFallDuration = TimeInterval(0.82) 120 | let leafDieDuration = TimeInterval(0.85) 121 | leaf.run(.sequence([ 122 | .group([ 123 | SKAction.scale(to: 0.5, duration: leafFallDuration).byEasingIn(), 124 | .fadeIn(withDuration: leafFallDuration)]), 125 | .run({ [weak self] in self?.createRipple(at: pointInPool) }), 126 | .group([ 127 | .rotate(byAngle: randomRotationAngle, duration: totalAnimationDuration), 128 | .move(to: driftDestination, duration: totalAnimationDuration), 129 | .colorize(with: blueFadeColor, colorBlendFactor: 0.4, duration: totalAnimationDuration)]), 130 | .group([ 131 | .scale(to: 0.25, duration: leafDieDuration), 132 | .colorize(with: blueFadeColor, colorBlendFactor: 0.6, duration: leafDieDuration), 133 | .fadeAlpha(to: 0.14, duration: leafDieDuration)]), 134 | .fadeOut(withDuration: 5.0), 135 | .removeFromParent(), 136 | ])) 137 | } 138 | 139 | func createRipple(at pointInPool: CGPoint) { 140 | let numRipples = 4 141 | for i in 0.. 0.5) { 57 | gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); 58 | } else { 59 | 60 | // Calculate the 'drift' (horizontal scrolling) based on u_time 61 | float driftAmount = glMod(int(u_time * 80), driftSteps); 62 | float adj = driftAmount / driftSteps; 63 | 64 | // Here the wave form is applied by offsetting the actual pixel 65 | // color we'll return based on cos/sin functions. We also apply 66 | // the 'drift' amount to the x coordinate to provide a slow 67 | // horizontal scrolling effect. Note that fract() is called on the 68 | // resulting coord.x as part of the calculation which allows us 69 | // to wrap the coordinate around if we exceed 1.0. 70 | coord.x += cos((coord.x + speed) * frequency) * intensity + adj; 71 | coord.x = fract(coord.x); 72 | coord.y += sin((coord.y + speed) * frequency) * intensity; 73 | 74 | // Get the color at the new coordinate we've calculated 75 | vec4 newColor = texture2D(u_texture, coord); 76 | 77 | // Finally, we apply a darkening effect here to the edges of the pool. 78 | // Set up a few values for the effect first 79 | float edgeShadowDistance = 0.16; 80 | float edgeShadowThreshold = (0.5 - edgeShadowDistance); 81 | float edgeShadowRippleIntensity = 0.16; 82 | float edgeShadowDarkeningValue = 50.0; 83 | 84 | // Check if this pixel is within the edge shadow threshold to be darkened 85 | if (distanceFromCenter > edgeShadowThreshold) { 86 | 87 | float darkenAmount = (distanceFromCenter - edgeShadowThreshold) / edgeShadowDistance; 88 | darkenAmount -= edgeShadowRippleIntensity; 89 | 90 | // Here we use cos() to give our edge darkening a nice moving wave shape 91 | // based on both the X,Y coordinates as well as the progressing simulation 92 | // time. (Try removing coord.x and coord.y from the calculation to see the 93 | // edge pulse instead.) 94 | float edgeCosineWaveValue = cos((coord.x + coord.y + glMod(int(u_time * 30), 2000)) * edgeShadowDarkeningValue); 95 | 96 | // Some final adjustments here to further adjust the output color 97 | darkenAmount += edgeCosineWaveValue * edgeShadowRippleIntensity; 98 | darkenAmount /= 2.3; 99 | darkenAmount = max(darkenAmount, 0.0); 100 | 101 | // For an additional subtlety we further adjust the edges of the rendered 102 | // texure by fading them slightly (reducing the alpha value) 103 | float edgeFadeDistance = edgeShadowDistance / 3.0; 104 | float edgeFadeThreshold = (0.5 - edgeFadeDistance); 105 | if (distanceFromCenter > edgeFadeThreshold) { 106 | float edgeFadeAmount = 1.0 - ((distanceFromCenter - edgeFadeThreshold) / edgeFadeDistance / 2.6); 107 | newColor.a = edgeFadeAmount; 108 | } 109 | 110 | // Here the final edge darkening adjustment is subtracted from our RGB values 111 | // (and the alpha value is multiplied). Try adding rather than subtracting for an edge glow 112 | gl_FragColor = vec4((newColor.rgb - darkenAmount) * newColor.a, newColor.a); 113 | } else { 114 | // If we're not applying any edge effects we can finish up immediately. 115 | gl_FragColor = newColor; 116 | } 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /SpriteKitShaders/Shaders/simpleLiquidShader.fsh: -------------------------------------------------------------------------------- 1 | // 2 | // A simple 'liquid' shader which creates a ripple effect 3 | // by offsetting the actual pixel color we return with a nearby 4 | // sample, which is calculated with a sine wave. 5 | // For LICENSE information please see AppDelegate.swift. 6 | // 7 | 8 | void main() { 9 | 10 | // Set up some animation parameters for the waveform 11 | 12 | float speed = u_time * 0.35; 13 | float frequency = 14.0; 14 | float intensity = 0.006; 15 | 16 | // Get the coordinate for the target pixel 17 | vec2 coord = v_tex_coord; 18 | 19 | // Modify (offset slightly) using a sine wave 20 | coord.x += cos((coord.x + speed) * frequency) * intensity; 21 | coord.y += sin((coord.y + speed) * frequency) * intensity; 22 | 23 | // Rather than the original pixel color, using the offset target pixel 24 | vec4 targetPixelColor = texture2D(u_texture, coord); 25 | 26 | // Finish up by setting the actual color on gl_FragColor 27 | gl_FragColor = targetPixelColor; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /SpriteKitShaders/SpriteKitShaders.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SpriteKitShaders/UtilityExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UtilityExtensions.swift 3 | // SpriteKitShaders 4 | // 5 | // Created by Matthew Reagan on 8/3/18. 6 | // Copyright © 2018 Matt Reagan. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | extension CGPoint { 12 | func distance(to otherPoint: CGPoint) -> CGFloat { 13 | let xDelta = x - otherPoint.x 14 | let yDelta = y - otherPoint.y 15 | return ((xDelta * xDelta) + (yDelta * yDelta)).squareRoot() 16 | } 17 | } 18 | 19 | extension SKAction { 20 | func byEasingIn() -> SKAction { 21 | self.timingMode = .easeIn 22 | return self 23 | } 24 | func byEasingInOut() -> SKAction { 25 | self.timingMode = .easeInEaseOut 26 | return self 27 | } 28 | func byEasingOut() -> SKAction { 29 | self.timingMode = .easeOut 30 | return self 31 | } 32 | } 33 | 34 | struct Random { 35 | static func seed() { 36 | srand48(Int(time(nil))) 37 | } 38 | static func between0And1() -> Float { 39 | return Float(drand48()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spriteKitShadersDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewreagan/SpriteKitShaders/6e08557580e4da9f215159a8880cd1e1fbeab357/spriteKitShadersDemo.gif --------------------------------------------------------------------------------