我正在制作一个软件光栅化程序,但遇到了一些麻烦:我似乎无法使透视正确的纹理映射正常工作。
我的算法是先按y
对要绘制的坐标进行排序。这将返回最高,最低和中心点。然后,我使用delta遍历扫描线:
// ordering by y is put here
order[0] = &a_Triangle.p[v_order[0]];
order[1] = &a_Triangle.p[v_order[1]];
order[2] = &a_Triangle.p[v_order[2]];
float height1, height2, height3;
height1 = (float)((int)(order[2]->y + 1) - (int)(order[0]->y));
height2 = (float)((int)(order[1]->y + 1) - (int)(order[0]->y));
height3 = (float)((int)(order[2]->y + 1) - (int)(order[1]->y));
// x
float x_start, x_end;
float x[3];
float x_delta[3];
x_delta[0] = (order[2]->x - order[0]->x) / height1;
x_delta[1] = (order[1]->x - order[0]->x) / height2;
x_delta[2] = (order[2]->x - order[1]->x) / height3;
x[0] = order[0]->x;
x[1] = order[0]->x;
x[2] = order[1]->x;
然后,我们从
order[0]->y
渲染为order[2]->y
,将x_start
和x_end
增加一个增量。渲染顶部时,增量为x_delta[0]
和x_delta[1]
。渲染底部时,增量为x_delta[0]
和x_delta[2]
。然后,我们在扫描线上的x_start和x_end之间进行线性插值。以相同的方式对UV坐标进行插值,并按y顺序从开始和结束开始,并在每个步骤中将其应用于增量。除非我尝试进行透视正确的UV贴图,否则此方法工作正常。基本算法是为每个顶点获取
UV/z
和1/z
并在它们之间进行插值。对于每个像素,UV坐标变为UV_current * z_current
。但是,这是结果:反面的部分告诉您变化量在哪里。如您所见,两个三角形似乎都朝着地平线上的不同点移动。
这是我用来计算空间点上的Z的方法:
float GetZToPoint(Vec3 a_Point)
{
Vec3 projected = m_Rotation * (a_Point - m_Position);
// #define FOV_ANGLE 60.f
// static const float FOCAL_LENGTH = 1 / tanf(_RadToDeg(FOV_ANGLE) / 2);
// static const float DEPTH = HALFHEIGHT * FOCAL_LENGTH;
float zcamera = DEPTH / projected.z;
return zcamera;
}
我说对了,这是z缓冲区问题吗?
最佳答案
ZBuffer与它无关。
ZBuffer仅在三角形重叠且您要确保正确绘制三角形时才有用(例如,在Z中正确排序)。 ZBuffer将为三角形的每个像素确定先前放置的像素是否更靠近相机,如果是,则不绘制三角形的像素。
由于您正在绘制2个不重叠的三角形,因此这不会成为问题。
我曾经(在手机上)定点制作过一个软件光栅化器,但是我的笔记本电脑上没有这些源。所以,让我今晚检查一下,我是如何做到的。本质上,您所拥有的还不错!这样的事情可能是由很小的错误引起的
调试的一般技巧是拥有一些测试三角形(左侧的斜度,右侧的斜度,90度角等),并与调试器一起逐步进行调试,并查看您的逻辑如何处理这些情况。
编辑:
我的光栅化器的伪代码(仅考虑U,V和Z ...如果还想进行gouraud,则还必须对R G和B进行所有操作,类似于对U,V和Z进行的操作:
这个想法是一个三角形可以分解为2个部分。顶部和底部。顶部从y [0]到y [1],底部从y [1]到y [2]。对于这两个集合,您都需要计算要进行插值的步长变量。下面的示例向您展示了如何进行顶部操作。如果需要,我也可以提供底部。
请注意,我已经为下面的“伪代码”片段的底部部分计算了所需的插值偏移量
leftDeltaX =(x [1]-x [0])/(y [1] -y [0])和rightDeltaX =(x [2]-x [0])/(y [2] -y [0] )
代码片段:
if (leftDeltaX < rightDeltaX)
{
leftDeltaX2 = (x[2]-x[1]) / (y[2]-y[1])
rightDeltaX2 = rightDeltaX
leftDeltaU = (u[1]-u[0]) / (y[1]-y[0]) //for texture mapping
leftDeltaU2 = (u[2]-u[1]) / (y[2]-y[1])
leftDeltaV = (v[1]-v[0]) / (y[1]-y[0]) //for texture mapping
leftDeltaV2 = (v[2]-v[1]) / (y[2]-y[1])
leftDeltaZ = (z[1]-z[0]) / (y[1]-y[0]) //for texture mapping
leftDeltaZ2 = (z[2]-z[1]) / (y[2]-y[1])
}
else
{
swap(leftDeltaX, rightDeltaX);
leftDeltaX2 = leftDeltaX;
rightDeltaX2 = (x[2]-x[1]) / (y[2]-y[1])
leftDeltaU = (u[2]-u[0]) / (y[2]-y[0]) //for texture mapping
leftDeltaU2 = leftDeltaU
leftDeltaV = (v[2]-v[0]) / (y[2]-y[0]) //for texture mapping
leftDeltaV2 = leftDeltaV
leftDeltaZ = (z[2]-z[0]) / (y[2]-y[0]) //for texture mapping
leftDeltaZ2 = leftDeltaZ
}
对于我的定点算法,需要使线条和纹理给人以比显示器的分辨率精细得多的步幅移动的错觉(
与U,V和z相同:HalfwayU =(u [2] -u [0])*(y [1] -y [0])/(y [2] -y [0])+ u [0 ]
if(halfwayX-x [1] == 0){坡度U = 0,坡度V = 0,坡度Z = 0}否则{坡度U =(halfwayU-U [1])/(halfwayX-x [1])}//(与v和z相同)
leftCurX = ceil(startx); leftCurY = ceil(endy);
unsigned int buf = destbuf +(ypitch)+ startX; (如果您正在执行24位或32位渲染,则为unsigned int)
还要在这里准备您的ZBuffer指针(如果您正在使用的话)
代码片段:
float tv = startV / startZ
float tu = startU / startZ;
tv %= texturePitch; //make sure the texture coordinates stay on the texture if they are too wide/high
tu %= texturePitch; //I'm assuming square textures here. With fixed point you could have used &=
unsigned int *textPtr = textureBuf+tu + (tv*texturePitch); //in case of fixedpoints one could have shifted the tv. Now we have to multiply everytime.
int destColTm = *(textPtr); //this is the color (if we only use texture mapping) we'll be needing for the pixel
//这是第一部分的结尾。现在,我们绘制了三角形的一半。从顶部到中间的Y坐标。
//现在,我们基本上做了完全相同的事情,但现在是三角形的下半部分(使用另一组插值器)
对“虚拟行”表示抱歉。.他们需要同步 Markdown 代码。 (花了我一段时间让一切看起来都按预期排序)
让我知道这是否可以帮助您解决所面临的问题!
关于c++ - 透视正确的纹理贴图; z距离计算可能错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2068134/