保存にはgimp-xcf-saveを使う。
; 画像番号は (gimp-image-list) で取得できる数字。今回は「1」 (gimp-xcf-save 0 ; ダミーパラメータ 1 ; 画像番号 (car (gimp-image-get-active-drawable 1)) ; 有効にしたいレイヤー(現在選択中のレイヤー) "c:/data/test.xcf" ; 保存ファイル名 URI形式 "test.xcf" ; 保存ファイルのベース名 )
第一引数はダミーパラメータ。0でも入れておけばいい
第二引数は保存したい画像。一度に複数の画像を開いていたり、開いた画像のいくつかを閉じたりすると「1」の画像がなくなる可能性がある。ので、「gimp-image-list」で確認するといい
第三引数はアクティブにしたいレイヤー。(gimp-image-get-active-drawable image)で取得するのが無難。
第四引数は保存するファイル名。URI形式で指定。
第五引数はベースファイル名(ファイル名+拡張子)だが色々指定したが無視されているようにしか思えない。
式自体は何のことはない。問題はGimpのScript-Fuでやりたいので結構手間取った。
さすがにGimpのScript-Fuコンソールから実験するのは環境が悪すぎたので、RacketをインストールしてScheme環境を作った。
remainderは剰余。
;; 画素番号と画像幅からx座標を計算 (define (getx pos width) (remainder pos width) ) ;; x座標と画素番号と画像幅からy座標を計算し、x,yで返す (define (getxy x pos width) (list x (/ (- pos x) width) ) ) ;; 画素番号と画像幅からx,y座標を計算する (define (2dcoord pos width) (getxy (getx pos width) pos width) )
width=50の画像の1162番のx,y座標は(12,23)。
// x,yから画素番号を算出 inline int calcpos(int x, int y,int width) { return y * width + x; } // 画素番号と画像幅からx座標を計算 int getx(int pos, int width) { return pos % width; } // 画素番号とx座標と画像幅からy座標を計算 int gety(int x, int pos, int width) { return (pos - x) / width; } int main() { int width = 50; int height = 25; int x = 12; int y = 23; int pos = calcpos(x, y, width); //1162 printf("pos %d\n", pos); int xx = getx(pos, width); int yy = gety(xx, pos, width); printf("xx %d\n", xx); printf("yy %d\n", yy); }
パス一覧は (gimp-image-get-vectors 画像番号) で取得できる
gimp-image-get-layersで取得できるのだが、vectorではなくlistでほしいのでラップする。
; レイヤー一覧 (define (get-layer-list image) (vector->list (car (cdr (gimp-image-get-layers image) ) ) ) )
レイヤーに対して、gimp-drawable-is-text-layerでテキストレイヤーかどうかを判定できる。
; imagelistの中からテキストレイヤーだけを抽出する (define (get-text-layers imagelist) (if ( null? imagelist ) '() (if (= 1 (car (gimp-drawable-is-text-layer (car imagelist) ) ) ) (cons (car imagelist) (get-text-layers (cdr imagelist) ) ) (get-text-layers (cdr imagelist) ) ) ) )
Script-Fuは フィルター → Script-Fu → Script-Fu コンソール からコンソールのウィンドウを開き一行ずつ入力できる。
初めてのGimpのスクリプト。
;; 画像一覧 (define Images (vector->list (car (cdr (gimp-image-list))))) (define ImageIndex (car Images) ) ;; ImageIndex内のレイヤー一覧 (define Layers (vector->list (car (cdr (gimp-image-get-layers ImageIndex))))) (define LayerIndex (car Layers) ) ;; 編集対象は以後Drawable (define Drawable LayerIndex) ;; 画像サイズ確認 (gimp-drawable-width Drawable) (gimp-drawable-height Drawable) ;; 座標 (define x 10) (define y 10) ;; ピクセルの更新 (gimp-drawable-set-pixel Drawable x x 3 #(255 0 0) ) (gimp-displays-flush) (gimp-drawable-update Drawable x y 1 1)
https://developer.gimp.org/api/2.0/libgimp/libgimp-gimpdrawable.html
C++でバリアント型を使う。C++17以降。
#include <cstdio> #include <string> #include <variant> enum VARIANT_TYPES { V_INT = 0, V_STR = 1, V_DBL = 2 }; int main() { std::variant<int, std::string, double> val; val = 2; //val = 5.5; //val = "abc"; if (val.index() == V_INT) { int& x = std::get<V_INT>(val); printf("--- %d\n", x); }
else if(val.index()==V_STR){ std::string& s = std::get<V_STR>(val); printf("--- %s\n", s.c_str()); }
else if (val.index() == V_DBL) { double& d = std::get<V_DBL>(val); printf("--- %lf\n", d); } int k = getchar(); }
std::variant型を引数で取る関数を作ってもいいが、std::visit関数でoperator()を定義したクラスのインスタンスを渡して呼び出す方が美しいらしい。
#include <cstdio> #include <string> #include <variant>
struct Print { // int が代入されたときに呼び出される void operator()(const int& data) { printf("--- %d\n", data); } // std::string が代入されたときに呼び出される void operator()(const std::string& data) { printf("--- %s\n", data.c_str()); } // double が代入されたときに呼び出される void operator()(const double& data) { printf("--- %lf\n", data); } };
int main() { std::variant<int, std::string, double> val; val = 2; std::visit(Print(), val); val = 5.5; std::visit(Print(), val); val = "abc"; std::visit(Print(), val); int k = getchar(); }
std::visitは第一引数に関数、第二引数以降はstd::variant型でなければならない(らしい)。
std::visit( 関数() , variant1 , variant2 , variant3 , … )
なので、std::visitの引数で渡す場合、std::variantで渡さなければいけない。
#include <cstdio> #include <string> #include <variant> struct Print { // int が代入されたときに呼び出される void operator()(const int& data) { printf("--- %d\n", data); } // std::string が代入されたときに呼び出される void operator()(const std::string& data) { printf("--- %s\n", data.c_str()); } // double が代入されたときに呼び出される void operator()(const double& data) { printf("--- %lf\n", data); } };
struct Add { // int が代入されたときに呼び出される void operator()(int& data, const int val) { data += val; } // std::string が代入されたときに呼び出される void operator()(std::string& data, const int val) { data += std::to_string(val); } // double が代入されたときに呼び出される void operator()(double& data, const int val) { data += val; } };
int main() { std::variant<int, std::string, double> val; val = 2; std::visit( Add(), val,std::variant<int>(4) ); std::visit(Print(),val); val = 5.5; std::visit( Add(), val, std::variant<int>(4)); std::visit(Print(), val); val = "abc"; std::visit( Add(), val, std::variant<int>(4)); std::visit(Print(), val); int k = getchar(); }
引数を渡すならコンストラクタに渡した方がスマートだと思う。
#include <cstdio> #include <string> #include <variant>
struct Print { std::string _sign; public: Print(const std::string& sign):_sign(sign) {} // int が代入されたときに呼び出される void operator()(const int& data) { printf("%s %d\n",_sign.c_str(), data); } // std::string が代入されたときに呼び出される void operator()(const std::string& data) { printf("%s %s\n", _sign.c_str(), data.c_str()); } // double が代入されたときに呼び出される void operator()(const double& data) { printf("%s %lf\n", _sign.c_str(), data); } };
int main() { std::variant<int, std::string, double> val; val = 2; std::visit(Print(">>"),val); val = 5.5; std::visit(Print(">>"), val); val = "abc"; std::visit(Print(">>"), val); int k = getchar(); }
PPM入出力:
https://www.study.suzulang.com/cppppm-readerwriter
NByteDataを扱うクラス
https://www.study.suzulang.com/2dcg-functions/nbyte-data-type
#include <cstdio> #include <vector> #include <cassert> #include "NByteData.hpp" #include "ppmP3_read.hpp" namespace szl{ inline int calc_position2d(const int x, const int y, const int width) { return x + y * width; }
//canvasの一部に矩形でアクセスできるためのクラス class D2CanvasWindow { public: //! @brief start ~ end の範囲の画素番号一覧 std::vector<size_t> m_imgp; int startx,starty; int endx,endy; int canvaswidth; //!< 元画像の範囲 int canvasheight;//!< 元画像の範囲 inline int width() {return endx - startx + 1;} inline int height() {return endy - starty + 1;} //! @brief 元画像サイズとクリップサイズの設定 //! @param [in] canvaswidth 元画像のサイズ //! @param [in] canvasheight 元画像のサイズ //! @param [in] sx 始点 //! @param [in] sy 始点 //! @param [in] ex 終点 //! @param [in] ey 終点 //! @return なし void set_size(const int canvaswidth, const int canvasheight, const int sx, const int sy, const int ex, const int ey) { startx = sx; starty = sy; endx = ex; endy = ey; const int Width = width(); const int Height = height(); m_imgp.resize(Width * Height); int wx, wy;// クリップする範囲の画素indexの一覧を作成 for (int cx = sx; cx <= ex; cx++) { for (int cy = sy; cy <= ey; cy++) { wx = cx - sx; wy = cy - sy; int ci = calc_position2d(cx, cy, canvaswidth); int wi = calc_position2d(wx, wy, Width); m_imgp[wi] = ci; } } } //! @brief 画像を切り出す //! @param [in] dst 結果画像 //! @param [in] src 元画像 //! @return なし template<typename Canvas> void Clip(Canvas* dst, const Canvas& src) { assert(iwidth(*dst) == width()); assert(iheight(*dst) == height()); for(size_t i =0;i<m_imgp.size();i++){ (*dst)[i] = src[m_imgp[i]]; } } };
} ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// using PixelT = NByteData<3>; //画像データ struct Img2d { std::vector<PixelT> m_img; int width; int height; Img2d(int w, int h) { m_img.resize(w*h); width = w; height = h; } PixelT& operator[](const size_t index) {return m_img[index];} const PixelT& operator[](const size_t index)const {return m_img[index];} }; //! @brief 画像の幅の取得関数 //! @param [in] i 画像 //! @return 画像の幅 int iwidth(const Img2d& i) { return i.width; } //! @brief 画像の高さ取得関数 //! @param [in] i 画像 //! @return 画像の幅 int iheight(const Img2d& i) { return i.height; } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// //! @brief 画像の書き込み void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax );
int main() { // 画像読み込み PixelT* pimg; int width; int height; int vmax; ppmP3_read( "C:\\test\\p.ppm", &width, &height, &vmax, (unsigned char**)&pimg ); Img2d img(width, height); for (size_t i = 0; i < width*height; i++) { img.m_img[i] = pimg[i]; } delete[]pimg; ///////////////////////////////// ///////////////////////////////// // 画像のクリップ szl::D2CanvasWindow win; win.set_size(img.width,img.height, 2, 3, 8, 10); Img2d clip(win.width(), win.height()); win.Clip(&clip, img); ///////////////////////////////// ///////////////////////////////// ppmP3_write( "C:\\test\\q.ppm", clip.width, clip.height, (unsigned char*)clip.m_img.data(), 255); }
//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @param [in] vmax 全てのRGBの中の最大値。普通の画像なら255 //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax ) { FILE* fp = fopen(fname, "wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax); 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 %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2] ); k++; } fprintf(fp, "\n"); } fclose(fp); }
diffuse反射はPhongの反射モデルの一部で、ADSのDの部分。やってることは面法線と光のベクトルの内積を光の強さとしているといっていい(だろう。たぶん。)。ベクトルの内積は、その角度が小さくなると大きくなり、角度が大きくなると小さくなる。
ただこの現象を文章で説明されてもぱっと思い浮かばないので可視化する
これは面ごとにEmissionで色を付けている。
角度が小さいほど明るい色になっている。
実行すると、光源の位置に該当する球と、対象物に対する球、光源から物体の方向のArrowと各面の面法線、その位置からの光源へのベクトルが生成される。
import bpy import mathutils import numpy import math # @brief オブジェクトを指定座標へ向けるための回転軸と回転角を計算する # @param [in] オブジェクト # @param [in] 向ける方向(座標) # @return 回転軸,回転角(ラジアン) def calc_lookat_param(obj,lookat): # オブジェクトのローカルのZ軸のベクトルを取得 mat = obj.matrix_world localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2])) #print(localZ) va = mathutils.Vector(lookat) - obj.location vb = mathutils.Vector(localZ) va.normalize() vb.normalize() # 外積 axis = mathutils.Vector.cross(va,vb) axis.normalize() # 内積 th = mathutils.Vector.dot(va,vb) # 角度算出 rad = -math.acos( numpy.clip(th,-1.0,1.0) ) return axis , rad # @brief 任意軸回転 # @param [in,out] obj 回転するオブジェクト # @param [in] axis 回転軸 # @param [in] radian 回転角をラジアンで指定 # @return なし # @sa https://www.study.suzulang.com/bpy-calculation/bpy-arbitrary-axis-rotation def rotate_object(obj,axis,radian): rot_mat= mathutils.Matrix.Rotation( radian, 4, axis ) # decompose world_matrix's components, and from them assemble 4x4 matrices orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose() # orig_loc_mat = mathutils.Matrix.Translation(orig_loc) orig_rot_mat = orig_rot.to_matrix().to_4x4() orig_scale_mat = (mathutils.Matrix.Scale(orig_scale[0],4,(1,0,0)) @ mathutils.Matrix.Scale(orig_scale[1],4,(0,1,0)) @ mathutils.Matrix.Scale(orig_scale[2],4,(0,0,1))) # # assemble the new matrix obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat def fix_arrow_dir(aobj,_to): # 方向を一致させるためのパラメータ取得 axis , rad = calc_lookat_param(aobj,_to) # 回転 rotate_object(aobj,axis,rad) def fix_arrow_fromto(aobj,_from,_to): aobj.location=_from fix_arrow_dir(aobj,_to) aobj.location=_from # _from → _to の距離を計算 objlen = ( _from - _to ).length # 矢印の長さ==スケール aobj.scale[0] = objlen aobj.scale[1] = objlen aobj.scale[2] = objlen # @brief オブジェクトの指定したpolygonの法線を取得 # @param [in] obj オブジェクト # @param [in] polyIndex ポリゴン番号 # @return 法線(グローバル座標系) def get_polygon_normal(obj,polyIndex): # オブジェクトの行列を取得 local44 = obj.matrix_world ll_inv = local44.inverted() ll_inv_norm = ll_inv.transposed().to_3x3() vn = obj.data.polygons[polyIndex].normal return ll_inv_norm @ vn # @brief _fromから_toを指すEmptyArrowを作成 # @param [in] 矢印の根元の座標 # @param [in] 矢印の先端の座標 # @return 作成したオブジェクト def make_empty_arrow(_from,_to): # 新しいEmpty ObjectのArrowを作成 bpy.ops.object.empty_add(type="SINGLE_ARROW",location=_from) aobj = bpy.context.object fix_arrow_fromto(aobj,_from,_to) return aobj ##################################################### ##################################################### # 矢印一個のオブジェクト。 class Arrow: _from = mathutils.Vector((0,0,0)) _to = mathutils.Vector((0,0,0)) _object = None def __init__(self,name): exist = bpy.context.scene.objects.get(name) if exist is None: bpy.ops.object.empty_add(type="SINGLE_ARROW",location=(0,0,0)) self._object = bpy.context.object self._object.name = name else: self._object = exist #obj = bpy.context.active_object # オブジェクトのローカルのZ軸のベクトルを取得 mat = self._object.matrix_world localZ = mathutils.Vector((mat[0][2],mat[1][2],mat[2][2])) _to = localZ + self._object.location def get_direction(self,normalize): v = _to - _from if normalize == True: v.normalize return v def get_arrow(self,normalize): pos = get_direction(normalize) __to = _from+pos return _from , __to def set_arrow_from_to(self,_from,_to): fix_arrow_fromto(self._object,_from,_to) self._from = _from self._to = _to def get_vnorm(self): v = self._to - self._from v.normalize() return v def set_length(self,length): self._object.scale[0]=length self._object.scale[1]=length self._object.scale[2]=length ##################################################### ##################################################### class TheLight: light = None arrowToTarget = None def __init__(self,_pos): self.light = bpy.context.scene.objects.get('TheLight') if self.light is None: # 新しいEmpty ObjectのArrowを作成 bpy.ops.mesh.primitive_ico_sphere_add(location=_pos,radius =0.3) self.light = bpy.context.object self.light.name = 'TheLight' def update(self,target): self.arrowToTarget = Arrow('LightToTarget') self.arrowToTarget.set_arrow_from_to( self.light.location, target.target.location ) self.arrowToTarget.set_length(2) def get_location(self): return self.light.location ############################################### ############################################### # 各面ごとに、面法線と光源の方向を示すための矢印を定義する class Plane: normal = None material_name = None plane_index = 0 toLightArrow = None normalArrow_name = "" Ld = 0.0 def __init__(self,obj,pindex): self.normalArrow_name = 'PlaneNormal-' + str(pindex) self.normal = Arrow(self.normalArrow_name) mat_name = "mat" + str(pindex) self.material_name = mat_name mat = bpy.data.materials.get(mat_name) # マテリアルがなければ作成する if mat is None: mat = bpy.data.materials.new(mat_name) # マテリアルをオブジェクトに設定する obj.data.materials.append(mat) mat.use_nodes = True self.material_name = mat_name self.plane_index = pindex obj.data.polygons[pindex].material_index = pindex def update(self,origin,dir,light): self.normal.set_arrow_from_to( origin, origin+dir) # direction ldir = light.get_location()-origin ldir.normalize() # 面に光が当たったときの明るさを計算 # 面法線とライト方向へのベクトルの内積 Kd = 1.0 I = 1.0 self.Ld = Kd*I*numpy.dot( self.normal.get_vnorm(), -light.arrowToTarget.get_vnorm() ) if self.Ld < 0: self.Ld = 0 else: # arrow-to-line lightarrowname = self.normalArrow_name +'-tolight' self.toLightArrow = Arrow(lightarrowname) tolight = origin-light.arrowToTarget.get_vnorm() self.toLightArrow.set_arrow_from_to(origin,tolight) self.toLightArrow.set_length(0.3) self.normal.set_length(self.Ld*0.5) def set_material(self,polyindex): material =bpy.data.materials.get(self.material_name) material_output = material.node_tree.nodes.get('Material Output') #emission = new_mat.node_tree.nodes.get('ShaderNodeEmission') emission = material.node_tree.nodes.get('Emission') if emission is None: print( 'Emission:',emission ) emission = material.node_tree.nodes.new('ShaderNodeEmission') material.node_tree.links.new(material_output.inputs[0], emission.outputs[0]) ntree = material.node_tree#.new('ShaderNodeEmission') print( self.Ld ) material.node_tree.nodes["Emission"].inputs["Color"].default_value[0] = 0.5 * self.Ld material.node_tree.nodes["Emission"].inputs["Color"].default_value[1] = 0.9 * self.Ld material.node_tree.nodes["Emission"].inputs["Color"].default_value[2] = 0.8 * self.Ld ############################################### ############################################### class TheTarget: target = None # 光が当たるオブジェクト planes = [] # 各面の設定を面ごとに入れる def create_planes(self): mesh = self.target.data k = len(mesh.polygons) # 全てのポリゴンに対するループ for i in range(0,k): self.planes.append( Plane(self.target,i) ) def update(self,light): mesh = self.target.data vcoords = mesh.vertices # オブジェクトの行列を取得 local44 = self.target.matrix_world k = len(self.planes) for i in range(0,k): n = get_polygon_normal(self.target,i) ## origin of polygon grav = mathutils.Vector((0,0,0)) for v in mesh.polygons[i].vertices: # v はpolyの各頂点のID grav += vcoords[ v ].co # 頂点座標の合計を頂点数で割る grav /= len(mesh.polygons[i].vertices) origin = local44 @ grav self.planes[i].update( origin, n, light) self.planes[i].set_material(self.target) def __init__(self,_pos): self.target = bpy.context.scene.objects.get('TargetMesh') if self.target is None: #bpy.ops.mesh.primitive_plane_add(location=_pos) bpy.ops.mesh.primitive_ico_sphere_add( location=_pos, radius =1.0 ) self.target = bpy.context.object self.target.name = 'TargetMesh' self.create_planes() ############################################### ############################################### ############################################### def my_function(): target = TheTarget( (-1,2,1) ) thelight = TheLight( (1,5,5) ) thelight.update(target) target.update(thelight) ############################################### ############################################### ############################################### my_function()
度々使う自前の画像読み込み関数。メモリ確保を関数側にやらせると呼び出し側の処理が煩雑になるので、いっそのことメモリ確保関数を渡してしまうことにした。
#pragma once #include <stdio.h> #include <string> #include <functional> #pragma warning(disable:4996) //! @brief PPM(RGB各1byte,カラー,テキスト)を読み込む //! @param [in] fname //! @param [out] width 画像幅 //! @param [out] height 画像高さ //! @param [out] vmax 最大値 //! @param [in] memalloc メモリ確保用の関数 //! @retval true 読み込み成功 //! @retval false 読み込み失敗 bool ppmP3_read( const char* const fname, int *width, int *height, int *vmax, std::function<unsigned char*(const size_t pixelcount)> memalloc ) { *width = -1; *height = -1; *vmax = -1; FILE* fp; fp = fopen(fname, "rb"); char tmp[2048]; char c; while (c = fgetc(fp)) { if (isspace(c)) continue; if (c == 'P') { //フォーマットを特定する c = fgetc(fp) - '0'; if (c != 3) { fclose(fp); return false; } continue; } if (c == '#') { //コメントを読み飛ばす while (c != '\r' && c != '\n') c = fgetc(fp); continue; } //幅取得 if (*width < 0) { int s = 0; while (1) { if (isdigit(c)) { tmp[s++] = c; c = fgetc(fp); } else { tmp[s] = '\0'; *width = atoi(tmp); break; } } continue; } //高さ取得 if (*height < 0) { int s = 0; while (1) { if (isdigit(c)) { tmp[s++] = c; c = fgetc(fp); } else { tmp[s] = '\0'; *height = atoi(tmp); break; } } continue; } //最大値(白)取得 if (*vmax < 0) { int s = 0; while (1) { if (isdigit(c)) { tmp[s++] = c; c = fgetc(fp); } else { tmp[s] = '\0'; *vmax = atoi(tmp); break; } } break; } else { break; } } if (*width < 0 || *height < 0 || *vmax < 0) { return false; }
//////////////////////////////////////// //メモリ確保 size_t count = *width* *height; size_t memsize = count*3; unsigned char* pimg = memalloc(count); ////////////////////////////////////////
int read = 0; char text[100]; int index = 0; //メモリサイズ分まで読み込む while (read < memsize) { int c = fgetc(fp);//一文字取得 if (c == EOF)//入力できなかったらループから出る break; //数字ならストックする if (isdigit(c)) { text[index] = (char)c; index++; } else { //数字以外が入力されたら、 //ストックした数字を数値化 //ただし数字が何もストックされていないなら //なにもしない if (index) { text[index] = '\0'; pimg[read] = atoi(text); read++; index = 0; } } } return read == memsize; }
#include <vector> // https://www.study.suzulang.com/2dcg-functions/nbyte-data-type #include "NByteData.hpp" // 以下をベースに変更: // https://www.study.suzulang.com/cppppm-readerwriter/ppm-p3-reader-cpp #include "ppmP3_read.hpp" ///////////////////////////////////////////////////// void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax );
int main() { using PixelT = NByteData<3>; std::vector<PixelT> mem; // 画像読み込み int width; int height; int vmax; ppmP3_read( "C:\\test\\p.ppm", &width, &height, &vmax, [&mem](const size_t pixelcount) { mem.resize(pixelcount); return (unsigned char*)&mem[0]; } ); // 色を反転 for(auto& p : mem){ p.data()[0] = 255 - p.data()[0]; p.data()[1] = 255 - p.data()[1]; p.data()[2] = 255 - p.data()[2]; } ///////////////////////////////// ///////////////////////////////// ppmP3_write( "C:\\test\\q.ppm", width, height, (unsigned char*)&mem[0], 255); }
//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @param [in] vmax 全てのRGBの中の最大値。普通の画像なら255 //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void ppmP3_write( const char* const fname, const int width, const int height, const unsigned char* const p, const int vmax ) { FILE* fp = fopen(fname, "wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, vmax); 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 %d %d ", p[k * 3 + 0], p[k * 3 + 1], p[k * 3 + 2] ); k++; } fprintf(fp, "\n"); } fclose(fp); }
C++の例が少ないので載せておく。
#include <Open3D/IO/ClassIO/TriangleMeshIO.h> #include<Open3D/Geometry/TriangleMesh.h> // 表示に必要 #include<Open3D/Visualization/Visualizer/Visualizer.h> // 表示に必要 #pragma comment(lib,"glew.lib") #pragma comment(lib,"glfw3.lib") #pragma comment(lib,"opengl32.lib") int main() { auto mesh = std::make_shared<open3d::geometry::TriangleMesh>(); open3d::io::ReadTriangleMeshFromPLY("bunny.ply",*mesh,true); //これがないとライトで照らせない mesh->ComputeTriangleNormals(true); open3d::visualization::Visualizer vis; vis.CreateVisualizerWindow("O3D"); //CreateVisualizerWindowを呼び出してからオブジェクトを登録 vis.AddGeometry(mesh); Eigen::Vector3d center = mesh->GetCenter(); // Change view open3d::visualization::ViewControl& view_control = vis.GetViewControl(); auto view_params = open3d::visualization::ViewParameters(); view_control.ConvertToViewParameters(view_params); view_params.front_ = Eigen::Vector3d(0, -1, 0); view_params.lookat_ = center; view_params.up_ = Eigen::Vector3d(0, 0, 1); //view_params.zoom_ = 1.0; view_control.ConvertFromViewParameters(view_params); // PollEventsはウィンドウが閉じられるときにfalseを返す while (vis.PollEvents() == true) { vis.UpdateGeometry(); vis.UpdateRender(); } vis.DestroyVisualizerWindow(); }
試験的なプログラムを書くだけでいちいちモデルを読み込むライブラリを使うのが面倒なので、Blender PythonでC++OpenGLのコードをprintする。まだやってなかったっけという感じなんだがやっていたら申し訳ない。
import bpy import sys def as_cpp_array(outputto): obj = bpy.context.active_object mesh = obj.data pvecname = "points" # 頂点データの配列の変数名 vnvecname = "vnorms" # 頂点法線の配列の変数名 fvecname = "faces" # 面データの配列の変数名 fnvecname = "fnorms" # 面法線の配列の変数名 print("/////////////////////////////////////") if not pvecname is None: print("//std::vector<std::array<float, 3> > ",pvecname,";",file=outputto) if not vnvecname is None: print("//std::vector<std::array<float, 3> > ",vnvecname,";",file=outputto) if not fvecname is None: print("//std::vector<std::array<int, 3> > ",fvecname,";",file=outputto) if not fnvecname is None: print("//std::vector<std::array<float, 3> > ",fnvecname,";",file=outputto) if not pvecname is None: for v in mesh.vertices: print( pvecname + ".push_back({", v.co.x,",", v.co.y,",", v.co.z, "});", file=outputto ) if not vnvecname is None: for v in mesh.vertices: print( vnvecname + ".push_back({", v.normal.x,",", v.normal.y,",", v.normal.z, "});", file=outputto ) if not fvecname is None: for f in mesh.polygons: print( fvecname + ".push_back({", f.vertices[0],",", f.vertices[1],",", f.vertices[2], "});", file=outputto ) if not fnvecname is None: for f in mesh.polygons: print( fnvecname + ".push_back({", f.normal.x,",", f.normal.y,",", f.normal.z, "});", file=outputto ) as_cpp_array(sys.stdout)
これをactive_objectに対して走らせると、例えば以下のような結果が得られる。
/////////////////////////////////////
//std::vector<std::array<float, 3> > points ;
//std::vector<std::array<float, 3> > vnorms ;
//std::vector<std::array<int, 3> > faces ;
//std::vector<std::array<float, 3> > fnorms ;
points.push_back({ 0.0 , 0.0 , -0.57135009765625 });
points.push_back({ 0.4134289026260376 , -0.3003701865673065 , -0.25551632046699524 });
points.push_back({ -0.15791259706020355 , -0.4860132336616516 , -0.25551632046699524 });
points.push_back({ -0.5110297799110413 , 0.0 , -0.25551632046699524 });
points.push_back({ -0.15791259706020355 , 0.4860132336616516 , -0.25551632046699524 });
points.push_back({ 0.4134289026260376 , 0.3003701865673065 , -0.25551632046699524 });
points.push_back({ 0.15791259706020355 , -0.4860132336616516 , 0.25551632046699524 });
points.push_back({ -0.4134289026260376 , -0.3003701865673065 , 0.25551632046699524 });
points.push_back({ -0.4134289026260376 , 0.3003701865673065 , 0.25551632046699524 });
points.push_back({ 0.15791259706020355 , 0.4860132336616516 , 0.25551632046699524 });
points.push_back({ 0.5110297799110413 , 0.0 , 0.25551632046699524 });
points.push_back({ 0.0 , 0.0 , 0.57135009765625 });
vnorms.push_back({ 0.0 , 0.0 , -1.0 });
vnorms.push_back({ 0.7235938310623169 , -0.5257118344306946 , -0.44718772172927856 });
vnorms.push_back({ -0.2763756215572357 , -0.8506424427032471 , -0.44718772172927856 });
vnorms.push_back({ -0.8944059610366821 , 0.0 , -0.44718772172927856 });
vnorms.push_back({ -0.2763756215572357 , 0.8506424427032471 , -0.44718772172927856 });
vnorms.push_back({ 0.7235938310623169 , 0.5257118344306946 , -0.44718772172927856 });
vnorms.push_back({ 0.2763756215572357 , -0.8506424427032471 , 0.44718772172927856 });
vnorms.push_back({ -0.7235938310623169 , -0.5257118344306946 , 0.44718772172927856 });
vnorms.push_back({ -0.7235938310623169 , 0.5257118344306946 , 0.44718772172927856 });
vnorms.push_back({ 0.2763756215572357 , 0.8506424427032471 , 0.44718772172927856 });
vnorms.push_back({ 0.8944059610366821 , 0.0 , 0.44718772172927856 });
vnorms.push_back({ 0.0 , 0.0 , 1.0 });
faces.push_back({ 0 , 1 , 2 });
faces.push_back({ 1 , 0 , 5 });
faces.push_back({ 0 , 2 , 3 });
faces.push_back({ 0 , 3 , 4 });
faces.push_back({ 0 , 4 , 5 });
faces.push_back({ 1 , 5 , 10 });
faces.push_back({ 2 , 1 , 6 });
faces.push_back({ 3 , 2 , 7 });
faces.push_back({ 4 , 3 , 8 });
faces.push_back({ 5 , 4 , 9 });
faces.push_back({ 1 , 10 , 6 });
faces.push_back({ 2 , 6 , 7 });
faces.push_back({ 3 , 7 , 8 });
faces.push_back({ 4 , 8 , 9 });
faces.push_back({ 5 , 9 , 10 });
faces.push_back({ 6 , 10 , 11 });
faces.push_back({ 7 , 6 , 11 });
faces.push_back({ 8 , 7 , 11 });
faces.push_back({ 9 , 8 , 11 });
faces.push_back({ 10 , 9 , 11 });
fnorms.push_back({ 0.18759654462337494 , -0.5773536562919617 , -0.7946510910987854 });
fnorms.push_back({ 0.6070646643638611 , 0.0 , -0.7946524620056152 });
fnorms.push_back({ -0.4911220967769623 , -0.35682904720306396 , -0.7946522235870361 });
fnorms.push_back({ -0.4911220967769623 , 0.35682904720306396 , -0.7946522235870361 });
fnorms.push_back({ 0.18759654462337494 , 0.5773536562919617 , -0.7946510910987854 });
fnorms.push_back({ 0.9822461009025574 , 0.0 , -0.18759679794311523 });
fnorms.push_back({ 0.3035355508327484 , -0.9341715574264526 , -0.1875891536474228 });
fnorms.push_back({ -0.7946491241455078 , -0.5773593187332153 , -0.1875869631767273 });
fnorms.push_back({ -0.7946491241455078 , 0.5773593187332153 , -0.1875869780778885 });
fnorms.push_back({ 0.3035355508327484 , 0.9341715574264526 , -0.1875891238451004 });
fnorms.push_back({ 0.7946491241455078 , -0.5773593187332153 , 0.1875869780778885 });
fnorms.push_back({ -0.3035355508327484 , -0.9341715574264526 , 0.1875891238451004 });
fnorms.push_back({ -0.9822461009025574 , 0.0 , 0.18759679794311523 });
fnorms.push_back({ -0.3035355508327484 , 0.9341715574264526 , 0.1875891536474228 });
fnorms.push_back({ 0.7946491241455078 , 0.5773593187332153 , 0.1875869631767273 });
fnorms.push_back({ 0.4911220967769623 , -0.35682904720306396 , 0.7946522235870361 });
fnorms.push_back({ -0.18759654462337494 , -0.5773536562919617 , 0.7946510910987854 });
fnorms.push_back({ -0.6070646643638611 , 0.0 , 0.7946524620056152 });
fnorms.push_back({ -0.18759654462337494 , 0.5773536562919617 , 0.7946510910987854 });
fnorms.push_back({ 0.4911220967769623 , 0.35682904720306396 , 0.7946522235870361 });
これをC++で書いたOpenGLの表示コードに貼り付ける。
#include <iostream> #include <cstdlib> #include <Windows.h> #include <gl/GL.h> #include <gl/glfw/glfw3.h> #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"glfw3.lib") #include <gl/GLU.h> #pragma comment(lib,"glu32.lib") #include<vector> #include<array>
struct d3item { std::vector<std::array<float, 3> > points; std::vector<std::array<float, 3> > vnorms; std::vector<std::array<int, 3> > faces; std::vector<std::array<float, 3> > fnorms; public: void init() { points.push_back({ 0.0 , 0.0 , -0.57135009765625 }); points.push_back({ 0.4134289026260376 , -0.3003701865673065 , -0.25551632046699524 }); points.push_back({ -0.15791259706020355 , -0.4860132336616516 , -0.25551632046699524 }); points.push_back({ -0.5110297799110413 , 0.0 , -0.25551632046699524 }); points.push_back({ -0.15791259706020355 , 0.4860132336616516 , -0.25551632046699524 }); points.push_back({ 0.4134289026260376 , 0.3003701865673065 , -0.25551632046699524 }); points.push_back({ 0.15791259706020355 , -0.4860132336616516 , 0.25551632046699524 }); points.push_back({ -0.4134289026260376 , -0.3003701865673065 , 0.25551632046699524 }); points.push_back({ -0.4134289026260376 , 0.3003701865673065 , 0.25551632046699524 }); points.push_back({ 0.15791259706020355 , 0.4860132336616516 , 0.25551632046699524 }); points.push_back({ 0.5110297799110413 , 0.0 , 0.25551632046699524 }); points.push_back({ 0.0 , 0.0 , 0.57135009765625 }); vnorms.push_back({ 0.0 , 0.0 , -1.0 }); vnorms.push_back({ 0.7235938310623169 , -0.5257118344306946 , -0.44718772172927856 }); vnorms.push_back({ -0.2763756215572357 , -0.8506424427032471 , -0.44718772172927856 }); vnorms.push_back({ -0.8944059610366821 , 0.0 , -0.44718772172927856 }); vnorms.push_back({ -0.2763756215572357 , 0.8506424427032471 , -0.44718772172927856 }); vnorms.push_back({ 0.7235938310623169 , 0.5257118344306946 , -0.44718772172927856 }); vnorms.push_back({ 0.2763756215572357 , -0.8506424427032471 , 0.44718772172927856 }); vnorms.push_back({ -0.7235938310623169 , -0.5257118344306946 , 0.44718772172927856 }); vnorms.push_back({ -0.7235938310623169 , 0.5257118344306946 , 0.44718772172927856 }); vnorms.push_back({ 0.2763756215572357 , 0.8506424427032471 , 0.44718772172927856 }); vnorms.push_back({ 0.8944059610366821 , 0.0 , 0.44718772172927856 }); vnorms.push_back({ 0.0 , 0.0 , 1.0 }); faces.push_back({ 0 , 1 , 2 }); faces.push_back({ 1 , 0 , 5 }); faces.push_back({ 0 , 2 , 3 }); faces.push_back({ 0 , 3 , 4 }); faces.push_back({ 0 , 4 , 5 }); faces.push_back({ 1 , 5 , 10 }); faces.push_back({ 2 , 1 , 6 }); faces.push_back({ 3 , 2 , 7 }); faces.push_back({ 4 , 3 , 8 }); faces.push_back({ 5 , 4 , 9 }); faces.push_back({ 1 , 10 , 6 }); faces.push_back({ 2 , 6 , 7 }); faces.push_back({ 3 , 7 , 8 }); faces.push_back({ 4 , 8 , 9 }); faces.push_back({ 5 , 9 , 10 }); faces.push_back({ 6 , 10 , 11 }); faces.push_back({ 7 , 6 , 11 }); faces.push_back({ 8 , 7 , 11 }); faces.push_back({ 9 , 8 , 11 }); faces.push_back({ 10 , 9 , 11 }); fnorms.push_back({ 0.18759654462337494 , -0.5773536562919617 , -0.7946510910987854 }); fnorms.push_back({ 0.6070646643638611 , 0.0 , -0.7946524620056152 }); fnorms.push_back({ -0.4911220967769623 , -0.35682904720306396 , -0.7946522235870361 }); fnorms.push_back({ -0.4911220967769623 , 0.35682904720306396 , -0.7946522235870361 }); fnorms.push_back({ 0.18759654462337494 , 0.5773536562919617 , -0.7946510910987854 }); fnorms.push_back({ 0.9822461009025574 , 0.0 , -0.18759679794311523 }); fnorms.push_back({ 0.3035355508327484 , -0.9341715574264526 , -0.1875891536474228 }); fnorms.push_back({ -0.7946491241455078 , -0.5773593187332153 , -0.1875869631767273 }); fnorms.push_back({ -0.7946491241455078 , 0.5773593187332153 , -0.1875869780778885 }); fnorms.push_back({ 0.3035355508327484 , 0.9341715574264526 , -0.1875891238451004 }); fnorms.push_back({ 0.7946491241455078 , -0.5773593187332153 , 0.1875869780778885 }); fnorms.push_back({ -0.3035355508327484 , -0.9341715574264526 , 0.1875891238451004 }); fnorms.push_back({ -0.9822461009025574 , 0.0 , 0.18759679794311523 }); fnorms.push_back({ -0.3035355508327484 , 0.9341715574264526 , 0.1875891536474228 }); fnorms.push_back({ 0.7946491241455078 , 0.5773593187332153 , 0.1875869631767273 }); fnorms.push_back({ 0.4911220967769623 , -0.35682904720306396 , 0.7946522235870361 }); fnorms.push_back({ -0.18759654462337494 , -0.5773536562919617 , 0.7946510910987854 }); fnorms.push_back({ -0.6070646643638611 , 0.0 , 0.7946524620056152 }); fnorms.push_back({ -0.18759654462337494 , 0.5773536562919617 , 0.7946510910987854 }); fnorms.push_back({ 0.4911220967769623 , 0.35682904720306396 , 0.7946522235870361 }); } void draw() { auto color = std::array<float, 4>{1.0, 0, 0, 1}; glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color.data()); glColor3d(1, 1, 1); glBegin(GL_TRIANGLES); for (size_t f = 0; f < faces.size(); f++) { //面法線 glNormal3fv(fnorms[f].data()); size_t p0 = faces[f][0]; size_t p1 = faces[f][1]; size_t p2 = faces[f][2]; //頂点法線 //glNormal3fv(vnorms[p0].data()); glVertex3fv(points[p0].data()); //頂点法線 //glNormal3fv(vnorms[p1].data()); glVertex3fv(points[p1].data()); //頂点法線 //glNormal3fv(vnorms[p2].data()); glVertex3fv(points[p2].data()); } glEnd(); } };
int main() { //////////////////////////////////////////////////////////////////////////////// // GLFW の初期化 if (glfwInit() == GL_FALSE) { // 初期化に失敗したら終了 return 1; } //////////////////////////////////////////////////////////////////////////////// // ウィンドウを作成 GLFWwindow* window = glfwCreateWindow( 400, //width 400, //height "window title",//title NULL, //monitor NULL //share ); //////////////////////////////////////////////////////////////////////////////// // ウィンドウを作成できなければ終了 if (window == nullptr) { glfwTerminate(); return 1; } d3item item; item.init(); glfwMakeContextCurrent(window); while (glfwWindowShouldClose(window) == GL_FALSE) { int width, height; glfwGetFramebufferSize(window, &width, &height); const GLfloat lightPos[] = { -3 , 0 , 0 , 1 }; const GLfloat lightCol[] = { 1 , 1 , 1 , 1 }; glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT0, GL_DIFFUSE, lightCol); glLightfv(GL_LIGHT0, GL_POSITION, lightPos); glEnable(GL_DEPTH_TEST); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); ///////////////////// // 描画 glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluPerspective(45, width / (double)height, 0.01, 10); { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); static int angle = 0; glTranslated(0, 0, -2); glRotated(angle++, 1, 1, 1); item.draw(); glPopMatrix(); } glMatrixMode(GL_PROJECTION); glPopMatrix(); // ///////////////////// //glFlush(); glfwSwapBuffers(window); // イベント取得 glfwWaitEvents(); } glfwTerminate(); }