RayMarching结合SDF可以渲染出不同形状的形体,那如何为SDF模型添加纹理呢?
1.Tri-Planar-Mapping[三面映射]
我们通过如下的方式为Cube添加纹理。
vec2 t = raymarch(ro, rd);
if(t.x > 0.) {
col = texture(iChannel0, p.xy).rgb;
}
输出结果:
看到前后两个面的纹理有正常显示,但是Repeat了,那是因为 p.xy
映射范围为 [-1, -1]
到 [1, 1]
,我们把其映射回 [0, 0]
到 [1, 1]
的范围。
vec2 t = raymarch(ro, rd);
if(t.x > 0.) {
// 通过-1~1的范围映射回0~1
col = texture(iChannel0, p.xy*0.5+0.5).rgb;
}
但是除了前后两个面,其它面的纹理都被拉伸了。如何解决呢?我们把 p.xy(前后)
改为 p.xz(上下)
与 p.zy(左右)
试试?
改为 p.zy(左右)
后:
改为 p.xz(上下)
后:
所以我们现在有:
vec2 t = raymarch(ro, rd);
if(t.x > 0.) {
vec3 mate = vec3(0.2);
vec3 sunCol = vec3(1.);
vec3 p = ro + rd*t.x;
vec3 colXZ = texture(iChannel0, p.xz*0.5+0.5).rgb;
vec3 colZY = texture(iChannel0, p.zy*0.5+0.5).rgb;
vec3 colXY = texture(iChannel0, p.xy*0.5+0.5).rgb;
}
现在问题的关键是怎么让这六个面同时贴到Cube中?我们得借助下法线:
通过以下代码直接输出法线颜色:
vec2 t = raymarch(ro, rd);
if(t.x > 0.) {
vec3 mate = vec3(0.2);
vec3 sunCol = vec3(1.);
vec3 p = ro + rd*t.x;
vec3 n = calNormal(p);
vec3 colXZ = texture(iChannel0, p.xz*0.5+0.5).rgb;
vec3 colZY = texture(iChannel0, p.zy*0.5+0.5).rgb;
vec3 colXY = texture(iChannel0, p.zy*0.5+0.5).rgb;
col = n;
}
不过也会看到背面是黑的,所以我们应该对法线取绝对值 col = abs(n)
:
好了,我们现在已经有法线了,也有各个面的纹理颜色了,剩下的就是想办法怎么让法线与各个纹理颜色结合。
很简单,通过如下代码即可完成:
vec2 t = raymarch(ro, rd);
if(t.x > 0.) {
vec3 mate = vec3(0.2);
vec3 sunCol = vec3(1.);
vec3 p = ro + rd*t.x;
vec3 n = calNormal(p);
vec3 colXZ = texture(iChannel0, p.xz*0.5+0.5).rgb;
vec3 colZY = texture(iChannel0, p.zy*0.5+0.5).rgb;
vec3 colXY = texture(iChannel0, p.xy*0.5+0.5).rgb;
vec3 absN = abs(n);
col = colXZ*absN.y + colZY*absN.x + colXY*absN.z;
}
如果对Cube做旋转操作,我们会发现纹理飘在了Cube上了,遇到这种情况,Cube怎么做旋转,采样的UV也要做同样的变换操作,才能让UV贴在Cube上。这种方式也可以让可重复纹理贴在类似球体之类的物体上。
以上是其中一种纹理贴图的方案,称为 Tri-Planar-Mapping[三面映射]
。
下面介绍另一种纹理映射方案,它基于极坐标的纹理映射方案。
2.极坐标的纹理映射
我们要让贴图贴到球体上,需要让u从0开始并到1结束,并让p.y作为v,且环绕球体一圈的范围。如下图:
我们采用极坐标,并使用 atan(p.z, p.x)
得出的范围是 [-PI,PI]
的范围,现在要做的是把 [-PI,PI]
范围映射为 [0, 1]
,使用如下的方法:
(atan(p.z, p.x)/3.1415)*0.5+0.5
接着把p.y作为v,如下:
col = texture(iChannel1, vec2(((atan(p.z, p.x)/3.1415)*0.5+0.5), p.y)).rgb;
图像输出结果: