Search Unity

extract world position of shader animated vertex (vert/frag)

Discussion in 'Shaders' started by jister, Jul 22, 2017.

  1. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    hey for some days now I'm trying to extract the position of the vertices animated by Unity's FX/Water4 shader. Failing big time (nooby beginner level :) ). all i get back is 0 or (0,0,0,1) :(
    Tried it in vert function as wel as in the frag function. to simplify things i wrote a small shader that just moves a plane up and down and tried the same there, fail again...
    any help would be greatly appreciated!
    my last attempt was trying to convert fragment into world position...
    Code (CSharp):
    1. Shader "Custom/GetHeight" {
    2.     Properties {
    3.         _MainTex("Main texture", 2D) = "white" {}
    4.         _Scale("Scale", float) = 2
    5.         _Speed ("Speed", float) = 0.5
    6.         _VertPos ("Vertex Position", Vector) = (0.0 ,0.0, 0.0, 1)
    7.     }
    8.     SubShader {
    9.         Tags { "RenderType"="Opaque" }
    10.         LOD 200
    11.         Pass
    12.         {
    13.             CGPROGRAM
    14.             // Physically based Standard lighting model, and enable shadows on all light types
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.             #include "UnityCG.cginc"
    18.          
    19.  
    20.             // Use shader model 3.0 target, to get nicer looking lighting
    21.             #pragma target 3.0
    22.             sampler2D _MainTex;
    23.             float _Speed;
    24.             float _Scale;
    25.             float4 _VertPos;
    26.  
    27.             struct Input {
    28.                 float4 vertex : POSITION;
    29.                 float2 uv : TEXCOORD0;
    30.             };
    31.  
    32.             struct v2f{
    33.                 float4 pos : SV_POSITION;
    34.                 float2 uv : TEXCOORD0;
    35.                 float4 worldSpacePosition : TEXCOORD1;
    36.             };
    37.          
    38.             v2f vert(appdata_full v)
    39.             {
    40.                 v2f o;
    41.                 //o.pos = UnityObjectToClipPos(v.vertex);
    42.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    43.                 o.pos.y += sin(_Time.w * _Speed )*_Scale;
    44.                 o.worldSpacePosition = mul( unity_ObjectToWorld, o.pos);
    45.                 o.uv = v.vertex.xz;
    46.                 return o;
    47.             }
    48.  
    49.  
    50.             fixed4 frag (v2f i) : SV_TARGET
    51.             {
    52.                 float4 pixelWorldSpacePosition = i.worldSpacePosition;
    53.                 _VertPos = pixelWorldSpacePosition;
    54.                 fixed4 col = tex2D(_MainTex, i.uv);
    55.                 return col;
    56.             }
    57.             ENDCG
    58.         }
    59.     }
    60.     FallBack "Diffuse"
    61. }
    62.  
    also last attempt was to get it in the OnRenderObject function instead of Update
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class GetWaterPlaneHeight : MonoBehaviour {
    6.  
    7.     public Material mat;
    8.     public Vector4 waterPlaneHeight;
    9.     public float height;
    10.    
    11.     // Use this for initialization
    12.     void Start ()
    13.     {
    14.         if(mat == null)
    15.             mat = GetComponentInChildren<Renderer>().sharedMaterial;
    16.     }
    17.  
    18.     // Update is called once per frame
    19.     void OnRenderObject ()
    20.     {
    21.         waterPlaneHeight = mat.GetVector("_VertPos");
    22.         height = mat.GetVector("_VertPos").y+transform.position.y;
    23.     }
    24. }
    25.  
     
    Agent0023 likes this.
  2. Johannski

    Johannski

    Joined:
    Jan 25, 2014
    Posts:
    826
    You are calculating the worldposition the correct way in the vertex shader, but I think you have a misunderstanding on how to pass parameters between shaders and code. As of my knowledge it is not possible to change a parameter in the fragment shader so you can retrieve it in the code. Also remember that the fragment shader is executed for every pixel, while OnRenderObject is executed only once each frame.

    You could control the parameter through code (in the update method) and then use SetVector to pass it to the shader. Then you can easily access the height information in your code and it would be calculated only once each frame (on the cpu instead of gpu, but that's totally ok.

    I would say the best solution depends on how "wavy" your solution gets. If the complete water just moves up and down, calculating it on the cpu is perfect, otherwise you could just control the speed scale and time information in the script and be able to calculate specific heights when you have real waves and a few objects moving on there.
     
  3. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    hey @Johannski thanks for the suggestion, i was afraid i'd have to move it to cpu, the calculations anyway
    only thing i can think of is to re-simulate the Gerstner wave algorithm the shader uses on the vertices.
    but thats gonna be quite some work:
    1) get the mesh and all it's properties, like size of a quad, size of plane, scale of mesh,...
    2) get the triangles closest to the player
    3) calculate the offset of the Gerstner wave algorithm
    4) try to intercept the offset when it comes to the triangles around the player?
    still confused how to do it but i hope it will become clear once i start working on it :)
    EDIT: seems I'm not that far of, this guy did it similar to do some buoyancy
     
    Last edited: Jul 22, 2017
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    You want to do the wave math on the c# side. Or animate your boat using it's own shader. Depends what you need.
     
  5. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    thanks i just need the height to get the players clothing wet in my character shader ;-)
    hmm come to think of it i is there any way to share values between shaders?
     
    bgolus likes this.
  6. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    You can set a global property via a script.
     
  7. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    so if i understand the docs right you can set a global property to share between shaders, but can i write the value of that property from a shader or is it more like a constant?
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Shader.SetGlobalFloat() and similar functions let you set a value from script that will get passed to all shaders. It's functionally equivalent to calling Material.SetFloat() on every material in the game, but much more efficient. It does not let you share data between shaders like you want, it is effectively a "constant" in that all invocations of the shaders receive the original value (more explicitly it's called a uniform in shaders).

    Using "global" values like you are attempting to with _VertexPos in your original example doesn't work because that isn't a global value, that too is a uniform. Uniform values are ones that are constant at the invocation of a shader stage and do not vary per vertex or per pixel. A good example of these would be material properties or Shader.SetGlobalFloat() values. Defining one in the shader but never setting it via a material or script means it's just undefined data and could be anything (though usually it'll just be zeros). However once a shader stage starts it can be modified as it's treated like a local value.

    I keep using the terms shader "stage" and "invocation". What I mean by this is the vertex shader function and fragment shader function are completely separate things as far as the GPU is concerned. Unity's ShaderLab puts the, both in the same file for ease of use as they work in pairs, but after the vertex shader runs the only data that the fragment shader has access to is the data passed to it via the "v2f" struct, or more explicitly via the vertex shader output semantics (the : TEXCOORD0 lines). By invocation I mean each time the vertex or fragment shader stages are run. The vertex shader runs once for each vertex of the mesh being rendered, and once for each pixel of the object rendered, and none of those invocations can pass data to another within the same stage. All of those "global" uniform values are really predefined local values for each invocation.

    The traditional way to share data between shaders is usually to have a shader render into a render texture, and then pass that render texture to the other shader. To do that means you need to render your object to a different render target, often using a second camera.

    Keijiro's skinner does do this to save skinned mesh position data and pass it to other shaders, as its not something that can be easily replicated on the CPU.
    https://github.com/keijiro/Skinner

    Technicallly with DX11 shaders you could also set a compute buffer on both shaders and have one write to the buffer within the shader and have another read from that buffer. This would even let vertex shaders write out data on its own that other shaders can use. However there's some details for implementing that in Unity which make it harder than that sounds.

    Any way you do this, the above methods are just describing how to pass data from one shader to another while keeping the data on the GPU. If you want access to it on the CPU there's going to be a significant performance hit in doing so, like 10-20ms or more, during which your entire game will freeze waiting for the data to be transferred back to the CPU.


    TLDR; It'll be cheaper and faster to just recalculate the data each time you need it rather than try to construct a way to pass the data around.
     
    jister likes this.
  9. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    ok at this point i've got the converted to C# Gerstner algorithm (thanks @_methotec_), it's not 100% precise but i don't need it to be.
    still one last though:
    what if i made Globals of Gerstner variables and then add this from the Water4 shader to my character shader?
    Code (CSharp):
    1. #include "WaterInclude.cginc"
    2. ...
    3. half3 nrml;
    4. half3 offsets;
    5.         Gerstner (
    6.             offsets, nrml, v.vertex.xyz, vtxForAni,                        // offsets, nrml will be written
    7.             _GAmplitude,                                                // amplitude
    8.             _GFrequency,                                                // frequency
    9.             _GSteepness,                                                // steepness
    10.             _GSpeed,                                                    // speed
    11.             _GDirectionAB,                                                // direction # 1, 2
    12.             _GDirectionCD                                                // direction # 3, 4
    13.         );
    14.    
    15.         v.vertex.xyz += offsets;
    i would effectively get the same values for "offsets" and "nrml" right?
     
    Last edited: Jul 23, 2017
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    If you're using the same data for both, then yes, they'll match. Using SetGlobal to set frequently reused values is certainly a good way to do that. Note that _Time.y in the shader and Time.timeSeconds in script do not match! In editor I believe _Time.y matches Time.timeSinceLevelLoad or Time.realtimeSinceStartup depending on if you're looking at game view or scene view, but I've never figured out which value is used for standalone builds, and it seems to vary based on platform, so I set a _GlobalTime value manually from script to keep time dependent c# and shader calculations in sync.
     
  11. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    yes i figured that much noticing the value is sometimes inward to the wave movement and sometimes not o_O
    for the rest it's working quite well. I'll fix that and then also try moving the gerstner into my character shader see which works best.
    also i'd like to add some splatter to the "wetness line" on my character created by the water height. dunno if i should just make it appear or scroll up with the water level...
    will post some screens later on.
     
  12. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    ok so this is the result with the gerstner in script. btw changed the time to Time.timeSinceLevelLoad like you said @bgolus which gives the right result (or at least better).

    i tried making all the gerstner setting Global like this:
    Code (CSharp):
    1. public class GerstnerGlobals : MonoBehaviour {
    2.  
    3.     public Vector4 amp = new Vector4(0.3f, 0.35f, 0.25f, 0.25f);
    4.     public Vector4 freq = new Vector4(1.3f, 1.35f, 1.25f, 1.25f);
    5.     public Vector4 steep = new Vector4(1.0f, 1.0f, 1.0f, 1.0f);
    6.     public Vector4 speed = new Vector4(1.2f, 1.375f, 1.1f, 1.5f);
    7.     public Vector4 AB = new Vector4(0.3f, 0.85f, 0.85f, 0.25f);
    8.     public Vector4 CD = new Vector4(0.1f, 0.9f, 0.5f, 0.5f);
    9.  
    10.     void Start()
    11.     {
    12.         Shader.SetGlobalVector(" _GAmplitude", amp);
    13.         Shader.SetGlobalVector("_GFrequency", freq);
    14.         Shader.SetGlobalVector("_GSteepness", steep);
    15.         Shader.SetGlobalVector("_GSpeed", speed);
    16.         Shader.SetGlobalVector("_GDirectionAB", AB);
    17.         Shader.SetGlobalVector("_GDirectionCD", CD);
    18.     }
    19.     void Update()
    20.     {
    21.         Shader.SetGlobalVector(" _GAmplitude", amp);
    22.         Shader.SetGlobalVector("_GFrequency", freq);
    23.         Shader.SetGlobalVector("_GSteepness", steep);
    24.         Shader.SetGlobalVector("_GSpeed", speed);
    25.         Shader.SetGlobalVector("_GDirectionAB", AB);
    26.         Shader.SetGlobalVector("_GDirectionCD", CD);
    27.     }
    28. }
    removed them in the FX/Water4 shader as properties and added this to my CharacterShader
    Code (CSharp):
    1. CGINCLUDE
    2.     #include "UnityCG.cginc"
    3.     #include "WaterInclude.cginc"
    4. ENDCG
    5. SubShader
    6. {
    7.     ...
    8.  
    9.     CGPROGRAM
    10.      
    11.     #pragma surface surf Standard fullforwardshadows vertex:vert
    12.  
    13.     uniform float4 _GAmplitude;
    14.     uniform float4 _GFrequency;
    15.     uniform float4 _GSteepness;
    16.     uniform float4 _GSpeed;
    17.     uniform float4 _GDirectionAB;
    18.     uniform float4 _GDirectionCD;
    19. void vert(inout appdata_full v, out Input o)
    20.          {
    21.              UNITY_INITIALIZE_OUTPUT(Input,o);
    22.              half3 worldSpaceVertex = mul(unity_ObjectToWorld,(v.vertex)).xyz;
    23.              half3 vtxForAni = (worldSpaceVertex).xzz;
    24.  
    25.             half3 nrml;
    26.             half3 offsets;
    27.             Gerstner (
    28.                 offsets, nrml, v.vertex.xyz, vtxForAni,                        // offsets, nrml will be written
    29.                 _GAmplitude,                                                // amplitude
    30.                 _GFrequency,                                                // frequency
    31.                 _GSteepness,                                                // steepness
    32.                 _GSpeed,                                                    // speed
    33.                 _GDirectionAB,                                                // direction # 1, 2
    34.                 _GDirectionCD                                                // direction # 3, 4
    35.             );
    36.          
    37.             o.waterLevel += offsets.y;
    38.  
    39.          }
    40.         ...
    which is as you can see a surface shader so maybe thats the problem because it doesn't work :)
    not sure if i have the whole pipeline difference between vert/frag and surface figured out... :oops:
    anyway I'll keep the gerstner script method for now. thanks for all the help!!
    now make the wetness stick and dry up slowly, but that should be a walk in the park :)
     
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    There's not a huge difference between surface shaders and vertex fragment shaders, in fact surface shaders are really just vertex fragment shaders in the end. There's a lot of surface shaders that are hidden to try to make things easier for users, but if you select the surface shader in the project view and click on Show Generated Code you can see the actual vertex fragment shader the game is really using.
     
    jister likes this.