소스 검색

Added bloom post-process effect.
Added global shader parameters to PostProcess.
Added possibility to specify texture units by number instead of name in materials and postprocesses.

Lasse Öörni 14 년 전
부모
커밋
a1c70a3476

+ 20 - 0
Bin/Data/PostProcess/Bloom.xml

@@ -0,0 +1,20 @@
+<postprocess>
+    <rendertarget name="vblur" sizedivisor="4 4" format="rgb" filter="true" />
+    <rendertarget name="hblur" sizedivisor="4 4" format="rgb" filter="true" />
+    <parameter name="BloomThreshold" value="0.3" />
+    <parameter name="BloomMix" value="0.9 0.4" />
+    <parameter name="BlurOffset" value="2 2" />
+    <pass vs="Bloom" ps="Bloom_Bright" output="vblur">
+        <texture unit="diffuse" name="viewport" />
+    </pass>
+    <pass vs="Bloom" ps="Bloom_HBlur" output="hblur">
+        <texture unit="diffuse" name="vblur" />
+    </pass>
+    <pass vs="Bloom" ps="Bloom_VBlur" output="vblur">
+        <texture unit="diffuse" name="hblur" />
+    </pass>
+    <pass vs="Bloom" ps="Bloom_Combine" output="viewport">
+        <texture unit="diffuse" name="viewport" />
+        <texture unit="normal" name="vblur" />
+    </pass>
+</postprocess>

+ 9 - 0
Bin/Data/Scripts/TestScene.as

@@ -4,6 +4,7 @@ Scene@ testScene;
 Camera@ camera;
 Node@ cameraNode;
 PostProcess@ edgeFilter;
+PostProcess@ bloom;
 
 float yaw = 0.0;
 float pitch = 0.0;
@@ -218,8 +219,13 @@ void InitScene()
         edgeFilter.parameters = cache.GetResource("XMLFile", "PostProcess/EdgeFilter.xml");
         edgeFilter.active = false; // Start out disabled
 
+        bloom = PostProcess();
+        bloom.parameters = cache.GetResource("XMLFile", "PostProcess/Bloom.xml");
+        bloom.active = false;
+
         renderer.viewports[0] = Viewport(testScene, camera);
         renderer.viewports[0].AddPostProcess(edgeFilter);
+        renderer.viewports[0].AddPostProcess(bloom);
     }
 }
 
@@ -322,6 +328,9 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
         if (input.keyPress['C'])
             camera.orthographic = !camera.orthographic;
 
+        if (input.keyPress['B'])
+            bloom.active = !bloom.active;
+
         if (input.keyPress['F'])
             edgeFilter.active = !edgeFilter.active;
 

+ 10 - 0
Bin/Data/Scripts/TestSceneOld.as

@@ -13,6 +13,8 @@ Camera@ camera;
 Node@ cameraLightNode;
 Light@ cameraLight;
 PostProcess@ edgeFilter;
+PostProcess@ bloom;
+
 float yaw = 0.0;
 float pitch = 0.0;
 float objectangle = 0.0;
@@ -318,8 +320,13 @@ void CreateCamera()
         edgeFilter.parameters = cache.GetResource("XMLFile", "PostProcess/EdgeFilter.xml");
         edgeFilter.active = false; // Start out disabled
 
+        bloom = PostProcess();
+        bloom.parameters = cache.GetResource("XMLFile", "PostProcess/Bloom.xml");
+        bloom.active = false;
+
         renderer.viewports[0] = Viewport(testScene, camera);
         renderer.viewports[0].AddPostProcess(edgeFilter);
+        renderer.viewports[0].AddPostProcess(bloom);
     }
 }
 
@@ -424,6 +431,9 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
         if (input.keyPress['C'])
             camera.orthographic = !camera.orthographic;
 
+        if (input.keyPress['B'])
+            bloom.active = !bloom.active;
+            
         if (input.keyPress['F'])
             edgeFilter.active = !edgeFilter.active;
         

+ 1 - 0
Docs/GettingStarted.dox

@@ -57,6 +57,7 @@ F7          Load scene
 T           Toggle profiling display
 C           Toggle orthographic camera
 F           Toggle FXAA edge filter
+B           Toggle bloom post-process
 \endverbatim
 
 TestScene also includes a network replication test, where clients can connect, move around as invisible cameras, and create new physics objects. For this, a server needs to be started with the command TestScene.bat server (-headless switch can optionally given so that the server will not open a graphics window) and clients can connect by specifying the server address on the command line, for example TestScene.bat 127.0.0.1

+ 3 - 2
Docs/Reference.dox

@@ -693,7 +693,8 @@ In addition to configuring programmatically, the PostProcess can be configured w
 
 \code
 <postprocess>
-    <rendertarget name="RenderTargetName" size="x y" | sizedivisor="x y" format="rgb|rgba|float" filtered="true|false" />
+    <rendertarget name="RenderTargetName" size="x y" | sizedivisor="x y" format="rgb|rgba|float" filter="true|false" />
+    <parameter name="GlobalShaderParameterName" value="x y z w" />
     <pass vs="VertexShaderName" ps="PixelShaderName" output="viewport|RenderTargetName" />
         <texture unit="diffuse|normal|specular|detail|environment|emissive" name="viewport|RenderTargetName|TextureName" />
         <parameter name="ShaderParameterName" value="x y z w" />
@@ -703,7 +704,7 @@ In addition to configuring programmatically, the PostProcess can be configured w
 
 Rendertargets can either specify an absolute width and height, or a divisor for the viewport size (for example sizedivisor="2 2" would divide the viewport dimensions by two.) The rendertargets are shared by all passes. A pass can output either into a rendertarget or into the viewport. The current contents of the viewport (either the scene render or a previous post-process pass) can also be read as a texture; this will always use the other half of the ping-pong buffer to avoid sampling the texture being rendered.
 
-In addition to the user-definable shader parameters, the shader parameters GBufferOffsets and SampleOffsets will always be set according to the viewport coordinates and the viewport rendertarget size, so that the GetScreenPos() shader function will operate correctly, and adjacent pixels from the viewport can be sampled. See the EdgeFilter shader for an example.
+Shader parameters can be defined either as global for all passes, or as pass-specific. In addition to the user-defined shader parameters, the shader parameters GBufferOffsets and SampleOffsets will always be set according to the viewport coordinates and the destination rendertarget size, so that the GetScreenPos() shader function will operate correctly, and adjacent pixels can be sampled. See the EdgeFilter shader for an example.
 
 Note that when hardware multisampling is used in conjunction with post-processing, the multisampled backbuffer will first be resolved to the ping-pong buffer before rendering the post-process passes. This has a small performance cost.
 

+ 2 - 0
Docs/ScriptAPI.dox

@@ -1500,6 +1500,7 @@ PostProcess
 Methods:<br>
 - bool CreateRenderTarget(const String&, uint, uint, uint, bool, bool)
 - void RemoveRenderTarget(const String&)
+- void RemoveShaderParameter(const String&)
 - PostProcess@ Clone() const
 - bool HasRenderTarget(const String&) const
 
@@ -1508,6 +1509,7 @@ Properties:<br>
 - String& typeName (readonly)
 - XMLFile@ parameters
 - uint numPasses
+- Vector4[] shaderParameters
 - bool active
 - PostProcessPass@[] passes (readonly)
 

+ 3 - 0
Engine/Engine/GraphicsAPI.cpp

@@ -385,12 +385,15 @@ static void RegisterPostProcess(asIScriptEngine* engine)
     RegisterObjectConstructor<PostProcess>(engine, "PostProcess");
     engine->RegisterObjectMethod("PostProcess", "bool CreateRenderTarget(const String&in, uint, uint, uint, bool, bool)", asMETHOD(PostProcess, CreateRenderTarget), asCALL_THISCALL);
     engine->RegisterObjectMethod("PostProcess", "void RemoveRenderTarget(const String&in)", asMETHOD(PostProcess, RemoveRenderTarget), asCALL_THISCALL);
+    engine->RegisterObjectMethod("PostProcess", "void RemoveShaderParameter(const String&in)", asMETHOD(PostProcess, RemoveShaderParameter), asCALL_THISCALL);
     engine->RegisterObjectMethod("PostProcess", "PostProcess@ Clone() const", asFUNCTION(PostProcessClone), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("PostProcess", "bool HasRenderTarget(const String&in) const", asMETHOD(PostProcess, HasRenderTarget), asCALL_THISCALL);
     engine->RegisterObjectMethod("PostProcess", "bool set_parameters(XMLFile@+)", asMETHOD(PostProcess, LoadParameters), asCALL_THISCALL);
     engine->RegisterObjectMethod("PostProcess", "XMLFile@+ get_parameters() const", asMETHOD(PostProcess, GetParameters), asCALL_THISCALL);
     engine->RegisterObjectMethod("PostProcess", "void set_numPasses(uint)", asMETHOD(PostProcess, SetNumPasses), asCALL_THISCALL);
     engine->RegisterObjectMethod("PostProcess", "uint get_numPasses() const", asMETHOD(PostProcess, GetNumPasses), asCALL_THISCALL);
+    engine->RegisterObjectMethod("PostProcess", "void set_shaderParameters(const String&in, const Vector4&in)", asMETHOD(PostProcess, SetShaderParameter), asCALL_THISCALL);
+    engine->RegisterObjectMethod("PostProcess", "Vector4 get_shaderParameters(const String&in) const", asMETHOD(PostProcess, GetShaderParameter), asCALL_THISCALL);
     engine->RegisterObjectMethod("PostProcess", "void set_active(bool)", asMETHOD(PostProcess, SetActive), asCALL_THISCALL);
     engine->RegisterObjectMethod("PostProcess", "bool get_active() const", asMETHOD(PostProcess, IsActive), asCALL_THISCALL);
     engine->RegisterObjectMethod("PostProcess", "PostProcessPass@+ get_passes(uint) const", asMETHOD(PostProcess, GetPass), asCALL_THISCALL);

+ 2 - 2
Engine/Graphics/Direct3D9/D3D9Shader.cpp

@@ -128,8 +128,8 @@ bool Shader::Load(Deserializer& source)
             TextureUnit tuIndex = graphics->GetTextureUnit(unitName);
             if (tuIndex != MAX_TEXTURE_UNITS)
                 variation->AddTextureUnit(tuIndex);
-            else
-                LOGWARNING("Unknown texture unit " + unitName);
+            else if (sampler < MAX_TEXTURE_UNITS)
+                variation->AddTextureUnit((TextureUnit)sampler);
         }
         
         variation->OptimizeParameters();

+ 8 - 3
Engine/Graphics/Material.cpp

@@ -161,9 +161,14 @@ bool Material::Load(Deserializer& source)
         if (textureElem.HasAttribute("unit"))
         {
             String unitName = textureElem.GetAttributeLower("unit");
-            unit = ParseTextureUnitName(unitName);
-            if (unit == MAX_MATERIAL_TEXTURE_UNITS)
-                LOGERROR("Unknown texture unit " + unitName);
+            if (unitName.Length() > 1)
+            {
+                unit = ParseTextureUnitName(unitName);
+                if (unit == MAX_MATERIAL_TEXTURE_UNITS)
+                    LOGERROR("Unknown texture unit " + unitName);
+            }
+            else
+                unit = (TextureUnit)Clamp(ToInt(unitName), 0, MAX_MATERIAL_TEXTURE_UNITS - 1);
         }
         if (unit != MAX_MATERIAL_TEXTURE_UNITS)
         {

+ 1 - 1
Engine/Graphics/Material.h

@@ -146,7 +146,7 @@ private:
     Vector<TechniqueEntry> techniques_;
     /// Textures.
     SharedPtr<Texture> textures_[MAX_MATERIAL_TEXTURE_UNITS];
-    /// Shader parameters.
+    /// %Shader parameters.
     HashMap<StringHash, MaterialShaderParameter> shaderParameters_;
     /// Last auxiliary view rendered frame number.
     unsigned auxViewFrameNumber_;

+ 13 - 0
Engine/Graphics/OpenGL/OGLShaderProgram.cpp

@@ -145,6 +145,19 @@ bool ShaderProgram::Link()
         {
             // Set the samplers here so that they do not have to be set later
             int unit = graphics_->GetTextureUnit(name.Substring(1));
+            if (unit >= MAX_TEXTURE_UNITS)
+            {
+                // If texture unit name is not recognized, search for a digit in the name and use that as the unit index
+                for (unsigned j = 1; j < name.Length(); ++j)
+                {
+                    if (name[j] >= '0' && name[j] <= '9')
+                    {
+                        unit = name[j] - '0';
+                        break;
+                    }
+                }
+            }
+            
             if (unit < MAX_TEXTURE_UNITS)
             {
                 useTextureUnit_[unit] = true;

+ 35 - 3
Engine/Graphics/PostProcess.cpp

@@ -119,6 +119,7 @@ bool PostProcess::LoadParameters(XMLFile* file)
     
     parameterSource_ = file;
     renderTargets_.Clear();
+    shaderParameters_.Clear();
     passes_.Clear();
     
     XMLElement rtElem = rootElem.GetChild("rendertarget");
@@ -162,6 +163,16 @@ bool PostProcess::LoadParameters(XMLFile* file)
         rtElem = rtElem.GetNext("rendertarget");
     }
     
+    XMLElement parameterElem = rootElem.GetChild("parameter");
+    while (parameterElem)
+    {
+        String name = parameterElem.GetAttribute("name");
+        Vector4 value = parameterElem.GetVector("value");
+        SetShaderParameter(name, value);
+        
+        parameterElem = parameterElem.GetNext("parameter");
+    }
+    
     XMLElement passElem = rootElem.GetChild("pass");
     while (passElem)
     {
@@ -177,9 +188,14 @@ bool PostProcess::LoadParameters(XMLFile* file)
             if (textureElem.HasAttribute("unit"))
             {
                 String unitName = textureElem.GetAttributeLower("unit");
-                unit = ParseTextureUnitName(unitName);
-                if (unit == MAX_MATERIAL_TEXTURE_UNITS)
-                    LOGERROR("Unknown texture unit " + unitName);
+                if (unitName.Length() > 1)
+                {
+                    unit = ParseTextureUnitName(unitName);
+                    if (unit == MAX_MATERIAL_TEXTURE_UNITS)
+                        LOGERROR("Unknown texture unit " + unitName);
+                }
+                else
+                    unit = (TextureUnit)Clamp(ToInt(unitName), 0, MAX_MATERIAL_TEXTURE_UNITS - 1);
             }
             if (unit != MAX_MATERIAL_TEXTURE_UNITS)
             {
@@ -238,6 +254,16 @@ void PostProcess::RemoveRenderTarget(const String& name)
     renderTargets_.Erase(StringHash(name));
 }
 
+void PostProcess::SetShaderParameter(const String& name, const Vector4& value)
+{
+    shaderParameters_[StringHash(name)] = value;
+}
+
+void PostProcess::RemoveShaderParameter(const String& name)
+{
+    shaderParameters_.Erase(StringHash(name));
+}
+
 void PostProcess::SetActive(bool active)
 {
     active_ = active;
@@ -267,3 +293,9 @@ bool PostProcess::HasRenderTarget(const String& name) const
 {
     return renderTargets_.Contains(StringHash(name));
 }
+
+const Vector4& PostProcess::GetShaderParameter(const String& name) const
+{
+    HashMap<StringHash, Vector4>::ConstIterator i = shaderParameters_.Find(StringHash(name));
+    return i != shaderParameters_.End() ? i->second_ : Vector4::ZERO;
+}

+ 14 - 4
Engine/Graphics/PostProcess.h

@@ -74,9 +74,9 @@ private:
     String vertexShaderName_;
     /// Pixel shader name.
     String pixelShaderName_;
-    /// Texture names by unit.
+    /// Textures.
     String textureNames_[MAX_MATERIAL_TEXTURE_UNITS];
-    /// Shader parameters.
+    /// %Shader parameters.
     HashMap<StringHash, Vector4> shaderParameters_;
     /// Output rendertarget name.
     String outputName_;
@@ -114,6 +114,10 @@ public:
     bool CreateRenderTarget(const String& name, unsigned width, unsigned height, unsigned format, bool sizeDivisor, bool filtered);
     /// Remove a rendertarget.
     void RemoveRenderTarget(const String& name);
+    /// %Set global shader parameter.
+    void SetShaderParameter(const String& name, const Vector4& value);
+    /// Remove global shader parameter.
+    void RemoveShaderParameter(const String& name);
     /// Set active flag.
     void SetActive(bool active);
     /// Clone the post-process.
@@ -129,6 +133,10 @@ public:
     bool HasRenderTarget(const String& name) const;
     /// Return all rendertargets.
     const HashMap<StringHash, PostProcessRenderTarget>& GetRenderTargets() const { return renderTargets_; }
+    /// Return shader parameter.
+    const Vector4& GetShaderParameter(const String& name) const;
+    /// Return all global shader parameters.
+    const HashMap<StringHash, Vector4>& GetShaderParameters() const { return shaderParameters_; }
     
     /// Return active flag.
     bool IsActive() const { return active_; }
@@ -136,10 +144,12 @@ public:
 private:
     /// Parameter XML file.
     SharedPtr<XMLFile> parameterSource_;
-    /// Effect passes.
-    Vector<SharedPtr<PostProcessPass> > passes_;
     /// Rendertargets.
     HashMap<StringHash, PostProcessRenderTarget> renderTargets_;
+    /// Global shader parameters.
+    HashMap<StringHash, Vector4> shaderParameters_;
+    /// Effect passes.
+    Vector<SharedPtr<PostProcessPass> > passes_;
     /// Active flag.
     bool active_;
 };

+ 4 - 0
Engine/Graphics/View.cpp

@@ -1350,6 +1350,10 @@ void View::RunPostProcesses()
             graphics_->SetShaders(renderer_->GetVertexShader(pass->GetVertexShader()),
                 renderer_->GetPixelShader(pass->GetPixelShader()));
             
+            const HashMap<StringHash, Vector4>& globalParameters = effect->GetShaderParameters();
+            for (HashMap<StringHash, Vector4>::ConstIterator k = globalParameters.Begin(); k != globalParameters.End(); ++k)
+                graphics_->SetShaderParameter(k->first_, k->second_);
+            
             const HashMap<StringHash, Vector4>& parameters = pass->GetShaderParameters();
             for (HashMap<StringHash, Vector4>::ConstIterator k = parameters.Begin(); k != parameters.End(); ++k)
                 graphics_->SetShaderParameter(k->first_, k->second_);

+ 53 - 0
SourceAssets/GLSLShaders/Bloom.frag

@@ -0,0 +1,53 @@
+#include "Uniforms.frag"
+#include "Samplers.frag"
+
+uniform float cBloomThreshold;
+uniform vec2 cBloomMix;
+uniform vec2 cBlurOffset;
+
+// We are blurring a 4x downsampled RT, so blur offsets are 4x in terms of the original RT
+static const float offsets[5] = {
+    8.0,
+    4.0,
+    0.0,
+    -4.0,
+    -8.0,
+};
+
+static const float weights[5] = {
+    0.1,
+    0.25,
+    0.3,
+    0.25,
+    0.1
+};
+
+void main()
+{
+    #ifdef BRIGHT
+    vec3 rgb = texture2D(sDiffMap, vScreenPos + cBlurOffset * cSampleOffsets).rgb;
+    gl_FragColor = vec4((rgb - vec3(cBloomThreshold, cBloomThreshold, cBloomThreshold)) / (1.0 - cBloomThreshold), 1.0);
+    #endif
+
+    #ifdef HBLUR
+    vec3 rgb = 0.0;
+    for (int i = 0; i < 5; ++i)
+        rgb += texture2D(sDiffMap, vTexCoord + (cBlurOffset + vec2(offsets[i], 0.0)) * cSampleOffsets).rgb * weights[i];
+    gl_FragColor = vec4(rgb, 1.0);
+    #endif
+
+    #ifdef VBLUR
+    vec3 rgb = 0.0;
+    for (int i = 0; i < 5; ++i)
+        rgb += texture2D(sDiffMap, vTexCoord + (cBlurOffset + vec2(0.0, offsets[i])) * cSampleOffsets).rgb * weights[i];
+    gl_FragColor = vec4(rgb, 1.0);
+    #endif
+
+    #ifdef COMBINE
+    vec3 original = texture2D(sDiffMap, iScreenPos).rgb * cBloomMix.x;
+    vec3 bloom = texture2D(sNormalMap, vTexCoord).rgb  * cBloomMix.y;
+    // Prevent oversaturation
+    original *= (vec3(1.0, 1.0, 1.0) - clamp(bloom, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));
+    gl_FragColor = vec4(original + bloom, 1.0);
+    #endif
+}

+ 15 - 0
SourceAssets/GLSLShaders/Bloom.vert

@@ -0,0 +1,15 @@
+#include "Uniforms.vert"
+#include "Transform.vert"
+#include "ScreenPos.vert"
+
+varying vec2 vTexCoord;
+varying vec2 vScreenPos;
+
+void main()
+{
+    mat4 modelMatrix = iModelMatrix;
+    vec3 worldPos = GetWorldPos(modelMatrix);
+    gl_Position = GetClipPos(worldPos);
+    vTexCoord = GetQuadTexCoord(gl_Position);
+    vScreenPos = GetScreenPosPreDiv(gl_Position);
+}

+ 9 - 0
SourceAssets/GLSLShaders/Bloom.xml

@@ -0,0 +1,9 @@
+<shaders>
+    <shader name="Bloom" type="vs" />
+    <shader name="Bloom" type="ps">
+        <variation name="Bright" define="BRIGHT" />
+        <variation name="HBlur" define="HBLUR" />
+        <variation name="VBlur" define="VBLUR" />
+        <variation name="Combine" define="COMBINE" />
+    </shader>
+</shaders>

+ 1 - 0
SourceAssets/GLSLShaders/CMakeLists.txt

@@ -2,6 +2,7 @@ set (ALL_SHADERS)
 
 add_shader (Ambient)
 add_shader (Basic)
+add_shader (Bloom)
 add_shader (CopyFramebuffer)
 add_shader (EdgeFilter)
 add_shader (ForwardLit)

+ 7 - 0
SourceAssets/GLSLShaders/ScreenPos.vert

@@ -14,6 +14,13 @@ vec2 GetScreenPosPreDiv(vec4 clipPos)
         clipPos.y / clipPos.w * cGBufferOffsets.w + cGBufferOffsets.y);
 }
 
+vec22 GetQuadTexCoord(float4 clipPos)
+{
+    return vec2(
+        clipPos.x / clipPos.w * 0.5 + 0.5,
+        clipPos.y / clipPos.w * 0.5 + 0.5);
+}
+
 vec3 GetFarRay(vec4 clipPos)
 {
     vec3 viewRay = vec3(

+ 69 - 0
SourceAssets/HLSLShaders/Bloom.hlsl

@@ -0,0 +1,69 @@
+#include "Uniforms.hlsl"
+#include "Transform.hlsl"
+#include "Samplers.hlsl"
+#include "ScreenPos.hlsl"
+
+uniform float cBloomThreshold;
+uniform float2 cBloomMix;
+uniform float2 cBlurOffset;
+
+// We are blurring a 4x downsampled RT, so blur offsets are 4x in terms of the original RT
+static const float offsets[5] = {
+    8.0,
+    4.0,
+    0.0,
+    -4.0,
+    -8.0,
+};
+
+static const float weights[5] = {
+    0.1,
+    0.25,
+    0.3,
+    0.25,
+    0.1
+};
+
+void VS(float4 iPos : POSITION,
+    out float4 oPos : POSITION,
+    out float2 oTexCoord : TEXCOORD0,
+    out float2 oScreenPos : TEXCOORD1)
+{
+    float4x3 modelMatrix = iModelMatrix;
+    float3 worldPos = GetWorldPos(modelMatrix);
+    oPos = GetClipPos(worldPos);
+    oTexCoord = GetQuadTexCoord(oPos);
+    oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+    float2 iScreenPos : TEXCOORD1,
+    out float4 oColor : COLOR0)
+{
+    #ifdef BRIGHT
+    float3 rgb = Sample(sDiffMap, iScreenPos + cBlurOffset * cSampleOffsets).rgb;
+    oColor = float4((rgb - cBloomThreshold) / (1.0 - cBloomThreshold), 1.0);
+    #endif
+
+    #ifdef HBLUR
+    float3 rgb = 0.0;
+    for (int i = 0; i < 5; ++i)
+        rgb += Sample(sDiffMap, iTexCoord + (cBlurOffset + float2(offsets[i], 0.0)) * cSampleOffsets).rgb * weights[i];
+    oColor = float4(rgb, 1.0);
+    #endif
+
+    #ifdef VBLUR
+    float3 rgb = 0.0;
+    for (int i = 0; i < 5; ++i)
+        rgb += Sample(sDiffMap, iTexCoord + (cBlurOffset + float2(0.0, offsets[i])) * cSampleOffsets).rgb * weights[i];
+    oColor = float4(rgb, 1.0);
+    #endif
+
+    #ifdef COMBINE
+    float3 original = Sample(sDiffMap, iScreenPos).rgb * cBloomMix.x;
+    float3 bloom = Sample(sNormalMap, iTexCoord).rgb  * cBloomMix.y;
+    // Prevent oversaturation
+    original *= (1.0 - saturate(bloom));
+    oColor = float4(original + bloom, 1.0);
+    #endif
+}

+ 9 - 0
SourceAssets/HLSLShaders/Bloom.xml

@@ -0,0 +1,9 @@
+<shaders>
+    <shader name="Bloom" type="vs" />
+    <shader name="Bloom" type="ps">
+        <variation name="Bright" define="BRIGHT" />
+        <variation name="HBlur" define="HBLUR" />
+        <variation name="VBlur" define="VBLUR" />
+        <variation name="Combine" define="COMBINE" />
+    </shader>
+</shaders>

+ 1 - 0
SourceAssets/HLSLShaders/CMakeLists.txt

@@ -2,6 +2,7 @@ set (ALL_SHADERS)
 
 add_shader (Ambient)
 add_shader (Basic)
+add_shader (Bloom)
 add_shader (EdgeFilter)
 add_shader (ForwardLit)
 add_shader (GBuffer)

+ 7 - 0
SourceAssets/HLSLShaders/ScreenPos.hlsl

@@ -14,6 +14,13 @@ float2 GetScreenPosPreDiv(float4 clipPos)
         -clipPos.y / clipPos.w * cGBufferOffsets.w + cGBufferOffsets.y);
 }
 
+float2 GetQuadTexCoord(float4 clipPos)
+{
+    return float2(
+        clipPos.x / clipPos.w * 0.5 + 0.5,
+        -clipPos.y / clipPos.w * 0.5 + 0.5);
+}
+
 float3 GetFarRay(float4 clipPos)
 {
     float3 viewRay = float3(