为什么我的Raycaster精灵会闪烁,如何解决?

时间:2019-01-27 23:48:58

标签: javascript html5-canvas raycasting

我的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
      ) {

基本上可以倒转检查以查看其是否在墙后。令人惊讶的是,闪烁继续。

1 个答案:

答案 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也可以工作,因为它也返回整数值。