JavaScript体素光线追踪:如何反转速度向量实现完美反弹?
体素光线追踪系统中的光线反弹实现方案
问题描述
我正在开发一个基于JavaScript的体素光线追踪系统,简单版本的实现是可行的,但卡在了虚拟光线的反弹效果算法逻辑上。
光线的核心逻辑是:每条光线是包含位置和速度的虚拟点,二者都是以{x, y, z}形式存储的向量。每帧将速度向量叠加到位置向量上,并检测新位置的体素,核心代码如下:
for(let step = 0; step < dist_max; step++) { pos.x += vel.x; pos.y += vel.y; pos.z += vel.z; if(solid_at(pos)) { // A voxel was hit } }
需求是:当检测到体素碰撞时,让光线像橡胶球一样反弹(只改方向不改速度大小),碰撞表面视为正对光线的平面;同时需要用0-1之间的数值控制反弹强度,适配半透明体素的反射/折射需求。
目前的困惑是:直接把速度整体乘-1会让光线原路返回,应该只反转速度的一个或多个轴,但光线朝向任意方向,X/Y/Z轴速度可正可负,不知道该判断翻转哪些轴?
解决方案
核心思路:利用轴对齐法向量计算反射方向
体素是轴对齐的立方体,所以碰撞时的法向量只会是(±1,0,0)、(0,±1,0)或(0,0,±1)这六种情况。关键是先确定碰撞的是体素的哪个面,再用反射公式计算新速度。
步骤1:精准判断碰撞面(修正碰撞检测逻辑)
原始代码直接移动后检测位置,无法精准定位碰撞面。需要先记录移动前的位置,碰撞时回溯,再对比位置变化确定法向量:
for(let step = 0; step < dist_max; step++) { // 记录移动前的位置 const prev_pos = {x: pos.x, y: pos.y, z: pos.z}; // 移动光线 pos.x += vel.x; pos.y += vel.y; pos.z += vel.z; if(solid_at(pos)) { // 回溯到碰撞前的位置,避免光线陷入体素内部 pos.x = prev_pos.x; pos.y = prev_pos.y; pos.z = prev_pos.z; // 计算当前所在体素的坐标(假设体素为1x1x1的立方体) const voxel = { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) }; let normal = {x: 0, y: 0, z: 0}; // 判断X轴方向的碰撞:撞到体素的右侧(x+1)或左侧(x)面 if(pos.x + vel.x >= voxel.x + 1) { normal.x = 1; } else if(pos.x + vel.x < voxel.x) { normal.x = -1; } // 判断Y轴方向的碰撞 else if(pos.y + vel.y >= voxel.y + 1) { normal.y = 1; } else if(pos.y + vel.y < voxel.y) { normal.y = -1; } // 判断Z轴方向的碰撞 else if(pos.z + vel.z >= voxel.z + 1) { normal.z = 1; } else if(pos.z + vel.z < voxel.z) { normal.z = -1; } // 计算反弹后的速度,1.0为完全反弹强度 bounce(vel, normal, 1.0); break; // 单次碰撞后跳出,如需多次反弹可移除break继续循环 } }
步骤2:实现带强度控制的反弹函数
用标准的反射向量公式计算新速度,再乘以0-1之间的强度系数:
function bounce(vel, normal, strength) { // 计算入射速度与法向量的点积 const dot = vel.x * normal.x + vel.y * normal.y + vel.z * normal.z; // 应用反射公式 vel.x = vel.x - 2 * dot * normal.x; vel.y = vel.y - 2 * dot * normal.y; vel.z = vel.z - 2 * dot * normal.z; // 乘以反弹强度:0=完全吸收,1=完全反弹,0.5=半强度反弹 vel.x *= strength; vel.y *= strength; vel.z *= strength; }
关键原理说明
- 因为体素是轴对齐的,法向量只会在单个轴上有±1的值,所以反射公式会自动反转对应轴的速度分量,其他分量保持不变,完全符合橡胶球的反弹逻辑。
- 强度系数直接控制反弹后的速度大小,适配半透明体素的部分反射需求(比如强度0.7表示70%的速度保留,30%被吸收)。
适配非1x1体素的修改
如果你的体素不是1x1x1的大小,只需把边界判断的voxel.x + 1改成voxel.x * size + size,voxel.x改成voxel.x * size即可(size为单个体素的边长)。
内容的提问来源于stack exchange,提问作者MirceaKitsune
相关产品推荐
相关产品推荐

