マイペースなプログラミング日記

DTMやプログラミングにお熱なd-kamiがマイペースに書くブログ

モデル描画

前回のエントリの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);
}