スキニング用 weight/group は頂点(MeshVertex)への追加情報でした。
同じように MeshPolygon (Face) にはマテリアル情報が含まれています。
Maya では Object Material と Face Material の両方を参照する必要が
ありますが、Blender は Face Material だけで情報が取れます。
その代わり Blender には Material 無しのデータが存在できるようです。
この場合でも MeshPolygon.material_index には有効な値 (0) が
入ってしまうのでこれだけでは判定できません。
事前に Mesh.materials のデータ数を調べておき、Material が無い場合は
適当なデフォルトデータを設定しておく必要があります。
Texture は Material.texture_slots からたどることができます。
画像かつ外部ファイルと限定すると下記のように取れます。
実際の描画時は Material 単位に行うので、
ポリゴンの描画データは Material 単位に分類しておく必要があります。
最終的には Shader によって必要となるデータが変わるので、
事前に Material やデータ構造を調べて Shader の機能を分類し、
必要となる情報を export する流れとなります。
実際に頂点データを出力してみます。
Primitive は material で分割した Mesh です。
この中に頂点と index を格納していきます。
index 化のために辞書を使っているので、出力時は index 順に
並べ替える必要があります。
Primitive.getVertex() で配列を作り直しています。
MeshPolygon は 4角形以上の可能性があるので decode_shape() で
3角形に変換しています。
まだ Fan なのであまり良い変換ではありません。
法線以外の頂点要素や Material はまだ含まれていません。
今頃気が付きましたが 3ds Max のように Z-up なので、出力時に
Y-up に変換しておいた方がよさそうです。
関連エントリ
・Blender 2.6 の Export plug-in を作る (3) 出力と mathutils
・Blender 2.6 の Export plug-in を作る (2) データアクセス
・Blender 2.6 の Export plug-in を作る (1)
同じように MeshPolygon (Face) にはマテリアル情報が含まれています。
Table Table の IDが含まれる ------------------------------------------------------------ group Object.vertex_groups MeshVertex.groups[n].group material Mesh.materials MeshPolygon.material_index
Maya では Object Material と Face Material の両方を参照する必要が
ありますが、Blender は Face Material だけで情報が取れます。
その代わり Blender には Material 無しのデータが存在できるようです。
この場合でも MeshPolygon.material_index には有効な値 (0) が
入ってしまうのでこれだけでは判定できません。
事前に Mesh.materials のデータ数を調べておき、Material が無い場合は
適当なデフォルトデータを設定しておく必要があります。
def decode_material( obj ): mesh= obj.data if not mesh.materials: primt( 'Empty' ) else: for material in mesh.materials: print( material.name ) print( material.ambient ) print( material.diffuse_color ) print( material.specular_color ) decode_material( bpy.data.objects['Cube'] )
Texture は Material.texture_slots からたどることができます。
画像かつ外部ファイルと限定すると下記のように取れます。
def decode_material( obj ): mesh= obj.data if not mesh.materials: primt( 'Empty' ) else: for material in mesh.materials: print( material.name ) print( material.ambient ) print( material.diffuse_color ) print( material.specular_color ) for slotid, slot in enumerate( material.texture_slots ): if slot and material.use_textures[ slotid ] and slot.use: if slot.texture.type == 'IMAGE': image= slot.texture.image if image.source == 'FILE': print( 'Texture=', image.filepath ) decode_material( bpy.data.objects['Cube'] )
実際の描画時は Material 単位に行うので、
ポリゴンの描画データは Material 単位に分類しておく必要があります。
最終的には Shader によって必要となるデータが変わるので、
事前に Material やデータ構造を調べて Shader の機能を分類し、
必要となる情報を export する流れとなります。
実際に頂点データを出力してみます。
Primitive は material で分割した Mesh です。
この中に頂点と index を格納していきます。
index 化のために辞書を使っているので、出力時は index 順に
並べ替える必要があります。
Primitive.getVertex() で配列を作り直しています。
MeshPolygon は 4角形以上の可能性があるので decode_shape() で
3角形に変換しています。
まだ Fan なのであまり良い変換ではありません。
法線以外の頂点要素や Material はまだ含まれていません。
# io_export_flatlib_test_a6.py import bpy import math from bpy_extras.io_utils import ExportHelper from bpy.props import BoolProperty from mathutils import Matrix, Vector, Color bl_info= { "name": "Export Test flatlib a6", "author": "Hiroyuki Ogasawara", "version": (1, 0, 0), "blender": (2, 6, 5), "location": "File > Export > flatlib (.a6)", "category": "Import-Export", } class Vertex: def __init__( self, pos ): self.pos= Vector( pos ) self.vbid= 0 def setNormal( self, normal ): self.normal= normal def setVBID( self, id ): self.vbid= id def getVBID( self ): return self.vbid def __hash__( self ): return int( math.frexp( self.pos.dot( self.normal ) )[0] * 0x7fffffff ) def __eq__( self, ver ): return self.pos == ver.pos and self.normal == ver.normal class Primitive: def __init__( self, material_id ): self._vertexBuffer= {} self._indexBuffer= [] self._material_id= material_id def addVertex( self, vertex ): if vertex in self._vertexBuffer: return vertex.getVBID() vertex.setVBID( len(self._vertexBuffer) ) self._vertexBuffer[ vertex ]= vertex return vertex.getVBID() def addIndex( self, index ): self._indexBuffer.append( index ) def getVertexBuffer( self ): mapbuf= [ None ] * len( self._vertexBuffer ) for vertex in self._vertexBuffer: mapbuf[ vertex.getVBID() ]= vertex return mapbuf def getIndexBuffer( self ): return self._indexBuffer class Node: def __init__( self, obj ): self._obj= obj def getName( self ): return self._obj.name def getParent( self ): return self._obj.parent.name if self._obj.parent else None def getMatrix( self ): return self._obj.matrix_local def isMesh( self ): return False class Shape(Node): def __init__( self, obj ): self._obj= obj self._primitive= {} def getMesh( self ): return self._obj.data def createPrimitive( self, material_id ): self._primitive[material_id]= Primitive( material_id ) def getPrimitive( self, material_id ): return self._primitive[material_id] def getPrimitiveList( self ): return self._primitive.values() def isMesh( self ): return True class A6Export: def __init__( self, options ): self.options= options def decode_tree( self, tree, obj ): tree.append( Shape( obj ) if obj.type == 'MESH' else Node( obj ) ) for child in obj.children: self.decode_tree( tree, child ) def decode_node( self ): tree= [] for obj in bpy.data.objects: if not obj.parent: if obj.type == 'MESH' or obj.type == 'EMPTY' or obj.type == 'ARMATURE': self.decode_tree( tree, obj ) return tree def decode_material( self, tree ): for node in tree: mesh= node.getMesh() materiallist= {} if not mesh.materials: node.createPrimitive( 0 ) else: for poly in mesh.polygons: materiallist[ poly.material_index ]= poly.material_index for material_id in materiallist.values(): node.createPrimitive( material_id ) def decode_shape( self, tree ): ver_index= [ None, None, None ] for node in tree: mesh= node.getMesh() for poly in mesh.polygons: prim= node.getPrimitive( poly.material_index ) triangle= 0 for id in range( poly.loop_start, poly.loop_start + poly.loop_total ): vertex= mesh.vertices[ mesh.loops[id].vertex_index ] ver= Vertex( vertex.co ) ver.setNormal( vertex.normal if poly.use_smooth else poly.normal ) ver_index[triangle]= ver triangle+= 1 if triangle >= 3: prim.addIndex( prim.addVertex( ver_index[0] ) ) prim.addIndex( prim.addVertex( ver_index[1] ) ) prim.addIndex( prim.addVertex( ver_index[2] ) ) triangle= 2 ver_index[1]= ver_index[2] def output_geometry( self, fo, tree ): fo.write( "#\nT \"Geometry\"\n" ) for gid,node in enumerate(tree): fo.write( "N \"%s\"\n" % node.getName() ) if node.getParent(): fo.write( "S \"Parent\" \"%s\"\n" % node.getParent() ) m= node.getMatrix() fo.write( "I \"ID\" %d\n" % gid ) fo.write( "F \"Matrix\"\n" ) fo.write( "F %f %f %f %f\n" % (m[0][0],m[1][0],m[2][0],m[3][0]) ) fo.write( "F %f %f %f %f\n" % (m[0][1],m[1][1],m[2][1],m[3][1]) ) fo.write( "F %f %f %f %f\n" % (m[0][2],m[1][2],m[2][2],m[3][2]) ) fo.write( "F %f %f %f %f\n" % (m[0][3],m[1][3],m[2][3],m[3][3]) ) def output_vertex( self, fo, tree ): fo.write( "#\nT \"Vertex\"\n" ) for nodeid, node in enumerate( tree ): for primid, prim in enumerate( node.getPrimitiveList() ): fo.write( "N \"_vb%03d_%03d\"\n" % (nodeid, primid) ) buffer= prim.getVertexBuffer() stride= 24 fo.write( "I \"Size\" %d %d\n" % (len(buffer), stride) ) fo.write( "M \"v\"\n" ) for vertex in buffer: fo.write( "F %f %f %f\n" % (vertex.pos.x, vertex.pos.y, vertex.pos.z) ) fo.write( "F %f %f %f\n" % (vertex.normal.x, vertex.normal.y, vertex.normal.z) ) def output_index( self, fo, tree ): fo.write( "#\nT \"Index\"\n" ) for nodeid, node in enumerate( tree ): for primid, prim in enumerate( node.getPrimitiveList() ): fo.write( "N \"_ib%03d_%03d\"\n" % (nodeid, primid) ) fo.write( "I \"Size\" %d\n" % len(prim.getIndexBuffer()) ) fo.write( "I \"i\"\n" ) indexlist= prim.getIndexBuffer() for tid in range( 0, len(indexlist), 3 ): fo.write( "I %d %d %d\n" % (indexlist[tid], indexlist[tid+1], indexlist[tid+2] ) ) def output_shape( self, fo, tree ): fo.write( "#\nT \"Command\"\n" ) for nodeid, node in enumerate( tree ): for primid, prim in enumerate( node.getPrimitiveList() ): fo.write( "N \"_pr%03d_%03d\"\n" % (nodeid, primid) ) fo.write( "S \"Geometry\" \"%s\"\n" % node.getName() ) fo.write( "S \"Vertex\" \"_vb%03d_%03d\"\n" % (nodeid, primid) ) fo.write( "S \"Index\" \"_ib%03d_%03d\"\n" % (nodeid, primid) ) fo.write( "S \"Material\" \"unknown\"\n" ) def output_material( self, fo, tree ): pass def export( self, filename ): print( self.options ) tree= self.decode_node() meshlist= [ node for node in tree if node.isMesh()] self.decode_material( meshlist ) self.decode_shape( meshlist ) # fo= open( filename, 'w' ) self.output_material( fo, meshlist ) self.output_geometry( fo, tree ) self.output_vertex( fo, meshlist ) self.output_index( fo, meshlist ) self.output_shape( fo, meshlist ) fo.close() class ExportTestA6( bpy.types.Operator, ExportHelper ): bl_label= 'export flatlib a6' bl_idname= 'export.flatlib_a6' filename_ext= '.a6' flag_selected= BoolProperty( name= "Selected Objects", description= "Export selected objects", default= False ) flag_lefthand= BoolProperty( name= "Left-Hand (DirectX)", description= "Lfef-Hand (DirectX)", default= True ) def execute( self, context ): A6Export( self ).export( self.filepath ) return {'FINISHED'} def menu_func(self, context): self.layout.operator( ExportTestA6.bl_idname, text="flatlib a6 (.a6)" ) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_file_export.remove(menu_func) def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_file_export.append(menu_func)
今頃気が付きましたが 3ds Max のように Z-up なので、出力時に
Y-up に変換しておいた方がよさそうです。
関連エントリ
・Blender 2.6 の Export plug-in を作る (3) 出力と mathutils
・Blender 2.6 の Export plug-in を作る (2) データアクセス
・Blender 2.6 の Export plug-in を作る (1)
スキニング用のジオメトリ情報を調べます。
頂点自体に、影響を受けるジオメトリの番号 (vertex group)と重みのセットが
必要個数含まれています。
vertex group の番号が実際に指しているジオメトリの情報は
Object に入っています。
Object ローカルに影響を受けるジオメトリセットがあり、
その index 番号 (group 番号) が頂点に格納されています。
ゲームなどリアルタイム描画時の構造と同じなので、非常にわかりやすく
扱いやすい形式になっているようです。
実際に影響を与えるジオメトリは Armature と呼ばれているようです。
Modifier の扱いとなっており Subdivision Surface 等と同じです。
Object から Modifier を列挙できるため、その中から 'ARMATURE' を
取り出すことができます。
通常の Node と同じだろうと思っていたのですが構造が少々違うようです。
Bone 周りは Blender の機能やデータ構造を知らないといけないので
正直よく理解していません。
なので後回しにします。
●ファイル出力
実際に addon としてデータを出力してみます。
exporter 本体を A6Export() として別に作成し呼び出す形に。
a6 は独自フォーマットの名称で CPU とかは無関係です。
まずは階層構造の解析と matrix の出力のみ。
読みだしたあとの Node を格納するコンテナが Shape です。
●コマンドとして呼び出す
addon として上記 io_export_test_a6.py を読み込むと
File → Export のメニューに組み込まれますが、これをコマンドとして
呼び出すこともできます。
Python Console で実行でき、またダイアログを経由しないので便利です。
● mathutils
頂点情報や Matrix には mathutils が使われています。
今まで出てきたのは Matrix, Vector, Color の 3種類です。
・Math Types & Utilities (mathutils)
Vector は shader のような swizzle が可能で、上のページを見ると
xxxx ~ wwww まですべての組み合わせが定義されていることがわかります。
例えば dest.xyz= src.zyx などができるようです。
Color や Matrix には swizzle がないので color.rg のような記述はできません。
それぞれ配列のようにシーケンス型として扱うこともできます。
hlsl 等の shader の場合 swizzle も代入も同一です。
上の (1), (2), (3) は全く同じもので値の代入です。
特に Shader Assembler では swizzle と mask の違いを明確にするため
あえて (1) の表記を用いることがあります。
Vector は python のオブジェクトなので、代入は参照であり
swizzle は copy になります。
(4) は dest に src の参照が入ります。
(5) は新 object を生成して copy します。
src.xyz の代わりに src.copy() や src[:] と記述した場合も同様です。
(6) は値代入なので dest と src は別物です。新たな object が作られません。
場合によっては (6) が欲しいのですが immutable なら (4) で十分で
むしろ効率が良くなります。
Vector 同士の比較には注意が必要です。
(7),(8) の結果を見ると (9) が成り立つはずですが False になります。
一致判定は Vector 個々の値を比較しますが、
大小判定には Vecotr の長さが用いられているためです。
つまり (7),(8) は下記の (10),(11) と同じです。
(9) も .length をつければ True になります。
頂点の検索アルゴリズムを作る場合このあたり注意が必要です。
●頂点のコンテナ
頂点をマージして index を作るためにコンテナを作ります。
検索は極めて重要です。
数百万頂点など大きなモデルデータを export した場合に終わらなくなります。
hash はもっと改良できると思いますがとりあえず key として使えました。
比較に関しても最終的にはもっと考慮が必要で、例えば clamp 処理が入ります。
続きます
関連エントリ
・Blender 2.6 の Export plug-in を作る (2) データアクセス
・Blender 2.6 の Export plug-in を作る (1)
def decode_shape2( obj ): mesh= obj.data for poly in mesh.polygons: for id in range( poly.loop_start, poly.loop_start + poly.loop_total ): vertex= mesh.vertices[ mesh.loops[ id ].vertex_index ] for vgroup in vertex.groups: print( vgroup.group, vgroup.weight ) shape( bpy.data.objects['Cube'] )
頂点自体に、影響を受けるジオメトリの番号 (vertex group)と重みのセットが
必要個数含まれています。
vertex group の番号が実際に指しているジオメトリの情報は
Object に入っています。
def decode_shape1( obj ): if len(obj.vertex_groups): for vgroup in obj.vertex_groups: print( vgroup.index, ':', vgroup.name )
Object ローカルに影響を受けるジオメトリセットがあり、
その index 番号 (group 番号) が頂点に格納されています。
ゲームなどリアルタイム描画時の構造と同じなので、非常にわかりやすく
扱いやすい形式になっているようです。
実際に影響を与えるジオメトリは Armature と呼ばれているようです。
Modifier の扱いとなっており Subdivision Surface 等と同じです。
Object から Modifier を列挙できるため、その中から 'ARMATURE' を
取り出すことができます。
def decode_shape3( obj ): for modifier in obj.modifiers if modifier.type == 'ARMATURE': armature_object= mod.object
通常の Node と同じだろうと思っていたのですが構造が少々違うようです。
Bone 周りは Blender の機能やデータ構造を知らないといけないので
正直よく理解していません。
なので後回しにします。
●ファイル出力
実際に addon としてデータを出力してみます。
# io_export_test_a6.py import bpy from bpy_extras.io_utils import ExportHelper from bpy.props import BoolProperty from mathutils import Vector, Matrix, Color bl_info= { "name": "Export flatlib a6", "category": "Import-Export", } class ExportTestA6( bpy.types.Operator, ExportHelper ): bl_label= 'export flatlib a6' bl_idname= 'export.flatlib_a6' filename_ext= '.a6' def execute( self, context ): A6Export( self ).export( self.filepath ) return {'FINISHED'} def menu_func(self, context): self.layout.operator( ExportTestA6.bl_idname, text="Export flatlib a6 (.a6)" ) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_file_export.remove(menu_func) def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_file_export.append(menu_func)
exporter 本体を A6Export() として別に作成し呼び出す形に。
a6 は独自フォーマットの名称で CPU とかは無関係です。
class A6Export: def __init__( self, options ): self.options= options def decode_tree( self, tree, obj ): tree.append( Shape( obj ) ) for child in obj.children: self.decode_tree( tree, child ) def decode_node( self ): tree= [] for obj in bpy.data.objects: if not obj.parent: if obj.type == 'MESH' or obj.type == 'EMPTY' or obj.type == 'ARMATURE': self.decode_tree( tree, obj ) return tree def decode_shape( self, tree ): pass def output_geometry( self, fo, tree ): fo.write( "T \"Geometry\"\n" ) for gid,node in enumerate(tree): fo.write( "N \"%s\"\n" % node.getName() ) if node.getParent(): fo.write( "S \"Parent\" \"%s\"\n" % node.getParent() ) m= node.getMatrix() fo.write( "I \"ID\" %d\n" % gid ) fo.write( "F \"Matrix\"\n" ) fo.write( "F %f %f %f %f\n" % (m[0][0],m[1][0],m[2][0],m[3][0]) ) fo.write( "F %f %f %f %f\n" % (m[0][1],m[1][1],m[2][1],m[3][1]) ) fo.write( "F %f %f %f %f\n" % (m[0][2],m[1][2],m[2][2],m[3][2]) ) fo.write( "F %f %f %f %f\n" % (m[0][3],m[1][3],m[2][3],m[3][3]) ) def export( self, filename ): print( self.options ) tree= self.decode_node() self.decode_shape( tree ) fo= open( filename, 'w' ) self.output_geometry( fo, tree ) fo.close()
まずは階層構造の解析と matrix の出力のみ。
読みだしたあとの Node を格納するコンテナが Shape です。
class Shape: def __init__( self, obj ): self._obj= obj def getMatrix( self ): return self._obj.matrix_local def getName( self ): return self._obj.name def getParent( self ): return self._obj.parent.name if self._obj.parent else None
●コマンドとして呼び出す
addon として上記 io_export_test_a6.py を読み込むと
File → Export のメニューに組み込まれますが、これをコマンドとして
呼び出すこともできます。
>>> bpy.ops.export.flatlib_a6( filepath="output.a6" )
Python Console で実行でき、またダイアログを経由しないので便利です。
● mathutils
頂点情報や Matrix には mathutils が使われています。
今まで出てきたのは Matrix, Vector, Color の 3種類です。
・Math Types & Utilities (mathutils)
Vector は shader のような swizzle が可能で、上のページを見ると
xxxx ~ wwww まですべての組み合わせが定義されていることがわかります。
例えば dest.xyz= src.zyx などができるようです。
Color や Matrix には swizzle がないので color.rg のような記述はできません。
それぞれ配列のようにシーケンス型として扱うこともできます。
hlsl 等の shader の場合 swizzle も代入も同一です。
float3 dest, src; dest.xyz= src.xyz; // -- (1) dest= src; // -- (2) dest.x= src.x; // | dest.y= src.y; // |-- (3) dest.z= src.z; // |
上の (1), (2), (3) は全く同じもので値の代入です。
特に Shader Assembler では swizzle と mask の違いを明確にするため
あえて (1) の表記を用いることがあります。
Vector は python のオブジェクトなので、代入は参照であり
swizzle は copy になります。
from mathutils import Vector src= Vector((1,2,3)) dest= Vector((4,5,6)) dest= src // -- (4) (dest is src) == True dest.xyz= src.xyz // -- (5) (dest is src) == False dest.x= src.x // | dest.y= src.y // |-- (6) (dest is src) == False dest.z= src.z // |
(4) は dest に src の参照が入ります。
(5) は新 object を生成して copy します。
src.xyz の代わりに src.copy() や src[:] と記述した場合も同様です。
(6) は値代入なので dest と src は別物です。新たな object が作られません。
場合によっては (6) が欲しいのですが immutable なら (4) で十分で
むしろ効率が良くなります。
Vector 同士の比較には注意が必要です。
Vector((1,2,3)) >= Vector((3,2,1)) # = True -- (7) Vector((1,2,3)) > Vector((3,2,1)) # = False -- (8) Vector((1,2,3)) == Vector((3,2,1)) # = False -- (9)
(7),(8) の結果を見ると (9) が成り立つはずですが False になります。
一致判定は Vector 個々の値を比較しますが、
大小判定には Vecotr の長さが用いられているためです。
つまり (7),(8) は下記の (10),(11) と同じです。
Vector((1,2,3)).length >= Vector((3,2,1)).length # = True -- (10) Vector((1,2,3)).length > Vector((3,2,1)).length # = False -- (11)
(9) も .length をつければ True になります。
Vector((1,2,3)).length == Vector((3,2,1)).length # = True -- (12)
頂点の検索アルゴリズムを作る場合このあたり注意が必要です。
●頂点のコンテナ
頂点をマージして index を作るためにコンテナを作ります。
検索は極めて重要です。
数百万頂点など大きなモデルデータを export した場合に終わらなくなります。
class Vertex: def __init__( self, pos, normal ): self.pos= Vector( pos ) self.normal= Vector( normal ) def __hash__( self ): return int( frexp( self.pos.dot( self.normal ) )[0] * 0x7fffffff ) def __eq__( self, ver ): return self.pos == ver.pos and self.normal == ver.normal
hash はもっと改良できると思いますがとりあえず key として使えました。
比較に関しても最終的にはもっと考慮が必要で、例えば clamp 処理が入ります。
>>> a={ Vertex(Vector((1,2,3)),Vector((0,1,1))): 123, Vertex(Vector((3,4,5)),Vector((1,0,1))): 456 } >>> a[ Vertex(Vector((1,2,3)),Vector((0,1,1))) ] 123
続きます
関連エントリ
・Blender 2.6 の Export plug-in を作る (2) データアクセス
・Blender 2.6 の Export plug-in を作る (1)
2012/12/28
Blender 2.6 の Export plug-in を作る (2) データアクセス
Blender の API は bpy で始まり、データアクセス手段は
bpy.data 以下に集約されています。
例えばシーン内のオブジェクトは bpy.data.objects で取れます。
下記のように Python Console で確認できます。
(太字がキー入力です)
↑Python の辞書(連想配列)のように見えますがちょっと違います。
↑数値 index でもアクセス可能で要素の順番が固定。
find() を使うと index を取ることが可能です。
特に for in では要素が列挙され、配列のような振る舞いをします。
key を持つがシーケンスとして扱える特殊な collection となっています。
python に慣れてないせいもありますが、
辞書のつもりで script を読んでいると少々混乱します。
● Object Type
Object の種類は type プロパティで確認できます。
bpy.types にデータの種類一覧があります。
・Types (bpy.types)
この bpy.types が Maya でいう Node に相当するもののようです。
例えば 'Cube' は bpy.types.Mesh で、独自のインスタンスととデータは
Object.data からアクセスします。
Object 自体が階層構造を持てるので DagNode を兼ねています。
Maya ではインスタンスである Object とメソッドでである Function が
分離していましたが、Blender はそのようなことがなくシンプルです。
Object の階層は下記のようにたどることができます。
Maya のように Transform Node が別だったりしません。
root の object だけ得るには
[obj for obj in bpy.data.objects if not obj.parent]
で出来ます。
bpy.data.objects はすべてのオブジェクトを列挙しますが、
同時に data type ごとの collection も持っているようです。
bpy.data.meshes に入っているのはデータそのもの、Object.data です。
こちらは階層などの Node 情報を含んでいません。
Object を削除しても Mesh は残っているようです。
この辺の Blender の内部挙動はまだ私の方がきちんと理解しておりません。
matrix_local はドキュメント Object(ID) では float array
と書かれてますが mathutils.Matrix を返すようです。
● Mesh のアクセス方法
Mesh には形状データが入っています。
頂点データ自体は Mesh.vertices ですが、下記ページの解説にあるように
edge, loop, polygon と 3種類のインデックスがあります。
・Mesh(ID)
・MeshVertex : 頂点データそのもの
・MeshEdge : 2頂点の index
・MeshLoop : 1頂点 + edge の index
・MeshPolygon : n個の loop の index
Mesh 内の MeshPolygon (Face) の列挙は下記の通り。
MeshPolygon に含まれる頂点 (Face Vertex) を得るには下記の通り。
MeshPolygon に含まれる MeshLoop をたどって MeshLoop.vertex_index を
参照しています。
なお、Loop を使わなくても MeshPolygon.vertices で直接頂点 index を
取れることがわかりました。
ただし、mesh.loops の参照用 index 値 (「Loop を使った場合」の id ) は
uv など他のデータアクセスにも使います。
loop は Face Vertex 単位のデータとなり、Cube の場合 24 個あります。
一見複雑ですが「Loop を使った場合」の方が都合が良いようです。
● 法線
頂点データ MeshVertex に直接頂点法線が格納されています。
頂点座標が共有される場合法線も共有されており、この値は
ハードエッジになりません。
頂点とは別に MeshPolygon に面法線が格納されており、プロパティ
Mesh.use_smooth の値によってこの両者を使い分ける必要があるようです。
● UV / 頂点カラー
Mesh.uv_layers に格納されます。値そのものは MeshUVLoop.uv です。
uv_layers が MeshUVLoopLayer で data に MeshUVLoop が頂点分 (Loop 数分)
格納されます。
頂点カラーも全く同じで Mesh.vertex_colors に入ります。
今までの情報を取り出すコードがこれです。
それぞれ最初の(active な) UV set と VertexColor しか参照していないので注意してください。
uv_array, vcolor_array の参照に Loop と同じ id を使っていることがわかります。
Matrix 同様、ドキュメントではただの array ですが実際に返す値は
mathutils.Vector, mathutils.Color となっています。
続きます
関連エントリ
・Blender 2.6 の Export plug-in を作る (1)
bpy.data 以下に集約されています。
例えばシーン内のオブジェクトは bpy.data.objects で取れます。
下記のように Python Console で確認できます。
(太字がキー入力です)
>>> bpy.data.objects['Cube'] bpy.data.objects['Cube'] >>> bpy.data.objects.keys() ['Camera', 'Cube', 'Lamp'] >>> bpy.data.objects.values() [bpy.data.objects['Camera'], bpy.data.objects['Cube'], bpy.dta.objects['Lamp']]
↑Python の辞書(連想配列)のように見えますがちょっと違います。
>>> bpy.data.objects <bpy_collection[3], BlendDataObjects> >>> bpy.data.objects[0] bpy.data.objects['Camera'] >>> bpy.data.objects.find('Cube') 1 >>> bpy.data.objects[1:] [bpy.data.objects['Cube'], bpy.dta.objects['Lamp']]
↑数値 index でもアクセス可能で要素の順番が固定。
find() を使うと index を取ることが可能です。
特に for in では要素が列挙され、配列のような振る舞いをします。
>>> for a in bpy.data.objects: print( a ) <bpy_struct, Object("Camera")> <bpy_struct, Object("Cube")> <bpy_struct, Object("Lamp")>
key を持つがシーケンスとして扱える特殊な collection となっています。
python に慣れてないせいもありますが、
辞書のつもりで script を読んでいると少々混乱します。
● Object Type
Object の種類は type プロパティで確認できます。
>>> for a in bpy.data.objects: print( a.type ) CAMERA MESH LAMP
bpy.types にデータの種類一覧があります。
・Types (bpy.types)
この bpy.types が Maya でいう Node に相当するもののようです。
例えば 'Cube' は bpy.types.Mesh で、独自のインスタンスととデータは
Object.data からアクセスします。
>>> bpy.data.objects['Cube'].data.vertices[1].co Vector((1.0, -1.0, -1.0))
Object 自体が階層構造を持てるので DagNode を兼ねています。
Maya ではインスタンスである Object とメソッドでである Function が
分離していましたが、Blender はそのようなことがなくシンプルです。
Object の階層は下記のようにたどることができます。
Maya のように Transform Node が別だったりしません。
def tree( obj ): print( obj ) print( obj.matrix_local ) for child in obj.children: tree( child ) def root(): for obj in bpy.data.objects: if not obj.parent: tree( obj ) root()
root の object だけ得るには
[obj for obj in bpy.data.objects if not obj.parent]
で出来ます。
bpy.data.objects はすべてのオブジェクトを列挙しますが、
同時に data type ごとの collection も持っているようです。
>>> bpy.data.objects.keys() ['Camera', 'Cube', 'Lamp', 'Sphere', 'Suzanne'] >>> bpy.data.meshes.keys() ['Cube', 'Sphere', 'Suzanne']
bpy.data.meshes に入っているのはデータそのもの、Object.data です。
こちらは階層などの Node 情報を含んでいません。
>>> bpy.data.meshes['Cube'].vertices[1].co Vector((1.0, -1.0, -1.0))
Object を削除しても Mesh は残っているようです。
この辺の Blender の内部挙動はまだ私の方がきちんと理解しておりません。
matrix_local はドキュメント Object(ID) では float array
と書かれてますが mathutils.Matrix を返すようです。
● Mesh のアクセス方法
Mesh には形状データが入っています。
頂点データ自体は Mesh.vertices ですが、下記ページの解説にあるように
edge, loop, polygon と 3種類のインデックスがあります。
・Mesh(ID)
・MeshVertex : 頂点データそのもの
・MeshEdge : 2頂点の index
・MeshLoop : 1頂点 + edge の index
・MeshPolygon : n個の loop の index
mesh= Suzanne <bpy_collection[500], MeshPolygons> <bpy_collection[507], MeshVertices> <bpy_collection[1968], MeshLoops> <bpy_collection[1005], MeshEdges> mesh= Cube <bpy_collection[6], MeshPolygons> <bpy_collection[8], MeshVertices> <bpy_collection[24], MeshLoops> <bpy_collection[12], MeshEdges> mesh= Sphere <bpy_collection[512], MeshPolygons> <bpy_collection[482], MeshVertices> <bpy_collection[1984], MeshLoops> <bpy_collection[992], MeshEdges>
Mesh 内の MeshPolygon (Face) の列挙は下記の通り。
def shape( obj ): mesh= obj.data for poly in mesh.polygons: print( poly )
MeshPolygon に含まれる頂点 (Face Vertex) を得るには下記の通り。
# Loop を使った場合 def shape( obj ): mesh= obj.data for poly in mesh.polygons: for id in range( poly.loop_start, poly.loop_start + poly.loop_total ): print( mesh.verticies[ mesh.loops[ id ].vertex_index ].co )
MeshPolygon に含まれる MeshLoop をたどって MeshLoop.vertex_index を
参照しています。
なお、Loop を使わなくても MeshPolygon.vertices で直接頂点 index を
取れることがわかりました。
def shape( obj ): mesh= obj.data for poly in mesh.polygons: for ver in poly.vertices: print( mesh.vertices[ ver ].co )
ただし、mesh.loops の参照用 index 値 (「Loop を使った場合」の id ) は
uv など他のデータアクセスにも使います。
loop は Face Vertex 単位のデータとなり、Cube の場合 24 個あります。
一見複雑ですが「Loop を使った場合」の方が都合が良いようです。
● 法線
頂点データ MeshVertex に直接頂点法線が格納されています。
頂点座標が共有される場合法線も共有されており、この値は
ハードエッジになりません。
頂点とは別に MeshPolygon に面法線が格納されており、プロパティ
Mesh.use_smooth の値によってこの両者を使い分ける必要があるようです。
● UV / 頂点カラー
Mesh.uv_layers に格納されます。値そのものは MeshUVLoop.uv です。
uv_layers が MeshUVLoopLayer で data に MeshUVLoop が頂点分 (Loop 数分)
格納されます。
>>> len(bpy.data.objects['Cube'].data.uv_layers) 1 >>> len(bpy.data.objects['Cube'].data.uv_layers[0].data) 24 >>> bpy.data.objects['Cube'].data.uv_layers[0].data[0].uv Vector((0.0), 0.0)) >>> bpy.data.objects['Cube'].data.uv_layers.active.data[0].uv Vector((0.0), 0.0))
頂点カラーも全く同じで Mesh.vertex_colors に入ります。
今までの情報を取り出すコードがこれです。
def shape( obj ): print( 'mesh=', obj.name ) mesh= obj.data uv_array= mesh.uv_layers.active.data if len(mesh.uv_layers) else None vcolor_array= mesh.vertex_colors.active.data if len(mesh.vertex_colors) else None for poly in mesh.polygons: for id in range( poly.loop_start, poly.loop_start + poly.loop_total ): vertex= mesh.vertices[ mesh.loops[ id ].vertex_index ] position= vertex.co normal= vertex.normal if not poly.use_smooth: normal= poly.normal print( 'pos=', position ) print( 'normal=', normal ) if uv_array: uv= uv_array[id].uv print( 'uv=', uv ) if vcolor_array: vcolor= vcolor_array[id].color print( 'vcolor=', vcolor ) def tree( obj ): if obj.type == 'MESH': shape( obj ) #print( 'matrix=', obj.matrix_local ) for child in obj.children: tree( child ) def root(): for obj in bpy.data.objects: if not obj.parent: tree( obj ) root()
それぞれ最初の(active な) UV set と VertexColor しか参照していないので注意してください。
uv_array, vcolor_array の参照に Loop と同じ id を使っていることがわかります。
Matrix 同様、ドキュメントではただの array ですが実際に返す値は
mathutils.Vector, mathutils.Color となっています。
続きます
関連エントリ
・Blender 2.6 の Export plug-in を作る (1)
2012/12/27
Blender 2.6 の Export plug-in を作る (1)
Blender を使った経験が全く無いので練習兼ねてプラグイン作ります。
・Blender
Blender 2.65 を install 。
Script は python 3.3 で、plug-in (addon) もこれで記述するようです。
・Scripts Development
本体に DirectX .x や fbx など多くの exporter addon が含まれているので、これらのスクリプトが参考になります。
Windows だとこの辺に入っています。
C:\Program Files\Blender Foundation\Blender\2.65\scripts\addons
●Python Console
Blender 上の 3D View など、Window を Python Console に切り替えられます。
直接コマンドを実行して動作確認ができるので便利です。
●スクリプトの置き場所
addon として作成した script は下記の場所に入れておきます。
・Add-Ons
・Configuration & Data Paths
上記場所に入れたスクリプトでは最低限 bl_info の定義が必要です。
User Preferences の Addons を開くときに、フォルダ内の python ファイルから bl_info を検索して定義内容を取り込んでいるようです。
bl_info がなかったり定義にミスがあるとエラーになります。
・Script meta informations
●スクリプトの読み込み&実行
Blender のメニューから addon を有効にします。
File → User Preferences ... → Addons
上記の Addon スクリプト置き場に入っていて、かつ bl_info が定義されていればここに列挙されます。
左サイドの Categories で絞り込むと見つけやすくなります。
上の test.py なら "Import-Export" に入ります。
test.py である「Import-Export: Test」のチェックボックスを On にすると script が読み込まれて走ります。
ただし上の test.py は class を定義しておらず、内容が不完全なのでエラーになります。
●エラーコンソール
addon としてロードした script の print 出力 (stdout) は、
Blender 内の Python Console には表示されません。
エラーなどのログを見るにはコンソールを開きます。
Windows の場合、メニューの "Window" から
Window → Toggle System Console
Maya でも別ウィンドウになっているあれ (Output Window) と同じです。
MacOS X/Linux の場合上記のメニューがないのでコマンドラインから起動します。
Finder からではなく、ターミナルを開いて blender コマンドを直接実行します。
・The Console Window
これで上記 test.py を読み込もうとすると "on load message" が表示されたあと
エラーが出ていることを確認できます。
●コマンドの追加と実行
addon はコマンドの class を定義してシステムに登録する必要があります。
Preferences Addons のチェックボックスの on/off で script 内の
register()/unregister() が呼び出されています。
このタイミングで class を登録を行います。
定義クラスは bl_label, bl_idname が必須で、bl_idname が識別子になっています。
(2) の test.py は Preferences Addos で正しくロードできます。
Preferences Addos の "Import-Export: Test" のチェックを入れると
register() が呼び出されて bl_idname で指定したコマンド名が有効になります。
Python Console を開いて export.test_a6() が実行できることを確認できます。
(↓太字は出力結果)
●Export Menu に追加する
付属の scripts/addons/io_scene_fbx を参考にしました。
io_export_direct_x.py の方は ExportHelper を使わずに実装されているようです。
register()/unregister() の中で class だけでなく menu_func も登録しています。
この menu_func() はメニューの
File → Export
のサブメニューを開くタイミングで呼ばれるようです。
self.layout.operator() の最初の引数が実行する class id、text が Menu のラベルです。
例えば menu_func() を
と書き換えると File → Export →「Export Test a6?」の選択で fbx の
export を呼び出せます。
●Export 時のオプション
Export 実行時に左側のサイドバーでオプション選択ができます。
例えば選択されたオブジェクトだけ Export したり、Axis を変更したりなど。
このようなプロパティは Export class に追加しておきます。
これで File → Export →「Export Test a6」を選択すると、
Export ファイル選択画面となり、左サイドバーから「Selected Objects」の
チェックが可能となります。
右上の「export test a6」ボタンを押すと、コンソール出力で
入力したファイル名と "Selected Objects" のチェック状態が読み取れている事がわかります。
まだ必要最小限ですが addon 登録周りの挙動がわかったので、
次は内部データのアクセスを行います。
・Blender
Blender 2.65 を install 。
Script は python 3.3 で、plug-in (addon) もこれで記述するようです。
・Scripts Development
本体に DirectX .x や fbx など多くの exporter addon が含まれているので、これらのスクリプトが参考になります。
Windows だとこの辺に入っています。
C:\Program Files\Blender Foundation\Blender\2.65\scripts\addons
●Python Console
Blender 上の 3D View など、Window を Python Console に切り替えられます。
直接コマンドを実行して動作確認ができるので便利です。
●スクリプトの置き場所
addon として作成した script は下記の場所に入れておきます。
◎ Windows 7/8 C:\Users\<ユーザー名>\AppData\Roaming\Blender Foundation\Blender\2.65\scripts\addons ◎ MacOS X /Users/<ユーザー名>/Library/Application Support/Blender/2.65/scripts/addons
・Add-Ons
・Configuration & Data Paths
上記場所に入れたスクリプトでは最低限 bl_info の定義が必要です。
User Preferences の Addons を開くときに、フォルダ内の python ファイルから bl_info を検索して定義内容を取り込んでいるようです。
bl_info がなかったり定義にミスがあるとエラーになります。
・Script meta informations
# test.py (Preference に列挙されるための最小限) bl_info= { "name": "Test", "category": "Import-Export", } print( "on load message" );
●スクリプトの読み込み&実行
Blender のメニューから addon を有効にします。
File → User Preferences ... → Addons
上記の Addon スクリプト置き場に入っていて、かつ bl_info が定義されていればここに列挙されます。
左サイドの Categories で絞り込むと見つけやすくなります。
上の test.py なら "Import-Export" に入ります。
test.py である「Import-Export: Test」のチェックボックスを On にすると script が読み込まれて走ります。
ただし上の test.py は class を定義しておらず、内容が不完全なのでエラーになります。
●エラーコンソール
addon としてロードした script の print 出力 (stdout) は、
Blender 内の Python Console には表示されません。
エラーなどのログを見るにはコンソールを開きます。
Windows の場合、メニューの "Window" から
Window → Toggle System Console
Maya でも別ウィンドウになっているあれ (Output Window) と同じです。
MacOS X/Linux の場合上記のメニューがないのでコマンドラインから起動します。
Finder からではなく、ターミナルを開いて blender コマンドを直接実行します。
$ /Applications/Blender/blender.app/Contents/MacOS/blender
・The Console Window
これで上記 test.py を読み込もうとすると "on load message" が表示されたあと
エラーが出ていることを確認できます。
●コマンドの追加と実行
addon はコマンドの class を定義してシステムに登録する必要があります。
Preferences Addons のチェックボックスの on/off で script 内の
register()/unregister() が呼び出されています。
このタイミングで class を登録を行います。
# test.py (2) import bpy bl_info= { "name": "Test", "category": "Import-Export", } class ExportTest( bpy.types.Operator ): bl_label= 'export test' bl_idname= 'export.test_a6' def execute( self, context ): print( "exec-command" ) return {'FINISHED'} def unregister(): bpy.utils.register_module(__name__) def register(): bpy.utils.unregister_module(__name__)
定義クラスは bl_label, bl_idname が必須で、bl_idname が識別子になっています。
(2) の test.py は Preferences Addos で正しくロードできます。
Preferences Addos の "Import-Export: Test" のチェックを入れると
register() が呼び出されて bl_idname で指定したコマンド名が有効になります。
Python Console を開いて export.test_a6() が実行できることを確認できます。
(↓太字は出力結果)
>>> bpy.ops.export.test_a6() exec-command {'NIFISHED'}
●Export Menu に追加する
付属の scripts/addons/io_scene_fbx を参考にしました。
io_export_direct_x.py の方は ExportHelper を使わずに実装されているようです。
# io_export_test_a6.py import bpy from bpy_extras.io_utils import ExportHelper bl_info= { "name": "Export Test a6", "category": "Import-Export", } class ExportTestA6( bpy.types.Operator, ExportHelper ): bl_label= 'export test a6' bl_idname= 'export.test_a6' filename_ext= '.txt' # ←必須 def execute( self, context ): print( 'file=' + self.filepath ) return {'FINISHED'} def menu_func(self, context): self.layout.operator( ExportTestA6.bl_idname, text="Export Test a6 (.txt)" ) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_file_export.remove(menu_func) def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_file_export.append(menu_func)
register()/unregister() の中で class だけでなく menu_func も登録しています。
この menu_func() はメニューの
File → Export
のサブメニューを開くタイミングで呼ばれるようです。
self.layout.operator() の最初の引数が実行する class id、text が Menu のラベルです。
例えば menu_func() を
def menu_func(self, context): self.layout.operator( "export_scene.fbx", text="Export Test a6?" )
と書き換えると File → Export →「Export Test a6?」の選択で fbx の
export を呼び出せます。
●Export 時のオプション
Export 実行時に左側のサイドバーでオプション選択ができます。
例えば選択されたオブジェクトだけ Export したり、Axis を変更したりなど。
このようなプロパティは Export class に追加しておきます。
~ class ExportTestA6( bpy.types.Operator, ExportHelper ): bl_label= 'export test a6' bl_idname= 'export.test_a6' filename_ext= '.txt' # ↓追加 flag_selected= BoolProperty( name= "Selected Objects", description= "Export selected objects", default= False ) def execute( self, context ): print( 'file=' + self.filepath ) print( 'flag=', self.flag_selected ) return {'FINISHED'} ~
これで File → Export →「Export Test a6」を選択すると、
Export ファイル選択画面となり、左サイドバーから「Selected Objects」の
チェックが可能となります。
右上の「export test a6」ボタンを押すと、コンソール出力で
入力したファイル名と "Selected Objects" のチェック状態が読み取れている事がわかります。
まだ必要最小限ですが addon 登録周りの挙動がわかったので、
次は内部データのアクセスを行います。
2012/12/23
Qualcomm APQ8064 Krait/A6 swift の浮動小数点演算能力
Qualcomm の CPU Krait の浮動小数点演算の速度を調べてみました。
(1)~(4) は過去の計測値です。
SDK や OS などの条件が完全に同一ではないのと、計測方法にも
いろいろミスや勘違いが含まれてる可能性があります。
予めご了承ください。
またアプリケーションの実速度ではなく CPU 単体の能力やパイプラインを
調べるのが目的なので、オンキャッシュ前提で意味のない演算を走らせています。
● a:~d: NEON を使った Matrix 4x4 同士の乗算
A はパイプライン最適化なし、B はインターリーブしたものです。
Q=128bit(float x4), D=64bit(float x2) 命令です。
64bit の方が実行命令数が倍になります。
具体的な違いは下記の通り。
● e:~z: VFP/NEON の命令単体の実行
vfma は VFPv4 の新しい命令なので、CPU A6(Swift), Krait のみ
実行できます。(Cortex-A9/Scorpion は VFPv3)
A は in/out が完全に別レジスタです。
B は 4 命令毎のインターリブで、レイテンシが大きい場合は
A よりもストールする可能性が高くなります。
● ALU
(1)/(2) Cortex-A9 の NEON は 64bit (float x2) なので
j: と m: のように D , Q で倍近い差が生じています。
(3) Qualcomm は Scorpion でも NEON が 128bit なので、
j: と m: の差がなく Q では Cortex-A9 の倍の速度が出ています。
(4)/(5) Scorpion 同様 D,Q の差がほとんど無く、NEON が 128bit
だと考えられます。
● Pipeline
(3) の Scorpion は 128bit NEON かつクロックが高いこともあり、
(1)/(2) の Cortex-A9 と比べると全体的に fp 性能が高くなっています。
ですが e:~o: の A に比べて p:~z: の落ち込みが起きており、
低クロックの Cortex-A9 より遅くなっているケースがあります。
Cortex-A9 の場合一部の命令以外は A と B の差が生じていません。
Scorpion は pipeline のステージが深いこと、Cortex-A9 も積和の
fmacs/vmla ではレイテンシが大きいことがわかります。
(4) の A6(Swift) は NEON の場合 A/B の差がほとんど無く、
全体的にスループットが高くなっています。
NEON も out of order で実行していると考えられます。
ただし VFP のスカラーの方が B で遅くなっているため、
レジスタリネーミングは 64bit 単位ではないかと思います。
特にスカラーの積和命令が極端に苦手です。
おそらく fmacs/vmla/vfma の sレジスタアクセスは d 全体を
置き換えるベクタ命令に置き換えた方が高速化できると思われます。
(5) の Krait は Scorpion と比べて明らかにパイプラインの実行効率が
高くなっています。
A/B の差は小さくなっており A6(swift) 同様 out-of-order か latency が
少ないと考えられます。特に a:~d: で差が顕著です。
それでも A6 には敵いません。
特徴的なのは vmla が遅いことで、VFP v4 の vfma と大きな開きがあります。
HW Native なのは vfma の方で、vmla は演算結果の互換性のために mul,add
2命令に展開している可能性があります。
よって、Krait ではできる限り vfma を使った方が良いことになります。
● Krait
Scorpion は浮動小数点演算能力が非常に高いのですが、
ピーク性能を引き出すのはあまり容易ではありませんでした。
その点 Krait は改良されているようです。
ただし整数演算やアプリケーション全体の実行速度ではまだ
大きな差を検出できていません。
Android NDK r8c/r8d では clang (LLVM) が追加されているので、
コンパイラを変えるとまた違った結果になる可能性があります。
また Scorpion にはなかった Quad core なので、システム全体の
速度では最も演算能力が高いことは間違いありません。
同じ Quad core でも Tegra3 などの Cortex-A9 とは世代が異なっており
特に浮動小数点演算能力は 2倍になります。
前回書いたように APQ8064 の Adreno 320 GPU の方もかなり速いのですが、
ベンチマークに使用しているアプリが安定して動作せず計測できていません。
まだ安定しない原因がわかっておりません。
関連エントリ
・Adreno 320 Snapdragon S4 Pro APQ8064
・iPhone 5 / A6 の CPU 速度 その 3
・iPhone 5 / A6 の浮動小数点演算
・Snapdragon の本当の浮動小数点演算能力
・ARM Cortex-A8 の NEON と浮動小数演算最適化
(1) (2) (3) (4) (5) iPad3 Touch5 EVO 3D iPad4 Butterfly A5X A5 8660 A6X 8064 C-A9 C-A9 Scorpion Swift Krait -------------------------------------------------------- a:mat44 neon_AQ 4.784 5.979 2.879 1.204 1.919 b:mat44 neon_BQ 2.408 3.008 1.146 1.266 1.273 c:mat44 neon_AD 4.781 5.974 3.079 1.554 2.453 d:mat44 neon_BD 2.406 3.007 1.440 1.344 2.041 e:fadds A 4.010 5.013 3.460 2.882 3.791 f:fmuls A 4.010 5.012 4.361 2.953 3.671 g:fmacs A 4.012 5.011 4.034 5.763 7.334 h:vfma.f32 A ----- ----- ----- 5.765 3.725 i:vadd.f32 D A 4.111 5.136 3.493 2.877 3.706 j:vmul.f32 D A 4.110 5.136 3.502 2.950 3.667 k:vmla.f32 D A 4.512 5.650 3.638 2.951 7.557 l:vadd.f32 Q A 8.023 10.036 3.408 2.878 3.677 m:vmul.f32 Q A 8.022 10.028 3.427 2.952 3.647 n:vmla.f32 Q A 8.025 10.028 3.400 2.955 7.362 o:vfma.f32 D A ----- ----- ----- 2.494 3.676 p:fadds B 4.014 5.013 5.972 5.757 4.664 q:fmuls B 5.013 6.265 5.960 5.760 4.583 r:fmacs B 8.023 10.024 8.573 11.521 8.266 s:vfma.f32 B ----- ----- ----- 11.519 4.611 t:vadd.f32 D B 4.113 5.137 5.945 2.881 4.746 u:vmul.f32 D B 4.118 5.145 5.098 2.951 4.680 v:vmla.f32 D B 9.027 11.278 8.498 5.757 8.361 w:vadd.f32 Q B 8.021 10.023 5.950 2.879 4.702 x:vmul.f32 Q B 8.029 10.023 5.095 2.951 4.595 y:vmla.f32 Q B 9.026 11.277 8.497 5.762 8.464 z:vfma.f32 D B ----- ----- ----- 5.759 4.660 -------------------------------------------------------- ↑数値は実行時間(秒) 数値が小さい方が速い (1)=Apple iPad 3 A5X ARM Cortex-A9 x2 1.0GHz (2)=Apple iPod touch 5 A5 ARM Cortex-A9 x2 0.8GHz (3)=HTC EVO 3D ISW12HT MSM8660 Scorpion x2 1.2GHz (4)=Apple iPad 4 A6X A6 (swift) x2 ?GHz (5)=HTC J butterfly HTL21 APQ8064 Krait x4 1.5GHz
(1)~(4) は過去の計測値です。
SDK や OS などの条件が完全に同一ではないのと、計測方法にも
いろいろミスや勘違いが含まれてる可能性があります。
予めご了承ください。
またアプリケーションの実速度ではなく CPU 単体の能力やパイプラインを
調べるのが目的なので、オンキャッシュ前提で意味のない演算を走らせています。
● a:~d: NEON を使った Matrix 4x4 同士の乗算
A はパイプライン最適化なし、B はインターリーブしたものです。
Q=128bit(float x4), D=64bit(float x2) 命令です。
64bit の方が実行命令数が倍になります。
具体的な違いは下記の通り。
; AQ vldmia %0, {d0-d7} vldmia %1, {d8-d15} vmul.f32 q8,q0,d8[0] vmla.f32 q8,q1,d8[1] vmla.f32 q8,q2,d9[0] vmla.f32 q8,q3,d9[1] vstmia %2!, {d16,d17} ~
; BQ vldmia %0, {d0-d7} vldmia %1, {d8-d15} vmul.f32 q8,q0,d8[0] vmul.f32 q9,q0,d10[0] vmul.f32 q10,q0,d12[0] vmul.f32 q11,q0,d14[0] ~
; BD vldmia %0, {d0-d7} vldmia %1, {d8-d15} vmul.f32 d16,d0,d8[0] vmul.f32 d17,d1,d8[0] vmla.f32 d16,d2,d8[1] vmla.f32 d17,d3,d8[1] vmla.f32 d16,d4,d9[0] vmla.f32 d17,d5,d9[0] vmla.f32 d16,d6,d9[1] vmla.f32 d17,d7,d9[1] vstmia %2!, {d16,d17} ~
● e:~z: VFP/NEON の命令単体の実行
vfma は VFPv4 の新しい命令なので、CPU A6(Swift), Krait のみ
実行できます。(Cortex-A9/Scorpion は VFPv3)
無印 = VFP スカラー (float x1) D = NEON d レジスタ 64bit (float x2) Q = NEON q レジスタ 128bit (float x4)
A は in/out が完全に別レジスタです。
B は 4 命令毎のインターリブで、レイテンシが大きい場合は
A よりもストールする可能性が高くなります。
// A #define NEON_OPSETv1_8D(op) \ op " d0, d1, d1 \n" \ op " d2, d1, d1 \n" \ op " d3, d1, d1 \n" \ op " d4, d1, d1 \n" \ op " d5, d1, d1 \n" \ op " d6, d1, d1 \n" \ op " d7, d1, d1 \n" \ op " d8, d1, d1 \n"
// B #define NEON_OPSETv2_8D(op) \ op " d0, d1, d5 \n" \ op " d2, d1, d6 \n" \ op " d3, d1, d7 \n" \ op " d4, d1, d8 \n" \ op " d5, d1, d0 \n" \ op " d6, d1, d2 \n" \ op " d7, d1, d3 \n" \ op " d8, d1, d4 \n"
● ALU
(1)/(2) Cortex-A9 の NEON は 64bit (float x2) なので
j: と m: のように D , Q で倍近い差が生じています。
(3) Qualcomm は Scorpion でも NEON が 128bit なので、
j: と m: の差がなく Q では Cortex-A9 の倍の速度が出ています。
(4)/(5) Scorpion 同様 D,Q の差がほとんど無く、NEON が 128bit
だと考えられます。
● Pipeline
(3) の Scorpion は 128bit NEON かつクロックが高いこともあり、
(1)/(2) の Cortex-A9 と比べると全体的に fp 性能が高くなっています。
ですが e:~o: の A に比べて p:~z: の落ち込みが起きており、
低クロックの Cortex-A9 より遅くなっているケースがあります。
Cortex-A9 の場合一部の命令以外は A と B の差が生じていません。
Scorpion は pipeline のステージが深いこと、Cortex-A9 も積和の
fmacs/vmla ではレイテンシが大きいことがわかります。
(4) の A6(Swift) は NEON の場合 A/B の差がほとんど無く、
全体的にスループットが高くなっています。
NEON も out of order で実行していると考えられます。
ただし VFP のスカラーの方が B で遅くなっているため、
レジスタリネーミングは 64bit 単位ではないかと思います。
特にスカラーの積和命令が極端に苦手です。
おそらく fmacs/vmla/vfma の sレジスタアクセスは d 全体を
置き換えるベクタ命令に置き換えた方が高速化できると思われます。
(5) の Krait は Scorpion と比べて明らかにパイプラインの実行効率が
高くなっています。
A/B の差は小さくなっており A6(swift) 同様 out-of-order か latency が
少ないと考えられます。特に a:~d: で差が顕著です。
それでも A6 には敵いません。
特徴的なのは vmla が遅いことで、VFP v4 の vfma と大きな開きがあります。
HW Native なのは vfma の方で、vmla は演算結果の互換性のために mul,add
2命令に展開している可能性があります。
よって、Krait ではできる限り vfma を使った方が良いことになります。
● Krait
Scorpion は浮動小数点演算能力が非常に高いのですが、
ピーク性能を引き出すのはあまり容易ではありませんでした。
その点 Krait は改良されているようです。
ただし整数演算やアプリケーション全体の実行速度ではまだ
大きな差を検出できていません。
Android NDK r8c/r8d では clang (LLVM) が追加されているので、
コンパイラを変えるとまた違った結果になる可能性があります。
また Scorpion にはなかった Quad core なので、システム全体の
速度では最も演算能力が高いことは間違いありません。
同じ Quad core でも Tegra3 などの Cortex-A9 とは世代が異なっており
特に浮動小数点演算能力は 2倍になります。
前回書いたように APQ8064 の Adreno 320 GPU の方もかなり速いのですが、
ベンチマークに使用しているアプリが安定して動作せず計測できていません。
まだ安定しない原因がわかっておりません。
関連エントリ
・Adreno 320 Snapdragon S4 Pro APQ8064
・iPhone 5 / A6 の CPU 速度 その 3
・iPhone 5 / A6 の浮動小数点演算
・Snapdragon の本当の浮動小数点演算能力
・ARM Cortex-A8 の NEON と浮動小数演算最適化
2012/12/13
Adreno 320 Snapdragon S4 Pro APQ8064
HTC J butterfly HTL21 (AQP8064) を手に入れたので、
今更ながらやっと Krait と Adreno 320 を試せるようになりました。
Krait は Qualcomm が開発した ARMv7A CPU の CPU core で
Scorpion の後継にあたります。
ARM でいえば Cortex-A15 の世代に相当し、Apple A6/A6X の CPU core も同様に
この世代に属します。
これら ARM, Qualcomm, Apple 3つの CPU は同じ ARMv7-A の命令
アーキテクチャでアプリケーションに互換性がありますが、
Intel と AMD のように設計や性能は異なります。
この中で Krait が最も先に登場しており、日本でも 5月の HTC J 以降
Snapdragon S4 MSM8960/8260A/8660A 搭載機種が増えています。
むしろ LTE では Krait でないスマートフォンの方が少ないかもしれません。
Snapdragon S4 Pro APQ8064 のもう一つ新しい点は、GPU も世代交代していることです。
GPU Adreno 320 はハードウエア的には OpenGL ES 3.0 に対応しており、
こちらも他社に先駆けて手に入る状態となりました。
実際に調べてみると、OpenGL ES 3.0 の新しい圧縮テクスチャ形式である
ETC2 / EAC に対応していることがわかります。
OpenGL ES 2.0 のままでも列挙されるので、すでに利用できる状態となっています。
ETC1 も同列に列挙されるのは OpenGL ES 2.0 API だからだと思われます。
また GPU Extension に GL_EXT_sRGB が増えています。
sRGB 対応もやはり OpenGL ES 3.0 の新機能です。
Extension の詳細は下記のページに追加しました。
・OpenGL ES Extension (Mobile GPU)
GPU 比較はこちら
・Mobile GPU の比較
いつものベンチマークはデータをとっている最中です。
使った感じは GPU が非常に速いです。
一番重いケースでも 30fps を余裕で超えており、とても Full HD (1920x1080)
でレンダリングしているとは思えません。
傾向としては Adreno 220 と同じでシェーダーの演算負荷に強く、同じ
Unified Shader ながら PowerVR のように面積が増えても極端に処理落ちしません。
演算精度によるペナルティが無いため、プログラマにとっては非常に扱いやすく
かつ演算精度が高いのでレンダリング結果も綺麗です。
ピクセルシェーダーの命令が少なく演算負荷が軽いケースでは PowerVR が
飛び抜けていますが、ピクセル(フラグメント)の演算量が多い場合はおそらく
A5X の PowerVR SGX543MP4 より速く、A6X の SGX554MP4 に近い数値が出るようです。
もちろん条件によるので万能ではありません。
CPU のデータも計測中ですが CPU core 単体では A6/A6X よりも遅いようです。
やはり A6/A6X は速いです。
その代わり core 数が A6/A6X の 2倍 (Quad core) なので、
総合的な能力では十分だと思います。
関連エントリ
・iPad 4 Apple A6X GPU PowerVR SGX554 MP4 の速度
・iPhone 5 / A6 の CPU 速度 その 3
・iPhone 5 / A6 の CPU 速度 その 2
・OpenGL / OpenGL ES ETC1 の互換性と KTX の落とし穴
・OpenGL 4.3/ES 3.0 ETC2 Texture 圧縮の仕組み (PVRTC2,ASTC)
・OpenGL 4.3 と GL_ARB_ES3_compatibility
今更ながらやっと Krait と Adreno 320 を試せるようになりました。
Krait は Qualcomm が開発した ARMv7A CPU の CPU core で
Scorpion の後継にあたります。
ARM でいえば Cortex-A15 の世代に相当し、Apple A6/A6X の CPU core も同様に
この世代に属します。
これら ARM, Qualcomm, Apple 3つの CPU は同じ ARMv7-A の命令
アーキテクチャでアプリケーションに互換性がありますが、
Intel と AMD のように設計や性能は異なります。
ARM Qualcomm Apple --------------------------------------------------------- Cortex-A8/A9 Scorpion vfpv3 ~2012 Cortex-A15 Krait Apple A6 vfpv4 2012~
この中で Krait が最も先に登場しており、日本でも 5月の HTC J 以降
Snapdragon S4 MSM8960/8260A/8660A 搭載機種が増えています。
むしろ LTE では Krait でないスマートフォンの方が少ないかもしれません。
Snapdragon S4 Pro APQ8064 のもう一つ新しい点は、GPU も世代交代していることです。
GPU Adreno 320 はハードウエア的には OpenGL ES 3.0 に対応しており、
こちらも他社に先駆けて手に入る状態となりました。
実際に調べてみると、OpenGL ES 3.0 の新しい圧縮テクスチャ形式である
ETC2 / EAC に対応していることがわかります。
TextureFormat 26 tc[00]=87f9 GL_3DC_X_AMD tc[01]=87fa GL_3DC_XY_AMD tc[02]=8c92 GL_ATC_RGB_AMD tc[03]=8c93 GL_ATC_RGBA_EXPLICIT_ALPHA_AMD tc[04]=87ee GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD tc[05]=8d64 GL_ETC1_RGB8_OES tc[06]=8b90 GL_PALETTE4_RGB8_OES tc[07]=8b91 GL_PALETTE4_RGBA8_OES tc[08]=8b92 GL_PALETTE4_R5_G6_B5_OES tc[09]=8b93 GL_PALETTE4_RGBA4_OES tc[10]=8b94 GL_PALETTE4_RGB5_A1_OES tc[11]=8b95 GL_PALETTE8_RGB8_OES tc[12]=8b96 GL_PALETTE8_RGBA8_OES tc[13]=8b97 GL_PALETTE8_R5_G6_B5_OES tc[14]=8b98 GL_PALETTE8_RGBA4_OES tc[15]=8b99 GL_PALETTE8_RGB5_A1_OES tc[16]=9270 GL_COMPRESSED_R11_EAC tc[17]=9271 GL_COMPRESSED_SIGNED_R11_EAC tc[18]=9272 GL_COMPRESSED_RG11_EAC tc[19]=9273 GL_COMPRESSED_SIGNED_RG11_EAC tc[20]=9274 GL_COMPRESSED_RGB8_ETC2 tc[21]=9275 GL_COMPRESSED_SRGB8_ETC2 tc[22]=9276 GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 tc[23]=9277 GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 tc[24]=9278 GL_COMPRESSED_RGBA8_ETC2_EAC tc[25]=9279 GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC
OpenGL ES 2.0 のままでも列挙されるので、すでに利用できる状態となっています。
ETC1 も同列に列挙されるのは OpenGL ES 2.0 API だからだと思われます。
また GPU Extension に GL_EXT_sRGB が増えています。
sRGB 対応もやはり OpenGL ES 3.0 の新機能です。
Extension の詳細は下記のページに追加しました。
・OpenGL ES Extension (Mobile GPU)
GPU 比較はこちら
・Mobile GPU の比較
いつものベンチマークはデータをとっている最中です。
使った感じは GPU が非常に速いです。
一番重いケースでも 30fps を余裕で超えており、とても Full HD (1920x1080)
でレンダリングしているとは思えません。
傾向としては Adreno 220 と同じでシェーダーの演算負荷に強く、同じ
Unified Shader ながら PowerVR のように面積が増えても極端に処理落ちしません。
演算精度によるペナルティが無いため、プログラマにとっては非常に扱いやすく
かつ演算精度が高いのでレンダリング結果も綺麗です。
ピクセルシェーダーの命令が少なく演算負荷が軽いケースでは PowerVR が
飛び抜けていますが、ピクセル(フラグメント)の演算量が多い場合はおそらく
A5X の PowerVR SGX543MP4 より速く、A6X の SGX554MP4 に近い数値が出るようです。
もちろん条件によるので万能ではありません。
CPU のデータも計測中ですが CPU core 単体では A6/A6X よりも遅いようです。
やはり A6/A6X は速いです。
その代わり core 数が A6/A6X の 2倍 (Quad core) なので、
総合的な能力では十分だと思います。
関連エントリ
・iPad 4 Apple A6X GPU PowerVR SGX554 MP4 の速度
・iPhone 5 / A6 の CPU 速度 その 3
・iPhone 5 / A6 の CPU 速度 その 2
・OpenGL / OpenGL ES ETC1 の互換性と KTX の落とし穴
・OpenGL 4.3/ES 3.0 ETC2 Texture 圧縮の仕組み (PVRTC2,ASTC)
・OpenGL 4.3 と GL_ARB_ES3_compatibility