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

Shader on Quad following the object Rotation

Discussion in 'Shaders' started by Kaztec37, Dec 11, 2018.

  1. Kaztec37

    Kaztec37

    Joined:
    Oct 3, 2018
    Posts:
    2
    Hi There.

    I'm working on a heat map implementation for a Unity project I'm working on

    The short of it is I have largely followed the basic setup here:

    https://www.alanzucconi.com/2016/01/27/arrays-shaders-heatmaps-in-unity3d/

    This has been updated to work in more recent versions of Unity and I've modified it such that the heatmap is generated from points generated via Raycasts. For the most part it works fine and I have an implementation that collects and generates a heatmap just fine as shown here:



    There is however one problem so far, the points generated by the shader does not follow the quads rotation as shown here:



    Even if the Quad is rotated initially on point capture it still results in the same effect





    I'm not sure how to make the shader follow the object rotation, or even if I am approaching this from the right direction. I've tried many solutions picked up through this forum and google but nothing seems to work. I'm more or less a complete noob when it comes to shaders, thought I've picked up some pieces here and there. If anyone could lend a hand I'd be mighty appreciative

    Below is the shader code I'm currently using: (Apologies if it's a little messy)


    Code (CSharp):
    1. Shader "Hidden/Heatmaptest" {
    2.     Properties{
    3.         _HeatTex("Texture", 2D) = "white" {}
    4.         _QuadTransform("Quad Transformation Vector", Vector) = (0,0,0,0)
    5.     }
    6.         SubShader{
    7.         Tags{ "Queue" = "Transparent" "DisableBatching" = "True"}
    8.         Blend SrcAlpha OneMinusSrcAlpha
    9.  
    10.         Pass{
    11.         CGPROGRAM
    12.         #pragma vertex vert          
    13.         #pragma fragment frag
    14.         #include "UnityCG.cginc"
    15.  
    16.  
    17.         float4 _QuadTransform;
    18.  
    19.  
    20.         struct vertInput {
    21.             float4 pos : POSITION;
    22.             float2 texcoord : TEXCOORD0;
    23.         };
    24.  
    25.         struct vertOutput {
    26.             float4 pos : SV_POSITION;
    27.             fixed3 worldPos : TEXCOORD1;
    28.             float2 uv : TEXCOORD0;
    29.         };
    30.  
    31.     vertOutput vert(vertInput v) {
    32.  
    33.         vertOutput o;
    34.         o.pos = UnityObjectToClipPos(v.pos);
    35.         o.worldPos = mul(unity_ObjectToWorld, v.pos);
    36.         o.worldPos.xyz -= _QuadTransform;
    37.  
    38.         return o;
    39.     }
    40.  
    41.  
    42.     uniform int _Points_Length = 0;
    43.     uniform float4 _Points[1000];        // (x, y, z) = position
    44.     uniform float4 _Properties[1000];    // x = radius, y = intensity
    45.  
    46.     sampler2D _HeatTex;
    47.  
    48.     half4 frag(vertOutput output) : COLOR{
    49.        
    50.         half h = 0;
    51.             for (int i = 0; i < _Points_Length; i++)
    52.             {
    53.                
    54.                 half di = distance(output.worldPos, _Points[i].xyz);
    55.  
    56.                 half ri = _Properties[i].x;
    57.                 half hi = 1 - saturate(di / ri);
    58.  
    59.                 h += hi * _Properties[i].y;
    60.             }
    61.  
    62.            
    63.             h = saturate(h);
    64.             half4 color = tex2D(_HeatTex, fixed2(h, 0.5));
    65.             return color;
    66.         }
    67.             ENDCG
    68.         }
    69.     }
    70.             Fallback "Diffuse"
    71. }
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    If you're passing in world space positions and then moving the quad away from those world positions, then what you're seeing is kind of the expected behavior here. The solution is don't use world space positions, but pass in either object space or UV space positions. If your object is always going to be a quad, then UV space would be a good, easy option as the Raycast will automatically return the UV texcoord position of the object it hit:
    https://docs.unity3d.com/ScriptReference/RaycastHit-textureCoord.html

    The other nice thing about using UV space is you only need to pass a float2 worth of data for the position, so you could use a single array to hold the position, radius, and intensity. You also don't need to calculate and pass the world position in the shader.

    If you intend to use this on more complex objects, like a sphere or box, then object space positions would be better. That takes a little more work to use than world space or UV space, just because the raycast hit doesn't provide that for you. For this you'll need to transform the world space positions you get from the raycast into object space, or more specifically local mesh space using the mesh renderer's worldToLocalMatrix.
    https://docs.unity3d.com/ScriptReference/Renderer-worldToLocalMatrix.html
    https://docs.unity3d.com/ScriptReference/Matrix4x4.MultiplyPoint3x4.html

    Then in the shader, instead of worldPos, just pass along a copy of v.vertex.xyz unmodified and do the distance test against that.

    One thing to note, for both object space and UV space the radius may not match the radius you would see using world space. If you want them to match then you'll want to do your distance calculations in world space still. That's relatively complicated to do in UV space, so I'd go the object space route. You'll just need to calculate the world space position of each point in the shader using:
    float3 worldPoint = mul(unity_ObjectToWorld, float4(_Points[i].xyz, 1.0)).xyz;


    Alternatively you could solve all of this in C# instead. Transform and store all of the world space positions into local gameobject space, then on every update set the points array with those points transformed back into world space.
    https://docs.unity3d.com/ScriptReference/Transform.InverseTransformPoint.html
    https://docs.unity3d.com/ScriptReference/Transform.TransformPoint.html

    Code (csharp):
    1. RaycastHit hitInfo;
    2. if (Physics.Raycast(rayStart, rayDir, out hitInfo)
    3. {
    4.   Vector3 objPos = heatMapObject.transform.InverseTransformPoint(hitInfo.point);
    5.   objectSpacePoints.Add(objPos);
    6. }
    7.  
    8. // Update
    9. for(int i=0; i<objectSpacePoints.Count; i++)
    10. {
    11.   points[i] = heatMapObject.transform.TransformPoint(objectSpacePoints[i]);
    12. }
    13. mat.SetVectorArray("_Points", points);
     
    Kaztec37 likes this.
  3. Kaztec37

    Kaztec37

    Joined:
    Oct 3, 2018
    Posts:
    2
    Took a little fiddling, but it worked. Thank you so much for your help.

    Changing the calculations to use v.vertex.xyz fixed the problem

    I had already been calculating and passing points in local space, and experienced some texture stretching on the heatmaps, however after recalculating as world co-ords (Like you suggested) it seems to be fine now.

    Again, thank you so much for all your help
     
  4. hasantastan

    hasantastan

    Joined:
    Apr 24, 2019
    Posts:
    1
    Hi Kaztec37,

    I have a same problem in SteamVR.
    Can you share your scripts or project files?
    Thanx...