当前位置: 代码迷 >> JavaScript >> 画布旋转星场 更新 更新2
  详细解决方案

画布旋转星场 更新 更新2

热度:74   发布时间:2023-06-07 13:47:45.0

我正在采用以下方法在屏幕上设置星形场的动画,但我仍然坚持下一部分。

JS

var c = document.getElementById('stars'),
    ctx = c.getContext("2d"),
    t = 0; // time

c.width = 300;
c.height = 300;

var w = c.width,
    h = c.height,
    z = c.height,
    v = Math.PI; // angle of vision

(function animate() {

    Math.seedrandom('bg');
    ctx.globalAlpha = 1;

    for (var i = 0; i <= 100; i++) {

        var x = Math.floor(Math.random() * w), // pos x
            y = Math.floor(Math.random() * h), // pos y
            r = Math.random()*2 + 1, // radius
            a = Math.random()*0.5 + 0.5, // alpha

            // linear
            d = (r*a),       // depth
            p = t*d;         // pixels per t

        x = x - p;       // movement
        x = x - w * Math.floor(x / w); // go around when x < 0

        (function draw(x,y) {
            var gradient = ctx.createRadialGradient(x, y, 0, x + r, y + r, r * 2);
            gradient.addColorStop(0, 'rgba(255, 255, 255, ' + a + ')');
            gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');

            ctx.beginPath();
            ctx.arc(x, y, r, 0, 2*Math.PI);
            ctx.fillStyle = gradient;
            ctx.fill();

            return draw;

        })(x, y);

    }

    ctx.restore();
    t += 1;

    requestAnimationFrame(function() {
        ctx.clearRect(0, 0, c.width, c.height);
        animate();
    });
})();

HTML

<canvas id="stars"></canvas>

CSS

canvas {
    background: black;
}

它现在所做的是用每个恒星动画三角形X来考虑恒星的不透明度和大小,所以最小的恒星似乎移动得更慢。

使用p = t; 让所有的星星以相同的速度移动。

我正在寻找一个明确定义的模型,其中速度给出了围绕期望者旋转的恒星的幻觉,根据旋转中心cX, cY和视角v ,即2π的分数可以是看到(如果圆的中心不是屏幕的中心,半径应该至少是最大部分)。 我正在努力寻找一种方法,将这个余弦应用于恒星运动的速度,即使对于旋转为π的居中圆也是如此。

这些图表可能会进一步解释我所追求的内容:

居中圈:

非中心:

不同的视角:

我真的迷失了如何前进。 我已经伸了一点自己到这儿了。 你可以先帮我一步吗?

谢谢


UPDATE

我在这段代码上取得了一些进展:

        // linear
        d = (r*a)*z,   // depth
        v = (2*Math.PI)/w,
        p = Math.floor( d * Math.cos( t * v ) );     // pixels per t

    x = x + p;       // movement
    x = x - w * Math.floor(x / w); // go around when x < 0

其中p是粒子在均匀圆周运动中的x坐标, v是角速度,但这会产生钟摆效应。 我不知道如何改变这些方程式来创造观察者转向的错觉。


更新2:

差不多了。 ## Math freenode频道的一位用户非常友好地建议以下计算:

        // linear
        d = (r*a),       // depth
        p = t*d;         // pixels per t

    x = x - p;       // movement
    x = x - w * Math.floor(x / w); // go around when x < 0

    x = (x / w) - 0.5;
    y = (y / h) - 0.5;

    y /= Math.cos(x);

    x = (x + 0.5) * w;
    y = (y + 0.5) * h;

这在视觉上实现了效果,但是在变量方面没有遵循明确定义的模型(它只是“破解”效果)所以我看不到采用不同实现的直接方式(改变中心,视角)。 真实模型可能与此非常相似。


更新3

根据Iftah的回应,我能够使用将旋转矩阵应用于星星,这需要首先保存在数组中。 此外,现在确定每个星的z坐标,并且相反地从它导出半径r和不透明度a 代码差异很大,所以我没有发布它,但它可能是朝着正确方向迈出的一步。 我不能让它连续旋转。 在每个帧上使用矩阵运算在性能方面似乎很昂贵。

这是一些伪代码,可以完成您所说的内容。

Make a bunch of stars not too far but not too close (via rejection sampling)
Set up a projection matrix (defines the camera frustum)
Each frame
    Compute our camera rotation angle
    Make a "view" matrix (repositions the stars to be relative to our view)
    Compose the view and projection matrix into the view-projection matrix
    For each star
        Apply the view-projection matrix to give screen star coordinates
        If the star is behind the camera skip it
        Do some math to give the star a nice seeming 'size'
        Scale the star coordinate to the canvas
        Draw the star with its canvas coordinate and size

我已经实现了上述功能。 它使用gl-matrix Javascript库来处理一些矩阵数学。 这是件好事。 (小提琴就 ,或者见下文。)

 var c = document.getElementById('c'); var n = c.getContext('2d'); // View matrix, defines where you're looking var viewMtx = mat4.create(); // Projection matrix, defines how the view maps onto the screen var projMtx = mat4.create(); // Adapted from http://stackoverflow.com/questions/18404890/how-to-build-perspective-projection-matrix-no-api function ComputeProjMtx(field_of_view, aspect_ratio, near_dist, far_dist, left_handed) { // We'll assume input parameters are sane. field_of_view = field_of_view * Math.PI / 180.0; // Convert degrees to radians var frustum_depth = far_dist - near_dist; var one_over_depth = 1 / frustum_depth; var e11 = 1.0 / Math.tan(0.5 * field_of_view); var e00 = (left_handed ? 1 : -1) * e11 / aspect_ratio; var e22 = far_dist * one_over_depth; var e32 = (-far_dist * near_dist) * one_over_depth; return [ e00, 0, 0, 0, 0, e11, 0, 0, 0, 0, e22, e32, 0, 0, 1, 0 ]; } // Make a view matrix with a simple rotation about the Y axis (up-down axis) function ComputeViewMtx(angle) { angle = angle * Math.PI / 180.0; // Convert degrees to radians return [ Math.cos(angle), 0, Math.sin(angle), 0, 0, 1, 0, 0, -Math.sin(angle), 0, Math.cos(angle), 0, 0, 0, 0, 1 ]; } projMtx = ComputeProjMtx(70, c.width / c.height, 1, 200, true); var angle = 0; var viewProjMtx = mat4.create(); var minDist = 100; var maxDist = 1000; function Star() { var d = 0; do { // Create random points in a cube.. but not too close. this.x = Math.random() * maxDist - (maxDist / 2); this.y = Math.random() * maxDist - (maxDist / 2); this.z = Math.random() * maxDist - (maxDist / 2); var d = this.x * this.x + this.y * this.y + this.z * this.z; } while ( d > maxDist * maxDist / 4 || d < minDist * minDist ); this.dist = Math.sqrt(d); } Star.prototype.AsVector = function() { return [this.x, this.y, this.z, 1]; } var stars = []; for (var i = 0; i < 5000; i++) stars.push(new Star()); var lastLoop = Date.now(); function loop() { var now = Date.now(); var dt = (now - lastLoop) / 1000.0; lastLoop = now; angle += 30.0 * dt; viewMtx = ComputeViewMtx(angle); //console.log('---'); //console.log(projMtx); //console.log(viewMtx); mat4.multiply(viewProjMtx, projMtx, viewMtx); //console.log(viewProjMtx); n.beginPath(); n.rect(0, 0, c.width, c.height); n.closePath(); n.fillStyle = '#000'; n.fill(); n.fillStyle = '#fff'; var v = vec4.create(); for (var i = 0; i < stars.length; i++) { var star = stars[i]; vec4.transformMat4(v, star.AsVector(), viewProjMtx); v[0] /= v[3]; v[1] /= v[3]; v[2] /= v[3]; //v[3] /= v[3]; if (v[3] < 0) continue; var x = (v[0] * 0.5 + 0.5) * c.width; var y = (v[1] * 0.5 + 0.5) * c.height; // Compute a visual size... // This assumes all stars are the same size. // It also doesn't scale with canvas size well -- we'd have to take more into account. var s = 300 / star.dist; n.beginPath(); n.arc(x, y, s, 0, Math.PI * 2); //n.rect(x, y, s, s); n.closePath(); n.fill(); } window.requestAnimationFrame(loop); } loop(); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.1/gl-matrix-min.js"></script> <canvas id="c" width="500" height="500"></canvas> 

一些链接:

更新

这是另一个具有键盘控件的版本。 有点好玩。 你可以看到旋转和扫视之间的区别。 效果最佳整页。 ( 小提琴在或见下文。)

 var c = document.getElementById('c'); var n = c.getContext('2d'); // View matrix, defines where you're looking var viewMtx = mat4.create(); // Projection matrix, defines how the view maps onto the screen var projMtx = mat4.create(); // Adapted from http://stackoverflow.com/questions/18404890/how-to-build-perspective-projection-matrix-no-api function ComputeProjMtx(field_of_view, aspect_ratio, near_dist, far_dist, left_handed) { // We'll assume input parameters are sane. field_of_view = field_of_view * Math.PI / 180.0; // Convert degrees to radians var frustum_depth = far_dist - near_dist; var one_over_depth = 1 / frustum_depth; var e11 = 1.0 / Math.tan(0.5 * field_of_view); var e00 = (left_handed ? 1 : -1) * e11 / aspect_ratio; var e22 = far_dist * one_over_depth; var e32 = (-far_dist * near_dist) * one_over_depth; return [ e00, 0, 0, 0, 0, e11, 0, 0, 0, 0, e22, e32, 0, 0, 1, 0 ]; } // Make a view matrix with a simple rotation about the Y axis (up-down axis) function ComputeViewMtx(angle) { angle = angle * Math.PI / 180.0; // Convert degrees to radians return [ Math.cos(angle), 0, Math.sin(angle), 0, 0, 1, 0, 0, -Math.sin(angle), 0, Math.cos(angle), 0, 0, 0, -250, 1 ]; } projMtx = ComputeProjMtx(70, c.width / c.height, 1, 200, true); var angle = 0; var viewProjMtx = mat4.create(); var minDist = 100; var maxDist = 1000; function Star() { var d = 0; do { // Create random points in a cube.. but not too close. this.x = Math.random() * maxDist - (maxDist / 2); this.y = Math.random() * maxDist - (maxDist / 2); this.z = Math.random() * maxDist - (maxDist / 2); var d = this.x * this.x + this.y * this.y + this.z * this.z; } while ( d > maxDist * maxDist / 4 || d < minDist * minDist ); this.dist = 100; } Star.prototype.AsVector = function() { return [this.x, this.y, this.z, 1]; } var stars = []; for (var i = 0; i < 5000; i++) stars.push(new Star()); var lastLoop = Date.now(); var dir = { up: 0, down: 1, left: 2, right: 3 }; var dirStates = [false, false, false, false]; var shiftKey = false; var moveSpeed = 100.0; var turnSpeed = 1.0; function loop() { var now = Date.now(); var dt = (now - lastLoop) / 1000.0; lastLoop = now; angle += 30.0 * dt; //viewMtx = ComputeViewMtx(angle); var tf = mat4.create(); if (dirStates[dir.up]) mat4.translate(tf, tf, [0, 0, moveSpeed * dt]); if (dirStates[dir.down]) mat4.translate(tf, tf, [0, 0, -moveSpeed * dt]); if (dirStates[dir.left]) if (shiftKey) mat4.rotate(tf, tf, -turnSpeed * dt, [0, 1, 0]); else mat4.translate(tf, tf, [moveSpeed * dt, 0, 0]); if (dirStates[dir.right]) if (shiftKey) mat4.rotate(tf, tf, turnSpeed * dt, [0, 1, 0]); else mat4.translate(tf, tf, [-moveSpeed * dt, 0, 0]); mat4.multiply(viewMtx, tf, viewMtx); //console.log('---'); //console.log(projMtx); //console.log(viewMtx); mat4.multiply(viewProjMtx, projMtx, viewMtx); //console.log(viewProjMtx); n.beginPath(); n.rect(0, 0, c.width, c.height); n.closePath(); n.fillStyle = '#000'; n.fill(); n.fillStyle = '#fff'; var v = vec4.create(); for (var i = 0; i < stars.length; i++) { var star = stars[i]; vec4.transformMat4(v, star.AsVector(), viewProjMtx); if (v[3] < 0) continue; var d = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); v[0] /= v[3]; v[1] /= v[3]; v[2] /= v[3]; //v[3] /= v[3]; var x = (v[0] * 0.5 + 0.5) * c.width; var y = (v[1] * 0.5 + 0.5) * c.height; // Compute a visual size... // This assumes all stars are the same size. // It also doesn't scale with canvas size well -- we'd have to take more into account. var s = 300 / d; n.beginPath(); n.arc(x, y, s, 0, Math.PI * 2); //n.rect(x, y, s, s); n.closePath(); n.fill(); } window.requestAnimationFrame(loop); } loop(); function keyToDir(evt) { var d = -1; if (evt.keyCode === 38) d = dir.up else if (evt.keyCode === 37) d = dir.left; else if (evt.keyCode === 39) d = dir.right; else if (evt.keyCode === 40) d = dir.down; return d; } window.onkeydown = function(evt) { var d = keyToDir(evt); if (d >= 0) dirStates[d] = true; if (evt.keyCode === 16) shiftKey = true; } window.onkeyup = function(evt) { var d = keyToDir(evt); if (d >= 0) dirStates[d] = false; if (evt.keyCode === 16) shiftKey = false; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.1/gl-matrix-min.js"></script> <div>Click in this pane. Use up/down/left/right, hold shift + left/right to rotate.</div> <canvas id="c" width="500" height="500"></canvas> 

更新2

Alain Jacomet Forte问道:

您建议的创建通用3d的方法是什么?如果您建议在矩阵级别工作,特别是对于此特定方案。

关于矩阵:如果你在任何平台上从头编写引擎,那么你不可避免地会最终使用矩阵,因为它们有助于推广基本的3D数学。 即使您使用OpenGL / WebGL或Direct3D,您仍然会最终制作视图和投影矩阵以及其他矩阵以用于更复杂的目的。 (处理法线贴图,对齐世界物体,剥皮等......)

关于创建通用3D的方法...不要。 它运行缓慢,没有大量工作就不会有效。 依靠硬件加速库来完成繁重的工作。 为特定项目创建有限的3D引擎既有趣又有启发性(例如,我希望在我的网页上有一个很酷的动画),但是当要将像素放在屏幕上以备任何严重的事情时,您希望硬件尽可能多地处理表现目的。

可悲的是,网络还没有很好的标准,但它正在WebGL中 - 学习WebGL,使用WebGL。 它运行良好,并且在支持时运行良好。 (但是,你可以侥幸逃脱。)

如果您正在进行桌面编程,我强烈推荐通过SDL进行OpenGL(我还没有在SFML上销售) - 它是跨平台的并得到很好的支持。

如果您正在编程移动电话,OpenGL ES几乎是您唯一的选择(除了狗慢速软件渲染器)。

如果你想完成任务而不是从头开始编写你自己的引擎,那么网络的事实就是Three.js(我发现它有效但平庸)。 如果你想要一个完整的游戏引擎,那么现在有一些免费选项,主要的商业选择是Unity和Unreal。 已经存在了很长时间 - 但从来没有机会使用它,但我听说它很好。

但是如果你想从头开始制作所有的3D东西......我总是发现Quake中的软件渲染器是如何做成一个非常好的案例研究。 其中一些可以在找到。

你正在重置每一帧的星星2d位置,然后移动星星(取决于每颗星的时间和速度) - 这是实现目标的一种不好的方法。 正如您所发现的,当您尝试将此解决方案扩展到更多方案时,它会变得非常复杂。

更好的方法是仅将星星3d位置设置一次(初始化时),然后每帧移动一个“相机”(取决于时间)。 当您想要渲染2D图像时,您可以计算屏幕上的星星位置。 屏幕上的位置取决于星星3d位置和当前摄像机位置。 这将允许您移动相机(在任何方向),旋转相机(任何角度)并呈现正确的星星位置并保持理智。

  相关解决方案