전체 글 (100)

  • 2021.12.24
  • 2021.12.24
  • 2021.12.22
  • 2021.12.21
  • 2021.12.21
  • 2021.12.20
  • 2021.12.20
  • 2021.12.20
  • 2021.12.17
  • 2021.12.17
  • 2021.12.17
  • 2021.12.16
  • 12
    24

     

    Visual Studio 2019

    Visual Studio에서 유니코드 문자 집합 사용이

    기본으로 되어 있는것을 볼 수 있다.

    멀티바이트로도 설정이 가능하다.

     

    멀티바이트, 유니코드는 무슨 차이이냐면

     

    아스키코드 문자 하나는 1Byte를 표시한다.

    1바이트만 쓸 수 있어 원래는 128문자만 가능하다

    한글이 안된다.

     

    그래서 총 2바이트 멀티바이트가 등장함

     

    유니코드는 2바이트 문자열로 컴퓨터에서 사용하는 

    모든 문자를 포함하도록 만들어졌다.

     

    둘다 2바이트인데 무슨 차이냐면

    멀티바이트는 영어는 1바이트 한글은 2바이트로 표현한다.

    유니코드는 한 문자를 2바이트로 통일한다.

     

    그런 차이가 있다.

     


    장난입니다.

     

    MessageBox로 메세지를 전달하고, 사용자에게 선택을 요할 수 있다.

     

     

    MessageBoxW와 MessageBoxA의 차이는 뭘까

    딱보면 알겠지만 W 유니코드, A는 멀티바이트 용 메세지 박스이다.

     

    MessageBox는 유니코드용은 아니고, 전처리기에서

    앞서 보여준 속성페이지에서 문자집합에 따라 달라진다.

    F12 누르고 들어가보면 

     

    사실 저는 W

    유니코드용 MessageBoxW인것을 확인 할 수 있다.

     

    MessageBoxW(
        _In_opt_ HWND hWnd,
        _In_opt_ LPCWSTR lpText,
        _In_opt_ LPCWSTR lpCaption,
        _In_ UINT uType);

     

    hWnd : 메시지 박스를 소유할 핸들값

    IpText : 내용 문자열 형식

    IpCaption : 창의 제목

    uType : 출력될 아이콘, 버튼 종류 나열

     

     

    ex)

    예제

     

    int check 결과 값을 반환해와서 IDOK면 한번 더 메세지를 띄운다.

    아니라면 return 0으로 윈도우 종료

     

    COMMENT
     
    12
    24

    콘솔에 int main()이라는 메인 함수가 있듯이, 

     

    Window 창에도 wWinmain()이라는 메인 함수가 있다.

    윈도우는 메시지 기반으로 모든 처리를 한다. 이거는 아래서

    좀더 구체적으로 써보겠다. 아래는 wWinmain() 함수이다.

     

    wWinmain() 예시

    여러 인자가 있는데 생성할때 운영체제에서 받아오는

    정보이다. _In_opt_은 옵션이란 뜻으로 그만 없어도 그만임 

     

    Window 창을 띄우기 위해서는

     

    1. 윈도우 클래스 등록

    2. 윈도우 생성

     

    과정을 해야한다.

    윈도우 클래스는 WNDCLASS,

    WNDCLASSEX,

    WNDCLASSEXW,

    추가적인 정보를 담는 차이가 있지만

    결과적으로 모두 다 사용 가능하다.

     

    다만 WNDCLASS로 클래스를 만들었으면 

    WNDCLASS로 등록을 해야한다. 

    나는 이번에 WNDCLASSEXW로 만들어보겠다.

     

    https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexw

     

    WNDCLASSEXW (winuser.h) - Win32 apps

    Contains window class information.

    docs.microsoft.com

     

    윈도우 MSDN를 참고하면 

    해당 구조체의 내용이 나온다.

    이 구조체를 모두 채워서 등록을 해야하나커서, 아이콘처럼 몇몇는 null값으로 두면알아서 기본값이 들아간다.

     

    typedef struct tagWNDCLASSEXW {
      UINT      cbSize;
      UINT      style;
      WNDPROC   lpfnWndProc;
      int       cbClsExtra;
      int       cbWndExtra;
      HINSTANCE hInstance;
      HICON     hIcon;
      HCURSOR   hCursor;
      HBRUSH    hbrBackground;
      LPCWSTR   lpszMenuName;
      LPCWSTR   lpszClassName;
      HICON     hIconSm;
    } WNDCLASSEXW, *PWNDCLASSEXW, *NPWNDCLASSEXW, *LPWNDCLASSEXW;

     

    이름만 봐도 어떠한 정보를 관리하는지 알 수 있다.

    ZeroMemory로 구조체를 모두 초기화한 후에 필요한 내용을 채워준다.

     

    채워줌, lpfnWndProc는 메세지 핸들러를 등록하는 것.

     

    클래스 이름을 dd로 했다.

    이제 윈도우클래스를 등록하면 된다.

     

    등록

     

    등록을 했으면 이제 생성을 해야한다. CreateWindowEXW로 생성한다.

     

    EX로도 생성이 된다. EXW랑 호환이 되나보다.

     

    클래스 이름과 같은 이름 dd로 윈도우를 생성한다.

    생성한 윈도우를 m_hWnd에 담음

    m_hWnd로 ShowWindow해서 윈도우를 보이게 한다.

     

     

     

    hWnd 핸들러나, 윈도우 크기나 여러 곳에서 쓰이니 

    외부변수로 꺼내준다.

     

    이제 윈도우 창 초기화는 끝났다.

     


     

    윈도우 애플리케이션은 메시지 기반으로 처리한다고 했다.

    메시지 루프라는 반복 구조로 큐에 모인 메세지를 하나씩 꺼내

    순서대로 처리하는 방식이다. 

     

    순서대로 처리하다보니, 하나의 메세지가 오래걸리는 작업이라면

    그 시간동안은 다른 작업을 할 수 가 없게 되어 버린다.

     

    그 해결방안으로는 처리를 분할하거나, 멀티스레드를 이용하는 방법이 있다.

     

    메시지를 받는 방식으로 GetMessage 와 PeekMessage가 있는데

    GetMessage는 메시지를 꺼내고 큐에서 삭제하는 반면,

     

    PeekMessage API는 메시지가 있는 경우 그 이벤트를 우선처리하고

    메시지가 없는 경우에 다른 일을 하게 할 수 있다.  

     

    Run
    PeekMessageW

    메시지(예를 들어 창닫기, 스크롤, 창 이동 등)가 있다면 

    메시지를 수행하도록하고 아닐 경우에는 다른 활동을 하게

    한다. 

     

    더보기
    //cpp
    #include "KWindow.h"
    #include <assert.h>
    KWindow* g_pWindow = nullptr;
    HWND  g_hWnd;
    RECT  g_rtClient;
    
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        // 메세지 핸들링
        assert(g_pWindow);
        return g_pWindow->MsgProc(hWnd, message, wParam, lParam);    
    }
    LRESULT  KWindow::MsgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)
        {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    }
    
    // 1. 윈도우 클래스 등록
    // 2. 윈도우 생성
    bool   KWindow::InitWindows(
    	HINSTANCE hInstance,
    	int nCmdShow,
    	const WCHAR* strWindowTitle)
    {
    	m_hInstance = hInstance;
    
        WNDCLASSEXW wcex;
        ZeroMemory(&wcex, sizeof(WNDCLASSEXW));
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = WndProc; //메시지 핸들러 등록
        wcex.hInstance = hInstance;
        wcex.hbrBackground = CreateSolidBrush(RGB(0,0,0));
        wcex.lpszClassName = L"dd";
        wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    
        if (!RegisterClassExW(&wcex))
        {
            return false;
        }
        RECT rc = { 0, 0, 800, 600 };
        // 작업영역(  타이틀 바/경계선/메뉴/스크롤 바 등의 영역을 제외한 영역), 윈도우 스타일, 메뉴여부
        AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
    	// 윈도우 생성 함수
    	m_hWnd = CreateWindowExW(
            0,
            L"dd",
            strWindowTitle,
            WS_OVERLAPPEDWINDOW,
            0,
            0,
            rc.right-rc.left,
            rc.bottom-rc.top,
            NULL,
            NULL,
            hInstance,
            NULL);
        if (m_hWnd == NULL)
        {
            return false;
        }
       
        GetWindowRect(m_hWnd, &m_rtWindow);
        GetClientRect(m_hWnd, &m_rtClient);
        
        g_hWnd = m_hWnd;
        g_rtClient = m_rtClient;
        
        // WM_SHOW
        
        ShowWindow(m_hWnd, nCmdShow);
    
    	return true;
    }
    
    bool	KWindow::Run()
    {
        GameInit();
        MSG msg;
        while (m_bGameRun)
        {
            if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
            {
                if (msg.message == WM_QUIT)
                {
                    break;
                }
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            else
            {
                m_bGameRun = GameRun();            
            }
        }
        GameRelease();
    	return true;
    }
    bool	KWindow::GameRun()
    {
        return true;
    }
    bool	KWindow::GameInit()
    {
        return true;
    }
    bool	KWindow::GameRelease() 
    {
        return true;
    }
    KWindow::KWindow() : m_bGameRun(true)
    {
        g_pWindow = this;
    }
       
    //h
    #pragma once
    #include <windows.h>
    #include "KDevice.h"
    class KWindow : public KDevice
    {
    public:
    	RECT m_rtWindow;
    	RECT m_rtClient;
    
    	HINSTANCE	m_hInstance;
    	HWND		m_hWnd;
    	bool		m_bGameRun;
    public:
    	bool   InitWindows(HINSTANCE hInstance,
    		int nCmdShow,
    		const WCHAR* strWindowTitle);	
    	LRESULT MsgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    	bool	Run();
    	virtual bool	GameInit();
    	virtual bool	GameRun();
    	virtual bool	GameRelease();
    public:
    	KWindow();
    };

     

    COMMENT
     
    12
    22

    외곽선은 명암이나 색조가 확 달라지는 픽셀을 찾은 결과이다.

    현재 픽셀을 기준으로 상하 좌우 텍셀을 체크하면서 외곽선인지 판단하면 된다.

    이때 컨벌루션(Convolution)이란 것을 사용한다.

    컨벌루션 : 블러나 샤프닝 등 효과에 쓰임,
    현재 픽셀 기준, 주위 펙셀마다 가중치를 곱해서 결과를
    모두 더한 값으로 현재 픽셀의 값을 변경하는 연산이다.

    소벨 연산자(sobel operator)의 커널을 이용해 컨벌루션을 해 외곽선을 찾을 수 있다.

    소벨 연산자의 3x3 커널

    Kx는 좌우연산, Ky는 상하연산을 한다. 결과값은 Lx, Ly이라고 하자.

    현재의 픽셀이 0.9라고 가정을 하자.

    주변 픽셀들도 0.9로 모두 같은 모습이다.
    이러면 외곽선이 생길 일이 없겠죠?
    저 소벨 연산자의 커널을 곱해서 다 더한 값은 0이다.
    -0.9 + 0.9 + -1.8 + 1.8 + -0.9 + 0.9 = 0

    명암차가 클 수록 Lx의 값도 커진다.
    또한 왼쪽이 오른쪽보다 크면 값이 작아지고 (음수)
    오른쪽이 더 크다면 값이 커진다 (양수).
    결국 0이 아니면 외곽선이 있다는 것.

    기존 흑백 셰이더에서
    이어서 해서 버텍스 셰이더는 생략하겠다.

    PS 픽셀 셰이더

    픽셀 셰이더 전체 코드


    앞에서 이야기한 커널 3x3 행렬을 만들고
    for문을 돌리며 주변 픽셀들 마다 Kx, Ky를 곱한다.

    2중 for문을 돌리며 Kx,Ky를 곱해서 더한 결과를 Lx Ly에 저장함


    픽셀 하나 하나 불러와야하는데 옆 픽셀과 현재 픽셀의 UV좌표 차이를 알아야한다.
    UV좌표는 0~1이고, U는 1/텍스쳐 width, V는 1/텍스쳐 height의 차이가 난다.

    이건 gPixelOffset 전역변수에

    뷰포트 디멘션 인벌스를 시멘틱으로 나누기를 생략함


    이렇게 해서 텍스쳐를 불러온다.

    주변 픽셀들에 커널을 곱해서 가중치를 구했다면 피타고라스 정리처럼 계산해야한다.
    hlsl 함수인 sqrt() 루트 연산 함수를 쓴다. (pow()는 제곱)

    Lx와 Ly를 합치려면 피타고라스 정리를 사용함


    책에서 그냥 더해도 된다는데 빗변의 길이를 구하는 피타고라스 정리를 사용했다.

    luminance는 우리가 흑백셰이더 만들었을때 그 위키피디아에서 인간 색영역에 맞춘 공식이다.

    그 결과를 리턴하면 된다.

    결과


    양각효과

    는 위 내용과 비슷하다. 커널을 좀 수정한다.

    왼쪽 위 픽셀이 오른쪽 아래 픽셀보다 밝다면 그림자를 드리움


    컨벌루션의 결과를 누적하는 res를 리턴한다.

    결과

    이렇게 외곽선과 엠보스 효과를 적용해 보았다.
    책에서는 색상보정, HDR효과, 비네트효과, Dof효과, SSAO 효과, 잔상효과 등
    여러 영상처리기법을 소개했다.

    책을 보며 따라 구현하며 빛, 그림자, 영상처리기법을
    완벽히는 아니여도 흐름을 이해하는 기회도 있었지만,
    무엇보다 셰이더와 친해질 수 있는 기회가 되어 좋았다.

    좋은 책을 써주신 김포프 교수님께 존경을 표한다.

    더보기

    //vs

    struct VS_INPUT
    {
    float4 mPosition : POSITION;
    float2 mUV : TEXCOORD0;
    };

    struct VS_OUTPUT
    {
    float4 mPosition : POSITION;
    float2 mUV : TEXCOORD0;
    };

    VS_OUTPUT vs_main( VS_INPUT Input )
    {
    VS_OUTPUT Output;

    Output.mPosition = Input.mPosition;
    Output.mUV= Input.mUV;

    return( Output );

    }
    //ps

    struct PS_INPUT
    {
    float2 mUV : TEXCOORD0;
    };

    sampler2D SceneSampler;
    float2 gPixelOffset;

    float3x3 Kx = {-1, 0, 1,
    -2, 0, 2,
    -1, 0, 1};
    float3x3 Ky = { 1, 2, 1,
    0, 0, 0,
    -1,-2,-1};

    float4 ps_main(PS_INPUT Input) : COLOR
    {
    float Lx = 0;
    float Ly = 0;

    for(int y = -1; y<= 1; y++)
    {
    for(int x = -1; x<=1; x++)
    {
    float2 offset = float2(x,y)*gPixelOffset;
    float3 tex = tex2D(SceneSampler, Input.mUV+offset).rgb;
    float luminance = dot(tex, float3(0.3f, 0.59f, 0.11f));

    Lx+= luminance * Kx[y+1][x+1];
    Ly+= luminance * Ky[y+1][x+1];

    }
    }

    float L = sqrt((Lx*Lx)+(Ly*Ly));
    return float4(L.xxx, 1);
    }
    //vs

    struct VS_INPUT
    {
    float4 mPosition : POSITION;
    float2 mUV : TEXCOORD0;
    };

    struct VS_OUTPUT
    {
    float4 mPosition : POSITION;
    float2 mUV : TEXCOORD0;
    };

    VS_OUTPUT vs_main( VS_INPUT Input )
    {
    VS_OUTPUT Output;

    Output.mPosition = Input.mPosition;
    Output.mUV= Input.mUV;

    return( Output );

    }
    //ps
    struct PS_INPUT
    {
    float2 mUV : TEXCOORD0;
    };

    sampler2D SceneSampler;
    float2 gPixelOffset;

    float3x3 K = {-2,-1, 1,
    -1, 0, 1,
    0, 1, 2};

    float4 ps_main(PS_INPUT Input) : COLOR
    {
    float res = 0;

    for(int y = -1; y<= 1; y++)
    {
    for(int x = -1; x<=1; x++)
    {
    float2 offset = float2(x,y)*gPixelOffset;
    float3 tex = tex2D(SceneSampler, Input.mUV+offset).rgb;
    float luminance = dot(tex, float3(0.3f, 0.59f, 0.11f));

    res+= luminance * K[y+1][x+1];

    }
    }

    res+=0.1f;
    return float4(res.xxx, 1);
    }

    COMMENT
     
    12
    21

    <본 포스팅은 강좌가 아닙니다.>

     

    포스트 프로세싱은 기존에 렌더링된 씬에 렌더링 효과를 더하는 작업이다.

    렌더링 결과를 2D로 저장해 그 이미지에서 추가 작업을 하는 것이다.

     

    화면을 2D 이미지로 렌더타깃으로 저장하는 셰이더 하나

    효과만 주는 셰이더 여러개로 가벼운 작업이 가능하다.

     

    렌더 화면이 저장될 SceneTexture

    use viewport dimension으로 화면에 꽉차는 이미지로 한다.

     

    렌더타깃으로 이 이미지로 저장하게 한다.

     

    이런 식으로 렌더링 화면이 저장이 된다.
    EnvironmentMapping은 렌더타깃, Noeffect 무효과, Grayscale은 흑백화면이다.

     

    ScreenAlighnedQuad는 렌더몽키에서 지원하는 화면 사각형 모델이다.

    하드웨어가 이 면을 뒷면으로 생각해서 RenderState를 추가해 CullMode를 None으로 설정한다.

     


     - 흑백셰이더

     

    VS 버텍스 셰이더

    정점 위치가 이미 투영공간에 있어서 변환이 필요없다.

     

     

    PS 버텍스 셰이더

    그저 색 평균으로 나누어도 되지만 회색처럼 보이지만

    인간의 눈이 RGB에서 명암을 지각하는 정도가 달라

    공식이 있다고 한다. 책에서는

    빨강은 30% 녹색은 59% 파랑은 11%라고 한다. 

     

    찾아보니 https://www.wikiwand.com/en/Grayscale#/Converting_colour_to_grayscale

     

    Grayscale | Wikiwand

    In digital photography, computer-generated imagery, and colorimetry, a grayscale image is one in which the value of each pixel is a single sample representing only an amount of light; that is, it carries only intensity information. Grayscale images, a kind

    www.wikiwand.com

    이게 더 정밀한 수치인듯 하다.
    사이트가 말하는 수치대로 적용해보았다.

    결과

    결과


    - 세피아 셰이더

    세피아 톤은 갈색빛 도는 감성이 도는 느낌이다.

    버텍스셰이더는 위에와 동일하다.

    PS 픽셀셰이더

    책에 있는 셰피아 공식을 그대로 적용했다.

     

    결과

     

    COMMENT
     
    12
    21

    <본 포스팅은 강좌가 아닙니다.>

     

    이전 다렉 프로젝트할때 뎁스맵 쉐도우를 적용했었다.

    그러면서 3가지의 그림자 기법인

    평면쉐도우, 프로젝션 쉐도우, 뎁스맵 쉐도우 대해 알고있었다.

     

    1. 평면쉐도우

    는 똑같은 캐릭터를 바닥면에 평면으로 렌더링 하는 것이고,

    2. 프로젝션 쉐도우

    는 카메라(광원)으로 부터 투영된 텍스쳐를 만들어 (렌더타깃 텍스처 = 그림자 맵)

    바닥 텍스쳐에 덧붙여 그림자를 만들어내는 것이다. 

    3. 뎁스맵 쉐도우

    는 프로젝션 쉐도우에서 더 해서 깊이 바이어스를 사용해

    self-shadow를 구현할 수 있는 그림자 기법이다.

     

    또한, 이전 다렉 프로젝트에서 그림자 여드름, 투영앨리어싱(계단현상), 피터펜현상 등

    쉐도우 구현에 고려해야할 문제들을 봤었다.

     

    셰이더 프로그램 입문 책에는 바로 이 뎁스맵 쉐도우를 설명하신다.

    렌더타깃을 이용해 그림자 깊이 값들을 저장해 픽셀 셰이더에서 그림자 계산에

    쓴다. 그 텍스처를 그림자 맵이라하고, 그래서 그림자 매핑이라고 한다.

     

    "그림자는 어떤 물체가 빛을 막아서 생기는 현상이다.

    빛을 막는 물체가 여럿이면 처음에 빛을 막는 물체만 의미가 있음

    처음 막는 물체의 깊이를 기억함

    기억한 깊이와 현재 픽셀의 깊이를 비교해서 처음으로 빛을 막는지 체크

    처음이 아니라면 그림자를 씌운다. "

     

     1. 그림자 생성단계

    - 깊이를 저장할 렌더타깃 정하기

    - 카메라를 광원의 위치에 두고 물체 그리기

    - 픽셀셰이더에서 빛으로부터 현재 픽셀까지의 깊이를 반환

     

     2. 그림자 적용단계

     - 렌더링 결과를 화면백버퍼에 저장

     - 카메라를 눈의 위치 두고 물체를 그림

     - 빛으로부터 현재 픽셀까지의 깊이를 그림자맵에 담겨있는 결과와 비교

     - 현재 깊이가 그림자맵의 깊이보다 크면 그림자 씌운다.


    렌더몽키를 켜보자.

    D3D에서는 여러개의 셰이더를 사용하지만 부득이하게도,

    랜더몽키에서 그림자 매핑을 만들려면 패스를 여러개 써야한다.

     

    우선 렌더타깃 텍스처 위에 뎁스를 저장하는 패스부터 구현한다.

     

    VS 버텍스셰이더

    vs 구조체와 변수

     

    카메라(플레이어)가 아닌 빛의 위치의 카메라다. 

    그래서 view 행렬과 proj행렬이 기존과 다르다.

     

    c++이였으면 행렬을 만들어서 보내주면 되는데

    렌더 몽키에서는 불가능하기 때문에

    여기 패스의 vs에서 광원 뷰 행렬을 제작해줘야한다.

     

    vs 광원뷰행렬 직접 만든다. c++이 아닌 렌더몽키여서 하는것

     

    직교를 구하는 외적을 이용해서 xyz의 방향을 구한다.

    그걸로 행렬을 만들어서 전치해, 변환해주는 행렬로 만든다.

     

    clipposition으로 보내는데 굳이 position을 안쓰는것은

    픽셀셰이더에서 position시멘틱 변수를 곧바로 엑세스 할수 없기 때문이다.

    그래서 다른 시멘틱인 texcoord로 보낸다. 그래도 position은 보내야한다.

    레스터라이저가 픽셀들을 찾지 못하기 때문이다.

     

    PS 픽셀셰이더

    뎁스를 리턴

     

    z는 나아가는 방향이다. w로 나누는 이유는

    3차원 좌표에서 위치벡터 : (x,y,z,1), 방향벡터 : (x,y,z,0) 이된다.

    위치벡터에 원근투영행렬을 곱하면 w 성분은 1이 아니게 된다.

     

    이런 좌표를 동차좌표라고 하는데 이 좌표를  w로 나누면 w값이 1로 된다.

    올바른 값을 구하기 위해 w로 나눈다고 생각하면 됨.

    z의 범위는 0~1이 된다.

     

    텍스처, 뎁스맵이다. clearcolor를 흰색으로 해야한다.

     

    여기로 텍스쳐 저장
    렌더타깃 설정

     

    텍스쳐 shadowMap를 만들고 그걸 createShadow 패스의 렌더타겟으로 설정한다.

    이게 깊이 값이 저장될 텍스쳐이다.


    ApplyShadowTorus이름으로 새로운 패스를 만든다.

     

    VS 버텍스셰이더

    VS 변수 구조체
    vs_main

    아까처럼 광원 뷰행렬 만들어주고

    실제 카메라 공간 변환

    mClipPosition은 깊이를 구하는 공간으로 변환함. 

     

    PS 픽셀셰이더

     

    깊이 맵 저장한거 읽는 sampler2D 변수로 읽어온다.

    tex2D로 읽어와야하는데 UV좌표를 우린 모른다. 

    투영좌표계로 그려진 텍스쳐는 XY -> UV가 필요하다.

    좌표를 구했으면 tex2D로 읽어온다.

    tex2D(ShadowSampler, uv).r 이 R값만 가져오는것은

    저 텍스처의 포맷이 R32F이다. R만 있음

     

    XY 좌표 -> UV 좌표 UV 좌표 -> XY 좌표
    u = x / 2 + 0.5 x = u * 2 - 1
    v = -y / 2 + 0.5 y = -v * 2 -1

     

    마지막으로 if문을 이용해 현재 뎁스 값이 깊이보다 크면 처음이 아니라는 뜻으로

    그림자를 씌우는 모습이다.

    0.0002f는 부동소수점 에러 때매 보정해주는 값이다. 보정치

     

    보정값 보정치가 없을때
    보정값 보정치가이 있을때

     

    또한 그림자맵이 카메라에 포함되는 영역이 절반도

    되지 않으면 그림자의 품질이 저하된다.

    그래서 바닥면을 깔아주었다.

     

    캐스케이드 그림자맵이나 , 퍼센티지 클로저 필터링

    다른 고급기법이 있는데, 나중에 한번 구현해보도록 해보겠다.

     

    특히 퍼센티지 클로저 필터링은 그림자의 외곽선을

    자연스럽게 부드럽게 해줘서 한번 해보고 싶다.

    더보기

    createshadow

    //vs
    struct VS_INPUT
    {
       float4 mPosition : POSITION;
    };
    
    struct VS_OUTPUT
    {
       float4 mPosition : POSITION;
       float4 mClipPosition : TEXCOORD1; 
    };
    float4x4 gWorldMatrix;
    float4x4 gLightViewMatrix;
    float4x4 gLightProjMatrix;
    float4   gWorldLightPos;
    
    VS_OUTPUT vs_main(VS_INPUT Input)
    {
       VS_OUTPUT Output;
       
       //게임엔진에서 해야하는 것
       float4x4 lightViewMatrix = gLightViewMatrix;
       float3 dirZ = -normalize(gWorldLightPos.xyz);
       float3 up = float3(0,1,0);
       float3 dirX = cross(up, dirZ);
       float3 dirY = cross(dirZ, dirX);
       
       lightViewMatrix = float4x4(
       float4(dirX, -dot(gWorldLightPos.xyz, dirX)),
       float4(dirY, -dot(gWorldLightPos.xyz, dirY)),
       float4(dirZ, -dot(gWorldLightPos.xyz, dirZ)),
       float4(0,0,0,1));
       lightViewMatrix = transpose(lightViewMatrix);
       //
       
       Output.mPosition = mul(Input.mPosition, gWorldMatrix);
       Output.mPosition = mul(Output.mPosition, lightViewMatrix);
       Output.mPosition = mul(Output.mPosition, gLightProjMatrix);
       Output.mClipPosition = Output.mPosition;
       
       return Output;
    }
    //ps
    struct PS_INPUT
    {
       float4 mClipPosition : TEXCOORD1;
    };
    // 깊이 값을 반환하는 함수
    float4 ps_main(PS_INPUT Input) : COLOR
    {   
       float depth = Input.mClipPosition.z/ Input.mClipPosition.w;
       return float4(depth.xxx,1);
    }

     

    applyshadowtorus

    //vs
    struct VS_INPUT 
    {
       float4 mPosition : POSITION;
       float3 mNormal   : NORMAL;
    };
    
    struct VS_OUTPUT 
    {
       float4  mPosition      : POSITION;
       float4  mClipPosition  : TEXCOORD1;
       float   mDiffuse       : TEXCOORD2;
    };
    
    float4x4 gWorldMatrix;
    float4x4 gLightViewMatrix;
    float4x4 gLightProjMatrix;
    
    float4 gWorldLightPos;
    
    float4x4 gViewProjMatrix;
    
    VS_OUTPUT vs_main( VS_INPUT Input )
    {
       VS_OUTPUT Output;
     //게임엔진에서 해야하는 것
       float4x4 lightViewMatrix = gLightViewMatrix;
       float3 dirZ = -normalize(gWorldLightPos.xyz);
       float3 up = float3(0,1,0);
       float3 dirX = cross(up, dirZ);
       float3 dirY = cross(dirZ, dirX);
       
       lightViewMatrix = float4x4(
       float4(dirX, -dot(gWorldLightPos.xyz, dirX)),
       float4(dirY, -dot(gWorldLightPos.xyz, dirY)),
       float4(dirZ, -dot(gWorldLightPos.xyz, dirZ)),
       float4(0,0,0,1));
       lightViewMatrix = transpose(lightViewMatrix);
       //
       //실제 장면
       float4 worldPosition = mul(Input.mPosition, gWorldMatrix);
       Output.mPosition = mul(worldPosition, gViewProjMatrix);
       
       Output.mClipPosition = mul(worldPosition, lightViewMatrix);
       Output.mClipPosition = mul(Output.mClipPosition, gLightProjMatrix);
       
       float3 lightDir = normalize(worldPosition.xyz - gWorldLightPos.xyz);
       float3 worldNormal = normalize(mul(Input.mNormal, (float3x3)gWorldMatrix));
       
       Output.mDiffuse = dot(-lightDir, worldNormal);
       return Output;
    }
    
    //ps
    struct PS_INPUT
    {
       float4  mClipPosition : TEXCOORD1;
       float   mDiffuse : TEXCOORD2;
    };
    sampler2D ShadowSampler;
    float4     gObjectColor;
    float4 ps_main(PS_INPUT Input) : COLOR
    {
      float3 rgb = saturate(Input.mDiffuse)  * gObjectColor;
      //depth 연산 광원으로부터 현재 픽셸의 깊이
      float currentDepth = Input.mClipPosition.z / Input.mClipPosition.w;
      
      float2 uv = Input.mClipPosition.xy / Input.mClipPosition.w;
      uv.y = -uv.y;
      uv= uv*0.5+0.5;
      
      float shadowDepth = tex2D(ShadowSampler, uv).r;
      
      if(currentDepth>shadowDepth+ 0.00002f)
      {
         rgb*=0.5f;
      }
       return(float4(rgb,1.0f));
    }
    COMMENT
     
    12
    20

    <이 포스팅은 강좌가 아닙니다. 셰이더 프로그램 입문 책 구매해서 읽으세요>

    UV 애니메이션

     

    정점셰이더에서 시간에 따라 UV 좌표를 이용해 애니메이션을 줄 수 있다.

     

    위 그림처럼 UV를 밀어서 강물이 흘러가는 듯한 효과를 줄 수 있는 것이다.

     

    시간의 흐름에 따라 UV 값을 천천히 변경해야하는데, GPU를 사용하는 쉐이더에서는

     

    시간을 구하는 함수가 없다 그래서 전역변수로 넘겨 받아야한다.

     

    VS 구조체, 변수
    mUV를 시간을 더해 Output로 보낸다.

    mUV의 UV값이 1을 더하면 다시 자리로 돌아온다. 0.25f를 곱한건

     

    너무 속도가 빨라서 속도를 조정한 것이다.

     

    결과

     

    UV를 이용해서 애니메이션을 주었다.

    이를 이용해 마그마나 물의 텍스쳐가 움직이게 할 수 있다.

    더보기
    //vs
    float4x4 gWorldMatrix;
    float4x4 gWorldViewProjMatrix;
    
    float4 gWorldLightPos;
    float4 gWorldCamPos;
    //time value
    float gTime;
    
    struct VS_INPUT 
    {
       float4 mPosition : POSITION;
       float3 mNormal   : NORMAL;
       float3 mTangent  : TANGENT;
       float3 mBinormal : BINORMAL;
       float2 mUV       : TEXCOORD0;
    };
    
    struct VS_OUTPUT 
    {
       float4 mPosition : POSITION;
       float2 mUV       : TEXCOORD0;
       float3 mLightDir : TEXCOORD1;
       float3 mViewDir  : TEXCOORD2;
       float3 mT        : TEXCOORD3;
       float3 mB        : TEXCOORD4;
       float3 mN        : TEXCOORD5;
    };
    
    VS_OUTPUT vs_main( VS_INPUT Input )
    {
       VS_OUTPUT Output;
     
       Output.mPosition = mul( Input.mPosition, gWorldViewProjMatrix );
       //uv
       //Output.mUV = Input.mUV;
       Output.mUV = Input.mUV + float2(gTime*0.25f,gTime*0.25f);
       
       float4 worldPos = mul(Input.mPosition, gWorldMatrix);
    
       float3 lightDir = worldPos.xyz - gWorldLightPos.xyz;
       Output.mLightDir = normalize(lightDir);
       
       float3 viewDir = normalize(worldPos.xyz-gWorldCamPos.xyz);
       Output.mViewDir = viewDir;
       
       float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatrix);
       Output.mN=normalize(worldNormal);
       
       float3 worldTangent = mul(Input.mTangent, (float3x3)gWorldMatrix);
       Output.mT=normalize(worldTangent);
       
       float3 worldBinormal = mul(Input.mBinormal, (float3x3)gWorldMatrix);
       Output.mB=normalize(worldBinormal);
      
       return  Output;
    }
    
    //ps
    struct PS_INPUT
    {
       float2 mUV : TEXCOORD0;
       float3 mLightDir : TEXCOORD1;
       float3 mViewDir : TEXCOORD2;
       float3 mT : TEXCOORD3;
       float3 mB : TEXCOORD4;
       float3 mN : TEXCOORD5;
    };
    
    sampler2D     DiffuseSampler;
    sampler2D     SpecularSampler;
    sampler2D     NormalSampler;
    samplerCUBE   EnvironmentSampler;
    float3        gLightColor;
    
    float4 ps_main( PS_INPUT Input) : COLOR
    {  
       float3 tangentNormal = tex2D(NormalSampler, Input.mUV).xyz;
       tangentNormal = normalize(tangentNormal*2-1);
       //tangentNormal = float3(0,0,1);
       float3x3 TBN = float3x3(normalize(Input.mT), normalize(Input.mB),normalize(Input.mN));
       TBN = transpose(TBN);
       float3 worldNormal = mul(TBN, tangentNormal); 
    
       float4 albedo = tex2D(DiffuseSampler, Input.mUV);
       float3 lightDir = normalize(Input.mLightDir);
       float3 diffuse = saturate(dot(worldNormal, -lightDir));
       diffuse = gLightColor * albedo.rgb*diffuse;
       
       float3 viewDir = normalize(Input.mViewDir);
       float3 specular = 0;
       
       if(diffuse.x > 0.0f)
       {
          float3 reflection = reflect(lightDir, worldNormal);
     
          specular = saturate(dot(reflection, -viewDir));
          specular = pow(specular,20.0f);
          
          float4 specularInten=tex2D(SpecularSampler, Input.mUV);
          specular*=specularInten.rgb*gLightColor;
       }
       float3 viewReflect = reflect(viewDir, worldNormal);
       float3 environment = texCUBE(EnvironmentSampler, viewReflect).rgb;
       float3 ambient = float3(0.1f, 0.1f, 0.1f)*albedo;
       
       return float4(ambient+diffuse+specular+(environment*0.5f),1);
    }

     

    울렁효과

     

    코사인 그래프를 보면 연속적으로 +1~-1로  연속 곡선을 이룬다.

    더해서 울렁효과는 Cos 함수를 이용해 출렁이게 하는 것이다.

     

     

    위 프로젝트에서 이어서 변수를 더해준다. 

     

    공간 변환하기전에 지역공간에서 mPosition 정점의 위치를 바꿔준다.

     

    cos이 -1 ~ +1 으로 연속적인 곡선이기 때문에

     

    위 아래로만 움직이는 것을 볼 수 있다. 

     

    모든 정점을 한꺼번에 높여줬다 내려줬다만 반복하는거다.

     

    정점 마다 높낮이를 달리 할라면 UV 좌표를 사용해야한다.

     

    UV값은 표면에 따라 지속적으로 변하므로 UV좌표의 U좌표를 시간에 더한 것으로

     

    코사인 값을 구하면 정점마다 코사인 함수에 들어가는 인자가 부드럽게 변한다.

     

    결과

    더보기
    //vs
    float4x4 gWorldMatrix;
    float4x4 gWorldViewProjMatrix;
    
    float4 gWorldLightPos;
    float4 gWorldCamPos;
    //time value
    float gTime;
    float gWaveHeight;
    float gSpeed;
    float gWaveFrequency;
    float gUVSpeed;
    
    struct VS_INPUT 
    {
       float4 mPosition : POSITION;
       float3 mNormal   : NORMAL;
       float3 mTangent  : TANGENT;
       float3 mBinormal : BINORMAL;
       float2 mUV       : TEXCOORD0;
    };
    
    struct VS_OUTPUT 
    {
       float4 mPosition : POSITION;
       float2 mUV       : TEXCOORD0;
       float3 mLightDir : TEXCOORD1;
       float3 mViewDir  : TEXCOORD2;
       float3 mT        : TEXCOORD3;
       float3 mB        : TEXCOORD4;
       float3 mN        : TEXCOORD5;
    };
    
    VS_OUTPUT vs_main( VS_INPUT Input )
    {
       VS_OUTPUT Output;
       //지역공간에서 적용
       float cosTime = gWaveHeight * cos((gTime*gSpeed)+(Input.mUV.x*gWaveFrequency));
       Input.mPosition.y+=cosTime;
         
       Output.mPosition = mul( Input.mPosition, gWorldViewProjMatrix );
       float4 worldPos = mul(Input.mPosition, gWorldMatrix);
       float3 lightDir = worldPos.xyz - gWorldLightPos.xyz;
       Output.mLightDir = normalize(lightDir);
       
       float3 viewDir = normalize(worldPos.xyz-gWorldCamPos.xyz);
       Output.mViewDir = viewDir;
       
       float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatrix);
       Output.mN=normalize(worldNormal);
       
       float3 worldTangent = mul(Input.mTangent, (float3x3)gWorldMatrix);
       Output.mT=normalize(worldTangent);
       
       float3 worldBinormal = mul(Input.mBinormal, (float3x3)gWorldMatrix);
       Output.mB=normalize(worldBinormal);
       
       Output.mUV = Input.mUV + float2(gTime*gUVSpeed,0);
       
       return  Output;
    }
    
    //ps
    struct PS_INPUT
    {
       float2 mUV : TEXCOORD0;
       float3 mLightDir : TEXCOORD1;
       float3 mViewDir : TEXCOORD2;
       float3 mT : TEXCOORD3;
       float3 mB : TEXCOORD4;
       float3 mN : TEXCOORD5;
    };
    
    sampler2D     DiffuseSampler;
    sampler2D     SpecularSampler;
    sampler2D     NormalSampler;
    samplerCUBE   EnvironmentSampler;
    float3        gLightColor;
    
    float4 ps_main( PS_INPUT Input) : COLOR
    {  
       float3 tangentNormal = tex2D(NormalSampler, Input.mUV).xyz;
       tangentNormal = normalize(tangentNormal*2-1);
       //tangentNormal = float3(0,0,1);
       float3x3 TBN = float3x3(normalize(Input.mT), normalize(Input.mB),normalize(Input.mN));
       TBN = transpose(TBN);
       float3 worldNormal = mul(TBN, tangentNormal); 
    
       float4 albedo = tex2D(DiffuseSampler, Input.mUV);
       float3 lightDir = normalize(Input.mLightDir);
       float3 diffuse = saturate(dot(worldNormal, -lightDir));
       diffuse = gLightColor * albedo.rgb*diffuse;
       
       float3 viewDir = normalize(Input.mViewDir);
       float3 specular = 0;
       
       if(diffuse.x > 0.0f)
       {
          float3 reflection = reflect(lightDir, worldNormal);
     
          specular = saturate(dot(reflection, -viewDir));
          specular = pow(specular,20.0f);
          
          float4 specularInten=tex2D(SpecularSampler, Input.mUV);
          specular*=specularInten.rgb*gLightColor;
       }
       float3 viewReflect = reflect(viewDir, worldNormal);
       float3 environment = texCUBE(EnvironmentSampler, viewReflect).rgb;
       float3 ambient = float3(0.1f, 0.1f, 0.1f)*albedo;
       
       return float4(ambient+diffuse+specular+(environment*0.5f),1);
    }
    COMMENT
     
    12
    20

    환경매핑이란, 주위의 환경을 미리 텍스처 안에 저장하고

    실시간으로 그 텍스처를 입히는 것이다.

    간접광을 재현하며 거울같은 표면을 표현하는 기법이다.

     

    유니티에서 hdri를 적용하거나 스카이박스를 만들어 적용하듯이

    똑같이 D3D환경에서 환경 매핑을 하는 것이다.

    김포프님의 책에서는 상하, 좌우, 전후로 6개 텍스쳐로 주위를 감싸는

    스카이박스 형식으로 구현하였다. 입방체 맵이라고 한다.

     

     

    위 그림은 카메라부터 면을 반사해 부딪히는 

    입방체 텍스쳐를 얻어내는 그림이다.

    Hlsl 내장함수로 쉽게 reflect() 함수를 이용해서

    카메라 벡터의 반사벡터를 얻어낼 수 있다. 

     

    그리고 texCUBE()로 텍스쳐샘플러와 반사벡터를 인자로 받아

    환경매핑을 얻어 올 수 있다.

     

    VS 버텍스 셰이더는 앞에 했던 포스팅과 내용이 같아서 생략한다.

    PS 픽셀셰이더

     

    PS 구조체와 변수

     

    입방체 텍스쳐는 samplerCUBE 변수를 쓴다.

     

     

    반사 벡터를 구하고 샘플러와 반사벡터를 사용해 환경매핑벡터를 구한다.

    출력확인을 위해 기존 법선매핑을 0,0,1로 덮어씌우고

    환경맵만 리턴하였다.

     

    확인했으면 기존 법선매핑, 난반사광, 정반사광도 리턴해준다. 

    환경맵이 너무 강해서 강도도 절반으로 줄였다.

    결과

    법선맵까지 추가

    간접광을 재현하며 거울같은 표면을 표현하는 기법으로

    빛나는 벽돌 텍스쳐의 주전자를 볼 수 있다.

     

    더보기
    더보기
    //vs
    float4x4 gWorldMatrix;
    float4x4 gWorldViewProjMatrix;
    
    float4 gWorldLightPos;
    float4 gWorldCamPos;
    
    struct VS_INPUT 
    {
       float4 mPosition : POSITION;
       float3 mNormal   : NORMAL;
       float3 mTangent  : TANGENT;
       float3 mBinormal : BINORMAL;
       float2 mUV       : TEXCOORD0;
    };
    
    struct VS_OUTPUT 
    {
       float4 mPosition : POSITION;
       float2 mUV       : TEXCOORD0;
       //float3 mDiffuse  : TEXCOORD1; 정점법선이 아닌 텍셀법선
       float3 mLightDir : TEXCOORD1;
       float3 mViewDir  : TEXCOORD2;
       float3 mT        : TEXCOORD3;
       float3 mB        : TEXCOORD4;
       float3 mN        : TEXCOORD5;
    };
    
    VS_OUTPUT vs_main( VS_INPUT Input )
    {
       VS_OUTPUT Output;
     
       Output.mPosition = mul( Input.mPosition, gWorldViewProjMatrix );
       Output.mUV = Input.mUV;
       
       float4 worldPos = mul(Input.mPosition, gWorldMatrix);
    
       float3 lightDir = worldPos.xyz - gWorldLightPos.xyz;
       Output.mLightDir = normalize(lightDir);
       
       float3 viewDir = normalize(worldPos.xyz-gWorldCamPos.xyz);
       Output.mViewDir = viewDir;
       
       float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatrix);
       Output.mN=normalize(worldNormal);
       
       float3 worldTangent = mul(Input.mTangent, (float3x3)gWorldMatrix);
       Output.mT=normalize(worldTangent);
       
       float3 worldBinormal = mul(Input.mBinormal, (float3x3)gWorldMatrix);
       Output.mB=normalize(worldBinormal);
      
       return  Output;
    }
    
    //ps
    struct PS_INPUT
    {
       float2 mUV : TEXCOORD0;
       float3 mLightDir : TEXCOORD1;
       float3 mViewDir : TEXCOORD2;
       float3 mT : TEXCOORD3;
       float3 mB : TEXCOORD4;
       float3 mN : TEXCOORD5;
    };
    
    sampler2D     DiffuseSampler;
    sampler2D     SpecularSampler;
    sampler2D     NormalSampler;
    samplerCUBE   EnvironmentSampler;
    float3        gLightColor;
    
    float4 ps_main( PS_INPUT Input) : COLOR
    {  
       float3 tangentNormal = tex2D(NormalSampler, Input.mUV).xyz;
       tangentNormal = normalize(tangentNormal*2-1);
       //tangentNormal = float3(0,0,1);
       float3x3 TBN = float3x3(normalize(Input.mT), normalize(Input.mB),normalize(Input.mN));
       TBN = transpose(TBN);
       float3 worldNormal = mul(TBN, tangentNormal); 
    
       float4 albedo = tex2D(DiffuseSampler, Input.mUV);
       float3 lightDir = normalize(Input.mLightDir);
       float3 diffuse = saturate(dot(worldNormal, -lightDir));
       diffuse = gLightColor * albedo.rgb*diffuse;
       
       float3 viewDir = normalize(Input.mViewDir);
       float3 specular = 0;
       
       if(diffuse.x > 0.0f)
       {
          float3 reflection = reflect(lightDir, worldNormal);
     
          specular = saturate(dot(reflection, -viewDir));
          specular = pow(specular,20.0f);
          
          float4 specularInten=tex2D(SpecularSampler, Input.mUV);
          specular*=specularInten.rgb*gLightColor;
       }
       float3 viewReflect = reflect(viewDir, worldNormal);
       float3 environment = texCUBE(EnvironmentSampler, viewReflect).rgb;
       float3 ambient = float3(0.1f, 0.1f, 0.1f)*albedo;
       
       return float4(ambient+diffuse+specular+(environment*0.5f),1);
    }
    COMMENT
     
    12
    20

    <본 포스팅은 강좌가 아닙니다.>

    조명을 계산할때 각 정점의 법선(노말) 정보를 가져와서 조명효과를 결정했다.

     

    박스면의 법선을 생각해보자면 한 면은 아래와 같은 모습일 것이다.

     

     

    만약 벽돌처럼 울퉁불퉁한 면이라면 법선이 이렇게 되지 않을 것이다.

     

     

    이렇게 각 픽셀에 사용할 법선 정보를 담은 텍스처를 노말맵, 법선맵이라고 한다.

     

    텍스처에 법선 정보는 3개 성분 xyz를 갖는다. 텍스쳐는 RGB가 있으므로

     

    xyz를 각 각 RGB에 대입하면 된다.

     

    왼손좌표계로 z가 forward(높여주는)방향이여서 울퉁불퉁한 면을 표현하게 한다.

     

    그래서 노말맵이 대부분 푸른빛을 띄고 있는 것이다. + 구간에서 존재하기 때문이다.

     

    z값이 최소한 0.5~1이다. 왜 0이 아닌지는 아래 공식을 보면 된다.

     

    Julian Herzog &amp;amp;nbsp;( Website )

     

    저장할때 정규화한 벡터를 쓰는데 이 범위는 -1~1이다.

     

     -1을 0으로 하고 0를 0.5로 +1를 +1로 변환을 해야한다.

     

    왜냐하면 텍스처의 각 채널에서 가질 수 있는 값의 범위는 0~1이기 때문이다.

     

    법선벡터->법선맵 법선맵->법선벡터
    법선맵 RGB = 법선벡터 XYZ * 0.5 + 0.5 법선벡터 XYZ = 법선맵 RGB * 2 - 1

     

    접선 공간 행렬 : Z는 정점의 법선(Normal),

    X는 UV중 하나의 값 접선(Tangent),

    Y는 두 벡터의 외적 결과인 종법선(Binnormal)

    이렇게 접선 공간 변환에 사용하는 행렬를 만들 수 있다.

    행 기준 행렬

     

    이제 렌더몽키를 켜서 제작해보자.

     

    vs input으로 받을 법선, 접선, 종법선을 추가한다. (Stream Mapping)

     

    VS 버텍스셰이더

    버텍스 셰이더 구조체, 변수이다.

    행렬은 월드행렬과 월드뷰투영행렬으로 구성했다.

     

    기존엔 정점에서 법선정보를 가져왔지만

    노말맵 즉, 텍스쳐에서 법선정보를 가져오므로

    LightDir인 방향벡터를 전달해 픽셀셰이더에서 조명연산을 한다.

     

    mT mB mN는 접선, 종법선, 법선 정보이다.

    이 벡터가 필요한것은 접선공간을 변환하기 위해서다.

    월드공간에서 계산하기위해 월드 공간으로 변환하는 행렬 만들 수 있다.

     

     

    입사광 방향 벡터와 카메라 벡터 구해야 한다.

    빛 방향 벡터 =월드 위치 - 라이트 위치 -> 정규화

    뷰 벡터 = 월드 위치 - 카메라 위치 -> 정규화

     

    물체 공간에 있는 법,접 종법 선 정보를 월드 공간으로 바꿔서

    Output를 리턴해 픽셀셰이더로 넘겨준다.

     

    PS 픽셀셰이더

    픽셸셰이더 구조체와 변수

     

    법선맵(Normal map)를 tex2D함수로 읽어와 변수에 저장한다. 

    읽어오면 텍스쳐값이므로 0~1의 범위를 갖는다.

    그리고 -1~+1인 법선벡터 범위를 올바른 범위로 확장시켜줌.

    tangentNormal * 2 - 1  // 0.5를 넣으면 0이된다.

     

    받아온 월드 공간의 법접종법선 정보로 TBN 행렬을 만든다.

    transpose() 전치행렬로 만든다. 월드 공간으로 바꾸기 위해

     

    tangentNoraml에 곱하여 월드 공간에서의 법선을 구할 수 있다.

    float3 worldNormal = mul (TßN , tangentNormal);

     

    그런 후에 빛, 카메라 방향 벡터로 난반사광의 양과

    정반사광의 양을 구해 각 맵에 곱한다.

     

    결과 

     

    더보기
    더보기
    더보기
    더보기
    //VS
    float4x4 gWorldMatrix;
    float4x4 gWorldViewProjMatrix;
    
    float4 gWorldLightPos;
    float4 gWorldCamPos;
    
    struct VS_INPUT 
    {
       float4 mPosition : POSITION;
       float3 mNormal   : NORMAL;
       float3 mTangent  : TANGENT;
       float3 mBinormal : BINORMAL;
       float2 mUV       : TEXCOORD0;
    };
    
    struct VS_OUTPUT 
    {
       float4 mPosition : POSITION;
       float2 mUV       : TEXCOORD0;
       //float3 mDiffuse  : TEXCOORD1; 정점법선이 아닌 텍셀법선
       float3 mLightDir : TEXCOORD1;
       float3 mViewDir  : TEXCOORD2;
       float3 mT        : TEXCOORD3;
       float3 mB        : TEXCOORD4;
       float3 mN        : TEXCOORD5;
    };
    
    VS_OUTPUT vs_main( VS_INPUT Input )
    {
       VS_OUTPUT Output;
     
       Output.mPosition = mul( Input.mPosition, gWorldViewProjMatrix );
       Output.mUV = Input.mUV;
       
       float4 worldPos = mul(Input.mPosition, gWorldMatrix);
    
       float3 lightDir = worldPos.xyz - gWorldLightPos.xyz;
       Output.mLightDir = normalize(lightDir);
       
       float3 viewDir = normalize(worldPos.xyz-gWorldCamPos.xyz);
       Output.mViewDir = viewDir;
       
       float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatrix);
       Output.mN=normalize(worldNormal);
       
       float3 worldTangent = mul(Input.mTangent, (float3x3)gWorldMatrix);
       Output.mT=normalize(worldTangent);
       
       float3 worldBinormal = mul(Input.mBinormal, (float3x3)gWorldMatrix);
       Output.mB=normalize(worldBinormal);
      
       return  Output;
    }
    
    //PS
    struct PS_INPUT
    {
       float2 mUV : TEXCOORD0;
       float3 mLightDir : TEXCOORD1;
       float3 mViewDir : TEXCOORD2;
       float3 mT : TEXCOORD3;
       float3 mB : TEXCOORD4;
       float3 mN : TEXCOORD5;
    };
    
    sampler2D DiffuseSampler;
    sampler2D SpecularSampler;
    sampler2D NormalSampler;
    float3 gLightColor;
    
    float4 ps_main( PS_INPUT Input) : COLOR
    {  
       float3 tangentNormal = tex2D(NormalSampler, Input.mUV).xyz;
       tangentNormal = normalize(tangentNormal*2-1);
       
       float3x3 TBN = float3x3(normalize(Input.mT), normalize(Input.mB),normalize(Input.mN));
       TBN = transpose(TBN);
       float3 worldNormal = mul(TBN, tangentNormal); 
       
       float4 albedo = tex2D(DiffuseSampler, Input.mUV);
       float3 lightDir = normalize(Input.mLightDir);
       float3 diffuse = saturate(dot(worldNormal, -lightDir));
       diffuse = gLightColor * albedo.rgb*diffuse;
       
       float3 specular = 0;
    
       if(diffuse.x > 0.0f)
       {
          float3 reflection = reflect(lightDir, worldNormal);
          float3 viewDir = normalize(Input.mViewDir);
          
          specular = saturate(dot(reflection, -viewDir));
          specular = pow(specular,20.0f);
          
          float4 specularInten=tex2D(SpecularSampler, Input.mUV);
          specular*=specularInten.rgb*gLightColor;
       }
       
       float3 ambient = float3(0.1f, 0.1f, 0.1f)*albedo;
       
       return float4(ambient+diffuse+specular,1);
    }
    COMMENT
     
    12
    17

    <본 포스팅은 강좌가 아닙니다.>

     

    앞 포스팅에서 난반사광에서 람베르트 모델을 썼다.

     

    (정반사광에서는 퐁 모델 썼었지..)

     

    람베르트 모델은 표면 법선과 입사광의 코사인 값이

    난반사광의 양이라고 했다. 

     

    툰셰이더는 기존 난반사광의 양처럼 코사인 곡선이 부드럽지 않게

    0.2단위로 올림을 하면 툰셰이더 값이 나온다. 

     

     

    빨간선처럼 각진 코사인의 난반사광을 적용하면 된다. 올림을 통해서.. 

     

    자세한 구현 내용은 아래 있다.

     

     

    이전 장에서는 계속 월드행렬, 뷰행렬, 투영행렬로 

    순차적으로 곱해서 픽셀셰이더로 넘겼는데

     

    이번 장부터는 월드뷰투영행렬을 미리 합쳐둔 행렬을 사용한다. 

    불필요한 연산을 피하기 위해서다.

     

    하지만 난반사광을 구할려면 월드행렬이 필요했다.

    이는 월드행렬의 역행렬로 지역공간으로 변환해서 해결한다.

    모든 변수들을 동일한 공간에서 시작할수있게 만들어준다.

    위 사진에서 WorldViewProjMatrix와 InverseWorldMatrix가 그것이다.

     

     

    초록색 주석부분보면 라이트의 월드위치를 역행렬로 지역공간으로 바꿔준다.

    정점의 법선과 Dot 내적을 통해 얻는 난반사의 양을

    diffuse 변수에 저장해서 PS로 보내주는 모습이다.

     

    픽셀 셰이더에서는 버텍스셰이더에서 받아온 값을

    0~1 사이로 바꿔주는 Saturate 함수

     

    그리고 0.2단위로 올림하는 ceil(diffuse * 5) / 5.0f;를 ceil함수를 사용한다.

     

    diffuse가 0~1의 값이니까 곱하기 5는 0~5의 값이다.

    ceil함수로 0.2가 나와도 0이 되는 즉, 0, 1, 2, 3, 4, 5로 올림이 된다.

    그 올림된 수를 나누기 5로 나누면 0.0, 0.2, 0.4, 0.6, 0.8, 1.0이 된다.

     

    그런 알고리즘이다.

     

     

     

    셰이더 프로그래밍 입문서 책 내용과 별개로

     

    텍스쳐를 추가해봤는데 카툰 형식에는 비교적 어울리지 않았다. 

     

     

    예시로는 보더랜드 그래픽을 보면 텍스쳐를 사용하긴 했지만

    주로 단색을 사용했다는 점이다. 

     

    툰셰이더의 좋은 예시 게임

     

    또한 책 내용과 별개로 정반사광도

    복습할겸 툰셰이더에 추가해보았다.

     

    아래는 최종 결과

     

     

    아래는 풀 소스코드이다.

    더보기
    더보기
    더보기
    더보기
    //VS
    struct VS_INPUT
    {
       float4 mPosition : POSITION;
       float3 mNormal : NORMAL;
    };
    
    struct VS_OUTPUT
    {
       float4 mPosition : POSITION;
       float3 mDiffuse : TEXCOORD1;
       //정반사
       float3 mViewDir : TEXCOORD2;
       float3 mReflection : TEXCOORD3;
    };
    
    float4x4 gWorldViewProjMatrix;
    float4x4 gInvWorldMatrix;
    float4   gWorldLightPos;
    
    float4   gWorldCamPos;
    
    VS_OUTPUT vs_main(VS_INPUT Input)
    {
       VS_OUTPUT Output;
       //바로 화면 뷰포트 행렬 변환
       Output.mPosition= mul(Input.mPosition, gWorldViewProjMatrix);
       //월드 역행렬로 라이트 위치 지역공간 로 바꿔줌
       float3 objectlightPos = mul(gWorldLightPos, gInvWorldMatrix);
       //카메라 캠 위치도 지역공간으로 바꿔줌
       float3 viewDir   = mul(gWorldCamPos, gInvWorldMatrix);
       Output.mViewDir=viewDir;
       float3 lightDir = normalize(Input.mPosition.xyz-objectlightPos);
       Output.mUV = Input.mUV;
       Output.mDiffuse = dot(-lightDir, normalize(Input.mNormal));
       Output.mReflection = reflect(lightDir, Input.mNormal);
       return Output;
    }
    
    //PS
    struct PS_INPUT
    {
       float3 mDiffuse : TEXCOORD1;
       float3 mViewDir : TEXCOORD2;
       float3 mReflection : TEXCOORD3;
    };
    
    float3 gSurfaceColor;
    
    float4 ps_main(PS_INPUT Input) : COLOR
    {
       float3 diffuse = saturate(Input.mDiffuse);
       float3 reflection = normalize(Input.mReflection);
       float3 viewDir = normalize(Input.mViewDir);
       float3 specular=0;
       if(diffuse.x > 0.0f)
       {
          specular = saturate(dot(reflection, viewDir));
          specular = pow(specular, 10.0f);
       }
       diffuse = ceil(diffuse * 5) / 5.0f;
       specular = ceil(specular *5)/5.0f;
       float3 ambient= float3(0.1f,0.1f,0.1f);
       return float4(gSurfaceColor*(ambient+specular+diffuse.xyz),1.0f);
    }
    COMMENT
     
    12
    17

    <본 포스팅은 강좌가 아닙니다.>

     

    빛을 흡수하는 성질 : 빨간색 표면은 빨간색의 스펙트럼을

    반사하고 나머지는 흡수해서 빨간색으로 보인다. 

     

    난반사광에 적용하는 텍스쳐를 Diffuse맵이라고 하고

    정반사광에 적용하는 텍스쳐를 Specular맵이라고 한다.

     

    왜 따로 텍스쳐를 사용하는 것이냐면

     

    난반사광이 반사하는 빛과 정반사광이 반사하는 빛의 스펙트럼이 다르고

    픽셀이 반사하는 정반사광의 정도를 조절하는 용도로 쓰기 위함이다.

    정리하자면 

    난반사광 = 빛의 색상 X 난반사광의 양 X 디퓨즈맵의 값

    정반사광 = 빛의 색상 X 정반사광의 양 X 스페큘러맵의 값

     

    난반사, 정반사를 구현했던 기존 프로젝트를 사용해서

     

    버텍스셰이더에서 해줄것은 별로 없다.

     

    UV값을 받아 Output 픽셀셰이더로 넘겨주는 코드만 있으면 된다.

     

    텍스쳐 tex2D 작업은 픽셀셰이더에서 하기 때문이다.

     

     

    UV좌표를 받아와서 디퓨즈맵을 샘플링한다.

    현재 픽셀이 반사하는 색깔이다.

    위에서 난반사광의 양과 빛의 색상을 곱한다했듯이 

    float3 diffuse = gLightColor * albedo.rgb * saturate(Input.mDiffuse); 곱해준다.

     

     

    이러한 결과가 나오는데 스페큘러맵을 쓰지않아 정반사광이 심한 모습이다.

    스페큘러도 마찬가지로 정반사광의 양과 빛의 색상을 곱해준다.

    또한 ambient 주변광에도 알베도를 곱해준다. 

    임의로 정한 빛의 양이기때문에 여기도 디퓨즈맵을 곱하는 것이다.

     

     

    틈새에 갈색부분은 갈색이라 갈색 빛을 흡수하는 모습이다. 

    정반사광이 강하지 않고 어두운 픽셀에서도 디퓨즈 맵을 확인할 수 있다.

    포스팅했던거 삭제되어서 다시 적어내렸다...

     

    최종결과

    더보기

    //VS

    float4x4 gWorldMatrix;
    float4x4 gViewMatrix;
    float4x4 gProjMatrix;
    float4 gWorldLightPos;
    float4 gWorldCamPos;

    struct VS_INPUT 
    {
       float4 mPosition : POSITION;
       float3 mNormal   : NORMAL;
       float2 mUV       : TEXCOORD0;
    };

    struct VS_OUTPUT 
    {
       float4 mPosition : POSITION;
       float2 mUV       : TEXCOORD0;
       float3 mDiffuse  : TEXCOORD1;
       float3 mViewDir  : TEXCOORD2;
       float3 mReflection : TEXCOORD3;
    };

    VS_OUTPUT vs_main( VS_INPUT Input )
    {
       //난반사광의 계산 동일한 계산을 PS VS 둘다 할 수 있음. 
       //하지만 픽셀단위 계산보다 정점단위 계산이 더 연산량이 적음
       VS_OUTPUT Output;
     
       Output.mPosition = mul( Input.mPosition, gWorldMatrix );
       
       //월드 행렬 곱함, 월드 공간에서의 위치여서 여기서 광원의 위치를 뺀다.
       float3 lightDir = Output.mPosition.xyz - gWorldLightPos.xyz;
       lightDir = normalize(lightDir);
       
       float3 viewDir = Output.mPosition.xyz-gWorldCamPos.xyz;
       Output.mViewDir = viewDir;
       
       Output.mPosition = mul( Output.mPosition, gViewMatrix );
       Output.mPosition = mul( Output.mPosition, gProjMatrix );
       
       float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatrix);
       worldNormal = normalize(worldNormal);
       //내적함수 dot()사용
       Output.mDiffuse = dot(-lightDir, worldNormal);
       Output.mReflection=reflect(lightDir,worldNormal);
       
       Output.mUV = Input.mUV;
       return  Output;
    }
    //PS

    struct PS_INPUT
    {
       float2 mUV : TEXCOORD0;
       float3 mDiffuse : TEXCOORD1;
       float3 mViewDir : TEXCOORD2;
       float3 mReflection : TEXCOORD3;
    };

    sampler2D DiffuseSampler;
    sampler2D SpecularSampler;
    float3 gLightColor;

    float4 ps_main( PS_INPUT Input) : COLOR
    {  
       //디퓨즈맵 생플링
       float4 albedo = tex2D(DiffuseSampler, Input.mUV);
       // 픽셀이 반사하는 색깔 
       float3 diffuse = gLightColor*albedo.rgb*saturate(Input.mDiffuse);

       float3 reflection = normalize(Input.mReflection);
       float3 viewDir = normalize(Input.mViewDir);
       float3 specular = 0;

       if(diffuse.x > 0.0f)
       {
          specular = saturate(dot(reflection, -viewDir));
          //20번 거듭제곱
          specular = pow(specular,20.0f);
          
          float4 specularInten=tex2D(SpecularSampler, Input.mUV);
          specular*=specularInten.rgb*gLightColor;
       }
       
       float3 ambient = float3(0.1f, 0.1f, 0.1f);
       
       return float4(ambient+diffuse+specular,1);
    }

    COMMENT
     
    12
    17

    <본 포스팅은 강좌가 아닙니다.>

     

    - 난반사광

    위는 입사광과 법선이 이루는 각도 그림이다.

    해가 저물어 감에 따라 표면도 점점 어두워진다.

    범위는 0~1로 0은 깜깜한 것 1은 가장 밝을때이다.

     

    람베르트 모델을 적용하면 코사인 함수로 난반사광을 구할 수 있다.

    1일때 가장 밝고, 0일때 어둡게 어두워지는 속도를 연산해준다.

    하지만 코사인 연산은 매번 호출하기엔 무거워서

    내적 연산으로 대체한다. -1 다른방향 , 0 직각 , 1 같은 방향

     

    내적공식으로 코사인 값은

    둘의 내적값에 두 벡터의 길이를 곱한 결과를 나누면 코사인 값이 나온다.

     

    법선 벡터와 입사광 벡터에 이를 적용하면 된다.

     

    법선벡터는 보통 각 정점에 저장되어 있다. 쉐이더에서 이를 받아와야한다.

     

     

    노말이 법선 벡터를 담은 float3 변수이다. 그리고

    광원의 위치를 담은 gWorldLightPos변수를 만든다.

     

    vs_main에서 변수 공간 일치를 위해, 정점의 위치는 지역공간에 있기때문에

    월드공간을 연산한 다음에 광원의 위치를 뺀다. 그리고 정규화로 길이를 1로 만듬

    (정규화 : 각 성분을 벡터의 길이로 나눔)

     

    법선도 월드 공간으로 변환해주고 정규화해준다.

     

    그리고 내적으로 각도를 구해준다. 여기서 -lightDir인데

    방향을 바꿔서 화살표의 밑동이 서로 만나야 해서이다.

     

    PS 

    픽셀셰이더에서는 saturate함수로 -1~1인 내적값을

    -1를 0으로 바꿔주는 hlsl 내장함수를 씀.

     

    결과

     

    - 정반사광

    정반사광은 한 방향으로만 반사되는 빛이다.

    김포프선생님은 퐁모델을 가르쳐주셨다.

     

    퐁모델 : 반사광카메라벡터 각도의 코사인 값을 구하고, 그 결과를 여러번 거듭제곱함.

     

    거듭제곱하는 이유는 난반사는 퍼지는 느낌이고 정반사는 한 방향으로 반사되기 때문에 

    타이트한 빛을 만들 수 있다.

     

     

    카메라 위치 변수에 시멘틱은 viewposition로 해준다.

    아래는 버텍스셰이더이다.

     

     

    위에 난반사와 유사하다. 변수만들고.. 월드공간에서 뷰방향 만들어주고..

    중요한건 Hlsl내장함수 reflect으로 입사광의 방향텍터와 반사면의 법선을 받아 정반사광을 만들어준다.

     

    PS

    픽셀셰이더에서 정규화를 한번 더 해준다. 

    보간기를 거치면서 값이 흐트러진다.

    if문은 난반사, 빛이 들어와야 정반사가 있기에 0보다 커야 실행한다.

    반사광과 카메라벡터 각도의 코사인 값을 구하고 그것을

    거듭제곱 POW함수를 실행한다. 20번 실행되게 했다.

     

    결과

     

    책에 써놓으신 요약부분이 좋아서 남겨본다..

    람베르트 모델은 난반사광 계산할때 코사인씀

    퐁 모델은 정반사광 계산할때 코사인 거듭제곱함

    벡터 정규화하면 내적으로 코사인 함수 대체가능함

    픽셸셰이더보다는 정점셰이더가 계산양 대체적으로 적음

    COMMENT
     
    12
    16

    <본 글은 강좌가 아닙니다. 제 기억노트입니다..>

    다이렉트X11로 엔진을 만들어 쉐이더 작성을 해보았으나

    따로 공부하고 싶어서 김포프 교수님의 셰이더 프로그램 입문 책을

    보며 공부하고 있다.

     

    이 책에서 소개한 AMD RenderMonkey는 별도의 다이렉트

    프로젝트를 구성하지않아도 쉐이더를 확인 할 수 있는 툴이다. 

     

    https://gpuopen.com/archived/rendermonkey-toolsuite/

     

    RenderMonkey™ Toolsuite

    RenderMonkey has reached end of life. AMD no longer supports RenderMonkey, but makes it available for download without warranty. RenderMonkey is a rich

    gpuopen.com

     

    기본적으로 다렉X9를 사용하는 프로그램인것같다. 2008년도가 마지막 업데이트인듯..

     

    빨간공를 출력하고 그 다음은 챕터3에서 텍스쳐에서 텍셀을 구해오는 샘플러 데이터형과

    텍스쳐 샘플링에 사용하는 HLSL함수인 tex2D() 함수에 대해서 알아보았다.

     

    기초설정을 한다.

     

    물체공간->월드공간->뷰공간->투영공간으로 만들어 주기 웨해

    월드 행렬, 뷰행렬, 투영행렬이 필요하다.

     

    따라서 월드, 뷰, 투영 행렬 변수를 만들어주고

    샘플러 데이터 형에 텍스쳐를 넣어준다.

     

     

    위는 버텍스 셰이더의 모습이다. 구조체를 만들어주고

    텍스쳐 UV값과 위치값를 PS로 넘겨준다.

     

     

    위는 픽셀셰이더의 모습이다. VS_OUPUT 구조체를 가져와

    mPositon을 지워버리는 모습이다. PS에서는 VS에서 반환한 값을

    가져오는 것이기 때문이다.

     

    tex2D 함수로 텍셀값을 구해온다. sampler2D 변수인 DiffuseSampler

    에서 Input에 TexCoord 좌표에 있는 텍셀을 잃어와 그 값을 알베도에 저장하는 코드이다.

     

    변수로 만들었던 DiffuseSampler는 HLSL에서 지원하는 데이터형으로

    텍셀 하나 구하는데에 쓰인다.

     

    이렇게 하고 왼쪽 Stream Mapping에서 

    Usage를 Texcoord를 추가하면 지구 모양이 나온다. 

    감히 예상해보는데 D3D에서 D3D11_INPUT_ELEMENT_DESC가 아닐까 싶다..?

     

    아래는 결과

     

    마지막으로 스위즐링이란 벡터의 성분의 순서를 바꿔서 접근하는 것을 스위즐이라고 한다.

     

    COMMENT
     
    1 ··· 3 4 5 6 7 8 9