본문 바로가기
셰이더 (Shader)/셰이더 프로그래밍 입문 - Pope Kim (완)

[HLSL] 챕터9 - UV애니메이션과 울렁효과

by Minkyu Lee 2023. 3. 20.

강물, 마그마 흘러가는 모습.

UV애니메이션을 사용한 것이다.

표면에 정점마다 UV 좌표를 지정한다.

이것을 한쪽으로 이동시킨다.

 

// 시간의 흐름에따라 UV를 변경

그렇다면 현재 시간을 알아야한다.

셰이더는 GPU상에서 실행되어 시간을 구하는 함수가 없다.

그래픽 카드에는 시스템 클럭이 안달려있기 때문이다.

현재시간은 CPU에서 계산한 값 넘겨 받는다.

 

정점셰이더

float4x4 gWorldMatrix;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;

float4 gWorldLightPosition;
float4 gWorldCameraPosition;

float gTime;
float gWaveHeight;
float gSpeed;
float gWaveFrequency;
float gUVSpeed;

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)
{
   VS_OUTPUT Output;
   
   float cosTime = cos(gTime * gSpeed + Input.mUV.x * gWaveFrequency) * gWaveHeight;
   Input.mPosition.y += cosTime;
   
   Output.mPosition = mul(Input.mPosition, gWorldMatrix);
   float3 lightDir = normalize(Output.mPosition.xyz-gWorldLightPosition.xyz);
   
   // viewDir
   float3 viewDir = normalize(Output.mPosition.xyz-gWorldCameraPosition.xyz);
   Output.mViewDir = viewDir;
   
   // Position
   Output.mPosition = mul(Output.mPosition, gViewMatrix);
   Output.mPosition = mul(Output.mPosition, gProjectionMatrix);
   
   // Diffuse & Reflection
   float3 worldNormal = mul(Input.mNormal, gWorldMatrix);
   Output.mDiffuse = dot(-lightDir, worldNormal);
   float3 reflection = reflect(lightDir, worldNormal);
   Output.mReflection = reflection;
   Output.mUV = Input.mUV + float2(gTime * gUVSpeed, 0);
   
   return Output;
}

// U좌표만 흐르도록 한다.

따라서 V 좌표에는 0을 더한다.

 

  • 울렁효과

흘러갈 때 정점도 위아래로 울렁이는 효과를 넣는다.

Y값에 코사인을 활용한다.

Y값은 지역공간에서 적용한다.

 

코사인 함수의 인자 호도법으로 정의된 각도를 받는다.

반환값이 -1 ~ 1이다.

 

정점마다 다른 높이를 주어야하므로 인자는 부드럽게 변화하는 값을 넣으면 된다.

UV를 인자로 사용한다.

여기서는 U를 사용하였다.

 

전역변수로 인자값들을 빼내면 다시 컴파일 하지 않아도 된다.

편리하다.

 

픽셀셰이더

float3 gLightColor; // *** Start Wool Lung Hyo Gwa !!!
sampler2D DiffuseSampler;
sampler2D SpecularSampler;

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

float4 ps_main(PS_INPUT Input) : COLOR
{
   float4 albedo = tex2D(DiffuseSampler, Input.mUV);

   float3 diffuse = gLightColor * albedo.rgb * saturate(Input.mDiffuse);
   
   float3 viewDir = normalize(Input.mViewDir);
   float3 reflection = normalize(Input.mReflection);
   float3 specular = 0;
   if(diffuse.x > 0)
   {
      specular = saturate(dot(reflection, -viewDir));
      specular = pow(specular, 20.0);
      
      float4 specularIntensity = tex2D(SpecularSampler, Input.mUV);
      specular *= specularIntensity.rgb * gLightColor;
   }
   float3 ambient = float3(0.1, 0.1, 0.1) * albedo.rgb;
   return float4(diffuse + specular + ambient, 1.0);
}

디퓨즈 / 스페큘러 매핑에 썼던 것과 동일하다.

 

  • 기타 고급 정점셰이더 기법

- 스키닝 : BLENDWEIGHT와 BLENDINDICES 시맨틱을 이용하면 정점데이터에서 스키닝 정보 가져올 수 있다.

최종 애니메이션 행렬 전역변수로 건네 받는다. CPU의 스키닝과 동일해진다.

전역변수 사용하지 않고, 행렬들을 텍스쳐로 저장한 뒤 정점셰이더에서 텍스쳐 샘플링하는 경우도 있다.

 

- 지형: 높이맵을 이용한 기법. 정점 텍스쳐를 사용한다.

 

- 표정 애니메이션 : 여러가지 표정 별도 메쉬에 저장한다.

정점셰이더에서 두 메쉬를 혼합하여 표정 표현한다.

이것과 얼굴 스키닝 함께 사용 가능하다.

댓글