Giải Nén Thông Tin Pháp Tuyến Từ Tệp RLA
Gần đây, nhóm phát triển game của tôi muốn thêm ánh sáng môi trường đơn giản vào game. Vì toàn bộ hình ảnh trong game đều là 2D, phương pháp tối ưu nhất được chúng tôi lựa chọn chính là áp dụng bản đồ pháp tuyến (normal map) cho các hình ảnh này.
Điều thuận lợi là toàn bộ tài nguyên hình ảnh gốc của game đều được dựng từ mô hình 3D sau đó ép phẳng thành 2D. Thông tin hình học cơ bản có thể được trích xuất trực tiếp từ mô hình 3D mà không cần tự xây dựng thuật toán phức tạp. Các phần mềm render hiện đại như 3ds Max đều hỗ trợ xuất dữ liệu pháp tuyến này.
Quy trình xuất thông qua định dạng RLA
Trong 3ds Max, khi chọn xuất tệp dưới định dạng .rla
, người dùng có thể kích hoạt các kênh phụ như Normal ZBuffer. Việc này giúp lưu trữ thông tin chiều sâu và pháp tuyến bề mặt cần thiết cho quá trình xử lý hậu kỳ.
Cách đây hơn một thập kỷ, tôi từng viết một chương trình nhỏ để phân tích định dạng RLA nhằm trích xuất kênh Z (chiều sâu). Tuy nhiên, code cũ đã thất lạc hoàn toàn và kiến thức về định dạng này cũng bị lãng quên theo thời gian. Vì vậy, tôi phải bắt đầu lại từ đầu.
Nghiên cứu và phân tích định dạng RLA
Đầu tiên, tôi kiểm tra thư viện ImageMagick - công cụ xử lý ảnh phổ biến. Tiếc rằng khả năng hỗ trợ của nó chỉ giới hạn ở các kênh màu và alpha thông thường. Sau khi xem xét mã nguồn và tìm kiếm trên Google, tôi nhận ra không có thư viện mã nguồn mở nào tồn tại để xử lý các kênh phụ như pháp tuyến.
Bắt tay vào nghiên cứu định dạng RLA gốc (thuộc chuẩn Wavefront), tôi nhận thấy phần header của tệp có cấu trúc cố định dưới dạng struct C, giúp việc đọc dữ liệu header tương đối đơn giản. Tuy nhiên, một vấn đề phát sinh: Khi xuất nhiều kênh phụ từ 3ds Max, trường NumOfAuxChannels
trong header luôn hiển thị là 0 bất chấp lựa chọn thực tế.
Xử lý dòng quét (scanline) và xác định kênh pháp tuyến
Dựa vào bảng offset, tôi lần lượt trích xuất các dòng quét và kiểm tra kênh RGB/alpha để xác minh tính chính xác. Dù không thể dựa vào header, các kênh phụ (nếu được xuất) đều được lưu kèm sau mỗi dòng quét. Với giả định chỉ xuất kênh pháp tuyến, kênh dư thừa này đương nhiên chính là bản đồ pháp tuyến cần tìm.
Mỗi dòng quét chứa thêm một số byte dữ liệu vô nghĩa ở cuối, nhưng điều này không ảnh hưởng đến mục tiêu chính của chúng ta.
Phân tích sâu: Giải mã giá trị pháp tuyến
Khi xuất kênh Z hoặc pháp tuyến từ 3ds Max, tệp RLA sẽ tăng thêm 4 kênh. Theo đặc tả định dạng RLA, 4 kênh này có thể hợp thành một giá trị float 32-bit hoặc số nguyên 32-bit.
Qua quan sát hình ảnh thực tế:
- Kênh Z mang giá trị float
- Kênh pháp tuyến có vẻ là số nguyên, không phải 4 kênh 8-bit độc lập
Thông qua SDK của 3ds Max, tôi tìm thấy hàm DeCompressNormal
- hàm nhận một uint32
và trả về ba giá trị float biểu thị pháp tuyến 3D. Không may, code nguồn không được cung cấp, buộc tôi phải reverse-engineering thư viện geom.dll
chứa hàm này.
Kết quả phân rã (disassembly)
Hàm DeCompressNormal
xử lý giá trị 32-bit bằng cách chia thành ba đoạn 10-bit, mỗi đoạn đại diện cho một giá trị nguyên từ 0 đến 1023. Sau đó, các giá trị này được chuẩn hóa về khoảng -1 đến 1 thông qua hệ số 1.9569471e-3 (tương đương 1/511).
|
|
Kiểm thử và kết quả
Sau khi viết công cụ giải nén, tôi trích xuất thành công:
- Bản đồ màu: !color.png
- Bản đồ pháp tuyến: !normal.png
Để kiểm tra, tôi tạo một ví dụ đơn giản trong ejoy2d với shader nhận tọa độ chuột làm hướng ánh sáng. Góc hợp giữa pháp tuyến và tia sáng được tính toán để xác định cường độ sáng, kết quả được vẽ chồng lên hình gốc.
!light.gif
Kết luận
Quá trình này không chỉ giúp nhóm tôi tối ưu đồ họa game mà còn củng cố hiểu biết về định dạng RLA và kỹ thuật nén pháp tuyến. Dù gặp nhiều trở ngại trong việc giải mã dữ liệu, việc phân tích ngược (reverse engineering) và kiểm tra thực nghiệm đã đưa ra giải pháp khả thi.