I have been provided with a framework where a simple path tracer is implemented. What I am trying to do so far is understanding the whole code because I'll need to put my hands on it. Unfortunately I am arrived on a step where I don't actually get what's happening and since I am a newbie in the advanced graphics field I don't manage to "decrypt" this part. The developer is trying to get the coordinates of the screen corners as for comments. What I need to understand is the math behind it and therefore some of the variables that are used. Here is the code:
// setup virtual screen plane
vec3 E( 2, 8, -26 ), V( 0, 0, 1 );
static float r = 1.85f;
mat4 M = rotate( mat4( 1 ), r, vec3( 0, 1, 0 ) );
float d = 0.5f, ratio = SCRWIDTH / SCRHEIGHT, focal = 18.0f;
vec3 p1( E + V * focal + vec3( -d * ratio * focal, d * focal, 0 ) ); // top-left screen corner
vec3 p2( E + V * focal + vec3( d * ratio * focal, d * focal, 0 ) ); // top-right screen corner
vec3 p3( E + V * focal + vec3( -d * ratio * focal, -d * focal, 0 ) ); // bottom-left screen corner
p1 = vec3( M * vec4( p1, 1.0f ) );
p2 = vec3( M * vec4( p2, 1.0f ) );
p3 = vec3( M * vec4( p3, 1.0f ) );
For example:
what is the "d" variable and why both "d" and "focal" are fixed?
is "focal" the focal length?
What do you think are the "E" and "V" vectors?
is the matrix "M" the CameraToWorldCoordinates matrix?
I need to understand every step of those formulas if possible, the variables, and the math used in those few lines of code. Thanks in advance.
My guesses:
E: eye position—position of the eye/camera in world space
V: view direction—the direction the camera is looking, in world coordinates
d: named constant for one half—corners are half the screen size away from the centre (where the camera is looking)
focal: distance of the image plane from the camera. Given its use in screen corner offsets, it also seems to be the height of the image plane in world coordinates.
M: I'd say this is the WorldToCamera matrix. It's used to transform a point which is based on E
How the points are computed:
Start at the camera: E
Move focal distance along the view direction, effectively moving to the centre of the image plane: + V * focal
Add offsets on X & Y which will move half a screen distance: + vec3( ::: )
Given that V does not figure in the vec3() arguments (nor does any up or right vector), this seems to hard-code the idea that V is collinear with the Z axis.
Finally, the points are tranformed as points (as opposed to directions, since their homogenous coordinate is 1) by M.
Related
I am creating a 2D C++ game engine from scratch minus making calls to the OS directly. For that, I am using SFML. I am essentially only using SFML to draw to the screen and collect input, so I am not looking for help with anything related to SFML.
Right now, I can pan the camera up and down and the sprites translate from world coordinates to screen coordinates correctly. The sprites also, for the most part, translate screen coordinates from world coordinates relative to camera rotation. The problem comes in when a rendered game object has a higher y world coordinate than the camera. When this happens it would appear that the sprite is reflected on the x-axis.
I will note that this does not happen if I comment out the rotation code shown below.
//shape's position has been set relative to camera position first
Vector2 screenCenter(windowWidth / 2, windowHeight / 2);
Vector2 shapePosition = shape->GetPosition ();
//create vector from center screen to shape position
Vector2 relativeVector = shapePosition - screenCenter;
float distance = relativeVector.Magnitude ();
if ( distance == 0 ) { return; }
float angle = Vector2::AngleInRads ( Vector2 ( 1, 0 ), relativeVector );
//rotation of camera in radians
float targetRotation = camera.GetFollowTarget ()->GetTransform ().GetRotation() * (M_PI / 180);
//combine rotation of camera and relative vector
float adjustedRotation = angle + targetRotation;
//convert rotation into a unit vector
Vector2 newPos ( cos ( adjustedRotation ), sin ( adjustedRotation ) );
//extend unit vector to be the same distance away from the camera
newPos *= distance;
//return vector origin to screen center
newPos += screenCenter;
shape->SetPosition ( newPos );
Below are some screenshots.
You can consider the blue square as my camera's focus.
The purple circle is (0,0) world coordinates
The arrow is pointing up in world space and as you can see is rendered incorrectly while the camera is below it.
It would be hard to show the rotation in action so you'll have to take my word for it. The rotation works as intended at any position of the camera aside from what I've described.
View while camera is at origin
View while camera is at the mid point of arrow
View while camera is above arrow
Please let me know if there's anything else I can provide that would be helpful.
It dawned on me to google this problem as a math problem more than a programming problem.
The solution to my problem can be found here. Solution
Vector2 shapePos = shape->GetPosition ();
//subtract screen center from point
shapePos.x -= windowWidth/2;
shapePos.y -= windowHeight/2;
float angleInRadians = camera.GetFollowTarget ()->GetTransform ().GetRotation () * ( M_PI / 180 );
//Get coordinates after rotaion
float x = ( shapePos.x * cos ( angleInRadians ) ) - ( shapePos.y * sin ( angleInRadians ) );
float y = ( shapePos.x * sin ( angleInRadians ) ) + ( shapePos.y * cos ( angleInRadians ) );
//add screen center back to point
Vector2 newPos ( x + windowWidth/2, y + windowHeight/2 );
shape->SetPosition ( newPos );
I have severals pairs of points in world space each pair have a different depth. I want to project those points on the near plane of the view frustrum, then recompute their new world position.
note: I want to keep the perspective effect
To do so, I convert the point's location in NDC space. I think that each pair of points on NDC space with the same z value lie on the same plane, parallel to the view direction. So if I set their z value to -1, they should lie on the near plane.
Now that I have thoose new NDC locations I need their world position, I lost the w component by changing the depth, I need to recompute it.
I found this link: unproject ndc
which said that:
wclip * inverse(mvp) * vec4(ndc.xyz, 1.0f) = 1.0f
wclip = 1.0f / (inverse(mvp) * vec4(ndc.xyz, 1.0f))
my full code:
glm::vec4 homogeneousClipSpaceLeft = mvp * leftAnchor;
glm::vec4 homogeneousClipSpaceRight = mvp * rightAnchor;
glm::vec3 ndc_left = homogeneousClipSpaceLeft.xyz() / homogeneousClipSpaceLeft.w;
glm::vec3 ndc_right = homogeneousClipSpaceRight.xyz() / homogeneousClipSpaceRight.w;
ndc_left.z = -1.0f;
ndc_right.z = -1.0f;
float clipWLeft = (1.0f / (inverseMVP * glm::vec4(ndc_left, 1.0f)).w);
float clipWRight = (1.0f / (inverseMVP * glm::vec4(ndc_right, 1.0f)).w);
glm::vec3 worldPositionLeft = clipWLeft * inverseMVP * (glm::vec4(ndc_left, 1.0f));
glm::vec3 worldPositionRight = clipWRight * inverseMVP * (glm::vec4(ndc_right, 1.0f));
It should work in practice but i get weird result, I start with 2 points in world space:
left world position: -116.463 15.6386 -167.327
right world position: 271.014 15.6386 -167.327
left NDC position: -0.59719 0.0790622 -1
right NDC position: 0.722784 0.0790622 -1
final left position: 31.4092 -9.22973 1251.16
final right position: 31.6823 -9.22981 1251.17
mvp
4.83644 0 0 0
0 4.51071 0 0
0 0 -1.0002 -1
-284.584 41.706 1250.66 1252.41
Am I doing something wrong ?
Would you recommend this way to project pair of points to the near plane, with perspective ?
If glm::vec3 ndc_left and glm::vec3 ndc_right are normalized device coordiantes, then the following projects the coordinates to the near plane in normaized device space:
ndc_left.z = -1.0f;
ndc_right.z = -1.0f;
If you want to get the model positon of a point in normalized device space in Cartesian coordinates, then you have to transform the point by the invers model view projection matrix and to devide the x, y and z component by the w component of the result. Not the transfomation by inverseMVP gives a Homogeneous coordinate:
glm::vec4 wlh = inverseMVP * glm::vec4(ndc_left, 1.0f);
glm::vec4 wrh = inverseMVP * glm::vec4(ndc_right, 1.0f);
glm::vec3 worldPositionLeft = glm::vec3( wlh.x, wlh.y, wlh.z ) / wlh.w;
glm::vec3 worldPositionRight = glm::vec3( wrh.x, wrh.y, wrh.z ) / wrh.w;
Note, that the OpenGL Mathematics (GLM) library provides operations vor "unproject". See glm::unProject.
Let's say there is a grid terrain for a game composed of tiles made of two triangles - made from four vertices. How would we find the Y (up) position of a point between the four vertices?
I have tried this:
float diffZ1 = lerp(heights[0], heights[2], zOffset);
float diffZ2 = lerp(heights[1], heights[3], zOffset);
float yPosition = lerp(diffZ1, diffZ2, xOffset);
Where z/yOffset is the z/y offset from the first vertex of the tile in percent / 100. This works for flat surfaces but not so well on bumpy terrain.
I expect this has something to do with the terrain being made from triangles where the above may work on flat planes. I'm not sure, but does anybody know what's going wrong?
This may better explain what's going on here:
In the code above "heights[]" is an array of the Y coordinate of surrounding vertices v0-3.
Triangle 1 is made of vertex 0, 2 and 1.
Triangle 2 is made of vertex 1, 2 and 3.
I wish to find coordinate Y of p1 when its x,y coordinates lay between v0-3.
So I have tried determining which triangle the point is between through this function:
bool PointInTriangle(float3 pt, float3 pa, float3 pb, float3 pc)
{
// Compute vectors
float2 v0 = pc.xz - pa.xz;
float2 v1 = pb.xz - pa.xz;
float2 v2 = pt.xz - pa.xz;
// Compute dot products
float dot00 = dot(v0, v0);
float dot01 = dot(v0, v1);
float dot02 = dot(v0, v2);
float dot11 = dot(v1, v1);
float dot12 = dot(v1, v2);
// Compute barycentric coordinates
float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
// Check if point is in triangle
return (u >= 0.0f) && (v >= 0.0f) && (u + v <= 1.0f);
}
This isn't giving me the results I expected
I am then trying to find the y coordinate of point p1 inside each triangle:
// Position of point p1
float3 pos = input[0].PosI;
// Calculate point and normal for triangles
float3 p1 = tile[0];
float3 n1 = (tile[2] - p1) * (tile[1] - p1); // <-- Error, cross needed
// = cross(tile[2] - p1, tile[1] - p1);
float3 p2 = tile[3];
float3 n2 = (tile[2] - p2) * (tile[1] - p2); // <-- Error
// = cross(tile[2] - p2, tile[1] - p2);
float newY = 0.0f;
// Determine triangle & get y coordinate inside correct triangle
if(PointInTriangle(pos, tile[0], tile[1], tile[2]))
{
newY = p1.y - ((pos.x - p1.x) * n1.x + (pos.z - p1.z) * n1.z) / n1.y;
}
else if(PointInTriangle(input[0].PosI, tile[3], tile[2], tile[1]))
{
newY = p2.y - ((pos.x - p2.x) * n2.x + (pos.z - p2.z) * n2.z) / n2.y;
}
Using the following to find the correct triangle:
if((1.0f - xOffset) <= zOffset)
inTri1 = true;
And correcting the code above to use the correct cross function seems to have solved the problem.
Because your 4 vertices may not be on a plane, you should consider each triangle separately. First find the triangle that the point resides in, and then use the following StackOverflow discussion to solve for the Z value (note the different naming of the axes). I personally like DanielKO's answer much better, but the accepted answer should work too:
Linear interpolation of three 3D points in 3D space
EDIT: For the 2nd part of your problem (finding the triangle that the point is in):
Because the projection of your tiles onto the xz plane (as you define your coordinates) are perfect squares, finding the triangle that the point resides in is a very simple operation. Here I'll use the terms left-right to refer to the x axis (from lower to higher values of x) and bottom-top to refer to the z axis (from lower to higher values of z).
Each tile can only be split in one of two ways. Either (A) via a diagonal line from the bottom-left corner to the top-right corner, or (B) via a diagonal line from the bottom-right corner to the top-left corner.
For any tile that's split as A:
Check if x' > z', where x' is the distance from the left edge of the tile to the point, and z' is the distance from the bottom edge of the tile to the point. If x' > z' then your point is in the bottom-right triangle; otherwise it's in the upper-left triangle.
For any tile that's split as B: Check if x" > z', where x" is the distance from the right edge of your tile to the point, and z' is the distance from the bottom edge of the tile to the point. If x" > z' then your point is in the lower-left triangle; otherwise it's in the upper-right triangle.
(Minor note: Above I assume your tiles aren't rotated in the xz plane; i.e. that they are aligned with the axes. If that's not correct, simply rotate them to align them with the axes before doing the above checks.)
I'm attempting to do ray casting on mouse click with the eventual goal of finding the collision point with a plane. However I'm unable to create the ray. The world is rendered using a frustum and another matrix I'm using as a camera, in the order of frustum * camera * vertex_position. With the top left of the screen as 0,0 I'm able to get the X,Y of the click in pixels. I then use the below code to convert this to the ray:
float x = (2.0f * x_screen_position) / width - 1.0f;
float y = 1.0f - (2.0f * y_screen_position) / height;
Vector4 screen_click = Vector4 (x, y, 1.0f, 1.0f);
Vector4 ray_origin_world = get_camera_matrix() * screen_click;
Vector4 tmp = (inverse(get_view_frustum()) * screen_click;
tmp = get_camera_matrix() * tmp;
Vector4 ray_direction = normalize(tmp);
view_frustum matrix:
Matrix4 view_frustum(float angle_of_view, float aspect_ratio, float z_near, float z_far) {
return Matrix4(
Vector4(1.0/tan(angle_of_view), 0.0, 0.0, 0.0),
Vector4(0.0, aspect_ratio/tan(angle_of_view), 0.0, 0.0),
Vector4(0.0, 0.0, (z_far+z_near)/(z_far-z_near), 1.0),
Vector4(0.0, 0.0, -2.0*z_far*z_near/(z_far-z_near), 0.0)
);
}
When the "camera" matrix is at 0,0,0 this gives the expected results however once I change to a fixed camera position in another location the results returned are not correct at all. The fixed "camera" matrix:
Matrix4(
Vector4(1.0, 0.0, 0.0, 0.0),
Vector4(0.0, 0.70710678118, -0.70710678118, 0.000),
Vector4(0.0, 0.70710678118, 0.70710678118, 0.0),
Vector4(0.0, 8.0, 20.0, 1.000)
);
Because many examples I have found online do not implement a camera in such a way I am unable to found much information to help in this case. Can anyone offer any insight into this or point me in a better direction?
Vector4 tmp = (inverse(get_view_frustum() * get_camera_matrix()) * screen_click; //take the inverse of the camera matrix as well
tmp /= tmp.w; //homogeneous coordinate "normalize" (different to typical normalization), needed with perspective projection or non-linear depth
Vector3 ray_direction = normalize(Vector3(tmp.x, tmp.y, tmp.z)); //make sure to normalize just the direction without w
[EDIT]
A more lengthy and similar post is here: https://stackoverflow.com/a/20143963/1888983
If you only have matrices, a start point and point in the ray direction should be used. It's common to use points on the near and far plane for this (an advantage is if you only want the ray to intersect things that are visible). That is,
(x, y, -1, 1) to (x, y, 1, 1)
These points are in normalized device coordinates (NDC, a -1 to 1 cube that is your viewing volume). All you need to do is move both points all the way to world space and normalize...
ndcPoint4 = /* from above */;
eyespacePoint4 = inverseProjectionMatrix * ndcPoint4;
worldSpacePoint4 = inverseCameraMatrix * eyespacePoint4;
worldSpacePoint3 = worldSpacePoint4.xyz / worldSpacePoint4.w;
//alternatively, with combined matrices
worldToClipMatrix = projectionMatrix * cameraMatrix; //called "clip" space before normalization
clipToWorldMatrix = inverse(worldToClipMatrix);
worldSpacePoint4 = clipToWorldMatrix * ndcPoint4;
worldSpacePoint3 = worldSpacePoint4.xyz / worldSpacePoint4.w;
//then for the ray, after transforming both start/end points
rayStart = worldPointOnNearPlane;
rayEnd = worldPointOnFarPlane;
rayDir = rayEnd - rayStart;
If you have the camera's world space position, you can drop either start or end point since all rays pass through the camera's origin.
I want to calculate AABB (axis aligned bounding box) from my Box class.
The box class:
Box{
Point3D center; //x,y,z
Point3D halfSize; //x,y,z
Point3D rotation; //x,y,z rotation
};
The AABB class (Box, but without rotation):
BoundingBox{
Point3D center; //x,y,z
Point3D halfSize; //x,y,z
};
Ofc, when rotation = (0,0,0), BoundingBox = Box. But how to calculate minimum BoundingBox that contains everything from Box when rotation = (rx,ry,rz)?
If somebody asks: the rotation is in radians and I use it in DirectX matrix rotation:
XMMATRIX rotX = XMMatrixRotationX( rotation.getX() );
XMMATRIX rotY = XMMatrixRotationY( rotation.getY() );
XMMATRIX rotZ = XMMatrixRotationZ( rotation.getZ() );
XMMATRIX scale = XMMatrixScaling( 1.0f, 1.0f, 1.0f );
XMMATRIX translate = XMMatrixTranslation( center.getX(), center.getY(), center.getZ() );
XMMATRIX worldM = scale * rotX * rotY * rotZ * translate;
You can use matrix rotations in Cartesian coordinates. A rotation of an angle A around the x axis is defined by the matrix:
1 0 0
Rx(A) = 0 cos(A) -sin(A)
0 sin(A) cos(A)
If you do the same for an angle B around y and C around z you have:
cos(B) 0 sin(B)
Ry(B) = 0 1 0
-sin(B) 0 cos(A)
and
cos(C) -sin(C) 0
Rz(C) = sin(C) cos(C) 0
0 0 1
With this you can calculate (even analytically) the final rotation matrix. Let's say that you rotate (in that order) along z, then along y then along x (note that the axis x,y,z are fixed in space, they do not rotate at each rotation). The final matrix is the product:
R = Rx(A) Ry(B) Rz(C)
Now you can construct vectors with the positions of the six corners and apply the full rotation matrix to these vectors. This will give the positions of the six corners in the rotated version. Then just calculate the distance between opposing corners and you have the new bounding box dimensions.
Well, you should apply the rotation on the vertices of the original bounding box (for the purposes of the calculation), then iterate over all of them to find the min and max x, y and z of all the vertices. That would define your axis-aligned bounding box. That's it at its most basic form, you should try and figure out the details. I hope that's a good start. :)