Skip to content

Commit 4fd1740

Browse files
MongooseSongabernier
authored andcommitted
manual: translate billboards into chinese (mrdoob#23878)
1 parent 1f6e577 commit 4fd1740

File tree

4 files changed

+323
-38
lines changed

4 files changed

+323
-38
lines changed

manual/examples/postprocessing-gui.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
const gui = new GUI();
118118
{
119119
const folder = gui.addFolder('BloomPass');
120-
folder.add(bloomPass.copyUniforms.opacity, 'value', 0, 2).name('strength');
120+
folder.add(bloomPass.combineUniforms.strength, 'value', 0, 2).name('strength');
121121
folder.open();
122122
}
123123
{

manual/list.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@
345345
"对齐HTML元素和3D对象": "zh/align-html-elements-to-3d",
346346
"Using Indexed Textures for Picking and Color": "zh/indexed-textures",
347347
"Using A Canvas for Dynamic Textures": "zh/canvas-textures",
348-
"Billboards and Facades": "zh/billboards",
348+
"广告牌(Billboards)": "zh/billboards",
349349
"释放资源": "zh/cleanup",
350350
"Making Voxel Geometry (Minecraft)": "zh/voxel-geometry",
351351
"Start making a Game": "zh/game"

manual/zh/billboards.html

Lines changed: 316 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,332 @@
1-
<!DOCTYPE html><html lang="zh"><head>
2-
<meta charset="utf-8">
3-
<title>Billboards</title>
4-
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
5-
<meta name="twitter:card" content="summary_large_image">
6-
<meta name="twitter:site" content="@threejs">
7-
<meta name="twitter:title" content="Three.js – Billboards">
8-
<meta property="og:image" content="https://threejs.org/files/share.png">
9-
<link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
10-
<link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
11-
12-
<link rel="stylesheet" href="/manual/resources/lesson.css">
13-
<link rel="stylesheet" href="/manual/resources/lang.css">
14-
<!-- Import maps polyfill -->
15-
<!-- Remove this when import maps will be widely supported -->
16-
<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
17-
18-
<script type="importmap">
1+
<!DOCTYPE html>
2+
<html lang="zh">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<title>广告牌(Billboards)</title>
7+
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
8+
<meta name="twitter:card" content="summary_large_image">
9+
<meta name="twitter:site" content="@threejs">
10+
<meta name="twitter:title" content="Three.js – Billboards">
11+
<meta property="og:image" content="https://threejs.org/files/share.png">
12+
<link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
13+
<link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
14+
15+
<link rel="stylesheet" href="/manual/resources/lesson.css">
16+
<link rel="stylesheet" href="/manual/resources/lang.css">
17+
<!-- Import maps polyfill -->
18+
<!-- Remove this when import maps will be widely supported -->
19+
<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
20+
21+
<script type="importmap">
1922
{
2023
"imports": {
2124
"three": "../../build/three.module.js"
2225
}
2326
}
2427
</script>
25-
<link rel="stylesheet" href="/manual/zh/lang.css">
26-
</head>
27-
<body>
28-
<div class="container">
29-
<div class="lesson-title">
30-
<h1>Billboards</h1>
31-
</div>
32-
<div class="lesson">
33-
<div class="lesson-main">
34-
<p>抱歉,还没有中文翻译哦。 <a href="https://github.com/mrdoob/three.js">欢迎加入翻译</a>! 😄</p>
35-
<p><a href="/manual/en/billboards.html">英文原文链接</a>.</p>
28+
<link rel="stylesheet" href="/manual/zh/lang.css">
29+
</head>
30+
31+
<body>
32+
<div class="container">
33+
<div class="lesson-title">
34+
<h1>广告牌(Billboards)</h1>
35+
</div>
36+
<div class="lesson">
37+
<div class="lesson-main">
38+
<p><a href="canvas-textures.html">上一篇文章</a> 我们使用了一个 <a href="/docs/#api/en/textures/CanvasTexture"><code
39+
class="notranslate" translate="no">CanvasTexture</code></a>
40+
在人物上创作标签(Labels)和徽标(Badges)。有时我们想制作一些总是面对相机的东西。Three.js提供了 <a href="/docs/#api/en/objects/Sprite"><code
41+
class="notranslate" translate="no">Sprite</code></a>
42+
<a href="/docs/#api/en/materials/SpriteMaterial"><code class="notranslate"
43+
translate="no">SpriteMaterial</code></a> 来实现这个功能。
44+
</p>
45+
46+
47+
<p>我们修改这个徽标的例子 <a href="canvas-textures.html">使用Canvas作为纹理</a>
48+
应用 <a href="/docs/#api/en/objects/Sprite"><code class="notranslate" translate="no">Sprite</code></a> and
49+
<a href="/docs/#api/en/materials/SpriteMaterial"><code class="notranslate"
50+
translate="no">SpriteMaterial</code></a>
51+
</p>
52+
53+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makePerson(x, labelWidth, size, name, color) {
54+
const canvas = makeLabelCanvas(labelWidth, size, name);
55+
const texture = new THREE.CanvasTexture(canvas);
56+
// 因为我们的Canvas的尺寸可能不是2的N次方
57+
// 在两个维度上适当地设置filter属性
58+
texture.minFilter = THREE.LinearFilter;
59+
texture.wrapS = THREE.ClampToEdgeWrapping;
60+
texture.wrapT = THREE.ClampToEdgeWrapping;
61+
62+
- const labelMaterial = new THREE.MeshBasicMaterial({
63+
+ const labelMaterial = new THREE.SpriteMaterial({
64+
map: texture,
65+
- side: THREE.DoubleSide,
66+
transparent: true,
67+
});
68+
69+
const root = new THREE.Object3D();
70+
root.position.x = x;
3671

72+
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
73+
root.add(body);
74+
body.position.y = bodyHeight / 2;
75+
76+
const head = new THREE.Mesh(headGeometry, bodyMaterial);
77+
root.add(head);
78+
head.position.y = bodyHeight + headRadius * 1.1;
79+
80+
- const label = new THREE.Mesh(labelGeometry, labelMaterial);
81+
+ const label = new THREE.Sprite(labelMaterial);
82+
root.add(label);
83+
label.position.y = bodyHeight * 4 / 5;
84+
label.position.z = bodyRadiusTop * 1.01;</pre>
85+
<p>现在标签始终是面向相机了。</p>
86+
<p></p>
87+
<div translate="no" class="threejs_example_container notranslate">
88+
<div><iframe class="threejs_example notranslate" translate="no" style=" "
89+
src="/manual/examples/resources/editor.html?url=/manual/examples/billboard-labels-w-sprites.html"></iframe>
90+
</div>
91+
<a class="threejs_center" href="/manual/examples/billboard-labels-w-sprites.html" target="_blank">点击在新窗口打开</a>
92+
</div>
93+
<p></p>
94+
<p>一个问题是,从某些角度来看的话,标签与人物重合了。 </p>
95+
<div class="threejs_center"><img src="../resources/images/billboard-label-z-issue.png" style="width: 455px;">
3796
</div>
97+
<p>我们可以通过移动标签的位置来解决此问题。</p>
98+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
99+
+// 如果单位是米,这里就用0.01
100+
+// 也就是以厘米作为标签的单位
101+
+const labelBaseScale = 0.01;
102+
const label = new THREE.Sprite(labelMaterial);
103+
root.add(label);
104+
-label.position.y = bodyHeight * 4 / 5;
105+
-label.position.z = bodyRadiusTop * 1.01;
106+
+label.position.y = head.position.y + headRadius + size * labelBaseScale;
107+
108+
-// 如果单位是米,这里就用0.01
109+
-// 也就是以厘米作为标签的单位
110+
-const labelBaseScale = 0.01;
111+
label.scale.x = canvas.width * labelBaseScale;
112+
label.scale.y = canvas.height * labelBaseScale;</pre>
113+
<p></p>
114+
<div translate="no" class="threejs_example_container notranslate">
115+
<div><iframe class="threejs_example notranslate" translate="no" style=" "
116+
src="/manual/examples/resources/editor.html?url=/manual/examples/billboard-labels-w-sprites-adjust-height.html"></iframe>
117+
</div>
118+
<a class="threejs_center" href="/manual/examples/billboard-labels-w-sprites-adjust-height.html"
119+
target="_blank">点击在新窗口打开</a>
120+
</div>
121+
<p></p>
122+
<p>我们可以用Billboard做的另一件事是绘制立面(Facades)。</p>
123+
<p>我们不绘制 3D 对象,而是使用图片绘制 2D 平面化的 3D 对象,这通常比绘制 3D 对象要快。</p>
124+
<p>例如,我们用树木网络制作一个场景,我们让每一棵树的底部是圆柱体,顶部是圆锥体。</p>
125+
<p>第一步,我们创建圆锥体和圆柱体的Geometry和Material,所有的树都会复用这些。</p>
126+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
127+
const trunkRadius = .2;
128+
const trunkHeight = 1;
129+
const trunkRadialSegments = 12;
130+
const trunkGeometry = new THREE.CylinderGeometry(
131+
trunkRadius, trunkRadius, trunkHeight, trunkRadialSegments);
132+
133+
const topRadius = trunkRadius * 4;
134+
const topHeight = trunkHeight * 2;
135+
const topSegments = 12;
136+
const topGeometry = new THREE.ConeGeometry(
137+
topRadius, topHeight, topSegments);
138+
139+
const trunkMaterial = new THREE.MeshPhongMaterial({color: 'brown'});
140+
const topMaterial = new THREE.MeshPhongMaterial({color: 'green'});</pre>
141+
<p>然后我们创建一个函数,对每一棵树的树干和树顶创建一个 <a href="/docs/#api/en/objects/Mesh"><code class="notranslate"
142+
translate="no">Mesh</code></a>
143+
,并把它们都加入到一个 <a href="/docs/#api/en/core/Object3D"><code class="notranslate"
144+
translate="no">Object3D</code></a>对象下。</p>
145+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
146+
function makeTree(x, z) {
147+
const root = new THREE.Object3D();
148+
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
149+
trunk.position.y = trunkHeight / 2;
150+
root.add(trunk);
151+
152+
const top = new THREE.Mesh(topGeometry, topMaterial);
153+
top.position.y = trunkHeight + topHeight / 2;
154+
root.add(top);
155+
156+
root.position.set(x, 0, z);
157+
scene.add(root);
158+
159+
return root;
160+
}</pre>
161+
<p>然后我们会创建一个循环,生成树网络。</p>
162+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
163+
for (let z = -50; z &lt;= 50; z += 10) {
164+
for (let x = -50; x &lt;= 50; x += 10) {
165+
makeTree(x, z);
166+
}
167+
}</pre>
168+
<p>让我们再增加一个地平面。</p>
169+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
170+
// 添加地面
171+
{
172+
const size = 400;
173+
const geometry = new THREE.PlaneGeometry(size, size);
174+
const material = new THREE.MeshPhongMaterial({color: 'gray'});
175+
const mesh = new THREE.Mesh(geometry, material);
176+
mesh.rotation.x = Math.PI * -0.5;
177+
scene.add(mesh);
178+
}</pre>
179+
<p>然后把背景调整为浅蓝(lightblue)</p>
180+
181+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
182+
const scene = new THREE.Scene();
183+
-scene.background = new THREE.Color('white');
184+
+scene.background = new THREE.Color('lightblue');</pre>
185+
<p>我们得到了一个树木网络</p>
186+
<p></p>
187+
<div translate="no" class="threejs_example_container notranslate">
188+
<div><iframe class="threejs_example notranslate" translate="no" style=" "
189+
src="/manual/examples/resources/editor.html?url=/manual/examples/billboard-trees-no-billboards.html"></iframe>
190+
</div>
191+
<a class="threejs_center" href="/manual/examples/billboard-trees-no-billboards.html"
192+
target="_blank">点击在新窗口打开</a>
193+
</div>
194+
<p></p>
195+
<p>这里有11x11或者121棵树,每棵树由12个多边形组成的锥体 +
196+
48个多边形组成的树干组成,所以每棵树包含60个多边形。121*60的结果是7260,这并不是很多,当然更精细的3D树可能有1000-3000个多边形构成。如果3000个多边形构成的树,那么121棵树将包含363000个多边形。
197+
</p>
198+
<p>使用Facades,可以降低这个数字。</p>
199+
<p>我们可以在一些绘图应用中手动创建一个Facade,现在让我们编写一些代码来手动生成一个。</p>
200+
<p>现在写一些代码把对象绘制到纹理中,使用一个 <code class="notranslate" translate="no">RenderTarget</code>,我们提到过使用
201+
<code class="notranslate" translate="no">RenderTarget</code>
202+
来渲染,具体在这篇 <a href="rendertargets.html">渲染目标</a> 文章里。
203+
</p>
204+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
205+
function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
206+
const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
207+
const halfFovY = THREE.MathUtils.degToRad(camera.fov * .5);
208+
const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
209+
210+
camera.position.copy(boxCenter);
211+
camera.position.z += distance;
212+
213+
// 为视锥体选择合适的near和far值
214+
// 可以把盒模型包裹进来
215+
camera.near = boxSize / 100;
216+
camera.far = boxSize * 100;
217+
218+
camera.updateProjectionMatrix();
219+
}
220+
221+
function makeSpriteTexture(textureSize, obj) {
222+
const rt = new THREE.WebGLRenderTarget(textureSize, textureSize);
223+
224+
const aspect = 1; // 因为Render Target是正方形
225+
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
226+
227+
scene.add(obj);
228+
229+
// 计算对象的盒模型
230+
const box = new THREE.Box3().setFromObject(obj);
231+
232+
const boxSize = box.getSize(new THREE.Vector3());
233+
const boxCenter = box.getCenter(new THREE.Vector3());
234+
235+
// 设置相机去构建盒模型
236+
const fudge = 1.1;
237+
const size = Math.max(...boxSize.toArray()) * fudge;
238+
frameArea(size, size, boxCenter, camera);
239+
240+
renderer.autoClear = false;
241+
renderer.setRenderTarget(rt);
242+
renderer.render(scene, camera);
243+
renderer.setRenderTarget(null);
244+
renderer.autoClear = true;
245+
246+
scene.remove(obj);
247+
248+
return {
249+
position: boxCenter.multiplyScalar(fudge),
250+
scale: size,
251+
texture: rt.texture,
252+
};
253+
}</pre>
254+
<p>关于上面代码的一些注意事项:</p>
255+
<p>我们使用之前代码定义好的视图的 (<code class="notranslate" translate="no">fov</code>) 属性,
256+
</p>
257+
<p>我们计算一个包含树的盒模型,这和 <a href="load-obj.html">加载.obj的文件</a> 中提到的方式一致,有一点微小的改变。</p>
258+
<p>我们再次调用 <code class="notranslate" translate="no">frameArea</code>,稍微改写了一下<a
259+
href="load-obj.html">加载.obj的文件</a>
260+
在这种情况下,我们计算相机需要离物体多远,从而让它的视野以包含对象。然后我们将相机的-z值设置为从对象盒模型的中心到此的距离。</p>
261+
<p>我们将想要适应的大小乘以1.1倍(<code class="notranslate" translate="no">fudge</code>)
262+
来确保树完全在渲染目标中。这是因为我们用来计算对象是否适合相机的视口的尺寸,没有考虑到对象的边缘可能会超出我们的可视区域之外。我们可以计算出如何让盒子100%合适,但这会浪费很多空间,所以我们就是蒙混
263+
<em>(fudge)</em> 一下。
264+
</p>
265+
<p>然后我们渲染到RenderTarget中,然后从场景中移除此对象。 </p>
266+
<p>重点是需要场景中的灯光,但我们需要确保场景中没有其他东西。</p>
267+
<p>我们也不能给场景设置背景色。</p>
268+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
269+
const scene = new THREE.Scene();
270+
-scene.background = new THREE.Color('lightblue');</pre>
271+
<p>最后我们返回了纹理,位置和缩放比例,我们需要创建Facade,让它看起来在同一个地方。</p>
272+
<p>然后我们制作一棵树,调用此代码:</p>
273+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
274+
// 创建Billboard纹理
275+
const tree = makeTree(0, 0);
276+
const facadeSize = 64;
277+
const treeSpriteInfo = makeSpriteTexture(facadeSize, tree);</pre>
278+
<p>然后我们可以制作一个Facade网络,而不是树网络。</p>
279+
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">
280+
+function makeSprite(spriteInfo, x, z) {
281+
+ const {texture, offset, scale} = spriteInfo;
282+
+ const mat = new THREE.SpriteMaterial({
283+
+ map: texture,
284+
+ transparent: true,
285+
+ });
286+
+ const sprite = new THREE.Sprite(mat);
287+
+ scene.add(sprite);
288+
+ sprite.position.set(
289+
+ offset.x + x,
290+
+ offset.y,
291+
+ offset.z + z);
292+
+ sprite.scale.set(scale, scale, scale);
293+
+}
294+
295+
for (let z = -50; z &lt;= 50; z += 10) {
296+
for (let x = -50; x &lt;= 50; x += 10) {
297+
- makeTree(x, z);
298+
+ makeSprite(treeSpriteInfo, x, z);
299+
}
300+
}</pre>
301+
<p>在上面的代码中,我们应用了定位Facade所需的偏移量和缩放比例,因此他会出现在和原树同一个地方。</p>
302+
<p>现在我们已经完成了Facade纹理的制作,我们可以再次设置背景。</p>
303+
<pre class="prettyprint showlinemods notranslate lang-js"
304+
translate="no">scene.background = new THREE.Color('lightblue');</pre>
305+
<p>现在我们得到了一个全是树Facades的场景。</p>
306+
<p></p>
307+
<div translate="no" class="threejs_example_container notranslate">
308+
<div><iframe class="threejs_example notranslate" translate="no" style=" "
309+
src="/manual/examples/resources/editor.html?url=/manual/examples/billboard-trees-static-billboards.html"></iframe>
310+
</div>
311+
<a class="threejs_center" href="/manual/examples/billboard-trees-static-billboards.html"
312+
target="_blank">点击在新窗口打开</a>
313+
</div>
314+
<p></p>
315+
<p>
316+
与上面的树模型相比,它们看起来非常相似。我们使用了低分辨率纹理,只有64x64像素,所以Facade是块状的,你当然可以提高分辨率。通常Facade只会用在非常远处的物体,因为当它们非常小的时候,低分辨率纹理就足够了。它节省了绘制远处只有几个像素的精致树模型时间。
317+
</p>
318+
<p>另一个问题是我们只能从一侧查看树。这往往是通过渲染更多的Facade来解决,比如绘制对象周围的8个方向,然后根据实际相机的方向来设置要展示的Facade。</p>
319+
<p>是否使用Facade由你决定,如果你决定去使用它们,希望这篇文章给了你一些想法和解决方案。</p>
38320
</div>
39321
</div>
40-
322+
</div>
323+
41324
<script src="/manual/resources/prettify.js"></script>
42325
<script src="/manual/resources/lesson.js"></script>
43326

44327

45328

46329

47-
</body></html>
330+
</body>
331+
332+
</html>

0 commit comments

Comments
 (0)