实现:比率法线与转换
这是一种手绘法线的方法及将其转化为标准法线的算法
法线贴图通常由软件根据纹理自动生成。然而对于低分辨率纹理,软件生成并不理想,且我们期望能精确地调控;
我们很难将手绘法线的三分量控制在理想范围内,但在某一分量上增减会导致另一分量变动,不同分量上的倾斜角度也不够直观;
尽管 LabPBR 将
分量所在的 b
通道 另作他用 在一定程度上缓解了模长小于的问题,但是当 时,用于重建 分量的算法 将产生非实根,从而导致 NaN
错误。
SPBR 的开发者 Shulker 最初使用手算法线表 (一张向四周发散的法线图),在其上进行取色并绘制法线。
这样虽然可以避免法线不按预期工作,但是其仍存在以下弊端:
依赖数据表,不好预期倾斜角度,难以保证变化曲线(特别是除中心点四方向外的法线);
角度受限(取决于数据表的分辨率);
由于
b
a
通道另作他用,在更改法线时只能使用下列两种方法:在
r
g
通道同一位置分别使用同一数据绘制;将
b
a
通道独立,然后覆盖绘制图片,最后再将b
a
通道回覆盖。
吸色太烦人了。
我们拟采用一种新格式,设法在前期绘制时将 r
g
分量较长的分量视为
当绘制完成之后,我们使用算法将其转换为标准法线(Standard Normals,简称 SN,角标使用
根据其按比率控制倾角的特性,我们将其命名为比率法线 (Ratio Normals,简称 RN,角标使用
我们期望法线都在半径为
虽然将二维映射到三维看起来有点不自量力,不过和 LabPBR 一样,我们使用
我们将

我们从图上可以知道,
然后,我们记对角线
其比值为
则以对角线为轴(记为

由于我们只能自由地控制
tip
想象一下如果直接用
的值来求得 值,当我们想要获得完美的 角时,我们需要 ,那么就需要 ,再反算到 和 上,简直头都大了。
于是我们在该平面上使用反三角函数来求得其角度的比率。
于是
将其从
我们从图 1 不难看出,
当
同理,当
于是我们可以将
为了让其适配实际在
这样一个简洁优雅的算法。
tip
当然如果想用
也可以,但是需要 作为中间变量,徒增麻烦。
现在绘制就变得非常简单了,如 解决方案 所说, r
g
通道较大的一个值现在会作为法线 r
g
通道值之间的比值则会作为在 r = 0.5, g = 0.5
,就和普通法线一样。
比如当 r = 0.25, g = 0.6
时
#include <math.h>
const float I_Pi = 0.3183098861838; // Pi 的倒数
void ratioNormal(float *img) {
img[0] = img[0] * 2.0 - 1.0;
img[1] = img[1] * 2.0 - 1.0;
float Sxy = acos((max(abs(img[0]), abs(img[1]))) * I_Pi);
img[0] = img[0] * Sxy * 0.5 + 0.5;
img[1] = img[1] * Sxy * 0.5 + 0.5;
}
warning
仅作示例这个解决方案并未被广泛采用过,并且使用了开销极大的反三角函数 ,因此我们不推荐将此算法内置于光影中,而是作为离线转换器,在前期创作时使用。
const float I_Pi = 0.3183098861838; // Pi 的倒数
vec2 ratioNormal(vec2 color) {
color = color * 2.0 - 1.0;
float Sxy = acos((max(abs(color.x), abs(color.y))) * I_Pi);
color = color * Sxy * 0.5 + 0.5;
return color;
}