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

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 import simd // バッファID番号 let ID_VERTEX:Int = 0 let ID_PROJECTION:Int = 1 // 1頂点情報の構造体 struct Vertex {

var pos:Float2 = Float2() var uv:Float2 = Float2() var rgba:Float4 = Float4() }

// パーティクル情報

struct Particle {

var pos:Float2 = Float2() // 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 render_circle:CAIMMetalRenderer?

private var mat:Matrix4x4 = .identity // 変換行列

private var circles = CAIMQuadrangles<Vertex>(count: 100) // 円用4頂点メッシュ群

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

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

DrawingViewController.swift

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

(5)

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

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

// 円描画の準備関数

private func setupCircles() {

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

render_circle = CAIMMetalRenderer(vertname:"vert2d", fragname:"fragCircleCosCurve") // アルファブレンドを有効にする

render_circle?.blendType = .alphaBlend

// デプスを無効にする

render_circle?.depthCompare = .always

render_circle?.depthEnabled = false

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

let wid = Float(CAIM.screenPixel.width) let hgt = Float(CAIM.screenPixel.height) for _ in 0 ..< circles.count {

var p:Particle = Particle()

p.pos = Float2(CAIM.random(wid), CAIM.random(hgt))

p.rgba = CAIMColor(CAIM.random(), CAIM.random(), CAIM.random(), CAIM.random()) p.radius = CAIM.random(100.0) p.life = CAIM.random() circle_parts.append(p) } } // 準備関数

override func setup() {

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

mat = Matrix4x4.pixelProjection(CAIM.screenPixel) // 円描画の準備

setupCircles() }

…(続く)…

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

setupCircles関数

を作成

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

vert2d

fragCircleCosCurve

をそのまま使う(後述)

(6)

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

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

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

private func updateCircles() { // パーティクル情報の更新

let wid = Float(CAIM.screenPixel.width) let hgt = Float(CAIM.screenPixel.height)

for i 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 = Float2(CAIM.random(wid), CAIM.random(hgt))

circle_parts[i].rgba = CAIMColor(CAIM.random(), CAIM.random(), CAIM.random(), CAIM.random()) circle_parts[i].radius = CAIM.random(100.0) circle_parts[i].life = 1.0 } } } …(続く)…

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

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

パーティクル情報から頂点情報の作成 (circle_parts → circles)

頂点CPUメモリからGPUバッファへ転送 (circles → circles.metalBuffer)

GPUバッファで画面描画

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

パーティクルを更新する

updateCircles()関数

を作る

(7)

Metal演習(1-4) : メッシュ情報の更新

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

// 円のパーティクル情報から頂点メッシュ情報を更新

private func genCirclesMesh() {

for i in 0 ..< circles.count {

// パーティクル情報を展開して、メッシュ情報を作る材料にする

let p = circle_parts[i]

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

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

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

var rgba = p.rgba // 色

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

// 四角形メッシュi個目の頂点0

circles[i][0].pos = Float2(x-r, y-r) circles[i][0].uv = Float2(-1.0, -1.0) circles[i][0].rgba = rgba.float4

// 四角形メッシュi個目の頂点1

circles[i][1].pos = Float2(x+r, y-r) circles[i][1].uv = Float2(1.0, -1.0) circles[i][1].rgba = rgba.float4

// 四角形メッシュi個目の頂点2

circles[i][2].pos = Float2(x-r, y+r) circles[i][2].uv = Float2(-1.0, 1.0) circles[i][2].rgba = rgba.float4

// 四角形メッシュi個目の頂点3

circles[i][3].pos = Float2(x+r, y+r) circles[i][3].uv = Float2(1.0, 1.0) circles[i][3].rgba = rgba.float4 } } …(続く)…

円パーティクル情報(circle_parts)からメッシュ情報(circles)を更新する

genCirclesMesh()関数

を作成

Particle内のlife値

を使って、

半径(r)

アルファ値(rgba.A)

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

(8)

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

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

// 円の描画

private func drawCircles(on metalView:CAIMMetalView)

{

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

render_circle?.beginDrawing(on: metalView)

// CPUメモリからGPUバッファを作成し、シェーダ番号をリンクする

render_circle?.link(circles.metalBuffer, to:.vertex, at:ID_VERTEX)

render_circle?.link(mat.metalBuffer, to:.vertex, at:ID_PROJECTION)

// GPU描画実行(circlesを渡すと四角形メッシュの中に丸を描く)

render_circle?.draw(circles)

}

// 繰り返し処理関数

override

func

update(metalView:

CAIMMetalView

) {

// 円情報の更新

updateCircles()

// 円情報で頂点メッシュ情報を更新

genCirclesMesh()

// 円の描画

drawCircles(on:metalView)

}

}

更新したメッシュ情報を使って、円を描画する

drawCircles(on metalView:)関数

を作成

(9)

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

#include <metal_stdlib> using namespace metal;

// バッファID番号

constant int ID_VERTEX = 0;

constant int ID_PROJECTION = 1;

// 入力頂点情報 struct VertexIn { float2 pos; float2 uv; 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 idx [[ vertex_id ]]) {

VertexOut vout;

// float2に、z=0,w=1を追加 → float4を作成し、行列を使って座標変換

vout.pos = proj_matrix * float4(vin[idx].pos, 0, 1); vout.uv = vin[idx].uv;

vout.rgba = vin[idx].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距離

float dist = sqrt(dist2);

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

if(dist >= 1.0) { discard_fragment(); }

// cosを用いて新しいアルファをもつ色情報をつくる(rgba[3]=アルファ)

float4 rgba = vout.rgba;

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

}

(10)

Metal演習(1-7) : 結果

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

(11)

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

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

(12)

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

import

UIKit

import

simd

…(省略)…

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

class

DrawingViewController :

CAIMMetalViewController

{

private

var

render_circle:

CAIMMetalRenderer

?

private var render_ring:CAIMMetalRenderer?

private

var

mat:

Matrix4x4

= .

identity

// 変換行列

private

var

circles =

CAIMQuadrangles

<

Vertex

>(count:

100

)

// 円用4頂点メッシュ群

private var rings = CAIMQuadrangles<Vertex>(count: 100)

// リング用4頂点メッシュ群

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

private

var

circle_parts = [

Particle

]()

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

private var ring_parts = [Particle]()

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

…(続く)…

DrawingViewController.swift

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

(13)

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

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

private func setupCircles() {

…(省略)…

}

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

private func setupRings() { // リング用のレンダラの作成

render_ring = CAIMMetalRenderer(vertname:"vert2d", fragname:"fragRing") // アルファブレンドを有効にする

render_ring?.blendType = .alphaBlend

// デプスを無効にする

render_ring?.depthCompare = .always

render_ring?.depthEnabled = false

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

let wid = Float(CAIM.screenPixel.width) let hgt = Float(CAIM.screenPixel.height) for _ in 0 ..< rings.count {

var p = Particle()

p.pos = Float2(CAIM.random(wid), CAIM.random(hgt))

p.rgba = CAIMColor(CAIM.random(), CAIM.random(), CAIM.random(), CAIM.random()) p.radius = CAIM.random(100.0)

p.life = CAIM.random()

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

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

(14)

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

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

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

private func updateRings()

{

let

wid =

Float

(

CAIM

.

screenPixel

.

width

)

let

hgt =

Float

(

CAIM

.

screenPixel

.

height

)

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

for

i

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 = Float2(CAIM.random(wid), CAIM.random(hgt))

ring_parts[i].rgba = CAIMColor(CAIM.random(), CAIM.random(), CAIM.random(), CAIM.random())

ring_parts[i].radius = CAIM.random(100.0)

ring_parts[i].life = 1.0

}

}

}

…(続く)…

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

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

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

(15)

Metal演習(2-4) : メッシュ情報の更新

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

// リングのパーティクル情報から頂点メッシュ情報を更新

private func genRingsMesh() { // リングの全ての点の情報を更新

for i in 0 ..< rings.count {

// パーティクル情報を展開して、メッシュ情報を作る材料にする

let p = ring_parts[i]

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

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

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

var rgba = p.rgba // 色

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

// 四角形メッシュi個目の頂点0

rings[i][0].pos = Float2(x-r, y-r) rings[i][0].uv = Float2(-1.0, -1.0) rings[i][0].rgba = rgba.float4

// 四角形メッシュi個目の頂点1

rings[i][1].pos = Float2(x+r, y-r) rings[i][1].uv = Float2(1.0, -1.0) rings[i][1].rgba = rgba.float4

// 四角形メッシュi個目の頂点2

rings[i][2].pos = Float2(x-r, y+r) rings[i][2].uv = Float2(-1.0, 1.0) rings[i][2].rgba = rgba.float4

// 四角形メッシュi個目の頂点3

rings[i][3].pos = Float2(x+r, y+r) rings[i][3].uv = Float2(1.0, 1.0) rings[i][3].rgba = rgba.float4 } } …(続く)…

Ringパーティクル情報(ring_parts)からメッシュ情報(rings)を更新する

genRingsMesh()関数

を作成

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

(16)

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

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

// リングの描画

private func drawRings(on metalView:CAIMMetalView) { // パイプライン(シェーダ)の切り替え

render_ring?.beginDrawing(on: metalView)

// CPUメモリからGPUバッファを作成し、シェーダ番号をリンクする

render_ring?.link(rings.metalBuffer, to:.vertex, at:ID_VERTEX) render_ring?.link(mat.metalBuffer, to:.vertex, at:ID_PROJECTION)

// GPU描画実行(ringsを渡すと四角形メッシュの中に丸を描く)

render_ring?.draw(rings)

}

// 繰り返し処理関数

override func update(metalView: CAIMMetalView) { // 円情報の更新

updateCircles()

// 円情報で頂点メッシュ情報を更新

genCirclesMesh() // 円の描画

drawCircles(on: metalView) // リング情報の更新 updateRings() // リング情報で頂点メッシュ情報を更新 genRingsMesh() // リングの描画 drawRings(on: metalView) } }

更新したメッシュ情報を使って、リングを描画する

drawRings(on metalView:)関数

を作成

(17)

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

…(省略)…

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

vertex

VertexOut vert2d(

device

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

constant

float4x4

&proj_matrix [[ buffer(ID_PROJECTION) ]],

uint

idx [[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株当たりの新株予約権の払

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