抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Unity URP卡通渲染包

最近工作之余抄了一个卡渲,结果越做感觉越没意思,主要是缺少一些美术资源,于是先pending,在此先记录一下

当前进度

代码已开源,仓库地址

patuo

设计理念

高复用,易拓展,低侵入

我遇到了什么问题

每次我要写Unity shader时,我都要不厌其烦地写

  1. Shader GUI、Properties
  2. 定义贴图和采样器,定义全局变量
  3. 维护CBuffer,尤其是SRP batcher
  4. 几乎一模一样的Vertex函数
  5. 大量贴图采样,参数初始化
  6. 照抄的渲染方程和灯光Loop

我一直认为,大部分情况,都不应该写Shading Function,这些BRDF是渲染的基石,是必须要用有科学依据、有论文支撑、经过行业验证的方案,而每次都自行写渲染方程,很容易产生错误

此外为了实现材质的多光源,渲染方程中会有大量的灯光着色Loop,他们代码十分重复,如果直接展开在片源函数中,很容易漏维护(比如只更新了主方向光的代码,其他的都没管)

因此我认为,应当将渲染方程视为黑盒,用户在定义材质shader时只需要收集数据,传入方程

高可复用

类似URP的UniversalFragmentPBR,我定义了一个float4 ToonFragment(inputData, surfaceData, input.uv)函数,用户只需要修改传入函数的参数

修改分为两个部分,材质修改和渲染修改

新材质需求

材质修改是通过PreProcessMaterial,对传入函数的InputDataToonSurfaceData进行修改,参考

在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频繁改动,让人没有上的欲望

评论