Updated shader and fixed PNG output
This commit is contained in:
@@ -20,10 +20,10 @@ bool WritePngFile(
|
|||||||
const std::filesystem::path& outputPath,
|
const std::filesystem::path& outputPath,
|
||||||
unsigned width,
|
unsigned width,
|
||||||
unsigned height,
|
unsigned height,
|
||||||
const std::vector<unsigned char>& rgbaPixels,
|
const std::vector<unsigned char>& bgraPixels,
|
||||||
std::string& error)
|
std::string& error)
|
||||||
{
|
{
|
||||||
if (width == 0 || height == 0 || rgbaPixels.size() < static_cast<std::size_t>(width) * height * 4)
|
if (width == 0 || height == 0 || bgraPixels.size() < static_cast<std::size_t>(width) * height * 4)
|
||||||
{
|
{
|
||||||
error = "Invalid screenshot dimensions or pixel buffer.";
|
error = "Invalid screenshot dimensions or pixel buffer.";
|
||||||
return false;
|
return false;
|
||||||
@@ -83,19 +83,19 @@ bool WritePngFile(
|
|||||||
if (SUCCEEDED(result))
|
if (SUCCEEDED(result))
|
||||||
result = frame->SetSize(width, height);
|
result = frame->SetSize(width, height);
|
||||||
|
|
||||||
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppRGBA;
|
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA;
|
||||||
if (SUCCEEDED(result))
|
if (SUCCEEDED(result))
|
||||||
result = frame->SetPixelFormat(&pixelFormat);
|
result = frame->SetPixelFormat(&pixelFormat);
|
||||||
if (SUCCEEDED(result) && pixelFormat != GUID_WICPixelFormat32bppRGBA)
|
if (SUCCEEDED(result) && pixelFormat != GUID_WICPixelFormat32bppBGRA)
|
||||||
{
|
{
|
||||||
error = "PNG encoder did not accept RGBA pixel format.";
|
error = "PNG encoder did not accept BGRA pixel format.";
|
||||||
result = E_FAIL;
|
result = E_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UINT stride = width * 4;
|
const UINT stride = width * 4;
|
||||||
const UINT imageSize = stride * height;
|
const UINT imageSize = stride * height;
|
||||||
if (SUCCEEDED(result))
|
if (SUCCEEDED(result))
|
||||||
result = frame->WritePixels(height, stride, imageSize, const_cast<BYTE*>(rgbaPixels.data()));
|
result = frame->WritePixels(height, stride, imageSize, const_cast<BYTE*>(bgraPixels.data()));
|
||||||
if (SUCCEEDED(result))
|
if (SUCCEEDED(result))
|
||||||
result = frame->Commit();
|
result = frame->Commit();
|
||||||
if (SUCCEEDED(result))
|
if (SUCCEEDED(result))
|
||||||
@@ -107,6 +107,8 @@ bool WritePngFile(
|
|||||||
if (FAILED(result))
|
if (FAILED(result))
|
||||||
{
|
{
|
||||||
error = "Could not write screenshot PNG: " + HResultToString(result);
|
error = "Could not write screenshot PNG: " + HResultToString(result);
|
||||||
|
std::error_code ignored;
|
||||||
|
std::filesystem::remove(outputPath, ignored);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +125,9 @@ void WritePngFileAsync(
|
|||||||
std::thread(
|
std::thread(
|
||||||
[outputPath, width, height, pixels = std::move(rgbaPixels)]() mutable
|
[outputPath, width, height, pixels = std::move(rgbaPixels)]() mutable
|
||||||
{
|
{
|
||||||
|
for (std::size_t index = 0; index + 3 < pixels.size(); index += 4)
|
||||||
|
std::swap(pixels[index], pixels[index + 2]);
|
||||||
|
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!WritePngFile(outputPath, width, height, pixels, error))
|
if (!WritePngFile(outputPath, width, height, pixels, error))
|
||||||
OutputDebugStringA(("Screenshot write failed: " + error + "\n").c_str());
|
OutputDebugStringA(("Screenshot write failed: " + error + "\n").c_str());
|
||||||
@@ -130,4 +135,3 @@ void WritePngFileAsync(
|
|||||||
OutputDebugStringA(("Screenshot written: " + outputPath.string() + "\n").c_str());
|
OutputDebugStringA(("Screenshot written: " + outputPath.string() + "\n").c_str());
|
||||||
}).detach();
|
}).detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "fisheye-equirectangular-mirror",
|
"id": "fisheye-equirectangular-mirror",
|
||||||
"name": "Fisheye Equirectangular Mirror",
|
"name": "Fisheye Equirectangular Mirror",
|
||||||
"description": "Unwraps a single fisheye lens into a 360x180 equirectangular map by mirroring the rear hemisphere into the same fisheye source.",
|
"description": "Unwraps a single width-filled 16:9 fisheye lens into a 360x180 equirectangular map by mirroring the rear hemisphere into the same fisheye source.",
|
||||||
"category": "Projection",
|
"category": "Projection",
|
||||||
"entryPoint": "shadeVideo",
|
"entryPoint": "shadeVideo",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"id": "lensFovDegrees",
|
"id": "lensFovDegrees",
|
||||||
"label": "Lens FOV",
|
"label": "Lens FOV",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default": 180.0,
|
"default": 190.0,
|
||||||
"min": 1.0,
|
"min": 1.0,
|
||||||
"max": 220.0,
|
"max": 220.0,
|
||||||
"step": 0.1
|
"step": 0.1
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"id": "radius",
|
"id": "radius",
|
||||||
"label": "Fisheye Radius",
|
"label": "Fisheye Radius",
|
||||||
"type": "vec2",
|
"type": "vec2",
|
||||||
"default": [0.5, 0.5],
|
"default": [0.5, 0.8889],
|
||||||
"min": [0.001, 0.001],
|
"min": [0.001, 0.001],
|
||||||
"max": [2.0, 2.0],
|
"max": [2.0, 2.0],
|
||||||
"step": [0.001, 0.001]
|
"step": [0.001, 0.001]
|
||||||
@@ -59,15 +59,6 @@
|
|||||||
"max": 180.0,
|
"max": 180.0,
|
||||||
"step": 0.1
|
"step": 0.1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "seamAngleDegrees",
|
|
||||||
"label": "Seam Angle",
|
|
||||||
"type": "float",
|
|
||||||
"default": 0.0,
|
|
||||||
"min": -180.0,
|
|
||||||
"max": 180.0,
|
|
||||||
"step": 0.1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "fisheyeModel",
|
"id": "fisheyeModel",
|
||||||
"label": "Fisheye Model",
|
"label": "Fisheye Model",
|
||||||
@@ -80,6 +71,24 @@
|
|||||||
{ "value": "orthographic", "label": "Orthographic" }
|
{ "value": "orthographic", "label": "Orthographic" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "edgeFill",
|
||||||
|
"label": "Edge Fill",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.06,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 0.3,
|
||||||
|
"step": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edgeBlur",
|
||||||
|
"label": "Edge Blur",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.018,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 0.12,
|
||||||
|
"step": 0.001
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "outsideColor",
|
"id": "outsideColor",
|
||||||
"label": "Outside Color",
|
"label": "Outside Color",
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ float normalizedFisheyeRadius(float theta, float halfFov)
|
|||||||
|
|
||||||
float3 equirectangularRay(float2 uv)
|
float3 equirectangularRay(float2 uv)
|
||||||
{
|
{
|
||||||
float longitude = (uv.x - 0.5) * TWO_PI + radiansFromDegrees(seamAngleDegrees);
|
float longitude = (uv.x - 0.5) * TWO_PI;
|
||||||
float latitude = (0.5 - uv.y) * PI;
|
float latitude = (0.5 - uv.y) * PI;
|
||||||
float latitudeCos = cos(latitude);
|
float latitudeCos = cos(latitude);
|
||||||
|
|
||||||
@@ -60,6 +60,39 @@ float3 equirectangularRay(float2 uv)
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float sourceUvOutsideDistance(float2 uv)
|
||||||
|
{
|
||||||
|
float2 lower = max(-uv, float2(0.0, 0.0));
|
||||||
|
float2 upper = max(uv - 1.0, float2(0.0, 0.0));
|
||||||
|
return max(max(lower.x, lower.y), max(upper.x, upper.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 sampleEdgeFilledVideo(float2 sourceUv, ShaderContext context)
|
||||||
|
{
|
||||||
|
float outsideDistance = sourceUvOutsideDistance(sourceUv);
|
||||||
|
if (outsideDistance <= 0.0)
|
||||||
|
return sampleVideo(sourceUv);
|
||||||
|
|
||||||
|
float fillDistance = max(edgeFill, 0.0);
|
||||||
|
if (outsideDistance > fillDistance)
|
||||||
|
return outsideColor;
|
||||||
|
|
||||||
|
float2 clampedUv = saturate(sourceUv);
|
||||||
|
float2 inward = clampedUv - sourceUv;
|
||||||
|
float inwardLength = max(length(inward), 0.000001);
|
||||||
|
inward /= inwardLength;
|
||||||
|
|
||||||
|
float blurDistance = max(edgeBlur, 0.0);
|
||||||
|
float4 color = sampleVideo(clampedUv) * 0.32;
|
||||||
|
color += sampleVideo(saturate(clampedUv + inward * blurDistance * 0.35)) * 0.26;
|
||||||
|
color += sampleVideo(saturate(clampedUv + inward * blurDistance * 0.75)) * 0.20;
|
||||||
|
color += sampleVideo(saturate(clampedUv + inward * blurDistance * 1.20)) * 0.14;
|
||||||
|
color += sampleVideo(saturate(clampedUv + inward * blurDistance * 1.75)) * 0.08;
|
||||||
|
|
||||||
|
float edgeFade = smoothstep(fillDistance * 0.78, fillDistance, outsideDistance);
|
||||||
|
return lerp(color, outsideColor, edgeFade);
|
||||||
|
}
|
||||||
|
|
||||||
float4 shadeVideo(ShaderContext context)
|
float4 shadeVideo(ShaderContext context)
|
||||||
{
|
{
|
||||||
float3 ray = equirectangularRay(context.uv);
|
float3 ray = equirectangularRay(context.uv);
|
||||||
@@ -86,8 +119,9 @@ float4 shadeVideo(ShaderContext context)
|
|||||||
center.y - sin(phi) * fisheyeRadius * radius.y
|
center.y - sin(phi) * fisheyeRadius * radius.y
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sourceUv.x < 0.0 || sourceUv.x > 1.0 || sourceUv.y < 0.0 || sourceUv.y > 1.0)
|
float2 guard = 0.5 / max(context.inputResolution, float2(1.0, 1.0));
|
||||||
|
if (edgeFill <= 0.0 && (sourceUv.x < -guard.x || sourceUv.x > 1.0 + guard.x || sourceUv.y < -guard.y || sourceUv.y > 1.0 + guard.y))
|
||||||
return outsideColor;
|
return outsideColor;
|
||||||
|
|
||||||
return sampleVideo(sourceUv);
|
return sampleEdgeFilledVideo(sourceUv, context);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user