Three.js - Scaling a plane to full screen - javascript

I am adding a plane to the scene like this:
// Camera
this.three.camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 60);
// Plane
const planeGeometry = new THREE.PlaneBufferGeometry(1,1,this.options.planeSegments,this.options.planeSegments);
const planeMat = new THREE.ShaderMaterial( ... )
this.three.plane = new THREE.Mesh(planeGeometry,planeMat);
this.three.scene.add(this.three.plane);
Pretty basic. I am than trying to find out how I have to move the plane in the Z axis for it to fill the browser-viewport. For that,
// See attachment "solving for this" is closeZ
const closeZ = 0.5 / Math.tan((this.three.camera.fov/2.0) * Math.PI / 180.0);
this.uniforms.uZMax = new THREE.Uniform(this.three.camera.position.z - closeZ);
So now I know in my shader how much I can add to Z to make the plane fill the viewport. Vertex Shader looks like this:
uniform float uZMax;
void main() {
vec3 pos = (position.xy, uZMax);
gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1 );
}
This actually zoom the plane to fill the viewport, but in Y-Axis, not in X-Axis.
I would like to discover why my math is referring to the Y-Axis and how I need to transform it, so the plane will fill the viewport width instead of it's height?
Edit:
I'm trying to achieve something like this https://tympanus.net/Tutorials/GridToFullscreenAnimations/index4.html - But in the given example they're just scaling the x- and y-pixels to fill the screen and therefore no actual 3d - and therefore again no lighting is going on.
I want to actually move the plane towards the camera using different z-values so I can calculate surface normals to then again calculate lighting in the fragment shader by how aligned the normal is with the light direction - like it's done in raymarching.

You can easily achieve such a fullscreen effect by using the following setup:
const camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
const geometry = new THREE.PlaneBufferGeometry( 2, 2 );
When creating a mesh with this geometry and a custom shader material, the orthographic camera will ensure the intended fullscreen effect. This approach is used in all post-processing example where the entire viewport has to be filled with a single quad.

I figured it out, and as suspected it has to do with the aspect ratio passed to the camera. For anyone looking for a solution after me, here is how it works:
I wrongly assumed that the field-of-value for the camera is the same in all directions. But the FOV is referring to the Y-Axis FOV, so we have to convert the camera-fov to the x-axis also:
function getXFOV() {
// Convert angle to radiant
const FOV = this.three.camera.fov;
let yFovRadiant = FOV * Math.PI/180;
// Calculate X-FOV Radiant
let xFovRadiant = 2 * Math.atan( Math.tan(yFovRadiant/2) * (window.innerWidth / window.innerHeight));
// Convert back to angle
let xFovAngle = xFovRadiant * 180/Math.PI;
return xFovAngle;
}
And then we simply use that angle in in the closeZ-calculation instead of the camera's fov. Now it snaps to the window-width.
const closeZ = 0.5 / Math.tan((this.getXFOV()) * Math.PI / 180.0);
this.uniforms.uZMax = new THREE.Uniform(this.three.camera.position.z - closeZ);

Related

Translating camera with radius/diameter instead of x,y,z?

So I have this example as shown below and I was wondering if its possible to translate a camera by changing the radius & diameter instead of using x,y,z positions (Vector). For now im using a cube but I want to add a second camera basically.
Since I know where 0,0,0 (origin) is, is there any way to translate the cube by setting diameter radius or whatever is required and also lock it on the origin?
What I use to move the Cube (Three.js)
var posX,posY,posZ;
var scene, camera, render;
var cubeMesh,cube_Geometry, cube_Material;
class myWorld{
/* ... Setup World ... */
//excecute cube();
/* ... Set/Get positions (xyz) ... */
cube(){
cube_Geometry = new THREE.BoxGeometry(20, 20, 20);
cube_Material = new THREE.MeshNormalMaterial();
cube_Mesh = new THREE.Mesh(cube_Geometry, cube_Material);
cube_Mesh.position.set(0, 100, 100);
scene.add(cube_Mesh);
}
animate(){ //loop function
//THREE.Mesh.position.set (Three.js)
cube_Mesh.position.set(posX, posY, posZ);
}
}
What I want to achieve:
Use Spherical and setFromSpherical:
var r = 10;
var theta = 310 * (Math.PI / 180); /// 310 degree to radians
var sphericalPos = new THREE.Spherical(r, 0, theta);
cube_Mesh.position.setFromSpherical(sphericalPos);
// or just do cube_Mesh.position.setFromSphericalCoords(radius, phi, theta)
Spherical(radius: Float, phi: Float, theta : Float)
radius - the radius, or the Euclidean distance (straight-line distance) from the point to the origin. Default is 1.0.
phi - polar angle from the y (up) axis. Default is 0.
theta - equator angle around the y (up) axis. Default is 0.
The poles (phi) are at the positive and negative y axis. The equator (theta) starts at positive z.

What is required to convert threejs perspective camera to orthographic

I have some code that converts a perspective camera to an orthographic camera. The problem is that when I make the conversion, the model becomes very tiny and hard to see.
I have calculated the zoom factor for the orthographic camera, based on the distance and the FOV. Are there any other properties that I need to set on the orthographic camera (e.g. clipping plane, etc..)?
I believe the position remains the same. I'm not sure what else I need to calculate.
fieldOfView = viewInfo.fov;
var getCameraPosition = function() {
return viewer._viewport._implementation.getCamera()._nativeCamera.position;
};
// Calculate the delta position between the camera and the object
var getPositionDelta = function(position1, position2) {
return {
x: position1.x - position2.x,
y: position1.y - position2.y,
z: position1.z - position2.z
}
};
var getDistance = function(positionDelta, cameraDirection) {
return dot(positionDelta, cameraDirection);
};
distance = getDistance(positionDelta, cameraDirection),
var depth = distance;
var viewportWidth = view.getDomRef().getBoundingClientRect().width;
var viewportHeight = view.getDomRef().getBoundingClientRect().height;
var aspect = viewportWidth / viewportHeight;
var height_ortho = depth * 2 * Math.atan( fieldOfView * (Math.PI/180) / 2 )
var width_ortho = height_ortho * aspect;
var near = viewInfo.near, far = viewInfo.far;
var newCamera = new THREE.OrthographicCamera(
width_ortho / -2, width_ortho / 2,
height_ortho / 2, height_ortho / -2,
near, far );
newCamera.position.copy( viewInfo.position );
var sCamera = new vk.threejs.OrthographicCamera(); //framework creatio of threejs cam
sCamera.setZoomFactor(orthoZoomFactor);
sCamera.setCameraRef(newCamera);
view.getViewport().setCamera(sCamera);
I also tried setting the same camera properties (e.g. clipping planes etc) of the perspective for the orthographic and I still had the same problem.
I guess I am missing some property or calculation required to put the object in the same position as when it was in perspective camera view.
Let's assume you have a perspective view with a given vertical field of view angle fov_y (in degrees) and you know the size of the viewport width and height. Furthermore, you have the near and far plane. These are the values which you use to setup the THREE.PerspectiveCamera:
perspCamera = new THREE.PerspectiveCamera( fov_y, width / height, near, far );
Also, you know the position of the object and the position of the camera. An object doesn't have only a single position, but you have to choose a representative position for its depth.
First you have to calculate the depth of the object.
var v3_object = .... // THREE.Vector3 : positon of the object
var v3_camera = perspCamera.position;
var line_of_sight = new THREE.Vector3();
perspCamera.getWorldDirection( line_of_sight );
var v3_distance = v3_object.clone().sub( v3_camera );
depth = v3_distance.dot( line_of_sight );
Then you have to calculate the "size" of the rectangle which is projected to the viewport at the depth:
aspect = width / height;
height_ortho = depth * 2 * Math.atan( fov_y*(Math.PI/180) / 2 )
width_ortho = height_ortho * aspect;
With these values the THREE.OrthographicCamera can be setup like this:
var orthoCamera = new THREE.OrthographicCamera(
width_ortho / -2, width_ortho / 2,
height_ortho / 2, height_ortho / -2,
near, far );
orthoCamera.position.copy( perspCamera.position );
The positon and direction of the perspective camera can be committed to the orthographic camera like this:
orthoCamera.position.copy( perspCamera.position );
orthoCamera.quaternion.copy( perspCamera.quaternion );
See also stackoverflow question Three.js - Find the current LookAt of a camera?

How to rotate a THREE.PerspectiveCamera around on object

I am making this program where you can click on an object, zoom to it, then look at it from all angles by holding the right mouse button and dragging. I need the camera to be going around the object, not rotate the object with the camera looking at it. I honestly just have no idea how to math it out!
For testing there is already a game object with an xyz we have selected and are looking at
var g = new GameObject(500, 0, 0);//The game object with xyz
this.selected = g;//set selected to g
//Create and set the camera
this.camera = new THREE.PerspectiveCamera(45, w/h, 1, 10000);
this.camera.position.x = 0;
this.camera.position.y = 0;
this.camera.position.z = 0;
//set camera to look at the object which is 500 away in the x direction
this.camera.lookAt(new THREE.Vector3(this.selected.x, this.selected.y, this.selected.z));
So the radius between the camera and the object is 500 and while selected and rotating, the camera should always be 500 away.
I update the scene here:
Main.prototype.update = function(){
this.renderer.render(this.scene, this.camera);//scene is just some ambient lighting
//what to do when mouse right is held down
if(this.rightMouseDown){
//placeholder functionality, needs to rotate around object based on mouse movements
this.camera.position.x -= 5;
}
}
How do I rotate this camera around g with a radius of 500?!?!
As gaitat mentioned, trackball controls are the best place to start with many configurable parameters to make camera rotation/revolution easy. One enormous potential benefit of this method ( especially for your project ) is avoiding "gimbal lock" which is the source of much frustration when working with rotations. Here's a link that might help you with Trackball controls and Orbitcontrols:
Rotate camera in Three.js with mouse
Another option would be setting camera coordinates yourself in the animation loop which is actually quite simple:
var angle = 0;
var radius = 500;
function animate() {
...
// Use Math.cos and Math.sin to set camera X and Z values based on angle.
camera.position.x = radius * Math.cos( angle );
camera.position.z = radius * Math.sin( angle );
angle += 0.01;
...
}
Another option would be to connect the camera to a pivot object and just rotate the pivot:
var camera_pivot = new THREE.Object3D()
var Y_AXIS = new THREE.Vector3( 0, 1, 0 );
scene.add( camera_pivot );
camera_pivot.add( camera );
camera.position.set( 500, 0, 0 );
camera.lookAt( camera_pivot.position );
...
camera_pivot.rotateOnAxis( Y_AXIS, 0.01 ); // radians
If you pursue this option, be aware that the camera object is in "camera pivot space", and might be more challenging to manipulate further.

three.js: transforming coordinates from camera space to scene space

I'm trying to place a cube relative to the camera, rather than relative to the scene. The thing is, to place it in the scene (which I have to do make it show), I have to know the scene coordinates that correspond to the cubes camera space coordinates. I found this function "projectionMatrixInverse" in THREE.Camera. It has a nice function called "multiplyVector3" which I hoped would enable me to transform a vector (1,1,1) back to scene space like this:
var camera, myvec, multvec; // (and others)
camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 2000, 1000 );
camera.position.x = 200;
camera.position.y = 100;
camera.position.z = 200;
myvec = new THREE.Vector3(1,1,1);
console.log("myvec: ", myvec);
multvec = camera.projectionMatrixInverse.multiplyVector3(THREE.Vector3(1,1,1));
console.log("multvec: ", multvec);
the thing is, on the console i get:
myvec: Object { x=1, y=1, z=1}
TypeError: v is undefined
var vx = v.x, vy = v.y, vz = v.z;
multiplyVector3 simply doesn't accept my myvec, or says it's undefined, even though the console says it's an object. I don't get it.
The camera is located at the origin of it's coordinate system, and looks down it's negative-Z axis. A point directly in front of the camera has camera coordinates of the form ( 0, 0, z ), where z is a negative number.
You convert a point p
p = new THREE.Vector3(); // create once and reuse if you can
p.set( x, y, z );
from camera coordinates to world coordinates like so:
p.applyMatrix4( camera.matrixWorld );
camera.matrixWorld is by default updated every frame, but if need be, you can update it yourself by calling camera.updateMatrixWorld();
three.js r.95
This may also be what you're after:
scene.add( camera );
brick.position.set( 0, 0, -1 );
camera.add( brick );

Adjusting camera for visible Three.js shape

I have a CubeGeometry which the camera is looking at, and I want the camera to zoom so that the cube is entirely visible, but no larger.
My initial attempt was to convert the cube verticies to the camera coordinate system,
function toScreenXY(position, camera) {
var pos = position.clone();
var projScreenMat = new THREE.Matrix4();
projScreenMat.multiply(camera.projectionMatrix, camera.matrixWorldInverse);
projScreenMat.multiplyVector3( pos );
return pos;
}
function ScaleInView() {
camera.fov = 0.0;
for (var i=0; i<8; i++) {
proj2d = toScreenXY(cube.geometry.vertices[i],camera);
angle = 57.296 * Math.max(Math.atan(proj2d.x/proj2d.z), Math.atan(proj2d.y/proj2d.z));
camera.fov = Math.max(camera.fov,angle);
}
camera.updateProjectionMatrix();
}
I thought this would work, but sometimes it's too small, and other times too large (depending on the position of the camera).
I also need to do this for Orthographic Camera.
Edit:
I know how to do this when the cube is facing the camera, I'm looking for a way to do it when the camera is moved to some arbitrary (r, theta, phi) position (spherical polar coordinates; r is actually constant for my purposes).
Perspective Camera. If the camera is centered and viewing the cube head-on, define
dist = distance from the camera to the front face ( important ! ) of the cube
and
height = height of the cube.
If you set the camera field-of-view as follows
fov = 2 * Math.atan( height / ( 2 * dist ) ) * ( 180 / Math.PI );
then the cube height will match the visible height.
Orthographic Camera. If the camera is centered and viewing the cube head-on, define
aspect = the aspect ratio of your window (i.e., width / height)
and
height = height of the cube.
Then construct your camera this way:
camera = new THREE.OrthographicCamera( -aspect * height/2, aspect * height/2, height/2, -height/2, near, far );
The cube height will match the visible height.
In either case, if the camera is not centered, or is otherwise viewing the cube at an angle, then the problem is more complicated.
Also, if the window is narrower than it is high, then the width is the constraining factor, and the problem is more complicated.
Multiplying by camera.matrixWorldInverse gives a vector in the camera's coordinates, but importantly does not apply perspective.
function toCameraCoords(position) {
return camera.matrixWorldInverse.multiplyVector3(position.clone());
}
We can then find the smallest angle that will fit all the box corners in the scene. arctan(D.x / D.z) gives the angle BCD where B is what the camera is looking at, C is the camera's position, and D the position of an object that you want to be visible in the camera coordinates.
In my case, the following ensures that the the cube boundbox is fully visible.
function ScaleInView() {
var tmp_fov = 0.0;
for (var i=0; i<8; i++) {
proj2d = toCameraCoords(boundbox.geometry.vertices[i]);
angle = 114.59 * Math.max( // 2 * (Pi / 180)
Math.abs(Math.atan(proj2d.x/proj2d.z) / camera.aspect),
Math.abs(Math.atan(proj2d.y/proj2d.z))
);
tmp_fov = Math.max(tmp_fov, angle);
}
camera.fov = tmp_fov + 5; // An extra 5 degrees keeps all lines visible
camera.updateProjectionMatrix();
}

Categories

Resources