From 99521f715aa128ffc2eb613994e8c9d5e1dad1b8 Mon Sep 17 00:00:00 2001 From: jaysmito Date: Sun, 12 Nov 2023 01:27:47 +0530 Subject: [PATCH] Fixed Slope bug, new gpu noise function --- .../generation/base_noise/noise_gen.glsl | 206 +++++++++++------- .../generation/base_shape/classic.glsl | 170 +++++++++------ TerraForge3D/include/Renderer/RendererSky.h | 8 +- .../Generators/BiomeBaseNoiseGenerator.cpp | 3 + TerraForge3D/src/Renderer/RendererSky.cpp | 18 +- 5 files changed, 250 insertions(+), 155 deletions(-) diff --git a/Binaries/Data/shaders/generation/base_noise/noise_gen.glsl b/Binaries/Data/shaders/generation/base_noise/noise_gen.glsl index 31f927609..85886c8e3 100644 --- a/Binaries/Data/shaders/generation/base_noise/noise_gen.glsl +++ b/Binaries/Data/shaders/generation/base_noise/noise_gen.glsl @@ -29,88 +29,130 @@ uniform int u_TransformFactor; uniform vec3 u_Offset; uniform float u_NoiseOctaveStrengths[16]; uniform int u_NoiseOctaveStrengthsCount; +uniform int u_SlopeSmoothingRadius; + +// +// GLSL textureless classic 3D noise "cnoise", +// with an RSL-style periodic variant "pnoise". +// Author: Stefan Gustavson (stefan.gustavson@liu.se) +// Version: 2011-10-11 +// +// Many thanks to Ian McEwan of Ashima Arts for the +// ideas for permutation and gradient selection. +// +// Copyright (c) 2011 Stefan Gustavson. All rights reserved. +// Distributed under the MIT license. See LICENSE file. +// https://github.com/stegu/webgl-noise +// + +vec3 mod289(vec3 x) +{ + return x - floor(x * (1.0 / 289.0)) * 289.0; +} -/* discontinuous pseudorandom uniformly distributed in [-0.5, +0.5]^3 */ -vec3 random3(vec3 c) +vec4 mod289(vec4 x) { - float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0))); - vec3 r; - r.z = fract(512.0*j); - j *= .125; - r.x = fract(512.0*j); - j *= .125; - r.y = fract(512.0*j); - return r-0.5; + return x - floor(x * (1.0 / 289.0)) * 289.0; } -/* skew constants for 3d simplex functions */ -const float F3 = 0.3333333; -const float G3 = 0.1666667; -// this is the 2d rotation matrix -// const mat2 rotationMat = mat2(cos(30.0), -sin(30.0), sin(30.0), cos(30.0)); -const mat2 rotationMat = mat2(0.86602540378, -0.5, 0.5, 0.86602540378); +vec4 permute(vec4 x) +{ + return mod289(((x * 34.0) + 10.0) * x); +} -/* 3d simplex noise */ -float simplex3d(vec3 p) +vec4 taylorInvSqrt(vec4 r) { - /* 1. find current tetrahedron T and it's four vertices */ - /* s, s+i1, s+i2, s+1.0 - absolute skewed (integer) coordinates of T vertices */ - /* x, x1, x2, x3 - unskewed coordinates of p relative to each of T vertices*/ - - /* calculate s and x */ - vec3 s = floor(p + dot(p, vec3(F3))); - vec3 x = p - s + dot(s, vec3(G3)); - - /* calculate i1 and i2 */ - vec3 e = step(vec3(0.0), x - x.yzx); - vec3 i1 = e*(1.0 - e.zxy); - vec3 i2 = 1.0 - e.zxy*(1.0 - e); - - /* x1, x2, x3 */ - vec3 x1 = x - i1 + G3; - vec3 x2 = x - i2 + 2.0*G3; - vec3 x3 = x - 1.0 + 3.0*G3; - - /* 2. find four surflets and store them in d */ - vec4 w, d; - - /* calculate surflet weights */ - w.x = dot(x, x); - w.y = dot(x1, x1); - w.z = dot(x2, x2); - w.w = dot(x3, x3); - - /* w fades from 0.6 at the center of the surflet to 0.0 at the margin */ - w = max(0.6 - w, 0.0); - - /* calculate surflet components */ - d.x = dot(random3(s), x); - d.y = dot(random3(s + i1), x1); - d.z = dot(random3(s + i2), x2); - d.w = dot(random3(s + 1.0), x3); - - /* multiply d by w^4 */ - w *= w; - w *= w; - d *= w; - - /* 3. return the sum of the four surflets */ - return dot(d, vec4(52.0)); + return 1.79284291400159 - 0.85373472095314 * r; } +vec3 fade(vec3 t) { + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} + +// Classic Perlin noise +float cnoise(vec3 P) +{ + vec3 Pi0 = floor(P); // Integer part for indexing + vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 + Pi0 = mod289(Pi0); + Pi1 = mod289(Pi1); + vec3 Pf0 = fract(P); // Fractional part for interpolation + vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 + vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); + vec4 iy = vec4(Pi0.yy, Pi1.yy); + vec4 iz0 = Pi0.zzzz; + vec4 iz1 = Pi1.zzzz; + + vec4 ixy = permute(permute(ix) + iy); + vec4 ixy0 = permute(ixy + iz0); + vec4 ixy1 = permute(ixy + iz1); + + vec4 gx0 = ixy0 * (1.0 / 7.0); + vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; + gx0 = fract(gx0); + vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); + vec4 sz0 = step(gz0, vec4(0.0)); + gx0 -= sz0 * (step(0.0, gx0) - 0.5); + gy0 -= sz0 * (step(0.0, gy0) - 0.5); + + vec4 gx1 = ixy1 * (1.0 / 7.0); + vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; + gx1 = fract(gx1); + vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); + vec4 sz1 = step(gz1, vec4(0.0)); + gx1 -= sz1 * (step(0.0, gx1) - 0.5); + gy1 -= sz1 * (step(0.0, gy1) - 0.5); + + vec3 g000 = vec3(gx0.x, gy0.x, gz0.x); + vec3 g100 = vec3(gx0.y, gy0.y, gz0.y); + vec3 g010 = vec3(gx0.z, gy0.z, gz0.z); + vec3 g110 = vec3(gx0.w, gy0.w, gz0.w); + vec3 g001 = vec3(gx1.x, gy1.x, gz1.x); + vec3 g101 = vec3(gx1.y, gy1.y, gz1.y); + vec3 g011 = vec3(gx1.z, gy1.z, gz1.z); + vec3 g111 = vec3(gx1.w, gy1.w, gz1.w); + + vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); + g000 *= norm0.x; + g010 *= norm0.y; + g100 *= norm0.z; + g110 *= norm0.w; + vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); + g001 *= norm1.x; + g011 *= norm1.y; + g101 *= norm1.z; + g111 *= norm1.w; + + float n000 = dot(g000, Pf0); + float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); + float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); + float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); + float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); + float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); + float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); + float n111 = dot(g111, Pf1); + + vec3 fade_xyz = fade(Pf0); + vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); + vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); + float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); + return 2.2 * n_xyz; +} + + uint PixelCoordToDataOffset(uint x, uint y) { return y * u_Resolution + x; } -float calculateSlopeFactor() +float calculateSlopeFactorAtCoord(uvec2 offsetv2) { - uvec2 offsetv2 = gl_GlobalInvocationID.xy; - - if ( offsetv2.x == 0) offsetv2.x = 1; - if ( offsetv2.y == 0) offsetv2.y = 1; - if ( offsetv2.x == u_Resolution - 1) offsetv2.x = u_Resolution - 2; - if ( offsetv2.y == u_Resolution - 1) offsetv2.y = u_Resolution - 2; + /* + if (offsetv2.x == 0) offsetv2.x = 1; + if (offsetv2.y == 0) offsetv2.y = 1; + if (offsetv2.x == u_Resolution - 1) offsetv2.x = u_Resolution - 2; + if (offsetv2.y == u_Resolution - 1) offsetv2.y = u_Resolution - 2; + */ float C = dataSource[PixelCoordToDataOffset(offsetv2.x, offsetv2.y)]; @@ -123,14 +165,30 @@ float calculateSlopeFactor() // calculate the slope factor - - // calculate the slope factor - float dX = (R - L) / 2.0f; float dY = (B - T) / 2.0f; - slopeFactor = sqrt(dX * dX + dY * dY); + slopeFactor = sqrt(dX * dX + dY * dY); + + return clamp(slopeFactor * u_Resolution, 0.0, 1.0); +} + +float calculateSlopeFactor() +{ + uvec2 offsetv2 = gl_GlobalInvocationID.xy; + + float factor = 0.0f; + + for (int i = -u_SlopeSmoothingRadius; i <= u_SlopeSmoothingRadius; i++) + { + for (int j = -u_SlopeSmoothingRadius; j <= u_SlopeSmoothingRadius; j++) + { + factor += calculateSlopeFactorAtCoord(offsetv2 + uvec2(i, j)); + } + } + + factor /= (u_SlopeSmoothingRadius * 2 + 1) * (u_SlopeSmoothingRadius * 2 + 1); - return slopeFactor * u_Resolution * 0.4; + return factor; } @@ -151,7 +209,7 @@ void main(void) float amplitude = 1.0f; for (int i = 0 ; i < u_NoiseOctaveStrengthsCount ; i++) { - n += simplex3d(seed * frequency) * amplitude * u_NoiseOctaveStrengths[i]; + n += cnoise(seed * frequency) * amplitude * u_NoiseOctaveStrengths[i]; frequency *= u_Lacunarity; amplitude *= u_Persistence; } diff --git a/Binaries/Data/shaders/generation/base_shape/classic.glsl b/Binaries/Data/shaders/generation/base_shape/classic.glsl index 007bfcee1..015162be3 100644 --- a/Binaries/Data/shaders/generation/base_shape/classic.glsl +++ b/Binaries/Data/shaders/generation/base_shape/classic.glsl @@ -53,84 +53,128 @@ } // CODE -/* discontinuous pseudorandom uniformly distributed in [-0.5, +0.5]^3 */ -vec3 random3(vec3 c) +// +// GLSL textureless classic 3D noise "cnoise", +// with an RSL-style periodic variant "pnoise". +// Author: Stefan Gustavson (stefan.gustavson@liu.se) +// Version: 2011-10-11 +// +// Many thanks to Ian McEwan of Ashima Arts for the +// ideas for permutation and gradient selection. +// +// Copyright (c) 2011 Stefan Gustavson. All rights reserved. +// Distributed under the MIT license. See LICENSE file. +// https://github.com/stegu/webgl-noise +// + +vec3 mod289(vec3 x) { - float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0))); - vec3 r; - r.z = fract(512.0*j); - j *= .125; - r.x = fract(512.0*j); - j *= .125; - r.y = fract(512.0*j); - return r-0.5; + return x - floor(x * (1.0 / 289.0)) * 289.0; } -/* skew constants for 3d simplex functions */ -const float F3 = 0.3333333; -const float G3 = 0.1666667; +vec4 mod289(vec4 x) +{ + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec4 permute(vec4 x) +{ + return mod289(((x * 34.0) + 10.0) * x); +} -/* 3d simplex noise */ -float simplex3d(vec3 p) +vec4 taylorInvSqrt(vec4 r) { - /* 1. find current tetrahedron T and it's four vertices */ - /* s, s+i1, s+i2, s+1.0 - absolute skewed (integer) coordinates of T vertices */ - /* x, x1, x2, x3 - unskewed coordinates of p relative to each of T vertices*/ - - /* calculate s and x */ - vec3 s = floor(p + dot(p, vec3(F3))); - vec3 x = p - s + dot(s, vec3(G3)); - - /* calculate i1 and i2 */ - vec3 e = step(vec3(0.0), x - x.yzx); - vec3 i1 = e*(1.0 - e.zxy); - vec3 i2 = 1.0 - e.zxy*(1.0 - e); - - /* x1, x2, x3 */ - vec3 x1 = x - i1 + G3; - vec3 x2 = x - i2 + 2.0*G3; - vec3 x3 = x - 1.0 + 3.0*G3; - - /* 2. find four surflets and store them in d */ - vec4 w, d; - - /* calculate surflet weights */ - w.x = dot(x, x); - w.y = dot(x1, x1); - w.z = dot(x2, x2); - w.w = dot(x3, x3); - - /* w fades from 0.6 at the center of the surflet to 0.0 at the margin */ - w = max(0.6 - w, 0.0); - - /* calculate surflet components */ - d.x = dot(random3(s), x); - d.y = dot(random3(s + i1), x1); - d.z = dot(random3(s + i2), x2); - d.w = dot(random3(s + 1.0), x3); - - /* multiply d by w^4 */ - w *= w; - w *= w; - d *= w; - - /* 3. return the sum of the four surflets */ - return dot(d, vec4(52.0)); + return 1.79284291400159 - 0.85373472095314 * r; +} + +vec3 fade(vec3 t) { + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); } +// Classic Perlin noise +float cnoise(vec3 P) +{ + vec3 Pi0 = floor(P); // Integer part for indexing + vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 + Pi0 = mod289(Pi0); + Pi1 = mod289(Pi1); + vec3 Pf0 = fract(P); // Fractional part for interpolation + vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 + vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); + vec4 iy = vec4(Pi0.yy, Pi1.yy); + vec4 iz0 = Pi0.zzzz; + vec4 iz1 = Pi1.zzzz; + + vec4 ixy = permute(permute(ix) + iy); + vec4 ixy0 = permute(ixy + iz0); + vec4 ixy1 = permute(ixy + iz1); + + vec4 gx0 = ixy0 * (1.0 / 7.0); + vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; + gx0 = fract(gx0); + vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); + vec4 sz0 = step(gz0, vec4(0.0)); + gx0 -= sz0 * (step(0.0, gx0) - 0.5); + gy0 -= sz0 * (step(0.0, gy0) - 0.5); + + vec4 gx1 = ixy1 * (1.0 / 7.0); + vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; + gx1 = fract(gx1); + vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); + vec4 sz1 = step(gz1, vec4(0.0)); + gx1 -= sz1 * (step(0.0, gx1) - 0.5); + gy1 -= sz1 * (step(0.0, gy1) - 0.5); + + vec3 g000 = vec3(gx0.x, gy0.x, gz0.x); + vec3 g100 = vec3(gx0.y, gy0.y, gz0.y); + vec3 g010 = vec3(gx0.z, gy0.z, gz0.z); + vec3 g110 = vec3(gx0.w, gy0.w, gz0.w); + vec3 g001 = vec3(gx1.x, gy1.x, gz1.x); + vec3 g101 = vec3(gx1.y, gy1.y, gz1.y); + vec3 g011 = vec3(gx1.z, gy1.z, gz1.z); + vec3 g111 = vec3(gx1.w, gy1.w, gz1.w); + + vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); + g000 *= norm0.x; + g010 *= norm0.y; + g100 *= norm0.z; + g110 *= norm0.w; + vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); + g001 *= norm1.x; + g011 *= norm1.y; + g101 *= norm1.z; + g111 *= norm1.w; + + float n000 = dot(g000, Pf0); + float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); + float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); + float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); + float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); + float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); + float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); + float n111 = dot(g111, Pf1); + + vec3 fade_xyz = fade(Pf0); + vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); + vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); + float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); + return 2.2 * n_xyz; +} + + float evaluateBaseShape(vec2 uv, vec3 seed) { seed += u_Offset; float n = 0.0f; for(int i = 0 ; i < u_Levels ; i++) { - seed = vec3(simplex3d(seed + vec3(0.0f, 0.0f, 0.0f)), - simplex3d(seed + vec3(1.0f, 2.0f, 0.0f)), - simplex3d(seed + vec3(3.0f, 4.0f, 0.0f)) + seed = vec3(cnoise(seed + vec3(0.0f, 0.0f, 0.0f)), + cnoise(seed + vec3(1.0f, 2.0f, 0.0f)), + cnoise(seed + vec3(3.0f, 4.0f, 0.0f)) ); } - n = simplex3d(seed * u_Scale + vec3(u_Seed)); + n = cnoise(seed * u_Scale + vec3(u_Seed)); if(u_AbsoluteValue) n = abs(n); if(u_SquareValue) n = n * n; - return n * u_Strength; + return n * u_Strength * 10.0f; } \ No newline at end of file diff --git a/TerraForge3D/include/Renderer/RendererSky.h b/TerraForge3D/include/Renderer/RendererSky.h index 4e9c210fe..c9aa045e7 100644 --- a/TerraForge3D/include/Renderer/RendererSky.h +++ b/TerraForge3D/include/Renderer/RendererSky.h @@ -24,10 +24,10 @@ class RendererSky private: ApplicationState* m_AppState = nullptr; - ComputeShader* m_EquirectToCube = nullptr; - ComputeShader* m_SpecularMap = nullptr; - ComputeShader* m_IrradianceMap = nullptr; - Shader* m_SkyboxShader = nullptr; + std::shared_ptr m_EquirectToCube = nullptr; + std::shared_ptr m_SpecularMap = nullptr; + std::shared_ptr m_IrradianceMap = nullptr; + std::shared_ptr m_SkyboxShader = nullptr; Model* m_SkyboxModel = nullptr; bool m_RenderSky = false; bool m_IsSkyReady = false; diff --git a/TerraForge3D/src/Generators/BiomeBaseNoiseGenerator.cpp b/TerraForge3D/src/Generators/BiomeBaseNoiseGenerator.cpp index 5fb82e306..8bb064b53 100644 --- a/TerraForge3D/src/Generators/BiomeBaseNoiseGenerator.cpp +++ b/TerraForge3D/src/Generators/BiomeBaseNoiseGenerator.cpp @@ -49,6 +49,8 @@ BiomeBaseNoiseGenerator::BiomeBaseNoiseGenerator(ApplicationState* appState) m_Inspector->AddIntegerVariable("TransformFactor", 1); m_Inspector->AddDropdownWidget("Transform Factor", "TransformFactor", { "None", "Slope", "Height" }); + m_Inspector->AddIntegerVariable("SlopeSmoothingRadius", 3); + m_Inspector->AddSliderWidget("Slope Smoothing Radius", "SlopeSmoothingRadius", 0, 10).SetRenderOnCondition("TransformFactor", 1); } } @@ -102,6 +104,7 @@ void BiomeBaseNoiseGenerator::Update(GeneratorData* sourceBuffer, GeneratorData* m_Shader->SetUniform3f("u_Offset", values.at("Offset").GetVector3()); m_Shader->SetUniform1i("u_MixMethod", values.at("MixMethod").GetInt()); m_Shader->SetUniform1i("u_TransformFactor", values.at("TransformFactor").GetInt()); + m_Shader->SetUniform1i("u_SlopeSmoothingRadius", values.at("SlopeSmoothingRadius").GetInt()); m_Shader->SetUniform1i("u_Seed", values.at("Seed").GetInt()); for (int i = 0; i < BIOME_BASE_NOISE_OCTAVE_COUNT; i++) { diff --git a/TerraForge3D/src/Renderer/RendererSky.cpp b/TerraForge3D/src/Renderer/RendererSky.cpp index e6451f1b8..fd08d5cfa 100644 --- a/TerraForge3D/src/Renderer/RendererSky.cpp +++ b/TerraForge3D/src/Renderer/RendererSky.cpp @@ -20,11 +20,7 @@ RendererSky::~RendererSky() { if (m_SkyboxTextureID > -1) glDeleteTextures(1, &m_SkyboxTextureID); if (m_IrradianceMapTextureID > -1) glDeleteTextures(1, &m_IrradianceMapTextureID); - delete m_EquirectToCube; - delete m_SpecularMap; - delete m_IrradianceMap; delete m_SkyboxModel; - delete m_SkyboxShader; } void RendererSky::ShowSettings() @@ -57,16 +53,10 @@ void RendererSky::Render(RendererViewport* viewport) void RendererSky::ReloadShaders() { - if (m_EquirectToCube) delete m_EquirectToCube; - if (m_SpecularMap) delete m_SpecularMap; - if (m_IrradianceMap) delete m_IrradianceMap; - bool tmp = false; - m_EquirectToCube = new ComputeShader(ReadShaderSourceFile(m_AppState->constants.shadersDir + PATH_SEPARATOR "equirect_to_cube" PATH_SEPARATOR "compute.glsl", &tmp)); - m_SpecularMap = new ComputeShader(ReadShaderSourceFile(m_AppState->constants.shadersDir + PATH_SEPARATOR "sky_specular_map" PATH_SEPARATOR "compute.glsl", &tmp)); - m_IrradianceMap = new ComputeShader(ReadShaderSourceFile(m_AppState->constants.shadersDir + PATH_SEPARATOR "sky_irradiance_map" PATH_SEPARATOR "compute.glsl", &tmp)); - m_SkyboxShader = new Shader( - ReadShaderSourceFile(m_AppState->constants.shadersDir + PATH_SEPARATOR "skybox" PATH_SEPARATOR "vert.glsl", &tmp), - ReadShaderSourceFile(m_AppState->constants.shadersDir + PATH_SEPARATOR "skybox" PATH_SEPARATOR "frag.glsl", &tmp)); + m_EquirectToCube = m_AppState->resourceManager->LoadComputeShader("equirect_to_cube/compute", true); + m_SpecularMap = m_AppState->resourceManager->LoadComputeShader("sky_specular_map/compute", true); + m_IrradianceMap = m_AppState->resourceManager->LoadComputeShader("sky_irradiance_map/compute", true); + m_SkyboxShader = m_AppState->resourceManager->LoadShader("skybox", true); } // From : https://github.com/Nadrin/PBR/blob/master/src/opengl.cpp