« 文明太空的评测 | 返回首页 | skynet 的 UDP 支持 »

RLA 文件中的法线信息提取

最近想在游戏中加一点简单的环境光,因为游戏中使用的都是 2d 图片,那么最廉价的方法应该是给图片加上法线图了。

好在我们游戏的原始图素都是用 3d 建模,然后再用平面修整的。基本几何结构信息可以从模型提取出来。当然,我们并不真的需要自己写程序去从模型中计算出最终渲染图的法线信息。所有渲染软件都可以输出它。

比如 3ds max ,如果你把渲染结果输出成 .rla 文件,那么就可以勾选 normal zbuffer 等额外的通道输出。

记得我读大学时写过一个 rla 文件解析程序,当时是为了提取里面的 Z 通道。这都过了十多年了,果然完全找不回当年写的代码了,也忘记曾经怎么实现的,所以就从头来搞。

动手之前先查阅了 imagemagick 的文档,发现它虽然能提取 rla 文件,但仅限于不同的颜色及 alpha 通道。核对了源代码 确认了这一点。google 也没有找到有人开源过相关库,那么只好自己写了。

RLA 是 wavefront 的 3d 图片文件格式。网上能找到格式说明

因为有固定长度的 C 结构头,解析出文件头还是很容易的。可惜不知道为什么,3ds max 导出的 rla 文件头中,即使导出了多个附加通道,NumOfAuxChannels 项还是 0 。

按 offset table 提取出 scanlines 后,试着解了一下 RGB 以及 alpha 通道都是正确的。暂时不理会文件头,每个 scanline 后都附有其它 channel (如果导出了的话)。暂时不知道如何识别每个通道对应着什么,不过如果你只导出 normal 通道的话,那么多出来的通道当然就是法线图了。

btw, 每个 scanline 节的最后,都有一些莫名其妙的数据,也不知道如何识别单个 scanline 结束标记。不过这些不影响我们做下面的工作。

做一个简单的测试会发现,如果在 max 里导出 Z 通道,那么在 rla 文件里会多 4 个通道出来;如果导出 normal 通道,会再多出 4 个通道。

按 rla 的格式说明,4 个通道的值可以合成一个 float 也可以合成一个 32bit 整数。 Z 通道看起来是一个 float ,而 normal 通道则比较像整数。(从导出图像上看,不像是 4 张独立的 8bit 通道图)

用 google 没有找到太多信息,搜到一些相关资料是来源于 3ds max 的 sdk 头文件。里面提到一个叫 DeCompressNormal 的函数。输入一个 uint32 ,输出三个 float 。可惜没有实现的源代码。

我反汇编了包含 DeCompressNormal 实现的动态库 geom.dll :

.text:67678890 ; class Point3 __cdecl DeCompressNormal(unsigned long)
.text:67678890                 public ?DeCompressNormal@@YA?AVPoint3@@K@Z
.text:67678890 ?DeCompressNormal@@YA?AVPoint3@@K@Z proc near
.text:67678890
.text:67678890 var_C           = dword ptr -0Ch
.text:67678890 arg_0           = dword ptr  4
.text:67678890 arg_4           = dword ptr  8
.text:67678890
.text:67678890                 sub     esp, 14h
.text:67678893                 mov     eax, [esp+14h+arg_0]
.text:67678897                 mov     edx, [esp+14h+arg_4]
.text:6767889B                 fld     ds:flt_676877EC   ; -1.0
.text:676788A1                 mov     ecx, edx
.text:676788A3                 and     ecx, 3FFh
.text:676788A9                 fld     ds:flt_676877E8  ; 1.9569471e-3
.text:676788AF                 mov     [esp+14h+var_C], ecx
.text:676788B3                 fild    [esp+14h+var_C]
.text:676788B7                 fmul    st, st(1)
.text:676788B9                 fadd    st, st(2)
.text:676788BB                 mov     ecx, edx
.text:676788BD                 fstp    dword ptr [eax+8]
.text:676788C0                 shr     ecx, 0Ah
.text:676788C3                 and     ecx, 3FFh
.text:676788C9                 mov     [esp+14h+var_C], ecx
.text:676788CD                 fild    [esp+14h+var_C]
.text:676788D1                 fmul    st, st(1)
.text:676788D3                 fadd    st, st(2)
.text:676788D5                 fstp    dword ptr [eax+4]
.text:676788D8                 shr     edx, 14h
.text:676788DB                 and     edx, 3FFh
.text:676788E1                 mov     [esp+14h+var_C], edx
.text:676788E5                 fild    [esp+14h+var_C]
.text:676788E9                 fmulp   st(1), st
.text:676788EB                 faddp   st(1), st
.text:676788ED                 fstp    dword ptr [eax]
.text:676788EF                 add     esp, 14h
.text:676788F2                 retn
.text:676788F2 ?DeCompressNormal@@YA?AVPoint3@@K@Z endp

稍有汇编基础就可以很容易看出,这个函数其实就是把一个 32bit 整数按 10bit 一段分成三段,每段是一个 0 到 1023 的整数,然后把这三个整数调整为 -1 到 1 之间的浮点数。

ps. 这里的常量 1.9569471e-3 是 1/511 。


最终,我把美术给我的 rla 文件。提取出一张色彩图:

color.png

和一张法线图:

normal.png

顺手用 ejoy2d 写了一个简单的例子(定义一个 shader 传入鼠标位置做为光源方向,算出和法线图上的法向量的夹角作为光强,叠加到最终的屏幕上)。效果还不错。

light.gif

Comments

https://github.com/cloudwu/skynet/wiki/Community 关于 skynet 的各种信息。
反汇编这个思路真好. 当年写导出插件的时候, maxsdk的很多功能都是靠猜得, 原来还有这么个好方法.
我把美术给我的 这个链接坏掉了。
@Cloud 作为野蛮成长,百度为家的屌丝程序,不知道怎么加入skynet的mailling list,是不是需要管理员拉进去? 我的邮箱pthread_t@qq.com。本来想提供一个看起来更牛X的google邮箱的,但是为了上邮箱每天都翻墙的成本还是略高,所以就提供了一个很low的企鹅邮箱。各位开源大大们莫要见笑,多多提携下没玩过古董技术的新人。
楼下讨论 skynet 请去 mailling list 发帖 :)
最近在拜读skynet源码,有几个疑问想请教下云风大大。 1. skynet框架可不可以分开语言框架和架构框架(如harbor从c代码中移出去)。(架构框架用纯lua实现,供使用者选择。) 2. skynet的lua socket接口,完全实现成posix标准的(同步写法,实际是异步调用),这样使用起来完全没有学习成本。 3. skynet.lua,是否应该提供一个event实现这在做lua级别的rpc或者其他功能时会方便很多。 本人比较擅长python脚本,在读skynet前完全没有lua基础,扯淡的地方直接忽略之。
流弊
算出和法线图上的法向量的夹角作为光强,叠加到最终的屏幕上) ==== 应该是用夹角的余弦作为光强? --- 夹角为0时,和顶点法向量同线反向,反射光最强,cos(0) = 1 ?
这个汇编是不是可以用SSE指令加速呢?
吊爆了!
貌似3D里经常用到的Bump Map技术
牛X,崇拜大牛啊 大牛,我收你为徒吧?
没看明白,怎样才能学会OpenGL呢?
好流 B

Post a comment

非这个主题相关的留言请到:留言本