后处理描边

9.4k words

代码

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

half3 ShaderGI(ToonSurfaceData surfaceData,ToonLightingData lightData){
half3 averageSH=SampleSH(0);
averageSH=max(_IndirectLightMinColor,averageSH);
return averageSH;
}

half3 ShadeSignleLight(ToonSurfaceData surfaceData,ToonLightingData lightData,Light light,bool isAdditionLight){
half3 N=normalize(lightData.normalWS);
half3 V=lightData.viewDirectionWS;
half3 L=light.direction;

half NoL=dot(N,L);
half HL=NoL*0.5+0.5;
half lightAttenuation=1;
half distanceAttenuation=min(4,light.distanceAttenuation);


half litOrShadowArea = NoL;//smoothstep(_CelShadeMidPoint-_CelShadeSoftness,_CelShadeMidPoint+_CelShadeSoftness, NoL);
litOrShadowArea*=surfaceData.occlusion;
// litOrShadowArea = _IsFace? lerp(0.5,1,litOrShadowArea) : litOrShadowArea;
// litOrShadowArea*=lerp(1,light.shadowAttenuation,_ReceiveShadowMappingAmount);
half3 litOrShadowColor=lerp(_ShadowMapColor,1,litOrShadowArea);
half3 sssColor=1;
half curve = 0;//length(fwidth(N)) / (length(fwidth(lightData.positionWS)) * _CurveFactor);
if(_IsSSS){
curve = length(fwidth(N)) / (length(fwidth(lightData.positionWS)) * _CurveFactor);
curve=saturate(curve);
sssColor=tex2D(_SSSLUTTex,float2(HL,curve));
litOrShadowColor=lerp(sssColor*_SSSScale+_ShadowMapColor,1,litOrShadowArea);
}
// return litOrShadowColor;
half3 lightAttenuationRGB=litOrShadowColor*distanceAttenuation;
half3 diffuseColor=light.color*lightAttenuationRGB;

return saturate(diffuseColor)*(isAdditionLight?_PointLightScale:1);
}

half3 CompositeAllLightResults(half3 indirectResult,half3 mainLightResult,half3 additionalLightSumResult,half3 emissionResult,half3 specularResult,ToonSurfaceData surfaceData,ToonLightingData lightData){
half3 rawLightSum=max(indirectResult,mainLightResult+additionalLightSumResult);
return surfaceData.albedo*rawLightSum+emissionResult+specularResult;
}

half3 ComputeAllLights(ToonSurfaceData surfaceData,ToonLightingData lightData){
half3 finialColor=0;

half3 indirectResult=ShaderGI(surfaceData,lightData);
half3 specularResult=0;
Light mainLight=GetMainLight();

float3 shadowTestPosWS=lightData.positionWS;
#ifdef _MAIN_LIGHT_SHADOWS
float4 shadowCoord=TransformWorldToShadowCoord(shadowTestPosWS);
mainLight.shadowAttenuation=MainLightRealtimeShadow(shadowCoord);
#endif
half3 mainLightResult=ShadeSignleLight(surfaceData,lightData,mainLight,false);
specularResult+=ShadeSpecular(surfaceData,lightData,mainLight,false);

// return mainLightResult;
half3 additionalLightSumResult=0;

#ifdef _ADDITIONAL_LIGHTS
int additionLightsCount=GetAdditionalLightsCount();
for(int i=0;i<additionLightsCount;i++){
int perObjectLightIndex=GetPerObjectLightIndex(i);
Light light=GetAdditionalPerObjectLight(perObjectLightIndex,lightData.positionWS);
light.shadowAttenuation=AdditionalLightRealtimeShadow(perObjectLightIndex,shadowTestPosWS);
additionalLightSumResult+=ShadeSignleLight(surfaceData,lightData,light,true);
specularResult+=ShadeSpecular(surfaceData,lightData,mainLight,false);
}
#endif

// return additionalLightSumResult;

half3 emissionResult=ShadeEmission(surfaceData,lightData);

finialColor=CompositeAllLightResults(indirectResult,mainLightResult,additionalLightSumResult,emissionResult,specularResult,surfaceData,lightData);
return finialColor;
}

漫反射处理

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14

half LighingRadiance(half nl,half hf,half useHalfLambert,half occlusion,half useOcclusion){
half radiance=lerp(nl,hf,useHalfLambert);
radiance=saturate(lerp(radiance,(radiance+occlusion)*0.5,useOcclusion));
return radiance;
}

half3 CellShadingDiffuse(half radiance,half threshold,half smooth,half3 hightCol,half3 drakCol){
half3 diffuse=0;
radiance=saturate(1+(radiance-threshold-smooth)/max(smooth,1e-3));
diffuse=lerp(drakCol,hightCol,radiance);
return saturate( diffuse);
}

后处理描边实现

Image text

cs代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (m_material == null)
{
return;
}
if (!renderingData.cameraData.postProcessEnabled)
{
return;
}
m_lineVolume = VolumeManager.instance.stack.GetComponent<OutLineVolume>();
CommandBuffer cmd = CommandBufferPool.Get(RenderTag);
this.m_Render(cmd, ref renderingData);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

private void m_Render(CommandBuffer cmd, ref RenderingData renderingData)
{
if (m_material != null)
{
float angleThreshold = m_lineVolume.angleThreshold.value;
float depthThreshold = m_lineVolume.depthThreshold.value;
Vector4 threshold = new Vector4(Mathf.Cos(angleThreshold * Mathf.Deg2Rad), m_lineVolume.thickness.value, depthThreshold, m_lineVolume.intensity.value);
m_material.SetVector(ShaderIDs.Threshold, threshold);
m_material.SetColor(ShaderIDs.Color, m_lineVolume.color.value);
CoreUtils.SetKeyword(m_material, ShaderIDs.LowQuality, m_lineVolume.LowQuality.value);
}

RenderTargetIdentifier source = renderingData.cameraData.renderer.cameraColorTarget;
RenderTextureDescriptor inRTDesc = renderingData.cameraData.cameraTargetDescriptor;

int destination = Shader.PropertyToID("Temp1");

cmd.SetGlobalTexture(ShaderIDs.Input, source);
cmd.GetTemporaryRT(destination, inRTDesc.width, inRTDesc.height, 0, FilterMode.Bilinear, RenderTextureFormat.DefaultHDR);
cmd.Blit(source, destination);
cmd.Blit(destination, source, m_material, 0);

}

shader代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

Shader "Hidden/PostProcess/EdgeDetectionOutline"
{
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

TEXTURE2D_X(_MainTex);

float4 _MainTex_TexelSize;
float4 _Threshold;
float3 _Color;

struct PostProcessVaryings
{
float4 positionCS : SV_POSITION;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_OUTPUT_STEREO
};

struct FullScreenTrianglePostProcessAttributes
{
uint vertexID : SV_VertexID;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

float3 SampleSceneNormals(float2 uv, TEXTURE2D_X_FLOAT(_Texture), SAMPLER(sampler_Texture))
{
return UnpackNormalOctRectEncode(SAMPLE_TEXTURE2D_X(_Texture, sampler_Texture, UnityStereoTransformScreenSpaceTex(uv)).xy) * float3(1.0, 1.0, -1.0);
}

float SampleSceneDepth(float2 uv, TEXTURE2D_X_FLOAT(_Texture), SAMPLER(sampler_Texture))
{
return SAMPLE_TEXTURE2D_X(_Texture, sampler_Texture, UnityStereoTransformScreenSpaceTex(uv)).r;
}

SAMPLER(sampler_linear_clamp);

// this method from https://github.com/yahiaetman/URPCustomPostProcessingStack"
float4 SampleSceneDepthNormal(float2 uv){
float depth = SampleSceneDepth(uv, _CameraDepthTexture, sampler_linear_clamp); // TODO:loadSceneDepth
float depthEye = LinearEyeDepth(depth, _ZBufferParams);
float3 normal = SampleSceneNormals(uv, _CameraNormalsTexture, sampler_linear_clamp);
return float4(normal, depthEye);
}

float4 SampleNeighborhood(float2 uv, float thickness){
const float2 offsets[8] = {
float2(-1, -1),
float2(-1, 0),
float2(-1, 1),
float2(0, -1),
float2(0, 1),
float2(1, -1),
float2(1, 0),
float2(1, 1)
};

float2 delta = _MainTex_TexelSize.xy * thickness;
float4 sum = 0;
float weight = 0;
// this method ref from https://github.com/yahiaetman/URPCustomPostProcessingStack"
for(int i=0; i<8; i++){
float4 sample = SampleSceneDepthNormal(uv + delta * offsets[i]);
sum += sample / sample.w; // for perspective
weight += 1/sample.w;
}
sum /= weight;
return sum;
}

float4 Sample4Neighborhood(float2 uv, float thickness){
const float2 offsets[4] = {
float2(-1, 0),
float2(0, -1),
float2(0, 1),
float2(1, 0),
};

float2 delta = _MainTex_TexelSize.xy * thickness;
float4 sum = 0;
float weight = 0;
// this method ref from https://github.com/yahiaetman/URPCustomPostProcessingStack"
for(int i=0; i<4; i++){
float4 sample = SampleSceneDepthNormal(uv + delta * offsets[i]);
sum += sample / sample.w; // for perspective
weight += 1/sample.w;
}
sum /= weight;
return sum;
}

PostProcessVaryings FullScreenTrianglePostProcessVertex (FullScreenTrianglePostProcessAttributes input)
{
PostProcessVaryings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID);
output.texcoord = GetFullScreenTriangleTexCoord(input.vertexID);
return output;
}

float4 EdgeDetectionFragment (PostProcessVaryings input) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

float2 uv = UnityStereoTransformScreenSpaceTex(input.texcoord);
float4 center = SampleSceneDepthNormal(uv);
#if _LowQuality
float4 neighborhood = Sample4Neighborhood(uv, _Threshold.y);
#else
float4 neighborhood = SampleNeighborhood(uv, _Threshold.y);
#endif
float normalSampler = smoothstep(_Threshold.x, 1, dot(center.xyz, neighborhood.xyz));
float depthSampler = smoothstep(_Threshold.z * center.w, 0.0001f * center.w, abs(center.w - neighborhood.w));
float edge = 1 - normalSampler * depthSampler;

float4 color = LOAD_TEXTURE2D_X(_MainTex, uv * _ScreenSize.xy);
color.rgb = lerp(color.rgb, _Color, edge * _Threshold.w);
return color;
}
ENDHLSL

SubShader
{
Cull Off ZWrite Off ZTest Always
Pass
{
HLSLPROGRAM
#pragma
#pragma shader_feature_local _LowQuality
#pragma vertex FullScreenTrianglePostProcessVertex
#pragma fragment EdgeDetectionFragment
ENDHLSL
}
}
Fallback Off
}

参考

UnityChan
URPCarToon
Quibli
Artstation
西川善司