ProtoTank/Engine/Graphics/3DRenderer.cpp

236 lines
9.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "3DRenderer.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
#include "../World/DbgCube.hpp"
#include "../World/Tank.hpp"
//#define DISABLE_AABB_CLIPPING
//#define DISABLE_TRIANGLE_CLIPPING
// 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 VertexClipTest(M3D_F4& V, sf::Vector2f& RTsize, float gb_factor);
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(75.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 invViewMat = M3D_MInverse(viewMat); // aka. camMat
M3D_MATRIX projMat = mMainCamera->GetProj();
M3D_MATRIX viewProjMat = viewMat * projMat;
// Create the frustrum "box"
M3D_BoundingFrustum camFrustrum(projMat, false);
camFrustrum.Transform(camFrustrum, invViewMat);
const float sgRatio = std::tan(mMainCamera->GetLook3f().y);
// -= Draw the sky =-
// To avoid unfilled pixels on screen, the "sky-plane" will be rendered
// all over the screen.
// It's will be useless to use and compute a specific rectangle from the
// size of the screen!
// The sky have an infinite z-depth (any objects will be rendered over).
context.clear(sf::Color::Cyan);
// -= Draw the ground =-
// A simple rectangle shape is used to draw the ground over the sky-plane.
// The ground is draw after the sky, and before any other object.
// Depending of the camera pitch, the ratio sky/ground on screen vary.
// Like the sky, the ground have an infinite z-depth (any objects will
// be rendered over).
sf::RectangleShape gndRect(sf::Vector2f(1280, mRTSize.y * (0.5f - sgRatio)));
gndRect.setPosition(sf::Vector2f(0, mRTSize.y * (0.5f + sgRatio)));
gndRect.setFillColor(sf::Color::Green);
context.draw(gndRect, sRS);
// 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_AABB_CLIPPING
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
#ifndef DISABLE_TRIANGLE_CLIPPING
//TODO: scissor/clipping depending of how many vertices are outside/inside the clipspace, implement complete Cohen-Sutherland algo or CyrusBeck one
if (VertexClipTest(projVertices[indicePtr[i]], mRTSize, 2.5f) &&
VertexClipTest(projVertices[indicePtr[i+1]], mRTSize, 2.5f) &&
VertexClipTest(projVertices[indicePtr[i+2]], mRTSize, 2.5f))
#endif
{
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]]);
// 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);
// Face culling
if (M3D_V4GetX(M3D_TNormal(V1,V2,V3))*0.5f <= 0) {
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 VertexClipTest(M3D_F4& V, sf::Vector2f& RTsize, float gb_factor) {
// Guard band are usually 2-3x the viewport size for the clipping test
return (V.x > -RTsize.x*gb_factor*V.w && V.x < RTsize.y*gb_factor*V.w &&
V.y > -RTsize.x*gb_factor*V.w && V.y < RTsize.y*gb_factor*V.w
);
}