Search Unity

Question I want to know how to specify the clip position in ShaderGraph.

Discussion in 'Shader Graph' started by mutsuyuki, Nov 5, 2021.

  1. mutsuyuki

    mutsuyuki

    Joined:
    Apr 16, 2015
    Posts:
    19
    I am new to ShaderGraph.

    I want to reproduce the following shader program that directly specifies the clip position in a shader graph.

    Code (HLSL):
    1.  
    2. struct appdata
    3. {
    4.     uint vertexId : SV_VertexID;
    5. };
    6.  
    7. struct v2f
    8. {
    9.     float4 vertex : SV_POSITION;
    10. };
    11.  
    12. v2f vert (appdata v)
    13. {
    14.     v2f o;
    15.     if (v.vertexId % 3 == 0) o.vertex = float4(-1.0f, -1.0f, 0.0f, 1.0f);
    16.     if (v.vertexId % 3 == 1) o.vertex = float4(-1.0f,  1.0f, 0.0f, 1.0f);
    17.     if (v.vertexId % 3 == 2) o.vertex = float4( 1.0f,  1.0f, 0.0f, 1.0f);
    18.     return o;
    19. }
    20.  
    21. fixed4 frag (v2f i) : SV_Target
    22. {
    23.     return fixed4(0.0f, 0.0f, 0.5f, 1.0f);
    24. }
    25.  
    In the shader program, the input mesh have 3 vertices, and I move directly to corner of the clip coordinates to create a triangle.

    The output result will look like this



    The triangle is always drawn at the same position regardless of the camera position.

    ----

    I want to reproduce this using the shader graph.
    My ShaderGraph is below.


    It's messy, but I'm just reproducing the if statement.
    The position of the triangle vertices is set in the red circle.

    The Result is below.


    The triangles are drawn on the spatial coordinates.
    After some research, I found out that the coordinates connected to the Vertex must be local coordinates.
    I understood that the MVP transformation is implicitly done.

    ----

    So I multiplied the inverse projection matrix and inverse view matrix and then connected them to the Vertex.
    I figured that this would return to the specified clip position by doing an implicit MVP.


    As a result, the orientation of the triangles is now correct.
    However, the size of the triangle seems to change depending on the zoom of the camera.



    What is wrong with my thinking?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    First problem is you’re only transforming the position back to world space, and the position input expects a local space position, but that’s not the real problem. The real problem is Shader Graph does not expose the clip space position as an output, only the float3 object space position. There’s no way to represent a specific float4 clip space position as just a float3 in any space unless you’re using an orthographic projection. So transferring the clip space position back to local space and passing in the float3 from that won’t produce anything useful.

    The TLDR is you cannot reproduce the original shader’s code in Shader Graph. There’s no work around or trick, it’s just straight up not possible.
     
  3. adam_unity450

    adam_unity450

    Joined:
    Aug 3, 2020
    Posts:
    13
    Well, "not possible" is a bit of a strong word. Depends on how you define "in Shader Graph". Here is my solution:

    1. Generate your shadergraph into .shader and save it in you assets folder (might want to change the shader name).
    2. Go inside the generated shader and figure out which passes you want to change and what kinds they are. For example, I was only interested in modifying the forward pass and my shader is unlit, so it was using the unlit pass's "main".
    3. This main is defined in
    Library\PackageCache\com.unity.render-pipelines.universal@12.1.6\Editor\ShaderGraph\Includes\UnlitPass.hlsl
    (again if you're using a lit shader it might be different). Make a copy of that file and also put it in your assets. I also renamed it to
    CustomUnlitPass.hlsl

    4. Do your modifications to the
    vert
    function in
    CustomUnlitPass.hlsl
    . For me, this was quite simple:

    Code (CSharp):
    1. #include "Assets/Shaders/Include/Utils.hlsl" // Added
    2.  
    3. PackedVaryings vert(Attributes input)
    4. {
    5.     Varyings output = (Varyings)0;
    6.     output = BuildVaryings(input);
    7.     PackedVaryings packedOutput = PackVaryings(output);
    8.  
    9.     packedOutput.positionCS.z = clipZPosAdjust(packedOutput.positionCS, _ZOffsetMultiplier); // Added
    10.  
    11.     return packedOutput;
    12.  
    5. Go back to your generated shadergraph .shader. In the pass that you want to modify, replace
    Code (CSharp):
    1.  #include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/UnlitPass.hlsl"
    with
    Code (CSharp):
    1. #include "Assets/Shaders/Include/CustomUnlitPass.hlsl"
    Again use paths which make sense in your situation.

    Bonus: you can even easily use uniforms defined in shadergraph in your custom code, as the
    CustomUnlitPass.hlsl
    is included after their declaration (I use _ZOffsetMultiplier).

    Now every time you update your shadergraph, you only have to re-do steps #1 and #5 (assuming you didn't change pass type).

    Not the most elegant solution, but works for my (simple) usecase.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I'd personally put this solution in the realm of "not in Shader Graph". Once you're modifying a .shader file, you're not using Shader Graph itself, you're writing a custom shader that uses Shader Graph's shader code. And once you're doing that you're not limited by Shader Graph anymore. It's a valid solution, but a destructive one as you can no longer edit the original Shader Graph node graph unless you do all of this manual modification again.

    I did plenty of this kind of hackery with Surface Shaders too, so it's not like I'm against this solution, it's just not "Shader Graph" anymore.
     
    adam_unity450 and lilacsky824 like this.