我一直在写一个简单的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的多边形填充方法填充每个多边形的点,而是使用java的多边形填充方法填充,每个像素将被单独渲染。但是,我不确定如何约束&#34;一个平面,它不会延伸超过多边形的边界,这对我来说有点棘手,因为我现在的数学对我来说有点太高级了。如果这是它应该做的方式,我可以学习它,我只是想事先确定它是一个可行的方法。
3)将每个多边形分成一组多个点,而不是分成更小的多边形:我认为这个方法会有缺陷,因为良好渲染所需的点数(即每个像素一个3d点,不需要多个同一多边形上的3d点渲染到完全相同的像素上,或者具有太少的3d点,以便像素在渲染过程中被跳过&#34;因z深度而变化,并且计算公式的公式每次移动相机时,放置这些点似乎很难配制并且运行起来很昂贵。
答案 0 :(得分:1)
我认为你误解了Z缓冲区的想法,最接近真相的是你的解决方案。 3) - 将多边形分割为单个像素。
Z缓冲区按像素工作,是的,有很多Z比较,但它只是它的工作原理。您不能简化它以仅使用多边形的特定顶点。
我假设你有一些网格让我们说'颜色'结构,你将用你的渲染填充。这将是您的目标图像。您需要添加另一个具有相同大小的浮动网格 - 这将是您的Z缓冲区。一开始,你用一些大的值填充你的Z缓冲区,比如1000000。
暂时忽略多边形的排序 - Z缓冲区将为您解决。您可以稍后添加排序以测试不同绘图订单之间的性能差异,但不需要它来使其工作。
现在您需要光栅化阶段,您可以在其中传递多边形角并返回此多边形所覆盖的所有像素的列表。你可以使用扫描线计算它,正如你在2)中提到的那样。我建议你只为一个三角形写光栅化并将所有多边形分成三角形,这样可以使你的代码更简单。如果你愿意的话,你可以从这个阶段返回一个像素列表(在这种情况下列表会很慢但是这对于学习目的来说很好。最好在这个阶段直接填充像素网格,而不是在内存中累积这些数据)但是你需要一个重要的改变 - 像素需要正确的Z值,除了X和Y.
当你有这样的光栅化像素列表时,你只需将它们放入像素网格中,这就是Z测试发生的地方。迭代每个像素并:使用像素屏幕位置(X,Y)从Z缓冲区读取当前Z值。如果该值大于当前像素的Z,则在颜色缓冲区中写入像素颜色,在Z缓冲区中写入像素Z.
答案 1 :(得分:0)
使用排序表,Playstation 1使用的算法。
将Z范围分成N个相等大小的部分。
C代码
Triangle *Order[256];
void Clear() {
memset(Order,0,sizeof(Order));
}
void Insert(Triangle *tri) {
int index = (tri->averageZ-zNear) * 256 / (zFar - zNear);
tri->next = Order[index];
Order[index] = tri;
}
void Paint() {
for(int i=255;i>=0;i--)
for(Triangle *tri=Order[i];tri;tri=tri->next)
DrawTriangle(tri);
}
来源:Fabien&#34; ryg&#34; Giesen(Farbrausch成名)有一个题为“#34;当光速不够快&#34;他提出这个想法,包括这个代码。