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);

}