我的raycaster的精灵一直闪烁,我认为这与ZBuffer有关。
使用此作为参考:https://lodev.org/cgtutor/index.html
我已经做了大量研究,找不到任何可以解决我问题的JS射线发生器。精灵是指广告牌图片,当您移动它们时,它们会像被推向极限的Atari游戏一样闪烁。
这是JSFiddle上的项目:https://jsfiddle.net/Vakore/bsvx1m26/
这是我使用ZBuffer的地方:
13
在代码的前面,声明了ZBuffer并为其分配了一个数组值。
for (var stripe = drawStartX; stripe < drawEndX; stripe++) {
var texX =
floor((256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * texWidth) / spriteWidth) / 256;
// the conditions in the if are:
// 1) it's in front of camera plane so you don't see things behind you
// 2) it's on the screen (left)
// 3) it's on the screen (right)
// 4) ZBuffer, with perpendicular distance
var ruffer = 0;
if (sprite[i][2] == "barrel") {
ruffer = 576 - 64;
}
if (sprite[i][2] == "pillar") {
ruffer = 576;
}
if (sprite[i][2] == "greenlight") {
ruffer = 576 + 64;
}
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[stripe]
) {
can.drawImage(
document.getElementById(sprite[i][2]),
texX + ruffer, 0, 1, texHeight, stripe, drawStartY, 1, drawEndY - drawStartY
);
}
}
var keys = {
"a": false,
"s": false,
"d": false,
"A": false,
"S": false,
"D": false,
"W": false,
"Z": false,
"X": false,
"C": false,
"ENTER": false
};
document.addEventListener("keydown", function(e) {
e.preventDefault();
if (e.keyCode === 16)
keys["SHIFT"] = true;
if (e.keyCode === 37)
keys["a"] = true;
if (e.keyCode === 39)
keys["d"] = true;
if (e.keyCode === 65)
keys["A"] = true;
if (e.keyCode === 68)
keys["D"] = true;
if (e.keyCode === 83)
keys["S"] = true;
if (e.keyCode === 87)
keys["W"] = true;
if (e.keyCode === 40)
keys["S"] = true;
if (e.keyCode === 38)
keys["W"] = true;
if (e.keyCode === 67)
keys["C"] = true;
if (e.keyCode === 88)
keys["X"] = true;
if (e.keyCode === 90)
keys["Z"] = true;
if (e.keyCode === 13)
keys["ENTER"] = true;
})
document.addEventListener("keyup", function(e) {
e.preventDefault();
if (e.keyCode === 16)
keys["SHIFT"] = false;
if (e.keyCode === 37)
keys["a"] = false;
if (e.keyCode === 39)
keys["d"] = false;
if (e.keyCode === 65)
keys["A"] = false;
if (e.keyCode === 68)
keys["D"] = false;
if (e.keyCode === 83)
keys["S"] = false;
if (e.keyCode === 87)
keys["W"] = false;
if (e.keyCode === 40)
keys["S"] = false;
if (e.keyCode === 38)
keys["W"] = false;
if (e.keyCode === 67)
keys["C"] = false;
if (e.keyCode === 88)
keys["X"] = false;
if (e.keyCode === 90)
keys["Z"] = false;
})
/*
Basic functions(ease of access)
*/
var canvas = document.getElementById("canva");
var can = canvas.getContext("2d");
canvas.requestPointerLock = canvas.requestPointerLock || canvas.mozRequestPointerLock;
document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock;
canvas.onclick = function() {
canvas.requestPointerLock();
clicked = true;
};
var mouseX = 0;
var mouseMove = function(e) {
mouseX += e.movementX;
};
var rect = function(x, y, w, h) {
can.fillRect(x, y, w, h);
};
var arc = function(x, y, r, start, stop) {
can.beginPath();
can.arc(x, y, r, start, stop);
can.fill();
};
var circle = function(x, y, r) {
arc(x, y, r, 0, 360);
};
var fill = function(r, g, b, a) {
if (a === undefined) {
a = 1;
}
can.fillStyle = "rgb(" + r + "," + g + "," + b + "," + a + ")";
};
var font = function(siz) {
can.font = siz;
};
var textAlign = function(ali) {
can.textAlign = ali;
};
var text = function(txt, x, y, w, h) {
can.fillText(txt, x, y, w, h);
};
var quad = function(x1, y1, x2, y2, x3, y3, x4, y4) {
can.beginPath();
can.moveTo(x1, y1);
can.lineTo(x2, y2);
can.lineTo(x3, y3);
can.lineTo(x4, y4);
can.closePath();
can.fill();
};
var random = function(min, max) {
return round(Math.random() * (max - min)) + min;
};
var translate = function(x, y) {
can.save();
can.translate(x, y);
};
var scale = function(w, h) {
can.scale(w, h);
}
var trestore = function() {
can.restore();
};
var floor = function(num) {
return Math.floor(num);
};
var sqrt = function(num) {
return Math.sqrt(num);
};
var abs = function(num) {
return Math.abs(num);
};
var color = function(r, g, b, a) {
return [r, g, b, a];
};
var dist = function() {
return true;
};
var cos = function(num) {
return Math.cos(num);
}
var sin = function(num) {
return Math.sin(num);
}
var dist = function(x1, y1, x2, y2) {
dx = x1 - x2;
dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
};
//Begin the raycaster
var scaler = 3; //This reduces the resolution so that it can display large rooms without lagging
var w = 640 / scaler;
var h = 480 / scaler;
var texWidth = 64;
var texHeight = 64;
var rotSpeed = 0.1;
var moveSpeed = 0.1;
var worldMap = [
"1111111111111111111111111111111111111111",
"1001000001234567800000000000000000000001",
"1001000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1001000000000000000000000000000000000001",
"1001000000000000000000000000222200000001",
"1001000000000000000000000000200200000001",
"1111000000000000000000000000200200000001",
"1000000000000000000000000033300333000001",
"8008000000000000000000000000000000000001",
"8000800000000000000000000033300333000001",
"8000080000000000000000000000300300000001",
"8000008000000000000000000000333300000001",
"8888888000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1666006660000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1666666660000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000040444444444444400000000000000001",
"1000000040000000000000400000000000000001",
"1000000040000000000000400000000000000001",
"1000000040000000000000400000000000000001",
"1000000044444444444444400000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000007775777000000000000000001",
"1000000000000007000007000000000000000001",
"1000000000000005000005000000000000000001",
"1000000000000007000007000000000000000001",
"1000000000000000000007000000000000000001",
"1111111111111117775777111111111111111111",
];
try {
var ZBuffer = [];
var sprite = [
[5, 5, "barrel", 0], //Y and X are reversed
[6.5, 2.5, "pillar", 1],
[6.5, 1.5, "pillar", 1.5],
[8, 6, "greenlight", 2],
];
var posX = 2,
posY = 2,
dirX = -1,
dirY = 0,
planeX = 0,
planeY = 0.66; //The 2d raycaster version of the camera plane
} catch (e) {
alert(e);
}
var castRays = function() {
for (var x = 0; x < w; x++) {
var cameraX = 2 * x / (w) - 1; //x-coordinate in camera space
var rayDirX = dirX + planeX * cameraX;
var rayDirY = dirY + planeY * cameraX;
//which box of the map we're in
var mapX = floor(posX);
var mapY = floor(posY);
//length of ray from current position to next x or y-side
var sideDistX;
var sideDistY;
//length of ray from one x or y-side to next x or y-side
var deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
var deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
var perpWallDist = 0;
//what direction to step in x or y-direction (either +1 or -1)
var stepX = 0;
var stepY = 0;
var hit = 0; //was there a wall hit?
var side = 0; //was a NS or a EW wall hit?
//calculate step and initial sideDist
if (rayDirX < 0) {
stepX = -1;
sideDistX = (posX - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - posX) * deltaDistX;
}
if (rayDirY < 0) {
stepY = -1;
sideDistY = (posY - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - posY) * deltaDistY;
}
while (hit == 0) {
//jump to next map square, OR in x-direction, OR in y-direction
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
//Check if ray has hit a wall
if (worldMap[mapX][mapY] == 0) {}
if (worldMap[mapX][mapY] != 0) {
hit = 1;
}
} //end of while loop
//Calculate distance of perpendicular ray (Euclidean distance will give fisheye effect!)
if (side == 0) {
perpWallDist = (mapX - posX + (1 - stepX) / 2) / rayDirX;
} else {
perpWallDist = (mapY - posY + (1 - stepY) / 2) / rayDirY;
}
//Calculate height of line to draw on screen
var lineHeight = abs(floor(h / perpWallDist));
//calculate lowest and highest pixel to fill in current stripe
var drawStart = -lineHeight / 2 + h / 2;
//if(drawStart < 0) {drawStart = 0;}
var drawEnd = lineHeight / 2 + h / 2;
//if(drawEnd >= h) {drawEnd = h - 1;}
//texturing calculations
//var texNum = worldMap[mapX][mapY] - 1; //1 subtracted from it so that texture 0 can be used!
//calculate value of wallX
var wallX = 0; //where exactly the wall was hit
if (side == 0) {
wallX = posY + perpWallDist * rayDirY;
} else {
wallX = posX + perpWallDist * rayDirX;
}
wallX -= floor((wallX));
//x coordinate on the texture
var texX = floor(wallX * (texWidth));
if (side == 0 && rayDirX > 0) {
texX = texWidth - texX - 1;
}
if (side == 1 && rayDirY < 0) {
texX = texWidth - texX - 1;
}
//Untextured variant
/*var currentColor = color(255, 255, 255);
if (worldMap[mapX][mapY] == "1") {currentColor = color(150, 150, 150);}
if (worldMap[mapX][mapY] == "2") {currentColor = color(255, 255, 255);}
if (worldMap[mapX][mapY] == "3") {currentColor = color(255,0,0);}
if (worldMap[mapX][mapY] == "4") {currentColor = color(0,0,255);}
if (worldMap[mapX][mapY] == "5") {currentColor = color(0,255,0);}
if (worldMap[mapX][mapY] == "6") {currentColor = color(255,0,255);}
if (worldMap[mapX][mapY]=="7") {currentColor = color(0,255,255);}
if (side === 1) {currentColor = [currentColor[0] - 40, currentColor[1] - 40, currentColor[2] - 40];}
//if (dist(mapX, mapY, px, pz) > 10) {continue;}
fill(currentColor[0],currentColor[1],currentColor[2]);
rect(x, drawStart, 1, drawEnd - drawStart);*/
var currentImg = "bluestone";
if (worldMap[mapX][mapY] == "1") {
currentImg = "bluestone";
}
if (worldMap[mapX][mapY] == "2") {
currentImg = "greystone";
}
if (worldMap[mapX][mapY] == "3") {
currentImg = "wood";
}
if (worldMap[mapX][mapY] == "4") {
currentImg = "colorstone";
}
if (worldMap[mapX][mapY] == "5") {
currentImg = "eagle";
}
if (worldMap[mapX][mapY] == "6") {
currentImg = "mossy";
}
if (worldMap[mapX][mapY] == "7") {
currentImg = "redbrick";
}
if (worldMap[mapX][mapY] == "8") {
currentImg = "purplestone";
}
//if (worldMap[mapX][mapY] =="a") {currentImg = "barrel";}
//if (dist(mapX, mapY, posX, posY) > 20) {continue;}
can.drawImage(document.getElementById(currentImg), texX + ((worldMap[mapX][mapY] - 1) * 64), 0, 1, texHeight, x, drawStart, 1, drawEnd - drawStart);
//fill(255, 0, 0);rect(x, drawStart, 1, drawEnd - drawStart);
if (side === 1) {
fill(0, 0, 0, 0.3);
rect(x, drawStart, 1, drawEnd - drawStart);
}
//Set the ZBuffer
ZBuffer[x] = perpWallDist;
//FLOOR CASTING(not using till figure out how to do without lag)
var floorXWall = 0,
floorYWall = 0;
if (side == 0 && rayDirX > 0) {
floorXWall = mapX;
floorYWall = mapY + wallX;
} else if (side == 0 && rayDirX < 0) {
floorXWall = mapX + 1.0;
floorYWall = mapY + wallX;
} else if (side == 1 && rayDirY > 0) {
floorXWall = mapX + wallX;
floorYWall = mapY;
} else {
floorXWall = mapX + wallX;
floorYWall = mapY + 1.0;
}
var distWall, distPlayer, currentDist;
distWall = perpWallDist;
distPlayer = 0.0;
for (var y = drawEnd + 1; y < h; y++) {
//if (dist(mapX, mapY, posX, posY) > 5) {continue;}
currentDist = h / (2.0 * y - h); //you could make a small lookup table for this instead
var weight = (currentDist - distPlayer) / (distWall - distPlayer);
var currentFloorX = weight * floorXWall + (1.0 - weight) * posX;
var currentFloorY = weight * floorYWall + (1.0 - weight) * posY;
var floorTexX, floorTexY;
floorTexX = floor(currentFloorX * texWidth) % texWidth;
floorTexY = floor(currentFloorY * texHeight) % texHeight;
//OPTIMIZE FOR LESS LAG!!!!!!!!!! (search
//FLOOR
//can.drawImage(document.getElementById("greystone"), floorTexX, floorTexY, texWidth, texHeight, x, y, texWidth, texHeight);
//CEILING
//can.drawImage(document.getElementById("wood"), floorTexX, floorTexY, texWidth, 1, x, h - y, texWidth, 1);
} //End of the 'y' loop
} //end the loop
//SPRITE CASTING
//Sort sprites
sprite.sort(function(a, b) {
return b[3] - a[3];
}); //Sort the depth of each sprite
//Draw sprites
for (var i = 0; i < sprite.length; i++) {
var spriteX = sprite[i][0] - posX;
var spriteY = sprite[i][1] - posY;
sprite[i][3] = abs((posX - sprite[i][0]) - (posY - sprite[i][1]));
var invDet = 1.0 / (planeX * dirY - dirX * planeY);
var transformX = invDet * (dirY * spriteX - dirX * spriteY);
var transformY = invDet * (-planeY * spriteX + planeX * spriteY); //this is actually the depth inside the screen, that what Z is in 3D(Thanks lodev.org for explanation!)
var spriteScreenX = floor((w / 2) * (1 + transformX / transformY));
//calculate height of the sprite on screen
var spriteHeight = abs(floor(h / (transformY))); //using "transformY" instead of the real distance prevents fisheye
//calculate lowest and highest pixel to fill in current stripe
var drawStartY = -spriteHeight / 2 + h / 2;
//if (drawStartY < 0) {drawStartY = 0;} Don't need THIS
var drawEndY = spriteHeight / 2 + h / 2;
//if (drawEndY >= h) {drawEndY = h - 1;} Or THIS
//calculate width of the sprite
var spriteWidth = abs(floor(h / (transformY)));
var drawStartX = -spriteWidth / 2 + spriteScreenX;
//if (drawStartX < 0) {drawStartX = 0;} Nor this
var drawEndX = spriteWidth / 2 + spriteScreenX;
//if (drawEndX >= w) {drawEndX = w - 1;} Nor dis
//loop through every vertical stripe of the sprite on screen
for (var stripe = drawStartX; stripe < drawEndX; stripe++) {
var texX = floor(256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * texWidth / spriteWidth) / 256;
//the conditions in the if are:
//1) it's in front of camera plane so you don't see things behind you
//2) it's on the screen (left)
//3) it's on the screen (right)
//4) ZBuffer, with perpendicular distance
var ruffer = 0;
if (sprite[i][2] == "barrel") {
ruffer = 576 - 64;
}
if (sprite[i][2] == "pillar") {
ruffer = 576;
}
if (sprite[i][2] == "greenlight") {
ruffer = 576 + 64;
}
if (transformY > 0 && stripe > 0 && stripe < w && transformY < ZBuffer[stripe]) {
can.drawImage(document.getElementById(sprite[i][2]), texX + ruffer, 0, 1, texHeight, stripe, drawStartY, 1, drawEndY - drawStartY);
}
}
} //End of 'i' loop
//Bottom of dat
if (keys["W"]) {
if (worldMap[floor(posX + dirX * moveSpeed)][floor(posY)] == false) {
posX += dirX * moveSpeed
};
if (worldMap[floor(posX)][floor(posY + dirY * moveSpeed)] == false) {
posY += dirY * moveSpeed
};
}
//move backwards if no wall behind you
if (keys["S"]) {
if (worldMap[floor(posX - dirX * moveSpeed)][floor(posY)] == false) {
posX -= dirX * moveSpeed;
}
if (worldMap[floor(posX)][floor(posY - dirY * moveSpeed)] == false) {
posY -= dirY * moveSpeed;
}
}
if (keys["D"]) {
if (worldMap[floor(posX + planeX * moveSpeed)][floor(posY)] == false) {
posX += planeX * moveSpeed
};
if (worldMap[floor(posX)][floor(posY + planeY * moveSpeed)] == false) {
posY += planeY * moveSpeed
};
}
if (keys["A"]) {
if (worldMap[floor(posX - planeX * moveSpeed)][floor(posY)] == false) {
posX -= planeX * moveSpeed;
}
if (worldMap[floor(posX)][floor(posY - planeY * moveSpeed)] == false) {
posY -= planeY * moveSpeed;
}
}
//rotate to the right
mouseX = -mouseX / 75;
var oldDirX = dirX;
dirX = dirX * cos(mouseX) - dirY * sin(mouseX);
dirY = oldDirX * sin(mouseX) + dirY * cos(mouseX);
var oldPlaneX = planeX;
planeX = planeX * cos(mouseX) - planeY * sin(mouseX);
planeY = oldPlaneX * sin(mouseX) + planeY * cos(mouseX);
mouseX = 0;
if (keys["d"]) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed);
dirY = oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed);
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
}
//rotate to the left
if (keys["a"]) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * cos(rotSpeed) - dirY * sin(rotSpeed);
dirY = oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * cos(rotSpeed) - planeY * sin(rotSpeed);
planeY = oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed);
}
};
var run = function() {
try {
if (document.pointerLockElement === canvas || document.mozPointerLockElement === canvas) {
document.addEventListener("mousemove", mouseMove, false);
} else {
document.removeEventListener("mousemove", mouseMove, false);
} //Mouse stuff
translate(0, 0);
scale(scaler, scaler);
fill(40, 40, 40);
rect(0, 0, w, h / 2);
fill(80, 80, 80);
rect(0, h / 2, w, h / 2);
if (keys["C"]) {
rotSpeed = 0.04;
} else {
rotSpeed = 0.1;
}
/*if (keys["W"]) {fill(255, 0, 0);}
if (keys["A"]) {fill(0, 255, 0);}
if (keys["S"]) {fill(0, 0, 255);}
if (keys["D"]) {fill(255, 0, 255);}
if (keys["X"]) {fill(255, 255, 0);}
if (keys["Z"]) {fill(0, 255, 255);}*/
castRays();
trestore();
requestAnimationFrame(run);
} catch (e) {
alert(e);
}
};
run();
更新:我尝试更改
<html>
<title>Jailbreak</title>
<div class="center">
<canvas width="640" height="480" id="canva"></canvas>
<br>
<img id="barrel" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="pillar" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="greenlight" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="bluestone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="greystone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="wood" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="colorstone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="eagle" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="mossy" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="redbrick" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="purplestone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
</div>
</html>
收件人:
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[stripe]//Pay attention to this line
) {
基本上可以倒转检查以查看其是否在墙后。令人惊讶的是,闪烁继续。
答案 0 :(得分:0)
答案很简单。这是因为stripe
不是一个偶数,并且如果您要拥有这样的数组:var thisArray = [1, 2, 3, 4];
,并且您将尝试获取var money = thisArray[1.5];
的值undefined
将返回。
所以我改变了这个:
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[stripe]
) {
对此:
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[Math.round(stripe)]
) {
Math.floor也可以工作,因为它也返回整数值。