着色器 API (surface.hlsl)

PackShade 函数契约规范、PackSurface 与 PackResult 结构体、内置工具函数以及引擎环境常量详解。

着色器入口契约

每个着色器包的 surface.hlsl 必须且仅需实现一个主着色函数:

PackResult PackShade(PackSurface s);

该函数在场景渲染阶段对角色模型的每个材质像素逐一调用,以替换 MMD 默认的光照着色。

编译环境与注意事项

  • 编译器:采用 DirectX Shader Compiler(DXC),目标着色器模型为 Shader Model 6.0(光线追踪管线下为 6.5)。
  • HLSL 规范:遵循 -HV 2018(HLSL 2018 语法规则)。
  • 头文件包含 (#include):surface.hlsl 内部引用的相对路径均以当前包文件夹为根进行解析。
  • 关键字冲突:请注意 DXC 将 line 视为保留关键字,切勿将其用作变量名或函数名。
  • 着色器缓存:首次绘制使用该包的角色时触发编译(会有轻微编译延迟),产物保存在 <MMDX12>/shader_cache 中。修改包内任意文件均会自动使缓存失效并重新编译。

输入结构体:PackSurface

由渲染管线计算后传递给像素着色器的表面数据:

字段类型说明
worldPosfloat3表面世界坐标(MMD 单位,+Y 向上)
Nfloat3表面世界法线(背面绘制时已自动翻转朝向摄像机)
Vfloat3表面指向摄像机视点的单位向量
Lfloat3表面指向太阳主光源的单位向量
uvfloat2材质纹理 UV 坐标
pixelfloat2屏幕像素坐标(SV_Position.xy)
viewZfloat观察空间线性深度(距相机距离)
texfloat4基础漫反射纹理采样色(伽马空间,已结算材质变形,无贴图时为白色)
alphafloat材质透明度 × 纹理透明度(低于 0.004 的像素已被引擎直接剔除)
shadowfloat太阳投射阴影(0 代表全影,1 代表受光;不接收阴影的材质恒为 1)
materialClassuint分类材质类别:PACK_BODY(0), PACK_SKIN(1), PACK_FACE(2), PACK_EYE(3), PACK_HAIR(4), PACK_WEAPON(5)

头部骨骼坐标系(Head Bone Frame)

针对二次元模型脸部若依随表面法线会产生凹凸鼻沟等结构瑕疵的问题,引擎提供了头部骨骼的姿态框架:

字段类型说明
hasHeadbool模型是否包含头部骨骼(优先查找 頭,其次 首);若无则回退至世界坐标轴
headPosfloat3头部骨骼所在的世界空间中心点
headRightfloat3头部右侧方向单位向量(+X)
headUpfloat3头部上方方向单位向量(+Y)
headForwardfloat3脸部正视朝向单位向量(MMD 角色默认面向 -Z)
headScalefloat模型显示缩放比(每模型单位对应的世界单位)

输出结构体:PackResult

PackShade 返回给引擎管线的像素着色结果:

struct PackResult {
    float3 color;         // 线性 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():漫反射颜色与 Alpha
    • 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):基于观察空间法线采样球形贴图。
  • MMD 色带渐变(Toon Map):
    • bool PackHasToon():材质是否绑定了色带贴图。
    • float3 PackSampleToon(float v):纵向采样色带(v=0 为受光端,v=1 为阴影端)。

引擎全局常量

可见的引擎公共光照变量:

  • 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):按声明的寻址方式(wrap / clamp)采样第 i 张贴图。
  • float4 PackSampleTexLevel(uint i, float2 uv, float lod):显式指定 mip 级别(Ramp 和数据贴图用 lod = 0)。也可在像素阶段之外使用,例如 PackEdge 中。
  • uint2 PackTexSize(uint i):第 0 级尺寸(像素)。i 越界时返回 0, 0。
  • uint PackTexCount():声明的贴图数量。

"srgb": true(默认)的贴图返回线性值,请直接使用,不要再调用 SrgbToLinear。s.tex 和 PackMmdLit 仍与以前一样处于 Gamma 空间。 "srgb": false 的贴图返回原始存储值。缺失的贴图和越界索引返回白色,因此即使用户尚未设置贴图目录,包也能正常渲染。

float3 ramp = PackSampleTexLevel(0, float2(saturate(dot(s.N, s.L) * 0.5 + 0.5), 0.5), 0).rgb;  // clamp Ramp,线性值
float4 lm   = PackSampleTex(1, s.uv);   // "srgb": false 的 LightMap:原始通道值

描边:PackEdge(可选)

描边由引擎的描边 Pass 绘制,与 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;   // Gamma 空间 RGBA
    e.widthScale = materialClass == PACK_FACE ? 0.5 : 1.0;   // 乘以 MMD 描边宽度;0 = 无描边
    return e;
}

mmdEdgeColor / mmdEdgeSize 为 PMX 材质的描边参数。未定义 PACK_HAS_EDGE 时,描边与引擎默认完全一致。 只有真正的 #define PACK_HAS_EDGE 行才会生效,注释中提到该名称不会触发。