我正在尝试使用 2 个重叠的四边形实现一个简单的颜色选择器:1 个四边形具有水平白色到值的渐变,另一个四边形具有黑色到透明的垂直渐变。
最终的结果是这样的,这不是我所期望的:
颜色太鲜艳(黑色和红色太少)。
正确的红色颜色选择器应该是这样的:
(最右边的)滑块也是错误的(它从全白插值到全黑,并且渐变是线性的(感知上是非线性的)。
因此,如果顶点属性颜色指定为线性(这应该不重要,因为单通道为 0 或 1:白色为 [1,1,1],红色为 [1,0,0])并且片段插值为如此在线性空间中完成,如果表面是 sRGB,为什么最终输出是线性的(感知上非线性的)? Vulkan 不应该在片段着色器之后在线性到 sRGB 之间进行转换吗?
如果我在像素着色器中应用线性到 srgb 转换,渐变会变得正确,但其他颜色会变得太暗。
请注意,最右边的滑块(黑到白)现在在感知上是线性的,但最左边的主选取器(黑到透明)上的 Alpha 仍然是错误的。
如果我尝试使用黑到红渐变渲染纹理并将其与使用顶点属性颜色生成的渐变进行比较,则存在差异(顶部是顶点属性,底部是纹理):
git clone --recursive https://github.com/SaschaWillems/Vulkan
cmake -G "Visual Studio 16 2019" -A x64
VK_FORMAT_B8G8R8A8_UNORM
和VK_FORMAT_B8G8R8A8_SRGB
// Setup vertices
std::vector<Vertex> vertexBuffer{
{ { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, // br
{ { -1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f } }, // bl
{ { -1.0f, -1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f } }, // tl
{ { 1.0f, -1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, // tr
};
或彩色四边形:
// Setup vertices
std::vector<Vertex> vertexBuffer{
{ { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, // br
{ { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } }, // bl
{ { -1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }, // tl
{ { 1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }, // tr
};
// Setup indices
std::vector<uint32_t> indexBuffer{ 0, 1, 2, 0, 2, 3 };
就我而言,结果是:
左上角的红色渐变看起来正确,但右上角的红色渐变太暗。 左下角的红色渐变看起来太亮,但右下角的红色渐变看起来是正确的。
着色器输出是线性值,这在感知上不是线性的。
当您使用渲染 0 到 1 渐变的基元时,中点线性光值 0.5 不是感知中点。从感知上来说,0.5 的线性光值实际上约为 0.73,这就是渐变看起来“关闭”的原因。
使用
format=VK_FORMAT_B8G8R8A8_UNORM
是一种直通,没有隐式伽马转换。为了使这一点有意义,您的着色器必须输出伽玛转换值。这适用于您正在做的事情,因为您实际上希望 0.5 成为感知中点,而不是 0.5 线性光强度。
如果您想使用
format=VK_FORMAT_B8G8R8A8_SRBB
,那么您的着色器输出需要输出真正的线性光值,请注意它们稍后将进行伽马转换。
开发人员在这里遇到的主要问题之一是,许多输入艺术资源(例如 UI 纹理)声称是 sRGB,但实际上并非如此。要做到“正确”,需要正确配置整个内容管道。