├── assets ├── bug.png ├── client.png ├── cube.png ├── editor.png ├── gear.png ├── logo.png ├── server.png ├── question.png ├── screenshot.png ├── client_thumbnail.jpg ├── editor_thumbnail.jpg └── server_thumbnail.jpg ├── docs ├── axes.png ├── coords.png ├── trigonometry.png ├── Projection_and_rejection.png ├── ideas.md └── features.md ├── include ├── server.h ├── modelmanager.h ├── model.h ├── framebuffer.h ├── gizmo.h ├── entity.h ├── scene.h ├── light.h ├── environment.h ├── material.h ├── ui.h ├── camera.h ├── client.h ├── renderer.h ├── window.h ├── utils.h ├── mesh.h ├── texture.h └── shader.h ├── shaders ├── plain │ ├── main.frag │ └── main.vert ├── equirect2cube │ ├── main.vert │ └── main.frag ├── iblsampler │ ├── main.vert │ └── main.frag ├── skybox │ ├── main.frag │ └── main.vert └── pbr │ └── main.vert ├── cmake ├── incbin │ └── CMakeLists.txt └── cimgui │ └── CMakeLists.txt ├── lib ├── gltf │ ├── CMakeLists.txt │ └── LICENSE └── stb │ ├── CMakeLists.txt │ └── include │ └── stb_image.h ├── .gitignore ├── src ├── main.c ├── server.c ├── light.c ├── framebuffer.c ├── entity.c ├── scene.c ├── camera.c ├── modelmanager.c ├── material.c ├── client.c ├── gizmo.c ├── mesh.c ├── texture.c ├── utils.c ├── renderer.c ├── window.c ├── model.c ├── environment.c ├── ui.c └── shader.c ├── .gitmodules ├── UNLICENSE ├── README.md ├── CMakeLists.txt └── .uncrustify /assets/bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/bug.png -------------------------------------------------------------------------------- /docs/axes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/docs/axes.png -------------------------------------------------------------------------------- /assets/client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/client.png -------------------------------------------------------------------------------- /assets/cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/cube.png -------------------------------------------------------------------------------- /assets/editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/editor.png -------------------------------------------------------------------------------- /assets/gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/gear.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/server.png -------------------------------------------------------------------------------- /docs/coords.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/docs/coords.png -------------------------------------------------------------------------------- /assets/question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/question.png -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/screenshot.png -------------------------------------------------------------------------------- /docs/trigonometry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/docs/trigonometry.png -------------------------------------------------------------------------------- /assets/client_thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/client_thumbnail.jpg -------------------------------------------------------------------------------- /assets/editor_thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/editor_thumbnail.jpg -------------------------------------------------------------------------------- /assets/server_thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/assets/server_thumbnail.jpg -------------------------------------------------------------------------------- /include/server.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_H 2 | #define SERVER_H 3 | 4 | int server_run(void); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /docs/Projection_and_rejection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrix/layman/HEAD/docs/Projection_and_rejection.png -------------------------------------------------------------------------------- /shaders/plain/main.frag: -------------------------------------------------------------------------------- 1 | uniform vec4 u_Color; 2 | out vec4 color; 3 | 4 | void main() { 5 | color = u_Color; 6 | } -------------------------------------------------------------------------------- /cmake/incbin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_LIBRARY(incbin STATIC ../../lib/incbin/incbin.c) 2 | TARGET_INCLUDE_DIRECTORIES(incbin PUBLIC ../../lib/incbin) -------------------------------------------------------------------------------- /lib/gltf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.0) 2 | PROJECT(gltf) 3 | 4 | ADD_LIBRARY(gltf src/gltf.c) 5 | TARGET_INCLUDE_DIRECTORIES(gltf PUBLIC include) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio Code editor. 2 | /.vscode 3 | /build 4 | 5 | # Do not commit large assets. 6 | /assets/*.glb 7 | /assets/*.gltf 8 | /assets/*.bin 9 | /assets/*.hdr -------------------------------------------------------------------------------- /lib/stb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.0) 2 | PROJECT(stb) 3 | 4 | ADD_LIBRARY(stb_image src/stb_image.c) 5 | TARGET_INCLUDE_DIRECTORIES(stb_image PUBLIC include) -------------------------------------------------------------------------------- /include/modelmanager.h: -------------------------------------------------------------------------------- 1 | #ifndef MODELMANAGER_H 2 | #define MODELMANAGER_H 3 | 4 | struct model *modelmanager_load_model(const char *filepath); 5 | void modelmanager_unload_model(const struct model *model); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /shaders/equirect2cube/main.vert: -------------------------------------------------------------------------------- 1 | in vec3 aPos; 2 | 3 | out vec3 localPos; 4 | 5 | uniform mat4 projection; 6 | uniform mat4 view; 7 | 8 | void main() { 9 | localPos = aPos; 10 | gl_Position = projection * view * vec4(localPos, 1.0); 11 | } -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | #include "server.h" 3 | 4 | int main(int argc, char *argv[]) { 5 | bool run_as_server = argc > 1 && strcmp(argv[1], "--server") == 0; 6 | 7 | if (run_as_server) { 8 | return server_run(); 9 | } else { 10 | return client_run(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /include/model.h: -------------------------------------------------------------------------------- 1 | #ifndef MODEL_H 2 | #define MODEL_H 3 | 4 | #include 5 | 6 | struct model { 7 | char *filepath; 8 | struct mesh *meshes; 9 | size_t meshes_count; 10 | }; 11 | 12 | struct model *model_load(const char *filepath); 13 | void model_destroy(struct model *model); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /shaders/iblsampler/main.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | out vec2 UV; 4 | 5 | void main() { 6 | float x = -1.0 + float((gl_VertexID & 1) << 2); 7 | float y = -1.0 + float((gl_VertexID & 2) << 1); 8 | UV.x = (x + 1.0) * 0.5; 9 | UV.y = (y + 1.0) * 0.5; 10 | gl_Position = vec4(x, y, 0.0, 1.0); 11 | } -------------------------------------------------------------------------------- /src/server.c: -------------------------------------------------------------------------------- 1 | #include "toolkit.h" 2 | #include "utils.h" 3 | #include 4 | #include 5 | 6 | int server_run(void) { 7 | printf("%s (%s)\n", DEFAULT_TITLE, VERSION); 8 | printf("Listening on 0.0.0.0:5000\n"); 9 | 10 | while (true) { 11 | time_millisleep(1000); 12 | } 13 | 14 | return EXIT_SUCCESS; 15 | } 16 | -------------------------------------------------------------------------------- /include/framebuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef FRAMEBUFFER_H 2 | #define FRAMEBUFFER_H 3 | 4 | #include "glad/glad.h" 5 | 6 | struct framebuffer { 7 | int width, height; 8 | GLuint fbo; 9 | GLuint rbo; 10 | }; 11 | 12 | void framebuffer_init(struct framebuffer *fb, int width, int height); 13 | void framebuffer_fini(struct framebuffer *fb); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/light.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | void light_init(struct light *light, enum light_type type) { 4 | light->type = type; 5 | glm_vec3_zero(light->position); 6 | glm_vec3_copy((vec3) { 0, 0, -1}, light->direction); 7 | light->range = 5; 8 | glm_vec3_copy((vec3) { 1, 1, 1}, light->color); 9 | light->intensity = 1; 10 | light->innerConeCos = 1; 11 | light->outerConeCos = 1; 12 | } 13 | -------------------------------------------------------------------------------- /include/gizmo.h: -------------------------------------------------------------------------------- 1 | #ifndef GIZMO_H 2 | #define GIZMO_H 3 | 4 | enum gizmo_mode { 5 | GIZMO_MODE_NONE, 6 | GIZMO_MODE_TRANSLATION, 7 | GIZMO_MODE_ROTATION, 8 | GIZMO_MODE_SCALE, 9 | }; 10 | 11 | struct gizmo { 12 | enum gizmo_mode mode; 13 | }; 14 | 15 | void gizmo_init(struct gizmo *gizmo); 16 | void gizmo_fini(struct gizmo *gizmo); 17 | void gizmo_render(struct gizmo *gizmo); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /shaders/skybox/main.frag: -------------------------------------------------------------------------------- 1 | out vec4 FragColor; 2 | 3 | in vec3 localPos; 4 | 5 | uniform samplerCube environmentMap; 6 | 7 | void main() { 8 | vec3 envColor = texture(environmentMap, localPos).rgb; 9 | 10 | // Tone correct HDR values to LDR. 11 | envColor = envColor / (envColor + vec3(1.0)); 12 | envColor = pow(envColor, vec3(1.0/2.2)); 13 | 14 | FragColor = vec4(envColor, 1.0); 15 | } -------------------------------------------------------------------------------- /include/entity.h: -------------------------------------------------------------------------------- 1 | #ifndef ENTITY_H 2 | #define ENTITY_H 3 | 4 | struct entity { 5 | uint32_t id; 6 | const struct model *model; 7 | vec3 translation; 8 | versor rotation; 9 | float scale; 10 | }; 11 | 12 | bool entity_init(struct entity *entity, const char *model_filepath); 13 | void entity_fini(struct entity *entity); 14 | void entity_id_as_color(uint32_t id, vec4 color); 15 | uint32_t entity_color_as_id(vec4 color); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /shaders/skybox/main.vert: -------------------------------------------------------------------------------- 1 | in vec3 aPos; 2 | 3 | uniform mat4 projection; 4 | uniform mat4 view; 5 | 6 | out vec3 localPos; 7 | 8 | void main() { 9 | localPos = aPos; 10 | 11 | mat4 rotView = mat4(mat3(view)); // Removes the translation from the view matrix 12 | vec4 clipPos = projection * rotView * vec4(localPos, 1.0); 13 | 14 | // Little swizzle trick here, so that all fragments end up at 1.0 depth. 15 | // Requires glDepthFunc(GL_LEQUAL); 16 | gl_Position = clipPos.xyww; 17 | } -------------------------------------------------------------------------------- /include/scene.h: -------------------------------------------------------------------------------- 1 | #ifndef SCENE_H 2 | #define SCENE_H 3 | 4 | struct scene { 5 | struct entity **entities; 6 | size_t entity_count; 7 | size_t entity_capacity; 8 | 9 | const struct light **lights; 10 | size_t lights_count; 11 | 12 | struct environment *environment; 13 | }; 14 | 15 | void scene_init(struct scene *scene); 16 | void scene_fini(struct scene *scene); 17 | bool scene_add_entity(struct scene *scene, struct entity *entity); 18 | bool scene_add_light(struct scene *scene, const struct light *light); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /shaders/equirect2cube/main.frag: -------------------------------------------------------------------------------- 1 | out vec4 FragColor; 2 | in vec3 localPos; 3 | 4 | uniform sampler2D equirectangularMap; 5 | 6 | const vec2 invAtan = vec2(0.1591, 0.3183); 7 | vec2 SampleSphericalMap(vec3 v) { 8 | vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); 9 | uv *= invAtan; 10 | uv += 0.5; 11 | return uv; 12 | } 13 | 14 | void main() { 15 | vec2 uv = SampleSphericalMap(normalize(localPos)); // make sure to normalize localPos 16 | vec3 color = texture(equirectangularMap, uv).rgb; 17 | 18 | FragColor = vec4(color, 1.0); 19 | } 20 | -------------------------------------------------------------------------------- /include/light.h: -------------------------------------------------------------------------------- 1 | #ifndef LIGHT_H 2 | #define LIGHT_H 3 | 4 | #include "cglm/cglm.h" 5 | 6 | // Passed as-is to the shaders and must therefore match the enums in them. 7 | enum light_type { 8 | LIGHT_TYPE_DIRECTIONAL = 0, 9 | LIGHT_TYPE_POINT = 1, 10 | LIGHT_TYPE_SPOT = 2, 11 | }; 12 | 13 | struct light { 14 | enum light_type type; 15 | vec3 position; 16 | vec3 direction; 17 | float range; 18 | vec3 color; 19 | float intensity; 20 | float innerConeCos; 21 | float outerConeCos; 22 | }; 23 | 24 | #define MAX_LIGHTS 4 25 | 26 | void light_init(struct light *light, enum light_type type); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /include/environment.h: -------------------------------------------------------------------------------- 1 | #ifndef ENVIRONMENT_H 2 | #define ENVIRONMENT_H 3 | 4 | #include "texture.h" 5 | 6 | struct environment { 7 | struct texture cubemap; 8 | 9 | size_t mip_count; 10 | struct texture lambertian; 11 | struct texture ggx; 12 | struct texture ggx_lut; 13 | struct texture charlie; 14 | struct texture charlie_lut; 15 | }; 16 | 17 | bool environment_init_from_file(struct environment *environment, const char *filepath); 18 | void environment_fini(struct environment *environment); 19 | 20 | void environment_debug(const struct environment *environment); 21 | void environment_switch(const struct environment *environment); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/framebuffer.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | void framebuffer_init(struct framebuffer *fb, int width, int height) { 4 | fb->width = width; 5 | fb->height = height; 6 | 7 | glGenFramebuffers(1, &fb->fbo); 8 | glGenRenderbuffers(1, &fb->rbo); 9 | } 10 | 11 | void framebuffer_fini(struct framebuffer *fb) { 12 | glDeleteRenderbuffers(1, &fb->rbo); 13 | glDeleteFramebuffers(1, &fb->fbo); 14 | } 15 | 16 | void framebuffer_switch(const struct framebuffer *new) { 17 | glBindFramebuffer(GL_FRAMEBUFFER, new->fbo); 18 | glBindRenderbuffer(GL_RENDERBUFFER, new->rbo); 19 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, new->width, new->height); 20 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, new->rbo); 21 | } 22 | -------------------------------------------------------------------------------- /include/material.h: -------------------------------------------------------------------------------- 1 | #ifndef MATERIAL_H 2 | #define MATERIAL_H 3 | 4 | #include "cglm/cglm.h" 5 | 6 | struct material { 7 | char *name; 8 | 9 | // Mettalic/roughness. 10 | vec4 base_color_factor; 11 | struct texture *base_color_texture; 12 | struct texture *metallic_roughness_texture; 13 | float metallic_factor; 14 | float roughness_factor; 15 | 16 | // Other things. 17 | struct texture *normal_texture; 18 | float normal_scale; 19 | struct texture *occlusion_texture; 20 | float occlusion_strength; 21 | struct texture *emissive_texture; 22 | vec3 emissive_factor; 23 | 24 | bool double_sided; 25 | }; 26 | 27 | void material_init(struct material *material); 28 | void material_fini(struct material *material); 29 | void material_switch(const struct material *material); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /include/ui.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_H 2 | #define UI_H 3 | 4 | #include "gizmo.h" 5 | #include "texture.h" 6 | 7 | struct ui { 8 | // UI via (c)imgui, aka ig. 9 | struct ImGuiContext *ig_context; 10 | struct ImGuiIO *ig_io; 11 | 12 | bool show; 13 | bool show_imgui_demo; 14 | bool show_scene_editor; 15 | bool show_debug_tools; 16 | bool show_settings; 17 | bool show_debug_camera; 18 | bool show_about; 19 | 20 | uint32_t selected_entity_id; 21 | 22 | struct texture assets_logo_png; 23 | struct texture assets_gear_png; 24 | struct texture assets_bug_png; 25 | struct texture assets_question_png; 26 | struct texture assets_cube_png; 27 | 28 | struct gizmo gizmo; 29 | }; 30 | 31 | bool ui_init(struct ui *ui); 32 | void ui_fini(struct ui *ui); 33 | void ui_render(struct ui *ui); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /docs/ideas.md: -------------------------------------------------------------------------------- 1 | # Ideas to look into 2 | 3 | - Face culling. 4 | - Flat terrain. 5 | - Transparency 6 | - Fog 7 | - Third person camera with tilt-pan-zoom offsets. 8 | - Skeletal animation 9 | - Multi-texturing 10 | - Terrain collision 11 | - Texture atlases 12 | - GUI 13 | - Day/night cycle 14 | - Cell shading 15 | - Font rendering 16 | - Distance field text rendering 17 | - Particle effects 18 | - Instanced rendering 19 | - Procedural terrain 20 | - Shadow mapping 21 | - Percentage closer filtering 22 | - Post-processing effects 23 | - Gaussian Blur 24 | - Bloom effect 25 | - Multiple render targets 26 | - Geometry shaders 27 | - Cube map reflections 28 | - Lens flare 29 | - Occlusion queries 30 | - Water 31 | - Runtime keybindings. 32 | - Sheen KHR extension. 33 | - Transmission KHR extension. 34 | - Specular/glossiness workflow. -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/glad"] 2 | path = lib/glad 3 | url = https://github.com/nitrix/glad 4 | [submodule "lib/glfw"] 5 | path = lib/glfw 6 | url = https://github.com/glfw/glfw 7 | [submodule "lib/cglm"] 8 | path = lib/cglm 9 | url = https://github.com/recp/cglm 10 | [submodule "lib/incbin"] 11 | path = lib/incbin 12 | url = https://github.com/graphitemaster/incbin/ 13 | [submodule "lib/cimgui"] 14 | path = lib/cimgui 15 | url = https://github.com/cimgui/cimgui 16 | [submodule "lib/toolkit"] 17 | path = lib/toolkit 18 | url = https://github.com/nitrix/toolkit 19 | [submodule "docs/gltf-tutorials"] 20 | path = docs/gltf-tutorials 21 | url = https://github.com/KhronosGroup/glTF-Tutorials 22 | [submodule "docs/gltf"] 23 | path = docs/gltf 24 | url = https://github.com/KhronosGroup/glTF 25 | [submodule "docs/gl"] 26 | path = docs/gl 27 | url = https://github.com/BSVino/docs.gl 28 | -------------------------------------------------------------------------------- /include/camera.h: -------------------------------------------------------------------------------- 1 | #ifndef CAMERA_H 2 | #define CAMERA_H 3 | 4 | #include "cglm/cglm.h" 5 | 6 | struct camera { 7 | // 3rd person camera. 8 | 9 | // The `center` is the reference point that the camera always looks at. 10 | // The reference point can turn, and that makes the camera turn too (always staying in its back). 11 | vec3 center; 12 | float center_rotation; 13 | 14 | // The `eye` is the actual camera, AUTOMATICALLY situated in the world. 15 | vec3 eye; // Computed by camera_update(). 16 | float eye_around; // Additional rotation around the center. 17 | float eye_above; // Additional rotation above the center. 18 | float eye_distance; // Zoom lever from the center. 19 | 20 | mat4 view_matrix; // Computed by camera_update(). 21 | }; 22 | 23 | void camera_init(struct camera *camera); 24 | void camera_center_move_relative(struct camera *camera, float straight, float sideways); 25 | void camera_update(struct camera *camera); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /cmake/cimgui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | SET(IMGUI_STATIC true) 2 | FILE(GLOB 3 | cimgui_files 4 | "../../lib/cimgui/*.cpp" 5 | "../../lib/cimgui/imgui/*.cpp" 6 | "../../lib/cimgui/imgui/backends/imgui_impl_glfw.cpp" 7 | "../../lib/cimgui/imgui/backends/imgui_impl_opengl3.cpp" 8 | ) 9 | 10 | ADD_LIBRARY(cimgui ${cimgui_files}) 11 | 12 | TARGET_COMPILE_DEFINITIONS(cimgui PRIVATE IMGUI_IMPL_API=extern\ \"C\" IMGUI_IMPL_OPENGL_LOADER_GLAD) 13 | TARGET_INCLUDE_DIRECTORIES(cimgui PRIVATE ../../lib/cimgui/imgui) 14 | TARGET_LINK_LIBRARIES(cimgui PRIVATE stdc++) 15 | TARGET_INCLUDE_DIRECTORIES(cimgui PUBLIC ../../lib/cimgui/generator/output) 16 | TARGET_INCLUDE_DIRECTORIES(cimgui PUBLIC ../../lib/cimgui) 17 | TARGET_LINK_LIBRARIES(cimgui PRIVATE glfw glad) 18 | TARGET_COMPILE_DEFINITIONS(cimgui INTERFACE CIMGUI_DEFINE_ENUMS_AND_STRUCTS) 19 | 20 | IF(WIN32) 21 | TARGET_LINK_LIBRARIES(cimgui PRIVATE imm32) 22 | ENDIF() 23 | 24 | IF(APPLE) 25 | TARGET_LINK_LIBRARIES(cimgui PRIVATE "-framework CoreFoundation") 26 | ENDIF() -------------------------------------------------------------------------------- /include/client.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIENT_H 2 | #define CLIENT_H 3 | 4 | #include "glad/glad.h" 5 | #include "gltf.h" 6 | #include "incbin.h" 7 | #include "stb_image.h" 8 | #include "toolkit.h" 9 | 10 | #include "camera.h" 11 | #include "entity.h" 12 | #include "environment.h" 13 | #include "framebuffer.h" 14 | #include "gizmo.h" 15 | #include "light.h" 16 | #include "material.h" 17 | #include "mesh.h" 18 | #include "model.h" 19 | #include "modelmanager.h" 20 | #include "renderer.h" 21 | #include "scene.h" 22 | #include "shader.h" 23 | #include "texture.h" 24 | #include "ui.h" 25 | #include "utils.h" 26 | #include "window.h" 27 | 28 | enum direction { 29 | FORWARD = 1, 30 | BACKWARD = 2, 31 | LEFT = 4, 32 | RIGHT = 8 33 | }; 34 | 35 | struct client { 36 | struct window window; 37 | struct renderer renderer; 38 | struct camera camera; 39 | struct scene scene; 40 | struct ui ui; 41 | 42 | enum direction moving; 43 | }; 44 | 45 | extern struct client client; 46 | 47 | int client_run(void); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /include/renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERER_H 2 | #define RENDERER_H 3 | 4 | #include "scene.h" 5 | #include "ui.h" 6 | 7 | #define FPS_HISTORY_MAX_COUNT 20 8 | 9 | struct renderer { 10 | // Viewport. 11 | float viewport_width; 12 | float viewport_height; 13 | 14 | // Necessary for PBR. 15 | float exposure; 16 | 17 | // Projection matrix. 18 | float fov; // In degrees (gets converted to radians in a few places). 19 | float far_plane; 20 | float near_plane; 21 | mat4 projection_matrix; 22 | 23 | // Debugging features. 24 | bool wireframe; 25 | 26 | // Mouse picking. 27 | struct shader *plain_shader; 28 | uint32_t mousepicking_entity_id; 29 | }; 30 | 31 | void renderer_init(struct renderer *renderer); 32 | void renderer_fini(struct renderer *renderer); 33 | void renderer_render(struct renderer *renderer, const struct camera *camera, const struct scene *scene); 34 | void renderer_wireframe(struct renderer *renderer, bool enabled); 35 | void renderer_switch(const struct renderer *renderer); 36 | void renderer_update_projection_matrix(struct renderer *renderer); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /include/window.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOW_H 2 | #define WINDOW_H 3 | 4 | #include "GLFW/glfw3.h" 5 | #include 6 | 7 | struct window { 8 | GLFWwindow *glfw_window; 9 | unsigned int width, height; 10 | int samples; 11 | bool fullscreen; 12 | char *title; 13 | 14 | double last_time, now_time; 15 | 16 | double cursor_pos_x, cursor_pos_y; 17 | vec4 cursor_ray_origin, cursor_ray_direction; 18 | }; 19 | 20 | bool window_init(struct window *window, unsigned int width, unsigned int height, const char *title, bool fullscreen); 21 | void window_fini(struct window *window); 22 | void window_close(struct window *window, int force); 23 | bool window_closed(const struct window *window); 24 | void window_poll_events(struct window *window); 25 | double window_elapsed(const struct window *window); 26 | void window_framebuffer_size(const struct window *window, int *width, int *height); 27 | void window_fullscreen(struct window *window, bool fullscreen); 28 | bool window_extension_supported(const char *name); 29 | void window_refresh(struct window *window); 30 | void window_update_title(struct window *window, const char *title); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /include/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #define DEFAULT_TITLE "Layman Game Engine" 5 | #define VERSION "1.0.0" 6 | 7 | #ifndef M_PI 8 | #define M_PI 3.14159265358979323846 9 | #endif 10 | 11 | #ifndef M_PI_2 12 | #define M_PI_2 1.57079632679489661923 13 | #endif 14 | 15 | #define UNUSED(x) (void) (x) 16 | #define ARRAY_COUNT(x) (sizeof (x) / sizeof (x)[0]) 17 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 18 | 19 | #include "cglm/cglm.h" 20 | 21 | void utils_render_cube(void); 22 | void utils_render_line(vec3 from, vec3 to, mat4 transform, vec4 color); 23 | void utils_render_ngon(int n, float radius, mat4 transform, vec4 color); 24 | float utils_closest_distance_between_two_rays(vec3 r1_origin, vec3 r1_direction, vec3 r2_origin, vec3 r2_direction, float *d1, float *d2); 25 | float utils_closest_distance_between_ray_and_circle(vec3 ray_origin, vec3 ray_direction, vec3 circle_origin, vec3 circle_orientation, float circle_radius, vec3 closest); 26 | bool utils_ray_plane_intersection(vec3 plane_origin, vec3 plane_normal, vec3 ray_origin, vec3 ray_direction, float *t); 27 | struct entity *find_selected_entity(void); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /lib/gltf/LICENSE: -------------------------------------------------------------------------------- 1 | cgltf is distributed under MIT license: 2 | 3 | Copyright (c) 2018 Johannes Kuhlmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/entity.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | static uint32_t next_entity_id = 1; // FIXME: Re-use entity ids after some point? 4 | 5 | bool entity_init(struct entity *entity, const char *model_filepath) { 6 | entity->model = NULL; 7 | glm_vec3_zero(entity->translation); 8 | glm_quat_identity(entity->rotation); 9 | entity->scale = 1; 10 | entity->id = next_entity_id++; 11 | 12 | entity->model = modelmanager_load_model(model_filepath); 13 | if (!entity->model) { 14 | return false; 15 | } 16 | 17 | return true; 18 | } 19 | 20 | void entity_fini(struct entity *entity) { 21 | modelmanager_unload_model(entity->model); 22 | } 23 | 24 | void entity_id_as_color(uint32_t id, vec4 color) { 25 | int r = (id & 0x000000FF) >> 0; 26 | int g = (id & 0x0000FF00) >> 8; 27 | int b = (id & 0x00FF0000) >> 16; 28 | int a = (id & 0xFF000000) >> 24; 29 | 30 | color[0] = r / 255.0f; 31 | color[1] = g / 255.0f; 32 | color[2] = b / 255.0f; 33 | color[3] = a / 255.0f; 34 | } 35 | 36 | uint32_t entity_color_as_id(vec4 color) { 37 | int r = color[0] * 255.0f; 38 | int g = color[1] * 255.0f; 39 | int b = color[2] * 255.0f; 40 | int a = color[3] * 255.0f; 41 | 42 | return r + (g * 256) + (b * 256 * 256) + (a * 256 * 256 * 256); 43 | } 44 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Layman Game Engine (layman) 2 | 3 | A simple 3D engine for the layman. 4 | 5 | ![Screenshot](assets/screenshot.png) 6 | 7 | ## Overview 8 | 9 | - Portable on Windows, Linux and Mac. 10 | - No programming or special knowledge required. 11 | - Integrated all-in-one single executable (client, server and editor). 12 | 13 | ## Features 14 | 15 | - [Client features](docs/features.md#client-features) 16 | - [Server features](docs/features.md#server-features) 17 | - [Editor features](docs/features.md#editor-features) 18 | 19 | ## Usage 20 | 21 | Download the engine, launch it, hit the F1 key. 22 | 23 | ## Screenshots 24 | 25 | | Client example | Editor example | Server example | 26 | | ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- | 27 | | [![Client](assets/client_thumbnail.jpg)](assets/client.png) | [![Editor](assets/editor_thumbnail.jpg)](assets/editor.png) | [![Server](assets/server_thumbnail.jpg)](assets/server.png) | 28 | 29 | ## License 30 | 31 | This is free and unencumbered software released into the public domain. See the [UNLICENSE](UNLICENSE) file for more details. 32 | -------------------------------------------------------------------------------- /src/scene.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | #define ENTITIES_CAPACITY_STEP 16 4 | 5 | // Scenes can only grow in size. The idea being that if there was ever at some point X entities, 6 | // it's equally likely in the future to have just as many entities again. 7 | 8 | void scene_init(struct scene *scene) { 9 | scene->entities = NULL; 10 | scene->entity_count = 0; 11 | scene->entity_capacity = 0; 12 | 13 | scene->lights = NULL; 14 | scene->lights_count = 0; 15 | } 16 | 17 | void scene_fini(struct scene *scene) { 18 | free(scene->entities); 19 | free(scene->lights); 20 | } 21 | 22 | bool scene_add_entity(struct scene *scene, struct entity *entity) { 23 | bool full = scene->entity_count == scene->entity_capacity; 24 | 25 | if (full) { 26 | size_t new_capacity = scene->entity_capacity + ENTITIES_CAPACITY_STEP; 27 | 28 | struct entity **new_entities = realloc(scene->entities, new_capacity * sizeof *new_entities); 29 | if (!new_entities) { 30 | return false; 31 | } 32 | 33 | scene->entities = new_entities; 34 | scene->entity_capacity = new_capacity; 35 | } 36 | 37 | scene->entities[scene->entity_count] = entity; 38 | scene->entity_count++; 39 | 40 | return true; 41 | } 42 | 43 | bool scene_add_light(struct scene *scene, const struct light *light) { 44 | const struct light **new_lights = realloc(scene->lights, (scene->lights_count + 1) * sizeof *scene->lights); 45 | if (!new_lights) { 46 | return false; 47 | } 48 | 49 | new_lights[scene->lights_count] = light; 50 | scene->lights = new_lights; 51 | scene->lights_count++; 52 | 53 | return true; 54 | } 55 | -------------------------------------------------------------------------------- /src/camera.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | void camera_update(struct camera *camera) { 4 | // Calculate the new eye position based on where it's situated relative to the center. 5 | 6 | float horizontal_distance = camera->eye_distance * cos(camera->eye_above); 7 | float vertical_distance = camera->eye_distance * sin(camera->eye_above); 8 | 9 | float x = horizontal_distance * sin(camera->eye_around + camera->center_rotation); 10 | float y = vertical_distance; 11 | float z = horizontal_distance * cos(camera->eye_around + camera->center_rotation); 12 | 13 | camera->eye[0] = x; 14 | camera->eye[1] = y; 15 | camera->eye[2] = z; 16 | camera->eye[0] += camera->center[0]; 17 | camera->eye[1] += camera->center[1]; 18 | camera->eye[2] += camera->center[2]; 19 | 20 | // Re-calculate the view matrix. 21 | glm_lookat(camera->eye, camera->center, (vec3) { 0, 1, 0}, camera->view_matrix); 22 | } 23 | 24 | void camera_init(struct camera *camera) { 25 | glm_vec3_zero(camera->eye); 26 | glm_vec3_zero(camera->center); 27 | camera->eye_distance = 0.00001; 28 | camera_update(camera); 29 | } 30 | 31 | void camera_center_move_relative(struct camera *camera, float straight, float sideways) { 32 | // "Straight" (Forward/Backward). 33 | camera->center[0] += straight * sin(camera->center_rotation); 34 | camera->center[2] += straight * cos(camera->center_rotation); 35 | 36 | // "Sideways" (Left/Right). 37 | camera->center[0] += sideways * cos(camera->center_rotation); 38 | camera->center[2] += sideways * -sin(camera->center_rotation); 39 | 40 | // camera->translation[0] += x * sin(camera->center_rotation); 41 | camera_update(camera); 42 | } 43 | -------------------------------------------------------------------------------- /include/mesh.h: -------------------------------------------------------------------------------- 1 | #ifndef MESH_H 2 | #define MESH_H 3 | 4 | #include "glad/glad.h" 5 | #include "material.h" 6 | #include 7 | 8 | enum mesh_attribute { 9 | MESH_ATTRIBUTE_POSITION, 10 | MESH_ATTRIBUTE_UV, 11 | MESH_ATTRIBUTE_NORMAL, 12 | MESH_ATTRIBUTE_TANGENT, 13 | MESH_ATTRIBUTE_WEIGHTS, 14 | MESH_ATTRIBUTE_JOINTS, 15 | MESH_ATTRIBUTE_COLORS, 16 | }; 17 | 18 | struct mesh { 19 | GLuint vao; 20 | GLuint vbo_positions; 21 | GLuint vbo_normals; 22 | GLuint vbo_uvs; 23 | GLuint ebo_indices; 24 | GLuint vbo_tangents; 25 | GLuint vbo_weights; 26 | GLuint vbo_joints; 27 | GLuint vbo_colors; 28 | 29 | size_t indices_count; 30 | GLenum indices_type; 31 | 32 | struct shader *shader; 33 | struct material material; 34 | 35 | mat4 initial_transform; 36 | }; 37 | 38 | bool mesh_init(struct mesh *mesh); 39 | void mesh_fini(struct mesh *mesh); 40 | void mesh_provide_vertices(struct mesh *mesh, const float *data, size_t count, size_t stride); 41 | void mesh_provide_normals(struct mesh *mesh, const float *data, size_t count, size_t stride); 42 | void mesh_provide_uvs(struct mesh *mesh, const float *data, size_t count, size_t stride); 43 | void mesh_provide_indices(struct mesh *mesh, const void *data, size_t count, GLenum type); 44 | void mesh_provide_tangents(struct mesh *mesh, const float *data, size_t count, size_t stride); 45 | void mesh_provide_weights(struct mesh *mesh, const float *data, size_t count, size_t stride); 46 | void mesh_provide_joints(struct mesh *mesh, const float *data, size_t count, size_t stride); 47 | void mesh_provide_colors(struct mesh *mesh, const float *data, size_t count, size_t stride, int components); 48 | void mesh_switch(const struct mesh *mesh); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.9) 2 | PROJECT(layman) 3 | 4 | SET(C_STANDARD 11) 5 | 6 | ADD_SUBDIRECTORY(cmake/cimgui) 7 | ADD_SUBDIRECTORY(cmake/incbin) 8 | 9 | ADD_SUBDIRECTORY(lib/cglm) 10 | ADD_SUBDIRECTORY(lib/glad) 11 | ADD_SUBDIRECTORY(lib/glfw) 12 | ADD_SUBDIRECTORY(lib/gltf) 13 | ADD_SUBDIRECTORY(lib/toolkit) 14 | ADD_SUBDIRECTORY(lib/stb) 15 | 16 | ADD_EXECUTABLE( 17 | layman 18 | src/camera.c 19 | src/client.c 20 | src/entity.c 21 | src/environment.c 22 | src/framebuffer.c 23 | src/gizmo.c 24 | src/light.c 25 | src/main.c 26 | src/material.c 27 | src/mesh.c 28 | src/model.c 29 | src/modelmanager.c 30 | src/renderer.c 31 | src/scene.c 32 | src/server.c 33 | src/shader.c 34 | src/texture.c 35 | src/ui.c 36 | src/utils.c 37 | src/window.c 38 | ) 39 | 40 | TARGET_COMPILE_OPTIONS(layman PRIVATE -std=c11 -Wall -Wextra -static) 41 | TARGET_LINK_LIBRARIES(layman PRIVATE cglm glad glfw gltf stb_image cimgui incbin toolkit) 42 | TARGET_INCLUDE_DIRECTORIES(layman PRIVATE include) 43 | 44 | # This tells GLFW to not include OpenGL, we use Glad for that. 45 | TARGET_COMPILE_DEFINITIONS(layman PRIVATE GLFW_INCLUDE_NONE) 46 | 47 | # No prefix and snake case for incbin. 48 | TARGET_COMPILE_DEFINITIONS(layman PRIVATE INCBIN_PREFIX=\ ) 49 | TARGET_COMPILE_DEFINITIONS(layman PRIVATE INCBIN_STYLE=INCBIN_STYLE_SNAKE) 50 | 51 | TARGET_COMPILE_DEFINITIONS(layman PRIVATE "$<$:DEBUG>") 52 | TARGET_COMPILE_OPTIONS(layman PRIVATE "$<$:-O0;-g;-ggdb>") 53 | TARGET_COMPILE_OPTIONS(layman PRIVATE "$<$:-O3>") 54 | 55 | # Useful sometimes for debugging. 56 | # TARGET_COMPILE_OPTIONS(layman PRIVATE -fsanitize=undefined -fsanitize-undefined-trap-on-error) 57 | # TARGET_COMPILE_OPTIONS(layman PRIVATE -fsanitize=address) 58 | # TARGET_LINK_OPTIONS(layman PRIVATE -fsanitize=undefined) 59 | # TARGET_LINK_OPTIONS(layman PRIVATE -fsanitize=address) -------------------------------------------------------------------------------- /src/modelmanager.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | struct entry { 4 | char *filepath; 5 | struct model *model; // Has to stay a pointer for address stability (we hand off these pointers). 6 | size_t uses; 7 | }; 8 | 9 | struct modelmanager { 10 | struct entry *entries; 11 | size_t used; 12 | size_t capacity; 13 | } mm; 14 | 15 | struct model *modelmanager_load_model(const char *filepath) { 16 | // Ensure the entry doesn't already exist. 17 | for (size_t i = 0; i < mm.used; i++) { 18 | struct entry *entry = &mm.entries[i]; 19 | if (strcmp(entry->filepath, filepath) == 0) { 20 | entry->uses++; 21 | return entry->model; 22 | } 23 | } 24 | 25 | // Load the model. 26 | struct model *model = model_load(filepath); 27 | if (!model) { 28 | return false; 29 | } 30 | 31 | // Prepare storage for the new entry if there isn't enough space. 32 | if (mm.used == mm.capacity) { 33 | size_t new_capacity = mm.capacity + 1; 34 | 35 | void *new_entries = realloc(mm.entries, new_capacity * sizeof *mm.entries); 36 | if (!new_entries) { 37 | return NULL; 38 | } 39 | 40 | mm.capacity = new_capacity; 41 | mm.entries = new_entries; 42 | } 43 | 44 | // Store the model. 45 | struct entry *entry = &mm.entries[mm.used++]; 46 | 47 | entry->uses = 1; 48 | entry->model = model; 49 | entry->filepath = strdup(filepath); 50 | 51 | return model; 52 | } 53 | 54 | void modelmanager_unload_model(const struct model *model) { 55 | for (size_t i = 0; i < mm.used; i++) { 56 | struct entry *entry = &mm.entries[i]; 57 | if (entry->model == model) { 58 | entry->uses--; 59 | 60 | // Check if this was the last usage of this entry. If so, time to cleanup. 61 | if (entry->uses == 0) { 62 | // Replace the current entry with the last entry, unless we are the last entry. 63 | bool is_last_entry = i == mm.used - 1; 64 | if (!is_last_entry) { 65 | *entry = mm.entries[mm.used - 1]; 66 | } 67 | 68 | mm.used--; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/material.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | void material_init(struct material *material) { 4 | material->name = NULL; 5 | 6 | // Mettalic/roughness. 7 | glm_vec4_one(material->base_color_factor); 8 | material->base_color_texture = NULL; 9 | material->metallic_roughness_texture = NULL; 10 | material->metallic_factor = 1; 11 | material->roughness_factor = 1; 12 | 13 | // Other things. 14 | material->normal_texture = NULL; 15 | material->normal_scale = 1; 16 | material->occlusion_texture = NULL; 17 | material->occlusion_strength = 1; 18 | material->emissive_texture = NULL; 19 | glm_vec3_zero(material->emissive_factor); 20 | 21 | material->double_sided = false; 22 | } 23 | 24 | void material_fini(struct material *material) { 25 | free(material->name); 26 | 27 | if (material->base_color_texture) { 28 | texture_fini(material->base_color_texture); 29 | } 30 | 31 | if (material->metallic_roughness_texture) { 32 | texture_fini(material->metallic_roughness_texture); 33 | } 34 | 35 | if (material->normal_texture) { 36 | texture_fini(material->normal_texture); 37 | } 38 | 39 | if (material->occlusion_texture) { 40 | texture_fini(material->occlusion_texture); 41 | } 42 | 43 | if (material->emissive_texture) { 44 | texture_fini(material->emissive_texture); 45 | } 46 | } 47 | 48 | void material_switch(const struct material *material) { 49 | if (material->base_color_texture) { 50 | texture_switch(material->base_color_texture); 51 | } 52 | 53 | if (material->normal_texture) { 54 | texture_switch(material->normal_texture); 55 | } 56 | 57 | if (material->metallic_roughness_texture) { 58 | texture_switch(material->metallic_roughness_texture); 59 | } 60 | 61 | if (material->occlusion_texture) { 62 | texture_switch(material->occlusion_texture); 63 | } 64 | 65 | if (material->emissive_texture) { 66 | texture_switch(material->emissive_texture); 67 | } 68 | 69 | if (material->double_sided) { 70 | glDisable(GL_CULL_FACE); 71 | } else { 72 | glEnable(GL_CULL_FACE); 73 | glCullFace(GL_BACK); 74 | glFrontFace(GL_CCW); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /docs/features.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | ## Client features 4 | 5 | ### Currently 6 | 7 | - Physically Based Rendering (metallic/roughness and specular/glossiness workflows). 8 | - Tone mapping (Linear, Uncharted, Hejl Richard, ACES). 9 | - Image-Based Lighting (IBL). 10 | - Analytical lights (punctual, directional, spot). 11 | - Mipmapping and anisotropic filtering. 12 | - Multisample anti-aliasing (MSAA). 13 | - Supports glTF 2.0 file format. 14 | - Supports the KHR_materials_unlit extension. 15 | - Orbital/3rd person/free camera. 16 | - Skybox. 17 | 18 | ### Planned 19 | 20 | - Support the KHR_lights_punctual extension. 21 | - Support assets streaming/lazy loading. 22 | - Cutscenes. 23 | - Self updates. 24 | 25 | ## Server features 26 | 27 | ### Planned 28 | 29 | - Multiplayer support (authentication, playing, saving). 30 | - Distributed infrastructure, self-healing. 31 | - Multiple characters and concurrent connections per account. 32 | - Seamless infinite worlds. 33 | 34 | ## Editor features 35 | 36 | ### Currently 37 | 38 | - Mouse picking. 39 | - Translation/rotation/scale gizmo. 40 | - Debugging (inspecting entities, textures, wireframe, etc). 41 | 42 | ### Planned 43 | 44 | - World editor. 45 | - Shader editor (node-based system). 46 | - Script editor (event-based system). 47 | - Management of assets (models, sounds, etc). 48 | - Management of player-generated data. 49 | - ACL access list, with permissions. 50 | - Version control of changes and real-time in-game deployments. 51 | 52 | ## Other features 53 | 54 | See [ideas](docs/ideas.md) for a bucket list of ideas to look into. 55 | 56 | ### References 57 | 58 | - [Real Shading in Unreal Engine 4](http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf) by Epic Games. 59 | - [Physically Based Shading](http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf) by Disney 60 | - [Environment Maps](https://github.com/KhronosGroup/glTF-WebGL-PBR/#environment-maps) by Kronos. 61 | - [An Inexpensive BRDF Model for Physically based Rendering](https://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf) by Christophe Schlick. -------------------------------------------------------------------------------- /src/client.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | struct client client; 4 | 5 | bool setup(void) { 6 | if (!window_init(&client.window, 1280, 720, DEFAULT_TITLE, false)) { 7 | fprintf(stderr, "Unable to create the window\n"); 8 | return false; 9 | } 10 | 11 | renderer_init(&client.renderer); 12 | camera_init(&client.camera); 13 | scene_init(&client.scene); 14 | ui_init(&client.ui); 15 | client.moving = 0; 16 | 17 | client.camera.eye_distance = 3; 18 | camera_update(&client.camera); 19 | 20 | struct environment *pisa = malloc(sizeof *pisa); 21 | if (!environment_init_from_file(pisa, "assets/pisa.hdr")) { 22 | return false; 23 | } 24 | 25 | client.scene.environment = pisa; 26 | 27 | return true; 28 | } 29 | 30 | void cleanup(void) { 31 | environment_fini(client.scene.environment); 32 | 33 | ui_fini(&client.ui); 34 | scene_fini(&client.scene); 35 | window_fini(&client.window); 36 | renderer_fini(&client.renderer); 37 | } 38 | 39 | void main_loop(void) { 40 | while (!window_closed(&client.window)) { 41 | window_poll_events(&client.window); 42 | 43 | double elapsed = window_elapsed(&client.window); 44 | 45 | // Orbit camera. 46 | // Moving the camera position around the model, makes the skybox reflection wiggle nicely. 47 | 48 | float movement_speed = 10.0f; 49 | 50 | if ((client.moving & FORWARD) == FORWARD) { 51 | camera_center_move_relative(&client.camera, -1 * elapsed * movement_speed, 0); 52 | } 53 | 54 | if ((client.moving & BACKWARD) == BACKWARD) { 55 | camera_center_move_relative(&client.camera, elapsed * movement_speed, 0); 56 | } 57 | 58 | if ((client.moving & LEFT) == LEFT) { 59 | camera_center_move_relative(&client.camera, 0, -1 * elapsed * movement_speed); 60 | } 61 | 62 | if ((client.moving & RIGHT) == RIGHT) { 63 | camera_center_move_relative(&client.camera, 0, elapsed * movement_speed); 64 | } 65 | 66 | renderer_render(&client.renderer, &client.camera, &client.scene); 67 | ui_render(&client.ui); 68 | window_refresh(&client.window); 69 | } 70 | } 71 | 72 | int client_run(void) { 73 | if (!setup()) { 74 | cleanup(); 75 | return EXIT_FAILURE; 76 | } 77 | 78 | do { 79 | struct light light; 80 | light_init(&light, LIGHT_TYPE_DIRECTIONAL); 81 | light.position[2] = -3; 82 | 83 | // scene_add_light(&state.scene, &light); 84 | 85 | main_loop(); 86 | } while (false); 87 | 88 | cleanup(); 89 | 90 | return EXIT_SUCCESS; 91 | } 92 | -------------------------------------------------------------------------------- /include/texture.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXTURE_H 2 | #define TEXTURE_H 3 | 4 | #include "glad/glad.h" 5 | 6 | enum texture_kind { 7 | // PBR textures. 8 | TEXTURE_KIND_ALBEDO, 9 | TEXTURE_KIND_NORMAL, 10 | TEXTURE_KIND_METALLIC_ROUGHNESS, 11 | TEXTURE_KIND_OCCLUSION, 12 | TEXTURE_KIND_EMISSION, 13 | 14 | // IBL environment textures. 15 | TEXTURE_KIND_ENVIRONMENT_LAMBERTIAN, 16 | TEXTURE_KIND_ENVIRONMENT_LAMBERTIAN_LUT, 17 | TEXTURE_KIND_ENVIRONMENT_GGX, 18 | TEXTURE_KIND_ENVIRONMENT_GGX_LUT, 19 | TEXTURE_KIND_ENVIRONMENT_CHARLIE, 20 | TEXTURE_KIND_ENVIRONMENT_CHARLIE_LUT, 21 | 22 | // Other things. 23 | TEXTURE_KIND_IMAGE, 24 | TEXTURE_KIND_EQUIRECTANGULAR, 25 | TEXTURE_KIND_CUBEMAP, 26 | 27 | // Keep there. 28 | TEXTURE_KIND_COUNT, 29 | }; 30 | 31 | enum texture_type { 32 | TEXTURE_TYPE_UNSIGNED_BYTE, 33 | TEXTURE_TYPE_FLOAT 34 | }; 35 | 36 | enum texture_format { 37 | TEXTURE_FORMAT_RGB, 38 | TEXTURE_FORMAT_RGBA, 39 | }; 40 | 41 | enum texture_format_internal { 42 | TEXTURE_FORMAT_INTERNAL_RGB, 43 | TEXTURE_FORMAT_INTERNAL_RGB8, 44 | TEXTURE_FORMAT_INTERNAL_RGB16F, 45 | TEXTURE_FORMAT_INTERNAL_RGB32F, 46 | TEXTURE_FORMAT_INTERNAL_RGBA, 47 | TEXTURE_FORMAT_INTERNAL_RGBA8, 48 | TEXTURE_FORMAT_INTERNAL_RGBA16F, 49 | TEXTURE_FORMAT_INTERNAL_RGBA32F, 50 | }; 51 | 52 | struct texture { 53 | size_t width; 54 | size_t height; 55 | size_t levels; 56 | 57 | enum texture_kind kind; 58 | 59 | // Object stuff. 60 | GLuint gl_id; 61 | 62 | // Sampler stuff. 63 | GLenum gl_target; 64 | GLenum gl_unit; 65 | 66 | // Pixel data stuff. 67 | GLenum gl_type; 68 | GLenum gl_format; 69 | GLenum gl_internal_format; 70 | }; 71 | 72 | void texture_init(struct texture *texture, enum texture_kind kind, size_t width, size_t height, bool mipmapping, enum texture_type type, enum texture_format format, enum texture_format_internal format_internal); 73 | bool texture_init_from_file(struct texture *texture, enum texture_kind kind, const char *filepath); 74 | bool texture_init_from_memory(struct texture *texture, enum texture_kind kind, const unsigned char *data, size_t size); 75 | void texture_fini(struct texture *texture); 76 | 77 | void texture_replace_data(struct texture *texture, unsigned int level, unsigned int width, unsigned int height, const void *data); 78 | void texture_anisotropic_filtering(struct texture *texture, float anisotropy); 79 | void texture_switch(const struct texture *texture); 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /include/shader.h: -------------------------------------------------------------------------------- 1 | #ifndef SHADER_H 2 | #define SHADER_H 3 | 4 | #include "camera.h" 5 | #include "environment.h" 6 | #include "light.h" 7 | #include "material.h" 8 | 9 | struct shader { 10 | GLuint program_id; 11 | 12 | // Material uniforms. 13 | GLint uniform_base_color_factor; 14 | GLint uniform_base_color_sampler; 15 | GLint uniform_normal_sampler; 16 | GLint uniform_normal_scale; 17 | GLint uniform_metallic_roughness_sampler; 18 | GLint uniform_metallic_factor; 19 | GLint uniform_roughness_factor; 20 | GLint uniform_occlusion_sampler; 21 | GLint uniform_occlusion_strength; 22 | GLint uniform_emissive_sampler; 23 | GLint uniform_emissive_factor; 24 | 25 | // Environment IBL. 26 | GLint uniform_environment_mip_count; 27 | GLint uniform_environment_lambertian; 28 | GLint uniform_environment_ggx; 29 | GLint uniform_environment_ggx_lut; 30 | GLint uniform_environment_charlie; 31 | GLint uniform_environment_charlie_lut; 32 | 33 | // Camera uniforms. 34 | GLint uniform_camera; 35 | 36 | // Light uniforms. 37 | GLint uniform_lights_type[MAX_LIGHTS]; 38 | GLint uniform_lights_position[MAX_LIGHTS]; 39 | GLint uniform_lights_direction[MAX_LIGHTS]; 40 | GLint uniform_lights_color[MAX_LIGHTS]; 41 | GLint uniform_lights_intensity[MAX_LIGHTS]; 42 | 43 | // Model/View/Projection (MVP matrices). 44 | GLint uniform_view_projection_matrix; 45 | GLint uniform_model_matrix; 46 | GLint uniform_normal_matrix; 47 | GLint uniform_exposure; 48 | }; 49 | 50 | struct shader_options { 51 | // Attributes. 52 | bool has_normals; 53 | bool has_uv_set1; 54 | bool has_tangents; 55 | bool has_weight_set1; 56 | bool has_joint_set1; 57 | bool has_color_vec3; 58 | bool has_color_vec4; 59 | 60 | // Textures. 61 | bool has_base_color_map; 62 | bool has_normal_map; 63 | bool has_occlusion_map; 64 | bool has_emissive_map; 65 | bool has_metallic_roughness_map; 66 | 67 | // FIXME: Enum for the workflow? 68 | bool material_metallicroughness; 69 | bool material_specularglossiness; 70 | 71 | // Lighting. 72 | bool material_unlit; 73 | bool use_hdr; 74 | bool use_ibl; 75 | bool use_punctual; 76 | int light_count; 77 | 78 | // Skinning. 79 | bool use_skinning; 80 | int joint_count; 81 | 82 | // Tone-mapping. 83 | // FIXME: Enum for this? 84 | bool tonemap_uncharted; 85 | bool tonemap_hejlrichard; 86 | bool tonemap_aces; 87 | 88 | // Debugging. 89 | // FIXME: Enum for this? 90 | bool debug_output; 91 | bool debug_basecolor; 92 | bool debug_normal; 93 | bool debug_tangent; 94 | bool debug_metallic; 95 | bool debug_roughness; 96 | bool debug_occlusion; 97 | bool debug_fdiffuse; 98 | bool debug_fspecular; 99 | bool debug_femissive; 100 | }; 101 | 102 | struct shader *shader_load_from_files(const struct shader_options *options, const char *vertex_filepath, const char *fragment_filepath, const char *compute_filepath); 103 | struct shader *shader_load_from_memory(const struct shader_options *options, const unsigned char *vertex_content, size_t vertex_length, const unsigned char *fragment_content, size_t fragment_length, const unsigned char *compute_content, size_t compute_length); 104 | void shader_destroy(struct shader *shader); 105 | 106 | void shader_bind_uniform_material(const struct shader *shader, const struct material *material); 107 | void shader_bind_uniform_camera(const struct shader *shader, const struct camera *camera); 108 | void shader_bind_uniform_lights(const struct shader *shader, const struct light **lights, size_t count); 109 | void shader_bind_uniform_environment(const struct shader *shader, const struct environment *environment); 110 | void shader_bind_uniform_mvp(const struct shader *shader, mat4 view_projection_matrix, mat4 model_matrix, float exposure); 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /src/gizmo.c: -------------------------------------------------------------------------------- 1 | #include "cimgui.h" 2 | #include "client.h" 3 | 4 | void gizmo_init(struct gizmo *gizmo) { 5 | gizmo->mode = GIZMO_MODE_NONE; 6 | } 7 | 8 | void gizmo_fini(struct gizmo *gizmo) { 9 | UNUSED(gizmo); 10 | } 11 | 12 | static void gizmo_render_translation(struct gizmo *gizmo) { 13 | UNUSED(gizmo); 14 | 15 | static bool dragging = false; 16 | static size_t drag_axis = 0; 17 | static vec3 drag_start; 18 | 19 | struct entity *entity = find_selected_entity(); 20 | 21 | mat4 model_matrix = GLM_MAT4_IDENTITY_INIT; 22 | glm_translate(model_matrix, entity->translation); 23 | 24 | bool proximity_already = false; 25 | 26 | for (size_t axis = 0; axis < 3; axis++) { 27 | float t1, t2; 28 | 29 | vec3 r1_origin, r1_direction, r2_origin, r2_direction; 30 | 31 | glm_vec4_copy3(client.window.cursor_ray_origin, r1_origin); 32 | glm_vec4_copy3(client.window.cursor_ray_direction, r1_direction); 33 | 34 | glm_vec3_copy(entity->translation, r2_origin); 35 | 36 | glm_vec3_zero(r2_direction); 37 | r2_direction[axis] = 1; 38 | 39 | float distance = utils_closest_distance_between_two_rays(r1_origin, r1_direction, r2_origin, r2_direction, &t1, &t2); 40 | 41 | vec3 line_start = GLM_VEC3_ZERO_INIT; 42 | vec3 line_end; 43 | glm_vec3_copy(r2_direction, line_end); // The line end, happens to be the same as the ray direction. 44 | vec4 color; 45 | glm_vec3_copy(r2_direction, color); // The color of the line, happens to be the same as the ray direction. 46 | 47 | // Closest axis intersect 48 | vec3 intersect; 49 | glm_vec3_copy(r2_origin, intersect); 50 | glm_vec3_muladds(r2_direction, t2, intersect); 51 | 52 | // Proximity. 53 | bool on_axis = distance < 0.05 && t2 >= 0 && t2 <= 1; 54 | bool proximity = on_axis && !dragging && !client.ui.ig_io->WantCaptureMouse && !proximity_already; 55 | 56 | if (proximity) { 57 | proximity_already = true; 58 | } 59 | 60 | // Mouse dragging. 61 | if (proximity && igIsMouseDragging(ImGuiMouseButton_Left, 0)) { 62 | if (!dragging) { 63 | dragging = true; 64 | drag_axis = axis; 65 | glm_vec3_copy(intersect, drag_start); 66 | } 67 | } 68 | 69 | if (igIsMouseReleased(ImGuiMouseButton_Left)) { 70 | dragging = false; 71 | } 72 | 73 | bool dragging_current_axis = dragging && axis == drag_axis; 74 | 75 | // Coloring the axis yellow. 76 | if (proximity || dragging_current_axis) { 77 | glm_vec4_copy((vec4) { 1, 1, 0, 1}, color); // Yellow. 78 | } 79 | 80 | // The actual mutation by the delta. 81 | if (dragging_current_axis) { 82 | vec3 delta; 83 | glm_vec3_sub(intersect, drag_start, delta); 84 | glm_vec3_add(entity->translation, delta, entity->translation); 85 | glm_vec3_copy(intersect, drag_start); 86 | 87 | // Massive guide line. 88 | vec3 guide_line_start, guide_line_end; 89 | glm_vec3_zero(guide_line_start); 90 | glm_vec3_zero(guide_line_end); 91 | glm_vec3_muladds(r2_direction, 1000, guide_line_end); 92 | utils_render_line(guide_line_start, guide_line_end, model_matrix, (vec4) { 0.5, 0.5, 0.5, 1}); 93 | glm_vec3_zero(guide_line_end); 94 | glm_vec3_muladds(r2_direction, -1000, guide_line_end); 95 | utils_render_line(guide_line_start, guide_line_end, model_matrix, (vec4) { 0.5, 0.5, 0.5, 1}); 96 | } 97 | 98 | // Render axis handle. 99 | utils_render_line(line_start, line_end, model_matrix, color); 100 | } 101 | 102 | if (dragging) { 103 | client.ui.ig_io->WantCaptureMouse = true; 104 | } 105 | } 106 | 107 | static void gizmo_render_rotation(struct gizmo *gizmo) { 108 | UNUSED(gizmo); 109 | 110 | struct entity *entity = find_selected_entity(); 111 | 112 | for (size_t axis = 0; axis < 3; axis++) { 113 | vec3 circle_origin = {0}; 114 | glm_vec3_copy(entity->translation, circle_origin); 115 | 116 | vec3 circle_orientation = {0}; 117 | 118 | circle_orientation[axis] = 1; 119 | 120 | float circle_radius = 1; 121 | 122 | vec3 closest = {0}; 123 | 124 | mat4 transform = GLM_MAT4_IDENTITY_INIT; 125 | 126 | glm_translate(transform, entity->translation); 127 | glm_quat_rotate(transform, entity->rotation, transform); 128 | switch (axis) { 129 | case 0: 130 | glm_rotate_y(transform, circle_orientation[0] * M_PI_2, transform); 131 | break; 132 | case 1: glm_rotate_x(transform, circle_orientation[1] * M_PI_2, transform); break; 133 | case 2: glm_rotate_z(transform, circle_orientation[2] * M_PI_2, transform); break; 134 | } 135 | 136 | vec4 color = {circle_orientation[0], circle_orientation[1], circle_orientation[2], 1}; 137 | 138 | mat4 t = GLM_MAT4_IDENTITY_INIT; 139 | glm_quat_rotate(t, entity->rotation, t); 140 | glm_vec3_rotate_m4(t, circle_orientation, circle_orientation); 141 | 142 | float distance = utils_closest_distance_between_ray_and_circle( 143 | client.window.cursor_ray_origin, 144 | client.window.cursor_ray_direction, 145 | circle_origin, 146 | circle_orientation, 147 | circle_radius, 148 | closest); 149 | 150 | bool proximity = distance < 0.1 && distance >= 0; 151 | 152 | if (proximity) { 153 | glm_vec4_copy((vec4) { 1, 1, 0, 1}, color); 154 | } 155 | 156 | utils_render_ngon(64, circle_radius, transform, color); 157 | } 158 | } 159 | 160 | void gizmo_render(struct gizmo *gizmo) { 161 | // Don't draw any gizmo if no entity is selected. 162 | if (!client.ui.selected_entity_id) { 163 | return; 164 | } 165 | 166 | // Don't pay attention to the depth buffer. 167 | glDisable(GL_DEPTH_TEST); 168 | 169 | // Don't mess up the depth buffer either. 170 | glDepthMask(false); 171 | 172 | switch (gizmo->mode) { 173 | case GIZMO_MODE_TRANSLATION: gizmo_render_translation(gizmo); return; 174 | case GIZMO_MODE_ROTATION: gizmo_render_rotation(gizmo); return; 175 | default: return; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/mesh.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | bool mesh_init(struct mesh *mesh) { 4 | mesh->vao = 0; 5 | mesh->vbo_positions = 0; 6 | mesh->vbo_normals = 0; 7 | mesh->vbo_uvs = 0; 8 | mesh->ebo_indices = 0; 9 | mesh->vbo_tangents = 0; 10 | mesh->vbo_weights = 0; 11 | mesh->vbo_joints = 0; 12 | mesh->vbo_colors = 0; 13 | 14 | mesh->indices_type = 0; 15 | 16 | mesh->shader = NULL; 17 | 18 | glm_mat4_identity(mesh->initial_transform); 19 | 20 | // FIXME: Currently, each mesh has its own material, but I believe glTF is able to share common materials. 21 | // If so, there should probably be a material manager of some sort. 22 | material_init(&mesh->material); 23 | 24 | // Vertex Array Object (VAO). 25 | // This contains multiple buffers and is the preferred way to change from one group of buffers to another when rendering models. 26 | glGenVertexArrays(1, &mesh->vao); 27 | 28 | return true; 29 | } 30 | 31 | void mesh_provide_vertices(struct mesh *mesh, const float *data, size_t count, size_t stride) { 32 | mesh_switch(mesh); 33 | 34 | glGenBuffers(1, &mesh->vbo_positions); 35 | glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo_positions); 36 | glBufferData(GL_ARRAY_BUFFER, count * 3 * sizeof (float), data, GL_STATIC_DRAW); 37 | glVertexAttribPointer(MESH_ATTRIBUTE_POSITION, 3, GL_FLOAT, false, stride, 0); 38 | glEnableVertexAttribArray(MESH_ATTRIBUTE_POSITION); 39 | } 40 | 41 | void mesh_provide_normals(struct mesh *mesh, const float *data, size_t count, size_t stride) { 42 | mesh_switch(mesh); 43 | 44 | glGenBuffers(1, &mesh->vbo_normals); 45 | glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo_normals); 46 | glBufferData(GL_ARRAY_BUFFER, count * 3 * sizeof (float), data, GL_STATIC_DRAW); 47 | glVertexAttribPointer(MESH_ATTRIBUTE_NORMAL, 3, GL_FLOAT, false, stride, 0); 48 | glEnableVertexAttribArray(MESH_ATTRIBUTE_NORMAL); 49 | } 50 | 51 | void mesh_provide_uvs(struct mesh *mesh, const float *data, size_t count, size_t stride) { 52 | mesh_switch(mesh); 53 | 54 | glGenBuffers(1, &mesh->vbo_uvs); 55 | glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo_uvs); 56 | glBufferData(GL_ARRAY_BUFFER, count * 2 * sizeof (float), data, GL_STATIC_DRAW); 57 | glVertexAttribPointer(MESH_ATTRIBUTE_UV, 2, GL_FLOAT, false, stride, 0); 58 | glEnableVertexAttribArray(MESH_ATTRIBUTE_UV); 59 | } 60 | 61 | void mesh_provide_indices(struct mesh *mesh, const void *data, size_t count, GLenum type) { 62 | mesh_switch(mesh); 63 | 64 | // Most buffers get assigned to a shader attribute (aka shader input variables) using glVertexAttribPointer. 65 | // The one exception is this indice buffer. Instead, we pass the count explicitly during the render call (glDrawElements). 66 | 67 | size_t size; 68 | switch (type) { 69 | case GL_BYTE: size = 1; break; 70 | case GL_UNSIGNED_BYTE: size = 1; break; 71 | case GL_SHORT: size = sizeof (short); break; 72 | case GL_UNSIGNED_SHORT: size = sizeof (unsigned short); break; 73 | case GL_UNSIGNED_INT: size = sizeof (unsigned int); break; 74 | default: 75 | fprintf(stderr, "Unsupported type for indices\n"); 76 | return; 77 | } 78 | 79 | glGenBuffers(1, &mesh->ebo_indices); 80 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->ebo_indices); 81 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * size, data, GL_STATIC_DRAW); 82 | mesh->indices_count = count; 83 | } 84 | 85 | void mesh_provide_tangents(struct mesh *mesh, const float *data, size_t count, size_t stride) { 86 | mesh_switch(mesh); 87 | 88 | glGenBuffers(1, &mesh->vbo_tangents); 89 | glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo_tangents); 90 | glBufferData(GL_ARRAY_BUFFER, count * 4 * sizeof (float), data, GL_STATIC_DRAW); 91 | glVertexAttribPointer(MESH_ATTRIBUTE_TANGENT, 4, GL_FLOAT, false, stride, 0); 92 | glEnableVertexAttribArray(MESH_ATTRIBUTE_TANGENT); 93 | } 94 | 95 | void mesh_provide_weights(struct mesh *mesh, const float *data, size_t count, size_t stride) { 96 | mesh_switch(mesh); 97 | 98 | glGenBuffers(1, &mesh->vbo_weights); 99 | glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo_weights); 100 | glBufferData(GL_ARRAY_BUFFER, count * 4 * sizeof (float), data, GL_STATIC_DRAW); 101 | glVertexAttribPointer(MESH_ATTRIBUTE_WEIGHTS, 4, GL_FLOAT, false, stride, 0); 102 | glEnableVertexAttribArray(MESH_ATTRIBUTE_WEIGHTS); 103 | } 104 | 105 | void mesh_provide_joints(struct mesh *mesh, const float *data, size_t count, size_t stride) { 106 | mesh_switch(mesh); 107 | 108 | glGenBuffers(1, &mesh->vbo_joints); 109 | glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo_joints); 110 | glBufferData(GL_ARRAY_BUFFER, count * 4 * sizeof (float), data, GL_STATIC_DRAW); 111 | glVertexAttribPointer(MESH_ATTRIBUTE_JOINTS, 4, GL_FLOAT, false, stride, 0); 112 | glEnableVertexAttribArray(MESH_ATTRIBUTE_JOINTS); 113 | } 114 | 115 | void mesh_provide_colors(struct mesh *mesh, const float *data, size_t count, size_t stride, int components) { 116 | mesh_switch(mesh); 117 | 118 | glGenBuffers(1, &mesh->vbo_colors); 119 | glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo_colors); 120 | glBufferData(GL_ARRAY_BUFFER, count * components * sizeof (float), data, GL_STATIC_DRAW); 121 | glVertexAttribPointer(MESH_ATTRIBUTE_COLORS, components, GL_FLOAT, false, stride, 0); 122 | glEnableVertexAttribArray(MESH_ATTRIBUTE_COLORS); 123 | } 124 | 125 | void mesh_switch(const struct mesh *mesh) { 126 | glBindVertexArray(mesh->vao); 127 | material_switch(&mesh->material); 128 | } 129 | 130 | void mesh_fini(struct mesh *mesh) { 131 | material_fini(&mesh->material); 132 | 133 | shader_destroy(mesh->shader); 134 | 135 | glDeleteBuffers(1, &mesh->vbo_positions); 136 | glDeleteBuffers(1, &mesh->ebo_indices); 137 | 138 | if (mesh->vbo_normals) { 139 | glDeleteBuffers(1, &mesh->vbo_normals); 140 | } 141 | 142 | if (mesh->vbo_normals) { 143 | glDeleteBuffers(1, &mesh->vbo_uvs); 144 | } 145 | 146 | if (mesh->vbo_tangents) { 147 | glDeleteBuffers(1, &mesh->vbo_tangents); 148 | } 149 | 150 | if (mesh->vbo_weights) { 151 | glDeleteBuffers(1, &mesh->vbo_weights); 152 | } 153 | 154 | if (mesh->vbo_joints) { 155 | glDeleteBuffers(1, &mesh->vbo_joints); 156 | } 157 | 158 | if (mesh->vbo_colors) { 159 | glDeleteBuffers(1, &mesh->vbo_colors); 160 | } 161 | 162 | glDeleteVertexArrays(1, &mesh->vao); 163 | } 164 | -------------------------------------------------------------------------------- /shaders/plain/main.vert: -------------------------------------------------------------------------------- 1 | // ===================================================================================================================== 2 | // ANIMATION 3 | // ===================================================================================================================== 4 | 5 | #ifdef HAS_TARGET_POSITION0 6 | in vec3 a_Target_Position0; 7 | #endif 8 | 9 | #ifdef HAS_TARGET_POSITION1 10 | in vec3 a_Target_Position1; 11 | #endif 12 | 13 | #ifdef HAS_TARGET_POSITION2 14 | in vec3 a_Target_Position2; 15 | #endif 16 | 17 | #ifdef HAS_TARGET_POSITION3 18 | in vec3 a_Target_Position3; 19 | #endif 20 | 21 | #ifdef HAS_TARGET_POSITION4 22 | in vec3 a_Target_Position4; 23 | #endif 24 | 25 | #ifdef HAS_TARGET_POSITION5 26 | in vec3 a_Target_Position5; 27 | #endif 28 | 29 | #ifdef HAS_TARGET_POSITION6 30 | in vec3 a_Target_Position6; 31 | #endif 32 | 33 | #ifdef HAS_TARGET_POSITION7 34 | in vec3 a_Target_Position7; 35 | #endif 36 | 37 | #ifdef HAS_TARGET_NORMAL0 38 | in vec3 a_Target_Normal0; 39 | #endif 40 | 41 | #ifdef HAS_TARGET_NORMAL1 42 | in vec3 a_Target_Normal1; 43 | #endif 44 | 45 | #ifdef HAS_TARGET_NORMAL2 46 | in vec3 a_Target_Normal2; 47 | #endif 48 | 49 | #ifdef HAS_TARGET_NORMAL3 50 | in vec3 a_Target_Normal3; 51 | #endif 52 | 53 | #ifdef HAS_TARGET_TANGENT0 54 | in vec3 a_Target_Tangent0; 55 | #endif 56 | 57 | #ifdef HAS_TARGET_TANGENT1 58 | in vec3 a_Target_Tangent1; 59 | #endif 60 | 61 | #ifdef HAS_TARGET_TANGENT2 62 | in vec3 a_Target_Tangent2; 63 | #endif 64 | 65 | #ifdef HAS_TARGET_TANGENT3 66 | in vec3 a_Target_Tangent3; 67 | #endif 68 | 69 | #ifdef USE_MORPHING 70 | uniform float u_morphWeights[WEIGHT_COUNT]; 71 | #endif 72 | 73 | #ifdef HAS_JOINT_SET1 74 | in vec4 a_Joint1; 75 | #endif 76 | 77 | #ifdef HAS_JOINT_SET2 78 | in vec4 a_Joint2; 79 | #endif 80 | 81 | #ifdef HAS_WEIGHT_SET1 82 | in vec4 a_Weight1; 83 | #endif 84 | 85 | #ifdef HAS_WEIGHT_SET2 86 | in vec4 a_Weight2; 87 | #endif 88 | 89 | #ifdef USE_SKINNING 90 | uniform mat4 u_jointMatrix[JOINT_COUNT]; 91 | uniform mat4 u_jointNormalMatrix[JOINT_COUNT]; 92 | #endif 93 | 94 | #ifdef USE_SKINNING 95 | mat4 getSkinningMatrix() 96 | { 97 | mat4 skin = mat4(0); 98 | 99 | #if defined(HAS_WEIGHT_SET1) && defined(HAS_JOINT_SET1) 100 | skin += 101 | a_Weight1.x * u_jointMatrix[int(a_Joint1.x)] + 102 | a_Weight1.y * u_jointMatrix[int(a_Joint1.y)] + 103 | a_Weight1.z * u_jointMatrix[int(a_Joint1.z)] + 104 | a_Weight1.w * u_jointMatrix[int(a_Joint1.w)]; 105 | #endif 106 | 107 | #if defined(HAS_WEIGHT_SET2) && defined(HAS_JOINT_SET2) 108 | skin += 109 | a_Weight2.x * u_jointMatrix[int(a_Joint2.x)] + 110 | a_Weight2.y * u_jointMatrix[int(a_Joint2.y)] + 111 | a_Weight2.z * u_jointMatrix[int(a_Joint2.z)] + 112 | a_Weight2.w * u_jointMatrix[int(a_Joint2.w)]; 113 | #endif 114 | 115 | return skin; 116 | } 117 | 118 | mat4 getSkinningNormalMatrix() 119 | { 120 | mat4 skin = mat4(0); 121 | 122 | #if defined(HAS_WEIGHT_SET1) && defined(HAS_JOINT_SET1) 123 | skin += 124 | a_Weight1.x * u_jointNormalMatrix[int(a_Joint1.x)] + 125 | a_Weight1.y * u_jointNormalMatrix[int(a_Joint1.y)] + 126 | a_Weight1.z * u_jointNormalMatrix[int(a_Joint1.z)] + 127 | a_Weight1.w * u_jointNormalMatrix[int(a_Joint1.w)]; 128 | #endif 129 | 130 | #if defined(HAS_WEIGHT_SET2) && defined(HAS_JOINT_SET2) 131 | skin += 132 | a_Weight2.x * u_jointNormalMatrix[int(a_Joint2.x)] + 133 | a_Weight2.y * u_jointNormalMatrix[int(a_Joint2.y)] + 134 | a_Weight2.z * u_jointNormalMatrix[int(a_Joint2.z)] + 135 | a_Weight2.w * u_jointNormalMatrix[int(a_Joint2.w)]; 136 | #endif 137 | 138 | return skin; 139 | } 140 | #endif // !USE_SKINNING 141 | 142 | #ifdef USE_MORPHING 143 | vec4 getTargetPosition() 144 | { 145 | vec4 pos = vec4(0); 146 | 147 | #ifdef HAS_TARGET_POSITION0 148 | pos.xyz += u_morphWeights[0] * a_Target_Position0; 149 | #endif 150 | 151 | #ifdef HAS_TARGET_POSITION1 152 | pos.xyz += u_morphWeights[1] * a_Target_Position1; 153 | #endif 154 | 155 | #ifdef HAS_TARGET_POSITION2 156 | pos.xyz += u_morphWeights[2] * a_Target_Position2; 157 | #endif 158 | 159 | #ifdef HAS_TARGET_POSITION3 160 | pos.xyz += u_morphWeights[3] * a_Target_Position3; 161 | #endif 162 | 163 | #ifdef HAS_TARGET_POSITION4 164 | pos.xyz += u_morphWeights[4] * a_Target_Position4; 165 | #endif 166 | 167 | return pos; 168 | } 169 | 170 | vec3 getTargetNormal() 171 | { 172 | vec3 normal = vec3(0); 173 | 174 | #ifdef HAS_TARGET_NORMAL0 175 | normal += u_morphWeights[0] * a_Target_Normal0; 176 | #endif 177 | 178 | #ifdef HAS_TARGET_NORMAL1 179 | normal += u_morphWeights[1] * a_Target_Normal1; 180 | #endif 181 | 182 | #ifdef HAS_TARGET_NORMAL2 183 | normal += u_morphWeights[2] * a_Target_Normal2; 184 | #endif 185 | 186 | #ifdef HAS_TARGET_NORMAL3 187 | normal += u_morphWeights[3] * a_Target_Normal3; 188 | #endif 189 | 190 | #ifdef HAS_TARGET_NORMAL4 191 | normal += u_morphWeights[4] * a_Target_Normal4; 192 | #endif 193 | 194 | return normal; 195 | } 196 | 197 | vec3 getTargetTangent() 198 | { 199 | vec3 tangent = vec3(0); 200 | 201 | #ifdef HAS_TARGET_TANGENT0 202 | tangent += u_morphWeights[0] * a_Target_Tangent0; 203 | #endif 204 | 205 | #ifdef HAS_TARGET_TANGENT1 206 | tangent += u_morphWeights[1] * a_Target_Tangent1; 207 | #endif 208 | 209 | #ifdef HAS_TARGET_TANGENT2 210 | tangent += u_morphWeights[2] * a_Target_Tangent2; 211 | #endif 212 | 213 | #ifdef HAS_TARGET_TANGENT3 214 | tangent += u_morphWeights[3] * a_Target_Tangent3; 215 | #endif 216 | 217 | #ifdef HAS_TARGET_TANGENT4 218 | tangent += u_morphWeights[4] * a_Target_Tangent4; 219 | #endif 220 | 221 | return tangent; 222 | } 223 | 224 | #endif // !USE_MORPHING 225 | 226 | // ===================================================================================================================== 227 | // MAIN 228 | // ===================================================================================================================== 229 | 230 | in vec3 a_Position; 231 | 232 | uniform mat4 u_ViewProjectionMatrix; 233 | uniform mat4 u_ModelMatrix; 234 | 235 | vec4 getPosition() { 236 | vec4 pos = vec4(a_Position, 1.0); 237 | 238 | #ifdef USE_MORPHING 239 | pos += getTargetPosition(); 240 | #endif 241 | 242 | #ifdef USE_SKINNING 243 | pos = getSkinningMatrix() * pos; 244 | #endif 245 | 246 | return pos; 247 | } 248 | 249 | void main() { 250 | gl_Position = u_ViewProjectionMatrix * u_ModelMatrix * getPosition(); 251 | } -------------------------------------------------------------------------------- /.uncrustify: -------------------------------------------------------------------------------- 1 | tok_split_gte=false 2 | utf8_byte=false 3 | utf8_force=false 4 | indent_cmt_with_tabs=false 5 | indent_align_string=false 6 | indent_braces=false 7 | indent_braces_no_func=false 8 | indent_braces_no_class=false 9 | indent_braces_no_struct=false 10 | indent_brace_parent=false 11 | indent_namespace=false 12 | indent_extern=false 13 | indent_class=false 14 | indent_class_colon=false 15 | indent_else_if=false 16 | indent_var_def_cont=false 17 | indent_func_call_param=true 18 | indent_func_def_param=true 19 | indent_func_proto_param=true 20 | indent_func_class_param=false 21 | indent_func_ctor_var_param=false 22 | indent_template_param=false 23 | indent_func_param_double=false 24 | indent_relative_single_line_comments=false 25 | indent_col1_comment=false 26 | indent_access_spec_body=false 27 | indent_paren_nl=false 28 | indent_comma_paren=false 29 | indent_bool_paren=false 30 | indent_first_bool_expr=false 31 | indent_square_nl=false 32 | indent_preserve_sql=false 33 | indent_align_assign=false 34 | sp_balance_nested_parens=false 35 | align_keep_tabs=false 36 | align_with_tabs=false 37 | align_on_tabstop=false 38 | align_number_left=false 39 | align_func_params=false 40 | align_same_func_call_params=false 41 | align_var_def_colon=false 42 | align_var_def_attribute=false 43 | align_var_def_inline=false 44 | align_right_cmt_mix=false 45 | align_on_operator=false 46 | align_mix_var_proto=false 47 | align_single_line_func=false 48 | align_single_line_brace=false 49 | align_nl_cont=false 50 | align_left_shift=false 51 | align_oc_decl_colon=false 52 | nl_collapse_empty_body=true 53 | nl_assign_leave_one_liners=true 54 | nl_class_leave_one_liners=false 55 | nl_enum_leave_one_liners=false 56 | nl_getset_leave_one_liners=false 57 | nl_func_leave_one_liners=true 58 | nl_if_leave_one_liners=false 59 | nl_multi_line_cond=false 60 | nl_multi_line_define=false 61 | nl_before_case=false 62 | nl_after_case=false 63 | nl_after_return=false 64 | nl_after_semicolon=false 65 | nl_after_brace_open=false 66 | nl_after_brace_open_cmt=false 67 | nl_after_vbrace_open=false 68 | nl_after_vbrace_open_empty=false 69 | nl_after_brace_close=false 70 | nl_after_vbrace_close=true 71 | nl_define_macro=false 72 | nl_squeeze_ifdef=false 73 | nl_ds_struct_enum_cmt=false 74 | nl_ds_struct_enum_close_brace=false 75 | nl_create_if_one_liner=true 76 | nl_create_for_one_liner=true 77 | nl_create_while_one_liner=true 78 | ls_for_split_full=false 79 | ls_func_split_full=false 80 | nl_after_multiline_comment=false 81 | eat_blanks_after_open_brace=true 82 | eat_blanks_before_close_brace=true 83 | mod_full_brace_if_chain=false 84 | mod_pawn_semicolon=false 85 | mod_full_paren_if_bool=false 86 | mod_remove_extra_semicolon=true 87 | mod_sort_import=false 88 | mod_sort_using=false 89 | mod_sort_include=true 90 | mod_move_case_break=false 91 | mod_remove_empty_return=true 92 | cmt_indent_multi=true 93 | cmt_c_group=false 94 | cmt_c_nl_start=false 95 | cmt_c_nl_end=false 96 | cmt_cpp_group=false 97 | cmt_cpp_nl_start=false 98 | cmt_cpp_nl_end=false 99 | cmt_cpp_to_c=false 100 | cmt_star_cont=false 101 | cmt_multi_check_last=true 102 | cmt_insert_before_preproc=false 103 | pp_indent_at_level=true 104 | pp_region_indent_code=false 105 | pp_if_indent_code=false 106 | pp_define_at_level=true 107 | input_tab_size=8 108 | output_tab_size=8 109 | indent_switch_case=4 110 | indent_label=1 111 | indent_paren_close=2 112 | sp_num_before_tr_emb_cmt=1 113 | nl_end_of_file_min=1 114 | nl_max=2 115 | nl_after_func_proto=0 116 | nl_after_func_proto_group=1 117 | nl_after_func_body=2 118 | nl_before_block_comment=1 119 | pp_space_count=4 120 | indent_with_tabs=1 121 | sp_arith=force 122 | sp_assign=force 123 | sp_assign_default=force 124 | sp_before_assign=force 125 | sp_after_assign=force 126 | sp_enum_assign=force 127 | sp_pp_concat=force 128 | sp_pp_stringify=remove 129 | sp_bool=force 130 | sp_compare=force 131 | sp_inside_paren=remove 132 | sp_paren_paren=remove 133 | sp_paren_brace=add 134 | sp_before_ptr_star=force 135 | sp_before_unnamed_ptr_star=force 136 | sp_between_ptr_star=remove 137 | sp_after_ptr_star=remove 138 | sp_after_byref=remove 139 | sp_after_byref_func=remove 140 | sp_before_sparen=force 141 | sp_inside_sparen=remove 142 | sp_inside_sparen_close=remove 143 | sp_after_sparen=force 144 | sp_sparen_brace=force 145 | sp_special_semi=force 146 | sp_before_semi=remove 147 | sp_before_semi_for=remove 148 | sp_before_semi_for_empty=remove 149 | sp_after_semi=force 150 | sp_after_semi_for=force 151 | sp_after_semi_for_empty=remove 152 | sp_before_square=remove 153 | sp_before_squares=remove 154 | sp_inside_square=remove 155 | sp_after_comma=force 156 | sp_before_comma=remove 157 | sp_after_cast=force 158 | sp_inside_paren_cast=remove 159 | sp_sizeof_paren=force 160 | sp_inside_braces=remove 161 | sp_inside_braces_empty=remove 162 | sp_type_func=force 163 | sp_func_proto_paren=remove 164 | sp_func_def_paren=remove 165 | sp_inside_fparens=remove 166 | sp_inside_fparen=remove 167 | sp_fparen_brace=force 168 | sp_func_call_paren=remove 169 | sp_func_call_paren_empty=remove 170 | sp_func_call_user_paren=remove 171 | sp_func_class_paren=remove 172 | sp_defined_paren=remove 173 | sp_else_brace=force 174 | sp_brace_else=force 175 | sp_not=remove 176 | sp_inv=remove 177 | sp_addr=remove 178 | sp_member=remove 179 | sp_deref=remove 180 | sp_sign=remove 181 | sp_incdec=remove 182 | sp_cond_colon=force 183 | sp_cond_question=force 184 | sp_case_label=force 185 | sp_cmt_cpp_start=force 186 | sp_before_tr_emb_cmt=force 187 | nl_end_of_file=force 188 | nl_assign_brace=remove 189 | nl_fcall_brace=remove 190 | nl_enum_brace=remove 191 | nl_struct_brace=remove 192 | nl_union_brace=remove 193 | nl_if_brace=remove 194 | nl_brace_else=remove 195 | nl_elseif_brace=remove 196 | nl_else_brace=remove 197 | nl_for_brace=remove 198 | nl_while_brace=remove 199 | nl_switch_brace=remove 200 | nl_case_colon_brace=remove 201 | nl_func_type_name=remove 202 | nl_func_type_name_class=remove 203 | nl_func_scope_name=remove 204 | nl_func_proto_type_name=remove 205 | nl_func_paren=remove 206 | nl_func_def_paren=remove 207 | nl_func_decl_start=remove 208 | nl_func_def_start=remove 209 | nl_func_decl_start_single=remove 210 | nl_func_def_start_single=remove 211 | nl_func_decl_args=remove 212 | nl_func_def_args=remove 213 | nl_func_decl_end=remove 214 | nl_func_def_end=remove 215 | nl_func_decl_end_single=remove 216 | nl_func_def_end_single=remove 217 | nl_func_decl_empty=remove 218 | nl_func_def_empty=remove 219 | nl_fdef_brace=remove 220 | nl_return_expr=remove 221 | nl_after_if=add 222 | nl_after_for=force 223 | nl_after_switch=force 224 | pos_assign=trail 225 | pos_compare=trail 226 | mod_full_brace_do=force 227 | mod_full_brace_for=force 228 | mod_full_brace_function=force 229 | mod_full_brace_if=force 230 | mod_case_brace=force 231 | pp_space=remove 232 | -------------------------------------------------------------------------------- /src/texture.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | void texture_init(struct texture *texture, enum texture_kind kind, size_t width, size_t height, bool mipmapping, enum texture_type type, enum texture_format format, enum texture_format_internal format_internal) { 4 | texture->gl_id = 0; 5 | texture->kind = kind; 6 | texture->width = width; 7 | texture->height = height; 8 | texture->levels = 1; 9 | 10 | // Automatic levels when mipmappign is enabled. 11 | if (mipmapping) { 12 | while ((width | height) >> texture->levels) { 13 | texture->levels++; 14 | } 15 | } 16 | 17 | texture->gl_unit = texture->kind; 18 | 19 | // Figure out what OpenGL target to use based on the kind. 20 | switch (kind) { 21 | case TEXTURE_KIND_ENVIRONMENT_LAMBERTIAN: 22 | case TEXTURE_KIND_ENVIRONMENT_GGX: 23 | case TEXTURE_KIND_ENVIRONMENT_CHARLIE: 24 | case TEXTURE_KIND_CUBEMAP: 25 | texture->gl_target = GL_TEXTURE_CUBE_MAP; 26 | break; 27 | default: 28 | texture->gl_target = GL_TEXTURE_2D; 29 | } 30 | 31 | // Translate our data type to OpenGL data type. 32 | switch (type) { 33 | case TEXTURE_TYPE_FLOAT: texture->gl_type = GL_FLOAT; break; 34 | case TEXTURE_TYPE_UNSIGNED_BYTE: texture->gl_type = GL_UNSIGNED_BYTE; break; 35 | } 36 | 37 | // Translate our data formats to OpenGL data formats. 38 | switch (format) { 39 | case TEXTURE_FORMAT_RGB: texture->gl_format = GL_RGB; break; 40 | case TEXTURE_FORMAT_RGBA: texture->gl_format = GL_RGBA; break; 41 | } 42 | 43 | // Translate our internal formats to OpenGL internal formats. 44 | switch (format_internal) { 45 | case TEXTURE_FORMAT_INTERNAL_RGB: texture->gl_internal_format = GL_RGB; break; 46 | case TEXTURE_FORMAT_INTERNAL_RGBA: texture->gl_internal_format = GL_RGBA; break; 47 | case TEXTURE_FORMAT_INTERNAL_RGB8: texture->gl_internal_format = GL_RGB8; break; 48 | case TEXTURE_FORMAT_INTERNAL_RGBA8: texture->gl_internal_format = GL_RGBA8; break; 49 | case TEXTURE_FORMAT_INTERNAL_RGB16F: texture->gl_internal_format = GL_RGB16F; break; 50 | case TEXTURE_FORMAT_INTERNAL_RGBA16F: texture->gl_internal_format = GL_RGBA16F; break; 51 | case TEXTURE_FORMAT_INTERNAL_RGB32F: texture->gl_internal_format = GL_RGB32F; break; 52 | case TEXTURE_FORMAT_INTERNAL_RGBA32F: texture->gl_internal_format = GL_RGBA32F; break; 53 | } 54 | 55 | glGenTextures(1, &texture->gl_id); 56 | 57 | texture_switch(texture); 58 | 59 | // Pre-allocate the storage for the pixel data. 60 | texture_replace_data(texture, 0, width, height, NULL); 61 | 62 | // Wrapping. 63 | // glTexParameteri(texture->gl_target, GL_TEXTURE_WRAP_S, GL_REPEAT); 64 | // glTexParameteri(texture->gl_target, GL_TEXTURE_WRAP_T, GL_REPEAT); 65 | 66 | // Filtering. 67 | // `NEAREST` is generally faster than `LINEAR`, but it can produce textured images with sharper edges 68 | // because the transition between texture elements is not as smooth. 69 | glTexParameteri(texture->gl_target, GL_TEXTURE_MIN_FILTER, mipmapping ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); 70 | glTexParameteri(texture->gl_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 71 | 72 | // Anisotropic filtering. 73 | // This is plenty high and will get capped on systems that don't support it. 74 | texture_anisotropic_filtering(texture, 16); 75 | } 76 | 77 | void texture_fini(struct texture *texture) { 78 | glDeleteTextures(1, &texture->gl_id); 79 | } 80 | 81 | bool texture_init_from_file(struct texture *texture, enum texture_kind kind, const char *filepath) { 82 | if (kind == TEXTURE_KIND_EQUIRECTANGULAR) { 83 | // Equirectangular things are always flipped down for some reason. 84 | stbi_set_flip_vertically_on_load(true); 85 | 86 | int width, height, components; 87 | float *data = stbi_loadf(filepath, &width, &height, &components, 0); 88 | 89 | // We have to unconditionally revert that setting back, otherwise stbi will incorrectly flip future images when they get loaded. 90 | stbi_set_flip_vertically_on_load(false); 91 | 92 | if (!data) { 93 | return false; 94 | } 95 | 96 | texture_init(texture, kind, width, height, false, TEXTURE_TYPE_FLOAT, TEXTURE_FORMAT_RGB, TEXTURE_FORMAT_INTERNAL_RGB16F); 97 | texture_replace_data(texture, 0, width, height, data); 98 | 99 | stbi_image_free(data); 100 | 101 | return true; 102 | } 103 | 104 | if (kind == TEXTURE_KIND_IMAGE) { 105 | int width, height, components; 106 | float *data = stbi_loadf(filepath, &width, &height, &components, 0); 107 | 108 | if (!data) { 109 | return false; 110 | } 111 | 112 | texture_init(texture, kind, width, height, false, TEXTURE_TYPE_FLOAT, TEXTURE_FORMAT_RGBA, TEXTURE_FORMAT_INTERNAL_RGBA); 113 | texture_replace_data(texture, 0, width, height, data); 114 | 115 | stbi_image_free(data); 116 | 117 | return true; 118 | } 119 | 120 | // Texture kind not supported yet. 121 | return false; 122 | } 123 | 124 | bool texture_init_from_memory(struct texture *texture, enum texture_kind kind, const unsigned char *data, size_t size) { 125 | int width, height, components; 126 | 127 | unsigned char *decoded = stbi_load_from_memory(data, size, &width, &height, &components, 0); 128 | if (!decoded) { 129 | return false; 130 | } 131 | 132 | enum texture_format format; 133 | switch (components) { 134 | case 3: format = TEXTURE_FORMAT_RGB; break; 135 | case 4: format = TEXTURE_FORMAT_RGBA; break; 136 | default: 137 | fprintf(stderr, "Unsupported texture data format\n"); 138 | stbi_image_free(decoded); 139 | return false; 140 | } 141 | 142 | texture_init(texture, kind, width, height, true, TEXTURE_TYPE_UNSIGNED_BYTE, format, TEXTURE_FORMAT_INTERNAL_RGBA8); 143 | texture_replace_data(texture, 0, width, height, decoded); 144 | 145 | // Cleanup. 146 | stbi_image_free(decoded); 147 | 148 | return true; 149 | } 150 | 151 | void texture_switch(const struct texture *texture) { 152 | // Figure out what OpenGL texture unit a given texture should be assigned to (based on its kind). 153 | // This is actually the recommended way to enumerate that constant. 154 | // You use the texture unit 0 and add your offset to it. 155 | glActiveTexture(GL_TEXTURE0 + texture->gl_unit); 156 | glBindTexture(texture->gl_target, texture->gl_id); 157 | } 158 | 159 | void texture_replace_data(struct texture *texture, unsigned int level, unsigned int width, unsigned int height, const void *data) { 160 | texture_switch(texture); 161 | 162 | glTexImage2D(texture->gl_target, level, texture->gl_internal_format, width, height, 0, texture->gl_format, texture->gl_type, data); 163 | 164 | // Mimapping. 165 | if (level == 0 && texture->levels > 1) { 166 | glGenerateMipmap(texture->gl_target); 167 | } 168 | } 169 | 170 | void texture_anisotropic_filtering(struct texture *texture, float anisotropy) { 171 | // Ensure the driver supports the anisotropic extension before we attempt to do anything. 172 | if (!window_extension_supported("GL_EXT_texture_filter_anisotropic")) { 173 | return; 174 | } 175 | 176 | texture_switch(texture); 177 | 178 | // Ask OpenGL what is the maximum anisotropy we can use. 179 | GLfloat max_anisotropy = 1; // Fallback in case the call fails. 180 | glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy); 181 | 182 | if (anisotropy <= max_anisotropy) { 183 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy); 184 | } else { 185 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | float utils_closest_distance_between_ray_and_circle(vec3 ray_origin, vec3 ray_direction, vec3 circle_origin, vec3 circle_orientation, float circle_radius, vec3 closest) { 4 | vec3 plane_origin, plane_orientation; 5 | glm_vec3_copy(circle_origin, plane_origin); 6 | glm_vec3_copy(circle_orientation, plane_orientation); 7 | 8 | float t; 9 | 10 | if (utils_ray_plane_intersection(plane_origin, plane_orientation, ray_origin, ray_direction, &t)) { 11 | // Find the ray intersection point on the plane that contains the circle. 12 | vec3 on_plane; 13 | glm_vec3_copy(ray_origin, on_plane); 14 | glm_vec3_muladds(ray_direction, t, on_plane); 15 | 16 | // Project that intersection on to the circle's circumference. 17 | glm_vec3_sub(on_plane, circle_origin, closest); 18 | glm_normalize(closest); 19 | glm_vec3_mul(closest, (vec3) { circle_radius, circle_radius, circle_radius}, closest); 20 | glm_vec3_add(closest, circle_origin, closest); 21 | 22 | vec3 tmp; 23 | glm_vec3_sub(on_plane, closest, tmp); 24 | return glm_vec3_norm(tmp); 25 | } 26 | 27 | return -1; 28 | } 29 | 30 | bool utils_ray_plane_intersection(vec3 plane_origin, vec3 plane_normal, vec3 ray_origin, vec3 ray_direction, float *t) { 31 | float denom = glm_dot(plane_normal, ray_direction); 32 | 33 | if (fabs(denom) > 0.000001) { 34 | vec3 between; 35 | glm_vec3_sub(plane_origin, ray_origin, between); 36 | float result = glm_dot(between, plane_normal) / denom; 37 | *t = result; 38 | return result >= 0; 39 | } 40 | 41 | return false; 42 | } 43 | 44 | float utils_closest_distance_between_two_rays(vec3 r1_origin, vec3 r1_direction, vec3 r2_origin, vec3 r2_direction, float *d1, float *d2) { 45 | vec3 dp; 46 | glm_vec3_sub(r2_origin, r1_origin, dp); 47 | 48 | float v12 = glm_vec3_dot(r1_direction, r1_direction); 49 | float v22 = glm_vec3_dot(r2_direction, r2_direction); 50 | float v1v2 = glm_vec3_dot(r1_direction, r2_direction); 51 | 52 | float det = v1v2 * v1v2 - v12 * v22; 53 | 54 | if (fabs(det) > FLT_MIN) { 55 | float inv_det = 1.f / det; 56 | 57 | float dpv1 = glm_vec3_dot(dp, r1_direction); 58 | float dpv2 = glm_vec3_dot(dp, r2_direction); 59 | 60 | // FIXME: I had to invert their signs, not sure why. 61 | float t1 = -1 * inv_det * (v22 * dpv1 - v1v2 * dpv2); 62 | float t2 = -1 * inv_det * (v1v2 * dpv1 - v12 * dpv2); 63 | 64 | *d1 = t1; 65 | *d2 = t2; 66 | 67 | glm_vec3_muladds(r2_direction, t2, dp); 68 | glm_vec3_muladds(r1_direction, -t1, dp); 69 | return glm_vec3_norm(dp); 70 | } else { 71 | vec3 a; 72 | glm_vec3_cross(dp, r1_direction, a); 73 | return sqrt(glm_vec3_dot(a, a) / v12); 74 | } 75 | } 76 | 77 | void utils_render_cube(void) { 78 | static GLuint cubeVAO, cubeVBO; 79 | 80 | // initialize (if necessary) 81 | if (cubeVAO == 0) { 82 | float vertices[] = { 83 | // back face 84 | -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left 85 | 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right 86 | 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right 87 | 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right 88 | -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left 89 | -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, // top-left 90 | // front face 91 | -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left 92 | 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right 93 | 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right 94 | 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right 95 | -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left 96 | -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left 97 | // left face 98 | -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right 99 | -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left 100 | -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left 101 | -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left 102 | -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right 103 | -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right 104 | // right face 105 | 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 106 | 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 107 | 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right 108 | 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 109 | 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 110 | 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left 111 | // bottom face 112 | -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right 113 | 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left 114 | 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left 115 | 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left 116 | -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right 117 | -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right 118 | // top face 119 | -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left 120 | 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right 121 | 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right 122 | 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right 123 | -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left 124 | -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left 125 | }; 126 | glGenVertexArrays(1, &cubeVAO); 127 | glGenBuffers(1, &cubeVBO); 128 | // fill buffer 129 | glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); 130 | glBufferData(GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW); 131 | // link vertex attributes 132 | glBindVertexArray(cubeVAO); 133 | glEnableVertexAttribArray(0); 134 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof (float), (void *) 0); 135 | glEnableVertexAttribArray(1); 136 | glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof (float), (void *) (3 * sizeof (float))); 137 | glEnableVertexAttribArray(2); 138 | glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof (float), (void *) (6 * sizeof (float))); 139 | } 140 | 141 | // render Cube 142 | glBindVertexArray(cubeVAO); 143 | glDrawArrays(GL_TRIANGLES, 0, 36); 144 | } 145 | 146 | void utils_render_ngon(int n, float radius, mat4 transform, vec4 color) { 147 | float theta = (M_PI * 2) / (float) n; 148 | 149 | for (int i = 0; i < n; i++) { 150 | vec3 p1 = {0, radius, 0}; 151 | vec3 p2 = {0, radius, 0}; 152 | 153 | glm_vec3_rotate(p1, theta * i, (vec3) { 0, 0, 1}); 154 | glm_vec3_rotate(p2, theta * (i + 1), (vec3) { 0, 0, 1}); 155 | 156 | utils_render_line(p1, p2, transform, color); 157 | } 158 | } 159 | 160 | void utils_render_line(vec3 from, vec3 to, mat4 transform, vec4 color) { 161 | static GLuint vao, vbo; 162 | 163 | static float vertices[6] = {0}; 164 | 165 | vertices[0] = from[0]; 166 | vertices[1] = from[1]; 167 | vertices[2] = from[2]; 168 | vertices[3] = to[0]; 169 | vertices[4] = to[1]; 170 | vertices[5] = to[2]; 171 | 172 | if (vao == 0) { 173 | glGenVertexArrays(1, &vao); 174 | glGenBuffers(1, &vbo); 175 | 176 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 177 | glBufferData(GL_ARRAY_BUFFER, sizeof vertices, vertices, GL_STATIC_DRAW); 178 | 179 | glBindVertexArray(vao); 180 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL); 181 | glEnableVertexAttribArray(0); 182 | } 183 | 184 | glUseProgram(client.renderer.plain_shader->program_id); 185 | 186 | static GLint color_location = -1; 187 | 188 | if (color_location == -1) { 189 | color_location = glGetUniformLocation(client.renderer.plain_shader->program_id, "u_Color"); 190 | } 191 | 192 | glEnable(GL_LINE_SMOOTH); 193 | 194 | glBindVertexArray(vao); 195 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 196 | glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof vertices, vertices); 197 | 198 | mat4 view_projection_matrix; 199 | glm_mat4_mul(client.renderer.projection_matrix, client.camera.view_matrix, view_projection_matrix); 200 | 201 | shader_bind_uniform_mvp(client.renderer.plain_shader, view_projection_matrix, transform, 1); 202 | glUniform4f(color_location, color[0], color[1], color[2], color[3]); 203 | 204 | glDrawArrays(GL_LINES, 0, 2); 205 | } 206 | 207 | struct entity *find_selected_entity(void) { 208 | if (client.ui.selected_entity_id == 0) { 209 | return NULL; 210 | } 211 | 212 | for (size_t i = 0; i < client.scene.entity_count; i++) { 213 | if (client.scene.entities[i]->id == client.ui.selected_entity_id) { 214 | return client.scene.entities[i]; 215 | } 216 | } 217 | 218 | return NULL; 219 | } 220 | -------------------------------------------------------------------------------- /src/renderer.c: -------------------------------------------------------------------------------- 1 | #include "cglm/cglm.h" 2 | #include "client.h" 3 | #include "entity.h" 4 | #include "incbin.h" 5 | #include "mesh.h" 6 | #include "model.h" 7 | #include "shader.h" 8 | #include "utils.h" 9 | 10 | #define RENDERER_FOV 45.0f 11 | #define RENDERER_PLANE_FAR 10000.0f 12 | #define RENDERER_PLANE_NEAR 0.1f 13 | 14 | INCBIN(shaders_skybox_main_vert, "../shaders/skybox/main.vert"); 15 | INCBIN(shaders_skybox_main_frag, "../shaders/skybox/main.frag"); 16 | 17 | INCBIN(shaders_plain_main_vert, "../shaders/plain/main.vert"); 18 | INCBIN(shaders_plain_main_frag, "../shaders/plain/main.frag"); 19 | 20 | void renderer_update_projection_matrix(struct renderer *renderer) { 21 | glm_mat4_identity(renderer->projection_matrix); 22 | // glm_perspective_default(renderer->viewport_width / renderer->viewport_height, projection); 23 | glm_perspective(glm_rad(renderer->fov), renderer->viewport_width / renderer->viewport_height, renderer->near_plane, renderer->far_plane, renderer->projection_matrix); 24 | } 25 | 26 | void renderer_init(struct renderer *renderer) { 27 | // TODO: Dynamic dimensions; what happens when the window gets resized? 28 | int width, height; 29 | window_framebuffer_size(&client.window, &width, &height); 30 | renderer->viewport_width = width; 31 | renderer->viewport_height = height; 32 | 33 | // TODO: Should provide means of modifying these. 34 | renderer->fov = RENDERER_FOV; 35 | renderer->far_plane = RENDERER_PLANE_FAR; 36 | renderer->near_plane = RENDERER_PLANE_NEAR; 37 | renderer_update_projection_matrix(renderer); 38 | 39 | renderer->exposure = 1; 40 | renderer->wireframe = false; 41 | 42 | renderer->plain_shader = shader_load_from_memory(NULL, shaders_plain_main_vert_data, shaders_plain_main_vert_size, shaders_plain_main_frag_data, shaders_plain_main_frag_size, NULL, 0); 43 | if (!renderer->plain_shader) { 44 | return; 45 | } 46 | 47 | renderer->mousepicking_entity_id = 0; 48 | } 49 | 50 | void renderer_fini(struct renderer *renderer) { 51 | shader_destroy(renderer->plain_shader); 52 | } 53 | 54 | void renderer_switch(const struct renderer *new) { 55 | // Resize the viewport, go back to the default framebuffer and clear color for rendering. 56 | glViewport(0, 0, new->viewport_width, new->viewport_height); 57 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 58 | glClearColor(0, 0, 0, 1); // Black. 59 | 60 | // Depth testing. 61 | glEnable(GL_DEPTH_TEST); 62 | glDepthFunc(GL_LEQUAL); 63 | 64 | // Do update the depth buffer. 65 | glDepthMask(GL_TRUE); 66 | 67 | // Multisampling. 68 | glEnable(GL_MULTISAMPLE); 69 | 70 | // Wireframe. 71 | if (new->wireframe) { 72 | glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 73 | } else { 74 | glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 75 | } 76 | 77 | // Necessary to avoid the seams of the cubemap being visible. 78 | glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); 79 | 80 | // Transparency. 81 | // glEnable(GL_BLEND); 82 | // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 83 | } 84 | 85 | static void render_mesh(struct renderer *renderer, const struct camera *camera, const struct scene *scene, const struct entity *entity, const struct shader *shader, const struct mesh *mesh) { 86 | mesh_switch(mesh); 87 | 88 | mat4 view_projection_matrix; 89 | glm_mat4_mul(renderer->projection_matrix, (vec4 *) camera->view_matrix, view_projection_matrix); 90 | 91 | mat4 model_matrix = GLM_MAT4_IDENTITY_INIT; 92 | 93 | // Translation, rotation, scale. 94 | glm_translate(model_matrix, (float *) entity->translation); 95 | glm_quat_rotate(model_matrix, (float *) entity->rotation, model_matrix); 96 | glm_scale(model_matrix, (vec3) { entity->scale, entity->scale, entity->scale}); 97 | glm_mat4_mul(model_matrix, (vec4 *) mesh->initial_transform, model_matrix); 98 | 99 | // Uniforms. 100 | shader_bind_uniform_environment(shader, scene->environment); 101 | shader_bind_uniform_material(shader, &mesh->material); 102 | shader_bind_uniform_camera(shader, camera); 103 | shader_bind_uniform_lights(shader, scene->lights, scene->lights_count); 104 | shader_bind_uniform_mvp(shader, view_projection_matrix, model_matrix, renderer->exposure); 105 | 106 | // Render. 107 | glDrawElements(GL_TRIANGLES, mesh->indices_count, mesh->indices_type, NULL); 108 | } 109 | 110 | static void render_skybox(const struct renderer *renderer, const struct camera *camera, const struct scene *scene) { 111 | static struct shader *skybox_shader = NULL; 112 | static GLint environment_map_location = 0; 113 | static GLint projection_location = 0; 114 | static GLint view_location = 0; 115 | 116 | // Must disable back-face culling because we're inside the cube. 117 | glDisable(GL_CULL_FACE); 118 | 119 | // FIXME: All this shouldn't be here. 120 | if (skybox_shader == NULL) { 121 | skybox_shader = shader_load_from_memory(NULL, shaders_skybox_main_vert_data, shaders_skybox_main_vert_size, shaders_skybox_main_frag_data, shaders_skybox_main_frag_size, NULL, 0); 122 | if (skybox_shader == NULL) { 123 | fprintf(stderr, "Unable to load skybox shader\n"); 124 | exit(EXIT_FAILURE); // TODO: Handle failure more gracefully. 125 | } 126 | 127 | glUseProgram(skybox_shader->program_id); 128 | 129 | environment_map_location = glGetUniformLocation(skybox_shader->program_id, "environmentMap"); 130 | projection_location = glGetUniformLocation(skybox_shader->program_id, "projection"); 131 | view_location = glGetUniformLocation(skybox_shader->program_id, "view"); 132 | } 133 | 134 | glUseProgram(skybox_shader->program_id); 135 | texture_switch(&scene->environment->cubemap); 136 | glUniform1i(environment_map_location, scene->environment->cubemap.kind); 137 | mat4 projection_matrix = GLM_MAT4_IDENTITY_INIT; 138 | // glm_perspective_default(renderer->viewport_width / renderer->viewport_height, projection); 139 | glm_perspective(glm_rad(renderer->fov), renderer->viewport_width / renderer->viewport_height, renderer->near_plane, renderer->far_plane, projection_matrix); 140 | glUniformMatrix4fv(projection_location, 1, false, projection_matrix[0]); 141 | glUniformMatrix4fv(view_location, 1, false, camera->view_matrix[0]); 142 | 143 | // glDisable(GL_CULL_FACE); 144 | utils_render_cube(); 145 | // glEnable(GL_CULL_FACE); 146 | } 147 | 148 | void renderer_render(struct renderer *renderer, const struct camera *camera, const struct scene *scene) { 149 | renderer_switch(renderer); 150 | environment_switch(scene->environment); 151 | 152 | // Clear the screen. 153 | glClearColor(0, 0, 0, 0); 154 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 155 | 156 | // Disable multisampling for color mouse-picking. 157 | glDisable(GL_MULTISAMPLE); 158 | 159 | // Render all entities, for mouse picking. 160 | for (size_t i = 0; i < scene->entity_count; i++) { 161 | const struct entity *entity = scene->entities[i]; 162 | 163 | // Render all meshes. 164 | for (size_t i = 0; i < entity->model->meshes_count; i++) { 165 | struct mesh *mesh = &entity->model->meshes[i]; 166 | 167 | // Render mesh. 168 | glUseProgram(renderer->plain_shader->program_id); 169 | static GLint picking_color_location = -1; 170 | if (picking_color_location == -1) { 171 | picking_color_location = glGetUniformLocation(renderer->plain_shader->program_id, "u_Color"); 172 | } 173 | 174 | // Convert entity ID to color. 175 | vec4 color; 176 | entity_id_as_color(entity->id, color); 177 | glUniform4fv(picking_color_location, 1, color); 178 | 179 | render_mesh(renderer, camera, scene, entity, renderer->plain_shader, mesh); 180 | } 181 | } 182 | 183 | // Mouse picking; super-duper slow, the framebuffer is on the GPU. 184 | double mouseX = client.window.cursor_pos_x; 185 | double mouseY = client.window.cursor_pos_y; 186 | if (mouseX > 0 && mouseY > 0 && mouseX < renderer->viewport_width && mouseY < renderer->viewport_height) { 187 | int x = mouseX, y = mouseY; 188 | vec4 color; 189 | glReadPixels(x, renderer->viewport_height - y, 1, 1, GL_RGBA, GL_FLOAT, color); 190 | 191 | // Convert color back to entity ID. 192 | renderer->mousepicking_entity_id = entity_color_as_id(color); 193 | } 194 | 195 | // Start over. 196 | glClearColor(0, 0, 0, 255); 197 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 198 | 199 | // Re-enable multisampling. 200 | glEnable(GL_MULTISAMPLE); 201 | 202 | // Render all entities. 203 | for (size_t i = 0; i < scene->entity_count; i++) { 204 | const struct entity *entity = scene->entities[i]; 205 | 206 | // Render all meshes. 207 | for (size_t i = 0; i < entity->model->meshes_count; i++) { 208 | struct mesh *mesh = &entity->model->meshes[i]; 209 | 210 | // Render mesh. 211 | glUseProgram(mesh->shader->program_id); 212 | render_mesh(renderer, camera, scene, entity, mesh->shader, mesh); 213 | } 214 | } 215 | 216 | // Render the skybox. 217 | // This is done last so that only the fragments that aren't hiding it gets computed. 218 | // The shader is written such that the depth buffer is always 1.0 (the furtest away). 219 | render_skybox(renderer, camera, scene); 220 | } 221 | 222 | void renderer_wireframe(struct renderer *renderer, bool enabled) { 223 | renderer->wireframe = enabled; 224 | } 225 | -------------------------------------------------------------------------------- /shaders/pbr/main.vert: -------------------------------------------------------------------------------- 1 | // ===================================================================================================================== 2 | // ANIMATION 3 | // ===================================================================================================================== 4 | 5 | #ifdef HAS_TARGET_POSITION0 6 | in vec3 a_Target_Position0; 7 | #endif 8 | 9 | #ifdef HAS_TARGET_POSITION1 10 | in vec3 a_Target_Position1; 11 | #endif 12 | 13 | #ifdef HAS_TARGET_POSITION2 14 | in vec3 a_Target_Position2; 15 | #endif 16 | 17 | #ifdef HAS_TARGET_POSITION3 18 | in vec3 a_Target_Position3; 19 | #endif 20 | 21 | #ifdef HAS_TARGET_POSITION4 22 | in vec3 a_Target_Position4; 23 | #endif 24 | 25 | #ifdef HAS_TARGET_POSITION5 26 | in vec3 a_Target_Position5; 27 | #endif 28 | 29 | #ifdef HAS_TARGET_POSITION6 30 | in vec3 a_Target_Position6; 31 | #endif 32 | 33 | #ifdef HAS_TARGET_POSITION7 34 | in vec3 a_Target_Position7; 35 | #endif 36 | 37 | #ifdef HAS_TARGET_NORMAL0 38 | in vec3 a_Target_Normal0; 39 | #endif 40 | 41 | #ifdef HAS_TARGET_NORMAL1 42 | in vec3 a_Target_Normal1; 43 | #endif 44 | 45 | #ifdef HAS_TARGET_NORMAL2 46 | in vec3 a_Target_Normal2; 47 | #endif 48 | 49 | #ifdef HAS_TARGET_NORMAL3 50 | in vec3 a_Target_Normal3; 51 | #endif 52 | 53 | #ifdef HAS_TARGET_TANGENT0 54 | in vec3 a_Target_Tangent0; 55 | #endif 56 | 57 | #ifdef HAS_TARGET_TANGENT1 58 | in vec3 a_Target_Tangent1; 59 | #endif 60 | 61 | #ifdef HAS_TARGET_TANGENT2 62 | in vec3 a_Target_Tangent2; 63 | #endif 64 | 65 | #ifdef HAS_TARGET_TANGENT3 66 | in vec3 a_Target_Tangent3; 67 | #endif 68 | 69 | #ifdef USE_MORPHING 70 | uniform float u_morphWeights[WEIGHT_COUNT]; 71 | #endif 72 | 73 | #ifdef HAS_JOINT_SET1 74 | in vec4 a_Joint1; 75 | #endif 76 | 77 | #ifdef HAS_JOINT_SET2 78 | in vec4 a_Joint2; 79 | #endif 80 | 81 | #ifdef HAS_WEIGHT_SET1 82 | in vec4 a_Weight1; 83 | #endif 84 | 85 | #ifdef HAS_WEIGHT_SET2 86 | in vec4 a_Weight2; 87 | #endif 88 | 89 | #ifdef USE_SKINNING 90 | uniform mat4 u_jointMatrix[JOINT_COUNT]; 91 | uniform mat4 u_jointNormalMatrix[JOINT_COUNT]; 92 | #endif 93 | 94 | #ifdef USE_SKINNING 95 | mat4 getSkinningMatrix() 96 | { 97 | mat4 skin = mat4(0); 98 | 99 | #if defined(HAS_WEIGHT_SET1) && defined(HAS_JOINT_SET1) 100 | skin += 101 | a_Weight1.x * u_jointMatrix[int(a_Joint1.x)] + 102 | a_Weight1.y * u_jointMatrix[int(a_Joint1.y)] + 103 | a_Weight1.z * u_jointMatrix[int(a_Joint1.z)] + 104 | a_Weight1.w * u_jointMatrix[int(a_Joint1.w)]; 105 | #endif 106 | 107 | #if defined(HAS_WEIGHT_SET2) && defined(HAS_JOINT_SET2) 108 | skin += 109 | a_Weight2.x * u_jointMatrix[int(a_Joint2.x)] + 110 | a_Weight2.y * u_jointMatrix[int(a_Joint2.y)] + 111 | a_Weight2.z * u_jointMatrix[int(a_Joint2.z)] + 112 | a_Weight2.w * u_jointMatrix[int(a_Joint2.w)]; 113 | #endif 114 | 115 | return skin; 116 | } 117 | 118 | mat4 getSkinningNormalMatrix() 119 | { 120 | mat4 skin = mat4(0); 121 | 122 | #if defined(HAS_WEIGHT_SET1) && defined(HAS_JOINT_SET1) 123 | skin += 124 | a_Weight1.x * u_jointNormalMatrix[int(a_Joint1.x)] + 125 | a_Weight1.y * u_jointNormalMatrix[int(a_Joint1.y)] + 126 | a_Weight1.z * u_jointNormalMatrix[int(a_Joint1.z)] + 127 | a_Weight1.w * u_jointNormalMatrix[int(a_Joint1.w)]; 128 | #endif 129 | 130 | #if defined(HAS_WEIGHT_SET2) && defined(HAS_JOINT_SET2) 131 | skin += 132 | a_Weight2.x * u_jointNormalMatrix[int(a_Joint2.x)] + 133 | a_Weight2.y * u_jointNormalMatrix[int(a_Joint2.y)] + 134 | a_Weight2.z * u_jointNormalMatrix[int(a_Joint2.z)] + 135 | a_Weight2.w * u_jointNormalMatrix[int(a_Joint2.w)]; 136 | #endif 137 | 138 | return skin; 139 | } 140 | #endif // !USE_SKINNING 141 | 142 | #ifdef USE_MORPHING 143 | vec4 getTargetPosition() 144 | { 145 | vec4 pos = vec4(0); 146 | 147 | #ifdef HAS_TARGET_POSITION0 148 | pos.xyz += u_morphWeights[0] * a_Target_Position0; 149 | #endif 150 | 151 | #ifdef HAS_TARGET_POSITION1 152 | pos.xyz += u_morphWeights[1] * a_Target_Position1; 153 | #endif 154 | 155 | #ifdef HAS_TARGET_POSITION2 156 | pos.xyz += u_morphWeights[2] * a_Target_Position2; 157 | #endif 158 | 159 | #ifdef HAS_TARGET_POSITION3 160 | pos.xyz += u_morphWeights[3] * a_Target_Position3; 161 | #endif 162 | 163 | #ifdef HAS_TARGET_POSITION4 164 | pos.xyz += u_morphWeights[4] * a_Target_Position4; 165 | #endif 166 | 167 | return pos; 168 | } 169 | 170 | vec3 getTargetNormal() 171 | { 172 | vec3 normal = vec3(0); 173 | 174 | #ifdef HAS_TARGET_NORMAL0 175 | normal += u_morphWeights[0] * a_Target_Normal0; 176 | #endif 177 | 178 | #ifdef HAS_TARGET_NORMAL1 179 | normal += u_morphWeights[1] * a_Target_Normal1; 180 | #endif 181 | 182 | #ifdef HAS_TARGET_NORMAL2 183 | normal += u_morphWeights[2] * a_Target_Normal2; 184 | #endif 185 | 186 | #ifdef HAS_TARGET_NORMAL3 187 | normal += u_morphWeights[3] * a_Target_Normal3; 188 | #endif 189 | 190 | #ifdef HAS_TARGET_NORMAL4 191 | normal += u_morphWeights[4] * a_Target_Normal4; 192 | #endif 193 | 194 | return normal; 195 | } 196 | 197 | vec3 getTargetTangent() 198 | { 199 | vec3 tangent = vec3(0); 200 | 201 | #ifdef HAS_TARGET_TANGENT0 202 | tangent += u_morphWeights[0] * a_Target_Tangent0; 203 | #endif 204 | 205 | #ifdef HAS_TARGET_TANGENT1 206 | tangent += u_morphWeights[1] * a_Target_Tangent1; 207 | #endif 208 | 209 | #ifdef HAS_TARGET_TANGENT2 210 | tangent += u_morphWeights[2] * a_Target_Tangent2; 211 | #endif 212 | 213 | #ifdef HAS_TARGET_TANGENT3 214 | tangent += u_morphWeights[3] * a_Target_Tangent3; 215 | #endif 216 | 217 | #ifdef HAS_TARGET_TANGENT4 218 | tangent += u_morphWeights[4] * a_Target_Tangent4; 219 | #endif 220 | 221 | return tangent; 222 | } 223 | 224 | #endif // !USE_MORPHING 225 | 226 | // ===================================================================================================================== 227 | // MAIN 228 | // ===================================================================================================================== 229 | 230 | in vec3 a_Position; 231 | out vec3 v_Position; 232 | 233 | #ifdef HAS_NORMALS 234 | in vec3 a_Normal; 235 | #endif 236 | 237 | #ifdef HAS_TANGENTS 238 | in vec4 a_Tangent; 239 | #endif 240 | 241 | #ifdef HAS_NORMALS 242 | #ifdef HAS_TANGENTS 243 | out mat3 v_TBN; 244 | #else 245 | out vec3 v_Normal; 246 | #endif 247 | #endif 248 | 249 | #ifdef HAS_UV_SET1 250 | in vec2 a_UV1; 251 | #endif 252 | 253 | #ifdef HAS_UV_SET2 254 | in vec2 a_UV2; 255 | #endif 256 | 257 | out vec2 v_UVCoord1; 258 | out vec2 v_UVCoord2; 259 | 260 | #ifdef HAS_VERTEX_COLOR_VEC3 261 | in vec3 a_Color; 262 | out vec3 v_Color; 263 | #endif 264 | 265 | #ifdef HAS_VERTEX_COLOR_VEC4 266 | in vec4 a_Color; 267 | out vec4 v_Color; 268 | #endif 269 | 270 | uniform mat4 u_ViewProjectionMatrix; 271 | uniform mat4 u_ModelMatrix; 272 | uniform mat4 u_NormalMatrix; 273 | 274 | vec4 getPosition() 275 | { 276 | vec4 pos = vec4(a_Position, 1.0); 277 | 278 | #ifdef USE_MORPHING 279 | pos += getTargetPosition(); 280 | #endif 281 | 282 | #ifdef USE_SKINNING 283 | pos = getSkinningMatrix() * pos; 284 | #endif 285 | 286 | return pos; 287 | } 288 | 289 | #ifdef HAS_NORMALS 290 | vec3 getNormal() 291 | { 292 | vec3 normal = a_Normal; 293 | 294 | #ifdef USE_MORPHING 295 | normal += getTargetNormal(); 296 | #endif 297 | 298 | #ifdef USE_SKINNING 299 | normal = mat3(getSkinningNormalMatrix()) * normal; 300 | #endif 301 | 302 | return normalize(normal); 303 | } 304 | #endif 305 | 306 | #ifdef HAS_TANGENTS 307 | vec3 getTangent() 308 | { 309 | vec3 tangent = a_Tangent.xyz; 310 | 311 | #ifdef USE_MORPHING 312 | tangent += getTargetTangent(); 313 | #endif 314 | 315 | #ifdef USE_SKINNING 316 | tangent = mat3(getSkinningMatrix()) * tangent; 317 | #endif 318 | 319 | return normalize(tangent); 320 | } 321 | #endif 322 | 323 | void main() 324 | { 325 | vec4 pos = u_ModelMatrix * getPosition(); 326 | v_Position = vec3(pos.xyz) / pos.w; 327 | 328 | #ifdef HAS_NORMALS 329 | #ifdef HAS_TANGENTS 330 | vec3 tangent = getTangent(); 331 | vec3 normalW = normalize(vec3(u_NormalMatrix * vec4(getNormal(), 0.0))); 332 | vec3 tangentW = normalize(vec3(u_ModelMatrix * vec4(tangent, 0.0))); 333 | vec3 bitangentW = cross(normalW, tangentW) * a_Tangent.w; 334 | v_TBN = mat3(tangentW, bitangentW, normalW); 335 | #else // !HAS_TANGENTS 336 | v_Normal = normalize(vec3(u_NormalMatrix * vec4(getNormal(), 0.0))); 337 | #endif 338 | #endif // !HAS_NORMALS 339 | 340 | v_UVCoord1 = vec2(0.0, 0.0); 341 | v_UVCoord2 = vec2(0.0, 0.0); 342 | 343 | #ifdef HAS_UV_SET1 344 | v_UVCoord1 = a_UV1; 345 | #endif 346 | 347 | #ifdef HAS_UV_SET2 348 | v_UVCoord2 = a_UV2; 349 | #endif 350 | 351 | #if defined(HAS_VERTEX_COLOR_VEC3) || defined(HAS_VERTEX_COLOR_VEC4) 352 | v_Color = a_Color; 353 | #endif 354 | 355 | gl_Position = u_ViewProjectionMatrix * pos; 356 | } 357 | -------------------------------------------------------------------------------- /shaders/iblsampler/main.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | #define UX3D_MATH_PI 3.1415926535897932384626433832795 4 | #define UX3D_MATH_INV_PI (1.0 / UX3D_MATH_PI) 5 | 6 | uniform samplerCube uCubeMap; 7 | 8 | // enum 9 | const uint cLambertian = 0; 10 | const uint cGGX = 1; 11 | const uint cCharlie = 2; 12 | 13 | uniform float pfp_roughness; 14 | uniform uint pfp_sampleCount; 15 | uniform uint pfp_currentMipLevel; 16 | uniform uint pfp_width; 17 | uniform float pfp_lodBias; 18 | uniform uint pfp_distribution; // enum 19 | 20 | in vec2 UV; 21 | 22 | // output cubemap faces 23 | out vec4 outFace0; 24 | out vec4 outFace1; 25 | out vec4 outFace2; 26 | out vec4 outFace3; 27 | out vec4 outFace4; 28 | out vec4 outFace5; 29 | 30 | out vec3 outLUT; 31 | 32 | void writeFace(int face, vec3 colorIn) 33 | { 34 | vec4 color = vec4(colorIn.rgb, 1.0f); 35 | 36 | if(face == 0) 37 | outFace0 = color; 38 | else if(face == 1) 39 | outFace1 = color; 40 | else if(face == 2) 41 | outFace2 = color; 42 | else if(face == 3) 43 | outFace3 = color; 44 | else if(face == 4) 45 | outFace4 = color; 46 | else //if(face == 5) 47 | outFace5 = color; 48 | } 49 | 50 | vec3 uvToXYZ(int face, vec2 uv) 51 | { 52 | if(face == 0) 53 | return vec3( 1.f, uv.y, -uv.x); 54 | 55 | else if(face == 1) 56 | return vec3( -1.f, uv.y, uv.x); 57 | 58 | else if(face == 2) 59 | return vec3( +uv.x, -1.f, +uv.y); 60 | 61 | else if(face == 3) 62 | return vec3( +uv.x, 1.f, -uv.y); 63 | 64 | else if(face == 4) 65 | return vec3( +uv.x, uv.y, 1.f); 66 | 67 | else //if(face == 5) 68 | return vec3( -uv.x, +uv.y, -1.f); 69 | } 70 | 71 | vec2 dirToUV(vec3 dir) 72 | { 73 | return vec2( 74 | 0.5f + 0.5f * atan(dir.z, dir.x) / UX3D_MATH_PI, 75 | 1.f - acos(dir.y) / UX3D_MATH_PI); 76 | } 77 | 78 | float saturate(float v) 79 | { 80 | return clamp(v, 0.0f, 1.0f); 81 | } 82 | 83 | float Hammersley(uint i) 84 | { 85 | return float(bitfieldReverse(i)) * 2.3283064365386963e-10; 86 | } 87 | 88 | vec3 getImportanceSampleDirection(vec3 normal, float sinTheta, float cosTheta, float phi) 89 | { 90 | vec3 H = normalize(vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta)); 91 | 92 | vec3 bitangent = vec3(0.0, 1.0, 0.0); 93 | 94 | // Eliminates singularities. 95 | float NdotX = dot(normal, vec3(1.0, 0.0, 0.0)); 96 | float NdotY = dot(normal, vec3(0.0, 1.0, 0.0)); 97 | float NdotZ = dot(normal, vec3(0.0, 0.0, 1.0)); 98 | if (abs(NdotY) > abs(NdotX) && abs(NdotY) > abs(NdotZ)) 99 | { 100 | // Sampling +Y or -Y, so we need a more robust bitangent. 101 | if (NdotY > 0.0) 102 | { 103 | bitangent = vec3(0.0, 0.0, 1.0); 104 | } 105 | else 106 | { 107 | bitangent = vec3(0.0, 0.0, -1.0); 108 | } 109 | } 110 | 111 | vec3 tangent = cross(bitangent, normal); 112 | bitangent = cross(normal, tangent); 113 | 114 | return normalize(tangent * H.x + bitangent * H.y + normal * H.z); 115 | } 116 | 117 | // https://github.com/google/filament/blob/master/shaders/src/brdf.fs#L136 118 | float V_Ashikhmin(float NdotL, float NdotV) 119 | { 120 | return clamp(1.0 / (4.0 * (NdotL + NdotV - NdotL * NdotV)), 0.0, 1.0); 121 | } 122 | 123 | // NDF 124 | float D_GGX(float NdotH, float roughness) 125 | { 126 | float alpha = roughness * roughness; 127 | 128 | float alpha2 = alpha * alpha; 129 | 130 | float divisor = NdotH * NdotH * (alpha2 - 1.0) + 1.0; 131 | 132 | return alpha2 / (UX3D_MATH_PI * divisor * divisor); 133 | } 134 | 135 | // NDF 136 | float D_Ashikhmin(float NdotH, float roughness) 137 | { 138 | float alpha = roughness * roughness; 139 | // Ashikhmin 2007, "Distribution-based BRDFs" 140 | float a2 = alpha * alpha; 141 | float cos2h = NdotH * NdotH; 142 | float sin2h = 1.0 - cos2h; 143 | float sin4h = sin2h * sin2h; 144 | float cot2 = -cos2h / (a2 * sin2h); 145 | return 1.0 / (UX3D_MATH_PI * (4.0 * a2 + 1.0) * sin4h) * (4.0 * exp(cot2) + sin4h); 146 | } 147 | 148 | // NDF 149 | float D_Charlie(float sheenRoughness, float NdotH) 150 | { 151 | sheenRoughness = max(sheenRoughness, 0.000001); //clamp (0,1] 152 | float alphaG = sheenRoughness * sheenRoughness; 153 | float invR = 1.0 / alphaG; 154 | float cos2h = NdotH * NdotH; 155 | float sin2h = 1.0 - cos2h; 156 | return (2.0 + invR) * pow(sin2h, invR * 0.5) / (2.0 * UX3D_MATH_PI); 157 | } 158 | 159 | vec3 getSampleVector(uint sampleIndex, vec3 N, float roughness) 160 | { 161 | float X = float(sampleIndex) / float(pfp_sampleCount); 162 | float Y = Hammersley(sampleIndex); 163 | 164 | float phi = 2.0 * UX3D_MATH_PI * X; 165 | float cosTheta = 0.f; 166 | float sinTheta = 0.f; 167 | 168 | if(pfp_distribution == cLambertian) 169 | { 170 | cosTheta = 1.0 - Y; 171 | sinTheta = sqrt(1.0 - cosTheta*cosTheta); 172 | } 173 | else if(pfp_distribution == cGGX) 174 | { 175 | float alpha = roughness * roughness; 176 | cosTheta = sqrt((1.0 - Y) / (1.0 + (alpha*alpha - 1.0) * Y)); 177 | sinTheta = sqrt(1.0 - cosTheta*cosTheta); 178 | } 179 | else if(pfp_distribution == cCharlie) 180 | { 181 | float alpha = roughness * roughness; 182 | sinTheta = pow(Y, alpha / (2.0*alpha + 1.0)); 183 | cosTheta = sqrt(1.0 - sinTheta * sinTheta); 184 | } 185 | 186 | return getImportanceSampleDirection(N, sinTheta, cosTheta, phi); 187 | } 188 | 189 | float PDF(vec3 V, vec3 H, vec3 N, vec3 L, float roughness) 190 | { 191 | if(pfp_distribution == cLambertian) 192 | { 193 | float NdotL = dot(N, L); 194 | return max(NdotL * UX3D_MATH_INV_PI, 0.0); 195 | } 196 | else if(pfp_distribution == cGGX) 197 | { 198 | float VdotH = dot(V, H); 199 | float NdotH = dot(N, H); 200 | 201 | float D = D_GGX(NdotH, roughness); 202 | return max(D * NdotH / (4.0 * VdotH), 0.0); 203 | } 204 | else if(pfp_distribution == cCharlie) 205 | { 206 | float VdotH = dot(V, H); 207 | float NdotH = dot(N, H); 208 | 209 | float D = D_Charlie(roughness, NdotH); 210 | return max(D * NdotH / abs(4.0 * VdotH), 0.0); 211 | } 212 | 213 | return 0.f; 214 | } 215 | 216 | vec3 filterColor(vec3 N) 217 | { 218 | vec4 color = vec4(0.f); 219 | uint NumSamples = pfp_sampleCount; 220 | float solidAngleTexel = 4.0 * UX3D_MATH_PI / (6.0 * float(pfp_width) * float(pfp_width)); 221 | 222 | for(uint i = 0; i < NumSamples; ++i) 223 | { 224 | vec3 H = getSampleVector(i, N, pfp_roughness); 225 | 226 | // Note: reflect takes incident vector. 227 | // Note: N = V 228 | vec3 V = N; 229 | 230 | vec3 L = normalize(reflect(-V, H)); 231 | 232 | float NdotL = dot(N, L); 233 | 234 | if (NdotL > 0.0) 235 | { 236 | float lod = 0.0; 237 | 238 | if (pfp_roughness > 0.0 || pfp_distribution == cLambertian) 239 | { 240 | // Mipmap Filtered Samples 241 | // see https://github.com/derkreature/IBLBaker 242 | // see https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html 243 | float pdf = PDF(V, H, N, L, pfp_roughness ); 244 | 245 | float solidAngleSample = 1.0 / (NumSamples * pdf); 246 | 247 | lod = 0.5 * log2(solidAngleSample / solidAngleTexel); 248 | lod += pfp_lodBias; 249 | } 250 | 251 | if(pfp_distribution == cLambertian) 252 | { 253 | color += vec4(texture(uCubeMap, H, lod).rgb, 1.0); 254 | } 255 | else 256 | { 257 | color += vec4(textureLod(uCubeMap, L, lod).rgb * NdotL, NdotL); 258 | } 259 | } 260 | } 261 | 262 | if(color.w == 0.f) 263 | { 264 | return color.rgb; 265 | } 266 | 267 | return color.rgb / color.w; 268 | } 269 | 270 | // From the filament docs. Geometric Shadowing function 271 | // https://google.github.io/filament/Filament.html#toc4.4.2 272 | float V_SmithGGXCorrelated(float NoV, float NoL, float roughness) { 273 | float a2 = pow(roughness, 4.0); 274 | float GGXV = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2); 275 | float GGXL = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2); 276 | return 0.5 / (GGXV + GGXL); 277 | } 278 | 279 | // Compute LUT for GGX distribution. 280 | // See https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf 281 | vec3 LUT(float NdotV, float roughness) 282 | { 283 | // Compute spherical view vector: (sin(phi), 0, cos(phi)) 284 | vec3 V = vec3(sqrt(1.0 - NdotV * NdotV), 0.0, NdotV); 285 | 286 | // The macro surface normal just points up. 287 | vec3 N = vec3(0.0, 0.0, 1.0); 288 | 289 | // To make the LUT independant from the material's F0, which is part of the Fresnel term 290 | // when substituted by Schlick's approximation, we factor it out of the integral, 291 | // yielding to the form: F0 * I1 + I2 292 | // I1 and I2 are slighlty different in the Fresnel term, but both only depend on 293 | // NoL and roughness, so they are both numerically integrated and written into two channels. 294 | float A = 0; 295 | float B = 0; 296 | float C = 0; 297 | 298 | for(uint i = 0; i < pfp_sampleCount; ++i) 299 | { 300 | // Importance sampling, depending on the distribution. 301 | vec3 H = getSampleVector(i, N, roughness); 302 | vec3 L = normalize(reflect(-V, H)); 303 | 304 | float NdotL = saturate(L.z); 305 | float NdotH = saturate(H.z); 306 | float VdotH = saturate(dot(V, H)); 307 | if (NdotL > 0.0) 308 | { 309 | if (pfp_distribution == cGGX) 310 | { 311 | // LUT for GGX distribution. 312 | 313 | // Taken from: https://bruop.github.io/ibl 314 | // Shadertoy: https://www.shadertoy.com/view/3lXXDB 315 | // Terms besides V are from the GGX PDF we're dividing by. 316 | float V_pdf = V_SmithGGXCorrelated(NdotV, NdotL, roughness) * VdotH * NdotL / NdotH; 317 | float Fc = pow(1.0 - VdotH, 5.0); 318 | A += (1.0 - Fc) * V_pdf; 319 | B += Fc * V_pdf; 320 | C += 0; 321 | } 322 | 323 | if (pfp_distribution == cCharlie) 324 | { 325 | // LUT for Charlie distribution. 326 | 327 | float sheenDistribution = D_Charlie(roughness, NdotH); 328 | float sheenVisibility = V_Ashikhmin(NdotL, NdotV); 329 | 330 | A += 0; 331 | B += 0; 332 | C += sheenVisibility * sheenDistribution * NdotL * VdotH; 333 | } 334 | } 335 | } 336 | 337 | // The PDF is simply pdf(v, h) -> NDF * . 338 | // To parametrize the PDF over l, use the Jacobian transform, yielding to: pdf(v, l) -> NDF * / 4 339 | // Since the BRDF divide through the PDF to be normalized, the 4 can be pulled out of the integral. 340 | return vec3(4.0 * A, 4.0 * B, 4.0 * 2.0 * UX3D_MATH_PI * C) / pfp_sampleCount; 341 | } 342 | 343 | void main() 344 | { 345 | vec2 newUV = UV * float(1 << (pfp_currentMipLevel)); 346 | 347 | newUV = newUV*2.0-1.0; 348 | 349 | for(int face = 0; face < 6; ++face) 350 | { 351 | vec3 scan = uvToXYZ(face, newUV); 352 | 353 | vec3 direction = normalize(scan); 354 | direction.y = -direction.y; 355 | 356 | writeFace(face, filterColor(direction)); 357 | 358 | //Debug output: 359 | //writeFace(face, texture(uCubeMap, direction).rgb); 360 | //writeFace(face, direction); 361 | } 362 | 363 | // Write LUT: 364 | // x-coordinate: NdotV 365 | // y-coordinate: roughness 366 | if (pfp_currentMipLevel == 0) 367 | { 368 | 369 | outLUT = LUT(UV.x, UV.y); 370 | 371 | } 372 | } -------------------------------------------------------------------------------- /src/window.c: -------------------------------------------------------------------------------- 1 | #include "GLFW/glfw3.h" 2 | #include "cimgui.h" 3 | #include "client.h" 4 | #include "entity.h" 5 | #include "utils.h" 6 | #include "utils.h" 7 | #include "window.h" 8 | #include 9 | 10 | static void apply_fallback_resolution(unsigned int *width, unsigned int *height) { 11 | GLFWmonitor *primary_monitor = glfwGetPrimaryMonitor(); 12 | if (!primary_monitor) { 13 | return; 14 | } 15 | 16 | const GLFWvidmode *video_mode = glfwGetVideoMode(primary_monitor); 17 | if (!video_mode) { 18 | return; 19 | } 20 | 21 | if (*width == 0) { 22 | *width = video_mode->width; 23 | } 24 | 25 | if (*height == 0) { 26 | *height = video_mode->height; 27 | } 28 | } 29 | 30 | static void recalculate_cursor_ray(void) { 31 | // Normalize the cursor position to the [-1, 1] range (normalized device space). 32 | double normalized_x = -1.0 + 2.0 * client.window.cursor_pos_x / (double) client.window.width; 33 | double normalized_y = 1.0 - 2.0 * client.window.cursor_pos_y / (double) client.window.height; 34 | // double normalized_y = -(1.0 - 2.0 * client.window.cursor_pos_y / (double) client.window.height); 35 | vec2 n = {normalized_x, normalized_y}; 36 | 37 | // Generate the inverse view-projection matrix, to convert from normalized device space to world space. 38 | mat4 view_projection_matrix, view_projection_inverse; 39 | glm_mat4_mul(client.renderer.projection_matrix, client.camera.view_matrix, view_projection_matrix); 40 | glm_mat4_inv(view_projection_matrix, view_projection_inverse); 41 | 42 | vec4 ray_start, ray_end; 43 | glm_mat4_mulv(view_projection_inverse, (vec4) { n[0], n[1], 0.f, 1.f}, ray_start); 44 | glm_vec4_scale(ray_start, 1.f / ray_start[3], ray_start); 45 | glm_mat4_mulv(view_projection_inverse, (vec4) { n[0], n[1], 1.f, 1.f}, ray_end); 46 | glm_vec4_scale(ray_end, 1.f / ray_end[3], ray_end); 47 | 48 | glm_vec4_copy(ray_start, client.window.cursor_ray_origin); 49 | glm_vec4_sub(ray_end, ray_start, client.window.cursor_ray_direction); 50 | glm_normalize(client.window.cursor_ray_direction); 51 | 52 | /* 53 | if (tracking) { 54 | for (size_t i = 0; i < client.scene.entity_count; i++) { 55 | struct entity *entity = client.scene.entities[i]; 56 | if (client.ui.selected_entity_id == entity->id) { 57 | float distance = tmp; 58 | 59 | vec3 position, direction_further; 60 | glm_vec3_mul(direction, (vec3) { distance, distance, distance}, direction_further); 61 | glm_vec3_copy(origin, position); 62 | glm_vec3_add(position, direction_further, position); 63 | 64 | // printf("-> %f %f %f\n", position[0], position[1], position[2]); 65 | glm_vec3_copy(position, entity->translation); 66 | } 67 | } 68 | } 69 | */ 70 | } 71 | 72 | static void scroll_callback(GLFWwindow *glfw_window, double xoffset, double yoffset) { 73 | UNUSED(glfw_window); 74 | UNUSED(xoffset); 75 | UNUSED(yoffset); 76 | 77 | client.camera.eye_distance += -yoffset * 0.2f; 78 | if (client.camera.eye_distance <= 0.00001) { 79 | client.camera.eye_distance = 0.00001; 80 | } 81 | 82 | camera_update(&client.camera); 83 | } 84 | 85 | static void cursor_pos_callback(GLFWwindow *glfw_window, double x, double y) { 86 | UNUSED(glfw_window); 87 | 88 | double delta_x = x - client.window.cursor_pos_x; 89 | double delta_y = y - client.window.cursor_pos_y; 90 | 91 | client.window.cursor_pos_x = x; 92 | client.window.cursor_pos_y = y; 93 | 94 | recalculate_cursor_ray(); 95 | 96 | if (client.ui.ig_io->WantCaptureMouse) { 97 | return; 98 | } 99 | 100 | if (glfwGetMouseButton(glfw_window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS) { 101 | client.camera.center_rotation += -delta_x * 0.005f; 102 | 103 | client.camera.eye_above += delta_y * 0.005f; 104 | if (client.camera.eye_above >= M_PI_2) { 105 | client.camera.eye_above = M_PI_2 - 0.00001f; 106 | } 107 | 108 | if (client.camera.eye_above < -M_PI_2) { 109 | client.camera.eye_above = -M_PI_2 + 0.00001f; 110 | } 111 | 112 | camera_update(&client.camera); 113 | } 114 | } 115 | 116 | static void mouse_button_callback(GLFWwindow *glfw_window, int button, int action, int mods) { 117 | UNUSED(glfw_window); 118 | UNUSED(mods); 119 | 120 | // Mouse picking, as long as the UI doesn't intercept it. 121 | if (button == GLFW_MOUSE_BUTTON_1 && !client.ui.ig_io->WantCaptureMouse) { 122 | static uint32_t saved = 0; 123 | 124 | if (action == GLFW_PRESS) { 125 | saved = client.renderer.mousepicking_entity_id; 126 | } else if (action == GLFW_RELEASE && saved == client.renderer.mousepicking_entity_id) { 127 | client.ui.selected_entity_id = saved; 128 | } 129 | } 130 | } 131 | 132 | static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) { 133 | UNUSED(window); 134 | UNUSED(scancode); 135 | UNUSED(mods); 136 | 137 | if (client.ui.ig_io->WantTextInput) { 138 | return; 139 | } 140 | 141 | if (key == GLFW_KEY_F1 && action == GLFW_PRESS) { 142 | client.ui.show = !client.ui.show; 143 | 144 | /* 145 | if (client.ui.show) { 146 | glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); 147 | } else { 148 | glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); 149 | } 150 | */ 151 | } 152 | 153 | if (key == GLFW_KEY_W) { 154 | if (action == GLFW_PRESS) { 155 | client.moving |= FORWARD; 156 | } else if (action == GLFW_RELEASE) { 157 | client.moving &= ~FORWARD; 158 | } 159 | } else if (key == GLFW_KEY_A) { 160 | if (action == GLFW_PRESS) { 161 | client.moving |= LEFT; 162 | } else if (action == GLFW_RELEASE) { 163 | client.moving &= ~LEFT; 164 | } 165 | } else if (key == GLFW_KEY_S) { 166 | if (action == GLFW_PRESS) { 167 | client.moving |= BACKWARD; 168 | } else if (action == GLFW_RELEASE) { 169 | client.moving &= ~BACKWARD; 170 | } 171 | } else if (key == GLFW_KEY_D) { 172 | if (action == GLFW_PRESS) { 173 | client.moving |= RIGHT; 174 | } else if (action == GLFW_RELEASE) { 175 | client.moving &= ~RIGHT; 176 | } 177 | } 178 | } 179 | 180 | static void window_size_callback(GLFWwindow *window, int width, int height) { 181 | UNUSED(window); 182 | 183 | client.window.width = width; 184 | client.window.height = height; 185 | } 186 | 187 | static void framebuffer_resize_callback(GLFWwindow *window, int width, int height) { 188 | UNUSED(window); 189 | 190 | client.renderer.viewport_width = width; 191 | client.renderer.viewport_height = height; 192 | glViewport(0, 0, width, height); 193 | } 194 | 195 | // TODO: Would be nice to use this functionality for something. 196 | static void drop_callback(GLFWwindow *window, int nb, const char *paths[]) { 197 | UNUSED(window); 198 | 199 | for (int i = 0; i < nb; i++) { 200 | printf("--> %s\n", paths[i]); 201 | } 202 | } 203 | 204 | void window_fullscreen(struct window *window, bool fullscreen) { 205 | static int original_width, original_height, original_x, original_y; 206 | 207 | if (fullscreen) { 208 | glfwGetWindowSize(window->glfw_window, &original_width, &original_height); 209 | glfwGetWindowPos(window->glfw_window, &original_x, &original_y); 210 | 211 | GLFWmonitor *monitor = glfwGetPrimaryMonitor(); 212 | const GLFWvidmode *vidmode = glfwGetVideoMode(monitor); 213 | glfwSetWindowMonitor(window->glfw_window, monitor, 0, 0, vidmode->width, vidmode->height, vidmode->refreshRate); 214 | } else { 215 | glfwSetWindowMonitor(window->glfw_window, NULL, original_x, original_y, original_width, original_height, 0); 216 | } 217 | 218 | window->fullscreen = fullscreen; 219 | } 220 | 221 | bool window_init(struct window *window, unsigned int width, unsigned int height, const char *title, bool fullscreen) { 222 | window->width = width; 223 | window->height = height; 224 | window->samples = 4; // TODO: Support changing the number of samples. This requires recreating the window and sharing the context. 225 | window->fullscreen = fullscreen; 226 | window->title = strdup(title); 227 | 228 | window->last_time = glfwGetTime(); 229 | window->now_time = glfwGetTime(); 230 | 231 | if (!window->title) { 232 | return false; 233 | } 234 | 235 | // Automatically initializes the GLFW library for the first window created. 236 | if (glfwInit() == GLFW_FALSE) { 237 | free(window->title); 238 | return false; 239 | } 240 | 241 | // Use the primary monitor's resolution when dimensions weren't specified. 242 | apply_fallback_resolution(&window->width, &window->height); 243 | 244 | // GLFW window hints. 245 | glfwWindowHint(GLFW_RESIZABLE, true); 246 | glfwWindowHint(GLFW_DOUBLEBUFFER, true); 247 | glfwWindowHint(GLFW_SAMPLES, window->samples); // Multisampling. 248 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Mac only supports OpenGL 3.2 or 4.1. 249 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); 250 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Modern rendering pipeline. 251 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true); // Mac OS X requires forward compatibility. 252 | 253 | // Fullscreen mode always uses the primary monitor for now. 254 | GLFWmonitor *monitor = fullscreen ? glfwGetPrimaryMonitor() : NULL; 255 | 256 | // Create the actual window using the GLFW library. 257 | window->glfw_window = glfwCreateWindow(window->width, window->height, title, monitor, NULL); 258 | if (!window->glfw_window) { 259 | glfwTerminate(); 260 | free(window->title); 261 | return false; 262 | } 263 | 264 | // Make the current thread use the new window's OpenGL context so that we can initialize OpenGL for it. 265 | glfwMakeContextCurrent(window->glfw_window); 266 | 267 | // Initialize OpenGL. 268 | if (!gladLoadGL()) { 269 | glfwDestroyWindow(window->glfw_window); 270 | glfwTerminate(); 271 | free(window->title); 272 | return false; 273 | } 274 | 275 | // Minimum number of monitor refreshes the driver should wait after the call to glfwSwapBuffers before actually swapping the buffers on the display. 276 | // Essentially, 0 = V-Sync off, 1 = V-Sync on. Leaving this on avoids ugly tearing artifacts. 277 | // It requires the OpenGL context to be effective on Windows. 278 | glfwSwapInterval(1); 279 | 280 | // Initial cursor position. 281 | glfwGetCursorPos(window->glfw_window, &window->cursor_pos_x, &window->cursor_pos_y); 282 | 283 | // Configure callbacks. 284 | glfwSetCursorPosCallback(window->glfw_window, cursor_pos_callback); 285 | glfwSetFramebufferSizeCallback(window->glfw_window, framebuffer_resize_callback); 286 | glfwSetWindowSizeCallback(window->glfw_window, window_size_callback); 287 | glfwSetKeyCallback(window->glfw_window, key_callback); 288 | glfwSetMouseButtonCallback(window->glfw_window, mouse_button_callback); 289 | glfwSetScrollCallback(window->glfw_window, scroll_callback); 290 | glfwSetDropCallback(window->glfw_window, drop_callback); 291 | 292 | return true; 293 | } 294 | 295 | double window_elapsed(const struct window *window) { 296 | return window->now_time - window->last_time; 297 | } 298 | 299 | void window_fini(struct window *window) { 300 | free(window->title); 301 | glfwDestroyWindow(window->glfw_window); 302 | glfwTerminate(); 303 | } 304 | 305 | void window_update_title(struct window *window, const char *title) { 306 | char *title_copy = strdup(title); 307 | if (!title_copy) { 308 | return; 309 | } 310 | 311 | free(window->title); 312 | window->title = title_copy; 313 | glfwSetWindowTitle(window->glfw_window, title_copy); 314 | } 315 | 316 | void window_close(struct window *window, int force) { 317 | glfwSetWindowShouldClose(window->glfw_window, 1); 318 | 319 | if (force) { 320 | glfwDestroyWindow(window->glfw_window); 321 | window->glfw_window = NULL; 322 | } 323 | } 324 | 325 | bool window_closed(const struct window *window) { 326 | if (window->glfw_window == NULL) { 327 | return true; 328 | } 329 | 330 | return glfwWindowShouldClose(window->glfw_window) == 1; 331 | } 332 | 333 | void window_poll_events(struct window *window) { 334 | UNUSED(window); 335 | glfwPollEvents(); 336 | window->now_time = glfwGetTime(); 337 | } 338 | 339 | void window_framebuffer_size(const struct window *window, int *width, int *height) { 340 | glfwGetFramebufferSize(window->glfw_window, width, height); 341 | } 342 | 343 | bool window_extension_supported(const char *name) { 344 | return glfwExtensionSupported(name) == GLFW_TRUE; 345 | } 346 | 347 | void window_refresh(struct window *window) { 348 | glfwSwapBuffers(window->glfw_window); 349 | window->last_time = window->now_time; 350 | } 351 | -------------------------------------------------------------------------------- /src/model.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | INCBIN(shaders_pbr_main_vert, "../shaders/pbr/main.vert"); 4 | INCBIN(shaders_pbr_main_frag, "../shaders/pbr/main.frag"); 5 | 6 | static void apply_material_to_mesh(const cgltf_data *gltf, const cgltf_material *material, struct mesh *mesh, struct shader_options *options) { 7 | mesh->material.name = strdup(material->name); 8 | 9 | // Metallic/roughness workflow (optional). 10 | if (material->has_pbr_metallic_roughness) { 11 | const cgltf_pbr_metallic_roughness *mr = &material->pbr_metallic_roughness; 12 | 13 | options->material_metallicroughness = true; 14 | 15 | // Base color factor. 16 | glm_vec3_copy(mr->base_color_factor, mesh->material.base_color_factor); 17 | 18 | // Base color texture (optional). 19 | if (mr->base_color_texture.texture) { 20 | options->has_base_color_map = true; 21 | 22 | struct texture *texture = malloc(sizeof *texture); 23 | texture_init_from_memory(texture, TEXTURE_KIND_ALBEDO, 24 | gltf->bin + mr->base_color_texture.texture->image->buffer_view->offset, 25 | mr->base_color_texture.texture->image->buffer_view->size 26 | ); 27 | 28 | mesh->material.base_color_texture = texture; 29 | } 30 | 31 | // Metallic/roughness texture (optional). 32 | if (mr->metallic_roughness_texture.texture) { 33 | options->has_metallic_roughness_map = true; 34 | 35 | struct texture *texture = malloc(sizeof *texture); 36 | texture_init_from_memory(texture, TEXTURE_KIND_METALLIC_ROUGHNESS, 37 | gltf->bin + mr->metallic_roughness_texture.texture->image->buffer_view->offset, 38 | mr->metallic_roughness_texture.texture->image->buffer_view->size 39 | ); 40 | 41 | mesh->material.metallic_roughness_texture = texture; 42 | } 43 | 44 | // Metallic factor. 45 | mesh->material.metallic_factor = mr->metallic_factor; 46 | 47 | // Roughness factor. 48 | mesh->material.roughness_factor = mr->roughness_factor; 49 | } 50 | 51 | // Normal texture (optional). 52 | if (material->normal_texture.texture) { 53 | options->has_normal_map = true; 54 | 55 | struct texture *texture = malloc(sizeof *texture); 56 | texture_init_from_memory(texture, TEXTURE_KIND_NORMAL, 57 | gltf->bin + material->normal_texture.texture->image->buffer_view->offset, 58 | material->normal_texture.texture->image->buffer_view->size 59 | ); 60 | 61 | mesh->material.normal_texture = texture; 62 | } 63 | 64 | // Occlusion texture (optional). 65 | if (material->occlusion_texture.texture) { 66 | options->has_occlusion_map = true; 67 | 68 | struct texture *texture = malloc(sizeof *texture); 69 | texture_init_from_memory(texture, TEXTURE_KIND_OCCLUSION, 70 | gltf->bin + material->occlusion_texture.texture->image->buffer_view->offset, 71 | material->occlusion_texture.texture->image->buffer_view->size 72 | ); 73 | 74 | mesh->material.occlusion_texture = texture; 75 | } 76 | 77 | // Emissive factor. 78 | glm_vec3_copy(material->emissive_factor, mesh->material.emissive_factor); 79 | 80 | // Emissive texture (optional). 81 | if (material->emissive_texture.texture) { 82 | options->has_emissive_map = true; 83 | 84 | struct texture *texture = malloc(sizeof *texture); 85 | texture_init_from_memory(texture, TEXTURE_KIND_EMISSION, 86 | gltf->bin + material->emissive_texture.texture->image->buffer_view->offset, 87 | material->emissive_texture.texture->image->buffer_view->size 88 | ); 89 | 90 | mesh->material.emissive_texture = texture; 91 | } 92 | 93 | // Double-sided. 94 | mesh->material.double_sided = material->double_sided; 95 | 96 | // Extensions. 97 | for (size_t i = 0; i < material->extensions_count; i++) { 98 | cgltf_extension *extension = &material->extensions[i]; 99 | 100 | // Unlit extension. 101 | if (strcmp(extension->name, "KHR_materials_unlit") == 0) { 102 | options->material_unlit = true; 103 | } 104 | } 105 | } 106 | 107 | static void accessor_extract_data_count_stride(const cgltf_data *gltf, const cgltf_accessor *accessor, const void **data, size_t *count, size_t *stride) { 108 | const char *ptr = NULL; 109 | 110 | if (!ptr && accessor->buffer_view->buffer->data) { 111 | ptr = accessor->buffer_view->buffer->data; 112 | } 113 | 114 | if (!ptr && accessor->buffer_view->data) { 115 | ptr = accessor->buffer_view->data; 116 | } 117 | 118 | if (!ptr && gltf->bin) { 119 | ptr = gltf->bin; 120 | } 121 | 122 | *data = ptr + accessor->offset + accessor->buffer_view->offset; 123 | *count = accessor->count; 124 | *stride = accessor->stride; 125 | } 126 | 127 | static void apply_attributes_to_mesh(const cgltf_data *gltf, const cgltf_primitive *primitive, struct mesh *mesh, struct shader_options *options) { 128 | const void *data = NULL; 129 | size_t count = 0; 130 | size_t stride = 0; 131 | 132 | for (size_t attribute_i = 0; attribute_i < primitive->attributes_count; attribute_i++) { 133 | const cgltf_attribute *attribute = primitive->attributes + attribute_i; 134 | 135 | switch (attribute->type) { 136 | case cgltf_attribute_type_position: 137 | accessor_extract_data_count_stride(gltf, attribute->data, &data, &count, &stride); 138 | mesh_provide_vertices(mesh, data, count, stride); 139 | break; 140 | 141 | case cgltf_attribute_type_normal: 142 | accessor_extract_data_count_stride(gltf, attribute->data, &data, &count, &stride); 143 | mesh_provide_normals(mesh, data, count, stride); 144 | options->has_normals = true; 145 | break; 146 | 147 | case cgltf_attribute_type_tangent: 148 | accessor_extract_data_count_stride(gltf, attribute->data, &data, &count, &stride); 149 | mesh_provide_tangents(mesh, data, count, stride); 150 | options->has_tangents = true; 151 | break; 152 | 153 | case cgltf_attribute_type_texcoord: 154 | accessor_extract_data_count_stride(gltf, attribute->data, &data, &count, &stride); 155 | mesh_provide_uvs(mesh, data, count, stride); 156 | options->has_uv_set1 = true; 157 | break; 158 | 159 | case cgltf_attribute_type_weights: 160 | accessor_extract_data_count_stride(gltf, attribute->data, &data, &count, &stride); 161 | mesh_provide_weights(mesh, data, count, stride); 162 | options->has_weight_set1 = true; 163 | break; 164 | 165 | case cgltf_attribute_type_joints: 166 | accessor_extract_data_count_stride(gltf, attribute->data, &data, &count, &stride); 167 | mesh_provide_joints(mesh, data, count, stride); 168 | options->has_joint_set1 = true; 169 | options->joint_count = count; 170 | break; 171 | 172 | case cgltf_attribute_type_color: { 173 | int components = 0; 174 | 175 | if (attribute->data->type == cgltf_type_vec3) { 176 | options->has_color_vec3 = true; 177 | components = 3; 178 | } else if (attribute->data->type == cgltf_type_vec4) { 179 | options->has_color_vec4 = true; 180 | components = 4; 181 | } else { 182 | break; 183 | } 184 | 185 | accessor_extract_data_count_stride(gltf, attribute->data, &data, &count, &stride); 186 | mesh_provide_colors(mesh, data, count, stride, components); 187 | } 188 | break; 189 | 190 | default: 191 | break; 192 | } 193 | } 194 | 195 | // Indices. 196 | switch (primitive->indices->component_type) { 197 | case cgltf_component_type_r_8: mesh->indices_type = GL_BYTE; break; 198 | case cgltf_component_type_r_8u: mesh->indices_type = GL_UNSIGNED_BYTE; break; 199 | case cgltf_component_type_r_16: mesh->indices_type = GL_SHORT; break; 200 | case cgltf_component_type_r_16u: mesh->indices_type = GL_UNSIGNED_SHORT; break; 201 | case cgltf_component_type_r_32u: mesh->indices_type = GL_UNSIGNED_INT; break; 202 | default: 203 | fprintf(stderr, "Unsupported mesh indices type\n"); 204 | return; 205 | } 206 | 207 | accessor_extract_data_count_stride(gltf, primitive->indices, &data, &count, &stride); 208 | 209 | if (count) { 210 | mesh_provide_indices(mesh, data, count, mesh->indices_type); 211 | } 212 | } 213 | 214 | static void apply_transforms_to_mesh(const cgltf_data *gltf, const cgltf_node *node, struct mesh *mesh, int mesh_index, mat4 previous_transform) { 215 | mat4 current_transform; 216 | glm_mat4_copy(previous_transform, current_transform); 217 | 218 | if (node->has_matrix) { 219 | mat4 tmp; 220 | glm_mat4_ucopy(node->matrix, tmp); 221 | glm_mat4_mul(current_transform, tmp, current_transform); 222 | } else { 223 | if (node->has_translation) { 224 | glm_translate(current_transform, node->translation); 225 | } 226 | 227 | if (node->has_rotation) { 228 | glm_quat_rotate(current_transform, (versor) { node->rotation[0], node->rotation[1], node->rotation[2], node->rotation[3]}, current_transform); 229 | } 230 | 231 | if (node->has_scale) { 232 | glm_scale(current_transform, node->scale); 233 | } 234 | } 235 | 236 | // Find a node corresponding to our mesh. 237 | if (node->mesh == gltf->meshes + mesh_index) { 238 | glm_mat4_copy(current_transform, mesh->initial_transform); 239 | return; 240 | } 241 | 242 | for (size_t i = 0; i < node->children_count; i++) { 243 | cgltf_node *child = node->children[i]; 244 | apply_transforms_to_mesh(gltf, child, mesh, mesh_index, current_transform); 245 | } 246 | } 247 | 248 | bool load_meshes(struct model *model, const cgltf_data *gltf) { 249 | size_t mesh_count = 0; 250 | 251 | // Find out how many meshes there are. 252 | for (size_t i = 0; i < gltf->meshes_count; i++) { 253 | for (size_t j = 0; j < gltf->meshes[i].primitives_count; j++) { 254 | // But only consider meshes with triangle primitives. 255 | if (gltf->meshes[i].primitives[j].type == cgltf_primitive_type_triangles) { 256 | mesh_count++; 257 | } 258 | } 259 | } 260 | 261 | model->meshes = malloc(mesh_count * sizeof *model->meshes); 262 | if (!model->meshes) { 263 | return false; 264 | } 265 | 266 | model->meshes_count = mesh_count; 267 | for (size_t i = 0; i < mesh_count; i++) { 268 | mesh_init(&model->meshes[i]); 269 | } 270 | 271 | // Loading the glTF meshes into our meshes. 272 | size_t final_mesh_i = 0; 273 | for (size_t mesh_i = 0; mesh_i < gltf->meshes_count; mesh_i++) { 274 | for (size_t primitive_i = 0; primitive_i < gltf->meshes[mesh_i].primitives_count; primitive_i++) { 275 | const cgltf_primitive *primitive = gltf->meshes[mesh_i].primitives + primitive_i; 276 | 277 | // Only support triangle primitives. 278 | if (primitive->type != cgltf_primitive_type_triangles) { 279 | fprintf(stderr, "Unsupported primitives\n"); 280 | return false; 281 | } 282 | 283 | struct shader_options options = { 284 | .tonemap_uncharted = true, 285 | .use_hdr = true, 286 | .use_ibl = true, 287 | // .use_punctual = true, 288 | // .light_count = MAX_LIGHTS, 289 | }; 290 | 291 | struct mesh *mesh = &model->meshes[final_mesh_i++]; 292 | 293 | // Attributes. 294 | apply_attributes_to_mesh(gltf, primitive, mesh, &options); 295 | 296 | // Material (optional). 297 | if (primitive->material) { 298 | apply_material_to_mesh(gltf, primitive->material, mesh, &options); 299 | } 300 | 301 | // Initial transform. 302 | for (size_t i = 0; i < gltf->scene->nodes_count; i++) { 303 | apply_transforms_to_mesh(gltf, gltf->scene->nodes[i], mesh, mesh_i, mesh->initial_transform); 304 | } 305 | 306 | // Skinning. 307 | if (options.has_weight_set1 && options.has_joint_set1) { 308 | // FIXME: Enable skinning once it's supported. 309 | // options.use_skinning = true; 310 | } 311 | 312 | // Shader. 313 | // TODO: Shader caching based on the #defines that it needs. Each mesh having their own shader is a bit wasteful otherwise. 314 | mesh->shader = shader_load_from_memory(&options, shaders_pbr_main_vert_data, shaders_pbr_main_vert_size, shaders_pbr_main_frag_data, shaders_pbr_main_frag_size, NULL, 0); 315 | if (!mesh->shader) { 316 | return false; 317 | } 318 | } 319 | } 320 | 321 | return true; 322 | } 323 | 324 | struct model *model_load(const char *filepath) { 325 | struct model *model = malloc(sizeof *model); 326 | if (!model) { 327 | return NULL; 328 | } 329 | 330 | cgltf_data *gltf = NULL; 331 | cgltf_options options = {0}; 332 | 333 | if (cgltf_parse_file(&options, filepath, &gltf) != cgltf_result_success) { 334 | free(model); 335 | return NULL; 336 | } 337 | 338 | model->meshes = NULL; 339 | model->meshes_count = 0; 340 | 341 | // Model name is the filepath for now. 342 | model->filepath = strdup(filepath); 343 | 344 | // Load file/base64 buffers. 345 | if (cgltf_load_buffers(&options, gltf, filepath) != cgltf_result_success) { 346 | cgltf_free(gltf); 347 | free(model); 348 | return NULL; 349 | } 350 | 351 | bool loaded = load_meshes(model, gltf); 352 | 353 | cgltf_free(gltf); 354 | 355 | if (!loaded) { 356 | model_destroy(model); 357 | return NULL; 358 | } 359 | 360 | return model; 361 | } 362 | 363 | void model_destroy(struct model *model) { 364 | if (model->meshes) { 365 | for (size_t i = 0; i < model->meshes_count; i++) { 366 | mesh_fini(&model->meshes[i]); 367 | } 368 | 369 | free(model->meshes); 370 | model->meshes = NULL; 371 | model->meshes_count = 0; 372 | } 373 | 374 | free(model->filepath); 375 | free(model); 376 | } 377 | -------------------------------------------------------------------------------- /src/environment.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | // FIXME: This needs to embedded the newer files on changes, maybe even hot-reload? 4 | INCBIN(shaders_equirect2cube_main_vert, "../shaders/equirect2cube/main.vert"); 5 | INCBIN(shaders_equirect2cube_main_frag, "../shaders/equirect2cube/main.frag"); 6 | INCBIN(shaders_iblsampler_main_vert, "../shaders/iblsampler/main.vert"); 7 | INCBIN(shaders_iblsampler_main_frag, "../shaders/iblsampler/main.frag"); 8 | 9 | static bool convert_equirectangular_to_cubemap(const struct texture *equirectangular, struct texture *cubemap) { 10 | // Load & convert equirectangular environment map to a cubemap texture. 11 | struct shader *equirect2cube_shader = shader_load_from_memory(NULL, 12 | shaders_equirect2cube_main_vert_data, shaders_equirect2cube_main_vert_size, 13 | shaders_equirect2cube_main_frag_data, shaders_equirect2cube_main_frag_size, 14 | NULL, 0 15 | ); 16 | 17 | if (!equirect2cube_shader) { 18 | return false; 19 | } 20 | 21 | glUseProgram(equirect2cube_shader->program_id); 22 | 23 | int width = 2048, height = 2048; 24 | 25 | struct framebuffer fb; 26 | 27 | framebuffer_init(&fb, width, height); 28 | 29 | // FIXME: The code below is doing a lot of OpenGL things directly instead of working with our abstractions. 30 | 31 | GLuint cubemap_id; 32 | glGenTextures(1, &cubemap_id); 33 | glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap_id); 34 | for (size_t face = 0; face < 6; face++) { 35 | glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, NULL); 36 | } 37 | 38 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 39 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 40 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); 41 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 42 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 43 | 44 | mat4 projection; 45 | glm_perspective(glm_rad(90), 1, 0.1, 1000.0, projection); 46 | 47 | mat4 views[6]; 48 | glm_lookat((vec3) { 0, 0, 0}, (vec3) { 1, 0, 0}, (vec3) { 0, -1, 0}, views[0]); 49 | glm_lookat((vec3) { 0, 0, 0}, (vec3) { -1, 0, 0}, (vec3) { 0, -1, 0}, views[1]); 50 | glm_lookat((vec3) { 0, 0, 0}, (vec3) { 0, 1, 0}, (vec3) { 0, 0, 1}, views[2]); 51 | glm_lookat((vec3) { 0, 0, 0}, (vec3) { 0, -1, 0}, (vec3) { 0, 0, -1}, views[3]); 52 | glm_lookat((vec3) { 0, 0, 0}, (vec3) { 0, 0, 1}, (vec3) { 0, -1, 0}, views[4]); 53 | glm_lookat((vec3) { 0, 0, 0}, (vec3) { 0, 0, -1}, (vec3) { 0, -1, 0}, views[5]); 54 | 55 | glUseProgram(equirect2cube_shader->program_id); 56 | GLint equirectangular_map_location = glGetUniformLocation(equirect2cube_shader->program_id, "equirectangularMap"); 57 | glUniform1i(equirectangular_map_location, 0); 58 | GLint projection_location = glGetUniformLocation(equirect2cube_shader->program_id, "projection"); 59 | glUniformMatrix4fv(projection_location, 1, false, (float *) projection); 60 | 61 | glActiveTexture(GL_TEXTURE0); 62 | glBindTexture(GL_TEXTURE_2D, equirectangular->gl_id); 63 | 64 | glViewport(0, 0, width, height); 65 | glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo); 66 | for (unsigned int i = 0; i < 6; ++i) { 67 | GLint view_location = glGetUniformLocation(equirect2cube_shader->program_id, "view"); 68 | glUniformMatrix4fv(view_location, 1, false, (float *) views[i]); 69 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemap_id, 0); 70 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 71 | utils_render_cube(); 72 | } 73 | 74 | shader_destroy(equirect2cube_shader); 75 | 76 | // TODO: This should use the texture module and not be created manually here. 77 | cubemap->gl_id = cubemap_id; 78 | cubemap->gl_unit = TEXTURE_KIND_CUBEMAP; 79 | cubemap->kind = TEXTURE_KIND_CUBEMAP; 80 | cubemap->gl_target = GL_TEXTURE_CUBE_MAP; 81 | cubemap->width = width; 82 | 83 | // Cleanup. 84 | framebuffer_fini(&fb); 85 | 86 | return true; 87 | } 88 | 89 | bool environment_init_from_file(struct environment *environment, const char *filepath) { 90 | struct texture equirectangular; 91 | if (!texture_init_from_file(&equirectangular, TEXTURE_KIND_EQUIRECTANGULAR, filepath)) { 92 | return false; 93 | } 94 | 95 | if (!convert_equirectangular_to_cubemap(&equirectangular, &environment->cubemap)) { 96 | texture_fini(&equirectangular); 97 | return false; 98 | } 99 | 100 | texture_fini(&equirectangular); 101 | 102 | struct shader *iblsampler_shader = shader_load_from_memory(NULL, 103 | shaders_iblsampler_main_vert_data, shaders_iblsampler_main_vert_size, 104 | shaders_iblsampler_main_frag_data, shaders_iblsampler_main_frag_size, 105 | NULL, 0 106 | ); 107 | 108 | if (!iblsampler_shader) { 109 | return false; 110 | } 111 | 112 | environment->mip_count = 10; 113 | int sample_count = 1024; 114 | size_t width = 1024, height = 1024; 115 | 116 | glUseProgram(iblsampler_shader->program_id); 117 | 118 | GLint pfp_roughness_location = glGetUniformLocation(iblsampler_shader->program_id, "pfp_roughness"); 119 | GLint pfp_samplecount_location = glGetUniformLocation(iblsampler_shader->program_id, "pfp_sampleCount"); 120 | GLint pfp_miplevel_location = glGetUniformLocation(iblsampler_shader->program_id, "pfp_currentMipLevel"); 121 | GLint pfp_width_location = glGetUniformLocation(iblsampler_shader->program_id, "pfp_width"); 122 | GLint pfp_lodbias_location = glGetUniformLocation(iblsampler_shader->program_id, "pfp_lodBias"); 123 | GLint pfp_distribution_location = glGetUniformLocation(iblsampler_shader->program_id, "pfp_distribution"); 124 | 125 | struct framebuffer fb; 126 | framebuffer_init(&fb, width, height); 127 | 128 | // FIXME: The code below does a lot of OpenGL things without using our nice abstractions. 129 | 130 | glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo); 131 | glViewport(0, 0, width, height); 132 | 133 | // Lambertian 134 | GLuint lambertian_id; 135 | glGenTextures(1, &lambertian_id); 136 | glBindTexture(GL_TEXTURE_CUBE_MAP, lambertian_id); 137 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); 138 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, environment->mip_count - 1); 139 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 140 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); 141 | for (size_t mip = 0; mip < environment->mip_count; mip++) { 142 | for (size_t face = 0; face < 6; face++) { 143 | glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mip, GL_RGBA16F, width >> mip, height >> mip, 0, GL_RGBA, GL_FLOAT, NULL); 144 | } 145 | } 146 | 147 | // GGX 148 | GLuint ggx_id; 149 | glGenTextures(1, &ggx_id); 150 | glBindTexture(GL_TEXTURE_CUBE_MAP, ggx_id); 151 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); 152 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, environment->mip_count - 1); 153 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 154 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); 155 | for (size_t mip = 0; mip < environment->mip_count; mip++) { 156 | for (size_t face = 0; face < 6; face++) { 157 | glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mip, GL_RGBA16F, width >> mip, height >> mip, 0, GL_RGBA, GL_FLOAT, NULL); 158 | } 159 | } 160 | 161 | GLuint ggx_lut_id; 162 | glGenTextures(1, &ggx_lut_id); 163 | glBindTexture(GL_TEXTURE_2D, ggx_lut_id); 164 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, NULL); 165 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 166 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 167 | 168 | // Charlie 169 | GLuint charlie_id; 170 | glGenTextures(1, &charlie_id); 171 | glBindTexture(GL_TEXTURE_CUBE_MAP, charlie_id); 172 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); 173 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, environment->mip_count - 1); 174 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 175 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); 176 | for (size_t mip = 0; mip < environment->mip_count; mip++) { 177 | for (size_t face = 0; face < 6; face++) { 178 | glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mip, GL_RGBA16F, width >> mip, height >> mip, 0, GL_RGBA, GL_FLOAT, NULL); 179 | } 180 | } 181 | 182 | GLuint charlie_lut_id; 183 | glGenTextures(1, &charlie_lut_id); 184 | glBindTexture(GL_TEXTURE_2D, charlie_lut_id); 185 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, NULL); 186 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 187 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 188 | 189 | texture_switch(&environment->cubemap); 190 | GLint cubemap_location = glGetUniformLocation(iblsampler_shader->program_id, "uCubeMap"); 191 | glUniform1i(cubemap_location, environment->cubemap.gl_unit); 192 | 193 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 194 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 195 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); 196 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 197 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 198 | 199 | glUniform1ui(pfp_samplecount_location, sample_count); 200 | glUniform1ui(pfp_width_location, width); 201 | // glUniform1ui(pfp_width_location, environment->cubemap->width); // FIXME: The cubemap width or current mip? 202 | glUniform1f(pfp_lodbias_location, 0); 203 | 204 | const GLenum buffers[] = { 205 | GL_COLOR_ATTACHMENT0, 206 | GL_COLOR_ATTACHMENT1, 207 | GL_COLOR_ATTACHMENT2, 208 | GL_COLOR_ATTACHMENT3, 209 | GL_COLOR_ATTACHMENT4, 210 | GL_COLOR_ATTACHMENT5, 211 | GL_COLOR_ATTACHMENT6, // LUT 212 | }; 213 | 214 | glDrawBuffers(7, buffers); 215 | 216 | glBindFragDataLocation(iblsampler_shader->program_id, 0, "outFace0"); 217 | glBindFragDataLocation(iblsampler_shader->program_id, 1, "outFace1"); 218 | glBindFragDataLocation(iblsampler_shader->program_id, 2, "outFace2"); 219 | glBindFragDataLocation(iblsampler_shader->program_id, 3, "outFace3"); 220 | glBindFragDataLocation(iblsampler_shader->program_id, 4, "outFace4"); 221 | glBindFragDataLocation(iblsampler_shader->program_id, 5, "outFace5"); 222 | glBindFragDataLocation(iblsampler_shader->program_id, 6, "outLUT"); 223 | 224 | GLuint VAO; 225 | glGenVertexArrays(1, &VAO); 226 | glBindVertexArray(VAO); 227 | // glDisable(GL_CULL_FACE); 228 | 229 | for (int mip = environment->mip_count - 1; mip != -1; mip--) { 230 | glUniform1f(pfp_roughness_location, (float) mip / (float) (environment->mip_count - 1)); 231 | glUniform1ui(pfp_miplevel_location, mip); 232 | 233 | // Lambertian 234 | glUniform1ui(pfp_distribution_location, 0); 235 | for (size_t face = 0; face < 6; face++) { 236 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + face, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, lambertian_id, mip); 237 | } 238 | 239 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT6, GL_TEXTURE_2D, 0, 0); // Detach LUT. 240 | 241 | glDrawArrays(GL_TRIANGLES, 0, 3); 242 | 243 | // GGX 244 | glUniform1ui(pfp_distribution_location, 1); 245 | for (size_t face = 0; face < 6; face++) { 246 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + face, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, ggx_id, mip); 247 | } 248 | 249 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT6, GL_TEXTURE_2D, ggx_lut_id, 0); 250 | 251 | glDrawArrays(GL_TRIANGLES, 0, 3); 252 | 253 | // Charlie 254 | glUniform1ui(pfp_distribution_location, 2); 255 | for (size_t face = 0; face < 6; face++) { 256 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + face, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, charlie_id, mip); 257 | } 258 | 259 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT6, GL_TEXTURE_2D, charlie_lut_id, 0); 260 | 261 | glDrawArrays(GL_TRIANGLES, 0, 3); 262 | } 263 | 264 | // glEnable(GL_CULL_FACE); 265 | 266 | // FIXME: These are bypassing texture_init and I don't like it. 267 | 268 | environment->lambertian.gl_id = lambertian_id; 269 | environment->lambertian.kind = TEXTURE_KIND_ENVIRONMENT_LAMBERTIAN; 270 | environment->lambertian.gl_unit = TEXTURE_KIND_ENVIRONMENT_LAMBERTIAN; 271 | environment->lambertian.gl_target = GL_TEXTURE_CUBE_MAP; 272 | 273 | environment->ggx.gl_id = ggx_id; 274 | environment->ggx.kind = TEXTURE_KIND_ENVIRONMENT_GGX; 275 | environment->ggx.gl_unit = TEXTURE_KIND_ENVIRONMENT_GGX; 276 | environment->ggx.gl_target = GL_TEXTURE_CUBE_MAP; 277 | 278 | environment->ggx_lut.gl_id = ggx_lut_id; 279 | environment->ggx_lut.kind = TEXTURE_KIND_ENVIRONMENT_GGX_LUT; 280 | environment->ggx_lut.gl_unit = TEXTURE_KIND_ENVIRONMENT_GGX_LUT; 281 | environment->ggx_lut.gl_target = GL_TEXTURE_2D; 282 | 283 | environment->charlie.gl_id = charlie_id; 284 | environment->charlie.kind = TEXTURE_KIND_ENVIRONMENT_CHARLIE; 285 | environment->charlie.gl_unit = TEXTURE_KIND_ENVIRONMENT_CHARLIE; 286 | environment->charlie.gl_target = GL_TEXTURE_CUBE_MAP; 287 | 288 | environment->charlie_lut.gl_id = charlie_lut_id; 289 | environment->charlie_lut.kind = TEXTURE_KIND_ENVIRONMENT_CHARLIE_LUT; 290 | environment->charlie_lut.gl_unit = TEXTURE_KIND_ENVIRONMENT_CHARLIE_LUT; 291 | environment->charlie_lut.gl_target = GL_TEXTURE_2D; 292 | 293 | // Cleanup. 294 | framebuffer_fini(&fb); 295 | shader_destroy(iblsampler_shader); 296 | 297 | return environment; 298 | } 299 | 300 | void environment_fini(struct environment *environment) { 301 | texture_fini(&environment->cubemap); 302 | texture_fini(&environment->lambertian); 303 | texture_fini(&environment->ggx); 304 | texture_fini(&environment->ggx_lut); 305 | texture_fini(&environment->charlie); 306 | texture_fini(&environment->charlie_lut); 307 | } 308 | 309 | void environment_switch(const struct environment *new) { 310 | texture_switch(&new->lambertian); 311 | texture_switch(&new->ggx); 312 | texture_switch(&new->ggx_lut); 313 | texture_switch(&new->charlie); 314 | texture_switch(&new->charlie_lut); 315 | } 316 | -------------------------------------------------------------------------------- /src/ui.c: -------------------------------------------------------------------------------- 1 | #include "cimgui.h" 2 | #include "cimgui_impl.h" 3 | #include "client.h" 4 | #include "entity.h" 5 | #include "incbin.h" 6 | #include "model.h" 7 | #include "ui.h" 8 | 9 | INCBIN(assets_logo_png, "../assets/logo.png"); 10 | INCBIN(assets_gear_png, "../assets/gear.png"); 11 | INCBIN(assets_bug_png, "../assets/bug.png"); 12 | INCBIN(assets_question_png, "../assets/question.png"); 13 | INCBIN(assets_cube_png, "../assets/cube.png"); 14 | 15 | static float step_size = 0.005; 16 | 17 | bool ui_init(struct ui *ui) { 18 | ui->ig_context = igCreateContext(NULL); 19 | ui->ig_io = igGetIO(); 20 | ui->ig_io->IniFilename = NULL; 21 | 22 | ui->show = false; 23 | ui->show_imgui_demo = false; 24 | ui->show_scene_editor = false; 25 | ui->show_debug_tools = false; 26 | ui->show_settings = false; 27 | ui->show_debug_camera = false; 28 | ui->show_about = false; 29 | 30 | ui->selected_entity_id = 0; // FIXME: Does not belong here. 31 | 32 | ImGui_ImplGlfw_InitForOpenGL(client.window.glfw_window, true); 33 | ImGui_ImplOpenGL3_Init("#version 410 core"); 34 | igStyleColorsDark(NULL); 35 | 36 | texture_init_from_memory(&ui->assets_logo_png, TEXTURE_KIND_IMAGE, assets_logo_png_data, assets_logo_png_size); 37 | texture_init_from_memory(&ui->assets_gear_png, TEXTURE_KIND_IMAGE, assets_gear_png_data, assets_gear_png_size); 38 | texture_init_from_memory(&ui->assets_bug_png, TEXTURE_KIND_IMAGE, assets_bug_png_data, assets_bug_png_size); 39 | texture_init_from_memory(&ui->assets_question_png, TEXTURE_KIND_IMAGE, assets_question_png_data, assets_question_png_size); 40 | texture_init_from_memory(&ui->assets_cube_png, TEXTURE_KIND_IMAGE, assets_cube_png_data, assets_cube_png_size); 41 | 42 | gizmo_init(&ui->gizmo); 43 | 44 | return true; 45 | } 46 | 47 | void ui_fini(struct ui *ui) { 48 | ImGui_ImplOpenGL3_Shutdown(); 49 | ImGui_ImplGlfw_Shutdown(); 50 | igDestroyContext(ui->ig_context); 51 | texture_fini(&ui->assets_logo_png); 52 | texture_fini(&ui->assets_gear_png); 53 | texture_fini(&ui->assets_bug_png); 54 | texture_fini(&ui->assets_question_png); 55 | texture_fini(&ui->assets_cube_png); 56 | gizmo_fini(&ui->gizmo); 57 | } 58 | 59 | static void center_next_window(void) { 60 | igSetNextWindowPos((ImVec2) { client.renderer.viewport_width / 2, client.renderer.viewport_height / 2}, ImGuiCond_Once, (ImVec2) { 0.5, 0.5}); 61 | } 62 | 63 | static void prepare_centered_text(const char *text) { 64 | float window_width = igGetWindowWidth(); 65 | 66 | ImVec2 dimension; 67 | igCalcTextSize(&dimension, text, NULL, false, -1); 68 | 69 | igSetCursorPosX(window_width / 2 - dimension.x / 2); 70 | } 71 | 72 | static void bring_window_to_focus_back(struct ui *ui, ImGuiWindow *window) { 73 | // Sorted in focus order, back to front. 74 | 75 | if (ui->ig_context->WindowsFocusOrder.Size > 1) { 76 | ImGuiWindow *carry = NULL; 77 | 78 | // If the first element is our window, no need to do anything else. 79 | if (ui->ig_context->WindowsFocusOrder.Data[0] == window) { 80 | return; 81 | } 82 | 83 | // Save the first element to the carry. 84 | carry = ui->ig_context->WindowsFocusOrder.Data[0]; 85 | 86 | // Replace the first element with our window. 87 | ui->ig_context->WindowsFocusOrder.Data[0] = window; 88 | 89 | // Nodge all of the remainder elements to the right, stopping if we find our previous selves again. 90 | for (int i = 1; i < ui->ig_context->WindowsFocusOrder.Size; i++) { 91 | // Fetch the current element. 92 | ImGuiWindow *element = ui->ig_context->WindowsFocusOrder.Data[i]; 93 | 94 | // Replace the element by the carry. 95 | ui->ig_context->WindowsFocusOrder.Data[i] = carry; 96 | 97 | // The element becomes the new carry. 98 | carry = element; 99 | 100 | // At this point, if we meet the original window, we can short-circuit the algorithm. 101 | if (element == window) { 102 | break; 103 | } 104 | } 105 | } 106 | } 107 | 108 | static void render_settings(struct ui *ui) { 109 | center_next_window(); 110 | 111 | igSetNextWindowSize((ImVec2) { 300, 0}, ImGuiCond_Once); 112 | 113 | if (igBegin("Settings", &ui->show_settings, ImGuiWindowFlags_NoResize)) { 114 | char buffer[1024]; 115 | snprintf(buffer, sizeof buffer, "%s", client.window.title); 116 | if (igInputText("Window title", buffer, sizeof buffer, ImGuiInputTextFlags_None, NULL, NULL)) { 117 | window_update_title(&client.window, buffer); 118 | } 119 | 120 | if (igSliderFloat("FOV", &client.renderer.fov, 10.0f, 120.0f, "%.0f", ImGuiSliderFlags_None)) { 121 | renderer_update_projection_matrix(&client.renderer); 122 | } 123 | } 124 | 125 | igEnd(); 126 | } 127 | 128 | static void render_debug_camera(struct ui *ui) { 129 | igSetNextWindowSize((ImVec2) { 300, 210}, ImGuiCond_Once); 130 | 131 | if (igBegin("Debug camera", &ui->show_debug_camera, ImGuiWindowFlags_NoResize)) { 132 | igText("Eye"); 133 | 134 | igSetNextItemWidth(-60); 135 | 136 | igPushStyleColorVec4(ImGuiCol_FrameBg, (ImVec4) { 0.140, 0.140, 0.140, 1}); 137 | igPushStyleColorVec4(ImGuiCol_FrameBgActive, (ImVec4) { 0.140, 0.140, 0.140, 1}); 138 | igPushStyleColorVec4(ImGuiCol_FrameBgHovered, (ImVec4) { 0.140, 0.140, 0.140, 1}); 139 | 140 | if (igDragFloat3("Position##camera_eye", client.camera.eye, 0.01, -FLT_MAX, FLT_MAX, "%f", ImGuiSliderFlags_ReadOnly)) { 141 | camera_update(&client.camera); 142 | } 143 | 144 | igPopStyleColor(3); 145 | 146 | igSetNextItemWidth(-60); 147 | 148 | if (igDragFloat("Distance##eye_distance", &client.camera.eye_distance, 0.01, -FLT_MAX, FLT_MAX, "%f", ImGuiSliderFlags_None)) { 149 | camera_update(&client.camera); 150 | } 151 | 152 | igSetNextItemWidth(-60); 153 | 154 | if (igDragFloat("Around##eye_around", &client.camera.eye_around, 0.01, -FLT_MAX, FLT_MAX, "%f", ImGuiSliderFlags_None)) { 155 | camera_update(&client.camera); 156 | } 157 | 158 | igSetNextItemWidth(-60); 159 | 160 | if (igDragFloat("Above##eye_above", &client.camera.eye_above, 0.01, -FLT_MAX, FLT_MAX, "%f", ImGuiSliderFlags_None)) { 161 | camera_update(&client.camera); 162 | } 163 | 164 | igSeparator(); 165 | 166 | igText("Center"); 167 | 168 | igSetNextItemWidth(-60); 169 | 170 | if (igDragFloat3("Position##camera_center", client.camera.center, 0.01, -FLT_MAX, FLT_MAX, "%f", ImGuiSliderFlags_None)) { 171 | camera_update(&client.camera); 172 | } 173 | 174 | igSetNextItemWidth(-60); 175 | 176 | if (igDragFloat("Rotation##center_rotation", &client.camera.center_rotation, 0.01, -FLT_MAX, FLT_MAX, "%f", ImGuiSliderFlags_None)) { 177 | camera_update(&client.camera); 178 | } 179 | } 180 | 181 | igEnd(); 182 | } 183 | 184 | static void render_debug_tools(struct ui *ui) { 185 | center_next_window(); 186 | 187 | igSetNextWindowSize((ImVec2) { 380, 0}, ImGuiCond_Once); 188 | 189 | if (igBegin("Debugging", &ui->show_debug_tools, ImGuiWindowFlags_NoResize)) { 190 | igText("Cursor position: %.0f %.0f", client.window.cursor_pos_x, client.window.cursor_pos_y); 191 | igText("Mouse picked: %u", client.renderer.mousepicking_entity_id); 192 | igText("Selected entity: %u", ui->selected_entity_id); 193 | 194 | igSeparator(); 195 | 196 | if (igCheckbox("Wireframe mode", &client.renderer.wireframe)) { 197 | renderer_wireframe(&client.renderer, client.renderer.wireframe); 198 | } 199 | 200 | if (igCheckbox("Fullscreen", &client.window.fullscreen)) { 201 | window_fullscreen(&client.window, client.window.fullscreen); 202 | } 203 | 204 | igCheckbox("Debug camera", &ui->show_debug_camera); 205 | igCheckbox("ImGUI Demo Window", &ui->show_imgui_demo); 206 | 207 | igSeparator(); 208 | 209 | igSetNextItemWidth(-130); 210 | 211 | const char *choices[] = {"None", "Translation", "Rotation", "Scale"}; 212 | static int mode = 0; 213 | if (igComboStr_arr("Gizmo mode", &mode, choices, ARRAY_COUNT(choices), 5)) { 214 | switch (mode) { 215 | case 1: ui->gizmo.mode = GIZMO_MODE_TRANSLATION; break; 216 | case 2: ui->gizmo.mode = GIZMO_MODE_ROTATION; break; 217 | case 3: ui->gizmo.mode = GIZMO_MODE_SCALE; break; 218 | default: ui->gizmo.mode = GIZMO_MODE_NONE; 219 | } 220 | } 221 | 222 | igText("Moving bitmask: %d", client.moving); 223 | } 224 | 225 | igEnd(); 226 | } 227 | 228 | static void render_about(struct ui *ui) { 229 | center_next_window(); 230 | 231 | // The window. 232 | if (igBegin(DEFAULT_TITLE, &ui->show_about, ImGuiWindowFlags_NoResize)) { 233 | float width = igGetWindowWidth(); 234 | 235 | // Display the logo centered. 236 | igSetCursorPosX((width - ui->assets_logo_png.width / 2) / 2); 237 | ImTextureID texture_id = (void *) (uintptr_t) ui->assets_logo_png.gl_id; // Absolutely gross. 238 | igImage(texture_id, (ImVec2) { ui->assets_logo_png.width / 2, ui->assets_logo_png.height / 2}, (ImVec2) { 0, 0}, (ImVec2) { 1, 1}, (ImVec4) { 1, 1, 1, 1}, (ImVec4) { 0, 0, 0, 0}); 239 | 240 | igSeparator(); 241 | 242 | igText("Alex Belanger (nitrix)"); 243 | igText("https://github.com/nitrix/layman"); 244 | 245 | igSeparator(); 246 | 247 | // Footer. 248 | char buffer[256]; 249 | sprintf(buffer, "Version %s", VERSION); 250 | prepare_centered_text(buffer); 251 | igTextDisabled(buffer); 252 | } 253 | 254 | igEnd(); 255 | } 256 | 257 | static void render_scene_editor(struct ui *ui) { 258 | igSetNextWindowSize((ImVec2) { 350, 340}, ImGuiCond_Once); 259 | 260 | center_next_window(); 261 | 262 | if (igBegin("Scene editor", &ui->show_scene_editor, ImGuiWindowFlags_NoResize)) { 263 | static char buf[1024] = "assets/DamagedHelmet.glb"; 264 | igSetNextItemWidth(-70); 265 | igInputText("##scene-load", buf, sizeof buf, ImGuiInputTextFlags_None, NULL, NULL); 266 | igSameLine(0, -1); 267 | igSetNextItemWidth(70); 268 | if (igButton("Load", (ImVec2) { -1, 0})) { 269 | struct entity *entity = malloc(sizeof *entity); 270 | if (entity_init(entity, buf)) { 271 | scene_add_entity(&client.scene, entity); 272 | buf[0] = '\0'; 273 | } 274 | } 275 | 276 | igSeparator(); 277 | 278 | igPushStyleColorVec4(ImGuiCol_ChildBg, (ImVec4) { 0.16, 0.29, 0.48, 0.54}); 279 | 280 | igBeginChildStr("##scene-entities", (ImVec2) { -1, 200}, false, ImGuiWindowFlags_None); 281 | 282 | struct entity *found_entity = NULL; 283 | 284 | for (size_t i = 0; i < client.scene.entity_count; i++) { 285 | struct entity *entity = client.scene.entities[i]; 286 | 287 | ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf; 288 | 289 | if (entity->id == ui->selected_entity_id) { 290 | flags |= ImGuiTreeNodeFlags_Selected; 291 | found_entity = entity; 292 | } 293 | 294 | char buffer[1024]; 295 | snprintf(buffer, sizeof buffer, "##entity-index-%zu", i); 296 | 297 | if (igTreeNodeExStrStr(buffer, flags, "%s", entity->model->filepath)) { 298 | igTreePop(); 299 | } 300 | 301 | if (igIsItemClicked(ImGuiMouseButton_Left)) { 302 | ui->selected_entity_id = entity->id; 303 | found_entity = entity; 304 | } 305 | } 306 | 307 | igEndChild(); 308 | 309 | igPopStyleColor(1); 310 | 311 | igSeparator(); 312 | 313 | if (found_entity) { 314 | if (igButton("R##reset-translation", (ImVec2) { 0, 0})) { 315 | glm_vec3_zero(found_entity->translation); 316 | } 317 | 318 | igSameLine(0, -1); 319 | igDragFloat3("Translation", found_entity->translation, step_size, -FLT_MAX, FLT_MAX, "%f", ImGuiSliderFlags_None); 320 | 321 | if (igButton("R##reset-rotation", (ImVec2) { 0, 0})) { 322 | glm_quat_identity(found_entity->rotation); 323 | } 324 | 325 | igSameLine(0, -1); 326 | 327 | vec3 old_euler_rotation, new_euler_rotation; 328 | mat4 rotation; 329 | 330 | glm_quat_mat4(found_entity->rotation, rotation); 331 | glm_euler_angles(rotation, old_euler_rotation); 332 | glm_vec3_copy(old_euler_rotation, new_euler_rotation); 333 | 334 | if (igDragFloat3("Rotation", new_euler_rotation, step_size, -FLT_MAX, FLT_MAX, "%f", ImGuiSliderFlags_None)) { 335 | vec3 delta_euler_rotation; 336 | glm_vec3_sub(new_euler_rotation, old_euler_rotation, delta_euler_rotation); 337 | 338 | versor rx, ry, rz, r; 339 | glm_quat(rx, delta_euler_rotation[0], 1, 0, 0); 340 | glm_quat(ry, delta_euler_rotation[1], 0, 1, 0); 341 | glm_quat(rz, delta_euler_rotation[2], 0, 0, 1); 342 | glm_quat_mul(rx, ry, r); 343 | glm_quat_mul(r, rz, r); 344 | 345 | glm_quat_mul(found_entity->rotation, r, found_entity->rotation); 346 | } 347 | 348 | if (igButton("R##reset-scale", (ImVec2) { 0, 0})) { 349 | found_entity->scale = 1; 350 | } 351 | 352 | igSameLine(0, -1); 353 | igDragFloat("Scale", &found_entity->scale, step_size, 0, FLT_MAX, "%f", ImGuiSliderFlags_None); 354 | } else { 355 | igText("Select an entity."); 356 | } 357 | } 358 | 359 | igEnd(); 360 | } 361 | 362 | static void render_menu_entry(GLuint texture_id, bool *active) { 363 | if (*active) { 364 | igPushStyleColorVec4(ImGuiCol_Button, (ImVec4) { 0.26, 0.59, 0.98, 0.6}); 365 | } else { 366 | igPushStyleColorVec4(ImGuiCol_Button, (ImVec4) { 0.06, 0.53, 0.98, 0.2}); 367 | } 368 | 369 | if (igImageButton((void *) (uintptr_t) texture_id, (ImVec2) { 32, 32}, (ImVec2) { 0, 0}, (ImVec2) { 1, 1}, 10, (ImVec4) { 0, 0, 0, 0}, (ImVec4) { 1, 1, 1, 1})) { 370 | *active = !*active; 371 | } 372 | 373 | igPopStyleColor(1); 374 | } 375 | 376 | static void render_menu(struct ui *ui) { 377 | igSetNextWindowPos((ImVec2) { 10, 10}, ImGuiCond_Always, (ImVec2) { 0, 0}); 378 | 379 | if (igBegin("Menu", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoBringToFrontOnFocus)) { 380 | // Force our menu to be behind everything. 381 | igBringWindowToDisplayBack(igGetCurrentWindow()); 382 | 383 | // Force our menu window to have the least focus. 384 | bring_window_to_focus_back(ui, igGetCurrentWindow()); 385 | 386 | render_menu_entry(ui->assets_cube_png.gl_id, &ui->show_scene_editor); 387 | render_menu_entry(ui->assets_bug_png.gl_id, &ui->show_debug_tools); 388 | render_menu_entry(ui->assets_gear_png.gl_id, &ui->show_settings); 389 | render_menu_entry(ui->assets_question_png.gl_id, &ui->show_about); 390 | } 391 | 392 | igEnd(); 393 | } 394 | 395 | void ui_render(struct ui *ui) { 396 | ImGui_ImplOpenGL3_NewFrame(); 397 | ImGui_ImplGlfw_NewFrame(); 398 | igNewFrame(); 399 | 400 | if (ui->show) { 401 | render_menu(ui); 402 | 403 | if (ui->show_debug_tools) { 404 | render_debug_tools(ui); 405 | } 406 | 407 | if (ui->show_scene_editor) { 408 | render_scene_editor(ui); 409 | } 410 | 411 | if (ui->show_settings) { 412 | render_settings(ui); 413 | } 414 | 415 | if (ui->show_debug_camera) { 416 | render_debug_camera(ui); 417 | } 418 | 419 | if (ui->show_about) { 420 | render_about(ui); 421 | } 422 | 423 | if (ui->show_imgui_demo) { 424 | igShowDemoWindow(&ui->show_imgui_demo); 425 | } 426 | 427 | gizmo_render(&client.ui.gizmo); 428 | } 429 | 430 | igRender(); 431 | ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData()); 432 | } 433 | -------------------------------------------------------------------------------- /src/shader.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | #include "toolkit.h" 3 | 4 | // FIXME: This function is a disaster. 5 | 6 | static GLuint compile_shader(GLenum type, const struct shader_options *options, const unsigned char *content, size_t length) { 7 | GLuint shader_id = glCreateShader(type); 8 | if (shader_id == 0) { 9 | return 0; 10 | } 11 | 12 | struct tk_buffer buffer; 13 | tk_buffer_init(&buffer); 14 | 15 | bool ok = true; 16 | 17 | #define SHADER_OPTION(cond, ...) (cond ? ok &= tk_buffer_append_format(&buffer, __VA_ARGS__) : 0) 18 | 19 | // Version. 20 | tk_buffer_append_format(&buffer, "#version 410 core\n"); 21 | 22 | // If there are options, process them. 23 | if (options) { 24 | // Attributes. 25 | SHADER_OPTION(options->has_normals, "#define HAS_NORMALS\n"); 26 | SHADER_OPTION(options->has_uv_set1, "#define HAS_UV_SET1\n"); 27 | SHADER_OPTION(options->has_tangents, "#define HAS_TANGENTS\n"); 28 | SHADER_OPTION(options->has_weight_set1, "#define HAS_WEIGHT_SET1\n"); 29 | SHADER_OPTION(options->has_joint_set1, "#define HAS_JOINT_SET1\n"); 30 | SHADER_OPTION(options->has_color_vec3, "#define HAS_VERTEX_COLOR_VEC3\n"); 31 | SHADER_OPTION(options->has_color_vec4, "#define HAS_VERTEX_COLOR_VEC4\n"); 32 | 33 | // Textures. 34 | SHADER_OPTION(options->has_base_color_map, "#define HAS_BASE_COLOR_MAP\n"); 35 | SHADER_OPTION(options->has_normal_map, "#define HAS_NORMAL_MAP\n"); 36 | SHADER_OPTION(options->has_occlusion_map, "#define HAS_OCCLUSION_MAP\n"); 37 | SHADER_OPTION(options->has_emissive_map, "#define HAS_EMISSIVE_MAP\n"); 38 | SHADER_OPTION(options->has_metallic_roughness_map, "#define HAS_METALLIC_ROUGHNESS_MAP\n"); 39 | 40 | // Material workflow. 41 | SHADER_OPTION(options->material_metallicroughness, "#define MATERIAL_METALLICROUGHNESS\n"); 42 | SHADER_OPTION(options->material_specularglossiness, "#define MATERIAL_SPECULARGLOSSINESS\n"); 43 | 44 | // Lighting. 45 | SHADER_OPTION(options->material_unlit, "#define MATERIAL_UNLIT\n"); 46 | SHADER_OPTION(options->use_hdr, "#define USE_HDR\n"); 47 | SHADER_OPTION(options->use_ibl, "#define USE_IBL\n"); 48 | SHADER_OPTION(options->use_punctual, "#define USE_PUNCTUAL\n"); 49 | SHADER_OPTION(options->light_count, "#define LIGHT_COUNT %d\n", MAX_LIGHTS); 50 | 51 | // Skinning. 52 | SHADER_OPTION(options->joint_count, "#define JOINT_COUNT %d\n", options->joint_count); 53 | SHADER_OPTION(options->use_skinning, "#define USE_SKINNING\n"); 54 | } 55 | 56 | // FIXME: When has_emissive_map is turned off, emissiveFactor must be forced to 0. 57 | 58 | ok &= tk_buffer_append(&buffer, content, length); 59 | 60 | if (!ok) { 61 | glDeleteShader(shader_id); 62 | return 0; 63 | } 64 | 65 | const char *const source = buffer.data; 66 | GLint source_length = buffer.used; 67 | glShaderSource(shader_id, 1, &source, &source_length); 68 | 69 | tk_buffer_fini(&buffer); 70 | 71 | glCompileShader(shader_id); 72 | 73 | GLint success; 74 | glGetShaderiv(shader_id, GL_COMPILE_STATUS, &success); 75 | if (success != GL_TRUE) { 76 | GLint info_log_size; 77 | glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &info_log_size); 78 | char info_log[info_log_size]; 79 | glGetShaderInfoLog(shader_id, info_log_size, NULL, info_log); 80 | 81 | fprintf(stderr, "Shader compilation error!\n%s\n", info_log); 82 | 83 | return 0; 84 | } 85 | 86 | return shader_id; 87 | } 88 | 89 | static void find_uniforms(struct shader *shader) { 90 | glUseProgram(shader->program_id); 91 | 92 | shader->uniform_base_color_factor = glGetUniformLocation(shader->program_id, "u_BaseColorFactor"); 93 | shader->uniform_base_color_sampler = glGetUniformLocation(shader->program_id, "u_BaseColorSampler"); 94 | shader->uniform_normal_sampler = glGetUniformLocation(shader->program_id, "u_NormalSampler"); 95 | shader->uniform_normal_scale = glGetUniformLocation(shader->program_id, "u_NormalScale"); 96 | shader->uniform_metallic_roughness_sampler = glGetUniformLocation(shader->program_id, "u_MetallicRoughnessSampler"); 97 | shader->uniform_metallic_factor = glGetUniformLocation(shader->program_id, "u_MetallicFactor"); 98 | shader->uniform_roughness_factor = glGetUniformLocation(shader->program_id, "u_RoughnessFactor"); 99 | shader->uniform_occlusion_sampler = glGetUniformLocation(shader->program_id, "u_OcclusionSampler"); 100 | shader->uniform_occlusion_strength = glGetUniformLocation(shader->program_id, "u_OcclusionStrength"); 101 | shader->uniform_emissive_sampler = glGetUniformLocation(shader->program_id, "u_EmissiveSampler"); 102 | shader->uniform_emissive_factor = glGetUniformLocation(shader->program_id, "u_EmissiveFactor"); 103 | shader->uniform_camera = glGetUniformLocation(shader->program_id, "u_Camera"); 104 | 105 | shader->uniform_environment_mip_count = glGetUniformLocation(shader->program_id, "u_MipCount"); 106 | shader->uniform_environment_lambertian = glGetUniformLocation(shader->program_id, "u_LambertianEnvSampler"); 107 | shader->uniform_environment_ggx = glGetUniformLocation(shader->program_id, "u_GGXEnvSampler"); 108 | shader->uniform_environment_ggx_lut = glGetUniformLocation(shader->program_id, "u_GGXLUT"); 109 | shader->uniform_environment_charlie = glGetUniformLocation(shader->program_id, "u_CharlieEnvSampler"); 110 | shader->uniform_environment_charlie_lut = glGetUniformLocation(shader->program_id, "u_CharlieLUT"); 111 | 112 | char name[64]; 113 | for (size_t i = 0; i < MAX_LIGHTS; i++) { 114 | sprintf(name, "u_Lights[%zu].type", i); 115 | shader->uniform_lights_type[i] = glGetUniformLocation(shader->program_id, name); 116 | 117 | sprintf(name, "u_Lights[%zu].position", i); 118 | shader->uniform_lights_position[i] = glGetUniformLocation(shader->program_id, name); 119 | 120 | sprintf(name, "u_Lights[%zu].direction", i); 121 | shader->uniform_lights_direction[i] = glGetUniformLocation(shader->program_id, name); 122 | 123 | sprintf(name, "u_Lights[%zu].color", i); 124 | shader->uniform_lights_color[i] = glGetUniformLocation(shader->program_id, name); 125 | 126 | sprintf(name, "u_Lights[%zu].intensity", i); 127 | shader->uniform_lights_intensity[i] = glGetUniformLocation(shader->program_id, name); 128 | } 129 | 130 | shader->uniform_view_projection_matrix = glGetUniformLocation(shader->program_id, "u_ViewProjectionMatrix"); 131 | shader->uniform_model_matrix = glGetUniformLocation(shader->program_id, "u_ModelMatrix"); 132 | shader->uniform_normal_matrix = glGetUniformLocation(shader->program_id, "u_NormalMatrix"); 133 | shader->uniform_exposure = glGetUniformLocation(shader->program_id, "u_Exposure"); 134 | } 135 | 136 | struct shader *shader_load_from_files(const struct shader_options *options, const char *vertex_filepath, const char *fragment_filepath, const char *compute_filepath) { 137 | char *vertex_content = NULL; 138 | char *fragment_content = NULL; 139 | char *compute_content = NULL; 140 | 141 | size_t vertex_length = 0; 142 | size_t fragment_length = 0; 143 | size_t compute_length = 0; 144 | 145 | if (vertex_filepath) { 146 | vertex_content = tk_file_get_content(vertex_filepath); 147 | vertex_length = strlen(vertex_content); 148 | } 149 | 150 | if (fragment_filepath) { 151 | fragment_content = tk_file_get_content(fragment_filepath); 152 | fragment_length = strlen(fragment_content); 153 | } 154 | 155 | if (compute_filepath) { 156 | compute_content = tk_file_get_content(compute_filepath); 157 | compute_length = strlen(compute_content); 158 | } 159 | 160 | struct shader *shader = shader_load_from_memory(options, 161 | (unsigned char *) vertex_content, vertex_length, 162 | (unsigned char *) fragment_content, fragment_length, 163 | (unsigned char *) compute_content, compute_length 164 | ); 165 | 166 | free(vertex_content); 167 | free(fragment_content); 168 | free(compute_content); 169 | 170 | return shader; 171 | } 172 | 173 | struct shader *shader_load_from_memory(const struct shader_options *options, const unsigned char *vertex_content, size_t vertex_length, const unsigned char *fragment_content, size_t fragment_length, const unsigned char *compute_content, size_t compute_length) { 174 | GLuint vertex_shader_id = 0; 175 | GLuint fragment_shader_id = 0; 176 | GLuint compute_shader_id = 0; 177 | bool something_went_wrong = false; 178 | 179 | if (vertex_content) { 180 | vertex_shader_id = compile_shader(GL_VERTEX_SHADER, options, vertex_content, vertex_length); 181 | if (!vertex_shader_id) { 182 | something_went_wrong = true; 183 | } 184 | } 185 | 186 | if (fragment_content) { 187 | fragment_shader_id = compile_shader(GL_FRAGMENT_SHADER, options, fragment_content, fragment_length); 188 | if (!fragment_shader_id) { 189 | something_went_wrong = true; 190 | } 191 | } 192 | 193 | if (compute_content) { 194 | compute_shader_id = compile_shader(GL_COMPUTE_SHADER, options, compute_content, compute_length); 195 | if (!compute_shader_id) { 196 | something_went_wrong = true; 197 | } 198 | } 199 | 200 | if (something_went_wrong) { 201 | if (vertex_shader_id) { 202 | glDeleteShader(vertex_shader_id); 203 | } 204 | 205 | if (fragment_shader_id) { 206 | glDeleteShader(fragment_shader_id); 207 | } 208 | 209 | if (compute_shader_id) { 210 | glDeleteShader(compute_shader_id); 211 | } 212 | 213 | return NULL; 214 | } 215 | 216 | GLuint program_id = glCreateProgram(); 217 | if (!program_id) { 218 | glDeleteShader(vertex_shader_id); 219 | glDeleteShader(fragment_shader_id); 220 | glDeleteShader(compute_shader_id); 221 | return NULL; 222 | } 223 | 224 | if (vertex_shader_id) { 225 | glAttachShader(program_id, vertex_shader_id); 226 | } 227 | 228 | if (fragment_shader_id) { 229 | glAttachShader(program_id, fragment_shader_id); 230 | } 231 | 232 | if (compute_shader_id) { 233 | glAttachShader(program_id, compute_shader_id); 234 | } 235 | 236 | // Bind attributes. Must be before linkage. 237 | glBindAttribLocation(program_id, MESH_ATTRIBUTE_POSITION, "a_Position"); 238 | glBindAttribLocation(program_id, MESH_ATTRIBUTE_UV, "a_UV1"); 239 | glBindAttribLocation(program_id, MESH_ATTRIBUTE_NORMAL, "a_Normal"); 240 | glBindAttribLocation(program_id, MESH_ATTRIBUTE_TANGENT, "a_Tangent"); 241 | glBindAttribLocation(program_id, MESH_ATTRIBUTE_WEIGHTS, "a_Weight1"); 242 | glBindAttribLocation(program_id, MESH_ATTRIBUTE_JOINTS, "a_Joint1"); 243 | glBindAttribLocation(program_id, MESH_ATTRIBUTE_COLORS, "a_Color"); 244 | 245 | glLinkProgram(program_id); 246 | 247 | GLint success; 248 | glGetProgramiv(program_id, GL_LINK_STATUS, &success); 249 | if (success != GL_TRUE) { 250 | GLint info_log_size; 251 | glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_log_size); 252 | char info_log[info_log_size]; 253 | glGetProgramInfoLog(program_id, info_log_size, NULL, info_log); 254 | 255 | fprintf(stderr, "Shader linking error: %s\n", info_log); 256 | 257 | return 0; 258 | } 259 | 260 | // Flag shaders for deletion as soon as they get detached (happens upon program deletion). 261 | glDeleteShader(vertex_shader_id); 262 | glDeleteShader(fragment_shader_id); 263 | glDeleteShader(compute_shader_id); 264 | 265 | struct shader *shader = malloc(sizeof *shader); 266 | if (!shader) { 267 | glDeleteProgram(program_id); 268 | return NULL; 269 | } 270 | 271 | shader->program_id = program_id; 272 | 273 | find_uniforms(shader); 274 | 275 | return shader; 276 | } 277 | 278 | void shader_destroy(struct shader *shader) { 279 | glDeleteProgram(shader->program_id); 280 | free(shader); 281 | } 282 | 283 | void shader_bind_uniform_material(const struct shader *shader, const struct material *material) { 284 | glUniform4fv(shader->uniform_base_color_factor, 1, material->base_color_factor); 285 | 286 | if (material->base_color_texture) { 287 | glUniform1i(shader->uniform_base_color_sampler, material->base_color_texture->gl_unit); 288 | } 289 | 290 | if (material->metallic_roughness_texture) { 291 | glUniform1i(shader->uniform_metallic_roughness_sampler, material->metallic_roughness_texture->gl_unit); 292 | } 293 | 294 | glUniform1f(shader->uniform_metallic_factor, material->metallic_factor); 295 | glUniform1f(shader->uniform_roughness_factor, material->roughness_factor); 296 | 297 | if (material->normal_texture) { 298 | glUniform1i(shader->uniform_normal_sampler, material->normal_texture->gl_unit); 299 | } 300 | 301 | glUniform1f(shader->uniform_normal_scale, material->normal_scale); 302 | 303 | if (material->occlusion_texture) { 304 | glUniform1i(shader->uniform_occlusion_sampler, material->occlusion_texture->gl_unit); 305 | } 306 | 307 | glUniform1f(shader->uniform_occlusion_strength, material->occlusion_strength); 308 | 309 | if (material->emissive_texture) { 310 | glUniform1i(shader->uniform_emissive_sampler, material->emissive_texture->gl_unit); 311 | } 312 | 313 | glUniform3fv(shader->uniform_emissive_factor, 1, material->emissive_factor); 314 | } 315 | 316 | void shader_bind_uniform_environment(const struct shader *shader, const struct environment *environment) { 317 | glUniform1i(shader->uniform_environment_mip_count, environment->mip_count); 318 | glUniform1i(shader->uniform_environment_lambertian, environment->lambertian.gl_unit); 319 | glUniform1i(shader->uniform_environment_ggx, environment->ggx.gl_unit); 320 | glUniform1i(shader->uniform_environment_ggx_lut, environment->ggx_lut.gl_unit); 321 | glUniform1i(shader->uniform_environment_charlie, environment->charlie.gl_unit); 322 | glUniform1i(shader->uniform_environment_charlie_lut, environment->charlie_lut.gl_unit); 323 | } 324 | 325 | void shader_bind_uniform_camera(const struct shader *shader, const struct camera *camera) { 326 | glUniform3fv(shader->uniform_camera, 1, camera->eye); 327 | } 328 | 329 | void shader_bind_uniform_lights(const struct shader *shader, const struct light **lights, size_t count) { 330 | for (size_t i = 0; i < count; i++) { 331 | // TODO: This doesn't clean up old light uniforms when they're removed! 332 | if (i >= MAX_LIGHTS) { 333 | break; 334 | } 335 | 336 | const struct light *light = lights[i]; 337 | 338 | switch (light->type) { 339 | case LIGHT_TYPE_DIRECTIONAL: 340 | glUniform1i(shader->uniform_lights_type[i], light->type); 341 | glUniform3fv(shader->uniform_lights_position[i], 1, light->position); 342 | glUniform3fv(shader->uniform_lights_direction[i], 1, light->direction); 343 | glUniform3fv(shader->uniform_lights_color[i], 1, light->color); 344 | glUniform1f(shader->uniform_lights_intensity[i], light->intensity); 345 | break; 346 | 347 | case LIGHT_TYPE_POINT: 348 | glUniform1i(shader->uniform_lights_type[i], light->type); 349 | glUniform3fv(shader->uniform_lights_position[i], 1, light->position); 350 | glUniform3fv(shader->uniform_lights_color[i], 1, light->color); 351 | glUniform1f(shader->uniform_lights_intensity[i], light->intensity); 352 | break; 353 | 354 | // TODO: The other types of lights. 355 | default: break; 356 | } 357 | } 358 | } 359 | 360 | void shader_bind_uniform_mvp(const struct shader *shader, mat4 view_projection_matrix, mat4 model_matrix, float exposure) { 361 | glUniformMatrix4fv(shader->uniform_view_projection_matrix, 1, false, view_projection_matrix[0]); 362 | glUniformMatrix4fv(shader->uniform_model_matrix, 1, false, model_matrix[0]); 363 | glUniformMatrix4fv(shader->uniform_normal_matrix, 1, false, model_matrix[0]); 364 | glUniform1f(shader->uniform_exposure, exposure); 365 | } 366 | -------------------------------------------------------------------------------- /lib/stb/include/stb_image.h: -------------------------------------------------------------------------------- 1 | #ifndef STBI_INCLUDE_STB_IMAGE_H 2 | #define STBI_INCLUDE_STB_IMAGE_H 3 | 4 | // DOCUMENTATION 5 | // 6 | // Limitations: 7 | // - no 12-bit-per-channel JPEG 8 | // - no JPEGs with arithmetic coding 9 | // - GIF always returns *comp=4 10 | // 11 | // Basic usage (see HDR discussion below for HDR usage): 12 | // int x,y,n; 13 | // unsigned char *data = stbi_load(filename, &x, &y, &n, 0); 14 | // // ... process data if not NULL ... 15 | // // ... x = width, y = height, n = # 8-bit components per pixel ... 16 | // // ... replace '0' with '1'..'4' to force that many components per pixel 17 | // // ... but 'n' will always be the number that it would have been if you said 0 18 | // stbi_image_free(data) 19 | // 20 | // Standard parameters: 21 | // int *x -- outputs image width in pixels 22 | // int *y -- outputs image height in pixels 23 | // int *channels_in_file -- outputs # of image components in image file 24 | // int desired_channels -- if non-zero, # of image components requested in result 25 | // 26 | // The return value from an image loader is an 'unsigned char *' which points 27 | // to the pixel data, or NULL on an allocation failure or if the image is 28 | // corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, 29 | // with each pixel consisting of N interleaved 8-bit components; the first 30 | // pixel pointed to is top-left-most in the image. There is no padding between 31 | // image scanlines or between pixels, regardless of format. The number of 32 | // components N is 'desired_channels' if desired_channels is non-zero, or 33 | // *channels_in_file otherwise. If desired_channels is non-zero, 34 | // *channels_in_file has the number of components that _would_ have been 35 | // output otherwise. E.g. if you set desired_channels to 4, you will always 36 | // get RGBA output, but you can check *channels_in_file to see if it's trivially 37 | // opaque because e.g. there were only 3 channels in the source image. 38 | // 39 | // An output image with N components has the following components interleaved 40 | // in this order in each pixel: 41 | // 42 | // N=#comp components 43 | // 1 grey 44 | // 2 grey, alpha 45 | // 3 red, green, blue 46 | // 4 red, green, blue, alpha 47 | // 48 | // If image loading fails for any reason, the return value will be NULL, 49 | // and *x, *y, *channels_in_file will be unchanged. The function 50 | // stbi_failure_reason() can be queried for an extremely brief, end-user 51 | // unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS 52 | // to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly 53 | // more user-friendly ones. 54 | // 55 | // Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. 56 | // 57 | // =========================================================================== 58 | // 59 | // UNICODE: 60 | // 61 | // If compiling for Windows and you wish to use Unicode filenames, compile 62 | // with 63 | // #define STBI_WINDOWS_UTF8 64 | // and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert 65 | // Windows wchar_t filenames to utf8. 66 | // 67 | // =========================================================================== 68 | // 69 | // Philosophy 70 | // 71 | // stb libraries are designed with the following priorities: 72 | // 73 | // 1. easy to use 74 | // 2. easy to maintain 75 | // 3. good performance 76 | // 77 | // Sometimes I let "good performance" creep up in priority over "easy to maintain", 78 | // and for best performance I may provide less-easy-to-use APIs that give higher 79 | // performance, in addition to the easy-to-use ones. Nevertheless, it's important 80 | // to keep in mind that from the standpoint of you, a client of this library, 81 | // all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. 82 | // 83 | // Some secondary priorities arise directly from the first two, some of which 84 | // provide more explicit reasons why performance can't be emphasized. 85 | // 86 | // - Portable ("ease of use") 87 | // - Small source code footprint ("easy to maintain") 88 | // - No dependencies ("ease of use") 89 | // 90 | // =========================================================================== 91 | // 92 | // I/O callbacks 93 | // 94 | // I/O callbacks allow you to read from arbitrary sources, like packaged 95 | // files or some other source. Data read from callbacks are processed 96 | // through a small internal buffer (currently 128 bytes) to try to reduce 97 | // overhead. 98 | // 99 | // The three functions you must define are "read" (reads some bytes of data), 100 | // "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). 101 | // 102 | // =========================================================================== 103 | // 104 | // SIMD support 105 | // 106 | // The JPEG decoder will try to automatically use SIMD kernels on x86 when 107 | // supported by the compiler. For ARM Neon support, you must explicitly 108 | // request it. 109 | // 110 | // (The old do-it-yourself SIMD API is no longer supported in the current 111 | // code.) 112 | // 113 | // On x86, SSE2 will automatically be used when available based on a run-time 114 | // test; if not, the generic C versions are used as a fall-back. On ARM targets, 115 | // the typical path is to have separate builds for NEON and non-NEON devices 116 | // (at least this is true for iOS and Android). Therefore, the NEON support is 117 | // toggled by a build flag: define STBI_NEON to get NEON loops. 118 | // 119 | // If for some reason you do not want to use any of SIMD code, or if 120 | // you have issues compiling it, you can disable it entirely by 121 | // defining STBI_NO_SIMD. 122 | // 123 | // =========================================================================== 124 | // 125 | // HDR image support (disable by defining STBI_NO_HDR) 126 | // 127 | // stb_image supports loading HDR images in general, and currently the Radiance 128 | // .HDR file format specifically. You can still load any file through the existing 129 | // interface; if you attempt to load an HDR file, it will be automatically remapped 130 | // to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; 131 | // both of these constants can be reconfigured through this interface: 132 | // 133 | // stbi_hdr_to_ldr_gamma(2.2f); 134 | // stbi_hdr_to_ldr_scale(1.0f); 135 | // 136 | // (note, do not use _inverse_ constants; stbi_image will invert them 137 | // appropriately). 138 | // 139 | // Additionally, there is a new, parallel interface for loading files as 140 | // (linear) floats to preserve the full dynamic range: 141 | // 142 | // float *data = stbi_loadf(filename, &x, &y, &n, 0); 143 | // 144 | // If you load LDR images through this interface, those images will 145 | // be promoted to floating point values, run through the inverse of 146 | // constants corresponding to the above: 147 | // 148 | // stbi_ldr_to_hdr_scale(1.0f); 149 | // stbi_ldr_to_hdr_gamma(2.2f); 150 | // 151 | // Finally, given a filename (or an open file or memory block--see header 152 | // file for details) containing image data, you can query for the "most 153 | // appropriate" interface to use (that is, whether the image is HDR or 154 | // not), using: 155 | // 156 | // stbi_is_hdr(char *filename); 157 | // 158 | // =========================================================================== 159 | // 160 | // iPhone PNG support: 161 | // 162 | // By default we convert iphone-formatted PNGs back to RGB, even though 163 | // they are internally encoded differently. You can disable this conversion 164 | // by calling stbi_convert_iphone_png_to_rgb(0), in which case 165 | // you will always just get the native iphone "format" through (which 166 | // is BGR stored in RGB). 167 | // 168 | // Call stbi_set_unpremultiply_on_load(1) as well to force a divide per 169 | // pixel to remove any premultiplied alpha *only* if the image file explicitly 170 | // says there's premultiplied data (currently only happens in iPhone images, 171 | // and only if iPhone convert-to-rgb processing is on). 172 | // 173 | // =========================================================================== 174 | // 175 | // ADDITIONAL CONFIGURATION 176 | // 177 | // - You can suppress implementation of any of the decoders to reduce 178 | // your code footprint by #defining one or more of the following 179 | // symbols before creating the implementation. 180 | // 181 | // STBI_NO_JPEG 182 | // STBI_NO_PNG 183 | // STBI_NO_BMP 184 | // STBI_NO_PSD 185 | // STBI_NO_TGA 186 | // STBI_NO_GIF 187 | // STBI_NO_HDR 188 | // STBI_NO_PIC 189 | // STBI_NO_PNM (.ppm and .pgm) 190 | // 191 | // - You can request *only* certain decoders and suppress all other ones 192 | // (this will be more forward-compatible, as addition of new decoders 193 | // doesn't require you to disable them explicitly): 194 | // 195 | // STBI_ONLY_JPEG 196 | // STBI_ONLY_PNG 197 | // STBI_ONLY_BMP 198 | // STBI_ONLY_PSD 199 | // STBI_ONLY_TGA 200 | // STBI_ONLY_GIF 201 | // STBI_ONLY_HDR 202 | // STBI_ONLY_PIC 203 | // STBI_ONLY_PNM (.ppm and .pgm) 204 | // 205 | // - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still 206 | // want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB 207 | // 208 | // - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater 209 | // than that size (in either width or height) without further processing. 210 | // This is to let programs in the wild set an upper bound to prevent 211 | // denial-of-service attacks on untrusted data, as one could generate a 212 | // valid image of gigantic dimensions and force stb_image to allocate a 213 | // huge block of memory and spend disproportionate time decoding it. By 214 | // default this is set to (1 << 24), which is 16777216, but that's still 215 | // very big. 216 | 217 | #ifndef STBI_NO_STDIO 218 | #include 219 | #endif // STBI_NO_STDIO 220 | 221 | #define STBI_VERSION 1 222 | 223 | enum 224 | { 225 | STBI_default = 0, // only used for desired_channels 226 | 227 | STBI_grey = 1, 228 | STBI_grey_alpha = 2, 229 | STBI_rgb = 3, 230 | STBI_rgb_alpha = 4 231 | }; 232 | 233 | #include 234 | typedef unsigned char stbi_uc; 235 | typedef unsigned short stbi_us; 236 | 237 | #ifndef STBIDEF 238 | #ifdef STB_IMAGE_STATIC 239 | #define STBIDEF static 240 | #else 241 | #define STBIDEF extern 242 | #endif 243 | #endif 244 | 245 | // //////////////////////////////////////////////////////////////////////////// 246 | // 247 | // PRIMARY API - works on images of any type 248 | // 249 | 250 | // 251 | // load image by filename, open file, or memory buffer 252 | // 253 | 254 | typedef struct 255 | { 256 | int (*read)(void *user, char *data, int size); // fill 'data' with 'size' bytes. return number of bytes actually read 257 | void (*skip)(void *user, int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative 258 | int (*eof)(void *user); // returns nonzero if we are at end of file/data 259 | } stbi_io_callbacks; 260 | 261 | // ////////////////////////////////// 262 | // 263 | // 8-bits-per-channel interface 264 | // 265 | 266 | STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); 267 | STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); 268 | 269 | #ifndef STBI_NO_STDIO 270 | STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); 271 | STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); 272 | // for stbi_load_from_file, file pointer is left pointing immediately after image 273 | #endif 274 | 275 | #ifndef STBI_NO_GIF 276 | STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); 277 | #endif 278 | 279 | #ifdef STBI_WINDOWS_UTF8 280 | STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t *input); 281 | #endif 282 | 283 | // ////////////////////////////////// 284 | // 285 | // 16-bits-per-channel interface 286 | // 287 | 288 | STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); 289 | STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); 290 | 291 | #ifndef STBI_NO_STDIO 292 | STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); 293 | STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); 294 | #endif 295 | 296 | // ////////////////////////////////// 297 | // 298 | // float-per-channel interface 299 | // 300 | #ifndef STBI_NO_LINEAR 301 | STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); 302 | STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); 303 | 304 | #ifndef STBI_NO_STDIO 305 | STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); 306 | STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); 307 | #endif 308 | #endif 309 | 310 | #ifndef STBI_NO_HDR 311 | STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); 312 | STBIDEF void stbi_hdr_to_ldr_scale(float scale); 313 | #endif // STBI_NO_HDR 314 | 315 | #ifndef STBI_NO_LINEAR 316 | STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); 317 | STBIDEF void stbi_ldr_to_hdr_scale(float scale); 318 | #endif // STBI_NO_LINEAR 319 | 320 | // stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR 321 | STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); 322 | STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); 323 | #ifndef STBI_NO_STDIO 324 | STBIDEF int stbi_is_hdr(char const *filename); 325 | STBIDEF int stbi_is_hdr_from_file(FILE *f); 326 | #endif // STBI_NO_STDIO 327 | 328 | // get a VERY brief reason for failure 329 | // on most compilers (and ALL modern mainstream compilers) this is threadsafe 330 | STBIDEF const char *stbi_failure_reason(void); 331 | 332 | // free the loaded image -- this is just free() 333 | STBIDEF void stbi_image_free(void *retval_from_stbi_load); 334 | 335 | // get image dimensions & components without fully decoding 336 | STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); 337 | STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); 338 | STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); 339 | STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); 340 | 341 | #ifndef STBI_NO_STDIO 342 | STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp); 343 | STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp); 344 | STBIDEF int stbi_is_16_bit(char const *filename); 345 | STBIDEF int stbi_is_16_bit_from_file(FILE *f); 346 | #endif 347 | 348 | // for image formats that explicitly notate that they have premultiplied alpha, 349 | // we just return the colors as stored in the file. set this flag to force 350 | // unpremultiplication. results are undefined if the unpremultiply overflow. 351 | STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); 352 | 353 | // indicate whether we should process iphone images back to canonical format, 354 | // or just pass them through "as-is" 355 | STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); 356 | 357 | // flip the image vertically, so the first pixel in the output array is the bottom left 358 | STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); 359 | 360 | // as above, but only applies to images loaded on the thread that calls the function 361 | // this function is only available if your compiler supports thread-local variables; 362 | // calling it will fail to link if your compiler doesn't 363 | STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); 364 | 365 | // ZLIB client - used by PNG, available for other purposes 366 | 367 | STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); 368 | STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); 369 | STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); 370 | STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); 371 | 372 | STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); 373 | STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); 374 | 375 | #endif // STBI_INCLUDE_STB_IMAGE_H 376 | --------------------------------------------------------------------------------