c++ - 透视正确的纹理贴图; z距离计算可能错误

标签 c++ rasterizing zbuffer

我正在制作一个软件光栅化程序,但遇到了一些麻烦:我似乎无法使透视正确的纹理映射正常工作。

我的算法是先按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_startx_end增加一个增量。渲染顶部时,增量为x_delta[0]x_delta[1]。渲染底部时,增量为x_delta[0]x_delta[2]。然后,我们在扫描线上的x_start和x_end之间进行线性插值。以相同的方式对UV坐标进行插值,并按y顺序从开始和结束开始,并在每个步骤中将其应用于增量。

除非我尝试进行透视正确的UV贴图,否则此方法工作正常。基本算法是为每个顶点获取UV/z1/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]。对于这两个集合,您都需要计算要进行插值的步长变量。下面的示例向您展示了如何进行顶部操作。如果需要,我也可以提供底部。

请注意,我已经为下面的“伪代码”片段的底部部分计算了所需的插值偏移量

  • 首先对coords(x,y,z,u,v)进行排序,以使coord [0] .y
  • 接下来检查是否有两组坐标相同(仅检查x和y)。如果是这样,请不要绘制
  • 异常(exception):三角形是否具有平顶?如果是这样,第一个斜率将是无限
  • exception2:三角形是否具有平坦的底部(是的三角形也可以具有这些平坦的底部; ^)),那么最后一个斜率也将是无限的
  • 计算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
      }
    
  • 在x [0]上同时设置currentLeftX和currentRightX
  • 在leftDeltaU上设置currentLeftU,在leftDeltaV上设置currentLeftV,在leftDeltaZ上设置currentLeftZ
  • 第一个Y范围的计算起点和终点:startY = ceil(y [0]); endY = ceil(y [1])
  • 为子像素精度对y的小数部分进行x,u,v和z的前置步骤(我想这对于浮点数也是必需的)
    对于我的定点算法,需要使线条和纹理给人以比显示器的分辨率精细得多的步幅移动的错觉(
  • 计算x应该在y [1]处的位置:halfwayX =(x [2] -x [0])*(y [1] -y [0])/(y [2] -y [0])+ x [0]
    与U,V和z相同:HalfwayU =(u [2] -u [0])*(y [1] -y [0])/(y [2] -y [0])+ u [0 ]
  • 并使用halfwayX计算U,V和z的步进器:
    if(halfwayX-x [1] == 0){坡度U = 0,坡度V = 0,坡度Z = 0}否则{坡度U =(halfwayU-U [1])/(halfwayX-x [1])}//(与v和z相同)
  • 对Y顶部进行裁剪(因此,计算出三角形顶部不在屏幕上(或在裁剪矩形之外)的情况下,我们将开始绘制的位置)
  • 表示y = startY; y {
  • 是Y在屏幕底部下方吗?停止渲染!
  • 为第一条水平线计算startX和endX
    leftCurX = ceil(startx); leftCurY = ceil(endy);
  • 将要绘制的线裁剪到屏幕的左水平边框(或裁剪区域)
  • 准备一个指向目标缓冲区的指针(每次通过数组索引执行它都太慢了)
    unsigned int buf = destbuf +(ypitch)+ startX; (如果您正在执行24位或32位渲染,则为unsigned int)
    还要在这里准备您的ZBuffer指针(如果您正在使用的话)
  • for(x = startX; x {
  • 现在用于透视图纹理映射(不使用bilineair插值,您可以执行以下操作):

  • 代码片段:
             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
    
  • 虚拟线
  • 虚拟线
  • 虚拟行
  • 可选:检查zbuffer是否在此坐标上先前绘制的像素高于或低于我们的像素。
  • 绘制像素
  • startZ + =斜率Z; startU + = slopeU; startV + =斜率V;//更新所有插值器
  • } x循环的结尾
  • leftCurX + = leftDeltaX; rightCurX + = rightDeltaX; leftCurU + = rightDeltaU; leftCurV + = rightDeltaV; leftCurZ + = rightDeltaZ;//更新Y个插值器
  • } y循环的结尾

    //这是第一部分的结尾。现在,我们绘制了三角形的一半。从顶部到中间的Y坐标。
    //现在,我们基本上做了完全相同的事情,但现在是三角形的下半部分(使用另一组插值器)

  • 对“虚拟行”表示抱歉。.他们需要同步 Markdown 代码。 (花了我一段时间让一切看起来都按预期排序)

    让我知道这是否可以帮助您解决所面临的问题!

    关于c++ - 透视正确的纹理贴图; z距离计算可能错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2068134/

    相关文章:

    c# - DirectX Z 缓冲区问题

    c++ - 以线程安全的方式返回指针

    C++ 属性声明

    c++ - 将信号和槽附加到 QSharedPointer 中的对象

    graphics - 将 3d 球体投影到屏幕上的 2d 圆中

    c - 关于优化 Z 缓冲区实现的建议?

    3d - 在 3ds max 中以 16 位分辨率获取 z-Buffer

    c++ - 在 C++ 中创建自定义可比树状数据结构

    java - 省略屏幕外三角形

    iphone - UICollectionView 单元格和栅格化