4. LOD Selection (on GPU)
GPU-based culling of invisible instances and Level-Of-Detail Selection of meshes
This example uses different materials for certain mesh LODs:
LOD 0 = gold (most detailed mesh version)
LOD 1 = silver
LOD 2 = bronze
LOD 3 = green plastic (least detailed mesh version)
Main.cpp
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <vector>
#include "Graphics.h"
using namespace std;
using namespace glm;
struct DebugUserParams {
bool ShowNotifications = false;
bool ShowPerformance = false;
} debuguserparams;
Camera camera;
void error_callback(int error, const char* description)
{
cout << "GLFW Error: " << description << endl;
cin.get();
}
void drop_callback(GLFWwindow* window, int pathcount, const char** paths)
{
for (unsigned int i = 0; i < pathcount; i++)
cout << "dropped file: " << paths[i] << endl;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
Graphics::ResizeFramebuffer(ivec2(width, height));
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (action == GLFW_PRESS)
{
if (key == GLFW_KEY_ESCAPE)
{
}
}
}
void cursor_position_callback(GLFWwindow* window, double xpos, double ypos)
{
static double xpos_prev = xpos;
static double ypos_prev = ypos;
double dx = xpos - xpos_prev;
double dy = ypos - ypos_prev;
xpos_prev = xpos;
ypos_prev = ypos;
camera.Theta = camera.Theta + 0.01f * float(dx);
camera.Phi = clamp(camera.Phi + 0.01f * float(dy), -1.5f, +1.5f);
}
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (action == GLFW_PRESS)
{
if (button == GLFW_MOUSE_BUTTON_RIGHT)
{
}
}
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.Distance = clamp(camera.Distance + 0.3f * float(yoffset), 1.0f, 20.0f);
}
/* debug */
void APIENTRY OpenGLDebugCallback(
GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userparam)
{
/* debug user params */
DebugUserParams* params = (DebugUserParams*)userparam;
/* filter out unnecessary warnings */
if (!params->ShowNotifications)
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION)
return;
if (!params->ShowPerformance)
if (type == GL_DEBUG_TYPE_PERFORMANCE)
return;
/* source */
string str_source;
if (source == GL_DEBUG_SOURCE_API) str_source = "API";
if (source == GL_DEBUG_SOURCE_WINDOW_SYSTEM) str_source = "Window System";
if (source == GL_DEBUG_SOURCE_SHADER_COMPILER) str_source = "Shader Compiler";
if (source == GL_DEBUG_SOURCE_THIRD_PARTY) str_source = "Third Party";
if (source == GL_DEBUG_SOURCE_APPLICATION) str_source = "Application";
if (source == GL_DEBUG_SOURCE_OTHER) str_source = "Other";
/* type */
string str_type;
if (type == GL_DEBUG_TYPE_ERROR) str_type = "Error";
if (type == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR) str_type = "Deprecated Behavior";
if (type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR) str_type = "Undefined Behavior";
if (type == GL_DEBUG_TYPE_PORTABILITY) str_type = "Portability";
if (type == GL_DEBUG_TYPE_PERFORMANCE) str_type = "Performance";
if (type == GL_DEBUG_TYPE_MARKER) str_type = "Marker";
if (type == GL_DEBUG_TYPE_PUSH_GROUP) str_type = "Push Group";
if (type == GL_DEBUG_TYPE_POP_GROUP) str_type = "Pop Group";
if (type == GL_DEBUG_TYPE_OTHER) str_type = "Other";
/* severity */
string str_severity;
if (severity == GL_DEBUG_SEVERITY_HIGH) str_severity = "High";
if (severity == GL_DEBUG_SEVERITY_MEDIUM) str_severity = "Medium";
if (severity == GL_DEBUG_SEVERITY_LOW) str_severity = "Low";
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) str_severity = "Notification";
/* print message */
cout << "OpenGL Debug Message:" << endl;
cout << "----------------------------------" << endl;
cout << "ID: \t\t" << id << endl;
cout << "Source: \t" << str_source << endl;
cout << "Type: \t\t" << str_type << endl;
cout << "Severity: \t" << str_severity << endl;
cout << "Message: \t" << message << endl;
cout << "----------------------------------" << endl << endl;
}
int main(int argc, char* argv[])
{
bool fullscreen = false;
bool debugcontext = true;
bool compatibilityprofilecontext = false;
bool vsync = true;
/* Initialize GLFW */
if (!glfwInit())
return 1;
/* Create a window and its OpenGL context */
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, debugcontext ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, compatibilityprofilecontext ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_OPENGL_PROFILE, compatibilityprofilecontext ? GLFW_OPENGL_COMPAT_PROFILE : GLFW_OPENGL_CORE_PROFILE);
GLFWmonitor* monitor = fullscreen ? glfwGetPrimaryMonitor() : NULL;
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", monitor, NULL);
if (!window)
{
glfwTerminate();
return 1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
/* Set V-sync */
glfwSwapInterval(vsync ? 1 : 0);
/* Set callback functions */
glfwSetErrorCallback(error_callback);
glfwSetDropCallback(window, drop_callback);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, cursor_position_callback);
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetScrollCallback(window, scroll_callback);
/* Initialize GLEW */
if (glewInit() != GLEW_OK)
{
glfwTerminate();
return 2;
}
/* Register debug callback if debug context is available */
GLint contextflags = 0;
glGetIntegerv(GL_CONTEXT_FLAGS, &contextflags);
if (contextflags & GL_CONTEXT_FLAG_DEBUG_BIT)
{
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(OpenGLDebugCallback, &debuguserparams);
}
/* Initialize graphics */
int width = 0, height = 0;
glfwGetFramebufferSize(window, &width, &height);
if (!Graphics::Initialize(ivec2(width, height)))
{
glfwTerminate();
cin.get();
return 3;
}
// setup example materials and meshes
//----------------------------------------
Material gold;
gold.SetKd(vec3(0.751f, 0.606f, 0.226f));
gold.SetKs(vec3(0.628f, 0.556f, 0.366f));
gold.Ns = 51.2f;
gold.d = 1.0f;
Material silver;
silver.SetKd(vec3(0.507f, 0.507f, 0.507f));
silver.SetKs(vec3(0.508f, 0.508f, 0.508f));
silver.Ns = 51.2f;
silver.d = 1.0f;
Material bronze;
bronze.SetKd(vec3(0.714f, 0.428f, 0.181f));
bronze.SetKs(vec3(0.393f, 0.272f, 0.167f));
bronze.Ns = 25.6f;
bronze.d = 1.0f;
Material green_plastic;
green_plastic.SetKd(vec3(0.100f, 0.350f, 0.100f));
green_plastic.SetKs(vec3(0.450f, 0.550f, 0.450f));
green_plastic.Ns = 32.0f;
green_plastic.d = 1.0f;
vector<uint> materialrefs = {
Graphics::AddMaterial(gold),
Graphics::AddMaterial(silver),
Graphics::AddMaterial(bronze),
Graphics::AddMaterial(green_plastic),
};
vector<uint> meshrefs = {
Graphics::AddMesh(Tetrahedron()),
Graphics::AddMesh(Cube()),
Graphics::AddMesh(Octahedron()),
Graphics::AddMesh(Cylinder()),
Graphics::AddMesh(Cone()),
Graphics::AddMesh(Sphere()),
};
//----------------------------------------
// setup array of example objects
//----------------------------------------
vector<Object> objects;
for (int x = -2; x <= 2; x++)
{
for (int y = -2; y <= 2; y++)
{
for (int z = -2; z <= 2; z++)
{
Object object;
object.Transform = translate(vec3(x, y, z) * 2.0f);
object.MaterialReference = materialrefs.at(abs(x + y + z) % materialrefs.size());
object.MeshReference = meshrefs.at(abs(x + y + z) % meshrefs.size());
objects.push_back(object);
}
}
}
//----------------------------------------
/* Loop until the user closes the window */
//----------------------------------------
while (!glfwWindowShouldClose(window))
{
/* draw objects */
Graphics::Render(camera, objects);
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
//----------------------------------------
/* Clean up graphics */
Graphics::CleanUp();
glfwTerminate();
return 0;
}
Material.h
#pragma once
#include <glm/glm.hpp>
struct Material
{
glm::uint Kd = 0;
glm::uint Ks = 0;
float Ns = 0.0f;
float d = 0.0f;
glm::uint MapKd = 0xFFFFFFFF;
void SetKd(const glm::vec3& kd) {
Kd = glm::packUnorm4x8(glm::vec4(kd.x, kd.y, kd.z, 0));
}
void SetKs(const glm::vec3& ks) {
Ks = glm::packUnorm4x8(glm::vec4(ks.x, ks.y, ks.z, 0));
}
};
Mesh.h
#pragma once
#include <glm/glm.hpp>
#include <vector>
struct BoundingSphere
{
glm::vec3 Center = { 0, 0, 0 };
float Radius = 0.0f;
};
struct Vertex
{
glm::vec3 Position = { 0, 0, 0 };
glm::vec3 Normal = { 0, 0, 0 };
glm::vec2 Texcoord = { 0, 0 };
bool operator==(const Vertex& other) const;
bool operator!=(const Vertex& other) const;
};
struct MeshLOD
{
std::vector<Vertex> Vertices;
std::vector<glm::uvec3> Faces;
glm::uint AddVertex(const Vertex& A);
glm::uvec3 AddFace(const Vertex& A, const Vertex& B, const Vertex& C);
BoundingSphere GetBoundingSphere() const;
};
struct Mesh
{
MeshLOD LODs[4];
};
Mesh Plane();
Mesh Tetrahedron();
Mesh Cube();
Mesh Octahedron();
Mesh Cylinder();
Mesh Cone();
Mesh Sphere();
struct MeshLODProperty
{
glm::uint BaseVertex = 0;
glm::uint BaseIndex = 0;
glm::uint IndexCount = 0;
glm::uint _padding1 = 0;
};
struct MeshProperty
{
MeshLODProperty LODs[4];
BoundingSphere BSphere;
};
Mesh.cpp
#include "Mesh.h"
#include <glm/gtc/constants.hpp>
#include <iostream>
using namespace std;
using namespace glm;
bool Vertex::operator==(const Vertex& other) const
{
return
Position == other.Position &&
Normal == other.Normal &&
Texcoord == other.Texcoord;
}
bool Vertex::operator!=(const Vertex& other) const
{
return !(*this == other);
}
glm::uint MeshLOD::AddVertex(const Vertex& A)
{
for (uint i = 0; i < Vertices.size(); i++)
if (A == Vertices.at(i))
return i;
Vertices.push_back(A);
return Vertices.size() - 1;
}
glm::uvec3 MeshLOD::AddFace(const Vertex& A, const Vertex& B, const Vertex& C)
{
uvec3 face = {
AddVertex(A),
AddVertex(B),
AddVertex(C)
};
Faces.push_back(face);
return face;
}
BoundingSphere MeshLOD::GetBoundingSphere() const
{
if (Vertices.empty())
return {};
// axis aligned bounding box
vec3 minimum = Vertices.at(0).Position;
vec3 maximum = Vertices.at(0).Position;
for (auto& vertex : Vertices)
{
minimum.x = std::min(minimum.x, vertex.Position.x);
minimum.y = std::min(minimum.y, vertex.Position.y);
minimum.z = std::min(minimum.z, vertex.Position.z);
maximum.x = std::max(maximum.x, vertex.Position.x);
maximum.y = std::max(maximum.y, vertex.Position.y);
maximum.z = std::max(maximum.z, vertex.Position.z);
}
// offset position of minimal sized sphere
vec3 center = (minimum + maximum) * 0.5f;
// radius of minimal sized sphere
float radius = 0.0f;
for (auto& vertex : Vertices)
radius = std::max(radius, length(vertex.Position - center));
return { center, radius };
}
Mesh Plane()
{
Mesh mesh;
MeshLOD lod;
lod.Vertices = {
{ { -0.707f, +0.000f, +0.707f }, { 0, +1, 0 }, { 0, 1 } },
{ { +0.707f, +0.000f, +0.707f }, { 0, +1, 0 }, { 1, 1 } },
{ { +0.707f, +0.000f, -0.707f }, { 0, +1, 0 }, { 1, 0 } },
{ { -0.707f, +0.000f, -0.707f }, { 0, +1, 0 }, { 0, 0 } },
{ { -0.707f, +0.000f, -0.707f }, { 0, -1, 0 }, { 0, 0 } },
{ { +0.707f, +0.000f, -0.707f }, { 0, -1, 0 }, { 1, 0 } },
{ { +0.707f, +0.000f, +0.707f }, { 0, -1, 0 }, { 1, 1 } },
{ { -0.707f, +0.000f, +0.707f }, { 0, -1, 0 }, { 0, 1 } },
};
lod.Faces = {
{ 0, 1, 2 },
{ 0, 2, 3 },
{ 4, 5, 6 },
{ 4, 6, 7 },
};
// only 1 lod available
mesh.LODs[0] = lod;
return mesh;
}
Mesh Tetrahedron()
{
Mesh mesh;
vec3 A = { +0.866f, -0.500f, +0.000f };
vec3 B = { -0.433f, -0.500f, -0.750f };
vec3 C = { -0.433f, -0.500f, +0.750f };
vec3 D = { +0.000f, +1.000f, +0.000f };
vec3 Nabd = normalize(cross(B - A, D - A));
vec3 Nbcd = normalize(cross(C - B, D - B));
vec3 Ncad = normalize(cross(A - C, D - C));
vec3 Nacb = normalize(cross(C - A, B - A));
MeshLOD lod;
lod.AddFace(
{ A, Nabd, { 0.0f, 1.0f } },
{ B, Nabd, { 1.0f, 1.0f } },
{ D, Nabd, { 0.5f, 0.0f } }
);
lod.AddFace(
{ B, Nbcd, { 0.0f, 1.0f } },
{ C, Nbcd, { 1.0f, 1.0f } },
{ D, Nbcd, { 0.5f, 0.0f } }
);
lod.AddFace(
{ C, Ncad, { 0.0f, 1.0f } },
{ A, Ncad, { 1.0f, 1.0f } },
{ D, Ncad, { 0.5f, 0.0f } }
);
lod.AddFace(
{ A, Nacb, { 0.0f, 1.0f } },
{ C, Nacb, { 1.0f, 1.0f } },
{ B, Nacb, { 0.5f, 0.0f } }
);
// only 1 lod available
mesh.LODs[0] = lod;
return mesh;
}
Mesh Cube()
{
Mesh mesh;
MeshLOD lod;
lod.Vertices = {
// +x
{ { +0.5f, -0.5f, -0.5f }, { +1, 0, 0 }, { 1, 1 } },
{ { +0.5f, +0.5f, -0.5f }, { +1, 0, 0 }, { 0, 1 } },
{ { +0.5f, +0.5f, +0.5f }, { +1, 0, 0 }, { 0, 0 } },
{ { +0.5f, -0.5f, +0.5f }, { +1, 0, 0 }, { 1, 0 } },
// -x
{ { -0.5f, -0.5f, -0.5f }, { -1, 0, 0 }, { 1, 1 } },
{ { -0.5f, +0.5f, -0.5f }, { -1, 0, 0 }, { 0, 1 } },
{ { -0.5f, +0.5f, +0.5f }, { -1, 0, 0 }, { 0, 0 } },
{ { -0.5f, -0.5f, +0.5f }, { -1, 0, 0 }, { 1, 0 } },
// +y
{ { -0.5f, +0.5f, -0.5f }, { 0, +1, 0 }, { 1, 1 } },
{ { +0.5f, +0.5f, -0.5f }, { 0, +1, 0 }, { 0, 1 } },
{ { +0.5f, +0.5f, +0.5f }, { 0, +1, 0 }, { 0, 0 } },
{ { -0.5f, +0.5f, +0.5f }, { 0, +1, 0 }, { 1, 0 } },
// -y
{ { -0.5f, -0.5f, -0.5f }, { 0, -1, 0 }, { 1, 1 } },
{ { +0.5f, -0.5f, -0.5f }, { 0, -1, 0 }, { 0, 1 } },
{ { +0.5f, -0.5f, +0.5f }, { 0, -1, 0 }, { 0, 0 } },
{ { -0.5f, -0.5f, +0.5f }, { 0, -1, 0 }, { 1, 0 } },
// +z
{ { -0.5f, -0.5f, +0.5f }, { 0, 0, +1 }, { 1, 1 } },
{ { +0.5f, -0.5f, +0.5f }, { 0, 0, +1 }, { 0, 1 } },
{ { +0.5f, +0.5f, +0.5f }, { 0, 0, +1 }, { 0, 0 } },
{ { -0.5f, +0.5f, +0.5f }, { 0, 0, +1 }, { 1, 0 } },
// -z
{ { -0.5f, -0.5f, -0.5f }, { 0, 0, -1 }, { 1, 1 } },
{ { +0.5f, -0.5f, -0.5f }, { 0, 0, -1 }, { 0, 1 } },
{ { +0.5f, +0.5f, -0.5f }, { 0, 0, -1 }, { 0, 0 } },
{ { -0.5f, +0.5f, -0.5f }, { 0, 0, -1 }, { 1, 0 } },
};
lod.Faces = {
// +x
{ 0, 1, 2 },
{ 0, 2, 3 },
// -x
{ 4, 6, 5 },
{ 4, 7, 6 },
// +y
{ 8, 10, 9 },
{ 8, 11, 10 },
// -y
{ 12, 13, 14 },
{ 12, 14, 15 },
// +z
{ 16, 17, 18 },
{ 16, 18, 19 },
// -z
{ 20, 22, 21 },
{ 20, 23, 22 },
};
for (auto& vertex : lod.Vertices)
vertex.Position = normalize(vertex.Position);
// only 1 lod available
mesh.LODs[0] = lod;
return mesh;
}
Mesh Octahedron()
{
Mesh mesh;
vec3 A = { -0.707f, +0.000f, +0.707f };
vec3 B = { +0.707f, +0.000f, +0.707f };
vec3 C = { +0.707f, +0.000f, -0.707f };
vec3 D = { -0.707f, +0.000f, -0.707f };
vec3 E = { +0.000f, +1.000f, +0.000f };
vec3 F = { +0.000f, -1.000f, +0.000f };
vec3 Nabe = normalize(cross(B - A, E - A));
vec3 Nbce = normalize(cross(C - B, E - B));
vec3 Ncde = normalize(cross(D - C, E - C));
vec3 Ndae = normalize(cross(A - D, E - D));
vec3 Nafb = normalize(cross(F - A, B - A));
vec3 Nbfc = normalize(cross(F - B, C - B));
vec3 Ncfd = normalize(cross(F - C, D - C));
vec3 Ndfa = normalize(cross(F - D, A - D));
MeshLOD lod;
lod.AddFace(
{ A, Nabe, { 0.0f, 1.0f } },
{ B, Nabe, { 1.0f, 1.0f } },
{ E, Nabe, { 0.5f, 0.0f } }
);
lod.AddFace(
{ B, Nbce, { 0.0f, 1.0f } },
{ C, Nbce, { 1.0f, 1.0f } },
{ E, Nbce, { 0.5f, 0.0f } }
);
lod.AddFace(
{ C, Ncde, { 0.0f, 1.0f } },
{ D, Ncde, { 1.0f, 1.0f } },
{ E, Ncde, { 0.5f, 0.0f } }
);
lod.AddFace(
{ D, Ndae, { 0.0f, 1.0f } },
{ A, Ndae, { 1.0f, 1.0f } },
{ E, Ndae, { 0.5f, 0.0f } }
);
lod.AddFace(
{ A, Nafb, { 0.0f, 1.0f } },
{ F, Nafb, { 1.0f, 1.0f } },
{ B, Nafb, { 0.5f, 0.0f } }
);
lod.AddFace(
{ B, Nbfc, { 0.0f, 1.0f } },
{ F, Nbfc, { 1.0f, 1.0f } },
{ C, Nbfc, { 0.5f, 0.0f } }
);
lod.AddFace(
{ C, Ncfd, { 0.0f, 1.0f } },
{ F, Ncfd, { 1.0f, 1.0f } },
{ D, Ncfd, { 0.5f, 0.0f } }
);
lod.AddFace(
{ D, Ndfa, { 0.0f, 1.0f } },
{ F, Ndfa, { 1.0f, 1.0f } },
{ A, Ndfa, { 0.5f, 0.0f } }
);
// only 1 lod available
mesh.LODs[0] = lod;
return mesh;
}
Mesh Cylinder()
{
Mesh mesh;
for (uint i = 0; i < 4; i++)
{
MeshLOD lod;
uint sectors = pow(2, 5 - i);
float dtheta = 2 * pi<float>() / sectors;
for (uint i = 0; i < sectors; i++)
{
float theta1 = ((i + 0) % sectors) * dtheta;
float theta2 = ((i + 1) % sectors) * dtheta;
float x0 = +0.000f;
float z0 = +0.000f;
float x1 = +0.707f * cos(theta1);
float z1 = +0.707f * sin(-theta1);
float x2 = +0.707f * cos(theta2);
float z2 = +0.707f * sin(-theta2);
float u1 = float(i + 0) / sectors;
float u2 = float(i + 1) / sectors;
lod.AddFace(
{ { x0, +0.707f, z0 }, { 0, +1, 0 }, { 0.0f, 1.0f } },
{ { x1, +0.707f, z1 }, { 0, +1, 0 }, { 1.0f, 0.0f } },
{ { x2, +0.707f, z2 }, { 0, +1, 0 }, { 0.0f, 0.0f } }
);
lod.AddFace(
{ { x0, -0.707f, z0 }, { 0, -1, 0 }, { 0.0f, 1.0f } },
{ { x2, -0.707f, z2 }, { 0, -1, 0 }, { 0.0f, 0.0f } },
{ { x1, -0.707f, z1 }, { 0, -1, 0 }, { 1.0f, 0.0f } }
);
if (i < sectors)
{
lod.AddFace(
{ { x1, -0.707f, z1 }, normalize(vec3(x1, 0, z1)), { u1, 1.0f } },
{ { x2, -0.707f, z2 }, normalize(vec3(x2, 0, z2)), { u2, 1.0f } },
{ { x2, +0.707f, z2 }, normalize(vec3(x2, 0, z2)), { u2, 0.0f } }
);
lod.AddFace(
{ { x1, -0.707f, z1 }, normalize(vec3(x1, 0, z1)), { u1, 1.0f } },
{ { x2, +0.707f, z2 }, normalize(vec3(x2, 0, z2)), { u2, 0.0f } },
{ { x1, +0.707f, z1 }, normalize(vec3(x1, 0, z1)), { u1, 0.0f } }
);
}
}
mesh.LODs[i] = lod;
}
return mesh;
}
Mesh Cone()
{
Mesh mesh;
for (uint i = 0; i < 4; i++)
{
MeshLOD lod;
uint sectors = pow(2, 5 - i);
float dtheta = 2 * pi<float>() / sectors;
vector<vec3> points(sectors);
for (uint i = 0; i < sectors; i++)
{
float theta = i * dtheta;
float x = +0.707f * cos(theta);
float z = +0.707f * sin(-theta);
points.at(i) = { x, -0.707f, z };
}
for (uint i = 0; i < sectors; i++)
{
vec3 p1 = points.at((i + 0) % sectors);
vec3 p2 = points.at((i + 1) % sectors);
float u1 = float(i + 0) / sectors;
float u2 = float(i + 0) / sectors;
float um = (u1 + u2) / 2;
vec3 p0 = { 0, -0.707f, 0 };
lod.AddFace(
{ p0, { 0, -1, 0 }, { um, 1.0f } },
{ p2, { 0, -1, 0 }, { u1, 0.0f } },
{ p1, { 0, -1, 0 }, { u2, 0.0f } }
);
p0 = { 0, 1, 0 };
vec3 nf1 = normalize(cross(cross(vec3(0, 1, 0), p1), p0 - p1));
vec3 nf2 = normalize(cross(cross(vec3(0, 1, 0), p2), p0 - p2));
vec3 nf0 = normalize(nf1 + nf2);
lod.AddFace(
{ p0, nf0, { um, 0.0f } },
{ p1, nf1, { u1, 1.0f } },
{ p2, nf2, { u2, 1.0f } }
);
}
mesh.LODs[i] = lod;
}
return mesh;
}
Mesh Sphere()
{
Mesh mesh;
for (uint i = 0; i < 4; i++)
{
MeshLOD lod;
uint sectors = pow(2, 5 - i);
uint slices = pow(2, 5 - i);
for (uint j = 0; j <= slices; j++)
{
for (uint i = 0; i <= sectors; i++)
{
float phi = pi<float>() * (-0.5f + float(j) / slices);
float theta = pi<float>() * 2 * float(i) / sectors;
float x = cos(phi) * cos(-theta);
float y = sin(phi);
float z = cos(phi) * sin(-theta);
float u = float(i) / sectors;
float v = 1 - float(j) / slices;
lod.Vertices.push_back({ { x, y, z }, { x, y, z }, { u, v } });
if (i < sectors && j < slices)
{
uint index_A = (j + 0) * (sectors + 1) + (i + 0);
uint index_B = (j + 0) * (sectors + 1) + (i + 1);
uint index_C = (j + 1) * (sectors + 1) + (i + 1);
uint index_D = (j + 1) * (sectors + 1) + (i + 0);
lod.Faces.push_back({ index_A, index_B, index_C });
lod.Faces.push_back({ index_A, index_C, index_D });
}
}
}
mesh.LODs[i] = lod;
}
return mesh;
}
Graphics.h
#pragma once
#define GLEW_STATIC
#include <GL/glew.h>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/transform.hpp>
#include <string>
#include <vector>
#include "Material.h"
#include "Mesh.h"
struct Drawable
{
glm::uint MaterialReference = 0xFFFFFFFF;
glm::uint MeshReference = 0xFFFFFFFF;
};
struct Object : public Drawable
{
glm::mat4 Transform = glm::mat4(1);
};
struct Camera
{
float Distance = 15.0f;
float Theta = 0.0f;
float Phi = 0.3f;
float FieldOfView = 1.57f;
float ZNear = 0.1f;
float ZFar = 100.0f;
glm::vec3 Position() const {
return glm::vec3(
Distance * cos(Phi) * cos(Theta),
Distance * sin(Phi),
Distance * cos(Phi) * sin(Theta));
}
glm::mat4 View() const {
return glm::lookAt(Position(), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
}
glm::mat4 Projection(float aspect_ratio) const {
return glm::perspective(FieldOfView, aspect_ratio, ZNear, ZFar);
}
};
namespace Graphics
{
bool Initialize(const glm::ivec2 framebuffer_size);
void CleanUp();
void Render(const Camera& camera, const std::vector<Object>& objects);
void ResizeFramebuffer(const glm::ivec2 framebuffer_size);
glm::uint AddMaterial(const Material& material);
glm::uint AddMesh(const Mesh& mesh);
glm::uint LoadTexture(const std::string& filename);
std::string LoadTextFile(const std::string& filepath);
std::string ShaderTypeName(GLuint shader);
bool CompileShader(GLuint shader, const std::string& sourcecode);
std::string ShaderInfoLog(GLuint shader);
bool LinkProgram(GLuint program, const std::vector<GLuint>& shaders);
std::string ProgramInfoLog(GLuint program);
}
Graphics.cpp
#include "Graphics.h"
#include <SOIL.h>
#include <iostream>
#include <fstream>
#define CheckForGLErrors CheckForGLErrors__(__FILE__, __LINE__)
#define NULL_TEXTURE_NAME "NULL_TEXTURE_NAME"
using namespace std;
using namespace glm;
/* argument type buffered in GL_DRAW_INDIRECT_BUFFER */
struct DrawElementsIndirectCommand
{
GLuint Count = 0;
GLuint InstanceCount = 0;
GLuint FirstIndex = 0;
GLuint BaseVertex = 0;
GLuint BaseInstance = 0;
};
GLuint program_preprocess = 0;
GLuint program = 0;
GLuint vertexarray = 0;
GLuint vertexbuffer = 0;
GLuint elementbuffer = 0;
GLuint instancebuffer = 0;
GLuint indirectbuffer = 0;
GLuint drawablesbuffer = 0;
GLuint materialbuffer = 0;
GLuint texturehandlebuffer = 0;
GLuint meshpropertybuffer = 0;
vector<Vertex> vertices;
vector<GLuint> indices;
vector<MeshProperty> meshproperties;
vector<Material> materials;
vector<string> texture_filenames;
vector<GLuint64> texture_handles;
vector<GLuint> textures;
bool reload_mesh_buffers = true;
bool reload_material_buffers = true;
float aspectratio = 1.0f;
void CheckForGLErrors__(const char* file, unsigned int line)
{
string errorstring;
for (GLenum error; (error = glGetError()) != GL_NO_ERROR;)
{
if (error == GL_INVALID_ENUM)
errorstring += " GL_INVALID_ENUM";
if (error == GL_INVALID_VALUE)
errorstring += " GL_INVALID_VALUE";
if (error == GL_INVALID_OPERATION)
errorstring += " GL_INVALID_OPERATION";
if (error == GL_STACK_OVERFLOW)
errorstring += " GL_STACK_OVERFLOW";
if (error == GL_STACK_UNDERFLOW)
errorstring += " GL_STACK_UNDERFLOW";
if (error == GL_OUT_OF_MEMORY)
errorstring += " GL_OUT_OF_MEMORY";
if (error == GL_INVALID_FRAMEBUFFER_OPERATION)
errorstring += " GL_INVALID_FRAMEBUFFER_OPERATION";
if (error == GL_CONTEXT_LOST)
errorstring += " GL_CONTEXT_LOST";
}
if (!errorstring.empty())
{
cout
<< (char)7
<< "OpenGL Error: " << endl
<< "\tLine: \t" << line << endl
<< "\tFile: \t" << file << endl
<< "\tErrors: \t" << errorstring << endl << endl;
cin.get();
}
}
void ReloadMaterialBuffer()
{
if (!reload_material_buffers)
return;
reload_material_buffers = false;
// fill material buffer
glNamedBufferData(
materialbuffer,
materials.size() * sizeof(Material),
materials.data(),
GL_DYNAMIC_DRAW);
// fill texture handle buffer
glNamedBufferData(
texturehandlebuffer,
texture_handles.size() * sizeof(GLuint64),
texture_handles.data(),
GL_DYNAMIC_DRAW);
}
void ReloadMeshBuffers()
{
if (!reload_mesh_buffers)
return;
reload_mesh_buffers = false;
// fill vertex buffer
glNamedBufferData(
vertexbuffer,
vertices.size() * sizeof(Vertex),
vertices.data(),
GL_DYNAMIC_DRAW);
// fill index buffer
glNamedBufferData(
elementbuffer,
indices.size() * sizeof(GLuint),
indices.data(),
GL_DYNAMIC_DRAW);
// fill mesh property buffer
glNamedBufferData(
meshpropertybuffer,
meshproperties.size() * sizeof(MeshProperty),
meshproperties.data(),
GL_DYNAMIC_DRAW);
}
bool Graphics::Initialize(const glm::ivec2 framebuffer_size)
{
// check if GL_ARB_bindless_texture is supported
//----------------------------------------
bool bindless_texture_supported = false;
GLint numberofextensions = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &numberofextensions);
for (uint i = 0; i < numberofextensions; i++)
{
string extension = (const char*)glGetStringi(GL_EXTENSIONS, i);
if (extension == "GL_ARB_bindless_texture")
{
bindless_texture_supported = true;
break;
}
}
if (!bindless_texture_supported)
{
cout << "error: GL_ARB_bindless_texture not supported" << endl;
return false;
}
//----------------------------------------
// create GL objects
//----------------------------------------
program_preprocess = glCreateProgram();
program = glCreateProgram();
glCreateVertexArrays(1, &vertexarray);
glCreateBuffers(1, &vertexbuffer);
glCreateBuffers(1, &elementbuffer);
glCreateBuffers(1, &instancebuffer);
glCreateBuffers(1, &indirectbuffer);
glCreateBuffers(1, &drawablesbuffer);
glCreateBuffers(1, &materialbuffer);
glCreateBuffers(1, &texturehandlebuffer);
glCreateBuffers(1, &meshpropertybuffer);
//----------------------------------------
// setup programs
//----------------------------------------
bool success = true;
GLuint vertexshader = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);
GLuint computeshader = glCreateShader(GL_COMPUTE_SHADER);
// program for instance culling and LOD selection
if (!CompileShader(computeshader, LoadTextFile("Shaders/preprocess.cs.glsl")))
success = false;
if (!LinkProgram(program_preprocess, { computeshader }))
success = false;
// program for drawing
if (!CompileShader(vertexshader, LoadTextFile("Shaders/model.vs.glsl")))
success = false;
if (!CompileShader(fragmentshader, LoadTextFile("Shaders/model.fs.glsl")))
success = false;
if (!LinkProgram(program, { vertexshader, fragmentshader }))
success = false;
glDeleteShader(vertexshader);
glDeleteShader(fragmentshader);
glDeleteShader(computeshader);
if (!success)
return false;
//----------------------------------------
// setup vertexarray
//----------------------------------------
// index buffer
glVertexArrayElementBuffer(vertexarray, elementbuffer);
// per-vertex attributes
glVertexArrayAttribFormat(vertexarray, 0, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribFormat(vertexarray, 1, 3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribFormat(vertexarray, 2, 2, GL_FLOAT, GL_FALSE, 0);
glVertexArrayVertexBuffer(vertexarray, 0, vertexbuffer, offsetof(Vertex, Position), sizeof(Vertex));
glVertexArrayVertexBuffer(vertexarray, 1, vertexbuffer, offsetof(Vertex, Normal), sizeof(Vertex));
glVertexArrayVertexBuffer(vertexarray, 2, vertexbuffer, offsetof(Vertex, Texcoord), sizeof(Vertex));
glEnableVertexArrayAttrib(vertexarray, 0);
glEnableVertexArrayAttrib(vertexarray, 1);
glEnableVertexArrayAttrib(vertexarray, 2);
// per-instance attributes
glVertexArrayAttribFormat(vertexarray, 3, 4, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribFormat(vertexarray, 4, 4, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribFormat(vertexarray, 5, 4, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribFormat(vertexarray, 6, 4, GL_FLOAT, GL_FALSE, 0);
glVertexArrayVertexBuffer(vertexarray, 3, instancebuffer, sizeof(vec4) * 0, sizeof(mat4));
glVertexArrayVertexBuffer(vertexarray, 4, instancebuffer, sizeof(vec4) * 1, sizeof(mat4));
glVertexArrayVertexBuffer(vertexarray, 5, instancebuffer, sizeof(vec4) * 2, sizeof(mat4));
glVertexArrayVertexBuffer(vertexarray, 6, instancebuffer, sizeof(vec4) * 3, sizeof(mat4));
glEnableVertexArrayAttrib(vertexarray, 3);
glEnableVertexArrayAttrib(vertexarray, 4);
glEnableVertexArrayAttrib(vertexarray, 5);
glEnableVertexArrayAttrib(vertexarray, 6);
glVertexArrayBindingDivisor(vertexarray, 3, 1);
glVertexArrayBindingDivisor(vertexarray, 4, 1);
glVertexArrayBindingDivisor(vertexarray, 5, 1);
glVertexArrayBindingDivisor(vertexarray, 6, 1);
//----------------------------------------
// SSBO bindings
//----------------------------------------
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, instancebuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, indirectbuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, drawablesbuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, materialbuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, texturehandlebuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, meshpropertybuffer);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
//----------------------------------------
// resize framebuffer
//----------------------------------------
ResizeFramebuffer(framebuffer_size);
//----------------------------------------
// load null texture first
//----------------------------------------
LoadTexture(NULL_TEXTURE_NAME);
//----------------------------------------
CheckForGLErrors;
return true;
}
void Graphics::CleanUp()
{
// destroy GL objects
//----------------------------------------
glDeleteProgram(program_preprocess);
glDeleteProgram(program);
glDeleteVertexArrays(1, &vertexarray);
glDeleteBuffers(1, &vertexbuffer);
glDeleteBuffers(1, &elementbuffer);
glDeleteBuffers(1, &instancebuffer);
glDeleteBuffers(1, &indirectbuffer);
glDeleteBuffers(1, &drawablesbuffer);
glDeleteBuffers(1, &materialbuffer);
glDeleteBuffers(1, &texturehandlebuffer);
glDeleteBuffers(1, &meshpropertybuffer);
//----------------------------------------
// delete dynamically created textures
//----------------------------------------
for (int i = 0; i < textures.size(); i++)
glMakeTextureHandleNonResidentARB(texture_handles.at(i));
glDeleteTextures(textures.size(), textures.data());
texture_filenames.clear();
texture_handles.clear();
textures.clear();
//----------------------------------------
CheckForGLErrors;
}
void Graphics::Render(const Camera& camera, const std::vector<Object>& objects)
{
// make sure all materials are stored in material buffer
ReloadMaterialBuffer();
// make sure all meshes are stored in vertex buffer and index buffer
ReloadMeshBuffers();
// camera matrices
//----------------------------------------
mat4 view = camera.View();
mat4 projection = camera.Projection(aspectratio);
//----------------------------------------
// calculate modelview matrices and draw commands
//----------------------------------------
vector<mat4> mvs;
vector<Drawable> drawables;
for (int i = 0; i < objects.size(); i++)
{
const auto& object = objects.at(i);
// modelview
mat4 mv = view * object.Transform;
mvs.push_back(mv);
drawables.push_back({ Drawable(object) });
}
int instance_count = mvs.size();
// fill instance buffer with modelview matrices
glNamedBufferData(
instancebuffer,
mvs.size() * sizeof(mat4),
mvs.data(),
GL_DYNAMIC_DRAW);
// allocate indirect buffer (empty, fill be filled in "preprocessing")
glNamedBufferData(
indirectbuffer,
instance_count * sizeof(DrawElementsIndirectCommand),
nullptr,
GL_DYNAMIC_DRAW);
// fill material indices buffer
glNamedBufferData(
drawablesbuffer,
drawables.size() * sizeof(Drawable),
drawables.data(),
GL_DYNAMIC_DRAW);
//----------------------------------------
// light parameters
//----------------------------------------
static float angle = 0;
angle += 0.016f * 3.141592654f / 4;
float light_ambient = 0.3f;
vec3 light_intensity = vec3(1, 1, 1);
vec3 light_direction = vec3(0.3f * cos(angle), -1.0f, 0.3f * sin(angle));
vec3 light_direction_eyespace = view * vec4(light_direction, 0);
//----------------------------------------
/* object culling + LOD selection */
//----------------------------------------
uint workgroup_count = instance_count / 1024;
if (instance_count % 1024)
workgroup_count++;
glUseProgram(program_preprocess);
glUniform1f(0, camera.FieldOfView);
glUniform1f(1, aspectratio);
glUniform1f(2, camera.ZNear);
glUniform1f(3, camera.ZFar);
glDispatchCompute(workgroup_count, 1, 1);
glUseProgram(0);
// data of indirectbuffer must reflect values written by "program_culling"
glMemoryBarrier(GL_COMMAND_BARRIER_BIT);
// show how many objects were culled
static float time_to_show_culled_instances = 0.0f;
time_to_show_culled_instances += 0.016f;
if (time_to_show_culled_instances >= 1.0f)
{
time_to_show_culled_instances -= 1.0f;
// return indirect buffer data
vector<DrawElementsIndirectCommand> cmds(instance_count);
glGetNamedBufferSubData(
indirectbuffer,
0,
cmds.size() * sizeof(DrawElementsIndirectCommand),
cmds.data());
// sum up visible instances
int visible_instances = 0;
for (auto& cmd : cmds)
visible_instances += cmd.InstanceCount;
cout << "visible_instances: " << visible_instances << endl;
}
//----------------------------------------
/* draw objects */
//----------------------------------------
// clear screen
glClearColor(0.2f, 0.4f, 0.6f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glUseProgram(program);
glBindVertexArray(vertexarray);
glUniformMatrix4fv(0, 1, false, value_ptr(projection));
glUniform3fv(4, 1, value_ptr(light_intensity));
glUniform3fv(5, 1, value_ptr(light_direction_eyespace));
glUniform1f(6, light_ambient);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectbuffer);
// one draw call, rendering all the draw commands contained in "indirectbuffer"
glMultiDrawElementsIndirect(
GL_TRIANGLES,
GL_UNSIGNED_INT,
nullptr,
instance_count,
sizeof(DrawElementsIndirectCommand));
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
glBindVertexArray(0);
glUseProgram(0);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
//----------------------------------------
CheckForGLErrors;
}
void Graphics::ResizeFramebuffer(const glm::ivec2 framebuffer_size)
{
glViewport(0, 0, framebuffer_size.x, framebuffer_size.y);
aspectratio = float(framebuffer_size.x) / framebuffer_size.y;
}
glm::uint Graphics::AddMaterial(const Material& material)
{
// reload materials next frame
reload_material_buffers = true;
materials.push_back(material);
// fix: texture index
if (materials.back().MapKd >= textures.size())
materials.back().MapKd = 0;
// fix: if diffuse texture is present, set diffuse value to vec3(1, 1, 1)
if (materials.back().MapKd != 0)
materials.back().SetKd(vec3(1, 1, 1));
return materials.size() - 1;
}
glm::uint Graphics::AddMesh(const Mesh& mesh)
{
// reload mesh data next frame
reload_mesh_buffers = true;
// TODO: ... sort lods after complexity (face count)
// store vertex and index offset as well as index count of mesh
MeshProperty meshproperty;
for (uint lod = 0; lod < 4; lod++)
{
uint elementcount = mesh.LODs[lod].Faces.size() * 3;
meshproperty.LODs[lod].BaseVertex = vertices.size();
meshproperty.LODs[lod].BaseIndex = indices.size();
meshproperty.LODs[lod].IndexCount = elementcount;
if (elementcount == 0)
continue;
// collect vertices for vertex buffer (containing all meshes)
vertices.insert(
vertices.end(),
mesh.LODs[lod].Vertices.begin(),
mesh.LODs[lod].Vertices.end());
// collect indices for index buffer (containing all meshes)
for (auto& face : mesh.LODs[lod].Faces)
{
indices.push_back(face.x);
indices.push_back(face.y);
indices.push_back(face.z);
}
}
// additional information for instance culling
meshproperty.BSphere = mesh.LODs[0].GetBoundingSphere();
meshproperties.push_back(meshproperty);
// return array index as "mesh reference"
return meshproperties.size() - 1;
}
glm::uint Graphics::LoadTexture(const std::string& filename)
{
/* check if texture already exists */
for (uint i = 0; i < texture_filenames.size(); i++)
if (texture_filenames.at(i) == filename)
return i;
/* check if null texture is requested */
if (filename == NULL_TEXTURE_NAME)
{
/* is wasnt found in the texture_map, so create it */
GLubyte null_texture_data[] = { 0xFF, 0xFF, 0xFF, 0xFF };
GLuint texture = 0;
glCreateTextures(GL_TEXTURE_2D, 1, &texture);
glTextureStorage2D(texture, 1, GL_RGBA8, 1, 1);
glTextureSubImage2D(texture, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, null_texture_data);
glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
GLuint64 texture_handle = glGetTextureHandleARB(texture);
glMakeTextureHandleResidentARB(texture_handle);
textures.push_back(texture);
texture_handles.push_back(texture_handle);
texture_filenames.push_back(filename);
CheckForGLErrors;
return textures.size() - 1;
}
/* load image */
int texture_width = 0;
int texture_height = 0;
int texture_channels = 0;
GLubyte* texture_data = SOIL_load_image(
filename.c_str(),
&texture_width, /* return width */
&texture_height, /* return height */
&texture_channels, /* return channels */
SOIL_LOAD_RGBA /* create texture with 4 components (RGBA) */
);
if (!texture_data)
{
cout << "error: cant load texture file \"" << filename << "\"" << endl;
return LoadTexture(NULL_TEXTURE_NAME);
}
/* calculate lods */
GLuint lods = std::max(1.0, std::log2(std::max(texture_width, texture_height)));
/* generate new texture */
GLuint texture = 0;
glCreateTextures(GL_TEXTURE_2D, 1, &texture);
glTextureStorage2D(texture, lods, GL_RGBA8, texture_width, texture_height);
glTextureSubImage2D(texture, 0, 0, 0, texture_width, texture_height, GL_RGBA, GL_UNSIGNED_BYTE, texture_data);
glGenerateTextureMipmap(texture);
//glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
//glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
GLuint64 texture_handle = glGetTextureHandleARB(texture);
glMakeTextureHandleResidentARB(texture_handle);
/* release texture memory */
SOIL_free_image_data(texture_data);
textures.push_back(texture);
texture_handles.push_back(texture_handle);
texture_filenames.push_back(filename);
CheckForGLErrors;
return textures.size() - 1;
}
std::string Graphics::LoadTextFile(const std::string& filepath)
{
string result;
fstream f(filepath.c_str(), ios::in);
while (f.good())
{
string line;
getline(f, line);
result += line + '\n';
}
return result;
}
std::string Graphics::ShaderTypeName(GLuint shader)
{
if (glIsShader(shader))
{
GLint type = 0;
glGetShaderiv(shader, GL_SHADER_TYPE, &type);
if (type == GL_VERTEX_SHADER)
return "Vertex Shader";
if (type == GL_TESS_CONTROL_SHADER)
return "Tessellation Control Shader";
if (type == GL_TESS_EVALUATION_SHADER)
return "Tessellation Evaluation Shader";
if (type == GL_GEOMETRY_SHADER)
return "Geometry Shader";
if (type == GL_FRAGMENT_SHADER)
return "Fragment Shader";
if (type == GL_COMPUTE_SHADER)
return "Compute Shader";
}
return "invalid shader";
}
bool Graphics::CompileShader(GLuint shader, const std::string& sourcecode)
{
if (!glIsShader(shader))
{
cout << "ERROR: shader compilation failed, no valid shader specified" << endl;
return false;
}
if (sourcecode.empty())
{
cout << "ERROR: shader compilation failed, no source code specified (" << ShaderTypeName(shader) << ")" << endl;
return false;
}
const char* sourcearray[] = { sourcecode.c_str() };
glShaderSource(shader, 1, sourcearray, NULL);
glCompileShader(shader);
// check compile status
GLint status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
// successfully compiled shader
if (status == GL_TRUE)
return true;
// show compile errors
cout << "ERROR: shader compilation failed (" << ShaderTypeName(shader) << ")" << endl << ShaderInfoLog(shader) << endl;
return false;
}
std::string Graphics::ShaderInfoLog(GLuint shader)
{
if (!glIsShader(shader))
return "invalid shader";
GLint logsize = 0;
GLchar infolog[1024] = { 0 };
glGetShaderInfoLog(shader, 1024, &logsize, infolog);
return string(infolog);
}
bool Graphics::LinkProgram(GLuint program, const std::vector<GLuint>& shaders)
{
if (!glIsProgram(program))
{
cout << "ERROR: shader linking failed, no valid program specified" << endl;
return false;
}
// attach all shaders to the program
for (auto& shader : shaders)
{
if (glIsShader(shader))
glAttachShader(program, shader);
}
// link program
glLinkProgram(program);
// detach all shaders again
for (auto& shader : shaders)
{
if (glIsShader(shader))
glDetachShader(program, shader);
}
GLint status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
// successfully linked program
if (status == GL_TRUE)
return true;
// show link errors
cout << "ERROR: shader linking failed" << endl << ProgramInfoLog(program) << endl;
return false;
}
std::string Graphics::ProgramInfoLog(GLuint program)
{
if (!glIsProgram(program))
return "invalid program";
GLint logsize = 0;
GLchar infolog[1024] = { 0 };
glGetProgramInfoLog(program, 1024, &logsize, infolog);
return string(infolog);
}
Computeshader: "Shaders/preprocess.cs.glsl"
#version 460 core
layout (local_size_x = 1024) in;
/* argument type buffered in GL_DRAW_INDIRECT_BUFFER */
struct DrawElementsIndirectCommand {
uint Count;
uint InstanceCount;
uint FirstIndex;
uint BaseVertex;
uint BaseInstance;
};
// contains for each mesh vertex offset, element
// offset, element count and bounding sphere
struct MeshLODProperty {
uint BaseVertex;
uint BaseIndex;
uint IndexCount;
uint _padding1;
};
struct MeshProperty {
MeshLODProperty LODs[4];
vec4 BSphere;
};
// array containing instanced data
layout (std430, binding = 1) buffer InstanceBlock {
mat4 modelview[];
};
// array containing indirect draw calls
layout (std430, binding = 2) buffer IndirectBlock {
DrawElementsIndirectCommand cmds[];
};
// array containing drawables
layout (std430, binding = 3) buffer DrawablesBlock {
uvec2 drawables[];
};
// array containing bounding spheres for each object instance
layout (std430, binding = 6) buffer MeshPropertyBlock {
MeshProperty meshproperties[];
};
layout (location = 0) uniform float FieldOfView = 0.0f;
layout (location = 1) uniform float AspectRatio = 0.0f;
layout (location = 2) uniform float ZNear = 0.0f;
layout (location = 3) uniform float ZFar = 0.0f;
void main()
{
// get drawable index
uint index = gl_GlobalInvocationID.x;
if (index >= modelview.length())
return;
if (index >= cmds.length())
return;
if (index >= drawables.length())
return;
// get mesh properties
uint meshindex = drawables[index].y;
if (meshindex >= meshproperties.length())
return;
MeshProperty meshproperty = meshproperties[meshindex];
// bsphere position (in view space)
vec3 center = meshproperty.BSphere.xyz;
float radius = meshproperty.BSphere.w;
vec3 position = (modelview[index] * vec4(center, 1)).xyz;
float A = 1.0f / tan(FieldOfView * AspectRatio / 2.0f);
float B = 1.0f / tan(FieldOfView / 2.0f);
// camera frustum face normals
vec3 normal_L = normalize(vec3(-A, 0, 1));
vec3 normal_R = normalize(vec3(+A, 0, 1));
vec3 normal_T = normalize(vec3(0, +B, 1));
vec3 normal_B = normalize(vec3(0, -B, 1));
// distances of bounding sphere to camera frustum faces
float distance_L = dot(position, normal_L);
float distance_R = dot(position, normal_R);
float distance_T = dot(position, normal_T);
float distance_B = dot(position, normal_B);
// cull invisible objects
cmds[index].InstanceCount = 0;
if (distance_L > radius)
return;
if (distance_R > radius)
return;
if (distance_T > radius)
return;
if (distance_B > radius)
return;
// get lod count
uint lodcount = 0;
for (uint lod = 0; lod < 4; lod++)
{
if (meshproperty.LODs[lod].IndexCount == 0)
break;
lodcount++;
}
if (lodcount == 0)
return;
// select lod
uint lod = uint(0.2f * length(position) / radius);
lod = clamp(lod, 0, lodcount - 1);
// object is visible: set mesh LOD parameters
cmds[index].InstanceCount = 1;
cmds[index].BaseInstance = index;
cmds[index].BaseVertex = meshproperty.LODs[lod].BaseVertex;
cmds[index].FirstIndex = meshproperty.LODs[lod].BaseIndex;
cmds[index].Count = meshproperty.LODs[lod].IndexCount;
// example: set material
// lod 0 = gold
// lod 1 = silver
// lod 2 = bronze
// lod 3 = green_plastic
drawables[index].x = lod;
}
Vertexshader: "Shaders/model.vs.glsl"
#version 460 core
// camera perspective
layout (location = 0) uniform mat4 projection = mat4(1);
// per-vertex attributes
layout (location = 0) in vec3 in_position;
layout (location = 1) in vec3 in_normal;
layout (location = 2) in vec2 in_texcoord;
// per-instance attributes
layout (location = 3) in mat4 in_mv;
// interface vertex shader --> fragment shader
out VS_FS {
smooth vec3 position;
smooth vec3 normal;
smooth vec2 texcoord;
flat uint drawID;
} vs_out;
void main()
{
// vertex position
gl_Position = projection * in_mv * vec4(in_position, 1);
// position and normal in eye-space
vs_out.position = (in_mv * vec4(in_position, 1)).xyz;
vs_out.normal = (in_mv * vec4(in_normal, 0)).xyz;
// texture coordinate
vs_out.texcoord = in_texcoord;
// draw command index [ 0 ; instancecount )
vs_out.drawID = gl_DrawID;
}
Fragmentshader: "model.fs.glsl"
#version 460 core
#extension GL_ARB_bindless_texture : enable
// material definition
struct Material {
uint Kd;
uint Ks;
float Ns;
float d;
uint MapKd;
};
// array of drawables (= pair of material and mesh indices)
layout (std430, binding = 3) buffer DrawablesBlock {
uvec2 drawables[];
};
// array of materials
layout (std430, binding = 4) buffer MaterialBlock {
Material materials[];
};
// array of bindless textures (referenced by materials)
layout (std430, binding = 5) buffer TextureHandleBlock {
uvec2 texture_handles[];
};
// light parameters
layout (location = 4) uniform vec3 light_intensity = vec3(0, 0, 0);
layout (location = 5) uniform vec3 light_direction = vec3(0, 0, 0);
layout (location = 6) uniform float light_ambient = 0.0f;
// interface vertex shader --> fragment shader
in VS_FS {
smooth vec3 position;
smooth vec3 normal;
smooth vec2 texcoord;
flat uint drawID;
} fs_in;
// fragment color
layout (location = 0) out vec4 out_color;
void main()
{
// get material index
uint material_index = drawables[fs_in.drawID].x;
// get material data
vec3 Kd = unpackUnorm4x8(materials[material_index].Kd).rgb;
vec3 Ks = unpackUnorm4x8(materials[material_index].Ks).rgb;
float Ns = clamp(materials[material_index].Ns, 1, 1000);
float d = clamp(materials[material_index].d, 0, 1);
// get texture data
sampler2D MapKd = sampler2D(texture_handles[ materials[material_index].MapKd ]);
Kd = Kd * texture(MapKd, fs_in.texcoord).rgb;
// directions (eye-space)
vec3 N = normalize(fs_in.normal);
vec3 L = normalize(-light_direction);
vec3 R = reflect(-L, N);
vec3 V = normalize(-fs_in.position);
// light data
vec3 Ia = light_intensity * clamp(light_ambient, 0, 1);
vec3 Id = light_intensity * max(0, dot(N, L));
vec3 Is = light_intensity * max(0, pow(max(0, dot(R, V)), Ns));
// color
vec3 color = (Ia + Id) * Kd + Is * Ks;
// to framebuffer
out_color = vec4(color, d);
}