constexpr レイトレーシング
これは、Sprout.Darkroom ライブラリの constexpr レイトレーサーでレンダリングした画像です。
画像サイズは 512×512 pixel。
もちろん画素データはすべてコンパイル時に生成され、ファイル出力のみが実行時に行なわれています。
光源は手前右側にあり、手前の青色がかった球から奥の赤色がかった球へ落ちる影や、
反射による球形の映り込みも確認できると思います。
レイトレーシングの基本的なアルゴリズムはとてもシンプルです。
視点から各ピクセルを通る光線を飛ばして、ベクトルが オブジェクトと交差する部分の拡散光と反射光の成分を得るだけです。
さて、ではこの Sprout.Darkroom が constexpr でどう実装されているか見てゆくことにしましょう。
とても明快で解りやすい実装です。
なお Sprout.Darkroom は未だ開発途上のライブラリであり、扱えるオブジェクトとして球しか実装していなかったり、
関数やクラスのインタフェースは今後変更される可能性があることをご了承ください。
また、使用されているテクニックや constexpr 自体の仕様については、
前出の『中3女子でもわかる constexpr』を一通り読んでいる程度の知識を前提とすることを予めお断りしておきます。
ライブラリ設計について
Sprout.Darkroom は、コンセプトベースの設計になっています。
コンセプトベースとは、Boost.Fusion や Boost.Geometry のように、
型を決め打ちしたり継承関係を要求することなく、必要な操作によって型の満たすべき要件を決定します。
ユーザが機能を拡張したい場合には、特殊化やオーバーロードによって行なうことになります。
ところで、継承関係による仮想関数ベースの動作カスタマイズは constexpr では使えません。
なぜならば、仮想関数を持つクラスは非リテラル型であり、
非リテラル型のオブジェクトはコンパイル時定数として扱えないわけです。
そのため、コンセプトベースの設計は、constexpr ライブラリの設計に欠かせないものだと言えます。
言ってしまえばこれがこの記事の結論部分です。
これだけ知っていただければ結構です。
『中3女子でもわかる constexpr』では個々の関数やクラスの constexpr 化について言及しましたが、
大きな括りとしての constexpr ライブラリを設計する場合は、そうしたことを考える必要があります。
以降は実装をつらつらと見ていくだけです。
長いのでコード部分は読み飛ばしても結構です。
データ型のアクセス
まずは扱うデータ型について定義します。
最も基本的なのはベクトルやカラーでしょう。
特徴的なのは、これらがいずれも複数の成分を持つことです。
- 3次元ベクトル (x, y, z)
- RBGカラー (r, g, b)
このようなデータ構造は典型的なタプルであると見なせます。
namespace access { // // element // template<std::size_t I, typename T> struct element : public sprout::tuples::tuple_element<I, T> {}; // // size // template<typename T> struct size : public sprout::tuples::tuple_size<T> {}; // // unit // template<typename T> struct unit : public sprout::darkroom::access::element<0, T> {}; // // get // template<std::size_t I, typename T> SPROUT_CONSTEXPR auto get( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::tuples::get<I>(sprout::forward<T>(t)))) -> decltype(sprout::tuples::get<I>(sprout::forward<T>(t))) { return sprout::tuples::get<I>(sprout::forward<T>(t)); } } // namespace access
element は、インデックス I の要素の型を返すメタ関数です。
size
unit
get は、T のインデックス I の要素を返す constexpr 関数です。
もしユーザが別の自分のクラスをベクトル型やカラー型として扱いたい場合、
これらをオーバーロードすることでアダプトできます。
なお SPROUT_CONSTEXPR や SPROUT_NOEXCEPT_EXPR は、それぞれ constexpr や noexcept に置換されます。
RGBカラー
namespace colors { // // r // g // b // template<typename T> SPROUT_CONSTEXPR auto r( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<0>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<0>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<0>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto g( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<1>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<1>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<1>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto b( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<2>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<2>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<2>(sprout::forward<T>(t)); } // // rgb // rgb_f // typedef sprout::tuples::tuple<std::uint8_t, std::uint8_t, std::uint8_t> rgb; typedef sprout::tuples::tuple<double, double, double> rgb_f; // // mul // template<typename Color, typename Fac> SPROUT_CONSTEXPR inline Color mul(Color const& lhs, Fac const& rhs) { return sprout::tuples::remake_clone<Color>( lhs, sprout::darkroom::colors::r(lhs) * rhs, sprout::darkroom::colors::g(lhs) * rhs, sprout::darkroom::colors::b(lhs) * rhs ); } // // add // template<typename Color1, typename Color2> SPROUT_CONSTEXPR inline Color1 add(Color1 const& lhs, Color2 const& rhs) { return sprout::tuples::remake_clone<Color1>( lhs, sprout::darkroom::colors::r(lhs) + sprout::darkroom::colors::r(rhs), sprout::darkroom::colors::g(lhs) + sprout::darkroom::colors::g(rhs), sprout::darkroom::colors::b(lhs) + sprout::darkroom::colors::b(rhs) ); } // // filter // template<typename Color1, typename Color2> SPROUT_CONSTEXPR inline Color1 filter(Color1 const& lhs, Color2 const& rhs) { return sprout::tuples::remake_clone<Color1>( lhs, sprout::darkroom::colors::r(lhs) * sprout::darkroom::colors::r(rhs), sprout::darkroom::colors::g(lhs) * sprout::darkroom::colors::g(rhs), sprout::darkroom::colors::b(lhs) * sprout::darkroom::colors::b(rhs) ); } // // rgb_f_to_rgb // template<typename RGB, typename RGB_F> SPROUT_CONSTEXPR inline RGB rgb_f_to_rgb(RGB_F const& col) { typedef typename sprout::darkroom::access::unit<RGB>::type unit_type; return sprout::tuples::make_clone<RGB>( sprout::darkroom::colors::r(col) < 0 ? std::numeric_limits<unit_type>::min() : sprout::darkroom::colors::r(col) > 1 ? std::numeric_limits<unit_type>::max() : sprout::darkroom::colors::r(col) * std::numeric_limits<unit_type>::max() , sprout::darkroom::colors::g(col) < 0 ? std::numeric_limits<unit_type>::min() : sprout::darkroom::colors::g(col) > 1 ? std::numeric_limits<unit_type>::max() : sprout::darkroom::colors::g(col) * std::numeric_limits<unit_type>::max() , sprout::darkroom::colors::b(col) < 0 ? std::numeric_limits<unit_type>::min() : sprout::darkroom::colors::b(col) > 1 ? std::numeric_limits<unit_type>::max() : sprout::darkroom::colors::b(col) * std::numeric_limits<unit_type>::max() ); } } // namespace colors
rgb 型は、各要素 8 bit の RGBカラーです。レンダリングされた画素には、通常この型が使われます。
rgb_f 型は、各要素が浮動小数点数の RGBカラーです。カラー演算で使用されます。
浮動小数点数が自然に使えるのは、constexpr の TMP に対する大きな利点です。
r, g, b をそれぞれタプルの 0, 1, 2 要素目としています。
もちろん他の型を使いたい場合は、これらをオーバーロードする必要があります。
mul, add, filter といったカラー演算が定義されています。
ベクトル
namespace coords { // // x // y // z // template<typename T> SPROUT_CONSTEXPR auto x( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<0>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<0>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<0>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto y( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<1>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<1>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<1>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto z( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<2>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<2>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<2>(sprout::forward<T>(t)); } // // vector3d // typedef sprout::tuples::tuple<double, double, double> vector3d; // // length_sq // template<typename Vector> SPROUT_CONSTEXPR inline typename sprout::darkroom::access::unit<Vector>::type length_sq(Vector const& vec) { return sprout::darkroom::coords::x(vec) * sprout::darkroom::coords::x(vec) + sprout::darkroom::coords::y(vec) * sprout::darkroom::coords::y(vec) + sprout::darkroom::coords::z(vec) * sprout::darkroom::coords::z(vec) ; } // // length // template<typename Vector> SPROUT_CONSTEXPR inline typename sprout::darkroom::access::unit<Vector>::type length(Vector const& vec) { using std::sqrt; return sqrt(sprout::darkroom::coords::length_sq(vec)); } // // add // template<typename Vector1, typename Vector2> SPROUT_CONSTEXPR inline Vector1 add(Vector1 const& lhs, Vector2 const& rhs) { return sprout::tuples::remake_clone<Vector1>( lhs, sprout::darkroom::coords::x(lhs) + sprout::darkroom::coords::x(rhs), sprout::darkroom::coords::y(lhs) + sprout::darkroom::coords::y(rhs), sprout::darkroom::coords::z(lhs) + sprout::darkroom::coords::z(rhs) ); } // // sub // template<typename Vector1, typename Vector2> SPROUT_CONSTEXPR inline Vector1 sub(Vector1 const& lhs, Vector2 const& rhs) { return sprout::tuples::remake_clone<Vector1>( lhs, sprout::darkroom::coords::x(lhs) - sprout::darkroom::coords::x(rhs), sprout::darkroom::coords::y(lhs) - sprout::darkroom::coords::y(rhs), sprout::darkroom::coords::z(lhs) - sprout::darkroom::coords::z(rhs) ); } // // scale // template<typename Vector, typename Fac> SPROUT_CONSTEXPR inline Vector scale(Vector const& lhs, Fac const& rhs) { return sprout::tuples::remake_clone<Vector>( lhs, sprout::darkroom::coords::x(lhs) * rhs, sprout::darkroom::coords::y(lhs) * rhs, sprout::darkroom::coords::z(lhs) * rhs ); } // // dot // template<typename Vector> SPROUT_CONSTEXPR inline typename sprout::darkroom::access::unit<Vector>::type dot(Vector const& lhs, Vector const& rhs) { return sprout::darkroom::coords::x(lhs) * sprout::darkroom::coords::x(rhs) + sprout::darkroom::coords::y(lhs) * sprout::darkroom::coords::y(rhs) + sprout::darkroom::coords::z(lhs) * sprout::darkroom::coords::z(rhs) ; } // // normalize // namespace detail { template<typename Vector> SPROUT_CONSTEXPR inline Vector normalize_impl( Vector const& vec, typename sprout::darkroom::access::unit<Vector>::type const& len ) { return sprout::tuples::remake_clone<Vector>( vec, sprout::darkroom::coords::x(vec) / len, sprout::darkroom::coords::y(vec) / len, sprout::darkroom::coords::z(vec) / len ); } } // namespace detail template<typename Vector> SPROUT_CONSTEXPR inline Vector normalize(Vector const& vec) { return sprout::darkroom::coords::detail::normalize_impl( vec, sprout::darkroom::coords::length(vec) ); } // // reflect // template<typename Incident, typename Normal> SPROUT_CONSTEXPR inline Incident reflect(Incident const& incid, Normal const& nor) { return sprout::darkroom::coords::sub( incid, sprout::darkroom::coords::scale(nor, sprout::darkroom::coords::dot(incid, nor) * 2) ); } // // normal_to_color // template<typename Color, typename Normal> SPROUT_CONSTEXPR Color normal_to_color(Normal const& nor) { return sprout::tuples::make_clone<Color>( 0.5 + sprout::darkroom::coords::x(nor) * 0.5, 0.5 + sprout::darkroom::coords::y(nor) * 0.5, 0.5 + sprout::darkroom::coords::z(nor) * 0.5 ); } } // namespace coords
vector3d は、浮動小数点数による3次元ベクトルです。
x, y, z をそれぞれタプルの 0, 1, 2 要素目としています。
他の型を使いたい場合は、これらをオーバーロードする必要があります。
length, add, sub, scale, dot, normalize, reflect などのベクトル演算が定義されています。
なお normalize は、ベクトルを単位ベクトル(== 長さ 1.0 のベクトル)に直すものです。
光線
namespace rays { // // position // direction // template<typename T> SPROUT_CONSTEXPR auto position( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<0>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<0>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<0>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto direction( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<1>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<1>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<1>(sprout::forward<T>(t)); } // // make_ray // template<typename Position, typename Direction> SPROUT_CONSTEXPR sprout::tuples::tuple<Position, Direction> make_ray(Position const& pos, Direction const& dir) { return sprout::tuples::make_tuple(pos, dir); } // // ray // typedef sprout::tuples::tuple<sprout::darkroom::coords::vector3d, sprout::darkroom::coords::vector3d> ray; // // point_of_intersection // template<typename Ray, typename Distance> SPROUT_CONSTEXPR inline typename sprout::darkroom::access::unit<Ray>::type point_of_intersection(Ray const& ray, Distance const& dist) { return sprout::darkroom::coords::add( sprout::darkroom::rays::position(ray), sprout::darkroom::coords::scale(sprout::darkroom::rays::direction(ray), dist) ); } } // namespace ray
ray は、position (原点からの位置) と direction (方向を示す単位ベクトル) の組です。
point_of_intersection は、光線がある距離でオブジェクトに交差したときの、交差位置を示すベクトルを返します。
マテリアル
namespace materials { // // color // reflection // template<typename T> SPROUT_CONSTEXPR auto color( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<0>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<0>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<0>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto reflection( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<1>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<1>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<1>(sprout::forward<T>(t)); } // // calc_color // calc_reflection // template<typename Image, typename Position, typename Normal> SPROUT_CONSTEXPR auto calc_color(Image&& t, Position const&, Normal const&) SPROUT_NOEXCEPT -> decltype(t) { return t; } template<typename Image, typename Position, typename Normal> SPROUT_CONSTEXPR auto calc_reflection(Image&& t, Position const&, Normal const&) SPROUT_NOEXCEPT -> decltype(t) { return t; } // // calc_material // template<typename Material, typename Position, typename Normal> SPROUT_CONSTEXPR auto calc_material(Material const& mat, Position const& pos, Normal const& nor) -> decltype(sprout::tuples::make_tuple( sprout::darkroom::materials::calc_color(sprout::darkroom::materials::color(mat), pos, nor), sprout::darkroom::materials::calc_reflection(sprout::darkroom::materials::reflection(mat), pos, nor) )) { return sprout::tuples::make_tuple( sprout::darkroom::materials::calc_color(sprout::darkroom::materials::color(mat), pos, nor), sprout::darkroom::materials::calc_reflection(sprout::darkroom::materials::reflection(mat), pos, nor) ); } // // make_material_image // template<typename ColorImage, typename ReflectionImage> SPROUT_CONSTEXPR sprout::tuples::tuple<ColorImage, ReflectionImage> make_material_image(ColorImage const& col, ReflectionImage const& ref) { return sprout::tuples::make_tuple(col, ref); } // // material // typedef sprout::tuples::tuple<sprout::darkroom::colors::rgb_f, double> material; } // namespace materials
material は、オブジェクトの任意の位置に関連付けられたカラー成分と反射成分です。
なお、透過や屈折については実装されていません。
ColorImage や ReflectionImage は、オブジェクトに関連付けられ各成分を計算する元となるもの、
つまり固定カラーやテクスチャマップ、スペキュラマップのことです。
calc_color, calc_reflection はそれぞれ ColorImage や ReflectionImage から成分を計算するものです。
ここでは固定カラーや固定値の反射率のみ定義されています。
テクスチャマップなどの実装の際には、これがオーバーロードされます。
光線とオブジェクトの交差
namespace intersects { // // does_intersect // distance // point_of_intersection // normal // material // template<typename T> SPROUT_CONSTEXPR auto does_intersect( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<0>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<0>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<0>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto distance( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<1>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<1>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<1>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto point_of_intersection( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<2>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<2>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<2>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto normal( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<3>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<3>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<3>(sprout::forward<T>(t)); } template<typename T> SPROUT_CONSTEXPR auto material( T&& t ) SPROUT_NOEXCEPT_EXPR(SPROUT_NOEXCEPT_EXPR(sprout::darkroom::access::get<4>(sprout::forward<T>(t)))) -> decltype(sprout::darkroom::access::get<4>(sprout::forward<T>(t))) { return sprout::darkroom::access::get<4>(sprout::forward<T>(t)); } // // make_intersection // template<typename Distance, typename Point, typename Normal, typename Material> SPROUT_CONSTEXPR sprout::tuples::tuple<bool, Distance, Point, Normal, Material> make_intersection(bool b, Distance const& dist, Point const& p, Normal const& nor, Material const& mat) { return sprout::tuples::make_tuple(b, dist, p, nor, mat); } // // intersection // typedef sprout::tuples::tuple< bool, double, sprout::darkroom::coords::vector3d, sprout::darkroom::coords::vector3d, sprout::darkroom::materials::material > intersection; } // namespace intersects
光線とオブジェクトの交差判定に使われます。
does_intersect - 交差したか
distance - 交差点までの距離
point_of_intersection - 交差点の位置
normal - 交差点の法線ベクトル
material - 交差点のマテリアル
intersection はこれらのタプルであり、交差判定関数の返値になります。
交差判定
namespace objects { // // intersect // template<typename Object, typename Ray> SPROUT_CONSTEXPR inline typename Object::template intersection<Ray>::type intersect(Object const& obj, Ray const& ray) { return obj.intersect(ray); } // // intersect_list // namespace detail { template<std::size_t N> struct intersect_list_impl { private: template<typename Objects, typename Ray, typename A, typename B> SPROUT_CONSTEXPR typename sprout::darkroom::access::unit<Objects>::type ::template intersection<Ray>::type comp(A const& a, B const& b) const { return sprout::darkroom::intersects::does_intersect(a) && sprout::darkroom::intersects::does_intersect(b) ? sprout::darkroom::intersects::distance(a) < sprout::darkroom::intersects::distance(b) ? a : b : sprout::darkroom::intersects::does_intersect(a) ? a : b ; } public: template<typename Objects, typename Ray> SPROUT_CONSTEXPR typename sprout::darkroom::access::unit<Objects>::type ::template intersection<Ray>::type operator()(Objects const& objs, Ray const& ray) const { return comp<Objects, Ray>( sprout::darkroom::objects::intersect(sprout::darkroom::access::get<N>(objs), ray), intersect_list_impl<N - 1>()(objs, ray) ); } }; template<> struct intersect_list_impl<0> { public: template<typename Objects, typename Ray> SPROUT_CONSTEXPR typename sprout::darkroom::access::unit<Objects>::type ::template intersection<Ray>::type operator()(Objects const& objs, Ray const& ray) const { return sprout::darkroom::objects::intersect(sprout::darkroom::access::get<0>(objs), ray); } }; } // namespace detail template<typename Objects, typename Ray> SPROUT_CONSTEXPR inline typename sprout::darkroom::access::unit<Objects>::type ::template intersection<Ray>::type intersect_list(Objects const& objs, Ray const& ray) { return sprout::darkroom::objects::detail::intersect_list_impl< sprout::darkroom::access::size<Objects>::value - 1 >()(objs, ray); } } // namespace objects
intersect は、光線とオブジェクトの交差判定を行う constexpr 関数です。
実装は、渡されたオブジェクトクラスの intersect メンバ関数を呼び出すだけです。
もちろんユーザが望むならば、これをオーバーロードしてカスタマイズ可能です。
intersect_list は、複数のオブジェクトのリスト(タプル)について交差判定を行い、
最も光線の始点に近い(最初に交差する)交差結果を返します。
球オブジェクト
namespace objects { // // basic_sphere // template<typename Material, typename Position = sprout::darkroom::coords::vector3d> class basic_sphere { public: typedef Material material_type; typedef Position position_type; typedef typename sprout::darkroom::access::unit<position_type>::type unit_type; typedef unit_type radius_type; public: template<typename Ray> struct intersection { typedef sprout::tuples::tuple< bool, unit_type, position_type, position_type, decltype(sprout::darkroom::materials::calc_material( std::declval<material_type const&>(), std::declval<position_type const&>(), std::declval<position_type const&>() )) > type; }; private: typedef sprout::tuples::tuple<int, bool, unit_type> zwo_type; typedef sprout::tuples::tuple<position_type, position_type> drei_type; struct zw { SPROUT_STATIC_CONSTEXPR std::size_t hit_side = 0; SPROUT_STATIC_CONSTEXPR std::size_t does_intersect = 1; SPROUT_STATIC_CONSTEXPR std::size_t distance = 2; }; struct dr { SPROUT_STATIC_CONSTEXPR std::size_t point_of_intersection = 0; SPROUT_STATIC_CONSTEXPR std::size_t normal = 1; }; private: position_type pos_; radius_type rad_; material_type mat_; private: template<typename Ray> SPROUT_CONSTEXPR zwo_type zweitens_2( Ray const& ray, unit_type const& i1, unit_type const& i2, int hit_side, bool does_intersect ) const { return zwo_type( hit_side, does_intersect, hit_side < 0 ? i2 : hit_side > 0 ? i1 : -1 ); } template<typename Ray> SPROUT_CONSTEXPR zwo_type zweitens_1( Ray const& ray, unit_type const& i1, unit_type const& i2 ) const { return zweitens_2( ray, i1, i2, i2 > 0 ? i1 < 0 ? -1 : 1 : 0 , i2 > 0 ); } template<typename Ray> SPROUT_CONSTEXPR zwo_type zweitens( Ray const& ray, bool neg, unit_type const& b, unit_type const& det ) const { return neg ? zweitens_1(ray, b - det, b + det) : zwo_type(0, false, -1) ; } template<typename Ray> SPROUT_CONSTEXPR drei_type drittens_1( Ray const& ray, typename sprout::darkroom::access::unit<Ray>::type point_of_intersection ) const { return drei_type( point_of_intersection, sprout::darkroom::coords::normalize( sprout::darkroom::coords::sub(point_of_intersection, pos_) ) ); } template<typename Ray> SPROUT_CONSTEXPR drei_type drittens( Ray const& ray, bool neg, unit_type const& distance ) const { return neg ? drittens_1(ray, sprout::darkroom::rays::point_of_intersection(ray, distance)) : drei_type( sprout::tuples::make_clone<position_type>(0, 0, 0), sprout::tuples::make_clone<position_type>(1, 1, 1) ) ; } template<typename Ray> SPROUT_CONSTEXPR typename intersection<Ray>::type intersect_5( Ray const& ray, zwo_type const& zwo, drei_type const& drei ) const { return typename intersection<Ray>::type( sprout::tuples::get<zw::does_intersect>(zwo), sprout::tuples::get<zw::distance>(zwo), sprout::tuples::get<dr::point_of_intersection>(drei), sprout::tuples::get<dr::normal>(drei), sprout::darkroom::materials::calc_material( mat_, sprout::tuples::get<dr::point_of_intersection>(drei), sprout::tuples::get<dr::normal>(drei) ) ); } template<typename Ray> SPROUT_CONSTEXPR typename intersection<Ray>::type intersect_4( Ray const& ray, zwo_type const& zwo ) const { return intersect_5( ray, zwo, drittens( ray, sprout::tuples::get<zw::does_intersect>(zwo), sprout::tuples::get<zw::distance>(zwo) ) ); } template<typename Ray> SPROUT_CONSTEXPR typename intersection<Ray>::type intersect_3( Ray const& ray, unit_type const& b, unit_type const& det_sq ) const { using std::sqrt; return intersect_4( ray, zweitens( ray, det_sq > 0, b, sqrt(det_sq) ) ); } template<typename Ray> SPROUT_CONSTEXPR typename intersection<Ray>::type intersect_2( Ray const& ray, position_type const& v, unit_type const& b ) const { return intersect_3( ray, b, b * b - sprout::darkroom::coords::length_sq(v) + rad_ * rad_ ); } template<typename Ray> SPROUT_CONSTEXPR typename intersection<Ray>::type intersect_1( Ray const& ray, position_type const& v ) const { return intersect_2( ray, v, -sprout::darkroom::coords::dot(v, sprout::darkroom::rays::direction(ray)) ); } public: SPROUT_CONSTEXPR basic_sphere( position_type const& pos, radius_type rad, material_type const& mat ) : pos_(pos) , rad_(rad) , mat_(mat) {}; template<typename Ray> SPROUT_CONSTEXPR typename intersection<Ray>::type intersect(Ray const& ray) const { return intersect_1( ray, sprout::darkroom::coords::sub(sprout::darkroom::rays::position(ray), pos_) ); } }; // // make_sphere // template<typename Material, typename Position, typename Radius> SPROUT_CONSTEXPR inline sprout::darkroom::objects::basic_sphere<Material, Position> make_sphere(Position const& pos, Radius const& rad, Material const& mat) { return sprout::darkroom::objects::basic_sphere<Material, Position>(pos, rad, mat); } } // namespace objects
basic_sphere は球を表現するオブジェクトで、光線との交差判定を行う intersect メンバ関数を持ちます。
逆に言えば、intersect だけ持っていればオブジェクトクラスとして定義できるということです。
継承関係などは要求されません。仮想関数も持つ必要はありません。
これがコンセプトベースライブラリの特徴と言えます。
計算式については、数学で直線と球の交点を求めるのと同じです。
constexpr 関数の制約ゆえ非常に分かり難くなっています。
constexpr が、強力な表現力を持っていながら実装が厄介なのはこの点に尽きると思います。
点光源
namespace lights { // // basic_point_light // template< typename Position = sprout::darkroom::coords::vector3d, typename Color = sprout::darkroom::colors::rgb_f > class basic_point_light { public: typedef Position position_type; typedef typename sprout::darkroom::access::unit<position_type>::type unit_type; typedef Color color_type; private: position_type pos_; color_type col_; private: template<typename Intersection> SPROUT_CONSTEXPR color_type shade_4( Intersection const& inter, unit_type const& intensity ) const { return sprout::tuples::remake_clone<color_type>( col_, sprout::darkroom::colors::r(col_) * sprout::darkroom::colors::r( sprout::darkroom::materials::color( sprout::darkroom::intersects::material(inter) ) ) * intensity , sprout::darkroom::colors::g(col_) * sprout::darkroom::colors::g( sprout::darkroom::materials::color( sprout::darkroom::intersects::material(inter) ) ) * intensity , sprout::darkroom::colors::b(col_) * sprout::darkroom::colors::b( sprout::darkroom::materials::color( sprout::darkroom::intersects::material(inter) ) ) * intensity ); } template<typename Intersection, typename LightRayIntersection> SPROUT_CONSTEXPR color_type shade_3( Intersection const& inter, position_type const& diff, position_type const& direction, LightRayIntersection const& light_ray_inter ) const { return shade_4( inter, !sprout::darkroom::intersects::does_intersect(light_ray_inter) || sprout::darkroom::intersects::distance(light_ray_inter) > sprout::darkroom::coords::length(diff) || sprout::darkroom::intersects::distance(light_ray_inter) < std::numeric_limits<unit_type>::epsilon() ? NS_SSCRISK_CEL_OR_SPROUT_DETAIL::max( std::numeric_limits<unit_type>::epsilon(), sprout::darkroom::coords::dot( direction, sprout::darkroom::intersects::normal(inter) ) / (sprout::darkroom::coords::length(diff) + 1) ) : 0 ); } template<typename Intersection, typename Objects> SPROUT_CONSTEXPR color_type shade_2( Intersection const& inter, Objects const& objs, position_type const& diff, position_type const& direction ) const { return shade_3( inter, diff, direction, sprout::darkroom::objects::intersect_list( objs, sprout::darkroom::rays::make_ray( sprout::darkroom::coords::add( sprout::darkroom::coords::scale( direction, std::numeric_limits<unit_type>::epsilon() * 256 ), sprout::darkroom::intersects::point_of_intersection(inter) ), direction ) ) ); } template<typename Intersection, typename Objects> SPROUT_CONSTEXPR color_type shade_1( Intersection const& inter, Objects const& objs, position_type const& diff ) const { return shade_2( inter, objs, diff, sprout::darkroom::coords::normalize(diff) ); } public: SPROUT_CONSTEXPR basic_point_light( position_type const& pos, color_type const& col ) : pos_(pos) , col_(col) {} template<typename Intersection, typename Objects> SPROUT_CONSTEXPR color_type operator()(Intersection const& inter, Objects const& objs) const { return shade_1( inter, objs, sprout::darkroom::coords::sub( pos_, sprout::darkroom::intersects::point_of_intersection(inter) ) ); } }; // // make_point_light // template<typename Position, typename Color> SPROUT_CONSTEXPR inline sprout::darkroom::lights::basic_point_light<Position, Color> make_point_light(Position const& pos, Color const& col) { return sprout::darkroom::lights::basic_point_light<Position, Color>(pos, col); } } // namespace lights
basic_point_light は点光源を表現します。
点光源は、ある一点から放たれる光源であり、光源からの距離によって減衰するという特徴を持ちます。
operator() はオブジェクトのリストと交差結果をとり、交差位置の陰影を計算します。
この陰影が通常 diffuse (拡散光)成分として使われます。
交差点から光源までのベクトルをシャドウテストレイと言い、これが途中で他のオブジェクトと交差しないかチェックします。
もし交差していたら、他のオブジェクトによって光が遮蔽されていることになり、その部分は影になります。
カメラ
namespace cameras { template<typename Unit = double> class basic_simple_camera { public: typedef Unit unit_type; typedef sprout::tuples::tuple<unit_type, unit_type, unit_type> position_type; typedef sprout::tuples::tuple<position_type, position_type> ray_type; private: unit_type far_plane_; public: SPROUT_CONSTEXPR explicit basic_simple_camera( unit_type const& far_plane ) : far_plane_(far_plane) {} SPROUT_CONSTEXPR ray_type operator()(unit_type const& u, unit_type const& v) const { return ray_type( position_type(0, 0, 0), sprout::darkroom::coords::normalize(position_type(u, v, far_plane_)) ); } }; // // make_simple_camera // template<typename Unit> SPROUT_CONSTEXPR inline sprout::darkroom::cameras::basic_simple_camera<Unit> make_simple_camera(Unit const& far_plane) { return sprout::darkroom::cameras::basic_simple_camera<Unit>(far_plane); } } // namespace cameras
カメラによって観測者の視点が決定されます。
basic_simple_camera は最も単純な定点カメラであって、常に原点から z 方向に向きます。
far_plane の値は画角の最近傍の距離であり、
視点(原点)からこの距離の位置にレンダリングする矩形を置くことになります。
レンダラ
namespace renderers { // // whitted_mirror // class whitted_mirror { private: template< typename Color, typename Camera, typename Objects, typename Lights, typename Ray, typename Intersection, typename Tracer, typename Direction > SPROUT_CONSTEXPR Color color_1( Camera const& camera, Objects const& objs, Lights const& lights, Ray const& ray, Intersection const& inter, Tracer const& tracer, std::size_t depth_max, Direction const& reflect_dir ) const { return tracer.template operator()<Color>( camera, objs, lights, sprout::tuples::remake_clone<Ray>( ray, sprout::darkroom::coords::add( sprout::darkroom::intersects::point_of_intersection(inter), sprout::darkroom::coords::scale( reflect_dir, std::numeric_limits<typename sprout::darkroom::access::unit<Direction>::type>::epsilon() * 256 ) // !!! // sprout::darkroom::coords::scale( // sprout::darkroom::intersects::normal(inter), // std::numeric_limits<typename sprout::darkroom::access::unit<Direction>::type>::epsilon() * 256 // ) ), reflect_dir ), depth_max - 1 ); } public: template< typename Color, typename Camera, typename Objects, typename Lights, typename Ray, typename Intersection, typename Tracer > SPROUT_CONSTEXPR Color operator()( Camera const& camera, Objects const& objs, Lights const& lights, Ray const& ray, Intersection const& inter, Tracer const& tracer, std::size_t depth_max ) const { typedef typename std::decay< decltype(sprout::darkroom::materials::reflection(sprout::darkroom::intersects::material(inter))) >::type reflection_type; return depth_max > 0 && sprout::darkroom::intersects::does_intersect(inter) && sprout::darkroom::materials::reflection(sprout::darkroom::intersects::material(inter)) > std::numeric_limits<reflection_type>::epsilon() ? color_1<Color>( camera, objs, lights, ray, inter, tracer, depth_max, sprout::darkroom::coords::reflect( sprout::darkroom::rays::direction(ray), sprout::darkroom::intersects::normal(inter) ) ) : sprout::tuples::make_clone<Color>(0, 0, 0) ; } }; // // whitted_style // class whitted_style { private: template< typename Color, typename Ray, typename Intersection > SPROUT_CONSTEXPR Color color_3( Ray const& ray, Intersection const& inter, Color const& diffuse_color, Color const& mirror_color ) const { return sprout::darkroom::intersects::does_intersect(inter) ? sprout::darkroom::colors::add( sprout::darkroom::colors::mul( diffuse_color, 1 - sprout::darkroom::materials::reflection(sprout::darkroom::intersects::material(inter)) ), sprout::darkroom::colors::mul( sprout::darkroom::colors::filter( sprout::darkroom::materials::color(sprout::darkroom::intersects::material(inter)), mirror_color ), sprout::darkroom::materials::reflection(sprout::darkroom::intersects::material(inter)) ) ) : sprout::darkroom::coords::normal_to_color<Color>(sprout::darkroom::rays::direction(ray)) ; } template< typename Color, typename Camera, typename Objects, typename Lights, typename Ray, typename Intersection > SPROUT_CONSTEXPR Color color_2( Camera const& camera, Objects const& objs, Lights const& lights, Ray const& ray, std::size_t depth_max, Intersection const& inter, Color const& diffuse_color ) const { return color_3<Color>( ray, inter, diffuse_color, sprout::darkroom::renderers::whitted_mirror().template operator()<Color>( camera, objs, lights, ray, inter, *this, depth_max ) ); } template< typename Color, typename Camera, typename Objects, typename Lights, typename Ray, typename Intersection > SPROUT_CONSTEXPR Color color_1( Camera const& camera, Objects const& objs, Lights const& lights, Ray const& ray, std::size_t depth_max, Intersection const& inter ) const { return color_2<Color>( camera, objs, lights, ray, depth_max, inter, lights.template operator()(inter, objs) ); } public: template< typename Color, typename Camera, typename Objects, typename Lights, typename Ray > SPROUT_CONSTEXPR Color operator()( Camera const& camera, Objects const& objs, Lights const& lights, Ray const& ray, std::size_t depth_max ) const { return color_1<Color>( camera, objs, lights, ray, depth_max, sprout::darkroom::objects::intersect_list(objs, ray) ); } }; } // namespace renderers
最も単純なミラー反射のモデルの実装です。
フレネル係数や屈折は全く考慮していません。
diffuse(拡散光)成分と mirror(反射光)成分を、マテリアルのスペキュラ値に応じて足し合わせます。
反射光成分は、反射した先について再帰的に計算されます。
オブジェクトの配置によっては何度も何度も反射するので、通常反射深度の限界を設定します。
レイトレーサー
namespace tracers { // // raytracer // template<typename Color = sprout::darkroom::colors::rgb_f> class raytracer { public: typedef Color color_type; public: template< typename Renderer, typename Camera, typename Objects, typename Lights, typename Unit > SPROUT_CONSTEXPR color_type operator()( Renderer const& renderer, Camera const& camera, Objects const& objs, Lights const& lights, Unit const& x, Unit const& y, Unit const& width, Unit const& height, std::size_t depth_max = 8 ) const { return renderer.template operator()<color_type>( camera, objs, lights, camera.template operator()( static_cast<typename Camera::unit_type>(x) / width - 0.5, static_cast<typename Camera::unit_type>(y) / height - 0.5 ), depth_max ); } }; } // namespace tracers
raytracer は、レンダラやシーン構成要素をもとに、ある画素に対するレイトレースを開始します。
処理の実際については、レンダラの実装に委譲されます。
ピクセル生成
namespace pixels { // // generate // namespace detail { template< typename Pixels, typename RayTracer, typename Renderer, typename Camera, typename Objects, typename Lights, std::ptrdiff_t... XIndexes > SPROUT_CONSTEXPR inline typename sprout::fixed_container_traits<Pixels>::value_type generate_impl_line( RayTracer const& raytracer, Renderer const& renderer, Camera const& camera, Objects const& objs, Lights const& lights, typename sprout::fixed_container_traits< typename sprout::fixed_container_traits<Pixels>::value_type >::size_type x , typename sprout::fixed_container_traits<Pixels>::size_type y, typename sprout::fixed_container_traits< typename sprout::fixed_container_traits<Pixels>::value_type >::size_type width , typename sprout::fixed_container_traits<Pixels>::size_type height, std::size_t depth_max, sprout::index_tuple<XIndexes...> ) { typedef typename sprout::fixed_container_traits<Pixels>::value_type pixel_line_type; typedef typename sprout::fixed_container_traits<pixel_line_type>::value_type pixel_type; return sprout::make_clone<pixel_line_type>( sprout::darkroom::colors::rgb_f_to_rgb<pixel_type>( raytracer.template operator()( renderer, camera, objs, lights, x + XIndexes, y, width, height, depth_max ) )... ); } template< typename Pixels, typename RayTracer, typename Renderer, typename Camera, typename Objects, typename Lights, std::ptrdiff_t... YIndexes > SPROUT_CONSTEXPR inline Pixels generate_impl( RayTracer const& raytracer, Renderer const& renderer, Camera const& camera, Objects const& objs, Lights const& lights, typename sprout::fixed_container_traits< typename sprout::fixed_container_traits<Pixels>::value_type >::size_type x , typename sprout::fixed_container_traits<Pixels>::size_type y, typename sprout::fixed_container_traits< typename sprout::fixed_container_traits<Pixels>::value_type >::size_type width , typename sprout::fixed_container_traits<Pixels>::size_type height, std::size_t depth_max, sprout::index_tuple<YIndexes...> ) { return sprout::make_clone<Pixels>( sprout::darkroom::pixels::detail::generate_impl_line<Pixels>( raytracer, renderer, camera, objs, lights, x, y + YIndexes, width, height, depth_max, typename sprout::index_range< 0, sprout::fixed_container_traits< typename sprout::fixed_container_traits<Pixels>::value_type >::fixed_size >::type() )... ); } } // namespace detail template< typename Pixels, typename RayTracer, typename Renderer, typename Camera, typename Objects, typename Lights > SPROUT_CONSTEXPR inline Pixels generate( RayTracer const& raytracer, Renderer const& renderer, Camera const& camera, Objects const& objs, Lights const& lights, typename sprout::fixed_container_traits< typename sprout::fixed_container_traits<Pixels>::value_type >::size_type x = 0 , typename sprout::fixed_container_traits<Pixels>::size_type y = 0 , typename sprout::fixed_container_traits< typename sprout::fixed_container_traits<Pixels>::value_type >::size_type width = sprout::fixed_container_traits< typename sprout::fixed_container_traits<Pixels>::value_type >::fixed_size , typename sprout::fixed_container_traits<Pixels>::size_type height = sprout::fixed_container_traits<Pixels>::fixed_size , std::size_t depth_max = 8 ) { return sprout::darkroom::pixels::detail::generate_impl<Pixels>( raytracer, renderer, camera, objs, lights, x, y, width, height, depth_max, typename sprout::index_range< 0, sprout::fixed_container_traits<Pixels>::fixed_size >::type() ); } // // color_pixels // template<std::size_t Width, std::size_t Height, typename Color = sprout::darkroom::colors::rgb> struct color_pixels { public: typedef sprout::array< sprout::array<Color, Width>, Height > type; }; } // namespace pixels
画像イメージは、ピクセル型の2次元配列として表現されます。
generate 関数によって、各々のピクセルについてレイトレースが実行されます。
ここで index_tuple イディオムが用いられています。
繰り返し処理が再帰無しに書けます。
素晴らしいですね。
ユーザコード
- darkroom.cpp
// // DARKROOM_TOTAL_WIDTH // DARKROOM_TOTAL_HEIGHT // #ifndef DARKROOM_TOTAL_WIDTH #define DARKROOM_TOTAL_WIDTH 32 #endif #ifndef DARKROOM_TOTAL_HEIGHT #define DARKROOM_TOTAL_HEIGHT DARKROOM_TOTAL_WIDTH #endif // // DARKROOM_TILE_WIDTH // DARKROOM_TILE_HEIGHT // #ifndef DARKROOM_TILE_WIDTH #define DARKROOM_TILE_WIDTH DARKROOM_TOTAL_WIDTH #endif #ifndef DARKROOM_TILE_HEIGHT #define DARKROOM_TILE_HEIGHT DARKROOM_TOTAL_HEIGHT #endif // // DARKROOM_OFFSET_X // DARKROOM_OFFSET_Y // #ifndef DARKROOM_OFFSET_X #define DARKROOM_OFFSET_X 0 #endif #ifndef DARKROOM_OFFSET_Y #define DARKROOM_OFFSET_Y 0 #endif #define SPROUT_CONFIG_SUPPORT_TEMPORARY_CONTAINER_ITERATION #include <cstddef> #include <iostream> #include <sprout/config.hpp> #include <sprout/darkroom.hpp> SPROUT_STATIC_CONSTEXPR std::size_t total_width = DARKROOM_TOTAL_WIDTH; SPROUT_STATIC_CONSTEXPR std::size_t total_height = DARKROOM_TOTAL_HEIGHT; SPROUT_STATIC_CONSTEXPR std::size_t tile_width = DARKROOM_TILE_WIDTH; SPROUT_STATIC_CONSTEXPR std::size_t tile_height = DARKROOM_TILE_HEIGHT; SPROUT_STATIC_CONSTEXPR std::size_t offset_x = DARKROOM_OFFSET_X; SPROUT_STATIC_CONSTEXPR std::size_t offset_y = DARKROOM_OFFSET_Y; int main() { using namespace sprout::darkroom; SPROUT_STATIC_CONSTEXPR auto object = sprout::make_tuple( objects::make_sphere( coords::vector3d(-1.0, -0.5, 5.0), 1.0, materials::make_material_image( colors::rgb_f(1.0, 0.75, 0.75), 0.2 ) ), objects::make_sphere( coords::vector3d(0.5, 0.5, 3.5), 1.0, materials::make_material_image( colors::rgb_f(0.75, 0.75, 1.0), 0.2 ) ), objects::make_sphere( coords::vector3d(0.5, -0.5, 3.0), 0.5, materials::make_material_image( colors::rgb_f(1.0, 1.0, 1.0), 0.75 ) ) ); SPROUT_STATIC_CONSTEXPR auto light = lights::make_point_light( coords::vector3d(1.0, 0.5, 1.0), colors::rgb_f(3.0, 3.0, 3.0) ); SPROUT_STATIC_CONSTEXPR auto camera = cameras::make_simple_camera(1.0); SPROUT_STATIC_CONSTEXPR auto renderer = renderers::whitted_style(); SPROUT_STATIC_CONSTEXPR auto raytracer = tracers::raytracer<>(); typedef pixels::color_pixels<tile_width, tile_height>::type image_type; SPROUT_STATIC_CONSTEXPR auto image = pixels::generate<image_type>( raytracer, renderer, camera, object, light, offset_x, offset_y, total_width, total_height ); std::cout << "P3" << std::endl << image[0].size() << ' ' << image.size() << std::endl << 255 << std::endl ; for (auto i = image.begin(), last = image.end(); i != last; ++i) { auto const& line = *i; for (auto j = line.begin(), last = line.end(); j != last; ++j) { auto const& pixel = *j; std::cout << unsigned(colors::r(pixel)) << ' ' << unsigned(colors::g(pixel)) << ' ' << unsigned(colors::b(pixel)) << std::endl ; } } }
これが最初の方で示した画像のレンダリングに使ったコードです。
このように、ユーザコードで使うぶんには、return 文1つしか書けないといった制約がないので、
ユーザは綺麗なコードを書くことができます。
最後の部分で標準出力に ppm 形式で書き出しています。
実行時に出力先をファイルにすれば、ppm 形式のファイルが出力されることになります。
シェル
- darkroom.sh
#!/bin/bash TARGET_DIR=supertrace mkdir -p $TARGET_DIR TOTAL_WIDTH=512 TOTAL_HEIGHT=512 TILE_WIDTH=32 TILE_HEIGHT=32 -I/home/boleros/git/sprout -I/home/boleros/git/cel-export -I/usr/include/opencv -lml -lcvaux -lhighgui -lcv -lcxcore -I/home/boleros/pub darkroom.cpp for ((y=0; y<TOTAL_HEIGHT; y+=TILE_HEIGHT)); do for ((x=0; x<TOTAL_WIDTH; x+=TILE_WIDTH)); do mkdir -p $TARGET_DIR/$y/ binname=$x.$y.out g++ -o $binname -std=gnu++0x \ -I/home/boleros/git/sprout \ -DDARKROOM_TOTAL_WIDTH=$TOTAL_WIDTH \ -DDARKROOM_TOTAL_HEIGHT=$TOTAL_HEIGHT \ -DDARKROOM_TILE_WIDTH=$TILE_WIDTH \ -DDARKROOM_TILE_HEIGHT=$TILE_HEIGHT \ -DDARKROOM_OFFSET_X=$x \ -DDARKROOM_OFFSET_Y=$y \ darkroom.cpp && ./$binname > $TARGET_DIR/$y/$x.ppm rm $binname done; pushd $TARGET_DIR/$y/ convert +append $(ls *.ppm | sort -n) ../$y.ppm popd done; echo "" > $TARGET_DIR/$2.done; echo "joining..." pushd $TARGET_DIR convert -append $(ls *.ppm | sort -n) darkroom.ppm popd
512×512 pixcel の画素を一気に生成しようとすれば、
おそらくどんなにメモリを積んでいてもコンパイラが落ちます。
darkroom.sh では、マクロ値を変えつつ連続的に darkroom.cpp をコンパイルします。
16×16 pixcel 毎のタイルとして画像を生成し、それを結合して 512×512 pixcel の画像としています。
まとめ
以上、これが Sprout.Darkroom の実装の全貌です。
正直、あまりの(constexpr の制限から来る)実装の汚さに、constexpr に厭気がさした人もいるかもしれません。
そもそも constexpr の仕様自体がこうした用途を想定したものではないということもあります。
しかしながら、constexpr は通常のシンプルな使用においては TMP やマクロによるコンパイル時計算を
より良く置き換えることの出来る非常に使いやすい道具であることは確かです。
その上でコンパイル時に更に色々遊んでみたいという C++er にとっては、
constexpr はこの上なく可能性を秘めた道具であるということも言えると思います。
!!! Let's constexpr !!!