Culling isn't bad, clipping need a better approach to cut partial triangles.
211 lines
8.6 KiB
C++
211 lines
8.6 KiB
C++
#include "3DRenderer.hpp"
|
|
|
|
#include "../World/DbgCube.hpp"
|
|
#include "../World/Tank.hpp"
|
|
|
|
//#define DISABLE_INVISIBLE_VERTICES
|
|
|
|
|
|
// Rendering pipeline:
|
|
// model matrix (Object SRT) -> view matrix (camera matrix inverted) -> proj matrix -> clipping -> perspective divide -> viewport transformation -> Rasterizer (draw pixels inside projected triangles on 2D screen)
|
|
// object space -> world space -> camera space -> homogeneous clip space -> NDC space -> raster space
|
|
//
|
|
// Rasterizer inputs elements:
|
|
// - texture-buffer (2D array of pixels color value)
|
|
// - z-buffer (2D array of float representing the nearest pixel's depth, all pixels beyond are ignored)
|
|
// - projected vertices-buffer on screen (using vertices-buffer and projection function)
|
|
//
|
|
// Refs:
|
|
// * https://en.wikipedia.org/wiki/3D_projection
|
|
// * https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/overview-rasterization-algorithm.html
|
|
// * https://ktstephano.github.io/rendering/stratusgfx/aabbs
|
|
// * https://en.wikipedia.org/wiki/Clipping_(computer_graphics)
|
|
// * https://www.coranac.com/tonc/text/mode7.htm
|
|
// * https://en.wikipedia.org/wiki/Back-face_culling
|
|
// * https://en.wikipedia.org/wiki/Hidden-surface_determination#Occlusion_culling
|
|
// * https://en.wikipedia.org/wiki/Bounding_volume_hierarchy
|
|
|
|
static bool VertexIsInsideClipSpace(M3D_F4& V);
|
|
|
|
Graphic3DRenderer::Graphic3DRenderer() {
|
|
mRTSize = {1280.f, 324.f};
|
|
|
|
if (mMainCamera == nullptr) {
|
|
mMainCamera = std::make_unique<Camera>();
|
|
mMainCamera->SetPosition(0.0f, 1.5f, -8.0f);
|
|
mMainCamera->SetFrustrum(90.0f, mRTSize.x/mRTSize.y, 1.0f, 100.f);
|
|
mMainCamera->UpdateCamView();
|
|
}
|
|
|
|
// Fill world object list to render
|
|
mRenderList.clear();
|
|
mRenderList.push_back(std::make_shared<ObjectDbgCube>());
|
|
mRenderList.back()->SetPosition(0.f, 0.f, 15.f);
|
|
mRenderList.back()->SetScale(2.0f);
|
|
mRenderList.push_back(std::make_shared<ObjectDbgCube>());
|
|
mRenderList.back()->SetPosition(6.f, 2.f, 2.f);
|
|
mRenderList.back()->SetScale(2.0f);
|
|
mRenderList.push_back(std::make_shared<ObjectDbgCube>());
|
|
mRenderList.back()->SetPosition(-8.f, 5.f, 10.f);
|
|
mRenderList.back()->SetScale(2.0f);
|
|
mRenderList.push_back(std::make_shared<Tank>());
|
|
mRenderList.back()->SetPosition(0.f, 0.f, 0.f);
|
|
mRenderList.back()->SetScale(5.0f);
|
|
}
|
|
|
|
Graphic3DRenderer::~Graphic3DRenderer() {}
|
|
|
|
void Graphic3DRenderer::SetRTSize(unsigned int w, unsigned int h) {
|
|
mRTSize.x = w; mRTSize.y = h;
|
|
mMainCamera->SetFrustrum(90.0f, mRTSize.x/mRTSize.y, 1.0f, 100.f);
|
|
}
|
|
|
|
void Graphic3DRenderer::UpdateCamera(CAMERA_MOVE type, const float value) {
|
|
switch (type) {
|
|
case CAMERA_MOVE_WALK:
|
|
mMainCamera->Walk(value);
|
|
break;
|
|
|
|
case CAMERA_MOVE_STRAFE:
|
|
mMainCamera->Strafe(value);
|
|
break;
|
|
|
|
case CAMERA_MOVE_FLY:
|
|
mMainCamera->Fly(value);
|
|
break;
|
|
|
|
case CAMERA_MOVE_PITCH:
|
|
mMainCamera->Pitch(value);
|
|
break;
|
|
|
|
case CAMERA_MOVE_YAW:
|
|
mMainCamera->Yaw(value);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mMainCamera->UpdateCamView();
|
|
}
|
|
|
|
void Graphic3DRenderer::Draw(sf::RenderTexture& context) {
|
|
sf::BlendMode sBM = sf::BlendNone;
|
|
sf::RenderStates sRS(sBM);
|
|
|
|
// Hardcoded debug movement, TODO: remove it
|
|
UpdateInternalTestObjects();
|
|
|
|
// Load main matrices
|
|
M3D_MATRIX viewMat = mMainCamera->GetView();
|
|
M3D_MATRIX projMat = mMainCamera->GetProj();
|
|
M3D_MATRIX viewProjMat = viewMat * projMat;
|
|
|
|
// Create the frustrum "box"
|
|
M3D_BoundingFrustum camFrustrum(projMat, false);
|
|
camFrustrum.Transform(camFrustrum, M3D_MInverse(viewMat));
|
|
|
|
// Process scene's objects
|
|
for (auto& obj : mRenderList) {
|
|
M3D_BoundingBox projAABB = obj->GetAABB();
|
|
auto oTMat = obj->GetTransform();
|
|
|
|
// Object outside frustrum clipping
|
|
projAABB.Transform(projAABB, oTMat);
|
|
M3D_ContainmentType objInFrustrum = camFrustrum.Contains(projAABB);
|
|
#ifndef DISABLE_INVISIBLE_VERTICES
|
|
if (objInFrustrum != DISJOINT)
|
|
#endif
|
|
{
|
|
size_t vCount = obj->GetObjectVerticesCount();
|
|
auto& oMesh = obj->GetObjectMesh();
|
|
M3D_F4 projVertices[vCount];
|
|
|
|
// Vertices homogeneous clip space transformation
|
|
M3D_V3Transform(
|
|
projVertices, sizeof(M3D_F4),
|
|
reinterpret_cast<const M3D_F3*>(oMesh.vertices.data()), sizeof(Vertex),
|
|
vCount,
|
|
oTMat * viewProjMat
|
|
);
|
|
|
|
// Draw the object indice triangles if visible or partially clipped
|
|
sf::Vertex v_tri[4];
|
|
for (auto& objPt : oMesh.parts) {
|
|
auto indicePtr = static_cast<const uint32_t*>(objPt.indices.data());
|
|
|
|
for (uint32_t i = 0; i < objPt.GetIndicesCount(); i += 3) {
|
|
// Misscontructed indices tree failsafe
|
|
if (i+2 > objPt.GetIndicesCount())
|
|
break;
|
|
|
|
// Triangle clipping
|
|
//TODO: implement complete Cohen-Sutherland algo or similar
|
|
if (VertexIsInsideClipSpace(projVertices[indicePtr[i]]) &&
|
|
VertexIsInsideClipSpace(projVertices[indicePtr[i+1]]) &&
|
|
VertexIsInsideClipSpace(projVertices[indicePtr[i+2]]))
|
|
{
|
|
|
|
M3D_VECTOR V1 = M3D_V4LoadF4(&projVertices[indicePtr[i]]);
|
|
M3D_VECTOR V2 = M3D_V4LoadF4(&projVertices[indicePtr[i+1]]);
|
|
M3D_VECTOR V3 = M3D_V4LoadF4(&projVertices[indicePtr[i+2]]);
|
|
|
|
// Face culling
|
|
M3D_VECTOR faceNormal = M3D_TNormal(V1,V2,V3);
|
|
if (M3D_V4GetX(M3D_V3Dot(V1, faceNormal)) >= 0) {
|
|
// Do the perspective divide
|
|
V1 = M3D_V4Divide(V1, M3D_V4SplatW(V1));
|
|
V2 = M3D_V4Divide(V2, M3D_V4SplatW(V2));
|
|
V3 = M3D_V4Divide(V3, M3D_V4SplatW(V3));
|
|
|
|
V1 = M3D_V3TransformNDCToViewport(V1, 0.f, 0.f, mRTSize.x, mRTSize.y, 1.f, 100.f);
|
|
V2 = M3D_V3TransformNDCToViewport(V2, 0.f, 0.f, mRTSize.x, mRTSize.y, 1.f, 100.f);
|
|
V3 = M3D_V3TransformNDCToViewport(V3, 0.f, 0.f, mRTSize.x, mRTSize.y, 1.f, 100.f);
|
|
|
|
if (objInFrustrum == DISJOINT) {
|
|
v_tri[0].color = sf::Color::Red;
|
|
v_tri[1].color = sf::Color::Red;
|
|
v_tri[2].color = sf::Color::Red;
|
|
} else if (objInFrustrum == INTERSECTS) {
|
|
v_tri[0].color = sf::Color::Yellow;
|
|
v_tri[1].color = sf::Color::Yellow;
|
|
v_tri[2].color = sf::Color::Yellow;
|
|
} else {
|
|
v_tri[0].color = oMesh.vertices[indicePtr[i]].color;
|
|
v_tri[1].color = oMesh.vertices[indicePtr[i+1]].color;
|
|
v_tri[2].color = oMesh.vertices[indicePtr[i+2]].color;
|
|
}
|
|
|
|
v_tri[0].position = sf::Vector2f(M3D_V4GetX(V1), M3D_V4GetY(V1));
|
|
v_tri[1].position = sf::Vector2f(M3D_V4GetX(V2), M3D_V4GetY(V2));
|
|
v_tri[2].position = sf::Vector2f(M3D_V4GetX(V3), M3D_V4GetY(V3));
|
|
v_tri[3] = v_tri[0];
|
|
context.draw(v_tri, 4, sf::LineStrip, sRS);
|
|
//context.draw(v_tri, 3, sf::Triangles, sRS);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Graphic3DRenderer::UpdateInternalTestObjects() {
|
|
static float thetaAngle = 0.31f;
|
|
thetaAngle = thetaAngle >= 6.283185f ? -6.283185f : thetaAngle + 0.004f;
|
|
static float thetaAngle2 = 2.12f;
|
|
thetaAngle2 = thetaAngle2 >= 6.283185f ? -6.283185f : thetaAngle2 + 0.005f;
|
|
static float thetaAngle3 = -4.78f;
|
|
thetaAngle3 = thetaAngle3 >= 6.283185f ? -6.283185f : thetaAngle3 + 0.008f;
|
|
mRenderList[0]->SetRotation(thetaAngle, 0.f, thetaAngle * 0.5f);
|
|
mRenderList[1]->SetRotation(thetaAngle2, 0.f, thetaAngle2 * 0.5f);
|
|
mRenderList[2]->SetRotation(thetaAngle3, 0.f, thetaAngle3 * 0.5f);
|
|
mRenderList[3]->SetRotation(0.f, thetaAngle, 0.f);
|
|
}
|
|
|
|
inline static bool VertexIsInsideClipSpace(M3D_F4& V) {
|
|
return (V.x > -V.w && V.x < V.w &&
|
|
V.y > -V.w && V.y < V.w &&
|
|
V.z > 0 && V.z < V.w
|
|
);
|
|
} |