1
0
Fork 0

Just a Vulkan triangle

tmp
May B. 2020-09-27 00:05:43 +02:00
parent ba8405e51f
commit 41eb636826
54 changed files with 2063 additions and 379 deletions

2
.gitattributes vendored
View File

@ -9,3 +9,5 @@
*.tga filter=lfs diff=lfs merge=lfs -text
*.tiff filter=lfs diff=lfs merge=lfs -text
*.BMP filter=lfs diff=lfs merge=lfs -text
# Shaders
*.spv filter=lfs diff=lfs merge=lfs -text

View File

@ -41,8 +41,8 @@ file(GLOB_RECURSE CORE_SOURCES "src/core/*.cpp" "include/tracy/TracyClient.cpp")
set(CORE_HEADERS "include/toml++" "include/robin_hood" "include/libguarded" "include/tracy")
set(CORE_LIBS pthread dl glm::glm_static enet::enet_static zstd::zstd_static)
file(GLOB_RECURSE CLIENT_SOURCES "src/client/*.cpp" "include/imgui/*.cpp" "include/meshoptimizer/*.cpp" "include/gl3w/gl3w.c")
set(CLIENT_HEADERS "include/imgui" "include/meshoptimizer" "include/gl3w")
file(GLOB_RECURSE CLIENT_SOURCES "src/client/*.cpp" "include/imgui/*.cpp" "include/meshoptimizer/*.cpp" "include/gl3w/gl3w.c" "include/volk/volk.c")
set(CLIENT_HEADERS "include/imgui" "include/meshoptimizer" "include/gl3w" "include/volk")
set(CLIENT_LIBS glfw)
file(GLOB_RECURSE SERVER_SOURCES "src/server/*.cpp" "include/FastNoiseSIMD/*.cpp")

View File

@ -54,6 +54,7 @@ To get a local copy up and running, follow these simple steps.
* Python: utility scripts
* Tracy v0.7: profiling
* glslc: compile vk shaders
### Installation

View File

@ -3,7 +3,8 @@
## Hello screen again
- [~] Extract OpenGL
- [ ] Minimal Vulkan
- Rename Passes/Programs as Pipelines
- [~] Minimal Vulkan
- [ ] ImGui
- [ ] Config (yaml)

BIN
resource/content/shaders/Tris.fs.spv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
resource/content/shaders/Tris.vs.spv (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,10 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}

View File

@ -0,0 +1,21 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) out vec3 fragColor;
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = colors[gl_VertexIndex];
}

View File

@ -0,0 +1,227 @@
#version 450 core
layout (constant_id = 0) const bool FOG = true;
layout (constant_id = 1) const bool PBR = true;
layout (constant_id = 2) const bool TRIPLANAR = false;
layout (constant_id = 3) const bool STOCHASTIC = false;
layout (constant_id = 4) const bool BLEND = true;
layout (constant_id = 16) UNIT_SIZE = 8;
// Ouput data
layout(location = 0) out vec3 color;
uniform sampler2DArray TextureAtlas;
uniform sampler2DArray NormalAtlas;
uniform sampler2DArray HOSAtlas;
uniform mat4 View;
uniform vec3 FogColor;
#ifdef GEOMETRY
in GeometryData
#else
in VertexData
#endif
{
vec3 Position_modelspace;
#ifdef GEOMETRY
flat uint Textures[3];
vec3 TextureRatio;
#else
flat uint Texture;
#endif
vec3 FaceNormal_modelspace;
vec3 FaceNormal_worldspace;
vec3 EyeDirection_cameraspace;
vec3 LightDirection_cameraspace;
float Depth;
} vs;
vec3 expand(vec3 v) {
return (v - 0.5) * 2;
}
vec2 hash2D(vec2 s) {
// magic numbers
return fract(sin(mod(vec2(dot(s, vec2(127.1, 311.7)), dot(s, vec2(269.5, 183.3))), 3.14159)) * 43758.5453);
}
vec4 textureStochastic(sampler2DArray sample, vec3 UV) {
if(STOCHASTIC) {
// triangular by approx 2*sqrt(3)
vec2 skewUV = mat2(1.0, 0.0, -0.57735027, 1.15470054) * (UV.xy * 3.46400);
// vertex id and barrycentric coords
vec2 vxID = vec2(floor(skewUV));
vec3 barry = vec3(fract(skewUV), 0);
barry.z = 1.0 - barry.x - barry.y;
vec3 BW_vx0;
vec3 BW_vx1;
vec3 BW_vx2;
vec3 BW_vx3;
if(barry.z > 0) {
BW_vx0 = vec3(vxID, 0);
BW_vx1 = vec3(vxID + vec2(0, 1), 0);
BW_vx2 = vec3(vxID + vec2(1, 0), 0);
BW_vx3 = barry.zyx;
} else {
BW_vx0 = vec3(vxID + vec2(1, 1), 0);
BW_vx1 = vec3(vxID + vec2(1, 0), 0);
BW_vx2 = vec3(vxID + vec2(0, 1), 0);
BW_vx3 = vec3(-barry.z, 1.0 - barry.y, 1.0 - barry.x);
}
vec2 dx = dFdx(UV.xy);
vec2 dy = dFdy(UV.xy);
return textureGrad(sample, vec3(UV.xy + hash2D(BW_vx0.xy), UV.z), dx, dy) * BW_vx3.x +
textureGrad(sample, vec3(UV.xy + hash2D(BW_vx1.xy), UV.z), dx, dy) * BW_vx3.y +
textureGrad(sample, vec3(UV.xy + hash2D(BW_vx2.xy), UV.z), dx, dy) * BW_vx3.z;
} else {
return texture(sample, UV);
}
}
vec4 getTexture(sampler2DArray sample, vec2 UV) {
#ifdef GEOMETRY
if(BLEND) {
vec4 colx = textureStochastic(sample, vec3(UV, vs.Textures[0]));
if(vs.Textures[1] == vs.Textures[0]) {
return vs.Textures[2] == vs.Textures[0] ? colx :
mix(colx, textureStochastic(sample, vec3(UV, vs.Textures[2])), vs.TextureRatio.z);
} else {
vec4 coly = textureStochastic(sample, vec3(UV, vs.Textures[1]));
return vs.Textures[2] == vs.Textures[0] ? mix(colx, coly, vs.TextureRatio.y) : (
vs.Textures[2] == vs.Textures[1] ? mix(coly, colx, vs.TextureRatio.x) :
colx * vs.TextureRatio.x + coly * vs.TextureRatio.y + textureStochastic(sample, vec3(UV, vs.Textures[2])) * vs.TextureRatio.z);
}
} else {
int mainTexture = vs.TextureRatio.x >= vs.TextureRatio.y ?
(vs.TextureRatio.x >= vs.TextureRatio.z ? 0 : 2) :
(vs.TextureRatio.y >= vs.TextureRatio.z ? 1 : 2);
return textureStochastic(sample, vec3(UV, vs.Textures[mainTexture]));
}
#else
return textureStochastic(sample, vec3(UV, vs.Texture));
#endif
}
vec3 getTriTexture(sampler2DArray sample, vec2 crdx, vec2 crdy, vec2 crdz, vec3 weights) {
return getTexture(sample, crdx).rgb * weights.x +
getTexture(sample, crdy).rgb * weights.y +
getTexture(sample, crdz).rgb * weights.z;
}
void main() {
float texScale = 1. / UNIT_SIZE;
if(TRIPLANAR) {
// Triplanar
float plateauSize = 0.001;
float transitionSpeed = 2;
vec3 blendWeights = abs(vs.FaceNormal_modelspace);
blendWeights = blendWeights - plateauSize;
blendWeights = pow(max(blendWeights, 0), vec3(transitionSpeed));
vec2 UVx = vs.Position_modelspace.yz * texScale;
vec2 UVy = vs.Position_modelspace.zx * texScale;
vec2 UVz = vs.Position_modelspace.xy * texScale;
vec3 tex = getTriTexture(TextureAtlas, UVx, UVy, UVz, blendWeights);
if(PBR) {
// Whiteout normal blend
vec3 texNx = expand(getTexture(NormalAtlas, UVx).rgb);
vec3 texNy = expand(getTexture(NormalAtlas, UVy).rgb);
vec3 texNz = expand(getTexture(NormalAtlas, UVz).rgb);
// Swizzle world normals into tangent space and apply Whiteout blend
texNx = vec3(texNx.xy + vs.FaceNormal_worldspace.zy, abs(texNx.z) * vs.FaceNormal_worldspace.x);
texNy = vec3(texNy.xy + vs.FaceNormal_worldspace.xz, abs(texNy.z) * vs.FaceNormal_worldspace.y);
texNz = vec3(texNz.xy + vs.FaceNormal_worldspace.xy, abs(texNz.z) * vs.FaceNormal_worldspace.z);
// Swizzle tangent normals to match world orientation and triblend
vec3 worldNormal = normalize(texNx.zyx * blendWeights.x + texNy.xzy * blendWeights.y +texNz.xyz * blendWeights.z);
vec3 texHOS = getTriTexture(HOSAtlas, UVx, UVy, UVz, blendWeights);
}
} else {
// Cheap planar
vec3 blendWeights = abs(vs.FaceNormal_modelspace);
vec3 nrm = normalize(pow(blendWeights, vec3(80 / sqrt(UNIT_SIZE))));
vec2 UV = (vec2(vs.Position_modelspace.xy * nrm.z) + vec2(vs.Position_modelspace.yz * nrm.x) + vec2(vs.Position_modelspace.zx * nrm.y)) * texScale;
vec3 tex = getTexture(TextureAtlas, UV).rgb;
if(PBR) {
vec3 texN = expand(getTexture(NormalAtlas, UV).rgb);
// Swizzle world normals into tangent space and apply Whiteout blend
// Swizzle tangent normals to match world orientation and triblend
vec3 worldNormal = normalize(vec3(texN.xy + vs.FaceNormal_worldspace.zy, abs(texN.z) * vs.FaceNormal_worldspace.x).zyx * blendWeights.x +
vec3(texN.xy + vs.FaceNormal_worldspace.xz, abs(texN.z) * vs.FaceNormal_worldspace.y).xzy * blendWeights.y +
vec3(texN.xy + vs.FaceNormal_worldspace.xy, abs(texN.z) * vs.FaceNormal_worldspace.z).xyz * blendWeights.z);
vec3 texHOS = getTexture(HOSAtlas, UV).rgb;
}
}
// Colors
if(PBR) {
// Texture properties
vec3 TextureDiffuseColor = tex;
vec3 TextureAmbientColor = vec3(.1) * TextureDiffuseColor * texHOS.y;
vec3 TextureSpecularColor = vec3(.8) * texHOS.z;
vec3 Normal_cameraspace = normalize((View * vec4(worldNormal,0)).xyz);
// Light emission properties
// You probably want to put them as uniforms
vec3 LightColor = vec3(1, 0.9, 0.9);
float LightPower = 1.2f;
// Distance to the light
float distance = 1.0f;//length( LightPosition_worldspace - Position_worldspace );
// Direction of the light (from the fragment to the light)
vec3 l = normalize(vs.LightDirection_cameraspace);
// Cosine of the angle between the normal and the light direction,
// clamped above 0
// - light is at the vertical of the triangle -> 1
// - light is perpendiular to the triangle -> 0
// - light is behind the triangle -> 0
float cosTheta = clamp(dot(Normal_cameraspace,l), 0,1 );
// Eye vector (towards the camera)
vec3 E = normalize(vs.EyeDirection_cameraspace);
// Direction in which the triangle reflects the light
vec3 R = reflect(-l,Normal_cameraspace);
// Cosine of the angle between the Eye vector and the Reflect vector,
// clamped to 0
// - Looking into the reflection -> 1
// - Looking elsewhere -> < 1
float cosAlpha = clamp( dot( E,R ), 0,1 );
float visibility=1.0;
// MAYBE: shadow
color =
// Ambient : simulates indirect lighting
TextureAmbientColor +
// Diffuse : "color" of the object
visibility * TextureDiffuseColor * LightColor * LightPower * cosTheta / (distance * distance) +
// Specular : reflective highlight, like a mirror
visibility * TextureSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance * distance);
} else {
color = tex;
}
if(FOG) {
float ratio = exp(vs.Depth * 0.69)-1;
color = mix(color, pow(FogColor, vec3(2.2)), clamp(ratio, 0, 1));
}
color = pow(color, vec3(1.0 / 2.2));
if(color.r > 1 || color.g > 1 || color.b > 1) {
color = vec3(1, 0, 0); //TODO: bloom
}
}

View File

@ -0,0 +1,71 @@
#version 450 core
layout (constant_id = 0) const bool FOG = true;
layout (constant_id = 1) const bool PBR = true;
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in VertexData {
vec3 Position_modelspace;
flat uint Texture;
vec3 FaceNormal_modelspace;
vec3 FaceNormal_worldspace;
vec3 EyeDirection_cameraspace;
vec3 LightDirection_cameraspace;
float Depth;
} vs_in[];
out GeometryData {
vec3 Position_modelspace;
flat uint Textures[3];
vec3 TextureRatio;
vec3 FaceNormal_modelspace;
vec3 FaceNormal_worldspace;
vec3 EyeDirection_cameraspace;
vec3 LightDirection_cameraspace;
float Depth;
} gs;
void main() {
for(int i = 0; i < gl_in.length(); i++) {
gl_Position = gl_in[i].gl_Position;
gs.Position_modelspace = vs_in[i].Position_modelspace;
gs.FaceNormal_modelspace = vs_in[i].FaceNormal_modelspace;
gs.Textures[i] = vs_in[i].Texture;
switch(int(mod(i,3))) {
case 0:
gs.TextureRatio = vec3(1,0,0);
break;
case 1:
gs.TextureRatio = vec3(0,1,0);
break;
case 2:
gs.TextureRatio = vec3(0,0,1);
break;
default:
gs.TextureRatio = vec3(0);
break;
};
if(PBR) {
gs.FaceNormal_worldspace = vs_in[i].FaceNormal_worldspace;
gs.EyeDirection_cameraspace = vs_in[i].EyeDirection_cameraspace;
gs.LightDirection_cameraspace = vs_in[i].LightDirection_cameraspace;
}
#ifdef SHADOW
gs.ShadowCoord = vs_in[i].ShadowCoord;
#endif
if(FOG) {
gs.Depth = vs_in[i].Depth;
}
EmitVertex();
}
EndPrimitive();
}

View File

@ -0,0 +1,76 @@
#version 450 core
layout (constant_id = 0) const bool FOG = true;
layout (constant_id = 1) const bool PBR = true;
// FS spe
layout (constant_id = 5) const bool DO_CURVATURE = false;
layout (constant_id = 6) const bool CURV_DEPTH = true;
layout(location = 0) in vec3 Position_modelspace;
layout(location = 1) in uint Texture_model;
layout(location = 2) in vec3 Normal_modelspace;
out VertexData {
vec3 Position_modelspace;
flat uint Texture;
vec3 FaceNormal_modelspace;
vec3 FaceNormal_worldspace;
vec3 EyeDirection_cameraspace;
vec3 LightDirection_cameraspace;
float Depth;
} vs;
#ifdef INSTANCED
layout(location = 6) in mat4 Model;
#else
uniform mat4 Model;
#endif
uniform mat4 View;
uniform mat4 Proj;
uniform vec4 SphereProj;
uniform float Curvature;
uniform vec3 LightInvDirection_worldspace;
uniform float FogDepth;
void main(){
vs.Position_modelspace = Position_modelspace;
if(DO_CURVATURE) {
if(Curvature > 0) {
vec3 Position_areaspace = Position_modelspace + SphereProj.xyz;
vec2 sph = vec2(acos(Position_areaspace.z / length(Position_areaspace.xyz)), atan(Position_areaspace.y, Position_areaspace.x));
if(CURV_DEPTH) {
float radius = max(max(abs(Position_areaspace.x), abs(Position_areaspace.y)), abs(Position_areaspace.z));
} else {
float radius = SphereProj.w;
}
vs.Position_modelspace = mix(vs.Position_modelspace, vec3(sin(sph.x)*cos(sph.y), sin(sph.x)*sin(sph.y), cos(sph.x)) * radius - SphereProj.xyz, Curvature);
}
}
vec4 Position_cameraspace = View * Model * vec4(vs.Position_modelspace, 1);
gl_Position = Proj * Position_cameraspace;
if(FOG) {
vs.Depth = length(Position_cameraspace.xyz) / FogDepth;
}
vs.Texture = Texture_model;
vs.FaceNormal_modelspace = normalize(Normal_modelspace);
if(PBR)
// TODO: correct normal curvature
vs.FaceNormal_worldspace = normalize((Model * vec4(vs.FaceNormal_modelspace, 0)).xyz);
// Vector that goes from the vertex to the camera, in camera space.
// In camera space, the camera is at the origin (0,0,0).
vs.EyeDirection_cameraspace = vec3(0,0,0) - Position_cameraspace.xyz;
// Vector that goes from the vertex to the light, in camera space
vs.LightDirection_cameraspace = (View * vec4(LightInvDirection_worldspace,0)).xyz;
}
}

29
resource/shaders-src/compile.sh Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
BASEDIR=$(dirname "$0")
TARGETDIR="$BASEDIR/../content/shaders"
# Tris
glslc -fshader-stage=vertex $BASEDIR/Tris.vs -o $TARGETDIR/Tris.vs.spv
glslc -fshader-stage=fragment $BASEDIR/Tris.fs -o $TARGETDIR/Tris.fs.spv
# Color
glslc -fshader-stage=vertex $TARGETDIR/Color.vs -o $TARGETDIR/Color.vs.spv
glslc -fshader-stage=fragment $TARGETDIR/Color.fs -o $TARGETDIR/Color.fs.spv
# Sky
glslc -fshader-stage=vertex $TARGETDIR/Sky.vs -o $TARGETDIR/Sky.vs.spv
glslc -fshader-stage=fragment $TARGETDIR/Sky.fs -o $TARGETDIR/Sky.fs.spv
# Voxel
glslc -fshader-stage=vertex $BASEDIR/Voxel.vs -o $TARGETDIR/Voxel.vs.spv
glslc -fshader-stage=fragment $BASEDIR/Voxel.fs -o $TARGETDIR/Voxel.fs.spv
glslc -fshader-stage=vertex $BASEDIR/Voxel.vs -DINSTANCED -o $TARGETDIR/Voxel.vs.ins.spv
glslc -fshader-stage=fragment $BASEDIR/Voxel.fs -DINSTANCED -o $TARGETDIR/Voxel.fs.ins.spv
glslc -fshader-stage=vertex $BASEDIR/Voxel.vs -DGEOMETRY -o $TARGETDIR/Voxel.vs.geo.spv
glslc -fshader-stage=geometry $BASEDIR/Voxel.gs -DGEOMETRY -o $TARGETDIR/Voxel.gs.geo.spv
glslc -fshader-stage=fragment $BASEDIR/Voxel.fs -DGEOMETRY -o $TARGETDIR/Voxel.fs.geo.spv
glslc -fshader-stage=vertex $BASEDIR/Voxel.vs -DGEOMETRY -DINSTANCED -o $TARGETDIR/Voxel.vs.geo.ins.spv
glslc -fshader-stage=geometry $BASEDIR/Voxel.gs -DGEOMETRY -DINSTANCED -o $TARGETDIR/Voxel.gs.geo.ins.spv
glslc -fshader-stage=fragment $BASEDIR/Voxel.fs -DGEOMETRY -DINSTANCED -o $TARGETDIR/Voxel.fs.geo.ins.spv

View File

@ -1,8 +1,8 @@
#include "Client.hpp"
#include "render/gl/Renderer.hpp"
#include "render/gl/UI.hpp"
#include "render/gl/Pipeline.hpp"
#include "render/index.hpp"
#include "render/UI.hpp"
#include "InputMap.hpp"
#include "world/index.hpp"
#include <glm/gtc/matrix_transform.hpp>
@ -13,7 +13,7 @@ Client::Client(config::client::options& options): options(options) { }
Client::~Client() { }
void Client::run(server_handle* const localHandle) {
if (!render::gl::Renderer::Load(window, {options.renderer.clear_color}))
if (!render::Load(window, options.preferVulkan, options.renderer))
return;
window.setTargetFPS(options.window.targetFPS);
@ -23,7 +23,7 @@ void Client::run(server_handle* const localHandle) {
Controllable player(window.getPtr(), inputs, options.control);
Camera camera(&player, options.camera);
auto *pipeline = static_cast<render::gl::Pipeline*>(render::Renderer::Get()->createPipeline(options.renderer));
auto pipeline = render::Renderer::Get();
pipeline->LightInvDir = glm::normalize(glm::vec3(-.5f, 2, -2));
render::Renderer::Get()->loadUI(window);
@ -119,7 +119,6 @@ void Client::run(server_handle* const localHandle) {
world->setContouring(contouring::load(options.contouring.idx, options.contouring.data));
state.contouring = world->getContouring();
}
pipeline->SkyEnable = options.renderer.skybox;
}
{ // Rendering
ZoneScopedN("Render");
@ -135,8 +134,7 @@ void Client::run(server_handle* const localHandle) {
{ // Chunks
const auto pass = pipeline->beginWorldPass();
const auto draw = [&](glm::mat4 model, buffer::Abstract *const buffer, const contouring::Abstract::area_info &area, const voxel_pos &pos) {
pipeline->SphereProj = glm::vec4(pos, std::get<1>(area));
pipeline->Curvature = std::get<2>(area);
pipeline->setCurvature(glm::vec4(pos, std::get<1>(area)), std::get<2>(area));
reports.models_count++;
reports.tris_count += buffer->draw(pass(model));
};
@ -160,10 +158,10 @@ void Client::run(server_handle* const localHandle) {
}
{ // Entities
const auto pass = pipeline->beginEntityPass();
const auto draw = [&](const std::vector<glm::mat4>& models, buffer::Abstract *const buffer) {
/*const auto draw = [&](const std::vector<glm::mat4>& models, buffer::Abstract *const buffer) {
reports.models_count += models.size();
reports.tris_count += buffer->draw(pass(models));
};
};*/
//world->getEntitiesModels(draw, frustum, offset, options.voxel_density);
}
if(state.look_at.has_value()) { // Indicator
@ -178,7 +176,7 @@ void Client::run(server_handle* const localHandle) {
{ // Swap buffers
ZoneScopedN("Swap");
pipeline->swapBuffer(window.getPtr());
pipeline->swapBuffer(window);
inputs.poll();
FrameMarkNamed("Client");
}
@ -187,7 +185,6 @@ void Client::run(server_handle* const localHandle) {
} while (!(inputs.isDown(Input::Quit) || window.shouldClose()));
render::UI::Unload();
delete pipeline;
render::Renderer::Unload();
window.destroy();

View File

@ -42,6 +42,11 @@ bool Window::create(const CreateInfo &opt) {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
break;
case CreateInfo::Client::Type::VK:
LOG_W("WIP client type");
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
break;
default:
FATAL("Unsupported client type");
break;
@ -105,6 +110,7 @@ void Window::waitTargetFPS() {
}
void Window::destroy() {
glfwDestroyWindow(ptr);
ptr = nullptr;
}
bool Window::shouldClose() { return glfwWindowShouldClose(ptr); }

View File

@ -1,6 +1,7 @@
#pragma once
#include "../core/flags.hpp"
#include <tuple>
typedef struct GLFWwindow GLFWwindow;
typedef void (*GLFWframebuffersizefun)(GLFWwindow*, int, int);

View File

@ -5,7 +5,7 @@
#include <limits.h>
#include <iomanip>
#include "world/Universe.hpp"
#include "render/Pipeline.hpp"
#include "render/Renderer.hpp"
#include "contouring/index.hpp"
#include "control/Camera.hpp"
@ -31,6 +31,7 @@ public:
window.targetFPS = config["window"]["target_fps"].value_or(window.targetFPS);
window.samples = config["window"]["samples"].value_or(window.samples);
window.fullscreen = config["window"]["fullscreen"].value_or(window.fullscreen);
renderer.inFlightFrames = config["window"]["parallel_frames"].value_or(renderer.inFlightFrames);
preferVulkan = config["render"]["prefer_vulkan"].value_or(preferVulkan);
renderer.textures = config["render"]["textures"].value_or(renderer.textures);
@ -99,7 +100,8 @@ public:
config.insert_or_assign("window", toml::table({
{"target_fps", window.targetFPS},
{"samples", window.samples},
{"fullscreen", window.fullscreen}
{"fullscreen", window.fullscreen},
{"parallel_frames", renderer.inFlightFrames}
}));
config.insert_or_assign("render", toml::table({
{"prefer_vulkan", preferVulkan},
@ -194,7 +196,7 @@ public:
} window;
bool preferVulkan = true;
render::Pipeline::options renderer;
render::renderOptions renderer;
int culling = 0;
struct {
@ -219,6 +221,6 @@ public:
int corner = 3;
} overlay;
std::optional<world::client::Universe::connection> connection;
std::optional<world::client::Universe::connection> connection = {};
};
}

View File

@ -1,45 +0,0 @@
#pragma once
#include "gl/pass/VoxelProgram.hpp"
struct GLFWwindow;
class Camera;
namespace render {
/// Handle rendering passes and params
class Pipeline {
public:
/// Rendering options
struct options {
/// Voxel passes
pass::VoxelProgram::options voxel;
/// Display skybox
bool skybox = true;
/// Display only wires
bool wireframe = false;
/// Texture pack name
std::string textures = "1024-realistic";
/// Textures quality
float mipMapLOD = -.5;
/// Textures anisotropic mapping
int anisotropy = 0;
/// Depth color
glm::vec4 clear_color;
};
virtual ~Pipeline() { }
/// Start new frame and setup
virtual void beginFrame() = 0;
/// Apply postprocessing
virtual void endPass() = 0;
/// Swap displayed image
virtual void swapBuffer(GLFWwindow*) = 0;
/// Apply camera matrices
virtual void lookFrom(const Camera&) = 0;
virtual void setClearColor(glm::vec4) = 0;
};
}

View File

@ -1,17 +1,85 @@
#pragma once
#include "../Window.hpp"
#include "Pipeline.hpp"
#include "../../core/flags.hpp"
#include "buffer/Abstract.hpp"
#include <cassert>
#include <memory>
#include <string>
#include <glm/glm.hpp>
#include <functional>
class Window;
class Camera;
namespace render {
/// Pass options
struct passOptions {
/// Apply light properties
bool pbr = true;
/// Triplanar texture mapping
bool triplanar = false;
/// Transform texture UV
bool stochastic = false;
/// Active geometry pass
bool geometry = false;
/// Blend voxel with mixed materials (requires geometry)
bool blend = true;
/// Depth fog
bool fog = true;
/// Map planets to sphere
bool curvature = true;
/// Keep depth in sphere
bool curv_depth = true;
};
/// Rendering options
struct renderOptions {
/// Voxel passes
passOptions voxel;
/// Display skybox
bool skybox = true;
/// Display only wires
bool wireframe = false;
/// Texture pack name
std::string textures = "1024-realistic";
/// Textures quality
float mipMapLOD = -.5;
/// Textures anisotropic mapping
int anisotropy = 0;
/// Depth color
glm::vec4 clear_color;
/// Parallel processing frames
/// Incease FPS but also a bit latency (Vulkan only)
int inFlightFrames = 2;
};
/// Rendering plateform interface
class Renderer {
public:
virtual ~Renderer() { }
virtual Pipeline* createPipeline(const Pipeline::options&) = 0;
glm::vec3 LightInvDir = glm::vec3(0.5f, 2, 2);
/// Start new frame and setup
virtual void beginFrame() = 0;
/// Get started world program
virtual std::function<buffer::params(glm::mat4)> beginWorldPass() = 0;
/// Get started entity program
virtual std::function<buffer::params(const std::vector<glm::mat4> &)> beginEntityPass() = 0;
/// Draw cube indicator
virtual size_t drawIndicatorCube(glm::mat4 model) = 0;
/// Apply postprocessing
virtual void endPass() = 0;
/// Swap displayed image
virtual void swapBuffer(Window &) = 0;
/// Apply camera matrices
virtual void lookFrom(const Camera &) = 0;
virtual void setClearColor(glm::vec4) = 0;
virtual void setCurvature(glm::vec4, float) = 0;
virtual void reloadShaders(const passOptions &) = 0;
virtual void reloadTextures(const std::string &, float mipMapLOD, float anisotropy) = 0;
virtual void loadUI(Window&) = 0;

View File

@ -0,0 +1,14 @@
#pragma once
#include <GL/gl3w.h>
#include <sys/types.h>
namespace buffer {
/// Draw options
struct params {
/// Bind only vertices positions
bool vertexOnly;
/// Draw instanced model
size_t instances = 1;
};
}

View File

@ -1,128 +0,0 @@
#include "Pipeline.hpp"
#include "../../../core/world/materials.hpp"
#include "../../control/Camera.hpp"
#include <TracyOpenGL.hpp>
using namespace render::gl;
Pipeline::Pipeline(const Pipeline::options& options):
IndicatorCubeBuffer(GL_LINES, 24, {
glm::vec3(0, 0, 0), glm::vec3(0, 0, 1),
glm::vec3(0, 0, 1), glm::vec3(0, 1, 1),
glm::vec3(0, 1, 1), glm::vec3(0, 1, 0),
glm::vec3(0, 1, 0), glm::vec3(0, 0, 0),
glm::vec3(1, 0, 0), glm::vec3(1, 0, 1),
glm::vec3(1, 0, 1), glm::vec3(1, 1, 1),
glm::vec3(1, 1, 1), glm::vec3(1, 1, 0),
glm::vec3(1, 1, 0), glm::vec3(1, 0, 0),
glm::vec3(0, 0, 0), glm::vec3(1, 0, 0),
glm::vec3(0, 0, 1), glm::vec3(1, 0, 1),
glm::vec3(0, 1, 1), glm::vec3(1, 1, 1),
glm::vec3(0, 1, 0), glm::vec3(1, 1, 0),
}, {
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
}) {
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);
reloadShaders(options.voxel);
SkyPass = std::make_unique<pass::SkyProgram>();
SkyEnable = options.skybox;
IndicatorPass = std::make_unique<pass::ColorProgram>();
FogColor = glm::vec3(options.clear_color.x, options.clear_color.y, options.clear_color.z);
loadTextures(options.textures, options.mipMapLOD, options.anisotropy);
}
Pipeline::~Pipeline() {
unloadTextures();
glDeleteVertexArrays(1, &VertexArrayID);
}
void Pipeline::beginFrame() {
TracyGpuZone("Render");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
std::function<buffer::params(glm::mat4)> Pipeline::beginWorldPass() {
WorldPass->useIt();
WorldPass->start(this);
return [&](glm::mat4 model) {
return WorldPass->setup(this, model);
};
}
std::function<buffer::params(const std::vector<glm::mat4> &)> Pipeline::beginEntityPass() {
EntityPass->useIt();
EntityPass->start(this);
return [&](const std::vector<glm::mat4>& models) {
return EntityPass->setup(this, models);
};
}
size_t Pipeline::drawIndicatorCube(glm::mat4 model) {
IndicatorPass->useIt();
return IndicatorCubeBuffer.draw(IndicatorPass->setup(this, model));
}
void Pipeline::endPass() {
if(SkyEnable) {
SkyPass->draw(this);
}
}
void Pipeline::swapBuffer(GLFWwindow* ptr) {
TracyGpuZone("Swap");
glfwSwapBuffers(ptr);
TracyGpuCollect;
}
void Pipeline::reloadShaders(const pass::VoxelProgram::options& options) {
WorldPass = std::make_unique<pass::WorldProgram>(options);
EntityPass = std::make_unique<pass::EntityProgram>(options);
}
void Pipeline::reloadTextures(const std::string& texturePath, float mipMapLOD, float anisotropy) {
unloadTextures();
loadTextures(texturePath, mipMapLOD, anisotropy);
}
void Pipeline::unloadTextures() {
glDeleteTextures(1, &HOSAtlas);
glDeleteTextures(1, &NormalAtlas);
glDeleteTextures(1, &TextureAtlas);
}
void Pipeline::loadTextures(const std::string& texturePath, float mipMapLOD, float anisotropy) {
std::vector<std::string> terrainTextures;
for(const auto& texture: world::materials::textures) {
terrainTextures.emplace_back(texturePath + "/terrain/" + texture);
}
const auto ani = anisotropy >= 1 ? (1 << (static_cast<int>(anisotropy)-1)) : 0;
TextureAtlas = pass::Program::loadTextureArray(terrainTextures, "", mipMapLOD, ani);
NormalAtlas = pass::Program::loadTextureArray(terrainTextures, ".nrm", mipMapLOD, ani);
HOSAtlas = pass::Program::loadTextureArray(terrainTextures, ".hos", mipMapLOD, ani);
Skybox = pass::Program::loadTextureCube(texturePath + "/sky/Space_tray");
}
void Pipeline::lookFrom(const Camera& camera) {
ProjectionMatrix = camera.getProjectionMatrix();
ViewMatrix = camera.getViewMatrix();
FogDepth = camera.getDepth();
}
void Pipeline::setClearColor(glm::vec4 c) {
FogColor = c;
glClearColor(c.r, c.g, c.b, c.a);
}

View File

@ -1,97 +0,0 @@
#pragma once
#include "../Pipeline.hpp"
#include <functional>
#include <memory>
#include <GL/gl3w.h>
#include "pass/WorldProgram.hpp"
#include "pass/EntityProgram.hpp"
#include "pass/SkyProgram.hpp"
#include "pass/ColorProgram.hpp"
#include "buffer/Colored.hpp"
namespace render::gl {
/// Handle OpenGL rendering passes and params
class Pipeline final: public render::Pipeline {
public:
Pipeline(const options&);
Pipeline(Pipeline &&) = delete;
Pipeline(const Pipeline &) = delete;
Pipeline &operator=(Pipeline &&) = delete;
Pipeline &operator=(const Pipeline &) = delete;
~Pipeline();
glm::vec3 LightInvDir = glm::vec3(0.5f, 2, 2);
glm::vec3 FogColor;
GLfloat FogDepth;
/// Sphere bending
/// offset.xyz radius.w
glm::vec4 SphereProj;
/// Ratio between spherical and cartesian
float Curvature;
bool SkyEnable;
glm::mat4 getProjectionMatrix() const {
return ProjectionMatrix;
}
glm::mat4 getViewMatrix() const {
return ViewMatrix;
}
GLuint getTextureAtlas() const {
return TextureAtlas;
}
GLuint getNormalAtlas() const {
return NormalAtlas;
}
GLuint getHOSAtlas() const {
return HOSAtlas;
}
GLuint getSkyTexture() const {
return Skybox;
}
/// Start new frame and setup
void beginFrame() override;
/// Get started world program
std::function<buffer::params(glm::mat4)> beginWorldPass();
/// Get started entity program
std::function<buffer::params(const std::vector<glm::mat4>&)> beginEntityPass();
/// Draw cube indicator
size_t drawIndicatorCube(glm::mat4 model);
/// Apply postprocessing
void endPass() override;
void swapBuffer(GLFWwindow *) override;
void setClearColor(glm::vec4) override;
/// Apply camera matrices
void lookFrom(const Camera&) override;
void reloadShaders(const pass::VoxelProgram::options &);
void reloadTextures(const std::string &, float mipMapLOD, float anisotropy);
private:
GLuint VertexArrayID;
std::unique_ptr<pass::WorldProgram> WorldPass;
std::unique_ptr<pass::EntityProgram> EntityPass;
std::unique_ptr<pass::SkyProgram> SkyPass;
std::unique_ptr<pass::ColorProgram> IndicatorPass;
buffer::Colored IndicatorCubeBuffer;
glm::mat4 ProjectionMatrix;
glm::mat4 ViewMatrix;
GLuint TextureAtlas;
GLuint NormalAtlas;
GLuint HOSAtlas;
GLuint Skybox;
void loadTextures(const std::string &, float mipMapLOD, float anisotropy);
void unloadTextures();
};
}

View File

@ -1,7 +1,9 @@
#include "Renderer.hpp"
#include "UI.hpp"
#include "Pipeline.hpp"
#include "../../../core/world/materials.hpp"
#include "../../control/Camera.hpp"
#include "../../Window.hpp"
#include "../../../core/utils/logger.hpp"
#include <GL/gl3w.h>
@ -12,11 +14,59 @@ using namespace render::gl;
constexpr auto GL_MAJOR = 4;
constexpr auto GL_MINOR = 6;
Renderer::Renderer(const Options& options): options(options) { }
Renderer::~Renderer() { }
Renderer::Renderer(const renderOptions& options):
IndicatorCubeBuffer(GL_LINES, 24, {
glm::vec3(0, 0, 0), glm::vec3(0, 0, 1),
glm::vec3(0, 0, 1), glm::vec3(0, 1, 1),
glm::vec3(0, 1, 1), glm::vec3(0, 1, 0),
glm::vec3(0, 1, 0), glm::vec3(0, 0, 0),
glm::vec3(1, 0, 0), glm::vec3(1, 0, 1),
glm::vec3(1, 0, 1), glm::vec3(1, 1, 1),
glm::vec3(1, 1, 1), glm::vec3(1, 1, 0),
glm::vec3(1, 1, 0), glm::vec3(1, 0, 0),
glm::vec3(0, 0, 0), glm::vec3(1, 0, 0),
glm::vec3(0, 0, 1), glm::vec3(1, 0, 1),
glm::vec3(0, 1, 1), glm::vec3(1, 1, 1),
glm::vec3(0, 1, 0), glm::vec3(1, 1, 0),
}, {
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
glm::vec4(1, 1, 1, 1), glm::vec4(1, 1, 1, 1),
}) {
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);
bool Renderer::Load(Window& window, const Options& options) {
auto windowInfo = GetWindowInfo();
reloadShaders(options.voxel);
SkyPass = std::make_unique<pass::SkyProgram>();
SkyEnable = options.skybox;
IndicatorPass = std::make_unique<pass::ColorProgram>();
FogColor = glm::vec3(options.clear_color.x, options.clear_color.y, options.clear_color.z);
loadTextures(options.textures, options.mipMapLOD, options.anisotropy);
}
Renderer::~Renderer() {
unloadTextures();
glDeleteVertexArrays(1, &VertexArrayID);
}
void framebuffer_size_callback(GLFWwindow *, int width, int height) {
glViewport(0, 0, width, height);
}
bool Renderer::Load(Window& window, const renderOptions& opt) {
Window::CreateInfo windowInfo;
windowInfo.pfnResize = framebuffer_size_callback;
windowInfo.client = {Window::CreateInfo::Client::Type::GL, GL_MAJOR, GL_MINOR};
windowInfo.samples = 1;
if (!window.create(windowInfo))
return false;
@ -30,21 +80,7 @@ bool Renderer::Load(Window& window, const Options& options) {
return false;
}
LOG_D("OpenGL " << glGetString(GL_VERSION) << ", GLSL " << glGetString(GL_SHADING_LANGUAGE_VERSION));
sInstance = new Renderer(options);
return true;
}
void framebuffer_size_callback(GLFWwindow *, int width, int height) {
glViewport(0, 0, width, height);
}
Window::CreateInfo Renderer::GetWindowInfo() {
Window::CreateInfo windowInfo;
windowInfo.pfnResize = framebuffer_size_callback;
windowInfo.client = { Window::CreateInfo::Client::Type::GL, GL_MAJOR, GL_MINOR };
return windowInfo;
}
render::Pipeline *Renderer::createPipeline(const render::Pipeline::options &opt) {
// Enable depth test
glEnable(GL_DEPTH_TEST);
// Accept fragment if it closer to the camera than the former one
@ -60,12 +96,95 @@ render::Pipeline *Renderer::createPipeline(const render::Pipeline::options &opt)
glEnable(GL_MULTISAMPLE);
}
glClearColor(options.clear_color.x, options.clear_color.y, options.clear_color.z, options.clear_color.w);
glClearColor(opt.clear_color.x, opt.clear_color.y, opt.clear_color.z, opt.clear_color.w);
TracyGpuContext;
return new Pipeline(opt);
sInstance = new Renderer(opt);
return true;
}
void Renderer::loadUI(Window& w) {
UI::Load(w);
}
void Renderer::beginFrame() {
TracyGpuZone("Render");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
std::function<buffer::params(glm::mat4)> Renderer::beginWorldPass() {
WorldPass->useIt();
WorldPass->start(this);
return [&](glm::mat4 model) {
return WorldPass->setup(this, model);
};
}
std::function<buffer::params(const std::vector<glm::mat4> &)> Renderer::beginEntityPass() {
EntityPass->useIt();
EntityPass->start(this);
return [&](const std::vector<glm::mat4>& models) {
return EntityPass->setup(this, models);
};
}
size_t Renderer::drawIndicatorCube(glm::mat4 model) {
IndicatorPass->useIt();
return IndicatorCubeBuffer.draw(IndicatorPass->setup(this, model));
}
void Renderer::endPass() {
if(SkyEnable) {
SkyPass->draw(this);
}
}
void Renderer::swapBuffer(Window& w) {
TracyGpuZone("Swap");
glfwSwapBuffers(w.getPtr());
TracyGpuCollect;
}
void Renderer::reloadShaders(const pass::VoxelProgram::options& options) {
WorldPass = std::make_unique<pass::WorldProgram>(options);
EntityPass = std::make_unique<pass::EntityProgram>(options);
}
void Renderer::reloadTextures(const std::string& texturePath, float mipMapLOD, float anisotropy) {
unloadTextures();
loadTextures(texturePath, mipMapLOD, anisotropy);
}
void Renderer::unloadTextures() {
glDeleteTextures(1, &HOSAtlas);
glDeleteTextures(1, &NormalAtlas);
glDeleteTextures(1, &TextureAtlas);
}
void Renderer::loadTextures(const std::string& texturePath, float mipMapLOD, float anisotropy) {
std::vector<std::string> terrainTextures;
for(const auto& texture: world::materials::textures) {
terrainTextures.emplace_back(texturePath + "/terrain/" + texture);
}
const auto ani = anisotropy >= 1 ? (1 << (static_cast<int>(anisotropy)-1)) : 0;
TextureAtlas = pass::Program::loadTextureArray(terrainTextures, "", mipMapLOD, ani);
NormalAtlas = pass::Program::loadTextureArray(terrainTextures, ".nrm", mipMapLOD, ani);
HOSAtlas = pass::Program::loadTextureArray(terrainTextures, ".hos", mipMapLOD, ani);
Skybox = pass::Program::loadTextureCube(texturePath + "/sky/Space_tray");
}
void Renderer::lookFrom(const Camera& camera) {
ProjectionMatrix = camera.getProjectionMatrix();
ViewMatrix = camera.getViewMatrix();
FogDepth = camera.getDepth();
}
void Renderer::setClearColor(glm::vec4 c) {
FogColor = c;
glClearColor(c.r, c.g, c.b, c.a);
}
void Renderer::setCurvature(glm::vec4 sp, float c) {
SphereProj = sp;
Curvature = c;
}

View File

@ -1,7 +1,12 @@
#pragma once
#include "../Renderer.hpp"
#include <glm/vec4.hpp>
#include <GL/gl3w.h>
#include <memory>
#include "pass/WorldProgram.hpp"
#include "pass/EntityProgram.hpp"
#include "pass/SkyProgram.hpp"
#include "pass/ColorProgram.hpp"
#include "buffer/Colored.hpp"
namespace render::gl {
@ -10,23 +15,85 @@ class Renderer final: public render::Renderer {
public:
virtual ~Renderer();
struct Options {
/// Depth color
glm::vec4 clear_color;
};
static bool Load(Window& window, const renderOptions& options);
static bool Load(Window& window, const Options &options);
glm::vec3 FogColor;
GLfloat FogDepth;
render::Pipeline *createPipeline(const render::Pipeline::options &) override;
glm::mat4 getProjectionMatrix() const {
return ProjectionMatrix;
}
glm::mat4 getViewMatrix() const {
return ViewMatrix;
}
GLuint getTextureAtlas() const {
return TextureAtlas;
}
GLuint getNormalAtlas() const {
return NormalAtlas;
}
GLuint getHOSAtlas() const {
return HOSAtlas;
}
GLuint getSkyTexture() const {
return Skybox;
}
void beginFrame() override;
std::function<buffer::params(glm::mat4)> beginWorldPass() override;
std::function<buffer::params(const std::vector<glm::mat4>&)> beginEntityPass() override;
size_t drawIndicatorCube(glm::mat4 model) override;
void endPass() override;
void swapBuffer(Window&) override;
void setClearColor(glm::vec4) override;
void setCurvature(glm::vec4, float) override;
glm::vec4 getSphereProj() const {
return SphereProj;
}
float getCurvature() const {
return Curvature;
}
/// Apply camera matrices
void lookFrom(const Camera&) override;
void reloadShaders(const pass::VoxelProgram::options &) override;
void reloadTextures(const std::string &, float mipMapLOD, float anisotropy) override;
void loadUI(Window &) override;
static _FORCE_INLINE_ Renderer *Get() { return static_cast<Renderer*>(render::Renderer::Get()); }
private:
Renderer(const Options &options);
static Window::CreateInfo GetWindowInfo();
Renderer(const renderOptions &options);
Options options;
GLuint VertexArrayID;
std::unique_ptr<pass::WorldProgram> WorldPass;
std::unique_ptr<pass::EntityProgram> EntityPass;
std::unique_ptr<pass::SkyProgram> SkyPass;
std::unique_ptr<pass::ColorProgram> IndicatorPass;
buffer::Colored IndicatorCubeBuffer;
glm::mat4 ProjectionMatrix;
glm::mat4 ViewMatrix;
GLuint TextureAtlas;
GLuint NormalAtlas;
GLuint HOSAtlas;
GLuint Skybox;
/// Sphere bending
/// offset.xyz radius.w
glm::vec4 SphereProj;
/// Ratio between spherical and cartesian
float Curvature;
/// Draw skybox
bool SkyEnable;
void loadTextures(const std::string &, float mipMapLOD, float anisotropy);
void unloadTextures();
};
}

View File

@ -1,5 +1,6 @@
#include "UI.hpp"
#include "../../Window.hpp"
#include <GL/gl3w.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>