Uber Shader

Uber Shader

Uber Shader也叫做全能着色器,这算是现代图形引擎着色器的标配技术了。下边来介绍一下如何使用,简单来说就是一句话:在Shader中使用宏定义来区分执行分支。

至于为什么要是用Uber Shader,也是简单一句话:提高着色器复用率,降低分支带来的性能开销。 至于分支带来的性能消耗问题,可参照GPU架构与管线总结

如果你在做跨平台的渲染引擎中使用了不同的着色器语言或着色器代码,您可能会为每种类型的渲染管线创建大量着色器文件和单独的头文件/功能文件类。那么问题来了:如何更高效的维护着色器代码并做到易拓展、高复用的目标?答案就是的Uber Shader。

Demo

Uber Shader的概念和实现实际上非常简单。让我们以一个可拓展的纹理效果着色器(HLSL)为例:

float4 SimpleLitPixelShader(VS_to_PS input) : SV_TARGET
{					  
    float4 textureColor = DiffuseTexture.Sample(DiffuseSampler, input.texCoord0);

    float nDotL = saturate(dot(input.normal, lightDirection));
	
    float4 output = textureColor * diffuseColor * nDotL;
	
    return output;
}

上边是一个十分标准的光照着色器。现在假设我们想要一个做相同功能的着色器,但需要在上边着色器基础上使用法线贴图。一般情况下,我们会将上述着色器使用Copy大发到一个新文件中并新增处理法线贴图相关的逻辑。但是问题又来了:功能可以实现,但是代码出现了大量的冗余与重复。此时会有大聪明说,在同一Shader中加一个判断条件吧,这样也可以。但是也带来两个问题:一个就是判断条件是否需要通过buffer来传递(或者直接使用Vulkan的特化常量),这样多多少少会带来些CPU与GPU间通信的消耗,如果引擎中存在大量这类情况,那么后续你就该抓头发了;第二个问题就是分支消耗的问题,当然了杠精们也可以说可以强制Warp中所有分支条件一致来解决(此处就不再叨叨了)。

上述问题其实皆可以使用Uber Shader的方式,即预处理器指令在这里发挥了作用,所以我们可以将两个单独的着色器文件变成一个通用头文件,这就是我们的 Uber Shader通用函数文件,如下所示:

float4 UberLitPixelShader(VS_to_PS input) : SV_TARGET
{					  
    float4 textureColor = DiffuseTexture.Sample(DiffuseSampler, input.texCoord0);
	
    float3 normal = input.normal;
	
#ifdef USE_NORMAL_MAP
    float4 bumpMap = NormalMap.Sample(NormalMapSampler, input.texCoord0);
    bumpMap = (bumpMap * 2.0f) - 1.0f;
	
    float3 viewSpaceBinormal = cross( normal, input.tangent );
	
    float3x3 texSpace = float3x3(input.tangent, viewSpaceBinormal, normal);
	
    normal = normalize(mul(bumpMap, texSpace));
#endif
	
    float nDotL = saturate(dot(normal, lightDirection));
	
    float4 output = textureColor * diffuseColor * nDotL;
	
    return output;
}

那么创建使用法线贴图的着色器现在只需要一个 #define 即可,之后包括我们的通用着色器即可,这样就可以启用法线贴图逻辑了。使用法线贴图的着色器文件就如下所示:

#ifndef USE_NORMAL_MAP
#define USE_NORMAL_MAP  1
#endif

#include "UberLitPixelShader.hlsl"

至此我们已经将所有逻辑压缩到一个通用文件中了,所以现在我们要创建一个新的着色器,只需声明我们想要的定义并包含 Uber Shader。当然了还有其他方法,例如micro shaders, branching shaders等方案,但我觉得这种方案最好用,主流引擎中也是使用的此类方法。

当然了,这仅仅是简单的不能再简单的Demo,至于实际功能中的情况则是千变万化,有兴趣的可以参照UE源码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值