The earth texture is downloaded form NASA website:
http://visibleearth.nasa.gov/view_cat.php?categoryID=1484
Textures are part of materials. To be able to have access to many different textures (2D) at the same time, we need an "Array Texture".
Array textures can have (at least!) up to 2048 different layers, each represents a separate texture / image.
There is however 1 important restriction: all layers have the same resolution.
That means, all of our images must have the predefined resolution or we have to resize them ourselves. Here i wont show how to resize textures automatically.
https://www.opengl.org/wiki/Array_Texture
In our fragment shader, we access these texture layers like this:
// this is the array texture
uniform sampler2DArray textureKd;
...
// read texel from layer X:
vec4 texel = texture(textureKd, vec3(vs_out.texcoord, X));
Now we have 2 different diffuse color values: the material's Kd or the texture. The easiest way is to just multiply both values for the "final" Kd value:
if a texture is present, replace the material's Kd value with "blank white" (vec3(1, 1, 1)), that means the product of the multiplication will result in the textures color value.
if there is NO texture available, we have to make sure that the textures value is "blank white" (vec3(1, 1, 1)). Thats why i have added an empty string "" to the texture-Kd array right at the beginning (before loading any model):
std::vector<std::string> texturesdiffuse = { "" };
That makes sure that when we load all the images, the first layer (at index 0) wont contain any image, but a completely white texture:
struct Pixel { unsigned char r{ 255 }, g{ 255 }, b{ 255 }, a{ 255 }; };
std::vector<Pixel> whitetexture(texturewidth * textureheight);
// load all textures Kd:
// there is 1 restriction: all textures MUST be of resolution "texturewidth" x "textureheight"
for (unsigned int i = 0; i < texturesdiffuse.size(); i++)
{
// this c++ class to load images was written by Benjamin Kalytta, http://www.kalytta.com/bitmap.h
CBitmap bmp;
void* texturedata = whitetexture.data();
if (bmp.Load(texturesdiffuse[i].c_str()))
if (bmp.GetWidth() == texturewidth && bmp.GetHeight() == textureheight)
texturedata = bmp.GetBits();
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, texturewidth, textureheight, 1, GL_RGBA, GL_UNSIGNED_BYTE, texturedata);
}
Loading models works like this:
create a vector<Vertex> vertices, vector<Material> materials, and vector<string> filepathsofalltextures
load the model data from .obj file and .mtl file
fill the geometry in our "vertices"
fill the materials in our "materials" and the texture filepaths in our "filepathsofalltextures"
When finished, put all the vertices into the vertexbuffer, put all the materials into the materialbuffer, and load all textures.
The reason for "pre-loading" all the data is that we can later load many different models without any problems, because all models will share the same resources:
Textures of ALL models will be accessible at the same textureunit
Vertices of ALL models will be put into the same vertexbuffer
Materials of ALL models will be put into the same materialbuffer
We only need to keep track of offset / range values for the vertices for each model, everything else will be manages by the "void LoadModel(...)" function.
Complete Source Code:
Scene.cpp
#include "Scene.h"
#include "Main.h"
Scene::Scene()
{
// put the camera back a little ...
Camera.SetPosition(vec3(0, 1, 5));
// create some lights
Light.DirectionalLights.push_back(DirectionalLight(vec3(5, -10, 0), vec3(1, 1, 1)));
Light.PointLights.push_back(PointLight(vec3(0, 5, 0), vec3(1, 1, 1), 0.1f));
//Light.SpotLights.push_back(SpotLight(vec3(0, 5, 0), vec3(0, -1, 0), vec3(0.7f, 0.7f, 0.7f), 5));
// create some objects ...
Objects.push_back(Object(vec3(-2, 0, -2)));
Objects.push_back(Object(vec3(+2, 0, -2)));
Objects.push_back(Object(vec3(+2, 0, +2)));
Objects.push_back(Object(vec3(-2, 0, +2)));
// ...
}
void Scene::AnimateNextFrame(float timestep, int tracked_ID)
{
// camera control
// hold right mouse button to rotate the camera
// scroll up/down to move the camera
// ----------------------------------------------------------------------------------------------------------
if (Framework->IsClicked(MOUSE_BUTTON_RIGHT))
{
Camera.Yaw(Framework->EventCursorMoved().x * -0.01f);
Camera.Pitch(Framework->EventCursorMoved().y * +0.01f);
}
if (Framework->ScrolledUp())
Camera.Move(Camera.Forward() * +0.5f);
if (Framework->ScrolledDown())
Camera.Move(Camera.Forward() * -0.5f);
// ----------------------------------------------------------------------------------------------------------
// animate all objects
// ----------------------------------------------------------------------------------------------------------
for (auto& object : Objects)
object.Yaw(radians(30.0f) * timestep);
// ----------------------------------------------------------------------------------------------------------
// animate lights
// ----------------------------------------------------------------------------------------------------------
double totaltime = Framework->Time();
// change intensity
//for (auto& directionallight : Light.DirectionalLights)
// directionallight.Intensity = vec3(abs2(std::cos(totaltime * 2));
// change Y-coordinate (height)
for (auto& pointlight : Light.PointLights)
pointlight.Position.y = 5 * std::cos(totaltime * 0.5f);
// rotate arount Y-axis
for (auto& spotlight : Light.SpotLights)
spotlight.Direction = vec3(std::cos(totaltime), -5, std::sin(totaltime));
// ----------------------------------------------------------------------------------------------------------
// just print the ID at cursor position:
// ----------------------------------------------------------------------------------------------------------
if (Framework->EventClicked(MOUSE_BUTTON_LEFT))
if (tracked_ID != -1)
std::cout << "clicked at object: \t" << tracked_ID << std::endl;
// ----------------------------------------------------------------------------------------------------------
// ...
}
Model.h
#pragma once
#include "Math.h"
#include <vector>
struct Vertex
{
vec3 Position;
vec2 TexCoord;
vec3 Normal;
int MaterialIndex;
};
struct ModelInstance
{
mat4 ModelMatrix;
int ObjectID;
};
// those padding variables are there to match the required memory layout for "std140":
// arrays of structs must be rounded up to a multiple of 16byte (or sizeof(vec4))
// vec3 are actually consuming sizeof(vec4) in GLSL
struct Material
{
vec3 Ka;
float PADDING1;
vec3 Kd;
float PADDING2;
vec3 Ks;
float PADDING3;
float Ns{ 1 };
float d{ 1 };
int map_Kd{ 0 };
float PADDING5;
Material();
Material(const vec3& ambient, const vec3& diffuse, const vec3& specular, float shininess, float transparency, int map_kd);
};
struct DirectionalLight
{
vec3 Direction;
float PADDING1;
vec3 Intensity;
float PADDING2;
DirectionalLight(const vec3& direction, const vec3& intensity);
};
struct PointLight
{
vec3 Position;
float PADDING1;
vec3 Intensity;
float PADDING2;
float AttenuationLinear{ 0.5 };
float PADDING3;
float PADDING4;
float PADDING5;
PointLight(const vec3& position, const vec3& intensity, float attenuationlinear);
};
struct SpotLight
{
vec3 Position;
float PADDING1;
vec3 Direction;
float PADDING2;
vec3 Intensity;
float PADDING3;
int Exponent{ 1 };
float PADDING4;
float PADDING5;
float PADDING6;
SpotLight(const vec3& position, const vec3& direction, const vec3& intensity, int exponent);
};
void LoadModel(
const std::string& filename, const std::string& base_path,
std::vector<Vertex>& vertexarray,
std::vector<Material>& materialarray,
std::vector<std::string>& textureKdarray);
Model.cpp
#include "Model.h"
#include "OBJ.h"
Material::Material()
{}
Material::Material(const vec3 & ambient, const vec3 & diffuse, const vec3 & specular, float shininess, float transparency, int map_kd)
{
Ka.r = max2(0, min2(1, ambient.r));
Ka.g = max2(0, min2(1, ambient.g));
Ka.b = max2(0, min2(1, ambient.b));
Kd.r = max2(0, min2(1, diffuse.r));
Kd.g = max2(0, min2(1, diffuse.g));
Kd.b = max2(0, min2(1, diffuse.b));
Ks.r = max2(0, min2(1, specular.r));
Ks.g = max2(0, min2(1, specular.g));
Ks.b = max2(0, min2(1, specular.b));
Ns = max2(1, shininess);
d = max2(0, min2(1, transparency));
map_Kd = max2(0, map_kd);
}
DirectionalLight::DirectionalLight(const vec3 & direction, const vec3 & intensity)
: Direction(direction)
{
Intensity.r = max2(0, min2(1, intensity.r));
Intensity.g = max2(0, min2(1, intensity.g));
Intensity.b = max2(0, min2(1, intensity.b));
}
PointLight::PointLight(const vec3 & position, const vec3 & intensity, float attenuationlinear)
: Position(position)
{
Intensity.r = max2(0, min2(1, intensity.r));
Intensity.g = max2(0, min2(1, intensity.g));
Intensity.b = max2(0, min2(1, intensity.b));
AttenuationLinear = max2(0, attenuationlinear);
}
SpotLight::SpotLight(const vec3 & position, const vec3 & direction, const vec3 & intensity, int exponent)
: Position(position), Direction(direction)
{
Intensity.r = max2(0, min2(1, intensity.r));
Intensity.g = max2(0, min2(1, intensity.g));
Intensity.b = max2(0, min2(1, intensity.b));
Exponent = max(1, exponent);
}
void LoadModel(
const std::string& filename, const std::string& base_path,
std::vector<Vertex>& vertexarray,
std::vector<Material>& materialarray,
std::vector<std::string>& textureKdarray)
{
// load model data
OBJ model(filename, base_path);
if (!model.GetErrorString().empty())
{
std::cout << model.GetErrorString() << std::endl;
return;
}
// create an array of .obj materials
std::vector<OBJ::Material> allobjmaterials;
for (auto& objmaterial : model.MaterialLibrary)
allobjmaterials.push_back(objmaterial.second);
bool hastexcoords = model.TexCoords.size() > 0;
bool hasnormals = model.Normals.size() > 0;
// for each object
for (auto& object : model.Objects)
{
// for each group member
for (auto& groupmember : object.second.Groups)
{
// for each materialmap
for (auto& materialmap : groupmember.second.Faces)
{
// get (global) materialindex:
std::string materialname = materialmap.first;
int materialindex = 0;
for (unsigned int i = 0; i < allobjmaterials.size(); i++)
{
if (allobjmaterials[i].Name == materialname)
{
materialindex = materialarray.size() + i;
break;
}
}
// for all facearrays
for (auto& facearraymap : materialmap.second)
{
// only process triangles:
if (facearraymap.first == 3)
{
// for all triangles
for (auto& face : facearraymap.second)
{
// for all triangle points:
vec3 position0 = model.Positions[face.Points[0].Index_v - 1];
vec3 position1 = model.Positions[face.Points[1].Index_v - 1];
vec3 position2 = model.Positions[face.Points[2].Index_v - 1];
vec2 texcoord0(0, 0);
vec2 texcoord1(0, 0);
vec2 texcoord2(0, 0);
if (hastexcoords)
{
texcoord0 = model.TexCoords[face.Points[0].Index_vt - 1];
texcoord1 = model.TexCoords[face.Points[1].Index_vt - 1];
texcoord2 = model.TexCoords[face.Points[2].Index_vt - 1];
}
vec3 normal0(0, 0, 0);
vec3 normal1(0, 0, 0);
vec3 normal2(0, 0, 0);
if (hasnormals)
{
normal0 = model.Normals[face.Points[0].Index_vn - 1];
normal1 = model.Normals[face.Points[1].Index_vn - 1];
normal2 = model.Normals[face.Points[2].Index_vn - 1];
}
else
{
// calculate geometric normal:
vec3 normalgeometric = normalize(cross(position1 - position0, position2 - position0));
normal0 = normal1 = normal2 = normalgeometric;
}
vertexarray.push_back({ position0, texcoord0, normal0, materialindex });
vertexarray.push_back({ position1, texcoord1, normal1, materialindex });
vertexarray.push_back({ position2, texcoord2, normal2, materialindex });
}
}
}
}
}
}
// return these materials:
for (auto& objmaterial : allobjmaterials)
{
int texturediffuse_index = -1;
if (!objmaterial.map.Kd.empty())
{
// texture available, check first if already stored in array:
for (unsigned int i = 0; i < textureKdarray.size(); i++)
{
if (textureKdarray[i] == objmaterial.map.Kd)
{
texturediffuse_index = i;
break;
}
}
if (texturediffuse_index == -1)
{
// add texture:
textureKdarray.push_back(objmaterial.map.Kd);
texturediffuse_index = textureKdarray.size() - 1;
}
// finally set Kd to white:
objmaterial.Kd = vec3(1, 1, 1);
}
materialarray.push_back(Material(objmaterial.Ka, objmaterial.Kd, objmaterial.Ks, objmaterial.Ns, objmaterial.d, texturediffuse_index));
}
}
Renderer_OpenGL.h
#pragma once
#include "Renderer.h"
#include <GL/glew.h>
class Renderer_OpenGL : public Renderer
{
public:
Renderer_OpenGL(unsigned int width, unsigned int height);
virtual ~Renderer_OpenGL();
virtual void Render(const Scene& scene, int& tracked_ID);
protected:
unsigned int m_width{ 0 }, m_height{ 0 };
unsigned int m_framebuffer{ 0 };
unsigned int m_framebuffercolortexture{ 0 };
unsigned int m_framebufferintegertexture{ 0 };
unsigned int m_framebufferdepthtexture{ 0 };
unsigned int m_program{ 0 };
unsigned int m_vertexarray{ 0 };
unsigned int m_vertexbuffer{ 0 };
unsigned int m_instancebuffer{ 0 };
unsigned int m_materialbuffer{ 0 };
unsigned int m_directionallightbuffer{ 0 };
unsigned int m_pointlightbuffer{ 0 };
unsigned int m_spotlightbuffer{ 0 };
unsigned int m_textureKd{ 0 };
unsigned int m_vertexcount{ 0 };
};
Renderer_OpenGL.cpp
#include "Renderer_OpenGL.h"
#include "Shader.h"
#include "Model.h"
#include "BMP.h"
#include "Main.h"
#include <iostream>
#define MAX_MODEL_INSTANCES 1000
#define MAX_MATERIALS 1000
#define MAX_POINTLIGHTS 10
#define MAX_DIRECTIONALLIGHTS 10
#define MAX_SPOTLIGHTS 10
void CheckForGLError()
{
GLenum error;
while ((error = glGetError()) != GL_NO_ERROR)
{
std::cout << "ERROR: (Renderer_OpenGL) \t";
if (error == GL_INVALID_ENUM)
std::cout << "GL_INVALID_ENUM";
if (error == GL_INVALID_VALUE)
std::cout << "GL_INVALID_VALUE";
if (error == GL_INVALID_OPERATION)
std::cout << "GL_INVALID_OPERATION";
if (error == GL_INVALID_FRAMEBUFFER_OPERATION)
std::cout << "GL_INVALID_FRAMEBUFFER_OPERATION";
if (error == GL_OUT_OF_MEMORY)
std::cout << "GL_OUT_OF_MEMORY";
if (error == GL_STACK_UNDERFLOW)
std::cout << "GL_STACK_UNDERFLOW";
if (error == GL_STACK_OVERFLOW)
std::cout << "GL_STACK_OVERFLOW";
std::cout << (char)7 << std::endl; /*play sound*/
std::cin.get();
}
}
Renderer_OpenGL::Renderer_OpenGL(unsigned int width, unsigned int height)
{
// settings
// ----------------------------------------------------------------------------------------------------------
glEnable(GL_DEPTH_TEST); // 3D
glEnable(GL_CULL_FACE); // dont render backward faces
// ----------------------------------------------------------------------------------------------------------
// setup framebuffer:
// ----------------------------------------------------------------------------------------------------------
// make sure that the framebuffer isnt too small
m_width = max2(100, width);
m_height = max2(100, height);
glViewport(0, 0, m_width, m_height);
glGenTextures(1, &m_framebuffercolortexture);
glBindTexture(GL_TEXTURE_2D, m_framebuffercolortexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glGenTextures(1, &m_framebufferintegertexture);
glBindTexture(GL_TEXTURE_2D, m_framebufferintegertexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// special texture to store only integer values (32 bit)
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32I, m_width, m_height, 0, GL_RED_INTEGER, GL_INT, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glGenTextures(1, &m_framebufferdepthtexture);
glBindTexture(GL_TEXTURE_2D, m_framebufferdepthtexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_width, m_height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
glBindTexture(GL_TEXTURE_2D, 0);
// create framebuffer
glGenFramebuffers(1, &m_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_framebuffercolortexture, 0);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, m_framebufferintegertexture, 0);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, m_framebufferdepthtexture, 0);
std::vector<GLenum> drawbuffers = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(drawbuffers.size(), drawbuffers.data());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// ERROR CHECK:
GLenum status = glCheckNamedFramebufferStatus(m_framebuffer, GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
if (status == GL_FRAMEBUFFER_UNDEFINED)
std::cout << "ERROR: undefined framebuffer" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
std::cout << "ERROR: a necessary attachment is uninitialized" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
std::cout << "ERROR: no attachments" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER)
std::cout << "ERROR: incomplete draw buffer" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER)
std::cout << "ERROR: incomplete read buffer" << std::endl;
if (status == GL_FRAMEBUFFER_UNSUPPORTED)
std::cout << "ERROR: combination of attachments is not supported" << std::endl;
if (status == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
std::cout << "ERROR: number if samples for all attachments does not match" << std::endl;
}
// ----------------------------------------------------------------------------------------------------------
// setup program
// ----------------------------------------------------------------------------------------------------------
m_program = glCreateProgram();
unsigned int vertexshader = glCreateShader(GL_VERTEX_SHADER);
unsigned int fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);
CompileShader(vertexshader, LoadTextFile("shader.vs"));
CompileShader(fragmentshader, LoadTextFile("shader.fs"));
LinkProgram(m_program, { vertexshader , fragmentshader });
glDeleteShader(vertexshader);
glDeleteShader(fragmentshader);
// ----------------------------------------------------------------------------------------------------------
// setup vertexarray
// ----------------------------------------------------------------------------------------------------------
glGenVertexArrays(1, &m_vertexarray);
glGenBuffers(1, &m_vertexbuffer);
glGenBuffers(1, &m_instancebuffer);
glBindVertexArray(m_vertexarray);
glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 0));
glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 3));
glVertexAttribPointer(2, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 5));
glVertexAttribIPointer(3, 1, GL_INT, sizeof(Vertex), (void*)(sizeof(float) * 8));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_instancebuffer);
glVertexAttribPointer(4, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 0));
glVertexAttribPointer(5, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 4));
glVertexAttribPointer(6, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 8));
glVertexAttribPointer(7, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 12));
glVertexAttribIPointer(8, 1, GL_INT, sizeof(ModelInstance), (void*)(sizeof(float) * 16));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glEnableVertexAttribArray(6);
glEnableVertexAttribArray(7);
glEnableVertexAttribArray(8);
// sent these attributes only once per instance to the program:
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1);
glVertexAttribDivisor(7, 1);
glVertexAttribDivisor(8, 1);
glBindVertexArray(0);
// ----------------------------------------------------------------------------------------------------------
// setup vertex buffer
// ----------------------------------------------------------------------------------------------------------
std::vector<Vertex> vertices;
std::vector<Material> materials;
std::vector<std::string> texturesdiffuse = { "" };
LoadModel("sphere.obj", "", vertices, materials, texturesdiffuse);
if (vertices.size() == 0)
{
vertices.push_back({ { 0, 0, 0 },{ 0, 0 },{ 0, 0, 1 },{ 0 } });
vertices.push_back({ { 1, 0, 0 },{ 0, 1 },{ 0, 0, 1 },{ 0 } });
vertices.push_back({ { 0, 1, 0 },{ 0, 0 },{ 0, 0, 1 },{ 0 } });
}
m_vertexcount = vertices.size();
glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// ----------------------------------------------------------------------------------------------------------
// setup instance buffer
// ----------------------------------------------------------------------------------------------------------
glBindBuffer(GL_UNIFORM_BUFFER, m_instancebuffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(ModelInstance) * MAX_MODEL_INSTANCES, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
// ----------------------------------------------------------------------------------------------------------
// setup material buffer
// ----------------------------------------------------------------------------------------------------------
if (materials.size() == 0)
materials.push_back(Material(vec3(1, 1, 1), vec3(1, 1, 1), vec3(1, 1, 1), 1.0f, 1.0f, 0));
materials.resize(MAX_MATERIALS);
glGenBuffers(1, &m_materialbuffer);
glBindBuffer(GL_UNIFORM_BUFFER, m_materialbuffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(Material) * materials.size(), materials.data(), GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferBase(GL_UNIFORM_BUFFER, 1, m_materialbuffer);
// ----------------------------------------------------------------------------------------------------------
// setup light buffer
// ----------------------------------------------------------------------------------------------------------
glGenBuffers(1, &m_directionallightbuffer);
glBindBuffer(GL_UNIFORM_BUFFER, m_directionallightbuffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(DirectionalLight) * MAX_DIRECTIONALLIGHTS, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glGenBuffers(1, &m_pointlightbuffer);
glBindBuffer(GL_UNIFORM_BUFFER, m_pointlightbuffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(PointLight) * MAX_POINTLIGHTS, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glGenBuffers(1, &m_spotlightbuffer);
glBindBuffer(GL_UNIFORM_BUFFER, m_spotlightbuffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(SpotLight) * MAX_SPOTLIGHTS, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
// bind buffers to uniform blocks:
glBindBufferBase(GL_UNIFORM_BUFFER, 2, m_directionallightbuffer);
glBindBufferBase(GL_UNIFORM_BUFFER, 3, m_pointlightbuffer);
glBindBufferBase(GL_UNIFORM_BUFFER, 4, m_spotlightbuffer);
// ----------------------------------------------------------------------------------------------------------
// setup textures
// ----------------------------------------------------------------------------------------------------------
unsigned int mipmapcount = 1;
unsigned int texturewidth = 1024;
unsigned int textureheight = 1024;
unsigned int numberoflayers = texturesdiffuse.size();
struct Pixel { unsigned char r{ 255 }, g{ 255 }, b{ 255 }, a{ 255 }; };
std::vector<Pixel> whitetexture(texturewidth * textureheight);
glGenTextures(1, &m_textureKd);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_textureKd);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// allocate texture memory:
glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipmapcount, GL_RGBA8, texturewidth, textureheight, numberoflayers);
// load all textures Kd:
// there is 1 restriction: all textures MUST be of resolution "texturewidth" x "textureheight"
for (unsigned int i = 0; i < texturesdiffuse.size(); i++)
{
// this c++ class to load images was written by Benjamin Kalytta, http://www.kalytta.com/bitmap.h
CBitmap bmp;
void* texturedata = whitetexture.data();
if (bmp.Load(texturesdiffuse[i].c_str()))
if (bmp.GetWidth() == texturewidth && bmp.GetHeight() == textureheight)
texturedata = bmp.GetBits();
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, texturewidth, textureheight, 1, GL_RGBA, GL_UNSIGNED_BYTE, texturedata);
}
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
// bind diffuse texture to textureunit 1:
glBindTextureUnit(1, m_textureKd);
glProgramUniform1i(m_program, glGetUniformLocation(m_program, "textureKd"), 1);
// ----------------------------------------------------------------------------------------------------------
}
Renderer_OpenGL::~Renderer_OpenGL()
{
glDeleteProgram(m_program);
glDeleteFramebuffers(1, &m_framebuffer);
glDeleteTextures(1, &m_framebuffercolortexture);
glDeleteTextures(1, &m_framebufferintegertexture);
glDeleteTextures(1, &m_framebufferdepthtexture);
glDeleteVertexArrays(1, &m_vertexarray);
glDeleteBuffers(1, &m_vertexbuffer);
glDeleteBuffers(1, &m_instancebuffer);
glDeleteBuffers(1, &m_materialbuffer);
glDeleteBuffers(1, &m_directionallightbuffer);
glDeleteBuffers(1, &m_pointlightbuffer);
glDeleteBuffers(1, &m_spotlightbuffer);
glDeleteTextures(1, &m_textureKd);
}
void Renderer_OpenGL::Render(const Scene & scene, int& tracked_ID)
{
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
// explicitly clear each buffer:
float clearvalue0[] = { 0.1f, 0.2f, 0.7f, 0.0f }; // background color
int clearvalue1[] = { -1 }; // background integer values
glClearBufferfv(GL_COLOR, 0, clearvalue0);
glClearBufferiv(GL_COLOR, 1, clearvalue1);
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glUseProgram(m_program);
glBindVertexArray(m_vertexarray);
// set camera
// ----------------------------------------------------------------------------------------------------------
glUniformMatrix4fv(glGetUniformLocation(m_program, "View"), 1, false, value_ptr(scene.Camera.View()));
glUniformMatrix4fv(glGetUniformLocation(m_program, "Projection"), 1, false, value_ptr(perspective(scene.Camera.FoV, 1.33f, scene.Camera.ZNear, scene.Camera.ZFar)));
// ----------------------------------------------------------------------------------------------------------
// set light sources:
// ----------------------------------------------------------------------------------------------------------
glUniform3fv(glGetUniformLocation(m_program, "CameraPosition"), 1, value_ptr(scene.Camera.Position()));
glUniform3fv(glGetUniformLocation(m_program, "Ambient"), 1, value_ptr(scene.Light.Ambient));
unsigned int directionallightcount = min2(MAX_DIRECTIONALLIGHTS, scene.Light.DirectionalLights.size());
unsigned int pointlightcount = min2(MAX_POINTLIGHTS, scene.Light.PointLights.size());
unsigned int spotlightcount = min2(MAX_SPOTLIGHTS, scene.Light.SpotLights.size());
glUniform1i(glGetUniformLocation(m_program, "DirectionalLightCount"), directionallightcount);
glUniform1i(glGetUniformLocation(m_program, "PointLightCount"), pointlightcount);
glUniform1i(glGetUniformLocation(m_program, "SpotLightCount"), spotlightcount);
if (directionallightcount > 0)
{
glBindBuffer(GL_UNIFORM_BUFFER, m_directionallightbuffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(DirectionalLight) * directionallightcount, scene.Light.DirectionalLights.data());
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
if (pointlightcount > 0)
{
glBindBuffer(GL_UNIFORM_BUFFER, m_pointlightbuffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(PointLight) * pointlightcount, scene.Light.PointLights.data());
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
if (spotlightcount > 0)
{
glBindBuffer(GL_UNIFORM_BUFFER, m_spotlightbuffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(SpotLight) * spotlightcount, scene.Light.SpotLights.data());
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
// ----------------------------------------------------------------------------------------------------------
// create instance buffer data:
// ----------------------------------------------------------------------------------------------------------
std::vector<ModelInstance> instances;
for (auto& object : scene.Objects)
instances.push_back({ object.ModelMatrix(), (int)object.ID() });
// ----------------------------------------------------------------------------------------------------------
// determine instance count:
// ----------------------------------------------------------------------------------------------------------
unsigned int instancecount = instances.size();
if (instancecount >= MAX_MODEL_INSTANCES)
instancecount = MAX_MODEL_INSTANCES;
// ----------------------------------------------------------------------------------------------------------
// upload instance buffer data:
// ----------------------------------------------------------------------------------------------------------
glBindBuffer(GL_UNIFORM_BUFFER, m_instancebuffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ModelInstance) * instancecount, instances.data());
glBindBuffer(GL_UNIFORM_BUFFER, 0);
// ----------------------------------------------------------------------------------------------------------
// render objects in scene
glDrawArraysInstanced(GL_TRIANGLES, 0, m_vertexcount, instancecount);
glBindVertexArray(0);
glUseProgram(0);
// read ID at cursor position:
ivec2 cursorposition = Framework->CursorPosition();
ivec2 windowsize = Framework->WindowSize();
int x = cursorposition.x * (float)m_width / windowsize.x;
int y = cursorposition.y * (float)m_height / windowsize.y;
glReadBuffer(GL_COLOR_ATTACHMENT1);
glReadPixels(x, y, 1, 1, GL_RED_INTEGER, GL_INT, &tracked_ID);
// copy to default framebuffer:
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_framebuffer);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(
0, 0, m_width, m_height,
0, 0, windowsize.x, windowsize.y,
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
// check for errors
CheckForGLError();
}
BMP.h
/*
* Windows Bitmap File Loader
* Version 1.2.5 (20120929)
*
* Supported Formats: 1, 4, 8, 16, 24, 32 Bit Images
* Alpha Bitmaps are also supported.
* Supported compression types: RLE 8, BITFIELDS
*
* Created by: Benjamin Kalytta, 2006 - 2012
* Thanks for bug fixes goes to: Chris Campbell
*
* Licence: Free to use, URL to my source and my name is required in your source code.
*
* Source can be found at http://www.kalytta.com/bitmap.h
*
* Warning: This code should not be used in unmodified form in a production environment.
* It should only serve as a basis for your own development.
* There is only a minimal error handling in this code. (Notice added 20111211)
*/
#ifndef BITMAP_H
#define BITMAP_H
#include <iostream>
#include <fstream>
#include <string>
#ifndef __LITTLE_ENDIAN__
#ifndef __BIG_ENDIAN__
#define __LITTLE_ENDIAN__
#endif
#endif
#ifdef __LITTLE_ENDIAN__
#define BITMAP_SIGNATURE 0x4d42
#else
#define BITMAP_SIGNATURE 0x424d
#endif
#if defined(_MSC_VER) || defined(__INTEL_COMPILER)
typedef unsigned __int32 uint32_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int8 uint8_t;
typedef __int32 int32_t;
#elif defined(__GNUC__) || defined(__CYGWIN__) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__PGI) || defined(__LCC__)
#include <stdint.h>
#else
typedef unsigned int uint32_t;
typedef unsigned short int uint16_t;
typedef unsigned char uint8_t;
typedef int int32_t;
#endif
#pragma pack(push, 1)
typedef struct _BITMAP_FILEHEADER {
uint16_t Signature;
uint32_t Size;
uint32_t Reserved;
uint32_t BitsOffset;
} BITMAP_FILEHEADER;
#define BITMAP_FILEHEADER_SIZE 14
typedef struct _BITMAP_HEADER {
uint32_t HeaderSize;
int32_t Width;
int32_t Height;
uint16_t Planes;
uint16_t BitCount;
uint32_t Compression;
uint32_t SizeImage;
int32_t PelsPerMeterX;
int32_t PelsPerMeterY;
uint32_t ClrUsed;
uint32_t ClrImportant;
uint32_t RedMask;
uint32_t GreenMask;
uint32_t BlueMask;
uint32_t AlphaMask;
uint32_t CsType;
uint32_t Endpoints[9]; // see http://msdn2.microsoft.com/en-us/library/ms536569.aspx
uint32_t GammaRed;
uint32_t GammaGreen;
uint32_t GammaBlue;
} BITMAP_HEADER;
typedef struct _RGBA {
uint8_t Red;
uint8_t Green;
uint8_t Blue;
uint8_t Alpha;
} RGBA;
typedef struct _BGRA {
uint8_t Blue;
uint8_t Green;
uint8_t Red;
uint8_t Alpha;
} BGRA;
#pragma pack(pop)
class CBitmap {
private:
BITMAP_FILEHEADER m_BitmapFileHeader;
BITMAP_HEADER m_BitmapHeader;
RGBA *m_BitmapData;
unsigned int m_BitmapSize;
// Masks and bit counts shouldn't exceed 32 Bits
public:
class CColor {
public:
static inline unsigned int BitCountByMask(unsigned int Mask) {
unsigned int BitCount = 0;
while (Mask) {
Mask &= Mask - 1;
BitCount++;
}
return BitCount;
}
static inline unsigned int BitPositionByMask(unsigned int Mask) {
return BitCountByMask((Mask & (~Mask + 1)) - 1);
}
static inline unsigned int ComponentByMask(unsigned int Color, unsigned int Mask) {
unsigned int Component = Color & Mask;
return Component >> BitPositionByMask(Mask);
}
static inline unsigned int BitCountToMask(unsigned int BitCount) {
return (BitCount == 32) ? 0xFFFFFFFF : (1 << BitCount) - 1;
}
static unsigned int Convert(unsigned int Color, unsigned int FromBitCount, unsigned int ToBitCount) {
if (ToBitCount < FromBitCount) {
Color >>= (FromBitCount - ToBitCount);
}
else {
Color <<= (ToBitCount - FromBitCount);
if (Color > 0) {
Color |= BitCountToMask(ToBitCount - FromBitCount);
}
}
return Color;
}
};
public:
CBitmap() : m_BitmapData(0), m_BitmapSize(0) {
Dispose();
}
CBitmap(const char* Filename) : m_BitmapData(0), m_BitmapSize(0) {
Load(Filename);
}
~CBitmap() {
Dispose();
}
void Dispose() {
if (m_BitmapData) {
delete[] m_BitmapData;
m_BitmapData = 0;
}
memset(&m_BitmapFileHeader, 0, sizeof(m_BitmapFileHeader));
memset(&m_BitmapHeader, 0, sizeof(m_BitmapHeader));
}
/* Load specified Bitmap and stores it as RGBA in an internal buffer */
bool Load(const char *Filename) {
std::ifstream file(Filename, std::ios::binary | std::ios::in);
if (file.bad()) {
return false;
}
if (file.is_open() == false) {
return false;
}
Dispose();
file.read((char*)&m_BitmapFileHeader, BITMAP_FILEHEADER_SIZE);
if (m_BitmapFileHeader.Signature != BITMAP_SIGNATURE) {
return false;
}
file.read((char*)&m_BitmapHeader, sizeof(BITMAP_HEADER));
/* Load Color Table */
file.seekg(BITMAP_FILEHEADER_SIZE + m_BitmapHeader.HeaderSize, std::ios::beg);
unsigned int ColorTableSize = 0;
if (m_BitmapHeader.BitCount == 1) {
ColorTableSize = 2;
}
else if (m_BitmapHeader.BitCount == 4) {
ColorTableSize = 16;
}
else if (m_BitmapHeader.BitCount == 8) {
ColorTableSize = 256;
}
// Always allocate full sized color table
BGRA* ColorTable = new BGRA[ColorTableSize]; // std::bad_alloc exception should be thrown if memory is not available
file.read((char*)ColorTable, sizeof(BGRA) * m_BitmapHeader.ClrUsed);
/* ... Color Table for 16 bits images are not supported yet */
m_BitmapSize = GetWidth() * GetHeight();
m_BitmapData = new RGBA[m_BitmapSize];
unsigned int LineWidth = ((GetWidth() * GetBitCount() / 8) + 3) & ~3;
uint8_t *Line = new uint8_t[LineWidth];
file.seekg(m_BitmapFileHeader.BitsOffset, std::ios::beg);
int Index = 0;
bool Result = true;
if (m_BitmapHeader.Compression == 0) {
for (unsigned int i = 0; i < GetHeight(); i++) {
file.read((char*)Line, LineWidth);
uint8_t *LinePtr = Line;
for (unsigned int j = 0; j < GetWidth(); j++) {
if (m_BitmapHeader.BitCount == 1) {
uint32_t Color = *((uint8_t*)LinePtr);
for (int k = 0; k < 8; k++) {
m_BitmapData[Index].Red = ColorTable[Color & 0x80 ? 1 : 0].Red;
m_BitmapData[Index].Green = ColorTable[Color & 0x80 ? 1 : 0].Green;
m_BitmapData[Index].Blue = ColorTable[Color & 0x80 ? 1 : 0].Blue;
m_BitmapData[Index].Alpha = ColorTable[Color & 0x80 ? 1 : 0].Alpha;
Index++;
Color <<= 1;
}
LinePtr++;
j += 7;
}
else if (m_BitmapHeader.BitCount == 4) {
uint32_t Color = *((uint8_t*)LinePtr);
m_BitmapData[Index].Red = ColorTable[(Color >> 4) & 0x0f].Red;
m_BitmapData[Index].Green = ColorTable[(Color >> 4) & 0x0f].Green;
m_BitmapData[Index].Blue = ColorTable[(Color >> 4) & 0x0f].Blue;
m_BitmapData[Index].Alpha = ColorTable[(Color >> 4) & 0x0f].Alpha;
Index++;
m_BitmapData[Index].Red = ColorTable[Color & 0x0f].Red;
m_BitmapData[Index].Green = ColorTable[Color & 0x0f].Green;
m_BitmapData[Index].Blue = ColorTable[Color & 0x0f].Blue;
m_BitmapData[Index].Alpha = ColorTable[Color & 0x0f].Alpha;
Index++;
LinePtr++;
j++;
}
else if (m_BitmapHeader.BitCount == 8) {
uint32_t Color = *((uint8_t*)LinePtr);
m_BitmapData[Index].Red = ColorTable[Color].Red;
m_BitmapData[Index].Green = ColorTable[Color].Green;
m_BitmapData[Index].Blue = ColorTable[Color].Blue;
m_BitmapData[Index].Alpha = ColorTable[Color].Alpha;
Index++;
LinePtr++;
}
else if (m_BitmapHeader.BitCount == 16) {
uint32_t Color = *((uint16_t*)LinePtr);
m_BitmapData[Index].Red = ((Color >> 10) & 0x1f) << 3;
m_BitmapData[Index].Green = ((Color >> 5) & 0x1f) << 3;
m_BitmapData[Index].Blue = (Color & 0x1f) << 3;
m_BitmapData[Index].Alpha = 255;
Index++;
LinePtr += 2;
}
else if (m_BitmapHeader.BitCount == 24) {
uint32_t Color = *((uint32_t*)LinePtr);
m_BitmapData[Index].Blue = Color & 0xff;
m_BitmapData[Index].Green = (Color >> 8) & 0xff;
m_BitmapData[Index].Red = (Color >> 16) & 0xff;
m_BitmapData[Index].Alpha = 255;
Index++;
LinePtr += 3;
}
else if (m_BitmapHeader.BitCount == 32) {
uint32_t Color = *((uint32_t*)LinePtr);
m_BitmapData[Index].Blue = Color & 0xff;
m_BitmapData[Index].Green = (Color >> 8) & 0xff;
m_BitmapData[Index].Red = (Color >> 16) & 0xff;
m_BitmapData[Index].Alpha = Color >> 24;
Index++;
LinePtr += 4;
}
}
}
}
else if (m_BitmapHeader.Compression == 1) { // RLE 8
uint8_t Count = 0;
uint8_t ColorIndex = 0;
int x = 0, y = 0;
while (file.eof() == false) {
file.read((char*)&Count, sizeof(uint8_t));
file.read((char*)&ColorIndex, sizeof(uint8_t));
if (Count > 0) {
Index = x + y * GetWidth();
for (int k = 0; k < Count; k++) {
m_BitmapData[Index + k].Red = ColorTable[ColorIndex].Red;
m_BitmapData[Index + k].Green = ColorTable[ColorIndex].Green;
m_BitmapData[Index + k].Blue = ColorTable[ColorIndex].Blue;
m_BitmapData[Index + k].Alpha = ColorTable[ColorIndex].Alpha;
}
x += Count;
}
else if (Count == 0) {
int Flag = ColorIndex;
if (Flag == 0) {
x = 0;
y++;
}
else if (Flag == 1) {
break;
}
else if (Flag == 2) {
char rx = 0;
char ry = 0;
file.read((char*)&rx, sizeof(char));
file.read((char*)&ry, sizeof(char));
x += rx;
y += ry;
}
else {
Count = Flag;
Index = x + y * GetWidth();
for (int k = 0; k < Count; k++) {
file.read((char*)&ColorIndex, sizeof(uint8_t));
m_BitmapData[Index + k].Red = ColorTable[ColorIndex].Red;
m_BitmapData[Index + k].Green = ColorTable[ColorIndex].Green;
m_BitmapData[Index + k].Blue = ColorTable[ColorIndex].Blue;
m_BitmapData[Index + k].Alpha = ColorTable[ColorIndex].Alpha;
}
x += Count;
// Attention: Current Microsoft STL implementation seems to be buggy, tellg() always returns 0.
if (file.tellg() & 1) {
file.seekg(1, std::ios::cur);
}
}
}
}
}
else if (m_BitmapHeader.Compression == 2) { // RLE 4
/* RLE 4 is not supported */
Result = false;
}
else if (m_BitmapHeader.Compression == 3) { // BITFIELDS
/* We assumes that mask of each color component can be in any order */
uint32_t BitCountRed = CColor::BitCountByMask(m_BitmapHeader.RedMask);
uint32_t BitCountGreen = CColor::BitCountByMask(m_BitmapHeader.GreenMask);
uint32_t BitCountBlue = CColor::BitCountByMask(m_BitmapHeader.BlueMask);
uint32_t BitCountAlpha = CColor::BitCountByMask(m_BitmapHeader.AlphaMask);
for (unsigned int i = 0; i < GetHeight(); i++) {
file.read((char*)Line, LineWidth);
uint8_t *LinePtr = Line;
for (unsigned int j = 0; j < GetWidth(); j++) {
uint32_t Color = 0;
if (m_BitmapHeader.BitCount == 16) {
Color = *((uint16_t*)LinePtr);
LinePtr += 2;
}
else if (m_BitmapHeader.BitCount == 32) {
Color = *((uint32_t*)LinePtr);
LinePtr += 4;
}
else {
// Other formats are not valid
}
m_BitmapData[Index].Red = CColor::Convert(CColor::ComponentByMask(Color, m_BitmapHeader.RedMask), BitCountRed, 8);
m_BitmapData[Index].Green = CColor::Convert(CColor::ComponentByMask(Color, m_BitmapHeader.GreenMask), BitCountGreen, 8);
m_BitmapData[Index].Blue = CColor::Convert(CColor::ComponentByMask(Color, m_BitmapHeader.BlueMask), BitCountBlue, 8);
m_BitmapData[Index].Alpha = CColor::Convert(CColor::ComponentByMask(Color, m_BitmapHeader.AlphaMask), BitCountAlpha, 8);
Index++;
}
}
}
delete[] ColorTable;
delete[] Line;
file.close();
return Result;
}
bool Save(const char* Filename, unsigned int BitCount = 32) {
bool Result = true;
std::ofstream file(Filename, std::ios::out | std::ios::binary);
if (file.is_open() == false) {
return false;
}
BITMAP_FILEHEADER bfh;
BITMAP_HEADER bh;
memset(&bfh, 0, sizeof(bfh));
memset(&bh, 0, sizeof(bh));
bfh.Signature = BITMAP_SIGNATURE;
bfh.BitsOffset = BITMAP_FILEHEADER_SIZE + sizeof(BITMAP_HEADER);
bfh.Size = (GetWidth() * GetHeight() * BitCount) / 8 + bfh.BitsOffset;
bh.HeaderSize = sizeof(BITMAP_HEADER);
bh.BitCount = BitCount;
if (BitCount == 32) {
bh.Compression = 3; // BITFIELD
bh.AlphaMask = 0xff000000;
bh.BlueMask = 0x00ff0000;
bh.GreenMask = 0x0000ff00;
bh.RedMask = 0x000000ff;
}
else if (BitCount == 16) {
bh.Compression = 3; // BITFIELD
bh.AlphaMask = 0x00000000;
bh.BlueMask = 0x0000001f;
bh.GreenMask = 0x000007E0;
bh.RedMask = 0x0000F800;
}
else {
bh.Compression = 0; // RGB
}
unsigned int LineWidth = (GetWidth() + 3) & ~3;
bh.Planes = 1;
bh.Height = GetHeight();
bh.Width = GetWidth();
bh.SizeImage = (LineWidth * BitCount * GetHeight()) / 8;
bh.PelsPerMeterX = 3780;
bh.PelsPerMeterY = 3780;
if (BitCount == 32) {
file.write((char*)&bfh, sizeof(BITMAP_FILEHEADER));
file.write((char*)&bh, sizeof(BITMAP_HEADER));
file.write((char*)m_BitmapData, bh.SizeImage);
}
else if (BitCount < 16) {
uint8_t* Bitmap = new uint8_t[bh.SizeImage];
BGRA *Palette = 0;
unsigned int PaletteSize = 0;
if (GetBitsWithPalette(Bitmap, bh.SizeImage, BitCount, Palette, PaletteSize)) {
bfh.BitsOffset += PaletteSize * sizeof(BGRA);
file.write((char*)&bfh, BITMAP_FILEHEADER_SIZE);
file.write((char*)&bh, sizeof(BITMAP_HEADER));
file.write((char*)Palette, PaletteSize * sizeof(BGRA));
file.write((char*)Bitmap, bh.SizeImage);
}
delete[] Bitmap;
delete[] Palette;
}
else {
uint32_t RedMask = 0;
uint32_t GreenMask = 0;
uint32_t BlueMask = 0;
uint32_t AlphaMask = 0;
if (BitCount == 16) {
RedMask = 0x0000F800;
GreenMask = 0x000007E0;
BlueMask = 0x0000001F;
AlphaMask = 0x00000000;
}
else if (BitCount == 24) {
RedMask = 0x00FF0000;
GreenMask = 0x0000FF00;
BlueMask = 0x000000FF;
}
else {
// Other color formats are not valid
Result = false;
}
if (Result) {
if (GetBits(NULL, bh.SizeImage, RedMask, GreenMask, BlueMask, AlphaMask)) {
uint8_t* Bitmap = new uint8_t[bh.SizeImage];
if (GetBits(Bitmap, bh.SizeImage, RedMask, GreenMask, BlueMask, AlphaMask)) {
file.write((char*)&bfh, sizeof(BITMAP_FILEHEADER));
file.write((char*)&bh, sizeof(BITMAP_HEADER));
file.write((char*)Bitmap, bh.SizeImage);
}
delete[] Bitmap;
}
}
}
file.close();
return Result;
}
unsigned int GetWidth() {
/* Add plausibility test */
// if (abs(m_BitmapHeader.Width) > 8192) {
// m_BitmapHeader.Width = 8192;
// }
return m_BitmapHeader.Width < 0 ? -m_BitmapHeader.Width : m_BitmapHeader.Width;
}
unsigned int GetHeight() {
/* Add plausibility test */
// if (abs(m_BitmapHeader.Height) > 8192) {
// m_BitmapHeader.Height = 8192;
// }
return m_BitmapHeader.Height < 0 ? -m_BitmapHeader.Height : m_BitmapHeader.Height;
}
unsigned int GetBitCount() {
/* Add plausibility test */
// if (m_BitmapHeader.BitCount > 32) {
// m_BitmapHeader.BitCount = 32;
// }
return m_BitmapHeader.BitCount;
}
/* Copies internal RGBA buffer to user specified buffer */
bool GetBits(void* Buffer, unsigned int &Size) {
bool Result = false;
if (Size == 0 || Buffer == 0) {
Size = m_BitmapSize * sizeof(RGBA);
Result = m_BitmapSize != 0;
}
else {
memcpy(Buffer, m_BitmapData, Size);
Result = true;
}
return Result;
}
/* Returns internal RGBA buffer */
void* GetBits() {
return m_BitmapData;
}
/* Copies internal RGBA buffer to user specified buffer and converts it into destination
* bit format specified by component masks.
*
* Typical Bitmap color formats (BGR/BGRA):
*
* Masks for 16 bit (5-5-5): ALPHA = 0x00000000, RED = 0x00007C00, GREEN = 0x000003E0, BLUE = 0x0000001F
* Masks for 16 bit (5-6-5): ALPHA = 0x00000000, RED = 0x0000F800, GREEN = 0x000007E0, BLUE = 0x0000001F
* Masks for 24 bit: ALPHA = 0x00000000, RED = 0x00FF0000, GREEN = 0x0000FF00, BLUE = 0x000000FF
* Masks for 32 bit: ALPHA = 0xFF000000, RED = 0x00FF0000, GREEN = 0x0000FF00, BLUE = 0x000000FF
*
* Other color formats (RGB/RGBA):
*
* Masks for 32 bit (RGBA): ALPHA = 0xFF000000, RED = 0x000000FF, GREEN = 0x0000FF00, BLUE = 0x00FF0000
*
* Bit count will be rounded to next 8 bit boundary. If IncludePadding is true, it will be ensured
* that line width is a multiple of 4. padding bytes are included if necessary.
*
* NOTE: systems with big endian byte order may require masks in inversion order.
*/
bool GetBits(void* Buffer, unsigned int &Size, unsigned int RedMask, unsigned int GreenMask, unsigned int BlueMask, unsigned int AlphaMask, bool IncludePadding = true) {
bool Result = false;
uint32_t BitCountRed = CColor::BitCountByMask(RedMask);
uint32_t BitCountGreen = CColor::BitCountByMask(GreenMask);
uint32_t BitCountBlue = CColor::BitCountByMask(BlueMask);
uint32_t BitCountAlpha = CColor::BitCountByMask(AlphaMask);
unsigned int BitCount = (BitCountRed + BitCountGreen + BitCountBlue + BitCountAlpha + 7) & ~7;
if (BitCount > 32) {
return false;
}
unsigned int w = GetWidth();
//unsigned int LineWidth = (w + 3) & ~3;
unsigned int dataBytesPerLine = (w * BitCount + 7) / 8;
unsigned int LineWidth = (dataBytesPerLine + 3) & ~3;
if (Size == 0 || Buffer == 0) {
//Size = (LineWidth * GetHeight() * BitCount) / 8 + sizeof(unsigned int);
Size = (GetWidth() * GetHeight() * BitCount) / 8 + sizeof(unsigned int);
return true;
}
uint8_t* BufferPtr = (uint8_t*)Buffer;
Result = true;
uint32_t BitPosRed = CColor::BitPositionByMask(RedMask);
uint32_t BitPosGreen = CColor::BitPositionByMask(GreenMask);
uint32_t BitPosBlue = CColor::BitPositionByMask(BlueMask);
uint32_t BitPosAlpha = CColor::BitPositionByMask(AlphaMask);
unsigned int j = 0;
for (unsigned int i = 0; i < m_BitmapSize; i++) {
*(uint32_t*)BufferPtr =
(CColor::Convert(m_BitmapData[i].Blue, 8, BitCountBlue) << BitPosBlue) |
(CColor::Convert(m_BitmapData[i].Green, 8, BitCountGreen) << BitPosGreen) |
(CColor::Convert(m_BitmapData[i].Red, 8, BitCountRed) << BitPosRed) |
(CColor::Convert(m_BitmapData[i].Alpha, 8, BitCountAlpha) << BitPosAlpha);
if (IncludePadding) {
j++;
if (j >= w) {
for (unsigned int k = 0; k < LineWidth - dataBytesPerLine; k++) {
BufferPtr += (BitCount >> 3);
}
j = 0;
}
}
BufferPtr += (BitCount >> 3);
}
Size -= sizeof(unsigned int);
return Result;
}
/* See GetBits().
* It creates a corresponding color table (palette) which have to be destroyed by the user after usage.
*
* Supported Bit depths are: 4, 8
*
* Todo: Optimize, use optimized palette, do ditehring (see my dithering class), support padding for 4 bit bitmaps
*/
bool GetBitsWithPalette(void* Buffer, unsigned int &Size, unsigned int BitCount, BGRA* &Palette, unsigned int &PaletteSize, bool OptimalPalette = false, bool IncludePadding = true) {
bool Result = false;
if (BitCount > 16) {
return false;
}
unsigned int w = GetWidth();
unsigned int dataBytesPerLine = (w * BitCount + 7) / 8;
unsigned int LineWidth = (dataBytesPerLine + 3) & ~3;
if (Size == 0 || Buffer == 0) {
Size = (LineWidth * GetHeight() * BitCount) / 8;
return true;
}
if (OptimalPalette) {
PaletteSize = 0;
// Not implemented
}
else {
if (BitCount == 1) {
PaletteSize = 2;
// Not implemented: Who need that?
}
else if (BitCount == 4) { // 2:2:1
PaletteSize = 16;
Palette = new BGRA[PaletteSize];
for (int r = 0; r < 4; r++) {
for (int g = 0; g < 2; g++) {
for (int b = 0; b < 2; b++) {
Palette[r | g << 2 | b << 3].Red = r ? (r << 6) | 0x3f : 0;
Palette[r | g << 2 | b << 3].Green = g ? (g << 7) | 0x7f : 0;
Palette[r | g << 2 | b << 3].Blue = b ? (b << 7) | 0x7f : 0;
Palette[r | g << 2 | b << 3].Alpha = 0xff;
}
}
}
}
else if (BitCount == 8) { // 3:3:2
PaletteSize = 256;
Palette = new BGRA[PaletteSize];
for (int r = 0; r < 8; r++) {
for (int g = 0; g < 8; g++) {
for (int b = 0; b < 4; b++) {
Palette[r | g << 3 | b << 6].Red = r ? (r << 5) | 0x1f : 0;
Palette[r | g << 3 | b << 6].Green = g ? (g << 5) | 0x1f : 0;
Palette[r | g << 3 | b << 6].Blue = b ? (b << 6) | 0x3f : 0;
Palette[r | g << 3 | b << 6].Alpha = 0xff;
}
}
}
}
else if (BitCount == 16) { // 5:5:5
// Not implemented
}
}
unsigned int j = 0;
uint8_t* BufferPtr = (uint8_t*)Buffer;
for (unsigned int i = 0; i < m_BitmapSize; i++) {
if (BitCount == 1) {
// Not implemented: Who needs that?
}
else if (BitCount == 4) {
*BufferPtr = ((m_BitmapData[i].Red >> 6) | (m_BitmapData[i].Green >> 7) << 2 | (m_BitmapData[i].Blue >> 7) << 3) << 4;
i++;
*BufferPtr |= (m_BitmapData[i].Red >> 6) | (m_BitmapData[i].Green >> 7) << 2 | (m_BitmapData[i].Blue >> 7) << 3;
}
else if (BitCount == 8) {
*BufferPtr = (m_BitmapData[i].Red >> 5) | (m_BitmapData[i].Green >> 5) << 3 | (m_BitmapData[i].Blue >> 5) << 6;
}
else if (BitCount == 16) {
// Not implemented
}
if (IncludePadding) {
j++;
if (j >= w) {
for (unsigned int k = 0; k < (LineWidth - dataBytesPerLine); k++) {
BufferPtr += BitCount / 8;
}
j = 0;
}
}
BufferPtr++;
}
Result = true;
return Result;
}
/* Set Bitmap Bits. Will be converted to RGBA internally */
bool SetBits(void* Buffer, unsigned int Width, unsigned int Height, unsigned int RedMask, unsigned int GreenMask, unsigned int BlueMask, unsigned int AlphaMask = 0) {
if (Buffer == 0) {
return false;
}
uint8_t *BufferPtr = (uint8_t*)Buffer;
Dispose();
m_BitmapHeader.Width = Width;
m_BitmapHeader.Height = Height;
m_BitmapHeader.BitCount = 32;
m_BitmapHeader.Compression = 3;
m_BitmapSize = GetWidth() * GetHeight();
m_BitmapData = new RGBA[m_BitmapSize];
/* Find bit count by masks (rounded to next 8 bit boundary) */
unsigned int BitCount = (CColor::BitCountByMask(RedMask | GreenMask | BlueMask | AlphaMask) + 7) & ~7;
uint32_t BitCountRed = CColor::BitCountByMask(RedMask);
uint32_t BitCountGreen = CColor::BitCountByMask(GreenMask);
uint32_t BitCountBlue = CColor::BitCountByMask(BlueMask);
uint32_t BitCountAlpha = CColor::BitCountByMask(AlphaMask);
for (unsigned int i = 0; i < m_BitmapSize; i++) {
unsigned int Color = 0;
if (BitCount <= 8) {
Color = *((uint8_t*)BufferPtr);
BufferPtr += 1;
}
else if (BitCount <= 16) {
Color = *((uint16_t*)BufferPtr);
BufferPtr += 2;
}
else if (BitCount <= 24) {
Color = *((uint32_t*)BufferPtr);
BufferPtr += 3;
}
else if (BitCount <= 32) {
Color = *((uint32_t*)BufferPtr);
BufferPtr += 4;
}
else {
/* unsupported */
BufferPtr += 1;
}
m_BitmapData[i].Alpha = CColor::Convert(CColor::ComponentByMask(Color, AlphaMask), BitCountAlpha, 8);
m_BitmapData[i].Red = CColor::Convert(CColor::ComponentByMask(Color, RedMask), BitCountRed, 8);
m_BitmapData[i].Green = CColor::Convert(CColor::ComponentByMask(Color, GreenMask), BitCountGreen, 8);
m_BitmapData[i].Blue = CColor::Convert(CColor::ComponentByMask(Color, BlueMask), BitCountBlue, 8);
}
return true;
}
};
#endif
Shader Source Code:
Fragment Shader: "shader.fs"
#version 450 core
#define MAX_MATERIALS 1000
#define MAX_POINTLIGHTS 10
#define MAX_DIRECTIONALLIGHTS 10
#define MAX_SPOTLIGHTS 10
in VS_FS_INTERFACE {
vec3 position;
vec2 texcoord;
vec3 normal;
flat int materialindex;
flat int objectID;
} vs_out;
// here the definition has vec4s instead of vec3
// thats because some drivers get the std140 layout for vec3s wrong
struct Material {
vec4 Ka, Kd, Ks;
float Ns;
float d;
int map_Kd;
};
// has constant intensity
struct DirectionalLight {
vec4 Direction;
vec4 Intensity;
};
// has distance-dependent intensity
struct PointLight {
vec4 Position;
vec4 Intensity;
float AttenuationLinear;
};
// has angle-dependent intensity
struct SpotLight {
vec4 Position;
vec4 Direction;
vec4 Intensity;
int Exponent;
};
layout (std140, binding = 1) uniform MaterialBlock
{
Material Materials[MAX_MATERIALS];
};
layout (std140, binding = 2) uniform DirectionalLightBlock
{
DirectionalLight DirectionalLights[MAX_DIRECTIONALLIGHTS];
};
layout (std140, binding = 3) uniform PointLightBlock
{
PointLight PointLights[MAX_POINTLIGHTS];
};
layout (std140, binding = 4) uniform SpotLightBlock
{
SpotLight SpotLights[MAX_SPOTLIGHTS];
};
uniform vec3 CameraPosition = vec3(0, 0, 0);
uniform vec3 Ambient = vec3(0, 0, 0);
uniform int DirectionalLightCount = 0;
uniform int PointLightCount = 0;
uniform int SpotLightCount = 0;
uniform sampler2DArray textureKd;
layout (location = 0) out vec4 Fragment0;
layout (location = 1) out int Fragment1;
void ProcessDirectionalLights( inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera);
void ProcessPointLights( inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera);
void ProcessSpotLights( inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera);
void main(void)
{
// total intensities
vec3 TotalDiffuse = vec3(0, 0, 0);
vec3 TotalSpecular = vec3(0, 0, 0);
vec3 SurfaceNormal = normalize(vs_out.normal);
vec3 SurfaceToCamera = normalize(CameraPosition - vs_out.position);
ProcessDirectionalLights( TotalDiffuse, TotalSpecular, SurfaceNormal, SurfaceToCamera);
ProcessPointLights( TotalDiffuse, TotalSpecular, SurfaceNormal, SurfaceToCamera);
ProcessSpotLights( TotalDiffuse, TotalSpecular, SurfaceNormal, SurfaceToCamera);
vec3 Ka = Materials[vs_out.materialindex].Ka.rgb;
vec3 Kd = Materials[vs_out.materialindex].Kd.rgb * texture(textureKd, vec3(vs_out.texcoord, Materials[vs_out.materialindex].map_Kd)).rgb;
vec3 Ks = Materials[vs_out.materialindex].Ks.rgb;
vec3 Intensity = Ambient * Ka + TotalDiffuse * Kd + TotalSpecular * Ks;
float d = Materials[vs_out.materialindex].d;
Fragment0 = vec4(Intensity, d);
Fragment1 = vs_out.objectID;
}
void ProcessDirectionalLights(inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera)
{
float Ns = Materials[vs_out.materialindex].Ns;
for (int i = 0; i < DirectionalLightCount; i++)
{
// for specular:
vec3 ReflectedLight = normalize(reflect(DirectionalLights[i].Direction.xyz, SurfaceNormal));
Diffuse += DirectionalLights[i].Intensity.rgb * max(0, dot(SurfaceNormal, -normalize(DirectionalLights[i].Direction.xyz)));
Specular += DirectionalLights[i].Intensity.rgb * max(0, pow(dot(ReflectedLight, SurfaceToCamera), max(1, Ns)));
}
}
void ProcessPointLights(inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera)
{
float Ns = Materials[vs_out.materialindex].Ns;
for (int i = 0; i < PointLightCount; i++)
{
// for diffuse:
vec3 SurfaceToLight = PointLights[i].Position.xyz - vs_out.position;
float DistanceLightSurface = length(SurfaceToLight);
SurfaceToLight /= DistanceLightSurface;
float Attenuation = 1.0f + PointLights[i].AttenuationLinear * DistanceLightSurface;
// for specular:
vec3 ReflectedLight = normalize(reflect(-SurfaceToLight, SurfaceNormal));
Diffuse += PointLights[i].Intensity.rgb * max(0, dot(SurfaceNormal, SurfaceToLight)) / Attenuation;
Specular += PointLights[i].Intensity.rgb * max(0, pow(dot(ReflectedLight, SurfaceToCamera), max(1, Ns))) / Attenuation;
}
}
void ProcessSpotLights(inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera)
{
float Ns = Materials[vs_out.materialindex].Ns;
for (int i = 0; i < SpotLightCount; i++)
{
// for diffuse:
vec3 SurfaceToLight = normalize(SpotLights[i].Position.xyz - vs_out.position);
float CosAngle = dot(-SurfaceToLight, normalize(SpotLights[i].Direction.xyz));
float Multiplier = pow(max(0, CosAngle), max(1, SpotLights[i].Exponent));
// for specular:
vec3 ReflectedLight = normalize(reflect(-SurfaceToLight, SurfaceNormal));
Diffuse += SpotLights[i].Intensity.rgb * max(0, dot(SurfaceNormal, SurfaceToLight)) * Multiplier;
Specular += SpotLights[i].Intensity.rgb * max(0, pow(dot(ReflectedLight, SurfaceToCamera), max(1, Ns))) * Multiplier;
}
}