banner



Can You Make The Camera Visible In Three Js

This article is one in a serial of manufactures near three.js. The beginning article was well-nigh fundamentals. If you haven't read that yet yous might want to offset there.

Let's talk about cameras in 3.js. We covered some of this in the first article but we'll cover it in more detail here.

The nigh mutual camera in iii.js and the one we've been using up to this point is the PerspectiveCamera. Information technology gives a 3d view where things in the altitude appear smaller than things up close.

The PerspectiveCamera defines a frustum. A frustum is a solid pyramid shape with the tip cut off. By name of a solid I mean for case a cube, a cone, a sphere, a cylinder, and a frustum are all names of different kinds of solids.

I only point that out because I didn't know if for years. Some book or page would mention frustum and my optics would coat over. Understanding it's the name of a type of solid shape fabricated those descriptions suddenly make more sense 😅

A PerspectiveCamera defines its frustum based on 4 backdrop. almost defines where the front of the frustum starts. far defines where it ends. fov, the field of view, defines how alpine the front and dorsum of the frustum are past computing the correct elevation to get the specified field of view at near units from the camera. The aspect defines how wide the front and back of the frustum are. The width of the frustum is merely the acme multiplied by the aspect.

Allow's use the scene from the previous commodity that has a ground plane, a sphere, and a cube and make it so nosotros can adjust the camera's settings.

To practise that we'll make a MinMaxGUIHelper for the near and far settings so far is e'er greater than near. It will have min and max properties that dat.GUI will adjust. When adjusted they'll set the 2 backdrop we specify.

          course MinMaxGUIHelper {   constructor(obj, minProp, maxProp, minDif) {     this.obj = obj;     this.minProp = minProp;     this.maxProp = maxProp;     this.minDif = minDif;   }   get min() {     return this.obj[this.minProp];   }   ready min(v) {     this.obj[this.minProp] = v;     this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif);   }   get max() {     render this.obj[this.maxProp];   }   gear up max(v) {     this.obj[this.maxProp] = 5;     this.min = this.min;  // this volition phone call the min setter   } }                  

Now we can setup our GUI similar this

          office updateCamera() {   camera.updateProjectionMatrix(); }  const gui = new dat.GUI(); gui.add(camera, 'fov', 1, 180).onChange(updateCamera); const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1); gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.one).name('virtually').onChange(updateCamera); gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).proper name('far').onChange(updateCamera);                  

Anytime the camera's settings alter nosotros need to call the camera's updateProjectionMatrix function so nosotros made a function chosen updateCamera add passed it to dat.GUI to call it when things change.

You tin can adjust the values and see how they piece of work. Notation we didn't brand aspect settable since it'due south taken from the size of the window so if you want to adjust the aspect open the example in a new window and then size the window.

Still, I retrieve it's a little hard to meet so let's change the example and so it has 2 cameras. Ane volition show our scene equally nosotros run across it in a higher place, the other will show some other camera looking at the scene the kickoff camera is drawing and showing that camera'due south frustum.

To practice this we tin use the scissor part of three.js. Let's change information technology to draw 2 scenes with 2 cameras adjacent using the scissor part

First off let's use some HTML and CSS to ascertain 2 side by side elements. This will also aid united states of america with events so both cameras can hands have their own OrbitControls.

          <body>   <sail id="c"></canvas> +  <div class="split up"> +     <div id="view1" tabindex="ane"></div> +     <div id="view2" tabindex="ii"></div> +  </div> </body>                  

And the CSS that volition make those 2 views bear witness upwards next overlayed on top of the canvas

          .divide {   position: absolute;   left: 0;   acme: 0;   width: 100%;   height: 100%;   brandish: flex; } .split>div {   width: 100%;   height: 100%; }                  

So in our lawmaking we'll add a CameraHelper. A CameraHelper draws the frustum for a Camera

          const cameraHelper = new THREE.CameraHelper(camera);  ...  scene.add(cameraHelper);                  

Now let'due south look up the 2 view elements.

          const view1Elem = document.querySelector('#view1'); const view2Elem = document.querySelector('#view2');                  

And nosotros'll set up our existing OrbitControls to respond to the starting time view element only.

          -const controls = new Three.OrbitControls(camera, sheet); +const controls = new THREE.OrbitControls(camera, view1Elem);                  

Let'southward make a second PerspectiveCamera and a second OrbitControls. The second OrbitControls is tied to the second photographic camera and gets input from the 2d view element.

          const camera2 = new THREE.PerspectiveCamera(   60,  // fov   2,   // attribute   0.1, // well-nigh   500, // far ); camera2.position.gear up(forty, ten, xxx); camera2.lookAt(0, 5, 0);  const controls2 = new Three.OrbitControls(camera2, view2Elem); controls2.target.set(0, five, 0); controls2.update();                  

Finally we need to render the scene from the point of view of each camera using the scissor office to but render to office of the canvas.

Hither is a function that given an element will compute the rectangle of that element that overlaps the canvass. It volition then set the scissor and viewport to that rectangle and return the aspect for that size.

          role setScissorForElement(elem) {   const canvasRect = sheet.getBoundingClientRect();   const elemRect = elem.getBoundingClientRect();    // compute a canvas relative rectangle   const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left;   const left = Math.max(0, elemRect.left - canvasRect.left);   const lesser = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.peak;   const top = Math.max(0, elemRect.elevation - canvasRect.top);    const width = Math.min(canvasRect.width, right - left);   const top = Math.min(canvasRect.height, bottom - top);    // setup the scissor to simply render to that part of the canvass   const positiveYUpBottom = canvasRect.pinnacle - lesser;   renderer.setScissor(left, positiveYUpBottom, width, summit);   renderer.setViewport(left, positiveYUpBottom, width, acme);    // return the aspect   return width / summit; }                  

And now nosotros can employ that function to draw the scene twice in our return function

                      function render() {  -    if (resizeRendererToDisplaySize(renderer)) { -      const canvas = renderer.domElement; -      photographic camera.attribute = sheet.clientWidth / canvass.clientHeight; -      camera.updateProjectionMatrix(); -    }  +    resizeRendererToDisplaySize(renderer); + +    // plow on the scissor +    renderer.setScissorTest(true); + +    // render the original view +    { +      const aspect = setScissorForElement(view1Elem); + +      // accommodate the camera for this aspect +      photographic camera.aspect = aspect; +      camera.updateProjectionMatrix(); +      cameraHelper.update(); + +      // don't draw the camera helper in the original view +      cameraHelper.visible = false; + +      scene.background.set up(0x000000); + +      // render +      renderer.render(scene, camera); +    } + +    // return from the 2nd camera +    { +      const aspect = setScissorForElement(view2Elem); + +      // adapt the photographic camera for this aspect +      camera2.aspect = aspect; +      camera2.updateProjectionMatrix(); + +      // depict the camera helper in the 2nd view +      cameraHelper.visible = true; + +      scene.background.set up(0x000040); + +      renderer.render(scene, camera2); +    }  -    renderer.render(scene, camera);      requestAnimationFrame(return);   }    requestAnimationFrame(render); }                  

The code above sets the background color of the scene when rendering the second view to dark blue simply to make information technology easier to distinguish the two views.

We can too remove our updateCamera code since we're updating everything in the render function.

          -function updateCamera() { -  camera.updateProjectionMatrix(); -}  const gui = new dat.GUI(); -gui.add(camera, 'fov', i, 180).onChange(updateCamera); +gui.add(camera, 'fov', i, 180); const minMaxGUIHelper = new MinMaxGUIHelper(photographic camera, 'near', 'far', 0.1); -gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).proper noun('near').onChange(updateCamera); -gui.add together(minMaxGUIHelper, 'max', 0.one, 50, 0.1).name('far').onChange(updateCamera); +gui.add together(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('well-nigh'); +gui.add together(minMaxGUIHelper, 'max', 0.1, 50, 0.1).proper name('far');                  

And at present you can use ane view to run into the frustum of the other.

On the left y'all can see the original view and on the right you can see a view showing the frustum of the camera on the left. As you adjust near, far, fov and move the camera with mouse you can come across that only what'south inside the frustum shown on the right appears in the scene on the left.

Accommodate virtually upwardly to effectually twenty and you'll easily see the front end of objects disappear as they are no longer in the frustum. Adapt far below about 35 and you lot'll outset to meet the basis plane disappear as it'south no longer in the frustum.

This brings up the question, why not just set up about to 0.0000000001 and far to 10000000000000 or something like that so yous can merely come across everything? The reason is your GPU only has so much precision to decide if something is in front or behind something else. That precision is spread out between nigh and far. Worse, by default the precision close the camera is detailed and the precision far from the camera is coarse. The units start with nigh and slowly expand as they approach far.

Starting with the top instance, let's alter the lawmaking to insert 20 spheres in a row.

          {   const sphereRadius = 3;   const sphereWidthDivisions = 32;   const sphereHeightDivisions = 16;   const sphereGeo = new Three.SphereBufferGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);   const numSpheres = 20;   for (let i = 0; i < numSpheres; ++i) {     const sphereMat = new THREE.MeshPhongMaterial();     sphereMat.colour.setHSL(i * .73, 1, 0.5);     const mesh = new 3.Mesh(sphereGeo, sphereMat);     mesh.position.set(-sphereRadius - one, sphereRadius + two, i * sphereRadius * -ii.2);     scene.add(mesh);   } }                  

and allow's set near to 0.00001

          const fov = 45; const aspect = 2;  // the sail default -const near = 0.one; +const near = 0.00001; const far = 100; const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);                  

We too need to tweak the GUI code a footling to allow 0.00001 if the value is edited

          -gui.add(minMaxGUIHelper, 'min', 0.1, l, 0.1).name('near').onChange(updateCamera); +gui.add together(minMaxGUIHelper, 'min', 0.00001, 50, 0.00001).name('well-nigh').onChange(updateCamera);                  

What do you retrieve will happen?

This is an example of z fighting where the GPU on your computer does not take enough precision to determine which pixels are in front and which pixels are behind.

Just in case the result doesn't show on your machine hither's what I run into on mine

I solution is to tell three.js use to a dissimilar method to compute which pixels are in front end and which are behind. We can do that by enabling logarithmicDepthBuffer when we create the WebGLRenderer

          -const renderer = new THREE.WebGLRenderer({canvas}); +const renderer = new Three.WebGLRenderer({ +  canvas, +  logarithmicDepthBuffer: true, +});                  

and with that information technology might work

If this didn't set the issue for you then you've run into one reason why yous can't always use this solution. That reason is because only certain GPUs back up it. As of September 2018 almost no mobile devices support this solution whereas most desktops do.

Another reason not to choose this solution is information technology can be significantly slower than the standard solution.

Even with this solution there is still limited resolution. Brand near even smaller or far even bigger and yous'll eventually run into the same issues.

What that means is that you should always make an effort to choose a almost and far setting that fits your use case. Set almost as far away from the camera every bit yous tin can and non have things disappear. Set far every bit close to the camera as you can and not take things disappear. If yous're trying to draw a giant scene and prove a shut up of someone'southward confront so you can see their eyelashes while in the groundwork you can see all the fashion to mountains 50 kilometers in the distance well and so you'll need to find other creative solutions that peradventure we'll become over afterward. For now, merely be enlightened you should take care to choose advisable near and far values for your needs.

The second most mutual camera is the OrthographicCamera. Rather than specify a frustum information technology specfies a box with the settings left, correct elevation, bottom, near, and far. Because it's projecting a box there is no perspective.

Let'due south change the 2 view example above to use an OrthographicCamera in the first view.

Outset let'southward setup an OrthographicCamera.

          const left = -1; const right = 1; const top = i; const bottom = -1; const nearly = 5; const far = fifty; const photographic camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far); camera.zoom = 0.2;                  

We gear up left and bottom to -1 and correct and superlative to 1. This would make a box 2 units broad and 2 units tall merely nosotros're going to adjust the left and acme past the aspect of the rectangle we're drawing to. We'll use the zoom holding to brand it easy to adjust how many units are actually shown past the photographic camera.

Let'south add together a GUI setting for zoom

          const gui = new dat.GUI(); +gui.add(camera, 'zoom', 0.01, 1, 0.01).listen();                  

The call to listen tells dat.GUI to watch for changes. This is hither because the OrbitControls can also command zoom. For case the scrollwheel on a mouse will zoom via the OrbitControls.

Last we just need to change the function that renders the left side to update the OrthographicCamera.

          {   const aspect = setScissorForElement(view1Elem);    // update the camera for this aspect -  photographic camera.aspect = aspect; +  camera.left   = -aspect; +  camera.right  =  aspect;   camera.updateProjectionMatrix();   cameraHelper.update();    // don't draw the photographic camera helper in the original view   cameraHelper.visible = false;    scene.groundwork.set(0x000000);   renderer.render(scene, camera); }                  

and now you tin run into an OrthographicCamera at piece of work.

An OrthographicCamera is most ofttimes used if using three.js to describe 2D things. You lot'd make up one's mind how many units you want the photographic camera to show. For case if you lot want 1 pixel of sail to match i unit in the photographic camera you could do something like

To put the origin at the heart and have one pixel = 1 three.js unit something like

          camera.left = -canvass.width / 2; photographic camera.right = canvas.width / 2; camera.top = canvas.heigth / two; photographic camera.bottom = -canvass.top / two; photographic camera.nearly = -ane; photographic camera.far = 1; camera.zoom = one;                  

Or if we wanted the origin to exist in the top left just like a 2d canvas we could use this

          camera.left = 0; camera.right = canvas.width; camera.top = 0; photographic camera.lesser = canvas.acme; camera.near = -1; camera.far = one; camera.zoom = 1;                  

In which case the summit left corner would be 0,0 simply similar a 2D canvas

Let's endeavor it! First allow's set the camera up

          const left = 0; const right = 300;  // default canvas size const meridian = 0; const bottom = 150;  // default canvas size const near = -1; const far = 1; const camera = new THREE.OrthographicCamera(left, right, top, lesser, virtually, far); photographic camera.zoom = 1;                  

Then let'southward load half-dozen textures and make half-dozen planes, one for each texture. We'll parent each airplane to a Iii.Object3D to make it easy to offset the plane and then it's center appears to be at it's meridian left corner.

          const loader = new THREE.TextureLoader(); const textures = [   loader.load('resource/images/flower-ane.jpg'),   loader.load('resource/images/flower-ii.jpg'),   loader.load('resources/images/flower-3.jpg'),   loader.load('resource/images/flower-4.jpg'),   loader.load('resources/images/bloom-5.jpg'),   loader.load('resources/images/flower-vi.jpg'), ]; const planeSize = 256; const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize); const planes = textures.map((texture) => {   const planePivot = new 3.Object3D();   scene.add(planePivot);   texture.magFilter = 3.NearestFilter;   const planeMat = new THREE.MeshBasicMaterial({     map: texture,     side: THREE.DoubleSide,   });   const mesh = new THREE.Mesh(planeGeo, planeMat);   planePivot.add together(mesh);   // motility plane so height left corner is origin   mesh.position.prepare(planeSize / 2, planeSize / 2, 0);   return planePivot; });                  

and we need to update the camera if the size of the sheet changes.

          function render() {    if (resizeRendererToDisplaySize(renderer)) {     camera.right = canvas.width;     camera.bottom = sail.peak;     camera.updateProjectionMatrix();   }    ...                  

planes is an array of THREE.Mesh, one for each plane. Let'due south move them around based on the fourth dimension.

          function return(time) {   time *= 0.001;  // convert to seconds;    ...    const xRange = Math.max(20, canvas.width - planeSize) * 2;   const yRange = Math.max(20, canvas.height - planeSize) * 2;    planes.forEach((plane, ndx) => {     const speed = 180;     const t = time * speed + ndx * 300;     const xt = t % xRange;     const yt = t % yRange;      const x = xt < xRange / ii ? xt : xRange - xt;     const y = yt < yRange / 2 ? yt : yRange - yt;      airplane.position.set(10, y, 0);   });    renderer.render(scene, photographic camera);                  

And y'all can run into the images bounce pixel perfect off the edges of the sail using pixel math simply like a 2d canvas

Another common utilize for an OrthographicCamera is to draw the upward, down, left, correct, front, back views of a 3D modeling program or a game engine's editor.

In the screenshot higher up you tin see 1 view is a perspective view and three views are orthographic views.

That'due south the fundamentals of cameras. We'll embrace a few mutual means to move cameras in other articles. For now let's move on to shadows.

Source: https://r105.threejsfundamentals.org/threejs/lessons/threejs-cameras.html

Posted by: desmondbaccough.blogspot.com

0 Response to "Can You Make The Camera Visible In Three Js"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel