camera.minZ and maxZ do not work properly when camera.parent is scaled


While porting my exercise project from Three.js to Babylon I noticed a weird behaviour of camera. Despite the fact that I am new to this ecosystem, I dare to think this is a bug.

My project is a yet another simulation of a solar system, so there is a hierarchy of objects: sun, planets, and their moons. Planets’ parent is sun, and moons’ parents are planets they circle around. Nothing unusual.

But unlike other simulation of this sort, all objects are spheres of fixed radius 1. Instead of setting radius to a specific value, I extensively use relative scaling. This approach has the advantage that I can easily focus on various objects just by changing camera.parent, while having camera.position fixed. Because the position is relative to its parent, the parent appears to be always of the same size on the screen, no matter if this is sun, a planet or moon.

And while porting this approach to Babylon, I noticed that planets are not rendered fully, and moons at all.

Please check this demo

if you focus on planet, you will see that it partially disappeared. If you focus on moon, it disappears completely.

The cause of this problem is that all properties (position, viewport, mouse wheel controls) of camera respect its parent’s world, except for camera.minZ, which still uses scene’s world units. And because planet and moon are closer to camera than 1 unit in scene’s world, the renderer does not render them fully,

I fixed this by adding the following (you need to uncomment line #43)

camera.minZ = !camera.parent ? 1 : camera.parent.absoluteScaling.z;

but I believe Babylon should work like this out of the box. That’s why I am reporting this as a bug.

And I am also not sure if my fix is safe, as the documentation says:

minZ: number
(…) This is important to note that the depth buffer are not infinite and the closer it starts the more your scene might encounter depth fighting issue.

like I said, I am new to Babylon…

1 Like

Well this is an interesting one

For me minZ and maxZ are in the view space so they should not inherit from parent’s scaling

But this is open to debate for sure

Pinging @sebavan, @Evgeni_Popov, @bghgary for other inputs

For me, minZ / maxZ are properties of the camera and are defined in the view camera space (minZ being the z-distance to the front plane in this space), so as such they may have to take into account the parent transformations of the camera…

If we imagine the camera as a node in the scene and the minZ value being represented by a plane at distance minZ (in the z-axis direction) from the local origin, then applying some z scaling on the camera node and/or on parent nodes would indeed change the position of the plane relative to the camera…

The problem of modifying the minZ/maxZ values based on the position of the camera in the scene hierarchy is that some artifacts could pop, as is said in the doc, the precision of the depth buffer depends on both minZ/maxZ values, but much more on minZ: you should normally try to have the largest minZ value as possible.

Also, I have never heard/read anything about the fact that the minZ value (and maxZ value, for that matter) of the camera should be updated based on the absolute z scale of the parent hierarchy, so I’m a little doubtful about this…

You are saying your project is a port from 3js and implying you had not this problem with 3js: however, I can’t see anything in their code that would modify the minZ value (named near in their code) based on the transforms of the parent nodes… Maybe you had a lower enough minZ value at start that was suitable for your use case without having the need to modify it on the fly?

[EDIT] Ogre3D does not seem to correct the minZ value neither [/EDIT]

No preferences on my side. I love the extra simplicity of not taking parent into account but could be limited lets say if you attach the camera to the head of a character which is moving in space.

This is an interesting question. I can’t say I have experience with near/far clip planes of a camera under a parent with non-unit scale. I think I would want them to scale, but I don’t know explicitly if any engines do this (I did some quick searching for what Unity does and didn’t find anything useful).

Yeah like Evgeni, I think these values lives in view space and thus should not be modified based on camera’s parent.

And it is clearly easy to do right now manually so unless someone has a string argument we should live it unchanged

Thank you all for giving attention to my problem.

@Evgeni_Popov I have recreated this example using Three.js (use this url to make it fullscreen)

In this example, minZ is set to 1 (like Babylon does) and you will see that there is no problem. If you set minZ to, say 6.6 (which partially overlaps target mesh), you will see that the cut-out is exactly the same at all scales (for the sun, planet and moon).

My “fix” works only if all dimensions are scaled evenly. That’s why it is working for me. In this case it is “easy”.

But in fact this is only a specific case. And in fact, in the formula

camera.minZ = !camera.parent ? 1 : camera.parent.absoluteScaling.z;

the letter “Z” in minZ and absoluteScaling.z refer to different coordinate systems. This gives an illusion of an easy and clean solution, but I could use instead absoluteScaling.x or absoluteScaling.y. And it would all work, just because they all happen to be the same. But in a generic case, when parent could be scaled unevenly, there is no clean workaround.

Consider another example , where objects in the solars system are modelled as ellipsoids. This is more realistic (although I slightly exaggerated the proportions). In this case the only solution to this problem is to set minZ so low, that it works for all dimensions.

The lack of a clean solution for such case, suggests that there is a conceptual/design problem. Just math isn’t working.

Summing up, it is not about changing adaptively camera.minZ to accommodate for camera.parent scaling. It is about keeping camera.minZ fixed (set to 1 in this case), and accommodating Babylon’js internal math formulas that dynamically calculate camera’s frustum.

Well again I kindly disagree, we do have people here (including me) who thinks that minZ and maxZ are (should be) in view space

I checked 3js shader and they do not seem to do anything specific. @Evgeni_Popov (As you know 3js code source better than me:)) can you check what they do when they built the perspective matrix? This must be where they deal with it

I do think they do nothing regarding minZ, the difference in behaviour must come from somewhere else, let me check this.

1 Like

Ok here is my finding:

There is a slight difference (we do not compute the a,b values) that could explain the difference. Again not the same engine and not the same math which is expected

That being said, setting minZ = 0.5 is just the best fix here

But overall the conclusion is that 3js is not doing anything like us to deal with camera.parent

Here’s the code for PerspectiveCamera.updateProjectionMatrix (only the bits that matter):

var near = this.near,
	top = near * Math.tan( _Math.DEG2RAD * 0.5 * this.fov ) / this.zoom,
	height = 2 * top,
	width = this.aspect * height,
	left = - 0.5 * width;

this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far );

a = (right + left) / (right - left) = 0 and b = (top + bottom) / (top - bottom) = 0 with the values from the function, so the perspective matrix ends up to being the same as in Babylon. The this.near variable in the code is the minZ value in Babylon, and as can be seen it is taken ‘as is’ to construct the perspective matrix.

I do also think the difference comes from the hierarchy handling, which is different in Babylon. It would be interesting to know in what way and compare the world matrices of the camera in both cases, but I don’t know how to do that in jsfiddle, it seems I can’t use the browser console to print variables…

The difference comes from the fact that the view matrix is built from scratch by the TargetCamera class and does not retain the scaling factors from the parent transformation in the final matrix.

For eg, when looking at the blue planet, the camera world matrix is:
[0.16, 0, 0, 0, 0, 0.16, 0, 0, 0, 0, 0.16, 0, 1.2, 0, 1.136, 1]
in 3js whereas it is:
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.2, 0, -1.136, 1] in Babylon.

When updating (by hand, for testing purpose) the camera world matrix to manage to have the same matrix as 3js, we don’t have the minZ clipping problem.

So, maybe that could be a good idea to have a deeper look at this?

Also, the PG should be updated because apparently we don’t use the same axis than 3js and/or the OrbitCamera does not work as the ArcRotateCamera. The PG below is in line with 3js and generate the same matrices (except for the scaling values explained above and for a sign inversion in z translation) whereas the initial PG does not:

@Evgeni_Popov the axis issue is just because I mixed up the order of values. I was bit clumsy as it was around midnight my time. I will fix it in a few hrs.


@Evgeni_Popov Indeed, there is something different in the orientation of cameras, or the world. I thought it was my mistake, but it wasn’t. If I change relative location of objects to (0, 0, 3) and camera position to (0, 0, distance) I get the following view in

They are indeed different. I am sorry for causing confusion.

@Deltakosh these are 2 different engines and it is understood that they use different algorithms. No argue with that. But they both aim to simulate the same physical reality. And from the user’s perspective, I would expect that the view of the elephant would be different than the view of a mouse, because scaling affects the perspective frustum. In that respect, I am perceiving the behaviour of Three.js as more intuitive. Unless there is a reason for keeping minZ unaffected by scaling, which makes sense in some other use cases.

No problem. Using ArcRotateCamera with the default 0/0 values for alpha/beta is a bit counter-intuitive to me because you get a top down view of the scene, meaning the camera is directed down the Y axis:

This is the view you get by using an ArcRotateCamera with 0/0 values for alpha/beta for the default playground. In this picture, +Y is through screen, +X from top to bottom and +Z from left to right.

In this one, I use a UniversalCamera located at (0,5,-10) and targetted at (0,0,0). In this picture, +Z is through screen, -Y from top to bottom and +X from left to right.

In fact it is not minZ in itself that is not affected by the scaling, it is the scaling factors of the parent matrix which are not carried through to the camera matrix, meaning some entries of the perspective matrix won’t be multiplied by the scaling (those are the x, y and c variables in the screenshot from @Deltakosh above).

Maybe that’s something that the team could discuss and see if something can be made about it.

1 Like

Note also that changing the camera position for an ArcRotateCamera does nothing, except that it triggers a matrix recomputation. But the position is not updated:
So, I do really think you should use a FreeCamera or UniversalCamera if you want to be extra sure of what is going on in your PG.

My PG ( that is using a FreeCamera is more in line with your 3js fiddle, you will notably see that I use object.position.copyFromFloats(3, 0, 0); (you do object.position.set(3, 0, 0); in the jsfiddle), whereas you do object.position.copyFromFloats(0, 0, 3); in your Babylon PG.

I noticed that, but didn’t want to open a new topic for discussion. I think I will still use ArcRotateCamera to be able to take a closer look at the target body, because it provides me with rotation mechanics and AutoRotateBehavior, which with FreeCamera I would need to implement on my own. Thank you for your suggestion.

Tentative PR:

1 Like

I like it! This is a better way to handle parenting for cameras

I just added a tiny comment and this can go in :slight_smile: