Jump to content
Sign in to follow this  
denali

Multi-monitor (dual, triple, etc) Stretch/Fisheye Distortion Fix

Recommended Posts

I've decided to get this out for you all to enjoy.  I have created a few tools to do install and configuration, but they are too messy to include, might cause more confusion than they're worth at this time.  I plan to clean them up later.  So this is just the most elegant smallest pieces.
 
This will also help reposition a view for use with two monitors as well, or work for a single ultra display to zap distortion.
 
This code is not maximally optimized, nor elegant.  I am a bad codemonkey.  Works now.  Polish later.  For your enjoyment:
 
Open notepad, but with administrator rights (right click / run as administrator).  
 
From here on "context menu" will mean "right click".
 
Paste this into notepad:
 
<SimBase.Document Type="AceXML" version="1,0">
 
    <PostProcessEffects.PostProcessDefinition>
        <EffectName>AS01</EffectName>
        <ShaderFileName>AS01.psh</ShaderFileName>
        <ShaderEntryPoint>PsAntiStretchMain</ShaderEntryPoint>
        <ColorRequest>TRUE</ColorRequest>
        <DepthRequest>FALSE</DepthRequest>
    </PostProcessEffects.PostProcessDefinition>
 
</SimBase.Document>
Save in C:\Program Files (x86)\Lockheed Martin\Prepar3D v2\ShadersHLSL\PostProcess as
AS01.xml  (adjust for your installation location) 
 
Be sure to save as type "all files" at the bottom of the save dialog, or it may have a txt extension.
 
Clear the contents of notepad and paste this: 
 
// Copyright John Lacquey 2014

#include "Quad.sh"

Texture2D<float4> srcTex;

//static const float PI = 3.14159265f; too much PI is a bad thing

static const float horizontalFOV = 150;
static const float verticalFOV = 70;

// this is not needed at this time
//static const float horizontalRes = 5760;
//static const float verticalRes = 1080;

//
//                   B   
//                  /|
//   		   / |
//	          /  |
//	         /   |
//	        /    |
//	  h    /     |
// hypotenuse /      |a opposite
//	     /       |
//	    /\       |
//	   / Theta   |
//    	  /____\_____|
//     A       b      C 
//          adjacent
//

float4 PsAntiStretchMain(PsQuad vert) : SV_Target
{ 
// Texture coordinates (0.0 - 1.0) 
float X = vert.texcoord.x;
float Y = vert.texcoord.y;

// Texture coordinates normalized to -1.0, 1.0
float normFactor = 0.5f;
float normX = 2 * (X - normFactor);
float normY = 2 * (Y - normFactor);

// Get the source texture dimemsions.
    uint2 uTDim;
    srcTex.GetDimensions(uTDim.x,uTDim.y);

// Get the radians of the new image.  This corresponds to the screen dimensions,
// the spherical rectangle which is the image that we want.
float horizontalFOVRadians_newImageXWidth = radians(horizontalFOV / 2);
float verticalFOVRadians_newImageYHeight = radians(verticalFOV / 2);
float rightAngleRadians = radians(asfloat(90));

// The dimensions of the current distorted image, tangent of FOV Radians, 
// the plane tangent to the sphere.
float distortedImageWidth = tan(horizontalFOVRadians_newImageXWidth);
float distortedImageHeight = tan(verticalFOVRadians_newImageYHeight);	

//////
// The ratio of the desired image to the distorted image
// possibly use the inverse of this to clip
float correctXRatio = 1; //(horizontalFOVRadians_newImageXWidth / distortedImageWidth);
float correctYRatio = 1; //(verticalFOVRadians_newImageYHeight / distortedImageHeight);

// Increase the dimensions of the distorted image radian measurement by correct_Ratio
// in order to have more to crop the new image from.
float expandedDistortionWidth = correctXRatio * distortedImageWidth;
float expandedDistortionHeight = correctYRatio * distortedImageHeight;

// normXY will find the radians of the desired pixel.
float horizontalRadiansOfTarget = normX * horizontalFOVRadians_newImageXWidth;
float verticalRadiansOfTarget = normY * verticalFOVRadians_newImageYHeight;

// find the x coordinate for the desired pixel
float targetXPixelRadians_xOfDistortion = tan(horizontalRadiansOfTarget);  // x coordinate of pixel on distortion
float colorU1 = targetXPixelRadians_xOfDistortion / expandedDistortionWidth; // the ratio of the target pixel over 

the full distorted dimension radians - to be multiplied by the current image in the final step.
float colorUfinal = colorU1;// * correctXRatio;  // distortion correction

// maintaining all of the color values from the original but distorted image, 
// some the y values will reach outside of the rectangular boundings of the original image,
// and render no color
//;float targetYPixelRadians_yOfDistortion = tan(verticalRadiansOfTarget);
 
// find the bottom of the y triangle, the secant of x, reciprocal of cos(A), 
float secantX_bottomOfYTriangle = 1 / cos(horizontalRadiansOfTarget);
//float secantX = 1 / (cos(verticalRadiansOfTarget) * sign(normY)) ;
//float secantX = cos(verticalRadiansOfTarget) * sign(normY) ;
// radians for the top angle of the new image at the target pixel
float oppositeTargetAngleRads = radians(90 - degrees(verticalRadiansOfTarget));

// the y dimension, the maximum top angle radians to the original image plane, some being outside of the rectangle,
// needs to be found for every y, for the final ratio, to be multiplied by the current image in the final step.
//;float oppositeMaxYAngleRads = radians(90 - degrees(verticalFOVRadians_newImageYHeight));
// the y dimension needs to be ratioed against the original image radians, not the maximum extrapolated from the new image
// the dimension in radians of the original image rectangle will be constant as x changes,
// so we can use the radians from the maximum degrees of the y FOV, where x = 0, distortedImageHeight 
// angles will vary because the secant will change.
//float oppositeMaxYAngleRads = radians(90 - degrees(verticalFOVRadians_newImageYHeight));

// Now we can use the Law of Sines for the maximum and target in y.
// Law of Sines: b/sin(B) = c/sin(C)
// b = (c * sin(B)) / sin(C)
float targetYRadians = (secantX_bottomOfYTriangle * sin(verticalRadiansOfTarget)) / sin(oppositeTargetAngleRads);
// float maxYRadians = (secantX_bottomOfYTriangle * sin(verticalFOVRadians_newImageYHeight)) / sin

(oppositeMaxYAngleRads);
// the final ratio, to be multiplied by the current image in the final step
float colorV1 = targetYRadians / distortedImageHeight;
float colorVfinal = colorV1;


//////
// Denormalize
float colorU = (colorUfinal / 2) + normFactor;
float colorV = (colorVfinal / 2) + normFactor;

// u, v coordinates for the new color
    int3 iTexCoord = int3(uTDim.x * colorU, uTDim.y * colorV, 0);

// load them into the buffer  
float4 color = srcTex.Load( iTexCoord );
 
return color;
} 
"Save as" in the same location as above (e.g. C:\Program Files (x86)\Lockheed Martin\Prepar3D v2\ShadersHLSL\PostProcess) as
AS01.psh .  Again, Be sure to save as type "all files" at the bottom of the save dialog, or it may have a txt extension, and P3D2 will not be forgiving.
 
So that is a "High Level Shader Language" shader.  It's c programming language with some DirectX additions, only scripted, not compiled.  You have to re-start P3D2 for every change, AFAIK so far.  This is one of what the Oculus Rift will need to work, but there is probably plenty of code to source from for the math already, and there is already one included in P3D2.2.  
 
Now to load it into P3D2:
 
Quick summary, right click (on the view you wish to augment) / custom camera / save location.  
 
Use AS01 as the name.
 
Then override FOV and SET THE FOV EXACTLY AS IT IS IN THE SHADER, 150 by 70.  You can set this whatever you like in both the code and here, but they must be the same.    But at this time you should use this setting as the image buffer faze is tuned for that, and it might give you a mess without these defaults to begin with.  There is more math I can put into this, and have in a tool for later, but it's just gonna hurt if I give it to you now.  
 
The math is precise, and precise FOV is important.  (There are FOV calculators on the web when you're ready to break into it)
 
Now attach the shader.  Under the camera effects / effects dropdown, look for AS01 (did you save that notepad file as "all"?).  Leave the excludes out.  Click the "Add" button after you have selected AS01 from the drop down list. You will then see it listed in the effects box. 
 
You can preview the view, and even grab the edges of the preview to fill your whole monitor/s.  Save and desire to exit the dialog.  Then  right click  / custom camera / [the name you gave] to use the view.  To edit the view: Views / Edit Custom Cameras / [your view name]
 
Then you can click the "Save" button.  
 
There are some issues when you change from view to view.  You have to reload the view (right click / custom camera / [view]) again to bring it back.  I have figured out how to save the shader view from session to session, but it requires a tool I'll put out later.  
 
You'll need to save the scenario in order to not have to go through loading the shader again.
 
Also, when you start a new scenario or have restarted P3D, you'll need to go into that custom view and recheck override FOV.  For some reason it doesn't stick.  It's a major pain I'm trying to figure out.
 
Now for the next part.  P3D needs to be tricked into giving us a large enough image which will act as a buffer to clip.
 
You need to download and install AutoHotKey:  http://ahkscript.org/v2/
(I don't think there is an installer, for version 2.0.  There is a 1.0 with installer here:  http://ahkscript.org/download/
I am not sure if that works with this code.  But give 1.0 a try if you want, and if it doesn't work with this code, just install 1.0 to use it's installer, then copy the 2.0 exe into it's folder)
 
This is the script you'll want around to run with AutoHotKey:  I'm sorry it's messy, but it works.
 
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

Gui, New  ; Creates a new unnamed GUI.
;Gui, Name:New  ; Creates a new GUI, destroying any existing GUI with that name
;Gui, MakeScene:New

Gui, Add, Text,, Height:
Gui, Add, ComboBox, vHeight, 1080|1500|2000|2500|2900||3500|4000|4500|5000|5500|6000|6500|7000|7500|8000|8500

Gui, Add, Text,, Width:
Gui, Add, ComboBox, vWidth, 1000|2000|3000|4000|5000|5760|6500||7000|8000|9000|10000|11000|12000|13000|14000|15000|16000|17000

Gui, Add, Button, gDefaultSize , 5760x1080  ;
Gui, Add, Button, default, OK  ;

Gui, Show,, Prepar3 Position


return  ; End of auto-execute section. The script is idle until the user does something.

GuiClose:
ExitApp
DefaultSize:
Gui, Submit, NoHide ; Save the input from the user to each control's associated variable.
CropBlankMargin(5760 ,1080)
return

ButtonOK:
Gui, Submit, NoHide ; Save the input from the user to each control's associated variable.
CropBlankMargin(Width ,Height)
return

CropBlankMargin(Width = 0,Height = 0, Posx = 0, Posy = 0)
{
  WinGetPos,X,Y,W,H, Lockheed Martin
  If %Width% = 0
    Width := W

  If %Height% = 0
    Height := H

  If %Posx% = 0
    Posx := X

  If %Posy% = 0
    Posy := Y
WinGet, Prepar3D, , Lockheed Martin
 finalXPos := Round(((5760 - Width) / 2) - 1920)
 finalYPos := Round(((1080 - Height) / 2))
hwnd := Prepar3D
DllCall("SetWindowPos"
    , "UInt", hwnd
    , "UInt", 0        ; hWndInsertAfter
    , "Int" , finalXPos    ; 
    , "Int" , finalYPos   ; 
    , "Int" , Width     ; 
    , "Int" , Height     ; 
    , "UInt", 4|0x400) ; uFlags
}

ViewFR(Width = 0,Height = 0, Posx = 0, Posy = 0)
{
  WinGetPos,X,Y,W,H,A
  If %Width% = 0
    Width := W

  If %Height% = 0
    Height := H


  If %Posx% = 0
    Posx := X


  If %Posy% = 0
    Posy := Y

hwnd := WinActive("A")
DllCall("SetWindowPos"
    , "UInt", hwnd
    , "UInt", 0        ; hWndInsertAfter
    , "Int" , -1910    ; cx (width)
    , "Int" , 0     ; cy (height)
    , "Int" , Width    ; X
    , "Int" , Height   ; Y
    , "UInt", 4|0x400) ; uFlags
}

ResizeWin(Width = 0,Height = 0, Posx = 0, Posy = 0)
{
  WinGetPos,X,Y,W,H,A
  If %Width% = 0
    Width := W

  If %Height% = 0
    Height := H


  If %Posx% = 0
    Posx := X


  If %Posy% = 0
    Posy := Y

  WinMove,A,,-1920,0,%Width%,%Height%
}

#!space::CropBlankMargin(Width ,Height)
#space::ResizeWin(5760,1080, 100, 1)
#!f::ViewFR(Width ,Height ,1960, 1)
There are three hotkeys in there.
 
[control][windows key][space]  does the same as OK, but with the gui hidden.
[control][space]  restores to 5760 window so you can get to menus.
[control][windows key][f]  lets you see the framerate display (may need adjusting, and I might be able to move the display in P3D later)
 
What is going on here is the view is being stretched to put just what you want on screen.  Change those numbers at your peril, or to move the view to the right a little bit for dual monitor configs (If someone asks me to make it do that easily I'll put that in.)
 
For reference, open the Prepar3d Learning Center, and follow the Contents Tree to Prepar3D Product/Getting Started/ View System/ Custom Camera UI and Post Process right below that.
 
Grey Skies

(I apologize to the admins for the condition of the the first post of this topic.  I realize I didn't specify code type in the code box.  That was the problem.  But I tried to fix it and it said I wasn't authorized.  Please delete the other post.)

Share this post


Link to post

So what does it do?  (Besides attract crickets?)

 

This eliminates the stretch distortion that is seen particularly on the side monitors of triple monitor displays.  But as it eliminates all sizing distortion, it will work for large hi-res displays, and dual displays, or any configuration.  For dual displays the panel can be centered on either display, instead of the bezel mucking up the panel view (although the AHK tool code included doesn't provide this just yet, you'll have to hack it a bit).

 

2 images to compare.

Before: Half Moon Bay, CA

Half_Moon_Bay_before.jpg

 

After:

Half_Moon_Bay_after.jpg

 

(if you know of a less annoying image host that will thumbnail wide images better please let me know)


The images were created at Albuquerque with some kind of Global mesh, ASN, and homegrown photoreal scenery using FSEarthtiles (I have some utitlities for that too ... but don't get me wrong, I love Orbx stuff just as much).

 

Capture.jpg

Capture2.jpg

Capture3.jpg

Capture4.jpg

 

So a few things.  It's not perfect.  

There is a distortion of the horizon curving depending on if you're looking up or down, but it isn't that noticeable, and seems to adjust for the inward angle of three monitors.

The clickspots are borked completely.  

The aircraft identifiers do not seem to get the correction and so show what the image was before being corrected.  It's interesting to see what we've been living with.

The first two I may be able to mitigate yet.  I've had some success with clickspots in an earlier version.  

Also, there is a bit of graininess that I have fixed in a previous version as well.  

 

Capture5.jpg

Capture6.jpg

Capture7.jpg

(There is a wicked turbulence on that ridge.)

Share this post


Link to post

Hi Denali 

 

I did the first two steps of saving the two files..then I was lost. LOL :)

 

Hmm...


Manny

Beta tester for SIMStarter 

Share this post


Link to post

Hi Manny.   

 

Yeah, It's no fun installing.  I think I need to finish that installer.  

 

So, you've installed the shader.  Now you need to get a copy of AutoHotKey.  Install version 1, it will probably work, and it has an actual installer, whereas AHK 2 is just the executable.  I think you need the version with the installer to make the .ahk extension to be used by AHK anyways.  

 

OK, so I think I missed something.  After installing AHK, the contents of the last code box need to be saved as something.AHK  It's a script that AutoHotKey will run.  I don't have a good name for it; let me know if you have a name you like.

 

After making the ahk file, start your sim, attach the shader to a view, the steps I managed to include.  Then run the ahk script.  If the AHK 1  installer associates ahk files with AHK then it should pop up a little dialog.  Defaults should be mostly OK, so just hit OK.

 

Hope that helps.  Sorry for the confusion.

Share this post


Link to post

From the P3D Learning Center, another, probably better, way to get the shader loaded, after creating the xml and psh files.  

 

Adding the Post-process to a Camera Definition

The final step in displaying the new Post-process within Prepar3D is to add the Post-process to one or more cameras in the Cameras.cfg file in the %APPDATA%\Lockheed Martin\Prepar3D v2 directory. Up to 16 post-process effects can be applied to a single camera, however to achieve the best performance, limit the number used to as few as possible. Add the following line to the camera config: PostProcess00="PostProcessName" where "PostProcessName" is the name of the .xml file without the .xml extension. Subsequent post-processes can be added by setting PostProcess01 - PostProcess15 in the camera config. Note also that these are applied to the rendered image in the order specified. Refer to the Camera Configuration section for more info on setting up cameras. 

So, what I did was place a new camara definition at the end of the Cameras.cfg file.  This definition is exactly like the VIrtual Cockpit definition, with a new GUID I created, and renamed to "AS Virtual Cockpit".  Also two FOV lines are added.  Your camera definition number may vary.  Please use the same GUID (lets not waste numbers, lol).

[CameraDefinition.019]
Title = AS Virtual Cockpit
Guid = {84304EBD-5823-485e-AEB6-8D1839B84D17}
Description = This is a non-distorted virtual cockpit
Origin = Virtual Cockpit
MomentumEffect = Yes
SnapPbhAdjust = Swivel
SnapPbhReturn = False
PanPbhAdjust = Swivel
PanPbhReturn = False
Track = None
ShowAxis = YES
AllowZoom = TRUE
InitialZoom = 0.6
SmoothZoomTime = 2.0
ZoomPanScalar = 1.0
ShowWeather = Yes
XyzAdjust = TRUE
ShowLensFlare=TRUE
Category = Cockpit
PitchPanRate=30
HeadingPanRate=75
PanAcceleratorTime=0
HotKeySelect=1
ObserverVerticalFOV=70
ObserverHorizontalFOV=150
PostProcess00 = AS01

What is nice about this, no more reloading, available for every aircraft, you can select it as the default view for a scenario, and it avoids the buggyness with the FOV settings in the custom view dialog.  

 

You will still need the AHK script above.  I could have compiled it and posted it, but in the interested of trust I left it in the open until it's been established.  

Share this post


Link to post

I think i sort of got it working, but i have a issue when i select the AS01 from the custom camera all the screens go black. Also have the same issue when i select as virtual cockpit. Maybe i messed up something somewhere, After all i am not a rocket scientist like you :)

 

Thank you so much for your effort. This is going to be great! 

Share this post


Link to post

Look at the shader code carefully.  There may be a blank line in there or a line return that shouldn't.   I had some sillyness with the posting tool here and there may be a goof in there that I missed. 

 

I'll be back later.


It's  not rocket science.  Somewhere in Trig class I bet we covered it in 11th grade.  But there was that girl.


Maybe 12th grade.

Share this post


Link to post

target pixel over

 

the full distorted

look for that line in the code above that is where the problem is there should be no space or new line

Share this post


Link to post

Found it and changed it. I think i have a problem whit getting auto hotkey to run properly. I can get the script to run in the hotkey program, but when i push ok on that small popup the view in the sim changes and gets weird and the screens go black again. 

Share this post


Link to post

With the shader loaded, without AHK involved, you should see something like this

 

abq_kingair.jpg

 

that is the shader correcting all of the "pixels" in the view, along with a little compression due to the current view shape vs the 70 degree vertical fov

 

if you can get that far the shader works.  

 

what the ahk script does is expand the view beyond the screen, and centers it (or can be an offset for dual or n monitor configurations) leaving in the screen a view that is uncompressed and without empty black pixels.  

 

it may be that the script is somehow showing only an area of empty pixels

Share this post


Link to post

Well i found that when i select the shader the screens don`t go black, they are just very very dark.

Share this post


Link to post

In the script pop up i can adjust Height and Width. Default it is set to Height 2900 and Width 6500. When i push it the view enters some kind of full screen mode, but it only covers left and center screen and 20 percent of the right screen.

 

Also i found that when i first saved those two notepad files in the shader map i did`t not do it as administrator. Once i did that i have eyes, but the colors looks like something from a Andy Worhol picture.  

 

Hope i don`t bother you to much. Think of me as a beta tester :) 

 

I do run p3d in AFR-friendly mode. You think that can cause some of my issues? 

Share this post


Link to post

LOL.   I just realized.  What are the resolutions of your monitors, and how many?  That is probably very important.  I am assuming 5760x1080.  I'm an assumer.  I haven't been getting good sleep lately.

 

If what you mean by eyes, like the shape of a mask?  There should be alot of compression of the image, it will be very distorted.  

 

I appreciate the feedback.  If nobody complains then I'm really in trouble!  Any ideas are also welcome.  It actually motivates me.  I might get the tools out sooner.

 

Once I know what resolution you're at I can give you the adjustments.  

Share this post


Link to post

Allright!! I got three 24" running at 5862 x 1080 ( bezel correction)

 

I can see everything just fine, but the colors are actually something like this : http://www.warhol.org/exhibitions/2012/15minuteseternal/en/index.html

 

I guess it is due to the compression and then again i assume that whit the right adjustment it will sort it self out. 

 

Talking about not getting enough sleep. It is three o`clock in the morning here in Norway. I better get some... 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  
  • Tom Allensworth,
    Founder of AVSIM Online


  • Flight Simulation's Premier Resource!

    AVSIM is a free service to the flight simulation community. AVSIM is staffed completely by volunteers and all funds donated to AVSIM go directly back to supporting the community. Your donation here helps to pay our bandwidth costs, emergency funding, and other general costs that crop up from time to time. Thank you for your support!

    Click here for more information and to see all donations year to date.
×
×
  • Create New...