# How to simulate a liquid like "The Cook" or "Bake it"?

I want to make a simple liquid something like these gifs:

I will explain the concept in shadertoy then I will convert it to Unity because learning with shadertoy is easier.

Shadertoy uses multiple passes, one per "Buffer". As the name indicates, this passes store the results in a buffer. A buffer is just a texture. I am sure Unity will let you render to textures too.

# Simple Liquid

## Buffer1: Painting

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
fragColor = vec4(distance(iMouse.xy, fragCoord) < 10.0);
fragColor += texture(iChannel0, fragCoord.xy / iResolution.xy);
}


## Buffer 2: Scaling Everything From Buffer 1

float s = 0.;

vec4 Move(vec2 U,vec2 dir) {

// smoothing gives a circle
s += 1./ length(dir);

// using texelFetch ( if speed always refer to an integer number of pixels ) would make your life a bit easier
vec4 result = texelFetch( iChannel1, ivec2(U+dir) , 0 );

result /= length(dir);

return result;
}

void mainImage( out vec4 O, vec2 U )
{
O = vec4(0);

// with 8-neighborhood you have square
O += Move( U, vec2( 1, 0) )
+  Move( U, vec2(-1, 0) )
+  Move( U, vec2( 0, 1) )
+  Move( U, vec2( 0,-1) )
+  Move( U, vec2( 1, 1) )
+  Move( U, vec2( 1,-1) )
+  Move( U, vec2(-1, 1) )
+  Move( U, vec2(-1,-1) );

O /= s;

O += texelFetch(iChannel0, ivec2(U), 0);
}


# Normal Calculation Using dFdx and dFdy

dFdx and dFdy return the partial derivative of expression p in x and y, respectively. Deviatives are calculated using local differencing. Expressions that imply higher order derivatives such as dFdx(dFdx(n)) have undefined results, as do mixed-order derivatives such as dFdx(dFdy(n)). It is assumed that the expression p is continuous and therefore, expressions evaluated via non-uniform control flow may be undefined. https://docs.gl/el3/dFdx

# Calculating Diffuse

diffuse = max(dot(N, L)  + 1., 0.);


# Calculating Specular

specular = clamp(dot(reflect(L, N),vec3(0, 0, -1)), 0., 1.0);


# Final Result

1. Main Camera -> Position (0,0,0)
2. 3D Object -> Quad -> Position (0,0,1)
4. Use left mouse to paint

# LiquidPaint script:

using UnityEngine;

public class LiquidPaint : MonoBehaviour
{
public int Resolution = 1024;
Material material;
RenderTexture RTA1, RTA2, RTB1, RTB2;
bool swap = true;

void Blit(RenderTexture source, RenderTexture destination, Material mat, string name, int pass)
{
RenderTexture.active = destination;
mat.SetTexture(name, source);
GL.PushMatrix();
GL.invertCulling = true;
mat.SetPass(pass);
GL.MultiTexCoord2(0, 0.0f, 0.0f);
GL.Vertex3(0.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 0.0f);
GL.Vertex3(1.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 1.0f);
GL.Vertex3(1.0f, 1.0f, 0.0f);
GL.MultiTexCoord2(0, 0.0f, 1.0f);
GL.Vertex3(0.0f, 1.0f, 0.0f);
GL.End();
GL.invertCulling = false;
GL.PopMatrix();
}

void Start ()
{
RTA1 = new RenderTexture(Resolution, Resolution, 0, RenderTextureFormat.ARGBFloat);  //buffer must be floating point RT
RTA2 = new RenderTexture(Resolution, Resolution, 0, RenderTextureFormat.ARGBFloat);  //buffer must be floating point RT
RTB1 = new RenderTexture(Resolution, Resolution, 0, RenderTextureFormat.ARGBFloat);  //buffer must be floating point RT
RTB2 = new RenderTexture(Resolution, Resolution, 0, RenderTextureFormat.ARGBFloat);  //buffer must be floating point RT
GetComponent<Renderer>().material = material;
}

void Update ()
{
RaycastHit hit;
if (Input.GetMouseButton(0))
{
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition) , out hit))
material.SetVector("iMouse", new Vector4(
hit.textureCoord.x * Resolution, hit.textureCoord.y * Resolution, 1.0f, 1.0f));
}
else
{
material.SetVector("iMouse", new Vector4(-1000.0f, -1000.0f, -1.0f, -1.0f));
}

material.SetInt("iFrame",Time.frameCount);
material.SetVector("iResolution", new Vector4(Resolution,Resolution,0.0f,0.0f));

if (swap)
{
material.SetTexture("_BufferA", RTA1);
Blit(RTA1, RTA2, material, "_BufferA", 0);
material.SetTexture("_BufferA", RTA2);

material.SetTexture("_BufferB", RTB1);
Blit(RTB1, RTB2, material, "_BufferB", 1);
material.SetTexture("_BufferB", RTB2);
}
else
{
material.SetTexture("_BufferA", RTA2);
Blit(RTA2, RTA1, material, "_BufferA", 0);
material.SetTexture("_BufferA", RTA1);

material.SetTexture("_BufferB", RTB2);
Blit(RTB2, RTB1, material, "_BufferB", 1);
material.SetTexture("_BufferB", RTB1);
}

swap = !swap;
}

void OnDestroy ()
{
RTA1.Release();
RTA2.Release();
RTB1.Release();
RTB2.Release();
}
}


Shader "LiquidPaint"
{
{

//------Common-------------------------------------------------------------------------------------------

CGINCLUDE
#pragma vertex VSMain
#pragma fragment PSMain

Texture2D<float4> _BufferA;
Texture2D<float4> _BufferB;
SamplerState _LinearClamp;

int iFrame;
float4 iMouse;
float4 iResolution;

void VSMain (inout float4 vertex:POSITION, inout float2 uv:TEXCOORD0)
{
vertex = UnityObjectToClipPos(vertex);
}

ENDCG

//------Buffer A-------------------------------------------------------------------------------------------

Pass
{
CGPROGRAM

void PSMain (float4 vertex:POSITION, float2 uv:TEXCOORD0, out float4 fragColor:SV_TARGET)
{
float2 fragCoord = uv * iResolution.xy;
fragColor = (distance(iMouse.xy, fragCoord) < 10.0).xxxx;
fragColor += _BufferA.Sample(_LinearClamp, fragCoord.xy / iResolution.xy);
}

ENDCG
}

//------Buffer B-------------------------------------------------------------------------------------------

Pass
{
CGPROGRAM

#define Move(U, dir) _BufferB.Load( int3(U + dir, 0) )

void PSMain (float4 vertex:POSITION, float2 uv:TEXCOORD0, out float4 O:SV_TARGET)
{
float2 U = uv * iResolution.xy;
O = float4(0,0,0,0);
float s = 0.;
for (int i=0; i<9; i+= i==3 ? 2 : 1 )
{
float2 D = float2( i%3-1, i/3-1);
O +=  Move( U, D ) / length(D);
s += 1./ length(D);
}
O /= s;
O += _BufferA.Load( int3(U, 0) );
}

ENDCG
}

//------Image-------------------------------------------------------------------------------------------

Pass
{
CGPROGRAM

float diffuse;
float specular;

void calculateLighting(float2 uv)
{
float l = length(_BufferB.Sample(_LinearClamp, uv).xyz);
float dx=ddx(l)*iResolution.x;
float dy=ddy(l)*iResolution.y;

// Calculating Normal by dx and dy
float3 N =normalize(float3(dx,dy,100.0));

// Light Direction
float3 L = normalize(float3(1.0, 1.0, 2.0));

// Calculating Diffuse
diffuse = max(dot(N, L)  + 1., 0.);

// Calculating Specular
specular = clamp(dot(reflect(L, N),float3(0, 0, -1)), 0., 1.0);

specular = pow(specular, 12.0);
}

void PSMain (float4 vertex:POSITION, float2 uv:TEXCOORD0, out float4 fragColor:SV_TARGET)
{
float2 fragCoord = uv * iResolution.xy;

// Normalized pixel coordinates (from 0 to 1)
calculateLighting(uv);

// Get Liquid From Buffer B
float3 liquid = _BufferB.Sample(_LinearClamp, uv).rgb;

// Normalizing Liquid
liquid = clamp(liquid,0.,0.5);

// Cream Color
float3 color = float3(1.,0.99,0.81);

float3 finalColor = liquid * color * diffuse + specular;

fragColor = float4(finalColor, 1.0);
}

ENDCG
}

//-------------------------------------------------------------------------------------------

}
}