最近面试也是经常被问到关于卡渲的知识,所以将一年前做的面向知乎的甘雨角色逆向复习了一下。
贴图解析
LightMap.r
LightMap.g LightMap.b LightMap.a
- LightMap.r: 存储这区分材质的信息主要是高光类型:BlinPhong或者裁边视角光
- LightMap.g: 存储着AO信息
- LightMap.b: 存储BlinPhong高光强度
- LightMap.a:存储 Ramp类型的LayerMask 0-0.45 0.45-0.55 0.6-1
甘雨的RampMask可分为三种丝绸,布料,皮肤。这里我有个疑问为什么没有将金属单独分开?
直接光漫反射
//ParamLine : X:RampAreaMask, Y:RampSmooth1, Z:RampSmooth2, W:RampSmooth3
float rampValue1 = smoothstep(_ParamLine1.y, _ParamLine1.z, halfLambert);
float rampValue2 = smoothstep(_ParamLine2.y, _ParamLine2.z, halfLambert);
float rampValue3 = smoothstep(_ParamLine3.y, _ParamLine3.z, halfLambert);
对halfLambert结果重新映射范围。
#if _DAYNIGHT_SWITCH_DAY
rampUV1 = float2(rampValue1, 0.95);
rampUV2 = float2(rampValue2, 0.85);
rampUV3 = float2(rampValue3, 0.75);
#else
rampUV1 = float2(rampValue1, 0.45);
rampUV2 = float2(rampValue2, 0.35);
rampUV3 = float2(rampValue3, 0.25);
#endif
准备采样Ramp图的uv分为白天黑夜两种,半兰伯特作为采样ramp的u值,y值指定采样Ramp的哪一条。
float3 var_RampMap1 = SAMPLE_TEXTURE2D(_RampMap,sampler_RampMap,rampUV1);
float3 var_RampMap2 = SAMPLE_TEXTURE2D(_RampMap,sampler_RampMap,rampUV2);
float3 var_RampMap3 = SAMPLE_TEXTURE2D(_RampMap,sampler_RampMap,rampUV3);
采样ramp图的结果。
float AOMask = min(LightMap.g+0.8,1);
//RampColor
half3 rampColor1 = var_RampMap1 * _ShadColLine1.rgb;
rampColor1 = lerp(rampColor1, _BaseColLine1.rgb, smoothstep(0.65, _ParamLine1.w, halfLambert * AOMask));
rampColor1 *= AllRampAreaMask[floor(_ParamLine1.x)];
half3 rampColor2 = var_RampMap2 * _ShadColLine2.rgb;
rampColor2 = lerp(rampColor2, _BaseColLine2.rgb, smoothstep(0.65, _ParamLine2.w, halfLambert * AOMask));
rampColor2 *= AllRampAreaMask[floor(_ParamLine2.x)];
half3 rampColor3 = var_RampMap3 * _ShadColLine3.rgb;
rampColor3 = lerp(rampColor3, _BaseColLine3.rgb, smoothstep(0.65, _ParamLine3.w, halfLambert * AOMask));
rampColor3 *= AllRampAreaMask[floor(_ParamLine3.x)];
float3 RampColor = rampColor1 + rampColor2 + rampColor3;
将RampColor跟BaseColor混合,混合参数是半兰伯特乘以AO再重映射值。 乘以BaseColor
直接光高光
非金属部分裁边高光
LightMap.r(0.3~0.5)非金属裁边高光遮罩
使用dot(N, V)高光
float3 normalVS=normalize(mul((float3x3)UNITY_MATRIX_V,N));
half stepLightLayer = LightMap.r * 255;
half StepMask = step(85, stepLightLayer) - step(135, stepLightLayer);
StepSpecular = step(1 - _StepSpecularGloss, saturate(dot(N, V))) * _StepSpecularIntensity * StepMask;
StepSpecular = min(StepSpecular,1);
金属高光
LightMap.r(0.7~1)金属高光遮罩
float2 MetalMapUV = mul((float3x3) UNITY_MATRIX_V,N).xy * 0.5 + 0.5;
准备采样金属的matcapUV:屏幕空间法线xy值并映射到0~1范围内。 采样金属MatCap。
blinPhong高光 GGX_D高光
// GGX_D 金属高光
half MetalMask = step(0.7, LightMap.r);
float2 MetalMapUV = mul((float3x3) UNITY_MATRIX_V,N).xy * 0.5 + 0.5;
float metallic = pow(lerp(0,SAMPLE_TEXTURE2D(_MetalCap, sampler_MetalCap,MetalMapUV).r, MetalMask), 2);
float GGXSpecular = min(D_GGX(dot(N, H), _GGX_DIntensity), 1) * 10;
MetalSpecular = GGXSpecular * metallic * diffuseColor;
FinalSpecular = StepSpecular + MetalSpecular.rgbb;
FinalSpecular = lerp(0, FinalSpecular * _DirectMainLightColor, LightMap.r);
FinalSpecular *= NL * LightMap.g;
最后将高光混合。
基于深度的等宽边缘光
float3 normalVS = normalize(mul((float3x3)UNITY_MATRIX_V,N));
float3 normalWS = input.normalWS;
float3 positionVS = input.positionVS;
float3 samplePositionVS = float3(positionVS.xy + normalVS.xy * _RimOffset, positionVS.z); // 保持z不变(CS.w = -VS.z)
float4 samplePositionCS = TransformWViewToHClip(samplePositionVS); // input.positionCS不是真正的CS 而是SV_Position屏幕坐标
float4 samplePositionVP = TransformHClipToViewPortPos(samplePositionCS);
float depth = input.positionNDC.z / input.positionNDC.w;
float linearEyeDepth = LinearEyeDepth(depth, _ZBufferParams); // 离相机越近越小
float offsetDepth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, samplePositionVP).r; // _CameraDepthTexture.r = input.positionNDC.z / input.positionNDC.w
float linearEyeOffsetDepth = LinearEyeDepth(offsetDepth, _ZBufferParams);
float depthDiff = linearEyeOffsetDepth - linearEyeDepth;
float rimIntensity = step(_RimThreshold, depthDiff);
原理:将相机空间的顶点沿法线XY方向偏移,采样偏移后的深度度图深度,与原来的深度作比较,大于某个阈值就是边缘。
边缘光混合菲涅尔和BaseColor。
float3 viewDirectionWS = SafeNormalize(GetCameraPositionWS() - input.positionWS);
float rimRatio = 1 - saturate(dot(viewDirectionWS, normalWS));
rimIntensity = lerp(0, rimIntensity, rimRatio);
half4 RimColor = float4(_RimColor.rgb,1) * rimIntensity * BaseMap;
*为了在URP中获取深度图需要加入DepthOnly Pass并且在管线设置中开启深度图
URP多Pass实现外轮廓
外轮廓的实现原理很简单经典的外轮廓Pass,顶点外扩,剔除正面,渲染模型背面。但是受FOV和距离影响,FOV较小的时候外轮廓会变得很粗,距离近的时候也会变很粗,我们需要FOV角度和距离对外轮廓的粗细进行矫正,保证在屏幕上看起来描边粗细不会变化。
float GetCameraFOV()
{
float t = unity_CameraProjection._m11;
float Rad2Deg = 180 / 3.1415;
float fov = atan(1.0f / t) * 2.0 * Rad2Deg;
return fov;
}
获取FOV角度与屏幕长宽比矫正外轮廓宽度。
unity_CameraProjection._m11是投影矩阵中第一行第一列也就是以下公式内容:
\[m_{11} = \left( \frac{w}{h} \right) \cdot \frac{1}{\tan\left(\frac{\theta}{2}\right)}\] float GetOutlineCameraFovAndDistanceFixMultiplier(float positionVS_Z)
{
float cameraMulFix;
if (unity_OrthoParams.w == 0)
{
cameraMulFix = abs(positionVS_Z);
cameraMulFix = ApplyOutlineDistanceFadeOut(cameraMulFix);
cameraMulFix *= GetCameraFOV();
}
else
{
float orthoSize = abs(unity_OrthoParams.y);
orthoSize = ApplyOutlineDistanceFadeOut(orthoSize);
cameraMulFix = orthoSize * 50;
}
return cameraMulFix * 0.0001;
}
分开处理透视投影和正交投影下的外轮廓宽度修复。
float3 TransformPositionWSToOutlinePositionWS(half vertexColorAlpha, float3 positionWS, float positionVS_Z, float3 normalWS)
{
float outlineExpandAmount = vertexColorAlpha * _OutlineWidth * GetOutlineCameraFovAndDistanceFixMultiplier(positionVS_Z);
return positionWS + normalWS * outlineExpandAmount;
}
应用顶点沿法线外扩并且应用距离和FOV矫正。
Pass
{
Name "CHARACTER_OUTLINE"
Tags { }
Cull Front
HLSLPROGRAM
#pragma shader_feature_local_fragment ENABLE_ALPHA_CLIPPING
#pragma vertex OutlinePassVertex
#pragma fragment OutlinePassFragment
float4 OutlinePassFragment(Varyings input): COLOR
{
half4 ColorMask = SAMPLE_TEXTURE2D(_LightMap, sampler_LightMap,input.uv).a;
half4 baseColor = SAMPLE_TEXTURE2D(_MainTex, sampler__MainTex, input.uv.xy);
float rampAreaMask0 = 0;
float rampAreaMask1 = step(0.00, ColorMask) - step(0.45, ColorMask);
float rampAreaMask2 = step(0.25, ColorMask) - step(0.85, ColorMask);
float rampAreaMask3 = step(0.60, ColorMask) - step(1.05, ColorMask);
float3 skinLineColor = rampAreaMask3*_skinLineColor;
float3 cloths1LineColor = rampAreaMask2 *_Coloth1LineColor;
float3 cloths2LineColor = rampAreaMask1 *_Coloth2LineColor;
half4 FinalColor = (_OutlineColor*(skinLineColor+cloths1LineColor+cloths2LineColor)).xyzz;
return FinalColor;
}
ENDHLSL
}
最后添加一个Pass渲染外轮廓。