前回のエントリのmmd.rbと今回のmain.rbとシェーダを合わせれば一応MMDのモデルは描画できる。ただし、テクスチャを貼ってないので目が表示されない。怖い。なので正面から見ないようにしましょう。MMDデフォルトのモデルの初音ミク.pmdをmiku.pmdに名前を変えてmodelフォルダに突っ込んだ。OpenGL使ってるので
gem install ruby-opengl
をやっておく必要がある
まずmain.rb
require 'opengl' require 'glut' require './mmd.rb' class Object3D def load_model(file_name) File.open(file_name, 'rb'){|file| @model = MMDModel.new() @model.load(file) } end def reshape(w,h) GL.Viewport(0,0,w,h) GL.MatrixMode(GL::GL_PROJECTION) GL.LoadIdentity() GLU.Perspective(45.0, w.to_f()/h.to_f(), 0.1, 100.0) end def display() GL.UseProgram(@program) GL.MatrixMode(GL::GL_MODELVIEW) GL.LoadIdentity() GLU.LookAt(0.0, 10.0, -30.0, 0.0, 10.0, 0.0, 0.0, 1.0, 0.0) GL.ClearColor(0.0, 0.0, 1.0, 1.0) GL.Clear(GL::GL_COLOR_BUFFER_BIT | GL::GL_DEPTH_BUFFER_BIT) GL.Rotate(@rotX, 1, 0, 0) GL.Rotate(@rotY, 0, 1, 0) start = 0 @model.materials.each do |material| draw(material, start) start += material.vert_count end GLUT.SwapBuffers() end def draw(material, start) GL.Uniform3fv(@ambientLocation, material.ambient) GL.Uniform1f(@alphaLocation, material.alpha) GL.Begin(GL::TRIANGLES) GL.Color(material.diffuse[0], material.diffuse[1], material.diffuse[2]) material.vert_count.times do |findex| vindex = @model.face.indices[start + findex] vertex = @model.vertices[vindex] pos = vertex.pos normal = vertex.normal GL.Normal(normal[0], normal[1], normal[2]); GL.Vertex(pos[0], pos[1], pos[2]) end GL.End() end def mouse(button,state,x,y) if button == GLUT::GLUT_LEFT_BUTTON && state == GLUT::GLUT_DOWN then @start_x = x @start_y = y @drag_flg = true elsif state == GLUT::GLUT_UP then @drag_flg = false end end def motion(x,y) if @drag_flg then dx = x - @start_x dy = y - @start_y @rotY += dx @rotY = @rotY % 360 @rotX -= dy @rotX = @rotX % 360 end @start_x = x @start_y = y GLUT.PostRedisplay() end def initialize() @start_x = 0 @start_y = 0 @rotY = 0 @rotX = 0 @drag_flg = false load_model('./model/miku.pmd'); GLUT.InitWindowPosition(100, 100) GLUT.InitWindowSize(300,300) GLUT.Init() GLUT.InitDisplayMode(GLUT::GLUT_DOUBLE | GLUT::GLUT_RGB | GLUT::GLUT_DEPTH) GLUT.CreateWindow('MMD on Ruby') GL.FrontFace(GL::GL_CW) GL.Enable(GL::GL_AUTO_NORMAL) GL.Enable(GL::GL_NORMALIZE) GL.Enable(GL::GL_DEPTH_TEST) GL.DepthFunc(GL::GL_LESS) @program = create_program('./shader/mmd.vert', './shader/mmd.frag') @ambientLocation = GL.GetUniformLocation(@program, 'ambient') @alphaLocation = GL.GetUniformLocation(@program, 'alpha') GLUT.ReshapeFunc(method(:reshape).to_proc()) GLUT.DisplayFunc(method(:display).to_proc()) GLUT.MouseFunc(method(:mouse).to_proc()) GLUT.MotionFunc(method(:motion).to_proc()) end def create_program(vert_name, frag_name) program = GL.CreateProgram() vert_shader = create_shader(vert_name, GL_VERTEX_SHADER) frag_shader = create_shader(frag_name, GL_FRAGMENT_SHADER) GL.AttachShader(program, vert_shader) GL.AttachShader(program, frag_shader) GL.LinkProgram(program) if !GL.GetProgramiv(program, GL_LINK_STATUS) raise(GL.GetProgramInfoLog(program)) end GL.DeleteShader(vert_shader) GL.DeleteShader(frag_shader) return program end def create_shader(file_name, type) shader = GL.CreateShader(type) File.open(file_name, 'rb') { |file| GL.ShaderSource(shader, file.read()) GL.CompileShader(shader) if !GL.GetShaderiv(shader, GL_COMPILE_STATUS) raise(GL.GetShaderInfoLog(shader)); end } return shader end def start() GLUT.MainLoop() end end Object3D.new().start()
あとはシェーダ。それぞれshaderディレクトリに突っ込む。まずはmmd.vert
varying vec3 normal; void main(void) { normal = gl_NormalMatrix * gl_Normal; gl_FrontColor = gl_Color; gl_Position = ftransform(); }
次にmmd.frag
uniform vec3 ambient; uniform float alpha; varying vec3 normal; vec3 lightDiffuse = vec3(1.0, 1.0, 1.0); vec3 lightDir = vec3(0.0, 0.0, 1.0); void main (void) { float cos = dot(normalize(normal), normalize(lightDir)); vec3 diffuse = lightDiffuse * max(0.0, cos); gl_FragColor = vec4(ambient + diffuse * gl_Color.rgb, alpha); }