当我们想利用 Raytrace 技术实现一些伪 3D 的效果的时候,常常会遇到 ViewRay 和平面 是否相交的问题。
首先我们来看看普通版的解决方法。dsadad
普通版
如果只是简单的判断从相机射出的光线 ViewRay 是否和平面相交,我们只需要把光线向量和平面的法向量进行点乘即可,如下代码所示
/// @note 光线追踪之平面
/// @param pl 平面法线
/// @param ro Ray Origin
/// @param rd Ray Direction
float tracePlane( in vec4 pl, in vec3 ro, in vec3 rd )
{
/// @note 最直接的方式,直接用 ViewRay 的方向向量和平面法线做点乘
/// 注意 ViewRay 和平面法线是反向的,所以要加负号。
/// 但缺点是这样没有景深的效果
return -dot(pl.xyz, rd);
}
效果如图所示
但这样缺点是,如果我们想根据 SDF (有向距离场)做进一步的复杂酷炫特效处理时,会发现是没有景深信息的。
还好我们只需要稍加修改(ViewRay 和平面相交的 Tricky 妙用),即把相机点到平面的距离加到上面的式子,就可以营造出景深的效果。如下图所示
优化升级版
令我们有一个平面,通过 “点法式” 的 S 点和法线 N 来确定,接着有一条射线从 O 发出沿着 D 方向,与平面相交于 P 点,则我们可以列出以下方程
,t 表示从 O 射出的有符号的距离
两式联立,可以结出 t,即如下所示
或
有一点需要注意,当 的时候,意味着射线是和平面平行的,那么除非 O 点本身就在平面上,否则是无解的。
/// @note 光线追踪之平面
/// @param pl 平面法线
/// @param ro Ray Origin
/// @param rd Ray Direction
float tracePlane( in vec4 pl, in vec3 ro, in vec3 rd )
{
/// @note 加入了相机和平面的距离,有景深距离的效果(离相机越远颜色越白)
/// w 是 “点法式” 的平面表示法中的点到平面的距离,即 dot(S, N),作用是微调距离,例如本例中的平面的拉近拉远(升高降低)
return -(dot(pl.xyz, ro) + pl.w) / dot(pl.xyz, rd);
}
有了距离之后,我们就可以结合相机的坐标和 ViewRay 的方向将纹理坐标给计算出来
vec3 p = from + dir * distGround;
完整代码
float tracePlane( in vec4 pl, in vec3 ro, in vec3 rd )
{
return -(dot(pl.xyz, ro) + pl.w) / dot(pl.xyz, rd);
// return -dot(pl.xyz, rd); ///< 没有景深距离的效果(离相机越远颜色越白)
}
#iUniform float w = 0. in {-1., 1.}
bool traceWall( in vec3 from, in vec3 dir, inout float dist, inout vec3 norm )
{
vec3 wallNorm = vec3(0, -1, 0);
float distWall = tracePlane( vec4(wallNorm, w), from, dir );
if (distWall > 0.0)
{
dist = distWall;
norm = wallNorm;
return true;
}
return false;
}
bool traceGround( in vec3 from, in vec3 dir, inout float dist, inout vec3 norm )
{
vec3 groundNorm = vec3(0, 0, 1);
float distGround = tracePlane( vec4(groundNorm, w), from, dir );
if (distGround > 0.0)
{
dist = distGround;
norm = groundNorm;
return true;
}
return false;
}
void mainImage()
{
vec2 uv = fragCoord.xy / iResolution.xy * 2.0 - 1.0;
uv.x *= iResolution.x / iResolution.y;
vec3 from = vec3(0, -6.5, 2);
vec3 dir = normalize(vec3(uv.x, 1.5, uv.y)); ///< 注意,uv.y 在 z 分量上
vec2 mouse = vec2(0);
if (iMouse.z > 0.0) mouse = iMouse.xy / iResolution.xy * 2.0 - 1.0;
dir.yz *= rot(-mouse.y * 0.4);
dir.xy *= rot(mouse.x * 0.4);
float distWall = 99999.9;
vec3 normWall = vec3(0);
bool hitWall = traceWall(from, dir, distWall, normWall);
float distGround = 99999.9;
vec3 normGround = vec3(0);
bool hitGround = traceGround(from, dir, distGround, normGround);
fragColor = vec4(distGround / 50.);
fragColor.a = 1.;
}