カスタムシェーダを使おうとして動かないときに、生成されたシェーダを確認したいことがある。
MapperのObserverを追加してコンパイルされるシェーダのソースコードを表示できる。
なおエラーがある場合はどのみちエラー出力で表示されるのでここまでしなくていい。
#include <iostream> //VTK_MODULE_INITに必要 #include <vtkAutoInit.h> #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> //円筒とその表示に必要 #include <vtkCylinderSource.h> #include <vtkPolyDataMapper.h> #include <vtkActor.h> #include <vtkPointData.h> // シェーダ関係のinclude #include <vtkShaderProgram.h> #include <vtkShaderProperty.h> #include <vtkOpenGLPolyDataMapper.h> //////////////////////////////// #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"psapi.lib") #pragma comment(lib,"dbghelp.lib") #pragma comment(lib,"ws2_32.lib") //必須 VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle); // データに色情報を追加 void SetColor(vtkSmartPointer<vtkPolyData> data, const char* scalarname, std::array<unsigned char, 3> rgb) { size_t PointNum = data->GetNumberOfPoints(); vtkSmartPointer<vtkUnsignedCharArray> colorsNew = vtkSmartPointer<vtkUnsignedCharArray>::New(); colorsNew->SetNumberOfComponents(3); colorsNew->SetName(scalarname); for (int i = 0; i < PointNum; i++) { // ランダムな色を作成 double r = vtkMath::Random(0.0, 255.0); double g = vtkMath::Random(0.0, 255.0); double b = vtkMath::Random(0.0, 255.0); colorsNew->InsertNextTuple3(r,g,b); } data->GetPointData()->AddArray(colorsNew); } void setShader(vtkSmartPointer<vtkActor> actor) { auto shaderProperty = actor->GetShaderProperty(); shaderProperty->AddVertexShaderReplacement( "//VTK::Color::Impl", true, R"( //VTK::Color::Impl // 頂点カラーから赤を除去 vertexColorVSOutput = vec4( 0.0, vertexColorVSOutput.g, vertexColorVSOutput.b, 1.0 ); )", false); }
class MyShaderCallback : public vtkCommand { public: static MyShaderCallback* New() { return new MyShaderCallback; } void Execute(vtkObject* caller, unsigned long eventId, void* callData) override { if (eventId == vtkCommand::UpdateShaderEvent && callData) { auto* program = static_cast<vtkShaderProgram*>(callData); if (!program) { return; } // 頂点シェーダとフラグメントシェーダのソースコードを取得 const std::string& vertSrc = program->GetVertexShader()->GetSource(); const std::string& fragSrc = program->GetFragmentShader()->GetSource(); std::cout << "=== Vertex Shader (頂点シェーダ)===" << std::endl; std::cout << "=== " << std::endl; std::cout << "=== " << std::endl << std::endl; std::cout << vertSrc << std::endl; std::cout << "///////////////////////////////////////////////" << std::endl; std::cout << "///////////////////////////////////////////////" << std::endl; std::cout << "///////////////////////////////////////////////" << std::endl; std::cout << "///////////////////////////////////////////////" << std::endl; std::cout << "=== Fragment Shader (フラグメントシェーダ)===" << std::endl; std::cout << "=== " << std::endl; std::cout << "=== " << std::endl << std::endl; std::cout << fragSrc << std::endl; } } };
int main(int /*argc*/, char** /*argv*/) { // シリンダー作成 vtkSmartPointer<vtkCylinderSource> cylinder = vtkSmartPointer<vtkCylinderSource>::New(); cylinder->SetResolution(50); cylinder->SetHeight(3.0); cylinder->SetRadius(1.0); cylinder->Update(); // actorを作成 vtkSmartPointer<vtkPolyData> points = cylinder->GetOutput(); SetColor(points, "ColorsRed", { 255,0,0 }); SetColor(points, "ColorsBlue", { 0,0,255 }); // actorを作成 vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputData(points); vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); ////////////////////////////////////// // 使用する色をAddArrayで指定したスカラー値を使用するように設定 mapper->SetScalarVisibility(1); // スカラー値を使用して色をつける設定 mapper->SetScalarModeToUsePointFieldData(); // スカラー値はポイントデータに設定されているものを使用 mapper->SetColorModeToDefault(); // カラーモードをデフォルトに設定 // 色の切り替えも有効 mapper->SelectColorArray("ColorsBlue"); mapper->Modified();// マッパーを更新 ////////////////////////////////////// // カスタムシェーダを設定 setShader(actor); mapper->Modified();// マッパーを更新
vtkOpenGLPolyDataMapper* glMapper = vtkOpenGLPolyDataMapper::SafeDownCast(mapper); if (!glMapper) { std::cout << "Shader is not OpenGL based." << std::endl; } else { // シェーダの更新イベントを監視 vtkNew<MyShaderCallback> callback; glMapper->AddObserver(vtkCommand::UpdateShaderEvent, callback); }
////////////////////////////////////// auto renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->AddActor(actor); renderer->ResetCamera(); ////////////////////////////////////// auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); ////////////////////////////////////// auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New(); renderWindow->AddRenderer(renderer); renderWindow->SetInteractor(interactor); renderWindow->Render(); interactor->Start(); //イベントループへ入る return 0; }
#include <iostream> //VTK_MODULE_INITに必要 #include <vtkAutoInit.h> #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> //円筒とその表示に必要 #include <vtkCylinderSource.h> #include <vtkPolyDataMapper.h> // shader #include <vtkShaderProperty.h> #include <vtkOpenGLPolyDataMapper.h> #include <vtkUniforms.h> #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"psapi.lib") #pragma comment(lib,"dbghelp.lib") #pragma comment(lib,"ws2_32.lib") //必須 VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle);
void setShader(vtkSmartPointer<vtkActor> actor) { // カスタムシェーダを設定 vtkShaderProperty* shaderProperty = actor->GetShaderProperty(); // フラグメントシェーダの宣言部分 shaderProperty->AddFragmentShaderReplacement( "//VTK::Light::Dec", // 挿入位置 true, "//VTK::Light::Dec\n" "uniform vec3 customColor;\n", // ユニフォーム変数を定義 false ); // フラグメントシェーダの実装部分 shaderProperty->AddFragmentShaderReplacement( "//VTK::Light::Impl", // 挿入位置 true, "//VTK::Light::Impl\n" " fragOutput0 = vec4(customColor, 1.0);\n", // 出力変数(オブジェクト色) false ); // ユニフォーム変数を設定 double customColor[3] = { 1.0, 0.5, 0.0 }; shaderProperty->GetVertexCustomUniforms()->SetUniform3f("customColor", customColor); }
int main(int /*argc*/, char** /*argv*/) { ////////////////////////////////////// // Create a vtkCylinderSource vtkSmartPointer<vtkCylinderSource> cylinderSource = vtkSmartPointer<vtkCylinderSource>::New(); cylinderSource->SetCenter(0.0, 0.0, 0.0); cylinderSource->SetRadius(5.0); cylinderSource->SetHeight(7.0); cylinderSource->SetResolution(100); ////////////////////////////////////// // Create a mapper and actor vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputConnection(cylinderSource->GetOutputPort()); vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); // カスタムシェーダを設定 setShader(actor); ////////////////////////////////////// auto renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->AddActor(actor); renderer->ResetCamera(); ////////////////////////////////////// auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); ////////////////////////////////////// auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New(); renderWindow->AddRenderer(renderer); renderWindow->SetInteractor(interactor); renderWindow->Render(); interactor->Start(); //イベントループへ入る return 0; }'
VTKでデータを管理しているときに、色用のスカラー値を複数作成している場合、SetArrayNameでスカラー値名を指定して、その色で保存できる。
VTKで一つのオブジェクトに頂点色情報を二つ以上与えて切り替えて表示
// #include <vtkPLYWriter.h> が必要 // filename : 出力ファイル名 // colorname : 使用する色情報のスカラー値名 void SavePolyData(vtkSmartPointer<vtkPolyData> data,const char* filename,const char* colorname) {
vtkSmartPointer<vtkPLYWriter> writer = vtkSmartPointer<vtkPLYWriter>::New(); writer->SetFileName(filename); writer->SetColorModeToDefault(); writer->SetArrayName(colorname); // 使用する色情報をスカラー値名で指定 writer->SetInputData(data); writer->SetFileTypeToBinary(); writer->Write(); }
頂点に何らかの重みを割り当て、サーモグラフィー的に表示したい。三角形一枚の色はデフォルトでは各頂点の線形補間になってしまうので、フラグメントシェーダで重みを色に変換する関数を通して着色する。なおこのコードは以前作ったRust+OpenGLで三角形を表示するコードのdraw.rsを書き換えている。
use std::ptr::null; use std::ffi::CString; use std::ffi::CStr;
pub fn draw_prepare() ->(gl::types::GLuint, gl::types::GLuint){ // 頂点の定義 let vertices: [f32;9]=[ 0.0, 0.5, 0.0, // 上頂点 -0.5, -0.5, 0.0, // 左下 0.5, -0.5, 0.0, // 右下 ]; // スカラー値 let scalar:[f32;3]=[ 1.0, 0.3, 0.0, ]; let mut vertexbuffer=0; unsafe{ gl::GenBuffers(1,&mut vertexbuffer); gl::BindBuffer(gl::ARRAY_BUFFER,vertexbuffer); gl::BufferData( gl::ARRAY_BUFFER, 3*3*std::mem::size_of::<gl::types::GLfloat>() as gl::types::GLsizeiptr, vertices.as_ptr() as *const _, gl::STATIC_DRAW ); } let mut scalarbuffer=0; unsafe{ gl::GenBuffers(1,&mut scalarbuffer); gl::BindBuffer(gl::ARRAY_BUFFER,scalarbuffer); gl::BufferData( gl::ARRAY_BUFFER, 3*std::mem::size_of::<gl::types::GLfloat>() as gl::types::GLsizeiptr, scalar.as_ptr() as *const _, gl::STATIC_DRAW ); } (vertexbuffer,scalarbuffer) }
pub fn prepare_vertex_shader()->gl::types::GLuint{ let mut VertexShaderID = 0; // 頂点シェーダプログラム let vertex_shader_source = "\ #version 460 core layout (location = 0) in vec3 aPos;
// layout (location = 1) in vec3 incolor; layout (location = 1) in float scalar; // out vec4 vertexColor; out float fragscalar; uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4(aPos, 1.0);
// vertexColor = vec4(incolor, 1.0); fragscalar = scalar; } "; let c_src = CString::new(vertex_shader_source).unwrap(); let mut Result:gl::types::GLint = 0; let mut InfoLogLength:i32 = 0; let mut info_log; unsafe{ VertexShaderID = gl::CreateShader(gl::VERTEX_SHADER); gl::ShaderSource( VertexShaderID, 1, &c_src.as_ptr(), std::ptr::null() ); gl::CompileShader(VertexShaderID); // シェーダのチェック gl::GetShaderiv(VertexShaderID,gl::COMPILE_STATUS,&mut Result); gl::GetShaderiv(VertexShaderID,gl::INFO_LOG_LENGTH,&mut InfoLogLength); if InfoLogLength > 0 { info_log = vec![0u8; InfoLogLength as usize]; if Result == gl::FALSE as i32 { gl::GetShaderInfoLog( VertexShaderID, InfoLogLength, std::ptr::null_mut(), info_log.as_mut_ptr() as *mut gl::types::GLchar ); if let Ok(msg) = CStr::from_ptr(info_log.as_ptr() as *const i8).to_str() { println!("Vertex Shader Error :\n {}\n", msg); } } } } VertexShaderID }
pub fn prepare_fragment_shader()->gl::types::GLuint{ let mut FragmentShaderID=0; let fragment_shader_source = "\ #version 460 core out vec4 FragColor; // in vec4 vertexColor; in float fragscalar; // スカラーをサーモグラフィーのRGBに変換 vec3 scalarToThermalColor(float scalar) { // 0.0〜1.0の範囲に制限 scalar = clamp(scalar, 0.0, 1.0); // サーモグラフィーカラーの計算 vec3 color; if (scalar < 0.25) { color = vec3(0.0, 4.0 * scalar, 1.0); // 青 → シアン } else if (scalar < 0.5) { color = vec3(0.0, 1.0, 1.0 - 4.0 * (scalar - 0.25)); // シアン → 緑 } else if (scalar < 0.75) { color = vec3(4.0 * (scalar - 0.5), 1.0, 0.0); // 緑 → 黄 } else { color = vec3(1.0, 1.0 - 4.0 * (scalar - 0.75), 0.0); // 黄 → 赤 } return color; } void main() { // FragColor = vertexColor; FragColor = vec4(scalarToThermalColor(fragscalar), 1.0); // 出力 } "; let c_str = CString::new(fragment_shader_source).unwrap(); let mut Result:gl::types::GLint=0; let mut InfoLogLength:i32=0; let mut info_log; unsafe { FragmentShaderID = gl::CreateShader(gl::FRAGMENT_SHADER); gl::ShaderSource( FragmentShaderID, 1, &c_str.as_ptr(), null() ); gl::CompileShader(FragmentShaderID); // フラグメントシェーダ gl::GetShaderiv(FragmentShaderID, gl::COMPILE_STATUS, &mut Result); gl::GetShaderiv(FragmentShaderID, gl::INFO_LOG_LENGTH, &mut InfoLogLength); if InfoLogLength > 0 { if Result == gl::FALSE as i32 { info_log = vec![0u8; InfoLogLength as usize]; gl::GetShaderInfoLog( FragmentShaderID, InfoLogLength, std::ptr::null_mut(), info_log.as_mut_ptr() as *mut gl::types::GLchar ); if let Ok(msg) = CStr::from_ptr(info_log.as_ptr() as *const i8).to_str() { println!("Fragment Shader Error :\n{}\n", msg); } } } } FragmentShaderID }
pub fn link_program(VertexShaderID:gl::types::GLuint,FragmentShaderID:gl::types::GLuint)->gl::types::GLuint{ let mut Result:gl::types::GLint = gl::FALSE as i32; let mut InfoLogLength:i32=0; let mut ProgramID:gl::types::GLuint=0; println!("Linking program"); unsafe{ ProgramID = gl::CreateProgram(); gl::AttachShader(ProgramID,VertexShaderID); gl::AttachShader(ProgramID,FragmentShaderID); gl::LinkProgram(ProgramID); gl::GetProgramiv(ProgramID,gl::LINK_STATUS,&mut Result); gl::GetProgramiv(ProgramID,gl::INFO_LOG_LENGTH,&mut InfoLogLength); if InfoLogLength > 0 { let mut ProgramErrorMessage = vec![0u8; InfoLogLength as usize]; gl::GetProgramInfoLog( ProgramID, InfoLogLength, std::ptr::null_mut(), ProgramErrorMessage.as_mut_ptr() as *mut gl::types::GLchar ); if let Ok(msg) = CStr::from_ptr(ProgramErrorMessage.as_ptr() as *const i8).to_str() { println!("Program Link Error:\n{}\n",msg); } } } ProgramID }
pub fn draw_triangle(programid:gl::types::GLuint,vertexbuffer: gl::types::GLuint,scalarbuffer:gl::types::GLuint){ unsafe { gl::UseProgram(programid); } let proj_mat:[f32;16]=[ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ]; let model_mat:[f32;16]=[ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ]; let proj; let model; unsafe { let proj_location_name = CString::new("projectionMatrix").unwrap(); let model_location_name = CString::new("modelViewMatrix").unwrap(); proj = gl::GetUniformLocation(programid,proj_location_name.as_ptr()); model = gl::GetUniformLocation(programid,model_location_name.as_ptr()); gl::UniformMatrix4fv(proj,1,gl::FALSE,proj_mat.as_ptr()); gl::UniformMatrix4fv(model,1,gl::FALSE,model_mat.as_ptr()); } /* // デバッグ println!("proj location: {}",proj); println!("model location: {}",model); */ unsafe{ gl::EnableVertexAttribArray(0); gl::BindBuffer(gl::ARRAY_BUFFER,vertexbuffer); gl::VertexAttribPointer( 0, 3, gl::FLOAT, gl::FALSE, 0, null() ); } unsafe{ gl::EnableVertexAttribArray(1); gl::BindBuffer(gl::ARRAY_BUFFER,scalarbuffer); gl::VertexAttribPointer( 1, 1, // 一次元配列 gl::FLOAT, gl::FALSE, 0, null() ); } unsafe { gl::DrawArrays(gl::TRIANGLES, 0,3); let err_check = gl::GetError(); if err_check != gl::NO_ERROR { println!("ERROR::: {}\n", err_check); } gl::DisableVertexAttribArray(0); gl::DisableVertexAttribArray(1); } unsafe { gl::UseProgram(0); } }
vtkPolyDataにAddArrayし、表示時にSetNameで指定したスカラー名でSelectColorArrayを呼び出す。かならずModifiedも呼び出す。
#include <iostream> //VTK_MODULE_INITに必要 #include <vtkAutoInit.h> #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkPolyDataMapper.h> #include <vtkActor.h> #include <vtkPointData.h> #include <vtkRenderWindowInteractor.h> #include <vtkCommand.h> #include <vtkInteractorStyleTrackballCamera.h> #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"psapi.lib") #pragma comment(lib,"dbghelp.lib") #pragma comment(lib,"ws2_32.lib") //必須 VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle);
// キーボード入力を処理 class MyCommand : public vtkCommand { bool IsRed = true; public: static MyCommand* New() { return new MyCommand; } void SetMapper(vtkSmartPointer<vtkPolyDataMapper> mapper) { this->Mapper = mapper; } void Execute(vtkObject* caller, unsigned long eventId, void*) override { if (eventId == vtkCommand::KeyPressEvent) { vtkRenderWindowInteractor* interactor = dynamic_cast<vtkRenderWindowInteractor*>(caller); std::string key = interactor->GetKeySym(); // 注意 デフォルト機能で E キーで終了してしまう
if(IsRed) { Mapper->SelectColorArray("ColorsRed");// 頂点色切替 Mapper->Modified(); } else { Mapper->SelectColorArray("ColorsBlue"); Mapper->Modified(); }
IsRed = !IsRed; interactor->GetRenderWindow()->Render(); } } private: vtkSmartPointer<vtkPolyDataMapper> Mapper; };
////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
// ランダムな点群を生成 vtkSmartPointer<vtkPolyData> CreateRandomCloud() { vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New(); vtkSmartPointer<vtkCellArray> vertices = vtkSmartPointer<vtkCellArray>::New(); vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New(); polyData->SetPoints(points); polyData->SetVerts(vertices); vtkSmartPointer<vtkCellArray> verts = vtkSmartPointer<vtkCellArray>::New(); vtkIdType pid[1]; for (int i = 0; i < 10000; i++) { double p[3]; p[0] = vtkMath::Random(-1.0, 1.0); p[1] = vtkMath::Random(-1.0, 1.0); p[2] = vtkMath::Random(-1.0, 1.0); pid[0] = points->InsertNextPoint(p); verts->InsertNextCell(1, pid); } polyData->SetVerts(verts); return polyData; }
// データに色情報を追加 // 呼び出すたびにdataに色情報が追加される void SetColor(vtkSmartPointer<vtkPolyData> data,const char* scalarname,std::array<unsigned char,3> rgb) { size_t PointNum = data->GetNumberOfPoints(); vtkSmartPointer<vtkUnsignedCharArray> colorsNew = vtkSmartPointer<vtkUnsignedCharArray>::New(); colorsNew->SetNumberOfComponents(3); colorsNew->SetName(scalarname); for (int i = 0; i < PointNum; i++) { colorsNew->InsertNextTuple3(rgb[0], rgb[1], rgb[2]); } data->GetPointData()->AddArray(colorsNew); }
int main(int /*argc*/, char** /*argv*/) { vtkSmartPointer<vtkPolyData> cloud = CreateRandomCloud(); SetColor(cloud, "ColorsRed",{255,0,0}); SetColor(cloud, "ColorsBlue", { 0,0,255 }); // cloudのactorを作成 vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputData(cloud); vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); ////////////////////////////////////// ////////////////////////////////////// ////////////////////////////////////// // 使用する色をAddArrayで指定したスカラー値を使用するように設定 mapper->SetScalarVisibility(1); // スカラー値を使用して色をつける設定 mapper->SetScalarModeToUsePointFieldData(); // スカラー値はポイントデータに設定されているものを使用 mapper->SetColorModeToDefault(); // カラーモードをデフォルトに設定 // 実際にどのスカラー値を使用するかを設定 // 切り替え時にはかならずModified()を呼ぶ mapper->SelectColorArray("ColorsRed"); mapper->Modified();// マッパーを更新 ////////////////////////////////////// ////////////////////////////////////// ////////////////////////////////////// ////////////////////////////////////// auto renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->AddActor(actor); renderer->ResetCamera(); ////////////////////////////////////// auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); ////////////////////////////////////// // キー入力で色を切り替えるコマンドを設定 vtkSmartPointer<MyCommand> colorToggleCommand = vtkSmartPointer<MyCommand>::New(); colorToggleCommand->SetMapper(mapper); interactor->AddObserver(vtkCommand::KeyPressEvent, colorToggleCommand); ////////////////////////////////////// auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New(); renderWindow->AddRenderer(renderer); renderWindow->SetInteractor(interactor); renderWindow->Render(); interactor->Start(); //イベントループへ入る return 0; }
SVGはXMLで書かれる画像フォーマットなので、pugixmlで出力できる。
以前pugixmlを導入したので、それを使う。
#include "pugixml.hpp" int main() { pugi::xml_document doc; pugi::xml_node mysvg = doc.append_child("svg"); mysvg.append_attribute("xmlns") = "http://www.w3.org/2000/svg"; mysvg.append_attribute("width") = "200"; mysvg.append_attribute("height") = "200"; // 背景を塗りつぶすための矩形 pugi::xml_node bgrect = mysvg.append_child("rect"); bgrect.append_attribute("x") = "0"; bgrect.append_attribute("y") = "0"; bgrect.append_attribute("width") = "200"; bgrect.append_attribute("height") = "200"; bgrect.append_attribute("fill") = "rgb(200,200,200)"; // 円 pugi::xml_node circle = mysvg.append_child("circle"); circle.append_attribute("cx") = "50"; circle.append_attribute("cy") = "50"; circle.append_attribute("r") = "40"; circle.append_attribute("fill") = "red"; // 線 pugi::xml_node line = mysvg.append_child("line"); line.append_attribute("x1") = "10"; line.append_attribute("y1") = "10"; line.append_attribute("x2") = "150"; line.append_attribute("y2") = "80"; line.append_attribute("stroke") = "black"; line.append_attribute("stroke-width") = "4"; // 矩形 pugi::xml_node rect = mysvg.append_child("rect"); rect.append_attribute("x") = "50"; rect.append_attribute("y") = "50"; rect.append_attribute("width") = "80"; rect.append_attribute("height") = "80"; rect.append_attribute("fill") = "blue"; rect.append_attribute("stroke") = "green"; rect.append_attribute("stroke-width") = "6"; // ファイルに保存 doc.save_file("test.svg"); }
imagemagickで画像に変換できる
点群の最小球を求めるのにWelzlのアルゴリズムというのがあるのを知った。
とりあえずコピペで動くことを確認したのでここに置く。何をやっているのかわからないのでこれから調べていく。
あと、そのままだと再帰が激しすぎてたった5000頂点でオーバーフローした。スタックサイズを変えればいけるのだろうがそういう問題ではない。
#include <iostream> #include <unordered_map> #include <set> #pragma warning(disable:4996) #include <vtkUnsignedCharArray.h> #include <vtkPointData.h> #include <vtkActor.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkProperty.h> #include <vtkSphere.h> #include <vtkVertexGlyphFilter.h> //球とその表示に必要 #include <vtkSphereSource.h> #include <vtkPolyDataMapper.h> //VTK_MODULE_INITに必要 #include <vtkAutoInit.h> //必須 VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle); #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "Psapi.lib") #pragma comment(lib, "Dbghelp.lib") #include <Eigen/Dense> #include <random> #pragma comment(lib,"opengl32.lib") // 点群型 struct MyCloud { std::vector<Eigen::Vector3d> points; std::vector<Eigen::Vector3d> colors; }; namespace welzl {
// 球を表す struct Sphere { Eigen::Vector3d center; double radius; Sphere() : center(Eigen::Vector3d::Zero()), radius(0.0) {} Sphere(const Eigen::Vector3d& c, double r) : center(c), radius(r) {} };
//------------------------------------- // R(最大4点)から決まる球を求める補助関数群 //-------------------------------------
// 1点からなる球(その点が中心、半径0) Sphere ball_from_one_point(const Eigen::Vector3d& a) { return Sphere(a, 0.0); }
// 2点からなる最小球(2点の中点が中心、半径は両点から中点までの距離) Sphere ball_from_two_points(const Eigen::Vector3d& a, const Eigen::Vector3d& b) { Eigen::Vector3d c = 0.5 * (a + b); double r = (a - c).norm(); return Sphere(c, r); }
// 3点から決まる球(3点が共通で外接する円=2次元平面での外接円を3D空間で表現) // 外接円の中心は3点からなる三角形の外心 Sphere ball_from_three_points(const Eigen::Vector3d& a, const Eigen::Vector3d& b, const Eigen::Vector3d& c) { // 2次元外心計算を3次元対応。3点は同一平面上にあるため、 // 外心は、(b - a), (c - a)に直交する2本の垂直二等分線の交点。 Eigen::Vector3d ab = b - a; Eigen::Vector3d ac = c - a; Eigen::Matrix3d M; M << ab.x(), ab.y(), ab.z(), ac.x(), ac.y(), ac.z(), ab.cross(ac).x(), ab.cross(ac).y(), ab.cross(ac).z(); // 上記はそのままでは重複があり、もう少し安定的な方法を取る必要がある。 // 簡易的には、外心は以下の方程式で求められる: // |x - a|^2 = |x - b|^2 = |x - c|^2 を解く。 // この方程式は線形代数で解ける。 // 以下では直接外心を求める一般的な手続きの1つを示す。 // 行列方程式で外心を求めるための設定 // (参考実装) 外心は幾何的には以下で求まる: // Let A, B, C be points. 外心Oは: // solve for O: // (O - A)・(B - A) = (|B-A|^2)/2 // (O - A)・(C - A) = (|C-A|^2)/2 // この2式からOが求まる。(O - A)を未知変数にした線形方程式となる Eigen::Matrix2d A_mat; A_mat << ab.dot(ab), ab.dot(ac), ab.dot(ac), ac.dot(ac); Eigen::Vector2d rhs; rhs << 0.5 * ab.dot(ab), 0.5 * ac.dot(ac); // 解く Eigen::Vector2d uv = A_mat.colPivHouseholderQr().solve(rhs); Eigen::Vector3d O = a + uv[0] * ab + uv[1] * ac; double r = (O - a).norm(); return Sphere(O, r); }
// 4点から決まる球(4点が共通球面上にある場合、その外接球を求める) // 4点外接球計算は3x3の線形方程式を解くことで可能。 Sphere ball_from_four_points(const Eigen::Vector3d& a, const Eigen::Vector3d& b, const Eigen::Vector3d& c, const Eigen::Vector3d& d) { // 方針: // (O - a)・(O - a) = (O - b)・(O - b) // (O - a)・(O - a) = (O - c)・(O - c) // (O - a)・(O - a) = (O - d)・(O - d) // という3つの方程式を立て、Oを求める。 Eigen::Vector3d A = a; Eigen::Vector3d B = b; Eigen::Vector3d C = c; Eigen::Vector3d D = d; // 方程式組み立て // (O - A)² = (O - B)² などは展開すると // O² - 2O·A + A² = O² - 2O·B + B² → 2O·(B - A) = B² - A² // 同様にC, Dについても同様の式を立てられる // よって: // 2(B - A)·O = B² - A² // 2(C - A)·O = C² - A² // 2(D - A)·O = D² - A² // ベクトルX = Oについて Eigen::Matrix3d M; M << 2 * (b.x() - a.x()), 2 * (b.y() - a.y()), 2 * (b.z() - a.z()), 2 * (c.x() - a.x()), 2 * (c.y() - a.y()), 2 * (c.z() - a.z()), 2 * (d.x() - a.x()), 2 * (d.y() - a.y()), 2 * (d.z() - a.z()); auto sq = [](const double& val) { return val * val; }; Eigen::Vector3d RHS; RHS << (b.squaredNorm() - a.squaredNorm()), (c.squaredNorm() - a.squaredNorm()), (d.squaredNorm() - a.squaredNorm()); Eigen::Vector3d O = M.colPivHouseholderQr().solve(RHS); double r = (O - a).norm(); return Sphere(O, r); }
// Rには最大4頂点が入っている。 // それに応じて最小包含球を求める Sphere ball_from_R(const std::vector<Eigen::Vector3d>& R) { switch (R.size()) { case 0: return Sphere(Eigen::Vector3d::Zero(), 0.0); case 1: return ball_from_one_point(R[0]); case 2: return ball_from_two_points(R[0], R[1]); case 3: return ball_from_three_points(R[0], R[1], R[2]); case 4: return ball_from_four_points(R[0], R[1], R[2], R[3]); default: // それ以上はありえない return Sphere(); } }
//------------------------------------- // 点 p が球 S の中に入っているか判定 //-------------------------------------
inline bool is_in_sphere(const Eigen::Vector3d& p, const Sphere& S) { return (p - S.center).squaredNorm() <= S.radius * S.radius + 1e-14; // 浮動小数対策 }
//------------------------------------- // Welzlの再帰関数 //------------------------------------- // 引数: // points: 対象の点集合(インデックス0〜endで使用) // R: 球を定義するための点集合 // end: 再帰処理内での有効点数境界
Sphere welzl(const std::vector<Eigen::Vector3d>& points, std::vector<Eigen::Vector3d> R, int end) { // 再帰の末端、これ以上頂点がないか、Rが4点になったら球を求める if (end < 0 || R.size() == 4) { return ball_from_R(R); } // 頂点を一つ取り出して、それ以外の点で再帰処理 Eigen::Vector3d p = points[end]; Sphere D = welzl(points, R, end - 1); // 今取り出した点が、それ以外の点で求めた球に入っている場合 // 球を再計算する必要が無いので、そのまま返す if (is_in_sphere(p, D)) { return D; } // pがD内に収まらない場合 R.push_back(p); return welzl(points, R, end - 1); }
//------------------------------------- // 公開関数: 最小包含球を計算 //------------------------------------- Sphere minimum_enclosing_sphere(const std::vector<Eigen::Vector3d>& points) { // 頂点をシャッフルすると期待線形時間になるが // 頂点順を変えたくないため今回は使用しない // なお引数pointsがconst参照のため、ここを有効にしてもエラーになる。 // 有効化するときは引数のconstを外すか、コピーを作成すること。 #if 0 // ポイントシャッフル推奨(期待線形時間のため) std::random_device rd; std::mt19937 g(rd()); std::shuffle(points.begin(), points.end(), g); #endif std::vector<Eigen::Vector3d> dumyR; return welzl(points, dumyR, (int)points.size() - 1); }
}
std::pair<Eigen::Vector3f,float> CalcBoundingSphere(MyCloud& cloud) { welzl::Sphere S = welzl::minimum_enclosing_sphere(cloud.points); return { S.center.cast<float>(), S.radius }; }
// 点群をVTK用のActorに変換 vtkSmartPointer<vtkActor> ConvertCloudVTKActor(const MyCloud& cloud); // 座標と半径から球のActorを作成 vtkSmartPointer<vtkActor> CreateSphereActor(const std::pair<Eigen::Vector3f, float>& sphere_data); // ランダムな点群を作成 MyCloud CreateRandomCloud(int num_points); // 表示データを作成 bool PrepareData(vtkSmartPointer<vtkRenderer> renderer) { // ランダムな点群を作成 auto cloud = CreateRandomCloud(1000); // welzlでBounding Sphereを計算 auto sphere = CalcBoundingSphere(cloud); std::cout << "center : " << sphere.first.transpose() << std::endl; std::cout << "radius : " << sphere.second << std::endl; // VTK Actorを作成 auto cloudActor = ConvertCloudVTKActor(cloud); auto sphereActor = CreateSphereActor(sphere); // Create a vtkSphereSource vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New(); sphereSource->SetCenter(sphere.first.x(), sphere.first.y(), sphere.first.z()); sphereSource->SetRadius(0.3); sphereSource->SetThetaResolution(32); sphereSource->SetPhiResolution(32); // Create a vtkPolyDataMapper vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputConnection(sphereSource->GetOutputPort()); // Create a vtkActor and assign the mapper vtkSmartPointer<vtkActor> CenterActor = vtkSmartPointer<vtkActor>::New(); CenterActor->SetMapper(mapper); renderer->AddActor(cloudActor); renderer->AddActor(sphereActor); renderer->AddActor(CenterActor); return true; } // 座標と半径から球のActorを作成 vtkSmartPointer<vtkActor> CreateSphereActor(const std::pair<Eigen::Vector3f, float>& sphere_data) { // 球の中心と半径を取得 const Eigen::Vector3f& center = sphere_data.first; float radius = sphere_data.second; // vtkSphereSourceを設定 vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New(); sphereSource->SetCenter(center.x(), center.y(), center.z()); sphereSource->SetRadius(radius); sphereSource->SetThetaResolution(32); // 解像度設定 sphereSource->SetPhiResolution(32); // 解像度設定 // Mapperを作成 vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputConnection(sphereSource->GetOutputPort()); // Actorを作成 vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); // 半透明に設定 actor->GetProperty()->SetOpacity(0.5); // 透明度(0.0~1.0) actor->GetProperty()->SetColor(1.0, 1.0, 1.0); // 白 return actor; }
// ランダムな点群を作成 MyCloud CreateRandomCloud(int num_points) { MyCloud cloud; std::random_device seed_gen; std::mt19937 engine(seed_gen()); std::uniform_real_distribution<> dist(-1.0, 1.0); for (int i = 0; i < num_points; i++) { cloud.points.emplace_back(dist(engine), dist(engine), dist(engine)); cloud.colors.emplace_back(dist(engine), dist(engine), dist(engine)); } return cloud; }
// 点群をVTK用のActorに変換 vtkSmartPointer<vtkActor> ConvertCloudVTKActor(const MyCloud& cloud) { // VTK用のポイントリストを作成 auto points = vtkSmartPointer<vtkPoints>::New(); for (const auto& point : cloud.points) { points->InsertNextPoint(point.x(), point.y(), point.z()); } // VTK用の頂点色リストを作成(Open3Dのcolors_を使用) auto colors = vtkSmartPointer<vtkUnsignedCharArray>::New(); colors->SetNumberOfComponents(3); // RGB if (!cloud.colors.empty()) { for (const auto& color : cloud.colors) { unsigned char r = static_cast<unsigned char>(color.x() * 255); unsigned char g = static_cast<unsigned char>(color.y() * 255); unsigned char b = static_cast<unsigned char>(color.z() * 255); colors->InsertNextTuple3(r, g, b); } } // ポリデータに点とオプションの属性(色、法線)を設定 auto polyData = vtkSmartPointer<vtkPolyData>::New(); polyData->SetPoints(points); if (!cloud.colors.empty()) { polyData->GetPointData()->SetScalars(colors); } // 点を頂点として描画可能にするフィルター auto glyphFilter = vtkSmartPointer<vtkVertexGlyphFilter>::New(); glyphFilter->SetInputData(polyData); glyphFilter->Update(); // マッパーを作成 auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputData(glyphFilter->GetOutput()); if (!cloud.colors.empty()) { mapper->ScalarVisibilityOn(); // 頂点色を使用する } else { mapper->ScalarVisibilityOff(); // 頂点色がない場合はオフ } // Actorを作成 auto actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); actor->GetProperty()->SetPointSize(5.0); actor->GetProperty()->SetInterpolationToFlat(); return actor; } int main() { vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New(); vtkSmartPointer<vtkRenderWindow> window = vtkSmartPointer<vtkRenderWindow>::New(); window->AddRenderer(renderer); vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); window->SetInteractor(interactor); PrepareData(renderer); window->SetSize(700, 400); interactor->Initialize(); window->Render(); interactor->Start(); }
WSLでインストールしたディストリビューションを直接変更できないので、一度.tarにエクスポートしてから名前を指定してインポートする。
一連の作業をpowershellでまとめた。
保存場所はインポートするディストリビューションの展開先なので、自分で専用の場所を決めておいたほうがいい。
$saveDirectory = Read-Host "保存場所 (デフォルト: ./distribution/)" # 入力が空の場合にデフォルト値を使用 if ([string]::IsNullOrWhiteSpace($saveDirectory)) { $saveDirectory = "./distribution/" } Write-Host "現在のディストリビューション一覧:" wsl -l -v # 名前を変更する $changeTarget = Read-Host -Prompt "変更するディストリビューション:" # 新たな名前を選択 $newname = Read-Host -Prompt "新たな名前" Write-Host "名前変更開始" Write-Host "現在のディストリビューションエクスポート" wsl --export $changeTarget ./temp_wsl.tar Write-Host "現在のディストリビューション削除" wsl --unregister $changeTarget Write-Host "インポート" wsl --import $newname $saveDirectory ./temp_wsl.tar Write-Host "一時ファイル削除" Remove-Item ./temp_wsl.tar #確認表示 wsl -l -v Write-Host "処理終了" Read-Host
skia-safeはSkiaのRustバインディング(非公式)。厳密にはskia-bindingというバインディングがあり、それをRustらしく使えるようにしたのがskia-safe。
use std::fs::File; use std::io::BufWriter;
fn main() { let width:u32 = 400; let height:u32 = 400; //(C++) SkBitmap bitmap; let mut bitmap = skia_safe::Bitmap::new(); //(C++) bitmap.setInfo(SkImageInfo::MakeN32Premul(400, 400)); let info = skia_safe::ImageInfo::new( (400,400), skia_safe::ColorType::N32, skia_safe::AlphaType::Premul, None ); bitmap.set_info(&info,None); //(C++) bitmap.allocPixels(); bitmap.alloc_pixels(); //(C++) SkCanvas canvas(bitmap); let mut canvas:skia_safe::OwnedCanvas = skia_safe::Canvas::from_bitmap(&bitmap,None).unwrap(); // bitmapの情報を表示 dispinfo(&bitmap); // 描画呼び出し draw(&mut canvas); //(C++) SkPngEncoder::Options options; //(C++) options.fZLibLevel = 9; // 圧縮レベル //(C++) options.fFilterFlags = SkPngEncoder::FilterFlag::kAll; // フィルタの種類 let mut opt = skia_safe::png_encoder::Options::default(); opt.z_lib_level = 9; opt.filter_flags = skia_safe::png_encoder::FilterFlag::ALL; opt.comments = vec![]; // ファイル出力 let file = File::create("out.png").unwrap(); let mut writer = BufWriter::new(file); let png_data = skia_safe::png_encoder::encode(&bitmap.pixmap(),&mut writer,&opt); }
fn draw(canvas:&mut skia_safe::OwnedCanvas){ //(C++) SkPaint paint; let mut paint: skia_safe::Paint = skia_safe::Paint::new(skia_safe::colors::BLACK,None); //(C++) paint.setStyle(SkPaint::kStroke_Style); paint.set_style(skia_safe::PaintStyle::Stroke); //(C++) paint.setStrokeWidth(5); paint.set_stroke_width(5.0); //(C++) paint.setColor(SK_ColorRED); paint.set_color(skia_safe::Color::RED); //(C++) canvas.drawCircle(200, 200, 100, paint); // (200,200) はタプル canvas.draw_circle((200,200),100.0,&paint); }
fn dispinfo(bitmap :&skia_safe::Bitmap) { let info = &bitmap.info(); let rowbytes = bitmap.row_bytes(); let channel = info.bytes_per_pixel(); let width = info.width(); let height = info.height(); println!("rowbytes:{}\nchannel:{}\nwidth:{}\nheight{}",rowbytes,channel,width,height); }
tiny-skiaはskiaの影響を受けたライブラリで、テキストレンダリングなどの機能を除いて「skiaっぽくつかえる軽量のライブラリ」として開発されている。本家とは関係ない。BSDライセンス。
[dependencies]
# tiny-skiaの使用。
# https://crates.io/crates/tiny-skia/
tiny-skia = "0.11"
# 乱数を使用 https://crates.io/crates/rand
rand = "0.8.5"
※コード中の「skia」は全てtiny-skiaの意味。
use rand::Rng;// 乱数用 use tiny_skia::{Pixmap, Paint, Color, PathBuilder, Transform};
fn main() { let width:u32 = 400; let height:u32 = 400; // skiaの画像を作成 let mut skiaimage = Pixmap::new(width,height).expect("skia error"); // イテレータ経由でピクセルバッファへアクセス // 全ての画素を緑で塗りつぶし let mut pixel_itr = skiaimage.data_mut().chunks_exact_mut(4); for pixel in pixel_itr{ pixel[0] = 0; pixel[1] = 255; // 緑 pixel[2] = 0; pixel[3] = 255; } // tiny-skiaで円を描く circle(&mut skiaimage,200.0,200.0,100.0,0,0,255); circle(&mut skiaimage,130.0,250.0,80.0,255,0,0); let mut rng = rand::thread_rng(); // 乱数を使用 // ランダムアクセス // スライスの配列を作成してアクセスするので、使い回さないのなら効率が悪い let mut pixels: Vec<&mut [u8]> = skiaimage.data_mut().chunks_exact_mut(4).collect(); (0..50000).for_each(|_|{ let x:u32=rng.gen_range(0 ..width as i32) as u32; let y:u32=rng.gen_range(0 .. height as i32) as u32; let pos = (y*width+x) as usize; pixels[pos][0] =255; pixels[pos][1] =255; pixels[pos][2] =255; pixels[pos][3] =255; }); // 結果をPNGに保存 skiaimage.save_png("output.png").expect("Failed to save PNG"); println!("Image saved as output.png"); }
fn circle(simg:&mut Pixmap,px:f32,py:f32,radius:f32,r:u8,g:u8,b:u8){ // 塗りつぶしの色を設定 let mut paint = Paint::default(); paint.set_color(Color::from_rgba8(r,g,b, 255)); // パス作成 let mut path_builder = PathBuilder::new(); path_builder.push_circle(px,py,radius); let path = path_builder.finish().expect("Failed to create path"); // 円を描画 simg.fill_path( &path, &paint, tiny_skia::FillRule::Winding, Transform::identity(), None, ); }