Unity URP卡通渲染包
最近工作之余抄了一个卡渲,结果越做感觉越没意思,主要是缺少一些美术资源,于是先pending,在此先记录一下
当前进度
代码已开源,仓库地址
设计理念
高复用,易拓展,低侵入
我遇到了什么问题
每次我要写Unity shader时,我都要不厌其烦地写
- Shader GUI、Properties
- 定义贴图和采样器,定义全局变量
- 维护CBuffer,尤其是SRP batcher
- 几乎一模一样的Vertex函数
- 大量贴图采样,参数初始化
- 照抄的渲染方程和灯光Loop
我一直认为,大部分情况,都不应该写Shading Function,这些BRDF是渲染的基石,是必须要用有科学依据、有论文支撑、经过行业验证的方案,而每次都自行写渲染方程,很容易产生错误
此外为了实现材质的多光源,渲染方程中会有大量的灯光着色Loop,他们代码十分重复,如果直接展开在片源函数中,很容易漏维护(比如只更新了主方向光的代码,其他的都没管)
因此我认为,应当将渲染方程视为黑盒,用户在定义材质shader时只需要收集数据,传入方程
高可复用
类似URP的UniversalFragmentPBR
,我定义了一个float4 ToonFragment(inputData, surfaceData, input.uv)
函数,用户只需要修改传入函数的参数
修改分为两个部分,材质修改和渲染修改
新材质需求
材质修改是通过PreProcessMaterial
,对传入函数的InputData
和ToonSurfaceData
进行修改,参考
在Standard的基础上,让石头顶部覆盖雪或草
// Feature +[Main(FeatureMode, _, off, off)] _FeatureGroup("Feature", float) = 0 +[KWEnum(FeatureMode, SnowRock, _SNOWROCK, GrassRock, _GRASSROCK)] _EnumFeatureMode ("Feature", float) = 0 +[Sub(FeatureMode)] [ShowIf(_EnumFeatureMode, Equal, 0)] _CustomVector1 ("Snow Color", Color) = (1,1,1,1) +[Sub(FeatureMode)] [ShowIf(_EnumFeatureMode, Equal, 0)] _CustomFloat1 ("Snow Line (World)", Float) = 0.5 +[Sub(FeatureMode)] [ShowIf(_EnumFeatureMode, Equal, 1)] _CustomVector2 ("Grass Rock Color", Color) = (1,1,1,1) +[Sub(FeatureMode)] [ShowIf(_EnumFeatureMode, Equal, 1)] _CustomFloat2 ("Grass Scale", Range(0,1)) = 0.9 +[Tex(FeatureMode_GRASSROCK)] _CustomMap1("GrassMap", 2D) = "white" {}
#pragma shader_feature_local _CELLSHADING _PBRSHADING _CUSTOMSHADING +#pragma shader_feature_local _SNOWROCK _GRASSROCK
+#define _SnowLine _CustomFloat1 +#define _SnowColor _CustomVector1 +#define _GrassScale _CustomFloat2 +#define _GrassColor _CustomVector2 +#define _GrassMap _CustomMap1
void PreProcessMaterial(inout InputData inputData, inout ToonSurfaceData surfaceData, float2 uv) { + #if _SNOWROCK + float snowScale = saturate(inputData.positionWS.y - _SnowLine); + surfaceData.albedo = lerp(surfaceData.albedo, _SnowColor.rgb, snowScale); + #endif
+ #if _GRASSROCK + float3 grassColor = _GrassColor.rgb; + grassColor *= SAMPLE_TEXTURE2D(_GrassMap, sampler_CustomMap1, uv).rgb;
+ float3 upVector = float3(0, 1, 0); + float NoU = dot(upVector, inputData.normalWS); + float grassScale = saturate(NoU - _GrassScale); + // surfaceData.albedo = lerp(surfaceData.albedo, grassColor, grassScale); + if(NoU > _GrassScale) + { + surfaceData.albedo = grassColor; + } + #endif }
float4 CustomFragment(InputData inputData, ToonSurfaceData toonSurfaceData, AdditionInputData additionInput) { return 0; }
#include "Packages/com.reubensun.toonurp/Shaders/ToonStandardForwardPass.hlsl"
|
新着色需求
渲染修改是着色后对diffuse、specular进行一次modify,参考
让材质的diffuse根据菲涅尔差值,实现简单的丝袜,这里的modify将会直接乘到diffuse颜色上
+// Feature +[Main(FeatureMode, _, off, off)] _FeatureGroup("Feature", float) = 0 +[KWEnum(FeatureMode, FresnelStocking, _FRESNEL_STOCKING)] _EnumFeatureMode ("Feature", float) = 0 +[Sub(FeatureMode)] [ShowIf(_EnumFeatureMode, Equal, 0)] _CustomFloat1 ("Stockings Pow", Float) = 0.5 +[Sub(FeatureMode)] [ShowIf(_EnumFeatureMode, Equal, 0)] _CustomVector1 ("Color Inside", Color) = (1,1,1,1) +[Sub(FeatureMode)] [ShowIf(_EnumFeatureMode, Equal, 0)] _CustomVector2 ("Color Outside", Color) = (1,1,1,1)
#pragma shader_feature_local _CELLSHADING _PBRSHADING +#pragma shader_feature_local _FRESNEL_STOCKING
+#define _StockingsPow _CustomFloat1 +#define _StockingsColorInside _CustomVector1 +#define _StockingsColorOutside _CustomVector2
void PreProcessMaterial(inout InputData inputData, inout ToonSurfaceData surfaceData, float2 uv) { + #if _FRESNEL_STOCKING + float NoV = max(dot(inputData.viewDirectionWS, inputData.normalWS), 0.001); + float fresnelStockings = pow(NoV, _StockingsPow); + float3 stockingsColor = lerp(_StockingsColorOutside.rgb, _StockingsColorInside.rgb, fresnelStockings); + surfaceData.diffuseModify = stockingsColor; + #endif
}
|
易拓展
必须承认,不同材质的ShadingMode不同,因此我们必须要支持自定义ShadingMode,于是我设计了一个CustomFragment
函数,并定义了一个宏,当这个宏打开,就不走ToonFragment
,而是CustomFragment
,参考
// Lighting mode [Main(ShadingMode, _, off, off)] _ShadingModeGroup("ShadingMode", float) = 0 +[KWEnum(ShadingMode, CelShading, _CELLSHADING, PBRShading, _PBRSHADING, CelHair, _CUSTOMSHADING)] _EnumShadingMode ("Mode", float) = 2
+#pragma shader_feature_local _CELLSHADING _PBRSHADING _CUSTOMSHADING
float4 CustomFragment(InputData inputData, ToonSurfaceData toonSurfaceData, AdditionInputData additionInput) { + ... return color; }
#include "Packages/com.reubensun.toonurp/Shaders/ToonStandardForwardPass.hlsl"
|
维护SRP batcher
为了让材质都支持SRP batcher,我们需要不同shader的CBuffer相同,但不同材质要很多不同的变量
为此我在CBuffer中提前定义了大量的float、vector、贴图,在使用时只需要先用#define
对这个custom变量重命名,就能兼容SRP batcher,参考
低侵入
我看到很多卡通渲染包,他们都把URP完整复制一份,然后在其中进行少部分的修改,结果就是用户很难分清哪些是自定义的,当用户打算升级URP版本时,就会遇到很多问题
于是我把URP复制到另一个项目中,仅仅做必须在URP做的修改(比如加一个GT Tonemapping),其他的RenderFeature和Shader都在主项目中进行,于是主项目中几乎没有Unity URP自带的内容,十分简洁
功能实现
详情
为什么不想做了
TA届有句名言,工匠克算法
我感觉卡渲的效果太依赖模型、材质了,而我自己不能产出这些资源,游戏公司公开的mmd模型并不是他们实际使用的,很多feature所需的资产并没有公开,非常劝退
而且我感觉URP的设计没有想象中那么好,我想添加一个Tonemapping需要改URP源码,想自己实现阴影要注释掉很多东西,RenderGraph的API频繁改动,让人没有上的欲望