Vala プログラミング

Rust プログラミング

おながのブログ

Rust Vulkano Triangle (3)

Rust Vulkano Triangle (2)の続きです。

スワップチェインとイメージの作成
 screen(サーフェイス)にイメージを描画するには、Swapchain
スワップチェイン)が必要になります。
 スワップチェインの作成と同時にSwapchainImage(イメージ)が作成され
ます。スワップチェインは、イメージをサーフェイスに描画する役目をします。

 スワップチェインは、Struct vulkano::swapchain::Swapchainのnewメソッドで
作成する。
書式

fn new(device, surface, num_images, format, dimensions,
    layers, usage, sharing, transform, alpha, mode,
    clipped, old_swapchain)
  -> Result<(Swapchain, SwapchainImage), SwapchainCreationError>

引数には、
device(使用するVulkanデバイス)、
surface(使用するサーフェイス)、
num_images(イメージバッファの数)、
format(イメージフォーマット)、
dimensions(イメージのサイズ)、
layers(各イメージのレイヤーの数)、
usage(イメージの使用形式)、
sharing(sharing mode、キューファミリーの使用モードを指定)、
transform(surface transform、スクリーンに描画する前のイメージ変換)、
alpha(Composite alphaの指定)、
mode(present mode、イメージの表示モードの指定)、
clipped(ブッファの非表示部分をクリップ)、
old_swapchain(以前のスワップチェインの使用)
を指定する。
戻り値はResult型で、Okの場合はスワップチェインとスワップチェインイメージ
のタプル ( Swapchain, SwapcainImage ) を返す。

 引数 num_images、format、dimensions、usage、alphaは、サーフェイスの
capabilitiesメソッドを用いて取得した値を指定する。
capabilitiesの取得と引数

let caps = window.surface().capabilities(physical)
    .expect("failed to get surface capabilities");

num_images : caps.min_image_count
format : caps.supported_formats[0].0 (タプルの最初の値)
dimensions : caps.current_extent.unwrap()
usage : caps.supported_usage_flags
alpha : caps.supported_composite_alpha.iter().next().unwrap()

その他の引数は、次のようにする。

・device : device.clone()
・surface : window.surface().clone()
・layers : 1
・sharing : &queue
・transform : SurfaceTransform::Identity
・present : PresentMode::Fifo
・clipped : true
・old_swapchain : None

スワップチェインとイメージの作成は次のようになる。

// dimension
let mut dimensions;

// Swapchain and images  
let (mut swapchain, mut images) = {
    // Surface capabilities
    let caps = window.surface().capabilities(physical)
        .expect("failed to get surface capabilities");
    // dimension
    dimensions = caps.current_extent.unwrap();
    // composite alpha
    let alpha = caps.supported_composite_alpha.iter().next().unwrap();
    // format
    let format = caps.supported_formats[0].0;
    //  Swapchain and images
    Swapchain::new(device.clone(), window.surface().clone(), caps.min_image_count, format,
        dimensions, 1, caps.supported_usage_flags, &queue, SurfaceTransform::Identity, alpha,
        PresentMode::Fifo, true, None).expect("failed to create swapchain")
};

Rust Vulkano Triangle (2)

前回の Vulkano API を用いた Triangle 描画のまとめです。
Vulkan と Vulkano のチュートリアルサイト、Vulkano 関連のドキュメントを参考にしています。
・ Vulkan ( Introduction - Vulkan Tutorial )
・ Vulkano ( Vulkano )
・ Vulkano ドキュメント ( vulkano - Rust )

Vulkano による Triangle 描画の手順
インスタンスの作成
2 物理デバイスの列挙と設定
3 ウィンドウとウィンドウサーフェイスの作成
4 Vulkanデバイスとコマンドキューの作成
5 スワップチェインとイメージの作成
6 vertex バッファと uniform バッファの作成
7 shader プログラムの読込み
8 レンダーパスの作成
9 パイプラインの作成
10 フレームバッファの作成
描画ループ内
11 スワップチェインの生成
12 フレームバッファの生成
13 uniform バッファの set の設定
14 コマンドバッファの作成
15 future の生成
16 event ループ

この手順に従って、Triangle 描画の方法を見ていきます。
* 書式は簡単に書いてあります。正しい書き方は、各クレート(crate)のドキュメントを参照して下さい。

インスタンスの作成
 インスタンス(Instance)オブジェクトは、Struct vulkano::instance::Instance のnewメソッドで作成します。
書式

fn new(app_infos, extensions, layers) -> Result<Instance, InstanceCreationError>

 screenに描画するには、newの2番目の引数extensions(InstanceExtensions)を用いて、サーフェイス(surface)を利用可能にします。
利用可能となっているextensionsは、vulkano_win クレートの required_extensions()メッソドを使って取得できます。これを設定します。
取得したextensions(利用可能なサーフェイスのリスト、Ubuntu 17.10)

[VK_KHR_surface, VK_KHR_xlib_surface, VK_KHR_xcb_surface, VK_KHR_wayland_surface, VK_KHR_mir_surface ]

残りの2つの引数は使用しないので、 Noneを設定します。

 処理結果は、 Result(Ok(), Err())型で帰ってきます。Result型は、正しく値が得られたときはOk()でラップ(wrap)され、得られなかったときはエラー値がErr()でラップされています。データの取り出しには、match式やunwrap()、expect(msg) メソッドなどを用います。
・match式
 パターンマッチを使って2つに場合分けし、処理する。
・unwrapメソッド
 正しい値が得られたとき:Ok()でラップされた値を帰す。
 正しい値が得られなかったとき:パニック(panic)を起こし処理を停止する。
・expect(msg)メッソド
 正しい値が得られたとき:Ok()でラップされた値を帰す。
 正しい値が得られなかったとき:パニック(panic)を起こし処理を停止するが、
 その時msgの文字を表示する。(エラー箇所の表示に用いる。)
ここではexpect(msg)を使っています。

 InstanceExtensionsの設定とInstanceの作成は、次のようになります。

let extensions = vulkano_win::required_extensions();
let instance = Instance::new(None, &extensions, None).expect("failed to create Vulkan instance");

extensionsの式は、instanceの式の中に入れることもできます。

let instance = {
    let extensions = vulkano_win::required_extensions();
    Instance::new(None, &extensions, None).expect("failed to create Vulkan instance")
};


2 物理デバイスの列挙
 使用できる物理デバイスの列挙には、Struct vulkano::instance::PhysicalDevice のenumerate メソッドを使用します。
書式

fn enumerate(instance) -> PhysicalDevicesIter

 引数に、先に作成した instance を渡します。結果は、物理デバイスイテレータ(PhysicalDevicesIter)が帰ってきます。
イテレータから個別の値を取り出すには、next() メソッドを使います。
next メソッドは Option(Some(), None) 型の値を帰します。Option型のデータの取り出しは、Result型と同様です。ここでも、expect(msg) メソッドを使用しています。

 物理デバイスの列挙は、次のようになります。

let physical = PhysicalDevice::enumerate(&instance).next().expect("no device available");

得られた物理デバイスの名前(NECノートPC 2016年製)
Intel(R) HD Graphics 520 (Skylake GT2)


3 ウィンドウとウィンドウサーフェイスの作成
 ウィンドウの作成には、Struct winit::WindowBuilderのnewメソッドを使います。
書式

fn new() -> WindowBuilder

newメソッドは、WindowBuilderを帰します。続けてWindowBuilderのwith_dimensions(width, height)、with_title(title)メソッドで、ウィンドウサイズやウィンドウタイトルを設定します。これらのメソッドもWindowBuilderを帰します。

 次にbuild_vk_surfaceメソッドを使って、Vulkanサーフェイスを持ったウィンドウを作成します。
書式

fn build_vk_surface(events_loop, instance) -> Result<Window, CreationError>

このメソッドは、引数にウィンドウのevents_loopの参照とinstanceのコピーを取ります。
結果はResult型で帰ってきます。作成したウィンドウ(Window)か作成できない場合は
エラー(CreationError)を帰します。データの取り出しには、unwrap()を使用しています。

 events_loopの設定とウィンドウサーフェイスの作成は次のようになります。

let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new()
    .with_dimensions(800, 600)
    .with_title(format!("Vulkano Triangle"))
    .build_vk_surface(&events_loop, instance.clone()).unwrap();


4 Vulkanデバイスとコマンドキューの作成
 Vulkanデバイス(論理デバイス)とコマンドキューを作成します。Vulkanデバイスは、物理デバイスとコニュニケーションするための窓口である。
 Vulkanデバイスとコマンドキューは、Struct vulkano::device::Deviceのnewメッソドで作成します。
書式

fn new(phys, requested_features, extensions, queue_families)
  -> Result<(Device, QueuesIter), DeviceCreationError> 

引数には、物理デバイス (phys) 、Vulkan features (requested_features) 、デバイス extensions (extensions) 、キューファミリー (queue_families) を指定します。
戻り値はResult型で、Okの場合はVulkanデバイス (Device) とキューのイテレータ (QueuesIter) のタプル ( Device, QueuesIter )を返します。

引数の指定

  • Vulkan features

 物理デバイスがサポートする features を取得して、指定する。
 physical.supported_features()

 利用するデバイスextensionsを設定する。
 (デバイスextensionsには、khr_swapchain、khr_display_swapchain、
 khr_sampler_mirror_clamp_to_edgeなどがある。)

let device_ext = vulkano::device::DeviceExtensions {
    khr_swapchain: true,
    .. vulkano::device::DeviceExtensions::none()
};

 利用しないextensionsは、none()メソッドを使ってまとめて「false」に設定
 している。

  • キューファミリー

 キューファミリーには、物理デバイスから取得したキューファミリーを指定
 する。
 キューファミリーの取得
 1 queue_families()メソッドを使って、キューファミリーのイテレータ
  取得する。
  physical.queue_families()
 2 find()メソッドを使って、条件「q.supports_graphics() &&
  window.surface().is_supported(q).unwrap_or(false)」を満たすキュー
  ファミリーを取り出す。
  qはイテレータの要素(キューファミリー)で、グラフィックス用で
  サーフェイスに描画できるキューファミリーを見つけ出している。
  サーフェイスへの描画をサポートするキューファミリーが見つからなった
  場合には、「false」を帰すようにunwrap_or(false)メソッドを使っている。
  find()の結果(Option型)は、expect()メソッドを使って取り出す。

let queue_family = physical.queue_families()
   .find(|&q| q.supports_graphics() && window.surface().is_supported(q).unwrap_or(false))
   .expect("couldn't find a graphical queue family");

 キューファミリーの指定
 引数queue_familiesは、タプル(QueueFamily, f32)を要素とするイテレータ
 指定する。f32はキューの計算スピードのpriorityを表し、0.0から1.0の値を設定
 する。
 キューファミリーは次のように指定する。
  [(queue_family, 0.5)].iter().cloned()
 タプルの配列 [(queue_family, 0.5)] からiter()とcloned()メソッドを用いて
 イテレータを生成する。cloned()を用いて、 イテレータの要素を参照ありから
 参照なしに変更している。

 キューファミリーの取得とVulkanデバイスとコマンドキューの作成は、
 次のようになる。

// queue family
let queue_family = physical.queue_families()
    .find(|&q| q.supports_graphics() && window.surface().is_supported(q).unwrap_or(false))
    .expect("couldn't find a graphical queue family");
    
// Device and queues
let (device, mut queues) = {
    let device_ext = vulkano::device::DeviceExtensions {
        khr_swapchain: true,
        .. vulkano::device::DeviceExtensions::none()
    };
    Device::new(physical, physical.supported_features(), &device_ext,
        [(queue_family, 0.5)].iter().cloned()).expect("failed to create device")
};

let queue = queues.next().unwrap();

最後の行で、next()メソッドを用いてキューのイテレータqueuesから初めの要素を
取り出している。

(2018年2月8日 更新)

Rust Vulkano ( Vulkan ) Triangle

Rust Vulkano プロジェクト( https://github.com/vulkano-rs/vulkano ) の triangle サンプルです。
マウスで簡単にズームや回転が出来るように、ArcBallCamera( arcball crate) を追加しています。

実行結果
スタート時
f:id:onagat12:20171218010019p:plain

zoom ( マウスホイールのアップ・ダウン )
f:id:onagat12:20171218010048p:plain

rotation ( 左ボタンのドラッグ )
f:id:onagat12:20171218010111p:plain

pan ( 右ボタンのドラッグ )
f:id:onagat12:20171218010130p:plain

プログラム

#![allow(dead_code)]

#[macro_use]
extern crate vulkano;
#[macro_use]
extern crate vulkano_shader_derive;
extern crate winit;
extern crate vulkano_win;
extern crate arcball;
extern crate cgmath;

use vulkano_win::VkSurfaceBuild;
use vulkano::buffer::BufferUsage;
use vulkano::buffer::CpuAccessibleBuffer;
use vulkano::command_buffer::AutoCommandBufferBuilder;
use vulkano::command_buffer::DynamicState;
use vulkano::device::Device;
use vulkano::framebuffer::Framebuffer;
use vulkano::framebuffer::Subpass;
use vulkano::instance::Instance;
use vulkano::instance::PhysicalDevice;
use vulkano::pipeline::GraphicsPipeline;
use vulkano::pipeline::viewport::Viewport;
use vulkano::swapchain;
use vulkano::swapchain::PresentMode;
use vulkano::swapchain::SurfaceTransform;
use vulkano::swapchain::Swapchain;
use vulkano::swapchain::AcquireError;
use vulkano::swapchain::SwapchainCreationError;
use vulkano::sync::now;
use vulkano::sync::GpuFuture;

use std::sync::Arc;
use std::mem;

fn main() {
    // Instance
    let instance = {
        // instance extension
        let extensions = vulkano_win::required_extensions();
        //println!("extensions {:?}", extensions);
        Instance::new(None, &extensions, None).expect("failed to create Vulkan instance")
    };

    // Physical Device
    let physical = PhysicalDevice::enumerate(&instance).next().expect("no device available");
    //println!("Using device: {} (type: {:?})", physical.name(), physical.ty());

    // Window
    let mut events_loop = winit::EventsLoop::new();
    let window = winit::WindowBuilder::new()
        .with_dimensions(800, 600)
		.with_title(format!("Vulkano Triangle"))
        .build_vk_surface(&events_loop, instance.clone()).unwrap();
    
    // Vulkan Device
    // queue family
    let queue_family = physical.queue_families()
        .find(|&q| q.supports_graphics() && window.surface().is_supported(q).unwrap_or(false))
        .expect("couldn't find a graphical queue family");
    
    // Device and queues
    let (device, mut queues) = {
        // device extension
        let device_ext = vulkano::device::DeviceExtensions {
            khr_swapchain: true,
            .. vulkano::device::DeviceExtensions::none()
        };
        // Device and queues
        Device::new(physical, physical.supported_features(), &device_ext,
            [(queue_family, 0.5)].iter().cloned()).expect("failed to create device")
    };
    //
    let queue = queues.next().unwrap();

    // dimension
    let mut dimensions;

    // Swapchain and images  
    let (mut swapchain, mut images) = {
        // Surface capabilities
        let caps = window.surface().capabilities(physical)
            .expect("failed to get surface capabilities");
        // dimension
        dimensions = caps.current_extent.unwrap();
        // composite alpha
        let alpha = caps.supported_composite_alpha.iter().next().unwrap();
        // format
        let format = caps.supported_formats[0].0;
        //  Swapchain and images
        Swapchain::new(device.clone(), window.surface().clone(), caps.min_image_count, format,
            dimensions, 1, caps.supported_usage_flags, &queue, SurfaceTransform::Identity, alpha,
            PresentMode::Fifo, true, None).expect("failed to create swapchain")
    };

    // vertex buffer
    let vertex_buffer = {
        #[derive(Debug, Clone)]
        struct Vertex { position: [f32; 2] }
        impl_vertex!(Vertex, position);

        CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), [
            Vertex { position: [-0.5, -0.25] },
            Vertex { position: [0.0, 0.5] },
            Vertex { position: [0.25, -0.1] }
        ].iter().cloned()).expect("failed to create buffer")
    };

    // uniform buffer
    let uniform_buffer = vulkano::buffer::cpu_pool::CpuBufferPool::<vs::ty::Data>
                         ::new(device.clone(), vulkano::buffer::BufferUsage::all());

    let vs = vs::Shader::load(device.clone()).expect("failed to create shader module");
    let fs = fs::Shader::load(device.clone()).expect("failed to create shader module");

    // render pass
    let render_pass = Arc::new(single_pass_renderpass!(device.clone(),
        attachments: {
            color: {
                load: Clear,
                store: Store,
                format: swapchain.format(),
                samples: 1,
            }
        },
        pass: {
            color: [color],
            depth_stencil: {}
        }
    ).unwrap());

    // pipeline
    let pipeline = Arc::new(GraphicsPipeline::start()
        .vertex_input_single_buffer()
        .vertex_shader(vs.main_entry_point(), ())
        .triangle_list()
        .viewports_dynamic_scissors_irrelevant(1)
        .fragment_shader(fs.main_entry_point(), ())
        .render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
        .build(device.clone())
        .unwrap());
    
    // framebuffer
    let mut framebuffers: Option<Vec<Arc<vulkano::framebuffer::Framebuffer<_,_>>>> = None;
        //  下の「if framebuffers.is_none() { }」がないとき: error: type annotations needed 

    let mut recreate_swapchain = false;

    let mut previous_frame_end = Box::new(now(device.clone())) as Box<GpuFuture>;

    //println!("window size {},{}", dimensions[0], dimensions[1]);

    let persp_proj:cgmath::Matrix4<f32> = cgmath::perspective(cgmath::Deg(65.0),
            dimensions[0] as f32 / dimensions[1] as f32, 0.01, 100.0);
    let mut arcball_camera = {
        let look_at = cgmath::Matrix4::look_at(cgmath::Point3::new(0.0, 0.0, 2.0),
                        cgmath::Point3::new(0.0, 0.0, 0.0), cgmath::Vector3::new(0.0, 1.0, 0.0));
        arcball::ArcballCamera::new(&look_at, 0.05, 4.0, [dimensions[0] as f32, dimensions[1] as f32])
    };
    //
    let mut arcball_camera_mat4: [[f32;4];4] = arcball_camera.get_mat4().into();
    //println!("arcball camera mat4: {:?}", arcball_camera_mat4);

    let mut mouse_pressed = [false, false];
    let mut prev_mouse: Option<(f64,f64)> = None;

    loop {
        previous_frame_end.cleanup_finished();

        // recreate swapchain (if it needs )
        if recreate_swapchain {
            dimensions = window.surface().capabilities(physical)
                        .expect("failed to get surface capabilities")
                        .current_extent.unwrap();
            
            let (new_swapchain, new_images) = match swapchain.recreate_with_dimension(dimensions) {
                Ok(r) => r,
                Err(SwapchainCreationError::UnsupportedDimensions) => {
                    continue;
                },
                Err(err) => panic!("{:?}", err)
            };

            mem::replace(&mut swapchain, new_swapchain);
            mem::replace(&mut images, new_images);

            framebuffers = None;

            recreate_swapchain = false;
        }

        // recreate framebuffers (framebuffers contains an Arc on the old swapchain)
        if framebuffers.is_none() {
            let new_framebuffers = Some(images.iter().map(|image| {
                Arc::new(Framebuffer::start(render_pass.clone())
                         .add(image.clone()).unwrap()
                         .build().unwrap())
            }).collect::<Vec<_>>());
            mem::replace(&mut framebuffers, new_framebuffers);
        }

        let proj = (persp_proj * arcball_camera.get_mat4()).into();

        let uniform_buffer_subbuffer = {
            let uniform_data = vs::ty::Data {
                proj : proj,
            };

            uniform_buffer.next(uniform_data).unwrap()
        };

        let set = Arc::new(vulkano::descriptor::descriptor_set::PersistentDescriptorSet::start(pipeline.clone(), 0)
            .add_buffer(uniform_buffer_subbuffer).unwrap()
            .build().unwrap()
        );

        let (image_num, acquire_future) =
            match swapchain::acquire_next_image(swapchain.clone(), None) {
                Ok(r) => r,
                Err(AcquireError::OutOfDate) => {
                    recreate_swapchain = true;
                    continue;
                },
                Err(err) => panic!("{:?}", err)
            };

        let command_buffer =
            AutoCommandBufferBuilder::primary_one_time_submit(device.clone(), queue.family()).unwrap()
            .begin_render_pass(framebuffers.as_ref().unwrap()[image_num].clone(), false,
                               vec![[0.0, 0.0, 1.0, 1.0].into()]).unwrap()
            // draw
            .draw(pipeline.clone(),
                  DynamicState {
                      line_width: None,
                      viewports: Some(vec![Viewport {
                          origin: [0.0, 0.0],
                          dimensions: [dimensions[0] as f32, dimensions[1] as f32],
                          depth_range: 0.0 .. 1.0,
                      }]),
                      scissors: None,
                  },
                  vertex_buffer.clone(), set.clone(), ()).unwrap()
            .end_render_pass().unwrap()
            .build().unwrap();

        let future = previous_frame_end.join(acquire_future)
            .then_execute(queue.clone(), command_buffer).unwrap()
            .then_swapchain_present(queue.clone(), swapchain.clone(), image_num)
            .then_signal_fence_and_flush().unwrap();
        previous_frame_end = Box::new(future) as Box<_>;

        let mut done = false;
        events_loop.poll_events(|ev| {
            match ev {
                winit::Event::WindowEvent {
                    event: winit::WindowEvent::MouseMoved { position: (x, y), ..}, ..}
                       if prev_mouse.is_none() => {
                                    prev_mouse = Some((x, y));
                },
                winit::Event::WindowEvent {
                    event: winit::WindowEvent::MouseMoved { position: (x, y), .. }, ..}  => {
                        //println!("MouseMoved {},{}", x, y);
                        let prev = prev_mouse.unwrap();
                        if mouse_pressed[0] {
                            arcball_camera.rotate(cgmath::Vector2::new(prev.0 as f32, prev.1 as f32),
                                                  cgmath::Vector2::new(x as f32, y as f32));
                            arcball_camera_mat4 = arcball_camera.get_mat4().into();
                            println!("rotate mat4: {:?}", arcball_camera_mat4);
                        } else if mouse_pressed[1] {
                            let mouse_delta = cgmath::Vector2::new((x - prev.0) as f32, -(y - prev.1) as f32);
                            arcball_camera.pan(mouse_delta, 0.16);
                            arcball_camera_mat4 = arcball_camera.get_mat4().into();
                            println!("pan mat4: {:?}", arcball_camera_mat4);
                        }
                        prev_mouse = Some((x, y));
                },
                winit::Event::WindowEvent {
                    event: winit::WindowEvent::MouseInput { state: _state, button: _button, ..}, ..} => {
                        //println!("button {:?}", button);
                        if _button == winit::MouseButton::Left {
                            mouse_pressed[0] = _state == winit::ElementState::Pressed;
                        } else if _button == winit::MouseButton::Right {
                            mouse_pressed[1] = _state == winit::ElementState::Pressed;
                        }
                },
                winit::Event::WindowEvent {
                    event: winit::WindowEvent::MouseWheel {
                        delta: winit::MouseScrollDelta::LineDelta(_, y), .. }, ..}  => {
                            //println!("ScrollDelta {}", y);
                            arcball_camera.zoom(y, 0.1);
                            arcball_camera_mat4 = arcball_camera.get_mat4().into();
                            println!("zoom mat4: {:?}", arcball_camera_mat4);
                },
                winit::Event::WindowEvent { event: winit::WindowEvent::Closed, .. } => done = true,
                _ => ()
            }
        });
        if done { return; }
    }
}

mod vs {
    #[derive(VulkanoShader)]
    #[ty = "vertex"]
    #[src = "
        #version 450

        layout(location = 0) in vec2 position;

        layout(set = 0, binding = 0) uniform Data {
            mat4 proj;
        } uniforms;

        void main() {
            gl_Position = uniforms.proj * vec4(position, 0.0, 1.0);
        }
    "]
    struct Dummy;
}

mod fs {
    #[derive(VulkanoShader)]
    #[ty = "fragment"]
    #[src = "
        #version 450

        layout(location = 0) out vec4 f_color;

        void main() {
            f_color = vec4(1.0, 0.0, 0.0, 1.0);
        }
    "]
    struct Dummy;
}

Cargo.toml

[package]
name = "triangle-arcball"
version = "0.1.0"
authors = ["xxxxxx"]

[dependencies]
vulkano = "0.7.2"
vulkano-shader-derive = "0.7.2"
vulkano-win = "0.7.2"
cgmath = "0.15.0"
image = "0.14.0"
winit = "0.7.0"
time = "0.1.37"
arcball = "0.3.0"

Rust kiss3d ライブラリ

kiss3dライブラリ(https://github.com/sebcrozet/kiss3d)の紹介です。

kiss3dは、3dのグラフィックス エンジン(graphics engine)です。
オブジェクト(cubeやsphereなど)の描画、オブジェクトの拡大縮小や回転などの
操作が簡単にできるように設定されています。
以下、examplesフォルダ内のサンプルです。

cube.rs
f:id:onagat12:20170507173142g:plain
実行直後は、オブジェクトが画面全体に広がっています。
オブジェクトの操作
・マウスホイールの上げ下げ:拡大縮小
・左ボタンのドラッグ;回転
・右ボタンのドラッグ:移動

primitives.rs
f:id:onagat12:20170507173234g:plain
(縮小した状態からのアニメーションです。以下の例も同様)

custom_mesh_shared.rs
f:id:onagat12:20170507173307g:plain

texturing.rs
f:id:onagat12:20170507173332g:plain

obj.rs
f:id:onagat12:20170507173359g:plain

Rust glium Tessellation (4) 3D surface

glium テッセレーション

David Wolff著「OpenGL 4.0 シェーディング言語 ( OpenGL 4.0 Shading
Language Cookbook )」
第6章「ジオメトリシェーダとテッセレーションシェーダ」
「3Dサーフェイスをテッセレートする」
「シェーディングしたメッシュの上にワイヤフレームを描く」

実行結果
f:id:onagat12:20170504171256p:plain tessellation level = 2
f:id:onagat12:20170504171311p:plain tessellation level = 4
f:id:onagat12:20170504171321p:plain tessellation level = 8

teapot多面体をテッセレーションを使って描画し、シェーディングした
メッシュの上にワイヤフレーム(エッジ)を描きます。

teapotの描画は、前回と同様です。
エッジの描画は次のようにします。

  1. メッシュ(三角形)の頂点と対辺との距離を求める。
  2. 描画するフラグメントが、3つの距離の最短の距離より短いか長いかにより、描画する色を決める。

(「シェーディングしたメッシュの上にワイヤフレームを描く」の項参照)


・シェーダプログラムの設定
バーテックスシェーダとテッセレーションシェーダは、前回(teapot)と
同じものを使用します。
ジオメトリシェーダとフラグメントシェーダは以下の様にします。

geometry_shader:  Some("
   #version 400

   layout(triangles) in;
   layout(triangle_strip, max_vertices = 3) out;

   in vec3 TENormal[];
   in vec4 TEPosition[];

   noperspective out vec3 EdgeDistance;
   out vec3 Normal;
   out vec4 Position;

   uniform mat4 ViewportMatrix;

   void main() {
      vec3 p0 = vec3(ViewportMatrix * (gl_in[0].gl_Position / gl_in[0].gl_Position.w));
      vec3 p1 = vec3(ViewportMatrix * (gl_in[1].gl_Position / gl_in[1].gl_Position.w));
      vec3 p2 = vec3(ViewportMatrix * (gl_in[2].gl_Position / gl_in[2].gl_Position.w));

      float a = length(p1 - p2);
      float b = length(p2 - p0);
      float c = length(p1 - p0);
      float alpha = acos( (b*b + c*c - a*a) / (2.0*b*c) );
      float beta = acos( (a*a + c*c - b*b) / (2.0*a*c) );
      float ha = abs( c * sin( beta ) );
      float hb = abs( c * sin( alpha ) );
      float hc = abs( b * sin( alpha ) );

      EdgeDistance = vec3( ha, 0, 0 );
      Normal = TENormal[0];
      Position = TEPosition[0];
      gl_Position = gl_in[0].gl_Position;
      EmitVertex();

      EdgeDistance = vec3( 0, hb, 0 );
      Normal = TENormal[1];
      Position = TEPosition[1];
      gl_Position = gl_in[1].gl_Position;
      EmitVertex();

      EdgeDistance = vec3( 0, 0, hc );
      Normal = TENormal[2];
      Position = TEPosition[2];
      gl_Position = gl_in[2].gl_Position;
      EmitVertex();

      EndPrimitive();
   }
"),
fragment_shader: "
   #version 140

   noperspective in vec3 EdgeDistance;
   in vec3 Normal;
   in vec4 Position;
   out vec4 f_color;

   uniform float LineWidth;
   uniform vec4 LineColor;
   uniform vec3 LightPosition;
   uniform vec3 LightIntensity;
   uniform vec3 Kd;

   vec3 diffuseModel( vec3 pos, vec3 norm ) {
      vec3 s = normalize( LightPosition - pos);
      float sDotN = max( dot(s,norm), 0.0 );
      vec3 diffuse = LightIntensity * Kd * sDotN;
      vec3 ambient = vec3(0.01, 0.01, 0.02);

      return ambient + diffuse;
   }

   float edgeMix() {
      float d = min( min( EdgeDistance.x, EdgeDistance.y ), EdgeDistance.z );

      if( d < LineWidth - 1 ) {
         return 1.0;
      } else if( d > LineWidth + 1 ) {
         return 0.0;
      } else {
         float x = d - (LineWidth - 1);
         return exp2(-2.0 * (x*x));
      }
   }

   void main() {
      float mixVal = edgeMix();
      vec4 color = vec4( diffuseModel( Position.xyz, Normal ), 1.0);
      color = pow( color, vec4(1.0/2.2) );
      f_color = mix( color, LineColor, mixVal );
   }
",

ジオメトリシェーダで三角形の頂点と対辺との距離を求め、フラグメント
シェーダで塗りつぶす色を決めています。
この部分は、Example code for the OpenGL Shading Language Cookbook
(https://github.com/daw42/glslcookbook)を参照しています。


・ビューポート(Viewport)変換
この例では、ビューポート変換を使います。
ビューポートの設定は、DrawParametersで行います。また、変換用の
マトリックスを設定します。

let params = glium::DrawParameters {
   depth: glium::Depth {
      test: glium::draw_parameters::DepthTest::IfLess,
      write: true,
      .. Default::default()
   },
   viewport: Some(glium::Rect {left:0, bottom:0, width:800, height:600}),
   .. Default::default()
};

let w2 :f32 = 800.0 / 2.0;
let h2 :f32 = 600.0 / 2.0;
let viewport = [
    [w2, 0.0, 0.0, 0.0f32],
    [0.0, h2, 0.0, 0.0f32],
    [0.0, 0.0, 1.0, 0.0f32],
    [w2, h2, 0.0, 1.0f32],
]; // マトリックス

Rust glium Tessellation (3) Teapot

glium テッセレーション
Teapot ( Phong Model )

実行結果
f:id:onagat12:20170503181454p:plain tessellation level = 2
f:id:onagat12:20170503181504p:plain tessellation level = 4
f:id:onagat12:20170503181513p:plain tessellation level = 8

1 depth bufferの設定

let display = glutin::WindowBuilder::new()
   .with_dimensions(800, 600)
   .with_title(format!("Tess Teapot PhongModel"))
   .with_depth_buffer(24)
   .build_glium().unwrap();

let params = glium::DrawParameters {
   depth: glium::Depth {
      test: glium::draw_parameters::DepthTest::IfLess,
      write: true,
      .. Default::default()
   },
   .. Default::default()
};

この例では、depth bufferを使用します。
depth bufferを有効にする方法

  1. OpenGL contextを生成する文で、「.with_depth_buffer(24)」を指定します。
  2. DrawParametersで、depthを設定します。


2 制御点の設定

#[derive(Copy, Clone)]
pub struct Vertex {
   position: [f32; 3],
}

implement_vertex!(Vertex, position);

pub const VERTICES: [Vertex; 512] = [
   // patch 1(rim1)
   Vertex { position: [ 1.4   ,  0.0   , 2.4] },
   Vertex { position: [ 1.4   , -0.784 , 2.4] },
   Vertex { position: [ 0.784 , -1.4   , 2.4] },
   Vertex { position: [ 0.0   , -1.4   , 2.4] },
   Vertex { position: [ 1.3375,  0.0   , 2.53125] },
   Vertex { position: [ 1.3375, -0.749 , 2.53125] },
   Vertex { position: [ 0.749 , -1.3375, 2.53125] },
   Vertex { position: [ 0.0   , -1.3375, 2.53125] },
   Vertex { position: [ 1.4375,  0.0   , 2.53125] },
   Vertex { position: [ 1.4375, -0.805 , 2.53125] },
   Vertex { position: [ 0.805 , -1.4375, 2.53125] },
   Vertex { position: [ 0.0   , -1.4375, 2.53125] },
   Vertex { position: [ 1.5   ,  0.0   , 2.4] },
   Vertex { position: [ 1.5   , -0.84  , 2.4] },
   Vertex { position: [ 0.84  , -1.5   , 2.4] },
   Vertex { position: [ 0.0   , -1.5   , 2.4] },
   // patch 2(rim2)
   Vertex { position: [ 0.0   , -1.4   , 2.4] },
   Vertex { position: [-0.784 , -1.4   , 2.4] },
   Vertex { position: [-1.4   , -0.784 , 2.4] },
   Vertex { position: [-1.4   ,  0.0   , 2.4] },
   ・・・
     省略
   ・・・
   // patch 32(bottom4)
   Vertex { position: [ 0.0  ,  0.0  , 0.0] },
   Vertex { position: [ 0.0  ,  0.0  , 0.0]},
   Vertex { position: [ 0.0  ,  0.0  , 0.0] },
   Vertex { position: [ 0.0  ,  0.0  , 0.0] },
   Vertex { position: [ 1.425,  0.0  , 0.0] },
   Vertex { position: [ 1.425,  0.798, 0.0] },
   Vertex { position: [ 0.798,  1.425, 0.0] },
   Vertex { position: [ 0.0  ,  1.425, 0.0] },
   Vertex { position: [ 1.5  ,  0.0  , 0.075] },
   Vertex { position: [ 1.5  ,  0.84 , 0.075] },
   Vertex { position: [ 0.84 ,  1.5  , 0.075] },
   Vertex { position: [ 0.0  ,  1.5  , 0.075] },
   Vertex { position: [ 1.5  ,  0.0  , 0.15] },
   Vertex { position: [ 1.5  ,  0.84 , 0.15] },
   Vertex { position: [ 0.84 ,  1.5  , 0.15] },
   Vertex { position: [ 0.0  ,  1.5  , 0.15] },
];

制御点はモジュールファイル(teapot.rs)で定義しています。
teapotは32のパッチで描画されます。各パッチは16個の制御点で構成
されます。
この16個の制御点がパッチプリミティブとなります。


3 VertexBufferの設定

let vertex_buffer = glium::VertexBuffer::new(&display, &teapot::VERTICES).unwrap();

モジュールファイルを使用しているので、「&teapot::VERTICES」と設定
します。


4 IndexBufferの設定(パッチプリミティブとパッチあたりの頂点数を設定)

let indices = glium::index::NoIndices(
    PrimitiveType::Patches { vertices_per_patch: 16 });

IndexBufferの設定は、1個のパッチ場合と同様です。これで全パッチの
描画が可能です。


5 シェーダプログラムの設定

let program = glium::Program::new(&display,
   glium::program::SourceCode {
      vertex_shader: "
         #version 140

         in vec3 position;

         void main() {
            gl_Position = vec4(position, 1.0);
         }
      ",
      tessellation_control_shader: Some("
         #version 400

         layout(vertices = 16) out;

         uniform int tess_level = 5;

         void main() {
            gl_out[gl_InvocationID].gl_Position =
               gl_in[gl_InvocationID].gl_Position;

            gl_TessLevelOuter[0] = tess_level;
            gl_TessLevelOuter[1] = tess_level;
            gl_TessLevelOuter[2] = tess_level;
            gl_TessLevelOuter[3] = tess_level;

            gl_TessLevelInner[0] = tess_level;
            gl_TessLevelInner[1] = tess_level;
         }
      "),
      tessellation_evaluation_shader: Some("
         #version 400

         layout(quads) in;

         out vec4 TEPosition;
         out vec3 TENormal;

         uniform mat4 model;
         uniform mat4 view;
         uniform mat4 perspective;

         void basisFunctions(out float[4] b, out float[4] db, float t) {
            float t1 = (1.0 - t);
            float t12 = t1 * t1;

            b[0] = t12 * t1;
            b[1] = 3.0 * t12 * t;
            b[2] = 3.0 * t1 * t * t;
            b[3] = t * t * t;

            db[0] = -3.0 * t1 * t1;
            db[1] = -6.0 * t * t1 + 3.0 * t12;
            db[2] = -3.0 * t * t + 6.0 * t * t1;
            db[3] = 3.0 * t * t;
         }

         void main() {
            float u = gl_TessCoord.x;
            float v = gl_TessCoord.y;

            vec4 p00 = gl_in[0].gl_Position;
            vec4 p01 = gl_in[1].gl_Position;
            vec4 p02 = gl_in[2].gl_Position;
            vec4 p03 = gl_in[3].gl_Position;
            vec4 p10 = gl_in[4].gl_Position;
            vec4 p11 = gl_in[5].gl_Position;
            vec4 p12 = gl_in[6].gl_Position;
            vec4 p13 = gl_in[7].gl_Position;
            vec4 p20 = gl_in[8].gl_Position;
            vec4 p21 = gl_in[9].gl_Position;
            vec4 p22 = gl_in[10].gl_Position;
            vec4 p23 = gl_in[11].gl_Position;
            vec4 p30 = gl_in[12].gl_Position;
            vec4 p31 = gl_in[13].gl_Position;
            vec4 p32 = gl_in[14].gl_Position;
            vec4 p33 = gl_in[15].gl_Position;

            float bu[4], bv[4];
            float dbu[4], dbv[4];
            basisFunctions(bu, dbu, u);
            basisFunctions(bv, dbv, v);

            TEPosition =
             p00*bu[0]*bv[0] + p01*bu[0]*bv[1] + p02*bu[0]*bv[2] +
             p03*bu[0]*bv[3] + p10*bu[1]*bv[0] + p11*bu[1]*bv[1] +
             p12*bu[1]*bv[2] + p13*bu[1]*bv[3] + p20*bu[2]*bv[0] +
             p21*bu[2]*bv[1] + p22*bu[2]*bv[2] + p23*bu[2]*bv[3] +
             p30*bu[3]*bv[0] + p31*bu[3]*bv[1] + p32*bu[3]*bv[2] +
             p33*bu[3]*bv[3];

            vec4 du =
             p00*dbu[0]*bv[0] + p01*dbu[0]*bv[1] +
             p02*dbu[0]*bv[2] + p03*dbu[0]*bv[3] +
             p10*dbu[1]*bv[0] + p11*dbu[1]*bv[1] +
             p12*dbu[1]*bv[2] + p13*dbu[1]*bv[3] +
             p20*dbu[2]*bv[0] + p21*dbu[2]*bv[1] +
             p22*dbu[2]*bv[2] + p23*dbu[2]*bv[3] +
             p30*dbu[3]*bv[0] + p31*dbu[3]*bv[1] +
             p32*dbu[3]*bv[2] + p33*dbu[3]*bv[3];

            vec4 dv =
             p00*bu[0]*dbv[0] + p01*bu[0]*dbv[1] +
             p02*bu[0]*dbv[2] + p03*bu[0]*dbv[3] +
             p10*bu[1]*dbv[0] + p11*bu[1]*dbv[1] +
             p12*bu[1]*dbv[2] + p13*bu[1]*dbv[3] +
             p20*bu[2]*dbv[0] + p21*bu[2]*dbv[1] +
             p22*bu[2]*dbv[2] + p23*bu[2]*dbv[3] +
             p30*bu[3]*dbv[0] + p31*bu[3]*dbv[1] +
             p32*bu[3]*dbv[2] + p33*bu[3]*dbv[3];

            vec3 n = normalize( cross(du.xyz, dv.xyz) );

            mat4 modelview = view * model;
            mat3 normal = mat3(modelview);

            gl_Position = perspective * modelview * TEPosition;

            TEPosition = modelview * TEPosition;
            TENormal = normalize( normal * n);
         }
      "),
      geometry_shader:  Some("
         #version 400

         layout(triangles) in;
         layout(triangle_strip, max_vertices = 3) out;

         in vec3 TENormal[];
         in vec4 TEPosition[];

         out vec3 Normal;
         out vec4 Position;

         void main() {
            Normal = TENormal[0];
            Position = TEPosition[0];
            gl_Position = gl_in[0].gl_Position;
            EmitVertex();

            Normal = TENormal[1];
            Position = TEPosition[1];
            gl_Position = gl_in[1].gl_Position;
            EmitVertex();

            Normal = TENormal[2];
            Position = TEPosition[2];
            gl_Position = gl_in[2].gl_Position;
            EmitVertex();

            EndPrimitive();
         }
      "),
      fragment_shader: "
         #version 140

         in vec3 Normal;
         in vec4 Position;
         out vec4 f_color;

         uniform vec3 LightPosition;
         uniform vec3 LightIntensity;
         uniform vec3 Ka;
         uniform vec3 Kd;
         uniform vec3 Ks;
         uniform float Shininess;

         void main() {
            vec3 s = normalize( LightPosition - Position.xyz);
            vec3 v = normalize(Position.xyz);
            vec3 r = reflect(-s, Normal);
            float sDotN = max( dot(s,Normal), 0.0 );

            vec3 ambient = Ka;
            vec3 diffuse = Kd * sDotN;
            vec3 specular = Ks * pow( max( dot(r,v), 0.0), Shininess);

            f_color = vec4(LightIntensity *(ambient + diffuse + specular), 0.1);
         }
      ",
   }).unwrap();

teapotの描画には、3次べジェ曲面を使います。
フォンモデルで描画するので、テッセレートされた各頂点での法線ベクトルも
必要になります。法線ベクトルは、補間式をテッセレーション変数u,vで微分し、
それらの外積から得られます。これらは、テッセレーション評価シェーダで
行います。
フォンモデルは、フラグメントシェーダで実装しています。


6 uniformの設定

let uniforms = uniform! {
   tess_level: tess_level,
   model: model,
   view: view,
   perspective: perspective,
   LightPosition: light_position,
   LightIntensity: light_intensity,
   Ka: ka,
   Kd: kd,
   Ks: ks,
   Shininess: shininess,
};

uniformの設定には、uniform!マクロを使います。コロンの左側がシェーダ内の
uniform変数で、右側がプログラム内で値を割り当てた変数束縛です。


7 描画

target.draw(&vertex_buffer, &indices, &program, &uniforms,
       &params).unwrap();

この例では、uniform変数と描画のパラメータ(DrawParameters)を使用して
います。
これらは、target.draw()文で、定義する変数束縛(uniformsとparams)を
設定します。

プロブラム(http://yahoo.jp/box/j6YVBz)

Rust glium Tessellation (2) 2D quad

glium テッセレーション

David Wolff著「OpenGL 4.0 シェーディング言語 ( OpenGL 4.0 Shading
Language Cookbook )」
第6章「ジオメトリシェーダとテッセレーションシェーダ」
「2D四角形をテッセレートする」

実行結果
f:id:onagat12:20170502132006p:plain tessellation level = 2
f:id:onagat12:20170502132017p:plain tessellation level = 4
f:id:onagat12:20170502132027p:plain tessellation level = 8
(Innerレベルは4に固定し、Outerレベルを変えている)

四角形の内部の点Pは、四角形の4隅(p0, p1, p2, p3)を制御点として、
次の補間式で表せる。u, vはパラメータで、0〜1の値をとる。

  P = p0 * (1-u) * (1-v)  + p1 * u * (1-v) +
      p2 * u * v + p3 * v * (1-u)
  制御点:p0 = [-0.8, -0.8], p1 = [ 0.8, -0.8], p2 = [ 0.8,  0.8],
         p3 = [-0.8,  0.8]

この制御点をパッチ プリミティブとして、テッセレーションを使って、
レベルに対応したu, vを生成し描画する。

1 制御点(コントールポイント)の設定

let vertex_buffer = {
   #[derive(Copy, Clone)]
   struct Vertex {
      position: [f32; 2],
   }

   implement_vertex!(Vertex, position);

   glium::VertexBuffer::new(&display,
      &[
         Vertex { position: [-0.8, -0.8] },
         Vertex { position: [ 0.8, -0.8] },
         Vertex { position: [ 0.8,  0.8] },
         Vertex { position: [-0.8,  0.8] },
      ]
   ).unwrap()
};

(内容は、前例と同様)


2 パッチ プリミティブとパッチあたりの頂点数を設定

let indices = glium::index::NoIndices(
    PrimitiveType::Patches { vertices_per_patch: 4 });

(内容は、前例と同様)


3 シェーダプログラムの設定

let program = glium::Program::new(&display,
  glium::program::SourceCode {
    vertex_shader: "
      #version 140

      in vec2 position;

      void main() {
        gl_Position = vec4(position, 0.0, 1.0);
      }
    ",
    tessellation_control_shader: Some("
      #version 400

      layout(vertices = 4) out;

      uniform int Outer = 5;
      uniform int Inner;

      void main() {
        gl_out[gl_InvocationID].gl_Position =
          gl_in[gl_InvocationID].gl_Position;

        gl_TessLevelOuter[0] = Outer;
        gl_TessLevelOuter[1] = Outer;
        gl_TessLevelOuter[2] = Outer;
        gl_TessLevelOuter[3] = Outer;

        gl_TessLevelInner[0] = Inner;
        gl_TessLevelInner[1] = Inner;
      }
    "),
    tessellation_evaluation_shader: Some("
      #version 400

      layout(quads, equal_spacing, ccw) in;

      void main() {
        float u = gl_TessCoord.x;
        float v = gl_TessCoord.y;

        vec4 p0 = gl_in[0].gl_Position;
        vec4 p1 = gl_in[1].gl_Position;
        vec4 p2 = gl_in[2].gl_Position;
        vec4 p3 = gl_in[3].gl_Position;

        gl_Position = p0 * (1-u) * (1-v)  + p1 * u * (1-v) +
              p2 * u * v + p3 * v * (1-u);
      }
    "),
    geometry_shader:  Some("
      #version 400

      layout(triangles) in;
      layout(triangle_strip, max_vertices = 3) out;

      out vec3 color;

      void main() {
         gl_Position = gl_in[0].gl_Position;
         color = vec3(1.0, 0.0, 0.0);
         EmitVertex();

         gl_Position = gl_in[1].gl_Position;
         color = vec3(1.0, 1.0, 0.0);
         EmitVertex();

         gl_Position = gl_in[2].gl_Position;
         color = vec3(0.0, 0.0, 1.0);
         EmitVertex();

         EndPrimitive();
      }
    "),
    fragment_shader: "
      #version 140

      in vec3 color;
      out vec4 f_color;

      void main() {
         f_color = vec4(color, 1.0);
      }
    ",
  }).unwrap();

この例ではジオメトリシェーダを使う。ジオメトリシェーダの設定にはOptionを
使うので、Some()で設定する。
テッセレーション座標u, vは、テッセレーション評価シェーダで、

 u = gl_TessCoord.x
 v = gl_TessCoord.y

の式で生成される。