Three.jsシェーダー・(平面ジオメトリでの実装・3D)

目次
  1. カメラ
  2. 球体とレイとの交差

カメラ

3Dの情報を2Dに変換して描画するにはカメラが必要
ro( カメラ(レイの原点))とrd(レイの方向)を定義する
3D空間におけるカメラから特定の点(p)までの距離を計算

ro( カメラ(レイの原点))とrd(レイの方向)を定義について

カメラが原点の場合
スクリーンの位置: スクリーンがカメラから少し離れた位置(例えば z = 1.0 )にあると仮定
レイの方向:レイは常にカメラから z = 1.0 の平面に向かって直線的に進む

vec3 ro = vec3(0.0);
vec3 rd = normalize(vec3(uv, 1.0));

カメラの位置が x や y で 0.0 以外の場合
スクリーンの位置:スクリーンが z = 0.0 の平面上にあると仮定
レイの方向ベクトルの計算:カメラの位置 ro から見たスクリーン上の各点までの実際の方向を求めるために、vec3(uv, 0.0) から ro を引いて計算

 // レイの起点: z軸上でカメラを少し後ろに
 vec3 ro = vec3(0.5, 0.0, -1.0);
 vec3 rd = normalize(vec3(uv, 0.0)-ro);

3D空間におけるカメラから特定の点(p)までの距離を計算

//カメラから無限に伸びる直線と点 p の距離が知りたい場合
//点pとレイroとの最短距離を計算する(両方向)
float DistLine(vec3 ro, vec3 rd, vec3 p) {
	return length(cross(p-ro, rd))/length(rd);
}

//特定の方向に向かうレイの距離を知りたい場合
//点pとレイroとの最短距離を計算する(特定の方向)
float DistRay(vec3 ro, vec3 rd, vec3 p) {
    vec3 fromOriginToPoint = p - ro;  // レイの原点から点pへのベクトル
    float projectionLength = dot(fromOriginToPoint, rd);  // レイの方向に対する点pの射影の長さ
    vec3 closestPoint;
    if (projectionLength < 0.0) {
        // レイの起点より前方(逆方向)にある場合、最近点はレイの起点
        closestPoint = ro;
    } else {
        // それ以外の場合、最近点はレイ上の計算された位置
        closestPoint = ro + projectionLength * rd;
    }
    return length(p - closestPoint);  // 最近点から点pまでの距離を返す
}

特定の点(p)を動かす

void main() {
 vec2 uv = (vUv * 2.0 - 1.0) * vec2(uResolution.x / uResolution.y, 1.0);
 // レイの起点: z軸上でカメラを少し後ろに
 vec3 ro = vec3(0.5, 0.0, -1.0);
 // レイの方向: スクリーンがz=0.0の平面上にあると仮定してカメラから見たスクリーン上の各点までの実際の方向
 vec3 rd = normalize(vec3(uv, 0.0)-ro);

 vec3 p = vec3(0.0, 0.0, 0.2 + sin(uTime));
//点pとレイroとの最短距離
 float d = DistLine(ro, rd, p);

 d = smoothstep(0.1, 0.09, d);
 gl_FragColor = vec4(vec3(d), 1.0);
}

カメラを動かす場合
カメラの方向ベクトル(f、r、u)を計算する必要がある
注視する点(lookat)と、カメラからスクリーンまでの距離(zoom)を定義
f (前方向ベクトル): lookat から ro への方向を正規化
r (右方向ベクトル):上方向ベクトルvec3(0.0, 1.,0 0.0)と f の外積
u (上方向ベクトル): f と r の外積

//struct キーワードを用いて構造体を定義
struct ray {
    vec3 o;  // レイの原点
    vec3 d;  // レイの方向
};

ray CameraSetup(vec2 uv, vec3 camPos, vec3 lookAt, vec3 up, float zoom) {
    ray cam;
    //レイの原点
    cam.o = camPos;
    //カメラのxyz軸(f、r、u)を計算
    vec3 f = normalize(lookAt - cam.o);
    vec3 r = normalize(cross(up, f));
    vec3 u = normalize(cross(f, r));

    //視野平面の中心点
    vec3 c = cam.o + f * zoom;
    //視野内のスクリーン上の点i
    vec3 i = c + uv.x * r + uv.y * u;
    //レイの方向
    cam.d = normalize(i - cam.o);
    return cam;
}

void main() {
    vec2 uv = (vUv * 2.0 - 1.0) * vec2(uResolution.x / uResolution.y, 1.0);
    float zoom = 1.0;
    vec3 camPos = vec3(0.0 + sin(uTime), 0.0, -2.0 + sin(uTime));
    vec3 lookAt = vec3(0.0);
/*
カメラのアップベクトル:カメラの「上」方向が常にY軸方向
 vec3(0.0, 1.0, 0.0)
例えば、カメラを45度傾ける場合のアップベクトル
vec3(0.707, 0.707, 0.0); // X軸とY軸45度
*/
    vec3 up = vec3(0.0, 1.0, 0.0);

    ray r = CameraSetup(uv, camPos, lookAt, up, zoom);

    float d = DistLine(r.o, r.d, vec3(0.0, 0.0, 0.0));
    float d2 = DistLine(r.o, r.d, vec3(0.5, 0.5, 0.0));
    float d3 = DistLine(r.o, r.d, vec3(-0.5, -0.5, 0.0));
    float d4 = DistLine(r.o, r.d, vec3(0.5, -0.5, 0.0));
    float d5 = DistLine(r.o, r.d, vec3(-0.5, 0.5, 0.0));

    d = smoothstep(0.1, 0.09, d);
    d += smoothstep(0.1, 0.09, d2);
    d += smoothstep(0.1, 0.09, d3);
    d += smoothstep(0.1, 0.09, d4);
    d += smoothstep(0.1, 0.09, d5);

    gl_FragColor = vec4(vec3(d), 1.0);
}

球体とレイとの交差

球体の定義

  • s:球体の中心
  • r:球体の半径
  • t :レイのパラメータで、レイ上の位置を決定
    t = dot(s – ro, rd)
  • p:レイ上の点の位置
    p = ro + rd * t
  • y:球体の中心からレイ上の最も近い点までの距離
    y = length(s – p)
    y < r なら、レイは球体と交差してこのとき t1 と t2 が実数値
  • x:球体の中心に最も近い点から球体の表面までの距離
    x = sqrt(r * r – y * y)
  • t1:レイが球体に入る点
    t1 = t – x
  • t2: はレイが球体から出る点
    t2 = t + x
float remap01(float a, float b, float t){
    return (t-a)/(b-a);
}

void main() {
    vec2 uv = (vUv * 2.0 - 1.0) * vec2(uResolution.x / uResolution.y, 1.0);
    vec3 col = vec3(0.0);

    vec3 ro = vec3(0.0);
    vec3 rd = normalize(vec3(uv, 1.0));

    vec3 s = vec3(0.0, 0.0, 4.0);
    float r = 1.5; 

    float t = dot(s - ro, rd);
/*
備考:外積を使ってレイに対する垂線の長さを計算
float y = DistLine(ro, rd, s);
*/
    vec3 p = ro + rd * t;
    float y = length(s - p);

    // レイが球と交差しているかを判定
    if (y < r) {
        // 交差している場合
        // 交差点の距離を計算
        float x = sqrt(r * r - y * y);
        // レイが球に入る点と出る点のパラメータを計算
        float t1 = t - x;
        float t2 = t + x;
        // 球のZ座標に基づいて色をリマップ : c=t1なら白
        float c = remap01(s.z, s.z - r, t1);
        col = vec3(c);
    }

     gl_FragColor = vec4(col, 1.0);
}

球体に対してマッピングする
*球体のUVマッピングは、「前面」と「背面」が存在
(U, V) = (0, 0) は球体の「背面」に配置され、「前面」に向かってテクスチャが展開される

mat2 Rot(float a) {
	float s=sin(a), c=cos(a);
    return mat2(c,-s,s,c);
}


vec3 col = vec3(0.0);
vec3 ro = vec3(0.0, 0.0, 0.0);
vec3 rd = normalize(vec3(uv, 1.0));
vec3 s = vec3(0.0, 0.0, 3.0);
float r = 1.0;

float t = dot(s - ro, rd);
vec3 p = ro + rd * t;
float y = length(s - p);

if (y < r) {
        float x = sqrt(r*r - y * y);
        float t1 = t - x;
        float t2 = t + x;
        //t1とt2の球体の中心sに対して相対的な位置
        vec3 posF = ro + rd * t1 - s;
        vec3 posB = ro + rd * t2 - s;

        // 回転
        posF.xz *= Rot(uTime);

        //球体表面の交差点の座標と、その座標を基にしたUVマッピング座標を計算
        vec2 uvF = vec2(atan( posF.x,  posF.z),  posF.y);
        vec2 uvB = vec2(atan( posB.x,  posB.z),  posB.y);

        //前面のマッピング座標
        float d =  length(uvF);
        float m = smoothstep(0.5,0.6, d);
        col = vec3(m);
    }

gl_FragColor = vec4(col, 1.0);

このページのエフェクトのフラグメントシェーダーのコード

コードを見る

uniform vec2 uResolution;
uniform float uTime;
varying vec2 vUv;

float smax( float a, float b, float k ) {
	float h =clamp( 0.5 + 0.5*(b-a)/k, 0.0, 1.0);
	return mix( a, b, h ) +  k* h *(1.0-h);
}
float Heart(vec2 uv){
    uv *= 3.0;
    uv.x *= 0.65;
    float r = 0.4;
    float b = 0.15;
    uv.y -= smax(sqrt(abs (uv.x)) * 0.7, b, 0.1);
    float d = length(uv);
    return smoothstep(r + b, r-b-0.01, d);
}
mat2 Rot(float a) {
	float s=sin(a), c=cos(a);
    return mat2(c,-s,s,c);
}
vec3 Transform(vec3 p, float a){
    p.xz *=Rot(a);
    p.xy *=Rot(a*0.3);
    return p;
}

float Ball(vec3 ro, vec3 rd, vec3 s, float angle){
    float r = 1.0;
    float t = dot(s - ro, rd);
    vec3 p = ro + rd * t;
    float y = length(s - p);
    float m = 0.0;
    if (y < r) {
        float x = sqrt(r*r - y * y);
        float t1 = t - x;
        float t2 = t + x;
        vec3 posF = ro + rd * t1 - s;
        vec3 posB = ro + rd * t2 - s;
        posF = Transform(posF, angle);
        posB = Transform(posB, angle);
        vec2 uvF = vec2(atan( posF.x,  posF.z),  posF.y);
        vec2 uvB = vec2(atan( posB.x,  posB.z),  posB.y);
        vec2 scale = vec2(0.6, 0.6);
        uvF *= scale;
        uvB *= scale;
        m = Heart(uvB)+ Heart(uvF);
    }
    return m;
}

void main() {
    vec2 uv = (vUv * 2.0 - 1.0) * vec2(uResolution.x / uResolution.y, 1.0);
    vec3 col = vec3(0.0);
    vec3 ro = vec3(0.0, 0.0, 0.0);
    vec3 rd = normalize(vec3(uv, 1.0));

    float t = uTime * 0.6;
    float t2 = uTime * 0.2;

    float m = 0.0;
    float stp = 1.0 / 40.0;
    for(float i=0.0; i<1.0; i+=stp) {
        float x = mix(-5.0, 5.0, fract(sin(i*564.3)*4570.3));
        float y = mix(-5.0, 5.0, fract(i+t*0.1));
        float z = mix(4.0, 3.0, i);
       float a = t2 + i *564.3;
       float d = Ball(ro, rd, vec3(x, y, z), a);
       m = max(m, clamp(d, 0.0, 1.0));
    }

    col = mix(col, vec3( cos(t2)*0.5+0.5, 0.1, sin(t2)*0.5+0.5), m);
    vec4 finalcol = vec4(col, 0.3);
    finalcol = pow(finalcol, vec4(.4545));
    gl_FragColor = finalcol;
}