And the shader was presented in the answer. I tried to reproduce it, but did not get the effect that its author received.
My try:
Shader "2DWater"
{
Properties
{
_MainTexture("MainTexture", 2D) = "white" {}
_Flow ("Flow", 2D) = "white" {}
_Wave ("Wave", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment Frag
#include "UnityCG.cginc"
// Colour texture / atlas for my tileset.
sampler2D _MainTexture;
// Flowmap texture.
sampler2D _Flow;
// Wave surface texture.
sampler2D _Wave;
// Tiling of the wave pattern texture.
float _WaveDensity = 0.5;
// Scrolling speed for the wave flow.
float _WaveSpeed = 5.0;
// Scaling from my world size of 8x8 tiles
// to the 0...1
float2 inverseFlowmapSize = (float2)(1.0f/8.0f);
struct v2f
{
// Projected position of tile vertex.
float4 vertex : SV_POSITION;
// Tint colour (not used in this effect, but handy to have.
fixed4 color : COLOR;
// UV coordinates of the tile in the tile atlas.
float2 texcoord : TEXCOORD0;
// Worldspace coordinates, used to look up into the flow map.
float2 flowPos : TEXCOORD1;
};
v2f vert(appdata_full IN)
{
v2f OUT;
// Save xy world position into flow UV channel.
OUT.flowPos = mul(unity_ObjectToWorld, IN.vertex).xy;
// Conventional projection & pass-throughs...
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color;
return OUT;
}
float2 WaveAmount(float2 uv, float2 sampleSite) {
// Sample from the flow map texture without any mipmapping/filtering.
// Convert to a vector in the -1...1 range.
float2 flowVector = tex2Dgrad(_Flow, sampleSite * inverseFlowmapSize, 0, 0).xy
* 2.0f - 1.0f;
// Optionally, you can skip this step, and actually encode
// a flow speed into the flow map texture too.
// I just enforce a 1.0 length for consistency without getting fussy.
flowVector = normalize(flowVector);
// I displace the UVs a little for each sample, so that adjacent
// tiles flowing the same direction don't repeat exactly.
float2 waveUV = uv * _WaveDensity + sin((3.3f * sampleSite.xy + sampleSite.yx) * 1.0f);
// Subtract the flow direction scaled by time
// to make the wave pattern scroll this way.
waveUV -= flowVector * _Time * _WaveSpeed;
// I use tex2DGrad here to avoid mipping down
// undesireably near tile boundaries.
float wave = tex2Dgrad(_Wave, waveUV,
ddx(uv) * _WaveDensity, ddy(uv) * _WaveDensity);
// Calculate the squared distance of this flowmap pixel center
// from our drawn position, and use it to fade the flow
// influence smoothly toward 0 as we get further away.
float2 offset = uv - sampleSite;
float fade = 1.0 - saturate(dot(offset, offset));
return float2(wave * fade, fade);
}
fixed4 Frag(v2f IN) : COLOR
{
// Sample the tilemap texture.
fixed4 c = tex2D(_MainTexture, IN.texcoord);
// In my case, I just select the water areas based on
// how blue they are. A more robust method would be
// to encode this into an alpha mask or similar.
float waveBlend = saturate(3.0f * (c.b - 0.4f));
// Skip the water effect if we're not in water.
if(waveBlend == 0.0f)
return c * IN.color;
float2 flowUV = IN.flowPos;
// Clamp to the bottom-left flowmap pixel
// that influences this location.
float2 bottomLeft = floor(flowUV);
// Sum up the wave contributions from the four
// closest flow map pixels.
float2 wave = WaveAmount(flowUV, bottomLeft);
wave += WaveAmount(flowUV, bottomLeft + float2(1, 0));
wave += WaveAmount(flowUV, bottomLeft + float2(1, 1));
wave += WaveAmount(flowUV, bottomLeft + float2(0, 1));
// We store total influence in the y channel,
// so we can divide it out for a weighted average.
wave.x /= wave.y;
// Here I tint the "low" parts a darker blue.
c = lerp(c, c*c + float4(0, 0, 0.05, 0), waveBlend * 0.5f * saturate(1.2f - 4.0f * wave.x));
// Then brighten the peaks.
c += waveBlend * saturate((wave.x - 0.4f) * 20.0f) * 0.1f;
// And finally return the tinted colour.
return c * IN.color;
}
ENDCG
}
}
}
It would help if you would describe what IS working and what is NOT working.
I see an animated image for the effect you want, but your example you’re using different input images, and your image isn’t animated. So … without mindreading, I ahve no idea what you’re saying is wrong?
This shader is not mine. As far as I understand, his task is to make the static image of water to move, but for some reason this does not work as it should. I also understand that the direction of the water flow is set using the Flow texture of 8x8 pixels (field float2 inverseFlowmapSize = (float2) (1.0f / 8.0f); also points to this),
but the movement still doesn’t happen.
I’m not asking you to explain the entire shader, I’m asking you to describe what you expected to see on screen vs what you actually see. This is the first step in fixing a problem - until you do that, you haven’t started.
Also, debugging simple shaders like this one is very easy, you just need to do small edits and see what happens. Unlike most shaders, this is very simple code, 100% of it can be learned from scratch in a couple of hours or less. I would strongly recommend you do some basic tutorials in scripting, so that you feel confident editing the shader line by line to see what happens. This will save you time right now, and save you lots of time in future.
(You don’t need to understand every line of code, but you need to know the basics of how lines of code are executed one after the other, and roughly what they do.)
well the issue is that it doesn’t do anything, water doesn’t scroll.
it has _Time there so something should animate, but it also uses worldspace XY coordinates,
so could be problem with the mesh location, scale of your mesh, or how the UV maps should be in the mesh?
I am at the stage of studying shaders. I read the Cg tutorial and rummaged through the documentation for ShaderLab. But I still don’t understand some points. For example:
wave += WaveAmount(flowUV, bottomLeft + float2(1, 0));
wouldn’t bottomLeft always be 0.0? That is, the lower left corner of the square?
Why is the addition operation bottomLeft + float2 (1, 0) performed in the parameters, if you could just specify float2 (1, 0) and have the same effect?
// Clamp to the bottom-left flowmap pixel
// that influences this location.
float2 bottomLeft = floor(flowUV);
UV’s don’t have to go 0…1, they can go 2 … 3 … 4 … etc. So, one common approach in shaders is to UV-map across an area with - say - UV 0…10, and then you can use floor(UV) to get a repeating 0…1, 0…1, 0…1 … but also get the full 0…1, …2, …3, …4 at the same time (for different parts of your shader).
as far as I understand, the movement should occur on the _Wave texture (the last in the inspector window on the screenshot) using tex2Dgrad(_Wave, waveUV, ddx (uv) * _WaveDensity, ddy (uv) * _WaveDensity);
where waveUV coordinates that are obtained using flowVector * _Time * _WaveSpeed;
but the problem is that I don’t quite understand the line
float2 flowVector = tex2Dgrad (_Flow, sampleSite * inverseFlowmapSize, 0, 0) .xy
My approach is usually to delete big parts of the shader, make a simpler version, and build it up in stages. That way, eventually you find the part that isn’t doing what you expected it to do, and you can fix it.
e.g. on line 122, you could immediately return the value of wave - that will show you if the function that calculates “wave” is even working at all.
Something like:
float4 result = float4(0,0,0,1); // black, fully opaque
result.rg = wave; // assign the wave to the R and G channels in result
return result;
…Or at Line 115, do the same with flowUV, and then with bottomLeft: try returning each of them, see if they look like they have the values you’d expect if they were correct.
I read theoretical material about mipmap, and that tex2D can take a lower quality texture, since the object can be far from the camera.
sampleSite * inverseFlowmapSize
I don’t understand this calculation, since it seems to me that every time
WaveAmount(flowUV, bottomLeft);
will have bottomLeft as 0,0
BUT … even better … try return’ing that variable as the R and G channels, and see what it is across your whole mesh - you can see for yourself. If it’s always 0,0 it’ll be black everywhere (I don’t think it will be though, unless that’s part of the bug)
Try just returning bottomLeft itself - so you can see what actual values it contains (if you return tex2D( …, bottomLeft), then you’re obscuring the value by mixing it with your texture, so you still don’t know what value it has).