Clean up and some optimising

This commit is contained in:
JackCarterSmith 2024-12-29 13:37:16 +01:00
parent bdf6228679
commit 5b5c04e368
Signed by: JackCarterSmith
GPG Key ID: 832E52F4E23F8F24
7 changed files with 123 additions and 57 deletions

View File

@ -5,8 +5,11 @@
#include "../World/DbgCube.hpp"
#include "../World/Tank.hpp"
#define SF_COLOR_4CHEX(h) sf::Color((uint32_t)h)
//#define DISABLE_AABB_CLIPPING
//#define DISABLE_TRIANGLE_CLIPPING
//#define DISABLE_WIREFRAME_MODE
// Rendering pipeline:
@ -58,7 +61,7 @@ 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);
mMainCamera->SetFrustrum(75.0f, mRTSize.x/mRTSize.y, 1.0f, 100.f);
}
void Graphic3DRenderer::UpdateCamera(CAMERA_MOVE type, const float value) {
@ -107,13 +110,16 @@ void Graphic3DRenderer::Draw(sf::RenderTexture& context) {
M3D_BoundingFrustum camFrustrum(projMat, false);
camFrustrum.Transform(camFrustrum, invViewMat);
const float sgRatio = ComputeSGRatio();
// -= 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);
#ifdef DISABLE_WIREFRAME_MODE
context.clear(SF_COLOR_4CHEX(0x00B5E2FF));
#endif
// -= Draw the ground =-
// A simple rectangle shape is used to draw the ground over the sky-plane.
@ -121,17 +127,26 @@ void Graphic3DRenderer::Draw(sf::RenderTexture& context) {
// 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).
const float sgRatio = ComputeSGRatio();
#ifdef DISABLE_WIREFRAME_MODE
sf::RectangleShape gndRect;
if (mMainCamera->GetPos3f().y >= 0) {
gndRect.setSize(sf::Vector2f(mRTSize.x, mRTSize.y * sgRatio));
gndRect.setPosition(sf::Vector2f(0, mRTSize.y * (1.f - sgRatio)));
gndRect.setPosition(sf::Vector2f(0, mRTSize.y * (1.f - sgRatio) - 1));
} else {
gndRect.setSize(sf::Vector2f(mRTSize.x, mRTSize.y * (1.f - sgRatio)));
gndRect.setPosition(sf::Vector2f(0, 0));
}
gndRect.setFillColor(sf::Color::Green);
gndRect.setFillColor(SF_COLOR_4CHEX(0x009A17FF));
//gndRect.setFillColor(SF_COLOR_4CHEX(0xD5C2A5FF));
context.draw(gndRect, sRS);
#else
sf::Vertex gndLine[2];
gndLine[0].position = sf::Vector2f(0, mRTSize.y * (1.f - sgRatio));
gndLine[0].color = sf::Color::White;
gndLine[1].position = sf::Vector2f(mRTSize.x - 1, mRTSize.y * (1.f - sgRatio));
gndLine[1].color = sf::Color::White;
context.draw(gndLine, 2, sf::Lines);
#endif
// Process scene's objects
for (auto& obj : mRenderList) {
@ -209,8 +224,11 @@ void Graphic3DRenderer::Draw(sf::RenderTexture& context) {
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);
#ifdef DISABLE_WIREFRAME_MODE
context.draw(v_tri, 4, sf::Triangles, sRS);
#else
context.draw(v_tri, 4, sf::LineStrip, sRS);
#endif
}
}
}
@ -233,21 +251,41 @@ void Graphic3DRenderer::UpdateInternalTestObjects() {
}
// Compute the screen ratio between the ground and the sky (aka. Line of Horizon)
inline float Graphic3DRenderer::ComputeSGRatio() {
// FoV angle for Y axis is recovered using frustrum FoV and apply RT screen ratio to it
const float fovYAngleDiv2 = M3D_Deg2Rad(mMainCamera->GetFoV()) * 0.5f;
// Get the camera pitch angle over camera FoV ratio
const float theta = M3D_ScalarASinEst(-mMainCamera->GetLook3f().y);
// Get the camera altitude from the ground
const float altitude = mMainCamera->GetPos3f().y;
float Graphic3DRenderer::ComputeSGRatio() {
static double sgRatio = 0.5f;
static float fovCos = 0.f;
static float fovSin = 0.f;
static float thetaCos = 0.f;
static float fovThetaSin = 0.f;
const bool isCamMoved = mMainCamera->IsCameraMoved();
const bool isFUpdated = mMainCamera->IsFrustrumUpdated();
if (isCamMoved || isFUpdated) {
mMainCamera->ResetUpdateFlags();
// Ground/Sky screen ratio calculation using "simple" trigonometric properties of the
// pinhole (frustrum) camera model.
// The triangle made by the ground plane intersection with the frustum. This intersection
// cross the far plane at some point. Instead of computing the coordinate of the point, we
// directly use the far plane length to get the corresponding ratio for the screen.
double sgRatio = -(altitude * M3D_ScalarCosEst(fovYAngleDiv2) - mMainCamera->GetFarZ() * M3D_ScalarSinEst(fovYAngleDiv2 + theta))
/ (2.f * mMainCamera->GetFarZ() * M3D_ScalarSinEst(fovYAngleDiv2) * M3D_ScalarCosEst(theta));
// FoV angle for Y axis is recovered using frustrum FoV and apply RT screen ratio to it
const float fovYAngleDiv2 = M3D_Deg2Rad(mMainCamera->GetFoV()) * 0.5f;
// Get the camera pitch angle over camera FoV ratio
const float theta = M3D_ScalarASinEst(-mMainCamera->GetLook3f().y);
// Get the camera altitude from the ground
const float altitude = mMainCamera->GetPos3f().y;
fovThetaSin = M3D_ScalarSinEst(fovYAngleDiv2 + theta);
if (isCamMoved)
thetaCos = M3D_ScalarCosEst(theta);
if (isFUpdated) {
fovCos = M3D_ScalarCosEst(fovYAngleDiv2);
fovSin = M3D_ScalarSinEst(fovYAngleDiv2);
}
// Ground/Sky screen ratio calculation using "simple" trigonometric properties of the
// pinhole (frustrum) camera model.
// The triangle made by the ground plane intersection with the frustum. This intersection
// cross the far plane at some point. Instead of computing the coordinate of the point, we
// directly use the far plane length to get the corresponding ratio for the screen.
sgRatio = -(altitude * fovCos - mMainCamera->GetFarZ() * fovThetaSin)
/ (2.f * mMainCamera->GetFarZ() * fovSin * thetaCos);
}
// Clamp
if (sgRatio > 1.f)

View File

@ -11,6 +11,7 @@ void Camera::SetFrustrum(float fov, float r, float zn, float zf) {
this->zf = zf;
M3D_MATRIX pMat = M3D_TransformMatrixFrustrumFovLH(M3D_Deg2Rad(fov), r, zn, zf);
M3D_V4StoreF4x4(&mProjMat, pMat);
frustrumUpdated = true;
}
void Camera::UpdateCamView() {
@ -19,6 +20,7 @@ void Camera::UpdateCamView() {
M3D_VECTOR U = M3D_V4LoadF3(&mUp);
M3D_V4StoreF4x4(&mViewMat, M3D_TransformMatrixCamLookToLH(P, L, U));
camMoved = true;
}
void Camera::LookAt(M3D_VECTOR pos, M3D_VECTOR target, M3D_VECTOR worldUp) {

View File

@ -15,6 +15,8 @@ public:
float GetFoV() const { return fov; }
float GetFarZ() const { return zf; }
const bool IsFrustrumUpdated() const { return frustrumUpdated; }
const bool IsCameraMoved() const { return camMoved; }
M3D_VECTOR GetPos() const { return M3D_V4LoadF3(&mPos); }
M3D_F3 GetPos3f() const { return mPos; }
M3D_VECTOR GetLook() const { return M3D_V4LoadF3(&mLook); }
@ -39,10 +41,15 @@ public:
void Pitch(float angle);
void Yaw(float angle);
void ResetUpdateFlags() { frustrumUpdated = false; camMoved = false;} //TODO: need a proper way to manage this flags
private:
float fov; // It's the Y-FoV!
float zf;
bool frustrumUpdated = true;
bool camMoved = true;
M3D_F4X4 mProjMat = M3D_MIdentity4x4();
M3D_F4X4 mViewMat = M3D_MIdentity4x4();

View File

@ -605,6 +605,7 @@ float M3D_ScalarASin(float Value) noexcept;
float M3D_ScalarASinEst(float Value) noexcept;
float M3D_ScalarACos(float Value) noexcept;
float M3D_ScalarACosEst(float Value) noexcept;
void M3D_ScalarSinCos(float* pSin, float* pCos, float Value) noexcept;
//
// Common values for vector/matrix manipulation

View File

@ -164,3 +164,35 @@ inline float M3D_ScalarACosEst(float Value) noexcept {
// acos(x) = pi - acos(-x) when x < 0
return (nonnegative ? result : M3D_PI - result);
}
inline void M3D_ScalarSinCos(float* pSin, float* pCos, float Value) noexcept {
// Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder.
float quotient = M3D_1DIV2PI * Value;
if (Value >= 0.0f)
quotient = static_cast<float>(static_cast<int>(quotient + 0.5f));
else
quotient = static_cast<float>(static_cast<int>(quotient - 0.5f));
float y = Value - M3D_2PI * quotient;
// Map y to [-pi/2,pi/2] with sin(y) = sin(Value).
float sign;
if (y > M3D_PIDIV2) {
y = M3D_PI - y;
sign = -1.0f;
} else if (y < -M3D_PIDIV2) {
y = -M3D_PI - y;
sign = -1.0f;
} else {
sign = +1.0f;
}
float y2 = y * y;
// 11-degree minimax approximation
*pSin = (((((-2.3889859e-08f * y2 + 2.7525562e-06f) * y2 - 0.00019840874f) * y2 + 0.0083333310f) * y2 - 0.16666667f) * y2 + 1.0f) * y;
// 10-degree minimax approximation
float p = ((((-2.6051615e-07f * y2 + 2.4760495e-05f) * y2 - 0.0013888378f) * y2 + 0.041666638f) * y2 - 0.5f) * y2 + 1.0f;
*pCos = sign * p;
}

View File

@ -1,38 +1,6 @@
#pragma once
inline void M3D_ScalarSinCos(float* pSin, float* pCos, float Value) noexcept {
// Map Value to y in [-pi,pi], x = 2*pi*quotient + remainder.
float quotient = M3D_1DIV2PI * Value;
if (Value >= 0.0f)
quotient = static_cast<float>(static_cast<int>(quotient + 0.5f));
else
quotient = static_cast<float>(static_cast<int>(quotient - 0.5f));
float y = Value - M3D_2PI * quotient;
// Map y to [-pi/2,pi/2] with sin(y) = sin(Value).
float sign;
if (y > M3D_PIDIV2) {
y = M3D_PI - y;
sign = -1.0f;
} else if (y < -M3D_PIDIV2) {
y = -M3D_PI - y;
sign = -1.0f;
} else {
sign = +1.0f;
}
float y2 = y * y;
// 11-degree minimax approximation
*pSin = (((((-2.3889859e-08f * y2 + 2.7525562e-06f) * y2 - 0.00019840874f) * y2 + 0.0083333310f) * y2 - 0.16666667f) * y2 + 1.0f) * y;
// 10-degree minimax approximation
float p = ((((-2.6051615e-07f * y2 + 2.4760495e-05f) * y2 - 0.0013888378f) * y2 + 0.041666638f) * y2 - 0.5f) * y2 + 1.0f;
*pCos = sign * p;
}
namespace M3D_Internal {
#ifdef DISABLE_INTRINSICS
// Round to nearest (even) a.k.a. banker's rounding

View File

@ -8,13 +8,31 @@ It (should be) a "very dirty" 80s-style arcade game with tanks.
I like big-canon, ballistics, retro and simulation, and that's where the initial concept came from.
The similarity with Battlezone (Atari, 1980) or 3D Tank Duel (ZX Spectrum, 1984) only became apparent to me later in the development.
The similarity with Battlezone (Atari, 1980), 3D Tank Duel (ZX Spectrum, 1984) and other tank-like arcade
games, their influence on development is limited to the graphic reference. In fact, I was only able to discover
some of them during my research phase.
The game itself isn't very polished. Mainly focused on game engine design, state-of-the-art rendering, and CPU-accelerated computing techniques.
This code has been written with experimentation and education in mind. It is subject to the MIT license.
I've used some libs for backend "boiler plates" like:
I've used some libs for backend boilerplate like:
- SFML (window management)
- tinyobjloader (OBJ file loading)
- Tweaked DirectXMath (Heavy math duty)
## Why ... ?
Why using SFML? I've really wanted to focus on the projection stuff and geometry constructs in first place.
SFML is a light library, giving a drawing window access and pretty easy to use, nothing more. I'll look on
a direct interface for window management (X11 or gdi32) later...
Why using DirectX for math? Yeah I know GLM exist, but it lacks of SIMD ready functions. And with a MIT license,
the question of the proprietary code issue was quickly answered!
## About
*Actually, 72 coffees have been used to produce this code.*
Feel free to ask question about the code: jackcartersmith@jcsmith.fr