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

Overriding MVP multiplication in a custom standard shader

Discussion in 'Shaders' started by kloveridge, Oct 21, 2015.

  1. kloveridge

    kloveridge

    Joined:
    Apr 25, 2015
    Posts:
    44
    We are making a specialized shader that requires us to compute the final vertex position by mulitplying UNITY_MATRIX_MVP. If we make just a simple Vertex/Fragment shader, everything works great.

    But if I wanted to do the same code in a custom Standard shader, the final UNITY_MATRIX_MVP apparently is computed outside of my function. Is there a way to tell the standard shader to not multiply the MVP matrix? Here' s the shader:

    Code (CSharp):
    1. SubShader {
    2.         Tags { "RenderType"="Opaque" }
    3.         LOD 200
    4.        
    5.         CGPROGRAM
    6.         // Physically based Standard lighting model, and enable shadows on all light types
    7.         #pragma surface surf Standard fullforwardshadows vertex:vert
    8.  
    9.         // Use shader model 3.0 target, to get nicer looking lighting
    10.         #pragma target 3.0
    11.  
    12.         sampler2D _MainTex;
    13.  
    14.         struct Input {
    15.             float2 uv_MainTex;
    16.         };
    17.  
    18.         half _Glossiness;
    19.         half _Metallic;
    20.         fixed4 _Color;
    21.         fixed4 _Curvature;
    22.        
    23.         float4 curvature( in float4 _pos, in float2 _curvature)
    24.         {
    25.             _pos.x = _pos.x + (_pos.z * _pos.z * 0.0020 * _curvature.x);
    26.             _pos.y = _pos.y - (_pos.z * _pos.z * 0.0005 * _curvature.y);
    27.             return _pos;
    28.         }
    29.        
    30.         void vert (inout appdata_full v) {
    31.             float4 mp = mul( UNITY_MATRIX_MVP, float4(v.vertex.xyz,1.0));
    32.             float4 p = curvature(mp, _Curvature.xy);
    33.             v.vertex.xyz = p.xyz;
    34.  
    35. // at this point, the MVP matrix has been multiplied. I don't want the standard shader to do it again!
    36.         }
    37.      
    38.         void surf (Input IN, inout SurfaceOutputStandard o) {
    39.             // Albedo comes from a texture tinted by color
    40.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    41.             o.Albedo = c.rgb;
    42.             // Metallic and smoothness come from slider variables
    43.             o.Metallic = _Metallic;
    44.             o.Smoothness = _Glossiness;
    45.             o.Alpha = c.a;
    46.         }
    47.         ENDCG
    48.     }
     
  2. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,591
    Not that I know of. If I remember correctly, the surface vertex shader must return the vertex position in local-space, so you have to transform it back after you applied your displacement.
     
  3. kloveridge

    kloveridge

    Joined:
    Apr 25, 2015
    Posts:
    44
    And that appears to be the case.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Use the vert / frag shader generated from your surface shader, then you can do whatever you want. Select your shader, then click on generate code button in the inspector.
     
  5. Assembler-Maze

    Assembler-Maze

    Joined:
    Jan 6, 2016
    Posts:
    630
    It would be nice to have a directive or something, since not having this really stinks.
     
  6. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    I believe you're running into some of unitys magic words. I suspect if you declare 'pos' in your input and return it from your vert it won't get recalculated.
     
  7. Assembler-Maze

    Assembler-Maze

    Joined:
    Jan 6, 2016
    Posts:
    630
    Could you give a quick example or something? I think this would help many people.
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    He means something like this:
    Code (CSharp):
    1. Shader "Custom/MVPSurfaceShaderTest" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.     }
    5.     SubShader {
    6.         Tags { "RenderType"="Opaque" }
    7.         LOD 200
    8.    
    9.         CGPROGRAM
    10.         // Physically based Standard lighting model, and enable shadows on all light types
    11.         #pragma surface surf Standard fullforwardshadows vertex:vert
    12.  
    13.         // Use shader model 3.0 target, to get nicer looking lighting
    14.         #pragma target 3.0
    15.  
    16.         struct Input {
    17.             float4 pos;
    18.         };
    19.  
    20.         fixed4 _Color;
    21.  
    22.         void vert (inout appdata_full v, out Input o) {
    23.             UNITY_INITIALIZE_OUTPUT(Input,o);
    24.        
    25.             float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
    26.             worldPos.y += sin(_Time.y);
    27.             o.pos = mul(UNITY_MATRIX_VP, worldPos);
    28.         }
    29.  
    30.         void surf (Input IN, inout SurfaceOutputStandard o) {
    31.             o.Albedo = _Color.rgb;
    32.         }
    33.         ENDCG
    34.     }
    35.     FallBack "Diffuse"
    36. }
    Which, as a warning, sadly does not work.

    A few of you might go "Ah, but you didn't use pos : SV_POSITION", which is true, but the semantics on the Input struct are to tell the surface shader where the data should come from, not where it's going to. Also I tried just for the hell of it, and no it still doesn't work. The semantics on the Input struct elements are actually ignored in the shader itself anyway, they're just there to be parsed by the surface shader generator. What happens in the case above is the data in "o.pos" gets calculated, then copied to o.custompack0.xyzw and passed from the vertex shader to fragment in a TEXCOORD, then passed into the surf function as IN.pos later. The "real" o.pos and the o.pos in the user defined vertex function never touch each other.
     
    Assembler-Maze likes this.
  9. Assembler-Maze

    Assembler-Maze

    Joined:
    Jan 6, 2016
    Posts:
    630
    Yea, tried that too, no chance. After my stuff gets executed the transform is still applied. I'm having that problem with tree billboards in my tree system. But as a workaround I can pass in the identity matrix along with some uniforms that represent position and other data that I require. But again there is a useless matrix multiplication.

    What we need is a
    #pragma dont_transfrom_vertex
    or something. Since generating the code and after that removing the multiplication manually from 10 shader variants really stinks.