Documentation
¶
Overview ¶
Package projection maps between directions a viewer looks in and positions in the source image.
This is the whole of what makes immersive video immersive, and it is a pure function: given where the eye is pointing, say where to sample. Nothing here touches a GPU, a decoder or a display, so the geometry can be checked against known directions instead of by putting a headset on and squinting.
The two directions are both needed. Projection.Sample answers "the eye looks there, where is that in the picture?", which is what a fragment shader asks once per output pixel. Viewport.Ray answers "which way is this output pixel looking?", which is what turns a rectangle of pixels into directions in the first place.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // Sphere360 is monoscopic or stereoscopic full-sphere equirectangular. Sphere360 = Projection{Kind: Equirect, HSpanDeg: 360, VSpanDeg: 180} // Hemisphere180 is VR180 equirectangular. Hemisphere180 = Projection{Kind: Equirect, HSpanDeg: 180, VSpanDeg: 180} // Fisheye180 is a circular fisheye covering a hemisphere. Fisheye180 = Projection{Kind: Fisheye, HSpanDeg: 180} // Screen is a flat virtual screen of a comfortable size: roughly what a // large television occupies from the sofa. Screen = Projection{Kind: Flat, HSpanDeg: 60, VSpanDeg: 34} )
Common source geometries.
Functions ¶
This section is empty.
Types ¶
type Kind ¶
type Kind int
Kind is the geometry of the source image.
const ( // Flat is ordinary rectilinear video: a virtual screen floating in front of // the viewer. Most material is this, including 3D films. Flat Kind = iota // Equirect is equirectangular: longitude across, latitude down. A full // sphere (360x180) or a hemisphere (180x180, "VR180") are both this, with // different spans. Equirect // Fisheye is an equidistant circular fisheye, where distance from the image // centre is proportional to the angle from straight ahead. Cameras that // shoot VR180 often deliver this without rectifying it. Fisheye )
The projections encountered in real material.
type Projection ¶
type Projection struct {
Kind Kind
// HSpanDeg and VSpanDeg are how much of the world the image covers, in
// degrees. For Equirect: 360x180 for a full sphere, 180x180 for VR180. For
// Flat they are the angular size of the virtual screen. For Fisheye only
// HSpanDeg is used, as the circular field of view (180, or 190-200 for a
// lens that overshoots).
HSpanDeg, VSpanDeg float64
}
Projection is a source geometry together with its extent.
func FillScreen ¶ added in v0.11.0
func FillScreen(fovyDeg, viewAspect, pictureAspect float64) (p Projection, ok bool)
FillScreen returns the flat virtual screen that FILLS a viewport: the largest screen showing a picture of the given aspect ratio whose whole picture still fits inside the view.
This is what a player wants and what picking spans by hand does not give. Screen is a fixed 60 by 34 degrees, chosen as a comfortable television from the sofa, and it is wrong twice over for a film. It is the wrong SHAPE for anything but a 1.889:1 picture — a 16:9 film stretches 6% across it — and it is the wrong SIZE for a view that is not what it assumed: a 90-degree view leaves that screen occupying 11% of the glasses, which is what the picture was measured to cover before this existed.
fovyDeg is the viewport's vertical field of view and viewAspect its output shape; together they say how much the eye sees. pictureAspect is the shape of the picture, width over height, and is preserved exactly — a screen that filled the view by stretching would fill it with the wrong picture.
Note that the RESULT of filling does not depend on the field of view: the screen grows with the view, so the pixels come out the same whatever fovyDeg is. The angle still matters for saying truthfully how large the picture is, which is why it is asked for rather than assumed.
ok is false for a field of view at or beyond a half turn, or for an aspect or an angle that is not positive and finite. There is no screen to describe then, and none is invented.
func (Projection) Sample ¶
func (p Projection) Sample(dir pose.Vec3) (u, v float64, ok bool)
Sample maps a direction to normalised coordinates in the eye image: u across from 0 at the left edge to 1 at the right, v down from 0 at the top. ok is false when the direction falls outside what the image covers — behind the viewer in a 180-degree piece, say — and the caller should show background rather than clamp, because clamping smears the edge pixels across the whole of the missing region.
dir need not be normalised; it is normalised here. The zero vector has no direction and is refused.
type Viewport ¶
type Viewport struct {
// Width and Height are the eye's output size in pixels.
Width, Height int
// FOVyDeg is the vertical field of view in degrees. The horizontal follows
// from the aspect ratio, which is what keeps the picture undistorted when
// the output is not square — and an XR headset's per-eye view rarely is.
FOVyDeg float64
}
Viewport is one eye's output rectangle and how much it sees.
func (Viewport) LookRay ¶
LookRay is Viewport.Ray turned into a world direction by the viewer's orientation: the one composition every renderer performs per pixel.
func (Viewport) Ray ¶
Ray returns the eye-space direction looked at by output pixel (x, y), measured through the pixel's CENTRE. Sampling through the corner biases the whole image by half a pixel, which is invisible in isolation and shows up as a seam where two views meet.
The direction is not normalised — Projection.Sample normalises what it is given, and skipping a square root per pixel is worth having in a function called a few million times a frame.