Search Unity

[FIXED] Vertex shader for a particle system: I can't seem to get this correct :(

Discussion in 'Shaders' started by invicticide, Jan 4, 2015.

  1. invicticide

    invicticide

    Joined:
    Nov 15, 2009
    Posts:
    109
    We use a vertex shader to offset geometry based on distance, to create the path bending effect you see in most 3D endless runners. I'm getting two different offset results depending on whether the shader is applied to mesh geometry, or a particle system, and I can't seem to figure out why.

    Here's the base vertex function:

    Code (CSharp):
    1. void vert (inout appdata_full v)
    2. {
    3.     float4 vPos = mul (_Object2World, v.vertex);
    4.     float3 camRelativePos = _WorldSpaceCameraPos - vPos;
    5.     float zOff = camRelativePos.z / _Dist;
    6.     vPos += _QOffset*zOff*zOff;
    7.     vPos = mul( _World2Object, vPos);
    8.     v.vertex = vPos;
    9.     v.vertex.xyz *= unity_Scale.w;
    10. }
    11.  
    _QOffset is a global vector which specifies the base amount of X and Y offset to apply. _Dist is a global float which specifies the distance from the camera over which _QOffset will be ramped in. The closer to the camera the vertex is, the less _QOffset will contribute to it.

    When I applied that vertex function to a particle system using the Stretched Billboard renderer, initially the particles just stopped rendering entirely. I had to tweak the particle shader to get them to show up again. Here's that version's vertex function:

    Code (CSharp):
    1. vertexOutput vert(vertexInput input)
    2. {
    3.     vertexOutput output;
    4.  
    5.     float4 vPos = mul (_Object2World, input.vertex);
    6.     float3 camRelativePos = _WorldSpaceCameraPos - vPos;
    7.     float zOff = camRelativePos.z / _Dist;
    8.     vPos += _QOffset*zOff*zOff;
    9.     vPos = mul( _World2Object, vPos);
    10.     output.pos = vPos;
    11.     output.pos.xyz *= unity_Scale.w;
    12.     output.pos = mul(UNITY_MATRIX_MVP, output.pos); // <-- THIS IS NEW
    13.  
    14.     output.texc = input.texcoord.xy;
    15.     output.color = input.color * _Color;
    16.  
    17.     return output;
    18. }
    19.  
    There's a little bit of other different stuff going on here in terms of the return value and texture coordinates and what-not, which is because the particle shader is using a fragment function while the original shader is using a surface function. As you can see, the actual vertex transformation code is identical, save for the matrix multiply that I called out.

    With that modification the particles show up again, but they're not quite in the right position. I have a mesh (with the first shader) and a particle system (with the second shader) placed at the exact same position as each other. When they're close to the camera, everything looks okay. The further from the camera they get, the more they appear to diverge from one another.

    I'm sure this is something simple and stupid, but I barely understand shaders in the first place. For example, I don't understand why I needed to add that matrix multiply to get the particles to show up at all. And obviously I don't understand why either way I'm getting two totally different results depending on whether I'm rendering geometry or particles.

    Any insight would be greatly appreciated! <3
     
  2. invicticide

    invicticide

    Joined:
    Nov 15, 2009
    Posts:
    109
    Upon further investigation, it looks like the vertex function for the particle shader needs to take the camera's rotation into account somehow. If I zero out the camera rotation, everything lines up perfectly. The more the camera is rotated, the more position error is introduced.

    Unfortunately, I have no idea how to account for the camera rotation in the shader. I'm way out of my depth, here. :(
     
  3. invicticide

    invicticide

    Joined:
    Nov 15, 2009
    Posts:
    109
    I LEARNED THINGS.

    _Object2World is useless for particle systems. There's a different (undocumented) built-in _CameraToWorld matrix which should work for this -- since particles are rendered in screen-space to begin with -- but it seems to be wrong.

    However, I was able to work around that by providing a custom camera-to-world matrix to the shader as suggested in this thread.

    Just don't do what I did, and forget that OnPreCull is only called if the script that defines it is attached directly to a Camera. I wasted most of a morning chasing red herrings because of that. :(
     
    Taaarrm and Branchie like this.