Skip to content

Instantly share code, notes, and snippets.

@touyou
Last active October 20, 2023 09:14
Show Gist options
  • Save touyou/72949ac2b30e2751e1a326aec70fafd6 to your computer and use it in GitHub Desktop.
Save touyou/72949ac2b30e2751e1a326aec70fafd6 to your computer and use it in GitHub Desktop.
雨粒シェーダー Metal / layerEffect版
#include <metal_stdlib>
#include <SwiftUI/SwiftUI_Metal.h>
using namespace metal;
// ref: https://www.shadertoy.com/view/ltffzl
float3 N13(float p) {
// from DAVE HOSKINS
float3 p3 = fract(float3(p) * float3(.1031,.11369,.13787));
p3 += dot(p3, p3.yzx + 19.19);
return fract(float3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x));
}
float4 N14(float t) {
return fract(sin(t*float4(123., 1024., 1456., 264.))*float4(6547., 345., 8799., 1564.));
}
float N(float t) {
return fract(sin(t*12345.564)*7658.76);
}
float Saw(float b, float t) {
return smoothstep(0., b, t)*smoothstep(1., b, t);
}
float2 DropLayer2(float2 uv, float t) {
float2 UV = uv;
uv.y += t*0.75;
float2 a = float2(6., 1.);
float2 grid = a*2.;
float2 id = floor(uv*grid);
float colShift = N(id.x);
uv.y += colShift;
id = floor(uv*grid);
float3 n = N13(id.x*35.2+id.y*2376.1);
float2 st = fract(uv*grid)-float2(.5, 0);
float x = n.x-.5;
float y = UV.y*20.;
float wiggle = sin(y+sin(y));
x += wiggle*(.5-abs(x))*(n.z-.5);
x *= .7;
float ti = fract(t+n.z);
y = (Saw(.85, ti)-.5)*.9+.5;
float2 p = float2(x, y);
float d = length((st-p)*a.yx);
float mainDrop = smoothstep(.4, .0, d);
float r = sqrt(smoothstep(1., y, st.y));
float cd = abs(st.x-x);
float trail = smoothstep(.23*r, .15*r*r, cd);
float trailFront = smoothstep(-.02, .02, st.y-y);
trail *= trailFront*r*r;
y = UV.y;
float trail2 = smoothstep(.2*r, .0, cd);
float droplets = max(0., (sin(y*(1.-y)*120.)-st.y))*trail2*trailFront*n.z;
y = fract(y*10.)+(st.y-.5);
float dd = length(st-float2(x, y));
droplets = smoothstep(.3, 0., dd);
float m = mainDrop+droplets*r*trailFront;
//m += st.x>a.y*.45 || st.y>a.x*.165 ? 1.2 : 0.;
return float2(m, trail);
}
float StaticDrops(float2 uv, float t) {
uv *= 40.;
float2 id = floor(uv);
uv = fract(uv)-.5;
float3 n = N13(id.x*107.45+id.y*3543.654);
float2 p = (n.xy-.5)*.7;
float d = length(uv-p);
float fade = Saw(.025, fract(t+n.z));
float c = smoothstep(.3, 0., d)*fract(n.z*10.)*fade;
return c;
}
float2 Drops(float2 uv, float t, float l0, float l1, float l2) {
float s = StaticDrops(uv, t)*l0;
float2 m1 = DropLayer2(uv, t)*l1;
float2 m2 = DropLayer2(uv*1.85, t)*l2;
float c = s+m1.x+m2.x;
c = smoothstep(.3, 1., c);
return float2(c, max(m1.y*l0, m2.y*l1));
}
[[ stitchable ]] half4 raindrop(float2 position, SwiftUI::Layer layer, float4 bounds, float time, float alpha, float zoom, float value) {
float2 uv = (position - .5 * bounds.zw) / bounds.w * float2(1.0, -1.0) + float2(0.0, 1.0);
float2 UV = position / bounds.zw * float2(1.0, -1.0) + float2(0.0, 1.0);
float T = float(time);
float t = T*.2;
float rainAmount = sin(T*.05)*.3+.7;
float maxBlur = mix(3., 6., rainAmount);
float minBlur = 2.;
uv *= .7 + zoom*.3;
UV = (UV-.5)*(.9 + zoom * .1) + .5;
float staticDrops = smoothstep(-.5, 1., rainAmount)*2.;
float layer1 = smoothstep(.25, .75, rainAmount);
float layer2 = smoothstep(.0, .5, rainAmount);
float2 c = Drops(uv, t, staticDrops, layer1, layer2);
float2 e = float2(.001, 0.);
float cx = Drops(uv+e, t, staticDrops, layer1, layer2).x;
float cy = Drops(uv+e.yx, t, staticDrops, layer1, layer2).x;
float2 n = float2(cx-c.x, cy-c.x); // expensive normals
float focus = mix(maxBlur-c.y, minBlur, smoothstep(.1, .2, c.x));
constexpr sampler textureSampler(address::clamp_to_edge, filter::linear);
half3 col = layer.tex.sample(textureSampler, UV + n, focus).rgb;
return half4(col, half(alpha));
}
import SwiftUI
import Resources
@available(iOS 17.0, *)
public struct RaindropView: View {
@State var value: Double = 1.0
@State var isSetting: Bool = false
let shaderFunction = ShaderFunction(library: .bundle(.module), name: "raindrop")
let start = Date()
public init() {}
public var body: some View {
TimelineView(.animation) { context in
ZStack {
Assets.image("shaderBackground")
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.blur(radius: 10.0)
VStack(spacing: 32) {
ZStack {
Assets.image("shaderBackground")
.resizable()
.scaledToFill()
.frame(width: 1534 / 5.0, height: 2300 / 5.0)
.rotation3DEffect(.degrees(180), axis: (x: 1, y: 0, z: 0))
.rainDropEffect(function: shaderFunction, seconds: context.date.timeIntervalSince1970 - start.timeIntervalSince1970, alpha: 1.0, zoom: -0.4, value: value)
.clipShape(RoundedRectangle(cornerRadius: 16.0))
.shadow(radius: 6.0)
Text("Hello iOS17")
.font(.title)
.bold()
.multilineTextAlignment(.center)
.foregroundStyle(.white)
.padding()
}
if isSetting {
HStack {
Text("\(value)")
Slider(value: $value, in: -3.0...3.0)
}
}
}
}
.onTapGesture {
isSetting.toggle()
}
}
.ignoresSafeArea(.all)
}
}
extension View {
@available(iOS 17.0, *)
func rainDropEffect(function: ShaderFunction, seconds: Double, alpha: Double, zoom: Double, value: Double) -> some View {
self.layerEffect(
Shader(
function: function,
arguments: [.boundingRect, .float(seconds), .float(alpha), .float(zoom), .float(value)]
),
maxSampleOffset: .zero
)
}
}
#Preview {
if #available(iOS 17.0, *) {
RaindropView()
} else {
EmptyView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment