Rust Vulkano Triangle (2)
前回の Vulkano API を用いた Triangle 描画のまとめです。
Vulkan と Vulkano のチュートリアルサイト、Vulkano 関連のドキュメントを参考にしています。
・ Vulkan ( Introduction - Vulkan Tutorial )
・ Vulkano ( Vulkano )
・ Vulkano ドキュメント ( vulkano - Rust )
Vulkano による Triangle 描画の手順
1 インスタンスの作成
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)のドキュメントを参照して下さい。
1 インスタンスの作成
インスタンス(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を設定する。
(デバイス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) を追加しています。
実行結果
スタート時
zoom ( マウスホイールのアップ・ダウン )
rotation ( 左ボタンのドラッグ )
pan ( 右ボタンのドラッグ )
プログラム
#![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
実行直後は、オブジェクトが画面全体に広がっています。
オブジェクトの操作
・マウスホイールの上げ下げ:拡大縮小
・左ボタンのドラッグ;回転
・右ボタンのドラッグ:移動
primitives.rs
(縮小した状態からのアニメーションです。以下の例も同様)
custom_mesh_shared.rs
texturing.rs
obj.rs
Rust glium Tessellation (4) 3D surface
glium テッセレーション
David Wolff著「OpenGL 4.0 シェーディング言語 ( OpenGL 4.0 Shading
Language Cookbook )」
第6章「ジオメトリシェーダとテッセレーションシェーダ」
「3Dサーフェイスをテッセレートする」
「シェーディングしたメッシュの上にワイヤフレームを描く」
実行結果
tessellation level = 2
tessellation level = 4
tessellation level = 8
teapot多面体をテッセレーションを使って描画し、シェーディングした
メッシュの上にワイヤフレーム(エッジ)を描きます。
teapotの描画は、前回と同様です。
エッジの描画は次のようにします。
- メッシュ(三角形)の頂点と対辺との距離を求める。
- 描画するフラグメントが、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 )
実行結果
tessellation level = 2
tessellation level = 4
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を有効にする方法
- OpenGL contextを生成する文で、「.with_depth_buffer(24)」を指定します。
- 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, ¶ms).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四角形をテッセレートする」
実行結果
tessellation level = 2
tessellation level = 4
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
の式で生成される。
Rust glium Tessellation (1) Bezier Curve
glium(https://github.com/tomaka/glium)のexamplesにあるtessellation
プログラム(tessellation.rs)を参考にして、David Wolff著「OpenGL 4.0
シェーディング言語 ( OpenGL 4.0 Shading Language Cookbook )」の6章
「ジオメトリシェーダとテッセレーションシェーダ」をgliumで書いてみました。
今回は、「曲線をテッセレートする」の項です。テッセレーションの方法で、
曲線を描きます。
実行結果
tessellation level = 3
tessellation level = 5
tessellation level = 8
この例では、曲線の描画に3次べジェ曲線(ブレンド関数はベルンシュタイン
多項式)を使うので、4個の制御点(コントロールポイント)が必要です。
この4個の制御点がパッチ プリミティブになります。
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.4, 0.8] }, Vertex { position: [ 0.4, -0.8] }, Vertex { position: [ 0.8, 0.8] }, ] ).unwrap() };
VertexBufferを使ってコントロールポイントの頂点座標を設定します。
2 パッチ プリミティブとパッチあたりの頂点数を設定
let indices = glium::index::NoIndices( PrimitiveType::Patches { vertices_per_patch: 4 });
IndexBufferのPrimitiveTypeとvertices_per_patchで、パッチ プリミティブの
使用とパッチあたりの頂点数を設定します。
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 tess_level = 5; void main() { gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; gl_TessLevelOuter[0] = 1; gl_TessLevelOuter[1] = tess_level; } "), tessellation_evaluation_shader: Some(" #version 400 layout(isolines) in; void main() { float u = gl_TessCoord.x; vec3 p0 = gl_in[0].gl_Position.xyz; vec3 p1 = gl_in[1].gl_Position.xyz; vec3 p2 = gl_in[2].gl_Position.xyz; vec3 p3 = gl_in[3].gl_Position.xyz; float u1 = (1.0 - u); float u2 = u * u; float b3 = u2 * u; float b2 = 3.0 * u2 * u1; float b1 = 3.0 * u * u1 * u1; float b0 = u1 * u1 * u1; vec3 p = p0 * b0 + p1 * b1 + p2 * b2 + p3 * b3; gl_Position = vec4(p, 1.0); } "), geometry_shader: None, fragment_shader: " #version 140 out vec4 f_color; void main() { f_color = vec4(1.0, 1.0, 1.0, 1.0); } ", }).unwrap();
このプログラムでは、バーテックスシェーダ、テッセレーション制御シェーダ、
テッセレーション評価シェーダを使用します。このような場合は、Programの
SourceCodeを使って設定します。
テッセレーションシェーダとジオメトリシェーダはOptionとして設定するので、
None(使用しない)かSome()(使用する場合)で設定します。
曲線の描画では、Innerレベルは使用しません。Outerレベルでは、Outer[0]を1、
Outer[1]にレベルを設定します。GPUによっては、入れ替えが必要な場合が
あります。