HLSL中能否用#define实现可替换方法?Unity下FBM函数适配需求
问题原因
宏是预编译阶段处理的,编译器解析FBM()函数定义时,NOISE_SAMPLE已经被替换成Random(x,y)了——预编译按代码顺序逐行处理,后面CSMain里的#undef和重定义根本影响不到已经处理过的FBM()内容。哪怕HLSL默认内联函数,内联是编译阶段的操作,宏替换早就完成了,所以FBM()里的采样逻辑从一开始就固定死了。
可行方案
方案1:宏模板生成多版本FBM(兼容性最好)
把FBM做成带参数的宏,传入采样函数名生成对应版本的FBM:
// 宏模板,SAMPLER是要传入的采样函数名 #define DEFINE_FBM(SAMPLER) \ float FBM_##SAMPLER(float x, float y) { \ float result = 0.0; \ float amplitude = 0.5; \ float frequency = 1.0; \ for (int i = 0; i < 4; i++) { \ result += amplitude * SAMPLER(x * frequency, y * frequency); \ frequency *= 2.0; \ amplitude *= 0.5; \ } \ return result; \ } // 生成Random版本的FBM DEFINE_FBM(Random) // 生成Perlin版本的FBM DEFINE_FBM(Perlin) void CSMain(uint3 id : SV_DispatchThreadID) { float x = id.x / 512.0; float y = id.y / 512.0; // 直接调用对应版本 float randomFbm = FBM_Random(x, y); float perlinFbm = FBM_Perlin(x, y); // ...后续逻辑 }
用##做宏拼接生成不同函数名,避免重复定义,预编译阶段就处理完成,性能和手写多个FBM完全一致,所有HLSL版本都支持。
方案2:HLSL模板函数(简洁但有版本要求)
如果目标平台支持Shader Model 5.0+(Unity里把Shader目标等级设为SM5),可以用模板函数传采样逻辑:
template<typename SamplerFunc> float FBM(float x, float y, SamplerFunc sampler) { float result = 0.0; float amplitude = 0.5; float frequency = 1.0; for (int i = 0; i < 4; i++) { result += amplitude * sampler(x * frequency, y * frequency); frequency *= 2.0; amplitude *= 0.5; } return result; } // 采样函数实现 float Random(float x, float y) { /* ...你的实现... */ } float Perlin(float x, float y) { /* ...你的实现... */ } void CSMain(uint3 id : SV_DispatchThreadID) { float x = id.x / 512.0; float y = id.y / 512.0; float randomFbm = FBM(x, y, Random); float perlinFbm = FBM(x, y, Perlin); // ...后续逻辑 }
写法更简洁直观,但要注意兼容性,移动端老平台可能不支持。
方案3:函数指针(谨慎使用)
HLSL支持函数指针,但兼容性差且可能有性能损耗,只在特殊场景用:
// 定义采样函数的指针类型 typedef float (*NoiseSampler)(float, float); float FBM(float x, float y, NoiseSampler sampler) { float result = 0.0; float amplitude = 0.5; float frequency = 1.0; for (int i = 0; i < 4; i++) { result += amplitude * sampler(x * frequency, y * frequency); frequency *= 2.0; amplitude *= 0.5; } return result; } float Random(float x, float y) { /* ...实现... */ } float Perlin(float x, float y) { /* ...实现... */ } void CSMain(uint3 id : SV_DispatchThreadID) { float x = id.x / 512.0; float y = id.y / 512.0; float randomFbm = FBM(x, y, Random); float perlinFbm = FBM(x, y, Perlin); // ...后续逻辑 }
灵活性高,但部分GPU驱动不支持,且函数指针调用比直接内联慢,优先选前两个方案。
内容的提问来源于stack exchange,提问作者Oliver
相关产品推荐
相关产品推荐

