Direct3D 9에서 HLSL 셰이더 작성

꼭짓점 셰이더 기본 사항

작업 중일 때 프로그래밍 가능한 꼭짓점 셰이더는 Microsoft Direct3D 그래픽 파이프라인에서 수행하는 꼭짓점 처리를 대체합니다. 꼭짓점 셰이더를 사용하는 동안 고정 함수 파이프라인에서 변환 및 조명 작업에 대한 상태 정보는 무시됩니다. 꼭짓점 셰이더가 비활성화되고 고정 함수 처리가 반환되면 모든 현재 상태 설정이 적용됩니다.

꼭짓점 셰이더가 실행되기 전에 고차 기본 형식의 테셀레이션을 수행해야 합니다. 셰이더 처리 후 표면 테셀레이션을 수행하는 구현은 애플리케이션 및 셰이더 코드에 명확하지 않은 방식으로 수행해야 합니다.

최소한 꼭짓점 셰이더는 동종 클립 공간에서 꼭짓점 위치를 출력해야 합니다. 필요에 따라 꼭짓점 셰이더는 텍스처 좌표, 꼭짓점 색, 꼭짓점 조명, 안개 인자 등을 출력할 수 있습니다.

픽셀 셰이더 기본 사항

픽셀 처리는 픽셀 셰이더가 개별 픽셀에 대해 수행합니다. 픽셀 셰이더는 꼭짓점 셰이더와 함께 작동하며, 꼭짓점 셰이더의 출력은 픽셀 셰이더에 대한 입력을 제공합니다. 셰이더를 실행한 후 다른 픽셀 작업(안개 혼합, 스텐실 작업 및 렌더링 대상 혼합)이 발생합니다.

텍스처 단계 및 샘플러 상태

픽셀 셰이더는 이전에 텍스처 단계 상태에서 정의한 작업을 포함하여 다중 텍스처 블렌더에서 지정한 픽셀 혼합 기능을 완전히 대체합니다. 축소, 배율, mip 필터링 및 래핑 주소 지정 모드를 위해 표준 텍스처 스테이지 상태로 제어된 텍스처 샘플링 및 필터링 작업은 셰이더에서 초기화할 수 있습니다. 애플리케이션은 현재 바인딩된 셰이더를 다시 생성할 필요 없이 이러한 상태를 자유롭게 변경할 수 있습니다. 셰이더가 효과 내에서 디자인된 경우 상태를 더 쉽게 설정할 수 있습니다.

픽셀 셰이더 입력

픽셀 셰이더 버전 ps_1_1 - ps_2_0의 경우, 확산 및 반사 색은 셰이더에서 사용하기 전에 0 ~ 1 범위로 포화(고정)됩니다.

픽셀 셰이더에 대한 색 값 입력은 관점이 올바른 것으로 간주되지만 모든 하드웨어에 대해 보장되지는 않습니다. 텍스처 좌표에서 샘플링된 색은 올바른 원근감으로 반복되며 반복하는 동안 0~1 범위로 고정됩니다.

픽셀 셰이더 출력

픽셀 셰이더 버전 ps_1_1 - ps_1_4의 경우 픽셀 셰이더에서 내보낸 결과는 레지스터 r0의 내용입니다. 셰이더가 처리를 완료할 때 포함된 항목은 무엇이든 안개 단계 및 렌더링 대상 블렌더로 전송됩니다.

픽셀 셰이더 버전 ps_2_0 이상의 경우 출력 색은 oC0 - oC4에서 내보냅니다.

셰이더 입력 및 셰이더 변수

셰이더 변수 선언

가장 간단한 변수 선언에는 형식 및 변수 이름(예: 다음 부동 소수점 선언)이 포함됩니다.

float fVar;

동일한 문으로 변수를 초기화할 수 있습니다.

float fVar = 3.1f;

변수 배열을 선언할 수 있습니다.

int iVar[3];

또는 동일한 문에서 선언한 후 초기화할 수 있습니다.

int iVar[3] = {1,2,3};

다음은 HLSL(High-Level Shader Language) 변수의 많은 특성을 보여 주는 몇 가지 선언입니다.

float4 color;
uniform float4 position : POSITION; 
const float4 lightDirection = {0,0,1};

데이터 선언은 다음을 비롯한 모든 유효한 형식을 사용할 수 있습니다.

셰이더에는 최상위 변수, 인수 및 함수가 있을 수 있습니다.

// top-level variable
float globalShaderVariable; 

// top-level function
void function(
in float4 position: POSITION0 // top-level argument
              )
{
  float localShaderVariable; // local variable
  function2(...)
}

void function2()
{
  ...
}

최상위 변수는 모든 함수 외부에서 선언됩니다. 최상위 인수는 최상위 함수에 대한 매개 변수입니다. 최상위 함수는 (다른 함수에서 호출하는 함수와 반대로) 애플리케이션에서 호출하는 함수입니다.

Uniform 셰이더 입력

꼭짓점 및 픽셀 셰이더는 두 종류의 입력 데이터 즉, varying 및 uniform입니다. varying 입력은 셰이더의 각 실행에 고유한 데이터입니다. 꼭짓점 셰이더의 경우 varying 데이터(예: 위치, 법선 등)는 꼭짓점 스트림에서 가져옵니다. uniform 데이터(예: 재질 색, 세계 좌표 변환 등)는 셰이더의 여러 실행에 대해 일정합니다. 어셈블리 셰이더 모델에 익숙한 경우 uniform 데이터는 상수 레지스터가, varying 데이터는 v 및 t 레지스터가 지정합니다.

Uniform 데이터는 두 가지 방법으로 지정할 수 있습니다. 가장 일반적인 방법은 전역 변수를 선언하고 셰이더 내에서 사용하는 것입니다. 셰이더 내에서 전역 변수를 사용하면 해당 셰이더에 필요한 균일한 변수 목록에 해당 변수가 추가됩니다. 두 번째 방법은 최상위 셰이더 함수의 입력 매개 변수를 uniform으로 표시하는 것입니다. 이 표시는 지정된 변수를 uniform 변수 목록에 추가하도록 지정합니다.

셰이더에서 사용하는 uniform 변수는 상수 테이블을 통해 애플리케이션에 다시 전달됩니다. 상수 테이블은 셰이더에서 사용하는 uniform 변수가 상수 레지스터에 맞는 방식을 정의하는 기호 테이블의 이름입니다. uniform 함수 매개 변수는 전역 변수와 달리 달러 기호($)가 앞에 추가된 상수 테이블에 나타납니다. 로컬 uniform 입력과 이름이 같은 전역 변수 간의 이름 충돌을 방지하려면 달러 기호가 필요합니다.

상수 테이블에는 셰이더에서 사용하는 모든 uniform 변수의 상수 레지스터 위치가 포함됩니다. 또한 테이블에는 형식 정보 및 기본값(지정된 경우)도 포함됩니다.

Varying 셰이더 입력 및 시맨틱

셰이더를 실행할 때 값이 일정함을 나타내는 시맨틱 또는 uniform 키워드로 (최상위 셰이더 함수)의 varying 입력 매개 변수를 표시해야 합니다. 최상위 셰이더 입력이 시맨틱 또는 uniform 키워드로 표시되지 않으면 셰이더가 컴파일에 실패합니다.

입력 시맨틱은 지정된 입력을 그래픽 파이프라인의 이전 부분의 출력에 연결하는 데 사용되는 이름입니다. 예를 들어 입력 시맨틱 POSITION0은 꼭짓점 셰이더에서 꼭짓점 버퍼의 위치 데이터를 연결할 위치를 지정하는 데 사용됩니다.

픽셀 및 꼭짓점 셰이더에는 각 셰이더 단위로 공급되는 그래픽 파이프라인의 여러 부분으로 인해 입력 시맨틱 집합이 다릅니다. 꼭짓점 셰이더 입력 시맨틱은 꼭짓점 버퍼에서 꼭짓점 셰이더에서 사용할 수 있는 양식으로 로드할 꼭짓점별 정보(예: 위치, 법선, 질감 좌표, 색, 탄젠트, 이진 등)를 설명합니다. 입력 시맨틱은 꼭짓점 선언 사용량 및 사용량 인덱스에 직접 매핑됩니다.

픽셀 셰이더 입력 시맨틱은 래스터화 단위에서 픽셀당 제공되는 정보를 설명합니다. 데이터는 현재 기본 형식의 각 꼭짓점에 대한 꼭짓점 셰이더의 출력 간에 보간하여 생성됩니다. 기본 픽셀 셰이더 입력 시맨틱은 출력 색 및 텍스처 좌표 정보를 입력 매개 변수에 연결합니다.

다음 두 가지 방법으로 셰이더 입력에 입력 시맨틱을 할당할 수 있습니다.

  • 매개 변수 선언에 콜론 및 시맨틱 이름을 추가합니다.
  • 각 구조체 멤버에 할당된 입력 시맨틱을 사용하여 입력 구조를 정의합니다.

꼭짓점 및 픽셀 셰이더는 후속 그래픽 파이프라인 단계에 출력 데이터를 제공합니다. 출력 시맨틱은 셰이더에서 생성된 데이터를 다음 단계의 입력에 연결하는 방법을 지정하는 데 사용됩니다. 예를 들어 꼭짓점 셰이더의 출력 시맨틱은 래스터라이저의 보간기 출력을 연결하여 픽셀 셰이더에 대한 입력 데이터를 생성하는 데 사용됩니다. 픽셀 셰이더 출력은 각 렌더링 대상의 알파 혼합 단위에 제공된 값 또는 깊이 버퍼에 기록된 깊이 값입니다.

꼭짓점 셰이더 출력 시맨틱은 셰이더를 픽셀 셰이더와 래스터라이저 단계에 연결하는 데 사용됩니다. 래스터라이저에서 사용하고 픽셀 셰이더에 노출되지 않는 꼭짓점 셰이더는 위치 데이터를 최소한으로 생성해야 합니다. 텍스처 좌표 및 색 데이터를 생성하는 꼭짓점 셰이더는 보간이 완료된 후 해당 데이터를 픽셀 셰이더에 제공합니다.

픽셀 셰이더 출력 시맨틱은 픽셀 셰이더의 출력 색을 올바른 렌더링 대상과 바인딩합니다. 픽셀 셰이더 출력 색은 대상 렌더링 대상이 수정되는 방법을 결정하는 알파 혼합 단계에 연결됩니다. 픽셀 셰이더 깊이 출력을 사용하여 현재 래스터 위치에서 대상 깊이 값을 변경할 수 있습니다. 깊이 출력 및 여러 렌더링 대상은 일부 셰이더 모델에서만 지원됩니다.

출력 시맨틱의 구문은 입력 시맨틱을 지정하는 구문과 동일합니다. 시맨틱은 "out" 매개 변수로 선언된 매개 변수에 직접 지정하거나 "out" 매개 변수 또는 함수의 반환 값으로 반환되는 구조체를 정의하는 중 할당될 수 있습니다.

시맨틱은 데이터의 출처를 식별합니다. 시맨틱은 셰이더 입출력을 식별하는 선택적 식별자입니다. 시맨틱은 다음 세 위치 중 하나에 표시됩니다.

  • 구조체 멤버 뒤
  • 함수의 입력 인수 목록에서 인수 뒤
  • 함수의 입력 인수 목록 뒤

이 예제에서는 구조체를 사용하여 하나 이상의 꼭짓점 셰이더 입력을 제공하고 다른 구조체를 사용하여 하나 이상의 꼭짓점 셰이더 출력을 제공합니다. 각 구조체 멤버는 시맨틱을 사용합니다.

vector vClr;

struct VS_INPUT
{
    float4 vPosition : POSITION;
    float3 vNormal : NORMAL;
    float4 vBlendWeights : BLENDWEIGHT;
};

struct VS_OUTPUT
{
    float4  vPosition : POSITION;
    float4  vDiffuse : COLOR;

};

float4x4 mWld1;
float4x4 mWld2;
float4x4 mWld3;
float4x4 mWld4;

float Len;
float4 vLight;

float4x4 mTot;

VS_OUTPUT VS_Skinning_Example(const VS_INPUT v, uniform float len=100)
{
    VS_OUTPUT out;

    // Skin position (to world space)
    float3 vPosition = 
        mul(v.vPosition, (float4x3) mWld1) * v.vBlendWeights.x +
        mul(v.vPosition, (float4x3) mWld2) * v.vBlendWeights.y +
        mul(v.vPosition, (float4x3) mWld3) * v.vBlendWeights.z +
        mul(v.vPosition, (float4x3) mWld4) * v.vBlendWeights.w;
    // Skin normal (to world space)
    float3 vNormal =
        mul(v.vNormal, (float3x3) mWld1) * v.vBlendWeights.x + 
        mul(v.vNormal, (float3x3) mWld2) * v.vBlendWeights.y + 
        mul(v.vNormal, (float3x3) mWld3) * v.vBlendWeights.z + 
        mul(v.vNormal, (float3x3) mWld4) * v.vBlendWeights.w;
    
    // Output stuff
    out.vPosition    = mul(float4(vPosition + vNormal * Len, 1), mTot);
    out.vDiffuse  = dot(vLight,vNormal);

    return out;
}

입력 구조는 셰이더 입력을 제공할 꼭짓점 버퍼의 데이터를 식별합니다. 이 셰이더는 꼭짓점 버퍼의 위치, 법선 및 blendweight 요소의 데이터를 꼭짓점 셰이더 레지스터에 매핑합니다. 입력 데이터 형식이 꼭짓점 선언 데이터 형식과 정확히 일치할 필요는 없습니다. 정확히 일치하지 않으면 꼭짓점 데이터가 셰이더 레지스터에 기록될 때 HLSL의 데이터 형식으로 자동으로 변환됩니다. 예를 들어 일반 데이터가 애플리케이션에서 UINT 형식으로 정의된 경우 셰이더에서 읽을 때는 float3으로 변환됩니다.

꼭짓점 스트림의 데이터에 해당 셰이더 데이터 형식보다 적은 구성 요소가 포함된 경우 누락된 구성 요소는 0으로 초기화됩니다(w를 제외하고 1로 초기화됨).

입력 시맨틱은 D3DDECLUSAGE의 값과 유사합니다.

출력 구조는 위치 및 색의 꼭짓점 셰이더 출력 매개 변수를 식별합니다. 이러한 출력은 (기본 처리 시) 삼각형 래스터화를 위해 파이프라인에서 사용됩니다. 위치 데이터로 표시된 출력은 동종 공간에서 꼭짓점의 위치를 나타냅니다. 최소한 꼭짓점 셰이더는 위치 데이터를 생성해야 합니다. 꼭짓점 셰이더가 완료된 후 (x, y, z) 좌표를 w로 나누어 화면 공간 위치를 계산합니다. 화면 공간에서 -1과 1은 뷰포트 경계의 최소 및 최대 x 및 y 값이고 z는 z 버퍼 테스트에 사용됩니다.

출력 시맨틱도 D3DDECLUSAGE의 값과 유사합니다. 일반적으로 픽셀 셰이더가 위치, 점 크기 또는 안개 시맨틱으로 표시된 변수에서 읽지 않는 경우 꼭짓점 셰이더의 출력 구조를 픽셀 셰이더의 입력 구조로 사용할 수도 있습니다. 이러한 시맨틱은 픽셀 셰이더에서 사용하지 않는 꼭짓점별 스칼라 값과 연결됩니다. 픽셀 셰이더에 이러한 값이 필요한 경우 픽셀 셰이더 시맨틱을 사용하는 다른 출력 변수로 복사할 수 있습니다.

전역 변수는 컴파일러에서 자동으로 레지스터에 할당됩니다. 변수의 내용이 셰이더가 호출될 때마다 처리되는 모든 픽셀에 대해 동일하기 때문에 전역 변수를 uniform 매개 변수라고도 합니다. 레지스터는 ID3DXConstantTable 인터페이스를 사용하여 읽을 수 있는 상수 테이블에 포함되어 있습니다.

픽셀 셰이더에 대한 입력 시맨틱은 꼭짓점 셰이더와 픽셀 셰이더 간의 전송을 위해 값을 특정 하드웨어 레지스터에 매핑합니다. 각 레지스터 형식에는 특정 속성이 있습니다. 현재 색 및 텍스처 좌표에 대한 시맨틱은 두 개뿐이므로 대부분의 데이터는 그렇지 않은 경우에도 텍스처 좌표로 표시되는 것이 일반적입니다.

꼭짓점 셰이더 출력 구조는 픽셀 셰이더에서 사용하지 않는 위치 데이터와 함께 입력을 사용했습니다. HLSL은 픽셀 셰이더에서 참조되지 않는 경우 픽셀 셰이더에 대한 유효한 입력 데이터가 아닌 꼭짓점 셰이더의 유효한 출력 데이터를 허용합니다.

입력 인수는 배열일 수도 있습니다. 시맨틱은 배열의 각 요소에 대해 컴파일러에 의해 자동으로 증가합니다. 예를 들어 다음 명시적 선언을 고려하세요.

struct VS_OUTPUT
{
    float4 Position   : POSITION;
    float3 Diffuse    : COLOR0;
    float3 Specular   : COLOR1;               
    float3 HalfVector : TEXCOORD3;
    float3 Fresnel    : TEXCOORD2;               
    float3 Reflection : TEXCOORD0;               
    float3 NoiseCoord : TEXCOORD1;               
};

float4 Sparkle(VS_OUTPUT In) : COLOR

위에 지정된 명시적 선언은 컴파일러에서 시맨틱을 자동으로 증가시키는 다음 선언과 동일합니다.

float4 Sparkle(float4 Position : POSITION,
                 float3 Col[2] : COLOR0,
                 float3 Tex[4] : TEXCOORD0) : COLOR0
{
   // shader statements
   ...

입력 시맨틱과 마찬가지로 출력 시맨틱은 픽셀 셰이더 출력 데이터에 대한 데이터 사용량을 식별합니다. 많은 픽셀 셰이더는 하나의 출력 색에만 씁니다. 픽셀 셰이더는 깊이 값을 하나 이상의 여러 렌더링 대상에 동시에 쓸 수도 있습니다(최대 4개). 꼭짓점 셰이더와 마찬가지로 픽셀 셰이더는 구조를 사용하여 둘 이상의 출력을 반환합니다. 이 셰이더는 색 구성 요소와 깊이 구성 요소에 0을 씁니다.

struct PS_OUTPUT
{
    float4 Color[4] : COLOR0;
    float  Depth  : DEPTH;
};

PS_OUTPUT main(void)
{
    PS_OUTPUT out;

   // Shader statements
   ...

  // Write up to four pixel shader output colors
  out.Color[0] =  ...
  out.Color[1] =  ...
  out.Color[2] =  ...
  out.Color[3] =  ...

  // Write pixel depth 
  out.Depth =  ...

    return out;
}

픽셀 셰이더 출력 색은 float4 형식이어야 합니다. 여러 색을 쓸 때는 모든 출력 색을 연속적으로 사용해야 합니다. 즉, COLOR0을 아직 쓰지 않은 경우 COLOR1은 출력이 될 수 없습니다. 픽셀 셰이더 깊이 출력은 float1 형식이어야 합니다.

샘플러 및 텍스처 개체

샘플러에는 샘플러 상태가 포함됩니다. 샘플러 상태는 샘플링할 텍스처를 지정하고 샘플링 중에 수행되는 필터링을 제어합니다. 텍스처를 샘플링하려면 다음 세 가지가 필요합니다.

  • 텍스처
  • 샘플러(샘플러 상태 포함)
  • 샘플링 명령

샘플러를 다음과 같이 텍스처 및 샘플러 상태로 초기화할 수 있습니다.

sampler s = sampler_state 
{ 
  texture = NULL; 
  mipfilter = LINEAR; 
};

다음은 2D 텍스처를 샘플링하는 코드의 예입니다.

texture tex0;
sampler2D s_2D;

float2 sample_2D(float2 tex : TEXCOORD0) : COLOR
{
  return tex2D(s_2D, tex);
}

텍스처는 텍스처 변수 tex0으로 선언됩니다.

이 예제에서는 s_2D라는 샘플러 변수가 선언됩니다. 샘플러에는 중괄호로 둘러싸인 샘플러 상태를 포함합니다. 여기에는 샘플링될 텍스처와 선택적으로 필터 상태(즉, 래핑 모드, 필터 모드 등)가 포함됩니다. 샘플러 상태를 생략하면 기본 샘플러 상태가 적용되어 텍스처 좌표에 대한 선형 필터링 및 래핑 모드를 지정합니다. 샘플러 함수는 2-구성 요소 부동 소수점 텍스처 좌표를 사용하고 2-성 요소 색을 반환합니다. float2 반환 형식으로 표현되며 빨간색 및 녹색 구성 요소의 데이터를 나타냅니다.

네 가지 유형의 샘플러가 정의되고(키워드 참조) 텍스처 조회는 내장 함수인 tex1D(s, t) (DirectX HLSL), tex2D(s, t) (DirectX HLSL), tex3D(s, t) (DirectX HLSL), texCUBE(s, t) (DirectX HLSL)에 의해 수행됩니다. 다음은 3D 샘플링의 예입니다.

texture tex0;
sampler3D s_3D;

float3 sample_3D(float3 tex : TEXCOORD0) : COLOR
{
  return tex3D(s_3D, tex);
}

이 샘플러 선언은 필터 설정 및 주소 모드에 기본 샘플러 상태를 사용합니다.

해당 큐브 샘플링 예제는 다음과 같습니다.

texture tex0;
samplerCUBE s_CUBE;

float3 sample_CUBE(float3 tex : TEXCOORD0) : COLOR
{
  return texCUBE(s_CUBE, tex);
}

마지막으로 1D 샘플링 예제는 다음과 같습니다.

texture tex0;
sampler1D s_1D;

float sample_1D(float tex : TEXCOORD0) : COLOR
{
  return tex1D(s_1D, tex);
}

런타임은 1D 텍스처를 지원하지 않으므로 컴파일러는 y 좌표가 중요하지 않다는 사실을 알면서 2D 텍스처를 사용합니다. tex1D(s, t)(DirectX HLSL)는 2D 텍스처 조회로 구현되므로 컴파일러는 효율적인 방식으로 y 구성 요소를 자유롭게 선택할 수 있습니다. 일부 드문 시나리오에서는 컴파일러가 효율적인 y 구성 요소를 선택할 수 없고, 이 경우 경고가 발생합니다.

texture tex0;
sampler s_1D_float;

float4 main(float texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float, texCoords);
}

이 특정 예제는 컴파일러가 입력 좌표를 다른 레지스터로 이동해야 하기 때문에 비효율적입니다(1D 조회가 2D 조회로 구현되고 텍스처 좌표가 float1로 선언되기 때문임). float1 대신 float2 입력을 사용하여 코드를 다시 쓰는 경우 컴파일러는 y가 무언가로 초기화되었다는 것을 알고 있기 때문에 입력 텍스처 좌표를 사용할 수 있습니다.

texture tex0;
sampler s_1D_float2;

float4 main(float2 texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float2, texCoords);
}

모든 텍스처 조회에는 "bias" 또는 "proj"(즉, tex2Dbias(DirectX HLSL), texCUBEproj(DirectX HLSL))가 추가될 수 있습니다. "proj" 접미사를 사용하면 텍스처 좌표가 w 구성 요소로 나뉩니다. “바이어스”를 사용하면 mip 수준이 w 구성 요소에 의해 이동됩니다. 따라서 접미사가 있는 모든 텍스처 조회는 항상 float4 입력을 사용합니다. tex1D(s, t)(DirectX HLSL)tex2D(s, t)(DirectX HLSL) 는 각각 yz 및 z 구성 요소를 무시합니다.

현재 샘플러의 동적 배열 액세스를 지원하는 백 엔드는 없지만 샘플러도 배열에서 사용할 수 있습니다. 따라서 컴파일 시 확인할 수 있으므로 다음이 유효합니다.

tex2D(s[0],tex)

그러나 이 예제는 유효하지 않습니다.

tex2D(s[a],tex)

샘플러의 동적 액세스는 리터럴 루프를 사용하여 프로그램을 작성하는 데 주로 유용합니다. 다음 코드는 샘플러 배열 액세스를 보여 줍니다.

sampler sm[4];

float4 main(float4 tex[4] : TEXCOORD) : COLOR
{
    float4 retColor = 1;

    for(int i = 0; i < 4;i++)
    {
        retColor *= tex2D(sm[i],tex[i]);
    }

    return retColor;
}

참고

Microsoft Direct3D 디버그 런타임을 사용하면 텍스처의 구성 요소 수와 샘플러 간의 불일치를 파악할 수 있습니다.

 

함수 작성

함수는 큰 작업을 더 작은 작업으로 분할합니다. 작은 작업은 디버그하기 쉽고 일단 입증되면 재사용할 수 있습니다. 함수를 사용하여 다른 함수의 세부 정보를 숨길 수 있으므로 함수로 구성된 프로그램을 더 쉽게 따를 수 있습니다.

HLSL 함수는 다음과 같은 점에서 C 함수와 유사합니다. 두 함수 모두 정의와 함수 본문을 포함하고 반환 형식과 인수 목록을 선언합니다. C 함수와 마찬가지로 HLSL 유효성 검사는 셰이더 컴파일 중에 인수, 인수 형식 및 반환 값에 대한 형식 검사를 수행합니다.

C 함수와 달리 HLSL 진입점 함수는 시맨틱을 사용하여 함수 인수를 셰이더 입출력에 바인딩합니다(내부적으로 호출된 HLSL 함수는 시맨틱을 무시함). 이렇게 하면 셰이더에 버퍼 데이터를 더 쉽게 바인딩하고 셰이더 출력을 셰이더 입력에 바인딩할 수 있습니다.

함수는 선언과 본문을 포함하고 선언은 본문 앞에 와야 합니다.

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

다음과 같이 함수 선언에는 중괄호 앞에 있는 모든 항목이 포함됩니다.

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

함수 선언에는 다음 항목이 포함됩니다.

  • 반환 유형
  • 함수 이름
  • 인수 목록(선택 사항)
  • 출력 시맨틱(선택 사항)
  • 주석(선택 사항)

반환 형식은 float4와 같은 HLSL 기본 데이터 형식일 수 있습니다.

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
   ...
}

반환 형식은 이미 정의된 구조체일 수 있습니다.

struct VS_OUTPUT
{
    float4  vPosition        : POSITION;
    float4  vDiffuse         : COLOR;
}; 

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

함수가 값을 반환하지 않으면 void를 반환 형식으로 사용할 수 있습니다.

void VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

반환 형식은 항상 함수 선언에서 가장 먼저 나타납니다.

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

인수 목록은 함수에 대한 입력 인수를 선언합니다. 반환될 값을 선언할 수도 있습니다. 일부 인수는 입력 인수와 출력 인수 둘 다입니다. 다음은 네 개의 입력 인수를 사용하는 셰이더의 예입니다.

float4 Light(float3 LightDir : TEXCOORD1, 
             uniform float4 LightColor,  
             float2 texcrd : TEXCOORD0, 
             uniform sampler samp) : COLOR 
{
    float3 Normal = tex2D(samp,texcrd);

    return dot((Normal*2 - 1), LightDir)*LightColor;
}

이 함수는 텍스처 샘플과 밝은 색의 혼합인 최종 색을 반환합니다. 함수는 4개의 인수를 사용합니다. 두 개의 입력에는 시맨틱이 있는데, LightDir에는 TEXCOORD1 시맨틱이 있고 texcrd에는 TEXCOORD0 시맨틱이 있습니다. 시맨틱은 이러한 변수에 대한 데이터를 꼭짓점 버퍼에서 가져온다는 것을 의미합니다. LightDir 변수에 TEXCOORD1 시맨틱이 있더라도 매개 변수는 텍스처 좌표가 아닐 수 있습니다. TEXCOORDn 시맨틱 형식은 미리 정의되지 않은 형식에 대한 시맨틱을 제공하는 데 자주 사용됩니다(조명 방향에 대한 꼭짓점 셰이더 입력 시맨틱은 없음).

LightColor와 samp의 다른 두 입력은 uniform 키워드로 레이블이 지정됩니다. 이러한 상수는 그리기 호출 간에 변경되지 않는 균일한 상수입니다. 이러한 매개 변수의 값은 셰이더 전역 변수에서 가져옵니다.

인수는 in 키워드를 사용하여 입력으로 레이블을 지정하고 out 키워드를 사용하여 출력 인수로 레이블을 지정할 수 있습니다. 인수는 참조로 전달할 수 없습니다. 그러나 인수는 inout 키워드로 선언된 경우 입출력 둘 다 일 수 있습니다. inout 키워드로 표시된 함수에 전달된 인수는 함수가 반환되고 다시 복사될 때까지 원본의 복사본으로 간주됩니다. inout을 사용하는 예제는 다음과 같습니다.

void Increment_ByVal(inout float A, inout float B) 
{ 
    A++; B++;
}

이 함수는 A 및 B의 값을 증가시키고 반환합니다.

함수 본문은 함수 선언 다음의 모든 코드입니다.

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

본문은 중괄호로 둘러싸인 문으로 구성됩니다. 함수 본문은 변수, 리터럴, 식 및 문을 사용하여 모든 기능을 구현합니다.

셰이더 본문은 행렬 곱셈을 수행하고 float4 결과를 반환하는 두 가지 작업을 수행합니다. 행렬 곱셈은 4x4 행렬 곱셈을 수행하는 mul(DirectX HLSL) 함수를 사용하여 수행됩니다. mul(DirectX HLSL)은 함수의 HLSL 라이브러리에 이미 기본 제공되어 있으므로 내장 함수라고 합니다. 내장 함수는 다음 섹션에서 자세히 설명합니다.

행렬 곱셈은 입력 벡터 Pos와 복합 행렬 WorldViewProj를 결합합니다. 결과는 화면 공간으로 변환된 위치 데이터입니다. 이는 수행할 수 있는 최소 꼭짓점 셰이더 처리입니다. 꼭짓점 셰이더 대신 고정 함수 파이프라인을 사용하는 경우 이 변환을 수행한 후 꼭짓점 데이터를 그릴 수 있습니다.

함수 본문의 마지막 문은 return 문입니다. C와 마찬가지로 이 문은 함수에서 함수를 호출한 문으로 컨트롤을 반환합니다.

함수 반환 형식은 bool, int half, float 및 double을 포함하여 HLSL에 정의된 간단한 데이터 형식일 수 있습니다. 반환 형식은 벡터 및 행렬과 같은 복잡한 데이터 형식 중 하나일 수 있습니다. 개체를 참조하는 HLSL 형식은 반환 형식으로 사용할 수 없습니다. 여기에는 pixelshader, vertexshader, 텍스처 및 샘플러가 포함됩니다.

다음은 반환 형식에 구조체를 사용하는 함수의 예입니다.

float4x4 WorldViewProj : WORLDVIEWPROJ;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VS_HLL_Example(float4 inPos : POSITION )
{
    VS_OUTPUT Out;

    Out.Pos = mul(inPos,  WorldViewProj );

    return Out;
};

float4 반환 형식은 이제 단일 float4 멤버를 포함하는 구조체 VS_OUTPUT으로 대체되었습니다.

return 문은 함수의 끝을 알릴 수 있습니다. 다음은 가장 간단한 반환 문입니다. 함수에서 호출 프로그램으로 컨트롤을 반환합니다. 값은 반환하지 않습니다.

void main()
{
    return ;
}

return 문은 하나 이상의 값을 반환할 수 있습니다. 다음 예제에서는 리터럴 값을 반환합니다.

float main( float input : COLOR0) : COLOR0
{
    return 0;
}

다음은 식의 스칼라 결과를 반환하는 예제입니다.

return  light.enabled;

다음 예제에서는 지역 변수 및 리터럴에서 생성된 float4를 반환합니다.

return  float4(color.rgb, 1) ;

다음 예제는 내장 함수와 몇 개 리터럴 값에서 반환된 결과에서 생성된 float4를 반환합니다.

float4 func(float2 a: POSITION): COLOR
{
    return float4(sin(length(a) * 100.0) * 0.5 + 0.5, sin(a.y * 50.0), 0, 1);
}

다음 예제에서는 하나 이상의 멤버가 포함된 구조를 반환합니다.

float4x4 WorldViewProj;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
    VS_OUTPUT out;
    out.Pos = mul(inPos, WorldViewProj );
    return out;
};

흐름 제어

대부분의 현재 꼭짓점 및 픽셀 셰이더 하드웨어는 셰이더 줄을 한 줄씩 실행하여 각 명령을 한 번 실행하도록 설계되었습니다. HLSL은 정적 분기, 조건자 명령, 정적 루핑, 동적 분기 및 동적 루핑을 포함하는 흐름 제어를 지원합니다.

이전에는 if 문을 사용하면 코드 흐름의 if 쪽과 else 쪽을 모두 구현하는 어셈블리 언어 셰이더 코드가 생성되었습니다. 다음은 vs_1_1 위해 컴파일된 HLSL 코드의 예입니다.

if (Value > 0)
    oPos = Value1; 
else
    oPos = Value2; 

결과 어셈블리 코드는 다음과 같습니다.

// Calculate linear interpolation value in r0.w
mov r1.w, c2.x               
slt r0.w, c3.x, r1.w         
// Linear interpolation between value1 and value2
mov r7, -c1                      
add r2, r7, c0                   
mad oPos, r0.w, r2, c1  

일부 하드웨어는 정적 또는 동적 루핑을 허용하지만 대부분 선형 실행이 필요합니다. 루핑을 지원하지 않는 모델에서는 모든 루프를 언롤해야 합니다. 예를 들어 ps_1_1 셰이더에도 등록되지 않은 루프를 사용하는 DepthOfField Sample 샘플이 있습니다.

이제 HLSL에는 다음과 같은 각 유형의 흐름 제어에 대한 지원이 포함됩니다.

  • 정적 분기
  • 조건자 명령
  • 정적 루핑
  • 동적 분기
  • 동적 루핑

정적 분기를 사용하면 부울 셰이더 상수에 따라 셰이더 코드 블록을 켜거나 끌 수 있습니다. 현재 렌더링 중인 개체의 형식에 따라 코드 경로를 사용하거나 사용하지 않도록 설정하는 편리한 방법입니다. 그리기 호출 사이에 현재 셰이더에서 지원하려는 기능을 결정한 다음 해당 동작을 가져오는 데 필요한 부울 플래그를 설정할 수 있습니다. 셰이더를 실행하는 동안 부울 상수에서 사용하지 않도록 설정된 모든 문을 건너뜁니다.

가장 친숙한 분기 지원은 동적 분기입니다. 동적 분기를 사용하면 비교 조건이 변수에 상주합니다. 즉, 런타임에 각 꼭짓점 또는 각 픽셀에 대해 비교가 수행됩니다(컴파일 시 또는 두 그리기 호출 간에 발생하는 비교와는 반대). 성능 저하는 분기의 비용과 수행된 분기 쪽의 명령 비용입니다. 동적 분기는 셰이더 모델 3 이상에서 구현됩니다. 이러한 모델에서 작동하는 셰이더 최적화는 CPU에서 실행되는 코드를 최적화하는 것과 비슷합니다.

HLSL에 대한 프로그래밍 지침