Add rolling shutter adjustment
This commit is contained in:
parent
fab7e2756c
commit
7cab3393e2
|
|
@ -218,6 +218,7 @@ void BreezyDesktopEffect::recenter() {
|
||||||
void BreezyDesktopEffect::setLookingAtScreenIndex(int index)
|
void BreezyDesktopEffect::setLookingAtScreenIndex(int index)
|
||||||
{
|
{
|
||||||
m_lookingAtScreenIndex = index;
|
m_lookingAtScreenIndex = index;
|
||||||
|
if (m_smoothFollowEnabled) updateDriverSmoothFollowSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BreezyDesktopEffect::reconfigure(ReconfigureFlags)
|
void BreezyDesktopEffect::reconfigure(ReconfigureFlags)
|
||||||
|
|
|
||||||
|
|
@ -55,20 +55,20 @@ Node {
|
||||||
let targetProgress;
|
let targetProgress;
|
||||||
if (smoothFollowEnabled && focusedIndex !== -1) {
|
if (smoothFollowEnabled && focusedIndex !== -1) {
|
||||||
focusedDisplay = breezyDesktop.displayAtIndex(focusedIndex);
|
focusedDisplay = breezyDesktop.displayAtIndex(focusedIndex);
|
||||||
if (focusedDisplay !== null) {
|
if (focusedDisplay) {
|
||||||
targetDisplay = focusedDisplay;
|
targetDisplay = focusedDisplay;
|
||||||
targetProgress = 1.0;
|
targetProgress = 1.0;
|
||||||
startSmoothFollowFocusAnimation = true;
|
startSmoothFollowFocusAnimation = true;
|
||||||
}
|
}
|
||||||
} else if (!smoothFollowEnabled && breezyDesktop.focusedMonitorIndex !== -1) {
|
} else if (!smoothFollowEnabled && breezyDesktop.focusedMonitorIndex !== -1) {
|
||||||
unfocusedDisplay = breezyDesktop.displayAtIndex(breezyDesktop.focusedMonitorIndex);
|
unfocusedDisplay = breezyDesktop.displayAtIndex(breezyDesktop.focusedMonitorIndex);
|
||||||
if (unfocusedDisplay !== null) {
|
if (unfocusedDisplay) {
|
||||||
targetDisplay = unfocusedDisplay;
|
targetDisplay = unfocusedDisplay;
|
||||||
targetProgress = 0.0;
|
targetProgress = 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetDisplay !== null) {
|
if (targetDisplay) {
|
||||||
smoothFollowTransitionAnimation.stop();
|
smoothFollowTransitionAnimation.stop();
|
||||||
smoothFollowTransitionAnimation.target = targetDisplay;
|
smoothFollowTransitionAnimation.target = targetDisplay;
|
||||||
smoothFollowTransitionAnimation.from = targetDisplay.smoothFollowTransitionProgress;
|
smoothFollowTransitionAnimation.from = targetDisplay.smoothFollowTransitionProgress;
|
||||||
|
|
@ -80,13 +80,16 @@ Node {
|
||||||
if (focusedIndex !== breezyDesktop.focusedMonitorIndex) {
|
if (focusedIndex !== breezyDesktop.focusedMonitorIndex) {
|
||||||
const unfocusedIndex = breezyDesktop.focusedMonitorIndex;
|
const unfocusedIndex = breezyDesktop.focusedMonitorIndex;
|
||||||
if (!focusedDisplay) focusedDisplay = focusedIndex !== -1 ? breezyDesktop.displayAtIndex(focusedIndex) : null;
|
if (!focusedDisplay) focusedDisplay = focusedIndex !== -1 ? breezyDesktop.displayAtIndex(focusedIndex) : null;
|
||||||
if (focusedDisplay === null) {
|
if (!focusedDisplay) {
|
||||||
if (!unfocusedDisplay) unfocusedDisplay = breezyDesktop.displayAtIndex(unfocusedIndex);
|
if (!unfocusedDisplay) unfocusedDisplay = breezyDesktop.displayAtIndex(unfocusedIndex);
|
||||||
zoomOutAnimation.target = unfocusedDisplay;
|
if (unfocusedDisplay) {
|
||||||
zoomOutAnimation.target.targetDistance = effect.allDisplaysDistance;
|
zoomOutAnimation.target = unfocusedDisplay;
|
||||||
zoomOutAnimation.start();
|
zoomOutAnimation.target.targetDistance = effect.allDisplaysDistance;
|
||||||
|
zoomOutAnimation.start();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (unfocusedIndex === -1) {
|
if (!unfocusedDisplay) unfocusedDisplay = unfocusedIndex !== -1 ? breezyDesktop.displayAtIndex(unfocusedIndex) : null;
|
||||||
|
if (!unfocusedDisplay) {
|
||||||
zoomInAnimation.target = focusedDisplay;
|
zoomInAnimation.target = focusedDisplay;
|
||||||
focusedDisplay.targetDistance = effect.focusedDisplayDistance;
|
focusedDisplay.targetDistance = effect.focusedDisplayDistance;
|
||||||
zoomInAnimation.start();
|
zoomInAnimation.start();
|
||||||
|
|
@ -94,9 +97,8 @@ Node {
|
||||||
zoomInSeqAnimation.target = focusedDisplay;
|
zoomInSeqAnimation.target = focusedDisplay;
|
||||||
focusedDisplay.targetDistance = effect.focusedDisplayDistance;
|
focusedDisplay.targetDistance = effect.focusedDisplayDistance;
|
||||||
|
|
||||||
if (!unfocusedDisplay) unfocusedDisplay = breezyDesktop.displayAtIndex(unfocusedIndex);
|
|
||||||
zoomOutSeqAnimation.target = unfocusedDisplay;
|
zoomOutSeqAnimation.target = unfocusedDisplay;
|
||||||
zoomOutSeqAnimation.target.targetDistance = effect.allDisplaysDistance;
|
unfocusedDisplay.targetDistance = effect.allDisplaysDistance;
|
||||||
|
|
||||||
zoomOnFocusSequence.start();
|
zoomOnFocusSequence.start();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,27 +7,56 @@ Item {
|
||||||
required property Camera camera
|
required property Camera camera
|
||||||
required property var fovDetails
|
required property var fovDetails
|
||||||
|
|
||||||
property var displayResolution: effect.displayResolution
|
Displays {
|
||||||
property real diagonalFOV: effect.diagonalFOV
|
id: displays
|
||||||
|
}
|
||||||
|
|
||||||
|
property real aspectRatio: effect.displayResolution[0] / effect.displayResolution[1]
|
||||||
property real lensDistanceRatio: effect.lensDistanceRatio
|
property real lensDistanceRatio: effect.lensDistanceRatio
|
||||||
property bool sbsEnabled: effect.sbsEnabled
|
property bool sbsEnabled: effect.sbsEnabled
|
||||||
property bool customBannerEnabled: effect.customBannerEnabled
|
property bool customBannerEnabled: effect.customBannerEnabled
|
||||||
property bool smoothFollowEnabled: effect.smoothFollowEnabled
|
property bool smoothFollowEnabled: effect.smoothFollowEnabled
|
||||||
|
property real lookAheadScanlineMs: effect.lookAheadConfig[2]
|
||||||
|
property var crossFovs: displays.diagonalToCrossFOVs(
|
||||||
|
displays.degreeToRadian(effect.diagonalFOV),
|
||||||
|
aspectRatio
|
||||||
|
);
|
||||||
|
|
||||||
// if true, then smoothFollowEnabled just cleared and the IMU data is slerping back,
|
// if true, then smoothFollowEnabled just cleared and the IMU data is slerping back,
|
||||||
// continue to use the origin data for the duration of the Timer
|
// continue to use the origin data for the duration of the Timer
|
||||||
property bool smoothFollowDisabling: false
|
property bool smoothFollowDisabling: false
|
||||||
|
|
||||||
Displays {
|
property real clipNear: 10.0
|
||||||
id: displays
|
property real clipFar: 10000.0
|
||||||
|
|
||||||
|
function ratesOfChange(rotations) {
|
||||||
|
const e0 = rotations[0].toEulerAngles();
|
||||||
|
const e1 = rotations[1].toEulerAngles();
|
||||||
|
const dt = effect.imuTimeElapsedMs;
|
||||||
|
const yawDegrees = (e0.y - e1.y) / dt;
|
||||||
|
const pitchDegrees = (e0.x - e1.x) / dt;
|
||||||
|
const rollDegrees = (e0.z - e1.z) / dt;
|
||||||
|
|
||||||
|
return {
|
||||||
|
eulerEnd: e0,
|
||||||
|
eulerStart: e1,
|
||||||
|
yawDegrees: yawDegrees,
|
||||||
|
yaw: displays.degreeToRadian(yawDegrees),
|
||||||
|
pitchDegrees: pitchDegrees,
|
||||||
|
pitch: displays.degreeToRadian(pitchDegrees),
|
||||||
|
rollDegrees: rollDegrees,
|
||||||
|
roll: displays.degreeToRadian(rollDegrees)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCamera(rotations) {
|
function updateCamera(rotations, rates) {
|
||||||
camera.eulerRotation = applyLookAhead(
|
camera.eulerRotation = applyLookAhead(
|
||||||
rotations[0],
|
rates,
|
||||||
rotations[1],
|
lookAheadMS(
|
||||||
effect.imuTimeElapsedMs,
|
effect.imuTimestamp,
|
||||||
lookAheadMS(effect.imuTimestamp, effect.lookAheadConfig, effect.lookAheadOverride)
|
effect.lookAheadConfig,
|
||||||
|
effect.lookAheadOverride
|
||||||
|
)
|
||||||
);
|
);
|
||||||
camera.position = rotations[0].times(Qt.vector3d(0, 0, -fovDetails.lensDistancePixels));
|
camera.position = rotations[0].times(Qt.vector3d(0, 0, -fovDetails.lensDistancePixels));
|
||||||
}
|
}
|
||||||
|
|
@ -42,43 +71,88 @@ Item {
|
||||||
return (override === -1 ? lookAheadConstant : override) + dataAge;
|
return (override === -1 ? lookAheadConstant : override) + dataAge;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyLookAhead(quatT0, quatT1, elapsedTimeMs, lookAheadMs) {
|
function applyLookAhead(rates, lookAheadMs) {
|
||||||
// convert both quats to euler angles
|
|
||||||
const eulerT0 = quatT0.toEulerAngles();
|
|
||||||
const eulerT1 = quatT1.toEulerAngles();
|
|
||||||
|
|
||||||
// compute the rate of change of the angles based on the elapsed time
|
|
||||||
const deltaX = (eulerT0.x - eulerT1.x);
|
|
||||||
const deltaY = (eulerT0.y - eulerT1.y);
|
|
||||||
const deltaZ = (eulerT0.z - eulerT1.z);
|
|
||||||
|
|
||||||
// how much of the delta to apply based on the look-ahead time
|
|
||||||
const timeConstant = lookAheadMs / elapsedTimeMs;
|
|
||||||
|
|
||||||
return Qt.vector3d(
|
return Qt.vector3d(
|
||||||
eulerT0.x + deltaX * timeConstant,
|
rates.eulerEnd.x + rates.pitchDegrees * lookAheadMs,
|
||||||
eulerT0.y + deltaY * timeConstant,
|
rates.eulerEnd.y + rates.yawDegrees * lookAheadMs,
|
||||||
eulerT0.z + deltaZ * timeConstant,
|
rates.eulerEnd.z + rates.rollDegrees * lookAheadMs,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFOV() {
|
function updateProjection() {
|
||||||
const aspectRatio = displayResolution[0] / displayResolution[1];
|
camera.projection = buildPerspectiveMatrix();
|
||||||
camera.fieldOfView = displays.radianToDegree(displays.diagonalToCrossFOVs(
|
|
||||||
displays.degreeToRadian(cameraController.diagonalFOV),
|
|
||||||
aspectRatio
|
|
||||||
).vertical);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDisplayResolutionChanged: updateFOV();
|
function buildPerspectiveMatrix() {
|
||||||
onDiagonalFOVChanged: updateFOV();
|
const f = 1.0 / crossFovs.verticalTangent;
|
||||||
|
const nf = 1.0 / (clipNear - clipFar);
|
||||||
|
const m00 = f / aspectRatio;
|
||||||
|
const m11 = f;
|
||||||
|
const m22 = (clipFar + clipNear) * nf;
|
||||||
|
const m23 = (2.0 * clipFar * clipNear) * nf;
|
||||||
|
|
||||||
|
// Standard OpenGL-style projection matrix
|
||||||
|
return Qt.matrix4x4(
|
||||||
|
m00, 0, 0, 0,
|
||||||
|
0, m11, 0, 0,
|
||||||
|
0, 0, m22, m23,
|
||||||
|
0, 0, -1, 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyRollingShutterShear(rates) {
|
||||||
|
// Convert to maximum shift at bottom of frame
|
||||||
|
const maxDxNdc = (rates.yaw * lookAheadScanlineMs) / crossFovs.horizontalTangent;
|
||||||
|
const maxDyNdc = -(rates.pitch * lookAheadScanlineMs) / crossFovs.verticalTangent;
|
||||||
|
|
||||||
|
let shx = maxDxNdc / 2.0;
|
||||||
|
let shy = maxDyNdc / 2.0;
|
||||||
|
|
||||||
|
const f = 1.0 / crossFovs.verticalTangent;
|
||||||
|
const nf = 1.0 / (clipNear - clipFar);
|
||||||
|
const m00 = f / aspectRatio;
|
||||||
|
const m11 = f;
|
||||||
|
const m22 = (clipFar + clipNear) * nf;
|
||||||
|
const m23 = (2.0 * clipFar * clipNear) * nf;
|
||||||
|
|
||||||
|
const r0c0 = m00;
|
||||||
|
const r0c1 = -(shx * m11) / 2.0;
|
||||||
|
const r0c2 = -(shx) / 2.0;
|
||||||
|
const r0c3 = 0.0;
|
||||||
|
|
||||||
|
const r1c0 = 0.0;
|
||||||
|
const r1c1 = m11 * (1.0 - shy / 2.0);
|
||||||
|
const r1c2 = -(shy) / 2.0;
|
||||||
|
const r1c3 = 0.0;
|
||||||
|
|
||||||
|
const r2c0 = 0.0;
|
||||||
|
const r2c1 = 0.0;
|
||||||
|
const r2c2 = m22;
|
||||||
|
const r2c3 = m23;
|
||||||
|
|
||||||
|
const r3c0 = 0.0;
|
||||||
|
const r3c1 = 0.0;
|
||||||
|
const r3c2 = -1.0;
|
||||||
|
const r3c3 = 0.0;
|
||||||
|
|
||||||
|
camera.projection = Qt.matrix4x4(
|
||||||
|
r0c0, r0c1, r0c2, r0c3,
|
||||||
|
r1c0, r1c1, r1c2, r1c3,
|
||||||
|
r2c0, r2c1, r2c2, r2c3,
|
||||||
|
r3c0, r3c1, r3c2, r3c3
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: updateProjection();
|
||||||
|
|
||||||
FrameAnimation {
|
FrameAnimation {
|
||||||
running: true
|
running: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
const rotations = (effect.smoothFollowEnabled || smoothFollowDisabling) ? effect.smoothFollowOrigin : effect.imuRotations;
|
const rotations = (effect.smoothFollowEnabled || smoothFollowDisabling) ? effect.smoothFollowOrigin : effect.imuRotations;
|
||||||
if (rotations && rotations.length > 0) {
|
if (rotations && rotations.length > 0) {
|
||||||
updateCamera(rotations);
|
const rates = ratesOfChange(rotations);
|
||||||
|
updateCamera(rotations, rates);
|
||||||
|
applyRollingShutterShear(rates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,15 @@ QtObject {
|
||||||
|
|
||||||
// Converts diagonal FOV in radians and aspect ratio to horizontal and vertical FOVs
|
// Converts diagonal FOV in radians and aspect ratio to horizontal and vertical FOVs
|
||||||
function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) {
|
function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) {
|
||||||
var flatDiagonalFOV = 2 * Math.tan(diagonalFOVRadians / 2);
|
var diagonalTangent = Math.tan(diagonalFOVRadians / 2);
|
||||||
var flatVerticalFOV = flatDiagonalFOV / Math.sqrt(1 + aspectRatio * aspectRatio);
|
var verticalTangent = diagonalTangent / Math.sqrt(1 + aspectRatio * aspectRatio);
|
||||||
var flatHorizontalFOV = flatVerticalFOV * aspectRatio;
|
var horizontalTangent = verticalTangent * aspectRatio;
|
||||||
return {
|
return {
|
||||||
diagonal: diagonalFOVRadians,
|
diagonal: diagonalFOVRadians,
|
||||||
horizontal: 2 * Math.atan(flatHorizontalFOV / 2),
|
horizontal: 2 * Math.atan(horizontalTangent),
|
||||||
vertical: 2 * Math.atan(flatVerticalFOV / 2)
|
horizontalTangent: horizontalTangent,
|
||||||
|
vertical: 2 * Math.atan(verticalTangent),
|
||||||
|
verticalTangent: verticalTangent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,16 +52,16 @@ QtObject {
|
||||||
|
|
||||||
function buildFovDetails(screens, viewportWidth, viewportHeight, viewportDiagonalFOV, lensDistanceRatio, defaultDisplayDistance, wrappingChoice) {
|
function buildFovDetails(screens, viewportWidth, viewportHeight, viewportDiagonalFOV, lensDistanceRatio, defaultDisplayDistance, wrappingChoice) {
|
||||||
const aspect = viewportWidth / viewportHeight;
|
const aspect = viewportWidth / viewportHeight;
|
||||||
const fovRadians = diagonalToCrossFOVs(degreeToRadian(viewportDiagonalFOV), aspect);
|
const crossFovs = diagonalToCrossFOVs(degreeToRadian(viewportDiagonalFOV), aspect);
|
||||||
const defaultDistanceVerticalRadians = 2 * Math.atan(Math.tan(fovRadians.vertical / 2) / defaultDisplayDistance);
|
const defaultDistanceVerticalRadians = 2 * Math.atan(crossFovs.verticalTangent / defaultDisplayDistance);
|
||||||
const defaultDistanceHorizontalRadians = 2 * Math.atan(Math.tan(fovRadians.horizontal / 2) / defaultDisplayDistance);
|
const defaultDistanceHorizontalRadians = 2 * Math.atan(crossFovs.horizontalTangent / defaultDisplayDistance);
|
||||||
|
|
||||||
// distance needed for the FOV-sized monitor to fill up the screen
|
// distance needed for the FOV-sized monitor to fill up the screen
|
||||||
const fullScreenDistance = viewportHeight / 2 / Math.tan(fovRadians.vertical / 2);
|
const fullScreenDistance = viewportHeight / (2 * crossFovs.verticalTangent);
|
||||||
const lensDistancePixels = fullScreenDistance / (1.0 - lensDistanceRatio) - fullScreenDistance;
|
const lensDistancePixels = fullScreenDistance / (1.0 - lensDistanceRatio) - fullScreenDistance;
|
||||||
|
|
||||||
// distance of a display at the default (most zoomed out) distance, plus the lens distance constant
|
// distance of a display at the default (most zoomed out) distance, plus the lens distance constant
|
||||||
const lensToScreenDistance = viewportHeight / 2 / Math.tan(defaultDistanceVerticalRadians / 2);
|
const lensToScreenDistance = viewportHeight / (2 * Math.tan(defaultDistanceVerticalRadians / 2));
|
||||||
const completeScreenDistancePixels = lensToScreenDistance + lensDistancePixels;
|
const completeScreenDistancePixels = lensToScreenDistance + lensDistancePixels;
|
||||||
|
|
||||||
let monitorWrappingScheme = actualWrapScheme(screens, viewportWidth, viewportHeight);
|
let monitorWrappingScheme = actualWrapScheme(screens, viewportWidth, viewportHeight);
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ Item {
|
||||||
root.effect.antialiasingQuality === 2 ? SceneEnvironment.High : SceneEnvironment.VeryHigh))
|
root.effect.antialiasingQuality === 2 ? SceneEnvironment.High : SceneEnvironment.VeryHigh))
|
||||||
}
|
}
|
||||||
|
|
||||||
PerspectiveCamera {
|
CustomCamera {
|
||||||
id: camera
|
id: camera
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue