8

我正在尝试使用 2D 渲染上下文在 HTML5 Canvas 中制作一种相机。正如您在下面绘制的图片中看到的那样,这就是我想要实现的目标:

  1. 假设黑色的是相机的眼睛,我希望它能够与画布一起移动(编辑:)(如图中的绿色箭头),并且看起来就像它在物体周围移动一样,就像红色的一样(我相信这是视差的东西)。
  2. 每当我在物体周围旅行时,当我旋转相机时,我希望它旋转相机的中心(参见蓝色旋转)。

我已经做到了,每当我移动相机时,红色框可以在相机中心旋转,[编辑],这是一个简化的例子:

*Within the requestAnimationFrame (game loop)*

...

ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);

ctx.save();
    // draw camera eye.
    ctx.lineWidth = 3;
    ctx.fillStyle = "black";
    ctx.beginPath();
    ctx.moveTo(ctx.canvas.width / 2 - 50, ctx.canvas.height / 2);
    ctx.lineTo(ctx.canvas.width / 2 + 50, ctx.canvas.height / 2);
    ctx.moveTo(ctx.canvas.width / 2, ctx.canvas.height / 2 - 50);
    ctx.lineTo(ctx.canvas.width / 2, ctx.canvas.height / 2 + 50);
    ctx.stroke();

    // Rotate by camera's center.
    ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
    ctx.rotate(worldRotate);
    ctx.translate(worldPos.x, worldPos.y);

    //
    // ADD WORLD ENTITIES BELOW to be viewed by the camera.
    //

    ctx.fillStyle = "red";
    ctx.fillRect(-5, -5, 10, 10);
ctx.restore();

...

(编辑:将转换对象更改为 ctx 以进一步简化代码。)

变量 worldRotate 和 worldPos 可通过输入更改以进行测试。

编辑:这是一个真实的例子,这个例子让我更清楚地了解我想要实现的目标

尝试将其向右或向左移动,看看会发生什么。并尝试向右旋转相机,使红色框与 x 轴对齐,然后移动相机,看看它如何完美地像我想要的相机一样工作。

(注:推荐的测试浏览器是IE10,最新的chrome,最新的firefox,最新的opera,以及桌面版safari 5+)。

问题是,当我移动相机(通过改变worldPos,使红色框远离相机),旋转(使用worldRotate),然后再次移动相机,红色物体总是朝着紫色箭头移动而不是橙色箭头。无论相机中心是否旋转,我都希望红色框向橙色箭头移动。

编辑:据我所知,很明显旋转会导致平移混乱,因为旋转会改变红框的坐标系,但我仍然不知道如何处理它,至少让无论红框的当前旋转坐标系如何,平移都像橙色箭头一样。

对此有任何解决方案吗?谢谢你。

示例图片

4

3 回答 3

4

[根据 OP 的进一步澄清进行编辑]

好的,我现在对你的问题有了更好的理解。你想让你的相机镜头透视你世界中的物体。

如果您的相机在与您的世界对象相关的 X 方向上看,您可以简单地使用视差。为此,您可以在画布上应用 2 个图层,这些图层在 X 方向上以不同的速度滚动。这是全景视差。因此,对于每一帧,像这样的伪代码绘制 2 层:

// Move the back layer
backLayerX += 3;
ctx.save();
ctx.translate(backLayerX,backLayerY);
// draw the objects in your back layer now
ctx.translate(-backLayerX,-backLayerY);
ctx.restore();

// Move the front layer faster than the back layer
frontLayerX += 10;
ctx.save();
ctx.translate(frontLayerX,frontLayerY);
// draw the objects in your front layer now
ctx.translate(frontLayerX,frontLayerY);
ctx.restore();

如果您在 X 方向上移动相机本身,您还必须更改前后图层的平移速度。

如果您的相机也相对于您的世界对象在 Z 方向(相机更近)查看,则该过程要复杂得多

例如,如果您靠近一座雕像,雕像会显得更大——ctx.scale(2,2)。背景景物也变大了,但没那么快——ctx.scale(1.25,1.25)。

然后如果你也向左走一步,那么雕像的左侧比雕像的右侧略大——上下文倾斜。背景也偏斜,但非常非常轻微。

然后,如果你也站在椅子上(改变 Y 方向),你会得到另一种倾斜。

即使有雕像,您的相机也永远无法移动,因为您的雕像是 2D 的——只是雕像的纸板切口。从摄像机的角度来看,雕像实际上会消失!

我希望我已经让您开始了您的项目,但完整的答案是 waaaaaaaaaay 超出了 Stackoverflow 的答案——这是一门关于计算机图形学的大学课程。

我确实有一个在线教程推荐给你。Wolfgang Hurst有一个关于 Perspective 的精彩讲座。这是他的 13 部分在线大学课程中的第 7 讲。您可以通过两种方式查看他的讲座。广泛地查看以了解赋予相机视角所涉及的概念。或深入查看并获得数学算法: http ://www.youtube.com/watch?v=q_HA-x5AujI&list=PLDFA8FCF0017504DE

祝你好运和学习...... :)

[以下内容解决了 OP 的原始问题]

这是有关如何在画布中成功旋转任何内容的快速教程

整个世界围绕一个 X/Y“注册点”旋转。

您可以通过在 ctx.rotate(angle) 之前执行 ctx.translate(X,Y) 来设置注册点。

在这个旋转空间中绘制“世界”对象后,您必须通过执行 translate(-X,-Y)重新设置注册点。

所以世界旋转是这样的:

  • 上下文.save();
  • Context.translate(registrationX, registrationY);
  • 上下文.旋转(角度);
  • 画东西。
  • Context.translate(-registrationX, -registrationY);
  • 上下文.restore();

这是理解旋转和注册点的好方法:

  • 拿一张纸,在纸上画一个字母。
  • 拿一支铅笔并在页面上的任何位置按住它。
  • --铅笔点是注册点。
  • --铅笔点是平移(X,Y)。
  • 围绕铅笔尖旋转纸张。
  • --纸张的旋转为rotate(angle)
  • ——注意字母是如何随着纸张移动而旋转的。
  • --您的红框将像字母一样旋转(围绕您的相机注册点)
  • 将纸张重置为垂直旋转。
  • --这个“不旋转”就像翻译(-X,-Y
  • 将铅笔移动到纸上的另一个点
  • --再一次,这就像 translate(newX,newY)
  • 再次旋转纸张,注意字母如何以不同的方式旋转。

这是代码和小提琴:http: //jsfiddle.net/m1erickson/nj29x/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; padding:30px; }
    canvas{border:1px solid red;}
</style>

<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    var camX=100;
    var camY=100;
    var camRadius=40;
    var camDegrees=0;

    draw();

    function draw(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
        drawWorldRect(75,75,30,20,0,"blue","A");
        drawWorldRect(100,100,30,20,15,"red","B");
        drawCamera();
    }

    function drawWorldRect(x,y,width,height,angleDegrees,fillstyle,letter){
        var cx=x+width/2;
        var cy=y+height/2;
        ctx.save();
        ctx.translate(cx, cy);
        ctx.rotate( (Math.PI / 180) * angleDegrees);
        ctx.fillStyle = fillstyle;
        ctx.fillRect(-width/2, -height/2, width, height);
        ctx.fillStyle="white";
        ctx.font = '18px Verdana';
        ctx.fillText(letter, -6, 7);
        ctx.restore();
    }


    function drawCamera(){
        ctx.save();
        ctx.lineWidth = 3;
        ctx.beginPath();
        // draw camera cross-hairs
        ctx.strokeStyle = "#dddddd";
        ctx.moveTo(camX,camY-camRadius);
        ctx.lineTo(camX,camY+camRadius);
        ctx.moveTo(camX-camRadius,camY);
        ctx.lineTo(camX+camRadius,camY);
        ctx.arc(camX, camY, camRadius, 0 , 2 * Math.PI, false);
        ctx.stroke();
        // draw camera site
        ctx.fillStyle = "#222222";
        ctx.beginPath();
        ctx.arc(camX, camY-camRadius, 3, 0 , 2 * Math.PI, false);
        ctx.fill()
        ctx.restore();
    }

    function rotate(){
        ctx.save();
        ctx.translate(camX,camY);
        ctx.rotate(Math.PI/180*camDegrees);
        ctx.translate(-camX,-camY);
        draw();
        ctx.restore();
    }

    $("#rotateCW").click(function(){ camDegrees+=30; rotate(); });
    $("#rotateCCW").click(function(){ camDegrees-=30; rotate(); });
    $("#camUp").click(function(){ camY-=20; draw(); });
    $("#camDown").click(function(){ camY+=20; draw(); });
    $("#camLeft").click(function(){ camX-=20; draw(); });
    $("#camRight").click(function(){ camX+=20; draw(); });

}); // end $(function(){});
</script>

</head>

<body>

    <canvas id="canvas" width=400 height=300></canvas><br/>
    <button id="camLeft">Camera Left</button>
    <button id="camRight">Camera Right</button>
    <button id="camUp">Camera Up.</button>
    <button id="camDown">Camera Down</button><br/>
    <button id="rotateCW">Rotate Clockwise</button>
    <button id="rotateCCW">Rotate CounterClockwise</button>

</body>
</html>
于 2013-03-18T02:48:12.103 回答
2

将世界中心移动到的 x 和 y 点是使用旋转角度的正切计算的。我有一个展示这个概念的例子,它不是最终的解决方案,而是你可以工作的起点。将其视为“粗剪”(没有时间完全打磨它)。例如,如您所见,旋转的枢轴点不会移动到相机镜头正下方的点,因此旋转不会直接发生在相机下方。

这是解释几何/三角如何工作的图表:

在此处输入图像描述

这是代码片段和指向功能齐全的 jsfiddle 的链接。键盘命令与您的示例相同。

http://jsfiddle.net/ricksuggs/X8rYF/

    //s (down)
    if (keycode === 83) {

        event.preventDefault();
        canvas.getContext('2d').clearRect(-200,-200,400,400);
        canvas.getContext('2d').translate(2*Math.tan(rotationSum), -2); 
        drawGrid();
        drawCamera();

    }

    // <-- (rotate left)
    if (keycode === 37) {

        event.preventDefault();
        canvas.getContext('2d').clearRect(-200,-200,400,400);
        rotationSum += rotation;
        console.log('rotationSum: ' + rotationSum);
        canvas.getContext('2d').rotate(-rotation);        
        drawGrid();
        drawCamera();

    }
于 2013-03-26T03:47:55.330 回答
0

顺序应该是这样的:

Ctx.save()
Ctx.translate(x, y) //to the center (Or top left of object to rotate)
Ctx.rotates(radian)
Ctx.translate(-x, -y) //important to move the context back!
Ctx.draw(...)
Ctx.restore()
于 2013-03-16T18:59:11.917 回答