Adjustments to PBR lighting code.

This commit is contained in:
Joey de Vries
2016-12-17 21:29:22 +01:00
parent 9e2f12aeaa
commit 15e7808d55
10 changed files with 77 additions and 207 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

View File

@@ -155,9 +155,7 @@ int main()
// 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]);
@@ -177,6 +175,7 @@ int main()
return 0;
}
// renders (and builds if necessary) a sphere
unsigned int sphereVAO = 0;
unsigned int indexCount;
void renderSphere()
@@ -205,7 +204,7 @@ void renderSphere()
{
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 xPos = std::cos(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
float yPos = std::cos(ySegment * PI);
float zPos = std::sin(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
@@ -218,7 +217,7 @@ void renderSphere()
bool oddRow = false;
for (int y = 0; y < Y_SEGMENTS; ++y)
{
if (!oddRow) // NOTE(Joey): even rows: y == 0, y == 2; and so on
if (!oddRow) // even rows: y == 0, y == 2; and so on
{
for (int x = 0; x <= X_SEGMENTS; ++x)
{
@@ -272,7 +271,6 @@ void renderSphere()
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);
@@ -280,109 +278,12 @@ void renderSphere()
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.

View File

@@ -18,23 +18,8 @@ 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);
}
const float PI = 3.14159265359;
// ----------------------------------------------------------------------------
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness*roughness;
@@ -48,7 +33,7 @@ float DistributionGGX(vec3 N, vec3 H, float roughness)
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
@@ -59,7 +44,7 @@ float GeometrySchlickGGX(float NdotV, float roughness)
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
@@ -69,7 +54,7 @@ float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
return ggx1 * ggx2;
}
// ----------------------------------------------------------------------------
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
@@ -79,75 +64,65 @@ 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
// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// of 0.04 and if it's a metal, use their albedo color as F0 (metallic workflow)
vec3 F0 = vec3(0.04);
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);
vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); // use modified Fresnel-Schlick approximation to take roughness into account
// NOTE(Joey): kS is equal to Fresnel
// 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.
// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 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).
// 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).
// first do ambient lighting (note that the next IBL tutorial will replace
// this ambient lighting with environment lighting).
vec3 ambient = vec3(0.01) * albedo * ao;
// for every light, calculate their contribution to the reflectance equation
// reflectance equation
vec3 Lo = vec3(0.0);
for(int i = 0; i < 4; ++i)
{
// calculate per-light radiance
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;
// Cook-Torrance BRDF
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
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
// scale light by NdotL
float NdotL = max(dot(N, L), 0.0);
// NOTE(Joey): reflectance equation
// add to outgoing radiance Lo
Lo += (kD * albedo / PI + kS * brdf) * radiance * NdotL;
}
vec3 color = ambient + Lo;
// NOTE(Joey): HDR tonemapping
// color = vec3(1.0) - exp(-color * exposure);
// HDR tonemapping
color = color / (color + vec3(1.0));
// NOTE(Joey): gamma correct
// gamma correct
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 1.0);
}

View File

@@ -144,7 +144,7 @@ int main()
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
// render rows*column number of spheres with material properties defined by textures (they all have the same material properties)
glm::mat4 model;
for (int row = 0; row < nrRows; ++row)
{
@@ -214,7 +214,7 @@ void renderSphere()
{
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 xPos = std::cos(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
float yPos = std::cos(ySegment * PI);
float zPos = std::sin(xSegment * 2.0f * PI) * std::sin(ySegment * PI);
@@ -227,7 +227,7 @@ void renderSphere()
bool oddRow = false;
for (int y = 0; y < Y_SEGMENTS; ++y)
{
if (!oddRow) // NOTE(Joey): even rows: y == 0, y == 2; and so on
if (!oddRow) // even rows: y == 0, y == 2; and so on
{
for (int x = 0; x <= X_SEGMENTS; ++x)
{

View File

@@ -19,9 +19,12 @@ uniform vec3 lightColors[4];
uniform vec3 camPos;
uniform float exposure;
const float PI = 3.14159265359;
// easy trick to get tangent-normals to world-space to keep PBR code simplified.
const float PI = 3.14159265359;
// ----------------------------------------------------------------------------
// Easy trick to get tangent-normals to world-space to keep PBR code simplified.
// Don't worry if you don't get what's going on; you generally want to do normal
// mapping the usual way for performance anways; I do plan make a note of this
// technique somewhere later in the normal mapping tutorial.
vec3 getNormal()
{
vec3 tangentNormal = texture(normalMap, TexCoords).xyz * 2.0 - 1.0;
@@ -38,7 +41,7 @@ vec3 getNormal()
return normalize(TBN * tangentNormal);
}
// ----------------------------------------------------------------------------
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness*roughness;
@@ -52,7 +55,7 @@ float DistributionGGX(vec3 N, vec3 H, float roughness)
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
@@ -63,7 +66,7 @@ float GeometrySchlickGGX(float NdotV, float roughness)
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
@@ -73,7 +76,7 @@ float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
return ggx1 * ggx2;
}
// ----------------------------------------------------------------------------
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
@@ -83,78 +86,70 @@ 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(albedoMap, TexCoords).rgb, vec3(2.2));
float metallic = texture(metallicMap, TexCoords).r;
float metallic = texture(metallicMap, TexCoords).r;
float roughness = texture(roughnessMap, TexCoords).r;
float ao = texture(aoMap, TexCoords).r;
float ao = texture(aoMap, TexCoords).r;
vec3 N = getNormal();
// 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);
// calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
// of 0.04 and if it's a metal, use their albedo color as F0 (metallic workflow)
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); // use modified Fresnel-Schlick approximation to take roughness into account
// NOTE(Joey): kS is equal to Fresnel
// 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.
// for energy conservation, the diffuse and specular light can't
// be above 1.0 (unless the surface emits light); to preserve this
// relationship the diffuse component (kD) should equal 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).
// 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.03) * albedo * ao;
// for every light, calculate their contribution to the reflectance equation
// first do ambient lighting (note that the next IBL tutorial will replace
// this ambient lighting with environment lighting).
vec3 ambient = vec3(0.01) * albedo * ao;
// reflectance equation
vec3 Lo = vec3(0.0);
for(int i = 0; i < 4; ++i)
{
// calculate per-light radiance
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;
// Cook-Torrance BRDF
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
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
// scale light by NdotL
float NdotL = max(dot(N, L), 0.0);
// NOTE(Joey): reflectance equation
// add to outgoing radiance Lo
Lo += (kD * albedo / PI + kS * brdf) * radiance * NdotL;
}
vec3 color = ambient + Lo;
// NOTE(Joey): HDR tonemapping
// HDR tonemapping
color = color / (color + vec3(1.0));
// NOTE(Joey): gamma correct
// gamma correct
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 1.0);
}

View File

@@ -15,8 +15,7 @@ void main()
{
TexCoords = texCoords;
WorldPos = vec3(model * vec4(pos, 1.0f));
Normal = mat3(model) * normal;
Normal = mat3(model) * normal;
gl_Position = projection * view * vec4(WorldPos, 1.0);
}