IronPython.Direct3D.Tutorial5

분류 Python



Direct3D 튜토리얼5

텍스쳐를 입힌 바나나를 출력하는 예제입니다. 이제는 너무 쉬워서 하품이 나올 정도군요. 실행하기 위해서는 banana.bmp 파일이 예제 파일과 같은 디렉토리에 위치하고 있어야 합니다.

주의깊게 볼만한 부분은 CustomVertex.PositionNormalTextured 포멧이 조금 예상하고 다른 모습(Vector2 가 아니네요)이라는 것과 TextureLoader 를 사용하기 위해서는 'Microsoft.DirectX.Direct3DX' 레퍼런스를 추가 해줘야 한다는 점입니다.

class CustomVertex.PositionNormalTextured:
    Vector3 pos,
    Vector3 nor,
    float u, float v
);

이전 코드까지 pause 와 OnResize 이벤트를 사용안하고 있었는데, 이번 예제부터 추가해놓았습니다. Resize 하는데 전혀 장치 리셋 처리를 직접 안해도 된다는 점이 감명 깊습니다. this.OnResetDevice 란 함수를 등록시켜놓았기 때문이죠. c++ 프로그래머라면 클래스 멤버함수 콜백을 지원하는 System.EventHandler 가 기본으로 존재한다는 점만으로도 상당히 행복하지 않을까 생각되네요 : )

# 외부 모듈을 불러오기 위해서는 미리 선언을 해주어야 합니다.
import clr
clr.AddReferenceByPartialName('System.Drawing')
clr.AddReferenceByPartialName('System.Windows.Forms')

# .NET Framework 1.1 DirectX
clr.AddReferenceByPartialName('Microsoft.DirectX, Version=1.0.2902.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35')

# Direct3D 도 StrongName 으로 적는게 좋겠지만 지금은 귀찮아서 생략합니다.
# 2.0 에서는 아예 외부 dll은 사라지고 DirectX 에 통합되어버렸습니다.
clr.AddReferenceByPartialName('Microsoft.DirectX.Direct3D')

# TextureLoader 를 사용하기 위해서 필요합니다.
clr.AddReferenceByPartialName('Microsoft.DirectX.Direct3DX')

import System
from System import *
from System.Drawing import *
from System.Windows.Forms import *

from Microsoft.DirectX import *
from Microsoft.DirectX.Direct3D import *

import Microsoft
Direct3D = Microsoft.DirectX.Direct3D # 갑자기 무슨 변덕인지 로컬로 바인딩

pause = False
device = None
vertexBuffer = None
texture = None

class Textures(Form): # 윈도우를 만들기 위해서는 Form  에서 상속받는다
    def CreateDevice(this):
        '장치를 만든다' # 파이썬 이렇게 하면 함수 도움말이 되어 help() 함수로 확인 가능하다

        this.ClientSize = System.Drawing.Size(400, 300) # 화면 크기 설정
        this.Text = 'D3D Tutorial 05: Textures'         # 타이틀 설정

    def InitializeGraphics(this):
        'D3D 화면 상태 설정'
        presentParams = PresentParameters()
        presentParams.Windowed = True # 윈도우 모드 사용
        presentParams.SwapEffect = SwapEffect.Discard
        presentParams.EnableAutoDepthStencil = True # 드디어 깊이 버퍼 사용
        presentParams.AutoDepthStencilFormat = DepthFormat.D16 # 16비트 해상도

        global device # 전역 변수를 설정할 때는 global 로 설정해주어야 한다
        device = Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams)
        device.DeviceReset += this.OnResetDevice

        this.OnCreateDevice(device, None)
        this.OnResetDevice(device, None) # 렌더 스테이트가 들어가면서 리셋 부분이 포함되었습니다.

        global pause
        pause = False

    def OnCreateDevice(this, dev, event):
        '그래픽 장치 설정시'

        global vertexBuffer # 전역 변수를 설정할 때는 global 로 설정해주어야 한다.
        vertexBuffer = VertexBuffer(
            CustomVertex.PositionNormalTextured, 100,
            dev,
            Usage.WriteOnly, # 쓰기 전용으로 설정
            CustomVertex.PositionNormalTextured.Format,
            Pool.Default)

        # 에러 발생시 버퍼 재생성 함수 연결
        vertexBuffer.Created += this.OnCreateVertexBuffer # c# 에서는 System.EventHandle 를 사용하지만 파이썬에서는 그냥 연결한다

        # 버텍스 버퍼 생성시
        this.OnCreateVertexBuffer(vertexBuffer, None)

    def OnResetDevice(this, dev, event):
        '디바이스 복구시'

        # 디바이스 복구시 렌더스테이트 설정을 초기화한다
        dev.RenderState.CullMode = Cull.None    # 백페이스 컬링 안함
        dev.RenderState.Lighting = False        # 라이팅 사용 안함
        dev.RenderState.ZBufferEnable = True    # 깊이 버퍼 사용

        # 텍스쳐를 읽어옵니다
        global texture
        texture = TextureLoader.FromFile(dev, 'banana.bmp') # Microsoft.DirectX.Direct3DX 모듈이 필요합니다.

    def OnCreateVertexBuffer(this, vb, event): # OnCreateDevice 의 On- 는 After 의미인데, OnCreateVertexBuffer 의 On- 는 Before 의 의미다
        '버텍스 버퍼 생성시'

        # 락을 건다
        # c# 에서는 LockFlags 를 0 으로 설정해도 대충 넘어가지만
        # 파이썬에서는 엄격하게 체크한다
        # Vertices 예제와 Lock 함수가 미묘하게 다르므로 주의해야한다
        verts = vb.Lock(0, LockFlags.None) # 버텍스 배열을 직접 리턴한다

        # 실린더를 만듭니다
        for i in xrange(50):
            theta = (2 * Math.PI * i) / 49
            sin_theta = Math.Sin(theta)
            cos_theta = Math.Cos(theta)
            verts[2 * i] = CustomVertex.PositionNormalTextured(
                Vector3(sin_theta, -1, cos_theta),  # Position
                Vector3(sin_theta,  0, cos_theta),  # Normal
                i/float(50-1), 1.0                  # TextureUV
            )
            verts[2 * i + 1] = CustomVertex.PositionNormalTextured(
                Vector3(sin_theta, 1, cos_theta),   # Position
                Vector3(sin_theta, 0, cos_theta),   # Normal
                i/float(50-1), 0.0                  # TextureUV
            )

        vb.Unlock() # 락을 푼다

    def Render(this):
        if not device:
            print 'error'
            return;

        if pause:
            return

        # 화면을 파란색으로 지운다
        device.Clear(ClearFlags.Target|ClearFlags.ZBuffer, System.Drawing.Color.Blue, 1.0, 0)

        # 장면 시작
        device.BeginScene()

        # 월드 매트릭스 설정
        this.SetupMatrices()

        # 텍스쳐 스테이지 설정
        device.SetTexture(0, texture)
        device.TextureState[0].ColorOperation = TextureOperation.Modulate
        device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor
        device.TextureState[0].ColorArgument2 = TextureArgument.Diffuse
        device.TextureState[0].AlphaOperation = TextureOperation.Disable

        # 버텍스 버퍼 스트림 등록
        device.SetStreamSource(0, vertexBuffer, 0)

        # 렌더링 버텍스 포멧 설정
        device.VertexFormat = CustomVertex.PositionNormalTextured.Format

        # 도형을 그린다
        device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, (4*25)-2)

        # 장면 종료
        device.EndScene()
        device.Present()

    def SetupMatrices(this):
        '월드 매트릭스 정보를 설정한다'

        # 축을 중심으로돌려라
        tick_step   = Environment.TickCount / 250.0
        roll_step   = Environment.TickCount / 1000.0
        axis        = Vector3(Math.Cos(tick_step), 1, Math.Sin(tick_step))
        device.Transform.World = Matrix.RotationAxis(axis, roll_step)

        # 실린더 캠빨 바라보기
        device.Transform.View = Matrix.LookAtLH(
            Vector3(0.0, 3.0, -5.0),
            Vector3(0.0, 0.0, 0.0),
            Vector3(0.0, 1.0, 0.0)
        )

        # 렌즈를 부착해 시야 확보
        device.Transform.Projection = Matrix.PerspectiveFovLH(
            Math.PI / 4, 1.0, 1.0, 100.0)

    def OnPaint(this, event):
        this.Render()

    def OnResize(this, event):
        global pause
        pause = (this.WindowState == FormWindowState.Minimized) or not this.Visible

    def OnKeyPress(this, event):
        # ESC 키와 비교하는 것인데 c# 을 잘모르는 상황이라
        # 일단 이렇게 숫자로 처리하도록 수정했다
        if event.KeyChar == 27: #System.Windows.Forms.Keys.Escape:
            this.Close()

frm = Textures()
frm.CreateDevice()
frm.InitializeGraphics()
frm.Show()

while (frm.Created):
    frm.Render()
    Application.DoEvents()