コントローラー用Sphereの状態に応じて地形を変化させる
VertexWeightProximityのFalloff→TypeをSmoothなどにすると地形がより自然になる。
SphereをMergeして点にしてしまえば、Sphereが表示されなくなる。影響範囲も若干変わる
標高はDisplacementモディファイアのStrengthとMidlevelで調節する。
コントローラ用の頂点を押し出して地形を複雑にする
まずPlaneを配置し、16分割する。地形本体用。
用意出来たらVertex Groupを作成しMountain-Areaと名前を付け、全ての頂点をAssignする。
次にSphereを追加し、Editモードで1/5のサイズにする。地形の形状のコントローラー用。このサイズ変更はradiusの値を変更しても勿論いいが、ObjectモードでScaleすると結果が変わってしまうので注意。地形の形状がコントローラーの変換行列の影響を受けるため。
Planeに対してDisplaceモディファイアを追加。テクスチャにはCloudsを使用。
Coordinatesにはコントローラー用のSphereを設定し、Vertex GroupにMountain-Areaを設定。
ここまでで、コントローラ用Sphereを移動すればDisplacementが位置や回転に応じて変形する。
VertexWeightProximityモディファイアを追加。PlaneのMountain-Areaの頂点を、Shpereとの距離で決定する。Lowest=1,Highest=0に設定すると、Sphereに近い位置の頂点ほど強い影響を受け、遠い位置の頂点ほど影響しなくなる。
一番上にSubdivision Surfaceを追加する。
ずっと以前にパスワード付き展開を忘れていたことを思い出した。
基本は下記で、使用関数を zip_fopen_encrypted にする。
#include <zip.h> #include <iostream> #pragma comment(lib,"zip.lib") #pragma warning(disable:4996) void compress_encrypted(); void uncompress_encrypted(); //ファイルを圧縮 int main(int argc, char* argv[]) { //compress_encrypted(); uncompress_encrypted(); int i; std::cin >> i; return 0; }
void uncompress_encrypted() { // パスワードの定義 const char* thePassWord = "j6KY4cwC"; // ZIPファイルを読み取り専用で開く int errorp; zip_t* zipper = zip_open(R"(C:\test\out.zip)", ZIP_RDONLY, &errorp); // ZIP内のファイルの個数を取得 zip_int64_t num_entries = zip_get_num_entries(zipper, 0); // ZIP内のファイルの各ファイル名を取得 std::cout << "count: " << num_entries << std::endl; for (zip_int64_t index = 0; index < num_entries; index++) { std::cout << "[" << index << "]" << zip_get_name(zipper, index, ZIP_FL_ENC_RAW) << std::endl; } // ZIP内の2番目のファイルに関する情報を取得する struct zip_stat sb; zip_int64_t index = 2; zip_stat_index(zipper, index, 0, &sb); // 2番目のファイルのファイルサイズと同じメモリを確保する char* contents = new char[sb.size]; // 2番目のファイルの内容をメモリに読み込む zip_file* zf = zip_fopen_encrypted(zipper, sb.name, 0, thePassWord); zip_fread(zf, contents, sb.size); zip_fclose(zf); zip_close(zipper); ////////////////// // ファイル名を出力できる形に変更 // ファイル一覧は階層構造をしておらず、ディレクトリ区切りは'/'で直接出力できないので // ファイル名中の'/'を'-'に置き換える。 // 本来なら再帰的にディレクトリを作るなどすべき。 std::string target = sb.name; for (size_t i = 0; i < target.size(); i++) { if (target[i] == '/') { target[i] = '-'; } } // ////////////////// // 解凍したファイルを作成 std::string outname = R"(C:\test\)" + target; FILE* of = fopen(outname.c_str(), "wb"); fwrite(contents, 1, sb.size, of); fclose(of); }
void compress_encrypted() { int errorp; zip_t* zipper = zip_open(R"(C:\test\out.zip)", ZIP_CREATE | ZIP_EXCL, &errorp); // パスワードの定義 const char* thePassWord = "j6KY4cwC"; zip_source_t* source; zip_int64_t iIndex; source = zip_source_file(zipper, R"(C:\test\data\snail1.png)", 0, 0); iIndex = zip_file_add(zipper, R"(snail1.png)", source, ZIP_FL_ENC_RAW); zip_file_set_encryption(zipper, iIndex, ZIP_EM_AES_128, thePassWord);//パスワードを指定 source = zip_source_file(zipper, R"(C:\test\data\snail2.png)", 0, 0); iIndex = zip_file_add(zipper, R"(snail2.png)", source, ZIP_FL_ENC_RAW); zip_file_set_encryption(zipper, iIndex, ZIP_EM_AES_128, thePassWord);//パスワードを指定 source = zip_source_file(zipper, R"(C:\test\data\snail5.png)", 0, 0); iIndex = zip_file_add(zipper, R"(snail5.png)", source, ZIP_FL_ENC_RAW); zip_file_set_encryption(zipper, iIndex, ZIP_EM_AES_128, thePassWord);//パスワードを指定 zip_close(zipper); }
使用する関数がshared_ptrを受け取るようになっていた場合(下記my_useful_function)、当然呼び出すときにshared_ptrでなければいけないが、そのポインタがshared_ptrで管理されていない場合。
unique_ptrであればrelease関数で管理を放棄できるがshared_ptrにはないらしい。
ちなみに無理やりshared_ptrにしてしまうとそのスマートポインタの破棄と同時にdeleteが走るので決してやってはいけない。
#include <iostream> struct MyObject { int value; ~MyObject() { std::cout << "destructor" << std::endl; } }; // この関数は社長の御子息が開発された大変優れたコードであり、開発者は常にこれを呼び出さなければならない。 // 仮に同様の動作をする異なる関数を自作し使用していた場合、現場の混乱を招いたと判断し、 // 最悪解雇も視野に入れた厳重な処分を下す void my_useful_function(std::shared_ptr<MyObject> ptr) { ptr->value = 5; } // この関数は先々代から脈々と受け継がれし伝統ある関数であり、MyObjectの生成には必ずこの関数を呼び出さなければならない。 // 仮に同様の動作をする異なる関数を自作し使用していた場合、現場の混乱を招いたと判断し、 // 最悪解雇も視野に入れた厳重な処分を下す MyObject* my_great_factory() { return new MyObject; }
// 顧客側の要求仕様に則り、この関数の引数を変更してはならない // 仮に同様の動作をする異なる関数を自作し使用していた場合、現場の混乱を招いたと判断し、 // 最悪解雇も視野に入れた厳重な処分を下す void project_job(MyObject* pobect) { // 仕様変更によりmy_normal_functionでは要求を満たせなくなった // 旧コード my_normal_function(pobject) my_useful_function(pobect); //新コード 型が違って呼び出せない }
int main() { MyObject* pmyo = my_great_factory(); project_job(pmyo); std::cout << pmyo->value; delete pmyo; }
スマートポインタにはdeleterを指定できるので、そこに何もしないラムダ関数を指定する。
// 顧客側の要求仕様に則り、この関数の引数を変更してはならない // 仮に同様の動作をする異なる関数を自作し使用していた場合、現場の混乱を招いたと判断し、 // 最悪解雇も視野に入れた厳重な処分を下す void project_job(MyObject* pobect) { // 仕様変更によりmy_normal_functionでは要求を満たせなくなった // 旧コード my_normal_function(pobject) // shared_ptrにする。deleterを指定し、何もしないことでpbojectの参照先が破棄されないようにする std::shared_ptr<MyObject> spobject(pobect, [](auto) {}); my_useful_function(spobject); //新コード shared_ptr型の引数を与える }
まずテスト用に以下のように関数宣言、定数の定義をしておく。
#include <iostream> #include <random> // 乱数用 constexpr int TESTCOUNT = 10; /////////////////////////// void cpp_rand_test_1(); void cpp_rand_test_2(); void cpp_rand_test_3(); void cpp_rand_test_4(); void cpp_rand_test_5(); /////////////////////////// int main() { cpp_rand_test_1(); //cpp_rand_test_2(); //cpp_rand_test_3(); //cpp_rand_test_4(); //cpp_rand_test_5(); }
// 疑似乱数 基本 void cpp_rand_test_1() { // *疑似乱数 // *初期シードが同じなら毎回同じ値が出る // *64bitの範囲が欲しいなら std::mt19937_64 std::mt19937 mt(0/*初期シード*/); for (size_t i = 0; i < TESTCOUNT; i++) std::cout << mt() << std::endl; }
// 範囲指定 , 一様乱数 void cpp_rand_test_2() { // 疑似乱数 std::mt19937 mt(0/*初期シード*/); // 0~5の範囲の一様乱数作成用 // float型が欲しいなら std::uniform_real_distribution<> std::uniform_int_distribution<> idistri(0, 5); for (size_t i = 0; i < TESTCOUNT; i++) std::cout << idistri(mt) << std::endl; }
// 正規分布乱数 void cpp_rand_test_3() { // 疑似乱数 std::mt19937 mt(0/*初期シード*/); // 正規分布乱数作成用 std::normal_distribution<> norm(50.0/*平均*/, 10.0/*分散*/); for (size_t i = 0; i < TESTCOUNT; i++) std::cout << norm(mt) << std::endl; }
// 非決定論的な乱数 void cpp_rand_test_4() { // 注意点: // *どんな値が出るか分からない // *遅い // *ハードウェアを使うのでプラットフォームによっては実装不可 // (実装できない場合は疑似乱数として動く) std::random_device rng; for (size_t i = 0; i < TESTCOUNT; i++) std::cout << rng() << std::endl; }
// 非決定論的な乱数をseedに疑似乱数を生成 void cpp_rand_test_5() { // 非決定論的な乱数 std::random_device rng; // 疑似乱数 std::mt19937 mt(rng()/*初期シード*/); for (size_t i = 0; i < TESTCOUNT; i++) std::cout << mt() << std::endl; }
「ページを Pocket に保存」「スクリーンショットを撮影」などは、自分にとって不要なので右クリックメニューから非表示にしたい。
about:configから設定する。実行すると「危険性を承知の上で使用する」の同意を求められるのでクリックする。
各設定項目の右側のチェックをクリックするとフラグが反転する。
「ページを Pocket に保存」 ... extensions.pocket.enabled
「スクリーンショットを撮影」... extensions.screenshots.disabled
線分を書くプログラムが動かなくて困ったので、C++でCUDAの動きを再現するプログラムを書いた。
#pragma once struct COORD { int x, y; COORD(int _x, int _y) { x = _x; y = _y; } COORD(int _x) { x = _x; y = 1; } COORD() { x = 0; y = 0; } }; using MyBlockIdx = COORD; using MyThreadIdx = COORD; using DimBlock = COORD; using DimGrid = COORD; struct thData { MyBlockIdx blockIdx; DimBlock blockDim; MyThreadIdx threadIdx; };
//! @brief grid x block回関数fを呼び出す //! @param grid グリッド //! @param block ブロック //! @param f 呼び出す関数 //! @param args N個の引数 template<class F, class ...Args> void fakeCudaKernel(DimBlock grid, DimGrid block, F&& f, Args&&... args) { thData _thread; _thread.blockDim = block; for (int gx = 0; gx < grid.x; gx++) { for (int gy = 0; gy < grid.y; gy++) { for (int bx = 0; bx < block.x; bx++) { for (int by = 0; by < block.y; by++) { _thread.blockIdx.x = gx; _thread.blockIdx.y = gy; _thread.threadIdx.x = bx; _thread.threadIdx.y = by; f(_thread, args...); } } } } }
#include <iostream> #include <algorithm> #include "fakeCUDA.hpp" #pragma warning(disable:4996)
//カーネルに該当する関数
void line( thData t, unsigned int* pimage, const int width, const int height, const int sx, const int sy, const int ex, const int ey) { //アクセス法 //このスレッドが担当する画素のx位置を二次元座標で求める int xpos = t.blockIdx.x * t.blockDim.x + t.threadIdx.x; //int ypos = blockIdx.y * blockDim.y + threadIdx.y; double a = (ey - sy) / double(ex - sx);//傾き double b = sy - a * sx;//切片 //xposは0~になっているので、offsetを足す int x = xpos +(std::min)(sx,ex); if (x < 0 || x >= width)return; if (x < (std::min)(sx, ex) || x >= (std::max)(sx, ex))return; int y = a * x + b; if (y < 0 || y >= height)return; if (y < (std::min)(sy, ey) || y >= (std::max)(sy, ey))return; int pos = y * width + x; pimage[pos] = 1; }
void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned int* const p);
int main() { int width = 100; int height = 50; int sx = 40; // 線分の始点・終点を設定 int sy = 5; int ex = 10; int ey = 20; //ループ回数を決定 int xlen = (std::max)(sx, ex) - (std::min)(sx, ex); // xlenを grids×blocksで表現 int blocks = 512; int grids = (xlen + 511) / blocks; DimBlock block(blocks, 1); DimGrid grid(grids, 1); unsigned int* p_cpu = new unsigned int[width*height]; for (int i = 0; i < width*height; i++) { p_cpu[i] = 0; } fakeCudaKernel(grid, block, line, p_cpu, width, height,sx,sy,ex,ey); /////////////////////////////////////////////// // 結果出力 pbmP1_Write("cudaline.pbm", width, height, p_cpu); delete[] p_cpu; }
//! @brief PBM(1byte,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details 1画素1Byteのメモリを渡すと、0,1テキストでファイル名fnameで書き込む void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned int* const p) { // PPM ASCII FILE* fp = fopen(fname, "wb"); fprintf(fp, "P1\n%d\n%d\n", width, height); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d ", p[k] /*? 1 : 0*/); k++; } fprintf(fp, "\n"); } fclose(fp); }
前回のプログラムでは、画像全ての画素に対して、線分上のピクセルかどうかを判断した。無駄が多いように感じるので、全てのx座標に対するy座標を算出するように書き換えた。
#include "cuda_runtime.h" #include "device_launch_parameters.h" #include <stdio.h> #include <stdlib.h>
/////////////////////////////////////////////// // GPU側 ////////////////////////////////////// __global__ void line( unsigned char* pimage, const int width, const int height, const int sx, const int sy, const int ex, const int ey) { //アクセス法 //このスレッドが担当する画素のx位置を二次元座標で求める int xpos = blockIdx.x * blockDim.x + threadIdx.x; //int ypos = blockIdx.y * blockDim.y + threadIdx.y; double a = (ey - sy) / double(ex - sx);//傾き double b = sy - a * sx;//切片 //xposは0~になっているので、offsetを足す int x = xpos + min(sx, ex); if (x < 0 || x >= width)return; if (x < min(sx,ex) || x >= max(sx, ex))return; int y = a * x + b;//xの時のy座標を求める if (y < 0 || y >= height)return; if (y < min(sy, ey) || y >= max(sy, ey))return; int pos = y * width + x; pimage[pos] = 1;//画素へ書き込み }
///// void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p); int main() { int width = 100; int height = 50; int sx = 40; // 線分の始点・終点を設定 int sy = 5; int ex = 10; int ey = 20; //書き込む画素数を算出 int xlen = max(sx, ex) - min(sx, ex); // xlenを grids×blocksで表現 int blocks = 512; int grids = (xlen+511) / blocks; dim3 block(blocks,1); dim3 grid(grids,1); unsigned char* p_cpu = new unsigned char[width*height]; for (int i = 0; i < width*height; i++) { p_cpu[i] = 0; } unsigned char* p_gpu;//GPU側メモリ確保 cudaError_t ce = cudaMalloc((void**)&p_gpu, width*height); printf("error:%d\n", ce); line << <grid, block >> > (p_gpu, width, height, sx, sy, ex, ey); //GPU→CPU側へメモリコピー cudaMemcpy(p_cpu, p_gpu, width*height, cudaMemcpyDeviceToHost); cudaFree(p_gpu); /////////////////////////////////////////////// // 結果出力 pbmP1_Write("cudaline.pbm", width, height, p_cpu); delete[] p_cpu; return 0; } //! @brief PBM(1byte,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details 1画素1Byteのメモリを渡すと、0,1テキストでファイル名fnameで書き込む void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p) { // PPM ASCII FILE* fp = fopen(fname, "wb"); fprintf(fp, "P1\n%d\n%d\n", width, height); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d ", p[k] ); k++; } fprintf(fp, "\n"); } fclose(fp); }
前回のC++のプログラムをCUDAで書き直してみる。
流石に無駄が多すぎることは自分でもわかるので次回はもっとましな方法を考える。
#include "cuda_runtime.h" #include "device_launch_parameters.h" #include <stdio.h> #include <stdlib.h> /////////////////////////////////////////////// // GPU側 ////////////////////////////////////// __global__ void line( unsigned char* pimage, const int width, const int height, const int sx, const int sy, const int ex, const int ey) { //アクセス法 //このスレッドが担当する画素の位置を二次元座標で求める int xpos = blockIdx.x * blockDim.x + threadIdx.x; int ypos = blockIdx.y * blockDim.y + threadIdx.y; int pos = ypos * width + xpos; if (xpos >= width || ypos >= height)return; if (pos < 0 || pos >= width * height)return; double a = (ey - sy) / double(ex - sx);//傾き double b = sy - a * sx;//切片 double y = int(a * xpos + b); if (ypos < min(sy, ey))return; if (xpos < min(sx, ex))return; if (ypos > max(sy, ey))return; if (xpos > max(sx, ex))return; if (y == ypos) { pimage[pos] = 1; } else { pimage[pos] = 0; } } ///// void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p); int main() { int width = 100; int height = 50; int sx = 98; // 線分の始点・終点を設定 int sy = 48; int ex = 1; int ey = 3; // 最小単位 合計で512未満 //10×10の領域に分けて計算する dim3 block(10, 10); //グリッド数 // スレッド数はblock数×grid数なので(10x100)x(10x100)=1000x1000 // 多すぎるが今回はそこまで考えない dim3 grid(100, 100); unsigned char* p_cpu = new unsigned char[width*height]; for (int i = 0; i < width*height; i++) { p_cpu[i] = 1; } unsigned char* p_gpu;//GPU側メモリ確保 cudaError_t ce = cudaMalloc((void**)&p_gpu, width*height); printf("error:%d\n", ce); line << <grid, block >> > (p_gpu, width, height,sx,sy,ex,ey); //GPU→CPU側へメモリコピー cudaMemcpy(p_cpu, p_gpu, width*height, cudaMemcpyDeviceToHost); cudaFree(p_gpu); /////////////////////////////////////////////// // 結果出力 pbmP1_Write("C:\\dev\\cudaline.pbm", width, height, p_cpu); delete[] p_cpu; return 0; } //! @brief PBM(1byte,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details 1画素1Byteのメモリを渡すと、0,1テキストでファイル名fnameで書き込む void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p) { // PPM ASCII FILE* fp = fopen(fname, "wb"); fprintf(fp, "P1\n%d\n%d\n", width, height); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d ", p[k] ? 1 : 0); k++; } fprintf(fp, "\n"); } fclose(fp); }
ブレゼンハムを並列化するには?と思ったのだが、そもそもブレゼンハムは、「シングルスレッドで高速化するためには全部整数演算にすればいいんだ」というアルゴリズムなので(多分)、並列化とは相性が悪そうなんじゃないかと言うことで、とりあえず普通の数学的な式をそのまま使用してみる。
なお私はCUDA入門者なのであまり人様の参考になるようなことは書けない。
#include <iostream> #include<vector> #include<algorithm> #pragma warning(disable:4996) //! @brief PBM(1byte,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details 1画素1Byteのメモリを渡すと、0,1テキストでファイル名fnameで書き込む void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p) { // PPM ASCII FILE* fp = fopen(fname, "wb"); fprintf(fp, "P1\n%d\n%d\n", width, height); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d ", p[k] ? 0 : 1); k++; } fprintf(fp, "\n"); } fclose(fp); } const std::uint8_t white = 255; const std::uint8_t black = 0;
//! @brief 与えられたピクセル座標が線分の方程式の上ならそのピクセルを塗りつぶす //! @param pimage 書き込み先 //! @param width 画像サイズ //! @param height 画像サイズ //! @param sx 始点 //! @param sy 始点 //! @param ex 終点 //! @param ey 終点 //! @param pixelx 処理するピクセル //! @param pixely 処理するピクセル void multiline_core(std::uint8_t* pimage, int width, int height, int sx, int sy, int ex, int ey, int pixelx, int pixely) { double a = (ey - sy) / double(ex - sx);//傾き double b = sy - a * sx;//切片 int pos = pixely * width + pixelx; if (pos < 0 || pos >= width * height)return; double y = int(a * pixelx + b); if (pixely < std::min(sy, ey))return; if (pixelx < std::min(sx, ex))return; if (pixely > std::max(sy, ey))return; if (pixelx > std::max(sx, ex))return; if (y == pixely) { pimage[pos] = black; } }
//! @brief 全ての画素について、そのピクセルが直線上にあるかをテストする //! @param image 書き込み先 //! @param width 画像サイズ //! @param height 画像サイズ //! @param sx 始点 //! @param sy 始点
//! @param ex 終点 //! @param ey 終点 void multiline( std::vector<std::uint8_t>& image, int width, int height, int sx, int sy, int ex, int ey) { //全てのピクセルについて直線上にあるかをチェック for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { multiline_core( image.data(), width, height, sx, sy, ex, ey, x, y); } } }
int main() { int width = 100; int height = 50; int sx = 98; // 線分の始点・終点を設定 int sy = 48; int ex = 1; int ey = 3; std::vector<std::uint8_t> image(width * height * 1, white); multiline(image, width, height, sx, sy, ex, ey); pbmP1_Write("c:\\test\\a.pbm", width, height, image.data()); }
普通に考えると「あるピクセルの隣のピクセルを塗る」という考え方でループするのだが、1ピクセル1スレッドで並列化する場合は「このピクセルは塗るべきか?をそれぞれのピクセルに対してチェックする」という考え方をすれば、ループする必要はない。
上記プログラムをCUDAで実装する。