관점 정확한 텍스처 매핑; z 거리 계산이 잘못 될 수 있습니다
-
20-09-2019 - |
문제
나는 소프트웨어 레스터 라이저를 만들고 있으며 약간의 걸려 넘어졌다 : 나는 원근법에 대한 텍스처 매핑을 작동시키는 것 같다.
내 알고리즘은 먼저 줄거리를 플롯 할 좌표를 정렬하는 것입니다. y
. 이것은 가장 높고 가장 낮은 중심점을 반환합니다. 그런 다음 델타를 사용하여 스캔 라인을 가로 질러 걷습니다.
// 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;
}
맞아요, AZ 버퍼 문제입니까?
해결책
Zbuffer는 그것과 아무 관련이 없습니다.
Zbuffer는 삼각형이 겹치는 경우에만 유용하며 올바르게 그려지는지 확인하려고합니다 (예 : Z에서 올바르게 주문). Zbuffer는 삼각형의 모든 픽셀에 대해 이전에 배치 된 픽셀이 카메라에 가까운 지 여부를 결정하고 그렇다면 삼각형의 픽셀을 그리지 않습니다.
겹치지 않는 2 개의 삼각형을 그리기 때문에 이것은 문제가 될 수 없습니다.
나는 고정 지점으로 소프트웨어 레스터 라이저를 한 번 (휴대 전화의 경우) 만들었지 만 노트북에 소스가 없습니다. 그러니 오늘 밤 내가 어떻게했는지 확인하겠습니다. 본질적으로 당신이 가진 것은 나쁘지 않습니다! 이와 같은 것은 매우 작은 오류로 인해 발생할 수 있습니다.
디버깅의 일반적인 팁이를 통해 몇 가지 테스트 삼각형 (슬로프 왼쪽, 슬로프 오른쪽, 90도 각도 등)을 갖추고 디버거를 사용하여 논리가 케이스를 어떻게 처리하는지 확인하는 것입니다.
편집하다:
Rasterizer의 Peudocode (U, V 및 Z 만 고려됩니다 ... Gouraud를 원한다면 RG 및 B를 위해 모든 작업을 수행해야합니다.
아이디어는 삼각형을 2 부분으로 분해 할 수 있다는 것입니다. 상단 부분과 하단 부분. 상단은 y [0]에서 Y [1]이고 하단 부분은 y [1]에서 Y [2]입니다. 두 세트 모두 보간중인 단계 변수를 계산해야합니다. 아래 예제는 상단 부분을 수행하는 방법을 보여줍니다. 필요한 경우 하단 부분도 제공 할 수 있습니다.
아래 '의사 코드'조각에서 바닥 부분에 필요한 보간 오프셋을 이미 계산합니다.
- 먼저 코디 (x, y, z, u, v)를 순서대로 주문하여 coord [0] .y <coord [1] .y <coord [2] .y.
- 다음으로 2 개의 좌표 세트가 동일한지 확인하십시오 (x 및 y 만 확인). 그렇다면 그리지 마십시오
- 예외 : 삼각형에는 평평한 상단이 있습니까? 그렇다면 첫 번째 경사는 무한합니다
- 예외 2 : 삼각형의 바닥이 평평한가?
- 두 개의 경사 (왼쪽 및 오른쪽)를 계산합니다.
LeftDeltax = (x [1] -x [0]) / (y [1] -y [0]) 및 RightDeltax = (x [2] -x [0]) / (y [2] -y [0] ) - 삼각형의 두 번째 부분은 다음에 따라 계산됩니다. 삼각형의 왼쪽이 실제로 leftside에있는 경우 (또는 교환이 필요한 경우)
코드 조각 :
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를 설정합니다.
- LeftDeltav에서 CurrentLeftu, LeftDeltav에서 CurrentLeftv 및 LeftDeltaz의 CurrentLeftz를 설정합니다.
- 첫 번째 y 범위에 대한 계산 시작 및 종말점 : Starty = Ceil (y [0]); endy = CEIL (Y [1])
- 서브 픽셀 정확도에 대한 y의 분수 부분에 대한 Prestep x, u, v 및 z (플로트에도 필요하다고 생각합니다) 내 고정 점 알고리즘에 대한 라인과 텍스처를 만들기 위해 필요했습니다. 디스플레이의 해상도)
- x가 y [1]에 있어야하는 위치를 계산하십시오. [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) {slopeu = 0, slopev = 0, slopez = 0} else {slopeu = (Halfwayu -u [1 ]) /(Halfwayx -x [1])} // (및 v와 z에 대해서도 동일)
- y 상단을 클리핑하십시오 (삼각형 상단이 화면에서 오프가있는 경우 (또는 클리핑 사각형)가있는 경우, 우리가 그려지기 시작하는 곳을 계산하십시오).
- y = 스타디; y <endy; y ++) {
- 화면의 바닥이 과거입니까? 렌더링 중지!
- 첫 번째 수평선 LeftCurx = Ceil (startx)의 Calc Startx 및 Endx; LeftCury = CEIL (Endy);
- 화면 (또는 클리핑 영역)의 왼쪽 수평 테두리로 그릴 라인을 클립합니다.
- 대상 버퍼에 대한 포인터 준비 (매번 배열 인덱스를 통해 너무 느리게 수행) 설계되지 않은 int buf = destbuf + (y피치) + startx; (24 비트 또는 32 비트 렌더링을 수행하는 경우 서명되지 않은 int) 여기에서 Zbuffer 포인터를 준비하십시오 (이 사용중인 경우)
- for (x = startx; x <endx; 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 += 슬로프; startu+= 경사; startv += slopev; // 모든 보간기를 업데이트합니다
- } x 루프의 끝
- LeftCurx+= LeftDeltax; RightCurx += RightDeltax; LeftCuru+= RightDeltau; LeftCurv += RightDeltav; LeftCurz += RightDeltaz; // y 보간기 업데이트
- 더미 라인
} y 루프의 끝
// 이것이 첫 번째 부분의 끝입니다. 우리는 이제 삼각형의 절반을 그렸습니다. 상단에서 중간 y 좌표로. // 이제 우리는 기본적으로 똑같은 일을하지만 이제 삼각형의 하단 절반 (다른 보간기 세트 사용).
'더미 라인'에 대해 죄송합니다. 마크 다운 코드를 동기화해야했습니다. (의도 한대로 모든 것을 분류하는 데 시간이 걸렸습니다)
이것이 당신이 직면 한 문제를 해결하는 데 도움이된다면 알려주세요!
다른 팁
나는 당신의 질문에 도움을 줄 수 있다는 것을 모르겠지만, 당시에 읽은 소프트웨어 렌더링에 관한 최고의 책 중 하나는 온라인으로 제공됩니다. 그래픽 프로그래밍 블랙 북 Michael Abrash.
보간 된 경우 1/z
, 당신은 곱해야합니다 UV/z
~에 의해 z
, 아니다 1/z
. 당신이 이것을 가지고 있다고 가정합니다 :
UV = UV_current * z_current
그리고 z_current
보간 중입니다 1/z
, 당신은 그것을 변경해야합니다.
UV = UV_current / z_current
그런 다음 이름을 바꾸고 싶을 수도 있습니다 z_current
같은 것입니다 one_over_z_current
.