Search code examples
javascriptvectorthree.jsglslshader

Three.js - Scaling a plane to full screen


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);

enter image description here

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.

enter image description here

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.


Solution

  • 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);