Добрый день. Я занимаюсь софтваре рендерингом- то есть без OpenGL/DirectX растеризую треугольники софтваре. Я использую следующий алгоритм отсечения по передней плоскости. Но в этом случае есть недочет- треугольники пропадают по сторонам и вверху- внизу, то есть способ грубый, просто если одна из вершин треугольника за передней плоскостью, а не перед, значит дистанция до плоскости от вершины отрицательная, если отрицательная дистанция хотя бы у одной вершины- пропустить не рисовать весь треугольник. А как на самом деле более элегантно это решить? Как например в Quake 1 source code кто смотрел? Так что бы без математических наворотов просто и элегантно было. Как правильно реализовать отсечение по передней плоскости?
Заранее спасибо.
for (int j =0; j < tree->polygons.PolygonCount; j++) { vector3 v0, v1, v2; v0 = tree->polygons.PolyList[j].Vertex[0]; v1 = tree->polygons.PolyList[j].Vertex[1]; v2 = tree->polygons.PolyList[j].Vertex[2]; float dist; dist = m_Frustum_Near.a * v0.x + m_Frustum_Near.b * v0.y + m_Frustum_Near.c * v0.z + m_Frustum_Near.d; if( dist < 0) { continue; } dist = m_Frustum_Near.a * v1.x + m_Frustum_Near.b * v1.y + m_Frustum_Near.c * v1.z + m_Frustum_Near.d; if( dist < 0) { continue; } dist = m_Frustum_Near.a * v2.x + m_Frustum_Near.b * v2.y + m_Frustum_Near.c * v2.z + m_Frustum_Near.d; if( dist < 0) { continue; }
Нужно отсекать только если все вершины за плоскостью. Когда ты проецируешь вершины, то есть умножаешь на матрицу поворота и матрицу проекции, у тебя должна получиться вершина в пространстве пирамиды отсечения, где x,y,z - в пределах [-1..1], для z [0..1]
В этом случае для каждой вершины ставишь флаги по какой оси они не попадают в диапазон ( 1+2 - x; 4+8 - y; 16+32 - z). Когда вопрос доходит до растеризации треугольников - сверяешь эти флаги. Если треугольник частично заходит за переднюю или какую либо другую грань отсечения, решаешь это на пиксельном уровне во время отрисовки.
Базовое условие "flag1 & flag2 & flag3 == 0" скажет что треугольник не отсекается одной плоскость.
SmoothBoy
> Как например в Quake 1 source code кто смотрел?
Смотрел очень давно, могу ошибиться, насколько помню там сам треугольник режется так - чтобы попадать полностью на экран. Если не путаю с порталами. От треугольника может остаться полигон, который дальше делиться на треугольники в одной плоскости, по мере нахождения отсечений.
Каждый раз когда ты режешь одной из 6 плоскостей треугольник, у тебя на выходе получиться 1-2 новых треугольника, с которыми ты продолжаешь тоже действие с оставшимися плоскостями. В пространстве "пирамиды отсечения" математика отсечения будет выглядеть упрощенной (по крайней мере при выводе формул можно многое сократить), поскольку каждая из плоскостей параллельна осям.
И да, с начало отсекаешь переднюю и дальнюю плоскости, а потом делишь на Z, чтобы отсечь уже боковые, иначе будут искажения.
Я смотрел доку по BSP OpenGL Doc , но нас сейчас BSP не интересует а интересует одна функция из этого мануала, функция
void Split_Polygon (polygon *poly, plane *part, polygon *&front, polygon *&back);
Подойдет она для этой цели? Делить полигоны на части передней плоскостью отсечения подойдет?
SmoothBoy
> Как правильно реализовать отсечение по передней плоскости?
Я когда-то делал так:
Треугольников на выходе может получиться больше одного (очевидно).
Вершины на входе должны быть после трансформации, но до деления на w (деление произойдет в конце функции).
По сути это https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
Спасибо огромное.
SmoothBoy
> Я смотрел доку по BSP
Причем тут BSP мне не понять. Все гораздо проще.
SmoothBoy
> Делить полигоны на части передней плоскостью отсечения подойдет?
В данном случае деление треугольника плоскостью, которое тривиально до нельзя. Где по условию если отсекается две вершины - на выходе 1 треугольник, если 1 вершина - тогда добавляется еще одна вершина и в результате имеем 2 треугольника. В обоих случаях нужно посчитать 2 раза пересечение плоскости с прямой, которая по факту является началом координат по Z.
То что ты уже посчитал у себя в шапке, это z. Но проще делать всем привычное умножение матрицы на вектор, а не разбрасывать его по отдельным функциям. И работать уже в экранном пространстве.
d = z1/(z1-z2); x2 = lerp(x1,x2,d); // x2 = (x2-x1)*d + x1; y2 = lerp(y1,y2,d); // y2 = (y2-y1)*d + y1; z2 = 0; аналогично vec2 = lerp(vec1, vec2, vec1.z/(vec1.z-vec2.z));
Конечно ты можешь делать все так как тебе лично удобнее и понятнее.
Спасибо foxes, я нашел функцию деления треугольника плоскостью в доке по BSP поэтому и вспомнил, а так BSP тут ясно ни причем. :-) А вот эти формулы что вы описали на какой стадии их использовать? В том смысле что в экранных координатах, или после умножения на mxView * mxProj?
SmoothBoy
Для отсечения по прямоугольнику экрана: Weiler–Atherton polygon clipping algorithm не создает промежуточных вершин, которые могут быть затем отсечены
Каждая из 6 плоскостей отсечения (near, far и по 4м сторонам экрана) может добавить максимум 1 вершину, т.е. из 3угольника получится максимум выпуклый 9угольник (даже меньше, но лень разбираться), который легко триангулировать как фан (012,023,034 итд)
SmoothBoy
> В том смысле что в экранных координатах, или после умножения на mxView *
> mxProj?
Разницы нет (хотя экранные координаты это mxModel * mxView). Полноценно и проще делать после умножения на mxModel * mxView * mxProj, до деления на Z или W. Но это только для дальней и ближней плоскости, чтобы отсечь остальные плоскости так же останется поделить на Z или W чтобы получить "двухмерные", точнее координаты в пространстве пирамиды отсечения. Останется только отрисовать усеченные треугольники без лишних расчетов. Также есть альтернативные варианты отсечения по оставшимся 4 плоскостям - как проверка выхода пикселей за экран/буфер во время отрисовки. То есть, по факту требуется посчитать отсечение только ближней и дальней плоскостей. В остальном ты просто делаешь горизонтальную заливку обходя только пиксели в диапазонах [0..width] [0..height], это просто поскольку эта проверка легко выноситься за циклы отрисовки - буквально четыре условия на тех значениях которые у тебя уже будут посчитаны.
Спасибо.
foxes а можно уточнить про экранные координаты?
Например я знаю экранные координаты что это:
//перед этим код обработка одной вершины, программный рендеринг v.x = v.x / v.z; v.y = v.y / v.z //дальше преобразование в экранные координаты ширина экрана 640 высота 480 vx = v.x * 640.0 / 2.0 + 640.0 / 2.0; vy =-v.y * 480.0 / 2.0 + 480.0 / 2.0; //дальше проводим линии, интерполируем цвета, текстурные координаты т.п.
Вот насколько я знаю это экранные координаты. А почему вы говорите что mxWorld * mxView это есть экранные координаты- это пространство вида.
SmoothBoy
> foxes а можно уточнить про экранные координаты?
Возможно я не совсем правильную терминологию использовал. Screen space -> "экранное пространство" отсюда экранные координаты. То есть экранные 3D и 2D, но из описания остального я надеялся что будет понятно о чем идет речь.
SmoothBoy
> А почему вы говорите что mxWorld * mxView это есть экранные координаты- это
> пространство вида.
Фактически для рендеринга достаточно общей матрицы "mxModel * mxView * mxProj", Отдельные вариации используются для спецэффектов.
Сама матрица mxProj не производит ни каких изменений влияющих на результат отсечения по плоскости. Только нужно не забыть про W.
w2 = (w2 - w1)*d + w1; - это на самом деле константа для конкретной плоскости отсечения. Проще это все интерполяцией вектора обернуть.
SmoothBoy
> Например я знаю экранные координаты что это:
Эти координаты можно так же назвать проекционными, точнее те что получаются после деления на Z.
SmoothBoy
> //дальше преобразование в экранные координаты ширина экрана 640 высота 480
А далее получаются координаты пикселя - пиксельные координаты. Эти координаты называются экранными чаще для 2D приложений и GUI. А в конкретной реализации софт рендера я бы их назвал буферными.
То есть вариаций и обобщений в разных "культурах" много. И в тех и в других плоскость проекции или ближайшая плоскость отсечения перпендикулярна оси Z.
SmoothBoy
> v.x = v.x / v.z;
> v.y = v.y / v.z
По феншую делается так
Если ты конечно правильно умножение матрицы на вектор делаешь.
https://gamedev.ru/code/forum/?id=16748&page=2&m=3636338#m18
Но этот пример скорее для точки чем для треугольника.
Спасибо!
Я использую BSP tree и добавил функцию Transform_BSP_Tree которая выполняет умножение на матрицы мира, вида, проекции и делает отсечение по передней плоскости. Но что то отсечение не работает. Причем я выполнял этот код по отсечению в другом проекте для проверки (проект без BSP tree) все работает, а у меня не работает. Вот мой код:
void CMeshManager::Transform_BSP_Tree (BSP_tree *tree) { if( !tree) return; ExtractFrustum( ); for ( int j = 0; j < tree->polygons.PolygonCount; j++) { if ( !PolygonInFrustum( 3, tree->polygons.PolyList[j].Vertex)) continue; vertex v0, v1, v2; v0 = tree->polygons.PolyList[j].Vertex[0]; v1 = tree->polygons.PolyList[j].Vertex[1]; v2 = tree->polygons.PolyList[j].Vertex[2]; v0 = mWorld * v0; v1 = mWorld * v1; v2 = mWorld * v2; v0 = mView * v0; v1 = mView * v1; v2 = mView * v2; polygon poly; poly.Vertex[0] = v0; poly.Vertex[1] = v1; poly.Vertex[2] = v2; poly.Texture.TexID = tree->polygons.PolyList[j].Texture.TexID; list front_list; vertex *InTemp[3]; float fZNear = 0.01f; float fZNearTemp = fZNear + 1.0f; int iNumVertsIn=0; bool bV1=false; bool bV2=false; bool bV3=false; if( poly.Vertex[0].z > fZNear ) { iNumVertsIn++; bV1=true; } if( poly.Vertex[1].z > fZNear ) { iNumVertsIn++; bV2=true; } if( poly.Vertex[2].z > fZNear ) { iNumVertsIn++; bV3=true; } if( iNumVertsIn == 1 ) { if( bV1 ) { InTemp[0]= &( poly.Vertex[0]); InTemp[1]= &( poly.Vertex[1]); InTemp[2]= &( poly.Vertex[2]); } if( bV2 ) { InTemp[0]= &( poly.Vertex[1]); InTemp[1]= &( poly.Vertex[0]); InTemp[2]= &( poly.Vertex[2]); } if( bV3 ) { InTemp[0]= &( poly.Vertex[2]); InTemp[1]= &( poly.Vertex[1]); InTemp[2]= &( poly.Vertex[0]); } //Parametric line stuff // p = v0 + v01*t vertex v01 = *InTemp[1] - *InTemp[0]; float t1 = ( ( fZNearTemp - ( *InTemp[0]).z)/v01.z ) ; float newz1 = fZNearTemp; float newx1 = ( *InTemp[0]).x + v01.x * t1; float newy1 = ( *InTemp[0]).y + v01.y * t1; // Second vert point vertex v02 = *InTemp[2] - *InTemp[0]; float t2 = ( ( fZNearTemp - ( *InTemp[0]).z)/v02.z ) ; float newz2 = fZNearTemp; float newx2 = ( *InTemp[0]).x + v02.x * t2; float newy2 = ( *InTemp[0]).y + v02.y * t2; InTemp[2]->x = newx2; InTemp[2]->y = newy2; InTemp[2]->z = newz2; InTemp[1]->x = newx1; InTemp[1]->y = newy1; InTemp[1]->z = newz1; float newtx1 = ( *InTemp[0]).tu + v01.tu * t1; float newty1 = ( *InTemp[0]).tv + v01.tv * t1; float newtx2 = ( *InTemp[0]).tu + v02.tu * t2; float newty2 = ( *InTemp[0]).tv + v02.tv * t2; InTemp[2]->tu = newtx2; InTemp[2]->tv = newty2; InTemp[1]->tu = newtx1; InTemp[1]->tv = newty1; front_list.Add_To_List( &poly); } else if( iNumVertsIn == 2 ) { if( bV1 ) { InTemp[0]= &( poly.Vertex[1]); InTemp[1]= &( poly.Vertex[2]); InTemp[2]= &( poly.Vertex[0]); } if( bV2 ) { InTemp[0]= &( poly.Vertex[0]); InTemp[1]= &( poly.Vertex[2]); InTemp[2]= &( poly.Vertex[1]); } if( bV3 ) { InTemp[0]= &( poly.Vertex[0]); InTemp[1]= &( poly.Vertex[1]); InTemp[2]= &( poly.Vertex[2]); } //Parametric line stuff // p = v0 + v01*t vertex v01 = *InTemp[2] - *InTemp[0]; float t1 = ( ( fZNearTemp - ( *InTemp[0]).z)/v01.z ) ; float newz1 = fZNearTemp; float newx1 = ( *InTemp[0]).x + v01.x * t1; float newy1 = ( *InTemp[0]).y + v01.y * t1; // Second point vertex v02 = *InTemp[2] - *InTemp[1]; float t2 = ( fZNearTemp - ( *InTemp[1]).z)/v02.z ; float newz2 = fZNearTemp; float newx2 = ( *InTemp[1]).x + v02.x * t2; float newy2 = ( *InTemp[1]).y + v02.y * t2; InTemp[2]->x = newx1; InTemp[2]->y = newy1; InTemp[2]->z = newz1; //------------ float newtx1 = ( *InTemp[0]).tu + v01.tu * t1; float newty1 = ( *InTemp[0]).tv + v01.tv * t1; float newtx2 = ( *InTemp[1]).tu + v02.tu * t2; float newty2 = ( *InTemp[1]).tv + v02.tv * t2; InTemp[2]->tu = newtx1; InTemp[2]->tv = newty1; front_list.Add_To_List( &poly); ////////////////// polygon temp; temp.Vertex[0] = vertex( newx1, newy1, newz1); temp.Vertex[1] = vertex( newx2, newy2, newz2); temp.Vertex[2] = ( *InTemp[1]); temp.Vertex[0].tu = newtx1; temp.Vertex[0].tv = newty1; temp.Vertex[1].tu = newtx2; temp.Vertex[1].tv = newty2; temp.Vertex[2].tu = ( *InTemp[1]).tu; temp.Vertex[2].tv = ( *InTemp[1]).tv; temp.Texture.TexID = poly.Texture.TexID; front_list.Add_To_List( &temp); } else if ( iNumVertsIn == 3 ) { front_list.Add_To_List( &poly); } for ( int i = 0; i < front_list.PolygonCount; i++) { vertex v0, v1, v2; v0 = front_list.PolyList[i].Vertex[0]; v1 = front_list.PolyList[i].Vertex[1]; v2 = front_list.PolyList[i].Vertex[2]; v0 = mProj * v0; v0.x = v0.x / v0.z; v0.y = v0.y / v0.z; v1 = mProj * v1; v1.x = v1.x / v1.z; v1.y = v1.y / v1.z; v2 = mProj * v2; v2.x = v2.x / v2.z; v2.y = v2.y / v2.z; //------------------------------------- vector3 edge1,edge2,vcross,vLook; edge1 = v1 - v0; edge2 = v2 - v0; edge1 = Vec3Normalize( edge1); edge2 = Vec3Normalize( edge2); vcross = Vec3Cross( edge1, edge2); vcross = Vec3Normalize( vcross); vLook.x = 0.0f; vLook.y = 0.0f; vLook.z = -1.0f; float angle_cos = Vec3Dot( vcross, vLook); if( angle_cos <= 0.0) //backface culling continue; //------------------------------------- float alpha = ( 0.5f*nViewWidth-0.5f); float beta = ( 0.5f*nViewHeight-0.5f); matrix4x4 mScreen( alpha, 0, 0, 0, 0, -beta, 0, 0, 0, 0, 1, 0, alpha, beta, 0, 1); v0 = mScreen * v0; v1 = mScreen * v1; v2 = mScreen * v2; polygon pt; pt.Texture.TexID = front_list.PolyList[i].Texture.TexID; pt.Vertex[0] = v0; pt.Vertex[1] = v1; pt.Vertex[2] = v2; tree->transformed_polygons.Add_To_List( &pt); } } Transform_BSP_Tree ( tree->front); Transform_BSP_Tree ( tree->back); }
Если точнее то я делаю по этой статье но не получаетсья.
https://xbdev.net/maths_of_3d/rasterization/clipping/index.php