Water Ripple Shader

hey I’m trying to make a shader that leaves some parabolic ripple behind (or better in front) an object that gets dragged along the surface.

my two questions are:

  1. how can i get a more smooth result?
  2. how can i dynamically set the length of my array in the shader?

I calculate the ripples in a C# script and try to translate them to my shader.
With some luck i was able to generate the first ripple for now:
3054228--229275--Screen Shot 2017-05-02 at 14.19.12.png

but as you can see it’s very choppy…
I’m very new to Shaders. But i would expect a more smooth displacement.
I’m doing it very rough in the shader so maybe thats the problem, but i have no clue if i can do it differently?

also I’m having a hard time to use _Length as the resolution for my Array.
I read i can use #define _Length but same here no idea how to set the value for this from my script.

the Shader:

Shader "Custom/Parabola" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Size ("Size", float) = 1
        _Speed ("Speed", float) = 1
        _Amp ("Amplitude", float) = 1
        _Length ("Resolution", int) = 1
        //_Points("Points", vector[])
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
       
        CGPROGRAM
// Upgrade NOTE: excluded shader from DX11, OpenGL ES 2.0 because it uses unsized arrays
#pragma exclude_renderers d3d11 gles
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows vertex:vert addshadow

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
       
        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        half _Size;
        half _Speed;
        half _Amp;
        fixed4 _Color;
        int _Length;
        float4 _Points[200];

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_CBUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_CBUFFER_END

        float4 getNewVertPosition(float4 p)
        {
            float3 worldPos = mul(unity_ObjectToWorld, p).xyz;

            for(int i=0; i<_Length; i++)
            {
                if(worldPos.z <= (_Points[i].z+_Size) && worldPos.z >= (_Points[i].z-_Size) &&
                worldPos.x <= (_Points[i].x+_Size) && worldPos.x >= (_Points[i].x-_Size))
                {
                    p.y += _Amp;
                    //v.normal.xyz += log10(sin(v.vertex.y)+_Amp);
                }
            }
            return p;
        }
        void vert( inout appdata_full v )
        {
            float4 vertPosition = getNewVertPosition( v.vertex );
            float4 bitangent = float4( cross( v.normal, v.tangent ), 0 );
            float vertOffset = 0.01;

            float4 v1 = getNewVertPosition( v.vertex + v.tangent * vertOffset );
            float4 v2 = getNewVertPosition( v.vertex + bitangent * vertOffset );

            float4 newTangent = v1 - vertPosition;
            float4 newBitangent = v2 - vertPosition;

            v.normal = cross( newTangent, newBitangent );
            v.vertex = vertPosition;
        }
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

the Script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Parabola : MonoBehaviour {

    public GameObject linePrefab, pointV;

    public float offset, amp, speed;
    public int freq;
    public float directrixLength;
    public int resolution;

    private Vector3 mouse, prev;
    private Vector3 directrix;
    private Vector3 focusPoint, directrixPointMid, directrixPointL, directrixPointR;
    private Vector3 focalLine;
    private Vector3 p;
    private List<List<Vector3>> pointsR = new List<List<Vector3>>();
    private List<LineRenderer> lines = new List<LineRenderer>();
    public Renderer render;

    void Start ()
    {
        render.material.SetInt("_Length", resolution);
        render.material.SetVectorArray("_Points", new Vector4[resolution]);

        prev = mouse;
        int i = 0;
        while(i <= freq)
        {
            GameObject g = Instantiate(linePrefab);
            g.transform.SetParent(transform);
            g.transform.localPosition = Vector3.zero;
            LineRenderer line = g.GetComponent<LineRenderer>();
            line.positionCount = resolution;
            lines.Add(line);
            List<Vector3> lR = new List<Vector3>();
            pointsR.Add(lR);
            i++;
        }
    }
   
    void FixedUpdate ()
    {
        if(Input.GetMouseButton(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if(Physics.Raycast(ray, out hit, Mathf.Infinity, 1<<LayerMask.NameToLayer("Plane")))
            {
                mouse = new Vector3(hit.point.x,0,hit.point.z);
                StopCoroutine(CalculateParabola(mouse));
                StartCoroutine(CalculateParabola(mouse));
                transform.position = mouse;
                transform.forward = -focalLine;
            }
        }
    }
    IEnumerator CalculateParabola(Vector3 v)
    {
        while(prev != v)
        {
            focalLine = prev - v;

            for(int h=0; h<=freq; h++)
            {
                pointsR[h] = new List<Vector3>();
                float s = Vector3.Distance(prev.normalized,v.normalized)*speed;
                for(float i = -directrixLength/2; i<=(directrixLength+1)/2; i+=directrixLength/resolution)
                {
                    p.x = pointV.transform.localPosition.x+(i*(freq-h+Time.fixedDeltaTime*s));
                    p.z = ((amp*(freq-h))*(Time.fixedDeltaTime*s))*-Mathf.Pow(p.x,2)+(1+offset*h);
                    pointsR[h].Add(p);
                }
                lines[h].SetPositions(pointsR[h].ToArray());
                Vector4[] renderPoints = new Vector4[resolution];
                for(int i=0; i<resolution; i++)
                {
                    renderPoints[i] = transform.TransformPoint(pointsR[0][i]);
                }
                render.material.SetVectorArray("_Points", renderPoints);
            }
            yield return new WaitForSeconds(0.1f);
            prev = v;
        }
    }
}

Thx,
J

ok so it’s not possible to dynamically allocate an array :slight_smile: got an answer in an other thread.

trying to get a more smooth result by adjusting the x and z of the verts on the plane.
but why does this work for detecting the verts that should be influenced:

float4 getNewVertPosition(float4 p)
        {
            float3 worldPos = mul(unity_ObjectToWorld, p).xyz;

            for(int i=0; i<_Length; i++)
            {
                if(worldPos.z <= (_Points[i].z+_Size) && worldPos.z >= (_Points[i].z-_Size) &&
                worldPos.x <= (_Points[i].x+_Size) && worldPos.x >= (_Points[i].x-_Size))
                {
                    p.y += _Amp;
                }
            }
            return p;
        }

and this gives me a wrong offset?

float4 getNewVertPosition(float4 p)
        {
            for(int i=0; i<_Length; i++)
            {
                float3 objPos = mul(unity_WorldToObject, _Points[i]).xyz;
               
                if(p.z <= (objPos.z+_Size) && p.z >= (objPos.z-_Size) &&
                p.x <= (objPos.x+_Size) && p.x >= (objPos.x-_Size))
                {
                    p.y += _Amp;
                }
            }
            return p;
        }

Am i understanding the WorldToObject function wrong? i thought it just did the opposite of ObjectToWorld…

ok got the WorldToObject working by subtracting the plane’s transform from _Point*,* strange that it needs that and ObjectToWorld doesn’t?
anyway I’m thinking i should switch to a compute shader maybe… any advice?