Bloom은 카메라 렌즈에서 빛이 작동하는 방식을 시뮬레이션하기 위해 그래픽 및 게임에서 사용되는 시각 효과다. 밝은 물체 주위에 빛나는 효과를 만든다. 

 

목차

     

     


     

     

    Bloom

     

    다음의 과정을 거쳐 Bloom을 구현한다.

     

    1. 장면을 텍스처로 렌더링: 첫 번째 단계는 장면을 BackBuffer 대신 텍스처로 렌더링하는 것이다. 이를 통해 원래 렌더링된 이미지에 영향을 주지 않고 장면에 후처리 효과를 적용할 수 있다.
    2. 임계값 적용: 다음으로 텍스처에 임계값을 적용하여 장면에서 밝은 픽셀을 식별한다. 이는 각 픽셀의 색상 값을 임계값과 비교하여 수행된다. 픽셀의 색상 값이 임계값보다 크면 밝은 픽셀로 간주되어 추가 처리된다.
    3. 밝은 픽셀 추출: 픽셀 셰이더를 사용하여 다른 텍스처로 렌더링하여 장면에서 밝은 픽셀을 추출한다. 이 쉐이더는 블러링 또는 글로우 효과 추가와 같은 원하는 효과를 밝은 픽셀에 적용하는 데 사용할 수 있다.
    4. 밝은 픽셀을 원본 장면과 결합: 밝은 픽셀을 원본 텍스처 위에 렌더링하여 원본 장면과 결합한다. 이것은 두 텍스처를 함께 혼합하여 최종 블룸 효과를 생성하는 또 다른 픽셀 셰이더를 사용하여 수행된다.
    5. BackBuffer로 렌더링: 마지막으로 결합된 텍스처를 백 버퍼로 렌더링하여 표시합니다.

    DirectX에서 Bloom을 구현하려면 Post Effect 기술을 사용하여 장면에서 밝은 픽셀을 식별하고 추출한 다음 원본 장면과 결합하여 빛나거나 후광 효과를 만든다. 

     


     

    Bloom.fx

     

    Bloom.fx

    더보기
    #include "00_Global.fx"
    #include "00_Light.fx"
    float2 PixelSize;
    struct VertexOutput
    {
    float4 Position : SV_Position;
    float2 Uv : Uv;
    };
    VertexOutput VS(float4 Position : Position)
    {
    VertexOutput output;
    output.Position = Position;
    output.Uv.x = Position.x * 0.5f + 0.5f;
    output.Uv.y = -Position.y * 0.5f + 0.5f;
    return output;
    }
    float4 PS_Diffuse(VertexOutput input) : SV_Target
    {
    return DiffuseMap.Sample(LinearSampler, input.Uv);
    }
    float Threshold = 0.6f;
    float4 PS_Luminosity(VertexOutput input) : SV_Target
    {
    float4 color = DiffuseMap.Sample(LinearSampler, input.Uv);
    return saturate((color - Threshold) / (1 - Threshold));
    }
    #define MAX_SAMPLE_COUNT 33
    int SampleCount = 15;
    float2 Offsets[MAX_SAMPLE_COUNT];
    float Weights[MAX_SAMPLE_COUNT];
    float4 PS_Blur(VertexOutput input) : SV_Target
    {
    float4 color = 0;
    for (int i = 0; i < SampleCount; i++)
    color += DiffuseMap.Sample(LinearSampler, input.Uv + Offsets[i]) * Weights[i];
    return color;
    }
    Texture2D LuminosityMap;
    Texture2D BlurMap;
    float4 PS_Composite(VertexOutput input) : SV_Target
    {
    float4 luminosity = LuminosityMap.Sample(LinearSampler, input.Uv);
    float4 blur = BlurMap.Sample(LinearSampler, input.Uv);
    luminosity *= (1.0f - saturate(blur));
    return float4((luminosity + blur).rgb, 1.0f);
    }
    technique11 T0
    {
    P_VP(P0, VS, PS_Diffuse)
    P_VP(P1, VS, PS_Luminosity)
    P_VP(P2, VS, PS_Blur)
    P_VP(P3, VS, PS_Composite)
    }

     

    float Threshold = 0.6f;
    float4 PS_Luminosity(VertexOutput input) : SV_Target
    {
    float4 color = DiffuseMap.Sample(LinearSampler, input.Uv);
    return saturate((color - Threshold) / (1 - Threshold));
    }

    threshold보다

    • 작은 값은 어두워진다.
    • 큰 값은 밝아진다.

    threshold보다 큰 값을 추출한다. 이렇게해서 밝은면을 추출한다. 


     

    Bloom Demo

     

    BloomDemo.h

    더보기
    #pragma once
    #include "Systems/IExecute.h"
    class BloomDemo : public IExecute
    {
    public:
    virtual void Initialize() override;
    virtual void Ready() override {}
    virtual void Destroy() override {}
    virtual void Update() override;
    virtual void PreRender() override;
    virtual void Render() override;
    virtual void PostRender() override;
    virtual void ResizeScreen() override {}
    private:
    void Billboards();
    void Mesh();
    void Airplane();
    void Kachujin();
    void KachujinCollider();
    void KachujinWeapon();
    void PointLighting();
    void SpotLighting();
    void Pass(UINT mesh, UINT model, UINT anim);
    private:
    void SetBlur();
    void GetBlurParameter(vector<float>& weights, vector<Vector2>& offsets, float x, float y);
    float GetGaussFunction(float val);
    private:
    bool bOrigin = false;
    float threshold = 0.05f;
    UINT blurCount = 15;
    //가중치 계산
    vector<float> weightX;
    vector<Vector2> offsetX;
    vector<float> weightY;
    vector<Vector2> offsetY;
    private:
    Shader* shader;
    RenderTarget* renderTarget[5];
    DepthStencil* depthStencil;
    Viewport* viewport;
    Render2D* render2D;
    PostEffect* postEffect;
    Billboard* billboard;
    CubeSky* sky;
    Material* floor;
    Material* stone;
    Material* brick;
    Material* wall;
    MeshRender* cube;
    MeshRender* cylinder;
    MeshRender* sphere;
    MeshRender* grid;
    ModelRender* airplane = NULL;
    ModelAnimator* kachujin = NULL;
    Transform* colliderInitTransforms;
    ColliderObject** colliders;
    ModelRender* weapon = NULL;
    Transform* weaponInitTransform;
    vector<MeshRender *> meshes;
    vector<ModelRender *> models;
    vector<ModelAnimator *> animators;
    };

    가중치 계산

    • vector<float> weightX;
    • vector<Vector2> offsetX;
    • vector<float> weightY;
    • vector<Vector2> offsetY;

     

     

    BloomDemo.cpp

    더보기
    #include "stdafx.h"
    #include "BloomDemo.h"
    void BloomDemo::Initialize()
    {
    Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
    Context::Get()->GetCamera()->Position(1, 36, -85);
    ((Freedom *)Context::Get()->GetCamera())->Speed(50, 2);
    shader = new Shader(L"96_Billboard.fxo");
    float width = D3D::Width(), height = D3D::Height();
    width = height = 4096;
    renderTarget[0] = new RenderTarget((UINT)width, (UINT)height); //Diffuse
    renderTarget[1] = new RenderTarget((UINT)width, (UINT)height); //Luminosity
    renderTarget[2] = new RenderTarget((UINT)width, (UINT)height); //BlurX
    renderTarget[3] = new RenderTarget((UINT)width, (UINT)height); //BlurY
    renderTarget[4] = new RenderTarget((UINT)width, (UINT)height); //Composite
    depthStencil = new DepthStencil((UINT)width, (UINT)height);
    viewport = new Viewport(width, height);
    render2D = new Render2D();
    render2D->GetTransform()->Scale(355, 200, 1);
    render2D->GetTransform()->Position(200, 120, 0);
    render2D->SRV(renderTarget[0]->SRV());
    postEffect = new PostEffect(L"106_Bloom.fxo");
    sky = new CubeSky(L"Environment/GrassCube1024.dds");
    Billboards();
    Mesh();
    Airplane();
    Kachujin();
    KachujinCollider();
    KachujinWeapon();
    PointLighting();
    SpotLighting();
    }
    void BloomDemo::Update()
    {
    static bool bOrigin = false;
    ImGui::Checkbox("Origin", &bOrigin);
    ImGui::InputFloat("Threshold", &threshold, 0.01f);
    postEffect->GetShader()->AsScalar("Threshold")->SetFloat(threshold);
    ImGui::InputInt("BlurCount", (int *)&blurCount, 2);
    blurCount %= 33;
    if (blurCount < 1)
    blurCount = 1;
    postEffect->GetShader()->AsScalar("BlurCount")->SetInt(blurCount);
    sky->Update();
    cube->Update();
    grid->Update();
    cylinder->Update();
    sphere->Update();
    airplane->Update();
    kachujin->Update();
    Matrix worlds[MAX_MODEL_TRANSFORMS];
    for (UINT i = 0; i < kachujin->GetTransformCount(); i++)
    {
    kachujin->GetAttachTransform(i, worlds);
    weapon->GetTransform(i)->World(weaponInitTransform->World() * worlds[40]);
    }
    weapon->UpdateTransforms();
    weapon->Update();
    billboard->Update();
    render2D->Update();
    postEffect->Update();
    }
    void BloomDemo::PreRender()
    {
    viewport->RSSetViewport();
    //Render
    {
    renderTarget[0]->PreRender(depthStencil);
    sky->Render();
    Pass(0, 1, 2);
    wall->Render();
    sphere->Render();
    brick->Render();
    cylinder->Render();
    stone->Render();
    cube->Render();
    floor->Render();
    grid->Render();
    airplane->Render();
    kachujin->Render();
    weapon->Render();
    billboard->Render();
    }
    Vector2 PixelSize = Vector2(1.0f / D3D::Width(), 1.0f / D3D::Height());
    postEffect->GetShader()->AsVector("PixelSize")->SetFloatVector(PixelSize);
    //Luminosity
    {
    renderTarget[1]->PreRender(depthStencil);
    postEffect->Pass(1);
    postEffect->SRV(renderTarget[0]->SRV());
    postEffect->Render();
    }
    SetBlur(); //Blur값을 계산하고 넘겨준다.
    //BlurX
    {
    postEffect->GetShader()->AsScalar("Weights")->SetFloatArray(&weightX[0], 0, weightX.size());
    postEffect->GetShader()->AsVector("Offsets")->SetRawValue(&offsetX[0], 0, sizeof(Vector2) * offsetX.size());
    renderTarget[2]->PreRender(depthStencil);
    viewport->RSSetViewport();
    postEffect->SRV(renderTarget[1]->SRV());
    postEffect->Pass(2);
    postEffect->Render();
    }
    //BlurY
    {
    postEffect->GetShader()->AsScalar("Weights")->SetFloatArray(&weightY[0], 0, weightY.size());
    postEffect->GetShader()->AsVector("Offsets")->SetRawValue(&offsetY[0], 0, sizeof(Vector2) * offsetY.size());
    renderTarget[3]->PreRender(depthStencil);
    viewport->RSSetViewport();
    postEffect->SRV(renderTarget[2]->SRV());
    postEffect->Pass(2);
    postEffect->Render();
    }
    //Comsite
    {
    renderTarget[4]->PreRender(depthStencil);
    viewport->RSSetViewport();
    postEffect->GetShader()->AsSRV("LuminosityMap")->SetResource(renderTarget[1]->SRV());
    postEffect->GetShader()->AsSRV("BlurMap")->SetResource(renderTarget[3]->SRV());
    postEffect->Pass(3);
    postEffect->Render();
    }
    }
    void BloomDemo::Render()
    {
    }
    void BloomDemo::PostRender()
    {
    postEffect->Pass(0);
    postEffect->SRV(renderTarget[4]->SRV());
    postEffect->Render();
    render2D->Render();
    }
    void BloomDemo::Billboards()
    {
    billboard = new Billboard(shader);
    //billboard->Pass(3);
    billboard->Pass(4);
    billboard->AddTexture(L"Terrain/grass_14.tga");
    billboard->AddTexture(L"Terrain/grass_07.tga");
    billboard->AddTexture(L"Terrain/grass_11.tga");
    for (UINT i = 0; i < 1200; i++)
    {
    Vector2 scale = Math::RandomVec2(1, 3);
    Vector2 position = Math::RandomVec2(-60, 60);
    billboard->Add(Vector3(position.x, scale.y * 0.5f, position.y), scale, 0);
    }
    for (UINT i = 0; i < 300; i++)
    {
    Vector2 scale = Math::RandomVec2(1, 3);
    Vector2 position = Math::RandomVec2(-60, 60);
    billboard->Add(Vector3(position.x, scale.y * 0.5f, position.y), scale, 1);
    }
    for (UINT i = 0; i < 700; i++)
    {
    Vector2 scale = Math::RandomVec2(1, 3);
    Vector2 position = Math::RandomVec2(-60, 60);
    billboard->Add(Vector3(position.x, scale.y * 0.5f, position.y), scale, 2);
    }
    }
    void BloomDemo::Mesh()
    {
    //Create Material
    {
    floor = new Material(shader);
    floor->DiffuseMap("Floor.png");
    floor->Specular(1, 1, 1, 20);
    floor->SpecularMap("Floor_Specular.png");
    floor->NormalMap("Floor_Normal.png");
    stone = new Material(shader);
    stone->DiffuseMap("Stones.png");
    stone->Specular(1, 1, 1, 20);
    stone->SpecularMap("Stones_Specular.png");
    stone->Emissive(0.15f, 0.15f, 0.15f, 0.3f);
    stone->NormalMap("Stones_Normal.png");
    brick = new Material(shader);
    brick->DiffuseMap("Bricks.png");
    brick->Specular(1, 0.3f, 0.3f, 20);
    brick->SpecularMap("Bricks_Specular.png");
    brick->Emissive(0.15f, 0.15f, 0.15f, 0.3f);
    brick->NormalMap("Bricks_Normal.png");
    wall = new Material(shader);
    wall->DiffuseMap("Wall.png");
    wall->Specular(1, 1, 1, 20);
    wall->SpecularMap("Wall_Specular.png");
    wall->Emissive(0.15f, 0.15f, 0.15f, 0.3f);
    wall->NormalMap("Wall_Normal.png");
    }
    //Create Mesh
    {
    Transform* transform = NULL;
    cube = new MeshRender(shader, new MeshCube());
    transform = cube->AddTransform();
    transform->Position(0, 5, 0);
    transform->Scale(20, 10, 20);
    grid = new MeshRender(shader, new MeshGrid(5, 5));
    transform = grid->AddTransform();
    transform->Position(0, 0, 0);
    transform->Scale(12, 1, 12);
    cylinder = new MeshRender(shader, new MeshCylinder(0.5f, 3.0f, 20, 20));
    sphere = new MeshRender(shader, new MeshSphere(0.5f, 20, 20));
    for (UINT i = 0; i < 5; i++)
    {
    transform = cylinder->AddTransform();
    transform->Position(-30, 6, -15.0f + (float)i * 15.0f);
    transform->Scale(5, 5, 5);
    transform = cylinder->AddTransform();
    transform->Position(30, 6, -15.0f + (float)i * 15.0f);
    transform->Scale(5, 5, 5);
    transform = sphere->AddTransform();
    transform->Position(-30, 15.5f, -15.0f + (float)i * 15.0f);
    transform->Scale(5, 5, 5);
    transform = sphere->AddTransform();
    transform->Position(30, 15.5f, -15.0f + (float)i * 15.0f);
    transform->Scale(5, 5, 5);
    }
    }
    sphere->UpdateTransforms();
    cylinder->UpdateTransforms();
    cube->UpdateTransforms();
    grid->UpdateTransforms();
    meshes.push_back(sphere);
    meshes.push_back(cylinder);
    meshes.push_back(cube);
    meshes.push_back(grid);
    }
    void BloomDemo::Airplane()
    {
    airplane = new ModelRender(shader);
    airplane->ReadMesh(L"B787/Airplane");
    airplane->ReadMaterial(L"B787/Airplane");
    Transform* transform = airplane->AddTransform();
    transform->Position(2.0f, 9.91f, 2.0f);
    transform->Scale(0.004f, 0.004f, 0.004f);
    airplane->UpdateTransforms();
    models.push_back(airplane);
    }
    void BloomDemo::Kachujin()
    {
    kachujin = new ModelAnimator(shader);
    kachujin->ReadMesh(L"Kachujin/Mesh");
    kachujin->ReadMaterial(L"Kachujin/Mesh");
    kachujin->ReadClip(L"Kachujin/Sword And Shield Idle");
    kachujin->ReadClip(L"Kachujin/Sword And Shield Walk");
    kachujin->ReadClip(L"Kachujin/Sword And Shield Run");
    kachujin->ReadClip(L"Kachujin/Sword And Shield Slash");
    kachujin->ReadClip(L"Kachujin/Salsa Dancing");
    Transform* transform = NULL;
    transform = kachujin->AddTransform();
    transform->Position(0, 0, -30);
    transform->Scale(0.075f, 0.075f, 0.075f);
    kachujin->PlayTweenMode(0, 0, 1.0f);
    transform = kachujin->AddTransform();
    transform->Position(-15, 0, -30);
    transform->Scale(0.075f, 0.075f, 0.075f);
    kachujin->PlayTweenMode(1, 1, 1.0f);
    transform = kachujin->AddTransform();
    transform->Position(-30, 0, -30);
    transform->Scale(0.075f, 0.075f, 0.075f);
    kachujin->PlayTweenMode(2, 2, 0.75f);
    transform = kachujin->AddTransform();
    transform->Position(15, 0, -30);
    transform->Scale(0.075f, 0.075f, 0.075f);
    kachujin->PlayBlendMode(3, 0, 1, 2);
    kachujin->SetBlendAlpha(3, 1.5f);
    transform = kachujin->AddTransform();
    transform->Position(30, 0, -32.5f);
    transform->Scale(0.075f, 0.075f, 0.075f);
    kachujin->PlayTweenMode(4, 4, 0.75f);
    kachujin->UpdateTransforms();
    animators.push_back(kachujin);
    }
    void BloomDemo::KachujinCollider()
    {
    UINT count = kachujin->GetTransformCount();
    colliders = new ColliderObject*[count];
    colliderInitTransforms = new Transform();
    colliderInitTransforms->Position(-2.9f, 1.45f, -50.0f);
    colliderInitTransforms->Scale(5, 5, 75);
    for (UINT i = 0; i < count; i++)
    {
    colliders[i] = new ColliderObject();
    //colliders[i]->Init = new Transform();
    //colliders[i]->Init->Position(0, 0, 0);
    //colliders[i]->Init->Scale(10, 30, 10);
    colliders[i]->Transform = new Transform();
    //colliders[i]->Collider = new Collider(colliders[i]->Transform, colliders[i]->Init);
    colliders[i]->Collider = new Collider(colliders[i]->Transform, colliderInitTransforms);
    }
    }
    void BloomDemo::KachujinWeapon()
    {
    weapon = new ModelRender(shader);
    weapon->ReadMesh(L"Weapon/Sword");
    weapon->ReadMaterial(L"Weapon/Sword");
    UINT count = kachujin->GetTransformCount();
    for (UINT i = 0; i < count; i++)
    weapon->AddTransform();
    weapon->UpdateTransforms();
    models.push_back(weapon);
    weaponInitTransform = new Transform();
    weaponInitTransform->Position(-2.9f, 1.45f, -6.45f);
    weaponInitTransform->Scale(0.5f, 0.5f, 0.75f);
    weaponInitTransform->Rotation(0, 0, 1);
    }
    void BloomDemo::PointLighting()
    {
    PointLight light;
    light =
    {
    Color(0.0f, 0.0f, 0.0f, 1.0f), //Ambient
    Color(0.0f, 0.0f, 1.0f, 1.0f), //Diffuse
    Color(0.0f, 0.0f, 0.7f, 1.0f), //Specular
    Color(0.0f, 0.0f, 0.7f, 1.0f), //Emissive
    Vector3(-30, 10, -30), 5.0f, 0.9f
    };
    Lighting::Get()->AddPointLight(light);
    light =
    {
    Color(0.0f, 0.0f, 0.0f, 1.0f),
    Color(1.0f, 0.0f, 0.0f, 1.0f),
    Color(0.6f, 0.2f, 0.0f, 1.0f),
    Color(0.6f, 0.3f, 0.0f, 1.0f),
    Vector3(15, 10, -30), 10.0f, 0.3f
    };
    Lighting::Get()->AddPointLight(light);
    light =
    {
    Color(0.0f, 0.0f, 0.0f, 1.0f), //Ambient
    Color(0.0f, 1.0f, 0.0f, 1.0f), //Diffuse
    Color(0.0f, 0.7f, 0.0f, 1.0f), //Specular
    Color(0.0f, 0.7f, 0.0f, 1.0f), //Emissive
    Vector3(-5, 1, -17.5f), 5.0f, 0.9f
    };
    Lighting::Get()->AddPointLight(light);
    light =
    {
    Color(0.0f, 0.0f, 0.0f, 1.0f),
    Color(0.0f, 0.0f, 1.0f, 1.0f),
    Color(0.0f, 0.0f, 0.7f, 1.0f),
    Color(0.0f, 0.0f, 0.7f, 1.0f),
    Vector3(-10, 1, -17.5f), 5.0f, 0.9f
    };
    Lighting::Get()->AddPointLight(light);
    }
    void BloomDemo::SpotLighting()
    {
    SpotLight light;
    light =
    {
    Color(0.3f, 1.0f, 0.0f, 1.0f),
    Color(0.7f, 1.0f, 0.0f, 1.0f),
    Color(0.3f, 1.0f, 0.0f, 1.0f),
    Color(0.3f, 1.0f, 0.0f, 1.0f),
    Vector3(-15, 20, -30), 25.0f,
    Vector3(0, -1, 0), 30.0f, 0.4f
    };
    Lighting::Get()->AddSpotLight(light);
    light =
    {
    Color(1.0f, 0.2f, 0.9f, 1.0f),
    Color(1.0f, 0.2f, 0.9f, 1.0f),
    Color(1.0f, 0.2f, 0.9f, 1.0f),
    Color(1.0f, 0.2f, 0.9f, 1.0f),
    Vector3(0, 20, -30), 30.0f,
    Vector3(0, -1, 0), 40.0f, 0.55f
    };
    Lighting::Get()->AddSpotLight(light);
    }
    void BloomDemo::Pass(UINT mesh, UINT model, UINT anim)
    {
    for (MeshRender* temp : meshes)
    temp->Pass(mesh);
    for (ModelRender* temp : models)
    temp->Pass(model);
    for (ModelAnimator* temp : animators)
    temp->Pass(anim);
    }
    void BloomDemo::SetBlur()
    {
    float x = 1.0f / D3D::Width();
    float y = 1.0f / D3D::Height();
    GetBlurParameter(weightX, offsetX, x, 0);
    GetBlurParameter(weightY, offsetY, 0, y);
    }
    void BloomDemo::GetBlurParameter(vector<float>& weights, vector<Vector2>& offsets, float x, float y)
    {
    weights.clear();
    weights.assign(blurCount, float());
    offsets.clear();
    offsets.assign(blurCount, Vector2());
    weights[0] = GetGaussFunction(0); //1
    offsets[0] = Vector2(0, 0);
    float sum = weights[0];
    for (UINT i = 0; i < blurCount / 2; i++)
    {
    float temp = GetGaussFunction((float)(i + 1));
    weights[i * 2 + 1] = temp;
    weights[i * 2 + 2] = temp;
    sum += temp * 2;
    Vector2 temp2 = Vector2(x, y) * (i * 2 + 1.5f);
    offsets[i * 2 + 1] = temp2;
    offsets[i * 2 + 2] = -temp2;
    }
    for (UINT i = 0; i < blurCount; i++)
    weights[i] /= sum;
    }
    float BloomDemo::GetGaussFunction(float val)
    {
    return (float)((1.0 / sqrt(2 * Math::PI * blurCount)) * exp(-(val * val) / (2 * blurCount * blurCount)));
    }

     


     

    실행화면