水底气泡的 gdshader
来自 shadertoy 的代码
在这里,我添加了 x 方向和 y 方向上的 uv 位移
但是还是感觉太弱智
shader_type canvas_item;
// Created by greenbird10
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0
uniform float bubble_size = 0.1; // Size of the bubbles
uniform float upward_speed = 0.2; // Speed at which bubbles rise
uniform float movable_x = 0.0;
uniform float movable_y = 0.0;
float hash(vec2 p) {
return 0.5*(
sin(dot(p, vec2(271.319, 413.975)) + 1217.13*p.x*p.y)
) + 0.5;
}
float noise(vec2 p) {
vec2 w = fract(p);
w = w * w * (3.0 - 2.0*w);
p = floor(p);
return mix(
mix(hash(p+vec2(0,0)), hash(p+vec2(1,0)), w.x),
mix(hash(p+vec2(0,1)), hash(p+vec2(1,1)), w.x), w.y);
}
// wave octave inspiration
// Alexander Alekseev - Seascape
// https://www.shadertoy.com/view/Ms2SD1
float map_octave(vec2 uv) {
uv = (uv + noise(uv)) / 2.5;
uv = vec2(uv.x*0.6-uv.y*0.8, uv.x*0.8+uv.y*0.6);
vec2 uvsin = 1.0 - abs(sin(uv));
vec2 uvcos = abs(cos(uv));
uv = mix(uvsin, uvcos, uvsin);
float val = 1.0 - pow(uv.x * uv.y, 0.65);
return val;
}
float map(vec3 p) {
vec2 uv = p.xz + TIME/2.;
float amp = 0.6, freq = 2.0, val = 0.0;
for(int i = 0; i < 3; ++i) {
val += map_octave(uv) * amp;
amp *= 0.3;
uv *= freq;
// uv = vec2(uv.x*0.6-uv.y*0.8, uv.x*0.8+uv.y*0.6);
}
uv = p.xz - 1000. - TIME/2.;
amp = 0.6;
freq = 2.0;
for(int i = 0; i < 3; ++i) {
val += map_octave(uv) * amp;
amp *= 0.3;
uv *= freq;
// uv = vec2(uv.x*0.6-uv.y*0.8, uv.x*0.8+uv.y*0.6);
}
return val + 3.0 - p.y;
}
vec3 getNormal(vec3 p, vec2 resolution) {
float eps = 1./resolution.x;
vec3 px = p + vec3(eps, 0, 0);
vec3 pz = p + vec3(0, 0, eps);
return normalize(vec3(map(px),eps,map(pz)));
}
// raymarch inspiration
// Alexander Alekseev - Seascape
// https://www.shadertoy.com/view/Ms2SD1
float raymarch(vec3 ro, vec3 rd, vec2 resolution, out vec3 outP, out float outT) {
float l = 0., r = 26.;
int i = 0, steps = 16;
float dist = 1000000.;
for(int i = 0; i < steps; ++i) {
float mid = (r+l)/2.;
float mapmid = map(ro + rd*mid);
dist = min(dist, abs(mapmid));
if(mapmid > 0.) {
l = mid;
}
else {
r = mid;
}
if(r - l < 1./resolution.x) break;
}
outP = ro + rd*l;
outT = l;
return dist;
}
float fbm(vec2 n) {
float total = 0.0, amplitude = 1.0;
for (int i = 0; i < 5; i++) {
total += noise(n) * amplitude;
n += n;
amplitude *= 0.4;
}
return total;
}
float lightShafts(vec2 st) {
float angle = -0.2;
vec2 _st = st;
float t = TIME / 16.;
st = vec2(st.x * cos(angle) - st.y * sin(angle),
st.x * sin(angle) + st.y * cos(angle));
float val = fbm(vec2(st.x*2. + 200. + t, st.y/4.));
val += fbm(vec2(st.x*2. + 200. - t, st.y/4.));
val = val / 3.;
float mask = pow(clamp(1.0 - abs(_st.y-0.15), 0., 1.)*0.49 + 0.5, 2.0);
mask *= clamp(1.0 - abs(_st.x+0.2), 0., 1.) * 0.49 + 0.5;
return pow(val*mask, 2.0);
}
vec2 bubble(vec2 uv, float scale) {
if(uv.y > 0.2) return vec2(0.);
float t = TIME/4.;
vec2 st = uv * scale;
vec2 _st = floor(st);
vec2 bias = vec2(0., 4. * sin(_st.x*128. + t));
float mask = smoothstep(0.1, 0.2, -cos(_st.x*128. + t));
st += bias;
vec2 _st_ = floor(st);
st = fract(st);
float warpped_time = mod(noise(_st_) - TIME * upward_speed, 1.0);
float size = noise(_st_)*bubble_size+0.01;
vec2 pos = vec2(noise(vec2(t, _st_.y*64.1)) * 0.8 + 0.1, 0.5);
if(length(st.xy - pos) < size) {
return (st + pos) * vec2(.1, .2) * mask;
}
return vec2(0.);
}
void fragment() {
vec2 resolution = 1.0/SCREEN_PIXEL_SIZE;
vec3 ro = vec3(0.,0.,2.);
vec3 lightPos = vec3(8, 3, -3);
vec3 lightDir = normalize(lightPos - ro);
// adjust uv
vec2 uv = FRAGCOORD.xy;
uv = (-(1.0/SCREEN_PIXEL_SIZE).xy + 2.0*uv) / (1.0/SCREEN_PIXEL_SIZE).y;
uv.y = 1.0 - uv.y; // flip
uv.y *= 0.5;
uv.x *= 0.45;
uv.y -= 0.4;
uv.x += movable_x * 0.1 * sin(TIME);
uv.y += movable_y * 0.1 * cos(TIME);
uv += bubble(uv, 12.) + bubble(uv, 12.) + bubble(uv, 36.) + bubble(uv, 48.); // add bubbles
vec3 rd = normalize(vec3(uv, -1.));
vec3 hitPos;
float hitT;
vec3 seaColor = vec3(11,82,142)/255.;
vec3 color;
// waves
float dist = raymarch(ro, rd, resolution, hitPos, hitT);
float diffuse = dot(getNormal(hitPos, resolution), rd) * 0.5 + 0.5;
color = mix(seaColor, vec3(15,120,152)/255., diffuse);
color += pow(diffuse, 12.0);
// refraction
vec3 ref = normalize(refract(hitPos-lightPos, getNormal(hitPos, resolution), 0.05));
float refraction = clamp(dot(ref, rd), 0., 1.0);
color += vec3(245,250,220)/255. * 0.6 * pow(refraction, 1.5);
vec3 col = vec3(0.);
col = mix(color, seaColor, pow(clamp(0., 1., dist), 0.2)); // glow edge
col += vec3(225,230,200)/255. * lightShafts(uv); // light shafts
// tone map
col = (col*col + sin(col))/vec3(1.8, 1.8, 1.9);
// vignette
// inigo quilez - Stop Motion Fox
// https://www.shadertoy.com/view/3dXGWB
vec2 q = FRAGCOORD.xy / resolution.xy;
col *= 0.7+0.3*pow(16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.2);
COLOR = vec4(col,1.0);
}
添加水平移动的功能
shader_type canvas_item;
// Created by greenbird10
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0
uniform float bubble_size = 0.1; // Size of the bubbles
uniform float upward_speed = 0.2; // Speed at which bubbles rise
uniform float yaw_angle = 0.0;
uniform vec3 camera_pos = vec3(0.,0.,2.);
mat2 rotation_matrix(float angle) {
float s = sin(angle);
float c = cos(angle);
return mat2(vec2(c, -s), vec2(s, c));
}
float hash(vec2 p) {
return 0.5*(
sin(dot(p, vec2(271.319, 413.975)) + 1217.13*p.x*p.y)
) + 0.5;
}
float noise(vec2 p) {
vec2 w = fract(p);
w = w * w * (3.0 - 2.0*w);
p = floor(p);
return mix(
mix(hash(p+vec2(0,0)), hash(p+vec2(1,0)), w.x),
mix(hash(p+vec2(0,1)), hash(p+vec2(1,1)), w.x), w.y);
}
// wave octave inspiration
// Alexander Alekseev - Seascape
// https://www.shadertoy.com/view/Ms2SD1
float map_octave(vec2 uv) {
uv = (uv + noise(uv)) / 2.5;
uv = vec2(uv.x*0.6-uv.y*0.8, uv.x*0.8+uv.y*0.6);
vec2 uvsin = 1.0 - abs(sin(uv));
vec2 uvcos = abs(cos(uv));
uv = mix(uvsin, uvcos, uvsin);
float val = 1.0 - pow(uv.x * uv.y, 0.65);
return val;
}
float map(vec3 p) {
vec2 uv = p.xz + TIME/2.;
float amp = 0.6, freq = 2.0, val = 0.0;
for(int i = 0; i < 3; ++i) {
val += map_octave(uv) * amp;
amp *= 0.3;
uv *= freq;
// uv = vec2(uv.x*0.6-uv.y*0.8, uv.x*0.8+uv.y*0.6);
}
uv = p.xz - 1000. - TIME/2.;
amp = 0.6;
freq = 2.0;
for(int i = 0; i < 3; ++i) {
val += map_octave(uv) * amp;
amp *= 0.3;
uv *= freq;
// uv = vec2(uv.x*0.6-uv.y*0.8, uv.x*0.8+uv.y*0.6);
}
return val + 3.0 - p.y;
}
vec3 getNormal(vec3 p, vec2 resolution) {
float eps = 1./resolution.x;
vec3 px = p + vec3(eps, 0, 0);
vec3 pz = p + vec3(0, 0, eps);
return normalize(vec3(map(px),eps,map(pz)));
}
// raymarch inspiration
// Alexander Alekseev - Seascape
// https://www.shadertoy.com/view/Ms2SD1
float raymarch(vec3 ro, vec3 rd, vec2 resolution, out vec3 outP, out float outT) {
float l = 0., r = 26.;
int i = 0, steps = 16;
float dist = 1000000.;
for(int i = 0; i < steps; ++i) {
float mid = (r+l)/2.;
float mapmid = map(ro + rd*mid);
dist = min(dist, abs(mapmid));
if(mapmid > 0.) {
l = mid;
}
else {
r = mid;
}
if(r - l < 1./resolution.x) break;
}
outP = ro + rd*l;
outT = l;
return dist;
}
float fbm(vec2 n) {
float total = 0.0, amplitude = 1.0;
for (int i = 0; i < 5; i++) {
total += noise(n) * amplitude;
n += n;
amplitude *= 0.4;
}
return total;
}
float lightShafts(vec2 st) {
float angle = -0.2;
vec2 _st = st;
float t = TIME / 16.;
st = vec2(st.x * cos(angle) - st.y * sin(angle),
st.x * sin(angle) + st.y * cos(angle));
float val = fbm(vec2(st.x*2. + 200. + t, st.y/4.));
val += fbm(vec2(st.x*2. + 200. - t, st.y/4.));
val = val / 3.;
float mask = pow(clamp(1.0 - abs(_st.y-0.15), 0., 1.)*0.49 + 0.5, 2.0);
mask *= clamp(1.0 - abs(_st.x+0.2), 0., 1.) * 0.49 + 0.5;
return pow(val*mask, 2.0);
}
vec2 bubble(vec2 uv, float scale) {
if(uv.y > 0.2) return vec2(0.);
float t = TIME/4.;
vec2 st = uv * scale;
vec2 _st = floor(st);
vec2 bias = vec2(0., 4. * sin(_st.x*128. + t));
float mask = smoothstep(0.1, 0.2, -cos(_st.x*128. + t));
st += bias;
vec2 _st_ = floor(st);
st = fract(st);
float warpped_time = mod(noise(_st_) - TIME * upward_speed, 1.0);
float size = noise(_st_)*bubble_size+0.01;
vec2 pos = vec2(noise(vec2(t, _st_.y*64.1)) * 0.8 + 0.1, 0.5);
if(length(st.xy - pos) < size) {
return (st + pos) * vec2(.1, .2) * mask;
}
return vec2(0.);
}
void fragment() {
vec2 resolution = 1.0/SCREEN_PIXEL_SIZE;
vec3 ro = camera_pos;
vec3 lightPos = vec3(8, 3, -3);
vec3 lightDir = normalize(lightPos - ro);
// adjust uv
vec2 uv = FRAGCOORD.xy;
uv = (-(1.0/SCREEN_PIXEL_SIZE).xy + 2.0*uv) / (1.0/SCREEN_PIXEL_SIZE).y;
uv.y = 1.0 - uv.y; // flip
uv.y *= 0.5;
uv.x *= 0.45;
uv.y -= 0.4;
uv = rotation_matrix(yaw_angle) * uv;
uv += bubble(uv, 12.) + bubble(uv, 12.) + bubble(uv, 36.) + bubble(uv, 48.); // add bubbles
vec3 rd = normalize(vec3(uv, -1.));
vec3 hitPos;
float hitT;
vec3 seaColor = vec3(11,82,142)/255.;
vec3 color;
// waves
float dist = raymarch(ro, rd, resolution, hitPos, hitT);
float diffuse = dot(getNormal(hitPos, resolution), rd) * 0.5 + 0.5;
color = mix(seaColor, vec3(15,120,152)/255., diffuse);
color += pow(diffuse, 12.0);
// refraction
vec3 ref = normalize(refract(hitPos-lightPos, getNormal(hitPos, resolution), 0.05));
float refraction = clamp(dot(ref, rd), 0., 1.0);
color += vec3(245,250,220)/255. * 0.6 * pow(refraction, 1.5);
vec3 col = vec3(0.);
col = mix(color, seaColor, pow(clamp(0., 1., dist), 0.2)); // glow edge
col += vec3(225,230,200)/255. * lightShafts(uv); // light shafts
// tone map
col = (col*col + sin(col))/vec3(1.8, 1.8, 1.9);
// vignette
// inigo quilez - Stop Motion Fox
// https://www.shadertoy.com/view/3dXGWB
vec2 q = FRAGCOORD.xy / resolution.xy;
col *= 0.7+0.3*pow(16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.2);
COLOR = vec4(col,1.0);
}
尝试使气泡跟随移动
本来想这么写
用粒子发射器来发射圆
模拟气泡的样子
但是首先这个就要对海底的 canvasitem 采样,才能做出气泡的通透的效果,这个效果我还没搞懂
然后就是我希望整个海底是可以移动的时候,气泡也跟着移动
粒子发射器发射出来的粒子似乎并不方便实现这个功能
虽然改一下 UV 确实可行,但是如果我想让所有粒子整体跟着摄像机动,并且还可以 repeat 的话,那么我感觉……一时间想不到什么方法
尝试修改原 shadertoy 的气泡尺寸
改 bubble_size 到很大的话,会超出矩形框
所以这个矩形框很烦人……理论上来讲,我是在大的 canvasitem 里面画的,不应该是像这样,好像我在单独的 sprite 里面画一样
后面有点看懂了一些
这个气泡的绘制是单纯给 UV 加一个偏移
uv += bubble(uv, bubble_scale); // add bubbles
就能得到类似透射的效果
因为你相当于直接对偏移的地方采样,但是绘制在原来的像素位置
真的很妙啊这个思路
于是我想能不能偏移 uv
uv += vec2(camera_pos.x, camera_pos.y);
但是这样会导致海面出错
于是加在 bubble 的参数里面就好了
uv += bubble(uv + 0.1 * vec2(camera_pos.x, camera_pos.y), bubble_scale); // add bubbles
气泡之所以增大半径之后会变成矩形,是因为他是 floor
取小数位作为一个新的坐标系了
vec2 _st = floor(st);
vec2 bias = vec2(0., 4. * sin(_st.x*128. + t));
float mask = smoothstep(0.1, 0.2, -cos(_st.x*128. + t));
所以气泡半径就是 0.5 也合理
但是现在这个气泡的这个一列上升会很怪
所以我在想能不能从粒子发射器获得粒子的数据呢
查到了别人的问题
https://www.reddit.com/r/godot/comments/rrrty9/is_it_possible_to_get_positions_of_particles_in/?rdt=50664
so,看上去不行,没有开放这个属性
但是源码很简洁
或许我可以尝试改源码,但是这应该会花一些时间
然后我给 bubble 加上了 size 参数,本来以为我可以过渡三种形态的
// add bubbles
uv += bubble(uv + 0.05 * vec2(camera_pos.x, camera_pos.y), mod(0.1 * camera_pos.z + 0.05, 0.3), 30.0);
uv += bubble(uv + 0.1 * vec2(camera_pos.x, camera_pos.y), mod(0.1 * camera_pos.z + 0.1, 0.3), 20.0);
uv += bubble(uv + 0.2 * vec2(camera_pos.x, camera_pos.y), mod(0.1 * camera_pos.z + 0.2, 0.3), 5.0);
但是随着前进后退,这个变化还是太奇怪
于是放弃