0
0

More than 3 years have passed since last update.

3Dグラフィックの基礎

Last updated at Posted at 2021-07-12

3Dグラフィックの基礎を JavaScript で実践

波紋のシミュレーションをワイヤフレームで表示します。
視点変換、透視変換の基礎が学べます。
hamon.png

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>3D</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>

var SCREEN_X = 1280;
var SCREEN_Y = 720;

var CX = SCREEN_X/2;    // 中央の位置を記録
var CY = SCREEN_Y/2;    // 中央の位置を記録
var L = 0.55;   // スクリーンと目までの距離[m]
var STOR = 1280/0.34;   // スクリーンとリアルの比率(34cmで1280px)

var POINT_X_NUM = 64;
var POINT_Y_NUM = 64;

var eye = {};
eye.p = {x: 5, y: 10, z: -18};  // 目の位置
eye.v = {x: -eye.p.x, y: -eye.p.y, z: -eye.p.z};    // 視線方向
eye.l = {x: -1, y: -1, z: 0};   // 光の方向

var canvas;
var ctx;
var p;  // 点の配列(x, y, z, vx, vy, vz, sx, sy)
var poly;   // ポリゴンの配列
var timer;  // インターバルのハンドル
var hamon;

$(document).ready(function(){
    canvas = $('#canvas')[0];
    canvas.width = SCREEN_X;
    canvas.height = SCREEN_Y;
    ctx = canvas.getContext('2d');

    p = [];
    poly = [];

    for(var i = 0; i < POINT_Y_NUM; i++){
        for(var j = 0; j < POINT_X_NUM; j++){
            var bp = i*POINT_Y_NUM + j;
            p[bp] = {
                x: j*10/POINT_X_NUM - 5,
                y: 0.0,
                z: 5 - i*10/POINT_Y_NUM,
                vx: 0.0,
                vy: 0.0,
                vz: 0.0,
                sx: 0,
                sy: 0
            };
        }
    }

    var n = 0;
    for(var i = 0; i < POINT_Y_NUM - 1; i++){
        for(var j = 0; j < POINT_X_NUM - 1; j++){
            var bp = i*POINT_Y_NUM + j;
            poly[n] = [bp, bp + POINT_Y_NUM + 1, bp + 1];
            n++;
            poly[n] = [bp, bp + POINT_Y_NUM    , bp + POINT_Y_NUM + 1];
            n++;
        }
    }


    var NUM = 16;
    hamon = [];
    for(var i = 0; i < NUM; i++){
        hamon[i] = {
            x: 5*Math.cos(2*Math.PI/NUM*i),
            z: 5*Math.sin(2*Math.PI/NUM*i),
            r: 0.05,
            i: 0
        };
    }

    calcViewG();
    var t = 0;
    timer = setInterval(function(){
        t += 0.1;
        if(t >= 2*Math.PI){
            t -= 2*Math.PI;
        }

        for(var i = 0; i < POINT_Y_NUM; i++){
            for(var j = 0; j < POINT_X_NUM; j++){
                bp = i*POINT_Y_NUM + j;
                p[bp].y = 0;
                for(var k in hamon){
                    var h = hamon[k];
                    var r = Math.sqrt((h.x - p[bp].x)*(h.x - p[bp].x) + (h.z - p[bp].z)*(h.z - p[bp].z));
                    p[bp].y += h.r*Math.sin(t - 5*r + h.i);
                }
            }
        }

        reDraw();
    }, 1);
});




function reDraw(){
    // 視点変換
    var g = eye.g;
    for(var i in p){
        p[i].vx = p[i].x*g[0][0] + p[i].y*g[0][1] + p[i].z*g[0][2] + g[0][3];
        p[i].vy = p[i].x*g[1][0] + p[i].y*g[1][1] + p[i].z*g[1][2] + g[1][3];
        p[i].vz = p[i].x*g[2][0] + p[i].y*g[2][1] + p[i].z*g[2][2] + g[2][3];
    }

    // 透視変換
    for(var i in p){
        p[i].sx = parseInt(CX + STOR*L*p[i].vx/p[i].vz);
        p[i].sy = parseInt(CY - STOR*L*p[i].vy/p[i].vz);
    }

    // キャンバス初期化
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 描画
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.strokeStyle = "black";
    ctx.lineWidth = 1;

    // 横線
    for(var i = 0; i < POINT_Y_NUM; i++){
        for(var j = 0; j < POINT_X_NUM; j++){
            bp = i*POINT_Y_NUM + j;
            if(j == 0){
                ctx.beginPath();
                ctx.moveTo(p[bp].sx, p[bp].sy);
            }else{
                ctx.lineTo(p[bp].sx, p[bp].sy);
            }
        }
        ctx.stroke();
    }

    // 縦線
    for(var j = 0; j < POINT_X_NUM; j++){
        for(var i = 0; i < POINT_Y_NUM; i++){
            bp = i*POINT_Y_NUM + j;
            if(i == 0){
                ctx.beginPath();
                ctx.moveTo(p[bp].sx, p[bp].sy);
            }else{
                ctx.lineTo(p[bp].sx, p[bp].sy);
            }
        }
        ctx.stroke();
    }
}


// 視線左回転
function turnLeft(){
    var cosT = Math.cos(-0.1);
    var sinT = Math.sin(-0.1);
    var x = eye.p.x;
    var y = eye.p.z;
    eye.p.x = x*cosT - y*sinT;
    eye.p.z = y*cosT + x*sinT;
    eye.v.x = -eye.p.x;
    eye.v.z = -eye.p.z;
    calcViewG();
}

// 視線右回転
function turnRight(){
    var cosT = Math.cos(0.1);
    var sinT = Math.sin(0.1);
    var x = eye.p.x;
    var y = eye.p.z;
    eye.p.x = x*cosT - y*sinT;
    eye.p.z = y*cosT + x*sinT;
    eye.v.x = -eye.p.x;
    eye.v.z = -eye.p.z;
    calcViewG();
}



/**
 * 行列計算
 */
function gyoretsu(a, b){
    var r = [];

    for(var y = 0; y < 4; y++){
        r[y] = [];
        for(var x = 0; x < 4; x++){
            r[y][x] = a[0][x]*b[y][0]
                    + a[1][x]*b[y][1]
                    + a[2][x]*b[y][2]
                    + a[3][x]*b[y][3];
        }
    }

    return r;
}


/**
 * 視点行列計算
 */
function calcViewG(){
    // 光源ベクトルを単位ベクトルに
    var r = Math.sqrt(eye.l.x*eye.l.x + eye.l.y*eye.l.y + eye.l.z*eye.l.z);
    eye.l.x = eye.l.x/r;
    eye.l.y = eye.l.y/r;
    eye.l.z = eye.l.z/r;

    // 平行移動
    var g = [
        [1, 0, 0, -eye.p.x],
        [0, 1, 0, -eye.p.y],
        [0, 0, 1, -eye.p.z],
        [0, 0, 0,        1]
    ];

    // y軸回転で視線をz軸方向に
    r = Math.sqrt(eye.v.x*eye.v.x + eye.v.z*eye.v.z);
    var cosT = eye.v.z/r;
    var sinT = eye.v.x/r;
    g = gyoretsu(g, [
        [cosT, 0, -sinT, 0],
        [   0, 1,     0, 0],
        [sinT, 0,  cosT, 0],
        [   0, 0,     0, 1]
    ]);

    // x軸回転で視線をz軸と平行に
    var ez = r;
    r = Math.sqrt(ez*ez + eye.v.y*eye.v.y);
    cosT = ez/r;
    sinT = -eye.v.y/r;
    eye.g = gyoretsu(g, [
        [   1,     0,    0, 0],
        [   0,  cosT, sinT, 0],
        [   0, -sinT, cosT, 0],
        [   0,     0,    0, 1]
    ]);
}


</script>
</head>
<body>
<div style="width: 1280px; margin: auto">
<h1 style="float:left">3D</h1>
<button id="left" style="float:left" onclick="turnLeft();">←</button>
<button id="right" style="float:left" onclick="turnRight();">→</button><br />
<button id="stop" style="float:left" onclick="clearInterval(timer);">停止</button>
<div style="clear;both"></div>

<canvas id="canvas" style="width:100%; height: 720px;border:solid 1px black"></canvas>
</div>
</body>
</html>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0