- सिर्फ 240 lines of GLSL code से Rick एनीमेशन बनाया जाता है, और बिना image या library के GPU shader हर pixel का रंग समय के अनुसार कैलकुलेट करता है
- मुख्य बात यह है कि
color_for_pixel(pixel, time) हर pixel पर चलती है, और signed distance field (SDF) shape के अंदर-बाहर और outline को distance value के रूप में व्यक्त करता है
- चेहरा
round_rect(), circle(), star(), bezier(), parabola() जैसे SDF shapes को move, scale, mirror, repeat और union करके बनाया गया है
- बालों की wave, दांतों की repetition, होंठों की outline, blink, pupil movement, और सिर की हलचल को domain warping,
mod(), sin(time), noise() से चरणबद्ध तरीके से जोड़ा गया है
- final version में portal background भी शामिल है, लेकिन editor में video export नहीं है, इसलिए
glslviewer और ffmpeg का अलग macOS workflow चाहिए
हर pixel का रंग कैलकुलेट करने वाला shader structure
- एनीमेशन OpenGL Shading Language (GLSL) में लिखा गया है
- पेज के अंदर live coding editor में example को तुरंत चलाया और बदला जा सकता है
- basic entry point
color_for_pixel(vec2 pixel, float time) function है
- GPU preview के हर pixel के लिए इस function को चलाता है
- यह function मूलतः यह जवाब देता है: “इस समय इस pixel का रंग क्या होना चाहिए?”
- एक सरल example
pixel.x, pixel.y, time values को color channel में डालकर pixel position और समय के बदलाव को visualize करता है
time आखिरी edit के बाद बीते हुए seconds हैं और लगातार बढ़ता रहता है
SDF से shape बनाने का तरीका
- circle को
length(pixel) से origin से pixel तक की दूरी निकालकर, फिर radius से compare करके बनाया जा सकता है
circle() function सिर्फ अंदर-बाहर बताने वाला bool नहीं, बल्कि boundary तक की दूरी लौटाता है
- shape के अंदर negative
- shape के बाहर positive
- boundary के पास 0 के करीब value
- इस तरह का function signed distance field (SDF) function कहलाता है
- distance value का उपयोग करने से fill और outline दोनों को एक ही तरीके से handle किया जा सकता है
dist < -0.01 हो तो अंदर का रंग
dist < 0.0 हो तो काली outline
- कई SDF shapes को
min() से जोड़ा जा सकता है
- अगर Rick के सिर और कान की outline अलग-अलग की जाए, तो उनके बीच अनचाही line बनती है
- दोनों distance values को
min() से जोड़ने पर सिर्फ सिर और कान की union outline बचती है
- अन्य combination methods भी हैं, और reference के रूप में Inigo Quilez की 2D distance functions और primitive combination सामग्री लिंक की गई है
Rick के चेहरे को बनाने वाले shapes
- सिर का आकार
round_rect() से बनाया गया है, और कान भी अलग round_rect() से जोड़े गए हैं
- season 1 poster की Rick image को preview पर blink की तरह overlap करके values मिलाई गईं
- कई numerical values इसी trial-and-error process में मिलीं
- color values image editor के eyedropper tool से ली गईं
- आंखें circular SDF और 6-point
star() से बनी हैं
- eyeball को
pixel.y *= .93 जैसे coordinate transform से vertical direction में थोड़ा stretch किया गया है
- pupil को 6-point star SDF से छोटी distance value घटाकर corners को round किया गया है
- दोनों आंखें code copy किए बिना
pixel.x = abs(pixel.x) से left-right mirroring करके बनाई गई हैं
- नाक, मुंह और eyebrows
bezier() से बनाए गए हैं
- बाल vertically stretched 11-point
star() से शुरू होते हैं
- shape draw करने वाले code का क्रम भी result को प्रभावित करता है
- जो color पहले return होता है, वही सामने दिखने वाला shape बनता है
- दांत और जीभ सिर्फ मुंह के shape के अंदर वाली condition में ही draw होते हैं, इसलिए वे मुंह के बाहर नहीं आते
बाल, दांत और decorative lines को refine करने की तकनीकें
- सख्त star-shaped बालों को domain warping से wave जैसा बनाया गया है
- domain warping pixel position पर आधारित random offset से coordinates को distort करने का तरीका है
- एक ही position पर एक ही offset लागू होता है, इसलिए समय के साथ distortion consistent रह सकता है
- संबंधित reference के रूप में Inigo Quilez का domain warping लिंक किया गया है
- दांतों में
parabola() SDF से एक shape बनाया जाता है और mod() से उसे horizontally repeat किया जाता है
mod(pixel.x, width) x coordinate को तय interval पर फिर 0 से शुरू करा देता है, जिससे वही shape repeat होता है
pixel.y = abs(pixel.y)-.06 से ऊपर-नीचे के दांत mirror किए जाते हैं
pow(pixel.x, 2.) आधारित y offset से दांतों को smile curve के अनुसार fit किया जाता है
abs(pixel.x+.06) < .194 जैसी conditions से infinite repetition सीमित की जाती है
- होंठ और आंखों के नीचे की lines, SDF outline को बाहर की ओर shift करके बनाई जाती हैं
- सामान्य outline:
abs(distance_to_shape) < thickness
- बाहर की ओर offset outline:
abs(distance_to_shape - outset) < thickness
- आंखों के नीचे की line में position condition जोड़कर उसे सिर्फ आंखों के नीचे के खास हिस्से में दिखाया जाता है
समय से movement बनाने का तरीका
- सबसे सरल animation
sin(time) को code में डालकर बनाई जाती है
sin() लगातार बढ़ते time को -1 से 1 के बीच wrap करके repeating animation बनाता है
sin(time)*.5 + .5 की तरह scale और offset से range adjust की जाती है
- Rick के सिर का rotation, जीभ का rotation और eyebrow height इसी तरीके से animate होते हैं
- rotation calculation के लिए
rotateAt() function जोड़ा गया है
- blink को समय के अनुसार draw होने वाले object को बदलने के तरीके से implement किया गया है
mod(time, 2.) < .09 हो तो बंद आंख draw होती है
- नहीं तो खुली आंख और pupil draw होते हैं
- pupils की irregular movement के लिए
noise() का उपयोग किया गया है
- ताकि आंखें लगातार smoothly move न करें,
round(time) लगाने के बाद उसे noise() में दिया जाता है
- बाल time domain warping से और flexible तरीके से move करते हैं
- space को distort करने की बजाय time value को position के अनुसार delay किया जाता है
- बालों के सिरों के करीब time delay बदलता है, इसलिए पूरा shape rigidly rotate नहीं करता बल्कि मुड़ता है
portal background और समापन
- final version में Rick के पीछे portal effect जोड़कर काम पूरा किया गया है
- portal effect ShaderToy user valena के effect पर आधारित है, और footnote में लिखा है कि यह FabriceNeyret2 का compact किया हुआ version है
- code में performance से ज्यादा readability को प्राथमिकता दी गई है
- पूरा result 2D shader animation के लिए जरूरी shape combination, SDF, warping, repetition, time-based transition, और noise-based movement को एक example में समेटता है
video export workflow
- पेज का editor अभी animation को video के रूप में export नहीं कर सकता
- अस्थायी तरीके के रूप में glslviewer और ffmpeg का macOS workflow दिया गया है
- dependency install example इस प्रकार है
brew install glslviewer ffmpeg
- export script एक temporary directory बनाती है और
glslViewer को headless mode में चलाती है
- resolution:
1920x1080
- sequence:
0 सेकंड से 7 सेकंड तक
- frame rate:
60
- output file:
animation.mp4
- local live coding example में यह command उपयोग की जाती है
glslViewer shader.frag -w 575 -h 324 --noncurses -x 0 -y 0
super sampling और लेख की शुरुआत
- shape edges smooth दिखने का कारण यह है कि पीछे super sampling लागू किया गया है
- super sampling में स्क्रीन के एक pixel के भीतर 9 positions पर
color_for_pixel() को call किया जाता है और उनका average दिखाया जाता है
#version 300 es इस्तेमाल करने पर editor का “pro” mode चालू होता है और automatic super sampling हट जाती है
- लेख की शुरुआत 8 महीने पहले जारी किए गए वीडियो I Made a 3D Modeler, in C, in a Week से हुई थी
- उस वीडियो में marching cubes algorithm समझाने वाला animation शामिल था
- सामान्य animation software से इसे सटीक और तेज़ी से बनाना मुश्किल लगा, इसलिए इसे code में बनाना शुरू किया गया
- बाद में लोगों ने animation बनाने की विधि पूछी, तो इसे इस लेख में संकलित किया गया
1 टिप्पणियां
Hacker News की राय
मूल रूप से
stepफ़ंक्शन कोaastepसे बदलना होता है: https://github.com/glslify/glsl-aastepयह उस web, protocol और application development से बहुत अलग, बेहद सघन और iterative काम करने का तरीका है जिससे ज़्यादातर लोग परिचित होते हैं
floatका एक मान बदलकरshift-enterदबाते ही तुरंत नतीजा दिखना काफ़ी संतोषजनक हैcanvasया उसके ऊपर p5.js जैसी abstraction के साथ काम करना भी ऐसा ही महसूस होता हैसमझ नहीं आ रहा कि यह graphics programming की बात सामान्य रूप से हो रही है, या खास तौर पर GPU shader work की
Inigo Quilez की एक संबंधित YouTube playlist भी है: https://www.youtube.com/watch?v=0ifChJ0nJfM&list=PL0EpikNmjs...
shader programming सच में एक अलग ही दुनिया है, और लेख भी बहुत शानदार है
सोच रहा हूँ कि इसे Vulkan या WebGPU/WebGL में करने का अनुभव कैसा होगा
Vulkan में तकनीकी रूप से यह GLSL से SPIR-V तक जाने वाला रूप है
browser में चलने वाला WebGPU तकनीकी रूप से सीधे GLSL इस्तेमाल नहीं कर सकता, लेकिन native WebGPU implementations GLSL ले सकती हैं, और conversion भी संभव है
या फिर सीधे WGSL इस्तेमाल किया जा सकता है, जो C-जैसे syntax की जगह Rust से प्रेरित syntax होने को छोड़कर, GLSL से काफ़ी मिलता-जुलता है
सही decimal values वाली 240 lines को trial and error से बनाना काफ़ी समय लेने वाला लगा होगा
binary search हाथ से भी तेज़ी से की जा सकती है
uniform shader तक भेजा जाता है, और बिना recompile किए उसे update किया जा सकता है