官方文章
gamma校正
gamma校正概念
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI2EzX4xSZz91ZsAzNfRHLGZkRGZkRfJ3bs92YsAjMfVmepNHL4dVW2ElMhVDOHJmNsdVN1w2V1UTQClGVF5UMR9Fd4VGdsATNfd3bkFGazxycykFaKdkYzZUbapXNXlleSdVY2pESa9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLjVWM5MWZ1UDM0IGNhBjMihDMjRTMkRTN0MDO0AjY3YzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
- 一個漸變的效果
進階opengl之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就行:
自此,你渲染的圖像就被進行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(); }
- 效果
關閉
開啟