[Godot4] 水底气泡的 gdshader

水底气泡的 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);

但是随着前进后退,这个变化还是太奇怪

请添加图片描述
于是放弃

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值