我正在尝试使用Java和OpenGL中的clip space approach(LWJGL)实现视锥体剔除。 对于投影矩阵,我使用以下方法:
private Matrix4f createPerspectiveProjection(final float fov, final float aspect, final float zNear, final float zFar) {
Matrix4f mat = new Matrix4f();
float yScale = 1f / (float) Math.tan(Math.toRadians(fov / 2f));
float xScale = yScale / aspect;
float frustumLength = zFar - zNear;
mat.m00 = xScale;
mat.m11 = yScale;
mat.m22 = -((zFar + zNear) / frustumLength);
mat.m23 = -1;
mat.m32 = -((2 * zFar * zNear) / frustumLength);
mat.m33 = 0;
return mat;
}
视锥体的系数是这样提取的:
Vector4f[] coefficients = new Vector4f[6];
Matrix4f a = Matrix4f.mul(projection, view, null);
// left
coefficients[0] = new Vector4f(
a.m00 + a.m03,
a.m10 + a.m13,
a.m20 + a.m23,
a.m30 + a.m33);
// right
coefficients[1] = new Vector4f(
-a.m00 + a.m03,
-a.m10 + a.m13,
-a.m20 + a.m23,
-a.m30 + a.m33);
// bottom
coefficients[2] = new Vector4f(
a.m01 + a.m03,
a.m11 + a.m13,
a.m21 + a.m23,
a.m31 + a.m33);
// top
coefficients[3] = new Vector4f(
-a.m01 + a.m03,
-a.m11 + a.m13,
-a.m21 + a.m23,
-a.m31 + a.m33);
// near
coefficients[4] = new Vector4f(
a.m02 + a.m03,
a.m12 + a.m13,
a.m22 + a.m23,
a.m32 + a.m33);
// far
coefficients[5] = new Vector4f(
-a.m02 + a.m03,
-a.m12 + a.m13,
-a.m22 + a.m23,
-a.m32 + a.m33);
for (int i = 0; i < 6; i++) {
coefficients[i].normalise();
}
使用以下方法进行测试,不论截头体是否包含给定点。
public boolean contains(final Vector4f v) {
for (int i = 0; i < 6; i++) {
float value = Vector4f.dot(coefficients[i], v);
if (value < 0.0f) {
return false;
}
}
return true;
}
问题是此方法返回的结果与预期不符。所以有时它是假的,虽然这个点在截头锥体的中间......
我希望你能帮助我。
这是一个小型测试程序,用于演示此问题。 FrustumTest类:
package frustum;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Vector4f;
public class FrustumTest {
private final int WIDTH = 800;
private final int HEIGHT = 600;
private final float SPEED = 2.0f;
private Matrix4f projection;
private Matrix4f camera;
private Matrix4f viewProjection;
private Frustum frustum;
private Vector3f[] quad;
private Vector3f position = new Vector3f();
public void create() {
projection = createPerspectiveProjection(45.0f, 1.0f, 0.1f, 100.0f);
camera = new Matrix4f();
frustum = new Frustum(projection);
quad = new Vector3f[4];
quad[0] = new Vector3f(-0.5f, 0.5f, -2.0f);
quad[1] = new Vector3f(-0.5f, -0.5f, -2.0f);
quad[2] = new Vector3f(0.5f, -0.5f, -2.0f);
quad[3] = new Vector3f(0.5f, 0.5f, -2.0f);
}
public void update(final float deltaTime) {
// Position
Vector3f deltaPosition = new Vector3f();
boolean changed = false;
if (Keyboard.isKeyDown(Keyboard.KEY_W)) {
deltaPosition.translate(0, 0, -SPEED * deltaTime);
changed = true;
}
if (Keyboard.isKeyDown(Keyboard.KEY_S)) {
deltaPosition.translate(0, 0, SPEED * deltaTime);
changed = true;
}
if (Keyboard.isKeyDown(Keyboard.KEY_A)) {
deltaPosition.translate(-SPEED * deltaTime, 0, 0);
changed = true;
}
if (Keyboard.isKeyDown(Keyboard.KEY_D)) {
deltaPosition.translate(SPEED * deltaTime, 0, 0);
changed = true;
}
if (changed || viewProjection == null) {
position = Vector3f.add(position, deltaPosition, null);
camera.setIdentity();
camera.translate(new Vector3f(position));
Matrix4f view = Matrix4f.invert(camera, null);
viewProjection = Matrix4f.mul(projection, view, null);
frustum.update(view);
System.out.println(position);
}
}
public void render() {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GL11.glBegin(GL11.GL_QUADS);
for (int i = 0; i < 4; i++) {
Vector4f v = Matrix4f.transform(viewProjection, new Vector4f(quad[i].x, quad[i].y, quad[i].z, 1.0f), null);
if (frustum.contains(v)) {
GL11.glColor3f(0, 1.0f, 0);
} else {
GL11.glColor3f(1.0f, 0, 0);
}
GL11.glVertex3f(v.x / v.w, v.y / v.w, v.z / v.w);
}
GL11.glEnd();
}
private Matrix4f createPerspectiveProjection(final float fov, final float aspect, final float zNear, final float zFar) {
Matrix4f mat = new Matrix4f();
float yScale = 1f / (float) Math.tan(Math.toRadians(fov / 2f));
float xScale = yScale / aspect;
float frustumLength = zFar - zNear;
mat.m00 = xScale;
mat.m11 = yScale;
mat.m22 = -((zFar + zNear) / frustumLength);
mat.m23 = -1;
mat.m32 = -((2 * zFar * zNear) / frustumLength);
mat.m33 = 0;
return mat;
}
private void run() throws LWJGLException {
Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT));
Display.create();
GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GL11.glViewport(0, 0, WIDTH, HEIGHT);
create();
long lastTime = System.currentTimeMillis();
while (!Display.isCloseRequested()) {
long currentTime = System.currentTimeMillis();
float deltaTime = (currentTime - lastTime) / 1000.0f;
lastTime = currentTime;
update(deltaTime);
render();
if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
System.exit(0);
}
Display.update();
Display.sync(60);
}
Display.destroy();
}
public static void main(final String[] args) {
try {
new FrustumTest().run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Class Frustum:
package frustum;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Vector4f;
public class Frustum {
private final Matrix4f projection;
private final Vector4f[] coefficients = new Vector4f[6];
public Frustum(final Matrix4f projection) {
this.projection = projection;
}
public void update(final Matrix4f view) {
Matrix4f a = Matrix4f.mul(projection, view, null);
// left
coefficients[0] = new Vector4f(a.m00 + a.m03, a.m10 + a.m13, a.m20 + a.m23, a.m30 + a.m33);
// right
coefficients[1] = new Vector4f(-a.m00 + a.m03, -a.m10 + a.m13, -a.m20 + a.m23, -a.m30 + a.m33);
// bottom
coefficients[2] = new Vector4f(a.m01 + a.m03, a.m11 + a.m13, a.m21 + a.m23, a.m31 + a.m33);
// top
coefficients[3] = new Vector4f(-a.m01 + a.m03, -a.m11 + a.m13, -a.m21 + a.m23, -a.m31 + a.m33);
// near
coefficients[4] = new Vector4f(a.m02 + a.m03, a.m12 + a.m13, a.m22 + a.m23, a.m32 + a.m33);
// far
coefficients[5] = new Vector4f(-a.m02 + a.m03, -a.m12 + a.m13, -a.m22 + a.m23, -a.m32 + a.m33);
for (int i = 0; i < 6; i++) {
coefficients[i].normalise();
}
}
public boolean contains(final Vector3f v) {
return contains(new Vector4f(v.x, v.y, v.z, 1));
}
public boolean contains(final Vector4f v) {
for (int i = 0; i < 6; i++) {
float value = Vector4f.dot(coefficients[i], v);
if (value < 0.0f) {
return false;
}
}
return true;
}
}