1
votes

I'm attempting to write a simple wave-like shader in Unity 2017.1.0f3 using the sin function, however it's all an undefined one color shape without redefining the normals so it can get the shading right. However despite my maths I can't seem to get these normals to look right, and as you can see in the GIF it's all super messed up.

The Wave

So here's what I'm doing:

void vert(inout appdata_full v, out Input o)
{
    UNITY_INITIALIZE_OUTPUT(Input, o);

    //Just basing the height of the wave on distance from the center and time

    half offsetvert = o.offsetVert = ((v.vertex.x*v.vertex.x) + (v.vertex.z * v.vertex.z))*100;//The 100 is to compensate for the massive scaling of the object
    half value = _Scale * sin(-_Time.w * _Speed + offsetvert * _Frequency)/100;

    v.vertex.y += value;
    o.pos = v.vertex.xyz;

}

// 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

void surf (Input IN, inout SurfaceOutputStandard o) 
{

    //Calculate new normals

    //Refer to MATH (1) for how I'm getting the y
    float3 norm = (0,sqrt(1/(1+1/(-100/(_Scale*_Frequency*cos(_Time.w * _Speed + IN.offsetVert * _Frequency))))),0);

    //Refer to Math (2) for how I'm getting the x and z
    float derrivative = _Scale*_Frequency*cos(-_Time.w * _Speed + IN.offsetVert * _Frequency)/100;
    float3 norm = (0,sqrt(1/(1+1/(-1/(derrivative)))),0);
    float remaining = 1 - pow(norm.y,2);
    norm.x = sqrt(remaining/(1 + IN.pos.z*IN.pos.z/(IN.pos.x*IN.pos.x)));
    norm.z = sqrt(1-norm.y*norm.y-norm.x*norm.x);

    //Assume this is facing away from the center
    if (IN.pos.z<0)
        norm.z = -norm.z;
    if (IN.pos.x<0)
        norm.x = -norm.x;

    //Flip the direction if necessary
    if (derrivative > 0){
        norm.x = -norm.x;
        norm.z = -norm.z;
    }

    norm.y = abs(norm.y);
    norm = normalize(norm);//Just to be safe

    o.Albedo = _Color.rgb;

    // Metallic and smoothness come from slider variables
    o.Metallic = _Metallic;
    o.Smoothness = _Glossiness;
    o.Alpha = c.a;
    o.Normal.xyz = norm;
}

MATH 1 If the y as a function of distance is

y = (scale/100)sin(time.w * speed + distance * frequency)

then

dy/d(distance) = (scale/100) * frequency * cos(time.w * speed + distance * frequency)

making the gradient of the normal of

y/(some x and z direction) -100/(scale * frequency * cos(time.w * speed + distance * frequency)).

We also know that

(y component)^2 + (some xz component)^2 = 1,

where

(y component)/(some xz component) = the normal gradient defined.

Solving these two simultaneous equations we get

y component = sqrt(1/(1+1/(gradient^2)))

MATH 2 We know that

(x component)/(z component) = (x position)/(z position)

and, by Pythagoras, that

(x component)^2 + (z component)^2 = 1 - (y component)^2

and solving these simultaneous equations we get

x component = sqrt((1 - (y component)^2)/(1 + (z position / x position)^2))

We can then get the z component through Pythagoras.

Please, let me know if you figure out what's wrong :)

1

1 Answers

0
votes

Why are you calculating the normals in the surface function? This is done per fragment and will be very inefficient. Why not just calculate the normal in the vertex function?

What i would do is to do the same calculation as for the vertex offset, except once again for two other points which are offset in the X and Y direction from the vertex - then you use the cross product of the vectors between them and the offset vertex to get the normal.

Let's say that you have the offset moved to its own function, which takes the coordinates as parameter. Then you could do this:

float3 offsetPos = VertexOffset(v.vertex.xy)
float3 offsetPosX = offsetPos - VertexOffset(v.vertex.xy + float2(0.1, 0))
float3 offsetPosY = offsetPos - VertexOffset(v.vertex.xy + float2(0, 0.1))

v.vertex.xyz = offsetPos
v.normal.xyz = cross(normalize(offsetPosX), normalize(offsetPosY))