レイトレースベンチを移植中

とりあえず知っている範囲の構文を使ってやっつけで書いてみた。
まだ未完成だけど更新。
あたらしい言語なのでわりとデバッグに苦労してしまった。
最大の落とし穴はlists:nthが1originなこと。VBかw
あとは3要素ベクトルの操作をリスト内包表記で書こうとして9要素のベクトルになったりとか
いろいろと嵌った。
地雷はすべて自ら踏む主義なのである意味順調。
とりあえずは、球の法線計算とシーンの球を増やす部分を書き上げていったん完成させる方向で。

それと前回書いたdib32.erlの一部が間違っていたので修正。
BITMAPINFOHEADER::biBitCountを4と書いていたが32と書かないといけない。

ToDoとしては、Erlang的な連想配列のようなものの(Record?))使い方と
内積を簡潔に表記する方法を調べる。

-module(renderer).
-export([compatible/3, rendering/4]).
-import(dib32).

% export
%------------------------------------------------------------%
% レイトレースベンチ互換の動作
compatible(F, Size, Level)->
  Scene=create_scene(Level),
  dib32:write_file(F, Size, Size, 
      lists:map(fun(PIXEL) -> sampling(PIXEL, Scene, 4, ray_generator(Size, Size, Size)) end, 
        create_screen(Size, Size))).

% 指定サイズでレンダリングして保存する
rendering(F, W, H, SamplingLevel)->
  Scene=create_scene(),
  dib32:write_file(F, W, H, 
      lists:map(fun(PIXEL) -> sampling(PIXEL, Scene, SamplingLevel, ray_generator(W, H, W)) end, 
        create_screen(W, H))).

% サンプリング
%------------------------------------------------------------%
% 幅と高さを指定してピクセル座標の配列を取得する
create_screen(W, H)->
  [{X, Y}||X<-generate_list(W), Y<-generate_list(H)].

% 指定ピクセルをサンプリング
% ピクセルの中央をサンプリング
sampling({PIXEL_X, PIXEL_Y}, Scene, 1, GET_RAY)->
  ray_trace(GET_RAY(PIXEL_X+0.5, PIXEL_Y+0.5), Scene);
% オーバーサンプリング(N x N倍サンプリングして平均する)
sampling({PIXEL_X, PIXEL_Y}, Scene, N, GET_RAY)->
  average_color(
    lists:map(fun({X, Y})->ray_trace(GET_RAY(X, Y), Scene) end, 
      over_sampling({PIXEL_X, PIXEL_Y}, N))
  ).

% オーバーサンプリング位置を生成
over_sampling({PIXEL_X, PIXEL_Y}, N)->
  D=1.0/N,
  [{X, Y}|| X<-append_times(N, D, [PIXEL_X]), Y<-append_times(N, D, [PIXEL_Y])].

% スクリーン位置XYに対応するレイを作成する関数を返す
ray_generator(W, H, Z)->
  fun (X, Y)->
    [
    {orig, [0.0, 0.0, -4.0]}, % 視点
    {dir, normalize([X-W/2, H/2-Y, -Z])} % 方向
    ]
  end.

% 指定されたピクセルにレイを飛ばしRGB値を得る
ray_trace(Ray, Scene)->
  %io:write(Ray), io:format("~n"),
  case intersect(Ray, get_value(Scene, objects)) of
    % 交差せず
    false -> 
      [0, 0, 0, 0];
    % 交差した
    Intersection -> 
      shading(Intersection, get_value(Scene, lights))
  end.

% シーン
%------------------------------------------------------------%
% シーンを作成する
create_scene()->
  [
  {objects, [
    {sphere, [{center, [0, -1.0, 1.0]}, {radius, 1.0}]} % 球
  ]},
  {lights, [
    {directional_light, [dir, normalize([-1.0, -3.0, 2.0])]} % 光源
  ]}
  ].
create_scene(Level)->
  create_scene(). % ToDo

% 交差判定
%------------------------------------------------------------%
% 球の交差判定
intersect(Ray, {sphere, Sphere})->
  [SX, SY, SZ]=get_value(Sphere, center),
  [OX, OY, OZ]=get_value(Ray, orig),
  % 視点から球の中心へのベクトル
  Vec=[SX-OX, SY-OY, SZ-OZ],
  % レイが球に再接近する点と視点の距離
  B=dot_product(Vec, get_value(Ray, dir)),
  % レイが球に再接近する点と球の中心との距離の二乗
  R=get_value(Sphere, radius),
  case B*B-sqnorm(Vec)+R*R of
    Det when Det<0 -> 
      {false}; % 遠い。交差しない
    Det ->
      {hit, [{distance, 10}]}
  end;
% リストの交差判定
intersect(Ray, Objects)->
  case [Intersection||{hit, Intersection}<-
    lists:map(fun(Object)-> intersect(Ray, Object) end, Objects)] of
    [] -> 
      false;
    Result -> 
      lists:nth(1, 
        lists:sort(fun(A, B)->get_value(A, distance) < get_value(B, distance) end, Result))
  end.

% 輝度計算
%------------------------------------------------------------%
shading(Intersection, Lights)->
  [255, 255, 255, 255].

% ベクトル
%------------------------------------------------------------%
% ベクトルの正規化
normalize(Vec)->
  [N/norm(Vec) || N<-Vec].
% ベクトルの大きさ
norm(Vec)->
  math:sqrt(sqnorm(Vec)).
% 大きさの二乗
sqnorm(Vec)->
  lists:sum([N*N || N<-Vec]).
% 内積
dot_product([LX, LY, LZ], [RX, RY, RZ])->
  LX*RX+LY*RY+LZ*RZ.

% その他
%------------------------------------------------------------%
% {atom, Param}のリストからatomの一致するものをゲットする
get_value([], _)->
  false;
get_value([H|T], Key)->
  {K, V}=H,
  if 
    K==Key -> V;
    true -> get_value(T, Key)
  end.

% 0からN-1のリストを作る
generate_list(N)->
  generate_list(N-1, []).
generate_list(0, L)->
  [0|L];
generate_list(N, L)->
  generate_list(N-1, [N|L]).

% 指定回数Dを加算した配列をつくる
append_times(0, _, L)->
  L;
append_times(N, D, [H|T])->
  append_times(N-1, D, [H+D|[H|T]]).

% RGBAを積算して平均を得る
average_color(L)->
  average_color(L, [0, 0, 0, 0], 0).
average_color([[R, G, B, A]|T], [SUMR, SUMG, SUMB, SUMA], N)-> 
  average_color(T, [SUMA+R, SUMG+G, SUMB+B, SUMA+A], N+1);
average_color([], [SUMR, SUMG, SUMB, SUMA], N)->
  [SUMR div N, SUMG div N, SUMB div N, SUMA div N].
15> renderer:compatible("tmp.bmp", 200, 1).
ok

なんとか円が表示されるところまでこぎつけた。