Message Boards Message Boards

How to make 360 degree videos

POSTED BY: Sander Huisman
12 Replies

enter image description here - another post of yours has been selected for the Staff Picks group, congratulations !

We are happy to see you at the tops of the "Featured Contributor" board. Thank you for your wonderful contributions, and please keep them coming!

POSTED BY: Moderation Team

Thanks!

POSTED BY: Sander Huisman

Sander, I tried the above code but looks like my outputs are always coming out zero. No errors are there so not sure what is missing. Any tips?

Best, Vijay

POSTED BY: Vijay Sharma
POSTED BY: Sander Huisman
POSTED BY: Matthias Odisio

Hi Matthias,

The python script is VERY simple (at least on Mac). But should be equally simple on Linux, windows: not so sure. Just download it here: https://github.com/google/spatial-media and follow the video to change the permissions chmod ....... and the run it ./gui.py in the window you can browse your file and resave it. DONE

POSTED BY: Sander Huisman

Thanks for the pointer!

POSTED BY: Matthias Odisio

This is what it should look like, and that is about all you need to do:

enter image description here

POSTED BY: Sander Huisman

Awesome, simply awesome, @Sander Huisman, I've just read about these yesterday and you just happen to run in with this beautiful post! Thanks so much for sharing, I'll pass this along to my various connections.

POSTED BY: Vitaliy Kaurov

Thanks! Glad you like it! I'm not yet sure if I will use it for something useful, but now I have some idea how to do it! It, however, take a LOT of time! So if you want to do higher resolution and more frames it will take considerable amount of time! Some renderers (like 3DS max) can render immediately in 360 degree format, the Wolfram language not yet! But that does make it more fun ;)

POSTED BY: Sander Huisman

A final note: the rendering took a LOT of time (order of 10 hours). The spherical to x-y coordinates calculation can probably be sped up using compile. Furthermore, instead of rendering 20 images, it would be nice to make a function that for each ?-? pixel chooses the 'right' view and get that specific pixel, that would speed it up ~20x.

And a Python script was used to 'inject' some metadata for YouTube to recognize it as 360 degree video: Have a look here: 360 metadata youtube

The scripts have changed slightly since they posted that video and you have to call (also through the Terminal) gui.py, this will show you a small interface in which you can select your video and have it injected with some metadata. Then simply uploading it to YouTube and they do the rest.

Creating the sphere can now be done much easier in version 10, by using regions:

DiscretizeRegion[Sphere[], MaxCellMeasure -> 1]
DiscretizeRegion[Sphere[], MaxCellMeasure -> 0.1]
DiscretizeRegion[Sphere[], MaxCellMeasure -> 0.01]

This will also create the polygons that are more and more refined, it looks like it is using the same algorithm to refine!

POSTED BY: Sander Huisman

All the code:

Viewing polyhedron

SetDirectory[NotebookDirectory[]];
$HistoryLength = 2;
ClearAll[RefineSphere]
RefineSphere[{fi_, vc_}] := 
 Module[{vcc, max = Max[fi], nfi, avgs, midpoints, newtriangles, 
   newvc},
  vcc = DeleteDuplicates[
    Sort /@ Flatten[Subsets[#, {2}] & /@ fi, 1]];
  nfi = MapThread[Rule, {vcc, max + Range[Length[vcc]]}];
  avgs = Mean[vc[[#]]] & /@ vcc;
  newvc = Normalize /@ (vc~Join~avgs);
  midpoints = Partition[#, 2, 1, 1] & /@ fi;
  newtriangles = 
   MapThread[
    Append[Flatten[#, 
         1] & /@ ({#2, 
          Partition[Sort /@ RotateRight[#1], 2, 1, 1]}\[Transpose]), 
      Sort /@ #1] &, {midpoints, fi}];
  newtriangles = Flatten[Replace[newtriangles, nfi, {3}], 1];
  {newtriangles, newvc}
  ]
refine = 0
sphereFI = PolyhedronData["Icosahedron", "FaceIndices"];
sphereVC = N@PolyhedronData["Icosahedron", "VertexCoordinates"];
{sphereFI, sphereVC} = 
  Nest[RefineSphere, {sphereFI, sphereVC}, refine];
sphereVC *= 40;
Graphics3D[GraphicsComplex[sphereVC, Polygon[sphereFI]], 
 Lighting -> "Neutral"]

Vectors

size = 600;  (* each view will be rendered this size squared *)

vangle = 80 \[Degree]; (* view angle is 80\[Degree] *)

(* calculate the various viewing angles *)

viewvectorup = 
  viewvectors = Normalize[Mean[Part[sphereVC, #]]] & /@ sphereFI;
crosslen = (Norm /@ viewvectors) Tan[vangle/2];
viewvectorup = 
  Normalize[{0, 0, 1} - ({0, 0, 1}.Normalize[#]) Normalize[#]] & /@ 
   viewvectorup;
viewvectorup *= crosslen;
viewvectorright = 
  MapThread[Normalize@*Cross, {viewvectors, viewvectorup}];
viewvectorright *= crosslen;

(* make black-white masks for each view, note that we use 0.96 \
viewing angle here such that each of the views overlaps a bit *)

masks = MapThread[
   Rasterize[
     Graphics3D[{EdgeForm[], White, 
       GraphicsComplex[sphereVC, Polygon[#1]]}, Boxed -> False, 
      Lighting -> "Neutral", ViewVertical -> {0, 0, 1}, 
      ViewVector -> {{0, 0, 0}, #2}, ViewAngle -> (0.96 vangle), 
      ImageSize -> {size, size}, Background -> None], "Image", 
     Background -> None] &, {sphereFI, viewvectors}];

(* this is what all the viewingvectors look like (blue), green is the \
pointing-up vector, and in red is the vector to the right *)
gr = 
 Graphics3D[
  MapThread[{Blue, Arrow[Tube[{{0, 0, 0}, #1}]], Green, 
     Arrow[Tube[{#1, #1 + #2}]], Red, 
     Arrow[Tube[{#1, #1 + #3}]]} &, {viewvectors, viewvectorup, 
    viewvectorright}]]

Functions

vvv = {viewvectors, viewvectorright, viewvectorup}\[Transpose];
normvvv = Map[Normalize, vvv, {2}];
nvvv = Map[Norm, vvv, {2}];
svv = vvv/nvvv^2;
ClearAll[invtransfunc];
invtransfunc[{\[Phi]_, \[Theta]_}, n_] := Module[{vp},
   vp = {Cos[\[Phi]] Sin[\[Theta]], Sin[\[Theta]] Sin[\[Phi]], 
     Cos[\[Theta]]};
   If[vp.vvv[[n, 1]] <= 0,
    vp = (vp nvvv[[n, 1]]/(vp.normvvv[[n, 1]]));
    {vp.svv[[n, 2]], vp.svv[[n, 3]]}
    ,
    {-2, -2}
    ]
   ];

ClearAll[MakeScene]
MakeScene[t_] := Module[{rot, p1, p2},
  rot = 2 \[Pi] t;
  p1 = ParametricPlot3D[{-8.2 + (8 + Cos[v]) Sin[u + t], (8 + 
        Cos[v]) Cos[u + t], Sin[v] + 0.75}, {u, 0, 2 Pi}, {v, 0, 
     2 Pi}, ViewVector -> {{0, 0, 0}, {0, 1, 0}}, 
    ViewAngle -> 80 \[Degree], 
    PlotStyle -> Directive[Green, Opacity[1]], 
    MeshShading -> {{Red, Blue}, {Blue, Red}}, 
    MeshFunctions -> {#4 &, #5 &}, PlotPoints -> 80, Axes -> False, 
    Mesh -> {51, 10}, Lighting -> {{"Ambient", White}}, 
    ViewVertical -> {0, 0, 1}];
  p2 = Graphics3D[{Orange, 
     Sphere[{-8.2 - 8 Sin[2 rot], 8 Cos[2 rot], 0.45}, 0.2]}, 
    Lighting -> {{"Ambient", White}}];
  Show[{p1, p2}]
  ]
ClearAll[Make360]
Make360[scenef_, t_] := 
 Module[{scn, views, antetransform, posttransform, \[Alpha]cs, imgout},
  \[Beta]++;
  scn = scenef[t];
  views = MapThread[
    Rasterize[
      Show[scn, ViewVector -> {{0, 0, 0}, #1}, 
       ViewVertical -> {0, 0, 1}, ViewAngle -> vangle, Boxed -> False,
        ImageSize -> {size, size}, Background -> White], 
      "Image"] &, {viewvectors}];
  antetransform = MapThread[ImageMultiply, {masks, views}];
  posttransform = 
   Table[ImageTransformation[antetransform[[n]], invtransfunc[#, n] &,
      DataRange -> {{-1, 1}, {-1, 1}}, 
     PlotRange -> {{-\[Pi], \[Pi]}, {0, \[Pi]}}, 
     Padding -> Transparent]
    ,
    {n, Length[antetransform]}
    ];
  \[Alpha]cs = AlphaChannel /@ posttransform;
  posttransform = RemoveAlphaChannel /@ posttransform;
  \[Alpha]cs = Binarize[#, 0.95] & /@ \[Alpha]cs;
  posttransform = 
   MapThread[SetAlphaChannel, {posttransform, \[Alpha]cs}];
  imgout = ImageCompose[First[posttransform], Rest[posttransform]];
  imgout = RemoveAlphaChannel[imgout];
  imgout
  ]

Calculation/Rendering/Export

SetDirectory[NotebookDirectory[]];
Dynamic[\[Beta]]
\[Beta] = 0;
n = 150;
t = Most[Subdivide[0, 1, n]];
fns = "out" <> ToString[#] <> ".png" & /@ Range[Length[t]];

CloseKernels[];
LaunchKernels[4];
DistributeDefinitions[Make360, MakeScene, n, t, fns, i, vvv, normvvv, 
  svv, nvvv, invtransfunc, masks, size, vangle, viewvectors, 
  viewvectorright, viewvectorup, sphereFI, sphereVC];
SetSharedVariable[\[Beta]];
ParallelEvaluate[$HistoryLength = 2];


ParallelDo[
 If[! FileExistsQ[fns[[i]]],
  out = Make360[MakeScene, t[[i]]];
  Export[fns[[i]], out];
  ]
 ,
 {i, 1, Length[fns]}
 ,
 Method -> "FinestGrained"
 ]
Attachments:
POSTED BY: Sander Huisman
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract