読者です 読者をやめる 読者になる 読者になる

加賀一稿一記

心は戦国

CGのお勉強

平日なのに授業がないので,お勉強をすることにした.
今日はCGについてチョコっと.
理論が正しい保証はないです.

余談
ところで少し前まで「CG = 3DCG」みたいな小さく浅い理解しかしていなかったのですが,
その分野の方から「もっと広くCGを理解するとよい」みたいなありがたいお言葉を聞きました.

今日のテーマ
曲面に光を当てるシミュレートをする.
座標系は
→x
↓y
⊗z

今日の曲面
{ \displaystyle
z = -40 e^{-\frac{(x-128)^2}{2\sigma^2}} e^{-\frac{(y-128)^2}{2\sigma^2}}\\
\sigma = 35
}
f:id:kgsn:20170428145937p:plain
(↑Microsoft Mathematics先生のplot)


法線の計算
座標(x, y)での単位法線ベクトルは
-\frac{\partial z}{\partial x} \equiv p
-\frac{\partial z}{\partial y} \equiv q
としたとき
$$
{\bf n} = \frac{1}{\sqrt{p^2+q^2+1}} \left(\begin{array}{ccc}p\\q\\1\end{array}\right)
$$
となる.
今日の曲面の場合は
{ \displaystyle
p = \frac{8}{245} (x-128) e^{-\frac{-(x-128)^2-(y-128)^2}{2450}}\\
q = \frac{8}{245} (y-128) e^{-\frac{-(x-128)^2-(y-128)^2}{2450}}
}
である.
法線を可視化するために
$$
\left(\begin{array}{ccc}R\\G\\B\end{array}\right) = \frac{255}{2}\left({\bf n}+\left(\begin{array}{ccc}1\\1\\1\end{array}\right)\right)
$$
とすれば法線マップが得られる.(範囲は0~255になる)
f:id:kgsn:20170428154633p:plain
(↑法線マップ,色が法線方向を表している)

#include "opencv2\opencv.hpp"
#include <math.h>

int main(){
	
	cv::Mat Img(256, 256, CV_8UC3);	

	for (int y = 0; y < 256; y++){
		for (int x = 0; x < 256; x++){
			double p = -8.0 / 245 * (x - 128)*exp((-std::pow(x - 128, 2) - std::pow(y - 128, 2)) / 2450);
			double q = -8.0 / 245 * (y - 128)*exp((-std::pow(x - 128, 2) - std::pow(y - 128, 2)) / 2450);
			double norm = std::sqrt(p*p + q*q + 1);
			double r = 255 * 0.5*((p / norm) + 1);
			double g = 255 * 0.5*((q / norm) + 1);
			double b = 255 * 0.5*((1.0 / norm) + 1);
			Img.at<cv::Vec3b>(y, x) = cv::Vec3b(b, g, r);
		}
	}
	
	cv::imwrite("Img.png", Img);

	return 0;
}

反射
光源を1つの平行光源のみとして,その単位光源ベクトルを{\bf s}とする.
また,ランバート反射を仮定する.
テクスチャが\rhoのとき,見え方i
i = \rho{\bf ns}
となる.

シミュレート
今日の曲面に単色白色テクスチャを割り当てて
$$
{\bf s} = \left(\begin{array}{ccc}
\frac{1}{\sqrt{2}}\\
0\\
\frac{1}{\sqrt{2}}
\end{array}\right)
$$
の光を当てるシミュレートをしてみた.
f:id:kgsn:20170428163554p:plain
(↑曲面に左側から光を当てた際のCG,凹にも凸にも見える)

#include "opencv2\opencv.hpp"
#include <math.h>

int main(){
	
	cv::Mat Src = cv::imread("whtie.png");
	cv::Vec3d s(1.0 / std::sqrt(2.0), 0, 1.0 / std::sqrt(2.0));
	cv::Mat Img(256, 256, CV_8UC3);	

	for (int y = 0; y < 256; y++){
		for (int x = 0; x < 256; x++){
			double p = -8.0 / 245 * (x - 128)*exp((-std::pow(x - 128, 2) - std::pow(y - 128, 2)) / 2450);
			double q = -8.0 / 245 * (y - 128)*exp((-std::pow(x - 128, 2) - std::pow(y - 128, 2)) / 2450);
			double norm = std::sqrt(p*p + q*q + 1);
			cv::Vec3d n(p / norm, q / norm, 1.0 / norm);
			Img.at<cv::Vec3b>(y, x) = Src.at<cv::Vec3b>(y, x)*n.ddot(s);
		}
	}
	
	cv::imwrite("Img.png", Img);

	return 0;
}

一見うまくいったように思えたが,次の画像を処理させたときにまだダメなことに気づいてしまった.
f:id:kgsn:20170428165822p:plain
(↑入力画像)
f:id:kgsn:20170428165835p:plain
(↑出力画像)
テクスチャそのものが変形していないので,違和感がある.

まとめ
簡単だが曲面(実際は微小な面)に光を当てるシミュレートはできたと思う.
レンダリングのライティング部分か?)
ただし,テクスチャが変形していないのに変形したつもりで計算をしても,違和感が生まれるだけだった.
今後はテクスチャを曲面に変形させる部分について取り組みたい.
モデリングに相当するのだろうか)

たわごと
今回は非常にシンプルに平行光源を使ったけれど,光源の種類もたくさんある.
反射の特性も材質によって様々.
これらを理解せずにCVに取り組むのはよくないんやろネ.

2D原画を変形させて3次元風の表現をするソフトが少し話題になってて興味深いけれど,
そのうちどなたかがフリー版を作って公開しちゃうんじゃないだろうか.
(たぶんそうなっても会社はプラットフォームとかモデル作成代行とかでどうにかなるんだろう)
HowTo動画や関連してそうな論文を読むと簡単なものならプログラムできそう.
時間があったら自分で描いたイラストを自分のプログラムで動かしてみたい.

追記(2017/04/29)
テクスチャを変形させてから光を当ててみた.
f:id:kgsn:20170429191723p:plain
(↑再シミュレート結果)
綺麗でないし,ちゃんと勉強しないといけない.
調べているうちにバンプマッピングという手法を知った.
「RGB+法線」を貼り付けるって面白い.