aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2021-02-24 21:04:15 -0500
committerGitHub <noreply@github.com>2021-02-24 21:04:15 -0500
commitf8c2897765b41e90c7e32944ae412f8c56c90b4a (patch)
treef0751961e560a5c61d617083bf6dade5ed4414bd
parentc6482fe2fca344241b18a857769e5715c230acc6 (diff)
downloadxesite-f8c2897765b41e90c7e32944ae412f8c56c90b4a.tar.xz
xesite-f8c2897765b41e90c7e32944ae412f8c56c90b4a.zip
Vrchat writeup (#333)
* first part of the VRChat for gmeets writeup Signed-off-by: Christine Dodrill <me@christine.website> * more words Signed-off-by: Christine Dodrill <me@christine.website> * polishing touches Signed-off-by: Christine Dodrill <me@christine.website>
-rw-r--r--.vscode/settings.json10
-rw-r--r--blog/convoluted-vrchat-gchat-setup-2021-02-24.markdown214
-rw-r--r--static/blog/vrchat/alvr_graph.svg172
-rw-r--r--static/blog/vrchat/simple_graph.svg73
-rw-r--r--static/blog/vrchat/total_graph.svg173
5 files changed, 642 insertions, 0 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..bf2bd93
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,10 @@
+{
+ "editor.wordWrap": "wordWrapColumn",
+ "editor.wordWrapColumn": 80,
+ "editor.wordBasedSuggestions": false,
+ "[markdown]":{
+ "editor.wordWrap": "wordWrapColumn",
+ "editor.wordWrapColumn": 80,
+ "vim.textwidth": 80,
+ },
+} \ No newline at end of file
diff --git a/blog/convoluted-vrchat-gchat-setup-2021-02-24.markdown b/blog/convoluted-vrchat-gchat-setup-2021-02-24.markdown
new file mode 100644
index 0000000..d813455
--- /dev/null
+++ b/blog/convoluted-vrchat-gchat-setup-2021-02-24.markdown
@@ -0,0 +1,214 @@
+---
+title: "My Convoluted VRChat Google Meet Setup"
+date: 2021-02-24
+tags:
+ - oculusquest2
+ - vr
+ - vrchat
+---
+
+# My Convoluted VRChat Google Meet Setup
+
+Recently the place I work for sent us all VR headsets. I decided to see what it
+would take to use that headset to make my camera show a virtual avatar instead
+of my meat body face. This is the story of my journey through chaining things
+together to make work meetings a bit more fun by using a 3D avatar instead of
+myself in some of them.
+
+[This post uses SVG for diagrams to help explain what's going on here. You may
+need to use a browser with SVG support in order to get the best experience with
+this article. All the diagrams will be explained after the fact so that people
+using screen readers are not left out.](conversation://Mara/hacker)
+
+<center>
+
+<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Working at <a href="https://twitter.com/Tailscale?ref_src=twsrc%5Etfw">@Tailscale</a> is great. They sent us all an Oculus Quest 2! <a href="https://t.co/dDhbwO9cFd">pic.twitter.com/dDhbwO9cFd</a></p>&mdash; Cadey A. Ratio (@theprincessxena) <a href="https://twitter.com/theprincessxena/status/1362871906597224456?ref_src=twsrc%5Etfw">February 19, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
+
+</center>
+
+So, let's cover the basics from a high level. At a high level a webcam is just
+a video source that may or may not have a microphone attached to it. So in
+order to get my avatar to show up in a video call, I need some way to make some
+window on my computer act as a webcam. This will make the overall dependency
+list look like this (for those of you using screen readers I will describe
+this diagram below):
+
+<center>
+
+![](/static/blog/vrchat/simple_graph.svg)
+
+</center>
+
+VRChat renders to the Desktop which is picked up by OBS which has the ability
+to pretend to be a webcam, which is finally picked up by Google Meet.
+
+If the VR headset that I got from work was a tethered to the PC kind of VR
+headset like the Valve Index or HTC Vive, the next steps would involve full
+body tracking or something so that I could have my movements in real life
+transfer into movements that my avatar makes.
+
+However, the VR headset we got sent was an Oculus Quest 2. This is a
+_standalone_ VR headset that is basically an Android tablet that you strap
+to your face. This makes things a bit more technically challenging because
+now you need some way to get the video to the headset and the motion tracking
+data from the headset and to the computer at 90 times per second. This requires
+a bit more cleverness.
+
+The Oculus desktop software ships with a feature called Oculus Link that allows
+you to use a gaming PC to render the VR data to your headset by sending the
+video streams over USB. I had to dig around for a compatible cable (It needs to
+be a specific kind of USB-3 to USB-C-3 cable with at least 5 gigabits per
+second of transfer capacity) since the ones that
+[Oculus sells](https://www.oculus.com/accessories/oculus-link/) are both at
+least CAD$110 and out of stock anywhere I can find them in Canada. The 0.75
+meter long cable I had been using was good enough to get me through the first
+couple days of experimenting with VR, but it was clear that a better solution
+was needed.
+
+I did some digging and found a bit of software called
+[ALVR](https://github.com/alvr-org/alvr#readme) that claimed to let me do VR
+from my computer wirelessly. So I set it up on the Quest and on my tower,
+which brought up the dependency graph to this:
+
+<center>
+
+![](/static/blog/vrchat/alvr_graph.svg)
+
+</center>
+
+ALVR talks with its counterpart on the Quest. This allows you to stream the VR
+video and audio bidirectionally. You also need to bring Virtual Audio Cable
+into the setup so that you can hear stuff in the game and so that other people
+can hear you using the headset mic. However, ALVR is not available on the Quest
+store. You need to install [SideQuest](https://sidequestvr.com/setup-howto) for
+that.
+
+[SideQuest lets you sideload Android APK files to your Quest 2 because the
+Quest 2 is basically an Android tablet that you strap to your face!](conversation://Mara/happy)
+
+So I used SideQuest to install the ALVR client on my Quest 2, and then I opened
+up VRChat and was able to do everything I was able to do with the wired cable.
+It worked beautifully until it didn't. I started running into issues with the
+video stream just dying. The foveated encoding (tl;dr: attempting to hack the
+image quality based on how eyes work so you don't notice the artifacting as
+much) could only do so much and it just ended up not working. Even when I was
+only doing it for short amounts of time. There is a lot of WiFi noise in my
+apartment or something and it was really interfering with ALVR's stream
+encoding. The latency was also noticeable after a bit.
+
+However, when it worked it worked beautifully. I had to upgrade to the nightly
+build of ALVR in order to get game audio and the headset mic working, but once
+it all worked it was really convenient. I could walk around my apartment and
+I'd also walk around in-game.
+
+A friend told me that the best experience I could have with wireless VR using a
+Quest 2 would be to use [Virtual Desktop](https://www.vrdesktop.net). Apparently
+Virtual Desktop has a
+[patch that enables SteamVR support](https://sidequestvr.com/app/16), so I
+purchased Virtual Desktop on a whim and decided to give it a go.
+
+Virtual Desktop made ALVR look like a tech demo. All of the latency issues were
+solved instantly. Virtual Desktop also made it convenient for me to access my
+tower's monitors while in VR, and it has the best typing experience in VR that
+I've ever used.
+
+This brings the dependency graph up to this:
+
+<center>
+
+![](/static/blog/vrchat/total_graph.svg)
+
+</center>
+
+Now all that was left was to make the camera view look somewhat like it does
+when I'm using my work laptop's webcam to make video calls. I started out by taking a picture of my office from about the angle that my laptop sits at.
+I ended up with this image:
+
+<center>
+
+![](https://cdn.christine.website/file/christine-static/blog/2021-02-24-20-20-58.jpg)
+
+</center>
+
+Then with some clever use of the
+[Chroma key filter in VRChat](https://docs.vrchat.com/docs/vrchat-201812)
+I was able to get some basic compositing of my avatar onto the picture. I
+fiddled with the placement of things and then I was able to declare success
+with this image I posted to Twitter:
+
+<center>
+
+![](https://cdn.christine.website/file/christine-static/blog/Eu6iR6jXUAQH0iq.jpeg)
+
+</center>
+
+And it worked! I was able to make a call in Google Meet to myself and my
+avatar's lip movements synchronized somewhat with the words I was saying. I
+had waifu mode enabled!
+
+[The avatar being used there is based on a character from Xenoblade Chronicles
+2 named Pneuma.](conversation://Mara/hacker)
+
+However, this setup was really janky. I didn't actually get the proper angle
+for what my work laptop's camera would actually see. Everything was offset to
+the side and it was at way the wrong angle in general. I'm also not sure if I
+messed up the sizing of the background image in the OBS view, it looks kinda
+stretched on my end as I'm writing this post.
+
+So I decided that the best way to get the most accurate angle was to record a
+video loop using my work laptop's webcam. After some googling I found
+[webcamera.io](https://webcamera.io) which let me record some footage of my
+office from my work laptop's camera angle. I got down under the desk (so I was
+out of view of the camera) and then recorded a 45 second loop of my office
+doing nothing (however the flag was slightly moving in the breeze from the desk
+fan).
+
+I also found a VRChat world that claimed to be as optimized as you could
+possibly make a VRChat world. It was a blue cube about 30m by 30m. Checking
+with SteamVR it brought my frame times down to 3 milliseconds with the stream
+camera set up for OBS. It looks like this:
+
+<center>
+
+![Screenshot of the optimized world](https://cdn.christine.website/file/christine-static/blog/154306141_1368071216896631_2989259612329820447_o.jpg)
+
+</center>
+
+It's very minimal. You can make the walls go away if you want, which somehow makes it render faster on my RX5700. I'm not sure what's going on there.
+
+[I'd heckin' love to get a new GPU but until the Bitcoin prices go down we may
+be stuck with this setup for a while. An RTX 3070 would really be useful about
+now.](conversation://Mara/hacker)
+
+Anyways, with this minimal world incurring very little to no GPU load, I was
+free to do video calls all I wanted. I even did a call with the CEO of the
+company I work for with a setup like this. It was fun.
+
+Now I had everything set up. I can pop on the headset, load up the world, open
+OBS, VRChat, Virtual Desktop and get everything set up in about 5 minutes at
+worst. Then I can use the seeing your desktop side of Virtual Desktop to
+actually watch the meeting and be able to see screen sharing. They can hear me
+because Virtual Desktop pipes the headset microphone audio back to my tower,
+and the meeting audio comes over my headphones.
+
+Also at some point I needed to bring AutoHotKey into the mix, so I borrowed
+this AutoHotKey script from [SuperUser](https://superuser.com/a/429845) to
+resize the VRChat window so that it would fit perfectly into the OBS view:
+
+```ahk
+#=:: ; [Win]+[=]
+ WinGet, window, ID, A
+ InputBox, width, Resize, Width:, , 140, 130
+ InputBox, height, Resize, Height:, , 140, 130
+ WinMove, ahk_id %window%, , , , width, height
+ return
+```
+
+Making the VRChat window smaller also helped with the frame times, because it
+needed to render less detail per frame. This helped push the framerate
+comfortably above 72 FPS in my VR view.
+
+That is how I get a 3d avatar to show up instead of pictures of the meat golem
+I am cursed inside of for work meetings. I will also use this for streaming
+coding in the future, so you can all witness the power of a VTube coding stream
+where I write Rust or something. \ No newline at end of file
diff --git a/static/blog/vrchat/alvr_graph.svg b/static/blog/vrchat/alvr_graph.svg
new file mode 100644
index 0000000..1da23cd
--- /dev/null
+++ b/static/blog/vrchat/alvr_graph.svg
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.40.1 (20161225.0304)
+ -->
+<!-- Title: G Pages: 1 -->
+<svg width="661pt" height="308pt"
+ viewBox="0.00 0.00 661.01 308.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 304)">
+<title>G</title>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-304 657.0118,-304 657.0118,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_0</title>
+<polygon fill="#d3d3d3" stroke="#d3d3d3" points="150.8243,-8 150.8243,-107 374.9943,-107 374.9943,-8 150.8243,-8"/>
+<text text-anchor="middle" x="262.9093" y="-90.4" font-family="Times,serif" font-size="14.00" fill="#000000">Quest</text>
+</g>
+<g id="clust2" class="cluster">
+<title>cluster_1</title>
+<polygon fill="#d3d3d3" stroke="#d3d3d3" points="8,-115 8,-292 645.0118,-292 645.0118,-115 8,-115"/>
+<text text-anchor="middle" x="326.5059" y="-275.4" font-family="Times,serif" font-size="14.00" fill="#000000">PC</text>
+</g>
+<!-- AVA -->
+<g id="node1" class="node">
+<title>AVA</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="328.7382" cy="-45" rx="38.0139" ry="29.3315"/>
+<text text-anchor="middle" x="328.7382" y="-49.2" font-family="Times,serif" font-size="14.00" fill="#000000">ALVR</text>
+<text text-anchor="middle" x="328.7382" y="-32.4" font-family="Times,serif" font-size="14.00" fill="#000000">Quest</text>
+</g>
+<!-- AVR -->
+<g id="node3" class="node">
+<title>AVR</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="328.7382" cy="-164" rx="42.2164" ry="29.3315"/>
+<text text-anchor="middle" x="328.7382" y="-168.2" font-family="Times,serif" font-size="14.00" fill="#000000">ALVR</text>
+<text text-anchor="middle" x="328.7382" y="-151.4" font-family="Times,serif" font-size="14.00" fill="#000000">desktop</text>
+</g>
+<!-- AVA&#45;&gt;AVR -->
+<g id="edge7" class="edge">
+<title>AVA&#45;&gt;AVR</title>
+<path fill="none" stroke="#000000" d="M328.7382,-74.75C328.7382,-91.2066 328.7382,-107.6631 328.7382,-124.1197"/>
+<polygon fill="#000000" stroke="#000000" points="325.2383,-124.3662 328.7382,-134.3662 332.2383,-124.3662 325.2383,-124.3662"/>
+</g>
+<!-- A -->
+<g id="node2" class="node">
+<title>A</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="202.0445" cy="-50" rx="43.4415" ry="18"/>
+<text text-anchor="middle" x="202.0445" y="-45.8" font-family="Times,serif" font-size="14.00" fill="#000000">Android</text>
+</g>
+<!-- A&#45;&gt;AVA -->
+<g id="edge1" class="edge">
+<title>A&#45;&gt;AVA</title>
+<path fill="none" stroke="#000000" d="M245.2909,-48.2933C256.4871,-47.8514 268.6178,-47.3727 280.071,-46.9207"/>
+<polygon fill="#000000" stroke="#000000" points="280.4983,-50.4066 290.3525,-46.5149 280.2222,-43.4121 280.4983,-50.4066"/>
+</g>
+<!-- AVR&#45;&gt;AVA -->
+<g id="edge6" class="edge">
+<title>AVR&#45;&gt;AVA</title>
+<path fill="none" stroke="#000000" d="M328.7382,-134.3662C328.7382,-117.9097 328.7382,-101.4531 328.7382,-84.9965"/>
+<polygon fill="#000000" stroke="#000000" points="332.2383,-84.75 328.7382,-74.75 325.2383,-84.75 332.2383,-84.75"/>
+</g>
+<!-- VAC -->
+<g id="node11" class="node">
+<title>VAC</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="463.4742" cy="-164" rx="39.2066" ry="41.0911"/>
+<text text-anchor="middle" x="463.4742" y="-176.6" font-family="Times,serif" font-size="14.00" fill="#000000">Virtual</text>
+<text text-anchor="middle" x="463.4742" y="-159.8" font-family="Times,serif" font-size="14.00" fill="#000000">Audio</text>
+<text text-anchor="middle" x="463.4742" y="-143" font-family="Times,serif" font-size="14.00" fill="#000000">Cable</text>
+</g>
+<!-- AVR&#45;&gt;VAC -->
+<g id="edge12" class="edge">
+<title>AVR&#45;&gt;VAC</title>
+<path fill="none" stroke="#000000" d="M370.9998,-164C384.6622,-164 399.9322,-164 414.0648,-164"/>
+<polygon fill="#000000" stroke="#000000" points="414.1153,-167.5001 424.1152,-164 414.1152,-160.5001 414.1153,-167.5001"/>
+</g>
+<!-- SVR -->
+<g id="node4" class="node">
+<title>SVR</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="202.0445" cy="-170" rx="48.6714" ry="18"/>
+<text text-anchor="middle" x="202.0445" y="-165.8" font-family="Times,serif" font-size="14.00" fill="#000000">SteamVR</text>
+</g>
+<!-- SVR&#45;&gt;AVR -->
+<g id="edge5" class="edge">
+<title>SVR&#45;&gt;AVR</title>
+<path fill="none" stroke="#000000" d="M250.3034,-167.7145C258.7549,-167.3143 267.5869,-166.896 276.1488,-166.4905"/>
+<polygon fill="#000000" stroke="#000000" points="276.5916,-169.9736 286.4148,-166.0044 276.2604,-162.9814 276.5916,-169.9736"/>
+</g>
+<!-- VRC -->
+<g id="node5" class="node">
+<title>VRC</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="66.7294" cy="-206" rx="42.8751" ry="18"/>
+<text text-anchor="middle" x="66.7294" y="-201.8" font-family="Times,serif" font-size="14.00" fill="#000000">VRChat</text>
+</g>
+<!-- VRC&#45;&gt;SVR -->
+<g id="edge4" class="edge">
+<title>VRC&#45;&gt;SVR</title>
+<path fill="none" stroke="#000000" d="M102.9914,-196.3527C118.1616,-192.3167 136.0376,-187.5609 152.3981,-183.2082"/>
+<polygon fill="#000000" stroke="#000000" points="153.5873,-186.5137 162.3513,-180.5602 151.7875,-179.749 153.5873,-186.5137"/>
+</g>
+<!-- DESK -->
+<g id="node10" class="node">
+<title>DESK</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="202.0445" cy="-229" rx="43.4396" ry="18"/>
+<text text-anchor="middle" x="202.0445" y="-224.8" font-family="Times,serif" font-size="14.00" fill="#000000">Desktop</text>
+</g>
+<!-- VRC&#45;&gt;DESK -->
+<g id="edge10" class="edge">
+<title>VRC&#45;&gt;DESK</title>
+<path fill="none" stroke="#000000" d="M106.5966,-212.7764C120.7849,-215.188 136.9469,-217.9351 151.8865,-220.4745"/>
+<polygon fill="#000000" stroke="#000000" points="151.5496,-223.9673 161.9947,-222.1926 152.7227,-217.0663 151.5496,-223.9673"/>
+</g>
+<!-- OBS -->
+<g id="node6" class="node">
+<title>OBS</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="328.7382" cy="-235" rx="29.6339" ry="18"/>
+<text text-anchor="middle" x="328.7382" y="-230.8" font-family="Times,serif" font-size="14.00" fill="#000000">OBS</text>
+</g>
+<!-- VIRC -->
+<g id="node7" class="node">
+<title>VIRC</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="463.4742" cy="-241" rx="56.7561" ry="18"/>
+<text text-anchor="middle" x="463.4742" y="-236.8" font-family="Times,serif" font-size="14.00" fill="#000000">VirtualCam</text>
+</g>
+<!-- OBS&#45;&gt;VIRC -->
+<g id="edge8" class="edge">
+<title>OBS&#45;&gt;VIRC</title>
+<path fill="none" stroke="#000000" d="M358.6405,-236.3316C370.0698,-236.8406 383.6086,-237.4435 397.0947,-238.044"/>
+<polygon fill="#000000" stroke="#000000" points="397.2155,-241.5528 407.3614,-238.5012 397.527,-234.5597 397.2155,-241.5528"/>
+</g>
+<!-- GM -->
+<g id="node8" class="node">
+<title>GM</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="596.5569" cy="-197" rx="40.4098" ry="29.3315"/>
+<text text-anchor="middle" x="596.5569" y="-201.2" font-family="Times,serif" font-size="14.00" fill="#000000">Google</text>
+<text text-anchor="middle" x="596.5569" y="-184.4" font-family="Times,serif" font-size="14.00" fill="#000000">Meet</text>
+</g>
+<!-- VIRC&#45;&gt;GM -->
+<g id="edge9" class="edge">
+<title>VIRC&#45;&gt;GM</title>
+<path fill="none" stroke="#000000" d="M503.043,-227.9177C517.7504,-223.0551 534.5739,-217.4929 549.8596,-212.4391"/>
+<polygon fill="#000000" stroke="#000000" points="551.2489,-215.6662 559.6447,-209.204 549.0515,-209.02 551.2489,-215.6662"/>
+</g>
+<!-- SQ -->
+<g id="node9" class="node">
+<title>SQ</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="66.7294" cy="-141" rx="50.9599" ry="18"/>
+<text text-anchor="middle" x="66.7294" y="-136.8" font-family="Times,serif" font-size="14.00" fill="#000000">SideQuest</text>
+</g>
+<!-- SQ&#45;&gt;AVA -->
+<g id="edge3" class="edge">
+<title>SQ&#45;&gt;AVA</title>
+<path fill="none" stroke="#000000" d="M103.9261,-128.7112C141.2245,-116.2351 200.2025,-96.0887 250.6301,-77 262.0869,-72.6632 274.3899,-67.7466 285.765,-63.0913"/>
+<polygon fill="#000000" stroke="#000000" points="287.15,-66.3061 295.0628,-59.2607 284.4835,-59.8339 287.15,-66.3061"/>
+</g>
+<!-- SQ&#45;&gt;A -->
+<g id="edge2" class="edge">
+<title>SQ&#45;&gt;A</title>
+<path fill="none" stroke="#000000" d="M90.6096,-124.9405C112.7736,-110.0351 145.9219,-87.7427 170.305,-71.3449"/>
+<polygon fill="#000000" stroke="#000000" points="172.5102,-74.0798 178.8551,-65.595 168.6038,-68.2712 172.5102,-74.0798"/>
+</g>
+<!-- DESK&#45;&gt;OBS -->
+<g id="edge11" class="edge">
+<title>DESK&#45;&gt;OBS</title>
+<path fill="none" stroke="#000000" d="M245.2909,-231.0481C259.3432,-231.7136 274.8677,-232.4488 288.6771,-233.1028"/>
+<polygon fill="#000000" stroke="#000000" points="288.6958,-236.6075 298.8502,-233.5846 289.027,-229.6153 288.6958,-236.6075"/>
+</g>
+<!-- VAC&#45;&gt;GM -->
+<g id="edge13" class="edge">
+<title>VAC&#45;&gt;GM</title>
+<path fill="none" stroke="#000000" d="M501.9672,-173.545C516.4509,-177.1364 533.1046,-181.266 548.3623,-185.0494"/>
+<polygon fill="#000000" stroke="#000000" points="547.6019,-188.4668 558.1504,-187.4765 549.2867,-181.6725 547.6019,-188.4668"/>
+</g>
+</g>
+</svg> \ No newline at end of file
diff --git a/static/blog/vrchat/simple_graph.svg b/static/blog/vrchat/simple_graph.svg
new file mode 100644
index 0000000..1f80347
--- /dev/null
+++ b/static/blog/vrchat/simple_graph.svg
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.40.1 (20161225.0304)
+ -->
+<!-- Title: G Pages: 1 -->
+<svg width="610pt" height="123pt"
+ viewBox="0.00 0.00 609.61 123.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 119)">
+<title>G</title>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-119 605.611,-119 605.611,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_1</title>
+<polygon fill="#d3d3d3" stroke="#d3d3d3" points="8,-8 8,-107 593.611,-107 593.611,-8 8,-8"/>
+<text text-anchor="middle" x="300.8055" y="-90.4" font-family="Times,serif" font-size="14.00" fill="#000000">PC</text>
+</g>
+<!-- VRC -->
+<g id="node1" class="node">
+<title>VRC</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="58.6871" cy="-45" rx="42.8751" ry="18"/>
+<text text-anchor="middle" x="58.6871" y="-40.8" font-family="Times,serif" font-size="14.00" fill="#000000">VRChat</text>
+</g>
+<!-- DESK -->
+<g id="node5" class="node">
+<title>DESK</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="180.5935" cy="-45" rx="43.4396" ry="18"/>
+<text text-anchor="middle" x="180.5935" y="-40.8" font-family="Times,serif" font-size="14.00" fill="#000000">Desktop</text>
+</g>
+<!-- VRC&#45;&gt;DESK -->
+<g id="edge3" class="edge">
+<title>VRC&#45;&gt;DESK</title>
+<path fill="none" stroke="#000000" d="M101.6668,-45C109.8542,-45 118.5168,-45 126.9873,-45"/>
+<polygon fill="#000000" stroke="#000000" points="127.1794,-48.5001 137.1794,-45 127.1793,-41.5001 127.1794,-48.5001"/>
+</g>
+<!-- OBS -->
+<g id="node2" class="node">
+<title>OBS</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="289.6291" cy="-45" rx="29.6339" ry="18"/>
+<text text-anchor="middle" x="289.6291" y="-40.8" font-family="Times,serif" font-size="14.00" fill="#000000">OBS</text>
+</g>
+<!-- VIRC -->
+<g id="node3" class="node">
+<title>VIRC</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="412.0734" cy="-45" rx="56.7561" ry="18"/>
+<text text-anchor="middle" x="412.0734" y="-40.8" font-family="Times,serif" font-size="14.00" fill="#000000">VirtualCam</text>
+</g>
+<!-- OBS&#45;&gt;VIRC -->
+<g id="edge1" class="edge">
+<title>OBS&#45;&gt;VIRC</title>
+<path fill="none" stroke="#000000" d="M319.5824,-45C327.4013,-45 336.1504,-45 345.0646,-45"/>
+<polygon fill="#000000" stroke="#000000" points="345.1584,-48.5001 355.1584,-45 345.1584,-41.5001 345.1584,-48.5001"/>
+</g>
+<!-- GM -->
+<g id="node4" class="node">
+<title>GM</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="545.1562" cy="-45" rx="40.4098" ry="29.3315"/>
+<text text-anchor="middle" x="545.1562" y="-49.2" font-family="Times,serif" font-size="14.00" fill="#000000">Google</text>
+<text text-anchor="middle" x="545.1562" y="-32.4" font-family="Times,serif" font-size="14.00" fill="#000000">Meet</text>
+</g>
+<!-- VIRC&#45;&gt;GM -->
+<g id="edge2" class="edge">
+<title>VIRC&#45;&gt;GM</title>
+<path fill="none" stroke="#000000" d="M468.8985,-45C477.4369,-45 486.1935,-45 494.5894,-45"/>
+<polygon fill="#000000" stroke="#000000" points="494.6128,-48.5001 504.6128,-45 494.6128,-41.5001 494.6128,-48.5001"/>
+</g>
+<!-- DESK&#45;&gt;OBS -->
+<g id="edge4" class="edge">
+<title>DESK&#45;&gt;OBS</title>
+<path fill="none" stroke="#000000" d="M224.0006,-45C232.4355,-45 241.241,-45 249.5623,-45"/>
+<polygon fill="#000000" stroke="#000000" points="249.7795,-48.5001 259.7795,-45 249.7795,-41.5001 249.7795,-48.5001"/>
+</g>
+</g>
+</svg> \ No newline at end of file
diff --git a/static/blog/vrchat/total_graph.svg b/static/blog/vrchat/total_graph.svg
new file mode 100644
index 0000000..dd2f813
--- /dev/null
+++ b/static/blog/vrchat/total_graph.svg
@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.40.1 (20161225.0304)
+ -->
+<!-- Title: G Pages: 1 -->
+<svg width="671pt" height="332pt"
+ viewBox="0.00 0.00 670.88 332.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 328)">
+<title>G</title>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-328 666.8816,-328 666.8816,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_0</title>
+<polygon fill="#d3d3d3" stroke="#d3d3d3" points="150.8243,-8 150.8243,-131 525.6487,-131 525.6487,-8 150.8243,-8"/>
+<text text-anchor="middle" x="338.2365" y="-114.4" font-family="Times,serif" font-size="14.00" fill="#000000">Quest</text>
+</g>
+<g id="clust2" class="cluster">
+<title>cluster_1</title>
+<polygon fill="#d3d3d3" stroke="#d3d3d3" points="8,-139 8,-316 654.8816,-316 654.8816,-139 8,-139"/>
+<text text-anchor="middle" x="331.4408" y="-299.4" font-family="Times,serif" font-size="14.00" fill="#000000">PC</text>
+</g>
+<!-- VD -->
+<g id="node1" class="node">
+<title>VD</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="473.3439" cy="-63" rx="44.1104" ry="29.3315"/>
+<text text-anchor="middle" x="473.3439" y="-67.2" font-family="Times,serif" font-size="14.00" fill="#000000">Virtual</text>
+<text text-anchor="middle" x="473.3439" y="-50.4" font-family="Times,serif" font-size="14.00" fill="#000000">Desktop</text>
+</g>
+<!-- VDS -->
+<g id="node4" class="node">
+<title>VDS</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="333.6731" cy="-188" rx="47.086" ry="41.0911"/>
+<text text-anchor="middle" x="333.6731" y="-200.6" font-family="Times,serif" font-size="14.00" fill="#000000">Virtual</text>
+<text text-anchor="middle" x="333.6731" y="-183.8" font-family="Times,serif" font-size="14.00" fill="#000000">Desktop</text>
+<text text-anchor="middle" x="333.6731" y="-167" font-family="Times,serif" font-size="14.00" fill="#000000">Streamer</text>
+</g>
+<!-- VD&#45;&gt;VDS -->
+<g id="edge4" class="edge">
+<title>VD&#45;&gt;VDS</title>
+<path fill="none" stroke="#000000" d="M450.736,-88.362C431.0544,-107.6122 401.9557,-134.0523 377.6943,-154.9249"/>
+<polygon fill="#000000" stroke="#000000" points="375.3377,-152.3348 370.0053,-161.4899 379.8831,-157.6583 375.3377,-152.3348"/>
+</g>
+<!-- VDM -->
+<g id="node2" class="node">
+<title>VDM</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="333.6731" cy="-57" rx="45.275" ry="41.0911"/>
+<text text-anchor="middle" x="333.6731" y="-69.6" font-family="Times,serif" font-size="14.00" fill="#000000">Virtual</text>
+<text text-anchor="middle" x="333.6731" y="-52.8" font-family="Times,serif" font-size="14.00" fill="#000000">Desktop</text>
+<text text-anchor="middle" x="333.6731" y="-36" font-family="Times,serif" font-size="14.00" fill="#000000">VR mod</text>
+</g>
+<!-- VDM&#45;&gt;VD -->
+<g id="edge3" class="edge">
+<title>VDM&#45;&gt;VD</title>
+<path fill="none" stroke="#000000" d="M379.0202,-58.948C391.5906,-59.488 405.3731,-60.0801 418.4262,-60.6408"/>
+<polygon fill="#000000" stroke="#000000" points="418.5601,-64.1497 428.7011,-61.0822 418.8606,-57.1562 418.5601,-64.1497"/>
+</g>
+<!-- A -->
+<g id="node3" class="node">
+<title>A</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="202.0445" cy="-68" rx="43.4415" ry="18"/>
+<text text-anchor="middle" x="202.0445" y="-63.8" font-family="Times,serif" font-size="14.00" fill="#000000">Android</text>
+</g>
+<!-- A&#45;&gt;VDM -->
+<g id="edge2" class="edge">
+<title>A&#45;&gt;VDM</title>
+<path fill="none" stroke="#000000" d="M244.7805,-64.4286C255.4706,-63.5353 267.0909,-62.5642 278.2968,-61.6277"/>
+<polygon fill="#000000" stroke="#000000" points="278.7592,-65.1014 288.433,-60.7806 278.1762,-58.1257 278.7592,-65.1014"/>
+</g>
+<!-- VDS&#45;&gt;VD -->
+<g id="edge5" class="edge">
+<title>VDS&#45;&gt;VD</title>
+<path fill="none" stroke="#000000" d="M363.2937,-155.8547C384.2099,-135.824 412.4271,-110.4172 435.0521,-91.1733"/>
+<polygon fill="#000000" stroke="#000000" points="437.4099,-93.7634 442.8005,-84.6424 432.8986,-88.411 437.4099,-93.7634"/>
+</g>
+<!-- GM -->
+<g id="node9" class="node">
+<title>GM</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="606.4267" cy="-216" rx="40.4098" ry="29.3315"/>
+<text text-anchor="middle" x="606.4267" y="-220.2" font-family="Times,serif" font-size="14.00" fill="#000000">Google</text>
+<text text-anchor="middle" x="606.4267" y="-203.4" font-family="Times,serif" font-size="14.00" fill="#000000">Meet</text>
+</g>
+<!-- VDS&#45;&gt;GM -->
+<g id="edge6" class="edge">
+<title>VDS&#45;&gt;GM</title>
+<path fill="none" stroke="#000000" d="M380.5611,-192.8134C429.5797,-197.8455 506.2147,-205.7126 556.3811,-210.8625"/>
+<polygon fill="#000000" stroke="#000000" points="556.0498,-214.3468 566.355,-211.8864 556.7647,-207.3834 556.0498,-214.3468"/>
+</g>
+<!-- SVR -->
+<g id="node5" class="node">
+<title>SVR</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="202.0445" cy="-188" rx="48.6714" ry="18"/>
+<text text-anchor="middle" x="202.0445" y="-183.8" font-family="Times,serif" font-size="14.00" fill="#000000">SteamVR</text>
+</g>
+<!-- SVR&#45;&gt;VDS -->
+<g id="edge10" class="edge">
+<title>SVR&#45;&gt;VDS</title>
+<path fill="none" stroke="#000000" d="M250.6841,-188C259.0146,-188 267.7397,-188 276.273,-188"/>
+<polygon fill="#000000" stroke="#000000" points="276.5472,-191.5001 286.5471,-188 276.5471,-184.5001 276.5472,-191.5001"/>
+</g>
+<!-- VRC -->
+<g id="node6" class="node">
+<title>VRC</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="66.7294" cy="-230" rx="42.8751" ry="18"/>
+<text text-anchor="middle" x="66.7294" y="-225.8" font-family="Times,serif" font-size="14.00" fill="#000000">VRChat</text>
+</g>
+<!-- VRC&#45;&gt;SVR -->
+<g id="edge7" class="edge">
+<title>VRC&#45;&gt;SVR</title>
+<path fill="none" stroke="#000000" d="M101.2253,-219.293C117.6099,-214.2074 137.4417,-208.0518 155.1663,-202.5504"/>
+<polygon fill="#000000" stroke="#000000" points="156.2483,-205.8793 164.7613,-199.5722 154.1732,-199.194 156.2483,-205.8793"/>
+</g>
+<!-- DESK -->
+<g id="node11" class="node">
+<title>DESK</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="202.0445" cy="-242" rx="43.4396" ry="18"/>
+<text text-anchor="middle" x="202.0445" y="-237.8" font-family="Times,serif" font-size="14.00" fill="#000000">Desktop</text>
+</g>
+<!-- VRC&#45;&gt;DESK -->
+<g id="edge12" class="edge">
+<title>VRC&#45;&gt;DESK</title>
+<path fill="none" stroke="#000000" d="M108.8022,-233.7311C121.6615,-234.8715 135.9668,-236.1401 149.4342,-237.3344"/>
+<polygon fill="#000000" stroke="#000000" points="149.2616,-240.8328 159.5317,-238.2299 149.88,-233.8601 149.2616,-240.8328"/>
+</g>
+<!-- OBS -->
+<g id="node7" class="node">
+<title>OBS</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="333.6731" cy="-265" rx="29.6339" ry="18"/>
+<text text-anchor="middle" x="333.6731" y="-260.8" font-family="Times,serif" font-size="14.00" fill="#000000">OBS</text>
+</g>
+<!-- VIRC -->
+<g id="node8" class="node">
+<title>VIRC</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="473.3439" cy="-255" rx="56.7561" ry="18"/>
+<text text-anchor="middle" x="473.3439" y="-250.8" font-family="Times,serif" font-size="14.00" fill="#000000">VirtualCam</text>
+</g>
+<!-- OBS&#45;&gt;VIRC -->
+<g id="edge8" class="edge">
+<title>OBS&#45;&gt;VIRC</title>
+<path fill="none" stroke="#000000" d="M363.2937,-262.8793C376.2998,-261.9481 392.129,-260.8147 407.6812,-259.7012"/>
+<polygon fill="#000000" stroke="#000000" points="408.1043,-263.18 417.8288,-258.9747 407.6044,-256.1979 408.1043,-263.18"/>
+</g>
+<!-- VIRC&#45;&gt;GM -->
+<g id="edge9" class="edge">
+<title>VIRC&#45;&gt;GM</title>
+<path fill="none" stroke="#000000" d="M515.087,-242.7672C529.0844,-238.6652 544.7902,-234.0626 559.1786,-229.8461"/>
+<polygon fill="#000000" stroke="#000000" points="560.28,-233.1706 568.8921,-226.9995 558.3114,-226.4531 560.28,-233.1706"/>
+</g>
+<!-- SQ -->
+<g id="node10" class="node">
+<title>SQ</title>
+<ellipse fill="#ffffff" stroke="#ffffff" cx="66.7294" cy="-165" rx="50.9599" ry="18"/>
+<text text-anchor="middle" x="66.7294" y="-160.8" font-family="Times,serif" font-size="14.00" fill="#000000">SideQuest</text>
+</g>
+<!-- SQ&#45;&gt;A -->
+<g id="edge1" class="edge">
+<title>SQ&#45;&gt;A</title>
+<path fill="none" stroke="#000000" d="M89.3781,-148.7644C111.9913,-132.5542 146.8811,-107.5436 171.8411,-89.6512"/>
+<polygon fill="#000000" stroke="#000000" points="174.1309,-92.3162 180.2192,-83.6453 170.0526,-86.6269 174.1309,-92.3162"/>
+</g>
+<!-- DESK&#45;&gt;VDS -->
+<g id="edge11" class="edge">
+<title>DESK&#45;&gt;VDS</title>
+<path fill="none" stroke="#000000" d="M232.9056,-229.3394C247.3882,-223.3979 265.0521,-216.1514 281.5218,-209.3948"/>
+<polygon fill="#000000" stroke="#000000" points="283.1245,-212.5204 291.0478,-205.4868 280.4677,-206.0442 283.1245,-212.5204"/>
+</g>
+<!-- DESK&#45;&gt;OBS -->
+<g id="edge13" class="edge">
+<title>DESK&#45;&gt;OBS</title>
+<path fill="none" stroke="#000000" d="M242.2526,-249.0257C259.0321,-251.9577 278.4424,-255.3493 295.05,-258.2512"/>
+<polygon fill="#000000" stroke="#000000" points="294.5265,-261.7127 304.9797,-259.9863 295.7315,-254.8172 294.5265,-261.7127"/>
+</g>
+</g>
+</svg> \ No newline at end of file