3D游戏常用技巧Normal Mapping (法线贴图)原理解析——高级篇
1、概述
上一篇博客,3D游戏常用技巧Normal Mapping (法线贴图)原理解析——基础篇,讲了法线贴图的基本概念和使用方法。而法线贴图和一般的纹理贴图一样,都需要进行压缩,也需要生成mipmap。但是由于法线贴图存储的是法线信息,压缩和生成mipmap的方法自然会有所变化。
现在已经许多用于法线贴图压缩和生成mipmap的工具,大部分商业游戏引擎也集成了相关方法,只需要点几下鼠标就可以完成。本文仅针对法线贴图的纹理压缩和mipmap的方法进行原理性的说明,至于在具体的工具中如何操作,可以参看相关工具的说明文档。
法线贴图压缩的中文资料还是比较多的,也不太复杂;但是生成mipmap的方法中文资料不多,《Real-Time Rendering 3rd》讲解的比较详细,本文的这部分内容主要来源于这本书,如果想详细了解的,可以看原书,网上有电子版。
2、法线贴图的压缩
传统的jpg等压缩方式解压时间太长,且压缩比不固定,所以在实时渲染中一般采用DXTC及其改进方法,简单来说,就是把4*4的像素当做一个Block,对其进行简化表示,详情参见百度百科http://baike.baidu.com/view/736449.htm。由于法线贴图存储的数据并不是RGB信息而是法线方向,所以需要在一般纹理压缩的方法的基础上进行一定的改变。
压缩的第一步很简单,由于归一化的法线长度为1,且在切线空间下,法线的z分量不可能为负数,所以只需要存储x和y值即可。本文的压缩方法在这一步压缩的基础上,利用现有的纹理压缩方法,进行进一步压缩。
在支持DirectX10的显卡上,可以使用BC5格式进行压缩。BC5的压缩方法内存情况如图1所示,该格式有两个颜色通道(R和G),每个通道使用两个1Byte的值来表示,每个像素使用3Bit在这两个颜色值之间进行插值。将法线贴图中每个法线的x和y值利用BC5格式进行压缩,如图2所示。对每个Block(16个像素)存储x的最大、小值和y的最大、小值,然后每个像素利用3Bit进行插值,相当于在图2右图所示的8*8区域内取样(为了简化表示,图2只画了4*4点)。
图1 BC5压缩方法
图2 法线贴图压缩示意图,右图框内应该是8*8个点,为了画图方便简单表示为4*4
对于不支持DirectX10的显卡,可以使用DXT5格式进行压缩(DXT5为DirectX9.0的纹理压缩格式,如果连DirectX9.0都不支持,建议直接送博物馆),将法线的x和y值存储到纹理的alpha和Green通道即可。之所以是存储到这两个通道,而非其他通道,是因为每个DXT5中每个Block选择的两个参考像素alpha通道有8Bit,RGB通道分别为5、6、5Bit,所以使用alpha和Green通道可以获得较高精度。
3、法线贴图的mipmap
使用一般纹理mipmap方法生成的法线贴图对于漫反射表面基本没问题,但是在镜面表面会导致严重的视觉问题。对于漫反射表面来说,光照的计算公式为l·n,l为光线方向的相反方向,n为法线,l·n1 + l·n2 + l·n3 + l·n4 = l·(n1 + n2 + n3 + n4) / 4,而mipmap则是事先计算(n1 + n2 + n3 + n4) / 4,所以对于漫反射表面,对法线贴图使用传统方式的mipmap基本没问题。为什么是基本没问题而不是完全没问题呢?因为这里存在一个近似,若l·n < 0,则光照值为0(光照不能为负),若将这个因素考虑进去,漫反射表面也会有问题,不过在实际当中这种情况表现不明显,所以可以认为基本没问题。
对于镜面表面来说,当视线偏离反射光线方向的时候,光照强度会急剧下降,反映在公式中是因为其含有cosm(h·n)项(具体公式可以Google),而漫反射光照是线性变化,所以对于镜面表面,不能使用传统方法生成法线贴图的mipmap。法线贴图对于镜面反射的mipmap如图3所示,第一幅图中有4个像素,每个像素有法线和镜面反射波瓣(红色的是法线,周围一圈是镜面反射波瓣,镜面反射波瓣用于表示不同方向的反射强度)。图2中间部分,表示正确的mipmap情况,分别从4个像素合并为2个像素,从两个像素合并为1个像素。而现有的方法中,没有方法可以做到这样的mipmap,所以只能用其他方法进行近似。
图3 法线贴图的mipmap示意图
图2的底部左图,是使用一般纹理的mipmap方法对法线进行平均,可以看到这种方法产生出的镜面反射波瓣和正确的镜面反射波瓣差距很大,其根本原因是使用线性方法对非线性的参数进行计算。图2底部图右图,每次在平均法线的同时,改变表面的光泽度(即改变镜面光公式中的m),虽然最终结果与正确的mipmap有一些差距,但是比一般纹理的mipmap的方法要好很多。
所以,对法线贴图的mipmap方法之一,就是在使用一般纹理的mipmap方法对法线进行平均的同时,每张mipmap都必须附带一张光泽贴图(gloss map),记录每个像素点的光泽度(即m),m的计算原则就是让最后的镜面反射波瓣与正确的镜面反射波瓣最接近。当然,还有其他很多方法能得到不错的结果,具体可以参看《Real-Time Rendering 3rd》,或去搜索相关论文。
参考资料
[1]Akenine-Möller T, Haines E, Hoffman N. Real-time rendering 3 [M].
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述