我一直在编写一个简单的 3d 渲染器,并且一直在研究绘制顺序。引擎将 3d 多边形(按正确绘制顺序的 3d 点组)渲染到 2d 空间中,返回代表给定多边形投影的 2d 点列表。我这样做的方法可能有点不正统,因为我想看看我是否能够自己做,所以我在下面附上了我的投影代码:
public class Camera {
/*position is the position of the camera, x, y, z;
cameraRotation is the rotation of the camera, in the order of rotation about x, rotation about y, rotation about z
the camera initially faces the +x direction
*/
private double focalAngle;
private double[] position, cameraRotation, cameraDirectionVector, cameraXVector, cameraZVector;
private double[][][] rotationMatrices = new double[3][3][3];
private double[][] compoundedRotationMatrices;
public Camera(double[] positionIn, double[] cameraRotationIn, double focalAngleIn){
position = positionIn;
focalAngle = focalAngleIn;
cameraRotation = cameraRotationIn;
updateRotation();
}
private void updateRotation(){
updateRotationMatrices();
updateCameraDirectionVector();
}
private void updateRotationMatrices(){
compoundedRotationMatrices = Matrix.getCompoundedRotationMatrix(cameraRotation[0], cameraRotation[1], cameraRotation[2]);
}
private void updateCameraDirectionVector(){
double[] xVector = {1,0,0};
double[] yVector = {0,-1,0};
double[] zVector = {0,0,1};
cameraDirectionVector = Matrix.vecMultiply(compoundedRotationMatrices, xVector);
cameraXVector = Matrix.vecMultiply(compoundedRotationMatrices, yVector);
cameraZVector = Matrix.vecMultiply(compoundedRotationMatrices, zVector);
}
public ArrayList<int[][]> getPolygonProjections(ArrayList<double[][]> polySets, double screenWidth, double screenHeight){
ArrayList<int[][]> outPoints = new ArrayList();
for(int i = 0; i < polySets.size(); i++){
int[][] polyPoints = new int[2][polySets.get(i).length];
/*in the calculation of proejctions, divide by zeros and NaNs can pop up,
polygonsLegitimate boolean keeps track of whether the polygon being drawn can be drawn without error,
and the while loop stops calcuating the polygon once it determines it cannot be properly drawn
*/
boolean polygonsLegitimate = true;
int j = 0;
while(j < polyPoints[0].length && polygonsLegitimate){
int[] xy = getVectorProjection(polySets.get(i)[j], screenWidth, screenHeight);
if(xy != null){
polyPoints[0][j] = xy[0];
polyPoints[1][j] = xy[1];
}else{
polygonsLegitimate = false;
}
j++;
}
if(polygonsLegitimate){
outPoints.add(polyPoints);
}
}
return outPoints;
}
private int[] getVectorProjection(double[] vector, double screenWidth, double screenHeight){
double[] subVector = Vector.subtract(vector, position);
double zDepth = getZDepthOfVector(subVector);
if(zDepth > 0){
double sliceSize = getSliceSizeAtDepth(zDepth);
double cameraXProj = Vector.dot(subVector, cameraXVector);
double cameraZProj = Vector.dot(subVector, cameraZVector);
double xPercent = (cameraXProj+(sliceSize/2))/sliceSize;
double zPercent = (cameraZProj+(sliceSize/2))/sliceSize;
int[] xy = {(int)(xPercent * screenWidth),(int)((((1-zPercent) * screenWidth))-(screenHeight/2))};
return xy;
}
return null;
}
public double getZDepthOfVector(double[] vector){
return Vector.dot(cameraDirectionVector, vector);
}
private double getSliceSizeAtDepth(double zDepth){
return 2.0*Math.cos(focalAngle)*zDepth;
}
目前,我通过按多边形最近角到相机的距离对三维多边形进行排序来确定绘制顺序,然后按照最远多边形到最近多边形的顺序绘制。但是,由于绘制顺序完全取决于多边形上最近点到相机的距离,因此有时会出现一些极端情况会阻止算法正常工作,如本视频所示:
我对 Z Buffer 做了很多研究,这个概念很简单——实际上与我正在做的非常相似。据我了解,对于每个渲染像素,都会比较在同一像素上渲染的所有点,并显示距离相机最近的 z 深度。但是,鉴于在这种情况下,我正在使用的唯一点是构成每个多边形角的点,我不知道一个比较包含在多边形内的任何点的 z 深度的好方法不仅在角落。
对于这个问题,我有两种可能的解决方案:
1)将每个多边形分成多个较小的多边形。当我在 python 中模拟渲染器时,我从未添加过 Z 深度排序,但我确实将每个多边形划分为多个较小的多边形,以便我可以非常轻松地单独点亮每个多边形,结果如下所示:
然而,这是非常昂贵的,因为许多投影点被多次投影,因为它们的值是通过计算相邻多边形的投影来确定的。也许有一种合法的方法可以解决这个问题,但对我来说这似乎太粗略以至于不正确。
2) 找到每个 3d 多边形所在的平面,将其绑定到多边形的形状,然后求解通过视角定向的各个扫描线与这些平面的交点,然后选择 z 深度最近的交点要在该扫描线的像素处显示的相机。这样,不是每个多边形的点被投影然后使用java的多边形填充方法填充,每个像素将被单独渲染。但是,我不确定如何“绑定”一个平面,使其不会超出多边形的边界,而且我理解起来有点棘手,因为目前数学对我来说有点太高级了。如果这是应该的方式,我可以学习它,我只是想事先确保它是一种可行的方法。
3) 将每个多边形分割成许多点而不是更小的多边形:我认为这种方法会有缺陷,因为良好渲染所需的点数(即每个像素一个 3d 点,不需要有多个 3d 点)相同的多边形形状渲染到完全相同的像素上,或者具有太少的 3d 点以致在渲染过程中“跳过”像素)随 z 深度而变化,并且计算放置这些点的位置的公式似乎很难制定每次移动相机时运行成本很高。