Search Unity

Scale UV Texture to match specified world size

Discussion in 'Shaders' started by kirbygc00, Apr 25, 2020.

  1. kirbygc00

    kirbygc00

    Joined:
    Apr 4, 2016
    Posts:
    50
    Hi All,

    I am trying to render some indicators using shaders and would like them to scale to match the specified world size. For example, rendering a circular texture so that it's radius matches the radius supplied by a script.

    Attached are a video and some code of what I currently have.

    Code (CSharp):
    1. Shader "Custom/RotateUVs" {
    2.     Properties {
    3.         _MainTex ("Base (RGB)", 2D) = "white" {}
    4.     }
    5.     SubShader {
    6.         Tags { "RenderType"="Opaque" }
    7.         LOD 200
    8.        
    9.         CGPROGRAM
    10.         #pragma surface surf Lambert vertex:vert
    11.  
    12.         sampler2D _MainTex;
    13.         static const float _RotationSpeed = 10.0;                
    14.         static const float _PI = 3.14159265;    
    15.  
    16.         struct Input {
    17.             float2 uv_MainTex;
    18.         };
    19.  
    20.         float2x2 rotate2d(float _angle){
    21.             return float2x2(cos(_angle),-sin(_angle),
    22.                             sin(_angle),cos(_angle));
    23.         }
    24.        
    25.         float2x2 scale(float2 _scale){
    26.             return float2x2(_scale.x,0.0,
    27.                         0.0,_scale.y);
    28.         }
    29.          
    30.         void vert (inout appdata_full v) {
    31.             float debugNum = _PI * _Time * _RotationSpeed;
    32.             v.texcoord.xy -= float2(0.500,0.500);
    33.             v.texcoord.xy  = mul(rotate2d(debugNum), v.texcoord.xy);
    34.             v.texcoord.xy += float2(0.500,0.500);
    35.            
    36.             v.texcoord.xy -= float2(0.5,0.5);
    37.             float2 scaleMatrix = float2(sin(debugNum) - 2, sin(debugNum) - 2);
    38.             v.texcoord.xy = mul(scale( scaleMatrix ), v.texcoord.xy);
    39.             v.texcoord.xy += float2(0.5,0.5);
    40.         }
    41.  
    42.         void surf (Input IN, inout SurfaceOutput o) {  
    43.             half4 c = tex2D (_MainTex, IN.uv_MainTex);
    44.             o.Albedo = c.rgb;
    45.             o.Alpha = c.a;
    46.         }
    47.         ENDCG
    48.     }
    49.     FallBack "Diffuse"
    50. }


    I currently have it pulse up and down just to visualize the scale, but ideally I'd like to set it to a value. Let's say 16f, and have the rendered texture be a world size of 16f. The rotation is just so I could figure out how to do that.

    I am thinking `unity_ObjectToWorld` would be useful here, but I am not exactly sure how to use it.
    https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html

    Thanks for checking guys, cheers!
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Modifying existing UVs to match the world scale is non-trivial to borderline impossible, depending on the mesh. For a simple flat quad it is plausible simply because its UVs are on a single plane with a constant size relative to the mesh. If you know the scale of the mesh (which that
    unity_ObjectToWorld
    matrix is exactly the thing to look at) you can extract the scale values for the axis the quad's UVs align to and use that to scale them.


    Or you can just use world space UVs and ignore the mesh's original UVs entirely.
     
    kirbygc00 likes this.
  3. kirbygc00

    kirbygc00

    Joined:
    Apr 4, 2016
    Posts:
    50
    Hey bgolus,

    Just wanted to get back to you on this:
    I added some code to my shader to get the effect you were speaking about:
    Code (CSharp):
    1.         void vert (inout appdata_full v) {
    2.             _Scale /= pow(_Scale, 2);
    3.             //v.texcoord.xy -= float2(0.500,0.500);
    4.             //v.texcoord.xy  = mul(rotate2d(debugNum), v.texcoord.xy);
    5.             //v.texcoord.xy += float2(0.500,0.500);
    6.        
    7.             float2 center = float2 (_DebugCenter.x,_DebugCenter.z) / unity_ObjectToWorld[0][0];
    8.             v.texcoord.xy += center;
    9.  
    10.             float2 worldScaleMatrixAdjusted = unity_ObjectToWorld[0][0] * _Scale;
    11.  
    12.             v.texcoord.xy -= float2(0.5,0.5);
    13.             v.texcoord.xy = mul(scale(worldScaleMatrixAdjusted), v.texcoord.xy);
    14.             v.texcoord.xy += float2(0.5,0.5);        
    15.         }

    Seems to work alright, but from your response this is definitely not the method I should be using to achieve this effect =D. I'm betting it won't render properly over uneven geometry, like you mentioned in your post (I haven't tried that use-case yet).

    If you don't mind me asking, how would you go about rendering some detailed indicator onto geometry (in a similar fashion to mobas or starcraft), e.g. something like this:

    targeting.jpg

    I initially rendered the circle directly onto the geometry using vector maths following the answer on this thread: https://forum.unity.com/threads/how-to-render-an-action-radius.430008/ and it worked very well...

    My problem with this approach was that it was very difficult to render details (like the above textures) for some nice fidelity/visual polish to the indicator... ergo I was planning to scale a texture and overlay it onto the rendered shape/geometry to get some juicy details on there.

    Thanks for the help, as well as your previous reply!
     
    Last edited: Apr 27, 2020
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    So, the funny thing is the method you linked to ... it's main advantage is that you can have it show up on "juicy details", which most methods won't work on! The way that works is by having all objects in the game that you want to be able to receive these projections use shaders that will take them.

    However that's not what the above screenshot is doing. That's probably using a deferred decal setup. You can tell because it's rendering on top of the transparent grass but not conforming to it, meaning it's rendering after the rest of the scene has rendered and using the scene's depth buffer to project onto. This is a little more complicated to use just because it requires calculating the world position from the camera depth texture. You can search for deferred decals and you'll find plenty of info on that.
     
    kirbygc00 likes this.
  5. kirbygc00

    kirbygc00

    Joined:
    Apr 4, 2016
    Posts:
    50
    Hey, thanks a ton bgolus!

    Yes, I agree! I may have been a bit unclear! I was hoping to have the shader 'provide' some of those 'juicy details' that weren't originally a part of the material it was rendering onto. For example: having a flat color for the ground normally, but when conditions are correct, rendering a secondary color on top of it (not too difficult, have this part working) and additional details (like intricate borders and textural patterns, seems pretty hard and working on it currently). Definitely a "nice-to-have" rather than something essential

    Fascinating! I 100% thought it was via shader!!

    It seems like the unity projector (via certain types of pipelines) has some pre configured support for this stuff as well, which is cool. But I want to get into the nitty gritty, so I think I will look into how to write my own deferred projection system... Hopefully via shaders *fingers crossed* hope it's not too large a lift! Haha

    Yeah, it's really cool to learn about alternative approaches to achieve this same effect. As well as the pros + cons.

    Once again thanks for sharing all the knowledge. This was super helpful!
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Yeah, totally doable. The above technique is based on world space UVs. Once you have that you can project any kind of texture at any kind of scale you want. Just look up "world space UVs" and you'll find a lot of examples, but it's as straight forward as "use the world space position as texture UVs". They're just using the world space position to calculate a range on top of that.

    No, not that.

    The built in Projector component is terrible. It really only works on fully opaque objects. Nothing alpha tested or transparent in any way can properly receive projections from it. There's a reason why it's so hidden.

    The HDRP has a "decal projector" which is more of what you want, but that only works with the HDRP. There are a number of assets on the store as well as free example projects for deferred decals out there. Many of which work even when using forward rendering and don't require the deferred rendering path to be used. That's what you want.
     
  7. kirbygc00

    kirbygc00

    Joined:
    Apr 4, 2016
    Posts:
    50
    Great!! Glad to hear it is so simple + well documented

    Yes! I did not know about the shortcomings of the built in projector system, seems like my attempts to avoid it were well founded, lol.

    I actually assumed the HDRP Decal Projector was using the same tech as the default Projector (since they both have projector in their name). Good to know that the HDRP Decal Projector is not a garbage fire ! Lol