5.3 Material + Texture

In case the texture is missing, or the material applied uses no textures, we'll get a completely "black" quad. To use instead the diffuse value of the material, we "merge" the texture value together with the material value by multiblying them:

vec3 Kd = material_Kd * texture(map_Kd, fs_in.texcoord).rgb;

In case there is a diffuse texture available, we replace the material's diffuse value with vec3(1, 1, 1), so that the result of the merging will be the texture's value. In case there is no texture, or the texture couldnt be loaded for any reason, we have to provide a texture that is completely "white" (which is equal to vec3(1, 1, 1) ), so that the result of the merging will be the material's value.

Note: the completely "white" texture will be called "NULL_TEXTURE" and is of size 1x1 (doesnt have to be larger).

Download

Complete Source Code:

Graphics.cpp

#include "Graphics.h"

#include <glm/glm.hpp>

#include <glm/gtc/type_ptr.hpp>

#include <glm/gtc/matrix_transform.hpp>

#include <glm/gtx/transform.hpp>

#include <SOIL.h>

#include <iostream>

#include <vector>

#include <map>

#include "Shader.h"

#include "DrawCommands.h"

#include "Mesh.h"

#include "BufferData.h"

#include "Material.h"

#define NULL_TEXTURE_NAME "NULL_TEXTURE"

using namespace std;

using namespace glm;

struct Vertex

{

vec3 Position;

vec3 Normal;

vec2 TexCoord;

};

uvec2 framebuffersize(0, 0);

GLuint program = 0;

GLuint vertexshader = 0;

GLuint fragmentshader = 0;

GLuint vertexarray = 0;

GLuint vertexbuffer = 0;

GLuint elementbuffer = 0;

DrawElementsBaseVertex drawelementsbasevertex_mesh;

/* collects multiple meshes, returns draw calls */

IndexedBufferData<Vertex> meshdata;

Mesh<Vertex> mesh;

Material material;

/* contains all GL texture objects */

map<string, GLuint> texture_map;

GLuint GetTexture(const string& filename)

{

/* check if texture already exists ... */

if (texture_map.find(filename) != texture_map.end())

return texture_map[filename];

/* check if null texture is requested */

if (filename == NULL_TEXTURE_NAME)

{

/* is wasnt found in the texture_map, so create it */

unsigned char null_texture_data[] = { 0xFF, 0xFF, 0xFF, 0xFF };

GLuint texture = 0;

glGenTextures(1, &texture);

glBindTexture(GL_TEXTURE_2D, texture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, null_texture_data);

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

glBindTexture(GL_TEXTURE_2D, 0);

texture_map[filename] = texture;

return texture;

}

/* load image */

int texture_width = 0;

int texture_height = 0;

int texture_channels = 0;

unsigned char* 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 " << filename << endl;

return GetTexture(NULL_TEXTURE_NAME);

}

/* generate new texture */

GLuint texture = 0;

glGenTextures(1, &texture);

glBindTexture(GL_TEXTURE_2D, texture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texture_width, texture_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_data);

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

glBindTexture(GL_TEXTURE_2D, 0);

/* release texture memory */

SOIL_free_image_data(texture_data);

/* store GL texture somewhere */

texture_map[filename] = texture;

return texture;

}

bool Graphics::Initialize(unsigned int width, unsigned int height)

{

/* set view port */

Resize(width, height);

/* general settings */

glClearColor(0.3f, 0.3f, 0.3f, 0.0f); /* background gray */

glEnable(GL_DEPTH_TEST);

/* create all objects */

program = glCreateProgram();

vertexshader = glCreateShader(GL_VERTEX_SHADER);

fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);

glGenVertexArrays(1, &vertexarray);

glGenBuffers(1, &vertexbuffer);

glGenBuffers(1, &elementbuffer);

/* shader source */

string vertexshader_source(

"#version 450 core\n"

"layout (location = 0) in vec3 in_position;"

"layout (location = 1) in vec3 in_normal;"

"layout (location = 2) in vec2 in_texcoord;"

"layout (location = 0) uniform mat4 Model = mat4(1);"

"layout (location = 4) uniform mat4 View = mat4(1);"

"layout (location = 8) uniform mat4 Projection = mat4(1);"

"out VS_FS {"

"smooth vec3 position;"

"smooth vec3 normal;"

"smooth vec2 texcoord;"

"} vs_out;"

"void main() {"

"mat4 MVP = Projection * View * Model;"

"gl_Position = MVP * vec4(in_position, 1);"

"vs_out.position = (Model * vec4(in_position, 1)).xyz;"

"vs_out.normal = (Model * vec4(in_normal, 0)).xyz;"

"vs_out.texcoord = in_texcoord;"

"}"

);

string fragmentshader_source(

"#version 450 core\n"

"layout (location = 12) uniform vec3 material_Kd = vec3(0, 0, 0);"

"layout (location = 13) uniform vec3 material_Ks = vec3(0, 0, 0);"

"layout (location = 14) uniform float material_Ns = 1.0f;"

"layout (binding = 1) uniform sampler2D map_Kd;"

"in VS_FS {"

"smooth vec3 position;"

"smooth vec3 normal;"

"smooth vec2 texcoord;"

"} fs_in;"

"layout (location = 0) out vec4 out_color;"

"void main() {"

"vec3 Kd = material_Kd * texture(map_Kd, fs_in.texcoord).rgb;"

"out_color = vec4(Kd, 1);"

"}"

);

/* compile shaders */

if (!CompileShader(vertexshader, vertexshader_source))

return false;

if (!CompileShader(fragmentshader, fragmentshader_source))

return false;

/* link program */

if (!LinkProgram(program, { vertexshader, fragmentshader }))

return false;

/* setup vertexarray */

glBindVertexArray(vertexarray);

glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, Position)));

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, Normal)));

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, TexCoord)));

glBindBuffer(GL_ARRAY_BUFFER, 0);

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glEnableVertexAttribArray(2);

/* NOTE: the elementbuffer is also part of the vertex array */

/* while the VAO is bound, bind the element buffer */

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);

glBindVertexArray(0);

/* anf after VAO is setup and unbound, savely unbind the elementbuffer again */

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

/* in OpenGL 4.5, there is a more "modern" way to do that */

/* glVertexArrayElementBuffer(vertexarray, elementbuffer); */

/* ... and another way to specify vertexbuffer: */

/* https://www.khronos.org/opengl/wiki/Vertex_Specification#Separate_attribute_format */

/* setup material */

material.Kd = vec3(0.3f, 0.4f, 0.8f);

material.Ks = vec3(1, 1, 1);

material.Ns = 1.0f;

material.map_Kd = GetTexture("malik_grizzer_shadow.jpg");

/* note: to make only the texture visible replace material value with vec3(1, 1, 1) */

if (material.map_Kd != GetTexture(NULL_TEXTURE_NAME))

material.Kd = vec3(1, 1, 1);

/* setup mesh */

mesh.Name = "triangle"; /* not really needed ... however */

mesh.Vertices.push_back({ { 0, 0, 0}, { 0, 0, 1 }, { 0, 1 } });

mesh.Vertices.push_back({ { 1, 0, 0}, { 0, 0, 1 }, { 1, 1 } });

mesh.Vertices.push_back({ { 1, 1, 0 },{ 0, 0, 1 }, { 1, 0 } });

mesh.Vertices.push_back({ { 0, 1, 0 },{ 0, 0, 1 }, { 0, 0 } });

/* first triangle */

mesh.Faces.push_back({});

mesh.Faces.back().VertexIndexA = 0;

mesh.Faces.back().VertexIndexB = 1;

mesh.Faces.back().VertexIndexC = 2;

/* second triangle */

mesh.Faces.push_back({});

mesh.Faces.back().VertexIndexA = 0;

mesh.Faces.back().VertexIndexB = 2;

mesh.Faces.back().VertexIndexC = 3;

/* the parameters for a "glDrawElements(...)" call */

drawelementsbasevertex_mesh = meshdata.AddMesh(mesh, GL_TRIANGLES);

/* get mesh data */

vector<Vertex> vertices = meshdata.Vertices();

vector<GLuint> indices = meshdata.Indices();

/* setup vertexbuffer */

glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);

glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vertices.size(), vertices.data(), GL_STATIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER, 0);

/* setup elementbuffer */

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices.size(), indices.data(), GL_STATIC_DRAW);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

/* successfully initialized */

return true;

}

void Graphics::CleanUp()

{

/* destroy all objects */

glDeleteProgram(program);

glDeleteShader(vertexshader);

glDeleteShader(fragmentshader);

glDeleteVertexArrays(1, &vertexarray);

glDeleteBuffers(1, &vertexbuffer);

glDeleteBuffers(1, &elementbuffer);

/* delete all GL textures */

for (auto& texture_pair : texture_map)

glDeleteTextures(1, &texture_pair.second);

}

void Graphics::Render(const Scene& scene)

{

float aspectratio= (float)framebuffersize.x / framebuffersize.y;

mat4 View = scene.camera.View();

mat4 Projection = scene.camera.Projection(aspectratio);

/* view and projection matrix */

glProgramUniformMatrix4fv(program, 4, 1, GL_FALSE, value_ptr(View));

glProgramUniformMatrix4fv(program, 8, 1, GL_FALSE, value_ptr(Projection));

/* clear framebuffer */

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

/* draw triangles */

glUseProgram(program);

glBindVertexArray(vertexarray);

/* apply material */

glUniform3fv(12, 1, value_ptr(material.Kd));

glUniform3fv(13, 1, value_ptr(material.Ks));

glUniform1f(14, material.Ns);

/* apply texture */

/* map_Kd ==> texture unit 1 (see "binding" in fragment shader) */

glActiveTexture(GL_TEXTURE0 + 1);

glBindTexture(GL_TEXTURE_2D, material.map_Kd);

glActiveTexture(GL_TEXTURE0);

for (auto& object : scene.triangles)

{

mat4 Model = object.Transformation();

glUniformMatrix4fv(0, 1, GL_FALSE, value_ptr(Model));

/* render mesh */

Draw(drawelementsbasevertex_mesh);

}

glBindVertexArray(0);

glUseProgram(0);

CheckForGLError();

}

void Graphics::Resize(unsigned int width, unsigned int height)

{

framebuffersize = uvec2(width, height);

glViewport(0, 0, width, height);

}

GLContextInfo Graphics::GetContextInfos()

{

GLContextInfo infos;

glGetIntegerv(GL_MAJOR_VERSION, &infos.Version.Major);

glGetIntegerv(GL_MINOR_VERSION, &infos.Version.Minor);

infos.Version.Driver = (const char*)glGetString(GL_VERSION);

infos.Vendor = (const char*)glGetString(GL_VENDOR);

infos.Renderer = (const char*)glGetString(GL_RENDERER);

infos.Version.ShadingLanguage = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);

GLint numberofextensions = 0;

glGetIntegerv(GL_NUM_EXTENSIONS, &numberofextensions);

for (int i = 0; i < numberofextensions; i++)

infos.SupportedExtensions.push_back((const char*)glGetStringi(GL_EXTENSIONS, i));

GLint numberofsupportedglslversions = 0;

glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &numberofsupportedglslversions);

for (int i = 0; i < numberofsupportedglslversions; i++)

infos.SupportedGLSLVersions.push_back((const char*)glGetStringi(GL_SHADING_LANGUAGE_VERSION, i));

return infos;

}

void Graphics::CheckForGLError()

{

for (GLenum error; (error = glGetError()) != GL_NO_ERROR;)

{

cout << "OpenGL Error: \t";

if (error == GL_INVALID_ENUM)

cout << "GL_INVALID_ENUM";

if (error == GL_INVALID_VALUE)

cout << "GL_INVALID_VALUE";

if (error == GL_INVALID_OPERATION)

cout << "GL_INVALID_OPERATION";

if (error == GL_STACK_OVERFLOW)

cout << "GL_STACK_OVERFLOW";

if (error == GL_STACK_UNDERFLOW)

cout << "GL_STACK_UNDERFLOW";

if (error == GL_OUT_OF_MEMORY)

cout << "GL_OUT_OF_MEMORY";

if (error == GL_INVALID_FRAMEBUFFER_OPERATION)

cout << "GL_INVALID_FRAMEBUFFER_OPERATION";

if (error == GL_CONTEXT_LOST)

cout << "GL_CONTEXT_LOST";

cout << (char)7 << endl; /*play sound*/

cin.get();

}

}