Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Proper vertex shaders in surface shaders

Discussion in 'Shaders' started by AlexApps, Sep 9, 2021.

  1. AlexApps

    AlexApps

    Joined:
    May 12, 2018
    Posts:
    3
    These past few days I've been tearing my hair out, trying to find out a way to modify vertices in screen space within a surface shader, like what can be achieved with an unlit shader. All examples, documentation, blogs, and tutorials cite do something like this, which can only transform vertices in model space, before they have been multiplied by the transformation matrix.

    Here's an example of what I currently have, as an unlit shader:

    Shader "Unlit/JitterWarp"
    {
    Properties
    {
    _MainTex ("Texture", 2D) = "white" {}
    _Jitter ("Jitter", Range(1, 64)) = 1
    [MaterialToggle] _Warp ("Warp", Float) = 0
    }
    SubShader
    {
    Tags { "RenderType"="Opaque" }
    LOD 100

    Pass
    {
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    // make fog work
    #pragma multi_compile_fog

    #include "UnityCG.cginc"

    struct appdata
    {
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
    };

    struct v2f
    {
    float2 uv : TEXCOORD0;
    UNITY_FOG_COORDS(1)
    float4 vertex : SV_POSITION;
    };

    sampler2D _MainTex;
    float4 _MainTex_ST;
    float _Jitter;
    float _Warp;

    v2f vert (appdata v)
    {
    v2f o;
    // Note that all calculations take place *after* this code
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    UNITY_TRANSFER_FOG(o,o.vertex);

    o.vertex.xyz /= abs(o.vertex.w);
    o.vertex.w = sign(o.vertex.w);

    float2 j = _ScreenParams.xy / _Jitter;
    o.vertex.xy = round(o.vertex.xy * j) / j;

    return o;
    }

    fixed4 frag (v2f i) : SV_Target
    {
    fixed4 col = tex2D(_MainTex, i.uv);
    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
    }
    ENDCG
    }
    }
    }


    The reason why I need it as a surface shader, is to apply this effect to terrain, which only seems to support surface shaders. I have looked online for hours to no avail, to find a shader or example which does not use surface shaders, but I have come up empty.

    If it turns out to be impossible/ridiculously complex to have screen space vertex shaders in surface shaders, I would also accept a way to have unlit shaders on terrain. I assume the latter might be easier but I don't know.

    I would greatly appreciate the help, I only started Unity a week ago and am trying to bend it to my will :)
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Surface Shaders are vertex fragment shader generators. You can take a Surface Shader written for terrain, and modify the generated shader code all you want.

    You can also do some wacky stuff like transform the vertex position into clip space, modify it, and transform it back to local space. The problem is Unity doesn’t actually provide the inverse transform matrix for the
    UNITY_MATRIX_VP
    matrix that
    UnityObjectToClipPos()
    uses. They do provide some other matrices that can get you the close equivalent with
    unity_CameraProjection
    and
    unity_CameraInvProjection
    . These don’t exactly match the
    UNITY_MATRIX_P
    , but they might get you what you need.

    So you’d need to do:
    Code (csharp):
    1. float4 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0));
    2. float4 viewPos = mul(UNITY_MATRIX_V, worldPos);
    3. float4 clipPos = mul(unity_CameraProjection, viewPos);
    4. // do your tweaks
    5. v.vertex = mul(unity_WorldToObject, mul(UNITY_MATRIX_I_V, mul(unity_CameraInvProjection, clipPos)));
    6. v.vertex /= v.vertex.w;
    It should be noted that this isn’t exactly the same as keeping the position in clip space, but it’s close, and the artifacts from doing this should only be visible if you’re really, really messing with the screen space positions, or close to the camera’s near plane.

    There’s also an even more crazy solution, and that is to transform the
    appdata
    values into the final forms the shader is going to output them in, and then set all of the transform matrices to identity matrices so the later code won’t modify them.

    Code (csharp):
    1. void vertex(inout appdata v)
    2. {
    3.   v.vertex = UnityObjectToClipPos(v.vertex);
    4.   unity_ObjectToWorld = UNITY_MATRIX_VP = float4x4(
    5.                                              1,0,0,0,
    6.                                              0,1,0,0,
    7.                                              0,0,1,0,
    8.                                              0,0,0,1
    9.                                              );
    10.   // do your modifications
    11. }
    When the generated shader code later calls
    UnityObjectToClipPos()
    , which uses those two matrices, the value in
    v.vertex
    won’t be modified at all and you’ll be good to go.
     
    Invertex likes this.
  3. AlexApps

    AlexApps

    Joined:
    May 12, 2018
    Posts:
    3
    I don't really like the roundtrip loss of precision in the first answer, but the second answer looks acceptable. Thanks!
     
  4. CuriosityWang

    CuriosityWang

    Joined:
    Jul 23, 2023
    Posts:
    1
    when I use the second method it seems like that the shadow was changed