1 पॉइंट द्वारा GN⁺ 2024-07-22 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • CSS box-shadow आमतौर पर UI में गहराई देने वाली एक property है, लेकिन अगर एक ही div पर सैकड़ों से हजारों shadow layers चढ़ा दी जाएँ, तो यह एक तरह के drawing API की तरह काम कर सकती है
  • भले ही यह box की सीमा में बंधी हो, rounded corners, कई shadow chains, और color·blur·opacity के संयोजन से circular dots और layered effects बनाए जा सकते हैं
  • प्रयोगों में blur और opacity ने animate किए जा सकने वाले elements की संख्या को काफ़ी कम कर दिया, और इन्हें हटाने पर M1 laptop पर हजारों box-shadow draw किए जा सके
  • ball simulation, image pixels, cube·sphere surface dots, और music-reactive visualization — ये सब हर frame में box-shadow string को update करने के तरीके से बनाए गए
  • अंत में CPU-आधारित ray tracer को Web Workers के साथ parallelize करके यह दिखाया गया कि सिर्फ एक div और box-shadow से भी scene rendering संभव है

box-shadow को drawing tool की तरह इस्तेमाल करना

  • CSS box-shadow drop shadow का एक रूप है, और मूल रूप से images या UI elements के पीछे shadow बनाकर depth देने के लिए उपयोग होता है
  • सामान्य drop shadow image raster को x/y axis पर shift करके, original के पीछे एक single color में draw करती है और depth का एहसास देती है
  • CSS filter: drop-shadow() x/y offset और color लेता है, और वैकल्पिक रूप से blur value भी support करता है
  • कई drop shadow filters को layer करने से screen composition में depth बढ़ाई जा सकती है

box-shadow की विशेषताएँ और सीमाएँ

  • box-shadow का “Box” इस बात का संकेत है कि shadow की shape container के bounding box तक सीमित रहती है
  • यह सीमा पहली नज़र में restrictive लग सकती है, लेकिन ज़्यादातर UI boxes से बने होते हैं, इसलिए वास्तविक UI में यह काफ़ी उपयोगी है
  • CSS box-shadow implementation rounded boxes को सस्ते में draw करने के लिए mathematical optimizations support करती है, और rounded box circle जैसा दिख सकता है
  • designers pre-rendered images का उपयोग किए बिना box-shadow से download size बढ़ने से बच सकते हैं
  • एक single div पर कई box-shadow को chain की तरह जोड़ा जा सकता है, और उदाहरण इसी तरीके से colors और shadows बनाता है
  • border-radius को adjust करने के लिए CSS border-radius generator पर प्रयोग किया जा सकता है

box-shadow का “गलत” उपयोग

  • सामान्य UI design में shadows, spacing, padding, typography और accessibility के साथ state और interaction को अलग दिखाने वाले सहायक तत्व के रूप में इस्तेमाल होते हैं
  • लेकिन box-shadow को मनचाही संख्या में rectangles screen पर paint करने और वैकल्पिक blur लगाने वाले painting API की तरह भी देखा जा सकता है
  • शुरुआती प्रयोग पिछले लेख की minimal art से शुरू हुए, जहाँ सिर्फ simple color blocks से visual result बनाया गया था
  • बाद में performance पर अधिक box-shadow, blur और opacity के असर को देखने के लिए visualization tool बनाया गया
    • एक बड़ा box-shadow string generate करके एक div पर set किया गया
    • animation की शुरुआत हर 300ms पर box-shadow string बदलकर और transition: all पर छोड़कर की गई
    • यह तरीका jank पैदा करता था, और हर frame में box-shadow set करने से भी धीमा था
  • 100 box-shadow example palette को tap से remix करता है, और बाईं ओर हाल की 10 palettes की history दिखाता है
  • blur लगाने पर animate किए जा सकने वाले shadows की संख्या कम हुई, और transparent colors ने भी drawable count को काफ़ी घटा दिया
  • div का size भी performance को प्रभावित करता था, और animation के दौरान software rasterizer शामिल होने जैसा लग रहा था
  • opacity और blur के बिना M1 laptop हजारों box-shadow draw कर सकता था

box-shadow से ball simulation बनाना

  • box-shadow rotate नहीं हो सकता, लेकिन border-radius की मदद से इसे circle जैसा दिखाया जा सकता है
  • कई circular shadows को balls की तरह treat किया गया, और z value के आधार पर size scale करके नकली 3D depth बनाई गई
  • implementation requestAnimationFrame में game state update करती है और हर frame बड़े box-shadow string को div पर set करती है
  • rendering process इस प्रकार है
    • balls को z value के आधार पर sort किया जाता है
    • z value के आधार पर size calculate किया जाता है
    • हर ball की x/y position, spread और color को box-shadow entry में बदला जाता है
    • entries को comma से जोड़कर एक string बनाई जाती है
  • 50 balls वाला example drag करके balls को move करने और box के अंदर bounce कराने देता है
  • नकली 3D scaling कुछ depth देती है, लेकिन जब ball “camera” के करीब आती है तो circle टूटता हुआ दिख सकता है
    • क्योंकि box-shadow बनाने वाला base div scaling approach की तुलना में बहुत छोटा होता है
    • container size बढ़ाने से यह ठीक होता है, लेकिन container जितना बड़ा होगा performance उतनी धीमी होगी
  • balls को आपस में collide कराने वाला version n^2 collision checks का उपयोग करता है और collision पर सिर्फ velocity reflect करता है
    • यह वास्तविक physics interaction simulate नहीं करता
    • देखने में आसान बनाने के लिए z position को स्थिर रखकर इसे 2D बनाया गया
  • एक example ऐसा भी बनाया गया जिसमें balls अपनी random starting positions पर लौटना चाहती हैं, और touch force से उन्हें खींचने पर sponge से टुकड़े अलग करने जैसा effect मिलता है

images और 3D point clouds

  • अगला प्रयोग image pixels को 2D plane पर dots में map करके, हर dot को box-shadow से draw करने का था
  • image pixels की position और color पढ़कर उन्हें state.particles में store किया गया, और हर pixel को एक particle की तरह इस्तेमाल किया गया
  • source CodeSandbox example में है
  • यह example 3D space में हजारों box-shadow render करता है, और drag करने पर image को बिखेरने जैसा interaction देता है
  • auto-rotate live version /box-shadow/v3?width=80&size=5&autoRotate=1 पर उपलब्ध है, और इसे चलाने पर battery usage की warning दिखाई जाती है
  • अधिक count वाले example में लगभग 12,000 box-shadow थे, और इस स्तर पर noticeable lag दिखा
  • M1 बहुत बड़ी संख्या में box-shadow संभाल सकता था, लेकिन desktop, iPhone और पुराने Android devices उसी स्तर को संभाल नहीं पाए

cube और sphere surface पर dots रखना

  • dots को mesh surface पर uniform तरीके से project करने का प्रयोग भी जारी रहा
  • cube को हर face पर dots रखने के तरीके से implement किया गया
    • यह मानकर कि सभी edges की length बराबर है
    • हर face पर iterate करके दिए गए size के अनुसार dots भरे जाते हैं
    • dots की संख्या बढ़ाने से cube की precision बढ़ती है
  • cube example tap के जरिए interact किया जा सकता है, और mouse position को follow करने वाली छोटी light जोड़कर depth बढ़ाई गई है
  • lighting calculation सटीक नहीं है और इसमें कई “magic constants” उपयोग किए गए हैं
  • cube particle mapping function और कई math helpers gypity की मदद से मिले
    • पहला result random distribution था
    • दूसरे प्रयास में cube surface पर uniform placement मिला
  • sphere surface पर uniform placement अधिक जटिल था, इसलिए spiral discretization method का उपयोग किया गया
    • विचार यह है कि sphere को नीचे से ऊपर तक लपेटने वाली एक line पर points को समान रूप से रखा जाए
    • इसे रस्सी के गेंद के चारों ओर लिपटने से तुलना की गई है
  • इसी linear mapping का उपयोग music की frequency values की array से जुड़ी animation बनाने में भी किया गया
  • dots पर समय के साथ interpolate होने वाली gradient palette animation लागू की गई, और यह हिस्सा पहले के काम की तुलना में अधिक कठिन था
  • कम rotation count वाले sphere example में spiral uniform distribution का approximation देती है, लेकिन पर्याप्त rotations न होने पर shape टूटती हुई दिखती है

box-shadow ray tracer

  • अगर triangles को box-shadow से draw किया जा सकता है, तो लगभग किसी भी CGI को बनाया जा सकता है — यही विचार ray tracing experiment तक पहुँचा
  • लक्ष्य सिर्फ एक div और box-shadow से scene draw करना था
  • बाद के examples low-precision live rendering और high-precision image rendering दोनों का उपयोग करते हैं, और कुछ examples में precision limit नहीं है, इसलिए चलाते समय सावधानी की ज़रूरत है
  • ray tracer और ray marcher सटीक लेकिन धीमे image generation methods हैं, और पूरे CGI industry में उपयोग किए जाते हैं
  • यहाँ GPU के बजाय CPU-based tracer इस्तेमाल किया गया
    • क्योंकि GPU का उपयोग करने से प्रयोग का मूल उद्देश्य कमजोर पड़ जाता और implementation भी कठिन हो जाती
  • शुरुआती tracer सिर्फ spheres render करता था, और scene data model में camera, कई spheres, और material information शामिल थी
  • gypity से मिला शुरुआती code real-time में चलता था, लेकिन उसमें समस्याएँ थीं
    • sampling optimization में ज़रूरी bias coefficient जोड़ने वाला code गायब था
    • संबंधित comment मौजूद था, लेकिन असली line नहीं थी; sampling technique को देखकर इसे ठीक किया गया

progressive rendering और Three.js

  • इसके बाद code को progressive rendering structure में बदला गया
  • progressive rendering बहुत सारे ray calculations को कई frames में बाँटकर करती है, ताकि rendering धीरे-धीरे “ground truth” के करीब पहुँचती दिखे
  • यह structure camera को interactive तरीके से move करने वाली functionality के साथ अच्छी तरह फिट बैठती है
  • camera और orbit controls के लिए Three.js library का उपयोग किया गया
    • ताकि matrix math और mobile support को खुद implement न करना पड़े
  • यह version सिर्फ spheres render कर सकता है, और scene की हर चीज़ किसी न किसी स्तर पर scaled sphere है
  • example डिफ़ॉल्ट रूप से full resolution के लगभग 6% से थोड़ा अधिक स्तर पर चलता है
  • screen से दूर जाने पर low-resolution scene अधिक साफ़ दिखती है, जबकि पास आने पर precision की कमी अधिक स्पष्ट हो जाती है
  • query parameters से resolution scale, pixelSize, ray bounce count, और maximum samples adjust किए जा सकते हैं
  • resolution और samples बढ़ाने पर result बेहतर होता है, लेकिन बहुत धीमा हो जाता है

tracer implementation और performance bottlenecks

  • rendering का standard तरीका इस्तेमाल हुआ: हर pixel के लिए camera से ray भेजना, color calculate करना, और frames के बीच average लेना
  • tracer में Three.js उपयोग होने के कारण बहुत सारी नई objects बनती थीं और जल्दी garbage बन जाती थीं
  • object reuse की कुछ कोशिशें की गईं, लेकिन performance को पूरी तरह निचोड़ना हो तो शायद Three.js का उपयोग न करना बेहतर होता
  • profiler के अनुसार garbage collection ने बहुत बड़ा अंतर नहीं पैदा किया, इसलिए Three.js का उपयोग जारी रखा गया
  • tracer का मूल विचार यह है कि ray scene के objects से टकराकर तब तक bounce करती है जब तक वह light तक न पहुँचे, और object व lighting properties के अनुसार color लौटाती है
  • बहुत सारी rays भेजने की ज़रूरत इसलिए होती है क्योंकि कुछ rays light source तक नहीं पहुँचतीं, जबकि कुछ पहुँच जाती हैं
  • यह tracer एक simple lighting model का उपयोग करता है
    • physically accurate BRDF नहीं
    • texture नहीं
    • subsurface scattering नहीं
    • simple diffuse light और specular reflection का उपयोग
  • ray tracing सीखने के लिए Ray Tracing in One Weekend देखा जा सकता है
  • बाद में planes को render करना संभव हुआ, और floating point values पर bit manipulation की गलती ठीक करने के बाद lighting rectangles भी render किए जा सके

Web Workers से parallelization

  • performance सुधारने के लिए Three.js GC issues ठीक करने के बजाय Web Workers multithreading का रास्ता चुना गया
  • ray tracing parallelization के लिए उपयुक्त है क्योंकि हर calculation एक single result लौटाती है और उसका कोई side effect नहीं होता
  • worker manager एक worker pool बनाता है और render, updateScene दो methods देता है ताकि runtime में scene बदला जा सके
  • worker code मौजूदा tracer की copy जैसा है, और scene replacement के लिए data marshaling की ज़रूरत पड़ी
  • postMessage overhead कम करने के लिए worker एक single pixel नहीं, बल्कि पूरा frame render करता है
  • full-screen example पहले की तुलना में काफ़ी तेज़ चलता है
  • downside यह है कि camera या scene बदलते ही पहले से चल रही ray calculations के results बेकार हो जाते हैं, जिससे screen काली दिख सकती है
  • इसका समाधान input events पर isDirty flag set करके और update loop में frame clear करना है या नहीं, यह तय करके किया गया
  • ऐसा हो सकता है कि worker पुराने scene data पर calculate किया गया frame, scene update के तुरंत बाद भेज दे
    • इसे timestamp या scene id को postMessage में शामिल करके discard किया जा सकता है
    • प्रयोग में इसे वैसे ही छोड़ दिया गया, क्योंकि एक frame का गलत ray data जल्दी average हो जाता है
  • नतीजतन box-shadow आधारित ray tracing पर्याप्त रूप से काम करती दिखी, और source CodeSandbox में उपलब्ध है
  • performance stats में कुल rays का अनुमान, sample count, और rendering resolution दिखाया जाता है
  • default sample count 1200 पर रुकती है, लेकिन इसे बदला जा सकता है
  • एक तेज़ लेकिन कम सटीक alternative lighting model भी toggle किया जा सकता है
  • scene data JSON में है, इसलिए उसे सीधे edit करना आसान है

निष्कर्ष: संभव है, लेकिन अनुशंसित नहीं

  • एक single div के box-shadow से ball simulation, point clouds, image-based particles, cube·sphere surfaces, और ray-traced scenes तक draw किए जा सकते हैं
  • लेकिन पूरा प्रयोग व्यावहारिक use case के बिना, लगभग “ऐसा नहीं करना चाहिए” वाली approach के करीब है
  • triangle mesh loading, acceleration structure, और अधिक सटीक lighting model जोड़कर इसे आगे बेहतर बनाया जा सकता है
  • gypity ने शुरुआत में कहा था कि box-shadow ray tracer संभव नहीं है, लेकिन वास्तविक example बना लिया गया
  • CSS सहज नहीं है, लेकिन कभी-कभी इसमें अजीब लगने वाली ऐसी संभावनाएँ होती हैं जो साफ़ तौर पर काम करती हैं

1 टिप्पणियां

 
GN⁺ 2024-07-22
Hacker News की राय
  • पारदर्शी रंग इस्तेमाल करने पर GPU drawing batch processing सीमित हो जाती है, इसलिए यह धीमा हो जाता है
    अपारदर्शी drawing में depth buffer का इस्तेमाल करके क्रम को मनमाने ढंग से बदला जा सकता है और batch processing को अधिकतम किया जा सकता है। पारदर्शी स्थिति में blending सही हो, इसके लिए painter's order में draw करना पड़ता है

    • मुझे लगता है यह उससे भी ज़्यादा जटिल है। Web browser भी GPU rendering का इस्तेमाल करते हैं, लेकिन वे game engine नहीं हैं, और अगर स्क्रीन के हर object को हर frame में फिर से draw किया जाए तो बड़े और जटिल page पर आसानी से अटकन आ सकती है
      खासकर Chromium कुल layer count को कम से कम रखने की कोशिश करता है, हर layer को pixel map के रूप में render करता है और फिर हर frame में दिख रही layers को final image में composite करता है। असल में कई layers में pixels नहीं बदलते, सिर्फ उनकी position बदलती है, इसलिए हर frame rasterize करने के बजाय केवल compositing काफी होता है
      अगर बिना transparency वाले बहुत सारे box-shadow हों, तो Chromium पूरे हिस्से को एक साथ एक ही layer में rasterize कर सकता है, लेकिन बहुत सारे transparent box-shadow हों तो वह हर एक को अलग layer बना सकता है। इस स्थिति में यह inefficient हो सकता है, लेकिन अगर semi-transparent box-shadow को page पर स्वतंत्र रूप से move करना है, तो यही तरीका जरूरी भी हो सकता है
    • Reverse painter's order कुछ मामलों में painter's order से बेहतर होता है, क्योंकि पूरी तरह ढके हुए objects को skip किया जा सकता है
      पूरी तरह transparent buffer (α=0.0) से शुरू करके सामने से पीछे तक हर face पर iterate करें, और हर pixel के लिए नए pixel का 1.0-buffer.α जितना हिस्सा मौजूदा buffer में blend कर दें। अगर buffer.α == 1.0 हो, तो depth buffer की तरह उसे पूरी तरह skip किया जा सकता है
      हालांकि किसी transparent object के पीछे एक और transparent object होने पर गणित को फिर से जांचना होगा, और जब faces cyclic तरीके से overlap करें या एक-दूसरे को intersect करें तो मामला कठिन हो जाता है
    • GPU को overdraw भी पसंद नहीं होता, इसलिए transparent elements की कई परतें overlap कराने से बचना बेहतर है
      Transparent textures की तुलना में अधिक triangles draw करना आम तौर पर बेहतर क्यों होता है, इसकी वजह भी यही है
    • Sorting step अपने-आप में आमतौर पर बड़ी समस्या नहीं होती। draw call submit करने से पहले वैसे भी कई बार sorting की जाती है। असली cost overdraw से आती है
      Opaque rendering में front-to-back draw किया जा सकता है, इसलिए final framebuffer में वास्तव में जो दिखाई देता है केवल वही render करना पड़ता है, और depth pass के बाद pixels की संख्या framebuffer size के अनुपात में होती है
      Transparent rendering में back-to-front draw करना पड़ता है, और उन scenes को भी काफी render करना पड़ता है जो बाद में दूसरे polygons से आंशिक रूप से ढक जाएंगे। इसलिए shader pipeline से गुजरने वाले pixels की संख्या mesh size के अनुपात तक बढ़ सकती है
      अगर elements एक-दूसरे से overlap नहीं करते, तो transparency की वजह से शायद ही ज्यादा slowdown होगा। वैसे भी हर pixel को एक बार touch करना है, और बदला सिर्फ shader formula है
    • विचार करने वाली एक और बात है: खासकर mobile devices में memory bandwidth bottleneck बन सकती है
      अगर opaque drawing किसी दूसरी drawing के ऊपर आती है, तो best case में overlapping draw work को पूरी तरह हटाया जा सकता है, और worst case में भी केवल अलग-अलग drawing जितनी bandwidth लगती है
      Transparency में, अगर operations को किसी तरह merge नहीं किया जा सकता, तो पूरे overlapping area को फिर से read करना पड़ता है, इसलिए हर transparent drawing के लिए final framebuffer size के कम से कम दोगुने के बराबर bitmap memory bus पर आना-जाना करता है
      यह सोचें कि कई mobile devices में 60fps बनाए रखने के लिए जरूरी समय में पूरी screen को दो बार blit करने लायक memory bandwidth भी नहीं थी, तो यह काफी बड़ी समस्या बन जाती है
  • यह सचमुच मजेदार exploration था
    Layering महत्वपूर्ण शब्द है, यह भी सही है। 14 साल पहले बनाए गए text shadow project के बेवकूफी भरे, लेकिन कभी-कभी शानदार दिखने वाले effects में भी layering ही मुख्य थी: https://paulirish.github.io/mothereffingtextshadow/

    • इस लेख में भी कई layers लगती हैं, और सच कहें तो यह सिर्फ box-shadow पर लेख नहीं लगता
  • आखिरी paragraph तक जाकर ही मुझे समझ आया कि gypity का मतलब ChatGPT है, तो थोड़ा शर्मिंदा हुआ

    • यह Primeagen YouTube channel पर इस्तेमाल होने वाला expression है
    • इसे देखकर पता चला कि वह शब्द किसी colleague द्वारा बनाया गया internal joke नहीं, बल्कि meme बनता जा रहा expression है
    • मुझे लगा था कि यह gyp से जुड़ा कोई client-side tool होगा
  • अच्छे पुराने अंदाज की impractical hacking का पूरी तरह स्वागत है। बस यह याद रखना चाहिए कि canvas पहले से मौजूद है, और ऐसे काम canvas ज्यादा आसान, तेज और बेहतर तरीके से करता है

    • लेकिन comedy value कम होती है। सिर्फ box-shadow से यह करना इसलिए मजेदार है क्योंकि यह इतना impractical है
    • Canvas में खासकर accessibility के मामले में बहुत कुछ छोड़ना पड़ता है
    • Canvas बेहतर तो है, लेकिन यह तब की बात है जब आप fixed-size box के अंदर रहने वाली चीज बना रहे हों
    • बेशक keyboard भी पहले से मौजूद है, तो floppy drive या rubber chicken से music बनाने की जरूरत भी नहीं है—कुछ वैसी ही बात है
  • Music visualization वाकई शानदार था। पुराने Winamp दिनों की बहुत याद आती है, जब music चलाकर visualization को full screen में खोला करते थे
    काश आजकल के streaming audio players भी ऐसी सुविधा देते

    • मैं अब भी Winamp, ठीक-ठीक कहें तो WACUP, Milkdrop visualization के साथ लगभग रोज इस्तेमाल करता हूं
      हालांकि यह सही है कि streaming audio players सचमुच बहुत basic-functionality वाले software हैं
  • आखिरकार Firefox और Chrome browser zoom 150% पर 1px box-shadow को अब भी अलग-अलग render करते हैं
    Baseline 2025 से उम्मीद लगाते हैं

  • मैं जानना चाहता हूं कि “UI लोगों को पसंद आने वाले rounded boxes को बहुत सस्ते में draw करने वाले mathematical hacks” के बारे में और सीखने के लिए कोई reference material है या नहीं

  • यह मेरी पसंद वाली hacking है
    इस विषय पर पढ़े Josh Comeau के लेखों के लगभग बिल्कुल उलट लगता है: https://www.google.com/search?q=josh+comeau+shadows

  • इस साल पढ़े गए लेखों में शायद सबसे बेहतरीन लेख के अंत में you're की जगह your welcome लिखा था
    इसे जल्दी ठीक करना चाहिए। या शायद मैं joke नहीं समझ पाया

  • पिछले 30 सालों में मैं programming में तो अच्छा हो गया, लेकिन games पसंद न होने की वजह से graphics लगभग नहीं किया
    अब इसे बड़ी गलती मानकर एक साल से ज्यादा समय से catch up करने की कोशिश कर रहा हूं, और यह सचमुच कठिन है

    • मैंने पहले games से शुरुआत की, फिर CRUD या simple Oracle Forms programming में चला गया, और रातोंरात अपने पुराने game programmer वेतन का तीन गुना कमाने लगा