LIBGDX / OpenGL : Reducing the size of everything - opengl-es

This could be the worse question ever asked however that would be a cool achievement.
I have created a 3D world made of cubes that are 1x1x1 (think Minecraft), all the maths works great etc. However 1x1x1 nearly fills the whole screen (viewable area)
Is there a way I can change the ViewPort or something so that 1x1x1 is half the size it currently is?
Code for setting up camera
float aspectRatio = Gdx.graphics.getWidth() / Gdx.graphics.getHeight();
camera = new PerspectiveCamera(67, 1.0f * aspectRatio, 1.0f);
camera.near = 0.1f; // 0.5 //todo find out what this is again
camera.far = 1000;
fps = new ControlsController(camera , this, stage);
I am using the FirstPersonCameraController and PerspectiveCamera to try and make a first person game

I guess the problem is:
camera = new PerspectiveCamera(67, 1.0f * aspectRatio, 1.0f);
An standard initialization of your camera could be (based on this tutorial):
camera = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
// ...
Note how the width and height of the camera is nearly (if not the same) of the width and height of the native gdx window dimension. In your case you set this size to 1 (the same size of your mesh). Try with a bigger viewport dimension to allow your mesh be smaller (in perspective), something like:
/** Not too sure since is a perspective view, but play with this values **/
float multiplier = 2; // <- to allow your mesh be a fraction
// of the size of the viewport of the camera
camera = new PerspectiveCamera(67, multiplier * aspectRatio, multiplier );

Related

Bounding box that is parallel to the camera

My problem is how to define the camera location, given a lookAt vector, when the camera is not on the z axis, so it captures all objects according to its fov and aspect.
I think I need to get a bounding box of my objects that is perpendicular to the camera's lookAt and top and bottom front and back edges are parallel to the xz plane. Then the back of the bounding box is the 'far' plane and I can calculate the distance from it (or fov) and set the camera accordingly.
My question is, how to get such a bounding box (Box3 instance), given some objects on the scene and the lookAt vector ?
My question is, how to get such a bounding box (Box3 instance), given some objects on the scene and the lookAt vector ?
Instances of THREE.Box3 are axis-aligned bounding boxes. No matter how the camera is rotated, it is not possible to generate a different bounding box for a given set of 3D objects.
Maybe you can use a quite common approach 3D viewers which ensures to always display an imported 3D object in the viewport. Exemplary code from the open source glTF viewer looks like this:
const aabb = new THREE.Box3().setFromObject( object );
const center = aabb.getCenter( new THREE.Vector3() );
const size = aabb.getSize( new THREE.Vector3() ).length();
// centering object
object.position.x += ( object.position.x - center.x );
object.position.y += ( object.position.y - center.y );
object.position.z += ( object.position.z - center.z );
// update camera
camera.near = size / 100;
camera.far = size * 100;
camera.updateProjectionMatrix();
camera.position.copy( center );
camera.position.x += size / 2.0;
camera.position.y += size / 5.0;
camera.position.z += size / 2.0;
camera.lookAt( center );

How to preserve threejs texture scale while applying texture rotation

I'd like to enable a user to rotate a texture on a rectangle while keeping the aspect ratio of the texture image intact. I'm doing the rotation of a 1:1 aspect ratio image on a surface that is rectangular (say width: 2 and length: 1)
Steps to reproduce:
In the below texture rotation example
https://threejs.org/examples/?q=rotation#webgl_materials_texture_rotation
If we change one of the faces of the geometry like below:
https://github.com/mrdoob/three.js/blob/master/examples/webgl_materials_texture_rotation.html#L57
var geometry = new THREE.BoxBufferGeometry( 20, 10, 10 );
Then you can see that as you play around with the rotation control, the image aspect ratio is distorted. (form a square to a weird shape)
At 0 degree:
At some angle between 0 and 90:
I understand that by changing the repeatX and repeatY factor I can control this. It's also easy to see what the values would be at 0 degree, 90 degree rotations.
But I'm struggling to come up with the formula for repeatX and repeatY that works for any texture rotation given length and width of the rectangular face.
Unfortunately when stretching geometry like that, you'll get a distortion in 3D space, not UV space. In this example, one UV.x unit occupies twice as much 3D space as one UV.y unit:
This is giving you those horizontally-skewed diamonds when in between rotations:
Sadly, there's no way to solve this with texture matrix transforms. The horizontal stretching will be applied after the texture transform, in 3D space, so texture.repeat won't help you avoid this. The only way to solve this is by modifying the UVs so the UV.x units take up as much 3D space as UV.y units:
With complex models, you'd do this kind of "equalizing" in a 3D editor, but since the geometry is simple enough, we can do it via code. See the example below. I'm using a width/height ratio variable to use in my UV.y remapping, that way the UV transformations will match up, regardless of how much wider it is.
//////// Boilerplate Three setup
const renderer = new THREE.WebGLRenderer({canvas: document.querySelector("canvas")});
const camera = new THREE.PerspectiveCamera(50, 1, 1, 100);
camera.position.z = 3;
const scene = new THREE.Scene();
/////////////////// CREATE GEOM & MATERIAL
const width = 2;
const height = 1;
const ratio= width / height; // <- magic number that will help with UV remapping
const geometry = new THREE.BoxBufferGeometry(width, height, width);
let uvY;
const uvArray = geometry.getAttribute("uv").array;
// Re-map UVs to avoid distortion
for (let i2 = 0; i2 < uvArray.length; i2 += 2){
uvY = uvArray[i2 + 1]; // Extract Y value,
uvY -= 0.5; // center around 0
uvY /= ratio; // divide by w/h ratio
uvY += 0.5; // remove center around 0
uvArray[i2 + 1] = uvY;
}
geometry.getAttribute("uv").needsUpdate = true;
const uvMap = new THREE.TextureLoader().load("https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/uv_grid_opengl.jpg");
// Now we can apply texture transformations as expected
uvMap.center.set(0.5, 0.5);
uvMap.repeat.set(0.25, 0.5);
uvMap.anisotropy = 16;
const material = new THREE.MeshBasicMaterial({map: uvMap});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("resize", resize);
// Add rotation on mousemove
function onMouseMove(ev) {
uvMap.rotation = (ev.clientX / window.innerWidth) * Math.PI * 2;
}
function resize() {
const width = window.innerWidth;
const height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
function animate(time) {
mesh.rotation.y = Math.cos(time/ 3000) * 2;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
resize();
requestAnimationFrame(animate);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://threejs.org/build/three.js"></script>
<canvas></canvas>
First of all, I agree with the solution #Marquizzo provided to your problem. And setting UV explicitly to the geometry should be the easiest way to solve your problem.
But #Marquizzo did not answer why changing the matrix of the texture (set repeatX and repeatY) does not work.
We all know the 2D rotation matrix R
cos -sin
sin cos
UVs are calculated in the shader with a transform matrix T, which is the texture matrix from your question.
T * UV = new UV
To simplify the question, we only consider rotation. And assume we have another additional matrix X for calculating the new UV. Then we have
X * R * UV = new UV
The question now is whether we can find a solution ofX, so that with any rotation, new UV of any points in your question can be calculated correctly. If there is a solution of X, then we can simply use
var X = new Matrix3();
//X.set(x,y,z,...)
texture.matrix.premultiply(X);
Otherwise, we can't find the approach you expected.
Let's create several equations to figure out X.
In the pic below, ABCD is one face of your geometry, and the transparent green is the texture. The UV of point A is (0,1), point B is (0,0), and (1,0), (1,1) for C and D respectively.
The first equation comes from the consideration, without any rotation, the original UV should never be changed (UV for A is always (0,1)). So we should have
X * I * (0, 1) = (0, 1) // I is the identity matrix
From here we can see X should also be an identity matrix.
Then let's see whether the identity matrix X can satisfy the second equation. What's the second equation? Simplify again, let B be the rotation centre(origin) and rotate the texture 90 degrees(counterclockwise). We use -90 to calculate UV though we rotate 90 degrees.
The new UV for point A after rotating the texture 90 degrees should be the current value of E. The value of E is (a/b, 0). Then we have
From this equation we can see X should not be an identity matrix, which means, WE ARE NOT ABLE TO FIND A SOLUTION OF X TO SOLVE YOUR PROBLEM WITH
X * R * UV = new UV
Certainly, you can change the shader of calculating new UVs, but it's even harder than the way #Marquizzo provided.

Unity3d UI issue with Xiaomi

In Xiaomi devices, there are drawn an image outside of camera's letterbox
In other devices everything is correct
I attached both sumsung and xiaomi images, the screenshot that looks ugly is xiaomi, and good look in samsung
float targetaspect = 750f / 1334f;
// determine the game window's current aspect ratio
float windowaspect = (float)Screen.width / (float)Screen.height;
// current viewport height should be scaled by this amount
float scaleheight = windowaspect / targetaspect;
// obtain camera component so we can modify its viewport
Camera camera = GetComponent<Camera>();
// if scaled height is less than current height, add letterbox
if (scaleheight < 1.0f)
{
Rect rect = camera.rect;
rect.width = 1.0f;
rect.height = scaleheight;
rect.x = 0;
rect.y = (1.0f - scaleheight) / 2.0f;
camera.rect = rect;
}
try setting the image to clamp instead of repeat.
this will give the result of black borders but you won't have that weird texture
I don't know what caused that problem, however i solved it in a tricky way. I just added second camera to display black background. Only My main camera's viewport is letterboxed, but not second camera. So it made display to look good

THREE.js Orthographic camera aspect must match viewport aspect?

Please could someone confirm that the following "supposed constraint" is correct?...
In order to render a three.js orthographic camera to a viewport (and to avoid distortion) the camera's frustum left,right,bottom and top planes must define a frontal frustum face (ocWidth, ocHeight) whose aspect ratio (width/height) is the same as the aspect ratio of the viewport?
In the following example the camera width and height are set first and then the viewport height is constrained by the desired viewport width and the given camera aspect ratio. (An alternative approach would be to set the viewport width and height first and then constrain the camera height to the desired camera width and the given viewport aspect ratio.)
//Orthographic Camera
ocWidth = 99000; //... World Units
ocHeight = 33000; //... World Units
var myCamera = new THREE.OrthographicCamera(
ocWidth / - 2, ocWidth / 2,
ocHeight / 2, ocHeight / - 2,
NEAR = 1, FAR = 1000 );
oc_aspect_ratio = ocWidth / ocHeight;
//Viewport
vp_aspect_ratio = oc_aspect_ratio;
vpXwidth = 800; //... pixels
vpYheight = vpXwidth /vp_aspect_ratio; //... pixels, to ensure no distortion
vpXmin = -vpXwidth /2; vpXmax = vpXwidth /2; //... pixels
vpYmin = -vpYheight /2; vpYmax = vpYheight /2; //... pixels
myRenderer.setViewport( vpXmin, vpYmin, vpXwidth, vpYheight );
Thus (in general) the width and height of the RENDERER are irrelevant as far as the orthographic camera is concerned (The exception is when the effective viewport fills the entire renderer, which is the default if no viewport is explicitly defined. In this case the renderer aspect ratio must match the camera aspect ratio).
I have studied
this documentation
this example
this SO question
which are all helpful but do not explicitly confirm the supposed constraint.
If you are rendering your scene with an orthographic camera, and you wish to prevent distortion of the rendered scene, you need to set your viewport aspect ratio to match the aspect ratio of the camera's frustum.

Calculating frame and aspect ratio guides to match cameras

I'm trying to visualize film camera crop and aspect ratio in Three.js. Please bear with me, it's a math problem, and I can't describe it in lesser words...
Instead of just using CameraHelper, I'm using three slightly modified CameraHelper objects for each camera. The helper lines can be seen when looking at a camera (cone), or when looking through a camera, the helper lines effectively create guide lines for the current camera.
Frame helper (bluish one with sides rendered). This is configured and supposed to be what an actual camera sees considering it's focal length and sensor or film dimensions. Calculated in getFOVFrame.
Monitor helper (white). Our frame aspect ratio here is 1.5. For example, if we plan to do a 2.35 (cinemascope) aspect ratio film with a camera of aspect ratio 1.5, this shows the crop area of the frame. So it needs to exactly fit the frame, with extra space either up and down or at the sides, but not both. Calculated in getFOVMonitor.
Screen helper (purple). We want full thing visible in the browser, and if the browser window dimensions/aspect ratio is different, we adjust the actual rendered Three.js camera so that it fits into the browser window and dimensions. So this helper always has the aspect ratio of current browser window, and focal length so that it fits the frame and monitor helper. Calculated in getFOVScreen
So based on our actual preferred camera (frame helper), we need to calculate the monitor camera and adjust it's fov that it exactly fits inside frame camera. Then we also need to calculate the screen camera and adjust it's fov that the frame camera exactly fits inside.
My current solution appears almost correct, but there is something wrong. With long lenses (small fov, big focal length) it seems correct:
Looking through, looks correct:
Both the current camera, and the camera in front look about correct:
Looking through, looks correct:
But at wide lenses (big fov, small focal length) the solution starts to break, there is extra space around the white monitor helper, for example:
Looking through, the white box should touch the bluish one from the sides:
Both the current camera, and the camera in front look wrong, the white boxes should touch the sides of blue box (both have very wide lens):
Looking through (very wide lens), looks wrong, white box should touch blue box and blue box should touch purple box:
So I think I'm calculating the various cameras wrong, although the result seems almost "close enough".
Here's the code that returns the vertical FOV, horizontal HFOV and aspect ratio, which are then used to configure the cameras and helpers:
// BLUE camera fov, based on physical camera settings (sensor dimensions and focal length)
var getFOVFrame = function() {
var fov = 2 * Math.atan( sensor_height / ( focal_length * 2 ) ) * ( 180 / Math.PI );
return fov;
}
var getHFOVFrame = function() {
return getFOVFrame() * getAspectFrame();
}
// PURPLE screen fov, should be able to contain the frame
var getFOVScreen = function() {
var fov = getFOVFrame();
var hfov = fov * getAspectScreen();
if (hfov < getHFOVFrame()) {
hfov = getHFOVFrame();
fov = hfov / getAspectScreen();
}
return fov;
}
var getHFOVScreen = function() {
return getFOVScreen() * getAspectScreen();
}
// WHITE crop area fov, should fit inside blue frame camera
var getFOVMonitor = function() {
var fov = getFOVFrame();
var hfov = fov * getAspectMonitor();
if (hfov > getHFOVFrame()) {
hfov = getHFOVFrame();
fov = hfov / getAspectMonitor();
}
return fov;
}
var getHFOVMonitor = function() {
return getFOVMonitor() * getAspectMonitor();
}
var getAspectScreen = function() {
return screen_width / screen_height;
}
var getAspectFrame = function() {
return sensor_width / sensor_height;
}
var getAspectMonitor = function() {
return monitor_aspect;
}
Why does this produce incorrect results when using large FOV / wide lenses? getFOVScreen and especially getFOVMonitor are the suspects.
Your equation var hfov = fov * getAspectScreen(); is not correct.
The relationship between the vertical FOV (vFOV) and the horizontal FOV (hFOV) are given by the following equations:
hFOV = 2 * Math.atan( Math.tan( vFOV / 2 ) * aspectRatio );
and likewise,
vFOV = 2 * Math.atan( Math.tan( hFOV / 2 ) / aspectRatio );
In these equations, vFOV and hFOV are in radians; aspectRatio = width / height.
In three.js, the PerspectiveCamera.fov is the vertical one, and is in degrees.
three.js r.59

Resources