首页 > 解决方案 > 光线投射器中的鱼眼效果

问题描述

我正在构建这个 raycaster 作为学校项目,我只是不知道如何修复这个鱼眼效果,我已经做了多次数学,查了一下,似乎没有任何效果,下面是一个效果示例:

鱼眼效果

现在,我正在使用公式,dist *= Math.cos(r.angle - player.angle);其中 dist 是光线长度,r 是光线。如上所示,这种清晰的剂量可以完全发挥作用,但它绝对会破坏效果,没有这个它会一团糟,仍然......我现在所拥有的不是我希望的效果,任何人都可以帮助我。

谢谢大家!

如果您需要完整的代码(我会建议在全屏或单独的选项卡中操作,因为否则它确实有效):

var c = document.getElementById('c');
var ctx = c.getContext('2d');

var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;

var mapTileSize = [];

var walls = [];

var FOV = Math.PI/180 * 90

var map = [
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ],
]
var COLORS = {
    tile:"grey",
    none:"white",
    clear:"lightgrey",
    wallx:"red",
    wally:"blue",
    floor:"green",
    sky:"blue",
    threeDwally: "#d1d1d1",
    threeDwallx: "#bababa"
}

var player = {
    x: null,
    y: null,
    angle: 0,
    rotationDirection: 0,
    moveDirection: 0,
    moveSpeed: 1,
    rotationSpeed: 0.03,
}

function isUndefined(_arr, _index1, _index2) {
    try {
        return _arr[_index1][_index2] == undefined;
    } catch (e) {
        return true;
    }
}
function genWalls(arr,tilesize){
    var walls = [];

    for (y = 0; y < arr.length; y++) {
        for (x = 0; x < arr.length; x++) {

            //       _
            // wall: ^
            if (!isUndefined(arr, y - 1, x)) {
                if (arr[y - 1][x] == 0 && arr[y][x] == 1) {
                    var wall = {
                        x1: x * tilesize,
                        y1: y * tilesize,
                        x2: x * tilesize + tilesize,
                        y2: y * tilesize,
                        axis: "x"
                    }
                    walls.push(wall)
                }
            } else {
                var wall = {
                    x1: x * tilesize,
                    y1: y * tilesize + 1,
                    x2: x * tilesize + tilesize,
                    y2: y * tilesize + 1,
                    axis: "x"
                }
                walls.push(wall)
            }

            // wall: |<
            if (!isUndefined(arr, y, x - 1)) {
                if (arr[y][x - 1] == 0 && arr[y][x] == 1) {
                    var wall = {
                        x1: x * tilesize,
                        y1: y * tilesize,
                        x2: x * tilesize,
                        y2: y * tilesize + tilesize,
                        axis: "y"
                    }
                    walls.push(wall)
                }
            } else {
                var wall = {
                    x1: x * tilesize + 1,
                    y1: y * tilesize,
                    x2: x * tilesize + 1,
                    y2: y * tilesize + tilesize,
                    axis: "y"
                }
                walls.push(wall)
            }

            // wall: _
            if (!isUndefined(arr, y + 1, x)) {
                if (arr[y + 1][x] == 0 && arr[y][x] == 1) {
                    var wall = {
                        x1: x * tilesize,
                        y1: y * tilesize + tilesize,
                        x2: x * tilesize + tilesize,
                        y2: y * tilesize + tilesize,
                        axis : "x"
                    }
                    walls.push(wall)
                }
            } else {
                var wall = {
                    x1: x * tilesize,
                    y1: y * tilesize + tilesize - 1,
                    x2: x * tilesize + tilesize,
                    y2: y * tilesize + tilesize - 1,
                    axis : "x"
                }
                walls.push(wall)
            }

            // wall: >|
            if (!isUndefined(arr, y, x + 1)) {
                if (arr[y][x + 1] == 0 && arr[y][x] == 1) {
                    var wall = {
                        x1: x * tilesize + tilesize,
                        y1: y * tilesize,
                        x2: x * tilesize + tilesize,
                        y2: y * tilesize + tilesize,
                        axis : "y"
                    }
                    walls.push(wall)
                }
            } else {
                var wall = {
                    x1: x * tilesize + tilesize - 1,
                    y1: y * tilesize,
                    x2: x * tilesize + tilesize - 1,
                    y2: y * tilesize + tilesize,
                    axis : "y"
                }
                walls.push(wall)
            }

        }
    }

    return walls;
}

function drawMinimap(offX,offY,size,rays){
    mapTileSize = size/map.length;
    
    for(y=0; y<map.length; y++){
        for(x=0; x<map[y].length; x++){
            switch(map[y][x]){
                case 1:
                    ctx.fillStyle = COLORS.tile
                break;
                case 0:
                    ctx.fillStyle = COLORS.none
                break;
            }
            ctx.fillRect(x * mapTileSize + offX, y * mapTileSize + offY, mapTileSize, mapTileSize)
        }
    }

    //uncomment for wall render
    /*
    walls.forEach(wall => {
        ctx.lineWidth = 2;
        ctx.beginPath()
        if(wall.axis == "x"){ctx.strokeStyle = COLORS.wallx;}else{ctx.strokeStyle = COLORS.wally;}
        ctx.moveTo(wall.x1 + offX,wall.y1 + offY);
        ctx.lineTo(wall.x2 + offX,wall.y2 + offY);
        ctx.stroke();
        ctx.lineWidth = 1;
    })
    */

    //drawing player
    if(player.x == null || player.y == null){player.x = 1.5 * mapTileSize; player.y = 1.5 * mapTileSize}

    ctx.beginPath();
    ctx.arc(player.x + offX, player.y + offY, 5, 0, Math.PI * 2);
    ctx.strokeStyle = COLORS.player;
    ctx.stroke();

    //uncomment for marker
    /*
    ctx.beginPath();
    ctx.moveTo(player.x + offX, player.y + offY);
    ctx.lineTo(player.x + 20 * Math.cos(player.angle) + offX, player.y + 20 * Math.sin(player.angle) + offY)
    ctx.strokeStyle = COLORS.player;
    ctx.stroke();
    */

    //rendering rays
    rays.forEach(r=>{
        ctx.beginPath();
        ctx.moveTo(r.x1 + offX, r.y1 + offY);
        ctx.lineTo(r.x2 + offX, r.y2 + offY)
        if(r.axis == "x"){ctx.strokeStyle = COLORS.wallx;}else{ctx.strokeStyle = COLORS.wally;}
        ctx.stroke();
    })
}
function updatePlayer() {
    player.angle += player.rotationDirection * player.rotationSpeed;

    var newY = player.y + player.moveDirection * (player.moveSpeed) * Math.sin(player.angle);
    var newX = player.x + player.moveDirection * (player.moveSpeed) * Math.cos(player.angle);

    if(typeof mapTileSize !== 'undefined'){
        tileVal = map[Math.floor(newY/mapTileSize)][Math.floor(newX/mapTileSize)]

        if (tileVal != 1) {
            player.x = newX;
            player.y = newY;
        }
    }
}

function distance(x1,y1,x2,y2) {
    return Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) )
}
function change(input,input_start,input_end,output_start,output_end){
    return output_start + ((output_end - output_start) / (input_end - input_start)) * (input - input_start)
}

function castRays(){
    var rays = [];
    for (rayAngle = player.angle - FOV/2; rayAngle < player.angle + FOV/2; rayAngle += Math.PI/180){
        var orginX = player.x;
        var orginY = player.y;
        
        collidedPositions = [];

        walls.forEach(w=>{
            //#region Math
                x3 = orginX;
                x4 = orginX + Math.cos(rayAngle);
                y3 = orginY;
                y4 = orginY + Math.sin(rayAngle);

                x1 = w.x1;
                x2 = w.x2;
                y1 = w.y1;
                y2 = w.y2;

                denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
                t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom  
                u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom

                if (t > 0 && t < 1 && u > 0) {
                    collidedPositions.push({colY: y1 + t * y2 - t*y1,colX: x1 + t * x2 - t*x1,wall:w})
                }
            //#endregion
        })
        var shortestPosition = null;
        collidedPositions.forEach(p => {
            if(shortestPosition == null){
                shortestPosition = p;
            }
            if(distance(orginX,orginY,p.colX,p.colY) < distance(orginX,orginY,shortestPosition.colX,shortestPosition.colY)){
                shortestPosition = p;
            }
        })

        var ray = {
            x1:orginX,
            y1:orginY,
            x2:shortestPosition.colX,
            y2:shortestPosition.colY,
            axis:shortestPosition.wall.axis,
            angle:rayAngle,
            length:distance(orginX,orginY,shortestPosition.colX,shortestPosition.colY),
        }
        rays.push(ray)
    }
    return rays;
}
function draw3dRays(rayList){
    segmentWidth = w / (FOV * 180/Math.PI)

    rayList.forEach((r,i) => {
        dist = r.length;
        dist *= Math.cos(r.angle - player.angle);
        height = change(dist,0,h/2,h-300,0);

        if(r.axis == "x"){ctx.fillStyle = COLORS.threeDwallx;}else{ctx.fillStyle = COLORS.threeDwally;}
        ctx.fillRect(i*segmentWidth,h/2 - height/2,segmentWidth+ 1,height)
        ctx.fillStyle = COLORS.floor;
        ctx.fillRect(i*segmentWidth,h/2 + height/2,segmentWidth+ 1,height)
        ctx.fillStyle = COLORS.sky;
        ctx.fillRect(i*segmentWidth,0,segmentWidth+ 1,h/2 - height/2)
    })

}

function setup(){
    drawMinimap(10,10,300,[]);
    walls = genWalls(map,mapTileSize)
    requestAnimationFrame(loop)
}
function loop(){
    ctx.fillStyle = COLORS.clear;
    ctx.fillRect(0,0,w,h);

    updatePlayer();
    rays = castRays()
    draw3dRays(rays)
    drawMinimap(10,10,300,rays);

    requestAnimationFrame(loop)
}

document.onkeydown = (e) => {
    switch (e.key) {
        case "w":
            player.moveDirection = 1;
            break;
        case "a":
            player.rotationDirection = -1;
            break;
        case "s":
            player.moveDirection = -1;
            break;
        case "d":
            player.rotationDirection = 1;
            break;
    }
}
document.onkeyup = (e) => {
    switch (e.key) {
        case "w":
            player.moveDirection = 0;
            break;
        case "a":
            player.rotationDirection = 0;
            break;
        case "s":
            player.moveDirection = 0;
            break;
        case "d":
            player.rotationDirection = 0;
            break;
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Raycasting</title>
</head>
<body onload="setup()"style="margin:0px;overflow:hidden;">
    <canvas id="c"></canvas>
    <script src="main.js"></script>
</body>
</html>

标签: javascripthtmlcanvasraycasting

解决方案


推荐阅读