diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/PngScreenshotWriter.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/PngScreenshotWriter.cpp index 3771acd..b1544ea 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/PngScreenshotWriter.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/PngScreenshotWriter.cpp @@ -20,10 +20,10 @@ bool WritePngFile( const std::filesystem::path& outputPath, unsigned width, unsigned height, - const std::vector& rgbaPixels, + const std::vector& bgraPixels, std::string& error) { - if (width == 0 || height == 0 || rgbaPixels.size() < static_cast(width) * height * 4) + if (width == 0 || height == 0 || bgraPixels.size() < static_cast(width) * height * 4) { error = "Invalid screenshot dimensions or pixel buffer."; return false; @@ -83,19 +83,19 @@ bool WritePngFile( if (SUCCEEDED(result)) result = frame->SetSize(width, height); - WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppRGBA; + WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA; if (SUCCEEDED(result)) 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; } const UINT stride = width * 4; const UINT imageSize = stride * height; if (SUCCEEDED(result)) - result = frame->WritePixels(height, stride, imageSize, const_cast(rgbaPixels.data())); + result = frame->WritePixels(height, stride, imageSize, const_cast(bgraPixels.data())); if (SUCCEEDED(result)) result = frame->Commit(); if (SUCCEEDED(result)) @@ -107,6 +107,8 @@ bool WritePngFile( if (FAILED(result)) { error = "Could not write screenshot PNG: " + HResultToString(result); + std::error_code ignored; + std::filesystem::remove(outputPath, ignored); return false; } @@ -123,6 +125,9 @@ void WritePngFileAsync( std::thread( [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; if (!WritePngFile(outputPath, width, height, pixels, error)) OutputDebugStringA(("Screenshot write failed: " + error + "\n").c_str()); @@ -130,4 +135,3 @@ void WritePngFileAsync( OutputDebugStringA(("Screenshot written: " + outputPath.string() + "\n").c_str()); }).detach(); } - diff --git a/image.png b/image.png deleted file mode 100644 index f8ceba4..0000000 Binary files a/image.png and /dev/null differ diff --git a/shaders/fisheye-equirectangular-mirror/shader.json b/shaders/fisheye-equirectangular-mirror/shader.json index 84cd1eb..d4d9fdb 100644 --- a/shaders/fisheye-equirectangular-mirror/shader.json +++ b/shaders/fisheye-equirectangular-mirror/shader.json @@ -1,7 +1,7 @@ { "id": "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", "entryPoint": "shadeVideo", "parameters": [ @@ -9,7 +9,7 @@ "id": "lensFovDegrees", "label": "Lens FOV", "type": "float", - "default": 180.0, + "default": 190.0, "min": 1.0, "max": 220.0, "step": 0.1 @@ -27,7 +27,7 @@ "id": "radius", "label": "Fisheye Radius", "type": "vec2", - "default": [0.5, 0.5], + "default": [0.5, 0.8889], "min": [0.001, 0.001], "max": [2.0, 2.0], "step": [0.001, 0.001] @@ -59,15 +59,6 @@ "max": 180.0, "step": 0.1 }, - { - "id": "seamAngleDegrees", - "label": "Seam Angle", - "type": "float", - "default": 0.0, - "min": -180.0, - "max": 180.0, - "step": 0.1 - }, { "id": "fisheyeModel", "label": "Fisheye Model", @@ -80,6 +71,24 @@ { "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", "label": "Outside Color", diff --git a/shaders/fisheye-equirectangular-mirror/shader.slang b/shaders/fisheye-equirectangular-mirror/shader.slang index f871453..9a6c4ae 100644 --- a/shaders/fisheye-equirectangular-mirror/shader.slang +++ b/shaders/fisheye-equirectangular-mirror/shader.slang @@ -49,7 +49,7 @@ float normalizedFisheyeRadius(float theta, float halfFov) 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 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) { float3 ray = equirectangularRay(context.uv); @@ -86,8 +119,9 @@ float4 shadeVideo(ShaderContext context) 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 sampleVideo(sourceUv); + return sampleEdgeFilledVideo(sourceUv, context); }