cameras.html 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. <!DOCTYPE html><html lang="en"><head>
  2. <meta charset="utf-8">
  3. <title>Cameras</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 – Cameras">
  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. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <script type="importmap">
  14. {
  15. "imports": {
  16. "three": "../../build/three.module.js"
  17. }
  18. }
  19. </script>
  20. </head>
  21. <body>
  22. <div class="container">
  23. <div class="lesson-title">
  24. <h1>Cameras</h1>
  25. </div>
  26. <div class="lesson">
  27. <div class="lesson-main">
  28. <p>This article is one in a series of articles about three.js.
  29. The first article was <a href="fundamentals.html">about fundamentals</a>.
  30. If you haven't read that yet you might want to start there.</p>
  31. <p>Let's talk about cameras in three.js. We covered some of this in the <a href="fundamentals.html">first article</a> but we'll cover it in more detail here.</p>
  32. <p>The most common camera in three.js and the one we've been using up to this point is
  33. the <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a>. It gives a 3d view where things in the distance appear
  34. smaller than things up close.</p>
  35. <p>The <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a> defines a <em>frustum</em>. <a href="https://en.wikipedia.org/wiki/Frustum">A <em>frustum</em> is a solid pyramid shape with
  36. the tip cut off</a>.
  37. By name of a solid I mean for example a cube, a cone, a sphere, a cylinder,
  38. and a frustum are all names of different kinds of solids.</p>
  39. <div class="spread">
  40. <div><div data-diagram="shapeCube"></div><div>cube</div></div>
  41. <div><div data-diagram="shapeCone"></div><div>cone</div></div>
  42. <div><div data-diagram="shapeSphere"></div><div>sphere</div></div>
  43. <div><div data-diagram="shapeCylinder"></div><div>cylinder</div></div>
  44. <div><div data-diagram="shapeFrustum"></div><div>frustum</div></div>
  45. </div>
  46. <p>I only point that out because I didn't know it for years. Some book or page would mention
  47. <em>frustum</em> and my eyes would glaze over. Understanding it's the name of a type of solid
  48. shape made those descriptions suddenly make more sense 😅</p>
  49. <p>A <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a> defines its frustum based on 4 properties. <code class="notranslate" translate="no">near</code> defines where the
  50. front of the frustum starts. <code class="notranslate" translate="no">far</code> defines where it ends. <code class="notranslate" translate="no">fov</code>, the field of view, defines
  51. how tall the front and back of the frustum are by computing the correct height to get
  52. the specified field of view at <code class="notranslate" translate="no">near</code> units from the camera. The <code class="notranslate" translate="no">aspect</code> defines how
  53. wide the front and back of the frustum are. The width of the frustum is just the height
  54. multiplied by the aspect.</p>
  55. <p><img src="../resources/frustum-3d.svg" width="500" class="threejs_center"></p>
  56. <p>Let's use the scene from <a href="lights.html">the previous article</a> that has a ground
  57. plane, a sphere, and a cube and make it so we can adjust the camera's settings.</p>
  58. <p>To do that we'll make a <code class="notranslate" translate="no">MinMaxGUIHelper</code> for the <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> settings so <code class="notranslate" translate="no">far</code>
  59. is always greater than <code class="notranslate" translate="no">near</code>. It will have <code class="notranslate" translate="no">min</code> and <code class="notranslate" translate="no">max</code> properties that lil-gui
  60. will adjust. When adjusted they'll set the 2 properties we specify.</p>
  61. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class MinMaxGUIHelper {
  62. constructor(obj, minProp, maxProp, minDif) {
  63. this.obj = obj;
  64. this.minProp = minProp;
  65. this.maxProp = maxProp;
  66. this.minDif = minDif;
  67. }
  68. get min() {
  69. return this.obj[this.minProp];
  70. }
  71. set min(v) {
  72. this.obj[this.minProp] = v;
  73. this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif);
  74. }
  75. get max() {
  76. return this.obj[this.maxProp];
  77. }
  78. set max(v) {
  79. this.obj[this.maxProp] = v;
  80. this.min = this.min; // this will call the min setter
  81. }
  82. }
  83. </pre>
  84. <p>Now we can setup our GUI like this</p>
  85. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function updateCamera() {
  86. camera.updateProjectionMatrix();
  87. }
  88. const gui = new GUI();
  89. gui.add(camera, 'fov', 1, 180).onChange(updateCamera);
  90. const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
  91. gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
  92. gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far').onChange(updateCamera);
  93. </pre>
  94. <p>Anytime the camera's settings change we need to call the camera's
  95. <a href="/docs/#api/en/cameras/PerspectiveCamera#updateProjectionMatrix"><code class="notranslate" translate="no">updateProjectionMatrix</code></a> function
  96. so we made a function called <code class="notranslate" translate="no">updateCamera</code> add passed it to lil-gui to call it when things change.</p>
  97. <p></p><div translate="no" class="threejs_example_container notranslate">
  98. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-perspective.html"></iframe></div>
  99. <a class="threejs_center" href="/manual/examples/cameras-perspective.html" target="_blank">click here to open in a separate window</a>
  100. </div>
  101. <p></p>
  102. <p>You can adjust the values and see how they work. Note we didn't make <code class="notranslate" translate="no">aspect</code> settable since
  103. it's taken from the size of the window so if you want to adjust the aspect open the example
  104. in a new window and then size the window.</p>
  105. <p>Still, I think it's a little hard to see so let's change the example so it has 2 cameras.
  106. One will show our scene as we see it above, the other will show another camera looking at the
  107. scene the first camera is drawing and showing that camera's frustum.</p>
  108. <p>To do this we can use the scissor function of three.js.
  109. Let's change it to draw 2 scenes with 2 cameras side by side using the scissor function</p>
  110. <p>First off let's use some HTML and CSS to define 2 side by side elements. This will also
  111. help us with events so both cameras can easily have their own <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>.</p>
  112. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
  113. &lt;canvas id="c"&gt;&lt;/canvas&gt;
  114. + &lt;div class="split"&gt;
  115. + &lt;div id="view1" tabindex="1"&gt;&lt;/div&gt;
  116. + &lt;div id="view2" tabindex="2"&gt;&lt;/div&gt;
  117. + &lt;/div&gt;
  118. &lt;/body&gt;
  119. </pre>
  120. <p>And the CSS that will make those 2 views show up side by side overlaid on top of
  121. the canvas</p>
  122. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">.split {
  123. position: absolute;
  124. left: 0;
  125. top: 0;
  126. width: 100%;
  127. height: 100%;
  128. display: flex;
  129. }
  130. .split&gt;div {
  131. width: 100%;
  132. height: 100%;
  133. }
  134. </pre>
  135. <p>Then in our code we'll add a <a href="/docs/#api/en/helpers/CameraHelper"><code class="notranslate" translate="no">CameraHelper</code></a>. A <a href="/docs/#api/en/helpers/CameraHelper"><code class="notranslate" translate="no">CameraHelper</code></a> draws the frustum for a <a href="/docs/#api/en/cameras/Camera"><code class="notranslate" translate="no">Camera</code></a></p>
  136. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cameraHelper = new THREE.CameraHelper(camera);
  137. ...
  138. scene.add(cameraHelper);
  139. </pre>
  140. <p>Now let's look up the 2 view elements.</p>
  141. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const view1Elem = document.querySelector('#view1');
  142. const view2Elem = document.querySelector('#view2');
  143. </pre>
  144. <p>And we'll set our existing <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> to respond to the first
  145. view element only.</p>
  146. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const controls = new OrbitControls(camera, canvas);
  147. +const controls = new OrbitControls(camera, view1Elem);
  148. </pre>
  149. <p>Let's make a second <a href="/docs/#api/en/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a> and a second <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>.
  150. The second <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> is tied to the second camera and gets input
  151. from the second view element.</p>
  152. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const camera2 = new THREE.PerspectiveCamera(
  153. 60, // fov
  154. 2, // aspect
  155. 0.1, // near
  156. 500, // far
  157. );
  158. camera2.position.set(40, 10, 30);
  159. camera2.lookAt(0, 5, 0);
  160. const controls2 = new OrbitControls(camera2, view2Elem);
  161. controls2.target.set(0, 5, 0);
  162. controls2.update();
  163. </pre>
  164. <p>Finally we need to render the scene from the point of view of each
  165. camera using the scissor function to only render to part of the canvas.</p>
  166. <p>Here is a function that given an element will compute the rectangle
  167. of that element that overlaps the canvas. It will then set the scissor
  168. and viewport to that rectangle and return the aspect for that size.</p>
  169. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function setScissorForElement(elem) {
  170. const canvasRect = canvas.getBoundingClientRect();
  171. const elemRect = elem.getBoundingClientRect();
  172. // compute a canvas relative rectangle
  173. const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left;
  174. const left = Math.max(0, elemRect.left - canvasRect.left);
  175. const bottom = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.top;
  176. const top = Math.max(0, elemRect.top - canvasRect.top);
  177. const width = Math.min(canvasRect.width, right - left);
  178. const height = Math.min(canvasRect.height, bottom - top);
  179. // setup the scissor to only render to that part of the canvas
  180. const positiveYUpBottom = canvasRect.height - bottom;
  181. renderer.setScissor(left, positiveYUpBottom, width, height);
  182. renderer.setViewport(left, positiveYUpBottom, width, height);
  183. // return the aspect
  184. return width / height;
  185. }
  186. </pre>
  187. <p>And now we can use that function to draw the scene twice in our <code class="notranslate" translate="no">render</code> function</p>
  188. <pre class="prettyprint showlinemods notranslate lang-js" translate="no"> function render() {
  189. - if (resizeRendererToDisplaySize(renderer)) {
  190. - const canvas = renderer.domElement;
  191. - camera.aspect = canvas.clientWidth / canvas.clientHeight;
  192. - camera.updateProjectionMatrix();
  193. - }
  194. + resizeRendererToDisplaySize(renderer);
  195. +
  196. + // turn on the scissor
  197. + renderer.setScissorTest(true);
  198. +
  199. + // render the original view
  200. + {
  201. + const aspect = setScissorForElement(view1Elem);
  202. +
  203. + // adjust the camera for this aspect
  204. + camera.aspect = aspect;
  205. + camera.updateProjectionMatrix();
  206. + cameraHelper.update();
  207. +
  208. + // don't draw the camera helper in the original view
  209. + cameraHelper.visible = false;
  210. +
  211. + scene.background.set(0x000000);
  212. +
  213. + // render
  214. + renderer.render(scene, camera);
  215. + }
  216. +
  217. + // render from the 2nd camera
  218. + {
  219. + const aspect = setScissorForElement(view2Elem);
  220. +
  221. + // adjust the camera for this aspect
  222. + camera2.aspect = aspect;
  223. + camera2.updateProjectionMatrix();
  224. +
  225. + // draw the camera helper in the 2nd view
  226. + cameraHelper.visible = true;
  227. +
  228. + scene.background.set(0x000040);
  229. +
  230. + renderer.render(scene, camera2);
  231. + }
  232. - renderer.render(scene, camera);
  233. requestAnimationFrame(render);
  234. }
  235. requestAnimationFrame(render);
  236. }
  237. </pre>
  238. <p>The code above sets the background color of the scene when rendering the
  239. second view to dark blue just to make it easier to distinguish the two views.</p>
  240. <p>We can also remove our <code class="notranslate" translate="no">updateCamera</code> code since we're updating everything
  241. in the <code class="notranslate" translate="no">render</code> function.</p>
  242. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function updateCamera() {
  243. - camera.updateProjectionMatrix();
  244. -}
  245. const gui = new GUI();
  246. -gui.add(camera, 'fov', 1, 180).onChange(updateCamera);
  247. +gui.add(camera, 'fov', 1, 180);
  248. const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
  249. -gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
  250. -gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far').onChange(updateCamera);
  251. +gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near');
  252. +gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far');
  253. </pre>
  254. <p>And now you can use one view to see the frustum of the other.</p>
  255. <p></p><div translate="no" class="threejs_example_container notranslate">
  256. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-perspective-2-scenes.html"></iframe></div>
  257. <a class="threejs_center" href="/manual/examples/cameras-perspective-2-scenes.html" target="_blank">click here to open in a separate window</a>
  258. </div>
  259. <p></p>
  260. <p>On the left you can see the original view and on the right you can
  261. see a view showing the frustum of the camera on the left. As you adjust
  262. <code class="notranslate" translate="no">near</code>, <code class="notranslate" translate="no">far</code>, <code class="notranslate" translate="no">fov</code> and move the camera with mouse you can see that
  263. only what's inside the frustum shown on the right appears in the scene on
  264. the left.</p>
  265. <p>Adjust <code class="notranslate" translate="no">near</code> up to around 20 and you'll easily see the front of objects
  266. disappear as they are no longer in the frustum. Adjust <code class="notranslate" translate="no">far</code> below about 35
  267. and you'll start to see the ground plane disappear as it's no longer in
  268. the frustum.</p>
  269. <p>This brings up the question, why not just set <code class="notranslate" translate="no">near</code> to 0.0000000001 and <code class="notranslate" translate="no">far</code>
  270. to 10000000000000 or something like that so you can just see everything?
  271. The reason is your GPU only has so much precision to decide if something
  272. is in front or behind something else. That precision is spread out between
  273. <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code>. Worse, by default the precision close the camera is detailed
  274. and the precision far from the camera is coarse. The units start with <code class="notranslate" translate="no">near</code>
  275. and slowly expand as they approach <code class="notranslate" translate="no">far</code>.</p>
  276. <p>Starting with the top example, let's change the code to insert 20 spheres in a
  277. row.</p>
  278. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  279. const sphereRadius = 3;
  280. const sphereWidthDivisions = 32;
  281. const sphereHeightDivisions = 16;
  282. const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
  283. const numSpheres = 20;
  284. for (let i = 0; i &lt; numSpheres; ++i) {
  285. const sphereMat = new THREE.MeshPhongMaterial();
  286. sphereMat.color.setHSL(i * .73, 1, 0.5);
  287. const mesh = new THREE.Mesh(sphereGeo, sphereMat);
  288. mesh.position.set(-sphereRadius - 1, sphereRadius + 2, i * sphereRadius * -2.2);
  289. scene.add(mesh);
  290. }
  291. }
  292. </pre>
  293. <p>and let's set <code class="notranslate" translate="no">near</code> to 0.00001</p>
  294. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 45;
  295. const aspect = 2; // the canvas default
  296. -const near = 0.1;
  297. +const near = 0.00001;
  298. const far = 100;
  299. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  300. </pre>
  301. <p>We also need to tweak the GUI code a little to allow 0.00001 if the value is edited</p>
  302. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
  303. +gui.add(minMaxGUIHelper, 'min', 0.00001, 50, 0.00001).name('near').onChange(updateCamera);
  304. </pre>
  305. <p>What do you think will happen?</p>
  306. <p></p><div translate="no" class="threejs_example_container notranslate">
  307. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-z-fighting.html"></iframe></div>
  308. <a class="threejs_center" href="/manual/examples/cameras-z-fighting.html" target="_blank">click here to open in a separate window</a>
  309. </div>
  310. <p></p>
  311. <p>This is an example of <em>z fighting</em> where the GPU on your computer does not have
  312. enough precision to decide which pixels are in front and which pixels are behind.</p>
  313. <p>Just in case the issue doesn't show on your machine here's what I see on mine</p>
  314. <div class="threejs_center"><img src="../resources/images/z-fighting.png" style="width: 570px;"></div>
  315. <p>One solution is to tell three.js use to a different method to compute which
  316. pixels are in front and which are behind. We can do that by enabling
  317. <code class="notranslate" translate="no">logarithmicDepthBuffer</code> when we create the <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a></p>
  318. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
  319. +const renderer = new THREE.WebGLRenderer({
  320. + antialias: true,
  321. + canvas,
  322. + logarithmicDepthBuffer: true,
  323. +});
  324. </pre>
  325. <p>and with that it might work</p>
  326. <p></p><div translate="no" class="threejs_example_container notranslate">
  327. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-logarithmic-depth-buffer.html"></iframe></div>
  328. <a class="threejs_center" href="/manual/examples/cameras-logarithmic-depth-buffer.html" target="_blank">click here to open in a separate window</a>
  329. </div>
  330. <p></p>
  331. <p>If this didn't fix the issue for you then you've run into one reason why
  332. you can't always use this solution. That reason is because only certain GPUs
  333. support it. As of September 2018 almost no mobile devices support this
  334. solution whereas most desktops do.</p>
  335. <p>Another reason not to choose this solution is it can be significantly slower
  336. than the standard solution.</p>
  337. <p>Even with this solution there is still limited resolution. Make <code class="notranslate" translate="no">near</code> even
  338. smaller or <code class="notranslate" translate="no">far</code> even bigger and you'll eventually run into the same issues.</p>
  339. <p>What that means is that you should always make an effort to choose a <code class="notranslate" translate="no">near</code>
  340. and <code class="notranslate" translate="no">far</code> setting that fits your use case. Set <code class="notranslate" translate="no">near</code> as far away from the camera
  341. as you can and not have things disappear. Set <code class="notranslate" translate="no">far</code> as close to the camera
  342. as you can and not have things disappear. If you're trying to draw a giant
  343. scene and show a close up of someone's face so you can see their eyelashes
  344. while in the background you can see all the way to mountains 50 kilometers
  345. in the distance well then you'll need to find other creative solutions that
  346. maybe we'll go over later. For now, just be aware you should take care
  347. to choose appropriate <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> values for your needs.</p>
  348. <p>The 2nd most common camera is the <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>. Rather than
  349. specify a frustum it specifies a box with the settings <code class="notranslate" translate="no">left</code>, <code class="notranslate" translate="no">right</code>
  350. <code class="notranslate" translate="no">top</code>, <code class="notranslate" translate="no">bottom</code>, <code class="notranslate" translate="no">near</code>, and <code class="notranslate" translate="no">far</code>. Because it's projecting a box
  351. there is no perspective.</p>
  352. <p>Let's change the 2 view example above to use an <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>
  353. in the first view.</p>
  354. <p>First let's setup an <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>.</p>
  355. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const left = -1;
  356. const right = 1;
  357. const top = 1;
  358. const bottom = -1;
  359. const near = 5;
  360. const far = 50;
  361. const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
  362. camera.zoom = 0.2;
  363. </pre>
  364. <p>We set <code class="notranslate" translate="no">left</code> and <code class="notranslate" translate="no">bottom</code> to -1 and <code class="notranslate" translate="no">right</code> and <code class="notranslate" translate="no">top</code> to 1. This would make
  365. a box 2 units wide and 2 units tall but we're going to adjust the <code class="notranslate" translate="no">left</code> and <code class="notranslate" translate="no">top</code>
  366. by the aspect of the rectangle we're drawing to. We'll use the <code class="notranslate" translate="no">zoom</code> property
  367. to make it easy to adjust how many units are actually shown by the camera.</p>
  368. <p>Let's add a GUI setting for <code class="notranslate" translate="no">zoom</code></p>
  369. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
  370. +gui.add(camera, 'zoom', 0.01, 1, 0.01).listen();
  371. </pre>
  372. <p>The call to <code class="notranslate" translate="no">listen</code> tells lil-gui to watch for changes. This is here because
  373. the <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> can also control zoom. For example the scroll wheel on
  374. a mouse will zoom via the <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>.</p>
  375. <p>Last we just need to change the part that renders the left
  376. side to update the <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>.</p>
  377. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  378. const aspect = setScissorForElement(view1Elem);
  379. // update the camera for this aspect
  380. - camera.aspect = aspect;
  381. + camera.left = -aspect;
  382. + camera.right = aspect;
  383. camera.updateProjectionMatrix();
  384. cameraHelper.update();
  385. // don't draw the camera helper in the original view
  386. cameraHelper.visible = false;
  387. scene.background.set(0x000000);
  388. renderer.render(scene, camera);
  389. }
  390. </pre>
  391. <p>and now you can see an <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a> at work.</p>
  392. <p></p><div translate="no" class="threejs_example_container notranslate">
  393. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-orthographic-2-scenes.html"></iframe></div>
  394. <a class="threejs_center" href="/manual/examples/cameras-orthographic-2-scenes.html" target="_blank">click here to open in a separate window</a>
  395. </div>
  396. <p></p>
  397. <p>An <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a> is most often used if using three.js
  398. to draw 2D things. You'd decide how many units you want the camera
  399. to show. For example if you want one pixel of canvas to match
  400. one unit in the camera you could do something like</p>
  401. <p>To put the origin at the center and have 1 pixel = 1 three.js unit
  402. something like</p>
  403. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">camera.left = -canvas.width / 2;
  404. camera.right = canvas.width / 2;
  405. camera.top = canvas.height / 2;
  406. camera.bottom = -canvas.height / 2;
  407. camera.near = -1;
  408. camera.far = 1;
  409. camera.zoom = 1;
  410. </pre>
  411. <p>Or if we wanted the origin to be in the top left just like a
  412. 2D canvas we could use this</p>
  413. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">camera.left = 0;
  414. camera.right = canvas.width;
  415. camera.top = 0;
  416. camera.bottom = canvas.height;
  417. camera.near = -1;
  418. camera.far = 1;
  419. camera.zoom = 1;
  420. </pre>
  421. <p>In which case the top left corner would be 0,0 just like a 2D canvas</p>
  422. <p>Let's try it! First let's set the camera up</p>
  423. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const left = 0;
  424. const right = 300; // default canvas size
  425. const top = 0;
  426. const bottom = 150; // default canvas size
  427. const near = -1;
  428. const far = 1;
  429. const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
  430. camera.zoom = 1;
  431. </pre>
  432. <p>Then let's load 6 textures and make 6 planes, one for each texture.
  433. We'll parent each plane to a <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">THREE.Object3D</code></a> to make it easy to offset
  434. the plane so its center appears to be at its top left corner.</p>
  435. <p>If you're running locally you'll also need to have <a href="setup.html">setup</a>.
  436. You might also want to read about <a href="textures.html">using textures</a>.</p>
  437. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const loader = new THREE.TextureLoader();
  438. const textures = [
  439. loader.load('resources/images/flower-1.jpg'),
  440. loader.load('resources/images/flower-2.jpg'),
  441. loader.load('resources/images/flower-3.jpg'),
  442. loader.load('resources/images/flower-4.jpg'),
  443. loader.load('resources/images/flower-5.jpg'),
  444. loader.load('resources/images/flower-6.jpg'),
  445. ];
  446. const planeSize = 256;
  447. const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
  448. const planes = textures.map((texture) =&gt; {
  449. const planePivot = new THREE.Object3D();
  450. scene.add(planePivot);
  451. texture.magFilter = THREE.NearestFilter;
  452. const planeMat = new THREE.MeshBasicMaterial({
  453. map: texture,
  454. side: THREE.DoubleSide,
  455. });
  456. const mesh = new THREE.Mesh(planeGeo, planeMat);
  457. planePivot.add(mesh);
  458. // move plane so top left corner is origin
  459. mesh.position.set(planeSize / 2, planeSize / 2, 0);
  460. return planePivot;
  461. });
  462. </pre>
  463. <p>and we need to update the camera if the size of the canvas
  464. changes.</p>
  465. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
  466. if (resizeRendererToDisplaySize(renderer)) {
  467. camera.right = canvas.width;
  468. camera.bottom = canvas.height;
  469. camera.updateProjectionMatrix();
  470. }
  471. ...
  472. </pre>
  473. <p><code class="notranslate" translate="no">planes</code> is an array of <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">THREE.Mesh</code></a>, one for each plane.
  474. Let's move them around based on the time.</p>
  475. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
  476. time *= 0.001; // convert to seconds;
  477. ...
  478. const distAcross = Math.max(20, canvas.width - planeSize);
  479. const distDown = Math.max(20, canvas.height - planeSize);
  480. // total distance to move across and back
  481. const xRange = distAcross * 2;
  482. const yRange = distDown * 2;
  483. const speed = 180;
  484. planes.forEach((plane, ndx) =&gt; {
  485. // compute a unique time for each plane
  486. const t = time * speed + ndx * 300;
  487. // get a value between 0 and range
  488. const xt = t % xRange;
  489. const yt = t % yRange;
  490. // set our position going forward if 0 to half of range
  491. // and backward if half of range to range
  492. const x = xt &lt; distAcross ? xt : xRange - xt;
  493. const y = yt &lt; distDown ? yt : yRange - yt;
  494. plane.position.set(x, y, 0);
  495. });
  496. renderer.render(scene, camera);
  497. </pre>
  498. <p>And you can see the images bounce pixel perfect off the edges of the
  499. canvas using pixel math just like a 2D canvas</p>
  500. <p></p><div translate="no" class="threejs_example_container notranslate">
  501. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/cameras-orthographic-canvas-top-left-origin.html"></iframe></div>
  502. <a class="threejs_center" href="/manual/examples/cameras-orthographic-canvas-top-left-origin.html" target="_blank">click here to open in a separate window</a>
  503. </div>
  504. <p></p>
  505. <p>Another common use for an <a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a> is to draw the
  506. up, down, left, right, front, back views of a 3D modeling
  507. program or a game engine's editor.</p>
  508. <div class="threejs_center"><img src="../resources/images/quad-viewport.png" style="width: 574px;"></div>
  509. <p>In the screenshot above you can see 1 view is a perspective view and 3 views are
  510. orthographic views.</p>
  511. <p>That's the fundamentals of cameras. We'll cover a few common ways to move cameras
  512. in other articles. For now let's move on to <a href="shadows.html">shadows</a>.</p>
  513. <p><canvas id="c"></canvas></p>
  514. <script type="module" src="../resources/threejs-cameras.js"></script>
  515. </div>
  516. </div>
  517. </div>
  518. <script src="../resources/prettify.js"></script>
  519. <script src="../resources/lesson.js"></script>
  520. </body></html>