diff --git a/CMakeLists.txt b/CMakeLists.txt index 0dd6183..4964593 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,7 +117,8 @@ set(6.pbr 1.2.lighting_textured 2.1.1.ibl_irradiance_conversion 2.1.2.ibl_irradiance - # 2.2.ibl_specular + 2.2.1.ibl_specular + 2.2.2.ibl_specular_textured ) set(7.in_practice diff --git a/resources/textures/pbr/gold/albedo.png b/resources/textures/pbr/gold/albedo.png new file mode 100644 index 0000000..5e89a92 Binary files /dev/null and b/resources/textures/pbr/gold/albedo.png differ diff --git a/resources/textures/pbr/gold/ao.png b/resources/textures/pbr/gold/ao.png new file mode 100644 index 0000000..6e40132 Binary files /dev/null and b/resources/textures/pbr/gold/ao.png differ diff --git a/resources/textures/pbr/gold/metallic.png b/resources/textures/pbr/gold/metallic.png new file mode 100644 index 0000000..e54d115 Binary files /dev/null and b/resources/textures/pbr/gold/metallic.png differ diff --git a/resources/textures/pbr/gold/normal.png b/resources/textures/pbr/gold/normal.png new file mode 100644 index 0000000..0adaf24 Binary files /dev/null and b/resources/textures/pbr/gold/normal.png differ diff --git a/resources/textures/pbr/gold/roughness.png b/resources/textures/pbr/gold/roughness.png new file mode 100644 index 0000000..053bfd2 Binary files /dev/null and b/resources/textures/pbr/gold/roughness.png differ diff --git a/resources/textures/pbr/grass/albedo.png b/resources/textures/pbr/grass/albedo.png new file mode 100644 index 0000000..0305255 Binary files /dev/null and b/resources/textures/pbr/grass/albedo.png differ diff --git a/resources/textures/pbr/grass/ao.png b/resources/textures/pbr/grass/ao.png new file mode 100644 index 0000000..1b34177 Binary files /dev/null and b/resources/textures/pbr/grass/ao.png differ diff --git a/resources/textures/pbr/grass/metallic.png b/resources/textures/pbr/grass/metallic.png new file mode 100644 index 0000000..66fff92 Binary files /dev/null and b/resources/textures/pbr/grass/metallic.png differ diff --git a/resources/textures/pbr/grass/normal.png b/resources/textures/pbr/grass/normal.png new file mode 100644 index 0000000..a47bb63 Binary files /dev/null and b/resources/textures/pbr/grass/normal.png differ diff --git a/resources/textures/pbr/grass/roughness.png b/resources/textures/pbr/grass/roughness.png new file mode 100644 index 0000000..3889b0a Binary files /dev/null and b/resources/textures/pbr/grass/roughness.png differ diff --git a/resources/textures/pbr/plastic/albedo.png b/resources/textures/pbr/plastic/albedo.png new file mode 100644 index 0000000..f5787bc Binary files /dev/null and b/resources/textures/pbr/plastic/albedo.png differ diff --git a/resources/textures/pbr/plastic/ao.png b/resources/textures/pbr/plastic/ao.png new file mode 100644 index 0000000..05536ef Binary files /dev/null and b/resources/textures/pbr/plastic/ao.png differ diff --git a/resources/textures/pbr/plastic/metallic.png b/resources/textures/pbr/plastic/metallic.png new file mode 100644 index 0000000..c74cb26 Binary files /dev/null and b/resources/textures/pbr/plastic/metallic.png differ diff --git a/resources/textures/pbr/plastic/normal.png b/resources/textures/pbr/plastic/normal.png new file mode 100644 index 0000000..935bf6e Binary files /dev/null and b/resources/textures/pbr/plastic/normal.png differ diff --git a/resources/textures/pbr/plastic/roughness.png b/resources/textures/pbr/plastic/roughness.png new file mode 100644 index 0000000..1acdb8d Binary files /dev/null and b/resources/textures/pbr/plastic/roughness.png differ diff --git a/resources/textures/pbr/wall/albedo.png b/resources/textures/pbr/wall/albedo.png new file mode 100644 index 0000000..4cc5dc7 Binary files /dev/null and b/resources/textures/pbr/wall/albedo.png differ diff --git a/resources/textures/pbr/wall/ao.png b/resources/textures/pbr/wall/ao.png new file mode 100644 index 0000000..fc32a4d Binary files /dev/null and b/resources/textures/pbr/wall/ao.png differ diff --git a/resources/textures/pbr/wall/metallic.png b/resources/textures/pbr/wall/metallic.png new file mode 100644 index 0000000..e1c6989 Binary files /dev/null and b/resources/textures/pbr/wall/metallic.png differ diff --git a/resources/textures/pbr/wall/normal.png b/resources/textures/pbr/wall/normal.png new file mode 100644 index 0000000..d613e58 Binary files /dev/null and b/resources/textures/pbr/wall/normal.png differ diff --git a/resources/textures/pbr/wall/roughness.png b/resources/textures/pbr/wall/roughness.png new file mode 100644 index 0000000..085fb45 Binary files /dev/null and b/resources/textures/pbr/wall/roughness.png differ diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.background.frag b/src/6.pbr/2.2.1.ibl_specular/2.2.1.background.frag new file mode 100644 index 0000000..0236b0d --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.background.frag @@ -0,0 +1,18 @@ +#version 330 core +out vec4 FragColor; +in vec3 WorldPos; + +uniform samplerCube environmentMap; +uniform float lod; + +void main() +{ + // vec3 envColor = textureLod(environmentMap, WorldPos, 1.0).rgb; + vec3 envColor = textureLod(environmentMap, WorldPos, lod).rgb; + + // HDR tonemap and gamma correct + envColor = envColor / (envColor + vec3(1.0)); + envColor = pow(envColor, vec3(1.0/2.2)); + + FragColor = vec4(envColor, 1.0); +} diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.background.vs b/src/6.pbr/2.2.1.ibl_specular/2.2.1.background.vs new file mode 100644 index 0000000..7de70de --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.background.vs @@ -0,0 +1,17 @@ +#version 330 core +layout (location = 0) in vec3 pos; + +uniform mat4 projection; +uniform mat4 view; + +out vec3 WorldPos; + +void main() +{ + WorldPos = pos; + + mat4 rotView = mat4(mat3(view)); + vec4 clipPos = projection * rotView * vec4(WorldPos, 1.0); + + gl_Position = clipPos.xyww; +} \ No newline at end of file diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.brdf.frag b/src/6.pbr/2.2.1.ibl_specular/2.2.1.brdf.frag new file mode 100644 index 0000000..8e2b798 --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.brdf.frag @@ -0,0 +1,113 @@ +#version 330 core +out vec2 FragColor; +in vec2 TexCoords; + +const float PI = 3.14159265359f; +// ---------------------------------------------------------------------------- +// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html +// efficient VanDerCorpus calculation. +float RadicalInverse_VdC(uint bits) +{ + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 +} +// ---------------------------------------------------------------------------- +vec2 Hammersley(uint i, uint N) +{ + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); +} +// ---------------------------------------------------------------------------- +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) +{ + float a = roughness*roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // NOTE(Joey): from spherical coordinates to cartesian coordinates - halfway vector + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + // NOTE(Joey): from tangent-space H vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); +} +// ---------------------------------------------------------------------------- +float GeometrySchlickGGX(float NdotV, float roughness) +{ + // note that we use a different k for IBL + float a = roughness; + float k = (a * a) / 2.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; +} +// ---------------------------------------------------------------------------- +vec2 IntegrateBRDF(float roughness, float NdotV) +{ + vec3 V; + V.x = sqrt(1.0 - NdotV*NdotV); + V.y = 0.0; + V.z = NdotV; + + float A = 0.0; + float B = 0.0; + + vec3 N = vec3(0.0, 0.0, 1.0); + + const uint SAMPLE_COUNT = 1024u; + for(uint i = 0u; i < SAMPLE_COUNT; ++i) + { + // NOTE(Joey): generates a sample vector that's biased towards the + // preferred alignment direction (importance sampling). + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(L.z, 0.0); + float NdotH = max(H.z, 0.0); + float VdotH = max(dot(V, H), 0.0); + + if(NdotL > 0.0) + { + float G = GeometrySmith(N, V, L, roughness); + float G_Vis = (G * VdotH) / (NdotH * NdotV); + float Fc = pow(1.0 - VdotH, 5.0); + + A += (1.0 - Fc) * G_Vis; + B += Fc * G_Vis; + } + } + A /= float(SAMPLE_COUNT); + B /= float(SAMPLE_COUNT); + return vec2(A, B); +} +// ---------------------------------------------------------------------------- +void main() +{ + vec2 integratedBRDF = IntegrateBRDF(TexCoords.y, TexCoords.x); + FragColor = integratedBRDF; +} \ No newline at end of file diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.brdf.vs b/src/6.pbr/2.2.1.ibl_specular/2.2.1.brdf.vs new file mode 100644 index 0000000..e207b45 --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.brdf.vs @@ -0,0 +1,11 @@ +#version 330 core +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texCoords; + +out vec2 TexCoords; + +void main() +{ + TexCoords = texCoords; + gl_Position = vec4(pos, 1.0); +} \ No newline at end of file diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.cubemap.vs b/src/6.pbr/2.2.1.ibl_specular/2.2.1.cubemap.vs new file mode 100644 index 0000000..a7ae633 --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.cubemap.vs @@ -0,0 +1,14 @@ +#version 330 core +layout (location = 0) in vec3 pos; + +out vec3 WorldPos; + +uniform mat4 projection; +uniform mat4 view; + +void main() +{ + WorldPos = pos; + + gl_Position = projection * view * vec4(WorldPos, 1.0); +} \ No newline at end of file diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.equirectangular_to_cubemap.frag b/src/6.pbr/2.2.1.ibl_specular/2.2.1.equirectangular_to_cubemap.frag new file mode 100644 index 0000000..cee67e6 --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.equirectangular_to_cubemap.frag @@ -0,0 +1,22 @@ +#version 330 core +out vec4 FragColor; +in vec3 WorldPos; + +uniform sampler2D equirectangularMap; + +const vec2 invAtan = vec2(0.1591, 0.3183); +vec2 SampleSphericalMap(vec3 v) +{ + vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + uv *= invAtan; + uv += 0.5; + return uv; +} + +void main() +{ + vec2 uv = SampleSphericalMap(normalize(WorldPos)); + vec3 color = texture(equirectangularMap, uv).rgb; + + FragColor = vec4(color, 1.0); +} diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.irradiance_convolution.frag b/src/6.pbr/2.2.1.ibl_specular/2.2.1.irradiance_convolution.frag new file mode 100644 index 0000000..25d5926 --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.irradiance_convolution.frag @@ -0,0 +1,43 @@ +#version 330 core +out vec4 FragColor; +in vec3 WorldPos; + +uniform samplerCube environmentMap; + +const float PI = 3.14159265359f; + +void main() +{ + // NOTE(Joey): the world vector acts as the normal of a tangent surface + // from the origin, aligned to WorldPos. Given this normal, calculate all + // incoming radiance of the environment. The result of this radiance + // is the radiance of light coming from -Normal direction, which is what + // we use in the PBR shader to sample irradiance. + vec3 N = normalize(WorldPos); + + vec3 irradiance = vec3(0.0); + + // NOTE(Joey): tangent space calculation from origin point + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = cross(up, N); + up = cross(N, right); + + float sampleDelta = 0.025f; + float nrSamples = 0.0f; + for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) + { + for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) + { + // spherical to cartesian (in tangent space) + vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); + // tangent space to world + vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; + + irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta); + nrSamples++; + } + } + irradiance = PI * irradiance * (1.0 / float(nrSamples)); + + FragColor = vec4(irradiance, 1.0); +} diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.pbr.frag b/src/6.pbr/2.2.1.ibl_specular/2.2.1.pbr.frag new file mode 100644 index 0000000..074fe16 --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.pbr.frag @@ -0,0 +1,147 @@ +#version 330 core +out vec4 FragColor; +in vec2 TexCoords; +in vec3 WorldPos; +in vec3 Normal; +in mat3 TBN; + +// material parameters +uniform vec3 albedo; +uniform float metallic; +uniform float roughness; +uniform float ao; + +// IBL +uniform samplerCube irradianceMap; +uniform samplerCube prefilterMap; +uniform sampler2D brdfLUT; + +// lights +uniform vec3 lightPositions[4]; +uniform vec3 lightColors[4]; + +uniform vec3 camPos; + +const float PI = 3.14159265359; +// ---------------------------------------------------------------------------- +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 = Normal; + vec3 V = normalize(camPos - WorldPos); + vec3 R = reflect(-V, N); + + // 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); + + // 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; + + // Cook-Torrance BRDF + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + + vec3 nominator = NDF * G * F; + float denominator = 4 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; // 0.001 to prevent divide by zero. + vec3 brdf = nominator / denominator; + + // kS is equal to Fresnel + vec3 kS = F; + // 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). + kD *= 1.0 - metallic; + + // scale light by NdotL + float NdotL = max(dot(N, L), 0.0); + + // add to outgoing radiance Lo + Lo += (kD * albedo / PI + brdf) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again + } + + // ambient lighting (we now use IBL as the ambient term) + vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); + + vec3 kS = F; + vec3 kD = 1.0 - kS; + kD *= 1.0 - metallic; + + vec3 irradiance = texture(irradianceMap, N).rgb; + vec3 diffuse = irradiance * albedo; + + // sample both the pre-filter map and the BRDF lut and combine them together as per the Split-Sum approximation to get the IBL specular part. + const float MAX_REFLECTION_LOD = 5.0; + vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb; + vec2 brdf = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg; + vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); + + vec3 ambient = (kD * diffuse + specular) * ao; + + vec3 color = ambient + Lo; + + // HDR tonemapping + color = color / (color + vec3(1.0)); + // gamma correct + color = pow(color, vec3(1.0/2.2)); + + FragColor = vec4(color , 1.0); +} diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.pbr.vs b/src/6.pbr/2.2.1.ibl_specular/2.2.1.pbr.vs new file mode 100644 index 0000000..1fe53b3 --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.pbr.vs @@ -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); +} \ No newline at end of file diff --git a/src/6.pbr/2.2.1.ibl_specular/2.2.1.prefilter.frag b/src/6.pbr/2.2.1.ibl_specular/2.2.1.prefilter.frag new file mode 100644 index 0000000..4728f5f --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/2.2.1.prefilter.frag @@ -0,0 +1,106 @@ +#version 330 core +out vec4 FragColor; +in vec3 WorldPos; + +uniform samplerCube environmentMap; +uniform float roughness; + +const float PI = 3.14159265359f; +// ---------------------------------------------------------------------------- +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; +} +// ---------------------------------------------------------------------------- +// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html +// efficient VanDerCorpus calculation. +float RadicalInverse_VdC(uint bits) +{ + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 +} +// ---------------------------------------------------------------------------- +vec2 Hammersley(uint i, uint N) +{ + return vec2(float(i)/float(N), RadicalInverse_VdC(i)); +} +// ---------------------------------------------------------------------------- +vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) +{ + float a = roughness*roughness; + + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // from spherical coordinates to cartesian coordinates - halfway vector + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + // from tangent-space H vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); +} +// ---------------------------------------------------------------------------- +void main() +{ + vec3 N = normalize(WorldPos); + + // make the simplyfying assumption that V equals R equals the normal + vec3 R = N; + vec3 V = R; + + const uint SAMPLE_COUNT = 1024u; + vec3 prefilteredColor = vec3(0.0); + float totalWeight = 0.0; + + for(uint i = 0u; i < SAMPLE_COUNT; ++i) + { + // generates a sample vector that's biased towards the preferred alignment direction (importance sampling). + vec2 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, N, roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(dot(N, L), 0.0); + if(NdotL > 0.0) + { + // sample from the environment's mip level based on roughness/pdf + float D = DistributionGGX(N, H, roughness); + float NdotH = max(dot(N, H), 0.0); + float HdotV = max(dot(H, V), 0.0); + float pdf = D * NdotH / (4.0 * HdotV) + 0.0001; + + float resolution = 512.0; // resolution of source cubemap (per face) + float saTexel = 4.0 * PI / (6.0 * resolution * resolution); + float saSample = 1.0 / (float(SAMPLE_COUNT) * pdf + 0.0001); + + float mipLevel = roughness == 0.0 ? 0.0 : 0.5 * log2(saSample / saTexel); + + prefilteredColor += textureLod(environmentMap, L, mipLevel).rgb * NdotL; + totalWeight += NdotL; + } + } + + prefilteredColor = prefilteredColor / totalWeight; + + FragColor = vec4(prefilteredColor, 1.0); +} diff --git a/src/6.pbr/2.2.1.ibl_specular/ibl_specular.cpp b/src/6.pbr/2.2.1.ibl_specular/ibl_specular.cpp new file mode 100644 index 0000000..8d2becb --- /dev/null +++ b/src/6.pbr/2.2.1.ibl_specular/ibl_specular.cpp @@ -0,0 +1,727 @@ +// Std. Includes +#include + +// GLEW +#define GLEW_STATIC +#include + +// GLFW +#include + +// GL includes +#include +#include + +// GLM Mathemtics +#include +#include +#include + +// Other Libs +#include + +#include "stb_image.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 renderSphere(); +void renderCube(); +void RenderQuad(); + +// 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 the application and run the Game loop +int main() +{ + // GLFW Init + // --------- + glfwInit(); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_SAMPLES, 4); + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + + GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed + glfwMakeContextCurrent(window); + + // GLFW config + // ----------- + glfwSetKeyCallback(window, key_callback); + glfwSetCursorPosCallback(window, mouse_callback); + glfwSetScrollCallback(window, scroll_callback); + + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + // Initialize GLEW to setup the OpenGL Function pointers + // ----------------------------------------------------- + glewExperimental = GL_TRUE; + glewInit(); + glGetError(); + + // Setup OpenGL state + // ------------------ + glEnable(GL_DEPTH_TEST); + // set depth function to less than AND equal for skybox depth trick. + glDepthFunc(GL_LEQUAL); + // enable seamless cubemap sampling for lower mip levels in the pre-filter map. + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + + // load and initialize shaders + // --------------------------- + Shader pbrShader("2.2.1.pbr.vs", "2.2.1.pbr.frag"); + Shader equirectangularToCubemapShader("2.2.1.cubemap.vs", "2.2.1.equirectangular_to_cubemap.frag"); + Shader irradianceShader("2.2.1.cubemap.vs", "2.2.1.irradiance_convolution.frag"); + Shader prefilterShader("2.2.1.cubemap.vs", "2.2.1.prefilter.frag"); + Shader brdfShader("2.2.1.brdf.vs", "2.2.1.brdf.frag"); + Shader backgroundShader("2.2.1.background.vs", "2.2.1.background.frag"); + + pbrShader.Use(); + glUniform1i(glGetUniformLocation(pbrShader.Program, "irradianceMap"), 0); + glUniform1i(glGetUniformLocation(pbrShader.Program, "prefilterMap"), 1); + glUniform1i(glGetUniformLocation(pbrShader.Program, "brdfLUT"), 2); + glUniform3f(glGetUniformLocation(pbrShader.Program, "albedo"), 0.5f, 0.0f, 0.0f); + glUniform1f(glGetUniformLocation(pbrShader.Program, "ao"), 1.0f); + + backgroundShader.Use(); + glUniform1i(glGetUniformLocation(backgroundShader.Program, "environmentMap"), 0); + + // 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(300.0f, 300.0f, 300.0f), + glm::vec3(300.0f, 300.0f, 300.0f), + glm::vec3(300.0f, 300.0f, 300.0f), + glm::vec3(300.0f, 300.0f, 300.0f) + }; + int nrRows = 7; + int nrColumns = 7; + float spacing = 2.5; + + // pbr: setup framebuffer + // ---------------------- + unsigned int captureFBO; + unsigned int captureRBO; + glGenFramebuffers(1, &captureFBO); + glGenRenderbuffers(1, &captureRBO); + + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, captureRBO); + + // pbr: load the HDR environment map + // --------------------------------- + stbi_set_flip_vertically_on_load(true); + int width, height, nrComponents; + float *data = stbi_loadf(FileSystem::getPath("resources/textures/hdr/newport_loft.hdr").c_str(), &width, &height, &nrComponents, 0); + unsigned int hdrTexture; + if (data) + { + glGenTextures(1, &hdrTexture); + glBindTexture(GL_TEXTURE_2D, hdrTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, data); // note how we specify the texture's data value to be float + + 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_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + stbi_image_free(data); + } + else + { + std::cout << "Failed to load HDR image." << std::endl; + } + + // pbr: setup cubemap to render to and attach to framebuffer + // --------------------------------------------------------- + unsigned int envCubemap; + glGenTextures(1, &envCubemap); + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + for (unsigned int i = 0; i < 6; ++i) + { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 512, 512, 0, GL_RGB, GL_FLOAT, nullptr); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // enable pre-filter mipmap sampling (combatting visible dots artifact) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // pbr: set up projection and view matrices for capturing data onto the 6 cubemap face directions + // ---------------------------------------------------------------------------------------------- + glm::mat4 captureProjection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f); + glm::mat4 captureViews[] = + { + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)) + }; + + // pbr: convert HDR equirectangular environment map to cubemap equivalent + // ---------------------------------------------------------------------- + equirectangularToCubemapShader.Use(); + glUniform1i(glGetUniformLocation(equirectangularToCubemapShader.Program, "equirectangularMap"), 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, hdrTexture); + glUniformMatrix4fv(glGetUniformLocation(equirectangularToCubemapShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(captureProjection)); + + glViewport(0, 0, 512, 512); // don't forget to configure the viewport to the capture dimensions. + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + for (unsigned int i = 0; i < 6; ++i) + { + glUniformMatrix4fv(glGetUniformLocation(equirectangularToCubemapShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(captureViews[i])); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, envCubemap, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + renderCube(); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // then let OpenGL generate mipmaps from first mip face (combatting visible dots artifact) + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + + // pbr: create an irradiance cubemap, and re-scale capture FBO to irradiance scale. + // -------------------------------------------------------------------------------- + unsigned int irradianceMap; + glGenTextures(1, &irradianceMap); + glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceMap); + for (unsigned int i = 0; i < 6; ++i) + { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB32F, 32, 32, 0, GL_RGB, GL_FLOAT, nullptr); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32); + + // pbr: solve diffuse integral by convolution to create an irradiance (cube)map. + // ----------------------------------------------------------------------------- + irradianceShader.Use(); + glUniform1i(glGetUniformLocation(irradianceShader.Program, "environmentMap"), 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + glUniformMatrix4fv(glGetUniformLocation(irradianceShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(captureProjection)); + + glViewport(0, 0, 32, 32); // don't forget to configure the viewport to the capture dimensions. + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + for (unsigned int i = 0; i < 6; ++i) + { + glUniformMatrix4fv(glGetUniformLocation(irradianceShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(captureViews[i])); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradianceMap, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + renderCube(); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // pbr: create a pre-filter cubemap, and re-scale capture FBO to pre-filter scale. + // -------------------------------------------------------------------------------- + unsigned int prefilterMap; + glGenTextures(1, &prefilterMap); + glBindTexture(GL_TEXTURE_CUBE_MAP, prefilterMap); + for (unsigned int i = 0; i < 6; ++i) + { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 128, 128, 0, GL_RGB, GL_FLOAT, nullptr); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // be sure to set minifcation filter to mip_linear + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // generate mipmaps for the cubemap so OpenGL automatically allocates the required memory. + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + + // pbr: run a quasi monte-carlo simulation on the environment lighting to create a prefilter (cube)map. + // ---------------------------------------------------------------------------------------------------- + prefilterShader.Use(); + glUniform1i(glGetUniformLocation(prefilterShader.Program, "environmentMap"), 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + glUniformMatrix4fv(glGetUniformLocation(prefilterShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(captureProjection)); + + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + unsigned int maxMipLevels = 5; + for (unsigned int mip = 0; mip < maxMipLevels; ++mip) + { + // reisze framebuffer according to mip-level size. + unsigned int mipWidth = 128 * std::pow(0.5, mip); + unsigned int mipHeight = 128 * std::pow(0.5, mip); + glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mipWidth, mipHeight); + glViewport(0, 0, mipWidth, mipHeight); + + float roughness = (float)mip / (float)(maxMipLevels - 1); + glUniform1f(glGetUniformLocation(prefilterShader.Program, "roughness"), roughness); + for (unsigned int i = 0; i < 6; ++i) + { + glUniformMatrix4fv(glGetUniformLocation(prefilterShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(captureViews[i])); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilterMap, mip); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + renderCube(); + } + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // pbr: generate a 2D LUT from the BRDF equations used. + // ---------------------------------------------------- + unsigned int brdfLUTTexture; + glGenTextures(1, &brdfLUTTexture); + + // pre-allocate enough memory for the LUT texture. + glBindTexture(GL_TEXTURE_2D, brdfLUTTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, 512, 512, 0, GL_RG, GL_FLOAT, 0); + // be sure to set wrapping mode to GL_CLAMP_TO_EDGE + 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_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // then re-configure capture framebuffer object and render screen-space quad with BRDF shader. + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdfLUTTexture, 0); + + glViewport(0, 0, 512, 512); + brdfShader.Use(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + RenderQuad(); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + + // initialize static shader uniforms before rendering + // -------------------------------------------------- + glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); + pbrShader.Use(); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + backgroundShader.Use(); + glUniformMatrix4fv(glGetUniformLocation(backgroundShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + + // then before rendering, configure the viewport to the actual screen dimensions + glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); + + // 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); + + // render scene, supplying the convoluted irradiance map to the final shader. + // ------------------------------------------------------------------------------------------ + pbrShader.Use(); + glm::mat4 view = camera.GetViewMatrix(); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view)); + glUniform3fv(glGetUniformLocation(pbrShader.Program, "camPos"), 1, &camera.Position[0]); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceMap); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_CUBE_MAP, prefilterMap); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, brdfLUTTexture); + + // 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) + { + glUniform1f(glGetUniformLocation(pbrShader.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(pbrShader.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, + -2.0f + )); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.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) + { + glm::vec3 newPos = lightPositions[i] + glm::vec3(sin(glfwGetTime() * 5.0) * 5.0, 0.0, 0.0); + newPos = lightPositions[i]; + glUniform3fv(glGetUniformLocation(pbrShader.Program, ("lightPositions[" + std::to_string(i) + "]").c_str()), 1, &newPos[0]); \ + glUniform3fv(glGetUniformLocation(pbrShader.Program, ("lightColors[" + std::to_string(i) + "]").c_str()), 1, &lightColors[i][0]); + + model = glm::mat4(); + model = glm::translate(model, newPos); + model = glm::scale(model, glm::vec3(0.5f)); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); + renderSphere(); + } + + // render skybox (render as last to prevent overdraw) + backgroundShader.Use(); + glUniformMatrix4fv(glGetUniformLocation(backgroundShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view)); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + //glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceMap); // display irradiance map + //glBindTexture(GL_TEXTURE_CUBE_MAP, prefilterMap); // display prefilter map + renderCube(); + + // render BRDF map to screen + //brdfShader.Use(); + //RenderQuad(); + + // Swap the buffers + glfwSwapBuffers(window); + } + + glfwTerminate(); + return 0; +} + + +// renders (and builds at first invocation) a sphere +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 positions; + std::vector uv; + std::vector normals; + std::vector 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); + 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) // 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 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); + } + } + 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) * 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))); + } + + glBindVertexArray(sphereVAO); + glDrawElements(GL_TRIANGLE_STRIP, indexCount, GL_UNSIGNED_INT, 0); +} +// RenderCube() Renders a 1x1 3D cube in NDC. +GLuint cubeVAO = 0; +GLuint cubeVBO = 0; +void renderCube() +{ + // Initialize (if necessary) + if (cubeVAO == 0) + { + GLfloat vertices[] = { + // Back face + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // Bottom-left + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,// top-left + // Front face + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left + // Left face + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right + // Right face + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left + // Bottom face + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom-left + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right + // Top face + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left + 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left + }; + glGenVertexArrays(1, &cubeVAO); + glGenBuffers(1, &cubeVBO); + // Fill buffer + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + // Link vertex attributes + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat))); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + // Render Cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); +} + +// RenderQuad() Renders a 1x1 XY quad in NDC +GLuint quadVAO = 0; +GLuint quadVBO; +void RenderQuad() +{ + if (quadVAO == 0) + { + GLfloat quadVertices[] = { + // Positions // Texture Coords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + // 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, 5 * sizeof(GLfloat), (GLvoid*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); + } + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + 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. +unsigned int loadTexture(char const * path) +{ + //Generate texture ID and load texture data + unsigned int textureID; + glGenTextures(1, &textureID); + int width, height, nrComponents; + unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0); + if (data) + { + GLenum format; + if (nrComponents == 1) + format = GL_RED; + else if (nrComponents == 3) + format = GL_RGB; + else if (nrComponents == 4) + format = GL_RGBA; + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + 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); + + stbi_image_free(data); + } + else + { + std::cout << "Texture failed to load at path: " << path << std::endl; + stbi_image_free(data); + } + + 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 \ No newline at end of file diff --git a/src/6.pbr/2.2.2.ibl_specular_textured/2.2.2.pbr.frag b/src/6.pbr/2.2.2.ibl_specular_textured/2.2.2.pbr.frag new file mode 100644 index 0000000..ef6cf92 --- /dev/null +++ b/src/6.pbr/2.2.2.ibl_specular_textured/2.2.2.pbr.frag @@ -0,0 +1,177 @@ +#version 330 core +out vec4 FragColor; +in vec2 TexCoords; +in vec3 WorldPos; +in vec3 Normal; +in mat3 TBN; + +// material parameters +uniform sampler2D albedoMap; +uniform sampler2D normalMap; +uniform sampler2D metallicMap; +uniform sampler2D roughnessMap; +uniform sampler2D aoMap; + +// IBL +uniform samplerCube irradianceMap; +uniform samplerCube prefilterMap; +uniform sampler2D brdfLUT; + +// lights +uniform vec3 lightPositions[4]; +uniform vec3 lightColors[4]; + +uniform vec3 camPos; + +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 getNormalFromMap() +{ + vec3 tangentNormal = texture(normalMap, TexCoords).xyz * 2.0 - 1.0; + + vec3 Q1 = dFdx(WorldPos); + vec3 Q2 = dFdy(WorldPos); + vec2 st1 = dFdx(TexCoords); + vec2 st2 = dFdy(TexCoords); + + vec3 N = normalize(Normal); + vec3 T = normalize(Q1*st2.t - Q2*st1.t); + vec3 B = -normalize(cross(N, T)); + mat3 TBN = mat3(T, B, N); + + 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() +{ + // material properties + vec3 albedo = pow(texture(albedoMap, TexCoords).rgb, vec3(2.2)); + float metallic = texture(metallicMap, TexCoords).r; + float roughness = texture(roughnessMap, TexCoords).r; + float ao = texture(aoMap, TexCoords).r; + + + // input lighting data + vec3 N = getNormalFromMap(); + vec3 V = normalize(camPos - WorldPos); + vec3 R = reflect(-V, N); + + // 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); + + // 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; + + // Cook-Torrance BRDF + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + + vec3 nominator = NDF * G * F; + float denominator = 4 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; // 0.001 to prevent divide by zero. + vec3 brdf = nominator / denominator; + + // kS is equal to Fresnel + vec3 kS = F; + // 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). + kD *= 1.0 - metallic; + + // scale light by NdotL + float NdotL = max(dot(N, L), 0.0); + + // add to outgoing radiance Lo + Lo += (kD * albedo / PI + brdf) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again + } + + // ambient lighting (we now use IBL as the ambient term) + vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); + + vec3 kS = F; + vec3 kD = 1.0 - kS; + kD *= 1.0 - metallic; + + vec3 irradiance = texture(irradianceMap, N).rgb; + vec3 diffuse = irradiance * albedo; + + // sample both the pre-filter map and the BRDF lut and combine them together as per the Split-Sum approximation to get the IBL specular part. + const float MAX_REFLECTION_LOD = 4.0; + vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb; + vec2 brdf = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg; + vec3 specular = prefilteredColor * (F * brdf.x + brdf.y); + + vec3 ambient = (kD * diffuse + specular) * ao; + + vec3 color = ambient + Lo; + + // HDR tonemapping + color = color / (color + vec3(1.0)); + // gamma correct + color = pow(color, vec3(1.0/2.2)); + + FragColor = vec4(color , 1.0); +} diff --git a/src/6.pbr/2.2.2.ibl_specular_textured/2.2.2.pbr.vs b/src/6.pbr/2.2.2.ibl_specular_textured/2.2.2.pbr.vs new file mode 100644 index 0000000..1fe53b3 --- /dev/null +++ b/src/6.pbr/2.2.2.ibl_specular_textured/2.2.2.pbr.vs @@ -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); +} \ No newline at end of file diff --git a/src/6.pbr/2.2.2.ibl_specular_textured/ibl_specular_textured.cpp b/src/6.pbr/2.2.2.ibl_specular_textured/ibl_specular_textured.cpp new file mode 100644 index 0000000..94240b2 --- /dev/null +++ b/src/6.pbr/2.2.2.ibl_specular_textured/ibl_specular_textured.cpp @@ -0,0 +1,835 @@ +// Std. Includes +#include + +// GLEW +#define GLEW_STATIC +#include + +// GLFW +#include + +// GL includes +#include +#include + +// GLM Mathemtics +#include +#include +#include + +// Other Libs +#include + +#include "stb_image.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 renderSphere(); +void renderCube(); +void RenderQuad(); + +// 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 the application and run the Game loop +int main() +{ + // GLFW Init + // --------- + glfwInit(); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_SAMPLES, 4); + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + + GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed + glfwMakeContextCurrent(window); + + // GLFW config + // ----------- + glfwSetKeyCallback(window, key_callback); + glfwSetCursorPosCallback(window, mouse_callback); + glfwSetScrollCallback(window, scroll_callback); + + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + // Initialize GLEW to setup the OpenGL Function pointers + // ----------------------------------------------------- + glewExperimental = GL_TRUE; + glewInit(); + glGetError(); + + // Setup OpenGL state + // ------------------ + glEnable(GL_DEPTH_TEST); + // set depth function to less than AND equal for skybox depth trick. + glDepthFunc(GL_LEQUAL); + // enable seamless cubemap sampling for lower mip levels in the pre-filter map. + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + + // load and initialize shaders + // --------------------------- + Shader pbrShader("2.2.2.pbr.vs", "2.2.2.pbr.frag"); + Shader equirectangularToCubemapShader("2.2.1.cubemap.vs", "2.2.1.equirectangular_to_cubemap.frag"); + Shader irradianceShader("2.2.1.cubemap.vs", "2.2.1.irradiance_convolution.frag"); + Shader prefilterShader("2.2.1.cubemap.vs", "2.2.1.prefilter.frag"); + Shader brdfShader("2.2.1.brdf.vs", "2.2.1.brdf.frag"); + Shader backgroundShader("2.2.1.background.vs", "2.2.1.background.frag"); + + pbrShader.Use(); + glUniform1i(glGetUniformLocation(pbrShader.Program, "irradianceMap"), 0); + glUniform1i(glGetUniformLocation(pbrShader.Program, "prefilterMap"), 1); + glUniform1i(glGetUniformLocation(pbrShader.Program, "brdfLUT"), 2); + glUniform1i(glGetUniformLocation(pbrShader.Program, "albedoMap"), 3); + glUniform1i(glGetUniformLocation(pbrShader.Program, "normalMap"), 4); + glUniform1i(glGetUniformLocation(pbrShader.Program, "metallicMap"), 5); + glUniform1i(glGetUniformLocation(pbrShader.Program, "roughnessMap"), 6); + glUniform1i(glGetUniformLocation(pbrShader.Program, "aoMap"), 7); + + backgroundShader.Use(); + glUniform1i(glGetUniformLocation(backgroundShader.Program, "environmentMap"), 0); + + // load PBR material textures + // -------------------------- + // rusted iron + unsigned int ironAlbedoMap = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/albedo.png").c_str()); + unsigned int ironNormalMap = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/normal.png").c_str()); + unsigned int ironMetallicMap = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/metallic.png").c_str()); + unsigned int ironRoughnessMap = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/roughness.png").c_str()); + unsigned int ironAOMap = loadTexture(FileSystem::getPath("resources/textures/pbr/rusted_iron/ao.png").c_str()); + + // gold + unsigned int goldAlbedoMap = loadTexture(FileSystem::getPath("resources/textures/pbr/gold/albedo.png").c_str()); + unsigned int goldNormalMap = loadTexture(FileSystem::getPath("resources/textures/pbr/gold/normal.png").c_str()); + unsigned int goldMetallicMap = loadTexture(FileSystem::getPath("resources/textures/pbr/gold/metallic.png").c_str()); + unsigned int goldRoughnessMap = loadTexture(FileSystem::getPath("resources/textures/pbr/gold/roughness.png").c_str()); + unsigned int goldAOMap = loadTexture(FileSystem::getPath("resources/textures/pbr/gold/ao.png").c_str()); + + // grass + unsigned int grassAlbedoMap = loadTexture(FileSystem::getPath("resources/textures/pbr/grass/albedo.png").c_str()); + unsigned int grassNormalMap = loadTexture(FileSystem::getPath("resources/textures/pbr/grass/normal.png").c_str()); + unsigned int grassMetallicMap = loadTexture(FileSystem::getPath("resources/textures/pbr/grass/metallic.png").c_str()); + unsigned int grassRoughnessMap = loadTexture(FileSystem::getPath("resources/textures/pbr/grass/roughness.png").c_str()); + unsigned int grassAOMap = loadTexture(FileSystem::getPath("resources/textures/pbr/grass/ao.png").c_str()); + + // plastic + unsigned int plasticAlbedoMap = loadTexture(FileSystem::getPath("resources/textures/pbr/plastic/albedo.png").c_str()); + unsigned int plasticNormalMap = loadTexture(FileSystem::getPath("resources/textures/pbr/plastic/normal.png").c_str()); + unsigned int plasticMetallicMap = loadTexture(FileSystem::getPath("resources/textures/pbr/plastic/metallic.png").c_str()); + unsigned int plasticRoughnessMap = loadTexture(FileSystem::getPath("resources/textures/pbr/plastic/roughness.png").c_str()); + unsigned int plasticAOMap = loadTexture(FileSystem::getPath("resources/textures/pbr/plastic/ao.png").c_str()); + + // wall + unsigned int wallAlbedoMap = loadTexture(FileSystem::getPath("resources/textures/pbr/wall/albedo.png").c_str()); + unsigned int wallNormalMap = loadTexture(FileSystem::getPath("resources/textures/pbr/wall/normal.png").c_str()); + unsigned int wallMetallicMap = loadTexture(FileSystem::getPath("resources/textures/pbr/wall/metallic.png").c_str()); + unsigned int wallRoughnessMap = loadTexture(FileSystem::getPath("resources/textures/pbr/wall/roughness.png").c_str()); + unsigned int wallAOMap = loadTexture(FileSystem::getPath("resources/textures/pbr/wall/ao.png").c_str()); + + // 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(300.0f, 300.0f, 300.0f), + glm::vec3(300.0f, 300.0f, 300.0f), + glm::vec3(300.0f, 300.0f, 300.0f), + glm::vec3(300.0f, 300.0f, 300.0f) + }; + int nrRows = 7; + int nrColumns = 7; + float spacing = 2.5; + + // pbr: setup framebuffer + // ---------------------- + unsigned int captureFBO; + unsigned int captureRBO; + glGenFramebuffers(1, &captureFBO); + glGenRenderbuffers(1, &captureRBO); + + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, captureRBO); + + // pbr: load the HDR environment map + // --------------------------------- + stbi_set_flip_vertically_on_load(true); + int width, height, nrComponents; + float *data = stbi_loadf(FileSystem::getPath("resources/textures/hdr/newport_loft.hdr").c_str(), &width, &height, &nrComponents, 0); + unsigned int hdrTexture; + if (data) + { + glGenTextures(1, &hdrTexture); + glBindTexture(GL_TEXTURE_2D, hdrTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, data); // note how we specify the texture's data value to be float + + 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_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + stbi_image_free(data); + } + else + { + std::cout << "Failed to load HDR image." << std::endl; + } + + // pbr: setup cubemap to render to and attach to framebuffer + // --------------------------------------------------------- + unsigned int envCubemap; + glGenTextures(1, &envCubemap); + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + for (unsigned int i = 0; i < 6; ++i) + { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 512, 512, 0, GL_RGB, GL_FLOAT, nullptr); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // enable pre-filter mipmap sampling (combatting visible dots artifact) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // pbr: set up projection and view matrices for capturing data onto the 6 cubemap face directions + // ---------------------------------------------------------------------------------------------- + glm::mat4 captureProjection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f); + glm::mat4 captureViews[] = + { + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)) + }; + + // pbr: convert HDR equirectangular environment map to cubemap equivalent + // ---------------------------------------------------------------------- + equirectangularToCubemapShader.Use(); + glUniform1i(glGetUniformLocation(equirectangularToCubemapShader.Program, "equirectangularMap"), 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, hdrTexture); + glUniformMatrix4fv(glGetUniformLocation(equirectangularToCubemapShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(captureProjection)); + + glViewport(0, 0, 512, 512); // don't forget to configure the viewport to the capture dimensions. + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + for (unsigned int i = 0; i < 6; ++i) + { + glUniformMatrix4fv(glGetUniformLocation(equirectangularToCubemapShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(captureViews[i])); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, envCubemap, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + renderCube(); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // then let OpenGL generate mipmaps from first mip face (combatting visible dots artifact) + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + + // pbr: create an irradiance cubemap, and re-scale capture FBO to irradiance scale. + // -------------------------------------------------------------------------------- + unsigned int irradianceMap; + glGenTextures(1, &irradianceMap); + glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceMap); + for (unsigned int i = 0; i < 6; ++i) + { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB32F, 32, 32, 0, GL_RGB, GL_FLOAT, nullptr); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32); + + // pbr: solve diffuse integral by convolution to create an irradiance (cube)map. + // ----------------------------------------------------------------------------- + irradianceShader.Use(); + glUniform1i(glGetUniformLocation(irradianceShader.Program, "environmentMap"), 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + glUniformMatrix4fv(glGetUniformLocation(irradianceShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(captureProjection)); + + glViewport(0, 0, 32, 32); // don't forget to configure the viewport to the capture dimensions. + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + for (unsigned int i = 0; i < 6; ++i) + { + glUniformMatrix4fv(glGetUniformLocation(irradianceShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(captureViews[i])); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradianceMap, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + renderCube(); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // pbr: create a pre-filter cubemap, and re-scale capture FBO to pre-filter scale. + // -------------------------------------------------------------------------------- + unsigned int prefilterMap; + glGenTextures(1, &prefilterMap); + glBindTexture(GL_TEXTURE_CUBE_MAP, prefilterMap); + for (unsigned int i = 0; i < 6; ++i) + { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 128, 128, 0, GL_RGB, GL_FLOAT, nullptr); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // be sure to set minifcation filter to mip_linear + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // generate mipmaps for the cubemap so OpenGL automatically allocates the required memory. + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + + // pbr: run a quasi monte-carlo simulation on the environment lighting to create a prefilter (cube)map. + // ---------------------------------------------------------------------------------------------------- + prefilterShader.Use(); + glUniform1i(glGetUniformLocation(prefilterShader.Program, "environmentMap"), 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + glUniformMatrix4fv(glGetUniformLocation(prefilterShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(captureProjection)); + + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + unsigned int maxMipLevels = 5; + for (unsigned int mip = 0; mip < maxMipLevels; ++mip) + { + // reisze framebuffer according to mip-level size. + unsigned int mipWidth = 128 * std::pow(0.5, mip); + unsigned int mipHeight = 128 * std::pow(0.5, mip); + glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mipWidth, mipHeight); + glViewport(0, 0, mipWidth, mipHeight); + + float roughness = (float)mip / (float)(maxMipLevels - 1); + glUniform1f(glGetUniformLocation(prefilterShader.Program, "roughness"), roughness); + for (unsigned int i = 0; i < 6; ++i) + { + glUniformMatrix4fv(glGetUniformLocation(prefilterShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(captureViews[i])); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilterMap, mip); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + renderCube(); + } + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // pbr: generate a 2D LUT from the BRDF equations used. + // ---------------------------------------------------- + unsigned int brdfLUTTexture; + glGenTextures(1, &brdfLUTTexture); + + // pre-allocate enough memory for the LUT texture. + glBindTexture(GL_TEXTURE_2D, brdfLUTTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, 512, 512, 0, GL_RG, GL_FLOAT, 0); + // be sure to set wrapping mode to GL_CLAMP_TO_EDGE + 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_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // then re-configure capture framebuffer object and render screen-space quad with BRDF shader. + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdfLUTTexture, 0); + + glViewport(0, 0, 512, 512); + brdfShader.Use(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + RenderQuad(); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + + // initialize static shader uniforms before rendering + // -------------------------------------------------- + glm::mat4 projection = glm::perspective(camera.Zoom, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); + pbrShader.Use(); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + backgroundShader.Use(); + glUniformMatrix4fv(glGetUniformLocation(backgroundShader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + + // then before rendering, configure the viewport to the actual screen dimensions + glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); + + // 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); + + + // render scene, supplying the convoluted irradiance map to the final shader. + // ------------------------------------------------------------------------------------------ + pbrShader.Use(); + glm::mat4 model; + glm::mat4 view = camera.GetViewMatrix(); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view)); + glUniform3fv(glGetUniformLocation(pbrShader.Program, "camPos"), 1, &camera.Position[0]); + + + // bind pre-computed IBL data + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceMap); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_CUBE_MAP, prefilterMap); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, brdfLUTTexture); + + // rusted iron + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, ironAlbedoMap); + glActiveTexture(GL_TEXTURE4); + glBindTexture(GL_TEXTURE_2D, ironNormalMap); + glActiveTexture(GL_TEXTURE5); + glBindTexture(GL_TEXTURE_2D, ironMetallicMap); + glActiveTexture(GL_TEXTURE6); + glBindTexture(GL_TEXTURE_2D, ironRoughnessMap); + glActiveTexture(GL_TEXTURE7); + glBindTexture(GL_TEXTURE_2D, ironAOMap); + + model = glm::mat4(); + model = glm::translate(model, glm::vec3(-5.0, 0.0, 2.0)); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); + renderSphere(); + + // gold + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, goldAlbedoMap); + glActiveTexture(GL_TEXTURE4); + glBindTexture(GL_TEXTURE_2D, goldNormalMap); + glActiveTexture(GL_TEXTURE5); + glBindTexture(GL_TEXTURE_2D, goldMetallicMap); + glActiveTexture(GL_TEXTURE6); + glBindTexture(GL_TEXTURE_2D, goldRoughnessMap); + glActiveTexture(GL_TEXTURE7); + glBindTexture(GL_TEXTURE_2D, goldAOMap); + + model = glm::mat4(); + model = glm::translate(model, glm::vec3(-3.0, 0.0, 2.0)); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); + renderSphere(); + + // grass + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, grassAlbedoMap); + glActiveTexture(GL_TEXTURE4); + glBindTexture(GL_TEXTURE_2D, grassNormalMap); + glActiveTexture(GL_TEXTURE5); + glBindTexture(GL_TEXTURE_2D, grassMetallicMap); + glActiveTexture(GL_TEXTURE6); + glBindTexture(GL_TEXTURE_2D, grassRoughnessMap); + glActiveTexture(GL_TEXTURE7); + glBindTexture(GL_TEXTURE_2D, grassAOMap); + + model = glm::mat4(); + model = glm::translate(model, glm::vec3(-1.0, 0.0, 2.0)); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); + renderSphere(); + + // plastic + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, plasticAlbedoMap); + glActiveTexture(GL_TEXTURE4); + glBindTexture(GL_TEXTURE_2D, plasticNormalMap); + glActiveTexture(GL_TEXTURE5); + glBindTexture(GL_TEXTURE_2D, plasticMetallicMap); + glActiveTexture(GL_TEXTURE6); + glBindTexture(GL_TEXTURE_2D, plasticRoughnessMap); + glActiveTexture(GL_TEXTURE7); + glBindTexture(GL_TEXTURE_2D, plasticAOMap); + + model = glm::mat4(); + model = glm::translate(model, glm::vec3(1.0, 0.0, 2.0)); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); + renderSphere(); + + // wall + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, wallAlbedoMap); + glActiveTexture(GL_TEXTURE4); + glBindTexture(GL_TEXTURE_2D, wallNormalMap); + glActiveTexture(GL_TEXTURE5); + glBindTexture(GL_TEXTURE_2D, wallMetallicMap); + glActiveTexture(GL_TEXTURE6); + glBindTexture(GL_TEXTURE_2D, wallRoughnessMap); + glActiveTexture(GL_TEXTURE7); + glBindTexture(GL_TEXTURE_2D, wallAOMap); + + model = glm::mat4(); + model = glm::translate(model, glm::vec3(3.0, 0.0, 2.0)); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.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) + { + glm::vec3 newPos = lightPositions[i] + glm::vec3(sin(glfwGetTime() * 5.0) * 5.0, 0.0, 0.0); + newPos = lightPositions[i]; + glUniform3fv(glGetUniformLocation(pbrShader.Program, ("lightPositions[" + std::to_string(i) + "]").c_str()), 1, &newPos[0]); \ + glUniform3fv(glGetUniformLocation(pbrShader.Program, ("lightColors[" + std::to_string(i) + "]").c_str()), 1, &lightColors[i][0]); + + model = glm::mat4(); + model = glm::translate(model, newPos); + model = glm::scale(model, glm::vec3(0.5f)); + glUniformMatrix4fv(glGetUniformLocation(pbrShader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); + renderSphere(); + } + + // render skybox (render as last to prevent overdraw) + backgroundShader.Use(); + + glUniformMatrix4fv(glGetUniformLocation(backgroundShader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view)); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap); + //glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceMap); // display irradiance map + //glBindTexture(GL_TEXTURE_CUBE_MAP, prefilterMap); // display prefilter map + renderCube(); + + // render BRDF map to screen + //brdfShader.Use(); + //RenderQuad(); + + // Swap the buffers + glfwSwapBuffers(window); + } + + glfwTerminate(); + return 0; +} + + +// renders (and builds at first invocation) a sphere +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 positions; + std::vector uv; + std::vector normals; + std::vector 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); + 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) // 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 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); + } + } + 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) * 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))); + } + + glBindVertexArray(sphereVAO); + glDrawElements(GL_TRIANGLE_STRIP, indexCount, GL_UNSIGNED_INT, 0); +} +// RenderCube() Renders a 1x1 3D cube in NDC. +GLuint cubeVAO = 0; +GLuint cubeVBO = 0; +void renderCube() +{ + // Initialize (if necessary) + if (cubeVAO == 0) + { + GLfloat vertices[] = { + // Back face + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // Bottom-left + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,// top-left + // Front face + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left + // Left face + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right + // Right face + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left + // Bottom face + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom-left + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right + // Top face + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left + 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left + }; + glGenVertexArrays(1, &cubeVAO); + glGenBuffers(1, &cubeVBO); + // Fill buffer + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + // Link vertex attributes + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat))); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + // Render Cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); +} + +// RenderQuad() Renders a 1x1 XY quad in NDC +GLuint quadVAO = 0; +GLuint quadVBO; +void RenderQuad() +{ + if (quadVAO == 0) + { + GLfloat quadVertices[] = { + // Positions // Texture Coords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + // 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, 5 * sizeof(GLfloat), (GLvoid*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); + } + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + 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. +unsigned int loadTexture(char const * path) +{ + //Generate texture ID and load texture data + unsigned int textureID; + glGenTextures(1, &textureID); + int width, height, nrComponents; + unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0); + if (data) + { + GLenum format; + if (nrComponents == 1) + format = GL_RED; + else if (nrComponents == 3) + format = GL_RGB; + else if (nrComponents == 4) + format = GL_RGBA; + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + 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); + + stbi_image_free(data); + } + else + { + std::cout << "Texture failed to load at path: " << path << std::endl; + stbi_image_free(data); + } + + 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 \ No newline at end of file