Я пишу простой рендер в C++. Он использует соглашение, аналогичное OpenGL, но не использует OpenGL и DirectX. float3, float4, float4x4
- это мои собственные пользовательские структуры.Пользовательский процессор вершины не работает - ошибка умножения матрицы или что-то еще?
Проблема в том, что когда я установил eye
где-то другим, то 0, 0, 0
, я получаю странные результаты с треугольниками, где я не ожидал бы их увидеть. Я предполагаю, что это из-за неправильной формулы умножения матрицы, неправильного порядка умножения, нормализации или неправильной формулы lookAt/setPerspective
. Но я застрял в этом, и я не могу найти ошибку.
Я загружу несколько иллюстраций/экранов позже, так как теперь у меня нет доступа к ним.
Я использую колонку-обозначение для матриц (matrix[column][row]
), как и OpenGL.
Вот код умножения матриц:
class float4x4 { //[column][row]
float4 columns[4];
public:
float4x4 multiplyBy(float4x4 &b){
float4x4 c = float4x4();
c.columns[0] = float4(
columns[0].x * b.columns[0].x + columns[1].x * b.columns[0].y + columns[2].x * b.columns[0].z + columns[3].x * b.columns[0].w,
columns[0].y * b.columns[0].x + columns[1].y * b.columns[0].y + columns[2].y * b.columns[0].z + columns[3].y * b.columns[0].w,
columns[0].z * b.columns[0].x + columns[1].z * b.columns[0].y + columns[2].z * b.columns[0].z + columns[3].z * b.columns[0].w,
columns[0].w * b.columns[0].x + columns[1].w * b.columns[0].y + columns[2].w * b.columns[0].z + columns[3].w * b.columns[0].w
);
c.columns[1] = float4(
columns[0].x * b.columns[1].x + columns[1].x * b.columns[1].y + columns[2].x * b.columns[1].z + columns[3].x * b.columns[1].w,
columns[0].y * b.columns[1].x + columns[1].y * b.columns[1].y + columns[2].y * b.columns[1].z + columns[3].y * b.columns[1].w,
columns[0].z * b.columns[1].x + columns[1].z * b.columns[1].y + columns[2].z * b.columns[1].z + columns[3].z * b.columns[1].w,
columns[0].w * b.columns[1].x + columns[1].w * b.columns[1].y + columns[2].w * b.columns[1].z + columns[3].w * b.columns[1].w
);
c.columns[2] = float4(
columns[0].x * b.columns[2].x + columns[1].x * b.columns[2].y + columns[2].x * b.columns[2].z + columns[3].x * b.columns[2].w,
columns[0].y * b.columns[2].x + columns[1].y * b.columns[2].y + columns[2].y * b.columns[2].z + columns[3].y * b.columns[2].w,
columns[0].z * b.columns[2].x + columns[1].z * b.columns[2].y + columns[2].z * b.columns[2].z + columns[3].z * b.columns[2].w,
columns[0].w * b.columns[2].x + columns[1].w * b.columns[2].y + columns[2].w * b.columns[2].z + columns[3].w * b.columns[2].w
);
c.columns[3] = float4(
columns[0].x * b.columns[3].x + columns[1].x * b.columns[3].y + columns[2].x * b.columns[3].z + columns[3].x * b.columns[3].w,
columns[0].y * b.columns[3].x + columns[1].y * b.columns[3].y + columns[2].y * b.columns[3].z + columns[3].y * b.columns[3].w,
columns[0].z * b.columns[3].x + columns[1].z * b.columns[3].y + columns[2].z * b.columns[3].z + columns[3].z * b.columns[3].w,
columns[0].w * b.columns[3].x + columns[1].w * b.columns[3].y + columns[2].w * b.columns[3].z + columns[3].w * b.columns[3].w
);
return c;
}
float4 multiplyBy(const float4 &b){
//based on http://stackoverflow.com/questions/25805126/vector-matrix-product-efficiency-issue
float4x4 a = *this; //getTransposed(); ???
float4 result(
dotProduct(a[0], b),
dotProduct(a[1], b),
dotProduct(a[2], b),
dotProduct(a[3], b)
);
return result;
}
inline float4x4 getTransposed() {
float4x4 transposed;
for (unsigned i = 0; i < 4; i++) {
for (unsigned j = 0; j < 4; j++) {
transposed.columns[i][j] = columns[j][i];
}
}
return transposed;
}
};
Где #define dotProduct(a, b) a.getDotProduct(b)
и:
inline float getDotProduct(const float4 &anotherVector) const {
return x * anotherVector.x + y * anotherVector.y + z * anotherVector.z + w * anotherVector.w;
}
Мои VertexProcessor
:
class VertexProcessor {
float4x4 obj2world;
float4x4 world2view;
float4x4 view2proj;
float4x4 obj2proj;
public:
inline float3 tr(const float3 & v) { //in object space
float4 r = obj2proj.multiplyBy(float4(v.x, v.y, v.z, 1.0f/*v.w*/));
return float3(r.x/r.w, r.y/r.w, r.z/r.w); //we get vector in unified cube from -1,-1,-1 to 1,1,1
}
inline void transform() {
obj2proj = obj2world.multiplyBy(world2view);
obj2proj = obj2proj.multiplyBy(view2proj);
}
inline void setIdentity() {
obj2world = float4x4(
float4(1.0f, 0.0f, 0.0f, 0.0f),
float4(0.0f, 1.0f, 0.0f, 0.0f),
float4(0.0f, 0.0f, 1.0f, 0.0f),
float4(0.0f, 0.0f, 0.0f, 1.0f)
);
}
inline void setPerspective(float fovy, float aspect, float nearP, float farP) {
fovy *= PI/360.0f;
float fValue = cos(fovy)/sin(fovy);
view2proj[0] = float4(fValue/aspect, 0.0f, 0.f, 0.0f);
view2proj[1] = float4(0.0f, fValue, 0.0f, 0.0f);
view2proj[2] = float4(0.0f, 0.0f, (farP + nearP)/(nearP - farP), -1.0f);
view2proj[3] = float4(0.0f, 0.0f, 2.0f * farP * nearP/(nearP - farP), 0.0f);
}
inline void setLookat(float3 eye, float3 center, float3 up) {
float3 f = center - eye;
f.normalizeIt();
up.normalizeIt();
float3 s = f.getCrossProduct(up);
float3 u = s.getCrossProduct(f);
world2view[0] = float4(s.x, u.x, -f.x, 0.0f);
world2view[1] = float4(s.y, u.y, -f.y, 0.0f);
world2view[2] = float4(s.z, u.z, -f.z, 0.0f);
world2view[3] = float4(eye/*.getNormalized() ???*/ * -1.0f, 1.0f);
}
inline void multByTranslation(float3 v) {
float4x4 m(
float4(1.0f, 0.0f, 0.0f, 0.0f),
float4(0.0f, 1.0f, 0.0f, 0.0f),
float4(0.0f, 0.0f, 1.0f, 0.0f),
float4(v.x, v.y, v.z, 1.0f)
);
world2view = m.multiplyBy(world2view);
}
inline void multByScale(float3 v) {
float4x4 m(
float4(v.x, 0.0f, 0.0f, 0.0f),
float4(0.0f, v.y, 0.0f, 0.0f),
float4(0.0f, 0.0f, v.z, 0.0f),
float4(0.0f, 0.0f, 0.0f, 1.0f)
);
world2view = m.multiplyBy(world2view);
}
inline void multByRotation(float a, float3 v) {
float s = sin(a*PI/180.0f), c = cos(a*PI/180.0f);
v.normalizeIt();
float4x4 m(
float4(v.x*v.x*(1-c)+c, v.y*v.x*(1 - c) + v.z*s, v.x*v.z*(1-c)-v.y*s, 0.0f),
float4(v.x*v.y*(1-c)-v.z*s, v.y*v.y*(1-c)+c, v.y*v.z*(1-c)+v.x*s, 0.0f),
float4(v.x*v.z*(1-c)+v.y*s, v.y*v.z*(1-c)-v.x*s, v.z*v.z*(1-c)+c, 0.0f),
float4(0.0f, 0.0f, 0.0f, 1.0f)
);
world2view = m.multiplyBy(world2view);
}
};
И Rasterizer
:
class Rasterizer final {
Buffer * buffer = nullptr;
inline float toScreenSpaceX(float x) { return (x + 1) * buffer->getWidth() * 0.5f; }
inline float toScreenSpaceY(float y) { return (y + 1) * buffer->getHeight() * 0.5f; }
inline int orient2d(float ax, float ay, float bx, float by, const float2& c) {
return (bx - ax)*(c.y - ay) - (by - ay)*(c.x - ax);
}
public:
Rasterizer(Buffer * buffer) : buffer(buffer) {}
//v - position in screen space ([0, width], [0, height], [-1, -1])
void triangle(
float3 v0, float3 v1, float3 v2,
float3 n0, float3 n1, float3 n2,
float2 uv0, float2 uv1, float2 uv2,
Light * light0, Light * light1,
float3 camera, Texture * texture
) {
v0.x = toScreenSpaceX(v0.x);
v0.y = toScreenSpaceY(v0.y);
v1.x = toScreenSpaceX(v1.x);
v1.y = toScreenSpaceY(v1.y);
v2.x = toScreenSpaceX(v2.x);
v2.y = toScreenSpaceY(v2.y);
//based on: https://fgiesen.wordpress.com/2013/02/08/triangle-rasterization-in-practice/
//compute triangle bounding box
int minX = MIN3(v0.x, v1.x, v2.x);
int minY = MIN3(v0.y, v1.y, v2.y);
int maxX = MAX3(v0.x, v1.x, v2.x);
int maxY = MAX3(v0.y, v1.y, v2.y);
//clip against screen bounds
minX = MAX(minX, 0);
minY = MAX(minY, 0);
maxX = MIN(maxX, buffer->getWidth() - 1);
maxY = MIN(maxY, buffer->getHeight() - 1);
//rasterize
float2 p(0.0f, 0.0f);
for (p.y = minY; p.y <= maxY; p.y++) {
for (p.x = minX; p.x <= maxX; p.x++) {
// Determine barycentric coordinates
//int w0 = orient2d(v1.x, v1.y, v2.x, v2.y, p);
//int w1 = orient2d(v2.x, v2.y, v0.x, v0.y, p);
//int w2 = orient2d(v0.x, v0.y, v1.x, v1.y, p);
float w0 = (v1.y - v2.y)*(p.x - v2.x) + (v2.x - v1.x)*(p.y - v2.y);
w0 /= (v1.y - v2.y)*(v0.x - v2.x) + (v2.x - v1.x)*(v0.y - v2.y);
float w1 = (v2.y - v0.y)*(p.x - v2.x) + (v0.x - v2.x)*(p.y - v2.y);
w1 /= (v2.y - v0.y)*(v1.x - v2.x) + (v0.x - v2.x)*(v1.y - v2.y);
float w2 = 1 - w0 - w1;
// If p is on or inside all edges, render pixel.
if (w0 >= 0 && w1 >= 0 && w2 >= 0) {
float depth = w0 * v0.z + w1 * v1.z + w2 * v2.z;
if (depth < buffer->getDepthForPixel(p.x, p.y)) {
//...
buffer->setPixel(p.x, p.y, diffuse.r, diffuse.g, diffuse.b, ALPHA_VISIBLE, depth);
}
}
}
}
}
};
Я твердо верю, что сам Rasterizer
работает хорошо, потому что когда я проверить его с кодом (вместо основного контура):
float3 v0{ 0, 0, 0.1f };
float3 v1{ 0.5, 0, 0.1f };
float3 v2{ 1, 1, 0.1f };
//Rasterizer test (without VertexProcessor)
rasterizer->triangle(v0, v1, v2, n0, n1, n2, uv0, uv1, uv2, light0, light1, eye, defaultTexture);
я получить правильное изображение, с треугольником, который имеет один угол в в середине экрана ([0, 0]
в едином пространстве), один в нижнем правом углу ([1, 1]
) и один на [0.5, 0]
.
float3
структура:
class float3 {
public:
union {
struct { float x, y, z; };
struct { float r, g, b; };
float p[3];
};
float3() = delete;
float3(const float3 &other) : x(other.x), y(other.y), z(other.z) {}
float3(float x, float y, float z) : x(x), y(y), z(z) {}
float &operator[](unsigned index){
ERROR_HANDLE(index < 3, L"The float3 index out of bounds (0-2 range, " + C::toWString(index) + L" given).");
return p[index];
}
float getLength() const { return std::abs(sqrt(x*x + y*y + z*z)); }
void normalizeIt();
inline float3 getNormalized() const {
float3 result(*this);
result.normalizeIt();
return result;
}
inline float3 getCrossProduct(const float3 &anotherVector) const {
//based on: http://www.sciencehq.com/physics/vector-product-multiplying-vectors.html
return float3(
y * anotherVector.z - anotherVector.y * z,
z * anotherVector.x - anotherVector.z * x,
x * anotherVector.y - anotherVector.x * y
);
}
inline float getDotProduct(const float3 &anotherVector) const {
//based on: https://www.ltcconline.net/greenl/courses/107/Vectors/DOTCROS.HTM
return x * anotherVector.x + y * anotherVector.y + z * anotherVector.z;
}
...
};
Основной цикл:
VertexProcessor vp;
DirectionalLight * light0 = new DirectionalLight({ 0.3f, 0.3f, 0.3f }, { 0.0f, -1.0f, 0.0f });
DirectionalLight * light1 = new DirectionalLight({ 0.4f, 0.4f, 0.4f }, { 0.0f, -1.0f, 0.5f });
while(!my_window.is_closed()) {
tgaBuffer.clearDepth(10.0f); //it could be 1.0f but 10.0f won't hurt, we draw pixel if it's depth < actual depth in buffer
tgaBuffer.clearColor(0, 0, 255, ALPHA_VISIBLE);
vp.setPerspective(75.0f, tgaBuffer.getWidth()/tgaBuffer.getHeight(), 10.0f, 2000.0f);
float3 eye = { 10.0f, 10.0f - frameTotal/10.0f, 10.0f }; //animate eye
vp.setLookat(eye, float3{ 0.0f, 0.0f, 0.0f }.getNormalized(), { 0.0f, 1.0f, 0.0f });
vp.setIdentity();
//we could call e.g. vp.multByRotation(...) here, but we won't to keep it simple
vp.transform();
//bottom
drawTriangle(0, 1, 2);
drawTriangle(2, 3, 0);
drawTriangle(3, 2, 7);
drawTriangle(7, 2, 6);
drawTriangle(5, 1, 0);
drawTriangle(0, 5, 4);
drawTriangle(4, 5, 6);
drawTriangle(6, 7, 4);
frameTotal++;
}
Где drawTriangle(...)
обозначает:
#define drawTriangle(i0, i1, i2) rasterizer->triangle(vp.tr(v[i0]), vp.tr(v[i1]), vp.tr(v[i2]), v[i0], v[i1], v[i2], n0, n1, n2, uv0, uv1, uv2, light0, light1, eye, defaultTexture);
А вот инициализация данных треугольников:
float3 offset{ 0.0f, 0.0f, 0.0f };
v.push_back(offset + float3{ -10, -10, -10 });
v.push_back(offset + float3{ +10, -10, -10 });
v.push_back(offset + float3{ +10, -10, +10 });
v.push_back(offset + float3{ -10, -10, +10 });
v.push_back(offset + float3{ -10, +10, -10 });
v.push_back(offset + float3{ +10, +10, -10 });
v.push_back(offset + float3{ +10, +10, +10 });
v.push_back(offset + float3{ -10, +10, +10 });
В 'setPerspective' вы используете' fovy * = PI/360.0f; ', но не должно быть' fovy * = PI/180.0f; ' ? – Gene
@Gene Не я принимаю это во внимание позже в 'setPerspective (...)' formula (не умножая на 2 и т. Д.)? – PolGraphic
Да, это могло бы быть правдой. Просто не заметили, извините. – Gene