gpt4 book ai didi

javascript - Canvas 旋转星空场

转载 作者:IT王子 更新时间:2023-10-29 03:22:37 27 4
gpt4 key购买 nike

我正在采用以下方法在屏幕上制作星空动画,但我被困在了下一部分。

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;
}

JSFiddle

它现在所做的是使用考虑到恒星的不透明度和大小的 delta X 为每颗恒星制作动画,因此最小的恒星看起来移动得更慢。

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

问题

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

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

居中圆:

center of vision in x,y

非居中:

shifted center

不同的视角:

different angle of vision

我真的不知道如何前进。我已经竭尽全力到达这里。你能帮我做一些第一步吗?

谢谢


更新

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

        // 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

JSFiddle

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


更新 2:

快到了。 ##Math freenode channel 的一位用户好心地建议了以下计算:

        // 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;

JSFiddle

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


更新 3

根据 Iftah 的回复,我能够使用 Sylvester将旋转矩阵应用于星星,需要先将其保存在数组中。现在还确定了每颗星的 z 坐标,并且半径 r 和不透明度 a 是从中导出的。该代码有很大的不同并且更长,所以我没有发布它,但这可能是朝着正确方向迈出的一步。我还不能让它连续旋转。就性能而言,对每一帧使用矩阵运算似乎代价高昂。

JSFiddle

最佳答案

这里有一些伪代码可以完成您所说的事情。

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 库来处理一些矩阵数学。这是好东西。 ( fiddle 是 here ,或见下文。)

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>

一些链接:

更新

这是另一个有键盘控制的版本。挺有趣的。您可以从扫射中看出旋转和视差之间的区别。整页效果最好。 ( fiddle 是 here 或见下文。)

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 问:

What is your recommended method of creating general purpose 3d and if you would recommend working at the matrices level or not, specifically perhaps to this particular scenario.

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

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

可悲的是,网络还没有很好的标准,但它正在 WebGL 中出现——学习 WebGL,使用 WebGL。它运行良好,在受支持时运行良好。 (然而,你可以逃脱很多 just using CSS 3D transforms and Javascript 。)

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

如果您正在为手机编程,OpenGL ES 几乎是您唯一的选择(而不是一个非常慢的软件渲染器)。

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

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

关于javascript - Canvas 旋转星空场,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31889513/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com