BasicCG

新卒1年目の時に書いた社内勉強用のテキストです。

コンピュータ・グラフィックスの基礎

デモ

BasicCGApp

概要

コンピュータ・グラフィックスとは

コンピュータグラフィックス(英語:computer graphics、略称:CG) はコンピュータを用いて作成される画像である。日本では、和製英語の「コンピュータグラフィック」または「グラフィック」も使われる。(wikiより)

コンピュータ・グラフィックスを構成する要素

目的

1. ベクトル・行列の基礎演算

CGの基礎を理解するのに必要な数学の公式を示す

2点間のベクトルの導出

vectorA    vectorB

vectorAB

vector_point

ベクトルの内積

innerProduction

innerProductionVector

行列の足し算・掛け算

matrix_add

matrix_mul

行列の法則

matrix_not_change

matrix_join

2. モデリング

モデルの種類

model

代表的な形状表現手法

境界表現(ソリッドモデル)

solid_model

ポリゴン曲面の表現(サーフェスモデル)

box

sphere

    BasicCG.Material.Triangle = Material.extend({
          initialize: function(p1, p2, p3) {
              this.vertexes = [p1, p2, p3];
          },
    BasicCG.util.Vector3D = function(x,y,z) {
            this.x = x;
            this.y = y;
            this.z = z;
    },
    BasicCG.Material.Box = Material.extend({
        initialize: function(x, y, z, w, h, d) {
            this.w = w;
            this.h = h;
            this.d = d;
            this.angle = new BasicCG.util.Vector3D(0, 0, 0);
            this.pos = new BasicCG.util.Vector3D(x, y, z);
            this.vertexes = {
                p11: new BasicCG.util.Vector3D(x - w/2, y - h/2, z - d/2),
                p12: new BasicCG.util.Vector3D(x - w/2, y + h/2, z - d/2),
                p13: new BasicCG.util.Vector3D(x + w/2, y + h/2, z - d/2),
                p14: new BasicCG.util.Vector3D(x + w/2, y - h/2, z - d/2),
                p21: new BasicCG.util.Vector3D(x - w/2, y - h/2, z + d/2),
                p22: new BasicCG.util.Vector3D(x - w/2, y + h/2, z + d/2),
                p23: new BasicCG.util.Vector3D(x + w/2, y + h/2, z + d/2),
                p24: new BasicCG.util.Vector3D(x + w/2, y - h/2, z + d/2),
            };
            var vertex = this.vertexes;
            this.triangles = [
                //正面
                new BasicCG.Material.Triangle(vertex.p11.getInstance(), vertex.p12.getInstance(), vertex.p13.getInstance()),
                new BasicCG.Material.Triangle(vertex.p13.getInstance(), vertex.p14.getInstance(), vertex.p11.getInstance()),
                //右面
                new BasicCG.Material.Triangle(vertex.p14.getInstance(), vertex.p13.getInstance(), vertex.p23.getInstance()),
                new BasicCG.Material.Triangle(vertex.p23.getInstance(), vertex.p24.getInstance(), vertex.p14.getInstance()),
                //裏面
                new BasicCG.Material.Triangle(vertex.p24.getInstance(), vertex.p23.getInstance(), vertex.p22.getInstance()),
                new BasicCG.Material.Triangle(vertex.p21.getInstance(), vertex.p24.getInstance(), vertex.p22.getInstance()),
                //左面
                new BasicCG.Material.Triangle(vertex.p21.getInstance(), vertex.p22.getInstance(), vertex.p11.getInstance()),
                new BasicCG.Material.Triangle(vertex.p12.getInstance(), vertex.p11.getInstance(), vertex.p22.getInstance()),
                //上面
                new BasicCG.Material.Triangle(vertex.p21.getInstance(), vertex.p11.getInstance(), vertex.p24.getInstance()),
                new BasicCG.Material.Triangle(vertex.p14.getInstance(), vertex.p24.getInstance(), vertex.p11.getInstance()),
                //下面
                new BasicCG.Material.Triangle(vertex.p13.getInstance(), vertex.p12.getInstance(), vertex.p23.getInstance()),
                new BasicCG.Material.Triangle(vertex.p22.getInstance(), vertex.p23.getInstance(), vertex.p12.getInstance()),
            ];
        },
        draw: function() {
            if(!this.drawable) return;
            var that = this;
            var ctx = BasicCG.context;
            ctx.beginPath();
            ctx.moveTo(this.vertexes[0].x, this.vertexes[0].y);
            for (var i = 1; i < 3; i++){
                ctx.lineTo(this.vertexes[i].x, this.vertexes[i].y);
            }
            ctx.closePath();
            ctx.strokeStyle = this.getStrokeColorTxt();
            ctx.stroke();
            ctx.fillStyle = this.getColorTxt();
            ctx.fill();
        },

ポリゴンを用いて曲面を表現するためには多数の手法がある

細分割曲面

3. 投影

2次元のディスプレイに3次元の図形を描画するには、3次元図形を2次元図形に変換する必要性がある。 これを投影という。

座標系

right_axis left_axis

座標変換

行列による移動

matrix_move

行列による拡大

matrix_scale

回転

matrix_rotate

行列による回転

rotate_x

rotate_y

rotate_z

コード



    BasicCG.util.Vector3D = function(x,y,z) {
            this.x = x;
            this.y = y;
            this.z = z;
    },
    BasicCG.util.Vector3D.prototype = {
        transCoordinatesPoint: function (trans_matrix) {
            var res = BasicCG.util.Matrix.mul(trans_matrix, [[this.x], [this.y], [this.z], [1]]);
            this.x = res[0][0];
            this.y = res[1][0];
            this.z = res[2][0];
        },
        //点の移動
        
        moveDistancePoint: function(x,y,z) {
            this.transCoordinatesPoint(
                [
                    [1,0,0,x],
                    [0,1,0,y],
                    [0,0,1,z],
                    [0,0,0,1]
                ]
            );
        },
        //点の拡大・縮小
        scalePoint: function (x, y, z) {
            this.transCoordinatesPoint(
                [
                    [x,0,0,0],
                    [0,y,0,0],
                    [0,0,z,0],
                    [0,0,0,1]
                ]
            );
        },
        //点のx-y平面内における回転(z軸中心の回転)
        xyRotatePoint: function (angle) {
            var theta = Math.PI * angle / 180;
            var sin = Math.sin(theta);
            var cos = Math.cos(theta);
            this.transCoordinatesPoint(
                        [
                            [cos,-sin,0,0],
                            [sin,cos,0,0],
                            [0,0,1,0],
                            [0,0,0,1]
                        ]
            );
        },
        //点のy-z平面内における回転(x軸中心の回転)
        yzRotatePoint: function (angle) {
            var theta = Math.PI * angle / 180;
            var sin = Math.sin(theta);
            var cos = Math.cos(theta);
            this.transCoordinatesPoint(
                        [
                            [1,0,0,0],
                            [0,cos,-sin,0],
                            [0,sin,cos,0],
                            [0,0,0,1]
                        ]
            );
        },
        //x-z平面内における回転(y軸中心の回転)
        xzRotatePoint: function (angle) {
            var theta = Math.PI * angle / 180;
            var sin = Math.sin(theta);
            var cos = Math.cos(theta);
                        this.transCoordinatesPoint(
                        [
                            [cos,-sin,0,0],
                            [sin,cos,0,0],
                            [0,0,1,0],
                            [0,0,0,1]
                        ]
            );
        },
        //点のy-z平面内における回転(x軸中心の回転)
        yzRotatePoint: function (angle) {
            var theta = Math.PI * angle / 180;
            var sin = Math.sin(theta);
            var cos = Math.cos(theta);
            this.transCoordinatesPoint(
                        [
                            [1,0,0,0],
                            [0,cos,-sin,0],
                            [0,sin,cos,0],
                            [0,0,0,1]
                        ]
            );
        },
        //x-z平面内における回転(y軸中心の回転)
        xzRotatePoint: function (angle) {
            var theta = Math.PI * angle / 180;
            var sin = Math.sin(theta);
            var cos = Math.cos(theta);
            this.transCoordinatesPoint(
                        [
                            [cos,0,sin,0],
                            [0,1,0,0],
                            [-sin,0,cos,0],
                            [0,0,0,1]
                        ]
            );
        },
    };
    
    BasicCG.util.Matrix = {
        //行列の掛け算
        mul: function(m1, m2){
            var res = new Array(m2.length);
            for (var i = 0; i < res.length; i++){
                res[i] = [];
            }
            var ans = 0;

            if (m1[0].length === m2.length) {
                for (var i = 0; i < m1.length; i++){
                    for (var j = 0; j < m2[0].length; j++){
                        ans = 0;
                        for (var k = 0; k < m2.length; k++){
                            ans += m1[i][k] * m2[k][j];
                        }
                        res[i][j] = ans;
                    }
                }
                return res;
            } else {
                console.log("error: cannnot mul matrix -> m1-col-length !== m2-row-length");
            }
        }
    };

合成変換

        rotate: function(angleX, angleY, angleZ) {
            var that = this;
            this._setAngle(angleX, angleY, angleZ);
            var currentPos = this.pos.getInstance();
            this.moveDistance(-currentPos.x, -currentPos.y, -currentPos.z);
            _.each(this.triangles, function(triangle) {
                if (angleZ !== 0) triangle.xyRotate(angleZ);
                if (angleY !== 0) triangle.xzRotate(angleY);
                if (angleX !== 0) triangle.yzRotate(angleX);
            });
            this.moveDistance(currentPos.x, currentPos.y, currentPos.z);
        },

投影法

透視投影

perspective_projection perspective_projection_explain

        getPerspectiveProjection: function(vector3D, camera) {
            var screenZ = camera.screenZ;
            var projectedX =((screenZ - camera.pos.z) / (vector3D.z - camera.pos.z)) * vector3D.x;
            var projectedY =((screenZ - camera.pos.z) / (vector3D.z - camera.pos.z)) * vector3D.y;
            return new BasicCG.util.Vector3D(projectedX, projectedY, vector3D.z);
        },

平行投影

parallel_projection

ビューボリューム

_calcInViewVolume: function(camera) {
  if (this.getMinZ() > camera.minZ && this.getMaxZ() < camera.maxZ) return;
    this.drawable = false;
},

ビューイングパイプライン

モデリング変換

視野変換

投影変換

ビューポート変換

全体の変換

        getModelingTransformation: function(vector3D, camera) {
            return vector3D.getInstance();
                //TODO
        },
        getViewingTransformation: function(vector3D, camera) {
            //TODO カメラの回転
            var _vector3D = vector3D.getInstance();
            _vector3D.moveDistancePoint(-camera.pos.x, -camera.pos.y, -camera.pos.z);
            return _vector3D;
        },
        getPerspectiveProjection: function(vector3D, camera) {
            var screenZ = camera.screenZ;
            var projectedX =((screenZ - camera.pos.z) / (vector3D.z - camera.pos.z)) * vector3D.x;
            var projectedY =((screenZ - camera.pos.z) / (vector3D.z - camera.pos.z)) * vector3D.y;
            return new BasicCG.util.Vector3D(projectedX, projectedY, vector3D.z);
        },
        getViewportTransformation: function(vector3D) {
            var Conf = BasicCG.Conf;
            var _vector3D = vector3D.getInstance();
            _vector3D.moveDistancePoint(Conf.screen.width/2, Conf.screen.height/2, 0);
            return _vector3D;
        },

4. レンダリング

レンダリングを構成する処理

隠面消去

バックフェースカリング

camera_vector

        _isTowardCamera: function(camera) {
            var normalLine = this.getNormalLine();
            var cameraVec = new BasicCG.util.Vector3D(
                      camera.pos.x -this.vertexes[0].x,
                      camera.pos.y -this.vertexes[0].y,
                      camera.pos.z -this.vertexes[0].z
            );
            var innerProduct = normalLine.x * cameraVec.x + normalLine.y * cameraVec.y + normalLine.z * cameraVec.z;
            if (innerProduct > 0) {
                return true;
            }
            else {
                return false;
            }
        },
        //面の法線ベクトル
        //現在持っている点からベクトルを2つ導出し、
        //そのベクトル2つの外積を求める
        getNormalLine: function () {
            var vec1 = new BasicCG.util.Vector3D(0, 0, 0);
            var vec2 = new BasicCG.util.Vector3D(0, 0, 0);
            var normalLine = new BasicCG.util.Vector3D(0, 0, 0); //法線ベクトル
            vec1.x = this.vertexes[1].x - this.vertexes[0].x;
            vec1.y = this.vertexes[1].y - this.vertexes[0].y;
            vec1.z = this.vertexes[1].z - this.vertexes[0].z;
            vec2.x = this.vertexes[2].x - this.vertexes[0].x;
            vec2.y = this.vertexes[2].y - this.vertexes[0].y;
            vec2.z = this.vertexes[2].z - this.vertexes[0].z;
            // 外積
            // | i    j    k |
            // |v1x  v1y  v1z|
            // |v2x  v2y  v2z|
            normalLine.x = vec1.y * vec2.z - vec1.z * vec2.y;
            normalLine.y = vec1.z * vec2.x - vec1.x * vec2.z;
            normalLine.z = vec1.x * vec2.y - vec1.y * vec2.x;

            return normalLine;
        },

normal_line_1 normal_line_2 normal_line_3

隠面消去法

隠面消去アルゴリズムは、処理を行う空間により、下記の3つに分類される

物体空間アルゴリズム
画像空間アルゴリズム
優先順位アルゴリズム

優先順位アルゴリズム

        drawAllObjects: function() {
            var that = this;
            var ctx = BasicCG.context;
            this._drawBackGround(ctx);

            var transformedTriangles = [];
            _.each(this.objects.get(), function(object) {
                $.merge(transformedTriangles, object.getTransformedTriangles(that.camera));
            });

            transformedTriangles = this._zSort(transformedTriangles);
            _.each(transformedTriangles, function(transformedTriangle) {
                transformedTriangle.draw();
            });
            this.topView.draw(this.camera, this.objects.get());
        },

        _zSort: function(transformedTriangles) {
            return _.sortBy(transformedTriangles, function(triangle) {
                return triangle.getMaxZ();
            }).reverse();

three_deadway penetration

画像空間アルゴリズム

Zバッファ法
全ての画素を背景色で初期化する;
スクリーンのすべての画素のz値を無限大で初期化する;
すべてのポリゴンを任意の順番で取り出し、
それぞれのポリゴンについて{
 ポリゴンを透視投影する;
 ポリゴン内部の各画素の座標値を決定する;
 ポリゴン内部の各画素(x,y)について{
  スクリーンの画素(x,y)に対応する点でのポリゴンの奥行き(z値)を求める
  ポリゴンのz値とスクリーンの画素を比較して、
  ポリゴンのz値 < スクリーンの画素のz値ならば {
   スクリーンの画素(x,y)の色をポリゴンの画素(x,y)に塗る;
   スクリーンの画素(x,y)のz値をポリゴンの画素(x,y)のz値に更新する;
  }
 }
}

レイトレーシング法
スクリーンのすべての画素を順番にとり出し
それぞれの画素について{
 視点から画素に向かうレイを決定する;
 レイとすべての物体との交差判定を行う;
 レイと交差する物体が1つ以上存在するならば{
  すべての交点のなかで最も視点に近いもの(可視点)を求める;
  その画素に可視点での物体の色を塗る;
 }
 そのほか(レイと交差する物体が存在しない)場合{
  その画素に背景色を塗る;
 }
}

まとめ

コンピュータ・グラフィックスの基礎として、

について発表しました。 CGについて、どのような基礎を元に表示されているのかを分かって頂けたら幸いです。

今回説明できなかったことや、更に深く知りたい場合は参考書籍を是非ご一読ください。

参考書籍

コンピュータ・グラフィックス