आप box-shadow के बारे में कितना जानते हैं?
(dgerrells.com)- 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-shadowdrop 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 करने से भी धीमा था
- एक बड़ा box-shadow string generate करके एक
- 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
divscaling approach की तुलना में बहुत छोटा होता है - container size बढ़ाने से यह ठीक होता है, लेकिन container जितना बड़ा होगा performance उतनी धीमी होगी
- क्योंकि box-shadow बनाने वाला base
- balls को आपस में collide कराने वाला version
n^2collision 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 की ज़रूरत पड़ी
postMessageoverhead कम करने के लिए worker एक single pixel नहीं, बल्कि पूरा frame render करता है- full-screen example पहले की तुलना में काफ़ी तेज़ चलता है
- downside यह है कि camera या scene बदलते ही पहले से चल रही ray calculations के results बेकार हो जाते हैं, जिससे screen काली दिख सकती है
- इसका समाधान input events पर
isDirtyflag set करके और update loop में frame clear करना है या नहीं, यह तय करके किया गया - ऐसा हो सकता है कि worker पुराने scene data पर calculate किया गया frame, scene update के तुरंत बाद भेज दे
- इसे timestamp या scene id को
postMessageमें शामिल करके discard किया जा सकता है - प्रयोग में इसे वैसे ही छोड़ दिया गया, क्योंकि एक frame का गलत ray data जल्दी average हो जाता है
- इसे timestamp या scene id को
- नतीजतन 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 टिप्पणियां
Hacker News की राय
पारदर्शी रंग इस्तेमाल करने पर GPU drawing batch processing सीमित हो जाती है, इसलिए यह धीमा हो जाता है
अपारदर्शी drawing में depth buffer का इस्तेमाल करके क्रम को मनमाने ढंग से बदला जा सकता है और batch processing को अधिकतम किया जा सकता है। पारदर्शी स्थिति में blending सही हो, इसके लिए painter's order में draw करना पड़ता है
खासकर 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 करना है, तो यही तरीका जरूरी भी हो सकता है
पूरी तरह 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 करें तो मामला कठिन हो जाता है
Transparent textures की तुलना में अधिक triangles draw करना आम तौर पर बेहतर क्यों होता है, इसकी वजह भी यही है
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 है
अगर 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/
आखिरी paragraph तक जाकर ही मुझे समझ आया कि
gypityका मतलब ChatGPT है, तो थोड़ा शर्मिंदा हुआgypसे जुड़ा कोई client-side tool होगाअच्छे पुराने अंदाज की impractical hacking का पूरी तरह स्वागत है। बस यह याद रखना चाहिए कि canvas पहले से मौजूद है, और ऐसे काम canvas ज्यादा आसान, तेज और बेहतर तरीके से करता है
Music visualization वाकई शानदार था। पुराने Winamp दिनों की बहुत याद आती है, जब music चलाकर visualization को full screen में खोला करते थे
काश आजकल के streaming audio players भी ऐसी सुविधा देते
हालांकि यह सही है कि 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 है या नहीं
https://www.folklore.org/Round_Rects_Are_Everywhere.html
http://wg20.criticalcodestudies.com/index.php?p=/discussion/...
Modern 3D-accelerated तरीके के लिए, जैसा किसी और ने अनुमान लगाया, SDF इस्तेमाल करने वाला लेख है
https://mortoray.com/quickly-drawing-a-rounded-rectangle-wit...
यह मेरी पसंद वाली hacking है
इस विषय पर पढ़े Josh Comeau के लेखों के लगभग बिल्कुल उलट लगता है: https://www.google.com/search?q=josh+comeau+shadows
इस साल पढ़े गए लेखों में शायद सबसे बेहतरीन लेख के अंत में
you'reकी जगह your welcome लिखा थाइसे जल्दी ठीक करना चाहिए। या शायद मैं joke नहीं समझ पाया
पिछले 30 सालों में मैं programming में तो अच्छा हो गया, लेकिन games पसंद न होने की वजह से graphics लगभग नहीं किया
अब इसे बड़ी गलती मानकर एक साल से ज्यादा समय से catch up करने की कोशिश कर रहा हूं, और यह सचमुच कठिन है