官方文章
gamma校正概念
- 一个渐变的效果
- 通过以下网站调整Gamma值可以观察到效果
色彩管理网
gamma校正
Gamma校正(Gamma Correction)的思路是在最终的颜色输出上应用监视器Gamma的倒数。回头看前面的Gamma曲线图,你会有一个短划线,它是监视器Gamma曲线的翻转曲线。我们在颜色显示到监视器的时候把每个颜色输出都加上这个翻转的Gamma曲线,这样应用了监视器Gamma以后最终的颜色将会变为线性的。我们所得到的中间色调就会更亮,所以虽然监视器使它们变暗,但是我们又将其平衡回来了。
我们来看另一个例子。还是那个暗红色(0.5,0.0,0.0)。在将颜色显示到监视器之前,我们先对颜色应用Gamma校正曲线。线性的颜色显示在监视器上相当于降低了2.2次幂的亮度,所以倒数就是1/2.2次幂。Gamma校正后的暗红色就会成为(0.5,0.0,0.0)1/2.2=(0.5,0.0,0.0)0.45=(0.73,0.0,0.0)。校正后的颜色接着被发送给监视器,最终显示出来的颜色是(0.73,0.0,0.0)2.2=(0.5,0.0,0.0)。你会发现使用了Gamma校正,监视器最终会显示出我们在应用中设置的那种线性的颜色。
有两种在你的场景中应用gamma校正的方式:
使用OpenGL内建的sRGB帧缓冲。 自己在像素着色器中进行gamma校正。 第一个选项也许是最简单的方式,但是我们也会丧失一些控制权。开启GL_FRAMEBUFFER_SRGB,可以告诉OpenGL每个后续的绘制命令里,在颜色储存到颜色缓冲之前先校正sRGB颜色。sRGB这个颜色空间大致对应于gamma2.2,它也是家用设备的一个标准。开启GL_FRAMEBUFFER_SRGB以后,每次像素着色器运行后续帧缓冲,OpenGL将自动执行gamma校正,包括默认帧缓冲。
开启GL_FRAMEBUFFER_SRGB简单的调用glEnable就行:
glEnable(GL_FRAMEBUFFER_SRGB);
自此,你渲染的图像就被进行gamma校正处理,你不需要做任何事情硬件就帮你处理了。有时候,你应该记得这个建议:gamma校正将把线性颜色空间转变为非线性空间,所以在最后一步进行gamma校正是极其重要的。如果你在最后输出之前就进行gamma校正,所有的后续操作都是在操作不正确的颜色值。例如,如果你使用多个帧缓冲,你可能打算让两个帧缓冲之间传递的中间结果仍然保持线性空间颜色,只是给发送给监视器的最后的那个帧缓冲应用gamma校正。
sRGB纹理(要看官方文档)
因为监视器总是在sRGB空间中显示应用了gamma的颜色,无论什么时候当你在计算机上绘制、编辑或者画出一个图片的时候,你所选的颜色都是根据你在监视器上看到的那种。这实际意味着所有你创建或编辑的图片并不是在线性空间,而是在sRGB空间中(译注:sRGB空间定义的gamma接近于2.2),假如在你的屏幕上对暗红色翻一倍,便是根据你所感知到的亮度进行的,并不等于将红色元素加倍。
衰减
在使用了gamma校正之后,另一个不同之处是光照衰减(Attenuation)。真实的物理世界中,光照的衰减和光源的距离的平方成反比。
// simple attenuation
float max_distance = 1.5;
float distance = length(lightPos - fragPos);
float attenuation = 1.0 / (gamma ? distance * distance : distance);
gamma校正代码(要看官方文档)
-
顶点着色器:顶点着色器:VAO获取顶点数据,接口块传递值,缓冲取值,绘制图形
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; out VS_OUT { vec3 FragPos; vec3 Normal; vec2 TexCoords; } vs_out; uniform mat4 projection; uniform mat4 view; void main() { vs_out.FragPos = aPos; vs_out.Normal = aNormal; vs_out.TexCoords = aTexCoords; gl_Position = projection * view * vec4(aPos, 1.0); }
-
片段着色器:
#version 330 core out vec4 FragColor; in VS_OUT { vec3 FragPos; vec3 Normal; vec2 TexCoords; } fs_in; uniform sampler2D floorTexture; uniform vec3 lightPositions[4]; uniform vec3 lightColors[4]; uniform vec3 viewPos; uniform bool gamma; vec3 BlinnPhong(vec3 normal, vec3 fragPos, vec3 lightPos, vec3 lightColor) { // diffuse vec3 lightDir = normalize(lightPos - fragPos); float diff = max(dot(lightDir, normal), 0.0); vec3 diffuse = diff * lightColor; // specular vec3 viewDir = normalize(viewPos - fragPos); vec3 reflectDir = reflect(-lightDir, normal); float spec = 0.0; vec3 halfwayDir = normalize(lightDir + viewDir); spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0); vec3 specular = spec * lightColor; // 衰减 float max_distance = 1.5; float distance = length(lightPos - fragPos); float attenuation = 1.0 / (gamma ? distance * distance : distance);//修改光照的gamma效果 diffuse *= attenuation; specular *= attenuation; return diffuse + specular; } void main() { vec3 color = texture(floorTexture, fs_in.TexCoords).rgb; vec3 lighting = vec3(0.0); for(int i = 0; i < 4; ++i) lighting += BlinnPhong(normalize(fs_in.Normal), fs_in.FragPos, lightPositions[i], lightColors[i]); color *= lighting; //比较不同的加载纹理方式 //选用的纹理是一样的但是透明值是发生改变的 if(gamma) color = pow(color, vec3(1.0/2.2)); FragColor = vec4(color, 1.0); }
-
主函数:
-
修改输入函数按“空格键”改变gamma传值(通过不同的纹理传递效果:SRGB和RGB,又因为SRGB的时候值发生改变所以在片段着色器中才有了if(gamma)中的内容)
void processInput(GLFWwindow* window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.ProcessKeyboard(FORWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(BACKWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(LEFT, deltaTime); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(RIGHT, deltaTime); if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS && !gammaKeyPressed) { gammaEnabled = !gammaEnabled; gammaKeyPressed = true; } if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_RELEASE) { gammaKeyPressed = false; } }
-
纹理加载函数:gamma为true则图片都想下拱了一次从0.5-0.2,是否进行gamma校正,可查看官方文档
// utility function for loading a 2D texture from file // --------------------------------------------------- unsigned int loadTexture(char const* path, bool gammaCorrection) { unsigned int textureID; glGenTextures(1, &textureID); int width, height, nrComponents; unsigned char* data = stbi_load(path, &width, &height, &nrComponents, 0); if (data) { GLenum internalFormat; GLenum dataFormat; if (nrComponents == 1) { internalFormat = dataFormat = GL_RED; } //以下都进行了gamma判断到底是加载SRGB还是RGB else if (nrComponents == 3) { internalFormat = gammaCorrection ? GL_SRGB : GL_RGB; dataFormat = GL_RGB; } //GL_SRGB_ALPHA 还是GL_RGBA else if (nrComponents == 4) { internalFormat = gammaCorrection ? GL_SRGB_ALPHA : GL_RGBA; dataFormat = GL_RGBA; } glBindTexture(GL_TEXTURE_2D, textureID); glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, dataFormat, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); 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; }
-
while循环:注意光照的相关信息是一个数组,所以传值需要用到uniform
// load textures // ------------- unsigned int floorTexture = loadTexture("resources/textures/wood.png", false); unsigned int floorTextureGammaCorrected = loadTexture("resources/textures/wood.png", true); .... .... .... // render loop // ----------- while (!glfwWindowShouldClose(window)) { // per-frame time logic // -------------------- . . . . . // draw objects shader.useShader(); glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); shader.setMat4("projection", projection); shader.setMat4("view", view); // set light uniforms glUniform3fv(glGetUniformLocation(shader.ID, "lightPositions"), 4, &lightPositions[0][0]); glUniform3fv(glGetUniformLocation(shader.ID, "lightColors"), 4, &lightColors[0][0]); shader.setVec3("viewPos", camera.Position); shader.setInt("gamma", gammaEnabled); // floor glBindVertexArray(planeVAO); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, gammaEnabled ? floorTextureGammaCorrected : floorTexture); glDrawArrays(GL_TRIANGLES, 0, 6); std::cout << (gammaEnabled ? "Gamma enabled" : "Gamma disabled") << std::endl; // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) // ------------------------------------------------------------------------------- glfwSwapBuffers(window); glfwPollEvents(); }
-
效果
关闭
开启