[Work/Class/Python3の基礎とデータ処理/2_PythonLibraries]

CSVの取得とPlot

各種ライブラリのCSV読み込み機能

標準csvライブラリcsv.reader()による読み込み

Python3の標準CSVライブラリは,

  • 標準ライブラリのcodecs.open()関数で,文字コードを指定しつつファイルを開く
  • csv.reader()関数で「データ本体を含むインスタンス」にする.ちなみにイテレータのインスタンスである.
  • イテレータのインスタンスなので,next()関数,もしくはfor文で1行ずつ読んでいく.1行が1次元の文字列のlistとして出てくるので,処理をしつつ格納する.
  • 最後にファイルをclose()する.

という流れになる.

CSVファイル内に入っている「処理したい」ターゲットが文字列の場合,今の所この標準csvライブラリを使う方法が一番適当である.

numpy.loadtxt()による読み込み

numpyで読み込む場合,CSVの中身が数字である必要がある.

流れは標準csvライブラリと同じく,codecs.open()関数でファイルを開いて,2次元配列として一気に読み込み,close()する,という流れだが,「数字である」ことが前提なので,ヘッダ等の文字列で構成されている行をskiprows=行数と「何行読み飛ばすか」を指定してから読み込み始めなければならない.

またdelimiter='区切り文字'指定により,区切り文字を明示的に指定する必要がある.(CSVは区切り文字がTABのこともあるため)

pandas.read_csv()による読み込み

pandasも内部的にはnumpyの配列を使うので,やはりnumpyの2次元配列で読み込む.が,ヘッダを特殊扱いして文字列として取り出すことができる(もちろん後述のようにnumpyと同じくヘッダを最初から読み飛ばすしてもできる).

またnumpy.loadtxt()だと強制的にfloatのみになるが,pandas.read_csv()は全て整数だった場合intの配列を作ってくれる.

さらに,標準csvライブラリやnumpy.loadtxt()のようにファイルを最初にopenして読み込み後にcloseする必要がない,numpy.loadtxt()と同じく全てのデータを一気に2次元配列として持ってくることができるが,列だけを1次元numpy配列として容易に抽出できる,等の特徴がある.

pandas.read_csv()は区切り文字は自動判定されるようである.

どのライブラリを使うべきか

それぞれ前述のような特徴があるため,pandas.read_csv()関数を使うのが一番楽である.

しかし,pandasは入っておらずインストールに苦労するPython3環境も多い.numpyが用意されていない,もしくは容易にインストールすることができないPython3環境はほぼないため,pandasがない場合は標準csvライブラリかnumpyライブラリを使って読み込む,というようにしておくとよい.

コード例

# -*- coding: utf-8 -*-
# CSVPlotter.py
import csv, numpy, pandas
import urllib.request
import codecs
from PyQt5.QtWidgets import QApplication
import matplotlib.pyplot


class CSVPlotter():
    def __init__(self, url_string):
        self.__url_string = url_string

    def download_csv(self, decoder):
        # 引数decoderで指定した文字コードのCSVファイルをダウンロードして,
	# UTF-8に直して保存する
        self.__gotten_http_response = urllib.request.urlopen(self.__url_string)
        if self.__gotten_http_response.code == 200:
        # コード200が返っていてきたら正常に取得できている
            print('Sucsess to get CSV from Internet.')
            # ダウンロードしてきたデータを一度ファイルに書き込む
            self.__downloaded_filename = 'downloaded.csv'
            downloaded_file = codecs.open(self.__downloaded_filename, mode='w', encoding='utf-8')
            downloaded_file.write(self.__gotten_http_response.read().decode(decoder))
            downloaded_file.close();
            print('Sucsess to write downloaded CSV data to file.')
        else:
            # それ以外のコードが返ってきたら,異常終了
            print('Cannot get CSV file. Code:', self.__gotten_http_response.code, 'Exiting...');
            sys.exit(1)

    def read_csv_with_csv(self):
        # 標準ライブラリのcsvを使ってCSVファイルを読み込む
        print('標準のcsvライブラリで読んで表示')
        csv_file = codecs.open(self.__downloaded_filename, mode='r', encoding='utf-8')
        self.__csv_data = csv.reader(csv_file)
        
        # header以外は2次元のlistで出てくる
        # 外側のlistがRow(行),
        # 内側のリストがColumn(列)でデータ型は文字列
        header = next(self.__csv_data)
        # 1行目(データ本体ではなくデータの内容を示すヘッダ)
        print(header)
        for a_row in self.__csv_data:
            print(a_row)

        csv_file.close()
            
    def read_csv_with_numpy(self):
        # NumPyライブラリを使ってCSVファイルを読み込む
        print('NumPyライブラリで読んで表示')
        # NumPyの読み込みデータはNumPyの2次元array(ndarray)でくるので,分解して表示
        # データ型はfloat
	csv_file = codecs.open(self.__downloaded_filename, mode='r', encoding='utf-8')
        self.__csv_data = numpy.loadtxt(csv_file, delimiter=',', skiprows=1)
	csv_file.close()

        # 区切り文字を','とし,1行目(ヘッダ)を抜かして読み込む
        for a_row in self.__csv_data:
            print(a_row)
      
    def read_csv_with_pandas(self):
        # Pandsライブラリを使ってCSVファイルを読み込む
        print('Pandasライブラリで読んで表示')
        df = pandas.read_csv(self.__downloaded_filename)

	# 文字列ヘッダが含まれててもエラーを起こさない
	# ヘッダだけを取り出すことができる
        header = df.columns.values.tolist() #headerは標準list
        self.__csv_data = df.values 
        print(header)

        # Pandsの読み込みデータもNumPyの2次元arrayでくるので,分解して表示
        # データ型は,中身が整数ならintに変換される
        for a_row in self.__csv_data:
            print(a_row)

    def make_plot_csv_data_2d(self, x_index, y_index, line_color):
        # インデックスで指定されたX軸要素とY軸要素を使い
        # NumPyのndarrayに入れて
        # Matplotlib.plotで図を描く
        plot_x_array = numpy.array([]); #空のnumpy ndarrayを生成
        plot_y_array = numpy.array([]);
        for a_row in self.__csv_data:
            if isinstance(a_row[x_index], str):
                # numpy ndarrayに追加していく
                plot_x_array = numpy.append(plot_x_array, float(a_row[x_index]))
            else:
                plot_x_array = numpy.append(plot_x_array, a_row[x_index])
            if isinstance(self.__csv_data[y_index], str):
                plot_y_array = numpy.append(plot_y_array, float(self.a_row[y_index]))
            else:
                plot_y_array = numpy.append(plot_y_array, a_row[y_index])
        # plotに使うデータを設定
        matplotlib.pyplot.plot(plot_x_array, plot_y_array, color=line_color)

    def plot_show(self, x_label, y_label):
        # plotの軸の名前を設定
        matplotlib.pyplot.xlabel(x_label, fontsize=10, fontname='serif')
        matplotlib.pyplot.ylabel(y_label, fontsize=10, fontname='serif')
        # plotする
        matplotlib.pyplot.show()


if __name__ == "__main__":
    # 八王子市年齢別人口
    # http://www.city.hachioji.tokyo.jp/contents/open/002/p005866.html
    csv_url = "http://www.city.hachioji.tokyo.jp/contents/open/002/p005866_d/fil/nenreibetsu_jinkou_2703.csv"
    csv_plotter = CSVPlotter(csv_url)
    csv_plotter.download_csv('shift-jis')
    csv_plotter.read_csv_with_csv()
    csv_plotter.read_csv_with_numpy()
    csv_plotter.read_csv_with_pandas()
    csv_plotter.make_plot_csv_data_2d(1, 2, 'gray')
    csv_plotter.plot_show('Age', 'Population Size')

応用

八王子市の年齢別人口データを年度/四半期ごとに複数持ってきて,一つのグラフに色を変えてプロットしてみる.

前述のコード例では,標準CSVライブラリで出てくるCSVのデータ形式に合わせて行ごとに読み込んだが,pandasのCSVライブラリで読み込むと,1列のみ簡単に取り出せることができる.また,ヘッダの行数を指定して読み込まない,等の処理が可能である.

コード例

# -*- coding: utf-8 -*-
# MultiCSVPlotter.py
import numpy, pandas
import urllib.request
import codecs
from PyQt5.QtWidgets import QApplication
import matplotlib.pyplot

class MultiCSVPlotter():
    def __init__(self, url_string_list):
        self.__url_string_list = url_string_list
        # numpyのndarrayで多次元配列を作るためには
        # np.array([])で初期化せずに(→append()しても連結された1次元配列になってしまう)
        # np.arange(0, x軸の最大値+1)で0〜最大値までの配列を最初に作って(X軸になる),
        # そこにvstack関数を使ってY軸になる配列を足していく形を取ると良い
        self.__csv_data_array = numpy.arange(0, 121)
        # 他のものも一応初期化しておく
        self.__column_index = 0

        return None

    def set_referring_column_index(self, column_index):
        # CSVファイルの何列目を見るか 0(1列目)~
        self.__column_index = column_index
        return self

    def download_csv_then_push_stack(self, decoder):
        # ShiftJISのCSVファイルのURL配列をダウンロードして,
        # UTF-8に直して一時保存してから,
        # pandasのCSV読み込み機能を使って読み込み,
        # ndarrayに2次元配列として入れていく
        for a_url in self.__url_string_list:
            self.__gotten_http_response = urllib.request.urlopen(a_url)
            if self.__gotten_http_response.code == 200:
                # コード200が返っていてきたら正常に取得できている
                print('Sucsess to get CSV from Internet.')
                # ダウンロードしてきたデータを一度ファイルに書き込む
                self.__downloaded_filename = 'tmp_downloaded.csv'
                downloaded_file = open(self.__downloaded_filename, mode='w', encoding='utf-8')
                downloaded_file.write(self.__gotten_http_response.read().decode(decoder))
                downloaded_file.close();
                print('Sucsess to download CSV data to file: ' + a_url)
                print('Now start to read CSV as ndarray with pandas library.')
            else:
                # それ以外のコードが返ってきたら,異常終了
                print('Cannot get CSV file. Code:', self.__gotten_http_response.code, 'Exiting...');
                sys.exit(1)

            # Pandsライブラリを使ってCSVファイルを読み込む
            # ヘッダは読み込まない指定(header=-1)
            # ヘッダは「1行」である指定(skiprows=1)
            df = pandas.read_csv(self.__downloaded_filename, header=-1, skiprows=1)
            
            # まず取得した2次元配列の中から,column_indexで指定した列だけを抜き出す
            # pandasは取得したCSV,ここではdfに対して,df[列idnex]でその列のみの
            # 1次元配列をとり出せる.
            populationSizeArray = df[self.__column_index]
            # できた1次元配列を,最初にX軸の配列で初期化した配列にvstack(())する.
            # vstackは2重括弧であることに注意
            # ただのstackでは同じ形(行と列が同じ量)の配列or行列同士しか足せないが,
            # vstackは要素数が同じ1次元配列をどんどん重ねていき
            # 多次元配列を作ることができる
            self.__csv_data_array = numpy.vstack((self.__csv_data_array, populationSizeArray))
            # ちなみにvstackの他に,hstack, column_stack, row_stackなどがある
            # これを,CSVファイル数分だけ回す
            print('Sucess to push new CSV column to stack')
        return self
        # return selfしないと,最後に実行した行の結果がreturnされるはず
        # この関数の場合はそれでもいいのだけれど
            
    def make_plot_csv_data_2d(self, line_color_list, line_legend_list):
        # Matplotlib.plotで図を描く
        # indexが1~shape(行列の次元数のtupleが得られる,ここでは(9, 121))[0]まで
        for index in range(1, self.__csv_data_array.shape[0]):
            # plotに使うデータを設定
            matplotlib.pyplot.plot(self.__csv_data_array[0], self.__csv_data_array[index], color=line_color_list[index-1], label=line_legend_list[index-1])
        #線に付与したlabel(凡例)を表示する
        matplotlib.pyplot.legend()

    def plot_show(self, x_label, y_label):
        # plotの軸の名前を設定
        matplotlib.pyplot.xlabel(x_label, fontsize=10, fontname='serif')
        matplotlib.pyplot.ylabel(y_label, fontsize=10, fontname='serif')
        # plotする
        matplotlib.pyplot.show()


if __name__ == "__main__":
    # 八王子市年齢別人口
    # http://www.city.hachioji.tokyo.jp/contents/open/002/p005866.html
    csv_url_list = list([])

    # matplotlibで使える色のリスト
    # http://matplotlib.org/examples/color/named_colors.html
    # 一応16進数カラーコードでも指定はできる
    line_color_list = list([])

    # 凡例(線の色がどの年のデータを表しているか)
    line_legend_list = list([])

    # H25 6月
    csv_url_list.append('http://www.city.hachioji.tokyo.jp/contents/open/002/p005866_d/fil/nenreibetsu_jinkou_2506.csv')
    line_color_list.append('blue')
    line_legend_list.append('H25.6')

    # H25 9月
    csv_url_list.append('http://www.city.hachioji.tokyo.jp/contents/open/002/p005866_d/fil/nenreibetsu_jinkou_2509.csv')
    line_color_list.append('green')
    line_legend_list.append('H25.9')

    # H25 12月
    csv_url_list.append('http://www.city.hachioji.tokyo.jp/contents/open/002/p005866_d/fil/nenreibetsu_jinkou_2512.csv')
    line_color_list.append('red')
    line_legend_list.append('H25.12')

    # H26 3月 (「平成25年3月末日」tと書いてあるのは25年「度」3月末……の意味らしい)
    csv_url_list.append('http://www.city.hachioji.tokyo.jp/contents/open/002/p005866_d/fil/nenreibetsu_jinkou_2503.csv')
    line_color_list.append('cyan')
    line_legend_list.append('H26.3')

    # H26 6月
    csv_url_list.append('http://www.city.hachioji.tokyo.jp/contents/open/002/p005866_d/fil/nenreibetsu_jinkou_2606.csv')
    line_color_list.append('magenta')
    line_legend_list.append('H26.6')

    # H26 9月
    csv_url_list.append('http://www.city.hachioji.tokyo.jp/contents/open/002/p005866_d/fil/nenreibetsu_jinkou_2609.csv')
    line_color_list.append('yellow')
    line_legend_list.append('H26.9')

    # H26 12月
    csv_url_list.append('http://www.city.hachioji.tokyo.jp/contents/open/002/p005866_d/fil/nenreibetsu_jinkou_2612.csv')
    line_color_list.append('black')
    line_legend_list.append('H26.12')

    # H27 3月 (同様にH26年「度」3月末の意味のようだ)
    csv_url_list.append('http://www.city.hachioji.tokyo.jp/contents/open/002/p005866_d/fil/nenreibetsu_jinkou_2703.csv')
    line_color_list.append('gray')
    line_legend_list.append('H27.3')

    csv_plotter = MultiCSVPlotter(csv_url_list)
    csv_plotter.set_referring_column_index(2)
    csv_plotter.download_csv_then_push_stack('shift-jis')
    csv_plotter.make_plot_csv_data_2d(line_color_list, line_legend_list)
    csv_plotter.plot_show('Age', 'Population Size')

[Work/Class/Python3の基礎とデータ処理/2_PythonLibraries]

CSVデータからの線形重回帰分析

この内容は前期試験の課題である

気象庁オープンデータ

前回やったのと同じ「気象庁|過去の気象データ・ダウンロード」ページから,必要なデータを絞ってCSV形式でダウンロードすることができる.今回は複数の観測地点のデータを持ってきて,年度と緯度と8月の日中最高気温の月平均を見てみる.

重回帰分析

重回帰分析とは,説明変数が2変数以上の回帰分析である.多変量解析と呼ばれる分析手法群の中の一つの手法である.重回帰分析は,モデルが線形である場合,つまり簡単に言えば「y = ax1 + bx2 + c」のaとbとcを求めることに相当する.

重回帰分析の時,モデルの中の説明変数x1やx2は,実は(x1)^2など二乗の場合でも分析できる.あくまで係数との関係が線形であれば線形回帰として求められる.この場合,説明変数を入力する際にあらかじめ二乗したデータを作っておいて,それを入力する作業が必要になる.

Scipyでの2変数重回帰分析

単純線形回帰の場合はnumpyだけでも計算を行うことができたが,重回帰分析ではscipyというライブラリを用いる.使い方は同じく簡単である.

import numpy
import scipy.linalg

explanatory_array1 = [........] # 説明変数1 配列の中身を入れておく
explanatory_array2 = [........] # 説明変数2 上記に同じ
responsive_array = [.......] # 目的変数の配列 中身を入れておく

N = len(explanatory_array1)
G = numpy.array([explanatory_array1, explanatory_array2, np.ones(N)]).T

# np.ones(N)は計算用に使う行列の生成

# Tについては以下を参照
# https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.T.html
result = scipy.linalg.solve(G.T.dot(G), G.T.dot(responsive_array))

# モデルが y = ax1 + bx2 + cの時
# result[0]: a, result[1]: b, result[2]:c となる配列が返って来る

課題

複数の観測点の観測データをもとに,説明変数1:観測年,説明変数2:観測された地点の緯度,日中平均気温の月平均を目的変数として,重回帰分析を行い,プロットするPythonコードを書け.


[Work/Class/Python3の基礎とデータ処理/1_PythonBasic]

lambda式, 高階関数, 内包表記

注意

このページは,Pythonの言語仕様の最大の特徴である部分を取り上げているのだが,NumPyなどの数値計算ライブラリには使えない(使うと遅くなる)ので,ぶっちゃけ覚えなくても良い

高階関数

map()関数,filter()関数など,関数そのもの(lamda式で定義する)を引数に取ったり,戻り値に指定する関数のことを「高階関数」と呼ぶ.関数型言語ではよく使われるものである.逆にC言語やJava等関数が必ず静的に存在していなければならない言語では使うことができない.

lambda式

lambda式とは,(Pythonでは)1行で表現でき,かつ何らかをreturnする関数を,その場で定義して使うものである.一般的には「lambda関数(無名関数)」と呼ばれるのだが,Pythonのそれは「returnする値を生成する1行のみの式」という縛りがあるため「lambda式」と呼ばれている.

lamda式は関数オブジェクトを生成する.

JavaScriptでコールバック関数に,その場で定義する関数を渡すのと同じ形である.

print((lambda x, y: x + y)(3, 7))

何を行っているかわかんねーと思うが(AA略,lambdaというキーワードに続けて「このlambda式の引数はx, y」であるという情報,returnする値を生成する処理の中身「x + y」,そのlambda式に,引数3と7を与えて呼び出す,という構成になっている.出力されるのは3+7で「10」になる.

lambda式の中でif〜elseを使いたい場合には,後ろに書くという決まりになっているが,その順番がややこしい.

lambda 引数: Trueの時に返す値を作る式 if 条件式 else Falseの時に返す値を作る式

という順番になる.

例えば「第1引数が第2引数より大きい場合は第1引数の2倍の値を,それ以外の場合は第2引数の3倍の値を返す」lambda式を定義し,即座に第1引数=3,第2引数=7を入れて実行した場合,以下のようになる.

(lambda x, y: x*2 if x>y else y*3)(3, 7)

3は7より大きいのでelseの後ろのy*3が実行され,返される.

Pythonにおいてはlambda式単体ではあまり使い所がないのだが,mapやfilter等の関数と組み合わせることで効果を発揮する.

map関数

map関数は,第一引数にlambda式(もしくは関数オブジェクト),第2引数にイテレータ(range()関数等)やジェネレータ,listtupleを取る.第2引数で与えられたものの各要素に対して与えられたlambda式を適用した結果をイテレータとして返す.それをlist()関数に突っ込むことで,lambda式の操作が適用された新たなlistを手に入れることができる.

my_list = list(map(lambda x: x * 2, range(10)))

と書くと,変数my_listの中には,[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]というリストが入る.

元々range(10)[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]を順次出力していくイテレータを返すが,この各要素をlambda式の引数xで受け取り,lambda式の処理部分x * 2で2倍して,map関数はそのイテレータを返す,と行った具合である.

filter関数

前述のmapは各要素に対して処理をするlambda式をとるが,filter関数は,TureもしくはFalseを返す条件式のlambda式(もしくは関数オブジェクト)を取る.

my_list = list(filter(lambda x: x%2 == 0, range(10)))

lambda式がTrueを返す時の要素のみを集めたlistが生成される.つまりrange(10)の段階では[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]が帰ってくるが,filter関数の第一引数であるlambda x: x%2 == 0の部分が「その要素を2で割った時の剰余が0の時Trueを返す」,さらにfilter関数はそのlambda式がTrueの時のみ値を返すため,それがlistに入って[0, 2, 4, 6, 8]という偶数のみのlistが生成される.

内包表記

内包表記とは,与えられたリストに対するイテレータ処理をリスト内に書いてしまうことによりコード量を減らすというアクロバットな書法である.Python独自の書法で「Pythonicな書き方」の典型例と言われる.

同じようにしてジェネレータを定義することもできる(煩雑になるため本稿では省くが概念自体は覚えておくとよい).

ジェネレータとは,イテレータがあらかじめ作られたリストの中からnext関数(メッセージ)により一つずつ要素を取り出してくれるのに対して,一つずつ生成するものをいう.イテレータとジェネレータは同じように扱うことができる.range()関数はイテレータを返すため,値を一度に生成しているが,generatorその度ごとに返す値を生成する,という違いがある.

リスト内包表記

前述の「2倍にして返す」map関数の処理

list(map(lambda x: x * 2, range(10)))

をリスト内包表記で書くと,以下のようになる.

[x*2 for x in range(10)]

「ホントに何を言っているのかわからない」という声が多数あるが,最初の「x*2」がlambda式の部分にあたり,その引数xに対して値を与えているのが後半のfor文「for x in range(10)」である.

filter関数のようなlambdaに条件式を与える場合は,「さらにマジで何を言ってるのかわからない」状態の書法になる.

list(filter(lambda x: x%2 == 0, range(10)))

この処理を内包表記にすると,

[x for x in range(10) if x%2 == 0]

条件式の場合は,TrueもしくはFalseを返すlambda式が後ろに来ることに注意.

なぜ頭にもう一つxがあるかというと,lambda式がTrueを返した要素に対してさらに処理を書くことができるからである.

上記の式では当然[0, 2, 4, 6, 8]が返って来るが,さらに各要素を2倍したリストが欲しいとする.この場合,

[x*2 for x in range(10) if x%2 == 0]

とすると,[0, 4, 8, 12, 16]が返って来る,という次第である.

内包表記は速度が速い

なぜ内包表記を用いるかというと,単純に処理速度が早くなるからである.しかし数値計算の処理速度ではNumPyの関数群を使う方が早くなるし,可読性が下がることも無視できない.共同開発の時は他者が読めるかどうか確認してからこれらの内包表記を使う方がよい.


[Work/Class/Python3の基礎とデータ処理/2_PythonLibraries]

NumPyと配列(array)とmatplotlib

NumPy

NumPyは数字(int, float)等の配列をlistとは別の数字専用の配列で扱えるようにしたライブラリである.listとは違いC言語で書かれているため,NumPyのデータ型(数字の配列)numpy.ndarrayとnumpyに用意されている関数群を使えば,listで処理をするよりもはるかに早い計算ができる.

NumPyは配列データ型ndarrayと,それを操作する静的クラス関数で構成されている.ndarrayのインスタンス関数はほぼない(インスタンス変数のshapeを使うぐらい)ので,C言語的な手続き型プログラミングのスタイルになる.

NumPyの配列numpy.ndarray

NumPyでは数字専用の配列numpy.ndarray(n次元配列)クラスが提供されており,そのインスタンスを用いることが前提のNumPyの関数群が用意されている.

ndarrayは一般的にコンストラクタ呼び出しで生成するのではなく,初期化用の静的クラス関数numpy.array(), numpy.empty(), numpy.zeros(), numpy.ones(), numpy.identity()などを使ってインスタンスを生成する.

NumPyの配列はC言語と同じくサイズが固定されている(サイズを動的に弄れない).したがって,インスタンスを生成してくれるる静的関数.array()を呼び出す時に要素を全て入れるか,.empty()関数で形状(n x mの行列である,など)を指定する必要がある.また.empty()でデータタイプ(int, floatなど)も生成時に指定しておくと,実行時に予期せぬエラーが起きることが少なくなる.他に単位行列を作る.identity()関数もある.

import numpy
# 要素を入れてarray関数を呼び出す
my_numpy_array = numpy.array([1, 3, 5])
print(my_numpy_array)

# float型の10要素の空配列(1次元)を生成
my_numpy_array = numpy.empty(10, dtype=float)
print(my_numpy_array) 
# emptyで生成すると,適当な値が入っていることがわかる

# float型0.0の要素で埋められた3x4の2次元配列(行列)を生成
# (3, 4)と中括弧で示すのはtupleの意味
my_numpy_array = numpy.zeros((3, 4), dtype=float)
print(my_numpy_array)

# int型の0の要素で埋められた2x6の行列を生成
my_numpy_array = numpy.zeros((2, 6), dtype=int)
print(my_numpy_array)

# int型の1の要素で埋められた3x4の行列を生成
my_numpy_array = numpy.ones((3, 4), dtype=int)
print(my_numpy_array)

# 4x4のfloatの単位行列を生成
# 単位行列は2次元配列であることが自明なので,
# tupleで(4,4)を与えずに1スカラ値変数だけでいける
my_numpy_array = numpy.identity(4, dtype=float)
print(my_numpy_array)

ndarrayのサイズを取得する

numpyの静的関数にndarrayのインスタンスを突っ込むと,n次元配列のnがわかる.

my_numpy_array = numpy.identity(4, dtype=float)
print(numpy.ndim(my_numpy_array)) # =>4x4の行列は2次元配列なので,出力は「2」

ndarrayのインスタンスが持つ.shape変数(tuple型)により,そのインスタンスの各次元のサイズを取得することができる.2次元配列の場合2要素のtupleなので,それぞれ[0][1]でアクセスする.

# 3要素の1次元配列を宣言
my_numpy_array = numpy.ones((3), dtype=int) 
print(my_numpy_array.shape) # => (3, )
print(my_numpy_array.shape()[0]) # => 3
print(my_numpy_array.shape()[1]) #1次元配列なので,これはエラーになる

# 5x4要素の2次元配列を宣言
my_numpy_array = numpy.zeros((5, 4), dtype=int)
print(my_numpy_array.shape) # => (5, 4)
print(my_numpy_array.shape[0]) # => 5
print(my_numpy_array.shape[1]) # => 4

3次元配列なら3要素のtupleになるので,[0], [1], [2]でそれぞれアクセスする.

ndarrayに要素を追加する

ndarrayの静的関数としてappend()関数が用意されていて,複数の1次元配列を新たな1次元配列として連結することができる.

前述の通りndarrayのサイズは変更不可なので,append()関数で連結しても,第1引数に入れたインスタンスそのものが拡張されているわけではなく,新しいndarrayのインスタンスを返すだけなので,別の変数に代入する必要がある.

my_numpy_array = numpy.array([1, 3, 5]) # 要素1, 3, 5を持つ1次元配列のインスタンスを作成
new_my_numpy_array = numpy.append(my_numpy_array, numpy.array([7, 9, 11]))
print(new_my_numpy_array) # 連結された新しいndarrayのインスタンス
print(my_numpy_array) # もとのインスタンスは変更されていない

arange()関数

標準listでいう所のrange関数に当たるのが,numpyの静的関数arange()である.使い方は同じ.

my_numpy_array = numpy.array(numpy.arange(10, 15, 0.1))
print(my_numpy_array)

vstackによる1次元配列の積み重ねによる2次元配列の生成

例えば要素数10の配列が二つあるとして,これをもとに2x10の2次元配列(2x10の行列)を作りたい時,

my_numpy_array1 = numpy.array(numpy.arange(10, 15, 0.1))
my_numpy_array2 = numpy.array(numpy.arange(5, 10, 0.1))
print(numpy.vstack((my_numpy_array1, my_numpy_array2)))

とすると,作り出せる.vstack()関数は小括弧が二重であることに注意.(tupleを意味するので)

これまでと同じく,元の配列が拡張されるわけではなく,新たな行列のインスタンスが生成されて返される.

vstack()関数で,要素数が同じ配列なら,どんどん足していく(行列の行が増えていく)ことが可能である.例えば,

my_numpy_array = numpy.array(numpy.arange(1, 10, 0.1))
my_numpy_matrix = numpy.vstack((my_numpy_array, numpy.array(numpy.arange(2, 11, 0.1))))
my_numpy_matrix = numpy.vstack((my_numpy_matrix, numpy.array(numpy.arange(3, 12, 0.1))))
print(my_numpy_matrix) # => 3x90の行列ができている.

という感じである.

配列とスカラ値,もしくは配列同士の四則演算

配列に対してスカラ値(値が一つだけ入っている)変数を四則演算すると,各要素に対してそれが適用される

my_array = numpy.array(numpy.arange(1, 10, 2))
my_array_plus_2 = my_array + 3 # => [4 6 8 10 12]
my_array_minus_2 = my_array - 2 # => [-1 1 3 5 7]

同様に,要素数が同じ配列同士を四則演算すると,要素同士の四則演算となる.(行列演算ではないことに注意)

行列の演算

上記の計算は,配列の各要素に対して適用されるが,行列として掛け算をしたい(行列の積を求めたい)場合に静的関数の.dot()関数を使う.やはり他の変数で受けとらなければならない.

my_matrix1 = numpy.ones((4, 4), dtype=float)
my_matrix2 = numpy.identity(4, dtype=float)
my_matrix3 = numpy.dot(my_matrix1, my_matrix2)

同様にdot()関数を使うことで,ベクトル(つまり1次元配列)と行列(つまり2次元配列)の積も求めることができる.(これは2次元CGや3次元CGの座標変換などでよく使われる)

my_vector = numpy.array([1, 3.2, 5.6])
my_matrix = numpy.array([[4.2, 3.6, 2.5], [3.4, 8.9, 0.4], [0.2, 1.1, 0.5]])
print(numpy.dot(my_matrix, my_vector))

他の行列演算は,numpyのさらに中に定義されているクラスlinalgの静的関数を使う.こちらも静的関数である.

my_matrix = numpy.ones(4, dtype=float)
my_transposed_matrix = numpy.linalg.transpose(my_matrix) # 転置行列
my_inversed_matrix = numpy.linalg.inv(my_matrix) # 逆行列
my_eigenvalue = numpy.linalg.eig(my_matrix) # 行列の固有値

matplotlib

簡単に言えばグラフを書くためのライブラリである.バックエンドのGUIライブラリに何を使っているかは環境によって違うが,AnacondaやMSYS2のmingw64でインストールされるPython3用のmatplotlibでは,C++用のマルチプラットフォームGUIライブラリQtをPythonから使うための「PyQt」Ver.5が用いられている.

import as

ここまで書いてくると,numpyだけならともかく,numpy.linalgクラスやmatplotlib.pyplotクラス内の静的関数を書くのが(文字数的に)面倒臭くなってくる.そこで,

import numpy as np
from numpy import linalg as la # numpyの中のlinalgクラスをimport……という意味
from matplotlib import pyplot as plt

asキーワードを使ってimportすると,その名前(この場合はnpla, plt)でそのクラスにアクセスできるようになる.

一般的にnumpyは「np」という名前で,linalglaという名前で,matplotlib.pyplotpltという名前でimportされることが多いので,慣習に従うべきである.


[Work/Class/OpenGL/2_OpenGL2toX]

glmによる変換行列生成と頂点シェーダ

とりあえず頂点の色は固定で,頂点シェーダのみの処理

/* GenerateMatrixVertexShader_1.cpp */
#include <GL/glew.h>

#ifdef __APPLE__
#include <OpenGL/opengl.h>
#else
#include <Gl/gl.h>
#endif

#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <string>
#include <vector>
#include <iostream>
#include <fstream>

//プロトタイプ宣言
GLuint loadShaders(const char* vertex_file_path,
		     const char* fragment_file_path);

//あらかじめ描画する三角形の座標を「1次元配列」として持っておく
//x1, y1, z1, x2, y2, z2, x3, y3, z3
//という感じで並べる
//GLFloatは要するにfloatそのまま
static const GLfloat myFirstTriangleVertexArray[9] = 
  {-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0};

int main(void){
  //OpenGL ver.3.2 Core Profileを選択する
  //内容は理解する必要はない
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //OSX用の指定
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

  GLFWwindow* glfwWindow;
  if(!glfwInit()){
    return -1;
  }

  int windowWidth = 640;
  int windowHeight = 480;
  glfwWindow = 
    glfwCreateWindow(windowWidth, windowHeight,
		     "GenerateMatrixVertexShader_1",
		     NULL, NULL);
  if(glfwWindow == NULL){
    glfwTerminate();
    return -1;
  }
  
  //作成したウィンドウをOpenGLの処理対象にするContext登録
  glfwMakeContextCurrent(glfwWindow);
  //GLEWを初期化する
  glewExperimental = true;
  if(glewInit() != GLEW_OK){
    fprintf(stderr, "Cannot initialize GLEW\n");
    return -1;
  }

  //配列バッファオブジェクトを生成し,現在のContextに登録する
  GLuint vertexArrayID;
  glGenVertexArrays(1, &vertexArrayID);
  glBindVertexArray(vertexArrayID);

  //シェーダプログラムを使えるようにする
  GLuint programID = 
    loadShaders("GenerateMatrixVertexShader_1_VertexShader.glsl",
		"GenerateMatrixVertexShader_1_FragmentShader.glsl");
  

  //-------------------ここまでは,この順番で行う必要がある--------------------------

  //頂点バッファの初期化 
  //GLfloatの配列から,MainLoop内で描画を実行できる頂点バッファの形を作る作業である
  //モデルごとに GLuint型の変数(ここではmyFirstTriangleVertexBuffer)を作り,
  //最初に作ったGLfloatの配列(ここではmyFirstTriangleVertexArray)をセットして,
  //バッファの番号がmyFirstTriangleVertexBuffer変数に入る
  GLuint myFirstTriangleVertexBuffer;
  glGenBuffers(1, &myFirstTriangleVertexBuffer);
  //GL_ARRAY_BUFFERタグに頂点バッファをバインドする
  glBindBuffer(GL_ARRAY_BUFFER, myFirstTriangleVertexBuffer);
  glBufferData(GL_ARRAY_BUFFER, 
	       sizeof(myFirstTriangleVertexArray), 
	       myFirstTriangleVertexArray, 
	       GL_STATIC_DRAW);
  
  while(!glfwWindowShouldClose(glfwWindow)){
    
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    //実際にシェーダを使って描画する
    glUseProgram(programID);

    
    //gluPerspectiveと同じ役割をする行列を生成する
    glm::mat4 projectionMatrix = 
      glm::perspective(glm::radians(45.0),
		       (double)windowWidth / (double)windowHeight,
		       0.1, 100000.0);
    
    //gluLookAtと同じ役割をする行列を生成する
    glm::mat4 viewMatrix = 
      glm::lookAt(glm::vec3(4.0, 3.0, 3.0), //カメラの位置
		  glm::vec3(0.0, 0.0, 0.0), //カメラが見ている座標は原点
		  glm::vec3(0.0, 1.0, 0.0)); 
    //カメラの位置とカメラが見る座標の線に乗っているカメラの「上」を指定する.
    //通常はY座標だけ1にしておけば良い
    
    glm::mat4 modelMatrix = //glRotate, glTranslate等と同じ役割をする行列を生成する
      glm::mat4(1.0); //単位行列を生成するので,原点のまま
    
    //glTranslateと同じ役割をする行列は以下で生成
    //glm::translate(1.0, 0.0, 0.0);

    //glRatateと同じ役割をする行列は以下で生成
    //glm::vec3 rotationAxis(0.0, 0.0, 1.0); //原点から(0, 0, 1)へ向かう軸を作り
    //glm::rotate(60, rotationAxis); //その軸にそって60度回転する
      
    //glm::scale(2.0, 2.0, 2.0); //大きさを2倍にする行列を生成

    //これらの行列をglRotate, glTranslate, glScaleと同じ順に掛け算してって
    //最終的なModelMatrixを生成する
    //glPopMatrixとglPushMatrixの間は処理が逆順になることに注意

    //すべての行列を掛け算すると最終的に座標に適用する行列ができる
    glm::mat4 finalModelViewProjectionMatrix = 
      projectionMatrix * viewMatrix * modelMatrix;

    //頂点シェーダのプログラムに渡す
    //頂点シェーダプログラムのuniform mat4型変数"finalMVP"のIDを取り出す
    GLuint matrixID = glGetUniformLocation(programID, "finalMVP");
    //取り出した頂点シェーダプログラム内のmat4型変数finalMVPに,
    //mainプログラム中のfinalModelViewProjection行列を渡す
    glUniformMatrix4fv(matrixID, 
		       1, 
		       GL_FALSE, 
		       &finalModelViewProjectionMatrix[0][0]);

    //頂点を描画する
    //頂点シェーダプログラムの
    //attribute vec3型変数"vertexPositionFromMain"のIDを取り出す
    GLint vertexAttributeID = 
      glGetAttribLocation(programID, "vertexPositionFromMain");
    //頂点描画モードをオンにする
    glEnableVertexAttribArray(vertexAttributeID);
    //初期化した頂点バッファの番号が入っているバッファをバインドする
    glBindBuffer(GL_ARRAY_BUFFER, myFirstTriangleVertexBuffer); 
    //頂点情報を転送する
    glVertexAttribPointer(vertexAttributeID,
			  3, //頂点数
			  GL_FLOAT, //頂点を指定した型floatで問題無い
			  GL_FALSE, //正規化をしない
			  0, //ストライド
			  (void*)0); //配列バッファオブジェクト
    //描く!
    glDrawArrays(GL_TRIANGLES, 0, 3); //頂点0から合計3つの頂点を描く.三角形型.
 
    //頂点描画モードをオフにする
    glDisableVertexAttribArray(vertexAttributeID);

    glfwSwapBuffers(glfwWindow);
    glfwPollEvents();
  }

  //GL_ARRAY_BUFFERにバインドされたバッファオブジェクトのバインドを解除
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  //頂点バッファを破棄
  glDeleteBuffers(1, &myFirstTriangleVertexBuffer);
}

//シェーダを読み込むプログラム
//大して理解する必要のない内容なので,これはこのまま使って良い
GLuint loadShaders(const char* vertex_file_path,
		   const char* fragment_file_path){
  // シェーダを表すIDを作る
  GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
  GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

  // ファイルから頂点シェーダのコードを読み込み
  std::string VertexShaderCode;
  std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
  if(VertexShaderStream.is_open()){
    std::string Line = "";
    while(getline(VertexShaderStream, Line))
      VertexShaderCode += "\n" + Line;
    VertexShaderStream.close();
  }
  
  // ファイルからフラグメントシェーダを読み込み
  std::string FragmentShaderCode;
  std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
  if(FragmentShaderStream.is_open()){
    std::string Line = "";
    while(getline(FragmentShaderStream, Line))
      FragmentShaderCode += "\n" + Line;
    FragmentShaderStream.close();
  }
  
  GLint Result = GL_FALSE;
  int InfoLogLength;
  
  // 頂点シェーダをコンパイル
  printf("Compiling shader : %s\n", vertex_file_path);
  char const * VertexSourcePointer = VertexShaderCode.c_str();
  glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
  glCompileShader(VertexShaderID);
  
  // 頂点シェーダをチェック
  glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
  glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  if(Result == GL_FALSE){
    std::vector VertexShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]);
    exit(1);
  }
  
  // フラグメントシェーダをコンパイル
  printf("Compiling shader : %s\n", fragment_file_path);
  char const * FragmentSourcePointer = FragmentShaderCode.c_str();
  glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
  glCompileShader(FragmentShaderID);
  
  // フラグメントシェーダをチェック
  glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
  if(Result == GL_FALSE){
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector FragmentShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]);
    exit(1);
  }
  
  // プログラムをリンク
  fprintf(stdout, "Linking program\n");
  GLuint ProgramID = glCreateProgram();
  glAttachShader(ProgramID, VertexShaderID);
  glAttachShader(ProgramID, FragmentShaderID);
  glLinkProgram(ProgramID);
  
  // プログラムをチェック
  fprintf(stdout, "Checking program\n");
  glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
  if(Result == GL_FALSE){
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector ProgramErrorMessage( std::max(InfoLogLength, int(1)) );
    glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
    fprintf(stdout, "%s\n", &ProgramErrorMessage[0]);
    exit(1);
  }
  fprintf(stdout, "Deleting Shader ID\n");
  glDeleteShader(VertexShaderID);
  glDeleteShader(FragmentShaderID);

  fprintf(stdout, "Finish load, compile, bind shader\n");  
  return ProgramID;
}
// GenerateMatrixVertexShader_1_VertexShader.glsl
// written in GLSL1.5 for OpenGL3.2
attribute vec3 vertexPositionFromMain; //元々の頂点座標

//main関数から渡される行列
uniform mat4 finalMVP;

void main(){
  //頂点のアウトプット座標は,最終的な変換行列 * 元々の座標
  //元々の座標は3次元なのだが,行列演算するために4次元vectorにする
  //意味合いはアフィン変換と同じような感じ

  //vec4 v = vec4(vertexPositionFromMain, 1);
  //4次元vectorにしたら最終的な変換行列とかける
  vec4 v = vec4(vertexPositionFromMain.x, 
		vertexPositionFromMain.y,
		vertexPositionFromMain.z,
		1.0);
  //gl_Positionというvec4型変数に値を打ち込むことで,頂点座標の描画が行われる
  gl_Position = finalMVP * v;
}
// GenerateMatrixVertexShader_1_FragmentShader.glsl
// written in GLSL1.5 for OpenGL3.2

void main(){
  //とりあえず色は赤で固定(RGBA)
  //gl_FragColorというvec4型変数に値を打ち込むことでピクセルの色を指定できる
  gl_FragColor = vec4(1.0, 0.0, 0.0, 0.0);
}

色情報も送り込んでみる

// GenerateMatrixVertexShader_2.cpp
#include &lt;GL/glew.h&gt;

#ifdef __APPLE__
#include &lt;OpenGL/opengl.h&gt;
#else
#include &lt;Gl/gl.h&gt;
#endif

#include &lt;GLFW/glfw3.h&gt;
#include &lt;glm/glm.hpp&gt;
#include &lt;glm/gtc/matrix_transform.hpp&gt;


#include &lt;string&gt;
#include &lt;vector&gt;
#include &lt;iostream&gt;
#include &lt;fstream&gt;


//プロトタイプ宣言
GLuint loadShaders(const char* vertex_file_path,
		     const char* fragment_file_path);

//あらかじめ描画する三角形の座標を「1次元配列」として持っておく
//x1, y1, z1, x2, y2, z2, x3, y3, z3
//という感じで並べる
//GLFloatは要するにfloatそのまま
static const GLfloat myFirstTriangleVertexArray[9] = 
  {-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0, 0.0};
static const GLfloat myTriangleVertexArray1[9] = 
  {0.8, 0.1, 0.0, -0.3, -0.2, 0.0, 0.4, -0.8, 0.0};
static const GLfloat myTriangleVertexArray2[9] = 
  {0.5, 0.0, 0.0, -0.5, -0.3, 0.0, 0.2, -0.4, 0.0};

//あらかじめ描画する色(RGB)の3頂点分を1次元配列として作っておく
static const GLfloat myFirstTriangleColorArray[9] = 
  {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0};

int main(void){
  //OpenGL ver.3.2 Core Profileを選択する
  //内容は理解する必要はない
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //OSX用の指定
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

  GLFWwindow* glfwWindow;
  if(!glfwInit()){
    return -1;
  }

  int windowWidth = 640;
  int windowHeight = 480;
  glfwWindow = 
    glfwCreateWindow(windowWidth, windowHeight,
		     "GenerateMatrixVertexShader_2",
		     NULL, NULL);
  if(glfwWindow == NULL){
    glfwTerminate();
    return -1;
  }
  
  //作成したウィンドウをOpenGLの処理対象にするContext登録
  glfwMakeContextCurrent(glfwWindow);
  //GLEWを初期化する
  glewExperimental = true;
  if(glewInit() != GLEW_OK){
    fprintf(stderr, "Cannot initialize GLEW\n");
    return -1;
  }

  //配列バッファオブジェクトを生成し,現在のContextに登録する
  GLuint vertexArrayID;
  glGenVertexArrays(1, &vertexArrayID);
  glBindVertexArray(vertexArrayID);

  //シェーダプログラムを使えるようにする
  GLuint programID = 
    loadShaders("GenerateMatrixVertexShader_2_VertexShader.glsl",
		"GenerateMatrixVertexShader_2_FragmentShader.glsl");
  

  //-------------------ここまでは,この順番で行う必要がある--------------------------

  //頂点バッファの初期化 
  //GLfloatの配列から,MainLoop内で描画を実行できる頂点バッファの形を作る作業である
  //モデルごとに GLuint型の変数(ここではmyFirstTriangleVertexBuffer)を作り,
  //最初に作ったGLfloatの配列(ここではmyFirstTriangleVertexArray)をセットして,
  //バッファの番号がmyFirstTriangleVertexBuffer変数に入る
  GLuint myFirstTriangleVertexBuffer;
  glGenBuffers(1, &myFirstTriangleVertexBuffer);
  glBindBuffer(GL_ARRAY_BUFFER, myFirstTriangleVertexBuffer);
  glBufferData(GL_ARRAY_BUFFER, 
	       sizeof(myFirstTriangleVertexArray), 
	       myFirstTriangleVertexArray, 
	       GL_STATIC_DRAW);

  //色付け用のバッファの初期化
  GLuint myFirstTriangleColorBuffer;
  glGenBuffers(1, &myFirstTriangleColorBuffer);
  glBindBuffer(GL_ARRAY_BUFFER, myFirstTriangleColorBuffer);
  glBufferData(GL_ARRAY_BUFFER,
	       sizeof(myFirstTriangleColorArray),
	       myFirstTriangleColorArray,
	       GL_STATIC_DRAW);
  
  
  while(!glfwWindowShouldClose(glfwWindow)){
    
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    //実際にシェーダを使って描画する
    glUseProgram(programID);

    //gluPerspectiveと同じ役割をする行列を生成する
    glm::mat4 projectionMatrix = 
      glm::perspective(glm::radians(45.0),
		       (double)windowWidth / (double)windowHeight,
		       0.1, 100000.0);
    
    //gluLookAtと同じ役割をする行列を生成する
    glm::mat4 viewMatrix = 
      glm::lookAt(glm::vec3(4.0, 3.0, 3.0), //カメラの位置
		  glm::vec3(0.0, 0.0, 0.0), //カメラが見ている座標は原点
		  glm::vec3(0.0, 1.0, 0.0)); 
    //カメラの位置とカメラが見る座標の線に乗っているカメラの「上」を指定する.
    //通常はY座標だけ1にしておけば良い
    
    // ------モデルの描画はここから-------------------------
    glm::mat4 modelMatrix = //glRotate, glTranslate等と同じ役割をする行列を生成する
      glm::mat4(1.0); //単位行列を生成するので,原点のまま
    
    //glTranslateと同じ役割をする行列は以下で生成
    //glm::translate(1.0, 0.0, 0.0);

    //glRatateと同じ役割をする行列は以下で生成
    //glm::vec3 rotationAxis(0.0, 0.0, 1.0); //原点から(0, 0, 1)へ向かう軸を作り
    //glm::rotate(60, rotationAxis); //その軸にそって60度回転する
      
    //glm::scale(2.0, 2.0, 2.0); //大きさを2倍にする行列を生成

    //これらの行列をglRotate, glTranslate, glScaleと同じ順に掛け算してって
    //最終的なModelMatrixを生成する
    //glPopMatrixとglPushMatrixの間は処理が逆順になることに注意

    //すべての行列を掛け算すると最終的に座標に適用する行列ができる
    glm::mat4 finalModelViewProjectionMatrix = 
      projectionMatrix * viewMatrix * modelMatrix;



    //頂点シェーダのプログラムに渡す
    //頂点シェーダープログラムのuniform mat4型変数 "finalMVP"を取り出す
    GLuint matrixID = glGetUniformLocation(programID, "finalMVP");
    //取り出した頂点シェーダプログラム内のmat4型変数finalMVPに,
    //mainプログラム中のfinalModelViewProjection行列を渡す
    glUniformMatrix4fv(matrixID, 
		       1, 
		       GL_FALSE, 
		       &finalModelViewProjectionMatrix[0][0]);
    
    //頂点シェーダのプログラムに色情報を渡す
    //頂点シェーダプログラムのattribute vec4型変数 "colorFromMain"を取り出す
    GLint colorAttributeID = 
      glGetAttribLocation(programID, "colorFromMain");
    glEnableVertexAttribArray(colorAttributeID);
    glBindBuffer(GL_ARRAY_BUFFER, myFirstTriangleColorBuffer);
    glVertexAttribPointer(colorAttributeID,
			  3,
			  GL_FLOAT,
			  GL_FALSE,
			  0,
			  (void*)0);

    //頂点シェーダのプログラムに頂点座標を渡す
    GLint vertexAttributeID = 
      glGetAttribLocation(programID, "vertexPositionFromMain");
    //頂点描画モードをオンにする
    glEnableVertexAttribArray(vertexAttributeID);
    //初期化した頂点バッファの番号が入っているバッファをバインドする
    glBindBuffer(GL_ARRAY_BUFFER, myFirstTriangleVertexBuffer); 
    //頂点情報を転送する
    glVertexAttribPointer(vertexAttributeID, 
			  3, //頂点数
			  GL_FLOAT, //頂点を指定した型floatで問題無い
			  GL_FALSE, //正規化をしない
			  0, //ストライド
			  (void*)0); //配列バッファオブジェクト
    //描く!
    glDrawArrays(GL_TRIANGLES, 0, 3); //頂点0から合計3つの頂点を描く.三角形型.

    //頂点描画モードをオフにする
    glDisableVertexAttribArray(colorAttributeID);
    glDisableVertexAttribArray(vertexAttributeID);


    glfwSwapBuffers(glfwWindow);
    glfwPollEvents();
  }
  //バッファオブジェクトを解放する
  glBindBuffer(GL_ARRAY_BUFFER, 0);
}

//シェーダを読み込むプログラム
//大して理解する必要のない内容なので,これはこのまま使って良い
GLuint loadShaders(const char* vertex_file_path,
		   const char* fragment_file_path){
  // シェーダを作ります。
  GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
  GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);


  // ファイルから頂点シェーダのコードを読み込みます。
  std::string VertexShaderCode;
  std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
  if(VertexShaderStream.is_open()){
    std::string Line = "";
    while(getline(VertexShaderStream, Line))
      VertexShaderCode += "\n" + Line;
    VertexShaderStream.close();
  }
  
  // ファイルからフラグメントシェーダを読み込みます。
  std::string FragmentShaderCode;
  std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
  if(FragmentShaderStream.is_open()){
    std::string Line = "";
    while(getline(FragmentShaderStream, Line))
      FragmentShaderCode += "\n" + Line;
    FragmentShaderStream.close();
  }
  
  GLint Result = GL_FALSE;
  int InfoLogLength;
  
  // 頂点シェーダをコンパイルします。
  printf("Compiling shader : %s\n", vertex_file_path);
  char const * VertexSourcePointer = VertexShaderCode.c_str();
  glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
  glCompileShader(VertexShaderID);
  
  // 頂点シェーダをチェックします。
  glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
  glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  if(Result == GL_FALSE){
    std::vector&lt;GLchar&gt; VertexShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]);
    exit(1);
  }
  
  // フラグメントシェーダをコンパイルします。
  printf("Compiling shader : %s\n", fragment_file_path);
  char const * FragmentSourcePointer = FragmentShaderCode.c_str();
  glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
  glCompileShader(FragmentShaderID);
  
  // フラグメントシェーダをチェックします。
  glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
  if(Result == GL_FALSE){
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector&lt;GLchar&gt; FragmentShaderErrorMessage(InfoLogLength);
    glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
    fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]);
    exit(1);
  }
  
  // プログラムをリンクします。
  fprintf(stdout, "Linking program\n");
  GLuint ProgramID = glCreateProgram();
  glAttachShader(ProgramID, VertexShaderID);
  glAttachShader(ProgramID, FragmentShaderID);
  glLinkProgram(ProgramID);
  
  // プログラムをチェックします。
  fprintf(stdout, "Checking program\n");
  glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
  if(Result == GL_FALSE){
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    std::vector&lt;GLchar&gt; ProgramErrorMessage( std::max(InfoLogLength, int(1)) );
    glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
    fprintf(stdout, "%s\n", &ProgramErrorMessage[0]);
    exit(1);
  }
  fprintf(stdout, "Deleting Shader ID\n");
  glDeleteShader(VertexShaderID);
  glDeleteShader(FragmentShaderID);

  fprintf(stdout, "Finish load, compile, bind shader\n");
  return ProgramID;
}
// GenerateMatrixVertexShader_2_VertexShader.glsl
// written in GLSL1.5 for OpenGL3.2
attribute vec3 vertexPositionFromMain; //元々の頂点座標
// OpenGL3.3の倍は,上の1行を下の2行で置き換える
//#version 330 core
//layout(location = 0) in vec3 vertexPositionFromMain; 

//色の情報もフラグメントシェーダに直接渡さず,頂点シェーダを経由するため
//まずmainプログラムから受け取る変数を定義して,
attribute vec3 vertexColorFromMain;
//上の値をフラグメントシェーダに渡すための変数も用意する
//フラグメントシェーダ側でも宣言されている.
varying vec4 vertexColorInShader; 

//main関数から渡される行列
uniform mat4 finalMVP;

void main(){
  //頂点のアウトプット座標は,最終的な変換行列 * 元々の座標
  //元々の座標は3次元なのだが,行列演算するために4次元vectorにする
  //意味合いはアフィン変換と同じような感じ

  //vec4 v = vec4(vertexPositionFromMain, 1);
  //4次元vectorにしたら最終的な変換行列とかける
  vec4 v = vec4(vertexPositionFromMain.x, 
		vertexPositionFromMain.y,
		vertexPositionFromMain.z,
		1.0);
  gl_Position = finalMVP * v;

  //フラグメントシェーダへ色の引き渡しを行う
  vertexColorInShader = 
    vec4(colorFromMain.x, colorFromMain.y, colorFromMain.z, 0.0);
}
// written in GLSL1.5 for OpenGL3.2
// OpenGL3.3の場合は,以下の2行の宣言が必要となる.
//#version 330 core
//out vec3 outputColorFromShader; //アウトプットデータ

//頂点シェーダから渡される値
//頂点シェーダ側でも同じ名前で宣言されている
varying vec4 vertexColorInShader;

void main(){
  // 頂点に設定されていた色(という名目で受け渡された色)をそのまま最終的な色にする
  gl_FragColor = vertexColorInShader;
}

glDrawArraysではなくglDrawElementsを用いる

今まで描画する全ての三角形ポリゴンの3点座標のx, y zを,頂点順に入力した配列をglDrawArrays()関数で描画してきた.この方法では頂点の数が「ポリゴンの数の3倍」になるのは自明である.

ところが描画するポリゴンが増えてくると,ポリゴンとポリゴンの間にできる三角形の隙間もポリゴンに自動的にならないか,という欲求が出てくる.すなわち「頂点の座標だけバラバラに指定して,もしその頂点で三角形が作れるのであれば,その間を勝手にポリゴンで埋めてほしい」という欲求である.これに対応するのがglDrawElements()関数である.

指定するデータの形を見てみる.

glDrawArraysとglDrawElementsで指定するデータ構造の違い.glDrawArraysは全てのポリゴンの座標情報が必要なのに対して,glDrawElementsは「頂点の情報」と「頂点番号の配列」に分けて保持することで,データ量の削減を図っている.

さすがに「勝手に埋める」ことはできないが,glDrawArraysは全てのポリゴンの座標情報が必要なのに対して,glDrawElementsは「頂点の情報」と「頂点番号の配列」に分けて保持することで,データ量の削減を図っている.これによりポリゴンの頂点が被っている場合などは頂点座標情報を省略できるため,データ量が大幅に削減できる.