Initial commit

- Deferred renderer (forward)
- Loading models (temp)
- Started towards differentiation
This commit is contained in:
Kevin Trogant 2024-02-25 12:01:19 +01:00
commit 7054c965a4
34 changed files with 15602 additions and 0 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
*.bin filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*.dll
*.so
*.o
*.zip
*.tar.gz
build/

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# PROBLEM STATEMENT
Differentiable Renderer that Supports One (1) Non-Obvious Camera Model

4
glad/README.md Normal file
View File

@ -0,0 +1,4 @@
Generated from: https://gen.glad.sh
Check include/glad/gl.h for license information.
Includes khrplatform.h (C) The Khronos Group Inc.

View File

@ -0,0 +1,311 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
# define KHRONOS_STATIC 1
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(KHRONOS_STATIC)
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
* header compatible with static linking. */
# define KHRONOS_APICALL
#elif defined(_WIN32)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
/*
* To support platform where unsigned long cannot be used interchangeably with
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
* unsigned long long or similar (this results in different C++ name mangling).
* To avoid changes for existing platforms, we restrict usage of intptr_t to
* platforms where the size of a pointer is larger than the size of long.
*/
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
#define KHRONOS_USE_INTPTR_T
#endif
#endif
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef KHRONOS_USE_INTPTR_T
typedef intptr_t khronos_intptr_t;
typedef uintptr_t khronos_uintptr_t;
#elif defined(_WIN64)
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
#endif
#if defined(_WIN64)
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

3637
glad/include/glad/gl.h Normal file

File diff suppressed because it is too large Load Diff

1620
glad/src/gl.c Normal file

File diff suppressed because it is too large Load Diff

32
meson.build Normal file
View File

@ -0,0 +1,32 @@
project('diffren', 'c',
default_options: 'warning_level=3')
cmake = import('cmake')
glfw_dep = dependency('glfw3', required: true)
assimp_opts = cmake.subproject_options()
assimp_subproj = cmake.subproject('assimp-5.3.1')
assimp_dep = assimp_subproj.dependency('assimp')
incdirs = include_directories('glad' / 'include')
glad_lib = static_library('glad',
'glad/src/gl.c',
override_options: ['warning_level=0'],
include_directories: incdirs)
executable('diffren',
'src/diffren.h',
'src/diffren_math.h',
'src/diffren_model.h',
'src/main.c',
'src/deferred.c',
'src/model_loader.c',
'src/math.c',
'src/stb_image.h',
dependencies: [glfw_dep, assimp_dep],
include_directories: incdirs,
override_options: ['c_std=c11'],
link_with: glad_lib)

BIN
model/WaterBottle.bin (Stored with Git LFS) Normal file

Binary file not shown.

172
model/WaterBottle.gltf Normal file
View File

@ -0,0 +1,172 @@
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 2549,
"type": "VEC2"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 2549,
"type": "VEC3"
},
{
"bufferView": 2,
"componentType": 5126,
"count": 2549,
"type": "VEC4"
},
{
"bufferView": 3,
"componentType": 5126,
"count": 2549,
"type": "VEC3",
"max": [
0.05445001,
0.130220339,
0.0544500239
],
"min": [
-0.05445001,
-0.130220339,
-0.0544500239
]
},
{
"bufferView": 4,
"componentType": 5123,
"count": 13530,
"type": "SCALAR"
}
],
"asset": {
"generator": "glTF Tools for Unity",
"version": "2.0"
},
"bufferViews": [
{
"buffer": 0,
"byteLength": 20392
},
{
"buffer": 0,
"byteOffset": 20392,
"byteLength": 30588
},
{
"buffer": 0,
"byteOffset": 50980,
"byteLength": 40784
},
{
"buffer": 0,
"byteOffset": 91764,
"byteLength": 30588
},
{
"buffer": 0,
"byteOffset": 122352,
"byteLength": 27060
}
],
"buffers": [
{
"uri": "WaterBottle.bin",
"byteLength": 149412
}
],
"images": [
{
"uri": "WaterBottle_baseColor.png"
},
{
"uri": "WaterBottle_occlusionRoughnessMetallic.png"
},
{
"uri": "WaterBottle_normal.png"
},
{
"uri": "WaterBottle_emissive.png"
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"TEXCOORD_0": 0,
"NORMAL": 1,
"TANGENT": 2,
"POSITION": 3
},
"indices": 4,
"material": 0
}
],
"name": "WaterBottle"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0
},
"metallicRoughnessTexture": {
"index": 1
}
},
"normalTexture": {
"index": 2
},
"occlusionTexture": {
"index": 1
},
"emissiveFactor": [
1.0,
1.0,
1.0
],
"emissiveTexture": {
"index": 3
},
"name": "BottleMat"
}
],
"nodes": [
{
"mesh": 0,
"rotation": [
0.0,
1.0,
0.0,
0.0
],
"name": "WaterBottle"
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"textures": [
{
"source": 0
},
{
"source": 1
},
{
"source": 2
},
{
"source": 3
}
]
}

BIN
model/WaterBottle_baseColor.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
model/WaterBottle_emissive.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
model/WaterBottle_normal.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
model/WaterBottle_occlusionRoughnessMetallic.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
papers/3D_EDGE_DETECTION_BASED_ON_NORMAL_VECTORS.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
papers/Differentiable_Rendering_A_Survey.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
papers/OpenDR.pdf (Stored with Git LFS) Normal file

Binary file not shown.

31
shader/geom_frag.glsl Normal file
View File

@ -0,0 +1,31 @@
#version 460 core
// Note: orm = occlusion/roughness/metallic
layout (location = 0) out vec3 g_position;
layout (location = 1) out vec3 g_albedo;
layout (location = 2) out vec3 g_normal;
layout (location = 3) out vec3 g_orm;
in VS_OUT {
vec3 viewPos;
vec2 texcoord;
mat3 TBN;
} fs_in;
layout (location = 1) uniform sampler2D tex_albedo;
layout (location = 2) uniform sampler2D tex_normal;
layout (location = 3) uniform sampler2D tex_orm;
void main() {
// Just sample albedo and occlusion/roughness/metallic
g_albedo = texture(tex_albedo, fs_in.texcoord).rgb;
g_orm = texture(tex_orm, fs_in.texcoord).rgb;
g_position = fs_in.viewPos;
// Normal mapping
vec3 normal = texture(tex_normal, fs_in.texcoord).rgb;
normal = normal * 2.0 - 1.0;
g_normal = normalize(fs_in.TBN * normal);
}

34
shader/geom_vert.glsl Normal file
View File

@ -0,0 +1,34 @@
#version 460 core
layout (location = 0) in vec3 v_pos;
layout (location = 1) in vec3 v_normal;
layout (location = 2) in vec3 v_tangent;
layout (location = 3) in vec2 v_texcoord;
out VS_OUT {
vec3 viewPos;
vec2 texcoord;
mat3 TBN;
} vs_out;
layout (std140, binding=0) uniform FrameMatrices {
mat4 view;
mat4 projection;
};
layout (location=0) uniform mat4 model;
void main() {
gl_Position = projection * view * model * vec4(v_pos, 1.0);
vs_out.texcoord = v_texcoord;
// Gram-schmidt process for re-orthogonalizing the TBN vectors
vec3 T = normalize(vec3(view * model * vec4(v_tangent, 0.0)));
vec3 N = normalize(vec3(view * model * vec4(v_normal, 0.0)));
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
vs_out.TBN = mat3(T, B, N);
vs_out.viewPos = (view * model * vec4(v_pos, 1.0)).xyz;
}

123
shader/light_frag.glsl Normal file
View File

@ -0,0 +1,123 @@
#version 460 core
// hdr color
layout (location = 0) out vec3 h_color;
struct Light {
// w contains the radius
vec4 pos_radius;
vec4 color;
};
layout(std430, binding = 0) buffer LightBuffer
{
Light lights[];
};
layout (location = 0) uniform uint light_count;
layout (location = 1) uniform vec3 ambient_color;
// GBuffer
layout (location = 2) uniform sampler2D g_position;
layout (location = 3) uniform sampler2D g_albedo;
layout (location = 4) uniform sampler2D g_normal;
layout (location = 5) uniform sampler2D g_orm;
struct Surface {
vec3 position;
vec3 albedo;
vec3 normal;
float occlusion;
float roughness;
float metallic;
};
in VS_OUT {
vec2 texcoord;
} fs_in;
const float PI = 3.1415926;
float GGXDistribution(float alpha, vec3 normal, vec3 H) {
float a2 = alpha * alpha;
float NdotH = dot(normal, H);
float NdotH2 = NdotH * NdotH;
float num = a2 * max(NdotH, 0.0); // both are length = 1, so this is heaviside
float den = (NdotH2 * (a2 - 1.0) + 1);
return num / (PI * den * den);
}
float SmithJoinMaskingShadowing(float alpha, vec3 normal, vec3 light_dir, vec3 view_dir, vec3 H) {
float NdotL = abs(dot(normal, light_dir));
float NdotV = abs(dot(normal, view_dir));
float NdotL2 = NdotL * NdotL;
float NdotV2 = NdotV * NdotV;
float alpha2 = alpha * alpha;
float num1 = 2 * NdotL * max(dot(H, light_dir), 0);
float den1 = NdotL + sqrt(alpha2 + (1-alpha2)*NdotL2);
float num2 = 2 * NdotV * max(dot(H, view_dir), 0);
float den2 = NdotV + sqrt(alpha2 + (1-alpha2)*NdotV2);
return (num1*num2)/(den1*den2);
}
vec3 FresnelSlick(vec3 f0, vec3 view_dir, vec3 H) {
float VdotH = dot(view_dir, H);
return f0 + (1.0 - f0) * pow(1 - abs(VdotH), 5);
}
vec3 Lerp(vec3 a, vec3 b, float t) {
return a + (b - a) * t;
}
Surface ReadSurface() {
Surface surf;
surf.position = texture(g_position, fs_in.texcoord).rgb;
surf.albedo = texture(g_albedo, fs_in.texcoord).rgb;
surf.normal = texture(g_normal, fs_in.texcoord).rgb;
vec3 orm = texture(g_orm, fs_in.texcoord).rgb;
surf.occlusion = orm.r;
surf.roughness = orm.g;
surf.metallic = orm.b;
return surf;
}
void main() {
Surface surf = ReadSurface();
// Eye location is always (0,0,0) because we calculate everything in view-space
vec3 view_dir = normalize(-1 * surf.position);
vec3 black = vec3(0, 0, 0);
vec3 c_diff = Lerp(surf.albedo, black, surf.metallic);
vec3 f0 = Lerp(vec3(0.04), surf.albedo, surf.metallic);
// We probably want a tiling based approach, but this is good enough for now
vec3 Lo = vec3(0.0);
for (uint i = 0; i < light_count; ++i) {
vec3 light_pos = lights[i].pos_radius.xyz;
float light_radius = lights[i].pos_radius.w;
float light_dist = length(light_pos - surf.position);
if (light_dist > light_radius)
continue;
float attenuation = 1.0 / (light_dist * light_dist);
vec3 radiance = lights[i].color.rgb * attenuation;
vec3 light_dir = normalize(-1 * light_pos);
vec3 H = normalize(light_dir + view_dir);
float NdotL = dot(surf.normal, light_dir);
vec3 F = FresnelSlick(f0, view_dir, H);
vec3 f_diffuse = (1 - F) * (1 / PI) * c_diff;
vec3 f_specular = F * GGXDistribution(surf.roughness, surf.normal, H)
* SmithJoinMaskingShadowing(surf.roughness, surf.normal, light_dir, view_dir, H)
/ (4 * abs(dot(view_dir, surf.normal)) * abs(NdotL) + 0.0001);
vec3 material = f_diffuse + f_specular;
Lo += material * radiance * NdotL;
}
vec3 ambient = ambient_color * surf.albedo * surf.occlusion;
h_color = ambient + Lo;
}

16
shader/light_vert.glsl Normal file
View File

@ -0,0 +1,16 @@
#version 460 core
out VS_OUT {
vec2 texcoord;
} vs_out;
void main() {
// Output a single full-screen triangle
vec2 verts[3] = vec2[3](
vec2(-1,-1),
vec2( 3,-1),
vec2(-1, 3)
);
gl_Position = vec4(verts[gl_VertexID], 0, 1);
vs_out.texcoord = 0.5 * gl_Position.xy + vec2(0.5);
}

View File

@ -0,0 +1,48 @@
#version 460 core
// Compute shader for classifying pixels as either:
// edge or internal
layout(local_size_x = 16, local_size_y = 16) in;
layout(rgba16f, binding = 0) uniform restrict readonly image2D g_normal;
layout(r8ui, binding = 1) uniform restrict writeonly uimage2D classification;
const uint INTERNAL = 0;
const uint EDGE = 1;
// approx cos(10°)
const float THRESH = 0.984;
void main() {
ivec2 img_coord = ivec2(gl_GlobalInvocationID.xy);
ivec2 img_size = imageSize(g_normal);
vec3 normals[9] = vec3[9](
vec3(0, 0, 0), vec3(0, 0, 0), vec3(0, 0, 0),
vec3(0, 0, 0), vec3(0, 0, 0), vec3(0, 0, 0),
vec3(0, 0, 0), vec3(0, 0, 0), vec3(0, 0, 0)
);
int i = 0;
for (int dy = -1; dy <= 1; ++dy) {
int y = img_coord.y + dy;
if (y < 0 || y == img_size.y)
continue;
for (int dx = -1; dx <= 1; ++dx) {
int x = img_coord.x + dx;
if (x < 0 || x == img_size.x)
continue;
normals[i] = imageLoad(g_normal, ivec2(x, y)).xyz;
}
}
uint classif = INTERNAL;
for (int i = 0; i < 9; ++i) {
float angle = abs(dot(normals[i], normals[4]));
if (angle >= THRESH)
classif = EDGE;
}
imageStore(classification, img_coord, uvec4(classif));
}

15
shader/tone_map_frag.glsl Normal file
View File

@ -0,0 +1,15 @@
#version 460
layout (location = 0) out vec4 frag_color;
layout (location = 0) uniform sampler2D h_color;
in VS_OUT {
vec2 texcoord;
} fs_in;
void main() {
vec3 hdr = texture(h_color, fs_in.texcoord).rgb;
vec3 ldr = hdr / (hdr + vec3(1.0));
frag_color = vec4(pow(ldr, vec3(1.0/2.2)), 1.0);
}

16
shader/tone_map_vert.glsl Normal file
View File

@ -0,0 +1,16 @@
#version 460 core
out VS_OUT {
vec2 texcoord;
} vs_out;
void main() {
// Output a single full-screen triangle
vec2 verts[3] = vec2[3](
vec2(-1,-1),
vec2( 3,-1),
vec2(-1, 3)
);
gl_Position = vec4(verts[gl_VertexID], 0, 1);
vs_out.texcoord = 0.5 * gl_Position.xy + vec2(0.5);
}

700
src/deferred.c Normal file
View File

@ -0,0 +1,700 @@
#include <glad/gl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "diffren.h"
#include "diffren_model.h"
/* Implements a deferred renderer for PBR models */
typedef struct {
/* 16bit RGB float for view space positions */
GLuint position;
/* RGB8 Buffer for surface base color */
GLuint albedo;
/* 16bit float RGBs for view space normals */
GLuint normal;
/* 16bit float RGB for occlusion, roughness and metallic */
GLuint orm;
/* D32 for depth */
GLuint depth;
/* Framebuffer object for the whole gbuffer */
GLuint fbo;
} GBuffer;
// Must match gl std140 layout
typedef struct {
M4 view;
M4 projection;
} FrameMatrices;
#define GEOMETRY_PASS_FRAME_MATRICES_BINDING 0
#define GEOMETRY_PASS_MODEL_LOCATION 0
#define GEOMETRY_PASS_ALBEDO_LOCATION 1
#define GEOMETRY_PASS_NORMAL_LOCATION 2
#define GEOMETRY_PASS_ORM_LOCATION 3
typedef struct {
/* Shader program for rendering to the gbuffer */
GLuint prog;
/* Buffer containing the view/projection matrices,
* at binding = 0 */
GLuint frame_matrices_ubo;
} GeometryPass;
typedef struct {
/* 16bit RGB float for HDR lighting computations */
GLuint color;
/* Framebuffer object */
GLuint fbo;
} HDRBuffer;
#define LIGHTING_PASS_LIGHTS_BINDING 0
#define LIGHTING_PASS_LIGHT_COUNT_LOCATION 0
#define LIGHTING_PASS_AMBIENT_COLOR_LOCATION 1
#define LIGHTING_PASS_POSITION_LOCATION 2
#define LIGHTING_PASS_ALBEDO_LOCATION 3
#define LIGHTING_PASS_NORMAL_LOCATION 4
#define LIGHTING_PASS_ORM_LOCATION 5
typedef struct {
/* Shader program for rendering the final image */
GLuint prog;
GLuint lights_sbo;
} LightingPass;
#define TONE_MAPPING_PASS_COLOR_LOCATION 0
typedef struct {
GLuint prog;
} ToneMappingPass;
#define PIXEL_CLASSIFIER_PASS_NORMAL_BINDING 0
#define PIXEL_CLASSIFIER_PASS_CLASSIFICATION_BINDING 1
typedef struct {
GLuint prog;
} PixelClassifierPass;
typedef struct {
// UI8 buffer for pixel classification
GLuint classification;
} DifferentiationBuffer;
#define MAX_LIGHTS 128
// Must match gl std430 layout
typedef struct {
V4 position_radius;
V4 color;
} Light;
struct DeferredRenderer {
GBuffer gbuf;
GeometryPass geometry_pass;
HDRBuffer hdr_buf;
LightingPass lighting_pass;
ToneMappingPass tone_mapping_pass;
PixelClassifierPass classifier_pass;
DifferentiationBuffer diff_buffer;
/* "Empty" vao for the full-screen triangle */
GLuint full_screen_triangle_vao;
int width;
int height;
/* Per Frame Data */
V3 eye_pos;
V3 center;
V3 up;
M4 projection;
V3 ambient_color;
Light lights[MAX_LIGHTS];
unsigned int light_count;
int light_ssbo_needs_update;
};
static void CreateGBuffer(int w, int h, GBuffer *gbuf) {
glGenFramebuffers(1, &gbuf->fbo);
glBindFramebuffer(GL_FRAMEBUFFER, gbuf->fbo);
/* Create the texture objects in one batch */
GLuint tex_objs[4];
glGenTextures(4, tex_objs);
gbuf->position = tex_objs[0];
gbuf->albedo = tex_objs[1];
gbuf->normal = tex_objs[2];
gbuf->orm = tex_objs[3];
/* position buffer */
glBindTexture(GL_TEXTURE_2D, gbuf->position);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, w, h, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gbuf->position, 0);
/* albedo buffer */
glBindTexture(GL_TEXTURE_2D, gbuf->albedo);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, w, h, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gbuf->albedo, 0);
/* normal buffer */
glBindTexture(GL_TEXTURE_2D, gbuf->normal);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, w, h, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gbuf->normal, 0);
/* occlusion/roughness/metallic buffer */
glBindTexture(GL_TEXTURE_2D, gbuf->orm);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, w, h, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, gbuf->orm, 0);
/* 32bit depth buffer */
glGenRenderbuffers(1, &gbuf->depth);
glBindRenderbuffer(GL_RENDERBUFFER, gbuf->depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32F, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, gbuf->depth);
GLuint attachments[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
glDrawBuffers(4, attachments);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fprintf(stderr, "ERROR::FRAMEBUFFER:: Framebuffer is not complete!\n");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
/* Return a zero terminated string containing the shader code */
static char *LoadShader(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "Failed to load %s: Could not fopen()\n", path);
return NULL;
}
fseek(f, 0, SEEK_END);
size_t fsz = (size_t)ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = malloc(fsz + 1);
if (!buf) {
fprintf(stderr, "Failed to load %s: Unable to allocate %zu bytes.\n", path, fsz + 1);
fclose(f);
return NULL;
}
if (fread(buf, fsz, 1, f) != 1) {
fprintf(stderr, "Failed to load %s: Unable to read %zu bytes.\n", path, fsz);
free(buf);
fclose(f);
return NULL;
}
buf[fsz] = '\0';
fclose(f);
return buf;
}
static int CreateGeometryPass(GeometryPass *pass) {
/* Load vertex & fragment shader */
char *vert_code = LoadShader("shader/geom_vert.glsl");
if (!vert_code)
return 0;
char *frag_code = LoadShader("shader/geom_frag.glsl");
if (!frag_code) {
free(vert_code);
return 0;
}
GLint result;
GLuint vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert, 1, &vert_code, NULL);
glCompileShader(vert);
glGetShaderiv(vert, GL_COMPILE_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetShaderInfoLog(vert, sizeof(info), NULL, info);
fprintf(stderr, "Failed to compile shader/geom_vert.glsl: %s\n", info);
free(vert_code);
free(frag_code);
return 0;
}
GLuint frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &frag_code, NULL);
glCompileShader(frag);
glGetShaderiv(frag, GL_COMPILE_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetShaderInfoLog(frag, sizeof(info), NULL, info);
fprintf(stderr, "Failed to compile shader/geom_frag.glsl: %s\n", info);
free(vert_code);
free(frag_code);
return 0;
}
pass->prog = glCreateProgram();
glAttachShader(pass->prog, vert);
glAttachShader(pass->prog, frag);
glLinkProgram(pass->prog);
glGetProgramiv(pass->prog, GL_LINK_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetProgramInfoLog(pass->prog, sizeof(info), NULL, info);
fprintf(stderr, "Failed to link geometry pass program: %s\n", info);
free(vert_code);
free(frag_code);
return 0;
}
glDeleteShader(vert);
glDeleteShader(frag);
free(vert_code);
free(frag_code);
glGenBuffers(1, &pass->frame_matrices_ubo);
glBindBuffer(GL_UNIFORM_BUFFER, pass->frame_matrices_ubo);
glBufferData(GL_UNIFORM_BUFFER, sizeof(FrameMatrices), NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
return 1;
}
static void CreateHDRBuffer(int w, int h, HDRBuffer *hdr_buf) {
glGenFramebuffers(1, &hdr_buf->fbo);
glBindFramebuffer(GL_FRAMEBUFFER, hdr_buf->fbo);
/* Create the texture objects in one batch */
glGenTextures(1, &hdr_buf->color);
/* position buffer */
glBindTexture(GL_TEXTURE_2D, hdr_buf->color);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, w, h, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hdr_buf->color, 0);
GLuint attachments[1] = { GL_COLOR_ATTACHMENT0 };
glDrawBuffers(1, attachments);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fprintf(stderr, "ERROR::FRAMEBUFFER:: Framebuffer is not complete!\n");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
static int CreateLightingPass(LightingPass *pass) {
/* Load vertex & fragment shader */
char *vert_code = LoadShader("shader/light_vert.glsl");
if (!vert_code)
return 0;
char *frag_code = LoadShader("shader/light_frag.glsl");
if (!frag_code) {
free(vert_code);
return 0;
}
GLint result;
GLuint vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert, 1, &vert_code, NULL);
glCompileShader(vert);
glGetShaderiv(vert, GL_COMPILE_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetShaderInfoLog(vert, sizeof(info), NULL, info);
fprintf(stderr, "Failed to compile shader/light_vert.glsl: %s\n", info);
free(vert_code);
free(frag_code);
return 0;
}
GLuint frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &frag_code, NULL);
glCompileShader(frag);
glGetShaderiv(frag, GL_COMPILE_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetShaderInfoLog(frag, sizeof(info), NULL, info);
fprintf(stderr, "Failed to compile shader/light_frag.glsl: %s\n", info);
free(vert_code);
free(frag_code);
return 0;
}
pass->prog = glCreateProgram();
glAttachShader(pass->prog, vert);
glAttachShader(pass->prog, frag);
glLinkProgram(pass->prog);
glGetProgramiv(pass->prog, GL_LINK_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetProgramInfoLog(pass->prog, sizeof(info), NULL, info);
fprintf(stderr, "Failed to link lighting pass program: %s\n", info);
free(vert_code);
free(frag_code);
return 0;
}
glDeleteShader(vert);
glDeleteShader(frag);
free(vert_code);
free(frag_code);
glGenBuffers(1, &pass->lights_sbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, pass->lights_sbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(Light) * MAX_LIGHTS, NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
return 1;
}
static int CreateToneMappingPass(ToneMappingPass *pass) {
/* Load vertex & fragment shader */
char *vert_code = LoadShader("shader/tone_map_vert.glsl");
if (!vert_code)
return 0;
char *frag_code = LoadShader("shader/tone_map_frag.glsl");
if (!frag_code) {
free(vert_code);
return 0;
}
GLint result;
GLuint vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert, 1, &vert_code, NULL);
glCompileShader(vert);
glGetShaderiv(vert, GL_COMPILE_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetShaderInfoLog(vert, sizeof(info), NULL, info);
fprintf(stderr, "Failed to compile shader/tone_map_vert.glsl: %s\n", info);
free(vert_code);
free(frag_code);
return 0;
}
GLuint frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &frag_code, NULL);
glCompileShader(frag);
glGetShaderiv(frag, GL_COMPILE_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetShaderInfoLog(frag, sizeof(info), NULL, info);
fprintf(stderr, "Failed to compile shader/tone_map_frag.glsl: %s\n", info);
free(vert_code);
free(frag_code);
return 0;
}
pass->prog = glCreateProgram();
glAttachShader(pass->prog, vert);
glAttachShader(pass->prog, frag);
glLinkProgram(pass->prog);
glGetProgramiv(pass->prog, GL_LINK_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetProgramInfoLog(pass->prog, sizeof(info), NULL, info);
fprintf(stderr, "Failed to link lighting pass program: %s\n", info);
free(vert_code);
free(frag_code);
return 0;
}
glDeleteShader(vert);
glDeleteShader(frag);
free(vert_code);
free(frag_code);
return 1;
}
static int CreatePixelClassifierPass(PixelClassifierPass *pass) {
/* Load vertex & fragment shader */
char *code = LoadShader("shader/pixel_classifier.glsl");
if (!code)
return 0;
GLint result;
GLuint comp = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(comp, 1, &code, NULL);
glCompileShader(comp);
glGetShaderiv(comp, GL_COMPILE_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetShaderInfoLog(comp, sizeof(info), NULL, info);
fprintf(stderr, "Failed to compile shader/pixel_classifier.glsl: %s\n", info);
free(code);
return 0;
}
pass->prog = glCreateProgram();
glAttachShader(pass->prog, comp);
glLinkProgram(pass->prog);
glGetProgramiv(pass->prog, GL_LINK_STATUS, &result);
if (result != GL_TRUE) {
char info[512];
glGetProgramInfoLog(pass->prog, sizeof(info), NULL, info);
fprintf(stderr, "Failed to link lighting pass program: %s\n", info);
free(code);
return 0;
}
glDeleteShader(comp);
free(code);
return 1;
}
static void CreateDifferentiationBuffer(int w, int h, DifferentiationBuffer *diff_buf) {
glGenTextures(1, &diff_buf->classification);
/* position buffer */
glBindTexture(GL_TEXTURE_2D, diff_buf->classification);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8UI, w, h, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
}
static void DestroyGBuffer(GBuffer *gbuf) {
glDeleteFramebuffers(1, &gbuf->fbo);
glDeleteRenderbuffers(1, &gbuf->depth);
glDeleteTextures(1, &gbuf->orm);
glDeleteTextures(1, &gbuf->normal);
glDeleteTextures(1, &gbuf->albedo);
glDeleteTextures(1, &gbuf->position);
memset(gbuf, 0, sizeof(*gbuf));
}
static void DestroyGeometryPass(GeometryPass *pass) {
glDeleteProgram(pass->prog);
memset(pass, 0, sizeof(*pass));
}
static void DestroyHDRBuffer(HDRBuffer *hdr_buf) {
glDeleteFramebuffers(1, &hdr_buf->fbo);
glDeleteTextures(1, &hdr_buf->color);
memset(hdr_buf, 0, sizeof(*hdr_buf));
}
static void DestroyLightingPass(LightingPass *pass) {
glDeleteProgram(pass->prog);
glDeleteBuffers(1, &pass->lights_sbo);
memset(pass, 0, sizeof(*pass));
}
static void DestroyToneMappingPass(ToneMappingPass *pass) {
glDeleteProgram(pass->prog);
memset(pass, 0, sizeof(*pass));
}
static void DestroyPixelClassificationPass(PixelClassifierPass *pass) {
glDeleteProgram(pass->prog);
memset(pass, 0, sizeof(*pass));
}
static void DestroyDifferentiationBuffer(DifferentiationBuffer *diff_buf) {
glDeleteTextures(1, &diff_buf->classification);
memset(diff_buf, 0, sizeof(*diff_buf));
}
/* Create the deferred renderer.
* w and h give the size of the gbuffer in pixels. */
DeferredRenderer *CreateDeferredRenderer(int w, int h, float near, float far) {
DeferredRenderer *renderer = malloc(sizeof(DeferredRenderer));
if (!renderer)
return NULL;
memset(renderer, 0, sizeof(*renderer));
CreateGBuffer(w, h, &renderer->gbuf);
if (!CreateGeometryPass(&renderer->geometry_pass)) {
DestroyGBuffer(&renderer->gbuf);
free(renderer);
return NULL;
}
CreateHDRBuffer(w, h, &renderer->hdr_buf);
if (!CreateLightingPass(&renderer->lighting_pass)) {
DestroyHDRBuffer(&renderer->hdr_buf);
DestroyGeometryPass(&renderer->geometry_pass);
DestroyGBuffer(&renderer->gbuf);
free(renderer);
return NULL;
}
if (!CreateToneMappingPass(&renderer->tone_mapping_pass)) {
DestroyLightingPass(&renderer->lighting_pass);
DestroyHDRBuffer(&renderer->hdr_buf);
DestroyGeometryPass(&renderer->geometry_pass);
DestroyGBuffer(&renderer->gbuf);
free(renderer);
return NULL;
}
if (!CreatePixelClassifierPass(&renderer->classifier_pass)) {
DestroyToneMappingPass(&renderer->tone_mapping_pass);
DestroyLightingPass(&renderer->lighting_pass);
DestroyHDRBuffer(&renderer->hdr_buf);
DestroyGeometryPass(&renderer->geometry_pass);
DestroyGBuffer(&renderer->gbuf);
free(renderer);
}
CreateDifferentiationBuffer(w, h, &renderer->diff_buffer);
glGenVertexArrays(1, &renderer->full_screen_triangle_vao);
glBindVertexArray(renderer->full_screen_triangle_vao);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
renderer->light_count = 0;
renderer->light_ssbo_needs_update = 0;
renderer->projection = Perspective(DegToRad(45.f), (float)w / (float)h, near, far);
renderer->width = w;
renderer->height = h;
return renderer;
}
/* Free all resources allocated for the deferred renderer. */
void DestroyDeferredRenderer(DeferredRenderer *ren) {
DestroyDifferentiationBuffer(&ren->diff_buffer);
DestroyPixelClassificationPass(&ren->classifier_pass);
DestroyToneMappingPass(&ren->tone_mapping_pass);
DestroyLightingPass(&ren->lighting_pass);
DestroyHDRBuffer(&ren->hdr_buf);
DestroyGeometryPass(&ren->geometry_pass);
DestroyGBuffer(&ren->gbuf);
free(ren);
}
void BeginFrame(DeferredRenderer *renderer, V3 eye_pos, V3 center, V3 up, V3 ambient_color) {
renderer->eye_pos = eye_pos;
renderer->center = center;
renderer->up = up;
renderer->ambient_color = ambient_color;
if (renderer->light_ssbo_needs_update) {
glBindBuffer(GL_SHADER_STORAGE_BUFFER, renderer->lighting_pass.lights_sbo);
glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(Light) * renderer->light_count, renderer->lights);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
renderer->light_ssbo_needs_update = 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, renderer->gbuf.fbo);
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void AddLight(DeferredRenderer *renderer, V3 center, V3 color, float radius) {
if (renderer->light_count == MAX_LIGHTS)
return;
Light l;
l.position_radius = (V4){ .x = center.x, .y = center.y, .z = center.z, .w = radius };
l.color = (V4){ color.x, color.y, color.z, 0.f };
renderer->lights[renderer->light_count++] = l;
renderer->light_ssbo_needs_update = 1;
}
void RenderModel(DeferredRenderer *renderer, const Model *model, M4 transform) {
glBindFramebuffer(GL_FRAMEBUFFER, renderer->gbuf.fbo);
glUseProgram(renderer->geometry_pass.prog);
glEnable(GL_DEPTH_TEST);
/* View & Projection */
FrameMatrices frame_matrices;
frame_matrices.projection = renderer->projection;
frame_matrices.view = LookAt(renderer->eye_pos, renderer->center, renderer->up);
glBindBuffer(GL_UNIFORM_BUFFER, renderer->geometry_pass.frame_matrices_ubo);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(frame_matrices), &frame_matrices);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, GEOMETRY_PASS_FRAME_MATRICES_BINDING, renderer->geometry_pass.frame_matrices_ubo);
glUniform1i(GEOMETRY_PASS_ALBEDO_LOCATION, 0);
glUniform1i(GEOMETRY_PASS_NORMAL_LOCATION, 1);
glUniform1i(GEOMETRY_PASS_ORM_LOCATION, 2);
for (unsigned int i = 0; i < model->mesh_count; ++i) {
glBindVertexArray(model->meshes[i].vao);
glUniformMatrix4fv(GEOMETRY_PASS_MODEL_LOCATION, 1, GL_FALSE, &transform.e[0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, model->meshes[i].material->albedo->tex);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, model->meshes[i].material->normal->tex);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, model->meshes[i].material->orm->tex);
glDrawElements(GL_TRIANGLES, model->meshes[i].index_count, GL_UNSIGNED_INT, NULL);
glBindVertexArray(0);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void ResolveGBuffer(DeferredRenderer *renderer) {
glBindFramebuffer(GL_FRAMEBUFFER, renderer->hdr_buf.fbo);
glUseProgram(renderer->lighting_pass.prog);
glDisable(GL_DEPTH_TEST);
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
glUniform1ui(LIGHTING_PASS_LIGHT_COUNT_LOCATION, renderer->light_count);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, LIGHTING_PASS_LIGHTS_BINDING, renderer->lighting_pass.lights_sbo);
glUniform3f(LIGHTING_PASS_AMBIENT_COLOR_LOCATION, renderer->ambient_color.x, renderer->ambient_color.y, renderer->ambient_color.z);
glUniform1i(LIGHTING_PASS_POSITION_LOCATION, 0);
glUniform1i(LIGHTING_PASS_ALBEDO_LOCATION, 1);
glUniform1i(LIGHTING_PASS_NORMAL_LOCATION, 2);
glUniform1i(LIGHTING_PASS_ORM_LOCATION, 3);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, renderer->gbuf.position);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, renderer->gbuf.albedo);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, renderer->gbuf.normal);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, renderer->gbuf.orm);
glBindVertexArray(renderer->full_screen_triangle_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void ToneMapping(DeferredRenderer *renderer) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glUseProgram(renderer->tone_mapping_pass.prog);
glDisable(GL_DEPTH_TEST);
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
glUniform1i(TONE_MAPPING_PASS_COLOR_LOCATION, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, renderer->hdr_buf.color);
glBindVertexArray(renderer->full_screen_triangle_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
void Differentiate(DeferredRenderer *renderer) {
glUseProgram(renderer->classifier_pass.prog);
GLuint groups_x = (GLuint)(renderer->width + 15) / 16;
GLuint groups_y = (GLuint)(renderer->height + 15) / 16;
glBindImageTexture(PIXEL_CLASSIFIER_PASS_NORMAL_BINDING, renderer->gbuf.normal, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA16F);
glBindImageTexture(PIXEL_CLASSIFIER_PASS_CLASSIFICATION_BINDING, renderer->diff_buffer.classification, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R8UI);
glDispatchCompute(groups_x, groups_y, 1);
}

43
src/diffren.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef DIFFREN_H
#define DIFFREN_H
#include "diffren_math.h"
/* Mark as unused to silence compiler warnings about unused parameters */
#define UNUSED(x) ((void)sizeof((x)))
/* Handles rendering of scenes */
typedef struct DeferredRenderer DeferredRenderer;
/* Opaque handle for an imported 3d model */
typedef struct Model Model;
/* Create the deferred renderer.
* w and h are the size of the gbuffer in pixels.
* near and far define the near and far plane distance. */
DeferredRenderer *CreateDeferredRenderer(int w, int h, float near, float far);
/* Free all resources allocated for the deferred renderer. */
void DestroyDeferredRenderer(DeferredRenderer *renderer);
void BeginFrame(DeferredRenderer *renderer, V3 eye_pos, V3 center, V3 up, V3 ambient_color);
void AddLight(DeferredRenderer *renderer, V3 center, V3 color, float radius);
/* Render a model into the scene */
void RenderModel(DeferredRenderer *renderer, const Model *model, M4 transform);
/* Resolve the geometry buffer and lights into a single rendered image */
void ResolveGBuffer(DeferredRenderer *renderer);
void ToneMapping(DeferredRenderer *renderer);
void Differentiate(DeferredRenderer *renderer);
/* Import a 3d model from the given file */
Model *ImportModel(const char *file);
/* Destroy an imported model */
void DestroyModel(Model *model);
#endif

82
src/diffren_math.h Normal file
View File

@ -0,0 +1,82 @@
#ifndef DIFFREN_MATH_H
#define DIFFREN_MATH_H
/* Math definitions */
/* 3d float vector */
typedef union {
struct {
float x;
float y;
float z;
};
float e[3];
} V3;
/* 2d float vector */
typedef union {
struct {
float x;
float y;
};
float e[2];
} V2;
/* 4d float vector */
typedef union {
struct {
float x;
float y;
float z;
float w;
};
float e[4];
} V4;
/* 4x4 float matrix */
typedef union {
V4 columns[4];
float e[4 * 4];
} M4;
inline V3 V3Sub(V3 a, V3 b) {
V3 s;
for (int i = 0; i < 3; ++i)
s.e[i] = a.e[i] - b.e[i];
return s;
}
inline V3 V3Add(V3 a, V3 b) {
V3 s;
for (int i = 0; i < 3; ++i)
s.e[i] = a.e[i] + b.e[i];
return s;
}
inline V3 V3Cross(V3 a, V3 b) {
V3 r;
r.x = a.y * b.z - a.z * b.y;
r.y = a.z * b.x - a.x * b.z;
r.z = a.x * b.y - a.y * b.x;
return r;
}
inline float DegToRad(float deg) {
return deg * (3.1415926f / 180.f);
}
V3 V3Norm(V3 v);
M4 LookAt(V3 eye, V3 center, V3 up);
M4 Perspective(float y_fov, float aspect, float n, float f);
M4 M4Mul(M4 a, M4 b);
M4 M4Identity(void);
M4 M4Translate(float x, float y, float z);
M4 M4Rotate(float x, float y, float z, float angle);
#endif

62
src/diffren_model.h Normal file
View File

@ -0,0 +1,62 @@
#ifndef DIFFREN_MODEL_H
#define DIFFREN_MODEL_H
/* 3D model structures */
#include <glad/gl.h>
#include <stdint.h>
#include "diffren_math.h"
typedef struct {
V3 position;
V3 normal;
V3 tangent;
V2 texcoord;
} Vertex;
typedef struct {
int width;
int height;
int channels;
char *name;
/* OpenGL objects */
GLuint tex;
} Texture;
typedef struct {
Texture *albedo;
Texture *normal;
Texture *orm;
} Material;
typedef struct {
Vertex *vertices;
uint32_t *indices;
unsigned int vertex_count;
unsigned int index_count;
Material *material;
/* OpenGL objects */
GLuint vbo;
GLuint ebo;
GLuint vao;
} Mesh;
typedef struct Model {
Mesh *meshes;
unsigned int mesh_count;
Material *materials;
unsigned int material_count;
Texture *textures;
unsigned int texture_count;
char *directory;
} Model;
#endif

136
src/main.c Normal file
View File

@ -0,0 +1,136 @@
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <stdio.h>
#include "diffren.h"
void APIENTRY glDebugOutput(GLenum source,
GLenum type,
unsigned int id,
GLenum severity,
GLsizei length,
const char *message,
const void *userParam)
{
UNUSED(length);
UNUSED(userParam);
// ignore non-significant error/warning codes
if (id == 131169 || id == 131185 || id == 131218 || id == 131204)
return;
fprintf(stderr, "---------------\n");
fprintf(stderr, "Debug message (%u): %s\n", id, message);
switch (source)
{
case GL_DEBUG_SOURCE_API: fprintf(stderr, "Source: API"); break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: fprintf(stderr, "Source: Window System"); break;
case GL_DEBUG_SOURCE_SHADER_COMPILER: fprintf(stderr, "Source: Shader Compiler"); break;
case GL_DEBUG_SOURCE_THIRD_PARTY: fprintf(stderr, "Source: Third Party"); break;
case GL_DEBUG_SOURCE_APPLICATION: fprintf(stderr, "Source: Application"); break;
case GL_DEBUG_SOURCE_OTHER: fprintf(stderr, "Source: Other"); break;
}
fprintf(stderr, "\n");
switch (type)
{
case GL_DEBUG_TYPE_ERROR: fprintf(stderr, "Type: Error"); break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: fprintf(stderr, "Type: Deprecated Behaviour"); break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: fprintf(stderr, "Type: Undefined Behaviour"); break;
case GL_DEBUG_TYPE_PORTABILITY: fprintf(stderr, "Type: Portability"); break;
case GL_DEBUG_TYPE_PERFORMANCE: fprintf(stderr, "Type: Performance"); break;
case GL_DEBUG_TYPE_MARKER: fprintf(stderr, "Type: Marker"); break;
case GL_DEBUG_TYPE_PUSH_GROUP: fprintf(stderr, "Type: Push Group"); break;
case GL_DEBUG_TYPE_POP_GROUP: fprintf(stderr, "Type: Pop Group"); break;
case GL_DEBUG_TYPE_OTHER: fprintf(stderr, "Type: Other"); break;
}
fprintf(stderr, "\n");
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH: fprintf(stderr, "Severity: high"); break;
case GL_DEBUG_SEVERITY_MEDIUM: fprintf(stderr, "Severity: medium"); break;
case GL_DEBUG_SEVERITY_LOW: fprintf(stderr, "Severity: low"); break;
case GL_DEBUG_SEVERITY_NOTIFICATION: fprintf(stderr, "Severity: notification"); break;
}
fprintf(stderr, "\n\n");
}
static void SetupDebugOutput() {
int flags;
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(glDebugOutput, NULL);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
}
}
int main(int argc, char **argv) {
UNUSED(argc);
UNUSED(argv);
if (!glfwInit())
return 1;
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
#ifndef NDEBUG
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
#endif
GLFWwindow *window = glfwCreateWindow(1024, 768, "diffren", NULL, NULL);
if (!window) {
glfwTerminate();
return 1;
}
glfwMakeContextCurrent(window);
if (!gladLoadGL((GLADloadfunc)glfwGetProcAddress)) {
glfwDestroyWindow(window);
glfwTerminate();
return 1;
}
#ifndef NDEBUG
SetupDebugOutput();
#endif
/* Vsync */
glfwSwapInterval(1);
int fbuf_w, fbuf_h;
glfwGetFramebufferSize(window, &fbuf_w, &fbuf_h);
DeferredRenderer *renderer = CreateDeferredRenderer(fbuf_w, fbuf_h, 0.1f, 100.f);
Model *waterbottle = ImportModel("model/WaterBottle.gltf");
AddLight(renderer, (V3) { -1.f, -1.f, -1.f, }, (V3) { 12.f, 12.f, 4.f }, 3.f);
AddLight(renderer, (V3) { 1.f, 1.f, -1.f, }, (V3) { 2.f, 2.f, 20.f }, 3.f);
float angle = 0.f;
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
BeginFrame(renderer, (V3) { -1.f, 0.f, 0.f }, (V3) { 0.f, 0.f, 0.f }, (V3) { 0.f, 1.f, 0.f }, (V3) { 0.3f, 0.3f, 0.3f });
M4 transform = M4Rotate(1.f, 0.f, 1.f, DegToRad(angle));
RenderModel(renderer, waterbottle, transform);
transform = M4Mul(M4Translate(0.f, 0.f, 0.2f), M4Rotate(0.f, 1.f, 0.f, DegToRad(angle)));
RenderModel(renderer, waterbottle, transform);
angle += 0.5f;
ResolveGBuffer(renderer);
ToneMapping(renderer);
Differentiate(renderer);
glfwSwapBuffers(window);
}
DestroyDeferredRenderer(renderer);
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

199
src/math.c Normal file
View File

@ -0,0 +1,199 @@
#include "diffren_math.h"
#include <math.h>
#include <string.h>
static float V4MulInner(V4 a, V4 b) {
float p = 0.f;
int i;
for (i = 0; i < 4; ++i)
p += b.e[i] * a.e[i];
return p;
}
static float V3Length(V3 v) {
return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
}
V3 V3Norm(V3 v) {
float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
V3 n = { .x = v.x / len, .y = v.y / len, .z = v.z / len };
return n;
}
static V4 Mat4Row(const M4 *m, int i)
{
V4 r;
int k;
for (k = 0; k < 4; ++k)
r.e[k] = m->columns[k].e[i];
return r;
}
static void M4TranslateInPlace(M4 *m, float x, float y, float z)
{
V4 t = { x, y, z, 0 };
V4 r;
int i;
for (i = 0; i < 4; ++i) {
r = Mat4Row(m, i);
m->columns[3].e[i] += V4MulInner(r, t);
}
}
M4 M4Add(M4 a, M4 b) {
M4 s;
for (int i = 0; i < 4 * 4; ++i)
s.e[i] = a.e[i] + b.e[i];
return s;
}
M4 M4Sub(M4 a, M4 b) {
M4 s;
for (int i = 0; i < 4 * 4; ++i)
s.e[i] = a.e[i] - b.e[i];
return s;
}
M4 M4Mul(M4 a, M4 b)
{
M4 temp;
int k, r, c;
for (c = 0; c < 4; ++c) {
for (r = 0; r < 4; ++r) {
temp.columns[c].e[r] = 0.f;
for (k = 0; k < 4; ++k)
temp.columns[c].e[r] += a.columns[k].e[r] * b.columns[c].e[k];
}
}
return temp;
}
M4 M4FromV3Outer(V3 a, V3 b)
{
M4 M;
int i, j;
for (i = 0; i < 4; ++i) for (j = 0; j < 4; ++j)
M.columns[i].e[j] = i < 3 && j < 3 ? a.e[i] * b.e[j] : 0.f;
return M;
}
M4 LookAt(V3 eye, V3 center, V3 up) {
M4 m;
V3 f = V3Sub(center, eye);
f = V3Norm(f);
V3 s = V3Cross(f, up);
s = V3Norm(s);
V3 t = V3Cross(s, f);
m.columns[0].e[0] = s.x;
m.columns[0].e[1] = t.x;
m.columns[0].e[2] = -f.x;
m.columns[0].e[3] = 0.f;
m.columns[1].e[0] = s.y;
m.columns[1].e[1] = t.y;
m.columns[1].e[2] = -f.y;
m.columns[1].e[3] = 0.f;
m.columns[2].e[0] = s.z;
m.columns[2].e[1] = t.z;
m.columns[2].e[2] = -f.z;
m.columns[2].e[3] = 0.f;
m.columns[3].e[0] = 0.f;
m.columns[3].e[1] = 0.f;
m.columns[3].e[2] = 0.f;
m.columns[3].e[3] = 1.f;
M4TranslateInPlace(&m, -eye.x, -eye.y, -eye.z);
return m;
}
M4 Perspective(float y_fov, float aspect, float n, float f)
{
M4 m;
float a = 1.f / tanf(y_fov / 2.f);
m.columns[0].e[0] = a / aspect;
m.columns[0].e[1] = 0.f;
m.columns[0].e[2] = 0.f;
m.columns[0].e[3] = 0.f;
m.columns[1].e[0] = 0.f;
m.columns[1].e[1] = a;
m.columns[1].e[2] = 0.f;
m.columns[1].e[3] = 0.f;
m.columns[2].e[0] = 0.f;
m.columns[2].e[1] = 0.f;
m.columns[2].e[2] = -((f + n) / (f - n));
m.columns[2].e[3] = -1.f;
m.columns[3].e[0] = 0.f;
m.columns[3].e[1] = 0.f;
m.columns[3].e[2] = -((2.f * f * n) / (f - n));
m.columns[3].e[3] = 0.f;
return m;
}
M4 M4MulScalar(M4 m, float s) {
M4 t;
for (int i = 0; i < 4 * 4; ++i)
t.e[i] = m.e[i] * s;
return t;
}
M4 M4Identity(void) {
M4 m;
memset(&m, 0, sizeof(m));
m.columns[0].e[0] = 1.f;
m.columns[1].e[1] = 1.f;
m.columns[2].e[2] = 1.f;
m.columns[3].e[3] = 1.f;
return m;
}
M4 M4Translate(float x, float y, float z) {
M4 m = M4Identity();
m.columns[3].x = x;
m.columns[3].y = y;
m.columns[3].z = z;
return m;
}
M4 M4Rotate(float x, float y, float z, float angle) {
float s = sinf(angle);
float c = cosf(angle);
V3 u = { x, y, z };
if (V3Length(u) > 1e-4) {
u = V3Norm(u);
M4 T = M4FromV3Outer(u, u);
M4 S = {
.columns[0] = { 0, u.z, -u.y, 0},
.columns[1] = {-u.z, 0, u.x, 0},
.columns[2] = { u.y, -u.x, 0, 0},
.columns[3] = { 0, 0, 0, 0}
};
S = M4MulScalar(S, s);
M4 C = M4Identity();
C = M4Sub(C, T);
C = M4MulScalar(C, c);
T = M4Add(T, C);
T = M4Add(T, S);
T.columns[3].e[3] = 1.f;
return T;
}
else {
return M4Identity();
}
}

284
src/model_loader.c Normal file
View File

@ -0,0 +1,284 @@
#include <assimp/cimport.h>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <stdlib.h>
#include <stdio.h>
#include "diffren.h"
#include "diffren_model.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
/* TODO(Kevin): Handle the node structure.
* Handle multiple meshes using the same texture.
* Turn allocations into flat preallocated buffers
*/
static int ProcessTexture(struct aiMaterial *mat, enum aiTextureType type, Model *model) {
if (aiGetMaterialTextureCount(mat, type) == 0)
return 0;
struct aiString path;
if (aiGetMaterialTexture(mat, type, 0, &path, NULL, NULL, NULL, NULL, NULL, NULL) != aiReturn_SUCCESS)
return 0;
Texture tex;
tex.name = malloc(path.length + 1);
if (!tex.name)
return 0;
memcpy(tex.name, path.data, path.length + 1);
char full_path[260];
snprintf(full_path, 259, "%s%s", model->directory, path.data);
full_path[259] = '\0';
stbi_uc *pixels = stbi_load(full_path, &tex.width, &tex.height, &tex.channels, 0);
if (!pixels) {
free(tex.name);
return 0;
}
glGenTextures(1, &tex.tex);
glBindTexture(GL_TEXTURE_2D, tex.tex);
GLenum internal_format, format;
if (tex.channels == 3) {
internal_format = GL_RGB8;
format = GL_RGB;
}
else if (tex.channels == 4) {
internal_format = GL_RGBA8;
format = GL_RGBA;
}
else if (tex.channels == 2) {
internal_format = GL_RG8;
format = GL_RG;
}
else if (tex.channels == 1) {
internal_format = GL_R8;
format = GL_RED;
}
else {
free(tex.name);
stbi_image_free(pixels);
return 0;
}
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, tex.width, tex.height, 0, format, GL_UNSIGNED_BYTE, pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
stbi_image_free(pixels);
model->textures[model->texture_count++] = tex;
return 1;
}
static int ProcessMaterial(struct aiMaterial *in_material, Model *model) {
Material mat;
if (ProcessTexture(in_material, aiTextureType_BASE_COLOR, model))
mat.albedo = &model->textures[model->texture_count - 1];
else
mat.albedo = NULL;
if (ProcessTexture(in_material, aiTextureType_NORMALS, model))
mat.normal = &model->textures[model->texture_count - 1];
else
mat.normal = NULL;
if (ProcessTexture(in_material, aiTextureType_DIFFUSE_ROUGHNESS, model))
mat.orm = &model->textures[model->texture_count - 1];
else
mat.orm = NULL;
model->materials[model->material_count++] = mat;
return 1;
}
static int ProcessMesh(struct aiMesh *in_mesh, const struct aiScene *scene, Model *model) {
Mesh m;
m.vertices = malloc(in_mesh->mNumVertices * sizeof(Vertex));
if (!m.vertices)
return 0;
m.indices = malloc(in_mesh->mNumFaces * 3 * sizeof(uint32_t));
if (!m.indices) {
free(m.vertices);
return 0;
}
m.vertex_count = in_mesh->mNumVertices;
m.index_count = 0;
for (unsigned int i = 0; i < in_mesh->mNumVertices; ++i) {
Vertex v;
v.position.x = in_mesh->mVertices[i].x;
v.position.y = in_mesh->mVertices[i].y;
v.position.z = in_mesh->mVertices[i].z;
v.normal.x = in_mesh->mNormals[i].x;
v.normal.y = in_mesh->mNormals[i].y;
v.normal.z = in_mesh->mNormals[i].z;
v.tangent.x = in_mesh->mTangents[i].x;
v.tangent.y = in_mesh->mTangents[i].y;
v.tangent.z = in_mesh->mTangents[i].z;
if (in_mesh->mTextureCoords[0]) {
v.texcoord.x = in_mesh->mTextureCoords[0][i].x;
v.texcoord.y = in_mesh->mTextureCoords[0][i].y;
}
else {
v.texcoord.x = 0.f;
v.texcoord.y = 0.f;
}
m.vertices[i] = v;
}
for (unsigned int i = 0; i < in_mesh->mNumFaces; ++i) {
struct aiFace face = in_mesh->mFaces[i];
for (unsigned int j = 0; j < face.mNumIndices; ++j) {
m.indices[m.index_count++] = face.mIndices[j];
}
}
if (in_mesh->mMaterialIndex >= 0) {
if (!ProcessMaterial(scene->mMaterials[in_mesh->mMaterialIndex], model)) {
free(m.vertices);
free(m.indices);
return 0;
}
m.material = &model->materials[model->material_count - 1];
}
else {
m.material = NULL;
}
glGenBuffers(1, &m.vbo);
glGenBuffers(1, &m.ebo);
glGenVertexArrays(1, &m.vao);
glBindVertexArray(m.vao);
glBindBuffer(GL_ARRAY_BUFFER, m.vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * m.vertex_count, m.vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * m.index_count, m.indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tangent));
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texcoord));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glBindVertexArray(0);
model->meshes[model->mesh_count++] = m;
return 1;
}
static int ProcessNode(struct aiNode *node, const struct aiScene *scene, Model *model) {
for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
struct aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
if (!ProcessMesh(mesh, scene, model))
return 0;
}
for (unsigned int i = 0; i < node->mNumChildren; ++i) {
if (!ProcessNode(node->mChildren[i], scene, model))
return 0;
}
return 1;
}
/* Import a 3d model from the given file */
Model *ImportModel(const char *file) {
Model *model = malloc(sizeof(*model));
if (!model) {
fprintf(stderr, "Could not import %s: Could not allocate storage.\n", file);
return NULL;
}
/* Determine directory */
model->directory = malloc(strlen(file) + 1);
if (!model->directory) {
fprintf(stderr, "Could not import %s: Could not allocate directory path storage.\n", file);
free(model);
return NULL;
}
memcpy(model->directory, file, strlen(file) + 1);
for (int i = (int)strlen(file); i >= 0; --i) {
if (model->directory[i] == '/' || model->directory[i] == '\\') {
model->directory[i + 1] = '\0';
break;
}
}
const struct aiScene *scene = aiImportFile(file,
aiProcess_CalcTangentSpace |
aiProcess_Triangulate |
aiProcess_JoinIdenticalVertices |
aiProcess_SortByPType |
aiProcess_OptimizeMeshes |
aiProcess_FlipUVs |
aiProcess_GenNormals);
if (!scene || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) != 0 || !scene->mRootNode) {
fprintf(stderr, "Could not import %s: %s", file, aiGetErrorString());
free(model);
return NULL;
}
model->meshes = malloc(sizeof(Mesh) * scene->mNumMeshes);
if (!model->meshes) {
fprintf(stderr, "Could not import %s: Could not allocate mesh storage.\n", file);
free(model);
aiReleaseImport(scene);
return NULL;
}
model->mesh_count = 0;
model->materials = malloc(sizeof(Material) * scene->mNumMaterials);
if (!model->materials) {
fprintf(stderr, "Could not import %s: Could not allocate material storage.\n", file);
free(model->meshes);
free(model);
aiReleaseImport(scene);
return NULL;
}
model->material_count = 0;
model->textures = malloc(sizeof(Texture) * scene->mNumMaterials * 3);
if (!model->textures) {
fprintf(stderr, "Could not import %s: Could not allocate texture storage.\n", file);
free(model->textures);
free(model->meshes);
free(model);
aiReleaseImport(scene);
return NULL;
}
model->texture_count = 0;
if (!ProcessNode(scene->mRootNode, scene, model)) {
DestroyModel(model);
model = NULL;
}
aiReleaseImport(scene);
return model;
}
/* Destroy an imported model */
void DestroyModel(Model *model) {
free(model->directory);
for (unsigned int i = 0; i < model->texture_count; ++i) {
free(model->textures[i].name);
glDeleteTextures(1, &model->textures[i].tex);
}
free(model->textures);
free(model->materials);
for (unsigned int i = 0; i < model->mesh_count; ++i) {
free(model->meshes[i].vertices);
free(model->meshes[i].indices);
glDeleteBuffers(1, &model->meshes[i].vbo);
glDeleteBuffers(1, &model->meshes[i].ebo);
glDeleteVertexArrays(1, &model->meshes[i].vbo);
}
free(model->meshes);
free(model);
}

7985
src/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

3
subprojects/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
assimp-5.3.1/*
glfw-*/*
packagecache/*

13
subprojects/glfw.wrap Normal file
View File

@ -0,0 +1,13 @@
[wrap-file]
directory = glfw-3.3.9
source_url = https://github.com/glfw/glfw/archive/refs/tags/3.3.9.tar.gz
source_filename = glfw-3.3.9.tar.gz
source_hash = a7e7faef424fcb5f83d8faecf9d697a338da7f7a906fc1afbc0e1879ef31bd53
patch_filename = glfw_3.3.9-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/glfw_3.3.9-1/get_patch
patch_hash = b4261ed4de479ea0496617feace62eec5b73f62bf3c88dd1537afcf5eebd5cab
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/glfw_3.3.9-1/glfw-3.3.9.tar.gz
wrapdb_version = 3.3.9-1
[provide]
glfw3 = glfw_dep