D3D Shader/OpenGL」カテゴリーアーカイブ

Nexus 7 上に開発環境をつくる (2) Bluetooth と OpenGL ES 2.0

●Bluetooth

前回の記事では未対応と書きましたが、その直後 2013/02/02 以降の ROM
Bluetooth が使えるようになっています。

ubuntu wiki: Using the Device

給電しながら外部キーボードやマウスが使えるようになりました。

●Nexus 7 に Android 端末をつなぐ

SDK を install できるわけではありませんが adb は使えます。

sudo apt-get install android-tools-adb

USB Host に他の Android 端末をつないで adb で接続できます。
端末によっては充電しようとするので、セルフパワー(ACアダプタ付き) USB ハブを
経由した方が良いと思います。

nexus7ubuntu3.jpg

↑HTC J butterfly HTL21 をつないで “adb devices” や “adb shell” 等が可能です。
Host の Nexus 7 (RAM 1GB A9 1.2GHz) よりも butterfly (RAM 2GB Krait 1.5GHz)
の方が性能が高いので本当は逆にしたいところです。

●3D / OpenGL ES 2.0

OpenGL でないため GLX は動作しないものの、
描画には OpenGL ES v2.0 + EGL が使われています。

NVIDIA: Linux For Tegra

↑上の NVIDIA サイトのドキュメントにあるように mesa-utils-extra を入れると
OpenGL ES 2.0 を使ったアプリの動作を確認できます。

sudo apt-get install mesa-utils-extra
/usr/bin/es2gears

nexus7ubuntu2.jpg

glxinfo の代わりに es2_info コマンドでドライバの確認ができるようです。

WebGL も試しましたが、Firefox/Chromium とも動作しませんでした。
フラグ自体は有効に出来ます。

●software

Software center の Installed tab が開きませんが、
Unity の dash 画面でアプリケーションの uninstall ができます。
Launcher 一番上のボタンで dash 画面を開いてタブを切り替え、
Installed の中でアイコンを右クリックです。

関連エントリ
Android Tablet Nexus 7 上に開発環境をつくる (Ubuntu)

Qualcomm APQ8064 GPU Adreno 320 の速度

今までテストに使用してきたプログラムが安定して動作しないため
Adreno 320 では速度の計測ができていませんでした。
LG Optimus G LGL21 を借りることができたのでもう一度テストしてみました。

Mobile GPU ベンチ OpenGL ES 2.0
CPU benchmark
OpenGL ES Extension (Mobile GPU)

GPU bench の (1) 以外は 60fps に達するため数値が出ていません。
GPU 性能が上がったため、もはやこのテストは計測に向いていないことになります。

また Adreno 320 は OpenGL ES 3.0 世代の GPU ですが、
現在の OS から使う限り API は OpenGL ES 2.0 になります。
GPU 性能をまだ引き出せていない可能性があります。

テストプログラムのエラーの原因は特定のケースでシェーダーのコンパイルが
エラーになるもので、回避方法はわかったもののまだ詳しい条件が
特定できていません。

VFP test では HTC J HTL21 よりも良い結果が出ています。

                 (1)     (2)     (3)      (4)    (5)     (6)
                 iPad3  Touch5   EVO 3D  iPad4 Butterfly OptimusG
                 A5X     A5      8660     A6X    8064    8064
                 C-A9    C-A9   Scorpion Swift   Krait   Krait
----------------------------------------------------------------
a:mat44 neon_AQ  4.784   5.979   2.879   1.204   1.919   1.354
b:mat44 neon_BQ  2.408   3.008   1.146   1.266   1.273   0.930
c:mat44 neon_AD  4.781   5.974   3.079   1.554   2.453   1.862
d:mat44 neon_BD  2.406   3.007   1.440   1.344   2.041   1.521
e:fadds      A   4.010   5.013   3.460   2.882   3.791   2.831
f:fmuls      A   4.010   5.012   4.361   2.953   3.671   2.793
g:fmacs      A   4.012   5.011   4.034   5.763   7.334   5.599
h:vfma.f32   A   -----   -----   -----   5.765   3.725   2.743
i:vadd.f32 D A   4.111   5.136   3.493   2.877   3.706   2.724
j:vmul.f32 D A   4.110   5.136   3.502   2.950   3.667   2.767
k:vmla.f32 D A   4.512   5.650   3.638   2.951   7.557   5.462
l:vadd.f32 Q A   8.023  10.036   3.408   2.878   3.677   2.717
m:vmul.f32 Q A   8.022  10.028   3.427   2.952   3.647   2.741
n:vmla.f32 Q A   8.025  10.028   3.400   2.955   7.362   5.446
o:vfma.f32 D A   -----   -----   -----   2.494   3.676   2.709
p:fadds      B   4.014   5.013   5.972   5.757   4.664   3.388
q:fmuls      B   5.013   6.265   5.960   5.760   4.583   3.384
r:fmacs      B   8.023  10.024   8.573  11.521   8.266   6.246
s:vfma.f32   B   -----   -----   -----  11.519   4.611   3.622
t:vadd.f32 D B   4.113   5.137   5.945   2.881   4.746   3.406
u:vmul.f32 D B   4.118   5.145   5.098   2.951   4.680   3.454
v:vmla.f32 D B   9.027  11.278   8.498   5.757   8.361   6.140
w:vadd.f32 Q B   8.021  10.023   5.950   2.879   4.702   3.481
x:vmul.f32 Q B   8.029  10.023   5.095   2.951   4.595   3.412
y:vmla.f32 Q B   9.026  11.277   8.497   5.762   8.464   6.249
z:vfma.f32 D B   -----   -----   -----   5.759   4.660   3.413
---------------------------------------------------------------
↑数値は実行時間(秒) 数値が小さい方が速い

(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
(6)=LG Optimus G LGL21     APQ8064  Krait         x4  1.5GHz

Krait の FP 性能は Swift に並ぶ新 core であることがよくわかります。

前回のテスト時はクロックが上限まで上がっていなかった可能性があります。
HTC J butterfly が修理から戻ってきたらもう一度計測してみたいと思っています。

関連エントリ
Adreno 320 Snapdragon S4 Pro APQ8064

Blender 2.6 の Export plug-in を作る (4) マテリアルと頂点出力

スキニング用 weight/group は頂点(MeshVertex)への追加情報でした。
同じように 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)

Blender 2.6 の Export plug-in を作る (3) 出力と mathutils

スキニング用のジオメトリ情報を調べます。

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)