Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

[SOLVED] Image Projection Shader

Discussion in 'Shaders' started by bfogerty, Jun 28, 2014.

  1. bfogerty

    bfogerty

    Joined:
    Nov 12, 2010
    Posts:
    58
    I know Unity has a built in image projector but I am writing my own to learn how it works.
    My implementation mostly works but I am seeing some weirdness.

    Essentially I have created a script attached to my projector object that creates a view and projection matrix that I pass to all the projector receiver objects, (IE. Plane_1 and Plane_2) and then have a shader that calculates where the new uvs.

    So my hierarchy looks like


    If I translate or rotate my Projector object, the image is projected correctly onto the planes.
    If I only translate my World object, again, the the image is still projected correctly.
    However if I rotate my World object, the projected image is skewed.
    If a picture is worth 1000 words, a video will be worth 1,000,000 so take a look here.


    My code looks like so
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ImageProjector : MonoBehaviour
    6. {
    7.     public Texture ProjectionTexture = null;
    8.     public GameObject[] ProjectionReceivers = null;
    9.     public float Angle = 0.0f;
    10.  
    11.     Vector4 Vec3ToVec4(Vector3 vec3, float w)
    12.     {
    13.         return new Vector4(vec3.x, vec3.y, vec3.z, w);
    14.     }
    15.  
    16.     // Use this for initialization
    17.     void Start()
    18.     {
    19.  
    20.     }
    21.  
    22.     // Update is called once per frame
    23.     void Update()
    24.     {
    25.         Matrix4x4 matProj = Matrix4x4.Perspective(this.camera.fieldOfView, 1, this.camera.nearClipPlane, this.camera.farClipPlane);
    26.  
    27.         Matrix4x4 matView = Matrix4x4.identity;
    28.         matView = Matrix4x4.TRS(Vector3.zero, this.camera.transform.rotation, Vector3.one);
    29.  
    30.         float x = Vector3.Dot(this.camera.transform.right,      -this.camera.transform.position);
    31.         float y = Vector3.Dot(this.camera.transform.up,         -this.camera.transform.position);
    32.         float z = Vector3.Dot(this.camera.transform.forward,    -this.camera.transform.position);
    33.  
    34.         matView.SetColumn(3, new Vector4(x, y, z, 1));
    35.         matView = matView.transpose;
    36.  
    37.         Matrix4x4 LightViewProjMatrix = matView * matProj;
    38.  
    39.         if (ProjectionReceivers == null || ProjectionReceivers.Length <= 0)
    40.         {
    41.             return;
    42.         }
    43.  
    44.         foreach (GameObject imageReceiver in ProjectionReceivers)
    45.         {
    46.             ProjectionTexture.wrapMode = TextureWrapMode.Clamp;
    47.             imageReceiver.renderer.sharedMaterial.SetTexture("_ShadowMap", ProjectionTexture);
    48.             imageReceiver.renderer.sharedMaterial.SetMatrix("_LightViewProj", LightViewProjMatrix);
    49.             imageReceiver.renderer.sharedMaterial.SetFloat("_Angle", Angle);
    50.         }
    51.     }
    52.  
    53.     void OnDrawGizmos()
    54.     {
    55.         Gizmos.color = Color.red;
    56.  
    57.         Gizmos.DrawLine(this.camera.transform.position, this.camera.transform.position + (this.camera.transform.forward * 100.0f));
    58.     }
    59. }
    60.  
    61.  
    My shader is
    Code (csharp):
    1.  
    2. Shader"Custom/DiffuseWithShadow"
    3. {
    4.     Properties
    5.     {
    6.         _MainTex ("Main Texture", 2D) = "white" {}
    7.         _ShadowMap ("Shadow Map", 2D) = "white" {}
    8.         _Angle ("Angle", Float) = 0.0
    9.     }
    10.     SubShader
    11.     {
    12.         Tags { "RenderType"="Opaque" }
    13.         LOD 200
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             // Upgrade NOTE: excluded shader from OpenGL ES 2.0 because it does not contain a surface program or both vertex and fragment programs.
    19.             #pragma exclude_renderers gles
    20.  
    21.             #pragma vertex v
    22.             #pragma fragment p
    23.  
    24.             uniform sampler2D _MainTex;
    25.             uniform sampler2D _ShadowMap;
    26.             uniform float4x4 _LightViewProj;
    27.             uniform float _Angle;
    28.  
    29.  
    30.             struct VertexOut
    31.             {
    32.                 float4 position : POSITION;
    33.                 float2 uv : TEXCOORD0;
    34.                 float4 proj : TEXCOORD1;
    35.             } ;
    36.  
    37.             VertexOut v( float4 position : POSITION, float2 uv : TEXCOORD0 )
    38.             {
    39.                 VertexOut OUT;
    40.  
    41.                 OUT.position =  mul( UNITY_MATRIX_MVP, position );
    42.                 OUT.uv = uv;
    43.                 OUT.proj = mul( mul( _Object2World, float4(position.xyz, 1)), _LightViewProj );
    44.  
    45.                 return OUT;
    46.             }
    47.  
    48.             struct PixelOut
    49.             {
    50.                 float4    color : COLOR;
    51.             } ;
    52.  
    53.             PixelOut p(VertexOut IN)
    54.             {
    55.                 PixelOut OUT;
    56.  
    57.                 float2 ndc = float2(IN.proj.x/IN.proj.w, IN.proj.y/IN.proj.w);
    58.                 float2 uv = (1 + float2( ndc.x, ndc.y)) * 0.5;
    59.  
    60.                 float theta = _Angle*3.14159/ 180;
    61.                 float2x2 matRot = float2x2( cos(theta), sin(theta),
    62.                                             -sin(theta), cos(theta) );
    63.                 uv = mul( uv, matRot);
    64.  
    65.                 float4 c = tex2D( _ShadowMap, uv );
    66.  
    67.                 if( uv.x < 0 || uv.y < 0 ||
    68.                     uv.x > 1  || uv.y > 1 || c.a <= 0.00f )
    69.                     {
    70.                         c = tex2D(_MainTex, IN.uv);
    71.                     }
    72.  
    73.                 OUT.color = c;
    74.  
    75.                 return OUT;
    76.             }
    77.  
    78.             ENDCG
    79.         }
    80.  
    81.     }
    82.     FallBack"Diffuse"
    83. }
    84.  
    Any clues as to what I might be doing wrong? Thanks!
     
  2. bfogerty

    bfogerty

    Joined:
    Nov 12, 2010
    Posts:
    58
    Ok, so I noticed a couple of issues with my math but the issue still remains...
    The following is my new update code.

    Code (csharp):
    1.  
    2. // Update is called once per frame
    3.     void Update()
    4.     {
    5.         Matrix4x4 matProj = Matrix4x4.Perspective(this.camera.fieldOfView, 1, this.camera.nearClipPlane, this.camera.farClipPlane);
    6.  
    7.         Matrix4x4 matView = Matrix4x4.identity;
    8.         matView = Matrix4x4.TRS(Vector3.zero, this.camera.transform.rotation, Vector3.one);
    9.  
    10. // Fixed - I should transpose before setting the poition.
    11.         matView = matView.transpose;
    12.  
    13.         float x = Vector3.Dot(this.camera.transform.right,      -this.camera.transform.position);
    14.         float y = Vector3.Dot(this.camera.transform.up,         -this.camera.transform.position);
    15.         float z = Vector3.Dot(this.camera.transform.forward,    -this.camera.transform.position);
    16.  
    17. // Fixed - This should be row and not column
    18.         matView.SetRow(3, new Vector4(x, y, z, 1));
    19.  
    20.         Matrix4x4 LightViewProjMatrix = matView * matProj;
    21.  
    22.         if (ProjectionReceivers == null || ProjectionReceivers.Length <= 0)
    23.         {
    24.             return;
    25.         }
    26.  
    27.         foreach (GameObject imageReceiver in ProjectionReceivers)
    28.         {
    29.             ProjectionTexture.wrapMode = TextureWrapMode.Clamp;
    30.             imageReceiver.renderer.sharedMaterial.SetTexture("_ShadowMap", ProjectionTexture);
    31.             imageReceiver.renderer.sharedMaterial.SetMatrix("_LightViewProj", LightViewProjMatrix);
    32.             imageReceiver.renderer.sharedMaterial.SetFloat("_Angle", Angle);
    33.         }
    34.     }
    35.  
     
  3. bfogerty

    bfogerty

    Joined:
    Nov 12, 2010
    Posts:
    58
    Haha ok I was being silly. I forgot that Unity's matricies are column major so when I calculate the projector view matrix, I don't need to transpose that matrix. The following is the corrected code for anyone interested!

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ImageProjector : MonoBehaviour
    6. {
    7.     public Texture ProjectionTexture = null;
    8.     public GameObject[] ProjectionReceivers = null;
    9.     public float Angle = 0.0f;
    10.  
    11.     Vector4 Vec3ToVec4(Vector3 vec3, float w)
    12.     {
    13.         return new Vector4(vec3.x, vec3.y, vec3.z, w);
    14.     }
    15.  
    16.     // Use this for initialization
    17.     void Start()
    18.     {
    19.  
    20.     }
    21.  
    22.     // Update is called once per frame
    23.     void Update()
    24.     {
    25.         Matrix4x4 matProj = Matrix4x4.Perspective(this.camera.fieldOfView, 1, this.camera.nearClipPlane, this.camera.farClipPlane);
    26.  
    27.         Matrix4x4 matView = Matrix4x4.identity;
    28.         matView = Matrix4x4.TRS(Vector3.zero, this.camera.transform.rotation, Vector3.one);
    29.  
    30.         float x = Vector3.Dot(this.camera.transform.right,      -this.camera.transform.position);
    31.         float y = Vector3.Dot(this.camera.transform.up,         -this.camera.transform.position);
    32.         float z = Vector3.Dot(this.camera.transform.forward,    -this.camera.transform.position);
    33.  
    34.         matView.SetRow(3, new Vector4(x, y, z, 1));
    35.  
    36.         Matrix4x4 LightViewProjMatrix = matView * matProj;
    37.  
    38.         if (ProjectionReceivers == null || ProjectionReceivers.Length <= 0)
    39.         {
    40.             return;
    41.         }
    42.  
    43.         foreach (GameObject imageReceiver in ProjectionReceivers)
    44.         {
    45.             ProjectionTexture.wrapMode = TextureWrapMode.Clamp;
    46.             imageReceiver.renderer.sharedMaterial.SetTexture("_ShadowMap", ProjectionTexture);
    47.             imageReceiver.renderer.sharedMaterial.SetMatrix("_LightViewProj", LightViewProjMatrix);
    48.             imageReceiver.renderer.sharedMaterial.SetFloat("_Angle", Angle);
    49.         }
    50.     }
    51.  
    52.     void OnDrawGizmos()
    53.     {
    54.         Gizmos.color = Color.red;
    55.  
    56.         Gizmos.DrawLine(this.camera.transform.position, this.camera.transform.position + (this.camera.transform.forward * 100.0f));
    57.     }
    58. }
    59.  
    60.  
    The final working demo!
     
    Last edited: Jun 28, 2014
    orangetech and domkia like this.
  4. JOMAM

    JOMAM

    Joined:
    May 30, 2017
    Posts:
    1
    if I add another image projector , i see only one is working , can you tell how to make the two image projectors work at the same time
     
    XSchoett likes this.
  5. luigis

    luigis

    Joined:
    Oct 30, 2013
    Posts:
    25
    You made my day is so simple! Have you ever thinked to put it in asset store?