ऐप डेवलपर के रूप में, हम केवल कोडर नहीं हैं - हम निर्माता, निर्माता और कभी-कभी भ्रम फैलाने वाले हैं। ऐप डेवलपमेंट की कला सिर्फ कोड और डिज़ाइन से परे है। कभी-कभी, यह आश्चर्य और भ्रम के एक तत्व को तैयार करने के बारे में होता है जो उपयोगकर्ताओं का ध्यान आकर्षित करता है और एक गहन अनुभव बनाता है। इस बार, हम 2D दुनिया के अपने सुविधा क्षेत्र से बाहर निकल रहे हैं, और 3D की मनोरम दुनिया में एक साहसिक छलांग लगा रहे हैं।
UIKit यूजर इंटरफेस बनाने के लिए उपकरणों के एक सेट से कहीं अधिक है। यह एक शक्तिशाली टूलकिट है, जिसका सही उपयोग किए जाने पर अद्भुत दृश्य प्रभाव पैदा कर सकता है। इस लेख में, हम UIKit में गहरी खुदाई करेंगे और दर्पण जैसा प्रतिबिंब बनाने की तकनीक दिखाएंगे। यह प्रभाव आपके ऐप को दृष्टिगत रूप से प्रभावशाली और आकर्षक रूप दे सकता है जो आम तौर पर जटिल ग्राफ़िकल टूल के साथ ही प्राप्त किया जा सकता है, फिर भी इसे कोड के अलावा और कुछ नहीं बनाया गया है।
इस खूबसूरत, चमकदार क्यूब को देखें। इसमें कभी जंग नहीं लगेगा, क्योंकि इसमें किसी धातु का इस्तेमाल नहीं होता है।
अब, आइए जानें कि कोड का उपयोग करके इसे कैसे बनाया जाता है।
हमारे उद्देश्यों के लिए, UIKit क्वार्ट्ज कोर के ऊपर एक पतली परत के रूप में कार्य करता है, जो हमें इसकी 3डी क्षमताओं तक मुफ्त पहुंच प्रदान करता है। एक UIView
एक CALayer
ऑब्जेक्ट का संदर्भ रखता है, जो ऑन-स्क्रीन रेंडरिंग के लिए OS द्वारा उपयोग किया जाने वाला वास्तविक घटक है। CALayer
के तीन गुण हैं जो इसकी ऑन-स्क्रीन प्रस्तुति को प्रभावित करते हैं: स्थिति, सीमा और परिवर्तन। पहले दो काफी स्व-व्याख्यात्मक हैं, जबकि transform
किसी भी 4x4 मैट्रिक्स के साथ आरंभ किया जा सकता है। जब कई 3D परतों को एक साथ प्रस्तुत करने की आवश्यकता होती है, तो हमें एक विशेष CATransformLayer को नियोजित करना चाहिए, जो उन्हें 2D तल पर चपटा करने के बजाय उनकी बाल परतों के 3D स्थान को विशिष्ट रूप से संरक्षित करता है।
आइए एक साधारण घन बनाकर शुरू करें। सबसे पहले, हम प्रत्येक पक्ष की स्थिति को समायोजित करने के लिए एक सहायक कार्य करेंगे:
func setupFace( layer: CALayer, size: CGFloat, baseTransform: CATransform3D, translation: (x: CGFloat, y: CGFloat, z: CGFloat), rotation: (angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat) ) { layer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size, height: size)) var transform = baseTransform transform = CATransform3DTranslate(transform, translation.x, translation.y, translation.z) transform = CATransform3DRotate(transform, rotation.angle, rotation.x, rotation.y, rotation.z) layer.transform = transform }
अगला, हमारे ViewController के viewDidLoad
फ़ंक्शन के मुख्य भाग में, हम घन के सभी छह पक्षों को इकट्ठा करेंगे:
let cubeLayer = CATransformLayer() cubeLayer.position = CGPoint(x: view.bounds.midX, y: view.bounds.midY) view.layer.addSublayer(cubeLayer) let cubeSize: CGFloat = 200.0 var baseTransform = CATransform3DIdentity baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) baseTransform = CATransform3DRotate(baseTransform, -0.5, 1.0, 0.0, 0.0) let frontFace = CALayer() frontFace.isDoubleSided = false frontFace.backgroundColor = UIColor.blue.cgColor setupFace(layer: frontFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, 0.0, cubeSize * 0.5), rotation: (0.0, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(frontFace) let backFace = CALayer() backFace.isDoubleSided = false backFace.backgroundColor = UIColor.red.cgColor setupFace(layer: backFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, 0.0, -cubeSize * 0.5), rotation: (-.pi, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(backFace) let leftFace = CALayer() leftFace.isDoubleSided = false leftFace.backgroundColor = UIColor.green.cgColor setupFace(layer: leftFace, size: cubeSize, baseTransform: baseTransform, translation: (-cubeSize * 0.5, 0.0, 0.0), rotation: (-.pi * 0.5, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(leftFace) let rightFace = CALayer() rightFace.isDoubleSided = false rightFace.backgroundColor = UIColor.yellow.cgColor setupFace(layer: rightFace, size: cubeSize, baseTransform: baseTransform, translation: (cubeSize * 0.5, 0.0, 0.0), rotation: (.pi * 0.5, 0.0, 1.0, 0.0)) cubeLayer.addSublayer(rightFace) let topFace = CALayer() topFace.isDoubleSided = false topFace.backgroundColor = UIColor.cyan.cgColor setupFace(layer: topFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, -cubeSize * 0.5, 0.0), rotation: (.pi * 0.5, 1.0, 0.0, 0.0)) cubeLayer.addSublayer(topFace) let bottomFace = CALayer() bottomFace.isDoubleSided = false bottomFace.backgroundColor = UIColor.gray.cgColor setupFace(layer: bottomFace, size: cubeSize, baseTransform: baseTransform, translation: (0.0, cubeSize * 0.5, 0.0), rotation: (-.pi * 0.5, 1.0, 0.0, 0.0)) cubeLayer.addSublayer(bottomFace)
यहाँ यह कोड क्रिया में कैसा दिखता है:
यह निर्विवाद रूप से 3डी है, लेकिन कुछ अजीब लगता है, है ना? कला में 3डी परिप्रेक्ष्य की अवधारणा को पहली बार 15वीं शताब्दी में इतालवी पुनर्जागरण चित्रकारों ने महारत हासिल की थी। सौभाग्य से, हम केवल परिप्रेक्ष्य प्रक्षेपण मैट्रिक्स का उपयोग करके समान प्रभाव प्राप्त कर सकते हैं:
var baseTransform = CATransform3DIdentity baseTransform.m34 = -1.0 / 400.0 baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) baseTransform = CATransform3DRotate(baseTransform, -0.5, 1.0, 0.0, 0.0)
बेहतर है, है ना? m34 पर -1.0 / 400.0
पद वह है जो परिप्रेक्ष्य प्रभाव बनाता है। वास्तविक गणित के लिए, देखें https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/build-basic-perspective-projection-matrix.html
हमारा लक्ष्य दर्पण प्रभाव प्रदर्शित करना है, इसलिए हमें प्रतिबिंबित करने के लिए कुछ चाहिए। 3डी ग्राफ़िक्स में, क्यूब मैप्स आमतौर पर परावर्तक सतहों का अनुकरण करने के लिए उपयोग किए जाते हैं। हमारे उदाहरण में, हम पहले बनाए गए वास्तविक घन का उपयोग करके एक बना सकते हैं। सबसे पहले, हम छवियों को संबंधित चेहरों को असाइन करते हैं:
frontFace.contents = UIImage(named: "front")?.cgImage backFace.contents = UIImage(named: "back")?.cgImage leftFace.contents = UIImage(named: "left")?.cgImage rightFace.contents = UIImage(named: "right")?.cgImage topFace.contents = UIImage(named: "up")?.cgImage bottomFace.contents = UIImage(named: "down")?.cgImage
अगला, प्रत्येक चेहरे के लिए, हम isDoubleSided = true
सेट करते हैं और घन के आकार को cubeSize: CGFloat = 2000.0
। यह अनिवार्य रूप से क्यूब के अंदर "कैमरा" रखता है:
अगला, चूंकि हम एक साथ कई क्यूब्स बनाने जा रहे हैं, आइए सेटअप कार्यों को सरल बनाएं:
enum CubeFace: CaseIterable { case front case back case left case right case top case bottom func translationAndRotation(size: CGFloat) -> (translation: (x: CGFloat, y: CGFloat, z: CGFloat), rotation: (angle: CGFloat, x: CGFloat, y: CGFloat, z: CGFloat)) { switch self { case .front: return ((0.0, 0.0, size * 0.5), (0.0, 0.0, 1.0, 0.0)) case .back: return ((0.0, 0.0, -size * 0.5), (-.pi, 0.0, 1.0, 0.0)) case .left: return ((-size * 0.5, 0.0, 0.0), (-.pi * 0.5, 0.0, 1.0, 0.0)) case .right: return ((size * 0.5, 0.0, 0.0), (.pi * 0.5, 0.0, 1.0, 0.0)) case .top: return ((0.0, -size * 0.5, 0.0), (.pi * 0.5, 1.0, 0.0, 0.0)) case .bottom: return ((0.0, size * 0.5, 0.0), (-.pi * 0.5, 1.0, 0.0, 0.0)) } } func texture() -> UIImage? { ... } func color() -> UIColor { ... } } func setupFace( layer: CALayer, size: CGFloat, baseTransform: CATransform3D, face: CubeFace, textured: Bool ) { layer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size, height: size)) layer.isDoubleSided = textured let (translation, rotation) = face.translationAndRotation(size: size) var transform = baseTransform transform = CATransform3DTranslate(transform, translation.x, translation.y, translation.z) transform = CATransform3DRotate(transform, rotation.angle, rotation.x, rotation.y, rotation.z) layer.transform = transform if textured { layer.contents = face.texture()?.cgImage } else { layer.backgroundColor = face.color().cgColor } } func setupCube( view: UIView, size: CGFloat, textured: Bool, baseTransform: CATransform3D, faces: [CubeFace] ) -> CATransformLayer { let cubeLayer = CATransformLayer() cubeLayer.position = CGPoint(x: view.bounds.midX, y: view.bounds.midY) for face in faces { let faceLayer = CALayer() setupFace(layer: faceLayer, size: size, baseTransform: baseTransform, face: face, textured: textured) cubeLayer.addSublayer(faceLayer) } return cubeLayer }
अब, क्यूब मैप और छोटे क्यूब दोनों को एक साथ रेंडर करते हैं:
var baseTransform = CATransform3DIdentity baseTransform.m34 = -1.0 / 400.0 baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) view.layer.addSublayer(setupCube(view: view, size: 2000.0, textured: true, baseTransform: baseTransform)) view.layer.addSublayer(setupCube(view: view, size: 100.0, textured: false, baseTransform: baseTransform))
UIKit एक मजबूत ढांचा है, फिर भी इसमें जटिल दृश्य प्रभावों के लिए अंतर्निहित सुविधाओं का अभाव है। हालाँकि, यह वस्तुओं पर मनमाना मास्क लगाने की क्षमता प्रदान करता है, और ठीक यही हम दर्पण प्रभाव बनाने के लिए उपयोग करने जा रहे हैं। अनिवार्य रूप से, हम पर्यावरण को छह बार प्रस्तुत करेंगे, प्रत्येक संबंधित क्यूब फेस द्वारा नकाबपोश होगा।
पेचीदा पहलू यह है कि हम CATransformLayer
सीधे मास्क नहीं कर सकते। हालाँकि, हम इस सीमा को एक CALayer
कंटेनर के अंदर नेस्ट करके दरकिनार कर सकते हैं:
func setupReflectiveFace( view: UIView, size: CGFloat, baseTransform: CATransform3D, face: CubeFace ) -> CALayer { let maskLayer = CALayer() maskLayer.frame = view.bounds maskLayer.addSublayer(setupCube(view: view, size: size, textured: false, baseTransform: baseTransform, faces: [face])) let colorLayer = CALayer() colorLayer.frame = view.bounds colorLayer.mask = maskLayer colorLayer.addSublayer(setupCube(view: view, size: 2000.0, textured: true, baseTransform: baseTransform, faces: [.front, .back, .left, .right, .top, .bottom])) return colorLayer }
और अब, हमारा viewDidLoad इस तरह दिखना चाहिए:
var baseTransform = CATransform3DIdentity baseTransform.m34 = -1.0 / 400.0 baseTransform = CATransform3DRotate(baseTransform, 0.5, 0.0, 1.0, 0.0) for face in CubeFace.allCases { view.layer.addSublayer(setupReflectiveFace(view: view, size: 100.0, baseTransform: baseTransform, face: face)) }
यह छवि पहले से ही काफी मिलती-जुलती है जिसे हम हासिल करना चाहते थे, लेकिन इस बिंदु पर क्यूब क्यूब मैप पर केवल एक 3डी-एस्क्यू मास्क है। तो, हम इसे वास्तविक दर्पण में कैसे बदल सकते हैं?
यह पता चला है कि 3डी अंतरिक्ष में एक मनमाना विमान के सापेक्ष दुनिया को प्रतिबिंबित करने का एक सीधा तरीका है। जटिल गणित में जाने के बिना, यह वह मैट्रिक्स है जिसकी हम तलाश कर रहे हैं:
func mirrorMatrix(planePoint: Vector4D, planeTransform: CATransform3D, planeNormal: Vector4D) -> CATransform3D { let pt = applyTransform(transform: planeTransform, point: planePoint) let normalTransform = CATransform3DInvert(planeTransform).transposed let normal = applyTransform(transform: normalTransform, point: planeNormal).normalized() let a = normal.x let b = normal.y let c = normal.z let d = -(a * pt.x + b * pt.y + c * pt.z) return CATransform3D([ 1 - 2 * a * a, -2 * a * b, -2 * a * c, -2 * a * d, -2 * a * b, 1 - 2 * b * b, -2 * b * c, -2 * b * d, -2 * a * c, -2 * b * c, 1 - 2 * c * c, -2 * c * d, 0.0, 0.0, 0.0, 1.0 ]).transposed }
अगला, हम निम्नलिखित कोड को क्यूब सेटअप फ़ंक्शन में शामिल करते हैं:
func setupCube( view: UIView, size: CGFloat, textured: Bool, baseTransform: CATransform3D, faces: [CubeFace], mirrorFace: CubeFace? = nil ) -> CATransformLayer { ... if let mirrorFace { let mirrorPlane = mirrorFace.transform(size: size, baseTransform: baseTransform) let mirror = mirrorMatrix(planePoint: Vector4D(x: 0.0, y: 0.0, z: 0.0, w: 1.0), planeTransform: mirrorPlane, planeNormal: Vector4D(x: 0.0, y: 0.0, z: 1.0, w: 1.0)) cubeLayer.sublayerTransform = mirror } }
और अंत में, हम उस चमकदार घन को देख सकते हैं जिसके लिए हम प्रयास कर रहे हैं:
निश्चित रूप से, समान प्रभाव प्राप्त करना मेटल या मेटल-आधारित फ्रेमवर्क जैसे SceneKit के साथ आसान लग सकता है। लेकिन वे अपनी सीमाओं के सेट के साथ आते हैं। बड़ा एक? आप मेटल द्वारा खींची गई 3D सामग्री में लाइव UIKit दृश्य नहीं ला सकते।
इस आलेख में हमने जिस विधि को देखा है, वह हमें सभी प्रकार की सामग्री को 3D सेटिंग में प्रदर्शित करने देती है। इसमें मानचित्र, वीडियो और इंटरैक्टिव दृश्य शामिल हैं। साथ ही, यह आसानी से किसी भी UIKit एनिमेशन के साथ मिश्रित हो सकता है जिसका आप उपयोग करना चाहते हैं।
इस लेख का स्रोत कोड, कुछ सहायक कार्यों के साथ, https://github.com/petertechstories/uikit-mirrors पर पाया जा सकता है।
हैप्पी कोडिंग!