Stereo - Anagylph Rendering HELP!!! ;)

OK, I’m at wits end. This is the first time I’ve messed with the Shader Lab coding and I think I’ve gone as far as I can.

We’re trying to do anagylphic rendering, and are close, but no cigar.

I’m using renderTexture to get the basic Right/Left view and coloring them with a shader to get the following:

Red Image

Blue Image

Here’s what it looks like in Photoshop using a Screen Overlay. This image POPS out of the screen and looks great!!!

And here’s the two combined in Unity, very washed out and does not pop at all.

Here’s the shader code being used;

Shader "Tutorial/Basic" {
    Properties {
        _RColor ("Red Color", Color) = (1,0,0,0)
        _LColor ("Blue Color", Color) = (0,1,1,0)
        _TexR ("Right Eye (RGB)", 2D) = "white" {}
        _TexL ("Left Eye (RGB)", 2D) = "white" {}
    }
    /*
       SubShader 
   { 
      Pass 
      { 
         SetTexture [_TexL]   { combine texture } 
         SetTexture [_TexR]   { combine texture } 
      } 
   }
    
*/
 SubShader 
   { 
      //cull off 
      Lighting On 
     // ZTest Always 
      //ZWrite Off 
      Blend One One 
             
             
             Pass 
      { 
      	Lighting off
         SetTexture [_TexR] 
         { 
            constantColor [_RColor] 
            combine constant * texture 
         } 
      } 
       
       
      Pass 
      { 
      	Lighting off
         SetTexture [_TexL] 
         { 
            constantColor [_LColor] 
            combine constant * texture 
         } 
      } 
       

   }
}

Any help would be great!!!

Thanks!

TheLorax

I’ve no experience implementing such an effect in Unity, and no idea what else you’re doing to achieve on this effect, but the issue you are seeing might be due to the fact that you’re shader blending is multiplying, when a Screen blend is desired.

Typically a Screen blend is the result of 2 inverted / negative images being multiplied and then inverted back*

Suggest trying to invert each “eye” texture, multiply the result (as the shader does currently via “Blend One One”) then invert it back (perhaps on the “final” Camera?).

If you can resolve such a blending, then you probably be closer to the desired effect (i’m no SL guru obviously).

*You can verify this by inverting the red blue layers in Photoshop, setting one of their blend modes to Multiply, then copying the combined result into a new layer and inverting it. The result will look exactly as your Screen blend image.

Any luck with this? It sounds really cool. :slight_smile:

I’ve been working on this over the last week in my spare time and I think I came up with a pretty solid solution. Big thanks to Daniel for setting me straight on the shader. Attach this script to your camera and you should be good to go. If you’re using image effects then this script obviously needs to be tweaked a bit. I’d also love to see any improvements people come up with to make this script a truly general anaglyph rendering solution.

public var eyeDistance = 0.03;
public var focalDistance = 10.0;

var anaglyphMat;

var leftEyeRT;    
var rightEyeRT;  

var leftEye;
var rightEye;


function Start () 
{
	leftEye = new GameObject ("leftEye", Camera);
	rightEye = new GameObject ("rightEye", Camera);
	
	leftEye.camera.CopyFrom (camera);
	rightEye.camera.CopyFrom (camera);
	
	leftEyeRT = new RenderTexture (Screen.width, Screen.height, 24);
	rightEyeRT = new RenderTexture (Screen.width, Screen.height, 24);
	
	anaglyphMat = new Material
	(	
		"Shader \"Hidden/Anaglyph\"" +
		"{" +
		"    Properties" + 
		"    {" +
		"        _Color (\"Main Color, Alpha\", Color) = (1,1,1,1)" +
		"        _LeftTex (\"Left (RGB)\", RECT) = \"white\" {}" +
		"        _RightTex (\"Right (RGB)\", RECT) = \"white\" {}" +
		"    }" +
		"    Category" +
		"    {" +
		"        ZWrite Off" +
		"        Lighting On" +
		"        Tags {Queue=Transparent}" +
		"        SubShader" + 
		"        {" +
		"            Pass" + 
		"            {" +
		"            	ColorMask R" +
		"	            Cull Off" +
		"	            Material" + 
		"	            {" +
		"	                Emission [_Color]" +
		"	            }" +
		"" +	            
		"           	SetTexture [_LeftTex]" +
		"            	{" +
		"	                Combine texture * primary, texture + primary" +
		"            	}" + 
		"        	}" +
		"" +        	
		"        	Pass" + 
		"            {" +
		"            	ColorMask GB" +
		"	            Cull Off" +
		"	            Material" +
		"	            {" +
		"	                Emission [_Color]" +
		"	            }" +
		"" +           
		"            	SetTexture [_RightTex]" +
		"            	{" +
		"	                Combine texture * primary, texture + primary" +
		"            	}" +
		"        	}" +
		"    	}" +
		" 	}" + 
		"}"
	);
	
	leftEye.camera.targetTexture = leftEyeRT;
	rightEye.camera.targetTexture = rightEyeRT;
		
	anaglyphMat.SetTexture ("_LeftTex", leftEyeRT);
	anaglyphMat.SetTexture ("_RightTex", rightEyeRT);
		
	leftEye.camera.depth = camera.depth -2;
	rightEye.camera.depth = camera.depth -1;
	
	leftEye.transform.position = transform.position + transform.TransformDirection(-eyeDistance, 0, 0);
	rightEye.transform.position = transform.position + transform.TransformDirection(eyeDistance, 0, 0);
	
	leftEye.transform.rotation = transform.rotation;
	rightEye.transform.rotation = transform.rotation;
	
	leftEye.transform.LookAt (transform.position + (transform.TransformDirection (Vector3.forward) * focalDistance)); 
	rightEye.transform.LookAt (transform.position + (transform.TransformDirection (Vector3.forward) * focalDistance)); 
	
	leftEye.transform.parent = transform; 
	rightEye.transform.parent = transform; 
	
	camera.cullingMask = 0;
	camera.backgroundColor = Color (0,0,0,0);
	camera.Render();
	camera.clearFlags = CameraClearFlags.Nothing;	
}

function OnPostRender ()
{		
	GL.PushMatrix ();
	GL.LoadOrtho ();
	
	anaglyphMat.SetPass (0);
	
	GL.Begin (GL.QUADS);
	
    GL.TexCoord2 (0, 0); GL.Vertex3 (0, 0, 1);
    GL.TexCoord2 (1, 0); GL.Vertex3 (1, 0, 1);
    GL.TexCoord2 (1, 1); GL.Vertex3 (1, 1, 1);
    GL.TexCoord2 (0, 1); GL.Vertex3 (0, 1, 1);
    
    GL.End ();
    GL.PopMatrix ();
    
    GL.PushMatrix ();
	GL.LoadOrtho ();
	
	anaglyphMat.SetPass (1);
	
	GL.Begin (GL.QUADS);
	
    GL.TexCoord2 (0, 0); GL.Vertex3 (0, 0, 1);
    GL.TexCoord2 (1, 0); GL.Vertex3 (1, 0, 1);
    GL.TexCoord2 (1, 1); GL.Vertex3 (1, 1, 1);
    GL.TexCoord2 (0, 1); GL.Vertex3 (0, 1, 1);
    
    GL.End (); 
	GL.PopMatrix ();	
}

Hey folks,

I thought this would be a neat thing to try out.

I seem to have some problems creating the render textures.

If I use the code above, nothing shows up in the Unity preview window or in the web player on the Mac. I looked at the “leftEye” and “rightEye” cameras in the inspector after they are created and they indicate that the target texture is missing.

I then manually created some RenderTextures and used those instead of creating them with the code:

leftEyeRT = new RenderTexture (Screen.width, Screen.height, 24);
rightEyeRT = new RenderTexture (Screen.width, Screen.height, 24);

This appeared to work correctly (worked in the Unity preview window and in the web player on the Mac). However, if I check it out on the web player on the PC, all I see is a black screen (if I use the code above, the web player on the PC shows the content without the anaglyphic effect).

So, do I need to do something in order for the code that creates the RenderTextures to work properly? Is there an issue with the PC web player that might cause a problem? I tried it on multiple Macs and multiple PCs and the results were consistent…

Thanks,
Jared

We used a version of this script for Paper Moon. I ran into problems with it on Windows. (probably the same ones?) I got some help from Shawn White of Flash Bang Studios and we came up with a solution.

Here’s an updated version! The script is a little disorganized right now - but it does contain the fix for the Windows problem.

(also includes persistent distance values and input handling so that you can change the effect using the keyboard while the project is running)

var anaglyphMat;

var leftEyeRT;   
var rightEyeRT; 

var leftEye;
var rightEye;

var enableKeys 				: boolean 	= true;

var downEyeDistance 		: KeyCode 	= KeyCode.O;
var upEyeDistance 			: KeyCode 	= KeyCode.P;
var downFocalDistance 		: KeyCode 	= KeyCode.K;
var upFocalDistance 		: KeyCode 	= KeyCode.L;

var zvalue					: float		= 0.0; // original: 1.0

class S3DV extends System.Object {
	static var eyeDistance = 0.035;
	static var focalDistance = 6.5;
};

function Start () {
   leftEye = new GameObject ("leftEye", Camera);
   rightEye = new GameObject ("rightEye", Camera);
   
   leftEye.camera.CopyFrom (camera);
   rightEye.camera.CopyFrom (camera);
   
   leftEyeRT = new RenderTexture (Screen.width, Screen.height, 24);
   rightEyeRT = new RenderTexture (Screen.width, Screen.height, 24);
   
   anaglyphMat = new Material
   (   
      "Shader \"Hidden/Anaglyph\"" +
      "{" +
      "    Properties" +
      "    {" +
      "        _Color (\"Main Color, Alpha\", Color) = (1,1,1,1)" +
      "        _LeftTex (\"Left (RGB)\", RECT) = \"white\" {}" +
      "        _RightTex (\"Right (RGB)\", RECT) = \"white\" {}" +
      "    }" +
      "    Category" +
      "    {" +
      "        ZWrite Off" +
      "        ZTest Always" +
      "        Lighting On" +
      "        Tags {Queue=Transparent}" +
      "        SubShader" +
      "        {" +
      "            Pass" +
      "            {" +
      "               ColorMask R" +
      "               Cull Off" +
      "               Material" +
      "               {" +
      "                   Emission [_Color]" +
      "               }" +
      "" +              
      "              SetTexture [_LeftTex]" +
      "               {" +
      "                   Combine texture * primary, texture + primary" +
      "               }" +
      "           }" +
      "" +           
      "           Pass" +
      "            {" +
      "               ColorMask GB" +
      "               Cull Off" +
      "               Material" +
      "               {" +
      "                   Emission [_Color]" +
      "               }" +
      "" +           
      "               SetTexture [_RightTex]" +
      "               {" +
      "                   Combine texture * primary, texture + primary" +
      "               }" +
      "           }" +
      "       }" +
      "    }" +
      "}"
   );
   
   leftEye.camera.targetTexture = leftEyeRT;
   rightEye.camera.targetTexture = rightEyeRT;
      
   anaglyphMat.SetTexture ("_LeftTex", leftEyeRT);
   anaglyphMat.SetTexture ("_RightTex", rightEyeRT);
      
   leftEye.camera.depth = camera.depth -2;
   rightEye.camera.depth = camera.depth -1;
   
   leftEye.transform.position = transform.position + transform.TransformDirection(-S3DV.eyeDistance, 0, 0);
   rightEye.transform.position = transform.position + transform.TransformDirection(S3DV.eyeDistance, 0, 0);
   
   leftEye.transform.rotation = transform.rotation;
   rightEye.transform.rotation = transform.rotation;
   
   leftEye.transform.LookAt (transform.position + (transform.TransformDirection (Vector3.forward) * S3DV.focalDistance));
   rightEye.transform.LookAt (transform.position + (transform.TransformDirection (Vector3.forward) * S3DV.focalDistance));
   
   leftEye.transform.parent = transform;
   rightEye.transform.parent = transform;
   
   camera.cullingMask = 0;
   camera.backgroundColor = Color (0,0,0,0);
   //camera.Render();
   camera.clearFlags = CameraClearFlags.Nothing;
   //camera.enabled = false;
}

function Stop () {
}

function UpdateView() {
   leftEye.camera.depth = camera.depth -2;
   rightEye.camera.depth = camera.depth -1;
   
   leftEye.transform.position = transform.position + transform.TransformDirection(-S3DV.eyeDistance, 0, 0);
   rightEye.transform.position = transform.position + transform.TransformDirection(S3DV.eyeDistance, 0, 0);
   
   leftEye.transform.rotation = transform.rotation;
   rightEye.transform.rotation = transform.rotation;
   
   leftEye.transform.LookAt (transform.position + (transform.TransformDirection (Vector3.forward) * S3DV.focalDistance));
   rightEye.transform.LookAt (transform.position + (transform.TransformDirection (Vector3.forward) * S3DV.focalDistance));
   
   leftEye.transform.parent = transform;
   rightEye.transform.parent = transform;
}

function LateUpdate() {
	UpdateView();
	
	if (enableKeys) {
		// o and p 
		var eyeDistanceAdjust : float = 0.01;
		if (Input.GetKeyDown(upEyeDistance)) {
			S3DV.eyeDistance += eyeDistanceAdjust;
		} else if (Input.GetKeyDown(downEyeDistance)) {
			S3DV.eyeDistance -= eyeDistanceAdjust;
		}
		
		// k and l
		var focalDistanceAdjust : float = 0.5;
		if (Input.GetKeyDown(upFocalDistance)) {
			//Debug.Log("focal up");
			S3DV.focalDistance += focalDistanceAdjust;
		} else if (Input.GetKeyDown(downFocalDistance)) {
			S3DV.focalDistance -= focalDistanceAdjust;
		}
	}
}

function OnRenderImage (source:RenderTexture, destination:RenderTexture) {
	RenderTexture.active = destination;
	GL.PushMatrix();
	GL.LoadOrtho();
	for(var i:int = 0; i < anaglyphMat.passCount; i++) {
		anaglyphMat.SetPass(i);
		DrawQuad();
	}
	GL.PopMatrix();
}
 
private function DrawQuad() {
	GL.Begin (GL.QUADS);		
		GL.TexCoord2( 0.0, 0.0 ); GL.Vertex3( 0.0, 0.0, zvalue );
		GL.TexCoord2( 1.0, 0.0 ); GL.Vertex3( 1.0, 0.0, zvalue );
		GL.TexCoord2( 1.0, 1.0 ); GL.Vertex3( 1.0, 1.0, zvalue );
		GL.TexCoord2( 0.0, 1.0 ); GL.Vertex3( 0.0, 1.0, zvalue );
	GL.End();
}

Cool man, thanks for sharing your tweaks and fixes!

A few of us here at UT were checking out Paper Moon last night with the anaglyph glasses on. It looks great :smile:

Ethan

This is cool. Nice Job.

Thanks a bunch.

Using a few lines of InfiniteAlec’s updated code I was able to tweak the original code to work correctly in Windows.

  • I commented out “camera.Render();” in the Start function.
  • I added “ZTest Always” to the shader declaration.
  • I changed the z value in the DrawQuad calls to 0.0 (from 1).

Out of curiosity, does anyone know why I still can’t seem to get it to work if I create the RenderTextures with the code:

leftEyeRT = new RenderTexture (Screen.width, Screen.height, 24);
rightEyeRT = new RenderTexture (Screen.width, Screen.height, 24);

Basically what I’ve done is comment this code out and use some RenderTextures from my library, which seems to work… just curious why it doesn’t work both ways.

Thanks,
Jared

Definitely cool beans. And Stereo - Anagylph Paper Moon looks particularly cool! :slight_smile:

Any objections to throwing this up on the Unity Wiki?

Very cool stuff! I’ve been doing similar stuff in a totally different way on one of my many projects (this one’s for work!) we have a stereoscopic screen that works by using polarising filters on two projectors laid ontop of each other.

The solution was really simple in Unity, just two viewports, two cameras. Job done. Only tricky thing is calculating the optimum eye seperation distance for viewing near/far objects :slight_smile:

The method in this thread actually is the incorrect way to do stereo and results in “vertical parallax distortions” in the rendered image. Check out chapter 14 in this free book if anyone is interested in doing it the accurate way:
http://www.pangeasoft.net/book/buy.html

Cheers,
-Jon

Hi, I tried to apply the above code to the Islands Demo project, it works fine within the Editor. However, when I build it to standalone application, it shows only the original contents without anaglyph. Any suggestion what I have missed? Or is there any extra build settings? I’m with Unity 2.5.

Thanks.

Can this be used in the indie version?

Cheers.

No, this is Unity Pro only.

can someone post a demo of this?

This is really interesting.

Cheers.

With the new nVidia beta divers you can create anaglyph renderings in almost any 3D application… but didn’t check if it works with unity.

But my eyes start to hurt anyway after a few minutes because of the retinal rivalry… did some tests with anaglyph rendering in XNA last year.

I’ve been playing around with this a bit today here are some screen grabs, using red/green rather than red/cyan. It works even better when it’s moving around and on windows where the gamma makes the red stronger. Might depend on your glasses too I suppose.


Thanks for this link!

I’ve implemented the skewed projection matrix method and it does seem easier on the eyes.

It’s quite simple to do if anyone else wants to try the code for skewing the matrix is in the Unity help, the code below can just be bolted into the original script.

function PerspectiveOffCenter(
    left : float, right : float,
    bottom : float, top : float,
    near : float, far : float ) : Matrix4x4
{        
    var x =  (2.0 * near) / (right - left);
    var y =  (2.0 * near) / (top - bottom);
    var a =  (right + left) / (right - left);
    var b =  (top + bottom) / (top - bottom);
    var c = -(far + near) / (far - near);
    var d = -(2.0 * far * near) / (far - near);
    var e = -1.0;

    var m : Matrix4x4;
    m[0,0] = x;  m[0,1] = 0;  m[0,2] = a;  m[0,3] = 0;
    m[1,0] = 0;  m[1,1] = y;  m[1,2] = b;  m[1,3] = 0;
    m[2,0] = 0;  m[2,1] = 0;  m[2,2] = c;  m[2,3] = d;
    m[3,0] = 0;  m[3,1] = 0;  m[3,2] = e;  m[3,3] = 0;
    return m;
}

function projectionMatrix(isLeftEye : boolean) : Matrix4x4 {
	var left : float;
	var right : float;
	var a : float;
	var b : float;
	var fov : float; 
    
	fov = camera.fieldOfView / 180.0 * Mathf.PI;  // convert FOV to radians 
 
	var aspect : float = camera.aspect; 
 
	a = camera.nearClipPlane * Mathf.Tan(fov * 0.5); 
	b = camera.nearClipPlane / S3DV.focalDistance;
	
	if (isLeftEye)      // left camera 
	{ 
		left  = - aspect * a + (S3DV.eyeDistance) * b; 
		right =   aspect * a + (S3DV.eyeDistance) * b; 
	} 
	else         // right camera 
	{ 
		left  = - aspect * a - (S3DV.eyeDistance) * b; 
		right =   aspect * a - (S3DV.eyeDistance) * b; 
	} 

 	return PerspectiveOffCenter(left, right, -a, a, camera.nearClipPlane, camera.farClipPlane);
 	
}

Then just modify the part of the original script that rotates the left and right eye cameras so instead of doing that they just copy the rotation from the original camera and then get their projection matrices modified thus

 if (useOffsetMatrix) {
		leftEye.transform.rotation = transform.rotation; 
		rightEye.transform.rotation = transform.rotation; 
		
		leftEye.camera.projectionMatrix = projectionMatrix(true);
		rightEye.camera.projectionMatrix = projectionMatrix(false);
	}

I’ve also been playing with the colour balancing method also found in the link posted above.

So instead of just using the anaglyph shader embedded in the script use this one instead

Shader "Hidden/Colour Balance Anaglyph" {
Properties {
	_LeftTex ("Left (RGB)", RECT) = "white" {}
	_RightTex ("Right (RGB)", RECT) = "white" {}
}

SubShader {
	Pass {
		ZTest Always Cull Off ZWrite Off
		Fog { Mode off }

		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#pragma fragmentoption ARB_precision_hint_fastest 
		#include "UnityCG.cginc"
		
		#define RED_RATIO_ADJ  .6f 
		#define GREEN_RATIO_ADJ  .4f
		#define BLUE_RATIO_ADJ  .8f 
		
		uniform samplerRECT _LeftTex;
		uniform samplerRECT _RightTex;
		
		struct v2f {
			float4 pos : POSITION;
			float2 uv : TEXCOORD0;
		};
		
		v2f vert( appdata_img v )
		{
			v2f o;
			o.pos = mul (glstate.matrix.mvp, v.vertex);
			float2 uv = MultiplyUV( glstate.matrix.texture[0], v.texcoord );
			o.uv = uv;
			return o;
		}
		
		half4 frag (v2f i) : COLOR
		{
			float r, g, b;
			float4 texR = texRECT(_LeftTex, i.uv);
			float4 texGB = texRECT(_RightTex, i.uv);
			float4 texRGB;
			
			r=texR.r;
			g=texGB.g;
			b=texGB.b;
			
			float lumR = texR.r * 0.299f;
			float lumGB = texR.g * 0.587f + texR.b * 0.114f;
			
			// Balance red
			float ratio = lumGB / lumR;
			float d = texR.r * ratio * RED_RATIO_ADJ;
			if (d > texR.r) {
				r = d;
				if (r > 0xff) r = 0xff;
			}
			
			lumR = texGB.r * 0.299f;
			lumGB = texGB.g * 0.587f + texGB.b * 0.114f;
			
			//Balance green
			ratio = lumR / lumGB;
			d = texGB.g * ratio * GREEN_RATIO_ADJ;
			if (d > texGB.g) {
				g = d;
				if (g > 0xff) g = 0xff;
			}
			
			//Balance blue
			d = texGB.b * ratio * BLUE_RATIO_ADJ;
			if (d > texGB.b) {
				b = d;
				if (b > 0xff) b = 0xff;
			}
			
			texRGB = float4(r,g,b,1);
			return texRGB;
		}
		ENDCG
	}
}	
	Fallback off
}

However I’m ussure as to whether this is supported on all hardware, can anyone who knows shaderLab better confirm if this would work always?