mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
Adds sRGB support for proper lighting
Currently we're incorrectly using sRGB colors as raw inputs for lighting instead of conversion to linear space. For proper PBR we need linear values.
This commit is contained in:
parent
e2f5b20922
commit
f781181899
14 changed files with 116 additions and 27 deletions
|
|
@ -78,6 +78,9 @@
|
|||
<optional>
|
||||
<attribute name="define"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="srgb"/>
|
||||
</optional>
|
||||
</element>
|
||||
</zeroOrMore>
|
||||
</interleave>
|
||||
|
|
|
|||
|
|
@ -62,8 +62,7 @@ void CMaterial::AddRenderQuery(const char* key)
|
|||
m_RenderQueries.Add(key);
|
||||
}
|
||||
|
||||
void CMaterial::AddRequiredSampler(const CStr& samplerName)
|
||||
void CMaterial::AddRequiredSampler(RequiredSampler sampler)
|
||||
{
|
||||
CStrIntern string(samplerName);
|
||||
m_RequiredSamplers.push_back(string);
|
||||
m_RequiredSamplers.emplace_back(std::move(sampler));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ public:
|
|||
CTexturePtr Sampler;
|
||||
};
|
||||
|
||||
struct RequiredSampler
|
||||
{
|
||||
CStrIntern name;
|
||||
bool sRGB;
|
||||
};
|
||||
|
||||
typedef std::vector<TextureSampler> SamplersVector;
|
||||
|
||||
CMaterial();
|
||||
|
|
@ -82,8 +88,8 @@ public:
|
|||
void AddRenderQuery(const char* key);
|
||||
const CShaderRenderQueries& GetRenderQueries() const { return m_RenderQueries; }
|
||||
|
||||
void AddRequiredSampler(const CStr& samplerName);
|
||||
const std::vector<CStrIntern>& GetRequiredSampler() const { return m_RequiredSamplers; }
|
||||
void AddRequiredSampler(RequiredSampler sampler);
|
||||
const std::vector<RequiredSampler>& GetRequiredSamplers() const { return m_RequiredSamplers; }
|
||||
|
||||
private:
|
||||
|
||||
|
|
@ -92,7 +98,7 @@ private:
|
|||
CTexturePtr m_DiffuseTexture;
|
||||
|
||||
SamplersVector m_Samplers;
|
||||
std::vector<CStrIntern> m_RequiredSamplers;
|
||||
std::vector<RequiredSampler> m_RequiredSamplers;
|
||||
|
||||
std::array<CStrIntern, static_cast<size_t>(Pass::COUNT)> m_ShaderEffects;
|
||||
CShaderDefines m_ShaderDefines;
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname)
|
|||
AT(material);
|
||||
AT(name);
|
||||
AT(pass);
|
||||
AT(srgb);
|
||||
AT(value);
|
||||
#undef AT
|
||||
#undef EL
|
||||
|
|
@ -161,7 +162,8 @@ CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname)
|
|||
}
|
||||
else if (token == el_required_texture)
|
||||
{
|
||||
material.AddRequiredSampler(attrs.GetNamedItem(at_name));
|
||||
const bool sRGB{attrs.GetNamedItem(at_srgb) == "true"};
|
||||
material.AddRequiredSampler({CStrIntern{attrs.GetNamedItem(at_name)}, sRGB});
|
||||
if (!attrs.GetNamedItem(at_define).empty())
|
||||
material.AddShaderDefine(CStrIntern(attrs.GetNamedItem(at_define)), str_1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,22 @@
|
|||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool ShouldInterpretTextureAsSRGB(const CObjectBase::Samp& sampler, const CMaterial& material)
|
||||
{
|
||||
const auto requiredSamplers{material.GetRequiredSamplers()};
|
||||
auto it{std::find_if(requiredSamplers.begin(), requiredSamplers.end(),
|
||||
[&](const CMaterial::RequiredSampler& requiredSampler)
|
||||
{
|
||||
return requiredSampler.name == sampler.m_SamplerName;
|
||||
})};
|
||||
return it != requiredSamplers.end() && it->sRGB;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CObjectEntry::CObjectEntry(const std::shared_ptr<CObjectBase>& base, const CSimulation2& simulation) :
|
||||
m_Base(base), m_Color(1.0f, 1.0f, 1.0f, 1.0f), m_Simulation(simulation)
|
||||
{
|
||||
|
|
@ -155,6 +171,7 @@ bool CObjectEntry::BuildVariation(const std::vector<const std::set<CStr>*>& comp
|
|||
{
|
||||
CTextureProperties textureProps(samp.m_SamplerFile);
|
||||
textureProps.SetAddressMode(Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
|
||||
textureProps.SetSRGB(ShouldInterpretTextureAsSRGB(samp, material));
|
||||
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
|
||||
// if we've loaded this model we're probably going to render it soon, so prefetch its texture.
|
||||
// All textures are prefetched even in the fixed pipeline, including the normal maps etc.
|
||||
|
|
@ -167,11 +184,14 @@ bool CObjectEntry::BuildVariation(const std::vector<const std::set<CStr>*>& comp
|
|||
CModel* model = newModel.get();
|
||||
m_Model = std::move(newModel);
|
||||
|
||||
for (const CStrIntern& requSampName : model->GetMaterial().GetRequiredSampler())
|
||||
for (const auto& requiredSampler : model->GetMaterial().GetRequiredSamplers())
|
||||
{
|
||||
if (std::find_if(m_Samplers.begin(), m_Samplers.end(),
|
||||
[&](const CObjectBase::Samp& sampler) { return sampler.m_SamplerName == requSampName; }) == m_Samplers.end())
|
||||
LOGERROR("Actor %s: required texture sampler %s not found (material %s)", m_Base->GetIdentifier(), requSampName.string().c_str(), m_Base->m_Material.string8().c_str());
|
||||
[&](const CObjectBase::Samp& sampler) { return sampler.m_SamplerName == requiredSampler.name; }) == m_Samplers.end())
|
||||
{
|
||||
LOGERROR("Actor %s: required texture sampler %s not found (material %s)",
|
||||
m_Base->GetIdentifier(), requiredSampler.name.string().c_str(), m_Base->m_Material.string8().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// calculate initial object space bounds, based on vertex positions
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -60,7 +60,7 @@ namespace
|
|||
{
|
||||
|
||||
Renderer::Backend::Format ChooseFormatAndTransformTextureDataIfNeeded(
|
||||
Renderer::Backend::IDevice* device, Tex& textureData, const bool hasS3TC)
|
||||
Renderer::Backend::IDevice* device, Tex& textureData, const bool hasS3TC, const bool sRGB)
|
||||
{
|
||||
const bool alpha = (textureData.m_Flags & TEX_ALPHA) != 0;
|
||||
const bool grey = (textureData.m_Flags & TEX_GREY) != 0;
|
||||
|
|
@ -81,13 +81,13 @@ Renderer::Backend::Format ChooseFormatAndTransformTextureDataIfNeeded(
|
|||
switch (dxt)
|
||||
{
|
||||
case DXT1A:
|
||||
return Renderer::Backend::Format::BC1_RGBA_UNORM;
|
||||
return sRGB ? Renderer::Backend::Format::BC1_RGBA_SRGB : Renderer::Backend::Format::BC1_RGBA_UNORM;
|
||||
case 1:
|
||||
return Renderer::Backend::Format::BC1_RGB_UNORM;
|
||||
return sRGB ? Renderer::Backend::Format::BC1_RGB_SRGB : Renderer::Backend::Format::BC1_RGB_UNORM;
|
||||
case 3:
|
||||
return Renderer::Backend::Format::BC2_UNORM;
|
||||
return sRGB ? Renderer::Backend::Format::BC2_SRGB : Renderer::Backend::Format::BC2_UNORM;
|
||||
case 5:
|
||||
return Renderer::Backend::Format::BC3_UNORM;
|
||||
return sRGB ? Renderer::Backend::Format::BC3_SRGB : Renderer::Backend::Format::BC3_UNORM;
|
||||
default:
|
||||
LOGERROR("Unknown DXT compression.");
|
||||
return Renderer::Backend::Format::UNDEFINED;
|
||||
|
|
@ -101,9 +101,12 @@ Renderer::Backend::Format ChooseFormatAndTransformTextureDataIfNeeded(
|
|||
{
|
||||
case 8:
|
||||
ENSURE(grey);
|
||||
ENSURE(!sRGB);
|
||||
return Renderer::Backend::Format::L8_UNORM;
|
||||
case 24:
|
||||
ENSURE(grey);
|
||||
ENSURE(!alpha);
|
||||
ENSURE(!sRGB);
|
||||
if (device->IsTextureFormatSupported(Renderer::Backend::Format::R8G8B8_UNORM))
|
||||
return Renderer::Backend::Format::R8G8B8_UNORM;
|
||||
else
|
||||
|
|
@ -114,7 +117,7 @@ Renderer::Backend::Format ChooseFormatAndTransformTextureDataIfNeeded(
|
|||
}
|
||||
case 32:
|
||||
ENSURE(alpha);
|
||||
return Renderer::Backend::Format::R8G8B8A8_UNORM;
|
||||
return sRGB ? Renderer::Backend::Format::R8G8B8A8_SRGB : Renderer::Backend::Format::R8G8B8A8_UNORM;
|
||||
default:
|
||||
LOGERROR("Unsupported BPP: %zu", textureData.m_Bpp);
|
||||
}
|
||||
|
|
@ -336,6 +339,7 @@ struct TPhash
|
|||
hash_combine(seed, textureProperties.m_AnisotropicFilterEnabled);
|
||||
hash_combine(seed, textureProperties.m_FormatOverride);
|
||||
hash_combine(seed, textureProperties.m_IgnoreQuality);
|
||||
hash_combine(seed, textureProperties.m_SRGB);
|
||||
return seed;
|
||||
}
|
||||
|
||||
|
|
@ -358,7 +362,8 @@ struct TPequal_to
|
|||
lhs.m_AddressModeV == rhs.m_AddressModeV &&
|
||||
lhs.m_AnisotropicFilterEnabled == rhs.m_AnisotropicFilterEnabled &&
|
||||
lhs.m_FormatOverride == rhs.m_FormatOverride &&
|
||||
lhs.m_IgnoreQuality == rhs.m_IgnoreQuality;
|
||||
lhs.m_IgnoreQuality == rhs.m_IgnoreQuality &&
|
||||
lhs.m_SRGB == rhs.m_SRGB;
|
||||
}
|
||||
|
||||
bool operator()(const CTexturePtr& lhs, const CTexturePtr& rhs) const
|
||||
|
|
@ -526,7 +531,8 @@ public:
|
|||
}
|
||||
else
|
||||
{
|
||||
format = ChooseFormatAndTransformTextureDataIfNeeded(m_Device, textureData, m_HasS3TC);
|
||||
format = ChooseFormatAndTransformTextureDataIfNeeded(
|
||||
m_Device, textureData, m_HasS3TC, texture->m_Properties.m_SRGB);
|
||||
}
|
||||
|
||||
if (format == Renderer::Backend::Format::UNDEFINED)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -245,6 +245,8 @@ public:
|
|||
|
||||
void SetIgnoreQuality(bool ignore) { m_IgnoreQuality = ignore; }
|
||||
|
||||
void SetSRGB(const bool sRGB) { m_SRGB = sRGB; }
|
||||
|
||||
private:
|
||||
// Must update TPhash, TPequal_to when changing these fields
|
||||
VfsPath m_Path;
|
||||
|
|
@ -257,6 +259,7 @@ private:
|
|||
Renderer::Backend::Format m_FormatOverride =
|
||||
Renderer::Backend::Format::UNDEFINED;
|
||||
bool m_IgnoreQuality = false;
|
||||
bool m_SRGB{false};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ enum class Format
|
|||
R8G8_UINT,
|
||||
R8G8B8_UNORM,
|
||||
R8G8B8A8_UNORM,
|
||||
R8G8B8A8_SRGB,
|
||||
R8G8B8A8_UINT,
|
||||
B8G8R8A8_UNORM,
|
||||
B8G8R8A8_SRGB,
|
||||
|
||||
// TODO: we need to drop legacy A8 and L8 formats as soon as we have proper
|
||||
// channel swizzling.
|
||||
|
|
@ -65,9 +67,13 @@ enum class Format
|
|||
D32_SFLOAT_S8_UINT,
|
||||
|
||||
BC1_RGB_UNORM,
|
||||
BC1_RGB_SRGB,
|
||||
BC1_RGBA_UNORM,
|
||||
BC1_RGBA_SRGB,
|
||||
BC2_UNORM,
|
||||
BC3_UNORM
|
||||
BC2_SRGB,
|
||||
BC3_UNORM,
|
||||
BC3_SRGB,
|
||||
};
|
||||
|
||||
inline bool IsDepthFormat(const Format format)
|
||||
|
|
|
|||
|
|
@ -352,11 +352,11 @@ std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window)
|
|||
#if CONFIG2_GLES
|
||||
// Some GLES implementations have GL_EXT_texture_compression_dxt1
|
||||
// but that only supports DXT1 so we can't use it.
|
||||
capabilities.S3TC = GLAD_GL_EXT_texture_compression_s3tc;
|
||||
capabilities.S3TC = GLAD_GL_EXT_texture_compression_s3tc && GLAD_GL_EXT_texture_compression_s3tc_srgb;
|
||||
#else
|
||||
// Note: we don't bother checking for GL_S3_s3tc - it is incompatible
|
||||
// and irrelevant (was never widespread).
|
||||
capabilities.S3TC = GLAD_GL_EXT_texture_compression_s3tc;
|
||||
capabilities.S3TC = GLAD_GL_EXT_texture_compression_s3tc && GLAD_GL_EXT_texture_sRGB;
|
||||
#endif
|
||||
#if CONFIG2_GLES
|
||||
capabilities.multisampling = false;
|
||||
|
|
@ -738,6 +738,7 @@ bool CDevice::IsTextureFormatSupported(const Format format) const
|
|||
|
||||
case Format::R8G8B8_UNORM:
|
||||
case Format::R8G8B8A8_UNORM:
|
||||
case Format::R8G8B8A8_SRGB:
|
||||
case Format::A8_UNORM:
|
||||
case Format::L8_UNORM:
|
||||
supported = true;
|
||||
|
|
@ -763,9 +764,13 @@ bool CDevice::IsTextureFormatSupported(const Format format) const
|
|||
break;
|
||||
|
||||
case Format::BC1_RGB_UNORM:
|
||||
case Format::BC1_RGB_SRGB:
|
||||
case Format::BC1_RGBA_UNORM:
|
||||
case Format::BC1_RGBA_SRGB:
|
||||
case Format::BC2_UNORM:
|
||||
case Format::BC2_SRGB:
|
||||
case Format::BC3_UNORM:
|
||||
case Format::BC3_SRGB:
|
||||
supported = m_Capabilities.S3TC;
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -348,7 +348,8 @@ void CDeviceCommandContext::UploadTextureRegion(
|
|||
GLenum pixelFormat = GL_RGBA;
|
||||
switch (dataFormat)
|
||||
{
|
||||
case Format::R8G8B8A8_UNORM:
|
||||
case Format::R8G8B8A8_UNORM: [[fallthrough]];
|
||||
case Format::R8G8B8A8_SRGB:
|
||||
break;
|
||||
case Format::R8G8B8_UNORM:
|
||||
pixelFormat = GL_RGB;
|
||||
|
|
@ -381,9 +382,13 @@ void CDeviceCommandContext::UploadTextureRegion(
|
|||
}
|
||||
else if (
|
||||
texture->GetFormat() == Format::BC1_RGB_UNORM ||
|
||||
texture->GetFormat() == Format::BC1_RGB_SRGB ||
|
||||
texture->GetFormat() == Format::BC1_RGBA_UNORM ||
|
||||
texture->GetFormat() == Format::BC1_RGBA_SRGB ||
|
||||
texture->GetFormat() == Format::BC2_UNORM ||
|
||||
texture->GetFormat() == Format::BC3_UNORM)
|
||||
texture->GetFormat() == Format::BC2_SRGB ||
|
||||
texture->GetFormat() == Format::BC3_UNORM ||
|
||||
texture->GetFormat() == Format::BC3_SRGB)
|
||||
{
|
||||
ENSURE(xOffset == 0 && yOffset == 0);
|
||||
ENSURE(texture->GetFormat() == dataFormat);
|
||||
|
|
@ -392,15 +397,27 @@ void CDeviceCommandContext::UploadTextureRegion(
|
|||
GLenum internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
||||
switch (texture->GetFormat())
|
||||
{
|
||||
case Format::BC1_RGB_SRGB:
|
||||
internalFormat = GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
|
||||
break;
|
||||
case Format::BC1_RGBA_UNORM:
|
||||
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
|
||||
break;
|
||||
case Format::BC1_RGBA_SRGB:
|
||||
internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
|
||||
break;
|
||||
case Format::BC2_UNORM:
|
||||
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
||||
break;
|
||||
case Format::BC2_SRGB:
|
||||
internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
|
||||
break;
|
||||
case Format::BC3_UNORM:
|
||||
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||
break;
|
||||
case Format::BC3_SRGB:
|
||||
internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,9 @@ std::unique_ptr<CTexture> CTexture::Create(
|
|||
break;
|
||||
case Format::R8G8B8A8_UNORM:
|
||||
break;
|
||||
case Format::R8G8B8A8_SRGB:
|
||||
internalFormat = GL_SRGB_ALPHA_EXT;
|
||||
break;
|
||||
case Format::R8G8B8_UNORM:
|
||||
internalFormat = GL_RGB;
|
||||
pixelFormat = GL_RGB;
|
||||
|
|
@ -254,9 +257,13 @@ std::unique_ptr<CTexture> CTexture::Create(
|
|||
break;
|
||||
#endif
|
||||
case Format::BC1_RGB_UNORM:
|
||||
case Format::BC1_RGB_SRGB:
|
||||
case Format::BC1_RGBA_UNORM:
|
||||
case Format::BC1_RGBA_SRGB:
|
||||
case Format::BC2_UNORM:
|
||||
case Format::BC2_SRGB:
|
||||
case Format::BC3_UNORM:
|
||||
case Format::BC3_SRGB:
|
||||
compressedFormat = true;
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -847,9 +847,13 @@ bool CDevice::IsTextureFormatSupported(const Format format) const
|
|||
return false;
|
||||
|
||||
case Format::BC1_RGB_UNORM:
|
||||
case Format::BC1_RGB_SRGB:
|
||||
case Format::BC1_RGBA_UNORM:
|
||||
case Format::BC1_RGBA_SRGB:
|
||||
case Format::BC2_UNORM:
|
||||
case Format::BC2_SRGB:
|
||||
case Format::BC3_UNORM:
|
||||
case Format::BC3_SRGB:
|
||||
if (m_Capabilities.S3TC)
|
||||
return true;
|
||||
else
|
||||
|
|
|
|||
|
|
@ -174,8 +174,10 @@ VkFormat FromFormat(const Format format)
|
|||
CASE(R8G8_UNORM)
|
||||
CASE(R8G8_UINT)
|
||||
CASE(R8G8B8A8_UNORM)
|
||||
CASE(R8G8B8A8_SRGB)
|
||||
CASE(R8G8B8A8_UINT)
|
||||
CASE(B8G8R8A8_UNORM)
|
||||
CASE(B8G8R8A8_SRGB)
|
||||
|
||||
CASE(R16_UNORM)
|
||||
CASE(R16_UINT)
|
||||
|
|
@ -204,9 +206,13 @@ VkFormat FromFormat(const Format format)
|
|||
CASE(D32_SFLOAT)
|
||||
|
||||
CASE2(BC1_RGB_UNORM, BC1_RGB_UNORM_BLOCK)
|
||||
CASE2(BC1_RGB_SRGB, BC1_RGB_SRGB_BLOCK)
|
||||
CASE2(BC1_RGBA_UNORM, BC1_RGBA_UNORM_BLOCK)
|
||||
CASE2(BC1_RGBA_SRGB, BC1_RGBA_SRGB_BLOCK)
|
||||
CASE2(BC2_UNORM, BC2_UNORM_BLOCK)
|
||||
CASE2(BC2_SRGB, BC2_SRGB_BLOCK)
|
||||
CASE2(BC3_UNORM, BC3_UNORM_BLOCK)
|
||||
CASE2(BC3_SRGB, BC3_SRGB_BLOCK)
|
||||
|
||||
#undef CASE
|
||||
#undef CASE2
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -179,13 +179,18 @@ void CRingCommandContext::ScheduleUpload(
|
|||
|
||||
const bool isCompressedFormat =
|
||||
format == Format::BC1_RGB_UNORM ||
|
||||
format == Format::BC1_RGB_SRGB ||
|
||||
format == Format::BC1_RGBA_UNORM ||
|
||||
format == Format::BC1_RGBA_SRGB ||
|
||||
format == Format::BC2_UNORM ||
|
||||
format == Format::BC3_UNORM;
|
||||
format == Format::BC2_SRGB ||
|
||||
format == Format::BC3_UNORM ||
|
||||
format == Format::BC3_SRGB;
|
||||
ENSURE(
|
||||
format == Format::R8_UNORM ||
|
||||
format == Format::R8G8_UNORM ||
|
||||
format == Format::R8G8B8A8_UNORM ||
|
||||
format == Format::R8G8B8A8_SRGB ||
|
||||
format == Format::A8_UNORM ||
|
||||
format == Format::L8_UNORM ||
|
||||
isCompressedFormat);
|
||||
|
|
|
|||
Loading…
Reference in a new issue