Я вижу, что этот вопрос немного устарел, но я все равно решил дать ответ тем, кто найдет этот вопрос с помощью поиска.
В настоящее время стандартным способом представления 2D / 3D преобразований является использование однородных координат . [x, y, w] для 2D и [x, y, z, w] для 3D. Поскольку у вас есть три оси в 3D и перевод, эта информация идеально вписывается в матрицу преобразования 4x4. Я буду использовать обозначение матрицы столбцов в этом объяснении. Все матрицы имеют размер 4х4, если не указано иное.
Этапы от трехмерных точек до растеризованной точки, линии или многоугольника выглядят так:
- Преобразуйте ваши 3D-точки с помощью матрицы обратной камеры, а затем выполните любые необходимые преобразования. Если у вас есть поверхностные нормали, также преобразуйте их, но с w, установленным в ноль, так как вы не хотите переводить нормали. Матрица, с которой вы преобразуете нормали, должна быть изотропной ; скейлинг и сдвиг приводят к искажению нормалей.
- Преобразование точки с помощью матрицы пространства клипа. Эта матрица масштабирует x и y с полем зрения и соотношением сторон, масштабирует z по ближним и дальним плоскостям отсечения и вставляет «старый» z в w. После преобразования вы должны разделить x, y и z на w. Это называется перспективный разрыв .
- Теперь ваши вершины находятся в пространстве клипа, и вы хотите выполнить отсечение, чтобы не отображать пиксели за пределами области просмотра. Отсечение по Сазерленду-Ходжману - самый распространенный используемый алгоритм отсечения.
- Преобразуйте x и y относительно w, полуширины и полувысоты. Ваши координаты x и y теперь находятся в координатах области просмотра. w отбрасывается, но 1 / w и z обычно сохраняются, потому что 1 / w требуется для правильной интерполяции по поверхности полигона, а z сохраняется в z-буфере и используется для проверки глубины.
Этот этап является фактической проекцией, поскольку z больше не используется в качестве компонента в позиции.
Алгоритмы:
Расчет поля зрения
Это вычисляет поле зрения. Независимо от того, принимает ли загар радианы или градусы, значение не должно совпадать. Обратите внимание, что результат достигает бесконечности, так как угол приближается к 180 градусам. Это особенность, так как невозможно иметь такой широкий фокус. Если вам нужна числовая стабильность, оставьте angle меньше или равным 179 градусам.
fov = 1.0 / tan(angle/2.0)
Также обратите внимание, что 1.0 / tan (45) = 1. Кто-то еще здесь предложил просто разделить на z. Результат здесь понятен. Вы получите 90 градусов FOV и соотношение сторон 1: 1. Использование подобных однородных координат также имеет несколько других преимуществ; например, мы можем выполнить отсечение для ближней и дальней плоскостей, не рассматривая ее как особый случай.
Расчет матрицы клипа
Это макет матрицы клипа. аспектное отношение - это ширина / высота. Таким образом, FOV для компонента x масштабируется на основе FOV для y. Далеко и близко - коэффициенты, которые являются расстояниями для ближнего и дальнего отсечения.
[fov * aspectRatio][ 0 ][ 0 ][ 0 ]
[ 0 ][ fov ][ 0 ][ 0 ]
[ 0 ][ 0 ][(far+near)/(far-near) ][ 1 ]
[ 0 ][ 0 ][(2*near*far)/(near-far)][ 0 ]
Проекция экрана
После отсечения это последнее преобразование для получения координат экрана.
new_x = (x * Width ) / (2.0 * w) + halfWidth;
new_y = (y * Height) / (2.0 * w) + halfHeight;
Тривиальный пример реализации в C ++
#include <vector>
#include <cmath>
#include <stdexcept>
#include <algorithm>
struct Vector
{
Vector() : x(0),y(0),z(0),w(1){}
Vector(float a, float b, float c) : x(a),y(b),z(c),w(1){}
/* Assume proper operator overloads here, with vectors and scalars */
float Length() const
{
return std::sqrt(x*x + y*y + z*z);
}
Vector Unit() const
{
const float epsilon = 1e-6;
float mag = Length();
if(mag < epsilon){
std::out_of_range e("");
throw e;
}
return *this / mag;
}
};
inline float Dot(const Vector& v1, const Vector& v2)
{
return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}
class Matrix
{
public:
Matrix() : data(16)
{
Identity();
}
void Identity()
{
std::fill(data.begin(), data.end(), float(0));
data[0] = data[5] = data[10] = data[15] = 1.0f;
}
float& operator[](size_t index)
{
if(index >= 16){
std::out_of_range e("");
throw e;
}
return data[index];
}
Matrix operator*(const Matrix& m) const
{
Matrix dst;
int col;
for(int y=0; y<4; ++y){
col = y*4;
for(int x=0; x<4; ++x){
for(int i=0; i<4; ++i){
dst[x+col] += m[i+col]*data[x+i*4];
}
}
}
return dst;
}
Matrix& operator*=(const Matrix& m)
{
*this = (*this) * m;
return *this;
}
/* The interesting stuff */
void SetupClipMatrix(float fov, float aspectRatio, float near, float far)
{
Identity();
float f = 1.0f / std::tan(fov * 0.5f);
data[0] = f*aspectRatio;
data[5] = f;
data[10] = (far+near) / (far-near);
data[11] = 1.0f; /* this 'plugs' the old z into w */
data[14] = (2.0f*near*far) / (near-far);
data[15] = 0.0f;
}
std::vector<float> data;
};
inline Vector operator*(const Vector& v, const Matrix& m)
{
Vector dst;
dst.x = v.x*m[0] + v.y*m[4] + v.z*m[8 ] + v.w*m[12];
dst.y = v.x*m[1] + v.y*m[5] + v.z*m[9 ] + v.w*m[13];
dst.z = v.x*m[2] + v.y*m[6] + v.z*m[10] + v.w*m[14];
dst.w = v.x*m[3] + v.y*m[7] + v.z*m[11] + v.w*m[15];
return dst;
}
typedef std::vector<Vector> VecArr;
VecArr ProjectAndClip(int width, int height, float near, float far, const VecArr& vertex)
{
float halfWidth = (float)width * 0.5f;
float halfHeight = (float)height * 0.5f;
float aspect = (float)width / (float)height;
Vector v;
Matrix clipMatrix;
VecArr dst;
clipMatrix.SetupClipMatrix(60.0f * (M_PI / 180.0f), aspect, near, far);
/* Here, after the perspective divide, you perform Sutherland-Hodgeman clipping
by checking if the x, y and z components are inside the range of [-w, w].
One checks each vector component seperately against each plane. Per-vertex
data like colours, normals and texture coordinates need to be linearly
interpolated for clipped edges to reflect the change. If the edge (v0,v1)
is tested against the positive x plane, and v1 is outside, the interpolant
becomes: (v1.x - w) / (v1.x - v0.x)
I skip this stage all together to be brief.
*/
for(VecArr::iterator i=vertex.begin(); i!=vertex.end(); ++i){
v = (*i) * clipMatrix;
v /= v.w; /* Don't get confused here. I assume the divide leaves v.w alone.*/
dst.push_back(v);
}
/* TODO: Clipping here */
for(VecArr::iterator i=dst.begin(); i!=dst.end(); ++i){
i->x = (i->x * (float)width) / (2.0f * i->w) + halfWidth;
i->y = (i->y * (float)height) / (2.0f * i->w) + halfHeight;
}
return dst;
}
Если вы все еще задумываетесь над этим, спецификация OpenGL - действительно хороший справочник для математики.
На форумах DevMaster на http://www.devmaster.net/ есть много хороших статей, связанных также с программными растеризаторами.