41 changed files with 1977 additions and 2151 deletions
@ -0,0 +1,49 @@
|
||||
cmake_minimum_required(VERSION 3.14) |
||||
project(renderer) |
||||
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17) |
||||
set(CMAKE_CXX_EXTENSIONS OFF) |
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) |
||||
|
||||
|
||||
find_package(SDL2 REQUIRED) |
||||
|
||||
|
||||
add_executable(renderer) |
||||
|
||||
target_compile_options( |
||||
renderer |
||||
PRIVATE |
||||
-fno-exceptions |
||||
-Wall) |
||||
|
||||
target_sources( |
||||
renderer |
||||
PRIVATE |
||||
Source/Engine.cpp |
||||
Source/Loader.cpp |
||||
Source/Main.cpp |
||||
Source/Platform.cpp |
||||
Source/Render.cpp |
||||
Source/Transform.cpp) |
||||
|
||||
target_sources( |
||||
renderer |
||||
PRIVATE |
||||
Source/Camera.hpp |
||||
Source/Color.hpp |
||||
Source/Geometry.hpp |
||||
Source/Matrix.hpp |
||||
Source/Point.hpp |
||||
Source/Vec.hpp) |
||||
|
||||
target_include_directories( |
||||
renderer |
||||
PRIVATE |
||||
Source) |
||||
|
||||
target_link_libraries( |
||||
renderer |
||||
PRIVATE |
||||
SDL2::SDL2) |
@ -1,61 +0,0 @@
|
||||
# Verbosity of make output
|
||||
ifeq ("$(VERBOSE)","1") |
||||
V :=
|
||||
else |
||||
V := @
|
||||
endif |
||||
|
||||
# Optimizations
|
||||
ifeq ("$(OPTIMIZE)","0") |
||||
O := -O0
|
||||
else ifeq ("$(OPTIMIZE)","1") |
||||
O := -O1
|
||||
else ifeq ("$(OPTIMIZE)", "2") |
||||
O := -O2
|
||||
else ifeq ("$(OPTIMIZE)", "3") |
||||
O := -O3
|
||||
else |
||||
O := -O3
|
||||
endif |
||||
|
||||
# Debugging
|
||||
ifeq ("$(DEBUG)","1") |
||||
D := -g
|
||||
else |
||||
D :=
|
||||
endif |
||||
|
||||
|
||||
SRC_DIR=src
|
||||
INCLUDE_DIR=include
|
||||
BUILD_DIR=build
|
||||
|
||||
CC=clang++
|
||||
WARNINGS_ON=-Weverything
|
||||
WARNINGS_OFF=-Wno-missing-braces -Wno-gnu-anonymous-struct -Wno-old-style-cast\
|
||||
-Wno-zero-as-null-pointer-constant -Wno-nested-anon-types\
|
||||
-Wno-padded -Wno-exit-time-destructors -Wno-global-constructors\
|
||||
-Wno-c++98-compat
|
||||
|
||||
CFLAGS=$(D) $(O) -std=c++11 $(WARNINGS_ON) $(WARNINGS_OFF) -I$(INCLUDE_DIR)
|
||||
LIBS=-lSDL2
|
||||
|
||||
_HEADERS = camera.h color.h engine.h geometry.h light.h loader.h matrix.h\
|
||||
platform.h point.h render.h transform.h util.h vec.h
|
||||
HEADERS = $(patsubst %,$(INCLUDE_DIR)/%,$(_HEADERS))
|
||||
|
||||
_OBJS = engine.o light.o loader.o main.o platform.o render.o\
|
||||
transform.o
|
||||
OBJS = $(patsubst %,$(BUILD_DIR)/%,$(_OBJS))
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp $(HEADERS) |
||||
@ if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
|
||||
$(V) $(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(BUILD_DIR)/engine: $(OBJS) |
||||
$(V) $(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
.PHONY: clean |
||||
|
||||
clean: |
||||
rm -rf $(BUILD_DIR)
|
@ -0,0 +1,47 @@
|
||||
#pragma once |
||||
|
||||
#include "Engine.hpp" |
||||
#include "Point.hpp" |
||||
#include <cmath> |
||||
|
||||
|
||||
constexpr float DegToRad(float degrees) |
||||
{ |
||||
return degrees * (float)M_PI / 180.0f; |
||||
} |
||||
|
||||
|
||||
class Camera |
||||
{ |
||||
public: |
||||
Camera() |
||||
{ |
||||
position.x = 0.0f; |
||||
position.y = 0.0f; |
||||
position.z = 0.0f; |
||||
|
||||
rotation.x = 0.0f; |
||||
rotation.y = 0.0f; |
||||
rotation.z = 0.0f; |
||||
|
||||
zClipBias0 = |
||||
(CAMERA_FAR_CLIP + CAMERA_NEAR_CLIP) |
||||
/ (CAMERA_FAR_CLIP - CAMERA_NEAR_CLIP); |
||||
|
||||
zClipBias1 = |
||||
(-2.0f * CAMERA_FAR_CLIP * CAMERA_NEAR_CLIP) |
||||
/ (CAMERA_FAR_CLIP - CAMERA_NEAR_CLIP); |
||||
|
||||
xZoom = 1.0f / tanf(DegToRad(CAMERA_FOV / 2.0f)); |
||||
yZoom = (xZoom * WINDOW_WIDTH) / WINDOW_HEIGHT; |
||||
|
||||
xScale = (0.5f * WINDOW_WIDTH) - 0.5f; |
||||
yScale = (0.5f * WINDOW_HEIGHT) - 0.5f; |
||||
} |
||||
|
||||
Point position; |
||||
Point rotation; |
||||
float zClipBias0, zClipBias1; |
||||
float xZoom, yZoom; |
||||
float xScale, yScale; |
||||
}; |
@ -0,0 +1,116 @@
|
||||
#pragma once |
||||
|
||||
#include <algorithm> |
||||
#include <cstdint> |
||||
|
||||
|
||||
class ColorU32 |
||||
{ |
||||
public: |
||||
ColorU32() |
||||
: b(0), g(0), r(0), a(0) |
||||
{} |
||||
|
||||
ColorU32(uint8_t b, uint8_t g, uint8_t r, uint8_t a) |
||||
: b(b), g(g), r(r), a(a) |
||||
{} |
||||
|
||||
union
|
||||
{ |
||||
struct
|
||||
{ |
||||
uint8_t b, g, r, a; |
||||
}; |
||||
|
||||
uint32_t u32; |
||||
}; |
||||
}; |
||||
|
||||
class ColorF32 |
||||
{ |
||||
public: |
||||
ColorF32() |
||||
: b(0.0f), g(0.0f), r(0.0f), a(0.0f) |
||||
{} |
||||
|
||||
ColorF32(float b, float g, float r, float a) |
||||
: b(b), g(g), r(r), a(a) |
||||
{} |
||||
|
||||
void Scale() |
||||
{ |
||||
float blue = std::max(b, 0.0f); |
||||
float green = std::max(g, 0.0f); |
||||
float red = std::max(r, 0.0f); |
||||
float alpha = std::max(a, 0.0f); |
||||
float max = std::max(std::max(std::max(blue, green), red), 1.0f); |
||||
|
||||
ColorF32 scaled(blue, green, red, alpha); |
||||
scaled /= max; |
||||
|
||||
b = scaled.b; |
||||
g = scaled.g; |
||||
r = scaled.r; |
||||
a = scaled.a; |
||||
} |
||||
|
||||
float b, g, r, a; |
||||
|
||||
ColorF32 operator+(ColorF32 const& rhs) const |
||||
{ |
||||
return ColorF32( |
||||
b + rhs.b, |
||||
g + rhs.g, |
||||
r + rhs.r, |
||||
a + rhs.a); |
||||
} |
||||
|
||||
ColorF32& operator+=(ColorF32 const& rhs) |
||||
{ |
||||
b += rhs.b; |
||||
g += rhs.g; |
||||
r += rhs.r; |
||||
a += rhs.a; |
||||
|
||||
return *this; |
||||
} |
||||
|
||||
ColorF32 operator*(float f) const |
||||
{ |
||||
return ColorF32( |
||||
b * f, |
||||
g * f, |
||||
r * f, |
||||
a * f); |
||||
} |
||||
|
||||
ColorF32 operator*(ColorF32 const& rhs) const |
||||
{ |
||||
return ColorF32( |
||||
b * rhs.b, |
||||
g * rhs.g, |
||||
r * rhs.r, |
||||
a * rhs.a); |
||||
} |
||||
|
||||
ColorF32 operator/(float f) const |
||||
{ |
||||
float fInv = 1.0f / f; |
||||
|
||||
return ColorF32( |
||||
b * fInv, |
||||
g * fInv, |
||||
r * fInv, |
||||
a * fInv); |
||||
} |
||||
|
||||
ColorF32& operator/=(float f) |
||||
{ |
||||
b /= f; |
||||
g /= f; |
||||
r /= f; |
||||
a /= f; |
||||
|
||||
return *this; |
||||
} |
||||
}; |
@ -0,0 +1,345 @@
|
||||
#include "Camera.hpp" |
||||
#include "Color.hpp" |
||||
#include "Engine.hpp" |
||||
#include "Geometry.hpp" |
||||
#include "Light.hpp" |
||||
#include "Loader.hpp" |
||||
#include "Matrix.hpp" |
||||
#include "Render.hpp" |
||||
#include "Transform.hpp" |
||||
#include "Vec.hpp" |
||||
#include <cstring> |
||||
#include <cstdio> |
||||
|
||||
|
||||
unsigned long constexpr CheckBit(uint32_t x, unsigned long bit) |
||||
{ |
||||
return x & (1UL << bit); |
||||
} |
||||
|
||||
static Mesh mesh; |
||||
static Camera camera; |
||||
static Light light; |
||||
static Matrix tPersp; |
||||
static Matrix tScreen; |
||||
static EngineMemory memory; |
||||
|
||||
|
||||
static void ComputeNormals(); |
||||
static void CheckInputs(uint32_t input); |
||||
static void ClearDepthBuffer(); |
||||
static void TransformToClipSpace(); |
||||
static void ClipAndCull(); |
||||
static void TransformToScreenSpace(); |
||||
static void LightMesh(); |
||||
|
||||
|
||||
int EngineInit(char* objFilename, char* mtlFilename) |
||||
{ |
||||
int result; |
||||
|
||||
|
||||
result = ParseOBJ(objFilename, memory); |
||||
if (result < 0) |
||||
{ |
||||
return -1; |
||||
} |
||||
|
||||
|
||||
result = ParseMTL(mtlFilename, memory); |
||||
if (result < 0) |
||||
{ |
||||
return -1; |
||||
} |
||||
|
||||
|
||||
printf("Verts: %lu\n", memory.localVerts.size); |
||||
printf("Faces: %lu\n", memory.localFaces.size); |
||||
printf("Materials: %lu\n", memory.materials.size); |
||||
|
||||
memory.transVerts.size = memory.localVerts.size; |
||||
|
||||
|
||||
// Compute vertex and face normals for lighting calculation
|
||||
ComputeNormals(); |
||||
|
||||
|
||||
// Mesh configuration
|
||||
mesh.position.z = 200; |
||||
mesh.position.y = -100; |
||||
mesh.scale = 1.0f; |
||||
|
||||
|
||||
// Light configuration
|
||||
light.position = Point(-300.0f, 200.0f, 0.0f); |
||||
light.color = ColorF32(1.0f, 1.0f, 1.0f, 1.0f); |
||||
light.intensity = 2.0f; |
||||
light.falloffConstant = 1.0f; |
||||
light.falloffLinear = 0.001f; |
||||
|
||||
|
||||
// Transformation matrices that do not change
|
||||
tPersp = Transform_Perspective(camera); |
||||
tScreen = Transform_Screen(camera); |
||||
|
||||
|
||||
return 0; |
||||
} |
||||
|
||||
void EngineRender(EngineBuffer& buffer, uint32_t input) |
||||
{ |
||||
// Check for user input
|
||||
CheckInputs(input); |
||||
|
||||
|
||||
// Clear the z-buffer
|
||||
ClearDepthBuffer(); |
||||
|
||||
|
||||
// Transform vertices to clip space
|
||||
TransformToClipSpace(); |
||||
|
||||
|
||||
// Clip near/far Z and cull backfaces
|
||||
ClipAndCull(); |
||||
|
||||
|
||||
// Light vertices and/or faces
|
||||
LightMesh(); |
||||
|
||||
|
||||
// Transform vertices to screen space
|
||||
TransformToScreenSpace(); |
||||
|
||||
|
||||
// Render
|
||||
Render(buffer, memory); |
||||
} |
||||
|
||||
void EngineShutdown() |
||||
{ |
||||
} |
||||
|
||||
|
||||
// PRIVATE FUNCTIONS
|
||||
static void CheckInputs(uint32_t input) |
||||
{ |
||||
if (CheckBit(input, TRANSLATE_X_POS)) |
||||
{ |
||||
light.position.x += 10; |
||||
} |
||||
else if (CheckBit(input, TRANSLATE_X_NEG)) |
||||
{ |
||||
light.position.x -= 10; |
||||
} |
||||
|
||||
if (CheckBit(input, TRANSLATE_Z_POS)) |
||||
{ |
||||
light.position.z += 10; |
||||
} |
||||
else if (CheckBit(input, TRANSLATE_Z_NEG)) |
||||
{ |
||||
light.position.z -= 10; |
||||
} |
||||
|
||||
if (CheckBit(input, TRANSLATE_Y_POS)) |
||||
{ |
||||
light.position.y += 10; |
||||
} |
||||
else if (CheckBit(input, TRANSLATE_Y_NEG)) |
||||
{ |
||||
light.position.y -= 10; |
||||
} |
||||
|
||||
if (CheckBit(input, ROTATE_X_POS)) |
||||
{ |
||||
mesh.rotation.x += 0.10f; |
||||
} |
||||
else if (CheckBit(input, ROTATE_X_NEG)) |
||||
{ |
||||
mesh.rotation.x -= 0.10f; |
||||
} |
||||
|
||||
if (CheckBit(input, ROTATE_Y_POS)) |
||||
{ |
||||
mesh.rotation.y += 0.10f; |
||||
} |
||||
else if (CheckBit(input, ROTATE_Y_NEG)) |
||||
{ |
||||
mesh.rotation.y -= 0.10f; |
||||
} |
||||
|
||||
if (CheckBit(input, ROTATE_Z_POS)) |
||||
{ |
||||
mesh.rotation.z += 0.10f; |
||||
} |
||||
else if (CheckBit(input, ROTATE_Z_NEG)) |
||||
{ |
||||
mesh.rotation.z -= 0.10f; |
||||
} |
||||
|
||||
if (CheckBit(input, SCALE_UP)) |
||||
{ |
||||
light.color.b = 0.0f; |
||||
light.color.g = 0.0f; |
||||
light.color.r = 1.0f; |
||||
} |
||||
else if (CheckBit(input, SCALE_DOWN)) |
||||
{ |
||||
light.color.b = 1.0f; |
||||
light.color.g = 1.0f; |
||||
light.color.r = 1.0f; |
||||
} |
||||
} |
||||
|
||||
|
||||
static void ComputeNormals() |
||||
{ |
||||
VertexList& verts = memory.localVerts; |
||||
FaceList& faces = memory.localFaces; |
||||
|
||||
int vertexNormalCount[VERTEX_LIMIT] = {}; |
||||
|
||||
for (size_t f = 0; f < faces.size; ++f) |
||||
{ |
||||
Face& face = faces.data[f]; |
||||
|
||||
Vertex& vert0 = verts.data[face.vertIndex[0]]; |
||||
Vertex& vert1 = verts.data[face.vertIndex[1]]; |
||||
Vertex& vert2 = verts.data[face.vertIndex[2]]; |
||||
|
||||
|
||||
Vector v01 = vert1.position - vert0.position; |
||||
Vector v02 = vert2.position - vert0.position; |
||||
|
||||
Vector normal = Vector::Cross(v01, v02); |
||||
|
||||
// Add each vertex's normal to the sum for future averaging
|
||||
vert0.normal += normal; |
||||
vert1.normal += normal; |
||||
vert2.normal += normal; |
||||
|
||||
++vertexNormalCount[face.vertIndex[0]]; |
||||
++vertexNormalCount[face.vertIndex[1]]; |
||||
++vertexNormalCount[face.vertIndex[2]]; |
||||
} |
||||
|
||||
for (size_t v = 0; v < verts.size; ++v) |
||||
{ |
||||
if (vertexNormalCount[v] > 0) |
||||
{ |
||||
// Compute the average normal for this vertex
|
||||
verts.data[v].normal /= static_cast<float>(vertexNormalCount[v]); |
||||
verts.data[v].normal.Normalize(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
static void ClearDepthBuffer() |
||||
{ |
||||
memset(memory.zbuffer, 0, sizeof(memory.zbuffer)); |
||||
} |
||||
|
||||
|
||||
static void TransformToClipSpace() |
||||
{ |
||||
VertexList& localVerts = memory.localVerts; |
||||
VertexList& transVerts = memory.transVerts; |
||||
|
||||
Matrix tTranslate = Transform_Translate(mesh.position); |
||||
Matrix tRotate = Transform_Rotate(mesh.rotation); |
||||
Matrix tScale = Transform_Scale(mesh.scale); |
||||
Matrix tView = Transform_View(camera); |
||||
|
||||
for (size_t v = 0; v < localVerts.size; ++v) |
||||
{ |
||||
transVerts.data[v].position = |
||||
localVerts.data[v].position * tScale * tRotate * tTranslate * tView * tPersp; |
||||
|
||||
transVerts.data[v].normal = |
||||
localVerts.data[v].normal * tScale * tRotate * tTranslate; |
||||
} |
||||
} |
||||
|
||||
void ClipAndCull() |
||||
{ |
||||
FaceList& localFaces = memory.localFaces; |
||||
FaceList& transFaces = memory.transFaces; |
||||
VertexList& verts = memory.transVerts; |
||||
|
||||
int faceIndex = 0; |
||||
|
||||
for (size_t f = 0; f < localFaces.size; ++f) |
||||
{ |
||||
Face& face = localFaces.data[f]; |
||||
|
||||
Point& p0 = verts.data[face.vertIndex[0]].position; |
||||
Point& p1 = verts.data[face.vertIndex[1]].position; |
||||
Point& p2 = verts.data[face.vertIndex[2]].position; |
||||
|
||||
// Ignore this face if its Z is outside the Z clip planes
|
||||
if ((p0.z < -p0.w) |
||||
|| (p0.z > p0.w) |
||||
|| (p1.z < -p1.w) |
||||
|| (p1.z > p1.w) |
||||
|| (p2.z < -p2.w) |
||||
|| (p2.z > p2.w)) |
||||
{ |
||||
continue; |
||||
} |
||||
|
||||
|
||||
// Calculate the face's normal (inverted for Blender-compatibility)
|
||||
Vector v01 = p1 - p0; |
||||
Vector v02 = p2 - p0; |
||||
Vector normal = -Vector::Cross(v01, v02); |
||||
|
||||
// Eye vector to viewport
|
||||
Vector view = camera.position - p0; |
||||
|
||||
float dot = Vector::Dot(normal, view); |
||||
|
||||
// Not a backface; add it to the list
|
||||
if (dot < 0.0f) |
||||
{ |
||||
transFaces.data[faceIndex] = face; |
||||
++faceIndex; |
||||
transFaces.size = (size_t)faceIndex; |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void TransformToScreenSpace() |
||||
{ |
||||
VertexList& verts = memory.transVerts; |
||||
|
||||
for (size_t v = 0; v < verts.size; ++v) |
||||
{ |
||||
verts.data[v].position *= tScreen; |
||||
verts.data[v].position.x /= verts.data[v].position.w; |
||||
verts.data[v].position.y /= verts.data[v].position.w; |
||||
verts.data[v].position.z /= verts.data[v].position.w; |
||||
} |
||||
} |
||||
|
||||
static void LightMesh() |
||||
{ |
||||
VertexList& verts = memory.transVerts; |
||||
FaceList& faces = memory.transFaces; |
||||
MaterialList& materials = memory.materials; |
||||
|
||||
for (size_t f = 0; f < faces.size; ++f) |
||||
{ |
||||
Face& face = faces.data[f]; |
||||
Material& material = materials.data[face.materialIndex]; |
||||
|
||||
// Gouraud shading
|
||||
for (auto index : face.vertIndex) |
||||
{ |
||||
Vertex& vert = verts.data[index]; |
||||
|
||||
vert.color = light.Compute(vert.position, vert.normal, material, camera); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,55 @@
|
||||
#pragma once |
||||
|
||||
#include "Geometry.hpp" |
||||
#include <cstdint> |
||||
|
||||
|
||||
const int WINDOW_WIDTH = 1920; |
||||
const int WINDOW_HEIGHT = 1080; |
||||
const int WINDOW_FPS = 30; |
||||
const float CAMERA_FOV = 90.0f; |
||||
const float CAMERA_NEAR_CLIP = 5.0f; |
||||
const float CAMERA_FAR_CLIP = 600.0f; |
||||
|
||||
|
||||
enum EngineInput |
||||
{ |
||||
TRANSLATE_X_POS, |
||||
TRANSLATE_X_NEG, |
||||
TRANSLATE_Y_POS, |
||||
TRANSLATE_Y_NEG, |
||||
TRANSLATE_Z_POS, |
||||
TRANSLATE_Z_NEG, |
||||
ROTATE_X_POS, |
||||
ROTATE_X_NEG, |
||||
ROTATE_Y_POS, |
||||
ROTATE_Y_NEG, |
||||
ROTATE_Z_POS, |
||||
ROTATE_Z_NEG, |
||||
SCALE_UP, |
||||
SCALE_DOWN |
||||
}; |
||||
|
||||
|
||||
struct EngineBuffer |
||||
{ |
||||
uint32_t* buffer; |
||||
int width; |
||||
int height; |
||||
}; |
||||
|
||||
struct EngineMemory |
||||
{ |
||||
float zbuffer[WINDOW_HEIGHT][WINDOW_WIDTH]; |
||||
VertexList localVerts; |
||||
VertexList transVerts; |
||||
FaceList localFaces; |
||||
FaceList transFaces; |
||||
UVList uvs; |
||||
MaterialList materials; |
||||
TextureList textures; |
||||
}; |
||||
|
||||
int EngineInit(char* objFilename, char* mtlFilename); |
||||
void EngineRender(EngineBuffer& buffer, uint32_t input); |
||||
void EngineShutdown(); |
@ -0,0 +1,85 @@
|
||||
#pragma once |
||||
|
||||
#include "Color.hpp" |
||||
#include "Point.hpp" |
||||
#include "Vec.hpp" |
||||
#include <cstdint> |
||||
|
||||
|
||||
const int FACE_LIMIT = 30000; |
||||
const int MATERIAL_LIMIT = 5; |
||||
const int TEXTURE_SIZE_LIMIT = 1024; |
||||
const int VERTEX_LIMIT = 20000; |
||||
|
||||
|
||||
struct Texture |
||||
{ |
||||
ColorU32 texels[TEXTURE_SIZE_LIMIT][TEXTURE_SIZE_LIMIT]; |
||||
unsigned int width; |
||||
unsigned int height; |
||||
}; |
||||
|
||||
struct TextureList |
||||
{ |
||||
Texture data[MATERIAL_LIMIT]; |
||||
}; |
||||
|
||||
struct Material |
||||
{ |
||||
ColorF32 ambient; |
||||
ColorF32 diffuse; |
||||
ColorF32 specular; |
||||
float glossiness; |
||||
float opacity; |
||||
}; |
||||
|
||||
struct MaterialList |
||||
{ |
||||
Material data[MATERIAL_LIMIT]; |
||||
unsigned long size; |
||||
}; |
||||
|
||||
struct UV |
||||
{ |
||||
float u; |
||||
float v; |
||||
}; |
||||
|
||||
struct UVList |
||||
{ |
||||
UV data[VERTEX_LIMIT]; |
||||
unsigned long size; |
||||
}; |
||||
|
||||
struct Vertex |
||||
{ |
||||
Point position; |
||||
Vector normal; |
||||
ColorF32 color; |
||||
}; |
||||
|
||||
struct VertexList |
||||
{ |
||||
Vertex data[VERTEX_LIMIT]; |
||||
unsigned long size; |
||||
}; |
||||
|
||||
struct Face |
||||
{ |
||||
int vertIndex[3]; |
||||
int uvIndex[3]; |
||||
int materialIndex; |
||||
}; |
||||
|
||||
struct FaceList |
||||
{ |
||||
Face data[FACE_LIMIT]; |
||||
unsigned long size; |
||||
}; |
||||
|
||||
struct Mesh |
||||
{ |
||||
Point position; |
||||
Point rotation; |
||||
float scale; |
||||
}; |
@ -0,0 +1,58 @@
|
||||
#pragma once |
||||
|
||||
#include "Camera.hpp" |
||||
#include "Color.hpp" |
||||
#include "Geometry.hpp" |
||||
#include "Point.hpp" |
||||
|
||||
|
||||
class Light |
||||
{ |
||||
public: |
||||
ColorF32 Compute(Point& eye, Vector& normal, Material& material, Camera& camera) |
||||
{ |
||||
// Point light intensity is a function of the falloff factors and the distance
|
||||
Vector direction = position - eye; |
||||
direction.Normalize(); |
||||
float distance = direction.Length(); |
||||
|
||||
ColorF32 totalIntensity = |
||||
(color * intensity) |
||||
/ (falloffConstant + (falloffLinear * distance)); |
||||
|
||||
|
||||
// Diffuse Light = Kr * I * (alpha + (1 - alpha) * n.d)
|
||||
float dotNormalDirection = std::max(Vector::Dot(normal, direction), 0.0f); |
||||
float alpha = 0.2f; |
||||
|
||||
ColorF32 diffuseLight = |
||||
material.diffuse |
||||
* totalIntensity |
||||
* (alpha + (1.0f - alpha) * dotNormalDirection); |
||||
|
||||
|
||||
// Specular Light = Ks * I * (r.v)^sp
|
||||
Vector view = camera.position - eye; |
||||
view.Normalize(); |
||||
Vector reflection = (normal * 2.0f * dotNormalDirection) - direction; |
||||
float dotReflectionView = std::max(Vector::Dot(reflection, view), 0.0f); |
||||
|
||||
ColorF32 specularLight = |
||||
material.specular |
||||
* totalIntensity |
||||
* powf(dotReflectionView, material.glossiness); |
||||
|
||||
|
||||
// Total light is sum of all lights
|
||||
ColorF32 result = diffuseLight + specularLight; |
||||
|
||||
|
||||
return result; |
||||
} |
||||
|
||||
Point position; |
||||
ColorF32 color; |
||||
float intensity; |
||||
float falloffConstant; |
||||
float falloffLinear; |
||||
}; |
@ -0,0 +1,256 @@
|
||||
#include "Engine.hpp" |
||||
#include "Loader.hpp" |
||||
#include <cctype> |
||||
#include <cstdio> |
||||
#include <cstdint> |
||||
#include <cstdlib> |
||||
#include <cstring> |
||||
|
||||
|
||||
static int LoadTexture(char* filename, Texture& texture, float opacity); |
||||
|
||||
|
||||
#pragma pack(push, 1) |
||||
struct BMP_Header |
||||
{ |
||||
uint16_t fileType; |
||||
uint32_t fileSize; |
||||
uint16_t reserved0; |
||||
uint16_t reserved1; |
||||
uint32_t bitmapOffset; |
||||
uint32_t size; |
||||
int32_t width; |
||||
int32_t height; |
||||
uint16_t planes; |
||||
uint16_t bitsPerPixel; |
||||
uint32_t compression; |
||||
uint32_t sizeOfBitmap; |
||||
int32_t horizRes; |
||||
int32_t vertRes; |
||||
uint32_t colorsUsed; |
||||
uint32_t colorsImportant; |
||||
}; |
||||
#pragma pack(pop) |
||||
|
||||
|
||||
int ParseOBJ(char* filename, EngineMemory& memory) |
||||
{ |
||||
FILE* fp = fopen(filename, "r"); |
||||
|
||||
if (fp == nullptr) |
||||
{ |
||||
fprintf(stderr, "Error loading file: %s\n", filename); |
||||
return -1; |
||||
} |
||||
|
||||
|
||||
char line[256]; |
||||
unsigned long vertIndex = 0; |
||||
unsigned long uvIndex = 0; |
||||
unsigned long faceIndex = 0; |
||||
int materialIndex = -1; |
||||
|
||||
VertexList& verts = memory.localVerts; |
||||
FaceList& faces = memory.localFaces; |
||||
UVList& uvs = memory.uvs; |
||||
|
||||
while (fgets(line, sizeof(line), fp)) |
||||
{ |
||||
char* separator = strchr(line, ' '); |
||||
|
||||
if (separator != nullptr) |
||||
{ |
||||
*separator = '\0'; |
||||
char* type = line; |
||||
char* data = separator + 1; |
||||
|
||||
if (strcmp(type, "v") == 0) |
||||
{ |
||||
sscanf( |
||||
data, "%f %f %f", |
||||
&verts.data[vertIndex].position.x, |
||||
&verts.data[vertIndex].position.y, |
||||
&verts.data[vertIndex].position.z); |
||||
|
||||
verts.data[vertIndex].position.w = 1.0f; |
||||
|
||||
++vertIndex; |
||||
} |
||||
|
||||
else if (strcmp(type, "vt") == 0) |
||||
{ |
||||
sscanf( |
||||
data, "%f %f", |
||||
&uvs.data[uvIndex].u, |
||||
&uvs.data[uvIndex].v); |
||||
|
||||
++uvIndex; |
||||
} |
||||
else if (strcmp(type, "usemtl") == 0) |
||||
{ |
||||
++materialIndex; |
||||
} |
||||
|
||||
else if (strcmp(type, "f") == 0) |
||||
{ |
||||
sscanf( |
||||
data, "%d/%d %d/%d %d/%d", |
||||
&faces.data[faceIndex].vertIndex[0], |
||||
&faces.data[faceIndex].uvIndex[0], |
||||
&faces.data[faceIndex].vertIndex[1], |
||||
&faces.data[faceIndex].uvIndex[1], |
||||
&faces.data[faceIndex].vertIndex[2], |
||||
&faces.data[faceIndex].uvIndex[2]); |
||||
|
||||
// Convert to 0-indexed
|
||||
faces.data[faceIndex].vertIndex[0] -= 1; |
||||
faces.data[faceIndex].vertIndex[1] -= 1; |
||||
faces.data[faceIndex].vertIndex[2] -= 1; |
||||
faces.data[faceIndex].uvIndex[0] -= 1; |
||||
faces.data[faceIndex].uvIndex[1] -= 1; |
||||
faces.data[faceIndex].uvIndex[2] -= 1; |
||||
|
||||
faces.data[faceIndex].materialIndex = materialIndex; |
||||
|
||||
++faceIndex; |
||||
} |
||||
} |
||||
} |
||||
|
||||
verts.size = vertIndex; |
||||
uvs.size = uvIndex; |
||||
faces.size = faceIndex; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int ParseMTL(char* filename, EngineMemory& memory) |
||||
{ |
||||
FILE* fp = fopen(filename, "r"); |
||||
|
||||
if (fp == nullptr) |
||||
{ |
||||
fprintf(stderr, "Error loading file: %s\n", filename); |
||||
return -1; |
||||
} |
||||
|
||||
char line[256]; |
||||
int materialIndex = -1; |
||||
|
||||
MaterialList& materials = memory.materials; |
||||
TextureList& textures = memory.textures; |
||||
|
||||
while (fgets(line, sizeof(line), fp)) |
||||
{ |
||||
char* separator = strchr(line, ' '); |
||||
|
||||
if (separator != nullptr) |
||||
{ |
||||
*separator = '\0'; |
||||
char* type = line; |
||||
char* data = separator + 1; |
||||
|
||||
if (strcmp(type, "newmtl") == 0) |
||||
{ |
||||
++materialIndex; |
||||
} |
||||
|
||||
else if (strcmp(type, "Ns") == 0) |
||||
{ |
||||
sscanf( |
||||
data, "%f", |
||||
&materials.data[materialIndex].glossiness); |
||||
} |
||||
|
||||
else if (strcmp(type, "Ka") == 0) |
||||
{ |
||||
sscanf( |
||||
data, "%f %f %f", |
||||
&materials.data[materialIndex].ambient.r, |
||||
&materials.data[materialIndex].ambient.g, |
||||
&materials.data[materialIndex].ambient.b); |
||||
} |
||||
|
||||
else if (strcmp(type, "Kd") == 0) |
||||
{ |
||||
sscanf( |
||||
data, "%f %f %f", |
||||
&materials.data[materialIndex].diffuse.r, |
||||
&materials.data[materialIndex].diffuse.g, |
||||
&materials.data[materialIndex].diffuse.b); |
||||
} |
||||
|
||||
else if (strcmp(type, "Ks") == 0) |
||||
{ |
||||
sscanf( |
||||
data, "%f %f %f", |
||||
&materials.data[materialIndex].specular.r, |
||||
&materials.data[materialIndex].specular.g, |
||||
&materials.data[materialIndex].specular.b); |
||||
} |
||||
|
||||
else if (strcmp(type, "d") == 0) |
||||
{ |
||||
sscanf( |
||||
data, "%f", |
||||
&materials.data[materialIndex].opacity); |
||||
} |
||||
|
||||
else if (strcmp(type, "map_Kd") == 0) |
||||
{ |
||||
char* textureFilename = data; |
||||
textureFilename[strcspn(textureFilename, "\r\n")] = 0; |
||||
LoadTexture( |
||||
textureFilename, textures.data[materialIndex], |
||||
materials.data[materialIndex].opacity); |
||||
} |
||||
} |
||||
} |
||||
|
||||
materials.size = materialIndex + 1; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int LoadTexture(char* filename, Texture& texture, float opacity) |
||||
{ |
||||
FILE* fp = fopen(filename, "r"); |
||||
if (fp == nullptr) |
||||
{ |
||||
fprintf(stderr, "Could not open file: %s\n", filename); |
||||
return -1; |
||||
} |
||||
|
||||
BMP_Header header = {}; |
||||
fread((void*)&header, sizeof(BMP_Header), 1, fp); |
||||
fseek(fp, header.bitmapOffset, SEEK_SET); |
||||
|
||||
// Padding is added to image to align to 4-byte boundaries
|
||||
unsigned long paddingSize = static_cast<unsigned long>(header.width % 4); |
||||
|
||||
for (int y = 0; y < header.height; ++y) |
||||
{ |
||||
for (int x = 0; x < header.width; ++x) |
||||
{ |
||||
fread(&texture.texels[y][x].b, 1, 1, fp); |
||||
fread(&texture.texels[y][x].g, 1, 1, fp); |
||||
fread(&texture.texels[y][x].r, 1, 1, fp); |
||||
texture.texels[y][x].a = (uint8_t)(255 * opacity); |
||||
} |
||||
|
||||
// Discard padding byte
|
||||
if (paddingSize != 0) |
||||
{ |
||||
uint32_t padding; |
||||
fread(&padding, paddingSize, 1, fp); |
||||
} |
||||
} |
||||
|
||||
texture.width = (unsigned int)header.width; |
||||
texture.height = (unsigned int)header.height; |
||||
|
||||
fclose(fp); |
||||
|
||||
return 0; |
||||
} |
||||
|
@ -0,0 +1,7 @@
|
||||
#pragma once |
||||
|
||||
#include "Engine.hpp" |
||||
|
||||
|
||||
int ParseOBJ(char* filename, EngineMemory& memory); |
||||
int ParseMTL(char* filename, EngineMemory& memory); |
@ -0,0 +1,60 @@
|
||||
#include "Engine.hpp" |
||||
#include "Platform.hpp" |
||||
#include <cstdint> |
||||
#include <cstdio> |
||||
#include <cstdlib> |
||||
|
||||
|
||||
// MAIN
|
||||
int main(int argc, char* argv[]) |
||||
{ |
||||
if (argc != 3) |
||||
{ |
||||
fprintf(stderr, "Usage: %s <OBJ> <MTL>\n", argv[0]); |
||||
return EXIT_FAILURE; |
||||
} |
||||
|
||||
char* objFilename = argv[1]; |
||||
char* mtlFilename = argv[2]; |
||||
Platform platform{}; |
||||
|
||||
if (Platform_Init(platform, WINDOW_WIDTH, WINDOW_HEIGHT) == PlatformStatus::Error) |
||||
{ |
||||
return EXIT_FAILURE; |
||||
} |
||||
|
||||
if (EngineInit(objFilename, mtlFilename) < 0) |
||||
{ |
||||
return EXIT_FAILURE; |
||||
} |
||||
|
||||
EngineBuffer buffer{}; |
||||
buffer.buffer = reinterpret_cast<uint32_t*>(platform.surface->pixels); |
||||
buffer.width = platform.surface->w; |
||||
buffer.height = platform.surface->h; |
||||
|
||||
while (true) |
||||
{ |
||||
Platform_GetFrameTime(platform); |
||||
|
||||
if (Platform_CheckForEvents(platform) == PlatformStatus::Quit) |
||||
{ |
||||
break; |
||||
} |
||||
|
||||
Platform_ClearWindow(platform); |
||||
|
||||
EngineRender(buffer, platform.input); |
||||
|
||||
Platform_UpdateWindow(platform); |
||||
|
||||
Platform_SyncToFramerate(platform); |
||||
} |
||||
|
||||
EngineShutdown(); |
||||
|
||||
Platform_Shutdown(platform); |
||||
|
||||
return EXIT_SUCCESS; |
||||
} |
||||
|
@ -0,0 +1,49 @@
|
||||
#pragma once |
||||
|
||||
|
||||
class Matrix |
||||
{ |
||||
public: |
||||
Matrix() |
||||
{ |
||||
e11 = 1.0; e12 = 0.0; e13 = 0.0; e14 = 0.0; |
||||
e21 = 0.0; e22 = 1.0; e23 = 0.0; e24 = 0.0; |
||||
e31 = 0.0; e32 = 0.0; e33 = 1.0; e34 = 0.0; |
||||
e41 = 0.0; e42 = 0.0; e43 = 0.0; e44 = 1.0; |
||||
} |
||||
|
||||
union
|
||||
{ |
||||
float e[4][4]; |
||||
|
||||
struct
|
||||
{ |
||||
float e11, e12, e13, e14; |
||||
float e21, e22, e23, e24; |
||||
float e31, e32, e33, e34; |
||||
float e41, e42, e43, e44; |
||||
}; |
||||
}; |
||||
|
||||
Matrix operator*(Matrix const& rhs) |
||||
{ |
||||
Matrix result; |
||||
|
||||
for (int row = 0; row < 4; ++row) |
||||
{ |
||||
for (int col = 0; col < 4; ++col) |
||||
{ |
||||
float sum = 0.0; |
||||
|
||||
for (int i = 0; i < 4; ++i) |
||||
{ |
||||
sum += e[row][i] * rhs.e[i][col]; |
||||
} |
||||
|
||||
result.e[row][col] = sum; |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
}; |
@ -0,0 +1,243 @@
|
||||
#include "Engine.hpp" |
||||
#include "Platform.hpp" |
||||
#include <cstdint> |
||||
#include <SDL2/SDL.h> |
||||
|
||||
|
||||
void constexpr SetBit(uint32_t& x, unsigned long bit) |
||||
{ |
||||
x |= (1UL << bit); |
||||
} |
||||
|
||||
void constexpr ClearBit(uint32_t& x, unsigned long bit) |
||||
{ |
||||
x &= ~(1UL << bit); |
||||
} |
||||
|
||||
static void HandleEvent(Platform& platform, SDL_Event& event); |
||||
|
||||
|
||||
PlatformStatus Platform_Init(Platform& platform, int width, int height) |
||||
{ |
||||
int result = SDL_Init(SDL_INIT_VIDEO); |
||||
if (result < 0) |
||||
{ |
||||
fprintf(stderr, "Error initializing SDL: %s\n", SDL_GetError()); |
||||
return PlatformStatus::Error; |
||||
} |
||||
|
||||
|
||||
SDL_Window* window = SDL_CreateWindow( |
||||
"Soft 3D Engine", |
||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, |
||||
width, height, |
||||
SDL_WINDOW_SHOWN); |
||||
if (window == nullptr) |
||||
{ |
||||
fprintf(stderr, "Error creating SDL window: %s\n", SDL_GetError()); |
||||
return PlatformStatus::Error; |
||||
} |
||||
|
||||
|
||||
SDL_Surface* surface = SDL_GetWindowSurface(window); |
||||
if (surface == nullptr) |
||||
{ |
||||
fprintf(stderr, "Error getting SDL window surface: %s\n", SDL_GetError()); |
||||
return PlatformStatus::Error; |
||||
} |
||||
|
||||
|
||||
result = SDL_ShowCursor(SDL_DISABLE); |
||||
if (result < 0) |
||||
{ |
||||
fprintf(stderr, "Error disabling cursor in SDL window: %s\n", SDL_GetError()); |
||||
return PlatformStatus::Error; |
||||
} |
||||
|
||||
platform.framerateMillis = (1000 / WINDOW_FPS); |
||||
platform.window = window; |
||||
platform.surface = surface; |
||||
|
||||
return PlatformStatus::Ok; |
||||
} |
||||
|
||||
PlatformStatus Platform_CheckForEvents(Platform& platform) |
||||
{ |
||||
SDL_Event event; |
||||
|
||||
while (SDL_PollEvent(&event) != 0) |
||||
{ |
||||
if (event.type == SDL_QUIT) |
||||
{ |
||||
return PlatformStatus::Quit; |
||||
} |
||||
else |
||||
{ |
||||
HandleEvent(platform, event); |
||||
} |
||||
} |
||||
|
||||
return PlatformStatus::Ok; |
||||
} |
||||
|
||||
void Platform_ClearWindow(Platform& platform) |
||||
{ |
||||
SDL_LockSurface(platform.surface); |
||||
SDL_FillRect(platform.surface, nullptr, 0); |
||||
} |
||||
|
||||
void Platform_UpdateWindow(Platform& platform) |
||||
{ |
||||
SDL_UnlockSurface(platform.surface); |
||||
SDL_UpdateWindowSurface(platform.window); |
||||
} |
||||
|
||||
void Platform_GetFrameTime(Platform& platform) |
||||
{ |
||||
platform.frameStartMillis = SDL_GetTicks(); |
||||
} |
||||
|
||||
void Platform_SyncToFramerate(Platform& platform) |
||||
{ |
||||
uint32_t stopTimeMillis = SDL_GetTicks(); |
||||
uint32_t framerateMillis = stopTimeMillis - platform.frameStartMillis; |
||||
|
||||
// Delay if time to spare
|
||||
if (framerateMillis < platform.framerateMillis) |
||||
{ |
||||
uint32_t delayMillis = platform.framerateMillis - framerateMillis; |
||||
SDL_Delay(delayMillis); |
||||
} |
||||
} |
||||
|
||||
void Platform_Shutdown(Platform& platform) |
||||
{ |
||||
SDL_DestroyWindow(platform.window); |
||||
SDL_Quit(); |
||||
} |
||||
|
||||
|
||||
// PRIVATE FUNCTIONS
|
||||
static void HandleEvent( |
||||
Platform& platform, SDL_Event& event) |
||||
{ |
||||
if (event.type == SDL_KEYDOWN) |
||||
{ |
||||
if (event.key.keysym.sym == SDLK_w) |
||||
{ |
||||
SetBit(platform.input, TRANSLATE_Z_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_s) |
||||
{ |
||||
SetBit(platform.input, TRANSLATE_Z_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_a) |
||||
{ |
||||
SetBit(platform.input, TRANSLATE_X_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_d) |
||||
{ |
||||
SetBit(platform.input, TRANSLATE_X_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_q) |
||||
{ |
||||
SetBit(platform.input, TRANSLATE_Y_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_e) |
||||
{ |
||||
SetBit(platform.input, TRANSLATE_Y_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_i) |
||||
{ |
||||
SetBit(platform.input, ROTATE_X_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_k) |
||||
{ |
||||
SetBit(platform.input, ROTATE_X_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_j) |
||||
{ |
||||
SetBit(platform.input, ROTATE_Y_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_l) |
||||
{ |
||||
SetBit(platform.input, ROTATE_Y_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_u) |
||||
{ |
||||
SetBit(platform.input, ROTATE_Z_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_o) |
||||
{ |
||||
SetBit(platform.input, ROTATE_Z_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_UP) |
||||
{ |
||||
SetBit(platform.input, SCALE_UP); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_DOWN) |
||||
{ |
||||
SetBit(platform.input, SCALE_DOWN); |
||||
} |
||||
} |
||||
else if (event.type == SDL_KEYUP) |
||||
{ |
||||
if (event.key.keysym.sym == SDLK_w) |
||||
{ |
||||
ClearBit(platform.input, TRANSLATE_Z_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_s) |
||||
{ |
||||
ClearBit(platform.input, TRANSLATE_Z_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_a) |
||||
{ |
||||
ClearBit(platform.input, TRANSLATE_X_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_d) |
||||
{ |
||||
ClearBit(platform.input, TRANSLATE_X_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_q) |
||||
{ |
||||
ClearBit(platform.input, TRANSLATE_Y_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_e) |
||||
{ |
||||
ClearBit(platform.input, TRANSLATE_Y_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_i) |
||||
{ |
||||
ClearBit(platform.input, ROTATE_X_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_k) |
||||
{ |
||||
ClearBit(platform.input, ROTATE_X_NEG); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_j) |
||||
{ |
||||
ClearBit(platform.input, ROTATE_Y_POS); |
||||
} |
||||
else if (event.key.keysym.sym == SDLK_l) |
||||
{ |
||||
ClearBit(platform.input, ROTATE_Y_NEG); |
||||