Search Unity

Sphere imposter on quad in deferred rendering

Discussion in 'Shaders' started by cecarlsen, Jun 16, 2017.

  1. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    862
    I came across this intriguing method of how to render a sphere using a single quad. This is useful for rendering a S***load of spheres, for example coming from a particle system.
    http://11235813tdd.blogspot.dk/2013/04/raycasted-spheres-and-point-sprites-vs.html
    http://www.sunsetlakesoftware.com/2011/05/08/enhancing-molecules-using-opengl-es-20

    I got it working perfectly in forward rendering, but in deferred rendering the lighting screws up. I get a dark line on the side of the sphere. The closer the sphere is located to the screen edges the worse the effect gets. I just don't understand why...

    I am suspecting that something fishy is happening after I pass the world normals through the g-buffer to Unity's lighting system. I also don't quite understand the size difference to the standard (triangulated) sphere.

    I have attached an example package. Shader model 4.0 is required. Not sure it will fly on OSX, I made it on Windows. It does this:

    1) Attaches a CommandBuffer to each camera requesting a single vertex to be rendered with a shader.
    2) In the shader, the vertex is expanded to a quad in the geometry function.
    3) In the shaders fragment function sphere normals are computed and outputted to the g-buffer.
    (More comments can be found in the code)

    Any hints are appreciated!
    Carl Emil

    DeferredImposterSphereWhy.jpg

    Code (CSharp):
    1.  
    2.         [maxvertexcount(4)]
    3.         void geom( point VS_OUT p[1], uint id : SV_PrimitiveID, inout TriangleStream<GS_OUT> triStream )
    4.         {
    5.             float4 pos = float4( 0, 0, 0, 1 ); // Draw at zero, just for testing.
    6.             float4 clipPos = mul( UNITY_MATRIX_VP, pos );
    7.  
    8.             float2 extents = 1;
    9.             extents.x *= _ScreenParams.y / _ScreenParams.x; // Aspect correction.
    10.  
    11.             GS_OUT o;
    12.             o.vertex = clipPos;
    13.  
    14.             // Expand.
    15.             o.vertex.xy = clipPos.xy + float2( extents.x, -extents.y );
    16.             o.quadpos = float2( 1, 1 );
    17.             triStream.Append(o);
    18.  
    19.             o.vertex.xy = clipPos.xy + extents;
    20.             o.quadpos = float2( 1, -1 );
    21.             triStream.Append(o);
    22.  
    23.             o.vertex.xy = clipPos.xy - extents;
    24.             o.quadpos = float2( -1, 1 );
    25.             triStream.Append(o);
    26.  
    27.             o.vertex.xy = clipPos.xy + float2( -extents.x, extents.y );
    28.             o.quadpos = float2( -1, -1 );
    29.             triStream.Append(o);
    30.         }
    31.        
    32.        
    33.         void fragDeferred
    34.         (
    35.             GS_OUT i,
    36.             out half4 outGBuffer0 : SV_Target0,
    37.             out half4 outGBuffer1 : SV_Target1,
    38.             out half4 outGBuffer2 : SV_Target2,
    39.             out half4 outEmission : SV_Target3    // RT3: emission (rgb), --unused-- (a)
    40.         ){
    41.             // Test against circle.
    42.             float r = dot( i.quadpos, i.quadpos ); // square distance
    43.             if( r > 1 ) discard;
    44.  
    45.             // Compute sphere world normal.
    46.             r = sqrt( r ); // Radius from quad center
    47.             float depth = sqrt( 1 - r*r ); // a'2 + b'2 = c'2
    48.             float3 screenNormal = float3( i.quadpos, depth );
    49.             float3 worldNormal = mul( (float3x3) _InvViewMatrix, screenNormal );
    50.  
    51.             // Populate UnityStandardData and assemble using UnityStandardDataToGbuffer.
    52.             UnityStandardData data;
    53.             data.diffuseColor = 1;
    54.             data.occlusion = 1;
    55.             data.specularColor = _Specular.xxx;
    56.             data.smoothness = _Smoothness;
    57.             data.normalWorld = worldNormal;
    58.             UnityStandardDataToGbuffer( data, outGBuffer0, outGBuffer1, outGBuffer2 );
    59.             outEmission = half4( 0, 0, 0, 1 );
    60.             #ifndef UNITY_HDR_ON
    61.                 outEmission.rgb = exp2(-outEmission.rgb);
    62.             #endif
    63.         }
    64.  
     

    Attached Files:

    Last edited: Jun 16, 2017
  2. cecarlsen

    cecarlsen

    Joined:
    Jun 30, 2006
    Posts:
    862
    Ok. I have a hint now.

    When Unity computes deferred lighting, each pixel is reprojected into the world using camera matrix and depth texture. That world position is used together with the normal to compute lighting. Since the depth of the sphere is flat (from the quad) this will result in wrong lighting. Also, I think I will have to cast a ray from the camera onto the quad to get the perspective right, so that it will match Unity's re-projection during lighting.

    I'll get back here if I solve it...
     
    vjblind and StaffanEk like this.