Cách dùng LUT (bảng tra cứu) trong video game
(blog.frost.kiwi)- LUT (bảng tra cứu) là cách tham chiếu các giá trị đã được tính trước như một bảng, được dùng rộng rãi từ tô màu video ảnh nhiệt đen trắng cho đến biến thể đối tượng trong game và chỉnh màu video
- Ví dụ WebGL cho thấy luồng đưa khung hình video lên GPU dưới dạng texture 2D, rồi áp dụng tinting, LUT 1D và LUT 3D trong Fragment Shader quyết định màu theo từng pixel
- LUT 1D dùng giá trị độ sáng
[0.0 - 1.0]làm tọa độ trong mảng màu để chuyển đầu vào đen trắng thành RGB; bộ lọc song tuyến tính của GPU giúp nội suy tự nhiên các giá trị trung gian ngay cả với LUT nhỏ - Như trường hợp Left 4 Dead của Valve, tinting và LUT được dùng để tạo các biến thể màu cho xe hơi, da zombie và trang phục trong giới hạn bộ nhớ texture
- LUT làm phát sinh chi phí đọc texture bổ sung, nhưng là công cụ thực dụng để lưu trước các phép tính đắt như hiệu chỉnh gamma hoặc đưa quy trình chỉnh màu bên ngoài vào đồ họa thời gian thực
Vai trò của LUT trong lập trình đồ họa
- Bảng tra cứu (LUT) là phương pháp tính trước các giá trị, lưu chúng vào hàng hoặc bảng, rồi tham chiếu khi cần
- Trong lập trình đồ họa, cấu trúc đơn giản này dẫn tới nhiều hiệu ứng thị giác
- Chuyển video đen trắng thành có màu
- Áp dụng hiệu chỉnh màu và tạo phong cách màu
- Tạo biến thể đối tượng trong video game một cách hiệu quả
- Ví dụ sử dụng WebGL, nhưng kỹ thuật tương tự cũng áp dụng được cho DirectX, OpenGL, Vulkan, Unity và trực quan hóa dữ liệu khoa học
- Video demo là bản capture từ Panasonic GH6 và camera ảnh nhiệt TESTO 890; quá trình xử lý không rời khỏi thiết bị mà được xử lý cục bộ trên GPU
Pipeline WebGL và tinting
- Đầu ra của camera ảnh nhiệt là video đen trắng, và mỗi khung hình được truyền tới GPU qua WebGL dưới dạng texture 2D
- Fragment Shader là đoạn mã quyết định màu cuối cùng cho từng fragment của đầu ra
- Trong ví dụ, màu video tại vị trí hiện tại được đọc bằng
texture2D(video, tex).rgb - Đầu ra cuối cùng của WebGL 1.0 được ghi vào
gl_FragColordưới dạng RGBA
- Trong ví dụ, màu video tại vị trí hiện tại được đọc bằng
- Tinting là cách nhân màu của texture với một màu cụ thể để phủ sắc thái màu
- Trong ví dụ, màu cam được nhân bằng
videoColor * vec3(1.0, 0.5, 0.0) - Màu trắng trở thành màu cam, còn màu đen vẫn là màu đen vì được nhân với 0
- Trong ví dụ, màu cam được nhân bằng
- Trong ngữ cảnh này, chi phí phép nhân của tinting có thể khó đo
- Chi phí lấy texture từ bộ nhớ lớn hơn phép nhân rất nhiều
- Khi nhiều luồng chạy song song, phép nhân cho pixel khác có thể được thực hiện trong lúc chờ đọc texture của một pixel
- Đây là giải thích về khác biệt mà tinting tạo ra, không có nghĩa là toàn bộ tối ưu hiệu năng đã hoàn tất
Valve sử dụng tinting
- Left 4 Dead của Valve Software dùng tinting để tạo biến thể xe hơi
- Cách tạo texture riêng cho từng loại xe và từng màu có thể vượt quá ngân sách bộ nhớ texture
- Valve dùng thêm texture mask để tinting vùng sơn xe bằng màu khác nhau cho từng instance
- Một texture màu được chia sẻ giữa 3 mẫu xe
- Với chi phí của hai texture, có thể tạo 4 mẫu xe và số biến thể màu gần như không giới hạn
- Trong Source Engine, không chỉ xe hơi mà về bản chất mọi thứ đều có thể tinting
Dùng LUT 1D để chuyển giá trị đen trắng thành màu
- LUT 1D là một mảng số; nếu là ảnh RGB thì đó là mảng một chiều gồm các màu
- Giá trị độ sáng của video đen trắng được dùng làm tọa độ trục X của LUT để lấy màu
- Giá trị độ sáng video nằm trong phạm vi
[0.0 - 1.0] - Màu đen
0.0được ánh xạ tới màu bên trái LUT, màu trắng1.0tới màu bên phải LUT - Các giá trị trung gian được ánh xạ tới màu ở vị trí tương ứng
- Giá trị độ sáng video nằm trong phạm vi
- WebGL 1.0 không có texture 1D nên dùng texture 2D cao 1px
- Mã ví dụ có dạng
texture2D(lut, vec2(videoColor, 0.5)) - Đầu vào là giá trị 1D và đầu ra là RGB, nên cấu trúc là “vector 1D vào, vector 3D ra”
- Mã ví dụ có dạng
- Bộ lọc song tuyến tính của GPU tự động nội suy giá trị trung gian khi đọc texture
- Video đen trắng 8-bit có 256 mức độ sáng
- Ngay cả khi LUT 1D chỉ rộng 32 pixel, truy cập giữa các pixel vẫn được nội suy tuyến tính
- Chỉ với thông tin của 8 pixel cũng có thể tạo kết quả gần với gradient nhiều màu rộng 256 pixel
Chọn colormap và tính đồng đều theo cảm nhận
- Ví dụ có kèm danh sách các colormap do matplotlib hỗ trợ được xuất ra thành LUT 1D
- Nếu dữ liệu không có cấu trúc cụ thể, nên dùng colormap đồng đều theo cảm nhận hoặc đặt màu dựa trên loại đó
- Họ viridis là ví dụ tiêu biểu
- Colormap đồng đều theo cảm nhận vẫn giữ vùng lạnh tối và vùng nóng sáng ngay cả khi xuất ra đen trắng
- Những colormap như jet, chủ yếu chỉ đổi màu sắc (hue) và bỏ qua độ sáng theo cảm nhận, không đảm bảo thuộc tính này
- Việc người có bất thường về thị giác màu cũng có thể diễn giải dữ liệu đúng là một tiêu chí quan trọng khi chọn colormap
Hiệu năng của LUT 1D và xấp xỉ đa thức
- Chi phí của LUT 1D nhỏ nhìn chung có thể thấp, nhưng có nhiều điểm cần cân nhắc hơn tinting
- Mối lo hiệu năng chính là đọc texture phụ thuộc
- Kết quả của lần đọc texture đầu tiên quyết định tọa độ cho lần đọc texture thứ hai
- Trong lập trình đồ họa, đây là mẫu có thể loại bỏ một phần các đường đi có thể tối ưu
- GPU có texture cache, và LUT nhỏ có thể nằm trong cache nên chi phí đọc LUT có thể rất thấp
- Đánh giá hiệu năng chi tiết cần công cụ phân tích theo từng nền tảng
- Nvidia NSight cung cấp phân tích hiệu năng theo từng giai đoạn shader, nhưng không hỗ trợ OpenGL
- Nếu triển khai colormap bằng xấp xỉ đa thức, không cần xử lý texture LUT
- Xấp xỉ viridis trên ShaderToy của Matt Zucker được viết theo dạng Horner’s method
- Màu được tính bằng các phép multiply-add dạng
c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6))))) - Việc nó có thực sự nhanh hơn LUT hay không khó kết luận nếu không phân tích theo từng nền tảng
Biến thể zombie trong Left 4 Dead 2
- Left 4 Dead 2 dùng LUT cho các biến thể màu da và trang phục, nơi chỉ tinting đơn giản là chưa đủ
- Tinting đơn giản có thể không cung cấp đủ sự đa dạng về độ chói của màu
- Với da và trang phục, một vài LUT được chọn ngẫu nhiên để tạo nhiều biến thể màu
- Artist chọn color ramp phù hợp để đưa vào game cuối cùng
- Từ một texture có thể tạo cả biến thể vest sáng và vest tối
- Bài trình bày đầy đủ liên quan có thể xem tại Shading a Bigger Better Sequel của GDC
- “Exclusive Masking” là cách đặt hai phạm vi texture trong cùng một kênh
- Texture 1 dùng phạm vi 0-128
- Texture 2 dùng phạm vi 128-256
- Cái giá phải trả là giảm độ chính xác màu
LUT 1D lưu trước phép tính
- LUT 1D cũng có thể được dùng làm cache cho phép tính đắt trong lập trình đồ họa
- Hiệu chỉnh gamma là ví dụ tiêu biểu
- Nếu không dùng xấp xỉ, cần hàm
pow() - Đặc biệt trên GPU cũ,
pow()có thể là lệnh đắt - Nếu dùng đường cong từng đoạn chuẩn sRGB, còn có thêm đường rẽ nhánh
- Nếu không dùng xấp xỉ, cần hàm
- Tính trước chuyển đổi gamma bằng LUT 1D giúp bỏ qua phép tính theo từng pixel
- Trong trường hợp này, cấu trúc là “vector 1D vào, vector 1D ra”
- LUT 1D có thể xuất tối đa 4 kênh màu
- Redshift là một ví dụ phần mềm tinting màn hình sang màu cam vào ban đêm để giảm mỏi mắt
- Thay đổi Gamma Ramp cho từng kênh Red, Green, Blue của màn hình
- Tính trước Kelvin Warmth → RGB và phần tính Gamma bổ sung thành 3 LUT 1D
- Phần triển khai liên quan nằm trong mã nguồn Redshift
- Cách này không ảnh hưởng hiệu năng vì việc remapping diễn ra ở màn hình chứ không phải card đồ họa, nhưng hỗ trợ giao diện phần cứng hiện nay thường chập chờn hoặc hỏng
- Graphics stack của Raspberry Pi từng mất hỗ trợ liên quan trong một bản cập nhật mới
- Microsoft cảnh báo về việc dùng SetDeviceGammaRamp
LUT 3D: remap toàn bộ không gian RGB
- LUT 3D biểu diễn toàn bộ không gian RGB như một khối lập phương và remap mọi màu khả dĩ
- Nó chuyển vector RGB đầu vào ba chiều thành vector RGB đầu ra ba chiều, nên có cấu trúc “vector 3D vào, vector 3D ra”
- Áp dụng LUT 1D cho từng kênh Red, Green, Blue có thể thay đổi cân bằng màu
- LUT 3D cần thiết cho các phép biến đổi cần tổ hợp RGB đầu vào
- Thay đổi độ bão hòa
- Thay đổi màu sắc (hue)
- Điều chỉnh màu cụ thể
- Tách màu
- LUT 3D thường có dạng khối lập phương; trong game nó được lưu ở dạng strip hoặc square, còn trong công cụ biên tập video được dùng dưới dạng file
.cube“Iridas/Adobe”
Triển khai LUT 3D trong WebGL 1.0
- Video trong ví dụ được quay bằng Panasonic GH6 với profile màu Panasonic V-Log
- V-Log là profile log giữ được dải động rộng hơn và có định nghĩa Gamut cùng Gamma rõ ràng
- WebGL 1.0 không hỗ trợ texture 3D, nên việc đọc texture 3D được triển khai bằng hai lần đọc texture 2D và nội suy
- Cách tiếp cận này dựa trên bài trình bày Google I/O 2011 của Gregg Tavares và phần giải thích trên webglfundamentals.org
- Mã ví dụ cũ từng có lỗi tính trục Z của khối lập phương khiến màu bị dịch sang xanh lam, và đã được sửa vào năm 2019
- Để giữ tương thích trong WebGL 1.0, OpenGLES 2 và OpenGL 2.1 khi không có
OES_texture_3D, cần dùng phiên bản mới nhất của mã lấy mẫu texture 3D
Đưa quy trình chỉnh màu vào bằng LUT 3D
- Các hiệu chỉnh áp dụng lên LUT 3D sẽ được áp dụng nguyên vẹn lên video hoặc cảnh đồ họa đi qua LUT đó
- Trong ví dụ, video V-Log và LUT được đưa vào DaVinci Resolve để xử lý
- Áp dụng V-Log to V-709 3D-LUT của Panasonic
- Hiệu chỉnh độ tương phản và white point để màu trắng trở thành full-bright
- Xuất lại LUT đã được hiệu chỉnh
- Video thực tế vẫn là bản gốc, còn chỉnh màu được áp dụng thời gian thực trong WebGL thông qua LUT
- LUT tải lên phải giữ cùng kích thước
- Ràng buộc của ví dụ là 1024px × 32px
- Phải giữ dạng khối lập phương 32³px
Cách ngành game dùng LUT 3D
- Dùng LUT 3D để tạo phong cách màu cho game bằng công cụ bên ngoài là workflow chuẩn, nổi tiếng trong ngành video game
- Quy trình thông thường như sau
- Chụp screenshot của cảnh cần chỉnh màu
- Mở screenshot cùng LUT 3D đã khởi tạo trong một công cụ như Photoshop
- Áp dụng cùng một hiệu chỉnh màu cho screenshot và LUT
- Cắt phần LUT 3D và xuất ra
- Left 4 Dead cũng dùng cách tương tự
- Có thể dùng tự do các công cụ chỉnh màu của Photoshop, nhưng không thể dùng bộ lọc làm thay đổi quan hệ giữa nhiều pixel
- Các loại convolution như blur, sharpen, emboss có thể làm nhòe màu đã remap và tạo kết quả ngoài dự đoán
Nướng chỉnh màu phức tạp vào một LUT
- Các công cụ chỉnh màu như DaVinci Resolve có thể tạo các phép chuyển đổi màu và color grade phức tạp rồi xuất ra LUT 3D
- Ví dụ áp dụng LUT Kodak 2383 nhằm tạo film print look
- Chuyển video V-Log gamma sang Cineon Film Log
- Chuyển không gian màu sang Rec.709
- Áp dụng mô phỏng phim và chuyển gamma trở lại Rec.709
- Cuối cùng điều chỉnh white point
- Có thể nướng các chuyển đổi chi tiết phức tạp vào một LUT rồi đưa vào ứng dụng đồ họa, thay vì tự triển khai trực tiếp
- Video ví dụ dùng định dạng nén mạnh 8-bit để tương thích với bài viết, nhưng thông thường đây là công việc thực hiện trên footage 10-bit
- Kích thước LUT được dùng nhỏ hơn nhiều so với tưởng tượng
- V-Log to V-709 3D-LUT chính thức của Panasonic có kích thước 33³px
- LUT giám sát trong camera của Panasonic dùng 17³px
- Ngay cả các camera cinema của Panasonic ở tầm giá năm chữ số USD cũng dùng 17³px
Ví dụ LUT ngoài đồ họa
- LUT còn được dùng ngoài lập trình đồ họa
- LUT cũng được sử dụng trong trường hợp port Tomb Raider lên Game Boy Advance
- Bản port engine mã nguồn mở OpenLara xử lý nhiều phần bằng phần mềm vì Game Boy Advance không có tính năng 3D
- Một trong số đó là LUT triển khai phép chia số nguyên
- LUT này được đặt ở phần đầu không gian địa chỉ ROM để bỏ qua load instruction
1 bình luận
Các ý kiến trên Hacker News
Nếu không có bảng tra cứu thì có lẽ hoàn toàn không thể triển khai hiệu ứng này: https://twitter.com/zeta0134/status/1756988843851383181
Điểm cốt lõi là dùng hai bảng tra cứu. Bảng lớn lưu thông tin chiếu sáng hình tròn trong một bán kính có thể cấu hình quanh người chơi, và mỗi bán kính có nguyên một bảng riêng. Bảng thứ hai chứa thứ tự giả ngẫu nhiên của các hàng nền. Mỗi lần routine chiếu sáng đuốc được gọi, chỉ có đủ thời gian cập nhật 1/20 màn hình, nhưng nếu làm thứ tự hơi ngẫu nhiên thì rìa trông mượt hơn và che được kiểu quét raster vốn sẽ lộ ra. Thứ tự này là một “grab bag” để các hàng không bị bỏ đói trong quá trình cập nhật, vì tính toán thời gian thực quá chậm nên phải dùng bảng
Hiệu ứng bảng tra cứu đầu tiên thật sự gây ấn tượng với mình là cách dùng để tạo đường hầm texture
Có một bảng tra cứu cho biết góc và khoảng cách từ tâm màn hình cho từng pixel trên màn hình, rồi dùng nó để chọn texel nào sẽ đặt vào từng vị trí pixel. Nó trông như đang di chuyển trong một đường hầm hình học 3D, nhưng chi phí rất thấp nên pico cũng làm được: https://www.lexaloffle.com/bbs/?pid=63818
Ban đầu mình nghĩ Stardust đã dùng hiệu ứng này, nhưng vừa tìm lại thì thực ra nó chỉ phát một animation 6 frame lặp lại ở nền: https://codetapper.com/amiga/sprite-tricks/stardust/
https://web.engr.oregonstate.edu/~mjb/cs557/Projects/Papers/Parallax_Occlusion_Mapping.pdf
Có video nói về cách Wind Waker dùng nhiều LUT để tạo diện mạo độc đáo. BoTW và ToTK cũng dùng cùng kỹ thuật
https://www.youtube.com/watch?v=mnxs6CR6Zrk
Mình là tác giả bài viết. Nếu có câu hỏi thì mình có thể trả lời
Tuy nhiên cách bài viết giới thiệu color grading bằng LUT hơi khiến mình ngạc nhiên. Trong bài, nó trông như một giải pháp ngách thú vị mà L4D2 đã dùng, nhưng thực tế từ lâu đây đã là chuẩn trong ngành và được dùng trong mọi game mình từng làm. Từ game AAA như NFS (2015) đến các game indie lớn như Lost in Random đều như vậy
[1] https://www.copetti.org/writings/consoles/game-boy-advance/#operating-system
“Here is every single colormap that matlibplot supports,...”
Ngay cả trong lĩnh vực quy trình nghiệp vụ nhàm chán, thứ này cũng hữu ích đến mức đáng ngạc nhiên
Nhiều khi có thể đơn giản hóa một codebase đầy câu điều kiện thành các bảng tra cứu gọn gàng. Có vẻ không phải lúc nào người ta cũng nghĩ tới, vì ngay cả thứ trông đơn giản cũng có thể thành bảng tra cứu 50.000 hàng. Với người dùng cuối thì có thể trông nhiều, nhưng máy tính thì không bận tâm lắm. Hơn nữa bảng tra cứu có tính lặp lại nên thường dễ quản lý, và kể cả khi không dễ thì vẫn đáng dùng. Người dùng cuối cũng có thể cấu hình mà không cần thay đổi code. Nhìn chung đây là một khái niệm đặc biệt hữu ích cho nhiều kịch bản thường xuyên xuất hiện khi code business logic
Các game retro dùng bảng tra cứu rất nhiều. Khi đó tốc độ bộ nhớ rất nhanh còn bộ xử lý thì chậm, nên việc đẩy càng nhiều phép tính càng tốt vào bảng là hợp lý. Càng làm khéo thì càng có thể tạo ra game hào nhoáng hơn
Tôi thích kênh đó. Anh ấy đã chỉnh toàn bộ mã của Mario 64 để mod của mình chạy nhanh hơn, đạt 60FPS ổn định [0]
[0] https://www.youtube.com/watch?v=t_rzYnXEQlE
Nếu bạn phải xử lý nhiều LUT, tôi đang làm một ứng dụng Mac để xử lý khoa học màu sắc nâng cao và các tác vụ chuyển đổi: https://videovillage.com/lattice
Trước đây tôi từng tìm hiểu một chút về giới homebrew NES, và thấy trong một game tên Bobl có hiệu ứng vật lý đáng kinh ngạc, vượt xa mức hệ thống có thể tính toán. Hóa ra đó là bảng tra cứu, và tôi nhận ra rằng bằng một công cụ đơn giản có thể khiến nó trông như một quá trình rất phức tạp
https://morphcatgames.itch.io/bobl
A Podcast Of Unnecessary Detail đã có một tập nói về bản port Doom lên SNES và cách LUT được dùng cho các hàm lượng giác. Bối cảnh là SNES không có bộ xử lý đồ họa
https://festivalofthespokennerd.com/podcast/series-3-episode-4/
https://github.com/RandalLinden/DOOM-FX
Nó cũng có thể xoay và co giãn nền. Đó là Mode 7 — cứ nghĩ đến Mario Kart — và còn hỗ trợ độ trong suốt phần cứng thấy trong nhiều game. Hơn nữa, trong các trường hợp như Doom và Starfox, cartridge còn có chip Super FX để xử lý tính toán 3D. Vì vậy với các tựa game cụ thể đó, thực tế là đang xử lý tới hai bộ xử lý đồ họa
Có một series Retro Game Mechanics Explained nói về phần cứng SNES cực kỳ hay và chi tiết đến mức đau đầu:
https://www.youtube.com/watch?v=57ibhDU2SAI&list=PLHQ0utQyFw5KCcj1ljIhExH_lvGwfn6GV&index=1
Những cách dùng LUT tôi nhớ mình từng viết là tán xạ khí quyển, tô màu sprite, ống ngắm nhìn đêm, ống ngắm FLIR, hiệu ứng “nguồn video” đen trắng, hiệu ứng glitch, shading heightmap, hệ số điểm alpha cho vệt phụt khí thải tàu vũ trụ, heatmap thời gian rê chuột của khách truy cập website, hiệu ứng tinh thể, và cuối cùng là tô màu hậu kỳ trong không gian màu thô
LUT là cách trực quan hóa một mảng các giá trị đã biết sẵn, và hữu ích đến đáng kinh ngạc
http://www.effectgames.com/demos/canvascycle/
Xbox đời đầu xử lý cái này không tốt và Photoshop cũng không hỗ trợ nên nó mất dần độ phổ biến. Nhưng phần cứng hiện đại hoàn toàn xử lý được palette swap tạo bằng AESprite