- Unity में object के आसपास outline रेंडर करने के प्रतिनिधि तरीके हैं rim effect, vertex expansion, blur buffer, Jump Flood algorithm, और edge detection; इनकी quality, performance, और setup cost एक-दूसरे से अलग होती है
- Rim effect normal और view direction के dot product से Fresnel का approximation बनाकर किनारे के अंदरूनी हिस्से को उभारता है, लेकिन गोल मॉडल और sharp मॉडल पर इसका परिणाम काफी अलग हो सकता है
- Vertex expansion duplicate mesh को पीछे की ओर बड़ा करके बाहरी रेखा बनाता है, और clip space में process करने पर स्क्रीन पर इसकी चौड़ाई अधिक स्थिर रखी जा सकती है
- Blur buffer और Jump Flood algorithm silhouette को buffer में render करके उसे expand करने का तरीका अपनाते हैं; इनमें से एक smooth lines के लिए और दूसरा बहुत चौड़े outlines के लिए खास तौर पर उपयोगी है
- Edge detection depth, normal, और color buffer की discontinuities को full-screen पर खोजकर रेखाएँ बनाता है, लेकिन अनचाही रेखाएँ कम करने के लिए threshold और modulation को बारीकी से adjust करना पड़ता है
outline rendering का उपयोग क्यों करें
- outline का उपयोग गेम में visual style बनाने या object highlight, selection जैसे gameplay support के लिए किया जाता है
- Sable comic-book जैसी style के लिए outlines का उपयोग करता है
- The Last of Us में खिलाड़ी के stealth mode में जाने पर दुश्मनों को highlight करने के लिए outlines का उपयोग किया जाता है
- ये पाँचों techniques performance, visual quality, और manual setup की मात्रा के बीच अलग-अलग trade-off देती हैं
Rim effect: Fresnel approximation से अंदरूनी किनारा उभारना
- rim effect Fresnel effect का approximation लेकर object के rim/किनारे पर line render करता है
- इसकी गणना normalized normal vector
Nऔर normalized view directionVके dot product पर आधारित होती है1.0 - saturate(dot(N, V))मान को powerPतक बढ़ाकर effect की strength बनाई जाती है- यह physically accurate Fresnel से अधिक outline के लिए एक approximation है
- Unity implementation में custom shader outline की width, power, softness, color को control करता है
- उदाहरण implementation में
smoothstep,lerp,_OutlineWidth,_OutlineSoftness,_OutlinePower,_OutlineColorका उपयोग होता है
- उदाहरण implementation में
- यह तरीका हमेशा अंदरूनी line के रूप में दिखता है और object के बाहर की outer boundary पर नहीं दिखता
- यह गोल और smooth objects पर अच्छा काम करता है, लेकिन cube या complex models में line width असमान हो सकती है या यह outline जैसा न लगे
Vertex expansion: duplicate mesh को बड़ा करके बाहरी line बनाना
- vertex expansion में मूल object या mesh की duplicate copy को फिर से render किया जाता है, और original के पीछे उसके vertices को expand करके बड़ा आकार बनाया जाता है, फिर उसे solid color में render किया जाता है
-
expansion direction चुनना
- vertices को किस दिशा में move किया जाए, इससे outline quality पर बड़ा असर पड़ता है
- vertex position direction में move करने पर mesh फूलकर बाहर की ओर फैलता हुआ दिखता है
- local space में vertex position को object center से vertex तक के vector की तरह इस्तेमाल किया जा सकता है
- normalize न करने पर center से दूर वाले vertices ज़्यादा move होते हैं
normalize(positionOS) * widthका उपयोग करने पर object space में अधिक समान movement मिल सकता है- normal vector direction में move करने पर sphere या capsule जैसे smooth edges वाले objects पर अच्छे परिणाम मिलते हैं
- cube जैसे sharp edges वाले objects में outline में gaps आ सकते हैं
- vertex color को भी expansion direction के रूप में इस्तेमाल किया जा सकता है
- यह custom normals बनाकर उन्हें mesh के vertex color channel में store करने का तरीका है
- cube पर spherical smooth normals को vertex color में bake करने से बेहतर outline मिल सकती है
- इसकी कमी यह है कि हर mesh के लिए custom normals बनाने पड़ते हैं; इसे script से automate किया जा सकता है
-
expansion space चुनना
- shader के vertex stage में vertex coordinates object space से शुरू होकर MVP matrix से clip space में transform होते हैं
- इसका flow है object/model/local space → world space → camera/view space → clip space → screen space → viewport/window space
- object space में expand करने पर, क्योंकि MVP transform अभी apply नहीं हुआ होता, outline distort हो सकती है
- model matrix apply होने पर scaling की समस्या आ सकती है
- clip space से screen space में जाते समय perspective divide के कारण perspective shrinkage होता है
- 3D space में होने वाली movement का कुछ हिस्सा camera direction में होता है, इसलिए वह स्क्रीन पर outline width में योगदान नहीं देता
- clip space में expand करने पर स्क्रीन पर लगभग समान width वाली साफ outline बनाई जा सकती है
- vertex position और normal को clip space में transform करने के बाद केवल
x,ycoordinates को modify किया जाता है - aspect ratio को ठीक रखने के लिए स्क्रीन की width और height से divide किया जाता है
- clip space के
wसे multiply किया जाता है ताकि बाद का perspective divide outline width पर net effect न डाले - width की 1 unit स्क्रीन के 1 pixel के बराबर हो, इसके लिए
width * 2से multiply किया जाता है - clip space तरीका भी mesh normals पर निर्भर करता है
- custom normals न हों तो sharp edges पर gaps आ सकते हैं
- अगर normals गलत सेट हों और उलटी दिशा में हों, तो outline vertices भी उलटी दिशा में move करेंगे और gaps बनेंगे
- संबंधित विवरण creating an outline in clip space में और देखा जा सकता है
-
masking
- duplicate mesh में केवल बाहर निकला हुआ outline हिस्सा ही दिखना चाहिए
- सामान्य तरीका duplicate mesh की front-facing geometry को cull करके backface से outline बनाना है
- depth test में less than or equal to का उपयोग किया जाता है ताकि backface केवल outline की position पर दिखे
- दूसरा तरीका stencil mask का उपयोग करना है ताकि duplicate mesh original के सामने दिखाई न दे
- इस स्थिति में culling की ज़रूरत नहीं होती
- object के अंदर कोई line बिल्कुल नहीं बनती
- जब दो objects overlap करते हैं, तो outline केवल दोनों objects के बाहरी हिस्से के आसपास दिखती है
Blur buffer: silhouette को blur करके फैलाना
- blur buffer तरीका object की silhouette को buffer में render करता है, फिर blur से उसे expand करता है, और उस परिणाम का उपयोग outline rendering में करता है
-
silhouette buffer
- पहला चरण silhouette buffer बनाना है
- हर object को solid-color output shader के साथ texture में render किया जाता है
- यदि सभी silhouettes को सफेद रंग में render किया जाए, तो अंत में मनचाहा outline color multiply करके एक single color बनाया जा सकता है
- अगर हर object के लिए अलग outline color चाहिए, तो हर silhouette को अलग color में render किया जा सकता है
-
blur pass
- blur pass का उपयोग silhouette buffer को expand करने के लिए किया जाता है
- आम तौर पर box blur या Gaussian blur का उपयोग होता है
- performance बेहतर करने के लिए blur से पहले silhouette buffer को downscale किया जा सकता है
- blur pass महँगा होता है क्योंकि इसमें हर pixel के आसपास कई pixels का average या weighted average निकालना पड़ता है
- blur को 2-pass में करना बेहतर है
- separable filter होने के कारण box blur और Gaussian blur में algorithm complexity को
O(N²)सेO(2N)तक घटाया जा सकता है - पहले vertical direction में blur किया जाता है, फिर उसके परिणाम को horizontal direction में blur करके final result बनाया जाता है
- outline width को blur shader के
_KernelSizeparameter से control किया जाता है
-
outline pass और masking
- blur pass के बाद blurred silhouette को original scene के साथ composite करके outline बनाई जाती है
- blur buffer soft या glowing outlines के लिए उपयुक्त है
- blur result पर step process लगाने से hard outline भी render की जा सकती है
- vertex expansion की तरह stencil mask का उपयोग करके outline को केवल geometry के पीछे render कराया जा सकता है
- इसका performance impact दूसरे तरीकों से अधिक हो सकता है
Jump Flood algorithm: बहुत चौड़े outlines को संभालना
- चौथा तरीका Jump Flood algorithm से outline render करना है
- इसका लाभ यह है कि बहुत चौड़े outlines को भी उचित performance cost पर render किया जा सकता है
- अधिक विवरण Ben Golus की The Quest for Very Wide Outlines में दिया गया है
Edge detection: full-screen पर discontinuities खोजना
- edge detection तरीका full-screen pass में scene की discontinuities खोजकर lines बनाता है
- discontinuities को depth buffer values, normal vectors, albedo colors, या rendering process में उपलब्ध अन्य data से detect किया जा सकता है
-
Roberts cross
- Roberts cross operator एक differential operator है जो diagonal pixels के अंतर के squared sum की गणना करता है
- वास्तविक implementation में kernel को original image पर convolve करके edges detect की जाती हैं
- इसमें
xdirection औरydirection के 2 kernels उपयोग होते हैं - kernel size
2 x 2होती है - एक pixel के आसपास केवल 4 samples चाहिए होते हैं
- यह एक simple operator है, लेकिन अच्छे परिणाम दे सकता है
-
Sobel operator
- Sobel operator भी
xdirection औरydirection के 2 kernels का उपयोग करता है - Sobel kernel size
3 x 3होती है, और एक pixel के आसपास 9 samples उपयोग किए जाते हैं - Sobel filter कैसे काम करता है, यह blog post on Sobel filters में और देखा जा सकता है
- Sobel operator भी
-
discontinuity sources
- सामान्य तरीका render pipeline द्वारा scene के लिए बनाए गए depth texture, normal texture, color texture से discontinuities ढूँढना है
- edge detection pass इन textures को sample करता है और ऊपर बताए गए operators से discontinuities detect करता है
- इस तरह बनी edge तीनों buffers में से किसी एक में मिली discontinuity के कारण draw हो सकती है
- इस तरीके में उन सभी objects पर outline लगती है जो इन buffers में लिखते हैं, इसलिए object-level control कम होता है
- कई discontinuity sources की अनुमति देने से अधिक robust outline system बनाया जा सकता है
- कुछ edges तीनों sources में detect होती हैं
- कई edges केवल किसी एक खास source के योगदान से detect होती हैं
- हर source को अलग weight और threshold दिया जा सकता है, जिससे outline के visual result को control किया जा सके
-
edge detection modulation
- केवल discontinuity buffer पर edge detection operator लगाने से artifact-free result पाना मुश्किल हो सकता है
- कई render pipelines में depth buffer non-linear होती है
- camera के पास मौजूद दो objects के बीच 1m का depth difference, दूर मौजूद दो objects के बीच 1m के difference से बड़ा दिखाई देता है
- इसे ठीक करने के लिए depth discontinuity detection threshold को depth buffer से ही modulate किया जा सकता है
- camera के पास की geometry को edge मानने से पहले depth value में बड़ा discontinuity चाहिए होता है
- छोटे grazing angle पर अनचाही edges भी बन सकती हैं
- इसे normal vector
Nऔर view directionVके dot product से बने Fresnel mask से modulate किया जा सकता है - यह वही प्रकार का mask है जो rim effect वाले तरीके में उपयोग हुआ था
- अन्य modulation techniques भी इस्तेमाल की जा सकती हैं, लेकिन चुनाव इच्छित visual effect पर निर्भर करेगा
-
custom discontinuity sources
- outline shader को custom discontinuity sources भी दिए जा सकते हैं
- यह rendering process में सीधे बनाया गया render texture होता है, जिसमें outline generation के लिए custom data रखा जाता है
- आप सीधे नियंत्रित कर सकते हैं कि कौन-से objects custom buffer में लिखें, इसलिए यह भी नियंत्रित किया जा सकता है कि किन objects पर outline लगे
- उदाहरण के लिए mesh के vertex colors को texture में render करके discontinuity source बनाया जा सकता है
- दूसरा तरीका world position के आधार पर faces को color करना, या depth buffer और normal buffer की जानकारी को मिलाकर custom buffer बनाना है
- अतिरिक्त जानकारी Linework section map में देखी जा सकती है
1 टिप्पणियां
Hacker News की राय
लिंक किया गया Jump Flood Algorithm वाला लेख वाकई अच्छा था: https://bgolus.medium.com/the-quest-for-very-wide-outlines-b...
pixel/texel स्तर पर इस्तेमाल किए जा सकने वाले अलग-अलग approaches के बारे में सोचना दिलचस्प है, और यहाँ भी signed distance field (SDF) को कई काम अपने-आप संभालने वाले एक smart तरीके के रूप में इस्तेमाल किया गया है
मनचाही चौड़ाई की outline linear time में बनाना, बड़ी चौड़ाई पर brute-force calculation वाले तरीके की तुलना में शानदार नतीजा है
चाहे vector-based हो, Inigo Quilez के काम की तरह function-based हो, या लेख की तरह texel/voxel-based raster तरीका हो, SDF की जोरदार सिफारिश करूंगा
Houdini भी raster SDF को अच्छी तरह support करता है और उसके पास mature SDF toolset है, इसलिए free version में भी देखने लायक है
GPU पर चलाना हो तो random-access writes, यानी compute shader, की जरूरत पड़ती है—यह इसकी कमी है
CPU ठीक हो तो implementations कुछ उपलब्ध हैं
JavaScript: https://parmanoir.com/distance/
C: https://github.com/983/df
C++: https://github.com/opencv/opencv/blob/4.x/modules/imgproc/sr...
Python: https://github.com/pymatting/pymatting/blob/afd2dec073cb08b8...
क्योंकि quality भी अच्छी होती है, और pulsating outline जैसे distance-based effects render किए जा सकते हैं
यह 3D line drawing tool भी SDF को छोटी texture में लिखकर runtime पर sample करता है: https://x.com/alexanderameye/status/1663523972485357569
SDF वाकई बहुत powerful है
इसी तरीके को radiance cascades तक extend करें तो यह और तेज हो सकता है, इसलिए काफी मजेदार है
कभी न कभी stylized 3D graphics को एक R&D project के रूप में गहराई से explore करना चाहता हूं
हाल में काफी progress हुई है, लेकिन अभी भी बहुत-से आसान मौके बचे दिखते हैं
उत्सुकता है कि camera दूर जाने पर toon-rendered 3D model की details कैसे घटाई जाएं, और ज्यादा stylized look व कम stylized look के बीच natural transition कैसे किया जाए
हाथ से बने 2D animation के watercolor backgrounds को 3D scene में convincingly render किया जा सकता है या नहीं, और screen space में brush strokes व paper texture को smoothly animate कैसे किया जाए—यह भी एक issue है
smoke, flames, trees, grass, mud, rain, fur, water जैसे elements को stylized 3D game में कैसे दिखाया जाए, यह भी open question है
जैसे hand-drawn animation में current camera angle से बेहतर दिखने के लिए model को subtly deform किया जाता है, क्या इसे free-camera game में automate किया जा सकता है—यह भी सोचने लायक है
stylized renderer के लिए ideal mesh editor और background editor कैसा होना चाहिए, और क्या physically accurate 3D surfaces व rigs जरूरी हैं या उन्हें ज्यादा abstract तरीके से define किया जा सकता है—ये भी बड़े सवाल हैं
simple 3D models से retro pixel art render करके procedural 2D game बनाया जा सकता है या नहीं, यह भी interesting है; और जब दो meshes गलती से intersect हों, तो stylization का उपयोग करके उस intersection को कम noticeable बनाया जा सकता है
सिर्फ सवालों से ही दस लोगों के careers भर सकते हैं, लेकिन शायद यही अच्छी बात है
Kuwahara filter भी ज्यादातर लोगों के लिए पर्याप्त believable है
stylized renderer के editing tools के लिए Blender + Rigify + shape keys + थोड़ा driver magic मेरी जरूरतों के लिए काफी रहा
Blender texturing झंझटभरा है, लेकिन hobby level पर सहने लायक है; अगर non-photorealistic rendering control ज्यादा चाहिए, तो DillonGoo Studio का fork बेहतर हो सकता है: https://www.dillongoostudios.com/gooengine
3D models से pixel art बनाने की कोशिश मैंने animations/models को low resolution में render करके की थी, और result ठीक था लेकिन trial-and-error चाहिए था
मुझे याद है कि pixel flicker जैसी चीजें हटाने के लिए post-processing को और sophisticated बनाने के examples भी थे
low resolution: https://x.com/Navy_Green/status/1525564342975995904
stabilization: https://x.com/Navy_Green/status/1693820282245431540
मैंने career की शुरुआत VR apps से की थी, फिर market बेहतर होने की वजह से जल्द ही web development में चला गया
ऐसे लेख पढ़कर वह field याद आती है
3D graphics, collision, shaders पर काम करने में एक ऐसी magical feeling थी जो दूसरे fields में मुश्किल से मिलती है
असल में आप एक world बना रहे होते हैं और physics को reproduce कर रहे होते हैं; math भी programming के दूसरे areas की तुलना में कहीं ज्यादा practical और बार-बार सामने आती है
work satisfaction काफी ज्यादा है, और subject की depth व ceiling भी ऊंची है
4 साल से कर रहा हूं, लेकिन web development की तरह boredom की wall से कभी नहीं टकराया
सोमवार को office जाकर engine editor खोलना, जिस छोटे world पर काम कर रहा हूं उसे render होते देखना, और सोचना कि आगे कौन-सा cool feature डालूं—इससे बेहतर कुछ नहीं
इसके उलट technical art field में अगर outputs सोखकर neural network को खिला दिए जाएं, तो interesting और rewarding हिस्सा गायब हो जाता है, और professional काम में सिर्फ पुराना boring काम बचता है या शायद और बढ़ जाता है
पूरे skillset की value भी घटती है, और tech industry के लोग ऐसे behave करते हैं जैसे वे यह काम असली practitioners से ज्यादा जानते हों; फिर वे complain करते हैं कि हम खुश क्यों नहीं हैं, जबकि Silicon Valley companies ने original data बनाने वालों को pay किए बिना F price पर C- outputs निकालकर market खराब कर दिया
pros-cons तौलें तो मुझे लगता है कि web development में जाना सही choice था
अपने गेम Astral Divide में मैंने एक ऐसी तकनीक बनाई थी जो लेख में नहीं है
यह Blurred Buffer जैसी है, लेकिन blur pass नहीं करती; इसके बजाय anti-aliasing से बनी boundary lines का इस्तेमाल करती है
transparent काले background पर object को opaque सफेद रंग में draw किया जाता है, और fragment shader में alpha channel वाले उन pixels को hardcoded threshold से filter किया जाता है जो न तो पूरी तरह opaque हैं और न ही पूरी तरह transparent
परिणाम काफी अच्छा है, performance cost भी कम है और implementation भी बहुत सरल है
पहले video editing software में crop tool implement करते समय, editing के दौरान cropped area के बाहर वाले हिस्से को अस्थायी रूप से blur करना था
एक और महंगा blur pass जोड़ना नहीं चाहता था, इसलिए सिर्फ mipmap bias बदलकर lower-resolution texture render करवा दी, और texture filtering ने मुफ्त में सारा काम कर दिया
PowerPoint के मिलते-जुलते crop blur effect से तुलना की तो यह लगभग वैसा ही था; बल्कि PowerPoint में हल्की color banding थी, जो मेरे implementation में नहीं थी
इसी तरह, एक स्थिति में full-resolution anti-aliasing नहीं था और canvas का ज्यादातर हिस्सा rotate हो सकने वाले 2D rectangles से बना था, इसलिए edges पर jaggies दिखते थे
full-screen anti-aliasing चालू करने के बजाय मैंने सभी rectangles को थोड़ा बड़ा किया और UV coordinates को proportionally घटा दिया, ताकि visible edge असली 3D rectangle के अंदर आ जाए; इस बार भी texture filtering ने मुफ्त में समस्या हल कर दी
या फिर alpha को बनाए रखते हुए सिर्फ color set किया जाता है, ताकि outline हल्की दिखे लेकिन jagged न हो?
notes शानदार हैं
हाल में edge detection approaches खोजते समय Mars First Logistics के developer का बनाया हुआ एक अच्छा तरीका देखा: https://www.reddit.com/r/Unity3D/comments/taq2ou/improving_e...
लेख भी शानदार है और result भी बहुत अच्छा दिखता है: https://ameye.dev/notes/rendering-outlines/edge-detection/co...
यह Dutch comic Franka के किसी scene जैसा लगता है
सचमुच बेहतरीन लेख है और reading experience भी शानदार है
कठिन concepts को ऐसी भाषा में समझाता है जिसे कोई भी समझ सके, diagrams और examples अच्छे हैं, और spacing व typography की वजह से readability भी top level की है
जानना चाहूंगा कि मौजूदा theme बनाने की प्रेरणा क्या थी, और क्या आपने engineers-केंद्रित publishing platform बनाने के बारे में कभी सोचा था
software में technical art निश्चित रूप से मेरा पहला प्यार है
काश Godot में post-processing effects के लिए compute shader pipeline आसान हो जाए
मौजूदा compositor plugin setup में काफी boilerplate है
यह repository Godot post-processing का अच्छा example है: https://github.com/sphynx-owner/JFA_driven_motion_blur_demo
मुझे याद है कि मैंने यह effect पहली बार Dreamcast के Wacky Races में देखा था
उस समय काफी प्रचार था कि यह इस effect को introduce करने वाला पहला game है और developers ने लगभग इसे invent ही किया है
यह सच था या marketing exaggeration, पता नहीं, लेकिन gamer के तौर पर मैंने इसे पहली बार निश्चित रूप से वहीं अनुभव किया था
Dreamcast ईमानदारी से कहें तो पहला hardware था जो इस effect को high quality में real time चला सकता था
मैंने Dreamcast game Looney Tunes: Space Race का cel shading effect develop किया था, और Dreamcast dev kit मिलने के पहले ही हफ्ते में इसे बना लिया था
Wacky Racers बनाने वाली Infogrames Sheffield ने हमारे implementation का early version देखा और अपने game में मिलता-जुलता effect जोड़ दिया
देखने में अच्छा था, लेकिन production के late stage में आने की वजह से वह हमारे game की तरह इस effect के हिसाब से optimize नहीं था
Jet Grind Radio team ने भी स्वतंत्र रूप से वही effect बनाया और हमसे पहले release किया
algorithm बिल्कुल वही था, लेकिन इस्तेमाल करने का तरीका अलग था: उन्होंने uneven, wide और jagged outlines को खुलकर अपनाया, जबकि Sheffield और हम ज्यादा uniform और traditional art style पाने के लिए उन characteristics से जूझ रहे थे
करीब 1 साल बाद Xbox के Dragons Lair 3D में किसी ने edge-detection cel shading को real time चलाने का तरीका ढूंढ निकाला लगता है
मैंने Dreamcast पर उस approach का test implementation किया था, लेकिन game चलाते हुए कई characters पर एक साथ apply करने के लिए performance बिल्कुल पर्याप्त नहीं थी
यह Xbox के ज्यादा powerful होने की वजह से था या algorithm ज्यादा smart था, पता नहीं, लेकिन result से इनकार नहीं किया जा सकता
अगर आप ऐसा game बनाना चाहते हैं जो असली hand-drawn cartoon जैसा दिखे, तो निजी तौर पर मुझे अब भी वही तरीका highest-quality method लगता है
किसी दिन इसे फिर से implement करने का बहाना ढूंढना चाहूंगा, और अब लगता है performance समस्या नहीं होगी
काफी कमाल का coincidence है
shader programming और 3D rendering जरूर शुरू करनी होगी
ऐसे लेख सच में अच्छे होते हैं, और काश मैं सचमुच shaders इस्तेमाल कर पाता