Graphics/DirectX

Direct X - 14. 조명효과 사용하기

MOLOKINI 2014. 6. 9. 00:18
3D 물체에 조명을 쏘는 것!

우선 우리 조명에 대해서 알고 들어갑시다
라이트(조명)는 메테리얼, 라이트, 법선이 있다



메테리얼
메테리얼은, 물질의 색깔이다, 기본적으로 개개의 3D모델마다 메테리얼의 파라미터를 보관 유지해주고 있다,
하지만, 메테리얼은 라이트와 함께 쓰지 않는다면 의미가 없다
어쨋든 라이트를 쓸려면 메테리얼을 꼬옥 써야한다는 것.
메테리얼의 색은 정점의 색과는 다르다
메테리얼에는 여러개의 파라미터가 있어서 물질의 질감표현에 다양한 변화를 줄 수 있다
Material 구조체
Diffuse : 물질의 기본색
Ambient : 앰비언트 받았을 때의 색(라이트가 맞지 않아도 보인다)
Specular : 경면 반사광(자동차의 광택같이 강하게 보인다)
SpecularSharpness : 반사의 날카로움(Specular의 날카로움)
Emissive : 발산빛(스스로 빛난다)


라이트와 법선
라이트를 사용하는 경우에는 법선이 반드시 필요하게 된다.
이게 바로 그늘을 결정하는 파라미터가 되는 것
법선은 정점데이터로 설정한다.
라이트와 물체의 위치관계로 조명이 어디에 있느냐에 따라 물체의 어디가 밝고 어디가 어두울지는 간단하게 머리속에 그려볼 수 있다.

면의 명암은 라이트가 있는 방향을 향하는 면은 밝고, 반대편은 어두워진다

이런 방향을 법선이라구 한다


박스로 설정하는 법선의 종류는 두가지가 있다

Flat Shading : 면과 면 사이가 모가 나 보이도록 한다. 요고는 완전하게 면의 법선과 같은 방향을 향하고 있기 때문에 그래서 모가 나 보이는 것이다, 정점을 공유할 수 없는 약점이 있다, 그런데 표현을 하다보면 이렇게 안하면 안되는 경우도 있기도 하다

Gouraud shading : 면과 면의 사이가 라이트를 대는 방법에 따라 뭉게져보이듯이 한다. 정점을 공유하므로 데이터의 양이 줄어드는 장점이 있다, 약점은 정점의 법선이 면의 방향과 같지 않기 때문에 그니까 바로 위에 라이트를 대면 윗면이 100%라이트를 받을 수 없다


더하자면

DirectX에서 두개의 쉐이드 모드를 구현해 놨는데, Flat과 Gouraud야, Flat은 말 그대로 면에 대한 법선 벡터로만 빛의 음영을 결정한다, 그렇기 때문에 물체가 각이 져서 나온다

반면에, Gouraud는 공유하고 있는 각 정점의 법선벡터의 평균치를 정점의 법선벡터로 잡고 면의 경우 각 정점사이를 보간(interpolation)하여 부드럽게 표현해준다

 

  

 

그림으로보니까 Flat과 Gouraud의 차이를 알겠죠?


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 Material _material = new Material();

        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._material.Diffuse = Color.FromArgb(255, 255, 255, 255);

            this._device.Lights[0].Type = LightType.Directional;
            this._device.Lights[0].Diffuse = Color.White;
            this._device.Lights[0].Enabled = true;
            this._device.Lights[0].Update();       

            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)));

            Matrix mat = Matrix.RotationY(Environment.TickCount / 1000.0f);
            this._device.Lights[0].Direction = Vector3.TransformCoordinate(Vector3.Normalize(new Vector3(0.0f, -1.5f, 2.0f)), mat);
            this._device.Lights[0].Update();
            
            this.SettingCamera(lensPosition);
                        
            // 화면을 단색(파랑색)으로 클리어
            this._device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);

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

            this._device.Material = this._material; 
            
            this.RenderSquare();                       
                        
            // 좌표 x=0 y=0 의 위치에 흰색으로 출력
            this._font.DrawText(null, "조명효과 사용하기", 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();
            }
        }
    }
}


        private Material _material = new Material();
박스용 메테리얼을 선언하자, 그냥 전역으로 안하고 매번 불러써도 괜찮아요


           this._vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionNormal),
                8, this._device, Usage.None, CustomVertex.PositionNormal.Format, Pool.Managed);
다른건없어, PositionColored였지만, 이번에는 PositionNormal이다
이게바로 위치와 법선 정보를 갖게하는 구조체


            vertices[0] = new CustomVertex.PositionNormal(new Vector3(-2.0f, 2.0f, 2.0f), Vector3.Normalize(new Vector3(-1.0f, 1.0f, 1.0f)));
            vertices[1] = new CustomVertex.PositionNormal(new Vector3(2.0f, 2.0f, 2.0f), Vector3.Normalize(new Vector3(1.0f, 1.0f, 1.0f)));
            vertices[2] = new CustomVertex.PositionNormal(new Vector3(-2.0f, 2.0f, -2.0f), Vector3.Normalize(new Vector3(-1.0f, 1.0f, -1.0f)));
            vertices[3] = new CustomVertex.PositionNormal(new Vector3(2.0f, 2.0f, -2.0f), Vector3.Normalize(new Vector3(1.0f, 1.0f, -1.0f)));
            vertices[4] = new CustomVertex.PositionNormal(new Vector3(-2.0f, -2.0f, 2.0f), Vector3.Normalize(new Vector3(-1.0f, -1.0f, 1.0f)));
            vertices[5] = new CustomVertex.PositionNormal(new Vector3(2.0f, -2.0f, 2.0f), Vector3.Normalize(new Vector3(1.0f, -1.0f, 1.0f)));
            vertices[6] = new CustomVertex.PositionNormal(new Vector3(-2.0f, -2.0f, -2.0f), Vector3.Normalize(new Vector3(-1.0f, -1.0f, -1.0f)));
            vertices[7] = new CustomVertex.PositionNormal(new Vector3(2.0f, -2.0f, -2.0f), Vector3.Normalize(new Vector3(1.0f, -1.0f, -1.0f)));
구조체가 바뀌었으니까 색이 아니고 법선을 대신 설정하는 것
Vector3.Normalize메서드로 정규화시킨다


            this._material.Diffuse = Color.FromArgb(255, 255, 255, 255);
박스의 메테리얼 설정 세팅, 물질의 기본색만 설정한 것이다


            this._device.Lights[0].Type = LightType.Directional;
            this._device.Lights[0].Diffuse = Color.White;
            this._device.Lights[0].Enabled = true;
            this._device.Lights[0].Update();  
저번까지 우리는 라이트를 끄고했다
이제는 라이트를 켜야하니까 설정을 세팅할 것이다
라이트에는 세가지 종류가 있어, Directional, Point, Spot
오늘은 가장 잘 쓰이는 Directional을 쓸겁니다



Device 클래스에는 DeviceLight 컬렉션이있어, 우리는 0번째 인덱스를 쓸 것입니다
1 이상의 인덱스를 사용하면 복수의 라이트를 사용할 수도 있다
라이트의 종류와 색을 설정했고
Ambient를 쓰고있으니까, 라이트의 방향을 설정해줘야 한다
LightType.Directional은 앰비언트 디렉셔널 광선을 사용한다는거야 이게 방향
그리구 0번째 라이트를 사용할 거니까 마지막에 Update해주면 되는 것


            Matrix mat = Matrix.RotationY(Environment.TickCount / 1000.0f);
            this._device.Lights[0].Direction = Vector3.TransformCoordinate(Vector3.Normalize(new Vector3(0.0f, -1.5f, 2.0f)), mat);
            this._device.Lights[0].Update();
여기서는 시간에 따라 라이트의 방향을 회전시키고 있어요
박스 위에서 라이트가 빙빙빙도는 겁니다

ㅇㅋ?
라이트의 위치가 Y축을 중심으로 시간에 의해서 회전하고있는 것
라이트의 방향이 정해지면 이것 역시 Update!!


             this._device.Material = this._material; 

박스를 그리기 전에 메테리얼을 먼저 세트한다는것!

세트하는 정점 포맷이 다른 것일 뿐 기본적인 변화는 없습니다