Skip to content

Commit

Permalink
added VR support (including single-pass stereo support) for 5.5+
Browse files Browse the repository at this point in the history
closes #6
  • Loading branch information
fuglsang committed Mar 31, 2017
1 parent 518fa30 commit 4795aa0
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 120 deletions.
23 changes: 23 additions & 0 deletions Assets/Scripts/EffectBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@

public abstract class EffectBase : MonoBehaviour
{
public void EnsureArray<T>(ref T[] array, int size, T initialValue = default(T))
{
if (array == null || array.Length != size)
{
array = new T[size];
for (int i = 0; i != size; i++)
array[i] = initialValue;
}
}

public void EnsureArray<T>(ref T[,] array, int size0, int size1, T defaultValue = default(T))
{
if (array == null || array.Length != size0 * size1)
{
array = new T[size0, size1];
for (int i = 0; i != size0; i++)
{
for (int j = 0; j != size1; j++)
array[i, j] = defaultValue;
}
}
}

public void EnsureMaterial(ref Material material, Shader shader)
{
if (shader != null)
Expand Down
36 changes: 33 additions & 3 deletions Assets/Scripts/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// This file is subject to the MIT License as seen in the root of this folder structure (LICENSE.TXT)
// AUTHOR: Lasse Jon Fuglsang Pedersen <lasse@playdead.com>

#if UNITY_5_5_OR_NEWER
#define SUPPORT_STEREO
#endif

using UnityEngine;

public static class Vector2Extension
Expand All @@ -20,7 +24,7 @@ public static float SignedAngle(this Vector2 v1, Vector2 v2)

float theta = Mathf.Acos(dot);
float sgn = Vector2.Dot(new Vector2(-n1.y, n1.x), n2);
if (sgn >= 0f)
if (sgn >= 0.0f)
return theta;
else
return -theta;
Expand Down Expand Up @@ -97,7 +101,7 @@ public static class CameraExtension
{
public static Vector4 GetProjectionExtents(this Camera camera)
{
return GetProjectionExtents(camera, 0f, 0f);
return GetProjectionExtents(camera, 0.0f, 0.0f);
}

public static Vector4 GetProjectionExtents(this Camera camera, float texelOffsetX, float texelOffsetY)
Expand All @@ -115,9 +119,35 @@ public static Vector4 GetProjectionExtents(this Camera camera, float texelOffset
return new Vector4(oneExtentX, oneExtentY, oneJitterX, oneJitterY);// xy = frustum extents at distance 1, zw = jitter at distance 1
}

#if SUPPORT_STEREO
public static Vector4 GetProjectionExtents(this Camera camera, Camera.StereoscopicEye eye)
{
return GetProjectionExtents(camera, eye, 0.0f, 0.0f);
}

public static Vector4 GetProjectionExtents(this Camera camera, Camera.StereoscopicEye eye, float texelOffsetX, float texelOffsetY)
{
Matrix4x4 inv = Matrix4x4.Inverse(camera.GetStereoProjectionMatrix(eye));
Vector3 ray00 = inv.MultiplyPoint3x4(new Vector3(-1.0f, -1.0f, 0.95f));
Vector3 ray11 = inv.MultiplyPoint3x4(new Vector3(1.0f, 1.0f, 0.95f));

ray00 /= -ray00.z;
ray11 /= -ray11.z;

float oneExtentX = 0.5f * (ray11.x - ray00.x);
float oneExtentY = 0.5f * (ray11.y - ray00.y);
float texelSizeX = oneExtentX / (0.5f * camera.pixelWidth);
float texelSizeY = oneExtentY / (0.5f * camera.pixelHeight);
float oneJitterX = 0.5f * (ray11.x + ray00.x) + texelSizeX * texelOffsetX;
float oneJitterY = 0.5f * (ray11.y + ray00.y) + texelSizeY * texelOffsetY;

return new Vector4(oneExtentX, oneExtentY, oneJitterX, oneJitterY);// xy = frustum extents at distance 1, zw = jitter at distance 1
}
#endif

public static Matrix4x4 GetProjectionMatrix(this Camera camera)
{
return GetProjectionMatrix(camera, 0f, 0f);
return GetProjectionMatrix(camera, 0.0f, 0.0f);
}

public static Matrix4x4 GetProjectionMatrix(this Camera camera, float texelOffsetX, float texelOffsetY)
Expand Down
52 changes: 32 additions & 20 deletions Assets/Scripts/FrustumJitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// This file is subject to the MIT License as seen in the root of this folder structure (LICENSE.TXT)
// AUTHOR: Lasse Jon Fuglsang Pedersen <lasse@playdead.com>

using System;
#if UNITY_5_5_OR_NEWER
#define SUPPORT_STEREO
#endif

using UnityEngine;

[ExecuteInEditMode]
Expand Down Expand Up @@ -110,8 +113,8 @@ private static void TransformPattern(float[] seq, float theta, float scale)
// http://en.wikipedia.org/wiki/Halton_sequence
private static float HaltonSeq(int prime, int index = 1/* NOT! zero-based */)
{
float r = 0f;
float f = 1f;
float r = 0.0f;
float f = 1.0f;
int i = index;
while (i > 0)
{
Expand Down Expand Up @@ -238,7 +241,7 @@ public Vector2 Sample(Pattern pattern, int index)
private Vector3 focalMotionDir = Vector3.right;

public Pattern pattern = Pattern.Halton_2_3_X16;
public float patternScale = 1f;
public float patternScale = 1.0f;

public Vector4 activeSample = Vector4.zero;// xy = current sample, zw = previous sample
public int activeIndex = -2;
Expand Down Expand Up @@ -271,13 +274,13 @@ void OnPreCull()

Vector3 oldPoint = (_camera.worldToCameraMatrix * oldWorld);
Vector3 newPoint = (_camera.worldToCameraMatrix * newWorld);
Vector3 newDelta = (newPoint - oldPoint).WithZ(0f);
Vector3 newDelta = (newPoint - oldPoint).WithZ(0.0f);

var mag = newDelta.magnitude;
if (mag != 0f)
if (mag != 0.0f)
{
var dir = newDelta / mag;// yes, apparently this is necessary instead of newDelta.normalized... because facepalm
if (dir.sqrMagnitude != 0f)
if (dir.sqrMagnitude != 0.0f)
{
focalMotionPos = newWorld;
focalMotionDir = Vector3.Slerp(focalMotionDir, dir, 0.2f);
Expand All @@ -287,25 +290,34 @@ void OnPreCull()
}

// update jitter
if (activeIndex == -2)
#if SUPPORT_STEREO
if (_camera.stereoEnabled)
{
activeSample = Vector4.zero;
activeIndex += 1;

_camera.projectionMatrix = _camera.GetProjectionMatrix();
Clear();
}
else
#endif
{
activeIndex += 1;
activeIndex %= AccessLength(pattern);
if (activeIndex == -2)
{
activeSample = Vector4.zero;
activeIndex += 1;

Vector2 sample = Sample(pattern, activeIndex);
activeSample.z = activeSample.x;
activeSample.w = activeSample.y;
activeSample.x = sample.x;
activeSample.y = sample.y;
_camera.projectionMatrix = _camera.GetProjectionMatrix();
}
else
{
activeIndex += 1;
activeIndex %= AccessLength(pattern);

Vector2 sample = Sample(pattern, activeIndex);
activeSample.z = activeSample.x;
activeSample.w = activeSample.y;
activeSample.x = sample.x;
activeSample.y = sample.y;

_camera.projectionMatrix = _camera.GetProjectionMatrix(sample.x, sample.y);
_camera.projectionMatrix = _camera.GetProjectionMatrix(sample.x, sample.y);
}
}
}

Expand Down
87 changes: 53 additions & 34 deletions Assets/Scripts/TemporalReprojection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// This file is subject to the MIT License as seen in the root of this folder structure (LICENSE.TXT)
// AUTHOR: Lasse Jon Fuglsang Pedersen <lasse@playdead.com>

#if UNITY_5_5_OR_NEWER
#define SUPPORT_STEREO
#endif

using UnityEngine;

[ExecuteInEditMode]
Expand All @@ -17,8 +21,8 @@ public class TemporalReprojection : EffectBase

public Shader reprojectionShader;
private Material reprojectionMaterial;
private RenderTexture[] reprojectionBuffer;
private int reprojectionIndex = 0;
private RenderTexture[,] reprojectionBuffer;
private int[] reprojectionIndex = new int[2] { -1, -1 };

public enum Neighborhood
{
Expand All @@ -37,10 +41,10 @@ public enum Neighborhood
public bool useMotionBlur = true;
public bool useOptimizations = true;

[Range(0f, 1f)] public float feedbackMin = 0.88f;
[Range(0f, 1f)] public float feedbackMax = 0.97f;
[Range(0.0f, 1.0f)] public float feedbackMin = 0.88f;
[Range(0.0f, 1.0f)] public float feedbackMax = 0.97f;

public float motionBlurStrength = 1f;
public float motionBlurStrength = 1.0f;
public bool motionBlurIgnoreFF = false;

void Reset()
Expand All @@ -52,7 +56,9 @@ void Reset()

void Clear()
{
reprojectionIndex = -1;
EnsureArray(ref reprojectionIndex, 2);
reprojectionIndex[0] = -1;
reprojectionIndex[1] = -1;
}

void Awake()
Expand All @@ -63,25 +69,40 @@ void Awake()

void Resolve(RenderTexture source, RenderTexture destination)
{
EnsureMaterial(ref reprojectionMaterial, reprojectionShader);
EnsureArray(ref reprojectionBuffer, 2, 2);
EnsureArray(ref reprojectionIndex, 2, initialValue: -1);

EnsureMaterial(ref reprojectionMaterial, reprojectionShader);
if (reprojectionMaterial == null)
{
Graphics.Blit(source, destination);
return;
}

if (reprojectionBuffer == null || reprojectionBuffer.Length != 2)
reprojectionBuffer = new RenderTexture[2];

#if SUPPORT_STEREO
int eyeIndex = (_camera.stereoActiveEye == Camera.MonoOrStereoscopicEye.Right) ? 1 : 0;
#else
int eyeIndex = 0;
#endif
int bufferW = source.width;
int bufferH = source.height;

if (EnsureRenderTarget(ref reprojectionBuffer[0], bufferW, bufferH, RenderTextureFormat.ARGB32, FilterMode.Bilinear, antiAliasing: source.antiAliasing))
if (EnsureRenderTarget(ref reprojectionBuffer[eyeIndex, 0], bufferW, bufferH, RenderTextureFormat.ARGB32, FilterMode.Bilinear, antiAliasing: source.antiAliasing))
Clear();
if (EnsureRenderTarget(ref reprojectionBuffer[1], bufferW, bufferH, RenderTextureFormat.ARGB32, FilterMode.Bilinear, antiAliasing: source.antiAliasing))
if (EnsureRenderTarget(ref reprojectionBuffer[eyeIndex, 1], bufferW, bufferH, RenderTextureFormat.ARGB32, FilterMode.Bilinear, antiAliasing: source.antiAliasing))
Clear();

#if SUPPORT_STEREO
bool stereoEnabled = _camera.stereoEnabled;
#else
bool stereoEnabled = false;
#endif
#if UNITY_EDITOR
bool allowMotionBlur = !stereoEnabled && Application.isPlaying;
#else
bool allowMotionBlur = !stereoEnabled;
#endif

EnsureKeyword(reprojectionMaterial, "CAMERA_PERSPECTIVE", !_camera.orthographic);
EnsureKeyword(reprojectionMaterial, "CAMERA_ORTHOGRAPHIC", _camera.orthographic);

Expand All @@ -94,23 +115,19 @@ void Resolve(RenderTexture source, RenderTexture destination)
EnsureKeyword(reprojectionMaterial, "USE_YCOCG", useYCoCg);
EnsureKeyword(reprojectionMaterial, "USE_CLIPPING", useClipping);
EnsureKeyword(reprojectionMaterial, "USE_DILATION", useDilation);
#if UNITY_EDITOR
EnsureKeyword(reprojectionMaterial, "USE_MOTION_BLUR", Application.isPlaying ? useMotionBlur : false);
#else
EnsureKeyword(reprojectionMaterial, "USE_MOTION_BLUR", useMotionBlur);
#endif
EnsureKeyword(reprojectionMaterial, "USE_MOTION_BLUR_NEIGHBORMAX", _velocityBuffer.velocityNeighborMax != null);
EnsureKeyword(reprojectionMaterial, "USE_MOTION_BLUR", useMotionBlur && allowMotionBlur);
EnsureKeyword(reprojectionMaterial, "USE_MOTION_BLUR_NEIGHBORMAX", _velocityBuffer.activeVelocityNeighborMax != null);
EnsureKeyword(reprojectionMaterial, "USE_OPTIMIZATIONS", useOptimizations);

if (reprojectionIndex == -1)// bootstrap
if (reprojectionIndex[eyeIndex] == -1)// bootstrap
{
reprojectionIndex = 0;
reprojectionBuffer[reprojectionIndex].DiscardContents();
Graphics.Blit(source, reprojectionBuffer[reprojectionIndex]);
reprojectionIndex[eyeIndex] = 0;
reprojectionBuffer[eyeIndex, reprojectionIndex[eyeIndex]].DiscardContents();
Graphics.Blit(source, reprojectionBuffer[eyeIndex, reprojectionIndex[eyeIndex]]);
}

int indexRead = reprojectionIndex;
int indexWrite = (reprojectionIndex + 1) % 2;
int indexRead = reprojectionIndex[eyeIndex];
int indexWrite = (reprojectionIndex[eyeIndex] + 1) % 2;

Vector4 jitterUV = _frustumJitter.activeSample;
jitterUV.x /= source.width;
Expand All @@ -119,32 +136,32 @@ void Resolve(RenderTexture source, RenderTexture destination)
jitterUV.w /= source.height;

reprojectionMaterial.SetVector("_JitterUV", jitterUV);
reprojectionMaterial.SetTexture("_VelocityBuffer", _velocityBuffer.velocityBuffer);
reprojectionMaterial.SetTexture("_VelocityNeighborMax", _velocityBuffer.velocityNeighborMax);
reprojectionMaterial.SetTexture("_VelocityBuffer", _velocityBuffer.activeVelocityBuffer);
reprojectionMaterial.SetTexture("_VelocityNeighborMax", _velocityBuffer.activeVelocityNeighborMax);
reprojectionMaterial.SetTexture("_MainTex", source);
reprojectionMaterial.SetTexture("_PrevTex", reprojectionBuffer[indexRead]);
reprojectionMaterial.SetTexture("_PrevTex", reprojectionBuffer[eyeIndex, indexRead]);
reprojectionMaterial.SetFloat("_FeedbackMin", feedbackMin);
reprojectionMaterial.SetFloat("_FeedbackMax", feedbackMax);
reprojectionMaterial.SetFloat("_MotionScale", motionBlurStrength * (motionBlurIgnoreFF ? Mathf.Min(1f, 1f / _velocityBuffer.timeScale) : 1f));
reprojectionMaterial.SetFloat("_MotionScale", motionBlurStrength * (motionBlurIgnoreFF ? Mathf.Min(1.0f, 1.0f / _velocityBuffer.timeScale) : 1.0f));

// reproject frame n-1 into output + history buffer
{
mrt[0] = reprojectionBuffer[indexWrite].colorBuffer;
mrt[0] = reprojectionBuffer[eyeIndex, indexWrite].colorBuffer;
mrt[1] = destination.colorBuffer;

Graphics.SetRenderTarget(mrt, source.depthBuffer);
reprojectionMaterial.SetPass(0);
reprojectionBuffer[indexWrite].DiscardContents();
reprojectionBuffer[eyeIndex, indexWrite].DiscardContents();

DrawFullscreenQuad();

reprojectionIndex = indexWrite;
reprojectionIndex[eyeIndex] = indexWrite;
}
}

void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (destination != null)// resolve without additional blit when not end of chain
if (destination != null && source.antiAliasing == destination.antiAliasing)// resolve without additional blit when not end of chain
{
Resolve(source, destination);
}
Expand All @@ -163,8 +180,10 @@ void OnApplicationQuit()
{
if (reprojectionBuffer != null)
{
ReleaseRenderTarget(ref reprojectionBuffer[0]);
ReleaseRenderTarget(ref reprojectionBuffer[1]);
ReleaseRenderTarget(ref reprojectionBuffer[0, 0]);
ReleaseRenderTarget(ref reprojectionBuffer[0, 1]);
ReleaseRenderTarget(ref reprojectionBuffer[1, 0]);
ReleaseRenderTarget(ref reprojectionBuffer[1, 1]);
}
}
}
Loading

0 comments on commit 4795aa0

Please sign in to comment.