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

Reconstructing world position in CommandBuffer.Blit

Discussion in 'Shaders' started by jvo3dc, Nov 3, 2015.

  1. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    This has been puzzling me for a while, but I think I'm getting somewhere now. So I'd like to share my experiences. I'm adding a full screen pass using CommandBuffer.Blit(). This pass needs to restore the world position from the depth buffer. Everywhere I look in the Unity built in shaders and the CommandBuffer examples from Aras, I see the same code that is supposed to pass a camera space ray from the camera towards the scene to the fragment shader:
    Code (csharp):
    1.  
    2. float _LightAsQuad;
    3.  
    4. unity_v2f_deferred vert_deferred (float4 vertex : POSITION, float3 normal : NORMAL)
    5. {
    6.    unity_v2f_deferred o;
    7.    o.pos = mul(UNITY_MATRIX_MVP, vertex);
    8.    o.uv = ComputeScreenPos (o.pos);
    9.    o.ray = mul (UNITY_MATRIX_MV, vertex).xyz * float3(-1,-1,1);
    10.  
    11.    // normal contains a ray pointing from the camera to one of near plane's
    12.    // corners in camera space when we are drawing a full screen quad.
    13.    // Otherwise, when rendering 3D shapes, use the ray calculated here.
    14.    o.ray = lerp(o.ray, normal, _LightAsQuad);
    15.  
    16.    return o;
    17. }
    18.  
    This doesn't seem to work with CommandBuffer.Blit() though.

    The examples of Aras all render actual meshes instead of using Blit, so then this works. Internal-PrePassLighting.shader uses this, but then _LightAsQuad is probably used for fullscreen quads and this is not set when using CommandBuffer.Blit().

    After a lot of breaking down, I noticed that when using CommandBuffer.Blit():
    - UNITY_MATRIX_MV is actually just the identity matrix, so that can't give you a camera space ray from camera to scene.
    - UNITY_MATRIX_MVP is an orthographic projection matrix. (Used for a simple projection in Blit())
    - The input normals are not set to point from the camera to the scene, so that also won't work.
    - What we really need now is the inverse projection matrix of the original camera, so we can project the vertices of the quad from clip space back to camera space based on the original camera projection. I came across this in UnityShaderVariables.cginc:
    Code (csharp):
    1.  
    2. CBUFFER_START(UnityPerCameraRare)
    3.    uniform float4 unity_CameraWorldClipPlanes[6];
    4.  
    5.    // Projection matrices of the camera. Note that this might be different from projection matrix
    6.    // that is set right now, e.g. while rendering shadows the matrices below are still the projection
    7.    // of original camera.
    8.    uniform float4x4 unity_CameraProjection;
    9.    uniform float4x4 unity_CameraInvProjection;
    10. CBUFFER_END
    11.  
    - So unity_CameraInvProjection should give us just what we need as long as it's set to the original camera and not the orthographic camera used in Blit(). And it seems it isn't. So we're out of options without using some script to set the global information we need.
    - We'll set the camera space direction of a vector going to the top right of the view in a script:
    Code (csharp):
    1.  
    2. Vector3 direction = camera.ViewportPointToRay(new Vector3(1.0f, 1.0f, 0.0f)).direction;
    3. direction = camera.worldToCameraMatrix.MultiplyVector(direction);
    4. direction /= Mathf.Abs(direction.z);
    5. buffer.SetGlobalVector("_GlobalProjection", direction);
    6.  
    - And then we use it in the vertex shader to determine the per vertex camera space ray:
    Code (csharp):
    1.  
    2. o.ray = float3((2.0 * v.xy - 1.0) * _GlobalProjection, 1.0);
    3.  
    If anyone has a solution that doesn't rely on a global vector being set by a script, I'd like to know it.
     
    Last edited: Nov 3, 2015