init commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.idea/
|
||||
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
14
CMakeLists.txt
Normal file
14
CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.31)
|
||||
set(PROJECT_NAME Rasterizer)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
project(${PROJECT_NAME})
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
file(GLOB_RECURSE SRC_SOURCE
|
||||
src/**.cpp
|
||||
src/vendor/stb/stb_image.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SRC_SOURCE})
|
||||
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
||||
## build by cmake
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
cmake -B build
|
||||
cmake --build build --config Release -j18
|
||||
```
|
||||
274
src/Application/Application.cpp
Normal file
274
src/Application/Application.cpp
Normal file
@ -0,0 +1,274 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
#include "../Core/Renderer/Renderer.h"
|
||||
#include "../Shader/DemoShader.h"
|
||||
|
||||
|
||||
|
||||
Application::Application(std::string name, const int width, const int height, CommandLineArgs args)
|
||||
: m_Name(std::move(name)),m_Width(width), m_Height(height), m_Window(nullptr)
|
||||
{
|
||||
if (args.argc != 2)
|
||||
{
|
||||
std::string programName = args.argv[0];
|
||||
std::cout << "should in command to use it\n" << std::endl;
|
||||
std::cout << " Usage: " << programName.substr(programName.find_last_of("\\/") + 1) << " path/to/model.obj\n" << std::endl;
|
||||
std::cout << " Or drag \"obj\" file in this program\n" << std::endl;
|
||||
system("pause");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
Init();
|
||||
|
||||
const std::string path = args.argv[1];
|
||||
const std::string pathDir = path.substr(0, path.find_last_of("/\\"));
|
||||
std::string mtllib;
|
||||
|
||||
LoadMesh(path.c_str(), mtllib);
|
||||
|
||||
if (!mtllib.empty())
|
||||
{
|
||||
std::map<std::string, std::string> textures;
|
||||
if (GetTexture((pathDir + "\\" + mtllib).c_str(), textures))
|
||||
{
|
||||
m_Uniform.Diffuse = Texture::CreateTexture(pathDir + "\\" + textures["diff"]);
|
||||
m_Uniform.Specular = Texture::CreateTexture(pathDir + "\\" + textures["spec"]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Application::Init()
|
||||
{
|
||||
|
||||
m_Window = Window::Create(m_Name, m_Width, m_Height);
|
||||
m_LastFrameTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Application::Run()
|
||||
{
|
||||
while (!m_Window->Close())
|
||||
{
|
||||
auto nowFrameTime = std::chrono::steady_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(nowFrameTime - m_LastFrameTime);
|
||||
const float deltaTime = duration.count() * 0.001f * 0.001f;
|
||||
m_LastFrameTime = nowFrameTime;
|
||||
|
||||
OnUpdate(deltaTime);
|
||||
Window::PollInputEvents();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::Terminate()
|
||||
{
|
||||
|
||||
delete m_Uniform.Diffuse;
|
||||
delete m_Uniform.Specular;
|
||||
|
||||
m_Window->Terminate();
|
||||
delete m_Window;
|
||||
}
|
||||
|
||||
Application::~Application()
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
|
||||
|
||||
void Application::OnUpdate(float ts)
|
||||
{
|
||||
|
||||
static float fpstime = 0.0f;
|
||||
static int fpsCount = 0;
|
||||
fpstime +=ts;
|
||||
fpsCount += 1;
|
||||
if (fpstime >= 0.5f)
|
||||
{
|
||||
std::cout << "fps: "<< (int)((float)fpsCount/fpstime) << std::endl;
|
||||
fpstime = 0.0f;
|
||||
fpsCount = 0;
|
||||
}
|
||||
|
||||
OnCameraUpdate(ts);
|
||||
|
||||
FrameBuffer framebuffer(m_Width, m_Height);
|
||||
framebuffer.Clear({0.2f, 0.2f, 0.2f});
|
||||
|
||||
|
||||
Math::Mat4 view = Math::Mat4LookAt(m_Camera.Position, m_Camera.Position + m_Camera.Front, {0.0f, 1.0f, 0.0f});
|
||||
Math::Mat4 projection = Math::Mat4Perspective(90.f/180.f *Math::PI, m_Camera.Aspect, 0.1f ,100.0f);
|
||||
Math::Mat4 model = Math::Mat4Identity();
|
||||
m_Uniform.MVP = projection * view * model;
|
||||
|
||||
m_Uniform.Camera = m_Camera.Position;
|
||||
m_Uniform.Model = model;
|
||||
m_Uniform.ModelNormalToWorld = Math::Mat4Identity();
|
||||
|
||||
|
||||
Program program(DemoVertexShader, DemoFragmentShader);
|
||||
for (const auto& triangle : m_Meshes)
|
||||
{
|
||||
Renderer::Draw(framebuffer, program, triangle, m_Uniform);
|
||||
}
|
||||
|
||||
m_Window->DrawFrameBuffer(framebuffer);
|
||||
}
|
||||
|
||||
|
||||
void Application::OnCameraUpdate(float ts)
|
||||
{
|
||||
constexpr float speed = 1.0f;
|
||||
|
||||
if (m_Window->GetKey(KEY_SPACE) == KEY_PRESS)
|
||||
m_Camera.Position += speed * ts * m_Camera.Up;
|
||||
if (m_Window->GetKey(KEY_LEFT_CONTROL) == KEY_PRESS)
|
||||
m_Camera.Position -= speed * ts * m_Camera.Up;
|
||||
if (m_Window->GetKey(KEY_W) == KEY_PRESS)
|
||||
m_Camera.Position += speed * ts * m_Camera.Front;
|
||||
if (m_Window->GetKey(KEY_S) == KEY_PRESS)
|
||||
m_Camera.Position -= speed * ts * m_Camera.Front;
|
||||
if (m_Window->GetKey(KEY_A) == KEY_PRESS)
|
||||
m_Camera.Position -= speed * ts * m_Camera.Right;
|
||||
if (m_Window->GetKey(KEY_D) == KEY_PRESS)
|
||||
m_Camera.Position += speed * ts * m_Camera.Right;
|
||||
// std::cout << m_Camera.Position << std::endl;
|
||||
constexpr float rotationSpeed = 1.0f;
|
||||
Math::Mat4 rotation = Math::Mat4Identity();
|
||||
if (m_Window->GetKey(KEY_Q) == KEY_PRESS)
|
||||
rotation = Math::Mat4RotateY(ts * rotationSpeed);
|
||||
if (m_Window->GetKey(KEY_E) == KEY_PRESS)
|
||||
rotation = Math::Mat4RotateY(-ts * rotationSpeed);
|
||||
m_Camera.Front = rotation * m_Camera.Front;
|
||||
m_Camera.Front = {Math::Normalize(m_Camera.Front), 0.0f};
|
||||
m_Camera.Right = {Math::Normalize(Math::Cross(m_Camera.Front, Math::Vec3(0,1,0))), 0.0f };
|
||||
}
|
||||
|
||||
void Application::LoadMesh(const std::string& filepath, std::string& mtllib)
|
||||
{
|
||||
if (filepath.substr(filepath.find_last_of('.')) != ".obj")
|
||||
{
|
||||
std::cout << "not a .obj file \n\t" << filepath << std::endl;
|
||||
Terminate();
|
||||
system("pause");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
std::ifstream file(filepath);
|
||||
if (!file)
|
||||
{
|
||||
std::cout << "Failed to load file! is exist? \n\t" << filepath << std::endl;
|
||||
Terminate();
|
||||
system("pause");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
std::vector<Math::Vec3> positions;
|
||||
std::vector<Math::Vec2> texCoords;
|
||||
std::vector<Math::Vec3> normals;
|
||||
std::vector<unsigned int> posIndices;
|
||||
std::vector<unsigned int> texIndices;
|
||||
std::vector<unsigned int> normalIndices;
|
||||
|
||||
std::string line;
|
||||
while (!file.eof())
|
||||
{
|
||||
std::getline(file, line);
|
||||
int items = -1;
|
||||
if (line.find("mtllib ") == 0)
|
||||
{
|
||||
mtllib = line.substr(7);
|
||||
}
|
||||
else if (line.find("v ") == 0)
|
||||
{
|
||||
Math::Vec3 position;
|
||||
items = sscanf(line.c_str(), "v %f %f %f", &position.x, &position.y, &position.z);
|
||||
ASSERT(items == 3);
|
||||
|
||||
positions.push_back(position);
|
||||
}else if (line.find("vt ") == 0)
|
||||
{
|
||||
Math::Vec2 texCoord;
|
||||
items = sscanf(line.c_str(), "vt %f %f", &texCoord.x, &texCoord.y);
|
||||
ASSERT(items == 2);
|
||||
texCoords.push_back(texCoord);
|
||||
}else if (line.find("vn ") == 0)
|
||||
{
|
||||
Math::Vec3 normal;
|
||||
items = sscanf(line.c_str(), "vn %f %f %f", &normal.x, &normal.y, &normal.z);
|
||||
ASSERT(items == 3);
|
||||
normals.push_back(normal);
|
||||
}else if (line.find("f ") == 0)
|
||||
{
|
||||
int pIndices[3], uvIndices[3], nIndices[3];
|
||||
items = sscanf(line.c_str(), "f %d/%d/%d %d/%d/%d %d/%d/%d",
|
||||
&pIndices[0], &uvIndices[0], &nIndices[0],
|
||||
&pIndices[1], &uvIndices[1], &nIndices[1],
|
||||
&pIndices[2], &uvIndices[2], &nIndices[2]);
|
||||
|
||||
ASSERT(items == 9);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
posIndices.push_back(pIndices[i] - 1);
|
||||
texIndices.push_back(uvIndices[i] - 1);
|
||||
normalIndices.push_back(nIndices[i] - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
|
||||
int triNum = posIndices.size() / 3;
|
||||
for (int i = 0; i < triNum; i++)
|
||||
{
|
||||
Triangle<BlinnVertex> triangle;
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
int index = 3 * i + j;
|
||||
int posIndex = posIndices[index];
|
||||
int texIndex = texIndices[index];
|
||||
int normIndex = normalIndices[index];
|
||||
|
||||
triangle[j].ModelPosition = {positions[posIndex], 1.0f };
|
||||
triangle[j].TexCoord = texCoords[texIndex];
|
||||
triangle[j].ModelNormal = normals[normIndex];
|
||||
}
|
||||
m_Meshes.emplace_back(triangle);
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::GetTexture(const char* filepath, std::map<std::string, std::string>& textures)
|
||||
{
|
||||
std::ifstream file(filepath);
|
||||
// ASSERT(file);
|
||||
if (!file)
|
||||
{
|
||||
std::cout << "Failed to open file: " << filepath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasTexture = false;
|
||||
std::string line;
|
||||
while (!file.eof())
|
||||
{
|
||||
std::getline(file, line);
|
||||
|
||||
if (line.find("map_Kd ") == 0)
|
||||
{
|
||||
textures["diff"] = line.substr(7);
|
||||
hasTexture = true;
|
||||
}else if (line.find("map_Ks ") == 0)
|
||||
{
|
||||
textures["spec"] = line.substr(7);
|
||||
hasTexture = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasTexture;
|
||||
}
|
||||
57
src/Application/Application.h
Normal file
57
src/Application/Application.h
Normal file
@ -0,0 +1,57 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef APPLICATION_H
|
||||
#define APPLICATION_H
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
|
||||
#include "../Core/Camera/Camera.h"
|
||||
#include "../Core/Renderer/Renderer.h"
|
||||
#include "../Core/Window/Window.h"
|
||||
#include "../Shader/DemoShader.h"
|
||||
|
||||
|
||||
struct CommandLineArgs
|
||||
{
|
||||
int argc;
|
||||
char **argv;
|
||||
};
|
||||
|
||||
class Application {
|
||||
public:
|
||||
Application(std::string name, int width, int height, CommandLineArgs args = CommandLineArgs());
|
||||
~Application();
|
||||
|
||||
void Run();
|
||||
private:
|
||||
void Init();
|
||||
void Terminate();
|
||||
|
||||
void OnUpdate(float ts);
|
||||
void OnCameraUpdate(float ts);
|
||||
|
||||
void LoadMesh(const std::string& filepath ,std::string& mtllib);
|
||||
bool GetTexture(const char* filepath, std::map<std::string, std::string>& textures);
|
||||
|
||||
private:
|
||||
|
||||
std::string m_Name;
|
||||
int m_Width, m_Height;
|
||||
Window* m_Window;
|
||||
|
||||
std::vector<Triangle<BlinnVertex>> m_Meshes;
|
||||
BlinnUniform m_Uniform;
|
||||
|
||||
|
||||
|
||||
std::chrono::steady_clock::time_point m_LastFrameTime;
|
||||
|
||||
Camera m_Camera;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //APPLICATION_H
|
||||
5
src/Core/Assert.cpp
Normal file
5
src/Core/Assert.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "Assert.h"
|
||||
12
src/Core/Assert.h
Normal file
12
src/Core/Assert.h
Normal file
@ -0,0 +1,12 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef ASSERT_H
|
||||
#define ASSERT_H
|
||||
|
||||
#define LOG(...)
|
||||
#define ASSERT(x, ...) {if(!(x)) {LOG(__VA_ARGS__); __debugbreak(); }}
|
||||
|
||||
|
||||
#endif //ASSERT_H
|
||||
5
src/Core/Camera/Camera.cpp
Normal file
5
src/Core/Camera/Camera.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by sfd on 25-7-30.
|
||||
//
|
||||
|
||||
#include "Camera.h"
|
||||
21
src/Core/Camera/Camera.h
Normal file
21
src/Core/Camera/Camera.h
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Created by sfd on 25-7-30.
|
||||
//
|
||||
|
||||
#ifndef CAMERA_H
|
||||
#define CAMERA_H
|
||||
|
||||
#include "../Math.h"
|
||||
|
||||
class Camera
|
||||
{
|
||||
public:
|
||||
Math::Vec4 Position{0.0f, 0.0f, 3.0f, 0.0f};
|
||||
Math::Vec4 Right{1.0f, 0.0f, 0.0f, 0.0f};
|
||||
Math::Vec4 Up{0.0f, 1.0f, 0.0f, 0.0f};
|
||||
Math::Vec4 Front{0.0f, 0.0f, -1.0f, 0.0f};
|
||||
float Aspect = 1.0f;
|
||||
};
|
||||
|
||||
|
||||
#endif //CAMERA_H
|
||||
5
src/Core/Input/InputCodes.cpp
Normal file
5
src/Core/Input/InputCodes.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "InputCodes.h"
|
||||
144
src/Core/Input/InputCodes.h
Normal file
144
src/Core/Input/InputCodes.h
Normal file
@ -0,0 +1,144 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef INPUTCODES_H
|
||||
#define INPUTCODES_H
|
||||
|
||||
|
||||
// From glfw3.h
|
||||
#define KEY_SPACE 32
|
||||
#define KEY_APOSTROPHE 39 /* ' */
|
||||
#define KEY_COMMA 44 /* , */
|
||||
#define KEY_MINUS 45 /* - */
|
||||
#define KEY_PERIOD 46 /* . */
|
||||
#define KEY_SLASH 47 /* / */
|
||||
#define KEY_0 48
|
||||
#define KEY_1 49
|
||||
#define KEY_2 50
|
||||
#define KEY_3 51
|
||||
#define KEY_4 52
|
||||
#define KEY_5 53
|
||||
#define KEY_6 54
|
||||
#define KEY_7 55
|
||||
#define KEY_8 56
|
||||
#define KEY_9 57
|
||||
#define KEY_SEMICOLON 59 /* ; */
|
||||
#define KEY_EQUAL 61 /* = */
|
||||
#define KEY_A 65
|
||||
#define KEY_B 66
|
||||
#define KEY_C 67
|
||||
#define KEY_D 68
|
||||
#define KEY_E 69
|
||||
#define KEY_F 70
|
||||
#define KEY_G 71
|
||||
#define KEY_H 72
|
||||
#define KEY_I 73
|
||||
#define KEY_J 74
|
||||
#define KEY_K 75
|
||||
#define KEY_L 76
|
||||
#define KEY_M 77
|
||||
#define KEY_N 78
|
||||
#define KEY_O 79
|
||||
#define KEY_P 80
|
||||
#define KEY_Q 81
|
||||
#define KEY_R 82
|
||||
#define KEY_S 83
|
||||
#define KEY_T 84
|
||||
#define KEY_U 85
|
||||
#define KEY_V 86
|
||||
#define KEY_W 87
|
||||
#define KEY_X 88
|
||||
#define KEY_Y 89
|
||||
#define KEY_Z 90
|
||||
#define KEY_LEFT_BRACKET 91 /* [ */
|
||||
#define KEY_BACKSLASH 92 /* \ */
|
||||
#define KEY_RIGHT_BRACKET 93 /* ] */
|
||||
#define KEY_GRAVE_ACCENT 96 /* ` */
|
||||
#define KEY_WORLD_1 161 /* non-US #1 */
|
||||
#define KEY_WORLD_2 162 /* non-US #2 */
|
||||
|
||||
/* Functkeys */
|
||||
#define KEY_ESCAPE 256
|
||||
#define KEY_ENTER 257
|
||||
#define KEY_TAB 258
|
||||
#define KEY_BACKSPACE 259
|
||||
#define KEY_INSERT 260
|
||||
#define KEY_DELETE 261
|
||||
#define KEY_RIGHT 262
|
||||
#define KEY_LEFT 263
|
||||
#define KEY_DOWN 264
|
||||
#define KEY_UP 265
|
||||
#define KEY_PAGE_UP 266
|
||||
#define KEY_PAGE_DOWN 267
|
||||
#define KEY_HOME 268
|
||||
#define KEY_END 269
|
||||
#define KEY_CAPS_LOCK 280
|
||||
#define KEY_SCROLL_LOCK 281
|
||||
#define KEY_NUM_LOCK 282
|
||||
#define KEY_PRINT_SCREEN 283
|
||||
#define KEY_PAUSE 284
|
||||
#define KEY_F1 290
|
||||
#define KEY_F2 291
|
||||
#define KEY_F3 292
|
||||
#define KEY_F4 293
|
||||
#define KEY_F5 294
|
||||
#define KEY_F6 295
|
||||
#define KEY_F7 296
|
||||
#define KEY_F8 297
|
||||
#define KEY_F9 298
|
||||
#define KEY_F10 299
|
||||
#define KEY_F11 300
|
||||
#define KEY_F12 301
|
||||
#define KEY_F13 302
|
||||
#define KEY_F14 303
|
||||
#define KEY_F15 304
|
||||
#define KEY_F16 305
|
||||
#define KEY_F17 306
|
||||
#define KEY_F18 307
|
||||
#define KEY_F19 308
|
||||
#define KEY_F20 309
|
||||
#define KEY_F21 310
|
||||
#define KEY_F22 311
|
||||
#define KEY_F23 312
|
||||
#define KEY_F24 313
|
||||
#define KEY_F25 314
|
||||
#define KEY_KP_0 320
|
||||
#define KEY_KP_1 321
|
||||
#define KEY_KP_2 322
|
||||
#define KEY_KP_3 323
|
||||
#define KEY_KP_4 324
|
||||
#define KEY_KP_5 325
|
||||
#define KEY_KP_6 326
|
||||
#define KEY_KP_7 327
|
||||
#define KEY_KP_8 328
|
||||
#define KEY_KP_9 329
|
||||
#define KEY_KP_DECIMAL 330
|
||||
#define KEY_KP_DIVIDE 331
|
||||
#define KEY_KP_MULTIPLY 332
|
||||
#define KEY_KP_SUBTRACT 333
|
||||
#define KEY_KP_ADD 334
|
||||
#define KEY_KP_ENTER 335
|
||||
#define KEY_KP_EQUAL 336
|
||||
#define KEY_LEFT_SHIFT 340
|
||||
#define KEY_LEFT_CONTROL 341
|
||||
#define KEY_LEFT_ALT 342
|
||||
#define KEY_LEFT_SUPER 343
|
||||
#define KEY_RIGHT_SHIFT 344
|
||||
#define KEY_RIGHT_CONTROL 345
|
||||
#define KEY_RIGHT_ALT 346
|
||||
#define KEY_RIGHT_SUPER 347
|
||||
#define KEY_MENU 348
|
||||
#define KEY_MAX_COUNT 512
|
||||
|
||||
|
||||
|
||||
#define BUTTON_LEFT 0
|
||||
#define BUTTON_RIGHT 1
|
||||
#define BUTTON_MAX_COUNT 8
|
||||
|
||||
#define KEY_RELEASE 0
|
||||
#define KEY_PRESS 1
|
||||
|
||||
|
||||
#endif //INPUTCODES_H
|
||||
322
src/Core/Math.cpp
Normal file
322
src/Core/Math.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "Math.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace Math
|
||||
{
|
||||
|
||||
Vec2 operator+ (const Vec2& left, const Vec2& right)
|
||||
{
|
||||
return Vec2{ left.x + right.x, left.y + right.y };
|
||||
}
|
||||
Vec2 operator- (const Vec2& left, const Vec2& right)
|
||||
{
|
||||
return Vec2{ left.x - right.x, left.y - right.y };
|
||||
}
|
||||
|
||||
Vec3 operator+ (const Vec3& left, const Vec3& right)
|
||||
{
|
||||
return Vec3{ left.x + right.x, left.y + right.y, left.z + right.z };
|
||||
}
|
||||
Vec3 operator- (const Vec3& left, const Vec3& right)
|
||||
{
|
||||
return left + (-1.0f * right);
|
||||
}
|
||||
Vec3 operator* (const float left, const Vec3& right)
|
||||
{
|
||||
return Vec3{ left * right.x, left * right.y, left * right.z };
|
||||
}
|
||||
Vec3 operator* (const Vec3& left, const float right)
|
||||
{
|
||||
return right * left;
|
||||
}
|
||||
Vec3 operator* (const Vec3& left, const Vec3& right)
|
||||
{
|
||||
return { left.x * right.x, left.y * right.y , left.z * right.z };
|
||||
}
|
||||
Vec3 operator/ (const Vec3& left, const float right)
|
||||
{
|
||||
ASSERT(right != 0);
|
||||
return left * (1.0f / right);
|
||||
}
|
||||
|
||||
float Dot(const Vec3& left, const Vec3& right)
|
||||
{
|
||||
return left.x * right.x + left.y * right.y + left.z * right.z;
|
||||
}
|
||||
Vec3 Cross(const Vec3& left, const Vec3& right)
|
||||
{
|
||||
float x = left.y * right.z - left.z * right.y;
|
||||
float y = left.z * right.x - left.x * right.z;
|
||||
float z = left.x * right.y - left.y * right.x;
|
||||
return { x, y, z };
|
||||
}
|
||||
Vec3 Normalize(const Vec3& v)
|
||||
{
|
||||
float len = (float)std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||
ASSERT(len != 0);
|
||||
return v / len;
|
||||
}
|
||||
|
||||
Vec4 operator+ (const Vec4& left, const Vec4& right)
|
||||
{
|
||||
return Vec4{ left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w };
|
||||
}
|
||||
Vec4 operator- (const Vec4& left, const Vec4& right)
|
||||
{
|
||||
return Vec4{ left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w };
|
||||
}
|
||||
|
||||
Vec4 operator+=(Vec4& left, const Vec4& right)
|
||||
{
|
||||
left = left + right;
|
||||
return left;
|
||||
}
|
||||
|
||||
Vec4 operator-=(Vec4& left, const Vec4& right)
|
||||
{
|
||||
left = left - right;
|
||||
return left;
|
||||
}
|
||||
|
||||
Vec4 operator* (const float left, const Vec4& right)
|
||||
{
|
||||
return Vec4{ left * right.x, left * right.y, left * right.z, left * right.w };
|
||||
}
|
||||
Vec4 operator* (const Vec4& left, const float right)
|
||||
{
|
||||
return right * left;
|
||||
}
|
||||
Vec4 operator/ (const Vec4& left, const float right)
|
||||
{
|
||||
ASSERT(right != 0);
|
||||
return left * (1.0f / right);
|
||||
}
|
||||
|
||||
Vec4 operator* (const Mat4& mat4, const Vec4& vec4)
|
||||
{
|
||||
Vec4 res;
|
||||
res.x = mat4.m[0][0] * vec4.x + mat4.m[0][1] * vec4.y + mat4.m[0][2] * vec4.z + mat4.m[0][3] * vec4.w;
|
||||
res.y = mat4.m[1][0] * vec4.x + mat4.m[1][1] * vec4.y + mat4.m[1][2] * vec4.z + mat4.m[1][3] * vec4.w;
|
||||
res.z = mat4.m[2][0] * vec4.x + mat4.m[2][1] * vec4.y + mat4.m[2][2] * vec4.z + mat4.m[2][3] * vec4.w;
|
||||
res.w = mat4.m[3][0] * vec4.x + mat4.m[3][1] * vec4.y + mat4.m[3][2] * vec4.z + mat4.m[3][3] * vec4.w;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Mat4::Mat4()
|
||||
{
|
||||
for (int i =0 ; i < 4; i++)
|
||||
for (int j =0 ; j < 4; j++)
|
||||
m[i][j] = 0.0f;
|
||||
}
|
||||
|
||||
Mat4::Mat4(const Vec4& v0, const Vec4& v1, const Vec4& v2, const Vec4& v3)
|
||||
{
|
||||
m[0][0] = v0.x; m[1][0] = v0.y; m[2][0] = v0.z; m[3][0] = v0.w;
|
||||
m[0][1] = v1.x; m[1][1] = v1.y; m[2][1] = v1.z; m[3][1] = v1.w;
|
||||
m[0][2] = v2.x; m[1][2] = v2.y; m[2][2] = v2.z; m[3][2] = v2.w;
|
||||
m[0][3] = v3.x; m[1][3] = v3.y; m[2][3] = v3.z; m[3][3] = v3.w;
|
||||
}
|
||||
|
||||
Mat4::operator std::string() const
|
||||
{
|
||||
std::string res;
|
||||
res += "[";
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
res += std::to_string(m[i][j]);
|
||||
res += (i == 3 && j == 3) ? "]" : ", ";
|
||||
}
|
||||
res += "\n";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Mat4 operator* (const Mat4& left, const Mat4& right)
|
||||
{
|
||||
Mat4 res;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
for (int k = 0; k < 4; k++)
|
||||
{
|
||||
res.m[i][j] += left.m[i][k] * right.m[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
Mat4& operator*= (Mat4& left, const Mat4& right)
|
||||
{
|
||||
left = left * right;
|
||||
return left;
|
||||
}
|
||||
|
||||
Mat4 Mat4Identity()
|
||||
{
|
||||
return Mat4({ 1,0,0,0 }, { 0,1,0,0 }, { 0,0,1,0 }, { 0,0,0,1 });
|
||||
}
|
||||
Mat4 Mat4Translate(float tx, float ty, float tz)
|
||||
{
|
||||
Mat4 m = Mat4Identity();
|
||||
m.m[0][3] = tx;
|
||||
m.m[1][3] = ty;
|
||||
m.m[2][3] = tz;
|
||||
return m;
|
||||
}
|
||||
Mat4 Mat4Scale(float sx, float sy, float sz)
|
||||
{
|
||||
Mat4 m = Mat4Identity();
|
||||
ASSERT(sx != 0 && sy != 0 && sz != 0);
|
||||
m.m[0][0] = sx;
|
||||
m.m[1][1] = sy;
|
||||
m.m[2][2] = sz;
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
Mat4 Mat4RotateX(float angle)
|
||||
{
|
||||
float c = (float)std::cos(angle);
|
||||
float s = (float)std::sin(angle);
|
||||
Mat4 m = Mat4Identity();
|
||||
m.m[1][1] = c;
|
||||
m.m[1][2] = -s;
|
||||
m.m[2][1] = s;
|
||||
m.m[2][2] = c;
|
||||
return m;
|
||||
}
|
||||
Mat4 Mat4RotateY(float angle)
|
||||
{
|
||||
float c = (float)std::cos(angle);
|
||||
float s = (float)std::sin(angle);
|
||||
Mat4 m = Mat4Identity();
|
||||
m.m[2][2] = c;
|
||||
m.m[2][0] = -s;
|
||||
m.m[0][2] = s;
|
||||
m.m[0][0] = c;
|
||||
return m;
|
||||
}
|
||||
Mat4 Mat4RotateZ(float angle)
|
||||
{
|
||||
float c = (float)std::cos(angle);
|
||||
float s = (float)std::sin(angle);
|
||||
Mat4 m = Mat4Identity();
|
||||
m.m[0][0] = c;
|
||||
m.m[0][1] = -s;
|
||||
m.m[1][0] = s;
|
||||
m.m[1][1] = c;
|
||||
return m;
|
||||
}
|
||||
Mat4 Mat4LookAt(const Vec3& xAxis, const Vec3& yAxis, const Vec3& zAxis, const Vec3& eye)
|
||||
{
|
||||
Mat4 m = Mat4Identity();
|
||||
|
||||
m.m[0][0] = xAxis.x;
|
||||
m.m[0][1] = xAxis.y;
|
||||
m.m[0][2] = xAxis.z;
|
||||
|
||||
m.m[1][0] = yAxis.x;
|
||||
m.m[1][1] = yAxis.y;
|
||||
m.m[1][2] = yAxis.z;
|
||||
|
||||
m.m[2][0] = zAxis.x;
|
||||
m.m[2][1] = zAxis.y;
|
||||
m.m[2][2] = zAxis.z;
|
||||
|
||||
m.m[0][3] = -Dot(xAxis, eye);
|
||||
m.m[1][3] = -Dot(yAxis, eye);
|
||||
m.m[2][3] = -Dot(zAxis, eye);
|
||||
|
||||
return m;
|
||||
}
|
||||
Mat4 Mat4LookAt(const Vec3& eye, const Vec3& target, const Vec3& up)
|
||||
{
|
||||
Vec3 zAxis = Normalize(eye - target);
|
||||
Vec3 xAxis = Normalize(Cross(up, zAxis));
|
||||
Vec3 yAxis = Normalize(Cross(zAxis, xAxis));
|
||||
return Mat4LookAt(xAxis, yAxis, zAxis, eye);
|
||||
}
|
||||
/*
|
||||
* fovy: the field of view angle in the y direction, in radians
|
||||
* aspect: the aspect ratio, defined as width divided by height
|
||||
* near, far: the distances to the near and far depth clipping planes
|
||||
*
|
||||
* 1/(aspect*tan(fovy/2)) 0 0 0
|
||||
* 0 1/tan(fovy/2) 0 0
|
||||
* 0 0 -(f+n)/(f-n) -2fn/(f-n)
|
||||
* 0 0 -1 0
|
||||
*
|
||||
* this is the same as
|
||||
* float half_h = near * (float)tan(fovy / 2);
|
||||
* float half_w = half_h * aspect;
|
||||
* mat4_frustum(-half_w, half_w, -half_h, half_h, near, far);
|
||||
*
|
||||
* see http://www.songho.ca/opengl/gl_projectionmatrix.html
|
||||
*/
|
||||
Mat4 Mat4Perspective(float fovy, float aspect, float near, float far)
|
||||
{
|
||||
float z_range = far - near;
|
||||
Mat4 m = Mat4Identity();
|
||||
ASSERT(fovy > 0 && aspect > 0);
|
||||
ASSERT(near > 0 && far > 0 && z_range > 0);
|
||||
m.m[1][1] = 1 / (float)std::tan(fovy / 2);
|
||||
m.m[0][0] = m.m[1][1] / aspect;
|
||||
m.m[2][2] = -(near + far) / z_range;
|
||||
m.m[2][3] = -2 * near * far / z_range;
|
||||
m.m[3][2] = -1;
|
||||
m.m[3][3] = 0;
|
||||
return m;
|
||||
}
|
||||
|
||||
float Lerp(const float start, const float end, const float t)
|
||||
{
|
||||
return end * t + start * (1.0f - t);
|
||||
}
|
||||
|
||||
Vec3 Lerp(const Vec3& start, const Vec3& end, const float t)
|
||||
{
|
||||
return end * t + start * (1.0f - t);
|
||||
}
|
||||
|
||||
Vec4 Lerp(const Vec4& start, const Vec4& end, const float t)
|
||||
{
|
||||
return end * t + start * (1.0f - t);
|
||||
}
|
||||
|
||||
unsigned char Float2UChar(const float f)
|
||||
{
|
||||
return (unsigned char)(f * 255.0f);
|
||||
}
|
||||
float UChar2Float(const unsigned char c)
|
||||
{
|
||||
return (float)c / 255.0f;
|
||||
}
|
||||
|
||||
float Clamp(const float val, const float min, const float max)
|
||||
{
|
||||
if (val < min)
|
||||
{
|
||||
return min;
|
||||
}
|
||||
else if (val > max)
|
||||
{
|
||||
return max;
|
||||
}
|
||||
else
|
||||
{
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
160
src/Core/Math.h
Normal file
160
src/Core/Math.h
Normal file
@ -0,0 +1,160 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef MATH_H
|
||||
#define MATH_H
|
||||
|
||||
#include <string>
|
||||
#include "Assert.h"
|
||||
|
||||
|
||||
|
||||
namespace Math
|
||||
{
|
||||
|
||||
constexpr float PI = 3.14159265359f;
|
||||
constexpr float EPSILON = 1e-5f;
|
||||
|
||||
struct Vec2
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
|
||||
constexpr Vec2() : x(0.0f), y(0.0f) {}
|
||||
constexpr Vec2(const float x, const float y) : x(x), y(y) {}
|
||||
|
||||
explicit operator std::string() const
|
||||
{
|
||||
return "(" + std::to_string(x) + ", " + std::to_string(y) + ")";
|
||||
}
|
||||
};
|
||||
|
||||
struct Vec3
|
||||
{
|
||||
union { float x, r; };
|
||||
union { float y, g; };
|
||||
union { float z, b; };
|
||||
|
||||
constexpr Vec3() : x(0.0f), y(0.0f), z(0.0f) {}
|
||||
constexpr Vec3(const Vec2& vec, const float z) : x(vec.x), y(vec.y), z(z) {}
|
||||
constexpr Vec3(const Vec3& vec) : x(vec.x), y(vec.y), z(vec.z) {}
|
||||
constexpr Vec3(const float x, const float y, const float z) : x(x), y(y), z(z) {}
|
||||
|
||||
explicit operator Vec2() const { return {x, y}; }
|
||||
|
||||
explicit operator std::string() const
|
||||
{
|
||||
return "(" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(x) + ")";
|
||||
}
|
||||
};
|
||||
|
||||
struct Vec4
|
||||
{
|
||||
union { float x, r; };
|
||||
union { float y, g; };
|
||||
union { float z, b; };
|
||||
union { float w, a; };
|
||||
|
||||
constexpr Vec4() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {}
|
||||
constexpr Vec4(const float val) : x(val), y(val), z(val), w(val) {}
|
||||
constexpr Vec4(const Vec2& vec2, const float z, const float w) : x(vec2.x), y(vec2.y), z(z), w(w) {}
|
||||
constexpr Vec4(const Vec3& vec3, const float w) : x(vec3.x), y(vec3.y), z(vec3.z), w(w) {}
|
||||
constexpr Vec4(const float x, const float y, const float z, const float w) : x(x), y(y), z(z), w(w) {}
|
||||
|
||||
operator Vec2() const { return {x, y}; }
|
||||
operator Vec3() const { return {x, y, z}; }
|
||||
|
||||
explicit operator std::string() const
|
||||
{
|
||||
std::string res;
|
||||
res += '(';
|
||||
res += std::to_string(x);
|
||||
res += ',';
|
||||
res += std::to_string(y);
|
||||
res += ',';
|
||||
res += std::to_string(z);
|
||||
res += ',';
|
||||
res += std::to_string(w);
|
||||
res += ')';
|
||||
res += '\n';
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Mat4
|
||||
{
|
||||
float m[4][4];
|
||||
|
||||
Mat4();
|
||||
Mat4(const Vec4& v0, const Vec4& v1, const Vec4& v2, const Vec4& v3);
|
||||
|
||||
explicit operator std::string() const;
|
||||
};
|
||||
|
||||
|
||||
Vec2 operator+(const Vec2& left, const Vec2& right);
|
||||
Vec2 operator-(const Vec2& left, const Vec2& right);
|
||||
Vec2 operator*(const Vec2& left, float right);
|
||||
Vec2 operator+=(Vec2& left, const Vec2& right);
|
||||
Vec2 operator-=(Vec2& left, const Vec2& right);
|
||||
|
||||
|
||||
Vec3 operator+ (const Vec3& left, const Vec3& right);
|
||||
Vec3 operator- (const Vec3& left, const Vec3& right);
|
||||
Vec3 operator* (const Vec3& left, const Vec3& right);
|
||||
Vec3 operator* (const Vec3& vec, float scalar);
|
||||
Vec3 operator* (float scalar, const Vec3& vec);
|
||||
Vec3 operator/ (const Vec3& left, float scalar);
|
||||
Vec3 operator+= (Vec3& left, const Vec3& right);
|
||||
Vec3 operator-= (Vec3& left, const Vec3& right);
|
||||
|
||||
Vec4 operator+ (const Vec4& left, const Vec4& right);
|
||||
Vec4 operator- (const Vec4& left, const Vec4& right);
|
||||
Vec4 operator* (const Mat4& mat4, const Vec4& vec4);
|
||||
Vec4 operator* (float value, const Vec4& vec4);
|
||||
Vec4 operator* (const Vec4& vec4, float value);
|
||||
Vec4 operator/ (const Vec4& vec4, float value);
|
||||
Vec4 operator+= (Vec4& left, const Vec4& right);
|
||||
Vec4 operator-= (Vec4& left, const Vec4& right);
|
||||
|
||||
Mat4 operator* (const Mat4& left, const Mat4& right);
|
||||
Mat4& operator*= (Mat4& left, const Mat4& right);
|
||||
|
||||
|
||||
|
||||
Vec3 Normalize(const Vec3& v);
|
||||
Mat4 Mat4Identity();
|
||||
Mat4 Mat4Translate(float tx, float ty, float tz);
|
||||
Mat4 Mat4Scale(float sx, float sy, float sz);
|
||||
Mat4 Mat4RotateX(float angle);
|
||||
Mat4 Mat4RotateY(float angle);
|
||||
Mat4 Mat4RotateZ(float angle);
|
||||
|
||||
Vec3 Cross(const Vec3& left, const Vec3& right);
|
||||
Vec3 Lerp(const Vec3& start, const Vec3& end, float t);
|
||||
Mat4 Mat4LookAt(const Vec3& eye, const Vec3& target, const Vec3& up);
|
||||
|
||||
float Dot(const Vec3& left, const Vec3& right);
|
||||
float Lerp(float start, float end, float t);
|
||||
float Clamp(float val, float min, float max);
|
||||
|
||||
/*
|
||||
*
|
||||
* 1/(aspect*tan(fovy/2)) 0 0 0
|
||||
* 0 1/tan(fovy/2) 0 0
|
||||
* 0 0 -(f+n)(f-n) -2fn/(f-n)
|
||||
* 0 0 -1 0
|
||||
*
|
||||
*/
|
||||
Mat4 Mat4Perspective(float fovy, float aspect, float zNear, float zFar);
|
||||
|
||||
inline unsigned char FloatToUchar(const float f) { return (unsigned char)(f * 255.0f); }
|
||||
inline float UCharToFloat(const unsigned char c) { return (float)c / 255.0f; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif //MATH_H
|
||||
89
src/Core/Renderer/FrameBuffer.cpp
Normal file
89
src/Core/Renderer/FrameBuffer.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "FrameBuffer.h"
|
||||
|
||||
#include "../Assert.h"
|
||||
#include "../../Platform/WindowsWindow.h"
|
||||
|
||||
FrameBuffer::FrameBuffer(const int width, const int height)
|
||||
: m_Width(width), m_Height(height)
|
||||
{
|
||||
ASSERT(width > 0 && height > 0);
|
||||
|
||||
m_PixelSize = width * height;
|
||||
m_ColorBuffer = new Math::Vec3[m_PixelSize]();
|
||||
m_DepthBuffer = new float[m_PixelSize]();
|
||||
Clear({0.0f, 0.0f, 0.0f});
|
||||
ClearDepth(1.0f);
|
||||
}
|
||||
|
||||
FrameBuffer::~FrameBuffer()
|
||||
{
|
||||
delete[] m_ColorBuffer;
|
||||
delete[] m_DepthBuffer;
|
||||
|
||||
m_ColorBuffer = nullptr;
|
||||
m_DepthBuffer = nullptr;
|
||||
}
|
||||
|
||||
|
||||
Math::Vec3 FrameBuffer::GetColor(const int x, const int y) const
|
||||
{
|
||||
int index = GetPixelIndex(x, y);
|
||||
if (index < m_PixelSize && index >= 0)
|
||||
return m_ColorBuffer[index];
|
||||
else
|
||||
{
|
||||
ASSERT(false);
|
||||
return Math::Vec3{0.0f, 0.0f, 0.0f};
|
||||
}
|
||||
}
|
||||
|
||||
void FrameBuffer::SetColor(const int x, const int y, const Math::Vec3& color) const
|
||||
{
|
||||
if ((x < 0) || (x >= m_Width) || (y < 0) || (y >= m_Height))
|
||||
{
|
||||
// ASSERT(false);
|
||||
return;
|
||||
}else
|
||||
{
|
||||
const int index = GetPixelIndex(x, y);
|
||||
m_ColorBuffer[index] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void FrameBuffer::SetDepth(const int x, const int y, const float depth)
|
||||
{
|
||||
const int index = GetPixelIndex(x, y);
|
||||
if (index < m_PixelSize && index >= 0)
|
||||
m_DepthBuffer[index] = depth;
|
||||
else
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
float FrameBuffer::GetDepth(const int x, const int y) const
|
||||
{
|
||||
int index = GetPixelIndex(x, y);
|
||||
if (index < m_PixelSize && index >= 0)
|
||||
return m_DepthBuffer[index];
|
||||
else
|
||||
{
|
||||
ASSERT(false);
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FrameBuffer::Clear(const Math::Vec3& color)
|
||||
{
|
||||
for (int i = 0; i < m_PixelSize; i++)
|
||||
m_ColorBuffer[i] = color;
|
||||
}
|
||||
|
||||
void FrameBuffer::ClearDepth(const float depth)
|
||||
{
|
||||
for (int i = 0; i < m_PixelSize; i++)
|
||||
m_DepthBuffer[i] = depth;
|
||||
}
|
||||
41
src/Core/Renderer/FrameBuffer.h
Normal file
41
src/Core/Renderer/FrameBuffer.h
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef FRAMEBUFFER_H
|
||||
#define FRAMEBUFFER_H
|
||||
|
||||
#include "../Math.h"
|
||||
|
||||
|
||||
class FrameBuffer {
|
||||
public:
|
||||
FrameBuffer(int width, int height);
|
||||
~FrameBuffer();
|
||||
|
||||
int GetWidth() const { return m_Width; }
|
||||
int GetHeight() const { return m_Height; }
|
||||
|
||||
Math::Vec3 GetColor(int x, int y) const;
|
||||
void SetColor(int x, int y, const Math::Vec3& color) const;
|
||||
void SetDepth(int x, int y, float depth);
|
||||
float GetDepth(int x, int y) const;
|
||||
|
||||
void Clear(const Math::Vec3& color = Math::Vec3{0.0f, 0.0f, 0.0f});
|
||||
void ClearDepth(float depth = 1.0f);
|
||||
|
||||
private:
|
||||
inline int GetPixelIndex(const int x, const int y) const { return y * m_Width + x; }
|
||||
|
||||
private:
|
||||
int m_Width = 800;
|
||||
int m_Height = 600;
|
||||
|
||||
int m_PixelSize = 0;
|
||||
float* m_DepthBuffer;
|
||||
Math::Vec3* m_ColorBuffer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //FRAMEBUFFER_H
|
||||
124
src/Core/Renderer/Renderer.cpp
Normal file
124
src/Core/Renderer/Renderer.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "Renderer.h"
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
void Renderer::CalculateWeights(float(& screenWeights)[3], float(& weights)[3], const Math::Vec4(& fragCoords)[3], const Math::Vec2& screenPoint)
|
||||
{
|
||||
const Math::Vec2 ab = fragCoords[1] - fragCoords[0];
|
||||
const Math::Vec2 ac = fragCoords[2] - fragCoords[0];
|
||||
const Math::Vec2 ap = screenPoint - fragCoords[0];
|
||||
|
||||
const float factor = 1.0f/(ab.x * ac.y - ab.y * ac.x);
|
||||
const float s = (ac.y * ap.x - ac.x * ap.y) * factor;
|
||||
const float t = (ab.x * ap.y - ab.y * ap.x) * factor;
|
||||
|
||||
screenWeights[0] = 1-s-t;
|
||||
screenWeights[1] = s;
|
||||
screenWeights[2] = t;
|
||||
|
||||
const float w0 = fragCoords[0].w * screenWeights[0];
|
||||
const float w1 = fragCoords[1].w * screenWeights[1];
|
||||
const float w2 = fragCoords[2].w * screenWeights[2];
|
||||
|
||||
const float normalizer = 1.0f/(w0 + w1 + w2);
|
||||
weights[0] = w0 * normalizer;
|
||||
weights[1] = w1 * normalizer;
|
||||
weights[2] = w2 * normalizer;
|
||||
}
|
||||
|
||||
bool Renderer::IsInsideTriangle(float(& weights)[3])
|
||||
{
|
||||
return weights[0] >= -Math::EPSILON && weights[1] >= -Math::EPSILON && weights[2] >= -Math::EPSILON;
|
||||
}
|
||||
|
||||
bool Renderer::IsVertexVisible(const Math::Vec4& clipPos)
|
||||
{
|
||||
return std::fabs(clipPos.x) <= clipPos.w && std::fabs(clipPos.y) <= clipPos.w && std::fabs(clipPos.z) <= clipPos.w;
|
||||
}
|
||||
|
||||
bool Renderer::IsInsidePlane(const Math::Vec4& clipPos, const Plane& plane)
|
||||
{
|
||||
switch (plane)
|
||||
{
|
||||
case Plane::POSITIVE_W: return clipPos.w >= 0.0f;
|
||||
case Plane::POSITIVE_X: return clipPos.x <= +clipPos.w;
|
||||
case Plane::POSITIVE_Y: return clipPos.y <= +clipPos.w;
|
||||
case Plane::POSITIVE_Z: return clipPos.z <= +clipPos.w;
|
||||
case Plane::NEGATIVE_X: return clipPos.x >= -clipPos.w;
|
||||
case Plane::NEGATIVE_Y: return clipPos.y >= -clipPos.w;
|
||||
case Plane::NEGATIVE_Z: return clipPos.z >= -clipPos.w;
|
||||
}
|
||||
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
float Renderer::GetIntersectRatio(const Math::Vec4& prev, const Math::Vec4& curr, const Plane plane)
|
||||
{
|
||||
switch (plane)
|
||||
{
|
||||
case Plane::POSITIVE_W: return (prev.w - 0.0f)/(prev.w - curr.w);
|
||||
case Plane::POSITIVE_X: return (prev.w - prev.x)/((prev.w - prev.x) - (curr.w - curr.x));
|
||||
case Plane::NEGATIVE_X: return (prev.w + prev.x)/((prev.w + prev.x) - (curr.w + curr.x));
|
||||
case Plane::POSITIVE_Y: return (prev.w - prev.y)/((prev.w - prev.y) - (curr.w - curr.y));
|
||||
case Plane::NEGATIVE_Y: return (prev.w + prev.y)/((prev.w + prev.y) - (curr.w + curr.y));
|
||||
case Plane::POSITIVE_Z: return (prev.w - prev.z)/((prev.w - prev.z) - (curr.w - curr.z));
|
||||
case Plane::NEGATIVE_Z: return (prev.w + prev.z)/((prev.w + prev.z) - (curr.w + curr.z));
|
||||
}
|
||||
|
||||
ASSERT(false);
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
Renderer::BoundingBox Renderer::GetBoundingBox(const Math::Vec4(& fragCoords)[3], const int width, const int height)
|
||||
{
|
||||
auto xList = { fragCoords[0].x, fragCoords[1].x, fragCoords[2].x};
|
||||
auto yList = { fragCoords[0].y, fragCoords[1].y, fragCoords[2].y};
|
||||
|
||||
float minX = std::min<float>(xList);
|
||||
float maxX = std::max<float>(xList);
|
||||
float minY = std::min<float>(yList);
|
||||
float maxY = std::max<float>(yList);
|
||||
|
||||
minX = Math::Clamp(minX, 0.0f, (float)(width - 1));
|
||||
maxX = Math::Clamp(maxX, 0.0f, (float)(width - 1));
|
||||
minY = Math::Clamp(minY, 0.0f, (float)(height - 1));
|
||||
maxY = Math::Clamp(maxY, 0.0f, (float)(height - 1));
|
||||
|
||||
BoundingBox box{};
|
||||
box.MinX = std::floor(minX);
|
||||
box.MinY = std::floor(minY);
|
||||
box.MaxX = std::ceil(maxX);
|
||||
box.MaxY = std::ceil(maxY);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
bool Renderer::IsBackingFacing(const Math::Vec4& a,const Math::Vec4& b,const Math::Vec4& c)
|
||||
{
|
||||
// 逆时针为正
|
||||
const float signedAera = a.x * b.y - a.y * b.x +
|
||||
b.x * c.y - b.y * c.x +
|
||||
c.x * a.y - c.y * a.x;
|
||||
|
||||
return signedAera <= 0;
|
||||
}
|
||||
|
||||
bool Renderer::PassDepthTest(const float writeDepth, const float fDepth, const DepthFuncType depthFunc)
|
||||
{
|
||||
switch (depthFunc)
|
||||
{
|
||||
case DepthFuncType::LESS: return fDepth - writeDepth > Math::EPSILON;
|
||||
case DepthFuncType::LEQUAL: return fDepth - writeDepth >= -Math::EPSILON;
|
||||
case DepthFuncType::ALWAYS: return true;
|
||||
}
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
347
src/Core/Renderer/Renderer.h
Normal file
347
src/Core/Renderer/Renderer.h
Normal file
@ -0,0 +1,347 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef RENDERER_H
|
||||
#define RENDERER_H
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
#include "FrameBuffer.h"
|
||||
#include "ShaderBase.h"
|
||||
|
||||
|
||||
template<typename vertex_t>
|
||||
struct Triangle
|
||||
{
|
||||
static_assert(std::is_base_of_v<VertexBase, vertex_t>,"vertex_t 必须继承自 VertexBase");
|
||||
|
||||
vertex_t vertex[3];
|
||||
vertex_t& operator[](size_t i) {return vertex[i];}
|
||||
const vertex_t& operator[](size_t i) const {return vertex[i];}
|
||||
|
||||
Triangle() = default;
|
||||
};
|
||||
|
||||
enum class DepthFuncType
|
||||
{
|
||||
LESS,
|
||||
LEQUAL,
|
||||
ALWAYS,
|
||||
};
|
||||
|
||||
template<typename vertex_t, typename uniforms_t, typename varyings_t>
|
||||
struct Program
|
||||
{
|
||||
using vertexShader_t = void(*)(varyings_t& , const vertex_t&, const uniforms_t&);
|
||||
vertexShader_t VertexShader;
|
||||
|
||||
using fragmentShader_t = Math::Vec4(*)(bool& discard, const varyings_t&, const uniforms_t&);
|
||||
fragmentShader_t FragmentShader;
|
||||
|
||||
bool EnableDoubleSided = false;
|
||||
bool EnableDepthTest = true;
|
||||
bool EnableWriteDepth = true;
|
||||
bool EnableBlend = true;
|
||||
|
||||
DepthFuncType DepthFunc = DepthFuncType::LESS;
|
||||
|
||||
explicit Program(const vertexShader_t vertexShader, const fragmentShader_t fragmentShader) : VertexShader(vertexShader), FragmentShader(fragmentShader) {}
|
||||
};
|
||||
|
||||
|
||||
class Renderer
|
||||
{
|
||||
private:
|
||||
static constexpr int VARYINGS_MAX_COUNT = 9;
|
||||
|
||||
|
||||
enum class Plane
|
||||
{
|
||||
POSITIVE_W,
|
||||
POSITIVE_X,
|
||||
NEGATIVE_X,
|
||||
POSITIVE_Y,
|
||||
NEGATIVE_Y,
|
||||
POSITIVE_Z,
|
||||
NEGATIVE_Z,
|
||||
};
|
||||
|
||||
struct BoundingBox {int MinX, MinY, MaxX, MaxY;};
|
||||
|
||||
private:
|
||||
static void CalculateWeights(float(&screenWeights)[3], float(&weights)[3], const Math::Vec4(&fragCoords)[3], const Math::Vec2& screenPoint);
|
||||
static bool IsInsideTriangle(float (&weights)[3]);
|
||||
|
||||
public:
|
||||
|
||||
static bool IsVertexVisible(const Math::Vec4& clipPos);
|
||||
static bool IsInsidePlane(const Math::Vec4& clipPos, const Plane& plane);
|
||||
static bool IsBackingFacing(const Math::Vec4& a, const Math::Vec4& b,const Math::Vec4& c);
|
||||
static bool PassDepthTest(float writeDepth, float fDepth, DepthFuncType depthFunc);
|
||||
|
||||
static float GetIntersectRatio(const Math::Vec4& prev, const Math::Vec4& curr, Plane plane);
|
||||
static BoundingBox GetBoundingBox(const Math::Vec4(&fragCoords)[3], int width, int height );
|
||||
|
||||
template<typename vertex_t, typename uniforms_t, typename varyings_t>
|
||||
static void ResterizeTriangle(FrameBuffer& frameBuffer, const Program<vertex_t, uniforms_t, varyings_t>& program, const varyings_t(&varyings)[3], const uniforms_t& uniforms)
|
||||
{
|
||||
|
||||
if (!program.EnableDoubleSided)
|
||||
{
|
||||
bool isBackingFacing = false;
|
||||
isBackingFacing = IsBackingFacing(varyings[0].NDCPos, varyings[1].NDCPos, varyings[2].NDCPos);
|
||||
if (isBackingFacing) return;
|
||||
}
|
||||
|
||||
|
||||
Math::Vec4 fragCoords[3];
|
||||
fragCoords[0] = varyings[0].FragPos;
|
||||
fragCoords[1] = varyings[1].FragPos;
|
||||
fragCoords[2] = varyings[2].FragPos;
|
||||
|
||||
const Math::Vec2 size = { (float)frameBuffer.GetWidth(), (float)frameBuffer.GetHeight()};
|
||||
const BoundingBox box = GetBoundingBox(fragCoords, frameBuffer.GetWidth(), frameBuffer.GetHeight());
|
||||
|
||||
for (int y = box.MinY; y <= box.MaxY; ++y)
|
||||
{
|
||||
for (int x = box.MinX; x <= box.MaxX; ++x)
|
||||
{
|
||||
// Varyings setup
|
||||
float screenWeights[3];
|
||||
float weights[3];
|
||||
Math::Vec2 screenPoint{ (float)x + 0.5f, (float)y + 0.5f};
|
||||
|
||||
CalculateWeights(screenWeights, weights, fragCoords, screenPoint);
|
||||
if (!IsInsideTriangle(weights))
|
||||
continue;
|
||||
|
||||
varyings_t pixVaryings;
|
||||
LerpVaryings(pixVaryings, varyings, weights, size.x, size.y);
|
||||
|
||||
// Early depth test
|
||||
if (program.EnableDepthTest)
|
||||
{
|
||||
const float depth = pixVaryings.FragPos.z;
|
||||
const float fDepth = frameBuffer.GetDepth(x, y);
|
||||
const DepthFuncType depthFunc = program.DepthFunc;
|
||||
if (!PassDepthTest(depth, fDepth, depthFunc))
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
ProcessPixel(frameBuffer, x, y, program, pixVaryings, uniforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename varyings_t>
|
||||
static void CalculateNDCPos(varyings_t(&varyings)[VARYINGS_MAX_COUNT], const int vertexNum)
|
||||
{
|
||||
for (int i = 0; i < vertexNum; ++i)
|
||||
{
|
||||
float w = varyings[i].ClipPos.w;
|
||||
varyings[i].NDCPos = varyings[i].ClipPos / w;
|
||||
varyings[i].NDCPos.w = 1.0f / w;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename varyings_t>
|
||||
static void CalculateFragPos(varyings_t(&varyings)[VARYINGS_MAX_COUNT],
|
||||
const int vertexNum,
|
||||
const float width,
|
||||
const float height)
|
||||
{
|
||||
for (int i = 0; i < vertexNum; ++i)
|
||||
{
|
||||
float x = ((varyings[i].NDCPos.x + 1.0f) * 0.5f * width);
|
||||
float y = ((varyings[i].NDCPos.y + 1.0f) * 0.5f * height);
|
||||
float z = ((varyings[i].NDCPos.z + 1.0f) * 0.5f);
|
||||
float w = varyings[i].NDCPos.w;
|
||||
// float w = varyings[i].FragPos.w;
|
||||
|
||||
varyings[i].FragPos.x = x;
|
||||
varyings[i].FragPos.y = y;
|
||||
varyings[i].FragPos.z = z;
|
||||
varyings[i].FragPos.w = w;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
template <typename varyings_t>
|
||||
static void LerpVaryings(varyings_t& out, const varyings_t& start, const varyings_t& end, const float ratio)
|
||||
{
|
||||
constexpr uint32_t floatNum = sizeof(varyings_t) / sizeof(float);
|
||||
float* startFloat = (float*)&start;
|
||||
float* endFloat = (float*)&end;
|
||||
float* outFloat = (float*)&out;
|
||||
|
||||
for (int i = 0; i < floatNum; ++i)
|
||||
{
|
||||
outFloat[i] = Math::Lerp(startFloat[i], endFloat[i], ratio);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename varyings_t>
|
||||
static void LerpVaryings(varyings_t& out, const varyings_t(&varyings)[3], float(&weigths)[3], const int width, const int height)
|
||||
{
|
||||
out.ClipPos = varyings[0].ClipPos * weigths[0] +
|
||||
varyings[1].ClipPos * weigths[1] +
|
||||
varyings[2].ClipPos * weigths[2];
|
||||
|
||||
out.NDCPos = out.ClipPos / out.ClipPos.w;
|
||||
out.NDCPos.w = 1.0f / out.ClipPos.w;
|
||||
|
||||
out.FragPos.x = (out.NDCPos.x + 1.0f) * 0.5f * width;
|
||||
out.FragPos.y = (out.NDCPos.y + 1.0f) * 0.5f * height;
|
||||
out.FragPos.z = (out.NDCPos.z + 1.0f) * 0.5f;
|
||||
out.FragPos.w = out.NDCPos.w;
|
||||
|
||||
constexpr uint32_t floatOffset = sizeof(Math::Vec4)*3 / sizeof(float);
|
||||
constexpr uint32_t floatNum = sizeof(varyings_t) / sizeof(float);
|
||||
float* v0 = (float*)&varyings[0];
|
||||
float* v1 = (float*)&varyings[1];
|
||||
float* v2 = (float*)&varyings[2];
|
||||
float* outFloat = (float*)&out;
|
||||
|
||||
for (int i = floatOffset; i < floatNum; ++i)
|
||||
{
|
||||
outFloat[i] = v0[i] * weigths[0] + v1[i] * weigths[1] + v2[i] * weigths[2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename varyings_t>
|
||||
static int ClipAginstPlane(varyings_t(& outVaryings)[VARYINGS_MAX_COUNT], const varyings_t(& inVaryings)[VARYINGS_MAX_COUNT], const Plane plane,
|
||||
const int inVertexNum)
|
||||
{
|
||||
ASSERT(inVertexNum >= 3);
|
||||
|
||||
int outVaryingNum = 0;
|
||||
for (int i = 0; i < inVertexNum; ++i)
|
||||
{
|
||||
int prevIndex = (i - 1 + inVertexNum) % inVertexNum;
|
||||
int currIndex = i;
|
||||
|
||||
const varyings_t& prevVarings = inVaryings[prevIndex];
|
||||
const varyings_t& currVarings = inVaryings[currIndex];
|
||||
|
||||
const bool prevInside = IsInsidePlane(prevVarings.ClipPos, plane);
|
||||
const bool currInside = IsInsidePlane(currVarings.ClipPos, plane);
|
||||
|
||||
if (prevInside != currInside)
|
||||
{
|
||||
float ratio = GetIntersectRatio(prevVarings.ClipPos, currVarings.ClipPos, plane);
|
||||
LerpVaryings(outVaryings[outVaryingNum], prevVarings, currVarings, ratio);
|
||||
outVaryingNum++;
|
||||
}
|
||||
|
||||
if (currInside)
|
||||
{
|
||||
outVaryings[outVaryingNum] = inVaryings[currIndex];
|
||||
outVaryingNum++;
|
||||
}
|
||||
}
|
||||
ASSERT(outVaryingNum <= VARYINGS_MAX_COUNT);
|
||||
return outVaryingNum;
|
||||
}
|
||||
|
||||
template <typename varyings_t>
|
||||
static int Clip(varyings_t(& varyings)[VARYINGS_MAX_COUNT])
|
||||
{
|
||||
const bool v0_Visable = IsVertexVisible(varyings[0].ClipPos);
|
||||
const bool v1_Visable = IsVertexVisible(varyings[1].ClipPos);
|
||||
const bool v2_Visable = IsVertexVisible(varyings[2].ClipPos);
|
||||
if (v0_Visable && v1_Visable && v2_Visable) return 3;
|
||||
|
||||
int vertexNum = 3;
|
||||
varyings_t varyings_[VARYINGS_MAX_COUNT];
|
||||
|
||||
vertexNum = ClipAginstPlane(varyings_, varyings, Plane::NEGATIVE_Z, vertexNum);
|
||||
if (vertexNum == 0) return 0;
|
||||
vertexNum = ClipAginstPlane(varyings, varyings_, Plane::POSITIVE_Z, vertexNum);
|
||||
if (vertexNum == 0) return 0;
|
||||
vertexNum = ClipAginstPlane(varyings_, varyings, Plane::NEGATIVE_X, vertexNum);
|
||||
if (vertexNum == 0) return 0;
|
||||
vertexNum = ClipAginstPlane(varyings, varyings_, Plane::POSITIVE_X, vertexNum);
|
||||
if (vertexNum == 0) return 0;
|
||||
vertexNum = ClipAginstPlane(varyings_, varyings, Plane::NEGATIVE_Y, vertexNum);
|
||||
if (vertexNum == 0) return 0;
|
||||
vertexNum = ClipAginstPlane(varyings, varyings_, Plane::POSITIVE_Y, vertexNum);
|
||||
if (vertexNum == 0) return 0;
|
||||
|
||||
return vertexNum;
|
||||
}
|
||||
|
||||
template <typename vertex_t, typename uniform_t, typename varyings_t>
|
||||
static void Draw(FrameBuffer& frameBuffer, const Program<vertex_t, uniform_t, varyings_t>& program,
|
||||
const Triangle<vertex_t>& triangle, const uniform_t& uniforms)
|
||||
{
|
||||
|
||||
static_assert(std::is_base_of_v<VertexBase, vertex_t>, "vertex_t 必须继承 BlinnVertex");
|
||||
static_assert(std::is_base_of_v<VaryingBase, varyings_t>, "varyings_t 必须继承 BlinnVaryings");
|
||||
|
||||
// Vertex Shading & Projection
|
||||
varyings_t varyings[VARYINGS_MAX_COUNT];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
program.VertexShader(varyings[i], triangle[i], uniforms);
|
||||
}
|
||||
|
||||
// Clipping
|
||||
int vertexNum = Clip(varyings);
|
||||
|
||||
// Screen Mapping
|
||||
CalculateNDCPos(varyings, vertexNum);
|
||||
int fWidth = frameBuffer.GetWidth();
|
||||
int fHeight = frameBuffer.GetHeight();
|
||||
|
||||
CalculateFragPos(varyings, vertexNum, (float)fWidth, (float)fHeight);
|
||||
|
||||
for (int i = 0; i < vertexNum - 2; ++i)
|
||||
{
|
||||
varyings_t triVaryings[3];
|
||||
triVaryings[0] = varyings[0];
|
||||
triVaryings[1] = varyings[i + 1];
|
||||
triVaryings[2] = varyings[i + 2];
|
||||
|
||||
ResterizeTriangle(frameBuffer, program, triVaryings, uniforms);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename vertex_t, typename uniform_t, typename varyings_t>
|
||||
static void ProcessPixel(FrameBuffer& framebuffer, const int x, const int y, const Program<vertex_t, uniform_t, varyings_t>& program, const varyings_t& varyings, const uniform_t& uniforms)
|
||||
{
|
||||
bool discard = false;
|
||||
|
||||
Math::Vec4 color{0.0f, 0.0f, 0.0f, 1.0f};
|
||||
color = program.FragmentShader(discard, varyings, uniforms);
|
||||
if (discard) return;
|
||||
|
||||
color.x = Math::Clamp(color.x , 0.0f, 1.0f);
|
||||
color.y = Math::Clamp(color.y , 0.0f, 1.0f);
|
||||
color.z = Math::Clamp(color.z , 0.0f, 1.0f);
|
||||
color.w = Math::Clamp(color.w , 0.0f, 1.0f);
|
||||
|
||||
if (program.EnableBlend)
|
||||
{
|
||||
Math::Vec3 dstColor = framebuffer.GetColor(x, y);
|
||||
Math::Vec3 srcColor = color;
|
||||
float alpha = color.w;
|
||||
color = { Math::Lerp(dstColor, srcColor, alpha), 1.0f };
|
||||
framebuffer.SetColor(x, y, color);
|
||||
}else
|
||||
{
|
||||
framebuffer.SetColor(x, y, color);
|
||||
}
|
||||
|
||||
if (program.EnableWriteDepth)
|
||||
{
|
||||
const float depth = varyings.FragPos.z;
|
||||
framebuffer.SetDepth(x, y, depth);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif //RENDERER_H
|
||||
5
src/Core/Renderer/ShaderBase.cpp
Normal file
5
src/Core/Renderer/ShaderBase.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "ShaderBase.h"
|
||||
32
src/Core/Renderer/ShaderBase.h
Normal file
32
src/Core/Renderer/ShaderBase.h
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef SHADERBASE_H
|
||||
#define SHADERBASE_H
|
||||
#include "../Math.h"
|
||||
|
||||
|
||||
struct VertexBase
|
||||
{
|
||||
Math::Vec4 ModelPosition;
|
||||
|
||||
explicit operator const std::string() const { return "ModedPos: " + (std::string)ModelPosition; }
|
||||
};
|
||||
|
||||
struct VaryingBase
|
||||
{
|
||||
Math::Vec4 ClipPos { 0.0f, 0.0f, 0.0f, 1.0f};
|
||||
Math::Vec4 NDCPos { 0.0f, 0.0f, 0.0f, 1.0f};
|
||||
Math::Vec4 FragPos { 0.0f, 0.0f, 0.0f, 1.0f};
|
||||
};
|
||||
|
||||
struct UniformBase
|
||||
{
|
||||
Math::Mat4 MVP;
|
||||
|
||||
|
||||
explicit operator const std::string() const { return "MVP: " + (std::string)MVP; }
|
||||
};
|
||||
|
||||
#endif //SHADERBASE_H
|
||||
103
src/Core/Texture/Texture.cpp
Normal file
103
src/Core/Texture/Texture.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
//
|
||||
// Created by sfd on 25-7-29.
|
||||
//
|
||||
|
||||
#include "Texture.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "../../vendor/stb/stb_image.h"
|
||||
|
||||
Texture::Texture(const std::string& filepath)
|
||||
: m_Filepath(filepath)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
Texture::~Texture()
|
||||
{
|
||||
if (m_Data)
|
||||
{
|
||||
delete[] m_Data;
|
||||
m_Data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Math::Vec4 Texture::Sample(Math::Vec2 texCoords) const
|
||||
{
|
||||
const float vx = Math::Clamp(texCoords.x, 0.0f, 1.0f);
|
||||
const float vy = Math::Clamp(texCoords.y, 0.0f, 1.0f);
|
||||
|
||||
const int x = (m_Width - 1) * vx + 0.5f;
|
||||
const int y = (m_Height - 1) * vy + 0.5f;
|
||||
|
||||
const int index = x + y * m_Width;
|
||||
|
||||
return m_Data[index];
|
||||
}
|
||||
|
||||
Texture* Texture::CreateTexture(const std::string& filepath)
|
||||
{
|
||||
const std::ifstream file(filepath);
|
||||
if (file.good())
|
||||
{
|
||||
return new Texture(filepath);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Texture::Init()
|
||||
{
|
||||
int width, height, channels;
|
||||
|
||||
stbi_set_flip_vertically_on_load(1);
|
||||
|
||||
const stbi_uc* data = stbi_load(m_Filepath.c_str(), &width, &height, &channels, 0);
|
||||
ASSERT(data);
|
||||
|
||||
m_Width = width;
|
||||
m_Height = height;
|
||||
m_Channels = channels;
|
||||
const int size = width * height;
|
||||
m_Data = new Math::Vec4[size];
|
||||
|
||||
if (channels == 4)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
m_Data[i].x = Math::UCharToFloat(data[i * 4 + 0]);
|
||||
m_Data[i].y = Math::UCharToFloat(data[i * 4 + 1]);
|
||||
m_Data[i].z = Math::UCharToFloat(data[i * 4 + 2]);
|
||||
m_Data[i].w = Math::UCharToFloat(data[i * 4 + 3]);
|
||||
}
|
||||
}else if (channels == 3)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
m_Data[i].x = Math::UCharToFloat(data[i * 3 + 0]);
|
||||
m_Data[i].y = Math::UCharToFloat(data[i * 3 + 1]);
|
||||
m_Data[i].z = Math::UCharToFloat(data[i * 3 + 2]);
|
||||
m_Data[i].w = 0.0f;
|
||||
}
|
||||
}else if (channels == 2)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
m_Data[i].x = Math::UCharToFloat(data[i * 2 + 0]);
|
||||
m_Data[i].y = Math::UCharToFloat(data[i * 2 + 1]);
|
||||
m_Data[i].z = 0.0f;
|
||||
m_Data[i].w = 0.0f;
|
||||
}
|
||||
}else if (channels == 1)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
m_Data[i].x = Math::UCharToFloat(data[i * 1 + 0]);
|
||||
m_Data[i].y = 0.0f;
|
||||
m_Data[i].z = 0.0f;
|
||||
m_Data[i].w = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
stbi_image_free((void*)data);
|
||||
}
|
||||
34
src/Core/Texture/Texture.h
Normal file
34
src/Core/Texture/Texture.h
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Created by sfd on 25-7-29.
|
||||
//
|
||||
|
||||
#ifndef TEXTURE_H
|
||||
#define TEXTURE_H
|
||||
#include <string>
|
||||
|
||||
#include "../Math.h"
|
||||
|
||||
|
||||
class Texture {
|
||||
public:
|
||||
Texture(const std::string& filepath);
|
||||
~Texture();
|
||||
|
||||
Math::Vec4 Sample(Math::Vec2 texCoords) const;
|
||||
static Texture* CreateTexture(const std::string& filepath);
|
||||
|
||||
private:
|
||||
|
||||
void Init();
|
||||
|
||||
private:
|
||||
int m_Width, m_Height, m_Channels;
|
||||
std::string m_Filepath;
|
||||
|
||||
Math::Vec4* m_Data;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //TEXTURE_H
|
||||
26
src/Core/Window/Window.cpp
Normal file
26
src/Core/Window/Window.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "Window.h"
|
||||
|
||||
#include "../Assert.h"
|
||||
#include "../../Platform/WindowsWindow.h"
|
||||
|
||||
|
||||
Window::Window(const std::string& name, const int width, const int height)
|
||||
: m_Name(name), m_Width(width), m_Height(height)
|
||||
{
|
||||
ASSERT((m_Width>0) && (m_Height>0));
|
||||
memset(m_Keys, KEY_RELEASE, sizeof(m_Keys));
|
||||
}
|
||||
|
||||
Window* Window::Create(const std::string& name, const int width, const int height)
|
||||
{
|
||||
return new WindowsWindow(name, width, height);
|
||||
}
|
||||
|
||||
void Window::PollInputEvents()
|
||||
{
|
||||
WindowsWindow::PollInputEvents();
|
||||
}
|
||||
46
src/Core/Window/Window.h
Normal file
46
src/Core/Window/Window.h
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef WINDOW_H
|
||||
#define WINDOW_H
|
||||
|
||||
#include "../Renderer/FrameBuffer.h"
|
||||
#include "../Input/InputCodes.h"
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
|
||||
class Window {
|
||||
public:
|
||||
static Window* Create(const std::string& name, int width, int height);
|
||||
|
||||
Window(const std::string& name, int width, int height);
|
||||
virtual ~Window() = default;
|
||||
|
||||
virtual void Show() const = 0;
|
||||
virtual void DrawFrameBuffer(const FrameBuffer& frameBuffer) = 0;
|
||||
|
||||
bool Close() const { return m_Closed; }
|
||||
|
||||
inline char GetKey(const uint32_t key) const { return m_Keys[key]; }
|
||||
|
||||
public:
|
||||
virtual void Init() = 0;
|
||||
virtual void Terminate() = 0;
|
||||
static void PollInputEvents();
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
std::string m_Name;
|
||||
int m_Width, m_Height;
|
||||
bool m_Closed = true;
|
||||
|
||||
char m_Keys[KEY_MAX_COUNT];
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //WINDOW_H
|
||||
220
src/Platform/WindowsWindow.cpp
Normal file
220
src/Platform/WindowsWindow.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "../Platform/WindowsWindow.h"
|
||||
#include <windows.h>
|
||||
|
||||
#include "../Core/Assert.h"
|
||||
#define WINDOW_ENTRY_NAME "Entry"
|
||||
#define WINDOW_CLASS_NAME "Class"
|
||||
|
||||
|
||||
bool WindowsWindow::s_Inited = false;
|
||||
|
||||
void WindowsWindow::Init()
|
||||
{
|
||||
ASSERT(!s_Inited);
|
||||
Register();
|
||||
s_Inited = true;
|
||||
}
|
||||
|
||||
void WindowsWindow::Terminate()
|
||||
{
|
||||
ASSERT(s_Inited);
|
||||
UnRegister();
|
||||
s_Inited = false;
|
||||
}
|
||||
|
||||
void WindowsWindow::Register()
|
||||
{
|
||||
ATOM atom;
|
||||
WNDCLASS wc = { 0 };
|
||||
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hbrBackground = (HBRUSH)(WHITE_BRUSH); // background Color
|
||||
wc.hCursor = NULL; // default cursor
|
||||
wc.hIcon = NULL; // default Icon
|
||||
wc.hInstance = GetModuleHandle(NULL);
|
||||
wc.lpfnWndProc = WindowsWindow::WindowProc; // window process function
|
||||
wc.lpszClassName = WINDOW_CLASS_NAME;
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW; // redraw when window resize
|
||||
wc.lpszMenuName = NULL; // no Menu
|
||||
atom = RegisterClass(&wc); // register Window
|
||||
}
|
||||
|
||||
void WindowsWindow::UnRegister()
|
||||
{
|
||||
UnregisterClass(WINDOW_CLASS_NAME, GetModuleHandle(NULL));
|
||||
}
|
||||
|
||||
void WindowsWindow::KeyPressImpl(WindowsWindow* window, const WPARAM wparam, const char state)
|
||||
{
|
||||
if (wparam >= '0' && wparam <= '9')
|
||||
{
|
||||
window->m_Keys[wparam] = state;
|
||||
return;
|
||||
}
|
||||
|
||||
if (wparam >= 'A' && wparam <= 'Z')
|
||||
{
|
||||
window->m_Keys[wparam] = state;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (wparam)
|
||||
{
|
||||
case VK_SPACE: window->m_Keys[KEY_SPACE] = state; break;
|
||||
case VK_SHIFT:
|
||||
{
|
||||
window->m_Keys[KEY_LEFT_SHIFT] = state;
|
||||
window->m_Keys[KEY_RIGHT_SHIFT] = state;
|
||||
}
|
||||
break;
|
||||
case VK_CONTROL:
|
||||
{
|
||||
window->m_Keys[KEY_LEFT_CONTROL] = state;
|
||||
window->m_Keys[KEY_RIGHT_CONTROL] = state;
|
||||
}
|
||||
|
||||
case VK_ESCAPE: window->m_Keys[KEY_ESCAPE] = state; break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
WindowsWindow::WindowsWindow(const std::string& name, const int width, const int height)
|
||||
: Window(name, width, height), m_Buffer(nullptr)
|
||||
{
|
||||
Init();
|
||||
|
||||
ASSERT(s_Inited, "not Init");
|
||||
|
||||
DWORD style = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
|
||||
RECT rect;
|
||||
rect.left = 0;
|
||||
rect.top = 0;
|
||||
rect.right = width;
|
||||
rect.bottom = height;
|
||||
|
||||
AdjustWindowRect(&rect, style, false);
|
||||
|
||||
m_hWnd = CreateWindow(
|
||||
WINDOW_CLASS_NAME,
|
||||
m_Name.c_str(),
|
||||
style,
|
||||
CW_USEDEFAULT,
|
||||
0,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
NULL,
|
||||
NULL,
|
||||
GetModuleHandle(NULL),
|
||||
NULL);
|
||||
|
||||
ASSERT(m_hWnd != NULL);
|
||||
m_Closed = false;
|
||||
SetProp(m_hWnd, WINDOW_ENTRY_NAME, this);
|
||||
|
||||
HDC hdc = GetDC(m_hWnd);
|
||||
m_MemoryDC = CreateCompatibleDC(hdc);
|
||||
|
||||
BITMAPINFOHEADER bitHeader = {};
|
||||
HBITMAP newBitMap;
|
||||
HBITMAP oldBitMap;
|
||||
|
||||
bitHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bitHeader.biWidth = ((long)width);
|
||||
bitHeader.biHeight = -((long)height);
|
||||
bitHeader.biPlanes = 1;
|
||||
bitHeader.biBitCount = 24;
|
||||
bitHeader.biCompression = BI_RGB;
|
||||
|
||||
// alloc memory
|
||||
newBitMap = CreateDIBSection(m_MemoryDC, (BITMAPINFO*)&bitHeader, DIB_RGB_COLORS, (void**)&m_Buffer, nullptr, 0);
|
||||
ASSERT(newBitMap != NULL);
|
||||
|
||||
constexpr int channelCount = 3;
|
||||
|
||||
int size = width * height * channelCount * sizeof(unsigned char);
|
||||
memset(m_Buffer, 0, size);
|
||||
oldBitMap = (HBITMAP)SelectObject(m_MemoryDC, newBitMap);
|
||||
|
||||
DeleteObject(oldBitMap);
|
||||
ReleaseDC(m_hWnd, m_MemoryDC);
|
||||
|
||||
WindowsWindow::Show();
|
||||
}
|
||||
|
||||
WindowsWindow::~WindowsWindow()
|
||||
{
|
||||
ShowWindow(m_hWnd, SW_HIDE);
|
||||
RemoveProp(m_hWnd, WINDOW_ENTRY_NAME);
|
||||
DeleteDC(m_MemoryDC);
|
||||
DeleteObject(m_MemoryDC);
|
||||
}
|
||||
|
||||
void WindowsWindow::Show() const
|
||||
{
|
||||
HDC windowDC = GetDC(m_hWnd);
|
||||
BitBlt(windowDC, 0, 0, m_Width, m_Height, m_MemoryDC, 0, 0, SRCCOPY);
|
||||
ShowWindow(m_hWnd, SW_SHOW);
|
||||
ReleaseDC(m_hWnd, windowDC);
|
||||
}
|
||||
|
||||
void WindowsWindow::DrawFrameBuffer(const FrameBuffer& frameBuffer)
|
||||
{
|
||||
const int fWidth = frameBuffer.GetWidth();
|
||||
const int fHeight = frameBuffer.GetHeight();
|
||||
const int width = m_Width < fWidth ? m_Width : fWidth;
|
||||
const int height = m_Height < fHeight ? m_Height : fHeight;
|
||||
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
// 反转RGB显示
|
||||
constexpr int channelCount = 3;
|
||||
constexpr int rChannel = 2;
|
||||
constexpr int gChannel = 1;
|
||||
constexpr int bChannel = 0;
|
||||
|
||||
Math::Vec3 color = frameBuffer.GetColor(j, fHeight - i - 1);
|
||||
const int pixelStart = (i * width + j) * channelCount;
|
||||
const int rIndex = pixelStart + rChannel;
|
||||
const int gIndex = pixelStart + gChannel;
|
||||
const int bIndex = pixelStart + bChannel;
|
||||
|
||||
m_Buffer[rIndex] = Math::FloatToUchar(color.r);
|
||||
m_Buffer[gIndex] = Math::FloatToUchar(color.g);
|
||||
m_Buffer[bIndex] = Math::FloatToUchar(color.b);
|
||||
}
|
||||
}
|
||||
Show();
|
||||
}
|
||||
|
||||
LRESULT WindowsWindow::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
WindowsWindow* window = (WindowsWindow*)GetProp(hWnd, WINDOW_ENTRY_NAME);
|
||||
if (window == nullptr) return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case WM_DESTROY: window->m_Closed = true; return 0;
|
||||
case WM_KEYDOWN: KeyPressImpl(window, wParam, KEY_PRESS); return 0;
|
||||
case WM_KEYUP: KeyPressImpl(window, wParam, KEY_RELEASE); return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
void WindowsWindow::PollInputEvents()
|
||||
{
|
||||
MSG message;
|
||||
while (PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
|
||||
{
|
||||
TranslateMessage(&message);
|
||||
DispatchMessage(&message);
|
||||
}
|
||||
}
|
||||
|
||||
43
src/Platform/WindowsWindow.h
Normal file
43
src/Platform/WindowsWindow.h
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef WINDOWSWINDOW_H
|
||||
#define WINDOWSWINDOW_H
|
||||
#include <Windows.h>
|
||||
|
||||
#include "../Core/Window/Window.h"
|
||||
|
||||
|
||||
class WindowsWindow : public Window{
|
||||
public:
|
||||
WindowsWindow(const std::string& name, int width, int height);
|
||||
virtual ~WindowsWindow();
|
||||
|
||||
virtual void Show() const override;
|
||||
virtual void DrawFrameBuffer(const FrameBuffer& frameBuffer) override;
|
||||
|
||||
public:
|
||||
void Init() override;
|
||||
void Terminate() override;
|
||||
static void PollInputEvents();
|
||||
|
||||
private:
|
||||
static void Register();
|
||||
static void UnRegister();
|
||||
|
||||
static void KeyPressImpl(WindowsWindow* window, WPARAM wparam, char state);
|
||||
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
|
||||
private:
|
||||
HWND m_hWnd;
|
||||
HDC m_MemoryDC;
|
||||
|
||||
unsigned char* m_Buffer;
|
||||
static bool s_Inited;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //WINDOWSWINDOW_H
|
||||
50
src/Shader/DemoShader.cpp
Normal file
50
src/Shader/DemoShader.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#include "DemoShader.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
void DemoVertexShader(BlinnVaryings& varyings, const BlinnVertex& vertex, const BlinnUniform& uniform)
|
||||
{
|
||||
varyings.ClipPos = uniform.MVP * vertex.ModelPosition;
|
||||
varyings.TexCoord = vertex.TexCoord;
|
||||
varyings.WorldPos = uniform.Model * vertex.ModelPosition;
|
||||
varyings.WorldNormal = uniform.ModelNormalToWorld * Math::Vec4{ vertex.ModelNormal, 0.0f};
|
||||
}
|
||||
|
||||
Math::Vec4 DemoFragmentShader(bool& discard, const BlinnVaryings& varyings, const BlinnUniform& uniform)
|
||||
{
|
||||
discard = false;
|
||||
|
||||
const Math::Vec3 cameraPos = uniform.Camera;
|
||||
const Math::Vec3 lightPos = uniform.LightPos;
|
||||
const Math::Vec3 worldPos = varyings.WorldPos;
|
||||
|
||||
Math::Vec3 worldNormal = Math::Normalize(varyings.WorldNormal);
|
||||
const Math::Vec3 viewDir = Math::Normalize(cameraPos - worldPos);
|
||||
const Math::Vec3 lightDir = Math::Normalize(lightPos - worldPos);
|
||||
|
||||
Math::Vec3 halfDir = Math::Normalize(viewDir + lightDir);
|
||||
|
||||
Math::Vec3 ambient = uniform.LightAmbient;
|
||||
Math::Vec3 specularStrength {1.0f, 1.0f, 1.0f};
|
||||
Math::Vec3 diffColor {1.0f , 1.0f , 1.0f};
|
||||
if (uniform.Diffuse)
|
||||
{
|
||||
const Math::Vec2& texCoord = varyings.TexCoord;
|
||||
diffColor = uniform.Diffuse->Sample(texCoord);
|
||||
ambient = ambient * diffColor;
|
||||
if (uniform.Specular)
|
||||
specularStrength = uniform.Specular->Sample(texCoord);
|
||||
}
|
||||
|
||||
|
||||
Math::Vec3 diffuse = std::max(0.0f, Math::Dot(worldNormal, lightDir)) * uniform.LightDiffuse * diffColor;
|
||||
Math::Vec3 specular = (float)pow(std::max(0.0f, Math::Dot(halfDir, worldNormal)), uniform.Shininess) * uniform.LightSPecular * specularStrength;
|
||||
|
||||
Math::Vec3 result = (ambient + diffuse + specular);
|
||||
|
||||
return {result, 1.0f};
|
||||
}
|
||||
44
src/Shader/DemoShader.h
Normal file
44
src/Shader/DemoShader.h
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// Created by sfd on 25-7-28.
|
||||
//
|
||||
|
||||
#ifndef BLINNSHADER_H
|
||||
#define BLINNSHADER_H
|
||||
#include "../Core/Renderer/ShaderBase.h"
|
||||
#include "../Core/Texture/Texture.h"
|
||||
|
||||
|
||||
struct BlinnVertex : public VertexBase
|
||||
{
|
||||
Math::Vec3 ModelNormal;
|
||||
Math::Vec2 TexCoord {0.0f, 0.0f};
|
||||
};
|
||||
|
||||
struct BlinnVaryings : public VaryingBase
|
||||
{
|
||||
Math::Vec3 WorldPos;
|
||||
Math::Vec3 WorldNormal;
|
||||
Math::Vec2 TexCoord;
|
||||
};
|
||||
|
||||
struct BlinnUniform : public UniformBase
|
||||
{
|
||||
Math::Mat4 Model;
|
||||
Math::Mat4 ModelNormalToWorld;
|
||||
Math::Vec3 LightPos { 0.0f, 1.0f, 2.0f};
|
||||
Math::Vec3 LightAmbient { 0.3f, 0.3f, 0.3f};
|
||||
Math::Vec3 LightDiffuse { 0.5f, 0.5f, 0.5f};
|
||||
Math::Vec3 LightSPecular { 1.0f, 1.0f, 1.0f};
|
||||
Math::Vec3 ObjectColor { 1.0f, 1.0f, 1.0f};
|
||||
Math::Vec3 Camera;
|
||||
|
||||
Texture* Diffuse = nullptr;
|
||||
Texture* Specular = nullptr;
|
||||
|
||||
float Shininess = 64.0f;
|
||||
};
|
||||
|
||||
void DemoVertexShader(BlinnVaryings& varyings, const BlinnVertex& vertex, const BlinnUniform& uniform);
|
||||
Math::Vec4 DemoFragmentShader(bool& discard, const BlinnVaryings& varyings, const BlinnUniform& uniform);
|
||||
|
||||
#endif //BLINNSHADER_H
|
||||
8
src/main.cpp
Normal file
8
src/main.cpp
Normal file
@ -0,0 +1,8 @@
|
||||
#include "Application/Application.h"
|
||||
|
||||
int main(const int argc, char **argv)
|
||||
{
|
||||
Application app("demo", 400, 400, {argc, argv});
|
||||
app.Run();
|
||||
return 0;
|
||||
}
|
||||
6
src/vendor/stb/stb_image.cpp
vendored
Normal file
6
src/vendor/stb/stb_image.cpp
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
//
|
||||
// Created by sfd on 25-7-29.
|
||||
//
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
7988
src/vendor/stb/stb_image.h
vendored
Normal file
7988
src/vendor/stb/stb_image.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user