週末レイトレーシングを読んでレイトレーサを作った
レイトレーシングのやり方が気になったので、以下のページを読みつつレイトレーサをつくったのでメモ。 英語のタイトルは「Ray Tracing in One Weekend」。 著者はNVIDIAの社員とのこと。すごい。 inzkyk.xyz
成果物は以下のリポジトリにおいた。 github.com
感想としては、アンチエイリアスとか、反射の方法についてちゃんと勉強できて非常に参考になった。 ベクトル(線形代数)の知識があれば誰でも読めると思う。 外積とか正規直交基底が何の説明もなく出てくるので高校レベルだときついか。 また、中空ガラス球や、焦点ボケに関しては説明が少なく、自分が正しく理解できたか心配。 また、日本語版はコードや記述に間違いがあったりするので(翻訳が悪いわけではなく、海外版は更新が速いため修正されている)、原著の方も参考にしたほうがよいと思われる。 日本語版の、屈折したレイの導出には間違いがあって、私はここでかなり時間がもっていかれた。 https://raytracing.github.io/books/RayTracingInOneWeekend.html
ちなみに、下記のページにもあるように、最後の画像の出力にはめっちゃ時間がかかる。 この記事は画像生成中に書いている。 questbeat.hatenablog.jp
なお、日本語版は、訳者が異なる2つのバージョンが存在する。 tatsu-zine.com
最初のリンクのバージョンは無料で読める&htmlなのでコード実行しやすくありがたい。しかも、3冊目まで訳されている。 BoothではPDF版が購入でき、学生なら無料で入手できる。
視聴したアニメの管理
たまには雑記でも。 私は、見たアニメを自作のソフトで管理して以下のウェブページ上に公開しているが、それを紹介する。
こんな感じで、1クール毎にいくつのアニメをみたか(移動平均含む)と、年毎のレポート(視聴数、完走率)を出すようにしている。
マークダウンで書いた視聴履歴ファイルを自作プログラムでjson形式に変換し、github page上に展開されたjavascriptでjsonを読み込む、という形で構成した。手間を減らすため、自作プログラムはgithub actionsによってコミット時に自動実行されるようになっている。また、グラフ描画にはHighChartsを使っている。
まぁ同じような機能を持ったサービスはいくらでもあると思うのだが。
オススメのC++入門書【2020年8月版】
必要に迫られてC++に入門したので使用したテキストをメモしておく。 結論としては、「独習C++新版」か「江添亮のC++入門」がよい。
C++書籍の一覧は以下のサイトが詳しい。
プログラミング経験者を対象とした、C++17までカバーしている入門書は2020年8月現在以下の2つ。
江添さんの方はエラッタがなく、かつ、近所の本屋に在庫もなかったので、独習C++を読んだ。 amazonレビューによれば誤植が多いとのことなので、エラッタを随時参照したほうがよい。 私はC, Golang, Pythonは使えるのでほとんどの項目はすんなり読めたが、テンプレートや右辺値参照のところは難しく感じた。 正直、テンプレートを使いこなせる日が来る気がしない。 C++20が普及してconceptが使われるようになれば若干は楽になるのだろうか。
実際にC++でソフトを書いてみた感想としては、仕様が新しくなっていくにつれ、ユーザーフレンドリーになっているように感じた。
C++というと玄人の使う黒魔術言語的なイメージだったのでこれは意外だった。
はやくcoutをぶん投げて std::format
を使いたいのだが、実装はまだだろうか。あと std::ranges
も便利そうではある。
また、Rustより簡単にかけて(もちろん安全性は低い)、ハイパフォーマンスを出せるので書いていて楽しい。
辛いところは、仕様が3年毎にしか更新されないことだろうか。Rustが毎月、Golangが半年に一度のペースで更新されていることを考えると非常に遅く感じる(その分、一回の更新量は大きいが)。
他の大多数の言語と違って、仕様決定者がコンパイラ作成者でないということが影響しているのだろうか。
コンパイラが複数あるという点に関して、MSVCではコンパイルが通るのに、GCCやClangではエラーが出るみたいなことがあるのも辛かった。
なお、江添亮のC++入門 については、ネットで公開されているので自分に合うかどうか読んでみるとよい。 ezoeryou.github.io
以下のページの方が見やすいが、ソースが最新版に同期されているかどうかはよくわからない。
C++でのグラフ描画
C++から使用できるグラフ描画ライブラリを探したのでメモ。
matplotlib/MATLAB使いはmatplotlib-cppが一番使いやすいと思う。
ただし、Windows環境において、PythonをAnacondaでインストールしているとpython38_d.libがないためDebugビルドが通らない(38の部分はインストールしているPythonバージョンにより変化)。
したがって、pyconfig.h
を編集してpython38.libへのリンクに変更するか、Releaseビルドのみで開発するかの二択になり、どちらも辛い。
matplotlib-cpp専用のPythonをいれるという手もあるが、それは負けた気がするので却下。
また、同プロジェクトは1.9kものstarを得ているにも関わらず、ドキュメントがexampleファイル以外ないという点も不安を感じさせる。
その他グラフ描画ライブラリはQCustomPlot、PLplot、DISLINなどがあるが、 これは触ってない。
探し回った末、学部以来懐かしのgnuplotを使うことにした。 gnuplotのラッパもいくつかあるが、今は以下のものを使っている。
多機能というわけではないが、そこまで凝った可視化をするわけではないのでこれで十分。
Header onlyなライブラリなので、インストールしやすいのも良い。
gnuplotを使う上で不便な点は、gnuplotが数式以外はファイルを通してでしかプロットできないことだ。
いちいち一時ファイルを作らねばならず、会社のPCだとウイルスチェックのせいか描画の際にいちいち数秒待たされる。
popen()が遅い模様。ウイルスバスターェ。。
追記
実は、gnuplotにはデータブロック という機能があり、コマンドからデータを入力できる。これをADAPT-GPM2から使用できるよう機能追加・プルリクを送り、無事マージされた。 大規模データだったり、記憶媒体がHDDの場合には多少速くなるはず。
参考
追記
Hacker Newsでみかけたやつ
Eigenをcerealでシリアライズする
自分で若干コードを書かないとEigenオブジェクトをシリアライズできなかったのでメモ。
結論から言うと、C++17環境であれば以下のようにload()
, save()
を定義してやればよい。
#pragma once #include <cereal/cereal.hpp> #include <cereal/types/vector.hpp> #include <Eigen/Dense> #include <vector> namespace cereal { //Eigenのデータの中身を配列として出力するためのラッパ template <class T> struct DataWrapper { DataWrapper(T& m) : mat(m) {} T& mat; template <class Archive> void save(Archive& ar) const { ar(cereal::make_size_tag(mat.size())); for (int iter = 0, end = static_cast<int>(mat.size()); iter != end; ++iter) ar(*(mat.data() + iter)); } template <class Archive> void load(Archive& ar) { cereal::size_type n_rows; ar(cereal::make_size_tag(n_rows)); for (int iter = 0, end = static_cast<int>(mat.size()); iter != end; ++iter) ar(*(mat.data() + iter)); } }; template <class Archive, class Derived, cereal::traits::DisableIf<cereal::traits::is_text_archive<Archive>::value> = cereal::traits::sfinae> inline void save(Archive& ar, Eigen::PlainObjectBase<Derived> const& m) { using ArrT = Eigen::PlainObjectBase<Derived>; if constexpr (ArrT::RowsAtCompileTime == Eigen::Dynamic) ar(m.rows()); if constexpr (ArrT::ColsAtCompileTime == Eigen::Dynamic) ar(m.cols()); ar(binary_data(m.data(), m.size() * sizeof(typename Derived::Scalar))); } template <class Archive, class Derived, cereal::traits::DisableIf<cereal::traits::is_text_archive<Archive>::value> = cereal::traits::sfinae> inline void load(Archive& ar, Eigen::PlainObjectBase<Derived>& m) { using ArrT = Eigen::PlainObjectBase<Derived>; Eigen::Index rows = ArrT::RowsAtCompileTime, cols = ArrT::ColsAtCompileTime; if constexpr (ArrT::RowsAtCompileTime == Eigen::Dynamic) ar(rows); if constexpr (ArrT::ColsAtCompileTime == Eigen::Dynamic) ar(cols); m.resize(rows, cols); ar(binary_data(m.data(), static_cast<std::size_t>(rows * cols * sizeof(typename Derived::Scalar)))); } template <class Archive, class Derived, cereal::traits::EnableIf<cereal::traits::is_text_archive<Archive>::value> = cereal::traits::sfinae> inline void save(Archive& ar, Eigen::PlainObjectBase<Derived> const& m) { using ArrT = Eigen::PlainObjectBase<Derived>; ar(cereal::make_nvp("rows", m.rows())); ar(cereal::make_nvp("cols", m.cols())); ar(cereal::make_nvp("data", DataWrapper(m))); } template <class Archive, class Derived, cereal::traits::EnableIf<cereal::traits::is_text_archive<Archive>::value> = cereal::traits::sfinae> inline void load(Archive& ar, Eigen::PlainObjectBase<Derived>& m) { using ArrT = Eigen::PlainObjectBase<Derived>; Eigen::Index rows = ArrT::RowsAtCompileTime, cols = ArrT::ColsAtCompileTime; ar(rows); ar(cols); m.resize(rows, cols); ar(DataWrapper(m)); } }
コードは以下のページをかなり参考にした。 stackoverflow.com
以下はテストコード。
#include "cereal_eigen.h" #include <random> #include <fstream> #include <filesystem> #include <cereal/archives/binary.hpp> #include <cereal/archives/json.hpp> namespace fs = std::filesystem; class TestClass { public: TestClass() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<double> distd(-1.0, 1.0); std::uniform_int_distribution<int> disti(0, 100); mat = Eigen::MatrixXd::NullaryExpr(5, 3, [&]() {return distd(gen); });; arr << disti(gen), disti(gen), disti(gen); vec3d << distd(gen), distd(gen), distd(gen);; } void Print() { std::cout << "mat: " << std::endl << mat << std::endl; std::cout << "arr: " << arr << std::endl; std::cout << "evec: " << vec3d << std::endl; } template<class Archive> void serialize(Archive& archive) { archive(CEREAL_NVP(vec3d), CEREAL_NVP(arr), CEREAL_NVP(mat)); } Eigen::MatrixXd mat; Eigen::Vector3d vec3d; Eigen::Array<int, 1, 3> arr; }; int main() { fs::create_directory("data"); fs::path path_binary = "data/class.cereal"; fs::path path_json = "data/class.json"; auto t = TestClass(); t.Print(); { std::ofstream out(path_binary, std::ios::binary); cereal::BinaryOutputArchive archive_o(out); archive_o(CEREAL_NVP(t)); std::ofstream out2(path_json); cereal::JSONOutputArchive archive_o2(out2); archive_o2(CEREAL_NVP(t)); } std::cout << std::endl; std::cout << "Load from a binary file" << std::endl; TestClass t_binary; { std::ifstream in(path_binary, std::ios::binary); cereal::BinaryInputArchive archive_i(in); archive_i(t_binary); } t_binary.Print(); std::cout << std::endl; std::cout << "Load from a json file" << std::endl; TestClass t_json; { std::ifstream in2(path_json); cereal::JSONInputArchive archive_i2(in2); archive_i2(t_json); } t_json.Print(); }
出力されるJSONファイルはこんな感じ
{ "t": { "vec3d": { "rows": 3, "cols": 1, "data": [ 0.3161424837703686, 0.09854066925246485, 0.6277061473999255 ] }, "arr": { "rows": 1, "cols": 3, "data": [ 94, 16, 40 ] }, "mat": { "rows": 5, "cols": 3, "data": [ -0.26028257842889648, 0.25791645918797637, 0.6778558335502285, -0.05895612669355521, 0.08350015176958703, 0.5584214393023781, 0.4016409143850439, 0.6676428322540648, 0.10603756836576128, -0.48229906250794388, 0.7801566848260653, -0.8826490291364187, -0.9816820696869016, -0.9338585559435026, 0.5447137103816357 ] } } }
conda-forgeにパッケージを追加する手順
mesaをconda (正確にはconda-forgeチャネル)からインストールできるようにしたときの手順をまとめておく。
mesaの詳細は以下を参照のこと。 waregawa-log.hatenablog.com
手順
このサイトに書いてある手順に従えばよい。 具体的には
- conda-forge/staged-recipesをフォークし、新しいブランチを作る。
recipes/[PACKAGE_NAME]
にビルドレシピ meta.yamlを作成、その中に設定を記述する。- プルリクを投げる。
これだけ。
meta.yamlの設定に関しては、まず
conda skeleton pypi PACKAGE_NAME
としてひな形を出力するとよい。
その後、こことかこことかを参照しつつ設定する。
staged-recipesのrecipes/example/meta.yaml
も参考になる。