🆕 HLSL
sampler2D
- 텍스처에서 텍셀을 구해올 때 사용하는 샘플러 데이터형tex2D()
- 텍스처 샘플링에 사용하는 HLSL 함수swizzle
- 벡터 성분의 순서를 마움대로 뒤섞을 수 있는 방법
DiffuseMap
으로 변경한다.DiffuseSampler
로 변경한다.
- 텍스처를 입히는 작업(텍스처매핑)은 표면을 구성하는 모든 픽셀에 입히는 것이기 때문에 각 픽셀마다 호출되는 픽셀셰이더에서 해야된다.
- UV좌표는 각 정점마다 지정해야되기 때문에 전역변수가 아닌 정점데이터의 일부로 전달된다.
1. 입력데이터
TEXCOORD
시맨틱을 가진다.struct VS_INPUT{
float4 mPosition : POSITION;
float2 mTexCoord : TEXCOORD0; //UV좌표 (TEXCOORD의 수가 여러개이므로 0을 붙임)
};
2. 출력데이터
struct VS_OUTPUT{
float4 mPosition : POSITION;
float2 mTexCoord : TEXCOORD0; //UV좌표 반환
};
☑️ 보간기
- 픽셀의 UV좌표는 정점의 UV좌표와 다른 것이 대부분이다.
- 픽셀의 올바른 UV좌표를 구하기 위해서 세 정점까지의 거리의 비율에 따라 UV값을 혼합한다. 이것을 보간기
Interpolator
가 처리한다.
3. 전역변수
float4x4 gWorldMatrix;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;
4. 함수
VS_OUTPUT vs_main(VS_INPUT Input){
VS_OUTPUT Output;
Output.mPosition = mul(Input.mPosition, gWorldMatrix);
Output.mPosition = mul(Output.mPosition, gViewMatrix);
Output.mPosition = mul(Output.mPosition, gProjectionMatrix);
Output.mTexCoord = Input.mTexCoord; //UV좌표 전달
return Output;
}
- 텍스처 이미지에서 텍셀
texel
을 구해와 그 색을 화면에 출력하기
1. 입력데이터 및 전역변수
sampler2D DiffuseSampler; //미리 만들어둔 텍스처 오브젝트
struct PS_INPUT{
float2 mTexCoord : TEXCOORD0; //정점셰이더로부터 보간기를 거쳐 들어온 UV좌표
};
2. 함수
PS_INPUT Input
매개변수 필요tex2D
내장 함수를 사용해 텍셀 값 구하기float4 ps_main(PS_INPUT Input): COLOR{
float4 albedo = tex2D(DiffuseSampler, Input.mTexCoord); //UV값과 텍스처 샘플러로 텍셀 값 구하기
return albedo.rgba;
}
☑️ 정점버퍼에서 올바른 UV좌표 값으로 불러오기
- Stream Mapping을 더블클릭하여 TEXCOORD 항목을 추가한다.
- F5를 눌러 결과를 확인한다.
// rgb값만 가져오기
float3 rgb = albedo.rgb;
//rgb채널의 순서 바꾸기
float4 newAlbedo = albedo.bgra;
//특정 채널만 반복
float4 newAlbedo = albedo.rrra;
🆕 HLSL
NORMAL
- 정점의 법선정보를 불러올 때 사용하는 시맨틱normalize()
- 벡터 정규화 (단위벡터로 만들기)dot()
- 내적saturate()
- 0~1을 넘어서는 값의 범위를 자름reflect()
- 벡터반사pow()
- 거듭제곱
diffuse Light
☑️ 람베르트 모델
Lambert
- 표면법선과 입사광이 이루는 각의 코사인 값이 난반사광의 양이다.
- 0도일때 가장 밝고
cos0=1
, 90도일때 가장 어둡다cos90=0
.- 그 이후로도 음수값이므로 어둡다.
- 빛의 밝기 그래프
- 셰이더에서 코사인 함수를 매번 호출하는 것의 대안으로 내적연산이 있다.
- 표면법선
A(a, b, c)
과 입사광B(d,e,f)
의 단위벡터를 내적하면 두 벡터를 이루는 각의 코사인값을 구할 수 있다.
cosΘ=|A'|·|B'| = (a x d) + (b x e) + (c x f)
gWorldLightPosition
를 만든다. 변수명을 더블클릭해 광원의 위치를 (500, 500, -500, 1)로 설정한다.1. 전역변수
float4x4 gWorldMatrix;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;
float4 gWorldLightPosition; //광원의 위치
2. 입력데이터
struct VS_INPUT{
float4 mPosition : POSITION;
float3 mNormal : NORMAL; //법선벡터
};
3. 출력데이터
mDiffuse
는 float가 아닌 float3으로 선언한다.mDiffuse
용도에 딱 맞는 시맨틱은 없다. 이런 경우 보통 TEXCOORD 시맨틱을 사용한다.struct VS_OUTPUT{
float4 mPostition : POSITION;
float3 mDiffuse : TEXCOORD1 //내적의 결과 (난반사광)
};
4. 함수
Output.mPosition
를 사용해야한다. VS_OUTPUT vs_main(VS_INPUT Input){
VS_OUTPUT Output;
Output.mPosition = mul(Input.mPosition, gWorldMatrix);
float3 lightDir = Output.mPosition.xyz - gWorldLightPosition.xyz; //입사광 벡터
lightDir = normalize(lightDir); //벡터의 길이 1로 만드는 정규화
Output.mPosition = mul(Output.mPosition, gViewMatrix);
Output.mPosition = mul(Output.mPosition, gProjectionMatrix);
//법선벡터를 월드공간으로 변환, Input.mNormal이 float3이므로 월드행렬을 3x3행렬로 캐스팅하여 계산한다.
float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatrix);
worldNormal = normalize(worldNormal); //정규화
-lightDir
를 사용한다.) Output.mDiffuse = dot(-lightDir, worldNormal); //내적
return Output; //결과 반환
}
struct PS_INPUT{
float3 mDiffuse : TEXCOORD1;
};
float4 ps_main(PS_INPUT Input) : COLOR
{
float3 diffuse = saturate(Input.mDiffuse); //난반사광의 범위를 -1~1에서 0~1로 바꿔준다.
return float4(diffuse, 1);
}
Specular Light
☑️퐁 모델
Phong
- 반사광과 카메라벡터가 이루는 각도의 코사인 값을 구하고, 그 결과를 여러번 거듭제곱한다.
- 정반사광의 좁은 폭을 재현하기 위해 거듭제곱하여 코사인값이 빠르게 줄어들도록 한다.
- 표면의 재질에 따라 제곱 횟수를 다르게 한다.
gWorldCameraPosition
으로 수정한다. ViewPosition 변수 시맨틱을 대입한다.1. 전역변수와 입출력데이터
V
, R
을 구해 픽셀셰이더에 전달해줘야한다.float4x4 gWorldMatrix;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;
float4 gWorldLightPosition;
float4 gWorldCameraPosition; //카메라 벡터
struct VS_INPUT{
float4 mPosition : POSITION;
float3 mNormal : NORMAL;
};
struct VS_OUTPUT{
float4 mPosition : POSITION;
float3 mDiffuse : TEXCOORD1;
float3 mViewDir : TEXCOORD2; //V - 카메라벡터
float3 mReflection : TEXCOORD3; //R - 반사광
};
2. 함수
VS_OUTPUT vs_main(VS_INPUT Input){
VS_OUTPUT Output;
Output.mPosition = mul(Input.mPosition, gWorldMatrix);
float3 lightDir = Output.mPosition.xyz - gWorldLightPosition.xyz;
lightDir = normalize(lightDir);
//카메라 벡터
float3 viewDir = normalize(Output.mPosition.xyz - gWorldCameraPosition.xyz);
Output.mViewDir = viewDir;
Output.mPosition = mul(Output.mPosition, gViewMatrix);
Output.mPosition = mul(Output.mPosition, gProjectionMatrix);
float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatrix);
worldNormal = normalize(worldNormal);
Output.mDiffuse = dot(-lightDir, worldNormal);
//입사광의 방향벡터, 반사면의 법선을 reflect함수에 전달하여 반사벡터를 구한다.
Output.mReflection = reflect(lightDir, worldNormal);
return Output;
}
1. 입력데이터
struct PS_INPUT{
float3 mDiffuse : TEXCOORD1;
float3 mViewDir : TEXCOORD2; //카메라벡터
float3 mReflection : TEXCOORD3; //반사광
};
2. 함수
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){ //난반사광이 존재하지 않는 표면에는 이미 빛이 닿지 않아 정반사광도 존재할 수 없다.
specular = saturate(dot(reflection, -viewDir)); // -viewDir를 사용한다.
specular = pow(specular, 20.0f); //거듭제곱 (수가 높을수록 정반사광의 범위가 좁아짐)
}
float3 ambient = float3(0.1f, 0.1f, 0.1f); //임의로 정의해준 간접광
return float4(ambient + diffuse + specular, 1);
}