├── .gitignore ├── GLPanoViewer.jpg ├── HSGLPanoViewer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── HSGLPanoViewer.xcscheme ├── HSGLPanoViewer ├── AppDelegate.swift ├── Array+MemorySize.swift ├── GLKitPano │ └── GLKitPanoViewController.swift ├── GLSLPano │ ├── GLSLPanoView.swift │ ├── GLSLPanoViewController.swift │ ├── GLViewable.swift │ └── Shader │ │ ├── Pano.fsh │ │ └── Pano.vsh ├── Info.plist ├── PanoViewType.swift ├── Resources │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ └── PanoImages │ │ ├── pano-2048-1024.jpg │ │ └── pano-4096-2048.jpg ├── SceneDelegate.swift ├── ViewController.swift └── ViewTransform.swift ├── LICENSE └── README.md /.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 | -------------------------------------------------------------------------------- /GLPanoViewer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyphs21/HSGLPanoViewer/bf85b8613718ba1e0c013493fb52d188750ac2c3/GLPanoViewer.jpg -------------------------------------------------------------------------------- /HSGLPanoViewer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C026225F250C72190035DF13 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C026225E250C72190035DF13 /* AppDelegate.swift */; }; 11 | C0262261250C72190035DF13 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0262260250C72190035DF13 /* SceneDelegate.swift */; }; 12 | C0262263250C72190035DF13 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0262262250C72190035DF13 /* ViewController.swift */; }; 13 | C0262266250C72190035DF13 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0262264250C72190035DF13 /* Main.storyboard */; }; 14 | C0262268250C721D0035DF13 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0262267250C721D0035DF13 /* Assets.xcassets */; }; 15 | C026226B250C721D0035DF13 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0262269250C721D0035DF13 /* LaunchScreen.storyboard */; }; 16 | C0262277250C7DCB0035DF13 /* GLKitPanoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0262276250C7DCB0035DF13 /* GLKitPanoViewController.swift */; }; 17 | C0262279250C7E260035DF13 /* PanoViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0262278250C7E260035DF13 /* PanoViewType.swift */; }; 18 | C026227B250C7E5A0035DF13 /* ViewTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = C026227A250C7E5A0035DF13 /* ViewTransform.swift */; }; 19 | C026227D250C7E960035DF13 /* Array+MemorySize.swift in Sources */ = {isa = PBXBuildFile; fileRef = C026227C250C7E960035DF13 /* Array+MemorySize.swift */; }; 20 | C0262286250C87F20035DF13 /* pano-2048-1024.jpg in Resources */ = {isa = PBXBuildFile; fileRef = C0262284250C87F20035DF13 /* pano-2048-1024.jpg */; }; 21 | C0262287250C87F20035DF13 /* pano-4096-2048.jpg in Resources */ = {isa = PBXBuildFile; fileRef = C0262285250C87F20035DF13 /* pano-4096-2048.jpg */; }; 22 | C0262289250CB8980035DF13 /* GLSLPanoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0262288250CB8980035DF13 /* GLSLPanoViewController.swift */; }; 23 | C026228B250CB8CB0035DF13 /* GLSLPanoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C026228A250CB8CB0035DF13 /* GLSLPanoView.swift */; }; 24 | C026228D250CB90C0035DF13 /* GLViewable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C026228C250CB90C0035DF13 /* GLViewable.swift */; }; 25 | C0262290250CB9840035DF13 /* Pano.fsh in Resources */ = {isa = PBXBuildFile; fileRef = C026228F250CB9830035DF13 /* Pano.fsh */; }; 26 | C0262292250CB9890035DF13 /* Pano.vsh in Resources */ = {isa = PBXBuildFile; fileRef = C0262291250CB9890035DF13 /* Pano.vsh */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | C026225B250C72190035DF13 /* HSGLPanoViewer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HSGLPanoViewer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | C026225E250C72190035DF13 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 32 | C0262260250C72190035DF13 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 33 | C0262262250C72190035DF13 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 34 | C0262265250C72190035DF13 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | C0262267250C721D0035DF13 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | C026226A250C721D0035DF13 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | C026226C250C721D0035DF13 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | C0262276250C7DCB0035DF13 /* GLKitPanoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GLKitPanoViewController.swift; sourceTree = ""; }; 39 | C0262278250C7E260035DF13 /* PanoViewType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanoViewType.swift; sourceTree = ""; }; 40 | C026227A250C7E5A0035DF13 /* ViewTransform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTransform.swift; sourceTree = ""; }; 41 | C026227C250C7E960035DF13 /* Array+MemorySize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+MemorySize.swift"; sourceTree = ""; }; 42 | C0262284250C87F20035DF13 /* pano-2048-1024.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "pano-2048-1024.jpg"; sourceTree = ""; }; 43 | C0262285250C87F20035DF13 /* pano-4096-2048.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "pano-4096-2048.jpg"; sourceTree = ""; }; 44 | C0262288250CB8980035DF13 /* GLSLPanoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GLSLPanoViewController.swift; sourceTree = ""; }; 45 | C026228A250CB8CB0035DF13 /* GLSLPanoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GLSLPanoView.swift; sourceTree = ""; }; 46 | C026228C250CB90C0035DF13 /* GLViewable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GLViewable.swift; sourceTree = ""; }; 47 | C026228F250CB9830035DF13 /* Pano.fsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = Pano.fsh; sourceTree = ""; }; 48 | C0262291250CB9890035DF13 /* Pano.vsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = Pano.vsh; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | C0262258250C72190035DF13 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | C0262252250C72190035DF13 = { 63 | isa = PBXGroup; 64 | children = ( 65 | C026225D250C72190035DF13 /* HSGLPanoViewer */, 66 | C026225C250C72190035DF13 /* Products */, 67 | ); 68 | sourceTree = ""; 69 | }; 70 | C026225C250C72190035DF13 /* Products */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | C026225B250C72190035DF13 /* HSGLPanoViewer.app */, 74 | ); 75 | name = Products; 76 | sourceTree = ""; 77 | }; 78 | C026225D250C72190035DF13 /* HSGLPanoViewer */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | C0262272250C753B0035DF13 /* GLKitPano */, 82 | C0262273250C75620035DF13 /* GLSLPano */, 83 | C026227E250C801D0035DF13 /* Resources */, 84 | C026225E250C72190035DF13 /* AppDelegate.swift */, 85 | C0262260250C72190035DF13 /* SceneDelegate.swift */, 86 | C0262262250C72190035DF13 /* ViewController.swift */, 87 | C026226C250C721D0035DF13 /* Info.plist */, 88 | C0262278250C7E260035DF13 /* PanoViewType.swift */, 89 | C026227A250C7E5A0035DF13 /* ViewTransform.swift */, 90 | C026227C250C7E960035DF13 /* Array+MemorySize.swift */, 91 | ); 92 | path = HSGLPanoViewer; 93 | sourceTree = ""; 94 | }; 95 | C0262272250C753B0035DF13 /* GLKitPano */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | C0262276250C7DCB0035DF13 /* GLKitPanoViewController.swift */, 99 | ); 100 | path = GLKitPano; 101 | sourceTree = ""; 102 | }; 103 | C0262273250C75620035DF13 /* GLSLPano */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | C026228E250CB9770035DF13 /* Shader */, 107 | C0262288250CB8980035DF13 /* GLSLPanoViewController.swift */, 108 | C026228A250CB8CB0035DF13 /* GLSLPanoView.swift */, 109 | C026228C250CB90C0035DF13 /* GLViewable.swift */, 110 | ); 111 | path = GLSLPano; 112 | sourceTree = ""; 113 | }; 114 | C026227E250C801D0035DF13 /* Resources */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | C026227F250C804D0035DF13 /* PanoImages */, 118 | C0262264250C72190035DF13 /* Main.storyboard */, 119 | C0262269250C721D0035DF13 /* LaunchScreen.storyboard */, 120 | C0262267250C721D0035DF13 /* Assets.xcassets */, 121 | ); 122 | path = Resources; 123 | sourceTree = ""; 124 | }; 125 | C026227F250C804D0035DF13 /* PanoImages */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | C0262284250C87F20035DF13 /* pano-2048-1024.jpg */, 129 | C0262285250C87F20035DF13 /* pano-4096-2048.jpg */, 130 | ); 131 | path = PanoImages; 132 | sourceTree = ""; 133 | }; 134 | C026228E250CB9770035DF13 /* Shader */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | C0262291250CB9890035DF13 /* Pano.vsh */, 138 | C026228F250CB9830035DF13 /* Pano.fsh */, 139 | ); 140 | path = Shader; 141 | sourceTree = ""; 142 | }; 143 | /* End PBXGroup section */ 144 | 145 | /* Begin PBXNativeTarget section */ 146 | C026225A250C72190035DF13 /* HSGLPanoViewer */ = { 147 | isa = PBXNativeTarget; 148 | buildConfigurationList = C026226F250C721D0035DF13 /* Build configuration list for PBXNativeTarget "HSGLPanoViewer" */; 149 | buildPhases = ( 150 | C0262257250C72190035DF13 /* Sources */, 151 | C0262258250C72190035DF13 /* Frameworks */, 152 | C0262259250C72190035DF13 /* Resources */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | ); 158 | name = HSGLPanoViewer; 159 | productName = HSGLPanoViewer; 160 | productReference = C026225B250C72190035DF13 /* HSGLPanoViewer.app */; 161 | productType = "com.apple.product-type.application"; 162 | }; 163 | /* End PBXNativeTarget section */ 164 | 165 | /* Begin PBXProject section */ 166 | C0262253250C72190035DF13 /* Project object */ = { 167 | isa = PBXProject; 168 | attributes = { 169 | LastSwiftUpdateCheck = 1160; 170 | LastUpgradeCheck = 1160; 171 | ORGANIZATIONNAME = Hanson; 172 | TargetAttributes = { 173 | C026225A250C72190035DF13 = { 174 | CreatedOnToolsVersion = 11.6; 175 | }; 176 | }; 177 | }; 178 | buildConfigurationList = C0262256250C72190035DF13 /* Build configuration list for PBXProject "HSGLPanoViewer" */; 179 | compatibilityVersion = "Xcode 9.3"; 180 | developmentRegion = en; 181 | hasScannedForEncodings = 0; 182 | knownRegions = ( 183 | en, 184 | Base, 185 | ); 186 | mainGroup = C0262252250C72190035DF13; 187 | productRefGroup = C026225C250C72190035DF13 /* Products */; 188 | projectDirPath = ""; 189 | projectRoot = ""; 190 | targets = ( 191 | C026225A250C72190035DF13 /* HSGLPanoViewer */, 192 | ); 193 | }; 194 | /* End PBXProject section */ 195 | 196 | /* Begin PBXResourcesBuildPhase section */ 197 | C0262259250C72190035DF13 /* Resources */ = { 198 | isa = PBXResourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | C0262287250C87F20035DF13 /* pano-4096-2048.jpg in Resources */, 202 | C026226B250C721D0035DF13 /* LaunchScreen.storyboard in Resources */, 203 | C0262268250C721D0035DF13 /* Assets.xcassets in Resources */, 204 | C0262292250CB9890035DF13 /* Pano.vsh in Resources */, 205 | C0262286250C87F20035DF13 /* pano-2048-1024.jpg in Resources */, 206 | C0262290250CB9840035DF13 /* Pano.fsh in Resources */, 207 | C0262266250C72190035DF13 /* Main.storyboard in Resources */, 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | /* End PBXResourcesBuildPhase section */ 212 | 213 | /* Begin PBXSourcesBuildPhase section */ 214 | C0262257250C72190035DF13 /* Sources */ = { 215 | isa = PBXSourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | C0262277250C7DCB0035DF13 /* GLKitPanoViewController.swift in Sources */, 219 | C0262263250C72190035DF13 /* ViewController.swift in Sources */, 220 | C0262279250C7E260035DF13 /* PanoViewType.swift in Sources */, 221 | C026228B250CB8CB0035DF13 /* GLSLPanoView.swift in Sources */, 222 | C026227B250C7E5A0035DF13 /* ViewTransform.swift in Sources */, 223 | C026225F250C72190035DF13 /* AppDelegate.swift in Sources */, 224 | C026227D250C7E960035DF13 /* Array+MemorySize.swift in Sources */, 225 | C0262289250CB8980035DF13 /* GLSLPanoViewController.swift in Sources */, 226 | C0262261250C72190035DF13 /* SceneDelegate.swift in Sources */, 227 | C026228D250CB90C0035DF13 /* GLViewable.swift in Sources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXSourcesBuildPhase section */ 232 | 233 | /* Begin PBXVariantGroup section */ 234 | C0262264250C72190035DF13 /* Main.storyboard */ = { 235 | isa = PBXVariantGroup; 236 | children = ( 237 | C0262265250C72190035DF13 /* Base */, 238 | ); 239 | name = Main.storyboard; 240 | sourceTree = ""; 241 | }; 242 | C0262269250C721D0035DF13 /* LaunchScreen.storyboard */ = { 243 | isa = PBXVariantGroup; 244 | children = ( 245 | C026226A250C721D0035DF13 /* Base */, 246 | ); 247 | name = LaunchScreen.storyboard; 248 | sourceTree = ""; 249 | }; 250 | /* End PBXVariantGroup section */ 251 | 252 | /* Begin XCBuildConfiguration section */ 253 | C026226D250C721D0035DF13 /* Debug */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_ANALYZER_NONNULL = YES; 258 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_ENABLE_OBJC_WEAK = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INFINITE_RECURSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 277 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 279 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 280 | CLANG_WARN_STRICT_PROTOTYPES = YES; 281 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 282 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 283 | CLANG_WARN_UNREACHABLE_CODE = YES; 284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 285 | COPY_PHASE_STRIP = NO; 286 | DEBUG_INFORMATION_FORMAT = dwarf; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | ENABLE_TESTABILITY = YES; 289 | GCC_C_LANGUAGE_STANDARD = gnu11; 290 | GCC_DYNAMIC_NO_PIC = NO; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_OPTIMIZATION_LEVEL = 0; 293 | GCC_PREPROCESSOR_DEFINITIONS = ( 294 | "DEBUG=1", 295 | "$(inherited)", 296 | ); 297 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 299 | GCC_WARN_UNDECLARED_SELECTOR = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 301 | GCC_WARN_UNUSED_FUNCTION = YES; 302 | GCC_WARN_UNUSED_VARIABLE = YES; 303 | IPHONEOS_DEPLOYMENT_TARGET = 13.6; 304 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 305 | MTL_FAST_MATH = YES; 306 | ONLY_ACTIVE_ARCH = YES; 307 | SDKROOT = iphoneos; 308 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 309 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 310 | }; 311 | name = Debug; 312 | }; 313 | C026226E250C721D0035DF13 /* 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++14"; 320 | CLANG_CXX_LIBRARY = "libc++"; 321 | CLANG_ENABLE_MODULES = YES; 322 | CLANG_ENABLE_OBJC_ARC = YES; 323 | CLANG_ENABLE_OBJC_WEAK = YES; 324 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 325 | CLANG_WARN_BOOL_CONVERSION = YES; 326 | CLANG_WARN_COMMA = YES; 327 | CLANG_WARN_CONSTANT_CONVERSION = YES; 328 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 329 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 330 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 331 | CLANG_WARN_EMPTY_BODY = YES; 332 | CLANG_WARN_ENUM_CONVERSION = YES; 333 | CLANG_WARN_INFINITE_RECURSION = YES; 334 | CLANG_WARN_INT_CONVERSION = YES; 335 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 337 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 338 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 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 | IPHONEOS_DEPLOYMENT_TARGET = 13.6; 358 | MTL_ENABLE_DEBUG_INFO = NO; 359 | MTL_FAST_MATH = YES; 360 | SDKROOT = iphoneos; 361 | SWIFT_COMPILATION_MODE = wholemodule; 362 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 363 | VALIDATE_PRODUCT = YES; 364 | }; 365 | name = Release; 366 | }; 367 | C0262270250C721D0035DF13 /* Debug */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 371 | CODE_SIGN_STYLE = Automatic; 372 | DEVELOPMENT_TEAM = XC238MH24L; 373 | GCC_PREPROCESSOR_DEFINITIONS = ( 374 | "DEBUG=1", 375 | "GLES_SILENCE_DEPRECATION=1", 376 | ); 377 | INFOPLIST_FILE = HSGLPanoViewer/Info.plist; 378 | LD_RUNPATH_SEARCH_PATHS = ( 379 | "$(inherited)", 380 | "@executable_path/Frameworks", 381 | ); 382 | PRODUCT_BUNDLE_IDENTIFIER = com.HansonStudio.HSGLPanoViewer; 383 | PRODUCT_NAME = "$(TARGET_NAME)"; 384 | SWIFT_VERSION = 5.0; 385 | TARGETED_DEVICE_FAMILY = "1,2"; 386 | }; 387 | name = Debug; 388 | }; 389 | C0262271250C721D0035DF13 /* Release */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 393 | CODE_SIGN_STYLE = Automatic; 394 | DEVELOPMENT_TEAM = XC238MH24L; 395 | GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1"; 396 | INFOPLIST_FILE = HSGLPanoViewer/Info.plist; 397 | LD_RUNPATH_SEARCH_PATHS = ( 398 | "$(inherited)", 399 | "@executable_path/Frameworks", 400 | ); 401 | PRODUCT_BUNDLE_IDENTIFIER = com.HansonStudio.HSGLPanoViewer; 402 | PRODUCT_NAME = "$(TARGET_NAME)"; 403 | SWIFT_VERSION = 5.0; 404 | TARGETED_DEVICE_FAMILY = "1,2"; 405 | }; 406 | name = Release; 407 | }; 408 | /* End XCBuildConfiguration section */ 409 | 410 | /* Begin XCConfigurationList section */ 411 | C0262256250C72190035DF13 /* Build configuration list for PBXProject "HSGLPanoViewer" */ = { 412 | isa = XCConfigurationList; 413 | buildConfigurations = ( 414 | C026226D250C721D0035DF13 /* Debug */, 415 | C026226E250C721D0035DF13 /* Release */, 416 | ); 417 | defaultConfigurationIsVisible = 0; 418 | defaultConfigurationName = Release; 419 | }; 420 | C026226F250C721D0035DF13 /* Build configuration list for PBXNativeTarget "HSGLPanoViewer" */ = { 421 | isa = XCConfigurationList; 422 | buildConfigurations = ( 423 | C0262270250C721D0035DF13 /* Debug */, 424 | C0262271250C721D0035DF13 /* Release */, 425 | ); 426 | defaultConfigurationIsVisible = 0; 427 | defaultConfigurationName = Release; 428 | }; 429 | /* End XCConfigurationList section */ 430 | }; 431 | rootObject = C0262253250C72190035DF13 /* Project object */; 432 | } 433 | -------------------------------------------------------------------------------- /HSGLPanoViewer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HSGLPanoViewer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HSGLPanoViewer.xcodeproj/xcshareddata/xcschemes/HSGLPanoViewer.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 | -------------------------------------------------------------------------------- /HSGLPanoViewer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // HSGLPanoViewer 4 | // 5 | // Created by Hanson on 2020/9/12. 6 | // Copyright © 2020 Hanson. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /HSGLPanoViewer/Array+MemorySize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+MemorySize.swift 3 | // OpenPano 4 | // 5 | // Created by Hanson on 2020/9/9. 6 | // Copyright © 2020 HansonStudio. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array { 12 | /// 根据数组类型和长度获取数组实际内存空间大小(Bytes) 13 | public func size() -> Int { 14 | return MemoryLayout.stride * self.count 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /HSGLPanoViewer/GLKitPano/GLKitPanoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GLKitPanoViewController.swift 3 | // OpenPano 4 | // 5 | // Created by Hanson on 2020/6/16. 6 | // Copyright © 2020 HansonStudio. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import GLKit 11 | import CoreMotion 12 | 13 | class GLKitPanoViewController: GLKViewController, GLKViewControllerDelegate { 14 | private var context: EAGLContext? 15 | private var baseEffect = GLKBaseEffect() 16 | 17 | // 顶点坐标缓存标记 18 | private var vertexBufferID = GLuint() 19 | // 顶点索引坐标缓存标记 20 | private var vertexIndicesBufferID = GLuint() 21 | // 纹理坐标缓存标记 22 | private var textureCoordID = GLuint() 23 | 24 | private var vertices = [GLfloat]() 25 | private var textures = [GLfloat]() 26 | // ⚠️ 注意是 GLushort 类型,UInt16 27 | private var vertexIndices = [GLushort]() 28 | 29 | private var xAxisRotate: Float = 0 // 绕 X 轴旋转角度 30 | private var yAxisRotate: Float = 0 // 绕 Y 轴旋转角度 31 | 32 | private var panoViewType: PanoViewType = .sphere 33 | 34 | private var segmentControl: UISegmentedControl! 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | setupGL() 39 | generateSphereVertices(slice: 200, radius: 1.0) 40 | loadVerticesData() 41 | loadTexture(name: "pano-4096-2048.jpg") 42 | setupSegmentControl() 43 | } 44 | 45 | override func glkView(_ view: GLKView, drawIn rect: CGRect) { 46 | glClearColor(1, 1, 1, 1) 47 | glClear(GLbitfield(GL_COLOR_BUFFER_BIT)) 48 | glClear(GLbitfield(GL_DEPTH_BUFFER_BIT)) 49 | // 开启深度测试,不然球滑动的时候会看到背面 50 | glEnable(GLenum(GL_DEPTH_TEST)) 51 | baseEffect.prepareToDraw() 52 | glDrawElements(GLenum(GL_TRIANGLES), GLsizei(vertexIndices.count), GLenum(GL_UNSIGNED_SHORT), nil) 53 | } 54 | 55 | // update 方法现在不会调用了,使用 delegate = self 触发此方法调用;此方法会在每次刷新屏幕帧调用 56 | func glkViewControllerUpdate(_ controller: GLKViewController) { 57 | var modelViewMatrix = GLKMatrix4Identity 58 | modelViewMatrix = GLKMatrix4RotateX(modelViewMatrix, xAxisRotate) 59 | modelViewMatrix = GLKMatrix4RotateY(modelViewMatrix, yAxisRotate) 60 | modelViewMatrix = GLKMatrix4Multiply(panoViewType.viewTransform.viewMatrix, modelViewMatrix) 61 | // print("---xAxisRotate: \(xAxisRotate)") 62 | 63 | let width = view.frame.size.width * UIScreen.main.scale 64 | let height = view.frame.size.height * UIScreen.main.scale 65 | let aspect = GLfloat(width / height) 66 | let projectionMatrix = panoViewType.viewTransform.projectionMatrix(aspect: aspect) 67 | 68 | baseEffect.transform.modelviewMatrix = modelViewMatrix 69 | baseEffect.transform.projectionMatrix = projectionMatrix 70 | } 71 | 72 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 73 | guard let touch = touches.first else { return } 74 | let location = touch.location(in: touch.view) 75 | let previousLocation = touch.previousLocation(in: touch.view) 76 | var diffX = Float(location.x - previousLocation.x) 77 | var diffY = Float(location.y - previousLocation.y) 78 | 79 | // 定义每移动的一个像素点则旋转 0.006 弧度 80 | let radiansPerPoint: Float = 0.006 81 | 82 | // 转换成手指移动量的弧度值 83 | if panoViewType == .sphere { 84 | diffX *= radiansPerPoint 85 | diffY *= radiansPerPoint 86 | } else { 87 | // 视角在球中心时,注意拖动的值是相反的 88 | diffX *= -radiansPerPoint 89 | diffY *= -radiansPerPoint 90 | } 91 | 92 | // 注意手指在屏幕左右滑动,绕的是 Y 轴旋转,上下滑动绕的是 X 轴 93 | xAxisRotate += diffY 94 | yAxisRotate += diffX 95 | 96 | if panoViewType == .sphere || panoViewType == .pano { 97 | // 控制不让拖动球绕过顶部底部 98 | let roundRadian = GLKMathDegreesToRadians(360) 99 | let ninetyRadian = GLKMathDegreesToRadians(90) 100 | if xAxisRotate.truncatingRemainder(dividingBy: roundRadian) > ninetyRadian { 101 | xAxisRotate = ninetyRadian 102 | } 103 | if xAxisRotate.truncatingRemainder(dividingBy: roundRadian) < -ninetyRadian { 104 | xAxisRotate = -ninetyRadian 105 | } 106 | } 107 | } 108 | 109 | private func setupGL() { 110 | context = EAGLContext(api: .openGLES2) 111 | EAGLContext.setCurrent(context) 112 | 113 | self.delegate = self 114 | if let glView = self.view as? GLKView, let context = context { 115 | glView.context = context 116 | // 配置颜色缓冲区的格式 117 | glView.drawableColorFormat = GLKViewDrawableColorFormat.RGBA8888 118 | // 配置深度缓冲区的格式 119 | glView.drawableDepthFormat = GLKViewDrawableDepthFormat.format24 120 | // 错误做法,应该直接 self.delegate = self 121 | // glView.delegate = self 122 | } 123 | } 124 | 125 | private func loadVerticesData() { 126 | // 顶点 127 | glGenBuffers(1, &vertexBufferID) 128 | glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBufferID) 129 | glBufferData(GLenum(GL_ARRAY_BUFFER), vertices.size(), vertices, GLenum(GL_STATIC_DRAW)) 130 | glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue)) 131 | let vertiesStrideSize = MemoryLayout.stride * 3 // 注意取的类型 132 | glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(vertiesStrideSize), nil) 133 | 134 | // 顶点索引 135 | glGenBuffers(1, &vertexIndicesBufferID) 136 | glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), vertexIndicesBufferID) 137 | glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER), vertexIndices.size(), vertexIndices, GLenum(GL_STATIC_DRAW)) 138 | 139 | // 纹理 140 | glGenBuffers(1, &textureCoordID) 141 | glBindBuffer(GLenum(GL_ARRAY_BUFFER), textureCoordID) 142 | glBufferData(GLenum(GL_ARRAY_BUFFER), textures.size(), textures, GLenum(GL_DYNAMIC_DRAW)) 143 | glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue)) 144 | let texturesStrideSize = MemoryLayout.stride * 2 // 注意取的类型 145 | glVertexAttribPointer(GLuint(GLKVertexAttrib.texCoord0.rawValue), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(texturesStrideSize), nil) 146 | } 147 | 148 | private func loadTexture(name: String) { 149 | guard let path = Bundle.main.path(forResource: name, ofType: nil) 150 | , let image = UIImage(contentsOfFile: path) 151 | , let textureImage = image.cgImage else { 152 | print("--- 加载全景图纹理失败 ---") 153 | return 154 | } 155 | do { 156 | // 为 true 时, 图片会在加载之前进行翻转(处理纹理坐标系原点不一样问题) 157 | let options = [GLKTextureLoaderOriginBottomLeft: NSNumber(value: true) ] 158 | let textureInfo = try GLKTextureLoader.texture(with: textureImage, options: options) 159 | baseEffect.texture2d0.target = GLKTextureTarget(rawValue: textureInfo.target) ?? .target2D 160 | baseEffect.texture2d0.name = textureInfo.name 161 | // 默认纹理 texture2d0 是默认 enable 的 162 | // baseEffect.texture2d0.enabled = GLboolean(GL_TRUE) 163 | } catch { 164 | print("---LoadTexture Error:", String(describing: error)) 165 | } 166 | } 167 | 168 | private func generateSphereVertices(slice: Int, radius: Float) { 169 | let parallelsNum = slice / 2 170 | // 顶点数量 171 | let verticesNum = (parallelsNum + 1) * (slice + 1) 172 | // 索引数量 173 | let indicesNum = parallelsNum * slice * 6 174 | // 角度步进值得 175 | let angleStep = (2 * Float(3.1415926)) / Float(slice) 176 | 177 | // 顶点坐标数组 (verticesNum * 3 意思是 x, y, z 三个分量的值) 178 | var vertexArray: [GLfloat] = Array(repeating: 0, count: verticesNum * 3) 179 | // 纹理坐标数组 (verticesNum * 2 意思 x, y 两个分量的值) 180 | var textureArray: [GLfloat] = Array(repeating: 0, count: verticesNum * 2) 181 | // 顶点坐标索引数组 182 | var vertexIndexArray: [Int] = Array(repeating: 0, count: indicesNum) 183 | 184 | print("slices: \(slice), parallelNum: \(parallelsNum), verticesNum: \(verticesNum), indicesNum: \(indicesNum)") 185 | 186 | /* 顶点坐标公式 187 | x = r * sin α * sin β 188 | y = r * cos α 189 | z = r * sin α * cos β 190 | */ 191 | for i in 0..<(parallelsNum + 1) { 192 | for j in 0..<(slice + 1) { 193 | let vertexIndex = (i * (slice + 1) + j) * 3 194 | vertexArray[vertexIndex + 0] = (radius * sinf(angleStep * Float(i)) * sinf(angleStep * Float(j))) 195 | vertexArray[vertexIndex + 1] = (radius * cosf(angleStep * Float(i))) 196 | vertexArray[vertexIndex + 2] = (radius * sinf(angleStep * Float(i)) * cosf(angleStep * Float(j))) 197 | 198 | let textureIndex = (i * (slice + 1) + j) * 2 199 | textureArray[textureIndex + 0] = Float(j) / Float(slice) 200 | textureArray[textureIndex + 1] = 1.0 - (Float(i) / Float(parallelsNum)) 201 | } 202 | } 203 | 204 | var vertexIndexTemp = 0 205 | for i in 0.., with event: UIEvent?) { 137 | guard let touch = touches.first else { return } 138 | let location = touch.location(in: touch.view) 139 | let previousLocation = touch.previousLocation(in: touch.view) 140 | var diffX = Float(location.x - previousLocation.x) 141 | var diffY = Float(location.y - previousLocation.y) 142 | 143 | // 定义每移动的一个像素点则旋转 0.006 弧度 144 | let radiansPerPoint: Float = 0.006 145 | 146 | // 转换成手指移动量的弧度值 147 | if panoViewType == .sphere { 148 | diffX *= radiansPerPoint 149 | diffY *= radiansPerPoint 150 | } else { 151 | // 视角在球中心时,注意拖动的值是相反的 152 | diffX *= -radiansPerPoint 153 | diffY *= -radiansPerPoint 154 | } 155 | 156 | // 注意手指在屏幕左右滑动,绕的是 Y 轴旋转,上下滑动绕的是 X 轴 157 | xAxisRotate += diffY 158 | yAxisRotate += diffX 159 | 160 | if panoViewType == .sphere || panoViewType == .pano { 161 | // 控制不让拖动球绕过顶部底部 162 | let roundRadian = GLKMathDegreesToRadians(360) 163 | let ninetyRadian = GLKMathDegreesToRadians(90) 164 | if xAxisRotate.truncatingRemainder(dividingBy: roundRadian) > ninetyRadian { 165 | xAxisRotate = ninetyRadian 166 | } 167 | if xAxisRotate.truncatingRemainder(dividingBy: roundRadian) < -ninetyRadian { 168 | xAxisRotate = -ninetyRadian 169 | } 170 | } 171 | 172 | update() 173 | } 174 | 175 | private func update() { 176 | glClearColor(0, 0, 0, 0) 177 | glClear(GLbitfield(GL_COLOR_BUFFER_BIT)) 178 | 179 | updateMVPMatrix() 180 | 181 | glEnable(GLenum(GL_DEPTH_TEST)) 182 | if panoViewType == .sphere { 183 | glEnable(GLenum(GL_CULL_FACE)) 184 | } else { 185 | glDisable(GLenum(GL_CULL_FACE)) 186 | } 187 | 188 | glDrawElements(GLenum(GL_TRIANGLES), GLsizei(indices.count), GLenum(GL_UNSIGNED_SHORT), nil) 189 | eaglContext.presentRenderbuffer(Int(GL_RENDERBUFFER)) 190 | } 191 | 192 | private func render() { 193 | glClearColor(0, 0, 0, 0) 194 | glClear(GLbitfield(GL_COLOR_BUFFER_BIT)) 195 | setupViewPort() 196 | 197 | glUseProgram(shaderProgram) 198 | 199 | let texture = loadTexture(name: "pano-4096-2048.jpg") 200 | glActiveTexture(GLenum(GL_TEXTURE0)) 201 | glBindTexture(GLenum(GL_TEXTURE_2D), texture) 202 | glUniform1i(glGetUniformLocation(shaderProgram, "textureSampler"), 0) 203 | 204 | updateMVPMatrix() 205 | 206 | let position = glGetAttribLocation(shaderProgram, "position") 207 | glEnableVertexAttribArray(GLuint(position)) 208 | let strideSize = MemoryLayout.stride * 5 209 | glVertexAttribPointer(GLuint(position), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(strideSize), nil) 210 | 211 | let textureCoord = glGetAttribLocation(shaderProgram, "attributeTextureCoordinates") 212 | glEnableVertexAttribArray(GLuint(textureCoord)) 213 | let texturesStrideSize = MemoryLayout.stride * 5 214 | let textureOffset = MemoryLayout.stride * 3 215 | let textureOffsetPointer = UnsafeRawPointer(bitPattern: textureOffset) 216 | glVertexAttribPointer(GLuint(textureCoord), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(texturesStrideSize), textureOffsetPointer) 217 | 218 | // 深度测试 219 | glEnable(GLenum(GL_DEPTH_TEST)) 220 | if panoViewType == .sphere { 221 | // 球体视角时,执行面剔除 222 | glEnable(GLenum(GL_CULL_FACE)) 223 | } else { 224 | glDisable(GLenum(GL_CULL_FACE)) 225 | } 226 | 227 | glDrawElements(GLenum(GL_TRIANGLES), GLsizei(indices.count), GLenum(GL_UNSIGNED_SHORT), nil) 228 | 229 | // 从渲染缓冲区显示到屏幕上 230 | self.eaglContext.presentRenderbuffer(Int(GL_RENDERBUFFER)) 231 | } 232 | 233 | private func updateMVPMatrix() { 234 | var modelViewMatrix = GLKMatrix4Identity 235 | modelViewMatrix = GLKMatrix4RotateX(modelViewMatrix, xAxisRotate) 236 | modelViewMatrix = GLKMatrix4RotateY(modelViewMatrix, yAxisRotate) 237 | modelViewMatrix = GLKMatrix4Multiply(panoViewType.viewTransform.viewMatrix, modelViewMatrix) 238 | 239 | let width = frame.size.width * UIScreen.main.scale 240 | let height = frame.size.height * UIScreen.main.scale 241 | let aspect = GLfloat(width / height) 242 | let projectionMatrix = panoViewType.viewTransform.projectionMatrix(aspect: aspect) 243 | 244 | // 最终的 MVP 矩阵 245 | var mvpMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix) 246 | 247 | // 这里取出 mvpMatrix.m 的指针操作 248 | let components = MemoryLayout.size(ofValue: mvpMatrix.m) / MemoryLayout.size(ofValue: mvpMatrix.m.0) 249 | withUnsafePointer(to: &mvpMatrix.m) { 250 | $0.withMemoryRebound(to: GLfloat.self, capacity: components) { 251 | glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "mvpMatrix"), 1, GLboolean(GL_FALSE), $0) 252 | } 253 | } 254 | } 255 | 256 | private func loadTexture(name: String = "pano-2048-1024.jpg") -> GLuint { 257 | guard let path = Bundle.main.path(forResource: name, ofType: nil) 258 | , let image = UIImage(contentsOfFile: path) 259 | , let textureImage = image.cgImage else { 260 | print("--- 加载全景图纹理失败 ---") 261 | return GLuint() 262 | } 263 | return generateTexture(from: textureImage) 264 | } 265 | 266 | private func generateSphereVertices(slice: Int, radius: Float) { 267 | let parallelsNum = slice / 2 268 | let verticesNum = (parallelsNum + 1) * (slice + 1) 269 | let indicesNum = parallelsNum * slice * 6 270 | let angleStep = (2 * Float.pi) / Float(slice) 271 | 272 | // 顶点坐标和纹理坐标 273 | var vertexArray: [GLfloat] = Array(repeating: 0, count: verticesNum * 5) 274 | // 顶点坐标索引数组 275 | var vertexIndexArray: [Int] = Array(repeating: 0, count: indicesNum) 276 | 277 | print("slices: \(slice), parallelNum: \(parallelsNum), verticesNum: \(verticesNum), indicesNum: \(indicesNum)") 278 | /* 顶点坐标公式 279 | x = r * sin α * sin β 280 | y = r * cos α 281 | z = r * sin α * cos β 282 | */ 283 | for i in 0..<(parallelsNum + 1) { 284 | for j in 0..<(slice + 1) { 285 | let vertexIndex = (i * (slice + 1) + j) * 5 286 | vertexArray[vertexIndex + 0] = (radius * sinf(angleStep * Float(i)) * sinf(angleStep * Float(j))) 287 | vertexArray[vertexIndex + 1] = (radius * cosf(angleStep * Float(i))) 288 | vertexArray[vertexIndex + 2] = (radius * sinf(angleStep * Float(i)) * cosf(angleStep * Float(j))) 289 | 290 | vertexArray[vertexIndex + 3] = Float(j) / Float(slice) 291 | vertexArray[vertexIndex + 4] = Float(1.0) - (Float(i) / Float(parallelsNum)) 292 | } 293 | } 294 | 295 | var vertexIndexTemp = 0 296 | for i in 0.. GLuint { 25 | let width = cgImage.width 26 | let height = cgImage.height 27 | let imageRect = CGRect(x: 0, y: 0, width: width, height: height) 28 | let colorSpace = CGColorSpaceCreateDeviceRGB() // cgImage.colorSpace! 29 | // 计算图片所占字节大小 (width * height * rgba) 30 | let imageData = calloc(width * height * 4, MemoryLayout.size) 31 | 32 | let context = CGContext(data: imageData, 33 | width: width, 34 | height: height, 35 | bitsPerComponent: 8, 36 | bytesPerRow: width * 4, 37 | space: colorSpace, 38 | bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) 39 | 40 | // print("---imageRect: \(imageRect)") 41 | // 上下翻转图片 42 | context?.translateBy(x: 0, y: imageRect.size.height) 43 | context?.scaleBy(x: 1.0, y: -1.0) 44 | context?.draw(cgImage, in: imageRect) 45 | 46 | // 创建纹理 47 | var texture = GLuint() 48 | glGenTextures(1, &texture) 49 | glBindTexture(GLenum(GL_TEXTURE_2D), texture) 50 | 51 | // 设置纹素映射成像素的方式 52 | glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR) 53 | glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR) 54 | glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE) 55 | glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE) 56 | 57 | // 加载纹理数据,写入缓存中 58 | glTexImage2D(GLenum(GL_TEXTURE_2D), 59 | 0, 60 | GL_RGBA, 61 | GLsizei(width), 62 | GLsizei(height), 63 | 0, 64 | GLenum(GL_RGBA), 65 | GLenum(GL_UNSIGNED_BYTE), 66 | imageData) 67 | 68 | // 将2D纹理绑定到默认的纹理,相当于解绑。 69 | // 打破之前的纹理绑定关系,使OpenGL的纹理绑定状态恢复到默认状态。(OpenGL Context 是个状态机) 70 | glBindTexture(GLenum(GL_TEXTURE_2D), 0) 71 | 72 | // 'CGContextRelease' is unavailable: Core Foundation objects are automatically memory managed 73 | // CGContextRelease(context) 74 | free(imageData) 75 | 76 | return texture 77 | } 78 | 79 | /// 加载着色器程序 80 | public func loadShader(vertexFile: String, fragmentFile: String) -> GLuint { 81 | var vertexShader = GLuint() 82 | var fragmentShader = GLuint() 83 | var program = glCreateProgram() 84 | 85 | // 编译 86 | compileShader(&vertexShader, type: GLenum(GL_VERTEX_SHADER), filePath: vertexFile) 87 | compileShader(&fragmentShader, type: GLenum(GL_FRAGMENT_SHADER), filePath: fragmentFile) 88 | 89 | // 装载 90 | glAttachShader(program, vertexShader) 91 | glAttachShader(program, fragmentShader) 92 | 93 | // 链接 94 | glLinkProgram(program) 95 | logLinkProgramStatus(program: &program) // 打印链接状态 96 | 97 | glDeleteShader(vertexShader) 98 | glDeleteShader(fragmentShader) 99 | 100 | return program 101 | } 102 | 103 | /// 编译着色器 104 | public func compileShader(_ shader: inout GLuint, type: GLenum, filePath: String) { 105 | let sourceContent = try? String(contentsOfFile: filePath, encoding: .utf8) 106 | let cStringContent = sourceContent?.cString(using: .utf8) 107 | var sourcePointer = UnsafePointer(cStringContent) 108 | 109 | shader = glCreateShader(type) // 创建着色器对象 110 | glShaderSource(shader, 1, &sourcePointer, nil) // 将着色器源码赋给 shader 对象 111 | 112 | glCompileShader(shader) // 编译着色器代码 113 | 114 | logShaderCompileStatus(shader: &shader) // 打印编译状态 115 | } 116 | 117 | // 打印 Compile Shader 状态 118 | func logShaderCompileStatus(shader: inout GLuint) { 119 | var status = GLint() 120 | glGetShaderiv(shader, GLenum(GL_COMPILE_STATUS), &status) 121 | if status == GL_FALSE { 122 | var infoLog = [GLchar](repeating: 0, count: 512) 123 | glGetShaderInfoLog(shader, GLsizei(infoLog.size()), nil, &infoLog) 124 | let info = String(cString: infoLog, encoding: .utf8) 125 | print("--- Compile Shader Error: \(String(describing: info)) ---") 126 | } else { 127 | print("--- Compile Shader Success ---") 128 | } 129 | } 130 | 131 | // 打印 Link Program 状态 132 | func logLinkProgramStatus(program: inout GLuint) { 133 | var status = GLint() 134 | glGetProgramiv(program, GLenum(GL_LINK_STATUS), &status) 135 | if status == GL_FALSE { 136 | var infoLog = [GLchar](repeating: 0, count: 512) 137 | glGetProgramInfoLog(program, GLsizei(infoLog.size()), nil, &infoLog) 138 | let info = String(cString: infoLog, encoding: .utf8) 139 | print("--- Link Program Error: \(String(describing: info)) ---") 140 | } else { 141 | print("--- Link Program Success ---") 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /HSGLPanoViewer/GLSLPano/Shader/Pano.fsh: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D textureSampler; // 纹理采样器 4 | varying vec2 varyingTextureCoordinates; // 纹理坐标 5 | 6 | void main() { 7 | gl_FragColor = texture2D(textureSampler, varyingTextureCoordinates); // 纹理采样 8 | } 9 | -------------------------------------------------------------------------------- /HSGLPanoViewer/GLSLPano/Shader/Pano.vsh: -------------------------------------------------------------------------------- 1 | uniform mat4 mvpMatrix; // 最终的 MVP 变换矩阵 2 | attribute vec4 position; // 顶点位置 3 | 4 | attribute vec2 attributeTextureCoordinates; // 纹理坐标 attribute 5 | varying vec2 varyingTextureCoordinates; // 纹理坐标 varying 会传递給片段着色器,与 fsh 的 varying 属性命名一致 6 | 7 | void main() { 8 | gl_Position = mvpMatrix * position; 9 | varyingTextureCoordinates = attributeTextureCoordinates; 10 | } 11 | -------------------------------------------------------------------------------- /HSGLPanoViewer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /HSGLPanoViewer/PanoViewType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PanoViewType.swift 3 | // HSGLPanoViewer 4 | // 5 | // Created by Hanson on 2020/9/12. 6 | // Copyright © 2020 Hanson. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import GLKit 11 | 12 | enum PanoViewType: Int, CaseIterable { 13 | case sphere 14 | case pano 15 | case asteroid 16 | 17 | var description: String { 18 | switch self { 19 | case .sphere: 20 | return "球体" 21 | case .pano: 22 | return "全景" 23 | case .asteroid: 24 | return "小行星" 25 | } 26 | } 27 | 28 | var viewTransform: ViewTransform { 29 | return ViewTransform(type: self) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /HSGLPanoViewer/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /HSGLPanoViewer/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HSGLPanoViewer/Resources/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /HSGLPanoViewer/Resources/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 | 29 | 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 | -------------------------------------------------------------------------------- /HSGLPanoViewer/Resources/PanoImages/pano-2048-1024.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyphs21/HSGLPanoViewer/bf85b8613718ba1e0c013493fb52d188750ac2c3/HSGLPanoViewer/Resources/PanoImages/pano-2048-1024.jpg -------------------------------------------------------------------------------- /HSGLPanoViewer/Resources/PanoImages/pano-4096-2048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyphs21/HSGLPanoViewer/bf85b8613718ba1e0c013493fb52d188750ac2c3/HSGLPanoViewer/Resources/PanoImages/pano-4096-2048.jpg -------------------------------------------------------------------------------- /HSGLPanoViewer/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // HSGLPanoViewer 4 | // 5 | // Created by Hanson on 2020/9/12. 6 | // Copyright © 2020 Hanson. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /HSGLPanoViewer/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // HSGLPanoViewer 4 | // 5 | // Created by Hanson on 2020/9/12. 6 | // Copyright © 2020 Hanson. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import GLKit 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | @IBAction func showGLKitPano(_ sender: Any) { 20 | let vc = GLKitPanoViewController() 21 | navigationController?.pushViewController(vc, animated: true) 22 | } 23 | 24 | @IBAction func showGLSLPano(_ sender: Any) { 25 | let vc = GLSLPanoViewController() 26 | navigationController?.pushViewController(vc, animated: true) 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /HSGLPanoViewer/ViewTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewTransform.swift 3 | // HSGLPanoViewer 4 | // 5 | // Created by Hanson on 2020/9/12. 6 | // Copyright © 2020 Hanson. All rights reserved. 7 | // 8 | 9 | import GLKit 10 | 11 | struct ViewTransform { 12 | var type: PanoViewType 13 | 14 | /// 投影矩阵的视角(单位: 度) 15 | var projectionFov: Float 16 | 17 | /// 相机位置 X 18 | var camEyeX: Float 19 | /// 相机位置 Y 20 | var camEyeY: Float 21 | /// 相机位置 Z 22 | var camEyeZ: Float 23 | 24 | /// 相机朝向点 X 25 | var camCenterX: Float 26 | /// 相机朝向点 Y 27 | var camCenterY: Float 28 | /// 相机朝向点 Z 29 | var camCenterZ: Float 30 | 31 | /// 相机上向量 X 32 | var camUpX: Float 33 | /// 相机上向量 Y 34 | var camUpY: Float 35 | /// 相机上向量 Z 36 | var camUpZ: Float 37 | 38 | init(type: PanoViewType) { 39 | self.type = type 40 | switch type { 41 | case .sphere: 42 | projectionFov = 65 43 | // 相机Z位置需要大于 1(球半径),这样才完整看到球体 44 | camEyeX = 0; camEyeY = 0; camEyeZ = 4 45 | camCenterX = 0; camCenterY = 0; camCenterZ = 0 46 | camUpX = 0; camUpY = 1; camUpZ = 0 47 | case .pano: 48 | projectionFov = 65 49 | // 远近平面距离为 [0.1, 100],球半径为 1,这里定相机位置要小于 1 才能在球内 50 | camEyeX = 0; camEyeY = 0; camEyeZ = 0.5 51 | camCenterX = 0; camCenterY = 0; camCenterZ = 0 52 | camUpX = 0; camUpY = 1; camUpZ = 0 53 | case .asteroid: 54 | projectionFov = 140 55 | // 相机位置放置到球体的边缘,camEyeZ 值为球体的半径 56 | camEyeX = 0; camEyeY = 0; camEyeZ = 1 57 | camCenterX = 0; camCenterY = 0; camCenterZ = 0; 58 | camUpX = 0; camUpY = 1; camUpZ = 0 59 | } 60 | } 61 | 62 | func projectionMatrix(aspect: Float, nearZ: Float = 0.1, farZ: Float = 100) -> GLKMatrix4 { 63 | return GLKMatrix4MakePerspective(GLKMathDegreesToRadians(projectionFov), aspect, nearZ, farZ) 64 | } 65 | 66 | var viewMatrix: GLKMatrix4 { 67 | if type == .asteroid { 68 | // 调试而得的 X 轴 和 Y 轴的旋转弧度,能让小行星更明显 69 | var matrix = GLKMatrix4RotateX(GLKMatrix4Identity, 1.2690004) 70 | matrix = GLKMatrix4RotateY(matrix, -0.138) 71 | let viewMatrix = GLKMatrix4MakeLookAt(camEyeX, camEyeY, camEyeZ, camCenterX, camCenterY, camCenterZ, camUpX, camUpY, camUpZ) 72 | return GLKMatrix4Multiply(viewMatrix, matrix) 73 | } else { 74 | return GLKMatrix4MakeLookAt(camEyeX, camEyeY, camEyeZ, camCenterX, camCenterY, camCenterZ, camUpX, camUpY, camUpZ) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Hanson Zhang 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HSGLPanoViewer 2 | A Panorama Browser using Swift and OpenGLES 3 | 4 | 最近在学习 OpenGLES,其实也就学到纹理那里,所以想着做个小项目来巩固一下知识。而一个全景浏览器正好囊括顶点坐标,纹理坐标,索引绘图,MVP 矩阵变换等等知识,是一个很不错的练手项目。我选择用 Swift 来写,当然用 Swift 会相对麻烦一点,特别是在处理指针方面。我同时写了两种实现方式,一种是基于 iOS 封装的 GLKit,一种则是用 GLSL 来写,具体实现请查看[源码-HSGLPanoViewer](https://github.com/zyphs21/HSGLPanoViewer)。 5 | 6 | ScreentShot 7 | 8 | 9 | 10 | ## 全景浏览器实现思路 11 | 12 | 实现思路其实挺简单的,首先我们需要计算出一个球的顶点坐标(这里还包括纹理坐标和索引数组的计算),然后把全景图以纹理的形式贴在这个球上,剩下的事情都是 MVP 矩阵的魔法了。 13 | 14 | ### 球的顶点坐标,纹理坐标,索引数组 15 | 16 | 对于一个球,通过圆柱投影方式我们可以把它剥开成一个长方形,然后对其进行切割成一块一块的小长方形,而这些长方形的顶点则相当于组成球的坐标。 17 | 18 | image-20200912191316204 19 | 20 | 21 | 22 | OpenGLES 里只能通过三角形来绘制,一个长方形可以以对角线分成两个三角形,而当为了便于绘制,需要有一个索引数组来表示哪几个点是组成一个三角形。 23 | 24 | image-20200912191415072 25 | 26 | 关于这块的计算我们可以参考 [《OpenGL ES 3.0 Programming Guide》](https://book.douban.com/subject/25845921/) 里有关于球坐标的[示例代码-esShapes.c](https://github.com/danginsburg/opengles3-book/blob/master/Common/Source/esShapes.c),这里贴一下我用 Swift 的实现代码: 27 | 28 | ```swift 29 | private var vertices = [GLfloat]() // 顶点坐标,包含纹理坐标 30 | private var indices = [GLushort]() // 索引坐标 31 | 32 | private func generateSphereVertices(slice: Int, radius: Float) { 33 | let parallelsNum = slice / 2 34 | let verticesNum = (parallelsNum + 1) * (slice + 1) 35 | let indicesNum = parallelsNum * slice * 6 36 | let angleStep = (2 * Float.pi) / Float(slice) 37 | 38 | // 顶点坐标和纹理坐标,乘以 5 代表顶点坐标 x,y,z 分量和纹理坐标的 u,v 分量 39 | var vertexArray: [GLfloat] = Array(repeating: 0, count: verticesNum * 5) 40 | // 顶点坐标索引数组 41 | var vertexIndexArray: [Int] = Array(repeating: 0, count: indicesNum) 42 | 43 | /* 顶点坐标公式 44 | x = r * sin α * sin β 45 | y = r * cos α 46 | z = r * sin α * cos β 47 | */ 48 | for i in 0..<(parallelsNum + 1) { 49 | for j in 0..<(slice + 1) { 50 | let vertexIndex = (i * (slice + 1) + j) * 5 51 | vertexArray[vertexIndex + 0] = (radius * sinf(angleStep * Float(i)) * sinf(angleStep * Float(j))) 52 | vertexArray[vertexIndex + 1] = (radius * cosf(angleStep * Float(i))) 53 | vertexArray[vertexIndex + 2] = (radius * sinf(angleStep * Float(i)) * cosf(angleStep * Float(j))) 54 | 55 | vertexArray[vertexIndex + 3] = Float(j) / Float(slice) 56 | vertexArray[vertexIndex + 4] = Float(1.0) - (Float(i) / Float(parallelsNum)) 57 | } 58 | } 59 | 60 | var vertexIndexTemp = 0 61 | for i in 0.. 90 | 91 | 1. 全景球 92 | 93 | 全景球是最简单的,我们只需要把球体放置在坐标系原点,然后我们的视角(即相机位置),放置在 z 轴上,同时需要大于球的半径,就能看到完整的球体。 94 | 95 | 2. 360度浏览 96 | 97 | 要想实现 360度环绕浏览,全景球还是放置在原点,把我们的视角放置在球体里面,那么我们就能 360 度环绕查看全景图了。所以我们的相机位置放在 z 轴上比球的半径小即可。 98 | 99 | 3. 小行星 100 | 101 | 小行星的效果需要与投影矩阵配合,当然上面的两种方式也需要投影矩阵配合,只是在小行星效果这里,投影的 FOV 比前两种的大,有种贴近球体看的感觉,所以我们的摄像机视角需要放置在刚刚好球的半径上。可以想象成我们在球体挖了个小孔,眼睛往里面看的样子。 102 | 103 | > 具体实现可以查看源码里的 [ViewTransform.swift](https://github.com/zyphs21/HSGLPanoViewer/blob/master/HSGLPanoViewer/ViewTransform.swift) 104 | 105 | ## 经验和踩坑分享 106 | 107 | ### 去除 GLKit API 弃用警告 108 | 109 | > 'GLKViewController' was deprecated in iOS 12.0: OpenGLES API deprecated. (Define GLES_SILENCE_DEPRECATION to silence these warnings) 110 | 111 | `GLKit` 相关的 API 从 iOS12 之后就已经标记为`弃用`了。 112 | 113 | 为了避免 Xcode 满屏的黄色警告⚠️,我们在 `Project--Build Settings` 里找到 `Preprocessor Macros` ,然后配置 `GLES_SILENCE_DEPRECATION=1` 即可把 OpenGLES 相关的弃用 API 警告去掉。这样 Xcode 的编辑界面就清爽很多了。 114 | 115 | ![image-20200912114634891](https://cdn.jsdelivr.net/gh/zhenwanping/cdn-assets@master/photo/20200912191829.png) 116 | 117 | 118 | 119 | ### 注意索引数组类型 120 | 121 | 在最后让 OpenGLES 进行绘图时,都会调用 `glDrawElements` 方法: 122 | 123 | ```swift 124 | glDrawElements(GLenum(GL_TRIANGLES), GLsizei(vertexIndices.count), GLenum(GL_UNSIGNED_SHORT), nil) 125 | ``` 126 | 127 | 这个方法的第三个参数是告诉 OpenGLES 当前索引数组的类型,比如上面指定的是 `GL_UNSIGNED_SHORT` 类型,所以我们的索引数组必须定义成是 `[GLushort]` 即数组里面的元素是 `UInt16` ,我们可以点击 GLushort 的定义是 `public typealias GLushort = UInt16)` 128 | 129 | 这里我当初很傻地犯了一个错误是:我点击 `GL_UNSIGNED_SHORT` 进去查看它的定义是 `public var GL_UNSIGNED_SHORT: Int32 { get }` ,然后下意识认为索引数组的元素类型是 `Int32` ,结果导致最后效果怎么都不对,最后才发现是这里被绕晕了。 130 | 131 | 132 | 133 | ### 数组所占内存大小 134 | 135 | OpenGLES 里有些方法是需要传递数值所占的内存大小。比如: 136 | 137 | ```swift 138 | // 将顶点数组复制到 GPU 中的顶点缓存区 139 | glBufferData(GLenum(GL_ARRAY_BUFFER), vertices.size(), vertices, GLenum(GL_STATIC_DRAW)) 140 | ``` 141 | 142 | 这里我给 Array 添加了一个扩展方法,能够比较方便地获取到数组实际所占内存的大小: 143 | 144 | ```swift 145 | extension Array { 146 | /// 根据数组类型和长度获取数组实际内存空间大小(Bytes) 147 | public func size() -> Int { 148 | return MemoryLayout.stride * self.count 149 | } 150 | } 151 | 152 | ``` 153 | 154 | ### 矩阵的构建 155 | 156 | 矩阵的构建和计算是一个较为复杂的部分,所以最好是把它交给程序。 157 | 158 | GLKit 中提供了不少便捷的类和方法,比如 159 | 160 | - `GLKMatrix4MakeLookAt` :构建摄像机视图矩阵 161 | 162 | - `GLKMatrix4MakePerspective` : 构建投影视图矩阵 163 | 164 | - `GLKMatrix4Multiply` : 用于矩阵的相乘 165 | 166 | - `GLKMatrix4RotateX` : 绕 x 轴旋转的旋转矩阵 167 | 168 | …… 169 | 170 | 171 | 172 | 利用这些方法在 GLKit 的实现中很方便,但是如何扩展利用在 GLSL 的实现呢?这里贴一下更新 MVP 矩阵的代码: 173 | 174 | ```swift 175 | private func updateMVPMatrix() { 176 | var modelViewMatrix = GLKMatrix4Identity 177 | modelViewMatrix = GLKMatrix4RotateX(modelViewMatrix, xAxisRotate) 178 | modelViewMatrix = GLKMatrix4RotateY(modelViewMatrix, yAxisRotate) 179 | modelViewMatrix = GLKMatrix4Multiply(panoViewType.viewTransform.viewMatrix, modelViewMatrix) 180 | 181 | let width = frame.size.width * UIScreen.main.scale 182 | let height = frame.size.height * UIScreen.main.scale 183 | let aspect = GLfloat(width / height) 184 | let projectionMatrix = panoViewType.viewTransform.projectionMatrix(aspect: aspect) 185 | 186 | // 最终的 MVP 矩阵 187 | var mvpMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix) 188 | 189 | // 这里取出 mvpMatrix.m 的指针操作 190 | let components = MemoryLayout.size(ofValue: mvpMatrix.m) / MemoryLayout.size(ofValue: mvpMatrix.m.0) 191 | withUnsafePointer(to: &mvpMatrix.m) { 192 | $0.withMemoryRebound(to: GLfloat.self, capacity: components) { 193 | glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "mvpMatrix"), 1, GLboolean(GL_FALSE), $0) 194 | } 195 | } 196 | } 197 | ``` 198 | 199 | 从上面的代码可以看到,我们通过 `GLKMatrix4` 的 `m` 属性拿到矩阵的数组数据,然后用 `withUnsafePointer` 的方式拿到数组的指针,以完成 `glUniformMatrix4fv` 的调用。 200 | 201 | ## 参考资料 202 | 203 | - [OpenGL-Tutorial-矩阵](http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-3-matrices/) 204 | - [How to Create a 360 Video Player](https://medium.com/@hanton.yang/how-to-create-a-360-video-player-with-opengl-es-3-0-and-glkit-360-3f29a9cfac88) 205 | - [OpenGLES3-Book](https://github.com/danginsburg/opengles3-book) 206 | --------------------------------------------------------------------------------