# 自定义Shader
在小游戏性能解决方案中,自定义Shader采用HLSL进行编写,提供的工具链会将HLSL跨平台编译成GLSL/MSL/SPIR-V,以便在不同的环境上运行
# 编写第一个自定义Shader
我们先来看一组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;
}
# 使用#include
示例中第一行
#include <common.inc>
表示引入内建的公共hlsl片段,该片段中定义了大量辅助公共宏用于辅助自定义shader的编写
内建变量
- float3 WorldSpaceViewPosition 世界空间的相机位置
- float3 WorldSpaceLightDir 实时平行光源在世界空间的方向
- float3 LightColor 实时平行光源颜色
- float3 AmbientLight 环境光颜色
- float GameTime 游戏时间,单位为s
内建结构体
// 3D的顶点输入数据原始结构,开发者不用关心内容
struct FEffect3DVertexInput
// 3D顶点处理输出数据结构
struct FVertexProcessOutput {
float4 Position; // 顶点模型空间坐标(某些特效情况下不保证该属性正确)
float4 WorldPosition; // 顶点世界空间坐标
float2 TexCoord; // 顶点贴图坐标
float4 Color; // 顶点颜色
float3 Normal; // 顶点法向量
float4 Tangent; // 顶点切向量
float2 LightMapCoord; // 光照贴图坐标
}
内建函数
- float4 ObjectToWorldPosition(float4 point):将local space position转换到world Space
- float4 WorldToClipPosition(float4 point):将world space position 转换到 clip space
- float2 TRANSFER_TEXCOORD(float2 TexCoord, float4 ScaleOffset) : 将UV坐标和scaleOffset进行计算,输出最终的UV坐标
- DECLARE_TEXTURE(name): 声明贴图
- SAMPLE_TEXTURE(name, float2 TexCoord): 贴图采样
- Effect3DVertexProcess(in FEffect3DVertexInput In, out FVertexProcessOutput Out) : 引擎内建顶点处理,进行Particle/Skin/Line/Trail等顶点处理
也可以使用如下方式引入自定义的公共hlsl片段
// relative path
#include "./yourCommonInc.hlsl"
# 自定义Shader进阶
我们先来看一组示例Shader
// blinnPhong3D.vertex.hlsl
#include <common.inc>
// varying define
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)
FOG_COORDS(5)
SHADOW_COORDS(6)
};
cbuffer material
{
float4 _Color;
float4 _MainTex_ST;
float _Shininess;
}
void Main(in FEffect3DVertexInput In, out FVertexOutput Out)
{
FVertexProcessOutput VPOut;
Effect3DVertexProcess(In, VPOut);
Out.TexCoord = TRANSFER_TEXCOORD(VPOut.TexCoord, _MainTex_ST);
// position
Out.Position = WorldToClipPosition(VPOut.WorldPosition);
float3 worldSpaceViewDir = normalize(WorldSpaceViewPosition - VPOut.WorldPosition.xyz);
float3 worldSpaceLightDir = WorldSpaceLightDir;
Out.WorldNormal = ObjectToWorldNormal(VPOut.Normal);
Out.LightDir = worldSpaceLightDir;
Out.ViewDir = worldSpaceViewDir;
TRANSFER_LIGHTMAP(In, Out);
TRANSFER_SHADOW(Out, VPOut.WorldPosition.xyz);
TRANSFER_FOG(Out, VPOut.WorldPosition.xyz);
}
// blonnPhong3D.pixel.hlsl
#include <common.inc>
struct FVertexOutput
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0;
float3 LightDir : TEXCOORD1;
float3 ViewDir : TEXCOORD2;
float3 WorldNormal : TEXCOORD3;
LIGHTMAP_COORDS(4)
FOG_COORDS(5)
SHADOW_COORDS(6)
};
cbuffer material
{
float4 _Color;
float4 _MainTex_ST;
float _Shininess;
}
DECLARE_TEXTURE(_MainTex);
// define BlinnPhong lighting function
void BlinnPhongLighting(in half3 lightColor, in half3 lightDir, in half3 normal, in half3 viewDir, in float specularFactor,
out float3 diffuseOut, out float3 specularOut)
{
half3 h = normalize(viewDir + lightDir);
half ln = max(0.0, dot(+lightDir, normal));
float nh = max(0.0, dot(h, normal));
diffuseOut = lightColor * ln;
specularOut = lightColor * pow(nh, specularFactor * 128.0);
}
float4 Main(in FVertexOutput In) : SV_Target0
{
fixed4 mainTexColor = SAMPLE_TEXTURE(_MainTex, In.TexCoord);
fixed4 albedo = mainTexColor * _Color;
// Blinnphong Lighting
fixed3 diffuse = fixed3(0.0, 0.0, 0.0);
fixed3 specular = fixed3(0.0, 0.0, 0.0);
half3 lightDir = normalize(In.LightDir);
half3 viewDir = normalize(In.ViewDir);
half3 worldspaceNormal = normalize(In.WorldNormal);
BlinnPhongLighting(LightColor, lightDir, worldspaceNormal, viewDir, _Shininess, diffuse, specular);
// shadow attenuation
float attenuation = SHADOW_ATTENUATION(In);
fixed3 finalDiffuse = fixed3(0.0,0.0,0.0);
// use lightMap or not
#if defined(USE_LIGHTMAP)
// sample lightMapColor
fixed3 lightMapColor = SAMPLE_LIGHTMAP(In);
// use lightMap, mix shadow with lightMap
finalDiffuse = MixLightmapWithRealtimeAttenuation(lightMapColor, attenuation, worldspaceNormal);
#else
// no lightMap, mix shadow with ambientLight
finalDiffuse = fixed3(AmbientLight) + diffuse * attenuation;
#endif
// get outColor
fixed4 outColor = fixed4(albedo.rgb * (finalDiffuse + specular), albedo.a);
// apply fog to outColor
APPLY_FOG(In, outColor);
return outColor;
}
# 自定义Shader中使用蒙皮骨骼
需要在effect文件中需要蒙皮的pass中添加compileFlags项,在内建的Effect3DVertexProcess中将处理蒙皮
"compileFlags": [
"Skin"
]
# 自定义Shader中使用雾效
首先需要在effect文件中需要使用雾效的pass中添加compileFlags项
"compileFlags": [
"Fog"
]
在vertexShader的output和pixelShader的input中添加shadow texCoord声明
FOG_COORDS(<TEXCOORD INDEX>)
在vertexShader中添加如下代码计算雾效系数
// FVertexProcessOutput VPOut
TRANSFER_FOG(Out, VPOut.WorldPosition.xyz);
在pixelShader的最后添加如下代码将最终输出颜色进行雾效混合
APPLY_FOG(In, outColor);
# 自定义Shader中使用阴影
首先需要在effect文件中需要使用阴影的pass中添加compileFlags项
"compileFlags": [
"Shadow"
]
在vertexShader的output和pixelShader的input中添加shadow texCoord声明
SHADOW_COORDS(n)
在vertexShader中添加如下代码计算shadow texCoord
// FVertexProcessOutput VPOut
TRANSFER_SHADOW(Out, VPOut.WorldPosition.xyz);
在pixelShader中添加如下代码计算shadow attenuation
float attenuation = SHADOW_ATTENUATION(In);
# 自定义Shader中使用lightMap
首先需要在effect文件中添加compileFlags项
"compileFlags": [
"LightMap"
]
在vertexShader的output和pixelShader的input中添加lightmap texCoord声明
LIGHTMAP_COORDS(n)
在vertexShader中添加如下代码计算lightmap texCoord
TRANSFER_LIGHTMAP(In, Out);
在pixelShader中添加如下代码采样lightMapColor,并与实时阴影进行混合
// need to get shadowAttenuation like this
// float attenuation = SHADOW_ATTENUATION(In);
#if defined(USE_LIGHTMAP)
fixed3 lightMapColor = SAMPLE_LIGHTMAP(In);
fixed3 finalDiffuse = MixLightmapWithRealtimeAttenuation(lightMapColor, attenuation, worldspaceNormal);
#else
fixed3 finalDiffuse = fixed3(AmbientLight) + diffuse * attenuation;
#endif