twglの利用もやめ、素のWebGL 2.0でレンダリングコードを書きなおす

公開:2018-01-18 21:11
更新:2020-02-15 04:37
カテゴリ:シューティングゲーム,ゲーム製作,HTML5,ES6,JS,Blender

twglを使ってコードを書きなおしたが、結局素のWebGL2.0で書き直すことにした。理由としては

  1. twglは初期設定を楽にする代わりに、レンダリング時の処理が複雑になっていて、パフォーマンス的に気になる
  2. twglはインターリーブ配列に対応していない
  3. 興味本位

の3点である。3番目が比重としては一番大きい気がする。

インターリーブに興味を持ったのは以下の記事を読んだから。 https://qiita.com/emadurandal/items/0bb83b545670475f51a3

今時であればインターリーブにしなくても速いらしいが。そういうわけでtwglの使用をやめ、素のWebGL2.0で書き直すことにしたのである。 といってもメインのシェーダーコードはtwglのサンプルコードそのまんまではあるのだが。行列ライブラリはglMatrixを使用した。

WebGL2.0は確かに冗長なコードにはなるが、慣れると面倒くささはかなり減った。WebGLはCっぽいAPIで、いじっているとなんかこう昔懐かしい感じがするのがよい。

このコードをもうちょっと整理したりして、元のゲームに適用しようと考えている。

Blenderからのメッシュデータをインターリーブ配列にしてJSONデータとしてエクスポートするコードも今回Pythonで書いた。Pythonでまともなコードを書くのはこれが初めてである。書くにあたってはthree.jsのエクスポータを参考にした。

# exports each selected object into its own file
# -*- coding: utf-8 -*-

import math
import mathutils
import bpy
import os
import json

print(os.getcwd())

import debug
#debug.start_debug()

XZ_Y = "XZ_Y"
X_ZY = "X_ZY"
XYZ = "XYZ"
_XY_Z = "_XY_Z"

def flip_axes (a, dir=XYZ):
    """
    :function to swap vectors:
    """

    if dir == XZ_Y:
        a = (a[0], a[2], -a[1])
    elif dir == X_ZY:
        a = (a[0], -a[2], a[1])
    elif dir == _XY_Z:
        a = (-a[0], -a[1], a[2])

    return (a[0], a[1], a[2])

# export to blend file location
basedir = os.path.dirname(bpy.data.filepath)

if not basedir:
    raise Exception("Blend file is not saved")

obj = bpy.context.scene.objects.active

bpy.ops.object.mode_set(mode='OBJECT')
mesh = obj.to_mesh(bpy.context.scene, True, 'RENDER')

# 三角形化

backup = obj.data
hide = obj.hide 
obj.data = mesh
obj.select = True
bpy.context.scene.objects.active = obj
bpy.ops.object.modifier_add(type='TRIANGULATE')
bpy.ops.object.modifier_apply(apply_as='DATA',modifier='Triangulate')

bpy.ops.object.modifier_add(type='EDGE_SPLIT')
bpy.context.object.modifiers['EdgeSplit'].use_edge_angle = False
bpy.context.object.modifiers['EdgeSplit'].use_edge_sharp = True
bpy.ops.object.modifier_apply(apply_as='DATA', modifier='EdgeSplit')

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.normals_make_consistent()
#bpy.ops.mesh.set_normals_from_faces()
bpy.ops.object.editmode_toggle()

obj.data = backup
# obj.select = False

rot = mathutils.Matrix.Rotation(-math.pi, 4, 'Z')
mesh.transform(rot * obj.matrix_world)

mesh.update(calc_tessface=True)
#mesh.calc_normals()
#bpy.ops.mesh.set_normals_from_faces()

mesh.calc_tessface()
scale_ = 1.0
mesh.transform(mathutils.Matrix.Scale(scale_, 4))

#print(len(mesh.vertices)*3,len(mesh.uv_layers[0].data),len(mesh.tessfaces)*3)


# mesh.update(calc_tessface=True)

# debug.start_debug()

objects = {}

data = []

float_size = 4
objects['position_size'] = 3
objects['texcoord_size'] = 2
objects['stride']= (objects['position_size'] + objects['texcoord_size']) * float_size


for (pos,uv) in zip(mesh.vertices,mesh.uv_layers[0].data):
    uv = uv.uv
    pos = pos.co
    data.extend(pos)
    data.extend(uv)

print(data)

objects['data'] = data

indices = []
indices_offset = 0
objects['indices'] = indices
drawinfos = []
objects['drawInfos'] = drawinfos

for i,material_ in enumerate(mesh.materials):
    drawinfo = {}
    material = {}
    drawinfo['material'] = material

    material['u_diffuse'] = list(material_.diffuse_color) + [1.0]
    material['u_specular'] = list(material_.specular_color) + [1.0]
    material['u_specularFactor'] = material_.specular_intensity
    material['u_shininess'] = material_.specular_hardness
    count = 0
    # 頂点
    for face in [f for f in mesh.tessfaces if f.material_index == i]:
        indices.extend(face.vertices)
        count += 3

    drawinfo['count'] = count
    drawinfo['offset'] = indices_offset
    indices_offset += count * 2
    drawinfos.append(drawinfo)

# JSONファイルとして保存
name = bpy.path.clean_name(obj.name)
path = os.path.join(basedir,name)

with open(path + '.json','w') as f:
    json.dump(objects,f,ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': '));
    #json.dump(objects,f);

print("written:", path + ".json")

ただまだきちんとアドオンとして動くようにはなっていないが。

で、今後であるが、元のゲームのコードに今回の成果を適用しつつ、AIを使って敵の動きを実装できないか研究することにした。このゲームは仮想画面320px x 240pxでローポリのキャラを動かすつもりなので、グラフィック処理の負荷はかなり低いと思っている。なのでJSであってもAIをリアルタイムで処理するためのリソースはあると思っていて、実装してみようと思っている。うまくすれば敵配置の省力化とか、敵の動きをリアルにできそうな気もするしね。WebGL2.0にしたのでGPGPUも多少やりやすいはずだし、それも活用すればJSでも十分動くものが作れるんではないかと。ただここでいうAIというのは、今流行っている深層学習とかではないけどね。

ゲームAIに興味を持ったのは以下の記事を読んだからだが。
http://news.denfaminicogamer.jp/interview/gameai_miyake

最後に、今の時点の成果物は以下の通り。

https://github.com/sfpgmr/www/tree/master/contents/test/twgl/