由于显示器只能显示值为0.0到1.0间的颜色,因此当数据存储在帧缓冲(Framebuffer)中时,亮度和颜色的值也是默认被限制在0.0到1.0之间的,这个颜色范围即是LDR(Low Dynamic Range)低动态范围
如果光源足够的多,越或者某个光源亮度非常的高,那么对当下的环境,就有可能遇到这样的情况:
图中的纹理已经变得难以分辨了,丢失了特别多的细节:在计算片段亮度的过程中,这些片段中超过1.0的亮度或者颜色值都会被约束在1.0,这样一大段的区域都会是单纯的白色
一个很容易想到的解决方法是:调整光照强度,但是这个太过于被动,并且一旦使用了不切实际的光照参数,当光源的数量发生变化时就可能需要再次进行调整,所以这个方法就不要再考虑了
正确的方法是:在计算光照时使用更高的颜色范围HDR(High Dynamic Range),也就是高动态范围,需要显示时再将所有HDR值转换成在[0.0, 1.0]范围内的LDR值。这种转换HDR值到LDR值得过程也叫做色调映射(Tone Mapping)
HDR原本只是被运用在摄影上,摄影师对同一个场景采取不同曝光拍多张照片,捕捉大范围的色彩值。这些图片被合成为HDR图片,从而综合不同的曝光等级使得大范围的细节可见
一个非常完美的HDR&映射算法可以使得亮的东西非常亮,暗的东西非常暗,并且充满细节
当帧缓冲使用了一个标准化的定点格式(例如之前的GL_RGB)为其颜色缓冲的内部格式时,OpenGL就会在将这些值存入帧缓冲前自动将其约束到0.0到1.0之间,而当一个帧缓冲的颜色缓冲的内部格式被设定成了 GL_RGB16F、GL_RGBA16F、GL_RGB32F 或者 GL_RGBA32F 时,这些帧缓冲就被叫做浮点帧缓冲(Floating Point Framebuffer),浮点帧缓冲可以存储超过0.0到1.0范围的浮点值,所以非常适合HDR渲染
想要创建一个浮点帧缓冲,只需要改变颜色缓冲的内部格式参数就可以了
glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, WIDTH, HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
到此第一步就搞定了,可以放心渲染场景到这个帧缓冲中
如果对帧缓冲不了解,可以回去参考《OpenGL基础34:帧缓冲(中)之附件》和 《OpenGL基础33:帧缓冲(上)之离屏渲染》,除了上面的颜色缓冲内部格式参数要改下外,写法没有任何变化
完整的主代码:
#include
#include
#define GLEW_STATIC
#include
#include"Shader.h"
#include"Camera.h"
#include"Light.h"
#include
#include
#include
#include"Mesh.h"
#include"Model.h"
#include
#include
bool keys[1024];
Camera camera;
GLfloat lastX, lastY;
bool firstMouse = true;
bool openSpotLight = true;
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
GLuint loadCubemap(vector faces);
GLuint getAttachmentTexture();
GLuint getMultiSampleTexture(GLuint samples); //MSAA几重采样
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
GLuint loadTexture(const GLchar* path, GLboolean alpha);
void cameraMove();
const GLuint WIDTH = 800, HEIGHT = 600;
int main()
{
//------------------------------------------------------初始化---------------------------------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glewExperimental = GL_TRUE;
glewInit();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
Shader shaderObj("ObjVShader.vert", "ObjFShader.frag", "ObjGShader.geom");
Shader shaderObjToDebug("ObjNormalVShader.vert", "ObjNormalFShader.frag", "ObjNormalGShader.geom");
Shader shaderObjWithoutTex("ObjVShaderWithoutTex.vert", "ObjFShaderWithoutTex.frag");
Shader shaderLight("LightVShader.vert", "LightFShader.frag");
Shader shaderSkyBox("SkyboxVShader.vert", "SkyboxFShader.frag");
Shader shaderScreen("ScreenVShader.vert", "ScreenFShader.frag");
//------------------------------------------------------数据定义---------------------------------------------------------
glm::vec3 positions[] =
{
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 0.89f, 0.0f),
glm::vec3(0.0f, 1.78f, 0.0f),
glm::vec3(-2.0f, 0.0f, 0.0f),
glm::vec3(-2.0f, 0.89f, 0.0f),
glm::vec3(-3.0f, 0.0f, 0.0f),
glm::vec3(-2.0f, 0.0f, 1.0f),
glm::vec3(-1.0f, 0.0f, 4.0f),
};
glm::vec3 wallPositions[] =
{
glm::vec3(-1, 0.3, -0.802),
glm::vec3(-5.07, 0.3, -0.8),
glm::vec3(3.07, 0.3, -0.8),
};
glm::vec3 pointLightPositions[] =
{
glm::vec3(-4.0f, 2.6f, -0.25f),
glm::vec3(1000000.0f, 0.8f, 2.0f),
glm::vec3(1000000.0f, 0.8f, 1.0f),
};
GLfloat quadVertices[] =
{
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
GLfloat skyboxVertices[] =
{
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f
};
//------------------------------------------------------VAO & VBO---------------------------------------------------------
GLuint quadVAO, quadVBO;
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
GLuint skyboxVAO, skyboxVBO;
glGenVertexArrays(1, &skyboxVAO);
glGenBuffers(1, &skyboxVBO);
glBindVertexArray(skyboxVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//------------------------------------------------------UBO---------------------------------------------------------
GLuint UBO;
glGenBuffers(1, &UBO);
glBindBuffer(GL_UNIFORM_BUFFER, UBO);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferRange(GL_UNIFORM_BUFFER, 0, UBO, 0, 2 * sizeof(glm::mat4));
//------------------------------------------------------帧缓冲---------------------------------------------------------
GLuint FBO, RBO;
glGenFramebuffers(1, &FBO);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);
GLuint textureColorBuffer = getAttachmentTexture();
glGenRenderbuffers(1, &RBO);
glBindRenderbuffer(GL_RENDERBUFFER, RBO);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, WIDTH, HEIGHT);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorBuffer, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, RBO);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
//------------------------------------------------------设置状态、加载模型和天空盒---------------------------------------------------------
glEnable(GL_CULL_FACE);
glEnable(GL_MULTISAMPLE);
glEnable(GL_STENCIL_TEST);
glEnable(GL_DEPTH_TEST);
glEnable(GL_PROGRAM_POINT_SIZE);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); //模板测试和深度测试都成功时,将对应像素的模板值设置为用glStencilFunc函数设置的ref值
glEnable(GL_BLEND);
glEnable(GL_PROGRAM_POINT_SIZE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Model wood("Object/wood/file.fbx", "Object/wood/file.fbx");
Model ground("Object/ground/ground.fbx", "Object/ground");
Model wall("Object/wall/stonewall.fbx", "Object/wall");
Model lightObj("Object/light/file.fbx", "Object/light");
vector faces;
faces.push_back("Texture/Skybox/StarryNight1024/Right.jpg");
faces.push_back("Texture/Skybox/StarryNight1024/Left.jpg");
faces.push_back("Texture/Skybox/StarryNight1024/Up.jpg");
faces.push_back("Texture/Skybox/StarryNight1024/Down.jpg");
faces.push_back("Texture/Skybox/StarryNight1024/Back.jpg");
faces.push_back("Texture/Skybox/StarryNight1024/Front.jpg");
GLuint cubemapTexture = loadCubemap(faces);
//------------------------------------------------------实例化---------------------------------------------------------
glm::mat4* modelMatrices;
modelMatrices = new glm::mat4[1000];
glm::mat4 model = glm::mat4(1.0f);
for (GLuint i = 0; i <= 7; i++)
{
model = glm::translate(glm::mat4(1.0f), positions[i]);
model = glm::scale(model, glm::vec3(0.01f));
modelMatrices[i] = model;
}
wood.UpdateModelMatrix(modelMatrices, 8);
GLint groundIndex = 0;
for (int i = -1; i <= 2; i++)
{
for (int j = 0; j <= 2; j++)
{
model = glm::translate(glm::mat4(1.0f), glm::vec3(i * 3.52f, -0.05f, j * 3.72f));
model = glm::scale(model, glm::vec3(0.1f));
model = glm::rotate(model, glm::radians(90.0f), glm::vec3(-1.0f, 0.0f, 0.0f));
modelMatrices[groundIndex++] = model;
}
}
ground.UpdateModelMatrix(modelMatrices, groundIndex);
for (GLuint i = 0; i <= 2; i++)
{
model = glm::translate(glm::mat4(1.0f), wallPositions[i]);
model = glm::scale(model, glm::vec3(0.15, 0.3, 0.15));
model = glm::rotate(model, glm::radians(90.0f), glm::vec3(-1.0f, 0.0f, 0.0f));
modelMatrices[i] = model;
}
wall.UpdateModelMatrix(modelMatrices, 3);
model = glm::translate(glm::mat4(1.0f), glm::vec3(-4.0f, 2.3f, -0.25f));
model = glm::scale(model, glm::vec3(0.015, 0.015, 0.015));
model = glm::rotate(model, glm::radians(90.0f), glm::vec3(-1.0f, 0.0f, 0.0f));
modelMatrices[0] = model;
lightObj.UpdateModelMatrix(modelMatrices, 1);
delete[] modelMatrices;
Light light;
light.AddSunLight(glm::vec3(-0.2f, -1.0f, -0.3f), glm::vec3(0.03f, 0.03f, 0.03f), glm::vec3(0.036f, 0.036f, 0.036f));
for (int i = 0; i <= 2; i++)
light.AddPointLight(glm::vec3(pointLightPositions[i].x, pointLightPositions[i].y, pointLightPositions[i].z), glm::vec3(0.6, 0.6, 0.6), glm::vec3(0.45, 0.45, 0.45));
light.AddSpotLight(glm::vec3(camera.Position.x, camera.Position.y, camera.Position.z), glm::vec3(camera.Front.x, camera.Front.y, camera.Front.z),
glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f));
//------------------------------------------------------渲染ing---------------------------------------------------------
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
glBindFramebuffer(GL_FRAMEBUFFER, FBO);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glStencilMask(0xFF); //设置模板缓冲区可写入,如果设置为不可写入之后清空模板缓冲区,将会清空失败!毕竟不可写入了
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
cameraMove();
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);
glBindBuffer(GL_UNIFORM_BUFFER, UBO);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(view));
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(projection));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glStencilFunc(GL_ALWAYS, 0xFF, 0xFF);
shaderObj.Use();
light.spotLight.clear();
if (openSpotLight)
{
light.AddSpotLight(glm::vec3(camera.Position.x, camera.Position.y, camera.Position.z), glm::vec3(camera.Front.x, camera.Front.y, camera.Front.z),
glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f));
}
light.AppAllLightToShader(shaderObj.Program);
glUniform3f(glGetUniformLocation(shaderObj.Program, "viewPos"), camera.Position.x, camera.Position.y, camera.Position.z);
wall.Draw(shaderObj, 3);
wood.Draw(shaderObj, 8);
ground.Draw(shaderObj, groundIndex + 1);
lightObj.Draw(shaderObj, 1);
shaderSkyBox.Use();
glDepthFunc(GL_LEQUAL);
view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
glUniformMatrix4fv(glGetUniformLocation(shaderSkyBox.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(shaderSkyBox.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glBindVertexArray(skyboxVAO);
glActiveTexture(GL_TEXTURE1);
glUniform1i(glGetUniformLocation(shaderSkyBox.Program, "skybox"), 1);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthFunc(GL_LESS);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
shaderScreen.Use();
glBindVertexArray(quadVAO);
glActiveTexture(GL_TEXTURE1);
glUniform1i(glGetUniformLocation(shaderScreen.Program, "screenTexture"), 1);
glBindTexture(GL_TEXTURE_2D, textureColorBuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glfwSwapBuffers(window);
}
//------------------------------------------------------解绑---------------------------------------------------------
glDeleteFramebuffers(1, &FBO);
glfwTerminate();
return 0;
}
GLuint getMultiSampleTexture(GLuint samples)
{
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, WIDTH, HEIGHT, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
return texture;
}
GLuint loadCubemap(vector faces)
{
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image;
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
for (GLuint i = 0; i < faces.size(); i++)
{
image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
SOIL_free_image_data(image);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
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);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
return textureID;
}
GLuint getAttachmentTexture()
{
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, WIDTH, HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
return textureID;
}
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
void cameraMove()
{
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
GLfloat cameraSpeed = 1.0f * deltaTime;
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(Camera_Movement(FORWARD), deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(Camera_Movement(BACKWARD), deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(Camera_Movement(LEFT), deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(Camera_Movement(RIGHT), deltaTime);
}
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 == GLFW_KEY_TAB && action == GLFW_PRESS)
openSpotLight = !openSpotLight;
if (action == GLFW_PRESS) //如果当前是按下操作
keys[key] = true;
else if (action == GLFW_RELEASE) //松开键盘
keys[key] = false;
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
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;
GLfloat sensitivity = 0.05;
xoffset *= sensitivity;
yoffset *= sensitivity;
camera.ProcessMouseMovement(xoffset, yoffset);
}
联动《OpenGL基础45:光照矫正(下)之Gamma矫正》:
在进行Gamma矫正的时候,或许可能遇到过这样的渲染错误:
可以看出天空盒的纹理出现了“断层”:出现这种情况的原因是在定点格式的帧缓冲中进行Gamma重校时,颜色值不但会被限制在[0, 1]之间,还会出现精度丢失,从而在默认帧缓冲中再次进行Gamma矫正时,整张纹理就变得“粗糙”了,对于颜色比较深的物体或者纹理复杂的物体可能还不怎么能看出来,但是对于天空盒这种单色调的就非常明显
因此,最好是在浮点帧缓冲中进行Gamma重校,或者能有方法保证精度(例如在同一帧缓冲中直接完成重校和校正)
接下来就是色调映射(Tone Mapping)了,它能将HDR颜色值转入[0, 1]范围内以正确显示在屏幕上,色调映射的算法很多,最终效果的风格也不同,但通常都会伴有特定的风格的色平衡(Stylistic Color Balance),也就是同时校正图像色偏、过饱和或饱和度不足的情况
和《OpenGL基础35:帧缓冲(下)之简单图像处理》一样,也可以将色调映射理解为一种特殊的图像处理方法
Reinhard色调映射:
最简单的映射算法,平均地将所有亮度值分散到LDR上:
曝光(Exposure)度色调映射算法:
HDR图片包含在不同曝光等级的细节,如果有一个场景要展现日夜交替,往往会在白天使用低曝光,在夜间使用高曝光,就像人眼调节方式一样,随着曝光度越来越低,高光部分的细节越来越清晰,当然,低光部分的细节就越来越模糊了
对应的着色器代码如下(带上了Gamma矫正):
#version 330 core
in vec2 texIn;
out vec4 color;
uniform sampler2D screenTexture;
const float offset = 1.0 / 500;
void main()
{
//可以在这里计算Kernel矩阵
vec3 result = vec3(1.0) - exp(-col);
result = pow(result, vec3(1.0 / 2.2));
color = vec4(result, 1.0);
}
更好/更多的色调映射算法:
这些算法各有长短,一些色调映射算法倾向于特定的某种颜色/强度,也有一些算法同时显示低高曝光颜色从而能够显示更加多彩和精细的图像,也有一些技巧被称作自动曝光调整(Automatic Exposure Adjustment)或是人眼适应(Eye Adaptation)技术,它能够检测前一帧场景的亮度并且缓慢调整曝光参数以模仿人眼,使得场景在黑暗区域逐渐变亮、在明亮区域逐渐变暗
当然上面提到的最简单的两个色调映射算法,已经可以起到一个还算不错的效果了
泛光:
可以利用高斯模糊使得光源带有泛光的效果以更加的真实,当然了:在筛出所有的光源前可以进行色调映射以保证颜色值为1的点一定是光源
HDR和抗锯齿的冲突:
可能会有比较少的一部分游戏玩家会遇到这样的问题:有些游戏/电脑无法同时应用HDR和抗锯齿
NV采用了OpenEXR做为HDR运算的缓存格式,GeForce 6/7系列显卡都提供了对OpenEXR的16位浮点(FP16)贴图、过滤、混合、存储支持(即HDR),然而在DX9C模式下运行FP16时,会占用到原本属于MSAA(多重采样抗锯齿)的缓冲区域,使得在开启HDR效果后无法进行AA处理。这也就是为什么几乎所有的FP16游戏无法同时支持FSAA和HDR的原因,如果强行打开FP16 HDR+AA则会出现贴图破碎混乱的问题
这个只需要了解以下就好,不必要深究