Khi tôi gặp vấn đề này khi làm việc trên các khối của mình , tôi đã tìm thấy bài báo "Thuật toán truyền tải nhanh Voxel cho truy tìm tia" của John Amanatides và Andrew Woo, 1987 mô tả một thuật toán có thể áp dụng cho nhiệm vụ này; nó là chính xác và chỉ cần một vòng lặp cho mỗi voxel giao nhau.
Tôi đã viết một triển khai các phần có liên quan của thuật toán của bài báo bằng JavaScript. Việc triển khai của tôi bổ sung hai tính năng: nó cho phép chỉ định giới hạn về khoảng cách của raycast (hữu ích để tránh các vấn đề về hiệu suất cũng như xác định 'tầm với' giới hạn) và cũng tính toán mặt nào của mỗi voxel mà tia đi vào.
origin
Vectơ đầu vào phải được chia tỷ lệ sao cho độ dài cạnh của voxel là 1. Độ dài của direction
vectơ không đáng kể nhưng có thể ảnh hưởng đến độ chính xác của thuật toán.
Thuật toán hoạt động bằng cách sử dụng biểu diễn tham số của tia , origin + t * direction
. Đối với mỗi phối hợp trục, chúng tôi tiếp tục theo dõi những t
giá trị mà chúng tôi sẽ có nếu chúng ta mất một bước đủ để vượt qua một ranh giới voxel dọc theo trục đó (tức là thay đổi một phần số nguyên của phối hợp) trong các biến tMaxX
, tMaxY
và tMaxZ
. Sau đó, chúng tôi thực hiện một bước (sử dụng các biến step
và tDelta
biến) dọc theo trục nào có ít nhất tMax
- tức là bất kỳ ranh giới voxel nào là gần nhất.
/**
* Call the callback with (x,y,z,value,face) of all blocks along the line
* segment from point 'origin' in vector direction 'direction' of length
* 'radius'. 'radius' may be infinite.
*
* 'face' is the normal vector of the face of that block that was entered.
* It should not be used after the callback returns.
*
* If the callback returns a true value, the traversal will be stopped.
*/
function raycast(origin, direction, radius, callback) {
// From "A Fast Voxel Traversal Algorithm for Ray Tracing"
// by John Amanatides and Andrew Woo, 1987
// <http://www.cse.yorku.ca/~amana/research/grid.pdf>
// <http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.42.3443>
// Extensions to the described algorithm:
// • Imposed a distance limit.
// • The face passed through to reach the current cube is provided to
// the callback.
// The foundation of this algorithm is a parameterized representation of
// the provided ray,
// origin + t * direction,
// except that t is not actually stored; rather, at any given point in the
// traversal, we keep track of the *greater* t values which we would have
// if we took a step sufficient to cross a cube boundary along that axis
// (i.e. change the integer part of the coordinate) in the variables
// tMaxX, tMaxY, and tMaxZ.
// Cube containing origin point.
var x = Math.floor(origin[0]);
var y = Math.floor(origin[1]);
var z = Math.floor(origin[2]);
// Break out direction vector.
var dx = direction[0];
var dy = direction[1];
var dz = direction[2];
// Direction to increment x,y,z when stepping.
var stepX = signum(dx);
var stepY = signum(dy);
var stepZ = signum(dz);
// See description above. The initial values depend on the fractional
// part of the origin.
var tMaxX = intbound(origin[0], dx);
var tMaxY = intbound(origin[1], dy);
var tMaxZ = intbound(origin[2], dz);
// The change in t when taking a step (always positive).
var tDeltaX = stepX/dx;
var tDeltaY = stepY/dy;
var tDeltaZ = stepZ/dz;
// Buffer for reporting faces to the callback.
var face = vec3.create();
// Avoids an infinite loop.
if (dx === 0 && dy === 0 && dz === 0)
throw new RangeError("Raycast in zero direction!");
// Rescale from units of 1 cube-edge to units of 'direction' so we can
// compare with 't'.
radius /= Math.sqrt(dx*dx+dy*dy+dz*dz);
while (/* ray has not gone past bounds of world */
(stepX > 0 ? x < wx : x >= 0) &&
(stepY > 0 ? y < wy : y >= 0) &&
(stepZ > 0 ? z < wz : z >= 0)) {
// Invoke the callback, unless we are not *yet* within the bounds of the
// world.
if (!(x < 0 || y < 0 || z < 0 || x >= wx || y >= wy || z >= wz))
if (callback(x, y, z, blocks[x*wy*wz + y*wz + z], face))
break;
// tMaxX stores the t-value at which we cross a cube boundary along the
// X axis, and similarly for Y and Z. Therefore, choosing the least tMax
// chooses the closest cube boundary. Only the first case of the four
// has been commented in detail.
if (tMaxX < tMaxY) {
if (tMaxX < tMaxZ) {
if (tMaxX > radius) break;
// Update which cube we are now in.
x += stepX;
// Adjust tMaxX to the next X-oriented boundary crossing.
tMaxX += tDeltaX;
// Record the normal vector of the cube face we entered.
face[0] = -stepX;
face[1] = 0;
face[2] = 0;
} else {
if (tMaxZ > radius) break;
z += stepZ;
tMaxZ += tDeltaZ;
face[0] = 0;
face[1] = 0;
face[2] = -stepZ;
}
} else {
if (tMaxY < tMaxZ) {
if (tMaxY > radius) break;
y += stepY;
tMaxY += tDeltaY;
face[0] = 0;
face[1] = -stepY;
face[2] = 0;
} else {
// Identical to the second case, repeated for simplicity in
// the conditionals.
if (tMaxZ > radius) break;
z += stepZ;
tMaxZ += tDeltaZ;
face[0] = 0;
face[1] = 0;
face[2] = -stepZ;
}
}
}
}
function intbound(s, ds) {
// Find the smallest positive t such that s+t*ds is an integer.
if (ds < 0) {
return intbound(-s, -ds);
} else {
s = mod(s, 1);
// problem is now s+t*ds = 1
return (1-s)/ds;
}
}
function signum(x) {
return x > 0 ? 1 : x < 0 ? -1 : 0;
}
function mod(value, modulus) {
return (value % modulus + modulus) % modulus;
}
Liên kết vĩnh viễn đến phiên bản nguồn này trên GitHub .