Template PBR lighting tut with folder re-structure to fit PBR tuts.

This commit is contained in:
Joey de Vries
2016-12-12 21:10:58 +01:00
parent b2657d7e1c
commit 0a46f53608
12 changed files with 1343 additions and 2 deletions

View File

@@ -58,7 +58,8 @@ set(CHAPTERS
3.model_loading
4.advanced_opengl
5.advanced_lighting
6.in_practice
6.pbr
7.in_practice
)
set(1.getting_started
@@ -111,7 +112,14 @@ set(5.advanced_lighting
9.ssao
)
set(6.in_practice
set(6.pbr
1.1.lighting
1.2.lighting_textured
# 2.1.ibl_irradiance
# 2.2.ibl_specular
)
set(7.in_practice
1.debugging
# 2.text_rendering
)

View File

@@ -0,0 +1,11 @@
// fragment shader
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D fboAttachment;
void main()
{
FragColor = texture(fboAttachment, TexCoords);
}

View File

@@ -0,0 +1,13 @@
// vertex shader
#version 330 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 texCoords;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(position, 0.0f, 1.0f);
TexCoords = texCoords;
}

View File

@@ -0,0 +1,473 @@
// Std. Includes
#include <string>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GL includes
#include <learnopengl/shader.h>
#include <learnopengl/camera.h>
#include <learnopengl/model.h>
// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Other Libs
#include <SOIL.h>
#include <learnopengl/filesystem.h>
// Properties
const GLuint SCR_WIDTH = 1280, SCR_HEIGHT = 720;
// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
GLuint loadTexture(GLchar const * path);
void RenderQuad();
void renderSphere();
// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// timing
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
// The MAIN function, from here we start our application and run our Game loop
int main()
{
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_SAMPLES, 32);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed
glfwMakeContextCurrent(window);
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// Options
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
glewInit();
// Define the viewport dimensions
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
// Setup some OpenGL options
glEnable(GL_DEPTH_TEST);
// Setup and compile our shaders
Shader shader("pbr.vs", "pbr.frag");
// set (constant) material properties
shader.Use();
glUniform3f(glGetUniformLocation(shader.Program, "albedo"), 0.5f, 0.0f, 0.0f);
glUniform1f(glGetUniformLocation(shader.Program, "ao"), 1.0f);
// projection setup
glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
// lights
glm::vec3 lightPositions[] = {
glm::vec3(-10.0f, 10.0f, 10.0f),
glm::vec3( 10.0f, 10.0f, 10.0f),
glm::vec3(-10.0f, -10.0f, 10.0f),
glm::vec3( 10.0f, -10.0f, 10.0f),
};
glm::vec3 lightColors[] = {
glm::vec3(5.0f, 5.0f, 5.0f),
glm::vec3(5.0f, 5.0f, 5.0f),
glm::vec3(5.0f, 5.0f, 5.0f),
glm::vec3(5.0f, 5.0f, 5.0f)
};
int nrRows = 7;
int nrColumns = 7;
float spacing = 2.5;
// Game loop
while (!glfwWindowShouldClose(window))
{
// set frame time
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// check and call events
glfwPollEvents();
Do_Movement();
// clear the colorbuffer
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// configure view matrix
shader.Use();
glm::mat4 view = camera.GetViewMatrix();
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
// setup relevant shader uniforms
glUniform3fv(glGetUniformLocation(shader.Program, "camPos"), 1, &camera.Position[0]);
glUniform1f(glGetUniformLocation(shader.Program, "exposure"), 1.0f);
// render rows*column number of spheres with varying metallic/roughness values scaled by rows and columns respectively
glm::mat4 model;
for (int row = 0; row < nrRows; ++row)
{
glUniform1f(glGetUniformLocation(shader.Program, "metallic"), (float)row / (float)nrRows);
for (int col = 0; col < nrColumns; ++col)
{
// we clamp the roughness to 0.025 - 1.0 as perfectly smooth surfaces (roughness of 0.0) tend to look a bit off
// on direct lighting.
glUniform1f(glGetUniformLocation(shader.Program, "roughness"), glm::clamp((float)col / (float)nrColumns, 0.05f, 1.0f));
model = glm::mat4();
model = glm::translate(model, glm::vec3(
(float)(col - (nrColumns / 2)) * spacing,
(float)(row - (nrRows / 2)) * spacing,
0.0f
));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
renderSphere();
}
}
// render light source (simply re-render sphere at light positions)
// this looks a bit off as we use the same shader, but it'll make their positions obvious and
// keeps the codeprint small.
for (unsigned int i = 0; i < sizeof(lightPositions) / sizeof(lightPositions[0]); ++i)
//for (unsigned int i = 0; i < 1; ++i)
{
glUniform3fv(glGetUniformLocation(shader.Program, ("lightPositions[" + std::to_string(i) + "]").c_str()), 1, &lightPositions[i][0]);
glUniform3fv(glGetUniformLocation(shader.Program, ("lightColors[" + std::to_string(i) + "]").c_str()), 1, &lightColors[i][0]);
model = glm::mat4();
model = glm::translate(model, lightPositions[i]);
model = glm::scale(model, glm::vec3(0.5f));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
renderSphere();
}
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
unsigned int sphereVAO = 0;
unsigned int indexCount;
void renderSphere()
{
if (sphereVAO == 0)
{
glGenVertexArrays(1, &sphereVAO);
unsigned int vbo, ebo;
glGenBuffers(1, &vbo);
glGenBuffers(1, &ebo);
std::vector<glm::vec3> positions;
std::vector<glm::vec2> uv;
std::vector<glm::vec3> normals;
std::vector<glm::vec3> tangents;
std::vector<glm::vec3> bitangents;
std::vector<unsigned int> indices;
const unsigned int X_SEGMENTS = 64;
const unsigned int Y_SEGMENTS = 64;
const float PI = 3.14159265359;
for (unsigned int y = 0; y <= Y_SEGMENTS; ++y)
{
for (unsigned int x = 0; x <= X_SEGMENTS; ++x)
{
float xSegment = (float)x / (float)X_SEGMENTS;
float ySegment = (float)y / (float)Y_SEGMENTS;
float xPos = std::cos(xSegment * 2.0f * PI) * std::sin(ySegment * PI); // NOTE(Joey): TAU is 2PI
float yPos = std::cos(ySegment * PI);
float zPos = std::sin(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
positions.push_back(glm::vec3(xPos, yPos, zPos));
uv.push_back(glm::vec2(xSegment, ySegment));
normals.push_back(glm::vec3(xPos, yPos, zPos));
}
}
bool oddRow = false;
for (int y = 0; y < Y_SEGMENTS; ++y)
{
if (!oddRow) // NOTE(Joey): even rows: y == 0, y == 2; and so on
{
for (int x = 0; x <= X_SEGMENTS; ++x)
{
indices.push_back(y * (X_SEGMENTS + 1) + x);
indices.push_back((y + 1) * (X_SEGMENTS + 1) + x);
}
}
else
{
for (int x = X_SEGMENTS; x >= 0; --x)
{
indices.push_back((y + 1) * (X_SEGMENTS + 1) + x);
indices.push_back(y * (X_SEGMENTS + 1) + x);
}
}
oddRow = !oddRow;
}
indexCount = indices.size();
std::vector<float> data;
for (int i = 0; i < positions.size(); ++i)
{
data.push_back(positions[i].x);
data.push_back(positions[i].y);
data.push_back(positions[i].z);
if (uv.size() > 0)
{
data.push_back(uv[i].x);
data.push_back(uv[i].y);
}
if (normals.size() > 0)
{
data.push_back(normals[i].x);
data.push_back(normals[i].y);
data.push_back(normals[i].z);
}
if (tangents.size() > 0)
{
data.push_back(tangents[i].x);
data.push_back(tangents[i].y);
data.push_back(tangents[i].z);
}
if (bitangents.size() > 0)
{
data.push_back(bitangents[i].x);
data.push_back(bitangents[i].y);
data.push_back(bitangents[i].z);
}
}
glBindVertexArray(sphereVAO);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(float), &data[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
//float stride = (3 + 2 + 3 + 3 + 3) * sizeof(float);
float stride = (3 + 2 + 3) * sizeof(float);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(3 * sizeof(float)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(5 * sizeof(float)));
//glEnableVertexAttribArray(3);
/* glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(8 * sizeof(float)));
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(11 * sizeof(float)));*/
}
glBindVertexArray(sphereVAO);
glDrawElements(GL_TRIANGLE_STRIP, indexCount, GL_UNSIGNED_INT, 0);
}
// RenderQuad() Renders a 1x1 quad in NDC
GLuint quadVAO = 0;
GLuint quadVBO;
void RenderQuad()
{
if (quadVAO == 0)
{
// positions
glm::vec3 pos1(-1.0, 1.0, 0.0);
glm::vec3 pos2(-1.0, -1.0, 0.0);
glm::vec3 pos3(1.0, -1.0, 0.0);
glm::vec3 pos4(1.0, 1.0, 0.0);
// texture coordinates
glm::vec2 uv1(0.0, 1.0);
glm::vec2 uv2(0.0, 0.0);
glm::vec2 uv3(1.0, 0.0);
glm::vec2 uv4(1.0, 1.0);
// normal vector
glm::vec3 nm(0.0, 0.0, 1.0);
// calculate tangent/bitangent vectors of both triangles
glm::vec3 tangent1, bitangent1;
glm::vec3 tangent2, bitangent2;
// - triangle 1
glm::vec3 edge1 = pos2 - pos1;
glm::vec3 edge2 = pos3 - pos1;
glm::vec2 deltaUV1 = uv2 - uv1;
glm::vec2 deltaUV2 = uv3 - uv1;
GLfloat f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
tangent1 = glm::normalize(tangent1);
bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
bitangent1 = glm::normalize(bitangent1);
// - triangle 2
edge1 = pos3 - pos1;
edge2 = pos4 - pos1;
deltaUV1 = uv3 - uv1;
deltaUV2 = uv4 - uv1;
f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
tangent2.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent2.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent2.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
tangent2 = glm::normalize(tangent2);
bitangent2.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent2.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent2.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
bitangent2 = glm::normalize(bitangent2);
GLfloat quadVertices[] = {
// Positions // normal // TexCoords // Tangent // Bitangent
pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos2.x, pos2.y, pos2.z, nm.x, nm.y, nm.z, uv2.x, uv2.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
pos4.x, pos4.y, pos4.z, nm.x, nm.y, nm.z, uv4.x, uv4.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z
};
// Setup plane VAO
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(8 * sizeof(GLfloat)));
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(11 * sizeof(GLfloat)));
}
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
// This function loads a texture from file. Note: texture loading functions like these are usually
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio).
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(GLchar const * path)
{
//Generate texture ID and load texture data
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB);
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
// Parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureID;
}
#pragma region "User input"
bool keys[1024];
bool keysPressed[1024];
// Moves/alters the camera positions based on user input
void Do_Movement()
{
// Camera controls
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, deltaTime);
}
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key >= 0 && key <= 1024)
{
if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
{
keys[key] = false;
keysPressed[key] = false;
}
}
}
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
// Moves/alters the camera positions based on user input
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
#pragma endregion

View File

@@ -0,0 +1,150 @@
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
in vec3 WorldPos;
in vec3 Normal;
// material parameters
uniform vec3 albedo;
uniform float metallic;
uniform float roughness;
uniform float ao;
// lights
uniform vec3 lightPositions[4];
uniform vec3 lightColors[4];
uniform vec3 camPos;
uniform float exposure;
const float PI = 3.14159265359;
vec3 getNormal(vec3 worldNormal, vec3 tangentNormal)
{
vec3 Q1 = dFdx(WorldPos);
vec3 Q2 = dFdy(WorldPos);
vec2 st1 = dFdx(TexCoords);
vec2 st2 = dFdy(TexCoords);
vec3 normal = normalize(worldNormal);
vec3 tangent = normalize(Q1*st2.t - Q2*st1.t);
vec3 binormal = -normalize(cross(normal, tangent));
mat3 TBN = mat3(tangent, binormal, normal);
return normalize(TBN * tangentNormal);
}
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness*roughness;
float a2 = a*a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH*NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
// ----------------------------------------------------------------------------
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}
void main()
{
vec3 N = normalize(Normal);
vec3 V = normalize(camPos - WorldPos);
vec3 R = reflect(-V, N);
// NOTE(Joey): calculate color/reflectance at normal incidence
// NOTE(Joey): if dia-electric (like plastic) use F0 as 0.04 and
// if it's a metal, use their albedo color as F0 (metallic workflow)
vec3 F0 = vec3(0.04); // NOTE(Joey): base reflectance at incident angle for non-metallic (dia-conductor) surfaces
F0 = mix(F0, albedo, metallic);
// NOTE(Joey): calculate reflectance w/ (modified for roughness) Fresnel
vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);
// NOTE(Joey): kS is equal to Fresnel
vec3 kS = F;
// NOTE(Joey): for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light) so to preserve this
// relationship the diffuse component (kD) equals 1.0 - kS.
vec3 kD = vec3(1.0) - kS;
// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals have
// no diffuse light).
kD *= 1.0 - metallic;
// first do ambient lighting (note that the next IBL tutorial will replace the ambient
// lighting with environment lighting).
vec3 ambient = vec3(0.01) * albedo * ao;
// for every light, calculate their contribution to the reflectance equation
vec3 Lo = vec3(0.0);
for(int i = 0; i < 4; ++i)
{
vec3 L = normalize(lightPositions[i] - WorldPos);
vec3 H = normalize(V + L);
float distance = length(lightPositions[i] - WorldPos);
float attenuation = 1.0 / distance * distance;
vec3 radiance = lightColors[i] * attenuation;
// NDF
float ndf = DistributionGGX(N, H, roughness);
// Geometry
float g = GeometrySmith(N, V, L, roughness);
// cook-torrance brdf
vec3 nominator = ndf * g * F;
float denominator = 4 * max(dot(V, N), 0.0) * max(dot(L, N), 0.0) + 0.001; // 0.001 to prevent divide by zero.
vec3 brdf = nominator / denominator;
// NOTE(Joey): scale light by NdotL
float NdotL = max(dot(N, L), 0.0);
// NOTE(Joey): reflectance equation
Lo += (kD * albedo / PI + kS * brdf) * radiance * NdotL;
}
vec3 color = ambient + Lo;
// NOTE(Joey): HDR tonemapping
// color = vec3(1.0) - exp(-color * exposure);
color = color / (color + vec3(1.0));
// NOTE(Joey): gamma correct
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 1.0);
}

View File

@@ -0,0 +1,21 @@
#version 330 core
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoords;
layout (location = 2) in vec3 normal;
out vec2 TexCoords;
out vec3 WorldPos;
out vec3 Normal;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
void main()
{
TexCoords = texCoords;
WorldPos = vec3(model * vec4(pos, 1.0f));
Normal = mat3(model) * normal;
gl_Position = projection * view * vec4(WorldPos, 1.0);
}

View File

@@ -0,0 +1,487 @@
// Std. Includes
#include <string>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GL includes
#include <learnopengl/shader.h>
#include <learnopengl/camera.h>
#include <learnopengl/model.h>
// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Other Libs
#include <SOIL.h>
#include <learnopengl/filesystem.h>
// Properties
const GLuint SCR_WIDTH = 1280, SCR_HEIGHT = 720;
// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
GLuint loadTexture(GLchar const * path);
void RenderQuad();
void renderSphere();
// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// timing
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
// The MAIN function, from here we start our application and run our Game loop
int main()
{
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_SAMPLES, 32);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed
glfwMakeContextCurrent(window);
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// Options
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
glewInit();
// Define the viewport dimensions
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
// Setup some OpenGL options
glEnable(GL_DEPTH_TEST);
// load material textures
GLuint albedo = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/albedo.png").c_str());
GLuint normal = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/normal.png").c_str());
GLuint metallic = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/metallic.png").c_str());
GLuint roughness = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/roughness.png").c_str());
GLuint ao = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/ao.png").c_str());
// Setup and compile our shaders
Shader shader("pbr.vs", "pbr.frag");
// set material texture uniforms
shader.Use();
glUniform1i(glGetUniformLocation(shader.Program, "albedo"), 0);
glUniform1i(glGetUniformLocation(shader.Program, "normal"), 1);
glUniform1i(glGetUniformLocation(shader.Program, "metallic"), 2);
glUniform1i(glGetUniformLocation(shader.Program, "roughness"), 3);
glUniform1i(glGetUniformLocation(shader.Program, "ao"), 4);
// projection setup
glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
// lights
glm::vec3 lightPositions[] = {
glm::vec3(-10.0f, 10.0f, 10.0f),
glm::vec3( 10.0f, 10.0f, 10.0f),
glm::vec3(-10.0f, -10.0f, 10.0f),
glm::vec3( 10.0f, -10.0f, 10.0f),
};
glm::vec3 lightColors[] = {
glm::vec3(2.0f, 2.0f, 2.0f),
glm::vec3(2.0f, 2.0f, 2.0f),
glm::vec3(2.0f, 2.0f, 2.0f),
glm::vec3(2.0f, 2.0f, 2.0f)
};
int nrRows = 7;
int nrColumns = 7;
float spacing = 2.5;
// Game loop
while (!glfwWindowShouldClose(window))
{
// set frame time
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// check and call events
glfwPollEvents();
Do_Movement();
// clear the colorbuffer
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// configure view matrix
shader.Use();
glm::mat4 view = camera.GetViewMatrix();
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
// setup relevant shader uniforms
glUniform3fv(glGetUniformLocation(shader.Program, "camPos"), 1, &camera.Position[0]);
// set material
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, albedo);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, normal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, metallic);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, roughness);
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, ao);
// render rows*column number of spheres with varying metallic/roughness values scaled by rows and columns respectively
glm::mat4 model;
for (int row = 0; row < nrRows; ++row)
{
for (int col = 0; col < nrColumns; ++col)
{
model = glm::mat4();
model = glm::translate(model, glm::vec3(
(float)(col - (nrColumns / 2)) * spacing,
(float)(row - (nrRows / 2)) * spacing,
0.0f
));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
renderSphere();
}
}
// render light source (simply re-render sphere at light positions)
// this looks a bit off as we use the same shader, but it'll make their positions obvious and
// keeps the codeprint small.
for (unsigned int i = 0; i < sizeof(lightPositions) / sizeof(lightPositions[0]); ++i)
{
glUniform3fv(glGetUniformLocation(shader.Program, ("lightPositions[" + std::to_string(i) + "]").c_str()), 1, &lightPositions[i][0]);
glUniform3fv(glGetUniformLocation(shader.Program, ("lightColors[" + std::to_string(i) + "]").c_str()), 1, &lightColors[i][0]);
model = glm::mat4();
model = glm::translate(model, lightPositions[i]);
model = glm::scale(model, glm::vec3(0.5f));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
renderSphere();
}
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
unsigned int sphereVAO = 0;
unsigned int indexCount;
void renderSphere()
{
if (sphereVAO == 0)
{
glGenVertexArrays(1, &sphereVAO);
unsigned int vbo, ebo;
glGenBuffers(1, &vbo);
glGenBuffers(1, &ebo);
std::vector<glm::vec3> positions;
std::vector<glm::vec2> uv;
std::vector<glm::vec3> normals;
std::vector<glm::vec3> tangents;
std::vector<glm::vec3> bitangents;
std::vector<unsigned int> indices;
const unsigned int X_SEGMENTS = 64;
const unsigned int Y_SEGMENTS = 64;
const float PI = 3.14159265359;
for (unsigned int y = 0; y <= Y_SEGMENTS; ++y)
{
for (unsigned int x = 0; x <= X_SEGMENTS; ++x)
{
float xSegment = (float)x / (float)X_SEGMENTS;
float ySegment = (float)y / (float)Y_SEGMENTS;
float xPos = std::cos(xSegment * 2.0f * PI) * std::sin(ySegment * PI); // NOTE(Joey): TAU is 2PI
float yPos = std::cos(ySegment * PI);
float zPos = std::sin(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
positions.push_back(glm::vec3(xPos, yPos, zPos));
uv.push_back(glm::vec2(xSegment, ySegment));
normals.push_back(glm::vec3(xPos, yPos, zPos));
}
}
bool oddRow = false;
for (int y = 0; y < Y_SEGMENTS; ++y)
{
if (!oddRow) // NOTE(Joey): even rows: y == 0, y == 2; and so on
{
for (int x = 0; x <= X_SEGMENTS; ++x)
{
indices.push_back(y * (X_SEGMENTS + 1) + x);
indices.push_back((y + 1) * (X_SEGMENTS + 1) + x);
}
}
else
{
for (int x = X_SEGMENTS; x >= 0; --x)
{
indices.push_back((y + 1) * (X_SEGMENTS + 1) + x);
indices.push_back(y * (X_SEGMENTS + 1) + x);
}
}
oddRow = !oddRow;
}
indexCount = indices.size();
std::vector<float> data;
for (int i = 0; i < positions.size(); ++i)
{
data.push_back(positions[i].x);
data.push_back(positions[i].y);
data.push_back(positions[i].z);
if (uv.size() > 0)
{
data.push_back(uv[i].x);
data.push_back(uv[i].y);
}
if (normals.size() > 0)
{
data.push_back(normals[i].x);
data.push_back(normals[i].y);
data.push_back(normals[i].z);
}
if (tangents.size() > 0)
{
data.push_back(tangents[i].x);
data.push_back(tangents[i].y);
data.push_back(tangents[i].z);
}
if (bitangents.size() > 0)
{
data.push_back(bitangents[i].x);
data.push_back(bitangents[i].y);
data.push_back(bitangents[i].z);
}
}
glBindVertexArray(sphereVAO);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(float), &data[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
//float stride = (3 + 2 + 3 + 3 + 3) * sizeof(float);
float stride = (3 + 2 + 3) * sizeof(float);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(3 * sizeof(float)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(5 * sizeof(float)));
//glEnableVertexAttribArray(3);
/* glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(8 * sizeof(float)));
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(11 * sizeof(float)));*/
}
glBindVertexArray(sphereVAO);
glDrawElements(GL_TRIANGLE_STRIP, indexCount, GL_UNSIGNED_INT, 0);
}
// RenderQuad() Renders a 1x1 quad in NDC
GLuint quadVAO = 0;
GLuint quadVBO;
void RenderQuad()
{
if (quadVAO == 0)
{
// positions
glm::vec3 pos1(-1.0, 1.0, 0.0);
glm::vec3 pos2(-1.0, -1.0, 0.0);
glm::vec3 pos3(1.0, -1.0, 0.0);
glm::vec3 pos4(1.0, 1.0, 0.0);
// texture coordinates
glm::vec2 uv1(0.0, 1.0);
glm::vec2 uv2(0.0, 0.0);
glm::vec2 uv3(1.0, 0.0);
glm::vec2 uv4(1.0, 1.0);
// normal vector
glm::vec3 nm(0.0, 0.0, 1.0);
// calculate tangent/bitangent vectors of both triangles
glm::vec3 tangent1, bitangent1;
glm::vec3 tangent2, bitangent2;
// - triangle 1
glm::vec3 edge1 = pos2 - pos1;
glm::vec3 edge2 = pos3 - pos1;
glm::vec2 deltaUV1 = uv2 - uv1;
glm::vec2 deltaUV2 = uv3 - uv1;
GLfloat f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
tangent1 = glm::normalize(tangent1);
bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
bitangent1 = glm::normalize(bitangent1);
// - triangle 2
edge1 = pos3 - pos1;
edge2 = pos4 - pos1;
deltaUV1 = uv3 - uv1;
deltaUV2 = uv4 - uv1;
f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
tangent2.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent2.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent2.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
tangent2 = glm::normalize(tangent2);
bitangent2.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent2.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent2.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
bitangent2 = glm::normalize(bitangent2);
GLfloat quadVertices[] = {
// Positions // normal // TexCoords // Tangent // Bitangent
pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos2.x, pos2.y, pos2.z, nm.x, nm.y, nm.z, uv2.x, uv2.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent1.x, tangent1.y, tangent1.z, bitangent1.x, bitangent1.y, bitangent1.z,
pos1.x, pos1.y, pos1.z, nm.x, nm.y, nm.z, uv1.x, uv1.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
pos3.x, pos3.y, pos3.z, nm.x, nm.y, nm.z, uv3.x, uv3.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z,
pos4.x, pos4.y, pos4.z, nm.x, nm.y, nm.z, uv4.x, uv4.y, tangent2.x, tangent2.y, tangent2.z, bitangent2.x, bitangent2.y, bitangent2.z
};
// Setup plane VAO
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(8 * sizeof(GLfloat)));
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(GLfloat), (GLvoid*)(11 * sizeof(GLfloat)));
}
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
// This function loads a texture from file. Note: texture loading functions like these are usually
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio).
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture(GLchar const * path)
{
//Generate texture ID and load texture data
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB);
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
// Parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureID;
}
#pragma region "User input"
bool keys[1024];
bool keysPressed[1024];
// Moves/alters the camera positions based on user input
void Do_Movement()
{
// Camera controls
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, deltaTime);
}
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key >= 0 && key <= 1024)
{
if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
{
keys[key] = false;
keysPressed[key] = false;
}
}
}
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
// Moves/alters the camera positions based on user input
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
#pragma endregion

View File

@@ -0,0 +1,157 @@
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
in vec3 WorldPos;
in vec3 Normal;
// material parameters
uniform sampler2D albedo;
uniform sampler2D normal;
uniform sampler2D metallic;
uniform sampler2D roughness;
uniform sampler2D ao;
// lights
uniform vec3 lightPositions[4];
uniform vec3 lightColors[4];
uniform vec3 camPos;
uniform float exposure;
const float PI = 3.14159265359;
vec3 getNormal(vec3 worldNormal, vec3 tangentNormal)
{
vec3 Q1 = dFdx(WorldPos);
vec3 Q2 = dFdy(WorldPos);
vec2 st1 = dFdx(TexCoords);
vec2 st2 = dFdy(TexCoords);
vec3 normal = normalize(worldNormal);
vec3 tangent = normalize(Q1*st2.t - Q2*st1.t);
vec3 binormal = -normalize(cross(normal, tangent));
mat3 TBN = mat3(tangent, binormal, normal);
return normalize(TBN * tangentNormal);
}
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness*roughness;
float a2 = a*a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH*NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
// ----------------------------------------------------------------------------
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}
void main()
{
vec3 albedo = pow(texture(albedo, TexCoords).rgb, vec3(2.2));
// vec3 normal = getWorldNormalFromTangentSpace(texture(normal, TexCoords).rgb);
float metallic = texture(metallic, TexCoords).r;
float roughness = texture(roughness, TexCoords).r;
float ao = texture(ao, TexCoords).r;
vec3 N = normalize(Normal);
vec3 V = normalize(camPos - WorldPos);
vec3 R = reflect(-V, N);
// NOTE(Joey): calculate color/reflectance at normal incidence
// NOTE(Joey): if dia-electric (like plastic) use F0 as 0.04 and
// if it's a metal, use their albedo color as F0 (metallic workflow)
vec3 F0 = vec3(0.04); // NOTE(Joey): base reflectance at incident angle for non-metallic (dia-conductor) surfaces
F0 = mix(F0, albedo, metallic);
// NOTE(Joey): calculate reflectance w/ (modified for roughness) Fresnel
vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);
// NOTE(Joey): kS is equal to Fresnel
vec3 kS = F;
// NOTE(Joey): for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light) so to preserve this
// relationship the diffuse component (kD) equals 1.0 - kS.
vec3 kD = vec3(1.0) - kS;
// multiply kD by the inverse metalness such that only non-metals
// have diffuse lighting, or a linear blend if partly metal (pure metals have
// no diffuse light).
kD *= 1.0 - metallic;
// first do ambient lighting (note that the next IBL tutorial will replace the ambient
// lighting with environment lighting).
vec3 ambient = vec3(0.01) * albedo * ao;
// for every light, calculate their contribution to the reflectance equation
vec3 Lo = vec3(0.0);
for(int i = 0; i < 4; ++i)
{
vec3 L = normalize(lightPositions[i] - WorldPos);
vec3 H = normalize(V + L);
float distance = length(lightPositions[i] - WorldPos);
float attenuation = 1.0 / distance * distance;
vec3 radiance = lightColors[i] * attenuation;
// NDF
float ndf = DistributionGGX(N, H, roughness);
// Geometry
float g = GeometrySmith(N, V, L, roughness);
// cook-torrance brdf
vec3 nominator = ndf * g * F;
float denominator = 4 * max(dot(V, N), 0.0) * max(dot(L, N), 0.0) + 0.001; // 0.001 to prevent divide by zero.
vec3 brdf = nominator / denominator;
// NOTE(Joey): scale light by NdotL
float NdotL = max(dot(N, L), 0.0);
// NOTE(Joey): reflectance equation
Lo += (kD * albedo / PI + kS * brdf) * radiance * NdotL;
}
vec3 color = ambient + Lo;
// NOTE(Joey): HDR tonemapping
// color = vec3(1.0) - exp(-color * exposure);
color = color / (color + vec3(1.0));
// NOTE(Joey): gamma correct
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 0.0, 1.0);
}

View File

@@ -0,0 +1,21 @@
#version 330 core
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoords;
layout (location = 2) in vec3 normal;
out vec2 TexCoords;
out vec3 WorldPos;
out vec3 Normal;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
void main()
{
TexCoords = texCoords;
WorldPos = vec3(model * vec4(pos, 1.0f));
Normal = mat3(model) * normal;
gl_Position = projection * view * vec4(WorldPos, 1.0);
}