वीडियो गेम में LUT (लुकअप टेबल) का उपयोग कैसे करें
(blog.frost.kiwi)- LUT (लुकअप टेबल) पहले से गणना किए गए मानों को तालिका की तरह संदर्भित करने का तरीका है, और इसका उपयोग ब्लैक-एंड-व्हाइट थर्मल इमेज को colorize करने से लेकर game object variations और video color correction तक व्यापक रूप से होता है
- WebGL उदाहरण दिखाता है कि video frames को GPU पर 2D texture के रूप में अपलोड करने के बाद, pixel-by-pixel रंग तय करने वाले Fragment Shader में tinting, 1D LUT और 3D LUT कैसे लागू किए जाते हैं
- 1D LUT brightness value
[0.0 - 1.0]को color array coordinate के रूप में इस्तेमाल करके black-and-white input को RGB में बदलता है, और GPU की bilinear filtering छोटे LUT में भी intermediate values को स्वाभाविक रूप से interpolate करती है - Valve के Left 4 Dead उदाहरण की तरह, tinting और LUT का उपयोग सीमित texture memory के भीतर cars, zombie skin और clothing color variations बनाने के लिए किया जाता है
- LUT अतिरिक्त texture read cost लाता है, लेकिन gamma correction जैसी महंगी calculations को पहले से store करने या external color-correction workflow को real-time graphics में लाने का व्यावहारिक tool है
Graphics programming में LUT क्या करता है
- लुकअप टेबल (LUT) वह तरीका है जिसमें values को पहले से calculate करके rows या table में store किया जाता है और जरूरत पड़ने पर refer किया जाता है
- Graphics programming में यह सरल structure कई visual effects में बदल जाता है
- Black-and-white video को color में बदलना
- Color correction और styling लागू करना
- Video games में object variations को efficient बनाना
- उदाहरण WebGL का उपयोग करता है, लेकिन वही techniques DirectX, OpenGL, Vulkan, Unity और scientific data visualization पर भी लागू होती हैं
- Demo video Panasonic GH6 और TESTO 890 thermal imaging camera captures हैं, और processing device से बाहर नहीं जाती बल्कि GPU पर locally process होती है
WebGL pipeline और tinting
- Thermal imaging camera output black-and-white video है, और हर frame WebGL के जरिए GPU को 2D texture के रूप में भेजा जाता है
- Fragment Shader वह code है जो output के हर fragment के लिए final color तय करता है
- उदाहरण में
texture2D(video, tex).rgbसे current position का video color पढ़ा जाता है - WebGL 1.0 का final output
gl_FragColorमें RGBA के रूप में लिखा जाता है
- उदाहरण में
- Tinting texture color को किसी खास color से multiply करके tint लगाने का तरीका है
- उदाहरण में
videoColor * vec3(1.0, 0.5, 0.0)से orange color multiply किया जाता है - White orange बन जाता है, और black पर 0 multiply होता है, इसलिए वह black ही रहता है
- उदाहरण में
- इस context में tinting की multiplication cost मापना मुश्किल हो सकता है
- Memory से texture fetch करने की cost multiplication से कहीं ज्यादा होती है
- कई threads parallel चलने पर, एक pixel के texture read का इंतजार करते समय दूसरे pixel की multiplication हो सकती है
- यह tinting से होने वाले फर्क की व्याख्या है, इसका मतलब यह नहीं कि पूरी performance optimization खत्म हो गई है
Valve का tinting उपयोग
- Valve Software के Left 4 Dead ने car variations बनाने के लिए tinting का उपयोग किया
- हर car type और color के लिए अलग texture रखने का तरीका texture memory budget से ज्यादा हो सकता है
- Valve ने अतिरिक्त mask texture का उपयोग करके cars के painted areas को हर instance में अलग color से tint किया
- एक color texture 3 car models में share किया गया
- दो textures की cost में 4 car models और practically unlimited color variations बनाए जा सकते हैं
- Source Engine में cars ही नहीं, मूल रूप से हर चीज को tint किया जा सकता है
1D LUT से black-and-white values को colors में बदलना
- 1D LUT एक number array है, और अगर यह RGB image है तो colors की one-dimensional array है
- Black-and-white video की brightness value को LUT के X-axis coordinate के रूप में इस्तेमाल करके color लाया जाता है
- Video brightness value की range
[0.0 - 1.0]होती है 0.0black LUT के left-side color में, और1.0white LUT के right-side color में map होता है- Intermediate values अपनी corresponding position के color में map होती हैं
- Video brightness value की range
- WebGL 1.0 में 1D texture नहीं है, इसलिए height 1px वाली 2D texture का उपयोग होता है
- Example code
texture2D(lut, vec2(videoColor, 0.5))जैसा है - Input 1D value है और output RGB, इसलिए structure “1D vector in, 3D vector out” है
- Example code
- GPU की bilinear filtering texture read के दौरान intermediate values को automatically interpolate करती है
- 8-bit black-and-white video में brightness के 256 levels होते हैं
- 1D LUT 32 pixels wide हो तब भी pixels के बीच access linear interpolate होता है
- सिर्फ 8 pixels की information से भी 256-pixel wide colorful gradient के करीब result बनाया जा सकता है
Colormap selection और perceptual uniformity
- उदाहरण में matplotlib द्वारा supported colormaps को 1D LUT के रूप में export की गई list शामिल है
- अगर data में कोई specific structure नहीं है, तो perceptually uniform colormap का उपयोग करना या उस पर आधारित colors सेट करना उचित है
- viridis family इसका प्रमुख उदाहरण है
- Perceptually uniform colormap black-and-white में output करने पर भी ठंडे हिस्सों को dark और गर्म हिस्सों को bright बनाए रखता है
- jet जैसे colormaps, जो मुख्य रूप से सिर्फ hue बदलते हैं और perceived brightness को ignore करते हैं, इस property की guarantee नहीं देते
- Color vision deficiency वाले लोग भी data को सही तरह interpret कर सकें, यह colormap selection का अहम criterion है
1D LUT की performance और polynomial approximation
- छोटे 1D LUT की cost आम तौर पर कम हो सकती है, लेकिन tinting की तुलना में consider करने वाली बातें ज्यादा हैं
- मुख्य performance concern dependent texture read है
- पहली texture read के result से दूसरी texture read के coordinates तय होते हैं
- Graphics programming में यह ऐसा pattern है जो कुछ optimizable paths को खत्म कर सकता है
- GPU में texture cache होता है, और छोटा LUT cache में fit हो सकता है, इसलिए LUT read की cost बहुत कम हो सकती है
- Fine-grained performance judgment के लिए platform-specific analysis tools चाहिए
- Nvidia NSight shader stage-wise performance analysis देता है, लेकिन OpenGL support नहीं करता
- Colormap को polynomial approximation के रूप में implement करने पर LUT texture संभालने की जरूरत नहीं रहती
- Matt Zucker का ShaderToy viridis approximation Horner’s method के रूप में लिखा गया है
c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))))जैसी multiply-add operations से color calculate होता है- यह वास्तव में LUT से fast है या नहीं, platform-specific analysis के बिना तय करना मुश्किल है
Left 4 Dead 2 में zombie variations
- Left 4 Dead 2 ने skin और clothing color variations के लिए LUT का उपयोग किया, जहां simple tinting पर्याप्त नहीं था
- Simple tinting color luminance की पर्याप्त diversity नहीं दे सकता
- Skin और clothing के लिए कुछ LUTs randomly चुनकर diverse color variations बनाए जाते हैं
- Artists उपयुक्त color ramp चुनकर final game में शामिल करते हैं
- एक ही texture से light suit और dark suit, दोनों variations बनाए जा सकते हैं
- संबंधित पूरा presentation GDC के Shading a Bigger Better Sequel में देखा जा सकता है
- “Exclusive Masking” एक channel के अंदर दो texture ranges रखने का तरीका है
- Texture 1 की range 0-128 है
- Texture 2 की range 128-256 है
- इसकी cost color precision में कमी है
Calculations को पहले से store करने वाला 1D LUT
- 1D LUT graphics programming में महंगी calculation cache के रूप में भी इस्तेमाल हो सकता है
- Gamma correction इसका representative example है
- Approximation नहीं हो तो
pow()function चाहिए - खासकर पुराने GPU पर
pow()महंगा instruction हो सकता है - Standard sRGB piecewise curve इस्तेमाल करने पर branching paths भी जुड़ते हैं
- Approximation नहीं हो तो
- Gamma transform को 1D LUT में पहले से calculate करने पर per-pixel calculation skip की जा सकती है
- इस case में structure “1D vector in, 1D vector out” है
- 1D LUT अधिकतम 4 color channels output कर सकता है
- Redshift रात में monitor को orange tint करके eye strain घटाने वाला software example है
- Monitor के Red, Green, Blue channels की Gamma Ramp बदली जाती है
- Kelvin Warmth → RGB और extra Gamma calculations को 3 1D LUTs में पहले से calculate किया जाता है
- संबंधित implementation Redshift source code में है
- इस तरीके में remapping graphics card के बजाय monitor पर होती है, इसलिए performance impact नहीं होता, लेकिन hardware interface support आजकल अक्सर unstable या broken है
- Raspberry Pi graphics stack में नए update के बाद related support खो जाने का case रहा है
- Microsoft SetDeviceGammaRamp के उपयोग को लेकर warning देता है
3D LUT: पूरे RGB space को remap करना
- 3D LUT पूरे RGB space को एक cube के रूप में represent करता है और सभी possible colors को remap करता है
- Input RGB 3D vector को output RGB 3D vector में बदलता है, इसलिए structure “3D vector in, 3D vector out” है
- Red, Green, Blue में से प्रत्येक पर 1D LUT लागू करने से color balance बदला जा सकता है
- 3D LUT उन transforms के लिए जरूरी है जिन्हें input RGB combination चाहिए
- Saturation बदलना
- Hue बदलना
- Specific color adjustment
- Color separation
- 3D LUT आमतौर पर cube shape में होता है, games में strip या square form में store होता है और video editing tools में “Iridas/Adobe”
.cubefile के रूप में इस्तेमाल होता है
WebGL 1.0 में 3D LUT implement करना
- उदाहरण का video Panasonic GH6 से shoot किया गया Panasonic V-Log color profile video है
- V-Log एक log profile है जो ज्यादा dynamic range preserve करता है और जिसकी Gamut और Gamma definitions स्पष्ट हैं
- WebGL 1.0 3D textures support नहीं करता, इसलिए 3D texture read को दो 2D texture reads और interpolation से implement किया जाता है
- यह approach Gregg Tavares की Google I/O 2011 presentation और webglfundamentals.org explanation पर आधारित है
- पुराने example code में cube के Z-axis calculation issue की वजह से colors blue की तरफ shift हो जाते थे, और इसे 2019 में fix किया गया
- WebGL 1.0, OpenGLES 2, OpenGL 2.1 में
OES_texture_3Dके बिना compatibility बनाए रखने के लिए latest version का 3D texture sampling code इस्तेमाल करना चाहिए
3D LUT से color-correction workflow लाना
- 3D LUT में लागू correction उस LUT से गुजरने वाले video या graphics scene पर ज्यों का त्यों लागू होता है
- उदाहरण में V-Log video और LUT को DaVinci Resolve में import करके process किया गया
- Panasonic का V-Log to V-709 3D-LUT apply किया गया
- Contrast और white point correct किए गए ताकि white full-bright बने
- Corrected LUT को फिर export किया गया
- Actual video original ही रहता है, और color correction WebGL में LUT के जरिए real time में apply होती है
- Upload की जाने वाली LUT का same size maintain करना जरूरी है
- Example constraint 1024px × 32px है
- 32³px cube shape maintain करनी होती है
Game industry में 3D LUT का उपयोग
- 3D LUT से game colors को external tools में style करने का तरीका video game industry में जाना-माना standard workflow है
- सामान्य procedure इस प्रकार है
- Color-correct करने वाले scene का screenshot लें
- Screenshot और initialized 3D LUT को Photoshop जैसे tool में साथ खोलें
- Screenshot और LUT पर वही color correction apply करें
- 3D LUT वाले हिस्से को crop करके export करें
- Left 4 Dead ने भी यही तरीका इस्तेमाल किया
- Photoshop के color-correction tools freely इस्तेमाल किए जा सकते हैं, लेकिन ऐसे filters नहीं जो कई pixels के बीच relationship बदलते हैं
- blur, sharpen, emboss जैसी convolution family remapped color को blur कर सकती है और unexpected results दे सकती है
Complex color correction को एक LUT में bake करना
- DaVinci Resolve जैसे color-correction tools complex color transforms और color grade बनाकर 3D LUT के रूप में export कर सकते हैं
- उदाहरण Kodak 2383 LUT apply करता है, जिसका goal film print look है
- V-Log gamma video को Cineon Film Log में convert किया जाता है
- Color space को Rec.709 में convert किया जाता है
- Film emulation apply की जाती है और gamma को फिर Rec.709 में convert किया जाता है
- अंत में white point adjust किया जाता है
- Complex detailed transforms को खुद implement किए बिना, उन्हें एक LUT में bake करके graphics application में लाया जा सकता है
- Example video article compatibility के लिए 8-bit highly compressed format में है, लेकिन आम तौर पर यह काम 10-bit footage पर किया जाता है
- LUT size उम्मीद से छोटा इस्तेमाल होता है
- Panasonic official V-Log to V-709 3D-LUT 33³px size का है
- Panasonic का in-camera monitoring LUT 17³px इस्तेमाल करता है
- Panasonic के five-figure dollar price range वाले cinema cameras में भी 17³px इस्तेमाल होता है
Graphics के बाहर LUT examples
- LUT graphics programming के अलावा भी इस्तेमाल होता है
- Tomb Raider को Game Boy Advance पर port करने के case में भी LUT का उपयोग हुआ
- Open source OpenLara engine port में Game Boy Advance पर 3D capability नहीं होने के कारण काफी चीजें software में process होती हैं
- उनमें से एक integer division implement करने वाला LUT है
- यह LUT load instruction skip करने के लिए ROM address space की शुरुआत में रखा गया है
1 टिप्पणियां
Hacker News टिप्पणियाँ
अगर lookup table नहीं होती तो शायद यह effect बिल्कुल implement नहीं हो पाता: https://twitter.com/zeta0134/status/1756988843851383181
मुख्य बात यह है कि इसमें दो lookup tables इस्तेमाल होती हैं। बड़ी table player के आसपास configurable radius के भीतर circular lighting जानकारी store करती है, और हर radius के लिए पूरी एक table होती है। दूसरी table background rows का pseudo-random order रखती है। Torch lighting routine हर बार call होने पर screen का सिर्फ 1/20 हिस्सा update करने जितना ही समय होता है, लेकिन order को थोड़ा randomize कर देने से किनारे smooth दिखते हैं और जो raster scan दिखना चाहिए था वह छिप जाता है। यह order एक grab bag है ताकि rows update से भूखी न रह जाएँ, इसलिए real-time calculation बहुत धीमी होने के कारण table इस्तेमाल की जाती है
पहली बार मुझे सच में impress करने वाला lookup table effect texture tunnel बनाने के लिए इस्तेमाल होने वाला तरीका था
screen के हर pixel के लिए screen center से angle और distance बताने वाली lookup table रखी जाती है, और उससे तय किया जाता है कि हर pixel position पर कौन-सा texel रखा जाए। यह 3D geometry tunnel के अंदर move करने जैसा दिखता है, लेकिन cost बहुत कम है, इसलिए pico पर भी संभव है: https://www.lexaloffle.com/bbs/?pid=63818
पहले मैंने सोचा था कि Stardust ने यह effect इस्तेमाल किया होगा, लेकिन अभी खोजने पर पता चला कि असल में वह background में repeat होने वाली 6-frame animation ही चला रहा था: https://codetapper.com/amiga/sprite-tricks/stardust/
https://web.engr.oregonstate.edu/~mjb/cs557/Projects/Papers/Parallax_Occlusion_Mapping.pdf
Wind Waker अपने unique look को बनाने के लिए कई LUT कैसे इस्तेमाल करता है, इस पर एक video है। BoTW और ToTK भी वही technique इस्तेमाल करते हैं
https://www.youtube.com/watch?v=mnxs6CR6Zrk
मैं article का author हूँ। अगर सवाल हों तो जवाब दे सकता हूँ
हालांकि LUT से color correction introduce करने का तरीका थोड़ा surprising था। article में यह L4D2 द्वारा इस्तेमाल किया गया कोई cool niche solution जैसा लगता है, लेकिन असल में यह लंबे समय से industry standard रहा है और मैंने जिन भी games पर काम किया, उनमें इस्तेमाल हुआ। AAA game NFS (2015) से लेकर Lost in Random जैसे बड़े indie games तक, सब में ऐसा था
[1] https://www.copetti.org/writings/consoles/game-boy-advance/#operating-system
“Here is every single colormap that matlibplot supports,...”
boring business process वाले area में भी यह हैरान करने वाली हद तक useful है
अक्सर conditions से भरे codebase को साफ़ lookup table से simplify किया जा सकता है। दिखने में simple चीज़ भी 50,000 rows वाली lookup table बन सकती है, इसलिए शायद यह हमेशा दिमाग में नहीं आता। end users को यह ज्यादा लग सकता है, लेकिन computer को खास फर्क नहीं पड़ता। ऊपर से lookup tables repetitive होती हैं, इसलिए आम तौर पर manage करना आसान होता है, और जब ऐसा नहीं भी हो तब भी वे valuable रहती हैं। end users बिना code changes के configure भी कर सकते हैं। कुल मिलाकर business logic code करते समय बार-बार आने वाले कई scenarios के लिए यह concept खास तौर पर useful है
रेट्रो गेम्स में tables का सचमुच बहुत इस्तेमाल होता था। उस समय memory speed बहुत तेज़ थी और processors धीमे थे, इसलिए जितनी ज़्यादा calculations हो सकें उन्हें tables में डाल देना समझदारी थी। जितना ज़्यादा चतुराई से करते, उतना ही शानदार game बना सकते थे
मुझे वह channel पसंद है। अपने mod को तेज़ चलाने के लिए उन्होंने Mario 64 के पूरे code को ठीक-ठाक किया और stable 60FPS तक पहुँचा दिया [0]
[0] https://www.youtube.com/watch?v=t_rzYnXEQlE
अगर आप LUTs के साथ बहुत काम करते हैं, तो मैं advanced color science और transformation work संभालने वाला एक Mac app बना रहा हूँ: https://videovillage.com/lattice
पहले मैंने NES homebrew scene में थोड़ा हाथ डाला था, और Bobl नाम के game में ऐसे कमाल के physics effects देखे जो system की calculation क्षमता से बहुत आगे लगते थे। बाद में पता चला कि वह lookup table था, और समझ आया कि simple tools से किसी चीज़ को बहुत complex process जैसा दिखाया जा सकता है
https://morphcatgames.itch.io/bobl
A Podcast Of Unnecessary Detail ने SNES Doom porting और trigonometric functions के लिए LUTs के इस्तेमाल पर एक episode किया था। context यह था कि SNES में graphics processor नहीं था
https://festivalofthespokennerd.com/podcast/series-3-episode-4/
https://github.com/RandalLinden/DOOM-FX
यह background rotation और scaling भी कर सकता था। Mode 7 वही है—Mario Kart याद करें—और कई games में दिखने वाली hardware transparency भी support करता था। ऊपर से Doom और Starfox जैसे मामलों में cartridge में 3D calculations संभालने वाली Super FX chip लगी होती थी। इसलिए उस specific title में असल में दो graphics processors से काम लिया जा रहा था
SNES hardware को शानदार और दर्दनाक रूप से detail में समझाने वाली Retro Game Mechanics Explained series है:
https://www.youtube.com/watch?v=57ibhDU2SAI&list=PLHQ0utQyFw5KCcj1ljIhExH_lvGwfn6GV&index=1
मुझे याद आने वाले LUT के अपने uses ये थे: atmospheric scattering, sprites को color करना, night-vision scopes, FLIR scopes, black-and-white “video feed” effect, glitch effects, heightmap shading, spaceship exhaust plume के alpha dot coefficient, website visitors के mouse dwell heatmaps, crystalline effects, और अंत में raw color space में post-processing colorization
LUT दरअसल पहले से ज्ञात values के array का visualization है, और हैरान करने वाली हद तक useful है
http://www.effectgames.com/demos/canvascycle/
original Xbox यह ठीक से नहीं कर पाता था और Photoshop भी support नहीं करता था, इसलिए इसकी popularity घट गई। लेकिन modern hardware AESprite में बने palette swaps को आराम से handle कर सकता है