Machinの公式を使ってπを近似する
どうも、drumathです。前回の記事からだいぶ日が経ってしまいましたね。
というのも、大学生活が始まり、なかなか執筆に時間がさけなかったのが原因です。
今日は気分で少し書いてみようと思ったのですが、テーマがまたπの近似という結果になってしまいました…(許して)
Machinの公式でπを求める!
冒頭で、大学生活が始まったと述べましたが、約1か月前の解析学の授業をきっかけにこのテーマを書こうと思いまして、
実はその授業の先生がπの近似値の世界記録を持ってるらしく(詳しい話はよく知りませんが...)、そんな経緯からなのでしょうか、逆三角関数の話をしているときにMachinの公式を教えてくださいました。
そもそも、逆三角関数は(辺の比)→(角度)という写像なので、πが絡む公式はいくつかあるらしく、下記のようなものが授業では紹介されました。
実は、一番最後の「239!?」ってなるやつがMachinの公式です。
Machinの公式を示してみる
今からこの公式を使うわけですが、本当にこうなるのかとりあえず示します。
これには、
まず、Machinの公式の両辺の正接をとります。すると
πを計算するには
さて、証明ができたので、さっそく使っていきましょう
この公式を使ってどうやってπを求めるかというと、、、
積分だ!
待ってたぜぃって感じですが、どちらかというと積分は最後にちょこっとするだけで(というか積分ほぼやらないか…)、メインはマクローリン展開です。
まず、下の式を与えておきます。
これらがわかると下の式が導けますね。
おぉ、アークタンジェントがシグマで表せました!
ここで、
プログラム書いてみた
さて、本記事も終盤です。実際にプログラミングで近似します。
その前に、プログラムのイメージがしやすいように(?)上でやったf(x)をちょっと改変したg(x,n)を以下のように定義します。
gを使ってπを表すと、以下のようになります。
以下、私が書いたRubyのコードです。
def maclaurin(x, n) sum = 0.0 0.upto(n) do |k| sum += (-1.0)**k * x**(2.0 * k + 1.0) / (2.0 * k + 1.0) end sum end def machin(n) 4 * maclaurin(1.0 / 5.0, n) - maclaurin(1.0 / 239.0, n) end gets.chomp.to_i.times do |i| puts "#{i}: #{4 * machin(i)}" end
ちなみに、rubocopからは「引数名が短い!」って怒られてますw
破壊力ぅ...
どうしてMachinの公式がπの近似に使われるか。
それはこの公式の破壊力がすさまじいからです(語彙力)
なんとサンプル数(ここでいうn)を10にすると、もうRubyではこれ以上の近似はできません!
収束がめっちゃ早い。
0: 3.18326359832636 1: 3.1405970293260603 2: 3.1416210293250346 3: 3.1415917721821773 4: 3.1415926824043994 5: 3.1415926526153086 6: 3.141592653623555 7: 3.1415926535886025 8: 3.141592653589836 9: 3.1415926535897922 10: 3.141592653589794 11: 3.141592653589794 12: 3.141592653589794 13: 3.141592653589794 14: 3.141592653589794 15: 3.141592653589794
終わりに
今回、英語の宿題を残して爆速で記事を書きましたが、久しぶりに楽しかったです。
またヒマになったら記事を上げていこうともうので、よかったらご覧になってください。
最後まで読んでいただき、ありがとうございました~。
もはや自前鯖はいらない!Node.jsで作ったシンプルなチャットを開発~デプロイまでをブラウザだけでやる
今回は最近はまっているNode.jsについて書こうと思います。 昔からリアルタイムチャットアプリを作るのは夢だったので、今回はそれをお題にしてみます。 なお、同じ記事をqiitaにも投稿しました。
どんなものを、どうやって作るか
今回はこんなものを作ります。 ルーム機能や、アカウント機能などない、シンプルなチャットです。ソケットの練習のために作ったみたいなものです。実際にデプロイしたものをこちらに公開しています。
どうやって作るの?
今回の開発では、リアルタイム通信機能が不可欠です。そのため、Socket.ioというパッケージを使います。Socket.ioと言ってしまったのでもうお分かりかもしれませんが、バックエンドはNode.jsを使います。オールJSで開発だ!いぇい ということで、今回の開発プランはこうです。
- Cloud9でBlankなワークスペースを作る
- Node.jsで鯖を立てる
- Socket.ioでソケット通信のイベントを記述(ここまでバックエンド)
- クライアントサイドのhtmlを整える
- クライアントサイドjsでソケットのイベント処理
- Sassでスタイルを整える(ここまでフロントエンド)
- Git周辺を整え、Herokuへデプロイ
ゆえに、今回はHTML、jsの基本はわかっている前提で、主にnode.jsやSocket.io、Herokuへのデプロイについて書きたいと思います。
Cloud9とは
Cloud9とは、ネット上で開発が行えるIDEで、Linux環境が使えます。私はWin機を使っているので、Linuxを使いたい時によく重宝しています。PythonもRubyもnodeもperlもphpも全部入ってるんでWebサービス開発にはもってこいの環境です。入門時は特に環境構築で手間取るので、入門者や、ちょっといじってみたいという方にお勧めです。 過去にCloud9についての記事も書いたことがあるのでよろしければ読んでみてください。
開発だ!
Node.jsでHello
さて、まずはCloud9でワークスペースを「Blank」で作ります。
すると、最初にREADMEが表示されると思いますが、別に消していただいていいでしょう。ではワークスペースにserver.js
を追加しましょう。フォルダのアイコンを右クリックか、ターミナルで
touch server.js
と打ってください。
では、さっそくNode.jsで鯖を立てましょう。
server.js
に以下のようにコードを書きます。
let server = require("http").createServer((req, res)=>{ res.writeHead(200, {'Content-Type': 'text/plane'}) res.write('Hello') res.end() }) server.listen(process.env.PORT, process.env.IP, ()=>{ console.log("server") })
ポート番号とホストはCloud9からprocess.env.PORT
とprocess.env.IP
を使えと言われます。
そして上のほうにあるRunボタンを押すとnodeが走ってCloud9でWebページがホストされますので、見てみてください。Runボタンを押したときに教示されるターミナルの一番上に表示されているURLがそれです。「Hello」と表示されたでしょうか。
Expressの導入
それではフレームワークであるExpressとソケット通信のパッケージ、Socket.ioを導入しましょう。まず、Node.jsのパッケージを管理するnpmを初期化します。
npm init
質問されますので、その都度適当なものを入力します。するとpackage.json
が作られます。
そしたら
npm install --save express socket.io
とコマンドを打ってください。この2つがインストされるはずです。 では、Expressを使ってHello,Worldしてみましょう。コードは以下の通りです。
let express = require("express") let app = express() app.get('/', (req, res)=>{ res.send("Hello, World") }) app.listen(process.env.PORT, ()=>{ console.log("Server listening") })
「Hello,World」と表示されたでしょうか。静的な文字ばかり表示していても面白くないので、今度はHTMLファイルを表示させましょう。まず、views
フォルダにindex.html
を作ります。ターミナルに
mkdir views touch views/index.html
とコマンドします。
そしてserver.js
に戻って
let express = require("express") let app = express() app.get('/', (req, res)=>{ res.sendFile(__dirname + '/views/index.html') // この行を変更 }) app.listen(process.env.PORT, ()=>{ console.log("Server listening") })
とします。そしてviews/index.htmlに
<!DOCTYPE html> <html> <head> </head> <body> <p><em>Hello. World!</em></p> </body> </html>
とします。ではRunしてみましょう。斜体のハローワールドが表示されましたでしょうか。 ここまで見てきてわかる通り、鯖立てるのめっちゃ簡単ですよね。合計20行にも満たないコードですが、HTML表示までできました。ここから肉付けしていきましょう。いよいよSocket.ioの導入です。
Socket.ioの導入
この後どんどんクライアントサイドのjsも書いていくのですが、いったんソケットの話をしましょう。わかりやすくするために図を作りました。
例えば、クライアントAがソケット通信でイベントを発生させると、それが鯖に通知され、それを処理します。
鯖は処理した内容に従ってまた新たなイベント(今回は同じイベントを発生させた)を発生させます。
鯖からイベント通知を受け取ったクライアントB、Cはイベントを処理します。
このような流れでリアルタイム通信が成り立ちます。以上のことを踏まえてソケット通信をしてみましょう。コードを書く前に、
mkdir js touch js/index.js
とコマンドを打ってindex.js
を作っておきましょう。
まずはサーバサイドから書いていきます。
let express = require('express') let http = require('http') let app = express() let server = http.createServer(app) let io = require('socket.io').listen(server); app.use(express.static(__dirname)) server.listen(process.env.PORT); app.get('/', (req, res)=> { res.sendFile(__dirname + '/views/index.html'); }); io.sockets.on('connection', (socket)=>{ socket.on('eventA', (eventData)=>{ io.sockets.emit('eventB', {msg: eventData.message}) }) })
上のrequire系はもう呪文だと思って書いています。ここで
app.use(express.static(__dirname))
ですが、これを行うことでjsファイルやcssファイルを読み込むことができます。最初読み込んでるはずのjsファイルが見つからないと言われ私はSANチェックだったのですが、ドキュメントを見てみると、ミドルウェアという仕組みを使って静的ファイルを読み込むことで解決するとのことでした。 そしてsocketの部分である
io.sockets.on('connection', (socket)=>{ socket.on('eventA', (eventData)=>{ io.sockets.emit('eventB', {msg: eventData.message}) }) })
この部分ですが、簡単に言うと、eventA
というイベントが発生したら、eventB
というイベントを発生させよ、と言っているだけです。でもってeventA
からはmessage
というインデックスを持ったハッシュが渡されているわけです。このハッシュをeventData
という引数に格納して即時関数を実行し、新たにmsg
というインデックスを持ったハッシュを渡しています。
クライアントサイドも見てみましょう。HTMLのほうはjqueryとsocket.ioと先ほど作ったjs/index.jsを読み込み、ボタンにonclickイベントを付け加えただけです。
<!DOCTYPE html> <html> <head> </head> <body> <p><em>Hello. World!</em></p> <input type="text" name="msg-input" id="msg-input"/> <button id="msg-btn" onclick="sendEvent()">Send</button> <div id="msg-whole"></div> <!--追加--> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script type="text/javascript" src="js/index.js"></script> </body> </html>
var socketio = io() var sendEvent = function(){ msg = $('input#msg-input').val() socketio.emit('eventA', {message: msg}) } socketio.on('eventB', function(data){ $('div#msg-whole').prepend("<div>"+ data.msg +"</div>") })
さて、クライアントjsですが、至極シンプルなコードですね。
ボタンを押したらsendEvent()
関数が呼び出されるのですが、そこではinputボックスの中身をmessage
というインデックスのハッシュに入れて渡しています。先ほどやりましたね。そしてeventA
が発生すると繋がっているすべてのクライアントPCにeventB
が発生するので、そのイベントハンドラを記述しています。
socketio.on('eventB', function(data){ $('div#msg-whole').prepend("<div>"+ data.msg +"</div>") })
この部分ですね。単純にmsg-whole
のidを持つdivにちっちゃいdivをどんどん詰め込んでるだけです。
このようなコードから、
1. メッセージを送信
2. 鯖が受け取り、受け取ったメッセージをすべてのクライアントに送信
3. クライアントが受け取り、divの中に追加
という構図が浮かびますでしょうか。実際にタブを複製してやってみてください。リアルタイムで通信しているのがよくわかります。
私はこんな感じで作りました。
さぁ、ソケットは理解できたでしょうか?そもそも私が初心者なので拙い説明になってしまったと思うので、わからないところがあったら別途調べていただければと思います。しかし、ここまで理解できれば、あとはフロントを整えたり、イベントを増やしたりするだけでシンプルなチャットはできます。ぜひやってみてください。その一例として、私が実際に書いたコードを載せて、少し補足をさせていただきます。
. ├── js/ │ ├── index.js │ └── def events.js ├── views/ │ └── index.html ├── style/ │ ├── index.sass │ ├── _vars.sass │ ├── _baseDesign.sass │ ├── _msgStyle.sass │ └── index.css ├── node_modules/ ├── server.js ├── package.json └── README.md
package.json
{ "name": "express-socket-test-app", "version": "0.1.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node server.js" }, "author": "drumath2237", "license": "ISC", "devDependencies": { "express": "^4.16.2", "socket.io": "^2.0.4" }, "dependencies": { "express": "^4.16.3", "socket.io": "^2.0.4" } }
server.js
これはあまり変わりませんね。変更点としては、メッセージを送ったユーザーの名前がわかるようにしたことです。こうすることによって、自分のメッセージと他人のメッセージによってスタイルを変えることができますので。
let express = require('express') let http = require('http') let app = express() let server = http.createServer(app) let io = require('socket.io').listen(server); app.use(express.static(__dirname)) server.listen(process.env.PORT); app.get('/', (req, res)=> { res.sendFile(__dirname + '/views/index.html'); }); io.sockets.on('connection', (socket)=>{ socket.on('message', (data)=>{ io.sockets.emit("message", { msg: data.message, name: data.name }) }) })
views/index.html
これもあまり変えていませんね。
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="style/index.css" type="text/css" /> <link href="https://fonts.googleapis.com/css?family=Itim" rel="stylesheet"> </head> <body> <h1>Hello,<input type="text" id="user-name-input" placeholder="put your name">! Send Messages!</h1> <input type="text" name="msg-input" id="msg-input"/> <button id="msg-btn" onclick="sendMessage()"> Send </button> <div id="msg-whole"></div> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script type="text/javascript" src="js/index.js"></script> <script type="text/javascript" src="js/def events.js"></script> </body> </html>
js/index.js
makeMessage関数が少しややこしくなっていますが、スタイルを整えるためにやむなく・・・。
var socket = io() socket.on('message', function(data){ addMessage(data.msg, data.name) }) var sendMessage = function(){ var msg = $('input#msg-input').val() var name = $('input#user-name-input').val() socket.emit('message', { message: msg, name: name }) $('input#msg-input').val('') } var addMessage = function(msg, username){ var msgWhole = $('div#msg-whole') msgWhole.prepend(makeMessage(msg, username)) } var makeMessage = function(msg, username){ if(username===$('input#user-name-input').val()) return "<div style='text-align: right'><div class='my-msg'><span class='user-name'>" + username + ":</span><span class='message'>" + msg + "</span></div></div></br>" else return "<div style='text-align: left'><div class='other-msg'><span class='user-name'>" + username + ":</span><span class='message'>" + msg + "</span></div></div></br>" }
js/def events.js
ボタンを押したときのアニメーションやエンターキーでMessageを送信するようなイベントハンドラを記述しています。jQueryしか使ってない。
$(function(){ $('button#msg-btn').on('mousedown', function(){ var button = $(this) button.css({ 'top': '3px', 'border-bottom': '0' }) }) $('button#msg-btn').on('mouseup', function(){ var button = $(this) button.css({ 'top': '0', 'border-bottom': '3px solid white' }) }) $('input#msg-input').keypress(function(e){ if(e.which == 13){ sendMessage() } }) })
style/index.sass
さてSassでスタイルを整えていますが、htmlに反映するためにはコンパイルしてcssファイルにする必要があります。毎度毎度やっているのはめんどくさいので私の場合はターミナルをもう一個起動して
sass index.sass:index.css --style expanded --watch
としておけばSassファイルを監視してくれるので保存するたびに自動でコンパイルしてくれます。
@import "baseDesign" @import "vars" @import "msgStyle" html, body margin: 0 padding: 0 background: white text-align: center background: $bg-color color: white font-family: 'Itim', cursive height: 100% input @include base-design border: 0px border-bottom: 2px solid white &#user-name-input width: 200px text-align: center button#msg-btn @include base-design border-radius: 3px height: 40px box-shadow: 0px 3px 0px 0px white position: relative top: 0 div#msg-whole height: 50% width: 80% border: 2px white dashed margin: 0 auto margin-top: 20px overflow: auto padding: 10px div.my-msg @include msg-style background: #96ed9e color: white &:after content: "" position: absolute top: 20px right: -15px height: 0 width: 0 border-top: 10px transparent solid border-right: 0px border-bottom: 2px transparent solid border-left: 15px white solid span.user-name font-weight: bolder font-size: 18px margin-right: 10px span.message div.other-msg @include msg-style background: $bg-color color: white &:after content: "" position: absolute top: 20px left: -15px height: 0 width: 0 border-top: 10px transparent solid border-right: 15px white solid border-bottom: 2px transparent solid border-left: 0 span.user-name font-weight: bolder font-size: 18px margin-right: 10px span.message
style/_vars.sass
変数などを定義するパーシャルですが、背景色しか定義しなかったんで全然varsみたいに複数形にすることなかった。
$bg-color: #96d7ed
style/_msgStyle
メッセージのスタイルを記述したパーシャルです。
@mixin msg-style border: 2px white solid line-height: 40px border-radius: 15px min-width: 100px max-width: 50% display: inline-block margin: 10px padding-left: 10px padding-right: 10px font-size: 20px position: relative word-wrap: break-word
style/_baseDesign.sass
コントロールのスタイルを規定したパーシャルです。
@mixin base-design border: white 2px solid line-height: 30px background: rgba(0,0,0,0) font-size: 30px color: white font-family: 'Itim', cursive padding: 5px margin: 5px &:focus outline: 0
herokuにデプロイだ!
Gitを整える
まずgit周辺をいじりましょう。コミットするだけなんですけどね。
git init git add . git commit -m "first commit"
とすれば、大丈夫です。
herokuにあげる
いよいよherokuにあげちゃいましょう。まずherokuのアカウントを作ってください。無料でクレジットカードなしだと5つまでしか上げられないみたいで、さっき怒られました。 herokuのアカウントを作成出来たらターミナルでherokuにログインします。
heroku login
メアドとパスを入力します。前はパス入力中は何も表示されなくて怖かったのですが、今はアスタリスクが表示されて安心します。ログインで来たら続けて以下のように打ちます。
heroku keys:add heroku create git push heroku master heroku rename "<好きな名前(既に使われているのは使用不可)>"
pushは時間かかるので少し待ちます。 さてではherokuのマイページへ移動します。renameでつけた名前をクリック→右上の「Open App」をクリックで表示できたでしょうか。 お疲れ様です!無事デプロイできました!
おわりに
お疲れさまでした。初心者なりに記事を書いてみましたが、いかがだったでしょうか。 私はサーバサイドに全然詳しくなくて、もちろん鯖も持っていなければNode.jsやRailsもthe素人ですので、この機会にちゃんと勉強したいなぁと思いました。それにしてもheroku便利ですよね。まさにブラウザだけで完結するWebアプリ開発。Cloud9もすごい便利なので(ソーシャルコーディングとかできるし)是非、この機会に使ってみてはいかがでしょうか。 以上となります。最後まで読んでいただき、ありがとうございました! Happy Coding!
【小ネタ】区分求積法でπの近似値を求めるアルゴリズム
どうも、drumathです。今回はちょっとしたチップスとして、πの近似値を求めたいと思います。使用する言語はC++です。
数Ⅲ生の十八番
数Ⅲで積分をしていると、置換積分やらなんやらのおかげで一見関係なさそうなところでが出てきます。
今回はそんな積分の中から、受験生の十八番ともいえるやつを使います。
区分求積法で近似する
ここで、区分求積法を用いると、
コードを書く
では先ほどの結果をもとにコードを書いてみました。
#include <stdio.h> #include <math.h> double pi_calc(double); int main(void) { for (int i = 1; i < 1000000; i++) printf("\r%d: %f", i, pi_calc((double)i)); return 0; } // 区分求積法を用いたアルゴリズム double pi_calc(double n) { double res = 0; for (int k = 1; k <= n; k++) res += 1.0 / (1.0 + pow(((double)k / n), 2.0)); return 4.0 * res / n; }
を100万まで動かします。
コードでところどころdoubleを使っているのは、floatを使ってしまうと誤差補正のせいで値がぶれてしまうことを防ぐためです。
今回は以上になります。最後まで読んでいただき、ありがとうございました。
レンダラー開発のための測光学覚書Vol.2 レイトレースのための基本的な物理量からレンダリング方程式まで
どうも、前回に引き続き、『レンダラー開発のための測光学覚書』シリーズ第2弾です。前回は立体角についての数学の話だったので、今回からは物理の話に入っていきましょう。
基本的な物理量
放射束
光とは粒子であり、波である。これが今の物理の定説です。しかし未だに謎が多く、光とは何かというのは完璧に答えることはできません。しかし、光は私たちに世界を知覚させることができる、とても身近な存在ですよね。ゆえに経験からいろんなことがわかります。例えば、虫眼鏡で黒い紙に光を1点に集中させれば火が出るように、光はエネルギーを持っています。ここで紹介する「放射束」は単位時間あたりの光のエネルギーという定義で、測光の分野では最も基本的な物理量です。
放射照度
単位面積当たりの放射束を放射照度といいます。
放射強度
ここで、前回の内容である、立体角の概念を導入します。ラジアンの立体版でしたね。忘れてしまった方はこちらをどうぞ。
drumath.hatenablog.com
放射強度は単位立体角当たりの放射束として与えられる物理量です。
放射輝度
放射輝度は、単位投影面積、単位立体角あたりの放射束として定義されます。先に式を出すと、
例えば、下の図のように、光が地面に向かっての角度で入射しているとしましょう。
このとき、光が当たっている面積をとします。このときの放射照度はですね。
ここで、光の進行方向に垂直な面を考えます。すると、その面と光が当たっている面積は、図に示した通り、となります。これを、投影面積といいます。
放射輝度は積分すれば、いろんな物理量が導けるので、シミュレーションでは大変重宝します。例えば放射束を出したい時には
マテリアルの決定
ここまで光について見てきました。ここでは実際にレンダリングするとき、もう一つ重要な「どんな材質に光が当たったか」を決めるための関数をご紹介します。
BSSRDF(英:bidirectional scattering surface reflectance distribution function、日:双方向散乱面反射率分布関数)
光が物質の表面に当たったら、入射角と同じ大きさの反射角で反射し、屈折の法則に従って屈折することは高校生、いや中学生で習いました。しかし現実はそんなに甘くありません。なぜなら完全に平らな面など存在しないからです。実際にはすごく小さな凸凹があって、その凸凹に当たって光はあらゆる方向に反射していきます。これを散乱というのでしたね。また、光はどの物質でも通過します。つまり物質の表面で屈折した光はそのうち物質中で反射して出てくるかもしれないし、反射を繰り返して物質を通過するかもしれないのです。このように物質を通過した光が反射して出ていくことを「表面下散乱」といいます。このように表面化散乱の様子を示す関数が、BSSRDFと呼ばれる関数です。上の画像からもわかる通り、人の肌の質感などのレンダリングではこの技術が用いられます。しかし、ちょっと複雑ですね。なので普通はBSSRDFを簡略化したBSDFを用います。
BRDF(英:bidirectional reflectance distribution function、日:双方向反射率分布関数)
BRDFはBSSRDFを簡略化したモデルです。正確に言うと、BSSRDFの簡略版は、BRDFとBTDFを足しあさせたBSDFというモデルなのですが、拡散反射や鏡面反射だけのモデルの場合、BRDFで事足りるということなんだと思います。BRDFは入射位置からでる反射光をモデル化したもので、先ほどの表面下散乱を考えないもので、入射光による放射照度と反射光による放射輝度の比で与えられます。
ここで、式の中の物理量に以下のものを使います。(なんか今更な感じもしますが)
物理量 | 意味 |
---|---|
放射束 | |
放射照度 | |
放射輝度 | |
BRDF | |
入射角 | |
入射光、及び反射光の方向 | |
入射面に対する法線 |
添え字のやはそれぞれ入射光関係(incidence)、反射光関係(reflectance)という意味です。
BRDFの意味(高校生なりの解釈)
私は最初BRDFの式を見て、なんでこうなるんだかわかりませんでしたw というのも、なぜ放射照度と放射輝度という二つの違う量の比で表すのかが意味不明でした。しかもこんなに複雑な式で、さも微積させる気しかないような雰囲気ですよね。頭の中ではグラサンかけてる厳ついおっさんが突然出てきたみたいなイメージでした。絡みづらいわ。
しかし、BRDFもこんな風に変形すれば、目的は一目瞭然です。
このようにすると、後述するレンダリング方程式とほぼ形は一緒です。つまり、いま私たちが求めたいのは放射輝度である。前述のとおり放射輝度さえわかればほかの物理量は積分すれば求まりますからね。だから普通はBRDFを求めよう、なんてことはしないんじゃないかな。
もう一回式(1)を見てみましょう。
半球で積分をしている意味は、面に対して、あらゆる方向から入射してくる光の和という意味ですね。このおかげで、グローバルイルミネーションが実現できます。
レンダリング方程式
さて、ついにレンダリング方程式です。とはいえ、先ほどの内容がわかっていれば余裕です。ようは出ていく放射輝度を求める方程式です。
出射する光というのは、表面から放射される光と反射される光の和で求まります。これは感覚的ですね。つまり、
反射光(reflected)の放射輝度 | |
放射光(emitted)の放射輝度 | |
出射光(outgoing)の放射輝度 |
とすれば、求めるは
最後に
ここまで学習するのにはとても時間がかかりました。しかし、レンダリングの原理が少しわかったような気がして、自信が付きました。既存のレンダラーなど見ていると、いつもは何気なく見ている言葉も、「これ知ってる!」ってなってなんだか楽しいですw BlenderのCyclesレンダラーも、マテリアルの名前は「~~BSDF」という風になってますけど、材質決定の関数だなんて思いませんでした。これからも頻度は高くないかもしれないけど、このシリーズは続けていこうと思います。最後まで見てくださり、ありがとうございました。
参考文献
qiita.com
↑いつもお世話になっています。より詳しい内容が知りたい方はこのリンクへ飛んでみてください。
raytracing.hatenablog.com
↑教育用のレンダラーを作ってくださっています。日本語のコメントがあるって素敵
qiita.com
↑こちらもわかりやすく解説してくださっています。
Blenderで水の中の表現
どうも、今回はYafarayレンダラーを使って海の中を作ってみます。あまり長くはならないと思うのでお付き合いください。
海のオブジェクトとその他の環境を作る
では早速作っていきます。まず、画面に平面のオブジェクトを作成し、「海洋モディファイア」を付与します。
すると、こんな感じになります。
そして海洋を囲むように空間を作ります。
私は上面を除去したCubeオブジェクトに「厚み付けモディファイア」を付与しました。別に上面が閉じていてもいいのですが、Lampでサンを使いたいので私は開けました。
今の画面はこんな感じ。中にカメラも入れてください。
海洋の設定
先ほど追加した「海洋もモディファイア」ですが、私の設定はこんな感じ
時間を変えると波が変化するので、いい感じの時間を選んでください。イイ感じの時間とは、波がまぁまぁ立っているときですかね。波がないと、海洋独特のコースティクスが生まれませんので。
マテリアルも編集していきます。
IOR(屈折率)は水の値を設定しました。
レンダリングしてみる
ではフォトンマッピングでレンダリングします。主要な設定は以下の通り。
名前 | 値 |
---|---|
Lighting Method | Photon Mapping |
深度 | 16 |
バウンス | 9 |
サンプル | 16 |
海洋モディファイアの解像度の値を7→10に変えてみましょう。
おわりに
今回はすごく早く書き終わりましたw とはいえCyclesでの再現で困っていたのでYafarayでできてよかったです。これからもYafarayの記事を書くかもしれないのでよろしくお願いします。
Yafarayについての他の記事↓
drumath.hatenablog.com
追記(2018/3/2)
動画で作ってみました。コンポジットし忘れたので、AviUtlでグロー効果をかけました。
youtu.be
レンダラー開発のための測光学覚書Vol.1 立体角について
どうも。大学生の勉強の予習になればと、レンダラー制作に向けて少しずつ勉強を始めている、drumathです。実際わからないことだらけなので、『レンダラー開発のための測光学覚書』というシリーズで、学習してきたことを整理していきたいと思います。もしよろしければ、解釈や式の導出などが間違っていた場合に教えていただけるとありがたいです。
今回は本格的に光学の分野には触れませんが、物理量の基本となる立体角についてまとめていきます。
立体角とは?
ラジアンの定義を振り返る
高校になっていきなり導入された弧度法ですが、なぜとなるのか、説明できますか?「簡単だよ!」という方は飛ばしていただいて構いません。
弧度法の定義はで、は弧度法での角度、単位はラジアンです。はでの弧の長さ。は半径です。つまり、円周の長さと半径の比であらわされる値なのです。とても直観的だと思いませんか?対して度数法の場合、一周を360個に分けた内、どれくらいを占めるかという、とてもあいまいな値です。一周が360°なんて一体だれが考えたのでしょうか。古代文明の60進法からですよね、たぶん。
数学Ⅲを習うと、弧度法を使うメリットがわかります。詳しくはほかの記事に書いてあることを参照してほしいのですが、のような三角関数の微分は弧度法を使うことによって、きれいな形で表されます。個人的な解釈なのですが、三角関数自体、直角三角形の辺の比で表される関数なので、弧と半径の比という定義はなじみやすいのではないかと考えます。
微小立体角を求める
設定
光の物理量は微分と積分のオンパレードです。ですので、ここで微小立体角、を決めたいと思います。まず先ほどの画像の領域について、横が、縦がの微小長方形で、立体角がと定めます。
当然、
微分だ!
さて、では、が微小な量だとします。そうすると立体角も微小な量になり、長方形領域は点のように小さくなります。ここで領域の方向を以下のように定めます。
ではまず微小について考えましょう。長さである横の辺を含む円周の長さは弧度法の定義から求めることができるでしょう。弧度法での円周の長さは
では、なぜ円周の長さはなのでしょうか。それは中心角がである扇形の弧の長さがと表わされ、今はの場合を考えているからです。そして、微小は、この円周が微小の場合を考えていることがわかります。よって
すみません、たぶんもっとうまい説明があるし、なにしろすごく遠回りな考えなのはわかっていますが、私が思考した結果がこれなのです。引き続きについても同じような導出を行います。このやり方が嫌だという人は読み飛ばしてください。申し訳ございません。
さて、続きを見ていきましょう。長さである縦の辺を含む円は、半径ですので、となりますね。図にするとこんな感じ。
の長さを求めたときのように、このというのはにおいてだからでした。よって
終わりに
今回は以上になります。立体角の概念は光学において基本的なものになりますが、私は学習当初あまりイメージできなかったので、数学Ⅲを習っている高校生にわかるように記事を書いたつもりです。最後まで読んでいただき。ありがとうございました。
参考文献
OpenGLでobjファイルの3Dデータを表示してみる
こんにちは。今回は入門したてのOpenGLでBlender出力のobjファイルビューワを作りたいと思います!
作るものについて
今回は下の画像のような3Dビューワを作ります。↓ほんとにシンプルなビューワですね(笑) 見て分かる通り、テクスチャやマテリアルなどは複雑化しそうなので今回は実装しません。今回使用するのは頂点、辺、面のデータのみです。
実装プラン
OpenGLをメインのライブラリとして使っていきますが、objファイル解析をRubyで実装しようと思います。その理由は、単純にRubyのほうがC++よりも文字列の処理がやりやすいからです。本当はオールC++でも全然できると思いますが、私が純粋なルビイストということもあり、Rubyを使用します。つまり、
という流れで実行していきます。また、objファイルはBlenderによって出力された3Dモデルデータを使用します。
開発環境など
使用する環境はこちらです。
項目 | 内容 |
---|---|
OS | Windows10 |
ライブラリなど | OpenGL、freeglut |
コンパイラ | Ruby2.2.4p230、bcc32 |
エディタ | Visual Studio Code 1.19.3 |
Blenderバージョン | Blender2.79 |
いざ、Rubyサイドを実装だ
Blenderでobjファイルにエクスポート
例えば、BlenderでCubeを追加したときのデフォルトのオブジェクトをobjファイル出力してみましょう。
まず、[ファイル]→[エクスポート]→[Wavefront(.obj)]とクリックしていきます。そうすると、下の画面のようになります。
普通にエクスポートボタンを押したくなると思いますが、ここで「三角面化」を忘れないでください!今回は面が三角形であることを前提に頂点を結んでいきます。もし多角形の面があったらちょっと厄介なことになりますので、必ず設定してください。
objファイルの形式を知る
先ほどの要領でobjファイルをエクスポートすると、このような内容が吐き出されます。
# Blender v2.79 (sub 0) OBJ File: '' # www.blender.org mtllib cube_obj_test.mtl o Cube_Cube.001 v -1.000000 -1.000000 1.000000 v -1.000000 1.000000 1.000000 v -1.000000 -1.000000 -1.000000 v -1.000000 1.000000 -1.000000 v 1.000000 -1.000000 1.000000 v 1.000000 1.000000 1.000000 v 1.000000 -1.000000 -1.000000 v 1.000000 1.000000 -1.000000 vn -1.0000 0.0000 0.0000 vn 0.0000 0.0000 -1.0000 vn 1.0000 0.0000 0.0000 vn 0.0000 0.0000 1.0000 vn 0.0000 -1.0000 0.0000 vn 0.0000 1.0000 0.0000 usemtl None s off f 2//1 3//1 1//1 f 4//2 7//2 3//2 f 8//3 5//3 7//3 f 6//4 1//4 5//4 f 7//5 1//5 3//5 f 4//6 6//6 8//6 f 2//1 4//1 3//1 f 4//2 8//2 7//2 f 8//3 6//3 5//3 f 6//4 2//4 1//4 f 7//5 5//5 1//5 f 4//6 2//6 6//6
これは何を示しているのでしょうか。
まず、
v -1.000000 -1.000000 1.000000
のような「v ~」ではじまる行は、頂点の座標を記録しています。Vはおそらく頂点の英語Vertexを意味しています。「v」につづく小数が左からx,y,z座標を表わしています。
そして、
f 2//1 3//1 1//1
のように「f~」ではじまる行は面のデータを記録しています。「f」のあとに「2//1」のような数字をスラッシュで区切ったような記述がみられますが、それは「頂点/テクスチャ座標/法線ベクトル」というデータを表わしています。今回使うのはい一つ目の数字だけですので、あとは無視してください。この1つ目の数字は、どの頂点を使うかを示しています。つまり「2//1」だった場合、一番左の「2」という数が、「2番目の頂点を使うぞ」と言っています。何番目の頂点かは「v」から始まる行の上から何番目かで決まります。
面の情報はどの三つの頂点を使うかを表しています。つまり
f 2//1 3//1 1//1
ならば、この面は2番目の頂点と3番目の頂点と1番目の頂点で構成されていることを示します。
Rubyでobjファイルコンバータを作る
Rubyでは、objファイルを読み込み、あとでC++側でscanfするだけで簡単にデータが格納できるようなファイルを新たに作ります。というわけでコードはこんな感じ。
# vars vertex = [] face = [] # const regexp Face_regexp = /^f (\d+)\/\d*\/\d* (\d+)\/\d*\/\d* (\d+)\/\d*\/\d*/ Vertex_regexp = /^v (.*) (.*) (.*)/ # conversion obj file File.open(ARGV[0], 'r') do |file| file.read.split("\n").each do |line| if line =~ Face_regexp face << [$1, $2, $3].map { |m| m.to_i - 1 } elsif line =~ Vertex_regexp vertex << [$1, $2, $3].map(&:to_f) end end end # output vertex data File.open('vData.txt', 'w') do |file| vertex.each do |e| file.puts e.join(', ') end end # output face data File.open('fData.txt', 'w') do |file| face.each do |e| file.puts e.join(', ') end end
たとえば、さきほどの立方体のデータを読み込むと、
ruby obj_convert.rb cube.obj
vData.txtとfData.txtという二つのファイルが作成され、vData.txtには、
-1.0, -1.0, 1.0 -1.0, 1.0, 1.0 -1.0, -1.0, -1.0 -1.0, 1.0, -1.0 1.0, -1.0, 1.0 1.0, 1.0, 1.0 1.0, -1.0, -1.0 1.0, 1.0, -1.0
fData.txtには、
1, 2, 0 3, 6, 2 7, 4, 6 5, 0, 4 6, 0, 2 3, 5, 7 1, 3, 2 3, 7, 6 7, 5, 4 5, 1, 0 6, 4, 0 3, 1, 5
という内容が出力されていることがわかります。vDataのほうは、単純に座標のリストです。fDataのほうは、三角面を構成する座標のインデックスを表わしていますが、実際コードを見るとわかりますが、インデックスを-1しています。それは、objファイルは上から1,2,3...と数えますが、C++の配列では0,1,2...と数えるので、C++の仕様に合わせるためです。
いざ、OpenGLで描画
ファイル階層
ファイル階層はこんな感じになります。
ObjTest.cppを書いていく
最初にコードを出します。私はこんな感じで書きました。
#include <windows.h> #include <stdio.h> #include "GL\freeglut.h" #pragma comment (lib, "lib\x86\freeglut.lib") #define ARRAY_MAX 10000000 float vertex[ARRAY_MAX]; int lines[ARRAY_MAX]; int vertexDataSize=0, lineDataSize=0; void disp() { glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3,GL_FLOAT,0,vertex); // 頂点の描画 glPointSize(3); glBegin(GL_POINTS); { for(int i=0;i<vertexDataSize;i++) glArrayElement(i); } glEnd(); glColor3f(0.6,0.35,0); glBegin(GL_TRIANGLES); { for(int i=0;i<lineDataSize;i++){ glArrayElement(lines[i*3]); glArrayElement(lines[i*3+1]); glArrayElement(lines[i*3+2]); } } glEnd(); glColor3f(1.0,0.75,0); glBegin(GL_LINES); { for(int i=0;i<lineDataSize;i++){ glArrayElement(lines[i*3]); glArrayElement(lines[i*3+1]); glArrayElement(lines[i*3+1]); glArrayElement(lines[i*3+2]); glArrayElement(lines[i*3+2]); glArrayElement(lines[i*3]); } } glEnd(); glFlush(); } void timer(int x) { glRotatef(1,0,1.0,0); glutPostRedisplay(); glutTimerFunc(50,timer,0); } void InitialProc() { FILE *fpVData, *fpFData; fpVData = fopen("vData.txt","r"); fpFData = fopen("fData.txt", "r"); if((fpVData==NULL)||(fpFData==NULL)){ printf("file error!!\n"); return; } while (fscanf(fpVData,"%f, %f, %f",&vertex[vertexDataSize*3],&vertex[vertexDataSize*3+1],&vertex[vertexDataSize*3+2])!=EOF) vertexDataSize+=1; while(fscanf(fpFData,"%d, %d, %d",&lines[lineDataSize*3],&lines[lineDataSize*3+1],&lines[lineDataSize*3+2])!=EOF) lineDataSize+=1; } int main(int argc, char **argv) { InitialProc(); glutInit(&argc, argv); glutInitWindowPosition(50,100); glutInitWindowSize(500,500); glutInitDisplayMode(GLUT_SINGLE|GLUT_RGBA); glutCreateWindow("Obj Test"); glRotatef(30,1.0,0,0); glRotatef(30,0,1.0,0); glScalef(0.8,0.8,0.8); glutDisplayFunc(disp); glutTimerFunc(100,timer,0); glutMainLoop(); return 0; }
InitialProc()関数内ではvData.txtとfData.txtからデータを入力して配列に格納しています。こうすることで、あとでglArrayElementsで簡単に頂点を描画したり、辺を作ったりすることができます。
また、timer()関数で0.05秒ごとに回転させています。