셰이더 API (surface.hlsl)
PackShade 함수 인터페이스, PackSurface 및 PackResult 구조체, 내장 헬퍼 함수와 환경 변수를 정리합니다.
셰이더 진입점 규격
모든 셰이더 팩의 surface.hlsl은 단 하나의 진입 함수를 구현해야 합니다:
PackResult PackShade(PackSurface s);
이 함수는 캐릭터 모델의 모든 재질 픽셀에 대해 실행되며, MMDX12의 기본 음영 계산 대신 호출됩니다.
컴파일 환경 및 주의점
- 컴파일러: DirectX Shader Compiler(DXC)를 사용하며 셰이더 모델 6.0(레이 트레이싱 경로는 6.5)으로 빌드됩니다.
- HLSL 표준:
-HV 2018(HLSL 2018 문법 규칙)을 따릅니다. - 헤더 포함 (
#include):surface.hlsl내부의#include는 해당 파일이 위치한 팩 폴더를 기준으로 상대 경로로 해석됩니다. - 예약어 주의: DXC에서
line은 예약어이므로 변수명이나 함수명으로 사용하지 마세요. - 셰이더 캐시: 팩이 적용된 캐릭터가 화면에 처음 그려질 때 컴파일되며, 결과는
<MMDX12>/shader_cache에 보관됩니다. 팩 파일을 수정하면 캐시가 자동으로 무효화되고 재컴파일됩니다.
입력 구조체: PackSurface
엔진이 픽셀 셰이더에서 계산하여 넘겨주는 표면 정보입니다:
| 필드 | 타입 | 설명 |
|---|---|---|
worldPos | float3 | 월드 공간 위치 (MMD 단위, +Y축 위쪽) |
N | float3 | 월드 노멀 벡터 (뒷면 렌더링 시 시선 방향으로 뒤집힘) |
V | float3 | 표면에서 카메라(시선)를 향하는 단위 벡터 |
L | float3 | 주 광원(태양광)을 향하는 단위 벡터 |
uv | float2 | 텍스처 좌표 |
pixel | float2 | 화면 픽셀 좌표 (SV_Position.xy) |
viewZ | float | 뷰 공간 깊이값 (카메라로부터의 거리) |
tex | float4 | 베이스 텍스처 색상 (감마 공간, 재질 모르프 반영, 텍스처 없으면 흰색) |
alpha | float | 재질 알파 x 텍스처 알파 (0.004 미만은 이미 엔진이 버림) |
shadow | float | 태양광 그림자 (0: 그림자 속 .. 1: 빛을 받음). 그림자를 받지 않는 재질은 항상 1 |
materialClass | uint | PACK_BODY(0), PACK_SKIN(1), PACK_FACE(2), PACK_EYE(3), PACK_HAIR(4), PACK_WEAPON(5) |
머리 본 좌표계 (Head Bone Frame)
캐릭터 얼굴 셰이딩 시 코나 뺨의 노멀 굴곡 때문에 얼굴이 찌그러지는 현상을 방지하기 위해 머리 본의 회전 틀을 제공합니다:
| 필드 | 타입 | 설명 |
|---|---|---|
hasHead | bool | 머리 본(頭 또는 首)이 모델에 존재하는지 여부 (없으면 기본 월드 축) |
headPos | float3 | 머리 본의 월드 공간 중심 좌표 |
headRight | float3 | 모델 머리의 오른쪽 방향 벡터 (+X, 단위 길이) |
headUp | float3 | 모델 머리의 위쪽 방향 벡터 (+Y, 단위 길이) |
headForward | float3 | 모델 머리가 바라보는 정면 방향 벡터 (MMD 모델은 -Z를 향함) |
headScale | float | 월드 단위 대비 모델 표시 비율 (크기 스케일) |
출력 구조체: PackResult
셰이더 팩이 픽셀의 최종 색상과 속성을 엔진에 반환하는 구조체입니다:
struct PackResult {
float3 color; // 선형(Linear) HDR 광도 (엔진 규칙에 따라 gSunIntensity를 곱해야 함)
float alpha; // 최종 알파 투명도
float reflectivity; // 0.0 .. 1.0 (화면 공간 반사 SSR 및 레이 트레이싱 반사 세기)
bool noAo; // true 설정 시 앰비언트 오클루전(SSAO/RTAO)과 반사가 꺼짐 (얼굴용)
};
내장 헬퍼 함수 (pack_api.hlsli)
float PackParam(uint i): 매니페스트에 등록된i번째 (0~15) 파라미터의 현재 슬라이더 값을 반환합니다.- 재질 속성 읽기 (감마 공간, 재질 모르프 적용됨):
float4 PackDiffuse(): 디퓨즈 색상 및 알파float3 PackAmbient(): 앰비언트 환경광 계수float3 PackSpecular(): 스펙큘러 하이라이트 색상float PackSpecularPower(): 하이라이트 지수 (광택도)float PackEdgeReflectivity(): 외곽 반사도
- MMD 기본 라이팅 연산:
float3 PackMmdLit(PackSurface s): 원작 MMD의 라이팅 모델saturate(ambient + diffuse * lightColor) * tex.rgb를 계산합니다.
- 스피어 맵 (Sphere Map):
uint PackSphereMode(): 모델의 스피어 맵 합성 모드 (0: 없음, 1: 곱하기 Multiply, 2: 더하기 Add).float3 PackSampleSphere(float3 N): 노멀 벡터를 기준으로 스피어 텍스처를 샘플링합니다.
- 툰 텍스처 (Toon Map):
bool PackHasToon(): 해당 재질에 MMD 툰 텍스처가 지정되어 있는지 여부.float3 PackSampleToon(float v): 툰 텍스처를 수직 축v로 샘플링합니다 (v=0밝은 쪽,v=1어두운 그림자 쪽). 툰이 없으면 흰색을 반환합니다.
엔진 환경 변수
pack_api.hlsli 상단에서 다음과 같은 씬 조명 변수들을 활용할 수 있습니다:
gLightColor(float3): 태양광 주 광원 색상gSunIntensity(float): 광원 세기 계수 (최종 컬러에 반드시 곱해 주어야 주간/야간 조절이 작동합니다)gRimColor(float3),gRimStrength(float): 전역 림 라이트 설정float3 Hemisphere(float3 n): 상공(gSkyZenith)과 지면(gGroundColor) 사이의 반구형 환경광float3 SrgbToLinear(float3 c): sRGB 색상을 선형 HDR 공간으로 변환
팩 텍스처 (API 2)
pack.json에 선언한 텍스처("textures", 매니페스트 참고)는 배열 순서대로 인덱스로 샘플링합니다.
float4 PackSampleTex(uint i, float2 uv):i번 텍스처를 선언된 주소 모드(wrap/clamp)로 샘플링합니다.float4 PackSampleTexLevel(uint i, float2 uv, float lod): 밉 레벨을 직접 지정합니다 (램프·데이터 맵은lod = 0). 픽셀 단계 밖, 예를 들어PackEdge에서도 쓸 수 있습니다.uint2 PackTexSize(uint i): 레벨 0 크기(픽셀).i가 범위를 벗어나면0, 0.uint PackTexCount(): 선언된 텍스처 수.
"srgb": true(기본값) 텍스처는 선형 값을 돌려주므로 SrgbToLinear 없이 그대로 쓰세요. s.tex와 PackMmdLit은 기존처럼
감마 공간입니다. "srgb": false 텍스처는 저장된 값을 그대로 돌려줍니다. 없는 텍스처와 범위를 벗어난 인덱스는 흰색을 돌려주므로,
사용자가 텍스처 폴더를 지정하지 않아도 팩은 렌더링됩니다.
float3 ramp = PackSampleTexLevel(0, float2(saturate(dot(s.N, s.L) * 0.5 + 0.5), 0.5), 0).rgb; // clamp 램프, 선형
float4 lm = PackSampleTex(1, s.uv); // "srgb": false 라이트맵: 채널 값 그대로
윤곽선: PackEdge (선택)
윤곽선은 PackShade와 별개로 엔진의 엣지 패스가 그립니다. surface.hlsl 맨 위에 PACK_HAS_EDGE를 정의하고 PackEdge를 구현하면
재질별로 윤곽선 색과 굵기를 정할 수 있습니다.
#define PACK_HAS_EDGE 1
PackEdgeResult PackEdge(uint materialClass, float4 mmdEdgeColor, float mmdEdgeSize) {
PackEdgeResult e;
e.color = materialClass == PACK_HAIR ? float4(0.25, 0.12, 0.10, 1.0) : mmdEdgeColor; // 감마 공간 RGBA
e.widthScale = materialClass == PACK_FACE ? 0.5 : 1.0; // MMD 굵기에 곱함, 0이면 윤곽선 없음
return e;
}
mmdEdgeColor / mmdEdgeSize는 PMX 재질의 엣지 값입니다. PACK_HAS_EDGE가 없으면 윤곽선은 엔진 기본값 그대로입니다.
실제 #define PACK_HAS_EDGE 줄만 인식하며, 주석에 적힌 이름은 무시합니다.