Star
별자리 애니메이션 를 만들어본다.
별자리 렌더링을 하기 위해서 다음 과정을 진행한다.
1> 화면의 구간을 여러개로 나누고 나눈 구간마다 공이 돌아다니는 씬을 만들어 본다.
화면의 중심을 원점으로 옮기고 uv 값에 특정 수를 곱해주고 fract를 시켜주면
특정 수 만큼 패턴이 반복이된다.
예를 들어 5를 곱하게 되면 uv 값은 0~5의 범위를 가지게 된다.
fract를 시켜주면 0.0 ~ 1.0의 구간이 5개가 생기므로 반복된 색상의 패턴이 나오게 된다.
.5를 빼주어서 반복된 패턴의 중심도 좌측하단에서 원점으로 바꾸어 준다.
vec2 uv = (fragCoord - 0.5*iResolution.xy)/iResolution.y;
vec2 repeat = vec2(10);
vec2 st = fract(uv * repeat) - .5;
각 좌표에서 거리를 구해고 특정값보다 작으면 하얀색으로 원을 그려본다.
반복되는 영역을 정확하게 알기 위해 구분하는 영역도 렌더링 해준다.
float f = smoothstep(0.1, 0.05, length(st2));
if (st.x < -.45 || st.y > .45) outline = 1.;
col = vec3 * f + outline * vec3(1,0,0);
여기서 원이 움직이는 애니메이션을 해야 한다.
각 구역마다 동일한 움직임을 가져야 하기 때문에, 각 구역마다 동일한 값을 가지기 위한
id 값을 생성한다.
floor를 하게되면 0~1 영역 해당하는 픽셀은-> 0, 1~2 영역 해당하는 픽셀은 -> 1을 얻을 수 있다.
위 값을 해쉬 함수를 사용하여 direction을 얻는다.
vec2 getPos(vec2 id, vec2 st) {
vec2 r = N22(id);
vec2 offset = cos(r * iTime) * .4;
return st + offset;
}
fun mainImage(..) {
vec2 st = fract(uv * repeat) - .5;
vec2 id = floor(uv * repeat);
vec2 st2 = getPos(id, st);
}
st 값은 각 영역의 중심점인 흰 영역에 해당되며,
offset 값은 영역마다 동일한 id 값으로 랜덤 좌표값을 얻고, cos(..) * .4 곱하여
원점 기준으로 반지름 -.4 ~ .4의 영역의 값을 계산한다.
흰 점은 원점 중심이고, 원이 포함되어 있는 영역은 -.5 ~ .5 이니,
원은 빨간 라인을 벗어나지 않는다.
1> 번 코드
// 구슬이 해당 영역내에서 움직이는 씬
float N12(vec2 p) {
p = fract(p * vec2(3521.2, 2414.5));
p += dot(p, p + 152.5);
return fract(p.x * p.y);
}
vec2 N22(vec2 st) {
float r = N12(st);
return vec2(r, N12(st + r));
}
vec2 getPos(vec2 id, vec2 st) {
vec2 r = N22(id);
vec2 offset = cos(r * iTime) * .4;
return st + offset;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = (fragCoord - 0.5*iResolution.xy)/iResolution.y;
vec3 col = vec3(0);
vec2 repeat = vec2(10);
vec2 st = fract(uv * repeat) - .5;
vec2 id = floor(uv * repeat);
vec2 st2 = getPos(id, st);
// length(st)는 빨간사각형 안의 내부 점하나!!
// float f = smoothstep(0.1, 0.05, length(st));
float f = smoothstep(0.1, 0.05, length(st2));
float outline = 0.;
if (st.x < -.45 || st.y > .45) outline = 1.;
col = f * vec3(1);
col += outline * vec3(1,0,0);
col = st *vec3(1);
//col = N12(uv) * vec3(1.); // test
fragColor = vec4(col, 1.0);
}
2> 이제 움직이는 원과 원을 연결하는 선을 렌더링 한다.
선을 렌더링하는 코드는 다음과 같다.
float getDist(vec2 p, vec2 a, vec2 b) {
vec2 ap = p - a;
vec2 ab = b - a;
float t = dot(ab, ap) / dot(ab, ab);
t = clamp(t, 0., 1.);
return length(ap - (t * ab));
}
float renderLine(vec2 p, vec2 a, vec2 b, float width) {
float d = getDist(p, a, b);
float f = smoothstep(width, width - width *.1, d);
f *= smoothstep(1.5, .5, length(a - b));
return f;
}
렌더 라인 함수의 의미는 vec2 a -> b 지점까지 선을 그릴때, 선의 width를 의미 한다.
p는 uv 값이다.
렌더 라인의 방식은
세점 (p, a, b) 이 주어졌을때, ab 벡터와 p점의 최단 거리를 구한다.
최단 거리와 width와 비교하여 width보다 작은 최단 거리인 경우 픽셀을 렌더링한다.
최단 거리를 구하는 공식은 내적 공식을 이용하는데 간단히 설명하면
t = ap 벡터를 ab벡터로 정사영 했을때의 비율, t를 먼저 구하고
t * ab벡터의 크기를 하면 p를 ab벡터로 정사영 했을때 얻을 수 있는 p1을 얻을수 있다.
p, p1 벡터의 크기가 최단 거리이다.
선을 렌더링하는 함수를 구했으면 선과 선을 연결하는데,
아래 그림처럼 9개의 영역중에 가운데 영역을 기준으로 인접한 8개의 점을 가져온다.
그리고 가운데 영역에서 8개의 점을 연결한다.
p2 배열에 내 근접 8개의 점에 해당하는 영역을 가져오고
renderLine을 통해 점과 점을 연결한다.
vec2 p2[9];
int p2Index = 0;
for (float i=-1.; i<=1.; i+=1.) {
for (float j=-1.; j<=1.; j+=1.) {
p2[p2Index++] = getPos(id, vec2(i, j));
}
}
for (int i=0; i<9; i++) {
line += renderLine(st, p2[4], p2[i], lw);
vec2 d = (p2[i] - st) * 15.;
float sparkle = 1. / dot(d, d);
line += sparkle * (sin(t + p2[i].x * 10.) * .5 + .5);
}
점과 점사이의 거리를 제곱을 한 값을 역수로 사용하여
점과 점사이의 거리가 멀면 렌더링하지 않고, 가까운 선만 렌더링 하는 코드도 넣어준다.
2> 전체코드
// 점과 점이 연결되어 있는 씬
// 점과 점이 연결부분이 반짝이는 것
float N12(vec2 p) {
p = fract(p * vec2(243.21, 152.1));
p += dot(p, p + 351.1);
return fract(p.x * p.y);
}
vec2 N22(vec2 st) {
float r = N12(st);
return vec2(r, N12(st + r));
}
float getDist(vec2 p, vec2 a, vec2 b) {
vec2 ap = p - a;
vec2 ab = b - a;
float t = dot(ab, ap) / dot(ab, ab);
t = clamp(t, 0., 1.);
return length(ap - (t * ab));
}
float renderLine(vec2 p, vec2 a, vec2 b, float width) {
float d = getDist(p, a, b);
float line = smoothstep(width * 1.2, width, d);
float alpha = smoothstep(1.5, .8, length(a - b));
return line * alpha;
}
vec2 getPos(vec2 id, vec2 offset) {
vec2 r = N22(id + offset);
vec2 k = cos(r * iTime) * .4;
return k + offset;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = (fragCoord - 0.5*iResolution.xy)/iResolution.y;
vec3 col = vec3(0);
vec2 repeat = vec2(10);
vec2 st = fract(uv * repeat) - .5;
vec2 id = floor(uv * repeat);
vec2 p1 = getPos(id, st);
vec2 p2[9];
int p2Index = 0;
for (float i=-1.; i<=1.; i+=1.) {
for (float j=-1.; j<=1.; j+=1.) {
p2[p2Index++] = getPos(id, vec2(i, j));
}
}
float line = 0.;
float lw= 0.03;
float t = iTime * 5.;
for (int i=0; i<9; i++) {
line += renderLine(st, p2[4], p2[i], lw);
vec2 d = (p2[i] - st) * 15.;
float sparkle = 1. / dot(d, d);
line += sparkle * (sin(t + p2[i].x * 10.) * .5 + .5);
}
line += renderLine(st, p2[1], p2[3], lw);
line += renderLine(st, p2[1], p2[5], lw);
line += renderLine(st, p2[3], p2[7], lw);
line += renderLine(st, p2[5], p2[7], lw);
col = line * vec3(1);
//col = vec3(1) * renderLine(uv, vec2(0.), vec2(.2), .1);
fragColor = vec4(col, 1.0);
}
3> 지금까지 렌더링 한 코드를 레이어 함수로 만들고 레이어를 여러개를 사이즈 다르게 렌더링하자.
여러개를 겹쳐서 렌더링 할때 작은 사이즈 곱하게 되면 멀어보이고,
큰 수를 곱하게 되면 가까워 진다.
사이즈 애니메이션을 사용하여 마치 별자리가 앞으로 이동되는 것처럼 표현이 된다.
// 레이어를 만들고 파라메터의 변수를 의미익히자.
// 사이즈가 커지게되는 애니메이션 실행하면 앞으로 오는것처럼 보임.
// 여러개의 레이어를 만들고 사이즈를 커지도록 하자.
float N12(vec2 p) {
p = fract(p * vec2(243.21, 152.1));
p += dot(p, p + 351.1);
return fract(p.x * p.y);
}
vec2 N22(vec2 st) {
float r = N12(st);
return vec2(r, N12(st + r));
}
float getDist(vec2 p, vec2 a, vec2 b) {
vec2 ap = p - a;
vec2 ab = b - a;
float t = dot(ab, ap) / dot(ab, ab);
t = clamp(t, 0., 1.);
return length(ap - (t * ab));
}
float renderLine(vec2 p, vec2 a, vec2 b, float width) {
float d = getDist(p, a, b);
float line = smoothstep(width * 1.5, width, d);
float dist2 = length(a - b);
float alpha = smoothstep(1.2, .5, dist2);
line = line * alpha * .5;
//line += smoothstep(.05, .03, abs(dist2 - 3.75));
return line;
}
vec2 getPos(vec2 id, vec2 offset) {
vec2 r = N22(id + offset);
vec2 k = cos(r * iTime) * .4;
return k + offset;
}
float layer(vec2 uv) {
vec2 repeat = vec2(6);
vec2 st = fract(uv * repeat) - .5;
vec2 id = floor(uv * repeat);
vec2 p1 = getPos(id, st);
vec2 p2[9];
int p2Index = 0;
for (float i=-1.; i<=1.; i+=1.) {
for (float j=-1.; j<=1.; j+=1.) {
p2[p2Index++] = getPos(id, vec2(i, j));
}
}
float line = 0.;
float lw= 0.02;
float t = iTime * 5.;
for (int i=0; i<9; i++) {
line += renderLine(st, p2[4], p2[i], lw);
vec2 d = (p2[i] - st) * 25.;
float sparkle = 1. / dot(d, d);
line += sparkle * (sin(t + fract(p2[i].x) * 10.) * .5 + .5);
}
line += renderLine(st, p2[1], p2[3], lw);
line += renderLine(st, p2[1], p2[5], lw);
line += renderLine(st, p2[3], p2[7], lw);
line += renderLine(st, p2[5], p2[7], lw);
return line;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = (fragCoord - 0.5*iResolution.xy)/iResolution.y;
vec3 col = vec3(0);
float line = 0.;
float t = iTime * .05;
for (float i=0.; i<=1.; i+= 1./4.) {
float z = fract(i + t);
float size = mix(1.5, .5, z);
line += layer(uv * size + i * 10.);
}
col = line * vec3(1);
//col = vec3(1) * renderLine(uv, vec2(0.), vec2(.2), .1);
fragColor = vec4(col, 1.0);
}
4> 컬러 및 효과를 넣자.
Gradient, Fadeout, 컬러, 반짝임 효과를 넣자.
vec3 base = sin(period * vec3(.345, .456, .657)) * .4 + .6;
컬러의 경우 iTime 주기를 곱해주고 0~1 영역으로 만들어 준뒤, 밝은 영역의 색감을 위해 .4를 곱하고 .6을 더해준다.
해당 컬러는 .6~1.0 의 값을 만들어준다.
for (float i=0.; i<=1.; i += 1./4.) {
float z = fract(i + t);
float fade = smoothstep(0., .5, z) * smoothstep(1., .8, z);
// col * fade
}
smoothstep을 사용하여 fade in out 변수를 만들어 준다.
Fade in : 시간이 0~.5 까지는 fade 값이 1에 가까워 지고,
Fade out : 시간이 .8 ~ 1.0 에서는 fade가 0에 가까워 진다.
두 값을 곱하여 fade in out 변수 값을 만들어 준다.
최종 코드 >
// 0. Layer
// 1. Fade in Fade out
// 3, roatate uv, uv mouse effect
// 4. 별자리 color 작업, 별자리 그레디언트 작업
float N12(vec2 p) {
p = fract(p * vec2(243.21, 152.1));
p += dot(p, p + 351.1);
return fract(p.x * p.y);
}
vec2 N22(vec2 st) {
float r = N12(st);
return vec2(r, N12(st + r));
}
float getDist(vec2 p, vec2 a, vec2 b) {
vec2 ap = p - a;
vec2 ab = b - a;
float t = dot(ab, ap) / dot(ab, ab);
t = clamp(t, 0., 1.);
return length(ap - (t * ab));
}
float renderLine(vec2 p, vec2 a, vec2 b, float width) {
float d = getDist(p, a, b);
float line = smoothstep(width, width * .9, d);
float dist2 = length(a - b);
float alpha = smoothstep(1., .5, dist2);
line *= alpha;
return line;
}
vec2 getPos(vec2 id, vec2 offset) {
vec2 r = N22(id + offset);
vec2 k = cos(r * iTime) * .4;
return k + offset;
}
float layer(vec2 uv) {
vec2 repeat = vec2(7.);
vec2 st = fract(uv * repeat) - .5;
vec2 id = floor(uv * repeat);
vec2 p1 = getPos(id, st);
vec2 p2[9];
int p2Index = 0;
for (float i=-1.; i<=1.; i+=1.) {
for (float j=-1.; j<=1.; j+=1.) {
p2[p2Index++] = getPos(id, vec2(i, j));
}
}
float line = 0.;
float lw= 0.015;
float t = iTime * 5.;
for (int i=0; i<9; i++) {
line += renderLine(st, p2[4], p2[i], lw);
vec2 d = (p2[i] - st) * 25.;
float sparkle = 1. / dot(d, d);
line += sparkle * (sin(t + fract(p2[i].x) * 10.) * .5 + .5);
}
line += renderLine(st, p2[1], p2[3], lw);
line += renderLine(st, p2[1], p2[5], lw);
line += renderLine(st, p2[3], p2[7], lw);
line += renderLine(st, p2[5], p2[7], lw);
return line;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = (fragCoord - 0.5*iResolution.xy)/iResolution.y;
vec2 mouse = (iMouse.xy / iResolution.xy) - .5;
vec3 col = vec3(0);
float t = iTime * .1;
float gradient = -uv.y;
// rotation
float s = sin(t);
float c = cos(t);
mat2 rot = mat2(c, -s, s, c);
uv *= rot;
mouse *= rot;
float line = 0.;
for (float i=0.; i<=1.; i += 1./4.) {
float z = fract(i + t);
float size = mix(1.5, .15, z);// 10.
float fade = smoothstep(0., .5, z) * smoothstep(1., .8, z);
vec2 uv2 = (uv * size + i * 10. - mouse);
line += layer(uv2) * fade;
}
// color
float period = iTime * 15.;
vec3 base = sin(period * vec3(.345, .456, .657)) * .4 + .6;
float fft = texelFetch(iChannel0, ivec2(.7, 0), 0).x;
gradient *= fft * 2. ;
col = line * base;
col += gradient * base;
fragColor = vec4(col, 1.0);
}