🕹️자체엔진/DirectX 12 개인공부
빛&카툰셰이딩 실습 끝!
이번에 빛이랑 셰이더공부하면서 책에 끝에있는 연습문제중에서 카툰셰이더 구현문제가 있길래, 풀어보았습니다.
흠.... 처음에 어떻게 구현할까 책의 힌트를 봐가며, 고민해봤죠.
[책에 적혀있던힌트]
카툰스타일조명의 한가지 특징은 한 색조에서 갑자기 다른 색조로 넘어간다는것이다.
흠.... 고민하다가, 분산반사율을 조정해야하나? 아니면 빛계산으로 구해진 평행광을 조정해야하나?
고민하다가 하나씩 다 넣어보니까 평행광->점광->점적광을 모두 계산한 빛에다가 카툰셰이딩을 지지고볶고해보았습니다.
실제 구현부분은 밑에 코드로 남겨놓겠습니다.
그냥 C++에서 일일이 DiffuseAlbedo를 조정하는것보다 hlsl셰이더쪽에서 모든 유형의 빛의 계산이 끝난뒤 한번에 계산하는것이 편하고 심플할것같아서 그렇게했습니다.
만들어본 카툰셰이더는 총 3가지이고, 그중 한가지입니다.
셰이더의 대략적인 소스는 책을 참고했습니다.
이해가 안가는부분이 없는지 주석으로 하나하나달아보았습니다.
그리고 BlinnPhong은 반영반사율이랑 분산반사율의 합을 구하는 함수입니다.
분산반사율은 저가 이미 상수버퍼로 주었기때문에 반영반사율만 구해준 소스다죠~
Shader.hlsl
#ifndef NUM_DIR_LIGHTS
#define NUM_DIR_LIGHTS 1
#endif
#ifndef NUM_POINT_LIGHTS
#define NUM_POINT_LIGHTS 0
#endif
#ifndef NUM_SPOT_LIGHTS
#define NUM_SPOT_LIGHTS 0
#endif
#define MaxLights 16
struct Light
{
float3 Strength;
float FalloffStart; // 점광/점적광
float3 Direction; // 평행광/점적광
float FalloffEnd; // 점광/점적광
float3 Position; // 점광
float SpotPower; // 점적광
};
struct Material
{
float4 DiffuseAlbedo; // 분산반사율: 빛이 매질을 통과할때 어떠한 빛은 흡수되고, 어떠한 빛은 분산되어 방출하지만, 이때 빛이 분산반사되는 크기
float3 FresnelR0; // 프레넬방정식에서 사용하는 슐릭근사에서의 R0: 매질마다의 굴절률
float Shininess; // (1 - Roughness)이며 거칠기의 반대: [0, 1]이며 0에 가까울수록 거칠고, 1에 가까울수록 매끈하다
};
float CalcAttenuation(float d, float falloffStart, float falloffEnd)
{
// 선형 감쇠함수 (falloff End - d / falloffEnd - falloffStart)
return saturate((falloffEnd - d) / (falloffEnd - falloffStart));
}
// 프레넬방정식에서 사용하는 슐릭근사(Schlick approximation)
float3 SchlickFresnel(float3 R0, float3 normal, float3 lightVec)
{
// 슐릭근사: R(r) = R(0) x {(1 - R(0)) * (cos(O)^5)}
// 람베르트 코사인법칙: cos(O) = max(L (dot) n, 0)
// L: lightVec
// n: normal
float cosIncidentAngle = saturate(dot(normal, lightVec));
float f0 = 1.0f - cosIncidentAngle;
float3 reflectPercent = R0 + (1.0f - R0) * (f0 * f0 * f0 * f0 * f0);
return reflectPercent;
}
float3 BlinnPhong(float3 lightStrength, float3 lightVec, float3 normal, float3 toEye, Material mat)
{
const float m = mat.Shininess * 256.0f;
float3 halfVec = normalize(toEye + lightVec);
float roughnessFactor = (m + 8.0f) * pow(max(dot(halfVec, normal), 0.0f), m) / 8.0f;
float3 fresnelFactor = SchlickFresnel(mat.FresnelR0, halfVec, lightVec);
float3 specAlbedo = fresnelFactor * roughnessFactor;
// LDR렌더링을 사용할것이기 때문에 반영반사율을 [0, 1]사이로 매핑
specAlbedo = specAlbedo / (specAlbedo + 1.0f);
return (mat.DiffuseAlbedo.rgb + specAlbedo) * lightStrength;
}
// 평행광 계산
float3 ComputeDirectionalLight(Light L, Material mat, float3 normal, float3 toEye)
{
// 빛의 방향에 -1을 곱하면 빛벡터가 된다
float3 lightVec = -L.Direction;
// 람베르트 코사인법칙
float ndotl = max(dot(lightVec, normal), 0.0f);
float3 lightStrength = L.Strength * ndotl;
return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
// 점광 계산
float3 ComputePointLight(Light L, Material mat, float3 pos, float3 normal, float3 toEye)
{
// 뷰x투영행렬의 위치에서에서 빛의 위치방향으로 가는 벡터
float3 lightVec = L.Position - pos;
// 빛벡터의 길이
float d = length(lightVec);
// 맞는지 확인
if (d > L.FalloffEnd)
return 0.0f;
// 빛벡터 일반화
lightVec /= d;
// 람베르트 코사인법칙
float ndotl = max(dot(lightVec, normal), 0.0f);
float3 lightStrength = L.Strength * ndotl;
// 빛의세기에 선형감쇠함수의 결과값을 곱한다.
float att = CalcAttenuation(d, L.FalloffStart, L.FalloffEnd);
lightStrength *= att;
return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
// 점적광 계산
float3 ComputeSpotLight(Light L, Material mat, float3 pos, float3 normal, float3 toEye)
{
// 뷰x투영행렬의 위치에서에서 빛의 위치방향으로 가는 벡터
float3 lightVec = L.Position - pos;
// 빛벡터의 길이
float d = length(lightVec);
// 맞는지 확인
if (d > L.FalloffEnd)
return 0.0f;
// 빛벡터 일반화
lightVec /= d;
// 람베르트 코사인법칙
float ndotl = max(dot(lightVec, normal), 0.0f);
float3 lightStrength = L.Strength * ndotl;
// 빛의세기에 선형감쇠함수의 결과값을 곱한다.
float att = CalcAttenuation(d, L.FalloffStart, L.FalloffEnd);
lightStrength *= att;
// 빛의세기에 max(-L (dot) d, 0)^s: s(L.SpotPower)를 조율함으로써 점적광원뿔의 크기를 변경할 수 있다.
float spotFactor = pow(max(dot(-lightVec, L.Direction), 0.0f), L.SpotPower);
lightStrength *= spotFactor;
return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
float4 ComputeLighting(Light gLights[MaxLights], Material mat,
float3 pos, float3 normal, float3 toEye,
float3 shadowFactor)
{
float3 result = 0.0f;
int i = 0;
#if (NUM_DIR_LIGHTS > 0)
for (i = 0; i < NUM_DIR_LIGHTS; ++i)
{
result += shadowFactor[i] * ComputeDirectionalLight(gLights[i], mat, normal, toEye);
}
#endif
#if (NUM_POINT_LIGHTS > 0)
for (i = NUM_DIR_LIGHTS; i < NUM_DIR_LIGHTS + NUM_POINT_LIGHTS; ++i)
{
result += ComputePointLight(gLights[i], mat, pos, normal, toEye);
}
#endif
#if (NUM_SPOT_LIGHTS > 0)
for (i = NUM_DIR_LIGHTS + NUM_POINT_LIGHTS; i < NUM_DIR_LIGHTS + NUM_POINT_LIGHTS + NUM_SPOT_LIGHTS; ++i)
{
result += ComputeSpotLight(gLights[i], mat, pos, normal, toEye);
}
#endif
return float4(result, 0.0f);
}
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
};
cbuffer cbMaterial : register(b1)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
cbuffer cbPass : register(b2)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
float4x4 gViewProj;
float4x4 gInvViewProj;
float3 gEyePosW;
float cbPerObjectPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
float4 gAmbientLight;
Light gLights[MaxLights];
};
struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;
float3 NormalW : NORMAL;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout = (VertexOut)0.0f;
// 월드 행렬로 변환
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
vout.PosW = posW.xyz;
// if1. 세계행렬에 비균등비례가 있다면, 역전치 월드행렬을 사용해야한다.
// if2. 세계행렬에 비균등비례가 없다면, 월드행렬을 이용해 법선을 변환한다.
vout.NormalW = mul(vin.NormalL, (float3x3)gWorld);
// 동차절단공간으로 변환
vout.PosH = mul(posW, gViewProj);
return vout;
}
float4 toonShading(float4 diffuse) {
float4 color = saturate(diffuse);
color = ceil(color * 5) / 5.0f;
color.a = diffuse.a;
return color;
}
float4 toonShading_kd(float4 kd) {
float4 color = saturate(kd);
if (color.x <= 0.0f) {
color.x = 0.4f;
}
else if (0.0f < color.x && color.x <= 0.5f) {
color.x = 0.6f;
}
else if (0.5f < color.x && color.x <= 1.0f) {
color.x = 1.0f;
}
if (color.y <= 0.0f) {
color.y = 0.4f;
}
else if (0.0f < color.y && color.y <= 0.5f) {
color.g = 0.6f;
}
else if (0.5f < color.y && color.y <= 1.0f) {
color.y = 1.0f;
}
if (color.z <= 0.0f) {
color.z = 0.4f;
}
else if (0.0f < color.z && color.z <= 0.5f) {
color.z = 0.6f;
}
else if (0.5f < color.z && color.z <= 1.0f) {
color.z = 1.0f;
}
color.a = saturate(kd).a;
return color;
}
float4 toonShading_ks(float4 ks) {
float4 color = saturate(ks);
if (0.0f < color.x && color.x <= 0.1f) {
color.x = 0.0f;
}
else if (0.1f < color.x && color.x <= 0.8f) {
color.x = 0.5f;
}
else if (0.8f < color.x && color.x <= 1.0f) {
color.x = 0.8f;
}
if (0.0f < color.y && color.y <= 0.1f) {
color.y = 0.0f;
}
else if (0.1f < color.y && color.y <= 0.8f) {
color.y = 0.5f;
}
else if (0.8f < color.y && color.y <= 1.0f) {
color.y = 0.8f;
}
if (0.0f < color.z && color.z <= 0.1f) {
color.z = 0.0f;
}
else if (0.1f < color.z && color.z <= 0.8f) {
color.z = 0.5f;
}
else if (0.8f < color.z && color.z <= 1.0f) {
color.z = 0.8f;
}
color.a = saturate(ks).a;
return color;
}
float4 PS(VertexOut pin) : SV_Target
{
// 법선을 보간하여, 단위길이가 아닐 수 있기때문에 정규화한다.
pin.NormalW = normalize(pin.NormalW);
// 조명되는 점에서 눈으로의 벡터
float3 toEyeW = normalize(gEyePosW - pin.PosW);
// 주변광계산
float4 ambient = gAmbientLight * gDiffuseAlbedo;
// 평행광계산
const float shininess = 1.0f - gRoughness;
Material mat = { gDiffuseAlbedo, gFresnelR0, shininess };
float3 shadowFactor = 1.0f;
float4 directLight = ComputeLighting(gLights, mat, pin.PosW,
pin.NormalW, toEyeW, shadowFactor);
directLight = toonShading(directLight);
//directLight = toonShading_ks(directLight);
//directLight = toonShading_kd(directLight);
float4 litColor = ambient + directLight;
// 분산재질에서 알파를 가져온다.
litColor.a = gDiffuseAlbedo.a;
return litColor;
}
짜잔~~ 끝!
흐아~~ 드디어 텍스쳐 매핑 배운다!
'🕹️자체엔진 > DirectX 12 개인공부' 카테고리의 다른 글
뭐지...? 왜이렇게 어렵지...? (0) | 2022.03.22 |
---|---|
DirectX12. 텍스쳐실습끝 혼합(blending)들어갑니다.....! (0) | 2022.03.19 |
Model loader(모델 불러오는 프로그램) 만들었습니다. (3) | 2022.03.03 |
[3장] 좌표 변경 변환 문제풀이 | DirectX 12를 이용한 3D 게임 프로그래밍 입문 (4) | 2021.12.16 |
게임개발 공부 (1) 행렬 | Direct 12를 이용한 3D 게임 프로그래밍 입문 (0) | 2021.12.11 |
댓글