# 自定义Shader
在小游戏框架里,自定义Shader采用类HLSL语法进行编写,提供的工具链会将HLSL跨平台编译成GLSL/MSL/SPIR-V,以便在不同的环境上运行
# 着色器输入
# 使用头文件
Shader中可以以下两种方式引入头文件
#include<common.inc>
#include "something.hlsl"
通过<>引入的是小游戏框架内建的头文件,开发者自定义的头文件可以通过""来引入,使用文件相对路径进行索引。
# 定义和使用纹理
小游戏框架里使用着色器可以通过宏来对纹理进行声明和采样
// 普通纹理的声明和采样
DECLARE_TEXTURE(_MainTex);
float4 color = SAMPLE_TEXTURE(name, TexCoord);
// Cube Map的声明和采样
DECLARE_CUBEMAP(SpeCube);
SAMPLE_CUBEMAP(SpeCube, reflectVec);
通过材质设置纹理时,需要在.effect里声明使用的贴图,具体可参考效果和材质
# 定义Shader Variables
目前小游戏框架支持玩家在一个Constant Buffer中定义Shader变量供材质使用,可以命名为cbuffer Material。可参考如下方式定义
cbuffer Material
{
float4 _MainTex_ST;
float4 _Color;
}
由于效率和兼容性的问题,顶点着色器和像素着色器中使用的cbuffer定义必须保证一致,未使用的uniform会在优化流程中被剔除。
小游戏框架内建Shader Variables包含以下内容
WorldSpaceViewPosition: float3 相机位置
WorldSpaceLightDir: float3 平行光方向
LightColor: float3 光照颜色
AmbientLight: float3 环境光颜色,用于多光源
GameTime: float 当前游戏时间
ShadowStrength: float 阴影强度
ShadowColor: float3 阴影颜色
EnvironmentMap: Cubemap 环境贴图
# 定义顶点输入和输出
在小游戏框架里,顶点输入是确定的,FEffect3DVertexInput是内建的顶点结构,通过common.inc文件里引用到shader中,开发者需要在顶点着色器里定义顶点着色器的输出。
#include <common.inc>
struct FVertexOutput
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0; // world space light direction
float3 LightDir : TEXCOORD1; // world space view direction
float3 ViewDir : TEXCOORD2; // world space normal direction
float3 WorldNormal:TEXCOORD3;
LIGHTMAP_COORDS(4) //builtin lightmap coords
FOG_COORDS(5) // builtin fog cooord
SHADOW_COORDS(6) // builtin shadow coord
};
顶点输出内容在GLES2.0会作为varing传递给像素着色器使用
# 编写一组着色器
结合上面内容,我们来看一组简单的Shader实现
// simple3D.vertex.hlsl
// 公共头文件,包含内建hlsl函数、宏和结构定义
#include <common.inc>
// 材质定义,需与.effect文件中的配置一致
cbuffer material
{
float4 _MainTex_ST;
float4 _Color;
}
// vertex output, 从vertex传递到fragment的数据
struct FVertexOutput
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0;
};
// vertex入口函数
void Main(in FEffect3DVertexInput In, out FVertexOutput Out)
{
// 内建顶点处理,进行Particle/Skin/Line/Trail等顶点处理
FVertexProcessOutput VPOut;
Effect3DVertexProcess(In, VPOut);
Out.Position = WorldToClipPosition(VPOut.WorldPosition);
Out.TexCoord = TRANSFER_TEXCOORD(VPOut.TexCoord, _MainTex_ST);
}
// simple3D.pixel.hlsl
#include <common.inc>
cbuffer material
{
float4 _MainTex_ST;
float4 _Color;
}
struct FVertexOutput
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0;
};
// texture声明,需与.effect文件中声明的一致
DECLARE_TEXTURE(_MainTex);
float4 Main(in FVertexOutput In) : SV_Target0
{
fixed4 texColor = SAMPLE_TEXTURE(_MainTex, In.TexCoord);
return texColor * _Color;
}
# 内建函数
- 通用内建函数
// Transform
// 将local space position转换到world Space
float3 ObjectToWorldNormal(float3 normal);
// 将world space position 转换到 clip space
float4 ObjectToWorldPosition(float4 point);
// 将UV坐标和scaleOffset进行计算,输出最终的UV坐标
float2 TRANSFER_TEXCOORD(float2 TexCoord, float4 ScaleOffset);
- 顶点着色器中使用的内建函数
//内建顶点处理,进行Particle/Skin/Line/Trail等顶点处理
void Effect3DVertexProcess(in FEffect3DVertexInput In, out FVertexProcessOutput Out);
// 顶点着色器中阴影参数计算
void TRANSFER_SHADOW(FVertexProcessOutput Out, float3 WorldPos);
// 顶点着色器中雾效参数计算
void TRANSFER_FOG(FVertexProcessOutput Out, float3 WorldPos);
// 顶点着色器中Light Map参数计算
void TRANSFER_LIGHTMAP(in FEffect3DVertexInput In, out FVertexProcessOutput Out);
// 开启Instancing的时候,可以在顶点着色器里通过以下内建函数进行transform
float3 ObjectToWorldDirInstancing(in FEffect3DVertexInput In, float3 dir);
float3 ObjectToWorldNormalInstancing(in FEffect3DVertexInput In, float3 normal);
float4 ObjectToWorldPositionInstancing(in FEffect3DVertexInput In, float4 point1);
- 像素着色器中使用的内建函数
// 计算阴影
float SHADOW_ATTENUATION(FVertexOutput In);
// 光照贴图采样
float3 SAMPLE_LIGHTMAP(FVertexOutput In);
// 计算雾效值
APPLY_FOG(FVertexOutput In, out float4 Color);
# 语法规则
- 矩阵存储和访问顺序 小游戏框架的runtime里,矩阵默认采用的是列矩阵进行存储,同样的,在Shader里,矩阵默认使用的是列矩阵,对矩阵的访问采用的是和runtime一致的列主序访问,即matrix[col][row]的方式。乘法规则采用的是和CG/GLSL一致的规则,即mul(Matrix, Vector),比如进行MVP矩阵变换,可以采用如下方式
mul(u_projection, mul(u_view, mul(u_world, position)));
- 合法性校验 开发者编写的Shader工具会进行HLSL的语法校验,对于非法语法会在工具中提示错误的shader以及行号。如果开发者在顶点着色器和像素着色器定义的公共结构,如FVertexOutput和cbuffer不一致,则可能引起Link error。
# 性能
小游戏框架提供的Shader编译工具会对开发者编写的Shader做一定程度的编译优化,包括但不限于
- 函数内联
- 循环展开