The comment on the linked question is correct. If you did ray casting/marching/tracing, you just need to shoot the rays in the desired configuration (i.e. a cylinder) - an the rest of the process would be same as usual for those techniques.
So, if you are familiar with those techniques that tidbit is enough to get you started. And why why do we want those techniques? Because under these projection straight lines may appear curved, which is not posible to accomplish on the vertex shader. So we resource to ray casting and similar techniques.
As an alternative, you could do a two pass process: the first pass projects the geometry with a regular perspective projection to a texture, and a second pass renders the texture with the desired distortion (a barrel distortion), which could be approximated by applying the texture to curved geometry. In fact, if you are interested in the old monitor look, I'd argue, this is the way to go.
If you were doing ray casting/marching/tracing. The direction of the ray would take the x (after normalizing) as an angle. So your ray setup looks something like this (I have tested this in ShaderToy):
float camDst = 1.0f / tan(FOV * 0.5 * PI / 180.0);
vec2 screenPos = (fragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;
vec3 rayTarget = vec3(sin(screenPos.x) * camDst, screenPos.y, cos(screenPos.x) * camDst);
vec3 rayDir = normalize(rayTarget);
vec3 rayPos = vec3(0.0, 0.0, 0.0);
Notice that here rayTarget
is a cylinder of camDst
radius, positioned vertically, centered at the origin.
When it would have usually been like this:
float camDst = 1.0f / tan(FOV * 0.5 * PI / 180.0);
vec2 screenPos = (fragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;
vec3 rayTarget = vec3(screenPos.x, screenPos.y, camDst);
vec3 rayDir = normalize(rayTarget);
vec3 rayPos = vec3(0.0, 0.0, 0.0);
Notice that here rayTarget
is a plane, parallel to the XY plane, at camDst
from the origin. Yes, the camera is looking toward positive Z. Thanks to this "EvIl" the x and y axis of screen space and camera space are oriented the same way.
Where:
iResolution
is an uniform with the pixel size of the viewport.
fraagCoord
is the position of the pixel in normalized device coordinates (they go from -1.0
to 1.0
).
FOV
is an uniform or constant with the field of view in degrees.
PI
is a constant with value the of π.
Alright, so we have a point in XYZ. And following with the above "EvIl", I'll work the screen coordinates from the above code.
The first line we can keep:
float camDst = 1.0f / tan(FOV * 0.5 * PI / 180.0);
Then, assume the point was hit a ray (and the camera is at the origin), so we know the direction of the ray:
float camDst = 1.0f / tan(FOV * 0.5 * PI / 180.0);
vec3 rayDir = normalized(X, Y, Z);
From the code:
vec3 rayDir = normalize(rayTarget);
We have that:
rayDir = normalize(rayTarget)
rayDir = f * rayTarget
We don't know f
.
Let us see the components separately:
rayDir = normalize(rayTarget)
f * rayTarget.x = rayDir.x
f * rayTarget.y = rayDir.y
f * rayTarget.z = rayDir.z
And replace with rayTarget
:
vec3 rayTarget = vec3(sin(screenPos.x) * camDst, screenPos.y, cos(screenPos.x) * camDst);
Which gives us:
rayDir = normalize(rayTarget)
f * sin(screenPos.x) * camDst = rayDir.x
f * screenPos.y = rayDir.y
f * cos(screenPos.x) * camDst = rayDir.z
I'll call screenPos.x
as angle
for now:
rayDir = normalize(rayTarget)
angle = screenPos.x
f * sin(angle) * camDst = rayDir.x
f * screenPos.y = rayDir.y
f * cos(angle) * camDst = rayDir.z
Since vec2(sin(angle), cos(angle))
must have length of 1
, we can get it like this:
vec2(sin(angle), cos(angle)) = normalize(vec2(rayDir.x, rayDir.z))
Thus, we can get screenPos.x
like this:
screenPos.x = angle = atan2(rayDir.x, rayDir.z)
We can get f
by either of these means now:
f = rayDir.x / (sin(angle) * camDst)
f = rayDir.z / (cos(angle) * camDst)
And with f
, we have screenPos.y
:
screenPos.y = rayDir.y / f
So far the code looks something like this:
float camDst = 1.0f / tan(FOV * 0.5 * PI / 180.0);
vec3 rayDir = normalized(X, Y, Z);
float angle = atan2(rayDir.x, rayDir.z);
float f = rayDir.z / (cos(angle) * camDst);
vec2 screenPos = vec2(angle, rayDir.y / f);
To get the fragment coordinates, we need to undo this:
vec2 screenPos = (fragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;
Like this:
float camDst = 1.0f / tan(FOV * 0.5 * PI / 180.0);
vec3 rayDir = normalized(X, Y, Z);
float angle = atan2(rayDir.x, rayDir.z);
float f = rayDir.z / (cos(angle) * camDst);
vec2 screenPos = vec2(angle, rayDir.y / f);
vec2 fargCoord = (screenPos * iResolution.y) + (0.5 * iResolution.xy);
And that fargCoord
should be the position in pixels on the screen.
Some links I found while looking at related questions: