①HDRFBO分别有2个浮点纹理,其中一个纹理捆绑到第一个颜色缓冲区绑定点,第二个纹理绑定到第二个颜色缓冲区绑定点,它们的内容都交给texReplaceProg着色器(basic.vs、tex_replace.fs)渲染,着色器主要内容就是照常渲染场景输出给第一个颜色缓冲区,而第二个颜色缓冲区内容是视野中明亮区域。具体代码如下:
#version 150
// tex_replace.fs
// outputs 1 color using texture replace
//
in vec2 vTexCoord;
uniform sampler2D textureUnit0;
uniform vec4 vColor;
out vec4 oColor;
out vec4 oBright;
void main(void)
{
const float bloomLimit = 1.0;
oColor = vColor*texture(textureUnit0, vTexCoord);
oColor.a = 1.0;
vec3 brightColor = max(vColor.rgb - vec3(bloomLimit), vec3(0.0));
float bright = dot(brightColor, vec3(1.0));
bright = smoothstep(0.0, 0.5, bright);
oBright.rgb = mix(vec3(0.0), vColor.rgb, bright).rgb;
oBright.a = 1.0;
}
其中,bloomLimit是固定值1.0,它是为了将HDR图像的颜色值小于1.0部分全部视为0,而大于1.0部分映射到都会整体-1。接着就是将3个分量相加得到亮度值,亮度值会被映射回[0,1]之间,其中小于0的会变为0,大于0.5的就视为1,接着用这个[0,1]范围的亮度值进行插值(黑色,HDR颜色),即亮度为0时 为黑色,亮度为1时 为HDR颜色,将这个最终得到的颜色输出给第二个颜色缓冲区以备使用。
到此我们得到了HDR原图像纹理对象和明亮区域图像,即代码中的hdrTextures[0]和brightBlurTextures[0]纹理对象存储着。
②brightPassFBO数组 一共有4个用于渲染模糊区域图像的FBO,它们分别都捆绑到了第一个颜色缓冲区绑定点,分别用纹理对象brightBlurTextures1~4来进行捆绑,着色器blurProg(basic.vs、blur.fs)进行渲染,结果是输出给第一个颜色缓冲区的。
#version 150
// blur.fs
// outputs 1 color using a gaussian blur of the input texture
//
in vec4 vFragColor;
in vec2 vTexCoord;
uniform sampler2D textureUnit0;
uniform vec2 tc_offset[25];
out vec4 oColor;
void main(void)
{
vec4 sample[25];
for (int i = 0; i < 25; i++)
{
sample[i] = texture(textureUnit0, vTexCoord.st + tc_offset[i]);
}
// 1 4 7 4 1
// 4 16 26 16 4
// 7 26 41 26 7 / 273
// 4 16 26 16 4
// 1 4 7 4 1
oColor = (
(1.0 * (sample[0] + sample[4] + sample[20] + sample[24])) +
(4.0 * (sample[1] + sample[3] + sample[5] + sample[9] +
sample[15] + sample[19] + sample[21] + sample[23])) +
(7.0 * (sample[2] + sample[10] + sample[14] + sample[22])) +
(16.0 * (sample[6] + sample[8] + sample[16] + sample[18])) +
(26.0 * (sample[7] + sample[11] + sample[13] + sample[17])) +
(41.0 * sample[12])
) / 273.0;
}
tc_offset[i]是一个对周围25个片段的偏移量,最终通过它获取周围25个像素的颜色值进行卷积得到oColor即一个2D纹理(全是模糊值)最终经过4次这样的采样,但每一次得到的模糊图像都是在前一个基础上进行的,每一次都会将图像缩小1/3再进行得到模糊图。
③将FBO恢复回默认FBO,将这些结果融合起来,即1张原图图像、1张亮度图、4张模糊图。使用hdrBloomProg着色器(basic.vs、hdr_exposure.fs)进行渲染。
#version 150
// hdr_exposure.fs
// Scale floating point texture to 0.0 - 1.0 based
// on the specified exposure
//
in vec2 vTexCoord;
uniform sampler2D origImage;
uniform sampler2D brightImage;
uniform sampler2D blur1;
uniform sampler2D blur2;
uniform sampler2D blur3;
uniform sampler2D blur4;
uniform float exposure;
uniform float bloomLevel;
out vec4 oColor;
out vec4 oBright;
void main(void)
{
// fetch from HDR & blur textures
vec4 vBaseImage = texture2D(origImage, vTexCoord);
vec4 vBrightPass = texture2D(brightImage, vTexCoord);
vec4 vBlurColor1 = texture2D(blur1, vTexCoord);
vec4 vBlurColor2 = texture2D(blur2, vTexCoord);
vec4 vBlurColor3 = texture2D(blur3, vTexCoord);
vec4 vBlurColor4 = texture2D(blur4, vTexCoord);
vec4 vBloom = vBrightPass +
vBlurColor1 +
vBlurColor2 +
vBlurColor3 +
vBlurColor4;
vec4 vColor = vBaseImage + bloomLevel * vBloom;
// Apply the exposure to this texel
vColor = 1.0 - exp2(-vColor * exposure);
oColor = vColor;
oColor.a = 1.0f;
}
它将亮度图采样出的亮度值 和 4张模糊图采样出的4个模糊值进行相加得到 曝光值,与一个bloomLevel统一值(曝光等级)相乘得到最终曝光颜色,将采样出的原色和曝光颜色叠加得到vColor接着进行一个常规曝光处理(完全看不懂)得到最终的经过曝光处理的颜色输出。
上面的basic.vs代码就是简单的转换了顶点坐标和计算了漫反射颜色(好像也没用到)
#version 150
// hdr_bloom.vs
// outputs MVP transformed position
// passes texture coordinates through
// color is * by normal and passed on.
in vec3 vVertex;
in vec3 vNormal;
in vec2 vTexCoord0;
uniform mat4 mvMatrix;
uniform mat4 pMatrix;
uniform vec3 vLightPos;
uniform vec4 vColor;
out vec4 vFragColor;
out vec2 vTexCoord;
void main(void)
{
mat3 mNormalMatrix;
mNormalMatrix[0] = normalize(mvMatrix[0].xyz);
mNormalMatrix[1] = normalize(mvMatrix[1].xyz);
mNormalMatrix[2] = normalize(mvMatrix[2].xyz);
vec3 vNorm = normalize(mNormalMatrix * vNormal);
vec4 ecPosition;
vec3 ecPosition3;
ecPosition = mvMatrix * vec4(vVertex, 1.0);
ecPosition3 = ecPosition.xyz /ecPosition.w;
vec3 vLightDir = normalize(vLightPos - ecPosition3);
float fDot = max(0.0, dot(vNorm, vLightDir));
vFragColor.rgb = vColor.rgb * fDot;
vFragColor.a = vColor.a;
vTexCoord = vTexCoord0;
mat4 mvpMatrix;
mvpMatrix = pMatrix * mvMatrix;
gl_Position = mvpMatrix * vec4(vVertex, 1.0);
}
#include <stdio.h>
#include <iostream>
#include <ImfRgbaFile.h> // OpenEXR headers
#include <ImfArray.h>
#include <GLTools.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include <GL\glu.h>
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
#ifdef _WIN32
#pragma comment (lib, "half.lib")
#pragma comment (lib, "Iex.lib")
#pragma comment (lib, "IlmImf.lib")
#pragma comment (lib, "IlmThread.lib")
#pragma comment (lib, "Imath.lib")
#pragma comment (lib, "zlib.lib")
#endif
#pragma warning( disable : 4244)
static GLfloat vWhite[] = { 1.0f, 1.0f, 1.0f, 1.0f };
static GLfloat vWhiteX2[] = { 2.0f, 2.0f, 2.0f, 2.0f };
static GLfloat vGrey[] = { 0.5f, 0.5f, 0.5f, 1.0f };
static GLfloat vLightPos[] = { -2.0f, 3.0f, -2.0f, 1.0f };
static GLfloat vSkyBlue[] = { 0.160f, 0.376f, 0.925f, 1.0f};
static const GLenum windowBuff[] = { GL_BACK_LEFT };
static const GLenum fboBuffs[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
GLsizei screenWidth; // Desired window or desktop width
GLsizei screenHeight; // Desired window or desktop height
GLboolean bFullScreen; // Request to run full screen
GLboolean bAnimated; // Request for continual updates
GLMatrixStack modelViewMatrix; // Modelview Matrix
GLMatrixStack projectionMatrix; // Projection Matrix
GLFrustum viewFrustum; // View Frustum
GLGeometryTransform transformPipeline; // Geometry Transform Pipeline
GLFrame cameraFrame; // Camera frame
GLBatch screenQuad;
M3DMatrix44f orthoMatrix;
GLBatch floorBatch;
GLBatch windowBatch;
GLBatch windowBorderBatch;
GLBatch windowGridBatch;
GLuint flatColorProg;
GLuint texReplaceProg;
GLuint hdrBloomProg;
GLuint blurProg;
GLuint hdrFBO[1];
GLuint brightPassFBO[4];
GLuint textures[1];
GLuint hdrTextures[1];
GLuint brightBlurTextures[5];
GLuint windowTexture;
GLfloat exposure;
GLfloat bloomLevel;
GLfloat texCoordOffsets[5*5*2];
void UpdateMode(void);
void GenerateOrtho2DMat(GLuint imageWidth, GLuint imageHeight);
bool LoadBMPTexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode);
bool LoadOpenEXRImage(char *fileName, GLint textureName, GLuint &texWidth, GLuint &texHeight);
void GenTexCoordOffsets(GLuint width, GLuint height);
void SetupTexReplaceProg(GLfloat *vLightPos, GLfloat *vColor);
void SetupFlatColorProg(GLfloat *vLightPos, GLfloat *vColor);
void SetupHDRProg();
void SetupBlurProg();
///
// Load in a BMP file as a texture. Allows specification of the filters and the wrap mode
bool LoadBMPTexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
GLint iWidth, iHeight;
pBits = gltReadBMPBits(szFileName, &iWidth, &iHeight);
if(pBits == NULL)
return false;
// Set Wrap modes
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, iWidth, iHeight, 0, GL_BGR, GL_UNSIGNED_BYTE, pBits);
// Do I need to generate mipmaps?
if(minFilter == GL_LINEAR_MIPMAP_LINEAR || minFilter == GL_LINEAR_MIPMAP_NEAREST || minFilter == GL_NEAREST_MIPMAP_LINEAR || minFilter == GL_NEAREST_MIPMAP_NEAREST)
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
void GenTexCoordOffsets(GLuint width, GLuint height)
{
float xInc = 1.0f / (GLfloat)(width);
float yInc = 1.0f / (GLfloat)(height);
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
texCoordOffsets[(((i*5)+j)*2)+0] = (-2.0f * xInc) + ((GLfloat)i * xInc);
texCoordOffsets[(((i*5)+j)*2)+1] = (-2.0f * yInc) + ((GLfloat)j * yInc);
}
}
}
///
// OpenGL related startup code is safe to put here. Load textures, etc.
void SetupRC(void)
{
GLenum err = glewInit();
if (GLEW_OK != err)
{
/* Problem: glewInit failed, something is seriously wrong. */
fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
}
glEnable(GL_DEPTH_TEST);
exposure = 1.0f;
bloomLevel = 0.5;
// Light Blue
glClearColor(vSkyBlue[0], vSkyBlue[1], vSkyBlue[2], vSkyBlue[3]);
// Load geometry
GLfloat alpha = 0.25f;
floorBatch.Begin(GL_TRIANGLE_FAN, 4, 1);
floorBatch.Color4f(0.0f, 1.0f, 0.0f, alpha);
floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
floorBatch.Normal3f(0.0, 1.0f, 0.0f);
floorBatch.Vertex3f(-20.0f, -0.41f, 20.0f);
floorBatch.Color4f(0.0f, 1.0f, 0.0f, alpha);
floorBatch.MultiTexCoord2f(0, 10.0f, 0.0f);
floorBatch.Normal3f(0.0, 1.0f, 0.0f);
floorBatch.Vertex3f(20.0f, -0.41f, 20.0f);
floorBatch.Color4f(0.0f, 1.0f, 0.0f, alpha);
floorBatch.MultiTexCoord2f(0, 10.0f, 10.0f);
floorBatch.Normal3f(0.0, 1.0f, 0.0f);
floorBatch.Vertex3f(20.0f, -0.41f, -20.0f);
floorBatch.Color4f(0.0f, 1.0f, 0.0f, alpha);
floorBatch.MultiTexCoord2f(0, 0.0f, 10.0f);
floorBatch.Normal3f(0.0, 1.0f, 0.0f);
floorBatch.Vertex3f(-20.0f, -0.41f, -20.0f);
floorBatch.End();
windowBatch.Begin(GL_TRIANGLE_FAN, 4, 1);
windowBatch.Color4f(1.0f, 0.0f, 0.0f, 1.0f);
windowBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
windowBatch.Normal3f( 0.0f, 1.0f, 0.0f);
windowBatch.Vertex3f(-1.0f, 0.0f, 0.0f);
windowBatch.Color4f(1.0f, 0.0f, 0.0f, 1.0f);
windowBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
windowBatch.Normal3f(0.0f, 1.0f, 0.0f);
windowBatch.Vertex3f(1.0f, 0.0f, 0.0f);
windowBatch.Color4f(1.0f, 0.0f, 0.0f, 1.0f);
windowBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
windowBatch.Normal3f(0.0f, 1.0f, 0.0f);
windowBatch.Vertex3f(1.0f, 2.0f, 0.0f);
windowBatch.Color4f(1.0f, 0.0f, 0.0f, 1.0f);
windowBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
windowBatch.Normal3f( 0.0f, 1.0f, 0.0f);
windowBatch.Vertex3f(-1.0f, 2.0f, 0.0f);
windowBatch.End();
const float width = 0.2f;
const float gridWidth = (float)0.05;
windowBorderBatch.Begin(GL_TRIANGLE_STRIP, 13);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(-1.01f, width, 0.01f);
windowBorderBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(-1.01f, 0.0f, 0.01f);
windowBorderBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(1.01f, width, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(1.01f, 0.0f, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(1.01-width, 0.0f, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(1.01f, 2.0f, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(1.01-width, 2.0f, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(1.01f, 2.0-width, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(-1.01f, 2.f, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(-1.01f, 2.0-width, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(-1.01+width, 2.f, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(-1.01f, 0.0f, 0.01f);
windowBorderBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowBorderBatch.Vertex3f(-1.01+width, 0.0f, 0.01f);
windowBorderBatch.End();
windowGridBatch.Begin(GL_TRIANGLES, 24);
// bottom horizontal
windowGridBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-1.0f, 0.7+gridWidth, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-1.0f, 0.7-gridWidth, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(1.0f, 0.7-gridWidth, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(1.0f, 0.7-gridWidth, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(1.0f, 0.7+gridWidth, 0.01f);
windowGridBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-1.0f, 0.7+gridWidth, 0.01f);
// Top horizontal
windowGridBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-1.0f, 1.3+gridWidth, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-1.0f, 1.3-gridWidth, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(1.0f, 1.3-gridWidth, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(1.0f, 1.3-gridWidth, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(1.0f, 1.3+gridWidth, 0.01f);
windowGridBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-1.0f, 1.3+gridWidth, 0.01f);
// Left Vertical
windowGridBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-0.3+gridWidth, 0.0f, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-0.3-gridWidth, 0.0f, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-0.3-gridWidth, 2.0f, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-0.3-gridWidth, 2.0f, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-0.3+gridWidth, 2.0, 0.01f);
windowGridBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(-0.3+gridWidth, 0.0f, 0.01f);
// Right Vertical
windowGridBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(0.3+gridWidth, 0.0f, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(0.3-gridWidth, 0.0f, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(0.3-gridWidth, 2.0f, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(0.3-gridWidth, 2.0f, 0.01f);
windowGridBatch.Normal3f(0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(0.3+gridWidth, 2.0, 0.01f);
windowGridBatch.Normal3f( 0.0f, 0.0f, 1.0f);
windowGridBatch.Vertex3f(0.3+gridWidth, 0.0f, 0.01f);
windowGridBatch.End();
glGenTextures(1, textures);
glBindTexture(GL_TEXTURE_2D, textures[0]);
LoadBMPTexture("marble.bmp", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);
// Setup HDR render texture
glGenTextures(1, hdrTextures);
glBindTexture(GL_TEXTURE_2D, hdrTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, screenWidth, screenHeight, 0, GL_RGBA, GL_FLOAT, NULL);
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_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// create textures for bloom effect
glGenTextures(5, brightBlurTextures);
int i = 0;
for (i=0; i<5; i++)
{
glBindTexture(GL_TEXTURE_2D, brightBlurTextures[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, screenWidth, screenHeight, 0, GL_RGBA, GL_FLOAT, NULL);
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_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
// Attach HDR texture to fbo
// Create and bind an FBO
glGenFramebuffers(1,hdrFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, hdrFBO[0]);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hdrTextures[0], 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, brightBlurTextures[0], 0);
// Create FBOs for bloom effect
glGenFramebuffers(4,brightPassFBO);
for (i=0; i<4; i++)
{
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, brightPassFBO[i]);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brightBlurTextures[i+1], 0);
}
// Create window texture
glGenTextures(1, &windowTexture);
glBindTexture(GL_TEXTURE_2D, windowTexture);
GLuint texWidth = 0;
GLuint texHeight = 0;
// Load HDR image from EXR file
LoadOpenEXRImage("window.exr", windowTexture, texWidth, texHeight);
// Load flat color shader
flatColorProg = gltLoadShaderPairWithAttributes("basic.vs", "color.fs", 3,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal",
GLT_ATTRIBUTE_TEXTURE0, "vTexCoord0");
glBindFragDataLocation(flatColorProg, 0, "oColor");
glBindFragDataLocation(flatColorProg, 1, "oBright");
glLinkProgram(flatColorProg);
// Load texture replace shader
texReplaceProg = gltLoadShaderPairWithAttributes("basic.vs", "tex_replace.fs", 3,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal",
GLT_ATTRIBUTE_TEXTURE0, "vTexCoord0");
glBindFragDataLocation(texReplaceProg, 0, "oColor");
glBindFragDataLocation(texReplaceProg, 1, "oBright");
glLinkProgram(texReplaceProg);
// Load bloom shader
hdrBloomProg = gltLoadShaderPairWithAttributes("basic.vs", "hdr_exposure.fs", 3,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal",
GLT_ATTRIBUTE_TEXTURE0, "vTexCoord0");
glBindFragDataLocation(hdrBloomProg, 0, "oColor");
glLinkProgram(hdrBloomProg);
// Load blur shader
blurProg = gltLoadShaderPairWithAttributes("basic.vs", "blur.fs", 2,
GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_TEXTURE0, "vTexCoord0");
glBindFragDataLocation(blurProg, 0, "oColor");
glLinkProgram(blurProg);
glUseProgram(blurProg);
// Setup tex coords to be used for fetching HDR kernel data
GenTexCoordOffsets(screenWidth, screenHeight);
glUniform2fv(glGetUniformLocation(blurProg, "tc_offset"), 25, &texCoordOffsets[0]);
// Make sure all went well
gltCheckErrors(flatColorProg);
gltCheckErrors(texReplaceProg);
gltCheckErrors(hdrBloomProg);
gltCheckErrors(blurProg);
// Reset framebuffer binding
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
// Take a file name/location and load an OpenEXR
// Load the image into the "texture" texture object and pass back the texture sizes
//
bool LoadOpenEXRImage(char *fileName, GLint textureName, GLuint &texWidth, GLuint &texHeight)
{
// The OpenEXR uses exception handling to report errors or failures
// Do all work in a try block to catch any thrown exceptions.
try
{
Imf::Array2D<Imf::Rgba> pixels;
Imf::RgbaInputFile file (fileName);
Imath::Box2i dw = file.dataWindow();
texWidth = dw.max.x - dw.min.x + 1;
texHeight = dw.max.y - dw.min.y + 1;
pixels.resizeErase (texHeight, texWidth);
file.setFrameBuffer (&pixels[0][0] - dw.min.x - dw.min.y * texWidth, 1, texWidth);
file.readPixels (dw.min.y, dw.max.y);
GLfloat* texels = (GLfloat*)malloc(texWidth * texHeight * 3 * sizeof(GLfloat));
GLfloat* pTex = texels;
// Copy OpenEXR into local buffer for loading into a texture
for (unsigned int v = 0; v < texHeight; v++)
{
for (unsigned int u = 0; u < texWidth; u++)
{
Imf::Rgba texel = pixels[texHeight - v - 1][u];
pTex[0] = texel.r;
pTex[1] = texel.g;
pTex[2] = texel.b;
pTex += 3;
}
}
// Bind texture, load image, set tex state
glBindTexture(GL_TEXTURE_2D, textureName);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, texWidth, texHeight, 0, GL_RGB, GL_FLOAT, texels);
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_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
free(texels);
}
catch(Iex::BaseExc & e)
{
std::cerr << e.what() << std::endl;
//
// Handle exception.
//
}
return true;
}
///
// Do your cleanup here. Free textures, display lists, buffer objects, etc.
void ShutdownRC(void)
{
// Make sure default FBO is bound
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
// Cleanup textures
for (int i=0; i<5;i++)
{
glActiveTexture(GL_TEXTURE0+i);
glBindTexture(GL_TEXTURE_2D, 0);
}
glDeleteTextures(1, textures);
glDeleteTextures(1, hdrTextures);
glDeleteTextures(5, brightBlurTextures);
glDeleteTextures(1, &windowTexture);
// Cleanup FBOs
glDeleteFramebuffers(1, hdrFBO);
glDeleteFramebuffers(1, brightPassFBO);
}
void SetupFlatColorProg(GLfloat *vLightPos, GLfloat *vColor)
{
glUseProgram(flatColorProg);
// Set projection matrix
glUniformMatrix4fv(glGetUniformLocation(flatColorProg, "pMatrix"),
1, GL_FALSE, transformPipeline.GetProjectionMatrix());
// Set MVP matrix
glUniformMatrix4fv(glGetUniformLocation(flatColorProg, "mvMatrix"),
1, GL_FALSE, transformPipeline.GetModelViewMatrix());
// Set Light Pos
glUniform3fv(glGetUniformLocation(flatColorProg, "vLightPos"), 1, vLightPos);
// Set Color
glUniform4fv(glGetUniformLocation(flatColorProg, "vColor"), 1, vColor);
gltCheckErrors(flatColorProg);
}
void SetupTexReplaceProg(GLfloat *vLightPos, GLfloat *vColor)
{
glUseProgram(texReplaceProg);
// Set projection matrix
glUniformMatrix4fv(glGetUniformLocation(texReplaceProg, "pMatrix"),
1, GL_FALSE, transformPipeline.GetProjectionMatrix());
// Set MVP matrix
glUniformMatrix4fv(glGetUniformLocation(texReplaceProg, "mvMatrix"),
1, GL_FALSE, transformPipeline.GetModelViewMatrix());
// Set Light Pos
glUniform3fv(glGetUniformLocation(texReplaceProg, "vLightPos"), 1, vLightPos);
// Set Color
glUniform4fv(glGetUniformLocation(texReplaceProg, "vColor"), 1, vColor);
// Set Tex Unit
glUniform1i(glGetUniformLocation(texReplaceProg, "textureUnit0"), 0);
gltCheckErrors(texReplaceProg);
}
void SetupHDRProg()
{
glUseProgram(hdrBloomProg);
// Set projection matrix
glUniformMatrix4fv(glGetUniformLocation(hdrBloomProg, "pMatrix"),
1, GL_FALSE, transformPipeline.GetProjectionMatrix());
// Set MVP matrix
glUniformMatrix4fv(glGetUniformLocation(hdrBloomProg, "mvMatrix"),
1, GL_FALSE, transformPipeline.GetModelViewMatrix());
// Set user controled uniforms
glUniform1fv(glGetUniformLocation(hdrBloomProg, "exposure"), 1, &exposure);
glUniform1fv(glGetUniformLocation(hdrBloomProg, "bloomLevel"), 1, &bloomLevel);
// Set texture uniforms
glUniform1i(glGetUniformLocation(hdrBloomProg, "origImage"), 0);
glUniform1i(glGetUniformLocation(hdrBloomProg, "brightImage"), 1);
glUniform1i(glGetUniformLocation(hdrBloomProg, "blur1"), 2);
glUniform1i(glGetUniformLocation(hdrBloomProg, "blur2"), 3);
glUniform1i(glGetUniformLocation(hdrBloomProg, "blur3"), 4);
glUniform1i(glGetUniformLocation(hdrBloomProg, "blur4"), 5);
// Now setup the right textures
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, hdrTextures[0]);
for (int i=0; i<5; i++)
{
glActiveTexture(GL_TEXTURE1+i);
glBindTexture(GL_TEXTURE_2D, brightBlurTextures[i]);
}
gltCheckErrors(hdrBloomProg);
}
void SetupBlurProg()
{
// Set the program to the cur
glUseProgram(blurProg);
// Set projection matrix
glUniformMatrix4fv(glGetUniformLocation(blurProg, "pMatrix"),
1, GL_FALSE, transformPipeline.GetProjectionMatrix());
// Set MVP matrix
glUniformMatrix4fv(glGetUniformLocation(blurProg, "mvMatrix"),
1, GL_FALSE, transformPipeline.GetModelViewMatrix());
glUniform1i(glGetUniformLocation(blurProg, "textureUnit0"), 0);
gltCheckErrors(blurProg);
}
///
// This is called at least once and before any rendering occurs. If the screen
// is a resizeable window, then this will also get called whenever the window
// is resized.
void ChangeSize(int nWidth, int nHeight)
{
glViewport(0, 0, nWidth, nHeight);
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
modelViewMatrix.LoadIdentity();
// update screen sizes
screenWidth = nWidth;
screenHeight = nHeight;
glBindTexture(GL_TEXTURE_2D, hdrTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, screenWidth, screenHeight, 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, brightBlurTextures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, screenWidth, screenHeight, 0, GL_RGBA, GL_FLOAT, NULL);
for(int i=1; i<5; i++)
{
glBindTexture(GL_TEXTURE_2D, brightBlurTextures[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, screenWidth/(i*3.0), screenHeight/(i*3.0), 0, GL_RGBA, GL_FLOAT, NULL);
}
// Setup tex coords to be used for fetching HDR kernel data
glUseProgram(blurProg);
GenTexCoordOffsets(screenWidth, screenHeight);
glUniform2fv(glGetUniformLocation(blurProg, "tc_offset"), 25, texCoordOffsets);
GenerateOrtho2DMat(nWidth, nHeight);
glUseProgram(0);
}
///
// Create a matrix that maps geometry to the screen. 1 unit in the x directionequals one pixel
// of width, same with the y direction.
//
void GenerateOrtho2DMat(GLuint imageWidth, GLuint imageHeight)
{
float right = (float)imageWidth;
float quadWidth = right;
float left = 0.0f;
float top = (float)imageHeight;
float quadHeight = top;
float bottom = 0.0f;
// set ortho matrix
orthoMatrix[0] = (float)(2 / (right));
orthoMatrix[1] = 0.0;
orthoMatrix[2] = 0.0;
orthoMatrix[3] = 0.0;
orthoMatrix[4] = 0.0;
orthoMatrix[5] = (float)(2 / (top));
orthoMatrix[6] = 0.0;
orthoMatrix[7] = 0.0;
orthoMatrix[8] = 0.0;
orthoMatrix[9] = 0.0;
orthoMatrix[10] = (float)(-2 / (1.0 - 0.0));
orthoMatrix[11] = 0.0;
orthoMatrix[12] = -1.0f;
orthoMatrix[13] = -1.0f;
orthoMatrix[14] = -1.0f;
orthoMatrix[15] = 1.0;
// set screen quad vertex array
screenQuad.Reset();
screenQuad.Begin(GL_TRIANGLE_STRIP, 4, 1);
screenQuad.Color4f(0.0f, 1.0f, 0.0f, 1.0f);
screenQuad.MultiTexCoord2f(0, 0.0f, 0.0f);
screenQuad.Vertex3f(0.0f, 0.0f, 0.0f);
screenQuad.Color4f(0.0f, 1.0f, 0.0f, 1.0f);
screenQuad.MultiTexCoord2f(0, 1.0f, 0.0f);
screenQuad.Vertex3f(right, 0.0f, 0.0f);
screenQuad.Color4f(0.0f, 1.0f, 0.0f, 1.0f);
screenQuad.MultiTexCoord2f(0, 0.0f, 1.0f);
screenQuad.Vertex3f(0.0f, top, 0.0f);
screenQuad.Color4f(0.0f, 1.0f, 0.0f, 1.0f);
screenQuad.MultiTexCoord2f(0, 1.0f, 1.0f);
screenQuad.Vertex3f(right, top, 0.0f);
screenQuad.End();
}
///
// Update the camera based on user input, toggle display modes
//
void SpecialKeys(int key, int x, int y)
{
static CStopWatch timer;
float fTime = timer.GetElapsedSeconds();
float linear = fTime / 100;
float smallLinear = fTime / 1000;
// Increase the scene exposure
if(key == GLUT_KEY_UP)
{
if((exposure + linear) < 20.0f)
exposure += linear;
}
// Decrease the scene exposure
if(key == GLUT_KEY_DOWN)
{
if((exposure - linear) > 0.01f)
exposure -= linear;
}
// Decrease amount of bloom effect
if(key == GLUT_KEY_LEFT)
{
if((bloomLevel - smallLinear) > 0.00f)
bloomLevel -= smallLinear;
}
// Increase amount of bloom effect
if(key == GLUT_KEY_RIGHT)
{
if((bloomLevel + smallLinear) < 1.5f)
bloomLevel += smallLinear;
}
}
///
// Render a frame. The owning framework is responsible for buffer swaps,
// flushes, etc.
void RenderScene(void)
{
int i =0;
// first render the scene in HDR to fbo
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, hdrFBO[0]);
glDrawBuffers(1, &fboBuffs[0]);
glClearColor(vSkyBlue[0], vSkyBlue[1], vSkyBlue[2], vSkyBlue[3]);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawBuffers(1, &fboBuffs[1]);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw to two textures, the first contains scene data
// the second contains only the bright areas
glDrawBuffers(2, fboBuffs);
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.MultMatrix(mCamera);
glBindTexture(GL_TEXTURE_2D, textures[0]); // Marble
// Draw the floor
SetupTexReplaceProg(vLightPos, vWhite);
floorBatch.Draw();
// Draw the window
modelViewMatrix.PushMatrix();
modelViewMatrix.Translate(0.0f, -0.4f, -4.0f);
modelViewMatrix.Rotate(10.0, 0.0, 1.0, 0.0);
glBindTexture(GL_TEXTURE_2D, windowTexture); // Window Tex
// First draw the window contents from texture
SetupTexReplaceProg(vLightPos, vWhiteX2);
windowBatch.Draw();
// Now draw the border and the grid
SetupFlatColorProg(vLightPos, vGrey);
windowGridBatch.Draw();
windowBorderBatch.Draw();
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
projectionMatrix.PushMatrix();
projectionMatrix.LoadMatrix(orthoMatrix);
modelViewMatrix.PushMatrix();
modelViewMatrix.LoadIdentity();
// Take the data from the brightness texture and
// blur it in 4 consecutive passes to textures of
// decreasing size
SetupBlurProg();
for (i =0; i<4; i++)
{
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, brightPassFBO[i]); // draws into brightBlurTextures[i+1]
glDrawBuffers(1, &fboBuffs[0]);
glViewport(0, 0, screenWidth/((i+1)*3.0), screenHeight/((i+1)*3.0));
glBindTexture(GL_TEXTURE_2D, brightBlurTextures[i]);
screenQuad.Draw();
}
// Combine original scene with blurred bright textures
// to create the bloom effect
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffers(1,windowBuff);
glViewport(0, 0, screenWidth, screenHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
SetupHDRProg();
screenQuad.Draw();
modelViewMatrix.PopMatrix();
projectionMatrix.PopMatrix();
// Put the texture units back the way they were
for (i=5; i>-1; i--)
{
glActiveTexture(GL_TEXTURE0+i);
glBindTexture(GL_TEXTURE_2D, 0);
}
// Do the buffer Swap
glutSwapBuffers();
// Do it again
glutPostRedisplay();
}
int main(int argc, char* argv[])
{
screenWidth = 800;
screenHeight = 600;
bFullScreen = false;
bAnimated = true;
bloomLevel = 0.0;
cameraFrame.MoveUp(0.50);
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(screenWidth,screenHeight);
glutCreateWindow("HDR Bloom");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
glutSpecialFunc(SpecialKeys);
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}