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