Skip to content

Commit

Permalink
Bloom: Improved Bloom (close #164)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikhailGorobets authored and TheMostDiligent committed Apr 23, 2024
1 parent dd4bd8b commit 6bc9af7
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 68 deletions.
7 changes: 6 additions & 1 deletion PostProcess/Bloom/interface/Bloom.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ class Bloom

enum RENDER_TECH : Uint32
{
RENDER_TECH_COMPUTE_DOWNSAMPLED_TEXTURE = 0,
RENDER_TECH_COMPUTE_PREFILTERED_TEXTURE = 0,
RENDER_TECH_COMPUTE_DOWNSAMPLED_TEXTURE,
RENDER_TECH_COMPUTE_UPSAMPLED_TEXTURE,
RENDER_TECH_COUNT
};
Expand All @@ -109,10 +110,14 @@ class Bloom
RESOURCE_IDENTIFIER_COUNT
};

void ComputePrefilteredTexture(const RenderAttributes& RenderAttribs);

void ComputeDownsampledTextures(const RenderAttributes& RenderAttribs);

void ComputeUpsampledTextures(const RenderAttributes& RenderAttribs);

Int32 ComputeMipCount(Uint32 Width, Uint32 Height, float Radius);

RenderTechnique& GetRenderTechnique(RENDER_TECH RenderTech, FEATURE_FLAGS FeatureFlags);

private:
Expand Down
87 changes: 71 additions & 16 deletions PostProcess/Bloom/src/Bloom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,10 @@ void Bloom::PrepareResources(IRenderDevice* pDevice, IDeviceContext* pDeviceCont
m_BackBufferWidth = FrameDesc.Width;
m_BackBufferHeight = FrameDesc.Height;

auto ComputeTextureCount = [](Uint32 Width, Uint32 Height) {
Uint32 MaxMipCount = ComputeMipLevelsCount(Width, Height);
return static_cast<Uint32>(0.75f * static_cast<float>(MaxMipCount));
;
};

Uint32 HalfWidth = m_BackBufferWidth / 2u;
Uint32 HalfHeight = m_BackBufferHeight / 2u;
Uint32 TextureCount = ComputeTextureCount(HalfWidth, HalfHeight);
Uint32 TextureCount = ComputeMipLevelsCount(HalfWidth, HalfHeight);

RenderDeviceWithCache_N Device{pDevice};

Expand Down Expand Up @@ -148,6 +143,56 @@ void Bloom::PrepareResources(IRenderDevice* pDevice, IDeviceContext* pDeviceCont
}
}

Int32 Bloom::ComputeMipCount(Uint32 Width, Uint32 Height, float Radius)
{
Uint32 MaxMipCount = ComputeMipLevelsCount(Width, Height);
return static_cast<Int32>(Radius * static_cast<float>(MaxMipCount));
}

void Bloom::ComputePrefilteredTexture(const RenderAttributes& RenderAttribs)
{
auto& RenderTech = GetRenderTechnique(RENDER_TECH_COMPUTE_PREFILTERED_TEXTURE, m_FeatureFlags);
if (!RenderTech.IsInitializedPSO())
{
const auto VS = PostFXRenderTechnique::CreateShader(RenderAttribs.pDevice, RenderAttribs.pStateCache, "FullScreenTriangleVS.fx", "FullScreenTriangleVS", SHADER_TYPE_VERTEX);
const auto PS = PostFXRenderTechnique::CreateShader(RenderAttribs.pDevice, RenderAttribs.pStateCache, "Bloom_ComputePrefilteredTexture.fx", "ComputePrefilteredTexturePS", SHADER_TYPE_PIXEL);

PipelineResourceLayoutDescX ResourceLayout;
ResourceLayout
.AddVariable(SHADER_TYPE_PIXEL, "cbBloomAttribs", SHADER_RESOURCE_VARIABLE_TYPE_STATIC)
.AddVariable(SHADER_TYPE_PIXEL, "g_TextureInput", SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC)
.AddImmutableSampler(SHADER_TYPE_PIXEL, "g_TextureInput", Sam_LinearBoarder);

RenderTech.InitializePSO(RenderAttribs.pDevice,
RenderAttribs.pStateCache, "Bloom::ComputePrefilteredTexture",
VS, PS, ResourceLayout,
{
m_DownsampledTextures[0]->GetDesc().Format,
},
TEX_FORMAT_UNKNOWN,
DSS_DisableDepth, BS_Default, false);

ShaderResourceVariableX{RenderTech.PSO, SHADER_TYPE_PIXEL, "cbBloomAttribs"}.Set(m_Resources[RESOURCE_IDENTIFIER_CONSTANT_BUFFER]);
}

if (!RenderTech.IsInitializedSRB())
RenderTech.InitializeSRB(true);

ScopedDebugGroup DebugGroup{RenderAttribs.pDeviceContext, "ComputePrefilteredTexture"};

ITextureView* pRTVs[] = {
m_DownsampledTextures[0]->GetDefaultView(TEXTURE_VIEW_RENDER_TARGET),
};

ShaderResourceVariableX{RenderTech.SRB, SHADER_TYPE_PIXEL, "g_TextureInput"}.Set(m_Resources[RESOURCE_IDENTIFIER_INPUT_COLOR].GetTextureSRV());

RenderAttribs.pDeviceContext->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
RenderAttribs.pDeviceContext->SetPipelineState(RenderTech.PSO);
RenderAttribs.pDeviceContext->CommitShaderResources(RenderTech.SRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
RenderAttribs.pDeviceContext->Draw({3, DRAW_FLAG_VERIFY_ALL, 1});
RenderAttribs.pDeviceContext->SetRenderTargets(0, nullptr, nullptr, RESOURCE_STATE_TRANSITION_MODE_NONE);
}

void Bloom::ComputeDownsampledTextures(const RenderAttributes& RenderAttribs)
{
auto& RenderTech = GetRenderTechnique(RENDER_TECH_COMPUTE_DOWNSAMPLED_TEXTURE, m_FeatureFlags);
Expand All @@ -165,7 +210,7 @@ void Bloom::ComputeDownsampledTextures(const RenderAttributes& RenderAttribs)
RenderAttribs.pStateCache, "Bloom::ComputeDownsampledTexture",
VS, PS, ResourceLayout,
{
m_UpsampledTextures[0]->GetDesc().Format,
m_DownsampledTextures[0]->GetDesc().Format,
},
TEX_FORMAT_UNKNOWN,
DSS_DisableDepth, BS_Default, false);
Expand All @@ -176,14 +221,15 @@ void Bloom::ComputeDownsampledTextures(const RenderAttributes& RenderAttribs)

ScopedDebugGroup DebugGroup{RenderAttribs.pDeviceContext, "ComputeDownsampledTexture"};

Int32 MipCount = ComputeMipCount(m_DownsampledTextures[0]->GetDesc().Width, m_DownsampledTextures[0]->GetDesc().Height, m_BloomAttribs->Radius);
ShaderResourceVariableX TextureInputSV{RenderTech.SRB, SHADER_TYPE_PIXEL, "g_TextureInput"};
for (Uint32 TextureIdx = 0; TextureIdx < m_DownsampledTextures.size(); TextureIdx++)
for (Int32 TextureIdx = 1; TextureIdx < MipCount; TextureIdx++)
{
ITextureView* pRTVs[] = {
m_DownsampledTextures[TextureIdx]->GetDefaultView(TEXTURE_VIEW_RENDER_TARGET),
};

TextureInputSV.Set(TextureIdx != 0 ? m_DownsampledTextures[TextureIdx - 1]->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE) : m_Resources[RESOURCE_IDENTIFIER_INPUT_COLOR].GetTextureSRV());
TextureInputSV.Set(m_DownsampledTextures[TextureIdx - 1]->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE));
RenderAttribs.pDeviceContext->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
RenderAttribs.pDeviceContext->SetPipelineState(RenderTech.PSO);
RenderAttribs.pDeviceContext->CommitShaderResources(RenderTech.SRB, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
Expand Down Expand Up @@ -229,15 +275,15 @@ void Bloom::ComputeUpsampledTextures(const RenderAttributes& RenderAttribs)
ShaderResourceVariableX TextureInputSV{RenderTech.SRB, SHADER_TYPE_PIXEL, "g_TextureInput"};
ShaderResourceVariableX TextureDowsampledSV{RenderTech.SRB, SHADER_TYPE_PIXEL, "g_TextureDownsampled"};

Int32 MaxDownsampledTexture = static_cast<Int32>(m_UpsampledTextures.size() - 1);
for (Int32 TextureIdx = MaxDownsampledTexture; TextureIdx > 0; TextureIdx--)
Int32 MipCount = ComputeMipCount(m_DownsampledTextures[0]->GetDesc().Width, m_DownsampledTextures[0]->GetDesc().Height, m_BloomAttribs->Radius) - 1;
for (Int32 TextureIdx = MipCount; TextureIdx > 0; TextureIdx--)
{
ITextureView* pRTVs[] = {
m_UpsampledTextures[TextureIdx - 1]->GetDefaultView(TEXTURE_VIEW_RENDER_TARGET),
};

TextureInputSV.Set(m_DownsampledTextures[TextureIdx]->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE));
TextureDowsampledSV.Set(TextureIdx != MaxDownsampledTexture ? m_UpsampledTextures[TextureIdx]->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE) : m_DownsampledTextures[TextureIdx]->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE));
TextureDowsampledSV.Set(TextureIdx != MipCount ? m_UpsampledTextures[TextureIdx]->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE) : m_DownsampledTextures[TextureIdx]->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE));

RenderAttribs.pDeviceContext->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
RenderAttribs.pDeviceContext->SetPipelineState(RenderTech.PSO);
Expand Down Expand Up @@ -291,6 +337,7 @@ void Bloom::Execute(const RenderAttributes& RenderAttribs)
RenderAttribs.pDeviceContext->UpdateBuffer(m_Resources[RESOURCE_IDENTIFIER_CONSTANT_BUFFER].AsBuffer(), 0, sizeof(HLSL::BloomAttribs), RenderAttribs.pBloomAttribs, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
}

ComputePrefilteredTexture(RenderAttribs);
ComputeDownsampledTextures(RenderAttribs);
ComputeUpsampledTextures(RenderAttribs);

Expand Down Expand Up @@ -318,13 +365,21 @@ bool Bloom::UpdateUI(HLSL::BloomAttribs& Attribs, FEATURE_FLAGS& FeatureFlags)
{
bool AttribsChanged = false;

if (ImGui::SliderFloat("Internal Blend", &Attribs.InternalBlend, 0.0f, 1.0f))
if (ImGui::SliderFloat("Intensity", &Attribs.Intensity, 0.0f, 1.0f))
AttribsChanged = true;
ImGui::HelpMarker("The intensity of the bloom effect.");

if (ImGui::SliderFloat("Radius", &Attribs.Radius, 0.3f, 0.85f))
AttribsChanged = true;
ImGui::HelpMarker("This variable controls the size of the bloom effect. A larger radius will result in a larger area of the image being affected by the bloom effect.");

if (ImGui::SliderFloat("Threshold", &Attribs.Threshold, 0.0f, 10.0f))
AttribsChanged = true;
ImGui::HelpMarker("Linear interpolation blending between the upsample passes");
ImGui::HelpMarker("This value determines the minimum brightness required for a pixel to contribute to the bloom effect.");

if (ImGui::SliderFloat("External Blend", &Attribs.ExternalBlend, 0.0f, 0.3f))
if (ImGui::SliderFloat("Soft Threshold", &Attribs.SoftTreshold, 0.0f, 1.0f))
AttribsChanged = true;
ImGui::HelpMarker("Linear interpolation blending between the current frame and the bloom texture");
ImGui::HelpMarker("This value determines the softness of the threshold. A higher value will result in a softer threshold.");

return AttribsChanged;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Texture2D<float3> g_TextureInput;
SamplerState g_TextureInput_sampler;

float3 SampleColor(float2 Texcoord, float2 Offset)
{
return g_TextureInput.SampleLevel(g_TextureInput_sampler, Texcoord + Offset, 0.0);
}

float3 ComputeDownsampledTexturePS(in FullScreenTriangleVSOutput VSOut) : SV_Target0
{
float2 TextureResolution;
Expand All @@ -11,33 +16,26 @@ float3 ComputeDownsampledTexturePS(in FullScreenTriangleVSOutput VSOut) : SV_Tar
float2 TexelSize = rcp(TextureResolution);
float2 CenterTexcoord = NormalizedDeviceXYToTexUV(VSOut.f2NormalizedXY.xy);

float2 Coords[13];
Coords[0] = float2(-1.0f, +1.0f); Coords[1] = float2(+1.0f, +1.0f);
Coords[2] = float2(-1.0f, -1.0f); Coords[3] = float2(+1.0f, -1.0f);
float3 A = SampleColor(CenterTexcoord, TexelSize * float2(-2.0, +2.0));
float3 B = SampleColor(CenterTexcoord, TexelSize * float2(+0.0, +2.0));
float3 C = SampleColor(CenterTexcoord, TexelSize * float2(+2.0, +2.0));

Coords[4] = float2(-2.0f, +2.0f); Coords[5] = float2(+0.0f, +2.0f); Coords[6] = float2(+2.0f, +2.0f);
Coords[7] = float2(-2.0f, +0.0f); Coords[8] = float2(+0.0f, +0.0f); Coords[9] = float2(+2.0f, +0.0f);
Coords[10] = float2(-2.0f, -2.0f); Coords[11] = float2(+0.0f, -2.0f); Coords[12] = float2(+2.0f, -2.0f);
float3 D = SampleColor(CenterTexcoord, TexelSize * float2(-2.0, +0.0));
float3 E = SampleColor(CenterTexcoord, TexelSize * float2(+0.0, +0.0));
float3 F = SampleColor(CenterTexcoord, TexelSize * float2(+2.0, +0.0));

float3 G = SampleColor(CenterTexcoord, TexelSize * float2(-2.0, -2.0));
float3 H = SampleColor(CenterTexcoord, TexelSize * float2(+0.0, -2.0));
float3 I = SampleColor(CenterTexcoord, TexelSize * float2(+2.0, -2.0));

float Weights[13];
// 4 samples
// (1 / 4) * 0.5f = 0.125f
Weights[0] = 0.125f; Weights[1] = 0.125f;
Weights[2] = 0.125f; Weights[3] = 0.125f;

// 9 samples
// (1 / 9) * 0.5f
Weights[4] = 0.0555555f; Weights[5] = 0.0555555f; Weights[6] = 0.0555555f,
Weights[7] = 0.0555555f; Weights[8] = 0.0555555f; Weights[9] = 0.0555555f,
Weights[10] = 0.0555555f; Weights[11] = 0.0555555f; Weights[12] = 0.0555555f;
float3 J = SampleColor(CenterTexcoord, TexelSize * float2(-1.0, +1.0));
float3 K = SampleColor(CenterTexcoord, TexelSize * float2(+1.0, +1.0));
float3 L = SampleColor(CenterTexcoord, TexelSize * float2(-1.0, -1.0));
float3 M = SampleColor(CenterTexcoord, TexelSize * float2(+1.0, -1.0));

float3 OutColor = float3(0.0f, 0.0f, 0.0f);
for (int SampleIdx = 0; SampleIdx < 13; SampleIdx++ )
{
float2 Texcoord = CenterTexcoord + Coords[SampleIdx] * TexelSize;
OutColor += Weights[SampleIdx] * g_TextureInput.SampleLevel(g_TextureInput_sampler, Texcoord, 0.0);
}

OutColor += (A + C + G + I) * 0.03125;
OutColor += (B + D + F + H) * 0.0625;
OutColor += (E + J + K + L + M) * 0.125;
return OutColor;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "FullScreenTriangleVSOutput.fxh"
#include "BloomStructures.fxh"
#include "SRGBUtilities.fxh"
#include "PostFX_Common.fxh"

cbuffer cbBloomAttribs
{
BloomAttribs g_BloomAttribs;
}

Texture2D<float3> g_TextureInput;
SamplerState g_TextureInput_sampler;

float3 SampleColor(float2 Texcoord, float2 Offset)
{
return g_TextureInput.SampleLevel(g_TextureInput_sampler, Texcoord + Offset, 0.0);
}

float KarisAverage(float3 Color)
{
return 1.0 / (1.0 + Luminance(Color));
}

float3 Prefilter(float3 Color)
{
float Brightness = max(Color.r, max(Color.g, Color.b));
float Knee = g_BloomAttribs.Threshold * g_BloomAttribs.SoftTreshold;
float Soft = Brightness - g_BloomAttribs.Threshold + Knee;
Soft = clamp(Soft, 0.0, 2.0 * Knee);
Soft = Soft * Soft * 0.25 / (Knee + 1.0e-5);

float Contribution = max(Soft, Brightness - g_BloomAttribs.Threshold);
Contribution /= max(Brightness, 1.0e-5);
return Color * Contribution;
}

float3 ComputePrefilteredTexturePS(in FullScreenTriangleVSOutput VSOut) : SV_Target0
{
float2 TextureResolution;
g_TextureInput.GetDimensions(TextureResolution.x, TextureResolution.y);

float2 TexelSize = rcp(TextureResolution);
float2 CenterTexcoord = NormalizedDeviceXYToTexUV(VSOut.f2NormalizedXY.xy);

float3 A = SampleColor(CenterTexcoord, TexelSize * float2(-2.0, +2.0));
float3 B = SampleColor(CenterTexcoord, TexelSize * float2(+0.0, +2.0));
float3 C = SampleColor(CenterTexcoord, TexelSize * float2(+2.0, +2.0));

float3 D = SampleColor(CenterTexcoord, TexelSize * float2(-2.0, +0.0));
float3 E = SampleColor(CenterTexcoord, TexelSize * float2(+0.0, +0.0));
float3 F = SampleColor(CenterTexcoord, TexelSize * float2(+2.0, +0.0));

float3 G = SampleColor(CenterTexcoord, TexelSize * float2(-2.0, -2.0));
float3 H = SampleColor(CenterTexcoord, TexelSize * float2(+0.0, -2.0));
float3 I = SampleColor(CenterTexcoord, TexelSize * float2(+2.0, -2.0));

float3 J = SampleColor(CenterTexcoord, TexelSize * float2(-1.0, +1.0));
float3 K = SampleColor(CenterTexcoord, TexelSize * float2(+1.0, +1.0));
float3 L = SampleColor(CenterTexcoord, TexelSize * float2(-1.0, -1.0));
float3 M = SampleColor(CenterTexcoord, TexelSize * float2(+1.0, -1.0));

float Weights[5];
Weights[0] = 0.125f;
Weights[1] = 0.125f;
Weights[2] = 0.125f;
Weights[3] = 0.125f;
Weights[4] = 0.5f;

float3 Groups[5];
Groups[0] = (A + B + D + E) / 4.0f;
Groups[1] = (B + C + E + F) / 4.0f;
Groups[2] = (D + E + G + H) / 4.0f;
Groups[3] = (E + F + H + I) / 4.0f;
Groups[4] = (J + K + L + M) / 4.0f;

float4 ColorSum = float4(0.0, 0.0, 0.0, 0.0);
for (int GroupId = 0; GroupId < 5; ++GroupId) {
float Weight = Weights[GroupId] * KarisAverage(Groups[GroupId]);
ColorSum += float4(Groups[GroupId], 1.0) * Weight;
}

return Prefilter(ColorSum.xyz / (ColorSum.w + 1.0e-5));
}
46 changes: 29 additions & 17 deletions Shaders/PostProcess/Bloom/private/Bloom_ComputeUpsampledTexture.fx
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,43 @@ SamplerState g_TextureInput_sampler;
Texture2D<float3> g_TextureDownsampled;
SamplerState g_TextureDownsampled_sampler;

float3 SampleColor(float2 Texcoord, float2 Offset)
{
return g_TextureDownsampled.SampleLevel(g_TextureDownsampled_sampler, Texcoord + Offset, 0.0);
}

float3 ComputeUpsampledTexturePS(in FullScreenTriangleVSOutput VSOut) : SV_Target0
{
float2 TextureResolution;
g_TextureDownsampled.GetDimensions(TextureResolution.x, TextureResolution.y);
g_TextureInput.GetDimensions(TextureResolution.x, TextureResolution.y);

float2 TexelSize = rcp(TextureResolution);
float2 CenterTexcoord = NormalizedDeviceXYToTexUV(VSOut.f2NormalizedXY);

float2 Coords[9];
Coords[0] = float2( -1.0f, 1.0f); Coords[1] = float2(0.0f, 1.0f); Coords[2] = float2(1.0f, 1.0f);
Coords[3] = float2( -1.0f, 0.0f); Coords[4] = float2(0.0f, 0.0f); Coords[5] = float2(1.0f, 0.0f);
Coords[6] = float2( -1.0f, -1.0f); Coords[7] = float2(0.0f, -1.0f); Coords[8] = float2(1.0f, -1.0f);

float Weights[9];
Weights[0] = 0.0625f; Weights[1] = 0.125f; Weights[2] = 0.0625f;
Weights[3] = 0.125f; Weights[4] = 0.25f; Weights[5] = 0.125f;
Weights[6] = 0.0625f; Weights[7] = 0.125f; Weights[8] = 0.0625f;
float3 A = SampleColor(CenterTexcoord, TexelSize * float2(-1.0, +1.0));
float3 B = SampleColor(CenterTexcoord, TexelSize * float2(+0.0, +1.0));
float3 C = SampleColor(CenterTexcoord, TexelSize * float2(+1.0, +1.0));

float3 D = SampleColor(CenterTexcoord, TexelSize * float2(-1.0, +0.0));
float3 E = SampleColor(CenterTexcoord, TexelSize * float2(+0.0, +0.0));
float3 F = SampleColor(CenterTexcoord, TexelSize * float2(+1.0, +0.0));

float3 G = SampleColor(CenterTexcoord, TexelSize * float2(-1.0, -1.0));
float3 H = SampleColor(CenterTexcoord, TexelSize * float2(+0.0, -1.0));
float3 I = SampleColor(CenterTexcoord, TexelSize * float2(+1.0, -1.0));

float3 ColorSum = E * 0.25;
ColorSum += (B + D + F + H) * 0.125;
ColorSum += (A + C + G + I) * 0.0625;

float3 ColorSum = float3(0.0f, 0.0f, 0.0f);
for (int SampleIdx = 0; SampleIdx < 9; SampleIdx++)
if (VSOut.uInstID != 0u)
{
float2 Texcoord = CenterTexcoord + Coords[SampleIdx] * TexelSize;
ColorSum += Weights[SampleIdx] * g_TextureDownsampled.SampleLevel(g_TextureDownsampled_sampler, Texcoord, 0.0);
float3 SourceColor = g_TextureInput.SampleLevel(g_TextureInput_sampler, CenterTexcoord, 0.0);
return SourceColor + g_BloomAttribs.Intensity * ColorSum;
}
else
{
float3 SourceColor = g_TextureInput.SampleLevel(g_TextureInput_sampler, CenterTexcoord, 0.0);
return SourceColor + ColorSum;
}

float BlendFactor = VSOut.uInstID != 0u ? g_BloomAttribs.ExternalBlend : g_BloomAttribs.InternalBlend;
return lerp(g_TextureInput.SampleLevel(g_TextureInput_sampler, CenterTexcoord, 0.0), ColorSum, BlendFactor);
}
Loading

0 comments on commit 6bc9af7

Please sign in to comment.