LUT interpolation
This commit is contained in:
@@ -4,12 +4,120 @@
|
|||||||
#include <wincodec.h>
|
#include <wincodec.h>
|
||||||
|
|
||||||
#include <atlbase.h>
|
#include <atlbase.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#ifndef GL_RGBA32F
|
||||||
|
#define GL_RGBA32F 0x8814
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::string LowercaseExtension(const std::filesystem::path& path)
|
||||||
|
{
|
||||||
|
std::string extension = path.extension().string();
|
||||||
|
std::transform(extension.begin(), extension.end(), extension.begin(),
|
||||||
|
[](unsigned char value) { return static_cast<char>(std::tolower(value)); });
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadCubeTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
||||||
|
{
|
||||||
|
std::ifstream file(textureAsset.path);
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
error = "Could not open shader LUT asset: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned lutSize = 0;
|
||||||
|
std::vector<float> values;
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line))
|
||||||
|
{
|
||||||
|
const std::size_t commentStart = line.find('#');
|
||||||
|
if (commentStart != std::string::npos)
|
||||||
|
line.resize(commentStart);
|
||||||
|
|
||||||
|
std::istringstream stream(line);
|
||||||
|
std::string firstToken;
|
||||||
|
if (!(stream >> firstToken))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (firstToken == "TITLE" || firstToken == "DOMAIN_MIN" || firstToken == "DOMAIN_MAX")
|
||||||
|
continue;
|
||||||
|
if (firstToken == "LUT_3D_SIZE")
|
||||||
|
{
|
||||||
|
stream >> lutSize;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (firstToken == "LUT_1D_SIZE")
|
||||||
|
{
|
||||||
|
error = "Only 3D .cube LUT assets are supported: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float red = 0.0f;
|
||||||
|
float green = 0.0f;
|
||||||
|
float blue = 0.0f;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
red = std::stof(firstToken);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
error = "Unsupported .cube directive in shader LUT asset: " + firstToken;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(stream >> green >> blue))
|
||||||
|
{
|
||||||
|
error = "Malformed RGB entry in shader LUT asset: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
values.push_back(red);
|
||||||
|
values.push_back(green);
|
||||||
|
values.push_back(blue);
|
||||||
|
values.push_back(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lutSize == 0)
|
||||||
|
{
|
||||||
|
error = "Shader LUT asset is missing LUT_3D_SIZE: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t expectedFloats = static_cast<std::size_t>(lutSize) * lutSize * lutSize * 4;
|
||||||
|
if (values.size() != expectedFloats)
|
||||||
|
{
|
||||||
|
error = "Shader LUT asset entry count does not match LUT_3D_SIZE: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLsizei atlasWidth = static_cast<GLsizei>(lutSize * lutSize);
|
||||||
|
const GLsizei atlasHeight = static_cast<GLsizei>(lutSize);
|
||||||
|
glGenTextures(1, &textureId);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, atlasWidth, atlasHeight, 0, GL_RGBA, GL_FLOAT, values.data());
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
||||||
{
|
{
|
||||||
textureId = 0;
|
textureId = 0;
|
||||||
|
if (LowercaseExtension(textureAsset.path) == ".cube")
|
||||||
|
return LoadCubeTextureAsset(textureAsset, textureId, error);
|
||||||
|
|
||||||
HRESULT comInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
HRESULT comInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||||
const bool shouldUninitializeCom = (comInitResult == S_OK || comInitResult == S_FALSE);
|
const bool shouldUninitializeCom = (comInitResult == S_OK || comInitResult == S_FALSE);
|
||||||
|
|||||||
48
shaders/lut-apply/shader.json
Normal file
48
shaders/lut-apply/shader.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"id": "lut-apply",
|
||||||
|
"name": "3D LUT Apply",
|
||||||
|
"description": "Applies the packaged 33-point .cube LUT to the incoming video using trilinear interpolation.",
|
||||||
|
"category": "Color",
|
||||||
|
"entryPoint": "shadeVideo",
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"id": "lutTexture",
|
||||||
|
"path": "test-lut.cube"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"id": "lutStrength",
|
||||||
|
"label": "LUT Strength",
|
||||||
|
"type": "float",
|
||||||
|
"default": 1.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0,
|
||||||
|
"step": 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "preExposure",
|
||||||
|
"label": "Pre Exposure",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.0,
|
||||||
|
"min": -4.0,
|
||||||
|
"max": 4.0,
|
||||||
|
"step": 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "postContrast",
|
||||||
|
"label": "Post Contrast",
|
||||||
|
"type": "float",
|
||||||
|
"default": 1.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 2.0,
|
||||||
|
"step": 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "clampInput",
|
||||||
|
"label": "Clamp Input",
|
||||||
|
"type": "bool",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
54
shaders/lut-apply/shader.slang
Normal file
54
shaders/lut-apply/shader.slang
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
static const float LUT_SIZE = 33.0;
|
||||||
|
static const float LUT_LAST_INDEX = 32.0;
|
||||||
|
|
||||||
|
float3 sampleLutCell(float3 index)
|
||||||
|
{
|
||||||
|
float r = floor(index.r + 0.5);
|
||||||
|
float g = floor(index.g + 0.5);
|
||||||
|
float b = floor(index.b + 0.5);
|
||||||
|
|
||||||
|
float atlasWidth = LUT_SIZE * LUT_SIZE;
|
||||||
|
float2 lutUv;
|
||||||
|
lutUv.x = (r + b * LUT_SIZE + 0.5) / atlasWidth;
|
||||||
|
lutUv.y = (g + 0.5) / LUT_SIZE;
|
||||||
|
return lutTexture.Sample(lutUv).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 applyLut33(float3 color)
|
||||||
|
{
|
||||||
|
float3 lutCoord = saturate(color) * LUT_LAST_INDEX;
|
||||||
|
float3 baseIndex = floor(lutCoord);
|
||||||
|
float3 nextIndex = min(baseIndex + 1.0, LUT_LAST_INDEX);
|
||||||
|
float3 blend = lutCoord - baseIndex;
|
||||||
|
|
||||||
|
float3 c000 = sampleLutCell(float3(baseIndex.r, baseIndex.g, baseIndex.b));
|
||||||
|
float3 c100 = sampleLutCell(float3(nextIndex.r, baseIndex.g, baseIndex.b));
|
||||||
|
float3 c010 = sampleLutCell(float3(baseIndex.r, nextIndex.g, baseIndex.b));
|
||||||
|
float3 c110 = sampleLutCell(float3(nextIndex.r, nextIndex.g, baseIndex.b));
|
||||||
|
float3 c001 = sampleLutCell(float3(baseIndex.r, baseIndex.g, nextIndex.b));
|
||||||
|
float3 c101 = sampleLutCell(float3(nextIndex.r, baseIndex.g, nextIndex.b));
|
||||||
|
float3 c011 = sampleLutCell(float3(baseIndex.r, nextIndex.g, nextIndex.b));
|
||||||
|
float3 c111 = sampleLutCell(float3(nextIndex.r, nextIndex.g, nextIndex.b));
|
||||||
|
|
||||||
|
float3 c00 = lerp(c000, c100, blend.r);
|
||||||
|
float3 c10 = lerp(c010, c110, blend.r);
|
||||||
|
float3 c01 = lerp(c001, c101, blend.r);
|
||||||
|
float3 c11 = lerp(c011, c111, blend.r);
|
||||||
|
float3 c0 = lerp(c00, c10, blend.g);
|
||||||
|
float3 c1 = lerp(c01, c11, blend.g);
|
||||||
|
return lerp(c0, c1, blend.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 shadeVideo(ShaderContext context)
|
||||||
|
{
|
||||||
|
float4 source = context.sourceColor;
|
||||||
|
float3 inputColor = source.rgb * pow(2.0, preExposure);
|
||||||
|
if (clampInput)
|
||||||
|
inputColor = saturate(inputColor);
|
||||||
|
|
||||||
|
float3 lutColor = applyLut33(inputColor);
|
||||||
|
float3 graded = lerp(inputColor, lutColor, lutStrength);
|
||||||
|
graded = (graded - 0.5) * postContrast + 0.5;
|
||||||
|
|
||||||
|
return float4(saturate(graded), source.a);
|
||||||
|
}
|
||||||
35940
shaders/lut-apply/test-lut.cube
Normal file
35940
shaders/lut-apply/test-lut.cube
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user