Introduction:
I'm trying to implement frustum culling and for that I created a projectionViewMatrix and then translate the vectors with the matrix. However some of the vectors seem to be incorrectly calculated. (Coordinate system: +x to the right, +y up and +z out of the screen)
Code:
public static void updateFrustum(Camera camera) {
Vector3f[] points = calculateFrustumVertices(camera);
plane[0].setPlane(points[1], points[0], points[2]);
plane[1].setPlane(points[4], points[5], points[7]);
plane[2].setPlane(points[0], points[4], points[3]);
plane[3].setPlane(points[5], points[1], points[6]);
plane[4].setPlane(points[2], points[3], points[6]);
plane[5].setPlane(points[4], points[0], points[1]);
}
private static Vector3f[] calculateFrustumVertices(Camera camera) {
// projectionMatrix was saved once at the beginning
Matrix4f inverseProjView = Matrix4f.mul(projectionMatrix, Maths.createViewMatrix(camera), null);
inverseProjView.invert();
Vector3f[] points = new Vector3f[8];
Vector4f vertex = new Vector4f();
vertex.w = 1;
for (int i = 0; i < 8; i++) {
vertex.x = clipCube[i].x;
vertex.y = clipCube[i].y;
vertex.z = clipCube[i].z;
Matrix4f.transform(inverseProjView, vertex, vertex);
vertex.x /= vertex.w;
vertex.y /= vertex.w;
vertex.z /= vertex.w;
vertex.w /= vertex.w;
points[i] = new Vector3f(vertex);
}
return points;
}
static Matrix4f viewMatrix = new Matrix4f();
public static Matrix4f createViewMatrix(Camera camera) {
viewMatrix.setIdentity();
Matrix4f.rotate((float) Math.toRadians(camera.getPitch()), Maths.xRotation, viewMatrix, viewMatrix);
Matrix4f.rotate((float) Math.toRadians(camera.getYaw()), Maths.yRotation, viewMatrix, viewMatrix);
Matrix4f.rotate((float) Math.toRadians(camera.getRoll()), Maths.zRotation, viewMatrix, viewMatrix);
Maths.reusableVector = camera.getPosition();
Maths.reusableVector2.set(-Maths.reusableVector.x, -Maths.reusableVector.y, -Maths.reusableVector.z);
Matrix4f.translate(Maths.reusableVector2, viewMatrix, viewMatrix);
return viewMatrix;
}
Example output:
For this I stand at the origin (0, 0, 0) and look in the direction of +z. My near plane is 0.001, my far plane is 1000 and the FOV is 60°. The result is (printed out each point in the for-loop in calculateFrustumVertices()):
points[0] = (0, 0, 0)
points[1] = (0, 0, 0)
points[2] = (0, 0, 0)
points[3] = (0, 0, 0)
points[4] = (1127, -591, 1110)
points[5] = (-1114, -591, 1110)
points[6] = (-1114, 668, 1110)
points[7] = (1127, 668, 1110)
Note that the first four points aren't exactly the same but due to the really small near plane distance (0.001) they almost are equal to zero. I left away the decimal places (because they're irreevant).
Problem:
Roughly the shape of the frustum is correct. But in my opinion the points are a bit wrong.
If the origin is at (0, 0, 0) shouldn't the coordinates be
symetric? So for example if the x-value of point 4 and 7 is equal to 1127,
then shouldn't the value of point 5 and 6 be eqal to -1127? (same for y and z).
(Here is a sketch for clarification) If the FOV is 60° and the far plane distance is 1000. Then in my opinion the z-value should be equal to ![zOffset/farPlaneDist=cos(30°)](https://latex.codecogs.com/gif.latex?%5Cfrac%7BzOffset%7D%7BfarPlaneDistance%7D%20%3D%20cos%2830%B0%29%20%5CLeftrightarrow%20zOffset%20%3D%20farPlaneDistance%20%5Ccdot%20cos%2830%29) (I'm not allowed to post links, sry. Maybe someone could edit the post and get rid of the " ` " so that it's a link and not a code block. Thanks!)
If you calculate it you will get zOffset = 866. This's around 300 units smaller then the value I get with the program.
Question:
What am I doing wrong when calculating the points? The basic form is the same, but the points still differ from what they should be. Do I have a mistake somewhere? If you need more information please say so, then I will provide it.
Related
This project is written entirely from scratch in Java. I've just been bored ever since Covid started, so I wanted something that would take up my time, and teach me something cool. I've been stuck on this problem for about a week now though. When I try to use my near plane clipping method it skews the new vertices to the opposite side of the screen, but sometimes times it works just fine.
Failure Screenshot
Success Screenshot
So my thought is maybe that since it works sometimes, I'm just not doing the clipping at the correct time in the pipeline?
I start by face culling and lighting,
Then I apply a Camera View Transformation to the Vertices,
Then I clip on the near plane
Finally I apply the projection matrix and Clip any remaining off screen Triangles
Code:
This calculates the intersection points. Sorry if it's messy or to long I'm not very experienced in coding, my major is physics, not CS.
public Vertex vectorIntersectPlane(Vector3d planePos, Vector3d planeNorm, Vector3d lineStart, Vector3d lineEnd){
float planeDot = planeNorm.dotProduct(planePos);
float startDot = lineStart.dotProduct(planeNorm);
float endDot = lineEnd.dotProduct(planeNorm);
float midPoint = (planeDot - startDot) / (endDot - startDot);
Vector3d lineStartEnd = lineEnd.sub(lineStart);
Vector3d lineToIntersect = lineStartEnd.scale(midPoint);
return new Vertex(lineStart.add(lineToIntersect));
}
public float distanceFromPlane(Vector3d planePos, Vector3d planeNorm, Vector3d vert){
float x = planeNorm.getX() * vert.getX();
float y = planeNorm.getY() * vert.getY();
float z = planeNorm.getZ() * vert.getZ();
return (x + y + z - (planeNorm.dotProduct(planePos)));
}
//When a triangle gets clipped it has 4 possible outcomes
// 1 it doesn't actually need clipping and gets returned
// 2 it gets clipped into 1 new triangle, for testing these are red
// 3 it gets clipped into 2 new triangles, for testing 1 is green, and 1 is blue
// 4 it is outside the view planes and shouldn't be rendered
public void clipTriangles(){
Vector3d planePos = new Vector3d(0, 0, ProjectionMatrix.fNear, 1f);
Vector3d planeNorm = Z_AXIS.clone();
final int length = triangles.size();
for(int i = 0; i < length; i++) {
Triangle t = triangles.get(i);
if(!t.isDraw())
continue;
Vector3d[] insidePoint = new Vector3d[3];
int insidePointCount = 0;
Vector3d[] outsidePoint = new Vector3d[3];
int outsidePointCount = 0;
float d0 = distanceFromPlane(planePos, planeNorm, t.getVerticesVectors()[0]);
float d1 = distanceFromPlane(planePos, planeNorm, t.getVerticesVectors()[1]);
float d2 = distanceFromPlane(planePos, planeNorm, t.getVerticesVectors()[2]);
//Storing distances from plane and counting inside outside points
{
if (d0 >= 0){
insidePoint[insidePointCount] = t.getVerticesVectors()[0];
insidePointCount++;
}else{
outsidePoint[outsidePointCount] = t.getVerticesVectors()[0];
outsidePointCount++;
}
if (d1 >= 0){
insidePoint[insidePointCount] = t.getVerticesVectors()[1];
insidePointCount++;
}else{
outsidePoint[outsidePointCount] = t.getVerticesVectors()[1];
outsidePointCount++;
}
if (d2 >= 0){
insidePoint[insidePointCount] = t.getVerticesVectors()[2];
insidePointCount++;
}else{
outsidePoint[outsidePointCount] = t.getVerticesVectors()[2];
}
}
//Triangle has 1 point still inside view, remove original triangle add new clipped triangle
if (insidePointCount == 1) {
t.dontDraw();
Vertex newVert1 = vectorIntersectPlane(planePos, planeNorm, insidePoint[0], outsidePoint[0]);
Vertex newVert2 = vectorIntersectPlane(planePos, planeNorm, insidePoint[0], outsidePoint[1]);
vertices.add(newVert1);
vertices.add(newVert2);
//Triangles are stored with vertex references instead of the actual vertex object.
Triangle temp = new Triangle(t.getVertKeys()[0], vertices.size() - 2, vertices.size() - 1, vertices);
temp.setColor(1,0,0, t.getBrightness(), t.getAlpha());
triangles.add(temp);
continue;
}
//Triangle has two points inside remove original add two new clipped triangles
if (insidePointCount == 2) {
t.dontDraw();
Vertex newVert1 = vectorIntersectPlane(planePos, planeNorm, insidePoint[0], outsidePoint[0]);
Vertex newVert2 = vectorIntersectPlane(planePos, planeNorm, insidePoint[1], outsidePoint[0]);
vertices.add(newVert1);
vertices.add(newVert2);
Triangle temp = new Triangle(t.getVertKeys()[0], t.getVertKeys()[1], vertices.size() - 1, vertices);
temp.setColor(0, 1, 0, t.getBrightness(), t.getAlpha());
triangles.add(temp);
temp = new Triangle(t.getVertKeys()[0], t.getVertKeys()[1], vertices.size() - 2, vertices);
temp.setColor(0, 0, 1, t.getBrightness(), t.getAlpha());
triangles.add(temp);
continue;
}
}
}
I figured out the problem, The new clipped triangles were not being given the correct vertex references. they were just being given the first vertex of the triangle irregardless of if that was inside the view or not.
I'm currently trying to register touches on the screen in World Space.
I first convert them to normalized Device Coordinates and then try to multiply a point at the near side of the normalized cube (z = -1) and a point at the far side of the normalized cube (z = 1) with the inverted ProjectionViewMatrix to get a Line between them.
My approach so far:
//Calculate ProjectionViewMatrix
Matrix.multiplyMM(projectionViewMatrix,0,perspectiveProjectionMatrix,0,viewMatrix,0);
//Calculate Inverse
Matrix.invertM(invertedProjectionViewMatrix,0,projectionViewMatrix,0);
float[] nearPoint = {x, y, -1, 1};
float[] farPoint = {x, y, 1, 1};
float[] nearPointWorldSpace = new float[4];
float[] farPointWorldSpace = new float[4];
Matrix.multiplyMV(nearPointWorldSpace,0, invertedProjectionViewMatrix,0, nearPoint,0);
Matrix.multiplyMV(farPointWorldSpace,0, invertedProjectionViewMatrix,0, farPoint,0);
perspectiveDevide(nearPointWorldSpace);
perspectiveDevide(farPointWorldSpace);
Where perspectiveDevide is defined as:
private static void perspectiveDevide(float[] vector) {
vector[0] /= vector[3];
vector[1] /= vector[3];
vector[2] /= vector[3];
}
Now what I should get is a near and far point that have the same or very similar X/Y-Coordinates, because my Camera is right above the lookAt and with no angle.
However what I do get is this:
NearPointWorld:
[0] -0.002805814
[1] 0.046295937
[2] 1.9
[3] 9.999999
FarPointWorld:
[0] -2.8057494
[1] 46.294872
[2] -97.99771
[3] 0.010000229
Any Ideas what might be wrong?
EDIT:
Here's my code for the View and Projection Matrix:
Projection:
Matrix.perspectiveM(perspectiveProjectionMatrix,0, 60, (float) width / (float) height, 0.1f, 100f);
View:
Matrix.setLookAtM(viewMatrix,0,
0,0,2,
0,0,0,
0,1,0);
As Nico Schertler pointed out, these results are actuallly reasonable.
To get the correct X/Y Coordinates I had to unproject the screen center.
I'm trying to make a useful/generic 2D polygon class for an OpenGL ES renderer.
When I create a polygon, I give it several parameters:
Polygon(Vector3 centerpoint, int numVertices, float inPolySize)
Then, I try to generate the vertices. This is where i'm having a tough time. I need to determine the number of vertices, get an angle, find the x/y position of that angle, someone take the size into account, AND offset by the position.
OpenGL works with big arrays of data. Nothing is nice like Lists of Vector3's. Instead it's float[] arrays, with the first index being X1, second being Y1, third being Z1, fourth being X2, etc...
final int XPOS = 0;
final int YPOS = 1;
final int ZPOS = 2;
int mvSize = 3; // (x, y, z);
float[] vertices = new float[mvSize * mNumVertices];
for (int verticeIndex = 0; verticeIndex < mNumVertices; verticeIndex++)
{
double angle = 2 * verticeIndex * Math.PI / mNumVertices;
vertices[mvSize * verticeIndex + XPOS] = (((float)Math.cos(angle)) * mPolygonSize) + mPosition.GetX();
vertices[mvSize * verticeIndex + YPOS] = (((float)Math.sin(angle)) * mPolygonSize) + mPosition.GetY();
vertices[mvSize * verticeIndex + ZPOS] = mPolygonSize + mPosition.GetZ();
}
Unfortunatley, my triangle is never quite right. It's skewed a lot, the size doesn't seem right...
I figure i'm throwing the size into the wrong formula, can anyone help?
EDIT:
Here's some sample data
Polygon test = new Polygon( new Vector3(0, 1, 0), 3, .5f);
vertices[0] = -0.25
vertices[1] = 1.4330127
vertices[2] = 0.0
vertices[3] = -0.25
vertices[4] = 0.5669873
vertices[5] = 0.0
vertices[6] = 0.5
vertices[7] = 1.0
vertices[8] = 0.0
vertices[9] = -0.25
vertices[10] = 1.4330127
vertices[11] = 0.0
I can't believe I was this stupid. Basically, my render window was smaller than my screen. If my screen is a rectangle, my render window was a square.
This being the case, any triangle I draw that was up was clipped by my render window. To me, it looked like the triangle was skewed. Really, it was just clipped!
The Java math library takes radians as input, not degrees. I didn't see the angles you were using for your calculation, but if you're not converting to radians from degrees, you will get some skewed looking shapes, and would explain that your calculations are correct, but the expected result is off.
I'm trying to use gluProject with my game. I've found a way to use it but I don't know how to deal with rotation.
Here is the code I made:
public static Vector2 getScreenCoordinates(float x, float y, float z, int height, int width)
{
FloatBuffer screen_coords = GLAllocation.createDirectFloatBuffer(4);
IntBuffer viewport = GLAllocation.createDirectIntBuffer(16);
FloatBuffer modelview = GLAllocation.createDirectFloatBuffer(16);
FloatBuffer projection = GLAllocation.createDirectFloatBuffer(16);
GL11.glGetFloat(2982, modelview);
GL11.glGetFloat(2983, projection);
GL11.glGetInteger(2978, viewport);
boolean result = GLU.gluProject(x, y, z, modelview, projection, viewport, screen_coords);
if (result)
{
float screen_x = screen_coords.get(0);
float screen_y = screen_coords.get(1);
float scrren_z = screen_coords.get(2);
screen_y -= height / 2;
screen_y = -screen_y;
screen_y += height / 2;
return new Vector2(screen_x, screen_y);
}
else
{
System.out.printf("Failed to convert 3D coords to 2D screen coords");
return null;
}
}
So x, y and z are coords in my 3D map. height and width are my window size.
How can I modify it to deal with rotation (yaw and pitch)?
Thanks.
Here is my new code:
public Vector2 worldToScreen(double x, double y, double z, int yaw, int pitch)
{
// Initialize the result buffer
FloatBuffer screen_coords = GLAllocation.createDirectFloatBuffer(4);
// Init the OpenGL buffers
IntBuffer viewport = GLAllocation.createDirectIntBuffer(16);
FloatBuffer modelview = GLAllocation.createDirectFloatBuffer(16);
FloatBuffer projection = GLAllocation.createDirectFloatBuffer(16);
// Add the rotation
GL11.glRotatef(yaw, 0, 0, 1);
GL11.glRotatef(pitch, 1, 0, 0);
// Get the OpenGL data
GL11.glGetFloat(2982, modelview);
GL11.glGetFloat(2983, projection);
GL11.glGetInteger(2978, viewport);
// Remove the rotation
GL11.glRotatef(-yaw, 0, 0, 1);
GL11.glRotatef(-pitch, 1, 0, 0);
// Calculate the screen position
boolean result = GLU.gluProject(x, y, z, modelview, projection, viewport, screen_coords);
if (result)
{
if ( (screen_coords.get(0) < -1.0f) || (screen_coords.get(0) > 1.0f) || (screen_coords.get(1) < -1.0f) || (screen_coords.get(1) > 1.0f) )
return null;
int window_half_width = getWidth() / 2;
int window_half_height = getHeight() / 2;
int screen_x = window_half_width + (window_half_width * -screen_coords.get(0));
int screen_y = window_half_height + (window_half_height * screen_coords.get(1));
System.out.printf("(Full Screen / No bounds) [" +x+ ", " +y+ ", " +z+ "] gives [" +screen_coords.get(0)+ ", " +screen_coords.get(1)+ "]");
System.out.printf("(Every Time) [" +x+ ", " +y+ ", " +z+ "] gives [" +screen_x+ ", " +screen_y+ "]");
return new Vector2(screen_x, screen_y);
}
else
{
System.out.printf("Failed to convert 3D coords to 2D screen coords");
return null;
}
}
Is that correct?
Yaw is a rotation about the y axis (pointing upwards), and pitch is a rotation about the x axis.
GL11.glRotatef(pitch, 1, 0, 0);
GL11.glRotatef(yaw, 0, 1, 0);
It may also be faster to push/pop the the projection matrix, and you should be in the correct matrix mode. (Both your model-view matrix and your projection matrix have to be correct).
GL11.glPushMatrix();
// Camera rotations
GL11.glPopMatrix();
But it's questionable as to why you need to undo the camera tranformations. You should probably just apply them once per frame or viewport. Render your scene with that view, then get the projected coordinate and then change to your 2D rendering mode.
Update
I'd prefer it if you used roll, pitch and yaw of the camera to mean what they actually mean in OpenGL's coordinate system - with respect to the camera. The point is, as long as you have the camera model-view and projection matrices setup correctly, you can project the point. You must already be setting up the camera somewhere. You will be able to see if this is correct, because you can SEE the results. Apply that transformation and you will know it's correct.
Is this correct? It might be? You have to apply all of the transformations. The exact same transformations that give you the camera view you see in game. This includes all translations and rotations. You will know if you are right, because you should already be applying these transformations in-game.
However, What you need to think about is what is the camera projection matrix before hand? If you don't know, call glLoadIdentity() to clear it, and then call your camera transformation logic. You need an accurate model-view and projection matrix - the exact same camera setup you are using in-game. If your model-view or projection matrices are in the wrong state, it just won't work.
Can someone tell me why my camera class isn't working correctly? I set the position vector to (0,0,-10) and the look at vector to (0,0,0) but when I draw something on (0,0,0) it isn't there. I'm very new to vector math and matrix stuff, so I'm betting it's LookThrough() where the problem is.
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Matrix3f;
import org.lwjgl.util.vector.Vector3f;
public class Camera {
//3d vector to store the camera's position in
Vector3f position = null;
Vector3f lookAt = null;
//the rotation around the Y axis of the camera
float yaw = 0;
//the rotation around the X axis of the camera
float pitch = 0;
//the rotation around the Z axis of the camera
float roll = 0;
public Camera(float x, float y, float z)
{
//instantiate position Vector3f to the x y z params.
position = new Vector3f(x, y, z);
lookAt = new Vector3f();
}
public void lookThrough()
{
Matrix3f m = new Matrix3f();
Vector3f out = new Vector3f();
Vector3f.sub(position, lookAt, out);
out.normalise();
//set forward vector
m.m00 = out.x;
m.m01 = out.y;
m.m02 = out.z;
//set right vector
m.m10 = 1;
m.m11 = 0;
m.m12 = 0;
//set up vector
m.m20 = 0;
m.m21 = 1;
m.m22 = 0;
yaw = (float) -(Math.tan(m.m10/m.m00));
pitch = (float) -(Math.tan((-m.m20)/(Math.sqrt(Math.pow(m.m21, 2) + Math.pow(m.m22, 2)))));
roll = (float) -(Math.tan(m.m21/m.m22));
//roatate the pitch around the X axis
GL11.glRotatef(pitch, 1.0f, 0.0f, 0.0f);
//roatate the yaw around the Y axis
GL11.glRotatef(yaw, 0.0f, 1.0f, 0.0f);
//roatate the yaw around the Y axis
GL11.glRotatef(roll, 0.0f, 0.0f, 1.0f);
//translate to the position vector's location
GL11.glTranslatef(position.x, position.y, position.z);
}
}
There are a couple of things about your class that I would highlight:
1) You are fixing your 'right' and 'up' vectors, leaving only one degree of rotational freedom. As the camera re-orients in 3D space these will need to change. As it stands, your calculation for pitch always evaluates to 0, while your calculation to roll always evaluates to tan(1/0).
2) Though I'm not familiar with Java, you appear to be using the calculation (position - lookAt) to derive your forward vector. Ought it not be the reverse? The forward vector points away from the viewer's position.
3) Again - not familiar with java - but calling pow() to do a single multiplication is likely overkill.
1 & 2 could be the cause of your woes. Ultimately I would suggest taking a look at gluLookAt - assuming the GLU library is available in Java. If it is not, then look at the source code for gluLookAt (one variant is available here).