事实上,从工作出来到现在,我都没有写过一篇技术文章。所以,写得不好请多关照。
关于为何要在JME3.X引擎中做这件事呢?主要是两个理由:
1.出于爱好。(我并非只专注于java,事实上我研究和做得最多的工作仍然是c/c++。u3d这些)
2.尝试优化移动端PBR,使用一张2D纹理代替6个面cubeMap和cubeMap lod采样。
好吧,不说太多废话。
我使用的是jme3.2.4稳定版。并使用自带的sdk进行场景编辑和材质编辑。
我使用的新的材质定义来编写我的材质。但是也可以很方便的转为旧的材质定义。
关于PBR的定义,实在轮不到我这个后辈来指指点点。这里只简单描述下大概。
实际上很多光照模型都由两部分组成,PBR描述下的材质也不例外,直接光照和间接光照。间接光照部分可以使用很复杂的全局光照技术来实现,大部分PBR材质使用环境纹理映射来粗糙模拟全局光照。
大部分real-time PBR材质使用的BRDF模型,都是cook-torrance。
Cook-Torrance BRDF将diffuse和specuarl分开:
Diffuse BRDF
可以选择简单的Lambert(Cook-Torrance使用兰伯特光照模型来计算)或Oren-Nayar。我的实现中使用Lambert光照模型:
Specular BRDF
这部分比较复杂,Cook-Torrance Specular BRDF:
在我的实现中,各个项依次是:
D项(法线分布函数):
G项(几何函数):
F项(菲涅尔方程):
JME3.x引擎的PBR材质库使用的是ue引擎在2013发布的pdf的计算方式,并且进行一些修改:
可以看到我的实现中各个项跟jme3.x自带的略有不同。
间接光照部分:
实时的间接光照部分在PBR中通常使用IBL的方式来粗糙模拟,当然,也可以通过复杂的全局光照技术实现。
Diffuse-间接光照部分:
没什么太多要做的,计算使用的BRDF模型是一样的,唯一不同的是光照信息从积分纹理中获取。通常卷积cubeMap积分纹理来获取Diffuse部分的间接光照。但对于移动端使用cubeMap作为纹理采样需要渲染6个面,开销稍微有点大:
作为补偿,我使用2D采样来替代:
texture2D( inIrradianceMap,n2uv(iNormal) )。
需要注意的是,由于积分纹理在计算中存储的光照信息远超过0-255,所以需要使用hdr来存储。通常作为渲染的hdr格式是radiance格式(rgbe)。如果光照信息没有正确保存下来,看起来会比正常的pbr暗。
Specular-间接光照部分:
这部分在常规做法中分为两个部分,根据粗糙度计算预计分纹理和lut查表来完成,C-T Specular PBR IBL:
Epic Games将这个公式拆为两个部分,这样可以单独进行计算:
用预过滤纹理实现,通常使用粗糙度来生成lod cubeMap使用。
是镜面积分部分,Epic Games的做法是使用存储了用变化的粗糙度来预计算每一个法线和光源方向组合的BRDF的值来生成一张积分纹理用作查表。但也有直接使用拟合方程实现这个部分。
预过滤纹理:
LUT:
同样,我使用2D积分纹理代替Lod cubeMap,这样在移动端只存储一张纹理。并且使用拟合方程实现镜面积分部分。jme3.x同时支持使用lut查表和使用拟合方程。
2D预过滤纹理:
拟合方程:
完整的PBR:
jme3.x渲染:
我的实现:
由于积分纹理我在gpu计算然后读回cpu生成hdr,这个过程会把0-255的光照信息丢失,所以看起来间接光照部分比jme3.x的暗淡。当然这也是可以改进的。
吐槽:
1.本来我以为jme3.x的sdk不能在编辑阶段预览完整的pbr。其实是可以的(我就在想怎么可能连这个都不行。。)
2.目前我的实现除了有两个地方需要改善下就可以投入移动端使用,并且加入了骨骼动画,实例化渲染的支持。
3.jme3编辑器中可以编写中文注释的shader,但是在代码中运行会报错不识别符号导致shader编译失败。所以我把中文注释去掉了。。
需要改善的地方:
1.积分纹理的生成改善。
2.因为不使用lod cubeMap,所以模拟lod采样在低精度下会有误差,渲染出来就是有交缝点。
3.由于我在改善积分纹理生成部分,这部分的工程我是做成了一个工具。因为还有可以改善的地方,所以暂时不放出来。
由于我github账号已注销,所以源码放到了码云...
地址:https://gitee.com/JoyClm/SimpleMatTest.git