static const float PI = 3.14159265358979323846; float radiansFromDegrees(float degrees) { return degrees * (PI / 180.0); } float3 rotateX(float3 ray, float angle) { float s = sin(angle); float c = cos(angle); return float3(ray.x, c * ray.y - s * ray.z, s * ray.y + c * ray.z); } float3 rotateY(float3 ray, float angle) { float s = sin(angle); float c = cos(angle); return float3(c * ray.x + s * ray.z, ray.y, -s * ray.x + c * ray.z); } float3 rotateZ(float3 ray, float angle) { float s = sin(angle); float c = cos(angle); return float3(c * ray.x - s * ray.y, s * ray.x + c * ray.y, ray.z); } float3 buildRectilinearRay(float2 screen, float outputAspect, float tanHalfFov) { return normalize(float3(screen.x * outputAspect * tanHalfFov, screen.y * tanHalfFov, 1.0)); } float3 buildCylindricalRay(float2 screen, float outputAspect, float tanHalfFov) { float horizontalFov = 2.0 * atan(outputAspect * tanHalfFov); float yaw = screen.x * horizontalFov * 0.5; float vertical = screen.y * tanHalfFov; return normalize(float3(sin(yaw), vertical, cos(yaw))); } float normalizedFisheyeRadius(float theta, float halfFov) { float safeHalfFov = max(halfFov, 0.0001); if (fisheyeModel == 1) { return sin(theta * 0.5) / max(sin(safeHalfFov * 0.5), 0.0001); } else if (fisheyeModel == 2) { return tan(theta * 0.5) / max(tan(safeHalfFov * 0.5), 0.0001); } else if (fisheyeModel == 3) { return sin(theta) / max(sin(safeHalfFov), 0.0001); } return theta / safeHalfFov; } float4 shadeVideo(ShaderContext context) { float2 screen = float2(context.uv.x * 2.0 - 1.0, 1.0 - context.uv.y * 2.0); float outputAspect = context.outputResolution.x / max(context.outputResolution.y, 1.0); float virtualFov = radiansFromDegrees(clamp(virtualFovDegrees, 1.0, 175.0)); float tanHalfFov = tan(virtualFov * 0.5); float3 ray = outputProjection == 1 ? buildCylindricalRay(screen, outputAspect, tanHalfFov) : buildRectilinearRay(screen, outputAspect, tanHalfFov); ray = rotateZ(ray, radiansFromDegrees(rollDegrees)); ray = rotateX(ray, radiansFromDegrees(-tiltDegrees)); ray = rotateY(ray, radiansFromDegrees(panDegrees)); float halfFov = radiansFromDegrees(clamp(lensFovDegrees, 1.0, 220.0) * 0.5); float theta = acos(clamp(ray.z, -1.0, 1.0)); if (theta > halfFov) return outsideColor; float phi = atan2(ray.y, ray.x); float fisheyeRadius = normalizedFisheyeRadius(theta, halfFov); float2 sourceUv = float2( center.x + cos(phi) * fisheyeRadius * radius.x, center.y - sin(phi) * fisheyeRadius * radius.y ); if (sourceUv.x < 0.0 || sourceUv.x > 1.0 || sourceUv.y < 0.0 || sourceUv.y > 1.0) return outsideColor; return sampleVideo(sourceUv); }