• 検索結果がありません。

caimmetal03.key

N/A
N/A
Protected

Academic year: 2021

シェア "caimmetal03.key"

Copied!
18
0
0

読み込み中.... (全文を見る)

全文

(1)

CAIM-Metal:03

Metalでパーティクルアニメーション

演習講師:渡邉 賢悟 (渡辺電気株式会社)

東京工科大学メディア学部

(2)

CAIM-Metalの準備

caimmetal03.zipをダウンロードして展開

(3)

03:1 毎フレームでの座標値更新

(アニメーションをつくる)

(4)

Metal演習(1-1) :パーティクル構造体の追加

import UIKit

// バッファID番号

let ID_VERTEX:Int = 0 let ID_PROJECTION:Int = 1 // 1頂点情報の構造体

struct VertexInfo : Initializable { var pos:Vec4 = Vec4()

var uv:Vec2 = Vec2()

var rgba:CAIMColor = CAIMColor() }

// パーティクル情報 struct Particle {

var pos:Vec2 = Vec2() // xy座標 var radius:Float = 0.0 // 半径

var rgba:CAIMColor = CAIMColor() // パーティクル色

var life:Float = 0.0 // パーティクルの生存係数(1.0~0.0) }

// CAIM-Metalを使うビューコントローラ

class DrawingViewController : CAIMMetalViewController

{

private var pl_circle:CAIMMetalRenderPipeline?

// GPU:バッファ

private var mat_buf:CAIMMetalBuffer? // 行列バッファ

private var circle_quads_buf:CAIMMetalBuffer? // 頂点バッファ(円を描く四角形)

// CPU:形状メモリ

private var circle_quads = CAIMQuadrangles<VertexInfo>() // 円用メモリ

// パーティクル情報配列

private var circle_parts = [Particle]() // 円用パーティクル情報

…(続く)…

DrawingViewController.swift

にパーティクル用の構造体(struct Particle)をつくる

(5)

Metal演習(1-2) : パーティクル準備関数の作成と呼び出し

…(前ページからのつづき)…

// 円描画の準備関数

private func setupCircles() {

// シェーダを指定してパイプラインの作成

pl_circle = CAIMMetalRenderPipeline(vertname:"vert2d", fragname:"fragCircleCosCurve")

pl_circle?.blend_type = .alpha_blend

// (GPUバッファ)頂点バッファ(四角形)の作成

circle_quads_buf = CAIMMetalBuffer(vertice:circle_quads)

// パーティクル情報配列を作る

let wid:Float = Float(CAIMScreenPixel.width)

let hgt:Float = Float(CAIMScreenPixel.height)

for _ in 0 ..< 100 {

var p:Particle = Particle()

p.pos = Vec2(CAIMRandom(wid), CAIMRandom(hgt))

p.rgba = CAIMColor(R: CAIMRandom(), G: CAIMRandom(), B: CAIMRandom(), A: CAIMRandom())

p.radius = CAIMRandom(100.0)

p.life = CAIMRandom()

circle_parts.append(p)

}

}

override func setup() {

// (GPUバッファ)ピクセルプロジェクション行列バッファの作成(画面サイズに合わせる)

mat_buf

=

CAIMMetalBuffer

(

Matrix4x4

.

pixelProjection

(

CAIMScreenPixel

))

// 円描画の準備

setupCircles()

}

…(続く)…

パーティクル構造体配列(circle_parts)を準備する

setupCircles関数

を作成

シェーダはCAIM-Metal02のShader.metalで作った

vert2d

fragCircleCosCurve

をそのまま使う(後述)

従来のsetup関数

内でsetupCircles()を呼ぶ

(6)

Metal演習(1-3) : パーティクル情報の更新

…(前ページからのつづき)…

// 円情報の更新

private func updateCircles() {

// パーティクル情報の更新

let wid:

Float

=

Float

(

CAIMScreenPixel

.

width

)

let hgt:

Float

=

Float

(

CAIMScreenPixel

.

height

)

for i:

Int

in 0 ..<

circle_parts

.

count

{

// パーティクルのライフを減らす(3秒間)

circle_parts[i].life -= 1.0 / (3.0 * 60.0)

// ライフが0以下になったら、新たなパーティクル情報を設定する

if(circle_parts[i].life <= 0.0) {

circle_parts[i].pos = Vec2(CAIMRandom(wid), CAIMRandom(hgt))

circle_parts[i].rgba = CAIMColor(R: CAIMRandom(), G: CAIMRandom(), B: CAIMRandom(), A: CAIMRandom())

circle_parts[i].radius = CAIMRandom(100.0)

circle_parts[i].life = 1.0

}

}

}

…(続く)…

update関数内で以下の作業を行いたい

アニメーションさせるため、パーティクル情報(circle_parts)を更新

パーティクル情報から頂点CPUメモリの作成 (circle_parts → circle_quads)

頂点CPUメモリからGPUバッファへ転送 (circle_quads → circle_quads_buf)

更新したGPUバッファで画面描画

上記処理を小さな関数に分割し、整理する

パーティクルを更新する

updateCircles()関数

を作る

(7)

Metal演習(1-4) : CPUメモリ,GPUバッファの更新

…(前ページからのつづき)…

// 円情報からCPUメモリの更新、GPUメモリに転送

private func genCirclesBuffer() {

// パーティクル配列からCPUメモリの作成(circle_parts -> circle_quads)

circle_quads.resize(count: circle_parts.count)

let p_circle_quads = circle_quads.pointer

for i:Int in 0 ..< circle_quads.count { // パーティクル情報を展開する

let p:Particle = circle_parts[i]

let x:Float = p.pos.x // x座標

let y:Float = p.pos.y // y座標

let r:Float = p.radius * (1.0 - p.life) // 半径(ライフが短いと半径が大きくなるようにする)

var rgba:CAIMColor = p.rgba // 色

rgba.A *= p.life // アルファ値の計算(ライフが短いと薄くなるようにする)

// 四角形頂点v0

p_circle_quads[i].v0.pos = Vec4(x-r, y-r, 0, 1) p_circle_quads[i].v0.uv = Vec2(-1.0, -1.0)

p_circle_quads[i].v0.rgba = rgba // 四角形頂点v1

p_circle_quads[i].v1.pos = Vec4(x+r, y-r, 0, 1) p_circle_quads[i].v1.uv = Vec2(1.0, -1.0)

p_circle_quads[i].v1.rgba = rgba // 四角形頂点v2

p_circle_quads[i].v2.pos = Vec4(x-r, y+r, 0, 1) p_circle_quads[i].v2.uv = Vec2(-1.0, 1.0)

p_circle_quads[i].v2.rgba = rgba // 四角形頂点v3

p_circle_quads[i].v3.pos = Vec4(x+r, y+r, 0, 1) p_circle_quads[i].v3.uv = Vec2(1.0, 1.0)

p_circle_quads[i].v3.rgba = rgba } // GPUバッファの内容を更新(circle_quads -> circle_quads_buf) circle_quads_buf?.update(vertice: circle_quads) } …(続く)…

円パーティクル情報(circle_parts)からCPUメモリ&GPUバッファを更新する

genCirclesBuffer()関数

を作成

Particle内のlife値

を使って、

半径(r)

アルファ値(rgba.A)

を変え、アニメーションを実現する

(8)

Metal演習(1-5) : 描画関数の作成・update関数の記述

…(前ページからのつづき)…

// 円の描画

private func drawCircles(renderer:CAIMMetalRenderer)

{

// パイプライン(シェーダ)の切り替え

renderer.

use

(

pl_circle

)

// 使用するバッファと番号をリンクする

renderer.

link

(

circle_quads_buf

!, to:.

vertex

, at:

ID_VERTEX

)

renderer.

link

(

mat_buf

!, to:.

vertex

, at:

ID_PROJECTION

)

// GPU描画実行(circle_quadsを渡すと四角形を描く)

renderer.

draw

(

circle_quads

)

}

// 繰り返し処理関数

override

func

update(renderer:

CAIMMetalRenderer

) {

// 円情報の更新

updateCircles()

// 円情報からGPUバッファを生成

genCirclesBuffer()

// 円の描画

drawCircles(renderer: renderer)

}

}

更新したGPUバッファを使って、円を描画する

drawCircles(renderer:)関数

を作成

(9)

Metal演習(1-6) : シェーダの準備

…(省略)…

// バッファID番号

constant int ID_VERTEX = 0;

constant int ID_PROJECTION = 1;

// 入力頂点情報 struct VertexIn { packed_float4 pos; packed_float2 uv; packed_float4 rgba; }; // 出力頂点情報 struct VertexOut {

float4 pos [[position]]; float2 uv;

float4 rgba; };

// 頂点シェーダ(2Dピクセル座標系へ変換)

vertex VertexOut vert2d(device VertexIn *vin [[ buffer(ID_VERTEX) ]],

constant float4x4 &proj_matrix [[ buffer(ID_PROJECTION) ]], uint vid [[vertex_id]])

{

VertexOut vout;

vout.pos = proj_matrix * float4(vin[vid].pos); vout.uv = vin[vid].uv;

vout.rgba = vin[vid].rgba; return vout;

}

// フラグメントシェーダ(Cosカーブを使って滑らかな変化の円を描く)

fragment float4 fragCircleCosCurve(VertexOut vout [[ stage_in ]]) { // 中心からのuv距離の二乗

float dist2 = vout.uv[0] * vout.uv[0] + vout.uv[1] * vout.uv[1];

// uv距離の二乗が1.0以上 = 円の外 (discard_fragment()を呼ぶとピクセルが破棄される)

if(dist2 >= 1.0) { discard_fragment(); } // 新しい色情報をつくる

float4 rgba = vout.rgba;

rgba[3] = vout.rgba[3] * (1.0 + cos(M_PI_F * dist2)) / 2.0; return rgba;

}

(10)

Metal演習(1-7) : 結果

アニメーションする100個のパーティクルが描画される

(11)

03:2 複数パイプラインによる描画

(シェーダを切り替える)

(12)

Metal演習(2-1) :リング描画の追加

import

UIKit

…(省略)…

// CAIM-Metalを使うビューコントローラ

class

DrawingViewController :

CAIMMetalViewController

{

private

var

pl_circle:

CAIMMetalRenderPipeline

?

// 円用のパイプライン

private var pl_ring:CAIMMetalRenderPipeline?

// リング用のパイプライン

// GPU:バッファ

private

var

mat_buf:

CAIMMetalBuffer

?

// 行列バッファ

private

var

circle_quads_buf:

CAIMMetalBuffer

?

// 頂点バッファ(円を描く四角形)

private var ring_quads_buf:CAIMMetalBuffer?

// 頂点バッファ(リングを描く四角形)

// CPU:形状メモリ

private

var

circle_quads =

CAIMQuadrangles

<

VertexInfo

>()

// 円用メモリ

private var ring_quads = CAIMQuadrangles<VertexInfo>()

// リング用メモリ

// パーティクル情報配列

private

var

circle_parts = [

Particle

]()

// 円用パーティクル情報

private var ring_parts = [Particle]()

// リング用パーティクル情報

…(続く)…

DrawingViewController.swift

にパイプラインを円に追加してリングを描画できるようにする

パイプライン、GPUバッファ、CPUメモリ、パーティクル情報をそれぞれ追加する

(13)

Metal演習(2-2) : パーティクル準備関数の作成と呼び出し

…(前ページからのつづき)…

private

func

setupCircles() {

(省略)…

}

// リング描画の準備関数

private func setupRings() {

// リング用のパイプラインの作成

pl_ring =

CAIMMetalRenderPipeline

(vertname:

"vert2d"

, fragname:

"fragRing"

)

pl_ring?.

blend_type

= .

alpha_blend

// (GPUバッファ)頂点バッファ(四角形)の作成

ring_quads_buf = CAIMMetalBuffer(vertice:ring_quads)

// リング用のパーティクル情報配列を作る

let

wid:

Float

=

Float

(

CAIMScreenPixel

.

width

)

let

hgt:

Float

=

Float

(

CAIMScreenPixel

.

height

)

for

_

in

0

..<

100

{

var

p:

Particle

=

Particle

()

p.

pos

=

Vec2

(

CAIMRandom

(wid),

CAIMRandom

(hgt))

p.

rgba

=

CAIMColor

(R:

CAIMRandom

(), G:

CAIMRandom

(), B:

CAIMRandom

(), A:

CAIMRandom

())

p.

radius

=

CAIMRandom

(

100.0

)

p.

life

=

CAIMRandom

()

ring_parts.

append

(p)

}

}

override

func

setup() {

// (GPUバッファ)ピクセルプロジェクション行列バッファの作成(画面サイズに合わせる)

mat_buf

=

CAIMMetalBuffer

(

Matrix4x4

.

pixelProjection

(

CAIMScreenPixel

))

// 円描画の準備

setupCircles

()

// リング描画の準備

setupRings()

}

…(続く)…

パーティクル構造体配列(rings_parts)を準備する

setupRings関数

を作成

シェーダはCAIM-Metal02のShader.metalで作った

vert2d

fragRing

を使う(※フラグメントシェーダが異なるパイプラインになる)

setup関数

内でsetupCircles()とともに

setupRings()も呼び出す

(14)

Metal演習(2-3) : パーティクル情報の更新

…(前ページからのつづき)…

// リング情報の更新

private func updateRings()

{

// パーティクル情報の更新

let

wid:

Float

=

Float

(

CAIMScreenPixel

.

width

)

let

hgt:

Float

=

Float

(

CAIMScreenPixel

.

height

)

// リング用のパーティクル情報の更新

for

i:

Int

in

0

..<

ring_parts.count

{

// パーティクルのライフを減らす(3秒間)

ring_parts[i].life -= 1.0 / (3.0 * 60.0)

// ライフが0以下になったら、新たなパーティクル情報を設定する

if

(

ring_parts[i].life <= 0.0

) {

ring_parts[i].pos = Vec2(CAIMRandom(wid), CAIMRandom(hgt))

ring_parts[i].rgba = CAIMColor(R: CAIMRandom(), G: CAIMRandom(), B: CAIMRandom(), A: CAIMRandom())

ring_parts[i].radius = CAIMRandom(100.0)

ring_parts[i].life = 1.0

}

}

}

…(続く)…

update関数内でringの更新処理を加える

作り方は基本的にCircleと同じ。updateRings()関数を作成

ring_parts[i].lifeの値を少しずつ減らし、0になったとき新たなパーティクルを再度設定するしくみ

(15)

Metal演習(2-4) : CPUメモリ,GPUバッファの更新

…(前ページからのつづき)…

// リング情報からCPUメモリの更新、GPUバッファへの転送

private func genRingsBuffer() {

// パーティクル配列からCPUメモリの作成(parts -> quads)

ring_quads.resize(count: ring_parts.count)

let p_ring_quads = ring_quads.pointer

for i:Int in 0 ..< ring_quads.count { // パーティクル情報を展開する

let p:Particle = ring_parts[i]

let x:Float = p.pos.x // x座標

let y:Float = p.pos.y // y座標

let r:Float = p.radius * (1.0 - p.life) // 半径(ライフが短いと半径が大きくなるようにする)

var rgba:CAIMColor = p.rgba // 色

rgba.A *= p.life // アルファ値の計算(ライフが短いと薄くなるようにする)

// 四角形頂点v0

p_ring_quads[i].v0.pos = Vec4(x-r, y-r, 0, 1) p_ring_quads[i].v0.uv = Vec2(-1.0, -1.0)

p_ring_quads[i].v0.rgba = rgba // 四角形頂点v1

p_ring_quads[i].v1.pos = Vec4(x+r, y-r, 0, 1) p_ring_quads[i].v1.uv = Vec2(1.0, -1.0)

p_ring_quads[i].v1.rgba = rgba // 四角形頂点v2

p_ring_quads[i].v2.pos = Vec4(x-r, y+r, 0, 1) p_ring_quads[i].v2.uv = Vec2(-1.0, 1.0)

p_ring_quads[i].v2.rgba = rgba // 四角形頂点v3

p_ring_quads[i].v3.pos = Vec4(x+r, y+r, 0, 1) p_ring_quads[i].v3.uv = Vec2(1.0, 1.0)

p_ring_quads[i].v3.rgba = rgba } // GPUバッファの内容を更新(quads -> quads_buf) ring_quads_buf?.update(vertice: ring_quads) } …(続く)…

Ringパーティクル情報(ring_parts)からCPUメモリ&GPUバッファを更新する

genRingsBuffer()関数

を作成

Particle内のlife変数の値を使って半径(r)とアルファ値(rgba.A)を変え、アニメーションを実現する

(16)

Metal演習(2-5) : 描画関数の作成・update関数の記述

…(前ページからのつづき)…

// リングの描画

private func drawRings(renderer:CAIMMetalRenderer) {

// パイプライン(シェーダ)の切り替え

renderer.use(pl_ring)

// 使用するバッファと番号をリンクする

renderer.

link

(ring_quads_buf!, to:.

vertex

, at:

ID_VERTEX

)

renderer.

link

(mat_buf!, to:.

vertex

, at:

ID_PROJECTION

)

// GPU描画実行(ring_quadsを渡すと四角形を描く)

renderer.

draw

(ring_quads)

}

// 繰り返し処理関数

override func update(renderer:

CAIMMetalRenderer

) {

// 円情報の更新

updateCircles

()

// 円情報からGPUバッファを生成

genCirclesBuffer

()

// 円の描画

drawCircles

(renderer: renderer)

// リング情報の更新

updateRings()

// リング情報からGPUバッファを生成

genRingsBuffer()

// リングの描画

drawRings(renderer: renderer)

}

}

更新したGPUバッファを使って、リングを描画する

drawRings(renderer:)関数

を作成

重要:パイプラインには

pl_ring

(=リングの描画)を用いる(補足: drawCirclesはpl_circle = 円の描画)

(17)

Metal演習(2-6) : シェーダの準備

…(省略)…

// 頂点シェーダ(2Dピクセル座標系へ変換)

vertex

VertexOut vert2d(

device

VertexIn *vin [[ buffer(ID_VERTEX) ]],

constant

float4x4

&proj_matrix [[ buffer(ID_PROJECTION) ]],

uint

vid [[vertex_id]])

{

…(省略)…

}

// フラグメントシェーダ(Cosカーブを使って滑らかな変化の円を描く)

fragment

float4

fragCircleCosCurve(VertexOut vout [[ stage_in ]]) {

…(省略)…

}

// フラグメントシェーダ(リングを描く)

fragment float4 fragRing(VertexOut vout [[ stage_in ]]) {

// 中心からのuv距離の二乗

float dist2 = vout.uv[0] * vout.uv[0] + vout.uv[1] * vout.uv[1];

// 中心からのuv距離

float dist = sqrt(dist2);

// uv距離が0.8以下か1.0以上ならピクセル破棄(リングの外)

if(dist <= 0.8 || 1.0 <= dist) { discard_fragment(); }

// リングの中心骨はdist2=0.9とする。kは中心骨との距離。これを10倍するとk=0.0~1.0になる。

// このkをcosに用いてリングを柔らかくする

float k = fabs(0.9 - dist) * 10.0;

// 新しい色情報をつくる

float4 rgba = vout.rgba;

rgba[3] = vout.rgba[3] * (1.0 + cos(M_PI_F * k)) / 2.0;

return rgba;

}

(18)

Metal演習(2-7) : 結果

アニメーションする円100個, リング100個のパーティクルが描画される

参照

関連したドキュメント

東京電力パワーグリッド株式会社 東京都千代田区 東電タウンプランニング株式会社 東京都港区 東京電設サービス株式会社

東電不動産株式会社 東京都台東区 株式会社テプコシステムズ 東京都江東区 東京パワーテクノロジー株式会社 東京都江東区

東京電力パワーグリッド株式会社 東京都千代田区 東電タウンプランニング株式会社 東京都港区 東京電設サービス株式会社

東電不動産株式会社 東京都台東区 株式会社テプコシステムズ 東京都江東区 東京パワーテクノロジー株式会社 東京都江東区

東電不動産株式会社 東京都台東区 東京発電株式会社 東京都台東区 株式会社テプコシステムズ 東京都江東区

4.「注記事項 連結財務諸表作成のための基本となる重要な事項 4.会計処理基準に関する事項 (8)原子力発 電施設解体費の計上方法

ⅴ)行使することにより又は当社に取得されることにより、普通株式1株当たりの新株予約権の払

ⅴ)行使することにより又は当社に取得されることにより、普通株式1株当たりの新株予約権の払