-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPerspectiveCamera.html
206 lines (192 loc) · 7.61 KB
/
PerspectiveCamera.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
<!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>Document</title>
<script src="three.min.js"></script>
<script src="gasp.min.js"></script>
<script src="babel.min.js"></script>
<script type="text/babel">
const imgUrl =
"https://pic3.zhimg.com/80/v2-d9592da1a7206f58c2307060e7246e95_1440w.jpg?source=1940ef5c";
const imgRatio = 1250 / 1000;
const targetCameraZ = 180;
const instanceSize = 1;
const randRangeZ = 2 * targetCameraZ * 0.99;
const initCamaraZ = targetCameraZ / 5;
const renderer = new THREE.WebGLRenderer({ alpha: true });
//创建渲染器对象, alpha表示canvas是否包含透明度, 默认是false;
const scene = new THREE.Scene(); //创建场景对象
const camera = new THREE.PerspectiveCamera(75, 2, 0.5, 1000);
//透视相机, 第一个参数是视野范围fov,第二个参数是画布的宽高比aspect, 默认是300*150px, 或者说2
//近平面和远平面
//摄像机默认指向Z轴负方向, 上方向朝向Y轴, 立方体放置在坐标原点
camera.position.set(0, 0, initCamaraZ);
function f(x, y, targetZ) {
//targetZ是一个随机数
const h = 0.5;
const d = targetCameraZ;
const D = -targetZ + d;
//距离d的位置
const H = (h / d) * D;
const s = H / h;
// D/d作为缩放系数
return { s, p: new THREE.Vector3(x * s, y * s, targetZ) };
}
const mesh = (() => {
const nRow = 256;
const nCol = (nRow * imgRatio) | 0;
const sz = instanceSize;
const geom = new THREE.BoxGeometry(sz, sz, sz).translate(
//移动几何体
//x, y, z上的宽度,高度和深度
0,
0,
-0.5 * sz
);
const mat = new THREE.MeshBasicMaterial();
//基础网格材质
const mesh = new THREE.InstancedMesh(geom, mat, nCol * nRow);
//网格模型来自哪个几何体,使用InstancedMesh来渲染大量具有相同几何体与材质,但具有不同世界变换的物体,可以减少draw call的数量, 提高程序的整体渲染性能
//nCol * cRow表示的是实例的数量;
for (let i = 0, c = 0; i < nRow; ++i) {
for (let j = 0; j < nCol; ++j) {
const { p, s } = f(
(j - nCol / 2 + 0.5) * sz,
(nRow / 2 - i + 0.5) * sz,
THREE.MathUtils.randFloatSpread(randRangeZ) * sz
//在区间[-range/2, range/2]之间随机选择一个浮点数
//targetZ是个随机数
);
const m = new THREE.Matrix4()
//生成一个四维单位变换矩阵
.setPosition(p)
//修改单位矩阵的位置向量
.multiply(new THREE.Matrix4().makeScale(s, s, s));
//进行缩放, 将当前矩阵乘以矩阵M
mesh.setMatrixAt(c, m);
//c表示的是实例的索引, m表示的是已定义的实例的本地变换矩阵
mesh.setColorAt(c, new THREE.Color("white"));
++c;
}
}
mesh.instanceMatrix.needsUpdate = true;
//表示当前attribute已经被修改过, 需要更新
mesh.instanceColor.needsUpdate = true;
return mesh;
})();
scene.add(mesh);
//给场景插入网格模型,将网格模型作为scene子对象的元素, 给几何体一个材质
{
const img = new Image();
img.onload = () => {
const { width, height } = img;
//取图片的值, 详细信息可以查询HTML中img标签的width和height的定义
const can = document.createElement("canvas");
can.height = 256;
can.width = (can.height * imgRatio) | 0;
const ctx = can.getContext("2d");
ctx.drawImage(img, 0, 0, width, height, 0, 0, can.width, can.height);
//void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
const { data } = ctx.getImageData(0, 0, can.width, can.height);
console.log(data.length);
//获取图像数据,放回一个ImageDate对象, 有三个属性, height, width和data
//ImageData.data是一个一维数组, 包含以RGBA顺序的数据, 数据使用0至255(包含)的整数表示
const c = new THREE.Color();
//默认为白色
const total = data.length >> 2;
//获取每一个像素
for (let i = 0; i < total; ++i) {
mesh.setColorAt(
i,
//实例的索引
c.setRGB(
data[i * 4 + 0] / 255,
//第i个像素(R)
data[i * 4 + 1] / 255,
data[i * 4 + 2] / 255
)
);
}
mesh.instanceColor.needsUpdate = true;
};
img.crossOrigin = "";
img.src = imgUrl;
}
// ----
// 渲染
// ----
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
//渲染器将场景和相机作为参数
//每个可用帧都会调用函数
});
// ----
// 视图
// ----
function resize(w, h, dpr = devicePixelRatio) {
renderer.setPixelRatio(dpr);
//设置设备像素比, 表示的是未缩放状态下, 物理像素和CSS像素的初始比例关系 设备像素比 = 物理像素/设备独立像素(CSS像素)
renderer.setSize(w, h, false);
//设置渲染区域尺寸
camera.aspect = w / h;
camera.updateProjectionMatrix();
//对大多数属性修改后, 需要调用这个函数进行更新
}
//渲染得到一个canvas对象
addEventListener("resize", () => resize(innerWidth, innerHeight));
dispatchEvent(new Event("resize"));
document.body.prepend(renderer.domElement);
//domElement由渲染器的构造函数自动创建(如果没有传入canvas参数), 这行代码的意思是将它加到页面中去;
// ----
// 滚动
// ----
function setCamPos() {
const H = document.documentElement.offsetHeight - window.innerHeight;
const r = window.scrollY / H;
//文档在垂直方向上已滚动的像素值/总共可滚动的像素值
const z = initCamaraZ + (targetCameraZ - initCamaraZ) * r;
//根据滚动, 按比例移动相机
gsap.killTweensOf(camera.position);
gsap.to(camera.position, { z }); //duration的默认值是0.5
}
document.addEventListener("scroll", setCamPos);
</script>
<style>
canvas {
position: fixed;
top: 0;
left: 0;
display: block;
width: 100%;
height: 100vh;
}
/* 网格对象可以理解为用一种特定的材质来绘制一个特定的几何体 */
:root {
scroll-behavior: smooth;
/* 实现页内跳转的平滑滚动 */
}
body {
background: black;
display: flex;
flex-flow: column;
background: #222;
}
article {
opacity: 0;
margin-block: 10vh;
}
article:last-of-type {
opacity: 0;
margin-block: 80vh;
}
</style>
</head>
<body>
<article></article>
<article></article>
<article></article>
</body>
</html>