Graphics/DirectX

Direct X - 13. IndexBuffer를 이용한 상자 만들기

MOLOKINI 2014. 6. 9. 00:11
인덱스버퍼를 사용해서 육면체를 만든다




using System;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Windows.Forms;
using System.Drawing;

namespace MDXSample
{
    /// <summary>
    /// 메인 샘플 클래스
    /// </summary>
    public partial class MainSample : IDisposable
    {
        /// <summary>
        /// 정점 버퍼
        /// </summary>
        private VertexBuffer _vertexBuffer = null;
        private IndexBuffer _IndexBuffer = null;

        private static Int16[] _vertexIndices = new Int16[] { 2, 0, 1, 1, 3, 2, 4, 0, 2, 2, 6, 4, 5,
            1, 0, 0, 4, 5, 7, 3, 1, 1, 5, 7, 6, 2, 3, 3, 7, 6, 4, 6, 7, 7, 5, 4 };

        private float _lensPosTheta = 270.0f;
        private float _lensPosPhi = 0.0f;

        private Point _oldMousePoint = Point.Empty;

        public bool InitializeApplication(MainForm topLevelForm)
        {
            // 폼의 참조를 보관 유지
            this._form = topLevelForm;

            PresentParameters pp = new PresentParameters();
            pp.Windowed = true;
            pp.SwapEffect = SwapEffect.Discard;
            pp.EnableAutoDepthStencil = true;
            pp.AutoDepthStencilFormat = DepthFormat.D16;
      
            topLevelForm.MouseMove += new MouseEventHandler(this.form_MouseMove);
            
            try
            {
                // Direct3D 디바이스 작성
                this.CreateDevice(topLevelForm, pp);
                // 폰트의 작성
                this.CreateFont();

            }
            catch (DirectXException ex)
            {
                // 예외 발생
                MessageBox.Show(ex.ToString(), "에러", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            this.SettingCamera();
            this.Square();

            this._device.RenderState.Lighting = false;           

            return true;
        }

        /// <summary>
        /// 메인 루프 처리
        /// </summary>
        public void MainLoop()
        {
            float radius = 10.0f;
            float theta = Geometry.DegreeToRadian(this._lensPosTheta);
            float phi = Geometry.DegreeToRadian(this._lensPosPhi);
            Vector3 lensPosition = new Vector3((float)(radius * Math.Cos(theta) * Math.Cos(phi)), 
                (float)(radius * Math.Sin(phi)), (float)(radius * Math.Sin(theta) * Math.Cos(phi)));
            
            this.SettingCamera(lensPosition);
            
            
            // 화면을 단색(파랑색)으로 클리어
            this._device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);

            // 「BeginScene」와「EndScene」의 사이에 화면에 출력할 내용을 코딩
            this._device.BeginScene();

            this.RenderSquare();
                        
            // 좌표 x=0 y=0 의 위치에 흰색으로 출력
            this._font.DrawText(null, "3D 육면체 출력", 0, 0, Color.White);
            
            // 화면출력은 여기까지
            this._device.EndScene();

            // 실제의 디스플레이에 출력
            this._device.Present();

        }

        private void form_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                this._lensPosTheta -= e.Location.X - this._oldMousePoint.X;
                this._lensPosPhi += e.Location.Y - this._oldMousePoint.Y;
            }
            this._oldMousePoint = e.Location;
        }

        /// <summary>
        /// 자원의 파기
        /// </summary>
        public void Dispose()
        {
            if (this._IndexBuffer != null)
            {
                this._IndexBuffer.Dispose();
            }
            // 정점 버퍼를 해제
            if (this._vertexBuffer != null)
            {
                this._vertexBuffer.Dispose();
            }

            // 폰트 자원을 해제
            if (this._font != null)
            {
                this._font.Dispose();
            }
            // Direct3D 디바이스의 자원 해제
            if (this._device != null)
            {
                this._device.Dispose();
            }
        }
    }
}

- Square 메서드
        private void Square()
        {
            // 사각형 다각형을 표시하기 위한 정점 버퍼를 작성
            this._vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored),
                8, this._device, Usage.None, CustomVertex.PositionColored.Format, Pool.Managed);

            // 4점의 정보를 보관하기 위한 메모리를 확보
            CustomVertex.PositionColored[] vertices = new CustomVertex.PositionColored[8];

            // 각 정점을 설정
            vertices[0] = new CustomVertex.PositionColored(-2.0f, 2.0f, 2.0f, Color.Yellow.ToArgb());
            vertices[1] = new CustomVertex.PositionColored(2.0f, 2.0f, 2.0f, Color.Gray.ToArgb());
            vertices[2] = new CustomVertex.PositionColored(-2.0f, 2.0f, -2.0f, Color.Purple.ToArgb());
            vertices[3] = new CustomVertex.PositionColored(2.0f, 2.0f, -2.0f, Color.Red.ToArgb());
            vertices[4] = new CustomVertex.PositionColored(-2.0f, -2.0f, 2.0f, Color.SkyBlue.ToArgb());
            vertices[5] = new CustomVertex.PositionColored(2.0f, -2.0f, 2.0f, Color.Wheat.ToArgb());
            vertices[6] = new CustomVertex.PositionColored(-2.0f, -2.0f, -2.0f, Color.Violet.ToArgb());
            vertices[7] = new CustomVertex.PositionColored(2.0f, -2.0f, -2.0f, Color.Gold.ToArgb());

            this._IndexBuffer = new IndexBuffer(this._device, 12 * 3 * 2, Usage.WriteOnly, Pool.Managed, true);

            using (GraphicsStream data = this._vertexBuffer.Lock(0, 0, LockFlags.None))
            {
                // 인덱스 데이터를 인덱스 버퍼에 씁니다
                data.Write(vertices);

                // 인덱스 버퍼의 락을 해제합니다
                this._vertexBuffer.Unlock();
            }

            // 인덱스 버퍼를 잠근다
            using (GraphicsStream data = this._IndexBuffer.Lock(0, 0, LockFlags.None))
            {
                // 인덱스 데이터를 인덱스 버퍼에 씁니다
                data.Write(_vertexIndices);

                // 인덱스 버퍼의 락을 해제합니다
                this._IndexBuffer.Unlock();
            }            
        }

- RenderSquare 메서드
        private void RenderSquare()
        {
            // 정점 버퍼를 디바이스의 데이터 스트림에 바인드
            this._device.SetStreamSource(0, this._vertexBuffer, 0);

            // 그릴려는 정점의 포맷을 세트
            this._device.VertexFormat = CustomVertex.PositionColored.Format;

            this._device.Indices = this._IndexBuffer;

            // 렌더링(그리기)
            this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);
        }



이 소스를 해석하기 전에, 먼저 박스에 대해서 생각을 좀 해보자
상자는 여섯개의 면으로 구성되어있고,
하나의 사각형은 삼각형 두개로 구성되어있다
그렇타면 내가 만들어야하는 면이 2*6 = 12개가 된다는 것.
그리구 삼각형 정점은 세개, 그럼 정점의 수는 12*3 = 36개가 된다.
그래서 VertexBuffer만으로는 36개의 데이터를 상자의 형태가 되도록 출력이 가능하다 (TriangleStrip의 경우에는 24개 만으로도 가능하지)

이쯤에서 상자를 생각해보면, 상자의 모서리는 8개
위치정보는 8개만으로도 충분하다는 것
정점데이터의 수는 많으면 많을수록 오버헤드가 생기기 때문에, 이 정점데이터의 수를 줄이는게 바로 인덱스버퍼
위치정보는 8개로도 가능하지만 다각형의 정보는 36개가 필요한데, 이거를 8개의 정점으로 공유해줄 수 있는게 바로 인덱스버퍼다



  private static Int16[] _vertexIndices = new Int16[] { 2, 0, 1, 1, 3, 2, 4, 0, 2, 2, 6, 4, 5,

            1, 0, 0, 4, 5, 7, 3, 1, 1, 5, 7, 6, 2, 3, 3, 7, 6, 4, 6, 7, 7, 5, 4 };
의미없는 숫자같지만 사실 그렇지 않다
이걸 삼각형의 정점으로 보면..

2, 0, 1,  // 첫번째 다각형

1, 3, 2,  // 두번째 다각형

4, 0, 2, 

2, 6, 4, 

5, 1, 0, 

0, 4, 5, 

7, 3, 1, 

1, 5, 7, 

6, 2, 3, 

3, 7, 6, 

4, 6, 7, 

7, 5, 4   // 열두번째 다각형

이걸 위의 정육면체 번호랑 연결해서 보시길

이게바로 정점번호배열이야, 36개의 배열을 갖고있는데, 8개의 정점 데이터 중에 몇번째 정점 데이터를 사용할 것이냐? 하는 의미가 되는 것이다

0~7사이의 인덱스를 사용하고있다


한마디 더하자면, 배열의 형태가 int16인데 short[]로 해도 괜찮다(2바이트) int(4바이트)로 하는 경우도 있는데, 정점의 수가 65535를 넘어가는 경우엔 int로한다 4바이트는 메모리 낭비가 심하니까^^



            this._vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored),
                8, this._device, Usage.None, CustomVertex.PositionColored.Format, Pool.Managed);

            // 4점의 정보를 보관하기 위한 메모리를 확보
            CustomVertex.PositionColored[] vertices = new CustomVertex.PositionColored[8];

            // 각 정점을 설정
            vertices[0] = new CustomVertex.PositionColored(-2.0f, 2.0f, 2.0f, Color.Yellow.ToArgb());
            vertices[1] = new CustomVertex.PositionColored(2.0f, 2.0f, 2.0f, Color.Gray.ToArgb());
            vertices[2] = new CustomVertex.PositionColored(-2.0f, 2.0f, -2.0f, Color.Purple.ToArgb());
            vertices[3] = new CustomVertex.PositionColored(2.0f, 2.0f, -2.0f, Color.Red.ToArgb());
            vertices[4] = new CustomVertex.PositionColored(-2.0f, -2.0f, 2.0f, Color.SkyBlue.ToArgb());
            vertices[5] = new CustomVertex.PositionColored(2.0f, -2.0f, 2.0f, Color.Wheat.ToArgb());
            vertices[6] = new CustomVertex.PositionColored(-2.0f, -2.0f, -2.0f, Color.Violet.ToArgb());
            vertices[7] = new CustomVertex.PositionColored(2.0f, -2.0f, -2.0f, Color.Gold.ToArgb());
Square 메서드야,.
정점버퍼는 36개가 아니라 8개만 가지고 한다 그거말고는 크게 다를건 없다


 this._IndexBuffer = new IndexBuffer(this._device, 12 * 3 * 2, Usage.WriteOnly, Pool.Managed, true);
인덱스버퍼를 사용하기 때문에 인덱스버퍼를 써야한다
구조체에 건네주는 데이터는 아래를 참고하세요
IndexBuffer 구조체
device : Direct 3D 디바이스
sizeOfBufferInBytes : 인덱스 데이터의 바이트 수를 건네준다, 위의 코멘트대로 건네주면 72바이트가 되는 것
usage : 사용법, 여기서는 Usage.WriteOnly
pool : 자원을 배치하는 유효한 클래스 지정, 특별한게 없으면 pool.Managed
sixteenBitIndices : 인덱스버퍼가 16비트의 인덱스를 보관하는 경우 true를 지정


            // 정점 버퍼를 디바이스의 데이터 스트림에 바인드
            this._device.SetStreamSource(0, this._vertexBuffer, 0);

            // 그릴려는 정점의 포맷을 세트
            this._device.VertexFormat = CustomVertex.PositionColored.Format;

            this._device.Indices = this._IndexBuffer;

            // 렌더링(그리기)
            this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);
처음 두줄은 변화없고
Device.Indices에는 사용하는 Index buffer를 세팅한다
마지막 그리기 메서드에서는 DrawIndexedPrimitives 메서드를 사용
DrawIndexedPrimitives 구조체
primitiveType : 출력하는 소스의 종류 (이번에는 TriangleList)
baseVertex : 인덱스 버퍼의 선두로부터 최초의 정점 인덱스까지의 오프셋, 없으면 0
minVertexIndex : 호출로 사용되는 정점의 최소 정점 인덱스, 이것도 별거없으면 0
numVertices : 정점 데이터의 수, 기본적으로 정점 버퍼에 쓴 정점수
startIndex : 인덱스 배열에 있어서의 정점의 읽기 캐시 위치, 별거없으면 0
primCount : 랜더링하는 소스의 개수, 삼각형 열두개니까 12개