[ShaderMonkey] 기초적인 조명 셰이더
by 브이담곰📄 HSLS
- NORMAL : 정점의 법선정보를 불러올 때 사용하는 시멘틱
- normalize(): 벡터 정규화 함수
- dot(): 내적 함수
- saturate(): 0~1을 넘어서는 값의 범위를 짤라 냄
- reflect(): 벡터반사 함수
- pow(): 거듭제곱 함수
📄 수학
- 내적: 코사인 함수를 빠르게 계산할 때 사용할 수 있다.
- 정규화: 벡터를 단위벡터로 만든다.
🤍 직접광과 간접광
- 직접광: 광원으로부터 직접 받는 빛.
- 간접광: 다른 물체에 반사되서 들어오는 빛.
간접광은 수없이 반사의 반사를 거치기 때문에 직접광보다 계산하기 어렵다. 간접광을 계산하는 방법에는 "광선추적(ray-tracing)"이라는 기법이 있다. 이러한 기법을 사용하는 이유는 하드웨어 사양이 따라주지 않기 때문에, 직접광만 제대로 계산하고 간접광은 흉내내기 정도에 그친다.
빛을 구성하는 요소 : 난반사광(diffuse light), 정반사관(specular light)
📌 난반사광(diffuse light)
우리가 물체를 볼 수 있는 이유 -> 빛이 물체의 표면에서 반사되기 때문.
1. 의미: 여러 방향으로 반사되는 빛
2. 특징 : 여러방향으로 고르게 펴진다. -> 어느 방향에서 바라봐도 물체의 명암이나 색조가 변하지 않는다.
3. 람베르트 모델
1. 게임에서는 주로 난반사광을 계산 할 때 "람베르트 모델"을 사용한다.
2. 정의 및 특징 : 표면법선과 입사광이 이루는 각의 코사인 값을 구하면 난반사광의 양이다.
해가 중천에 떠있을 때(90도) 가장 밝고, 해가 지평선에 있을 때(0도) 가장 어둡다.
-90~90사이에 ?인 이유는 각도에 따라 얼마나 빠르게 어두워지는 모르기 때문이다. 하지만 y=cos(x) 그래프와 추이가 비슷하다. 따라서 람베르트 모델을 사용하면 코사인 함수로 난반사광을 구할 수 있다.
하지만 코사인함수를 매번 호출하기엔 비용이 너무 든다.
내적을 이용해 코사인을 대신하자!
4. 셰이더 구현
(1) Vertex Shader : 전역변수 선언
float4x4 gWorldMatirx;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;//월드,뷰,프로젝션 세가지 행렬 선언
float4 gWorldLightPosition; //광원의 위치
(2) Vertex Shader : 구조체 정의
기본 Input, Output 구조체에서, Input에는 mNormal이라는 NORMAL시멘틱을 갖고있는 변수를 추가해주고,
Output에는 mDiffuse라는 TEXCOORD1이라는 시멘틱을 갖고있는 변수를 추가해준다.
struct VS_INPUT
{
float4 mPosition : POSITION;
float3 mNormal: NORMAL;
};
struct VS_OUTPUT
{
float4 mPosition: POSITION;
float3 mDiffuse: TEXCOORD1;
};
(3) Vertex Shader : vs_main 작성
VS_OUTPUT vs_main(VS_INPUT Input)
{
VS_OUTPUT Output; //반환할 Output변수 선언
//1. 입사광 벡터 계산
//World좌표를 지역공간에 곱해줘서 월드공간으로 만든다.
Output.mPosition = mul(Input.mPosition, gWorldMatirx);
/*Output.mPositon은 투영공간(우리가 보는 공간)의 좌표이다.
Input.mPositon은 지역공간의 좌표이다.
3D행렬계산은 모든 변수의 공간이 일치해야한다.*/
//현재위치(월드공간 위치)에서 광원의 위치(전역변수)를 빼준다.
float3 lightDir = Output.mPosition.xyz - gWorldLightPosition.xyz;
lightDir = normalize(lightDir);//백터의 길이를 1로(내적을위해서)만들어야 하므로
// 정규화 시켜준다 -> 단위벡터로 만든다. HSLS내장함수 사용.
Output.mPosition = mul(Output.mPosition, gViewMatrix);
Output.mPosition = mul(Output.mPosition, gProjectionMatrix);
//2. 법선 벡터 계산
/* 법선 벡터는 정점버퍼에서 곧바로 오기는 데이터이기 때문에 물체공간에 존재한다.
따라서, 월드공간으로 변환해주어야 한다.*/
float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatirx);
//gWorldMatrix는 4x4인데, Input.mNormal은 3x3이므로 형변환 후 계산한다.
//4x4의 4번째 행렬은 평행이동 값이므로 방향벡터에 아무런 영향도 미치지 않는다.
worldNormal = normalize(worldNormal); //단위벡터로 만들기: 정규화
Output.mDiffuse = dot(-lightDir, worldNormal); // 내적
//-lightDir인 이유는, 두 벡터의 내적을 구할 때는 화살표의 밑동이 서로 만나야 하기 때문.
return Output;
}
(4) Pixel Shader
struct PS_INPUT
{
float3 mDiffuse : TEXCOORD1;
};
float4 ps_main(PS_INPUT Input) :COLOR
{
float3 diffuse = saturate(Input.mDiffuse);
return float4(diffuse, 1);
}
📌 정반사광(specular light)
- 입사광 중 일부는 난반사광이 되고 다른 일부는 정반사광이 된다.
1. 특징 : 한 방향으로반 반사되는 빛. 입사각이 출사각과 같다.
-> 정반사광의 효과를 보려면 빛이 반사되는 방향에서 물체를 바라봐야 한다.
2. 예시 : 모니터에 빛이 반사되어 보기 힘들었던 경우.
3. 퐁(Phong) 모델
- 반사광과 카메라벡터(카메라에서 현재위치까지 선을 그은 벡터)가 이루는 각도의 코사인 값을 구하고, 그 결과값을 여러번 거듭제곱하면 구할 수 있다.
하지만, 정반사광은 난반사광과 다르게 폭이 매우 좁다. 따라서 이를 구현하기 위해서 코사인값을 거듭제곱 하면.
* 표면의 재질에 따라 거듭제곱의 횟수는 달라질 수 있다.
4. 셰이더 구현
(1) Vertex Shader : 전역변수 추가
float4 gWorldCameraPosition; //카메라 위치 값
(2) Vertex Shader : 구조체 VS_OUTPUT에 멤버변수 추가
float3 mViewDir: TEXCOORD2; //카메라V 방향벡터
float3 mReflection: TEXCOORD3;//반사광R 벡터
(3) Vertex Shader : 카메라벡터 구하기
float3 viewDir = normalize(Output.mPosition.xyz - gWorldCameraPosition.xyz);
//동시에 정규화도 해주기. 월드 - 카메라 = 카메라 벡터
Output.mViewDir = viewDir;
(4) Vertex Shader : reflect() 함수 사용
Output.mReflection = reflect(lightDir, worldNormal);
//첫번째 인자: 입사광의 방향벡터/ 두번째 인자: 반사면의 법선
(5) Pixel Shader : 구조체 PS_INPUT에 멤버변수 추가
float3 mViewDir: TEXCOORD2;
float3 mReflection: TEXCOORD3;
(6) Pixel Shader : 값 재 정규화
보간기를 거치는 동안 값이 흐트러질 수 있기 때문에, 백터들을 다시 정규화 시켜준다.
float3 reflection = normalize(Input.mReflection);
float3 viewDir = normalize(Input.mViewDir);
(7) Pixel Shader : 내적 후 제곱 계산
float3 specular = 0;
if(diffuse.x > 0)/*난반사가 없는 표면에는 정반사도 존재하지 않으므로 난반사광이 0%이상일때만
계산*/
{
specular = saturate(dot(reflection, -viewDir));//내적
specular = pow(specular, 20.0f);//거듭제곱
}
return float4(specular, 1);
return float4(diffuse+specular, 1);
float3 ambient = float3(0.1f, 0.1f, 0.1f); //주변광
return float4(ambient + diffuse + specular, 1);//주변광+난반사+정반사
📌 DirextX11에 셰이더 적용하기
'Technical Artist > ShaderMonkey' 카테고리의 다른 글
[ShaderMonkey] Texture Mapping (0) | 2021.11.23 |
---|---|
[ShaderMonkey] 빨강 셰이더 (0) | 2021.11.23 |
[shader monkey] 첫 셰이더 만들기 (0) | 2021.11.09 |
블로그의 정보
농담곰담곰이의곰담농
브이담곰