I’ve seen this type of question here before, but none that gave me a full answer, so I’m going to post my code and see if anyone can give me any insight. Basically, this code works, but I have a magical z offset of (height * 1.205) [irrespective of width] and I have no idea why.
Setting the size and creating an alignment vector:
gl.viewportWidth = gl.canvas.width;
gl.viewportHeight = gl.canvas.height;
gl.viewportHalfWidth = gl.viewportWidth / 2;
gl.viewportHalfHeight = gl.viewportHeight / 2;
gl.viewportAspect = gl.viewportWidth / gl.viewportHeight;
gl.viewportZoom = -gl.viewportHeight * 1.205;
if (gl.alignmentTest) {
gl.alignmentVertices = (new Buffer( // TL, TM, BM, BR, TR, BL, TL
[ 1.0, -1.0, 0.0,
gl.viewportHalfWidth - 0.5, -1.0, 0.0,
gl.viewportHalfWidth - 0.5, -(gl.viewportHeight - 1.0), 0.0,
gl.viewportWidth - 1.0, -(gl.viewportHeight - 1.0), 0.0,
gl.viewportWidth - 1.0, -1.0, 0.0,
1.0, -(gl.viewportHeight - 1.0), 0.0,
1.0, -1.0, 0.0], 3)).post();
}
Drawing the alignment vector:
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
mat4.perspective(45, gl.viewportAspect, 0.1, 1000.0, gl.perspective.current);
gl.perspective.post(); // ^ writes directly to current perspective buffer
gl.modelView.resetToIdentity();
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
// Translate to pixel resolution
gl.modelView.push();
gl.modelView.translate([-gl.viewportHalfWidth, gl.viewportHalfHeight, gl.viewportZoom]);
gl.modelView.post();
// Alignment test?
if (gl.alignmentTest && gl.alignmentVertices) {
gl.red.useAsAttribute(gl.shaderProg.vertexColour);
gl.zero.vec2.useAsAttribute(gl.shaderProg.vertexTextureCoord);
gl.alignmentVertices.drawAsPrimitives(gl.shaderProg.vertexPosition, gl.LINE_STRIP);
}
gl.disable(gl.BLEND);
The lines in question are:
gl.viewportZoom = -gl.viewportHeight * 1.205;
and:
gl.modelView.translate([-gl.viewportHalfWidth, gl.viewportHalfHeight, gl.viewportZoom]);
This code works for any size.
Here’s a screenshot at 700×300: http://oi49.tinypic.com/2my9ir7.jpg
And another at 400×600: http://oi46.tinypic.com/i2irh3.jpg
So the question is: Why does this magical scaling factor of 1.205 exist? Or what could I be doing wrong to make it necessary?
I haven’t worked out the math, but I’m guessing that that factor arises from the FOV and near-plane of your perspective projection matrix. Most applications attempting to get 1:1 pixels do not also use perspective projection.
If you have reason to use perspective, then I suggest not using FOV-based perspective calculations and instead use frustum-based (
glFrustum-alike) — it is much easier to actually derive the effects of a projection frustum, and you could work out that correction factor from first principles rather than adjusting it by hand.This is essentially a trigonometric problem. Imagine the view frustum, or rather the view pyramid (i.e. not cut off by the near and far planes); it has its tip at the “camera position” and four sloping faces. The FOV as conventionally defined is equal to the angle between the top and bottom faces (this is why, as you mention, the width has no influence). However, for your purposes, the parameter you care about is not the angle, but the slope (ratio of Z to X-or-Y movement) — which is where your arbitrary figure comes from.
If we look at the documentation for good ol’
gluPerspective(which I am going to assume is using the same math as your perspective operation is using), it says:f = cotangent(fovy/2). This is exactly the calculation we care about. (If trying to reproduce this, note that most trig functions in math libraries take angles in radians, not degrees.)The factor of 2 occurs because FOV is conventionally measured as the full view angle, but the value of interest is half of that — the angle between “straight ahead” and “just at the edge of the view”.
As you may know, the value of the cotangent (or tangent) function can be interpreted as a slope. This slope f above tells you exactly the ratio between Z movement and X-or-Y movement; for example, if you scale an object by some value
d, and also move it along the Z axis byf*d, then it will not appear to change size. This may be exactly what yourviewportZoomis already doing:which is pretty close to your 1.205. If this is in fact the case, I haven’t worked out where the factor of 2 is coming from.
All you need to do is compute
fonce (or possibly its reciprocaltan(fovy/2)depending on which is more convenient in the end) and use it both to compute your “zoom” distances and your projection matrix.(By the way, your code contains a lot of stuff that is not WebGL, e.g.
Buffer,post,drawAsPrimitives, etc. It would be potentially helpful if you mentioned what library you are using.)