Android XR

Jetpack XR SDK में कस्टम 3D ज्यामिति की मदद से, 2D से आगे बढ़ना

स्पीकर

  • ईगोर युसोव

    सॉफ़्टवेयर इंजीनियर

  • जय अबी-साद

    सॉफ़्टवेयर इंजीनियर

इसके बारे में

बातचीत के बारे में जानकारी

अपने Android ऐप्लिकेशन को पारंपरिक 2D स्क्रीन से स्पेशल एनवायरमेंट में ट्रांज़िशन करने के लिए, शेप बनाने का एक नया तरीका अपनाना होगा.

इस सेशन में, हम Jetpack XR SDK में मौजूद कस्टम मेश एपीआई के बारे में जानेंगे. इसकी मदद से, रनटाइम के दौरान प्रोग्राम के हिसाब से 3D ज्यामिति जनरेट की जा सकती है. हम आपको मुख्य वर्कफ़्लो के बारे में बताएंगे. इसमें वर्टेक्स लेआउट तय करने से लेकर, उपयोगकर्ता की फ़िज़िकल स्पेस में डाइनैमिक ऑब्जेक्ट को आसानी से रेंडर करने तक की जानकारी शामिल है.

इस सेशन के आखिर तक, आपको कोड का इस्तेमाल करके इंटरैक्टिव और कस्टम 3D ज्यामिति बनाने की प्रैक्टिकल स्किल मिल जाएगी.

खास जानकारी

  • Jetpack SceneCore में glTF इंट्रोस्पेक्शन एपीआई, रन टाइम के दौरान मॉडल का मेटाडेटा दिखाता है. आपके पास एम्बेड किए गए ऐनिमेशन चलाने, सबनोड को बदलने या मटीरियल और टेक्स्चर के साथ मेश को बदलने का विकल्प होता है.
  • Jetpack SceneCore में मेश एपीआई शामिल हैं. इनकी मदद से, Kotlin में कस्टम 3D ज्यामिति जनरेट की जा सकती है. VertexLayout और MeshBuffer जैसे चार बिल्डिंग ब्लॉक, 3D टूल के बिना एक-एक करके ट्रायंगल मॉडल बनाते हैं.
  • डाइनैमिक मेश, रीयल-टाइम अपडेट के साथ काम करते हैं. इससे उपयोगकर्ताओं को पाथ बनाने या सरफ़ेस को ऐनिमेट करने में मदद मिलती है. सिर्फ़ बदले गए वर्टेक्स डेटा को अपडेट करने से, मेश या इकाई को फिर से बनाए बिना, बदलाव सीधे जीपीयू में स्ट्रीम हो जाते हैं.
  • Android Studio में Gemini, मेश एपीआई के लिए मुश्किल वर्टेक्स ऐनिमेशन और लाइटिंग मैथ लिख सकता है. एक प्रॉम्प्ट से ऐसा कोड जनरेट होता है जो वर्टेक्स ग्रिड को ऐनिमेट करता है और हर फ़्रेम में नॉर्मल को फिर से कैलकुलेट करता है.

संसाधन

Gemini Notebook

विषय

  • jetpack-xr
  • jetpack-scenecore
  • android-xr
  • 3d-geometry
  • mesh-api
  • gltf

ट्रांसक्रिप्ट

ट्रांसक्रिप्ट डाउनलोड करें

Android XR और SceneCore के बारे में जानकारी

  • नमस्ते दोस्तों.

  • मैं जय हूं.

  • और मैं ईगोर हूं.

  • हम Google में Jetpack XR पर काम करने वाले सॉफ़्टवेयर इंजीनियर हैं.

  • XREAL Aura जल्द ही लॉन्च होने वाला है. इसलिए, हम यह पक्का करना चाहते हैं कि आपके पास इस प्लैटफ़ॉर्म पर बेहतरीन स्पेशल एक्सपीरियंस बनाने के लिए ज़रूरी सभी टूल हों.

  • सबसे अच्छी बात यह है कि इसके लिए आपको एक्सआर हार्डवेयर की भी ज़रूरत नहीं है.

  • हम जो भी सुविधाएं दिखाएंगे वे सभी Android Studio में Android XR एम्युलेटर में आज उपलब्ध हैं.

  • हमारा मकसद आपको यह दिखाना है कि Jetpack SceneCore लाइब्रेरी की मदद से, इस प्लैटफ़ॉर्म पर बेहतरीन स्पेशल एक्सपीरियंस बनाने के लिए, आपको 3D की ज़्यादा जानकारी की ज़रूरत नहीं है.

  • आपको जो कुछ भी दिखेगा वह Android ऐप्लिकेशन में चल रहा है. इसे Android Studio में, Kotlin भाषा में लिखा गया है.

  • हमने हाल ही में Jetpack SceneCore को स्टेबल वर्शन पर प्रमोट किया है. आज हम अपने 3D मॉडल एपीआई की कुछ शानदार सुविधाओं के बारे में बताएंगे. साथ ही, हम अपने नए मेश एपीआई के बारे में भी बताएंगे. इनकी मदद से, तुरंत कस्टम 3D ज्यामिति बनाई जा सकती है.

glTF 3D मॉडल के साथ काम करना

  • चलिए, 3D मॉडल से शुरुआत करते हैं.

  • 3D मॉडल के लिए, सबसे पहले हमें एक फ़ाइल फ़ॉर्मैट की ज़रूरत होती है.

  • Android XR का मुख्य मकसद, ओपन स्टैंडर्ड को बढ़ावा देना है.

  • इसलिए, हमने Khronos Group के glTF फ़ॉर्मैट का इस्तेमाल किया.

  • अगर आप 3D की दुनिया से नहीं हैं, तो हो सकता है कि आप पूछें, "glTF क्या है?"

  • इसे 3D मॉडल के लिए JPEG की तरह समझा जा सकता है.

  • glTF ऐसेट स्ट्रक्चर से, ऐनिमेशन, सबनोड, और मेश जैसी चीज़ों का डेटा मिलता है.

    {
        "asset" : {...},
        "nodes" : [
            {
                "name" : "Right_Arm",
                "rotation" : [...],
                "scale" : [...],
                "translation" : [...],
            },
        ],
        "animations" : [
            {
                "name" : "Wave",
            },
        ],
        "meshes" : [
            {
                "name" : "Torso",
                "primitives" : [ ... ],
            },
        ],
    
        // Way way more!
    
    }
  • हालांकि, इस बातचीत के लिए, सबनोड को मॉडल के हिस्सों के तौर पर माना जा सकता है. हर हिस्से की अपनी पोज़िशन और रोटेशन होती है.

  • मेश, वे सतहें होती हैं जो आपको दिखती हैं. वहीं, ऐनिमेशन उन सबनोड और मेश की गतिविधियां होती हैं जिन्हें ऐसेट के लेखक ने पहले से ही बेक किया होता है.

  • चलिए.

  • अब आपके मन में यह सवाल आ सकता है कि "मुझे glTF मॉडल कहां से मिलेगा?"

  • पहले से मौजूद कई मॉडल के अलावा, आपके पास अपना मॉडल बनाने का विकल्प भी है.

  • अब हमने glTF फ़ॉर्मैट की बुनियादी बातें जान ली हैं. चलिए, इसे अपने ऐप्लिकेशन में इस्तेमाल करते हैं.

  • हम Jetpack SceneCore का इस्तेमाल करके, Android XR के एक बेसिक प्रोजेक्ट से शुरुआत करते हैं.

  • आपको उपयोगकर्ता के स्पेस में, फ़्लोटिंग गतिविधि वाला एक स्पेशल पैनल दिखेगा.

  • इसे अपनी पसंद के मुताबिक पकड़ा जा सकता है, एक जगह से दूसरी जगह ले जाया जा सकता है, और इसका साइज़ बदला जा सकता है.

  • हालांकि, इस बातचीत में 3D के बारे में ही बात की गई है, लेकिन Jetpack XR SDK के बाकी हिस्सों को भी ज़रूर देखें.

  • ठीक है, स्पेशल पैनल मज़ेदार हैं, लेकिन हमें और जादू चाहिए.

  • अपने इफ़ेक्ट को और शानदार बनाने के लिए, 3D मॉडल जोड़ने से बेहतर और क्या हो सकता है?

  • हमारे डिज़ाइनर ने कार का यह glTF मॉडल बनाया है.

    // Load assets/models/car.glb
    val carModel = GltfModel.create(
        session,
        Paths.get("models", "car.glb")
    )
    
    // Create an entity in the scene
    val carEntity = GltfModelEntity.create(
        session,
        carModel,
        parent = session.scene.activitySpace
    )
  • उन्होंने इसमें ऐनिमेशन भी एम्बेड किए हैं, जिनका इस्तेमाल हम बाद में करेंगे.

  • SceneCore का इस्तेमाल करके, मैं 3D मॉडल एपीआई की मदद से इसे सीधे अपने सीन में लोड कर सकता हूं.

मॉडल ऐनिमेशन और सबनोड में बदलाव करना

  • हालांकि, कार को थोड़ा और बेहतर बनाते हैं.

  • क्या आपको glTF में मौजूद रिच मेटाडेटा याद है?

  • नए glTF इंट्रोस्पेक्शन एपीआई की मदद से, अब इसे ऐक्सेस किया जा सकता है. इसकी शुरुआत ऐनिमेशन से की जा सकती है.

    // Access every animation in car.glb
    val animations = carEntity.getAnimations()
       .associateBy { it.name }
    
    // Open the doors...
    animations["lf_door_open"]?.start()
    animations["rt_door_open"]?.start()
    
    // ...and close them again
    animations["lf_door_close"]?.start()
    animations["rt_door_close"]?.start()
    
    // Same for the hood and trunk
    animations["hood_open"]?.start()
    animations["trunk_open"]?.start()
  • कोड की कुछ ही लाइनों में, मैं कार के मॉडल में मौजूद किसी भी glTF ऐनिमेशन को अलग से चला सकता हूं. जैसे, दरवाज़े, हुड या ट्रंक को खोलना और बंद करना.

  • हालांकि, ऐनिमेशन के अलावा, अन्य मेटाडेटा भी ऐक्सेस किया जा सकता है.

  • glTF introspection API, मॉडल में सबनोड को GltfModelNode के तौर पर भी दिखाता है. इससे आपको उन्हें अपनी पसंद के मुताबिक मूव करने, घुमाने, और स्केल करने की सुविधा मिलती है.

    // Wheel sub nodes, named by our designer
    val nodes = carEntity.nodes.associateBy { it.name }
    val front = listOf("lf_frontWheel_geo",
       "rt_fontWheel_geo").map { nodes[it]!! }
    val rear = listOf("lf_RearWheel_geo",
       "rt_RearWheel_geo").map { nodes[it]!! }
    
    // Update a sub node's transform at runtime
    fun GltfModelNode.rotate(q: Quaternion) {
       localPose = localPose.copy(rotation = q)
    }
    
    // Each frame: steer front, spin all four
    fun drive(steerDeg: Float, spinDeg: Float) {
       val steer = Quaternion.fromAxisAngle(
           Vector3.Up, steerDeg)
       val spin = Quaternion.fromAxisAngle(
           Vector3.Right, spinDeg)
       front.forEach { it.rotate(steer * spin) }
       rear.forEach { it.rotate(spin) }
    }
  • उदाहरण के लिए, यहां कार के चलने पर, पहियों की पोज़िशन और रोटेशन को अलग-अलग अपडेट किया जा सकता है.

  • साफ़ तौर पर बता दें कि आपको जो दिख रहा है वह एम्बेड किया गया glTF ऐनिमेशन नहीं है.

  • यहां, मैं रन टाइम के दौरान हर पहिए को अलग-अलग घुमा रहा हूं और कंट्रोल कर रहा हूं.

मटीरियल और टेक्सचर लागू करना

  • glTF मॉडल में मौजूद किसी भी मेश को ऐक्सेस किया जा सकता है. यह मेटाडेटा का आखिरी हिस्सा है.

  • अब ईगोर, मेश के बारे में ज़्यादा जानकारी देंगे.

  • फ़िलहाल, चलिए अपनी कार को स्टाइल करते हैं.

  • टेक्सचर एपीआई का इस्तेमाल करके, हम पीएनजी इमेज टेक्सचर लोड कर सकते हैं. जैसे, मेटल पैटर्न.

    // Load a PNG with the Texture API
    val metal = Texture.create(
       session, Paths.get("textures", "metal.png"))
    
    // Create KhronosPbrMaterials
    val (body, doors, hood) = List(3) {
       KhronosPbrMaterial
           .create(session, AlphaMode.OPAQUE)
    }
    body.setBaseColorFactor(CYAN)
    doors.setBaseColorFactor(PINK)
    hood.setBaseColorTexture(metal)
    
    // Override meshes by sub node name
    val nodes = carEntity.nodes.associateBy { it.name }
    nodes["mainBody_geo"]?.setMaterialOverride(body)
    nodes["lf_door_geo"]?.setMaterialOverride(doors)
    nodes["rt_door_geo"]?.setMaterialOverride(doors)
    nodes["hood_geo"]?.setMaterialOverride(hood)
  • इसके बाद, मटीरियल एपीआई की मदद से, हम इसे सीधे KhronosPbrMaterial में प्लग कर सकते हैं.

  • अगर आप 3D के बारे में ज़्यादा नहीं जानते, तो मेश को टेबलटॉप, मटीरियल को टेबलक्लॉथ, और टेक्सचर को उस कपड़े पर प्रिंट किए गए पैटर्न के तौर पर समझा जा सकता है.

  • हालांकि, यहां टेबलटॉप के बजाय, हम अपने बनाए गए मटीरियल ऑब्जेक्ट का इस्तेमाल करते हैं. इससे कार के हुड और दरवाज़ों के सबनोड से जुड़ी मेश को बदला जा सकता है.

  • कोड की कुछ ही लाइनों में, हमने अपनी कार के लुक और स्टाइल को बदल दिया है. वह भी रन टाइम पर.

  • यह तो कमाल की बात है, लेकिन हमारे पास इससे भी बेहतर आइडिया है.

  • कार के लिए रेस ट्रैक बनाना कितना मज़ेदार होगा?

  • यहां पहले से बना glTF मॉडल सही नहीं है.

  • इसके बजाय, हमें तुरंत 3D ज्यामिति जनरेट करने की सुविधा चाहिए.

  • इसलिए, मैं ईगोर को बुलाना चाहूंगा, ताकि वे आपको हमारे नए Jetpack SceneCore मेश एपीआई के बारे में बता सकें.

मेश एपीआई के बारे में जानकारी

  • धन्यवाद, जय.

  • चलिए, इसे ऐसा बनाते हैं कि उपयोगकर्ता अपने लिविंग रूम में अपनी उंगली से रेस ट्रैक बना सके.

  • ट्रैक को ड्रॉ करने से पहले, वह मौजूद नहीं होता. इसलिए, कोई भी व्यक्ति उसे पहले से मॉडल नहीं कर सकता.

  • हम इसे कोड में बनाएंगे, एक बार में एक त्रिभुज.

  • मेश की शुरुआत वर्टिसेस से होती है. ये स्पेस में मौजूद पॉइंट होते हैं.

    // 4 vertices: x, y, z
    val positions = floatArrayOf(
        0f, 0f, 0f,   // v0
        1f, 0f, 0f,   // v1
        0f, 1f, 0f,   // v2
        1f, 1f, 0f    // v3
    )
    
    // 2 triangles = 6 indices
    val indices = intArrayOf(
        0, 1, 2,
        2, 1, 3
    )
  • हर वर्टेक्स में पोज़िशन, लाइटिंग के लिए नॉर्मल, रंग, और टेक्सचर कोऑर्डिनेट जैसे एट्रिब्यूट होते हैं.

  • इंडेक्स, अलग-अलग सोर्स में मौजूद जानकारी को एक ही जगह उपलब्ध कराते हैं.

  • हर तीन पॉइंट से एक त्रिकोण बनता है.

  • दो त्रिभुज मिलकर एक वर्ग बनाते हैं, जिसमें छह इंडेक्स होते हैं, लेकिन सिर्फ़ चार वर्टिसेस होते हैं, क्योंकि वे दो वर्टिसेस शेयर करते हैं.

  • आगे बढ़ते रहें और आपको एक क्यूब मिलेगा.

  • हर 3D मॉडल को इसी तरह बनाया जाता है. जैसे, जय की कार. बस इसमें ज़्यादा ट्रायंगल होते हैं.

मेश एपीआई के बिल्डिंग ब्लॉक

  • Mesh API, चार बिल्डिंग ब्लॉक के साथ इस पर मैप करता है: VertexLayout, MeshBuffer, CustomMesh, और MeshEntity.

  • आइए, एक-एक करके इनके बारे में जानते हैं.

  • सबसे पहले, VertexLayout.

    // How to read each vertex from bytes
    val layout = VertexLayout.Builder()
        .addAttribute(POSITION, FLOAT3) // 12 B
        .addAttribute(NORMAL, FLOAT3)   // 12 B
        .addAttribute(UV0, FLOAT2)      //  8 B
        .build()              // stride: 32 B
    
    // Offsets and stride are computed,
    // or set them explicitly.
    
    // Attributes can live in separate buffers
    val split = VertexLayout.Builder()
        .addAttribute(POSITION, FLOAT3)
        .startNextBuffer()
        .addAttribute(COLOR, UBYTE4_NORM)
        .build()
  • आपका वर्टेक्स डेटा, ByteBuffer में सिर्फ़ बाइट होता है. साथ ही, हर मेश उन्हें अलग-अलग तरीके से पैक करता है.

  • VertexLayout से पता चलता है कि आपके वर्टेक्स को कैसे पढ़ा जाए: कौनसे एट्रिब्यूट हैं, किस टाइप के हैं, और हर एक कहां मौजूद है.

  • यहां, यह एक पोज़िशन, एक नॉर्मल, और यूवी है. ये टेक्सचर कोऑर्डिनेट हैं, जो कुल 32 बाइट के हैं.

  • एट्रिब्यूट को अलग-अलग बफ़र में भी रखा जा सकता है, ताकि सिर्फ़ उन एट्रिब्यूट को अपडेट किया जा सके जिनमें बदलाव हुआ है.

  • दूसरा, MeshBuffer, जो रॉ डेटा के लिए एक कंटेनर है.

    // Vertex bytes + 32-bit indices
    val meshBuffer = MeshBuffer.create(
        session, layout,
        vertexData = listOf(ByteBufferRegion(
            vertexBytes, 0, vertexBytes.capacity())),
        indexData = ByteBufferRegion(
            indexBytes, 0, indexBytes.capacity()))
    
    // Copied to the GPU: reuse your
    // ByteBuffers right away
    
    // Need updates at runtime?
    val dynamic = MeshBuffer.createDynamic(
        session, layout,
        vertexCount = 1000, indexCount = 3000)
  • आपको इसे लेआउट, वर्टेक्स बाइट, और 32-बिट इंडेक्स देने होंगे.

  • डेटा को तुरंत जीपीयू में कॉपी कर दिया जाता है, ताकि आप अपने ByteBuffer को फिर से इस्तेमाल कर सकें या रिलीज़ कर सकें.

  • डिफ़ॉल्ट रूप से, MeshBuffer स्टैटिक होता है.

  • इसे एक बार सेट किया जाता है और तेज़ी से रेंडर करने के लिए ऑप्टिमाइज़ किया जाता है.

  • अगर आपको रनटाइम के दौरान इसे बदलना है, तो createDynamic एक तय क्षमता असाइन करता है. इसे बाद में अपडेट किया जा सकता है.

  • तीसरा, CustomMesh, MeshBuffer का व्यू.

    // A view: which indices to draw, and how
    val road = CustomMesh
        .BuilderFromMeshBuffer(session, meshBuffer)
        // subset 0: asphalt
        .addSubset(TRIANGLES, indexOffset = 0,
            indexCount = roadCount)
        // subset 1: lane markings
        .addSubset(TRIANGLES,
            indexOffset = roadCount,
            indexCount = lineCount)
        .build()
    
    // Shortcut: builds MeshBuffer + CustomMesh
    CustomMesh.BuilderFromMeshData(session, layout)
  • इससे कोई डेटा कॉपी नहीं होता.

  • यह बताता है कि इंडेक्स की किस रेंज को ड्रॉ करना है और कैसे, जैसे कि ट्रायंगल या ट्रायंगल स्ट्रिप.

  • यहां, डामर और लेन मार्किंग एक ही MeshBuffer में मौजूद हैं.

  • एक से ज़्यादा कस्टम मेश, एक ही MeshBuffer शेयर कर सकते हैं. जब आपको शेयर करने की ज़रूरत न हो, तब BuilderFromMeshData एक ही चरण में मेश और बफ़र बनाता है.

  • आखिर में, MeshEntity मेश को सीन में डालता है.

    // Place the mesh in the scene graph
    val roadEntity = MeshEntity.create(
        session,
        mesh = road,
        // one material per subset
        materials = listOf(asphalt, paint),
        pose = Pose(Vector3(0f, 0f, -1f)),
        parent = session.scene.activitySpace)
    
    // It's an Entity: move it, re-parent it
    roadEntity.setPose(Pose(Vector3(0f, -0.5f, -1f)))
    roadEntity.parent = tableAnchor
    
    // Same mesh, another entity
    val ghost = MeshEntity.create(
        session, road, listOf(glass, glass))
  • मेश क्या है.

  • इकाई से पता चलता है कि कोई चीज़ कहां है.

  • पैनल और जय की कार की तरह, यह भी एक इकाई है. इसलिए, इसकी एक पोज़िशन और एक पैरंट है. पैरंट वह चीज़ है जिससे यह जुड़ा हुआ है.

  • पैरंट को मूव करें. इससे सड़क भी मूव हो जाएगी.

  • हर सबसेट के लिए एक मटीरियल भी दिया जाता है, ताकि एक ही मेश अलग-अलग इकाइयों में अलग-अलग मटीरियल के साथ दिख सके.

कस्टम और डाइनैमिक मेश बनाना

  • अब हम इन सभी को एक साथ जोड़कर, सड़क का मेश बनाते हैं.

    // Describe a vertex
    val layout = VertexLayout.Builder()
        .addAttribute(POSITION, FLOAT3)
        .addAttribute(NORMAL, FLOAT3)
        .addAttribute(UV0, FLOAT2)
        .build()
    
    // Path points -> ribbon of triangles
    val (vertices, indices) = buildRoad(path)
    
    // Build the mesh...
    val roadMesh = CustomMesh
        .BuilderFromMeshData(session, layout)
        .addVertexData(vertices)
        .setIndexData(indices)
        .setTopology(TRIANGLES)
        .build()
    
    // ...and put it in the scene
    val road = MeshEntity.create(
        session, roadMesh, listOf(asphalt),
        parent = session.scene.activitySpace)
  • हम वर्टेक्स को तय करते हैं: पोज़िशन, नॉर्मल, और यूवी, टेक्सचर कोऑर्डिनेट.

  • एक छोटा हेल्पर फ़ंक्शन, उपयोगकर्ता के पाथ को रिबन में बदल देता है.

  • पाथ के हर पॉइंट के लिए, यह हर तरफ़ दो वर्टिसेस और दो ट्रायंगल जोड़ता है, जो उन्हें अगले पेयर से कनेक्ट करते हैं.

  • यह Kotlin का सामान्य कोड है, जो ByteBuffer में बाइट लिखता है.

  • आपको किसी भी तरह के 3D टूल की ज़रूरत नहीं है.

  • BuilderFromMeshData, वह शॉर्टकट है जिसे हमने अभी देखा. यह मेश और MeshBuffer बनाता है. साथ ही, हमारे लिए बाउंड की गणना करता है.

  • आखिर में, MeshEntity.create ने सीन में roadMesh को ऐस्फ़ॉल्ट मटीरियल के साथ जोड़ा.

  • लेकिन हम चाहते हैं कि उपयोगकर्ता के ड्रॉ करते ही, हमारा ट्रैक लाइव हो जाए.

  • इसके लिए, हम डाइनैमिक मेश का इस्तेमाल करते हैं.

    // Fixed GPU capacity, allocated once
    val track = CustomMesh.BuilderFromMeshData(
            session, layout,
            maxVertices = 2 * MAX_POINTS,
            maxIndices = 6 * MAX_POINTS)
        .setTopology(TRIANGLES)
        // Covers everything the track can become
        .setBounds(playArea)
        .build()
    
    // Hand moved: upload only the new segment
    val buffer = track.meshBuffer
    buffer.updateVertexData(
        bufferIndex = 0,
        vertexData = segmentVertices,
        offsetInBytes = n * VERTEX_BYTES)
    buffer.updateIndexData(
        segmentIndices,
        offsetInBytes = n * INDEX_BYTES)
  • फ़र्क़ सिर्फ़ इतना है कि हम बिल्डर को पहले ही ज़्यादा से ज़्यादा क्षमता दे देते हैं, ताकि जीपीयू बफ़र को असाइन किया जा सके.

  • इसके बाद, जब उपयोगकर्ता ड्रॉ करता है, तब updateVertexData और updateIndexData सिर्फ़ नए सेगमेंट अपलोड करते हैं, न कि पूरा मेश.

  • एक बात का ध्यान रखें: रेंडरर, स्क्रीन से बाहर की चीज़ों को स्किप करने के लिए, बाउंड का इस्तेमाल करता है. इसलिए, बाउंड को इस तरह सेट करें कि ट्रैक में मौजूद हर चीज़ कवर हो जाए. जैसे, यहां मौजूद प्ले एरिया.

  • आपको यह लग सकता है कि एपीआई का इस्तेमाल करना मुश्किल नहीं है.

  • सबसे मुश्किल काम है वर्टेक्स मैथ, नॉर्मल, टेक्स्चर कोऑर्डिनेट, और ट्रायंगल ऑर्डर.

  • एआई इस तरह के कोड लिखने में माहिर है.

  • मान लीजिए कि हमें एक कदम और आगे बढ़ना है.

  • Now Now, मुझे डेस्टिनेशन पर हवा में लहराता हुआ चेकर फ़्लैग चाहिए.

Gemini की मदद से ऐनिमेटेड मेश जनरेट करना

  • वर्टेक्स ऐनिमेशन के लिए मैथ का इस्तेमाल करना मुश्किल काम है. इसलिए, मैंने Gemini से मदद मांगी.

  • Android Studio में, मैंने इसे एक प्रॉम्प्ट दिया: "Jetpack SceneCore Mesh API का इस्तेमाल करके, चेकरबोर्ड यूवी के साथ 20x12 फ़्लैग ग्रिड बनाएं. साथ ही, updateVertexData की मदद से हर फ़्रेम में हवा की लहर को ऐनिमेट करें और नॉर्मल को फिर से कैलकुलेट करें, ताकि रोशनी फ़ोल्ड के हिसाब से दिखे."

  • कुछ सेकंड बाद, मुझे काम करने वाला कोड मिल गया.

  • आइए, देखते हैं कि इससे क्या हुआ.

  • इसने 20 बाई 12 सेल की ग्रिड के तौर पर फ़्लैग मेश बनाया. इसमें हर सेल में दो त्रिकोण थे. साथ ही, इसने लेआउट के लिए एक स्मार्ट विकल्प चुना.

    // 20 x 12 cells = 273 vertices
    val layout = VertexLayout.Builder()
        // buffer 0: updated every frame
        .addAttribute(POSITION, FLOAT3)
        .addAttribute(NORMAL, FLOAT3)
        .startNextBuffer()
        // buffer 1: never changes
        .addAttribute(UV0, FLOAT2)
        .build()
    
    val flagMesh = CustomMesh.BuilderFromMeshData(
            session, layout,
            maxVertices = 273, maxIndices = 1440)
        .addVertexData(restPose)   // buffer 0
        .addVertexData(checkerUVs) // buffer 1
        .setIndexData(gridIndices)
        .setTopology(TRIANGLES)
        .setBounds(swingBounds)
        .build()
  • पोज़िशन और नॉर्मल को बफ़र 0 में रखा जाता है. साथ ही, इन्हें हर फ़्रेम में अपडेट किया जाता है.

  • टेक्सचर कोऑर्डिनेट, बफ़र 1 में रखे जाते हैं और वे कभी नहीं बदलते.

  • मेश डाइनैमिक है और बाउंड को इस तरह सेट किया गया है कि वे झंडे के पूरे स्विंग को कवर कर सकें.

  • हर फ़्रेम में, हर वर्टेक्स को एक साइन वेव मिलती है, जो खुले किनारे की ओर बढ़ती है. इससे पोल वाला हिस्सा अपनी जगह पर बना रहता है.

    // Gemini wrote the math
    fun wave(t: Float) {
        for (i in 0 until 273) {
            val u = (i % 21) / 20f  // 0 at the pole
            setZ(i, 0.06f * u * sin(6f * u - 4f * t))
        }
        recomputeNormals()          // from neighbors
    }
    
    // Every frame: one upload (~6.5 KB)
    while (isActive) withFrameNanos { ns ->
        wave(ns / 1e9f)
        flagMesh.meshBuffer.updateVertexData(
            bufferIndex = 0,
            vertexData = ByteBufferRegion(
                posNormals, 0, 273 * 24))
    }
  • नॉर्मल को आस-पास की पोज़िशन से फिर से कैलकुलेट किया जाता है. इससे फ़ोल्ड पर रोशनी पड़ती है.

  • updateVertexData हर फ़्रेम में जीपीयू पर करीब 6.5 केबी डेटा अपलोड करता है.

  • हम मेश या इकाई को फिर से नहीं बनाते हैं.

  • अब चलिए, इन सभी को एक साथ देखते हैं.

  • हम कार के मॉडल को टेबल पर रखते हैं. इसके बाद, ARCore का इस्तेमाल करके ट्रैक बनाते हैं.

  • हमने जो फ़्लैग मेश बनाया है वह डेस्टिनेशन पर दिखता है.

  • कार ट्रैक पर चलती है. हम कार के पहियों को घुमाने और उन्हें कंट्रोल करने के लिए, glTF Introspection API का इस्तेमाल करते हैं.

  • मेश, अस्फ़ाल्ट मटीरियल के साथ दिखता है. हालांकि, सड़क के लुक और स्टाइल को बदलने के लिए, मेश एपीआई के साथ-साथ मटीरियल एपीआई का भी इस्तेमाल किया जा सकता है. जैसा कि वीडियो में देखा जा सकता है.

  • यह एक बेहतरीन सुविधा है, जो आपके लिए आसानी से उपलब्ध है.

खास जानकारी और इस्तेमाल शुरू करना

  • आज हमने अपने ऐप्लिकेशन को 2D से स्पेशल बनाने के कई तरीके बताए हैं.

  • glTF Introspection API की मदद से, अब सीधे glTF मॉडल के इनर मेटाडेटा को ऐक्सेस किया जा सकता है. इससे, एम्बेड किए गए ऐनिमेशन ट्रिगर किए जा सकते हैं, अलग-अलग सबनोड को बदला जा सकता है, और रनटाइम के दौरान उसके लुक और स्टाइल को बदलने के लिए मटीरियल भी बदले जा सकते हैं.

  • इसके बाद, हमने देखा कि जब पहले से बने 3D मॉडल काफ़ी नहीं थे, तब हम कस्टम ज्यामिति बनाने के लिए, अपने नए मेश एपीआई का इस्तेमाल कर सकते थे.

  • चाहे आपको स्टैटिक शेप को नए सिरे से बनाना हो या डाइनैमिक शेप को रीयल टाइम में स्ट्रीम करना हो, अब आपके पास ऐसा करने के लिए टूल मौजूद हैं.

  • जैसा कि ईगोर ने अभी दिखाया, इन कॉम्पोनेंट को Gemini के साथ जोड़ने का मतलब है कि आपको कैलकुलेशन करने की ज़रूरत नहीं है.

  • अब मैं अपनी बात दोहराना चाहूंगा: आपने जो कुछ भी देखा वह Android ऐप्लिकेशन में चल रहा था. इसे Android Studio में, Kotlin में लिखा गया था.

  • आज ऑडियंस में मुझे कई लैपटॉप दिख रहे हैं. मुझे उम्मीद है कि मैंने और ईगोर ने आपको Android Studio खोलने और Android XR एम्युलेटर को आज ही इस्तेमाल करने के लिए काफ़ी उत्साहित किया है.

  • अपनी कल्पना को उड़ान दें और Android से जुड़ी अपनी मौजूदा स्किल का इस्तेमाल करें. साथ ही, आज हमने जो बताया है उसका इस्तेमाल करके, XR के शानदार अनुभव बनाएं.

  • ईगोर और मेरी तरफ़ से, आज हमारे साथ जुड़ने के लिए आपका बहुत-बहुत धन्यवाद. Android Dev Summit के बाकी सेशन का आनंद लें.

कोड

कोड स्निपेट

"Working with glTF 3D models" में 01:22 पर JSON स्निपेट
{
    "asset" : {...},
    "nodes" : [
        {
            "name" : "Right_Arm",
            "rotation" : [...],
            "scale" : [...],
            "translation" : [...],
        },
    ],
    "animations" : [
        {
            "name" : "Wave",
        },
    ],
    "meshes" : [
        {
            "name" : "Torso",
            "primitives" : [ ... ],
        },
    ],

    // Way way more!

}
"glTF 3D मॉडल के साथ काम करना" में 02:28 पर Kotlin स्निपेट
// Load assets/models/car.glb
val carModel = GltfModel.create(
    session,
    Paths.get("models", "car.glb")
)

// Create an entity in the scene
val carEntity = GltfModelEntity.create(
    session,
    carModel,
    parent = session.scene.activitySpace
)
"मॉडल ऐनिमेशन और सबनोड मैनिप्युलेशन" में 02:50 पर Kotlin स्निपेट
// Access every animation in car.glb
val animations = carEntity.getAnimations()
   .associateBy { it.name }

// Open the doors...
animations["lf_door_open"]?.start()
animations["rt_door_open"]?.start()

// ...and close them again
animations["lf_door_close"]?.start()
animations["rt_door_close"]?.start()

// Same for the hood and trunk
animations["hood_open"]?.start()
animations["trunk_open"]?.start()
"मॉडल ऐनिमेशन और सबनोड मैनिपुलेशन" में 03:12 पर Kotlin स्निपेट
// Wheel sub nodes, named by our designer
val nodes = carEntity.nodes.associateBy { it.name }
val front = listOf("lf_frontWheel_geo",
   "rt_fontWheel_geo").map { nodes[it]!! }
val rear = listOf("lf_RearWheel_geo",
   "rt_RearWheel_geo").map { nodes[it]!! }

// Update a sub node's transform at runtime
fun GltfModelNode.rotate(q: Quaternion) {
   localPose = localPose.copy(rotation = q)
}

// Each frame: steer front, spin all four
fun drive(steerDeg: Float, spinDeg: Float) {
   val steer = Quaternion.fromAxisAngle(
       Vector3.Up, steerDeg)
   val spin = Quaternion.fromAxisAngle(
       Vector3.Right, spinDeg)
   front.forEach { it.rotate(steer * spin) }
   rear.forEach { it.rotate(spin) }
}
"मटीरियल और टेक्सचर लागू करना" में 03:51 पर Kotlin स्निपेट
// Load a PNG with the Texture API
val metal = Texture.create(
   session, Paths.get("textures", "metal.png"))

// Create KhronosPbrMaterials
val (body, doors, hood) = List(3) {
   KhronosPbrMaterial
       .create(session, AlphaMode.OPAQUE)
}
body.setBaseColorFactor(CYAN)
doors.setBaseColorFactor(PINK)
hood.setBaseColorTexture(metal)

// Override meshes by sub node name
val nodes = carEntity.nodes.associateBy { it.name }
nodes["mainBody_geo"]?.setMaterialOverride(body)
nodes["lf_door_geo"]?.setMaterialOverride(doors)
nodes["rt_door_geo"]?.setMaterialOverride(doors)
nodes["hood_geo"]?.setMaterialOverride(hood)
"मेश एपीआई के बारे में जानकारी" में 05:05 पर Kotlin स्निपेट
// 4 vertices: x, y, z
val positions = floatArrayOf(
    0f, 0f, 0f,   // v0
    1f, 0f, 0f,   // v1
    0f, 1f, 0f,   // v2
    1f, 1f, 0f    // v3
)

// 2 triangles = 6 indices
val indices = intArrayOf(
    0, 1, 2,
    2, 1, 3
)
"मेश एपीआई के बिल्डिंग ब्लॉक" में 05:45 पर Kotlin स्निपेट
// How to read each vertex from bytes
val layout = VertexLayout.Builder()
    .addAttribute(POSITION, FLOAT3) // 12 B
    .addAttribute(NORMAL, FLOAT3)   // 12 B
    .addAttribute(UV0, FLOAT2)      //  8 B
    .build()              // stride: 32 B

// Offsets and stride are computed,
// or set them explicitly.

// Attributes can live in separate buffers
val split = VertexLayout.Builder()
    .addAttribute(POSITION, FLOAT3)
    .startNextBuffer()
    .addAttribute(COLOR, UBYTE4_NORM)
    .build()
"मेश एपीआई के बिल्डिंग ब्लॉक" में 06:10 पर Kotlin स्निपेट
// Vertex bytes + 32-bit indices
val meshBuffer = MeshBuffer.create(
    session, layout,
    vertexData = listOf(ByteBufferRegion(
        vertexBytes, 0, vertexBytes.capacity())),
    indexData = ByteBufferRegion(
        indexBytes, 0, indexBytes.capacity()))

// Copied to the GPU: reuse your
// ByteBuffers right away

// Need updates at runtime?
val dynamic = MeshBuffer.createDynamic(
    session, layout,
    vertexCount = 1000, indexCount = 3000)
"मेश एपीआई के बिल्डिंग ब्लॉक" में 06:37 पर Kotlin स्निपेट
// A view: which indices to draw, and how
val road = CustomMesh
    .BuilderFromMeshBuffer(session, meshBuffer)
    // subset 0: asphalt
    .addSubset(TRIANGLES, indexOffset = 0,
        indexCount = roadCount)
    // subset 1: lane markings
    .addSubset(TRIANGLES,
        indexOffset = roadCount,
        indexCount = lineCount)
    .build()

// Shortcut: builds MeshBuffer + CustomMesh
CustomMesh.BuilderFromMeshData(session, layout)
"मेश एपीआई के बिल्डिंग ब्लॉक" में 07:04 पर Kotlin स्निपेट
// Place the mesh in the scene graph
val roadEntity = MeshEntity.create(
    session,
    mesh = road,
    // one material per subset
    materials = listOf(asphalt, paint),
    pose = Pose(Vector3(0f, 0f, -1f)),
    parent = session.scene.activitySpace)

// It's an Entity: move it, re-parent it
roadEntity.setPose(Pose(Vector3(0f, -0.5f, -1f)))
roadEntity.parent = tableAnchor

// Same mesh, another entity
val ghost = MeshEntity.create(
    session, road, listOf(glass, glass))
"कस्टम और डाइनैमिक मेश बनाना" में 07:31 पर Kotlin स्निपेट
// Describe a vertex
val layout = VertexLayout.Builder()
    .addAttribute(POSITION, FLOAT3)
    .addAttribute(NORMAL, FLOAT3)
    .addAttribute(UV0, FLOAT2)
    .build()

// Path points -> ribbon of triangles
val (vertices, indices) = buildRoad(path)

// Build the mesh...
val roadMesh = CustomMesh
    .BuilderFromMeshData(session, layout)
    .addVertexData(vertices)
    .setIndexData(indices)
    .setTopology(TRIANGLES)
    .build()

// ...and put it in the scene
val road = MeshEntity.create(
    session, roadMesh, listOf(asphalt),
    parent = session.scene.activitySpace)
"कस्टम और डाइनैमिक मेश बनाना" में 08:18 पर Kotlin स्निपेट
// Fixed GPU capacity, allocated once
val track = CustomMesh.BuilderFromMeshData(
        session, layout,
        maxVertices = 2 * MAX_POINTS,
        maxIndices = 6 * MAX_POINTS)
    .setTopology(TRIANGLES)
    // Covers everything the track can become
    .setBounds(playArea)
    .build()

// Hand moved: upload only the new segment
val buffer = track.meshBuffer
buffer.updateVertexData(
    bufferIndex = 0,
    vertexData = segmentVertices,
    offsetInBytes = n * VERTEX_BYTES)
buffer.updateIndexData(
    segmentIndices,
    offsetInBytes = n * INDEX_BYTES)
"Gemini की मदद से ऐनिमेटेड मेश जनरेट करना" में 09:34 पर Kotlin स्निपेट
// 20 x 12 cells = 273 vertices
val layout = VertexLayout.Builder()
    // buffer 0: updated every frame
    .addAttribute(POSITION, FLOAT3)
    .addAttribute(NORMAL, FLOAT3)
    .startNextBuffer()
    // buffer 1: never changes
    .addAttribute(UV0, FLOAT2)
    .build()

val flagMesh = CustomMesh.BuilderFromMeshData(
        session, layout,
        maxVertices = 273, maxIndices = 1440)
    .addVertexData(restPose)   // buffer 0
    .addVertexData(checkerUVs) // buffer 1
    .setIndexData(gridIndices)
    .setTopology(TRIANGLES)
    .setBounds(swingBounds)
    .build()
"Gemini की मदद से ऐनिमेटेड मेश जनरेट करना" में 09:56 पर Kotlin स्निपेट
// Gemini wrote the math
fun wave(t: Float) {
    for (i in 0 until 273) {
        val u = (i % 21) / 20f  // 0 at the pole
        setZ(i, 0.06f * u * sin(6f * u - 4f * t))
    }
    recomputeNormals()          // from neighbors
}

// Every frame: one upload (~6.5 KB)
while (isActive) withFrameNanos { ns ->
    wave(ns / 1e9f)
    flagMesh.meshBuffer.updateVertexData(
        bufferIndex = 0,
        vertexData = ByteBufferRegion(
            posNormals, 0, 273 * 24))
}