当前位置:   article > 正文

Unity HDRP Custom Pass (Post processing) 后处理特效学习(一)总结

hdrp custompostprocess

目录

B站视频

B站视频,录制不易,求关注、一键三连(点赞、投币、收藏)。置顶评论内有PPT(内有优秀参考资源链接)、工程、插件、群号。

官方社区

文字版总结,编写不易,求关注、点赞。手机版阅读困难的,记得是在电脑浏览器中查看 开发者社区

原因

突然开始做分享主要2个原因。
  • 有朋友和我说Unity是不是只是手游引擎呀,UE能做的Unity能不能做呀,恰巧看到虚幻官方月神的直播讲后处理,所以就试着也录一期Unity的,证明Unity并不只是手游引擎。
  • 最近被一个Unity官方合作的知名博主up主麦扣的群里的管理员傲娇修酒z侮辱了,发现如果没有粉丝,被人侮辱的时候甚至没有人帮我说话,所以希望吸引一些愿意帮我说话的粉丝。除非麦扣把侮辱人的管理员踢掉,否则这事情永远不会揭过。
另外这可能不是最好的分享,但是绝对是最详细的分享,不会不求甚解的去分享一些一眼就能看明白的东西。

感谢

首先要感谢一下叶月葵(Hazukiaoi),没有他,无法制作这系列视频和分享。

什么是HDRP Custom Pass呢?

HDRP Custom Pass允许你注入Shader或者C#在渲染管线循环的特定的点,赋予你绘制对象、做全屏pass、读取一些摄像机缓冲(如深度、颜色、法线)的能力。

延迟渲染

月神的后处理视频中,虚幻使用的是延迟渲染管线。因此,作为对比,这里也选择的是同样使用延迟渲染的HDRP高清渲染管线。因为延迟渲染的各种缓冲数据,给了我们更多的可能性,从而可以实现后处理的各种特效。

创建HDRP Custom Pass着色器

右键->Create->Shader->HDRP->Custom Full Screen Pass,由于这里我们针对后处理,所以创建是全屏Custom Pass。

获取缓冲数据

那么,我们要如何获取各种缓冲数据呢?

获取PositionInputs数据

获取的方式在我们创建的Shader里就有这个例子。获取方式如下:
  1. float
  2. depth
  3. =
  4. LoadCameraDepth
  5. (
  6. varyings
  7. .
  8. positionCS
  9. .
  10. xy
  11. )
  12. ;
  13. PositionInputs
  14. posInput
  15. =
  16. GetPositionInput
  17. (
  18. varyings
  19. .
  20. positionCS
  21. .
  22. xy
  23. ,
  24. _ScreenSize
  25. .
  26. zw
  27. ,
  28. depth
  29. ,
  30. UNITY_MATRIX_I_VP
  31. ,
  32. UNITY_MATRIX_V
  33. )
  34. ;
另外,可以从Common.hlsl中的顺着这4个函数,可以看出posInput中的positionSS就是传递进去的varyings.positionCS.xy转换成uint2。最后一个当中做了如下转换:
  1. posInput
  2. .
  3. positionSS
  4. =
  5. uint2
  6. (
  7. positionSS
  8. )
  9. ;
  • PositionInputs GetPositionInput(float2 positionSS, float2 invScreenSize, float deviceDepth, float4x4 invViewProjMatrix, float4x4 viewMatrix, uint2 tileCoord)
  • PositionInputs GetPositionInput(float2 positionSS, float2 invScreenSize, float deviceDepth, float4x4 invViewProjMatrix, float4x4 viewMatrix, uint2 tileCoord)
  • PositionInputs GetPositionInput(float2 positionSS, float2 invScreenSize, float deviceDepth, float linearDepth, float3 positionWS, uint2 tileCoord)
  • PositionInputs GetPositionInput(float2 positionSS, float2 invScreenSize, uint2 tileCoord)
PositionInput结构体中的数据如下
  • positionWS,世界空间位置(可能是摄像机相对位置)
  • positionNDC,视口中的单位化屏幕坐标,值域[0,1)(带半像素偏移),和我们平常理解的NDC不太一样,平常理解的NDC是-1到1的,这里的是被映射过的。
  • positionSS,屏幕空间像素坐标,值域[0, NumPixels)
  • tileCoord,屏幕tile坐标,值域[0, NumTiles)
  • deviceDepth,深度缓冲中的深度,设备深度,值域[0,1),一般取反,越近越靠近1,越远越靠近0
  • linearDepth,线性深度,视图空间Z坐标,值域[Near, Far],远近裁剪面之间。
  1. // The PositionInputs struct allow you to retrieve a lot of useful information for your fullScreenShader:
  2. // struct PositionInputs
  3. // {
  4. // float3 positionWS; // World space position (could be camera-relative)
  5. // float2 positionNDC; // Normalized screen coordinates within the viewport : [0, 1) (with the half-pixel offset)
  6. // uint2 positionSS; // Screen space pixel coordinates : [0, NumPixels)
  7. // uint2 tileCoord; // Screen tile coordinates : [0, NumTiles)
  8. // float deviceDepth; // Depth from the depth buffer : [0, 1] (typically reversed)
  9. // float linearDepth; // View space Z coordinate : [Near, Far]
  10. // };

获取颜色数据

  • 方法1,使用CustomPassLoadCameraColor加载获取。
  1. float4
  2. color
  3. =
  4. float4
  5. (
  6. 0.0
  7. ,
  8. 0.0
  9. ,
  10. 0.0
  11. ,
  12. 0.0
  13. )
  14. ;
  15. // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
  16. if
  17. (
  18. _CustomPassInjectionPoint
  19. !=
  20. CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING
  21. )
  22. color
  23. =
  24. float4
  25. (
  26. CustomPassLoadCameraColor
  27. (
  28. varyings
  29. .
  30. positionCS
  31. .
  32. xy
  33. ,
  34. 0
  35. )
  36. ,
  37. 1
  38. )
  39. ;
  40. // get color method 1
  41. finalColor
  42. =
  43. color
  44. ;
  • 方法2,使用CustomPassSampleCameraColor采样获取。
  1. // get color method 2
  2. // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
  3. if
  4. (
  5. _CustomPassInjectionPoint
  6. !=
  7. CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING
  8. )
  9. color
  10. =
  11. float4
  12. (
  13. CustomPassSampleCameraColor
  14. (
  15. varyings
  16. .
  17. positionCS
  18. .
  19. xy
  20. *
  21. _ScreenSize
  22. .
  23. zw
  24. ,
  25. 0
  26. )
  27. ,
  28. 1
  29. )
  30. ;
  31. finalColor
  32. =
  33. color
  34. ;
  35. //finalColor = color;

获取设备深度数据

  • 方法1,使用LoadCameraDepth加载获取。其实这里的depth就是设备深度,之前PositionInput结构体获取的代码中的变量。需要注意的是,这是对Z取反的,就是越近的深度越趋向于1,越远的深度越趋向于0。
  1. // get device depth method 1
  2. deviceDepth
  3. =
  4. depth
  5. ;
  • 方法2,通过PositionInputs获取。其实这里没有必要,因为depth其实就已经是设备深度数据了。但是如果是别的函数中使用,可以直接把PositionInput结构体传递出去带的数据更多,故此列出。
  1. // get device depth method 2
  2. deviceDepth
  3. =
  4. posInput
  5. .
  6. deviceDepth
  7. ;
  • 方法3,通过SampleCameraDepth采样获取。
  1. // get device depth method 3
  2. deviceDepth
  3. =
  4. SampleCameraDepth
  5. (
  6. varyings
  7. .
  8. positionCS
  9. .
  10. xy
  11. *
  12. _ScreenSize
  13. .
  14. zw
  15. )
  16. ;
  • 方法4( 错误 ),写了一个真正的采样方法, 结果错误(如上图),而且没必要 。
  1. // Incorrect result
  2. float
  3. SampleCameraDepthSK
  4. (
  5. float2
  6. uv
  7. )
  8. {
  9. return
  10. SAMPLE_TEXTURE2D_X_LOD
  11. (
  12. _CameraDepthTexture
  13. ,
  14. sampler_CameraDepthTexture
  15. ,
  16. uv
  17. *
  18. _RTHandleScaleHistory
  19. .
  20. xy
  21. ,
  22. 0
  23. )
  24. .
  25. r
  26. ;
  27. }
  28. // get device depth method 4 (Incorrect result)
  29. deviceDepth
  30. =
  31. SampleCameraDepthSK
  32. (
  33. varyings
  34. .
  35. positionCS
  36. .
  37. xy
  38. *
  39. _ScreenSize
  40. .
  41. zw
  42. )
  43. ;
首先,为什么说真正的采样方法?我们可以跟踪一下方法3中的SampleCameraDepth方法的定义,在ShaderVariables.hlsl中,里面调用的其实是LoadCameraDepth,并没有真正的用Sampler进行采样。
  1. float
  2. SampleCameraDepth
  3. (
  4. float2
  5. uv
  6. )
  7. {
  8. return
  9. LoadCameraDepth
  10. (
  11. uint2
  12. (
  13. uv
  14. *
  15. _ScreenSize
  16. .
  17. xy
  18. )
  19. )
  20. ;
  21. }
其次,为什么结果错误呢(看起来像Pyramid贴图)?我们可以继续跟踪一下,看注释,目前它是一个Atlas,它的Layout可以再HDUtils.cs中的ComputePackedMipChainInfo中找到。
  1. // Note: To sample camera depth in HDRP we provide these utils functions because the way we store the depth mips can change
  2. // Currently it's an atlas and it's layout can be found at ComputePackedMipChainInfo in HDUtils.cs
  3. float
  4. LoadCameraDepth
  5. (
  6. uint2
  7. pixelCoords
  8. )
  9. {
  10. return
  11. LOAD_TEXTURE2D_X_LOD
  12. (
  13. _CameraDepthTexture
  14. ,
  15. pixelCoords
  16. ,
  17. 0
  18. )
  19. .
  20. r
  21. ;
  22. }
我们继续跟踪一下,看注释,我们打包所有MIP到顶层MIP层级中,也就是LOD0中,来避免Pow2 MIP链的限制。这就是为什么结果错误,因为他根本没有用Pyramid进行储存各个层级,而是直接全都存到LOD0里,自然采样出来就是图中的效果了。那么你可能又会问,那么为什么Load不需要特殊处理,可以适配Pyramid的ColorTexture,又能兼容打包的非Pyramid的DepthTexture呢?那是因为Load是通过绝对坐标来加载,无论是ColorTexture和DepthTexture,对于LOD0,他的坐标都是从左下角的(0,0)到右上角的(W,H)。
  1. // We pack all MIP levels into the top MIP level to avoid the Pow2 MIP chain restriction.
  2. // We compute the required size iteratively.
  3. // This function is NOT fast, but it is illustrative, and can be optimized later.
  4. public
  5. void
  6. ComputePackedMipChainInfo
  7. (
  8. Vector2Int
  9. viewportSize
  10. )
那么,为什么说没必要呢?那是因为,Depth的映射关系本来就是非线性的,不适合用线性过滤进行插值采样,即使你要采样也是应该采用点过滤。而点过滤的处理方式跟Load就没有什么差别了,因此,这里说没必要。

获得线性深度数据

这里全白了,曝了是因为线性深度,视图空间Z坐标,值域[Near, Far],远近裁剪面之间,实际数字大于1。虚线是后处理等导致的。
  1. float
  2. depth
  3. =
  4. LoadCameraDepth
  5. (
  6. varyings
  7. .
  8. positionCS
  9. .
  10. xy
  11. )
  12. ;
  13. PositionInputs
  14. posInput
  15. =
  16. GetPositionInput
  17. (
  18. varyings
  19. .
  20. positionCS
  21. .
  22. xy
  23. ,
  24. _ScreenSize
  25. .
  26. zw
  27. ,
  28. depth
  29. ,
  30. UNITY_MATRIX_I_VP
  31. ,
  32. UNITY_MATRIX_V
  33. )
  34. ;
  35. float
  36. linearDepth
  37. =
  38. posInput
  39. .
  40. linearDepth
  41. ;
  42. `

获取绝对世界空间位置数据

注意如果你是想获取绝对世界空间位置,一定要调用GetAbsolutePositionWS,这样永远都会返回绝对世界空间位置。
  1. float
  2. depth
  3. =
  4. LoadCameraDepth
  5. (
  6. varyings
  7. .
  8. positionCS
  9. .
  10. xy
  11. )
  12. ;
  13. PositionInputs
  14. posInput
  15. =
  16. GetPositionInput
  17. (
  18. varyings
  19. .
  20. positionCS
  21. .
  22. xy
  23. ,
  24. _ScreenSize
  25. .
  26. zw
  27. ,
  28. depth
  29. ,
  30. UNITY_MATRIX_I_VP
  31. ,
  32. UNITY_MATRIX_V
  33. )
  34. ;
  35. float3
  36. positionWS
  37. =
  38. posInput
  39. .
  40. positionWS
  41. ;
  42. positionWS
  43. =
  44. GetAbsolutePositionWS
  45. (
  46. positionWS
  47. )
  48. ;
参考SpaceTransforms.hlsl的GetAbsolutePositionWS可以知道,在相对摄像机渲染模式中,posInput.positionWS中并不是真正的世界空间位置。
  1. // This function always return the absolute position in WS
  2. float3
  3. GetAbsolutePositionWS
  4. (
  5. float3
  6. positionRWS
  7. )
  8. {
  9. #
  10. if
  11. (SHADEROPTIONS_CAMERA_RELATIVE_RENDERING != 0)
  12. positionRWS
  13. +=
  14. _WorldSpaceCameraPos
  15. ;
  16. #
  17. endif
  18. return
  19. positionRWS
  20. ;
  21. }

获取世界法线数据

  • 方法1,这里使用的是屏幕空间坐标来获取,你只能获得绝对屏幕坐标的法线数据。
  1. NormalData
  2. normalData
  3. ;
  4. // get world normal method 1
  5. DecodeFromNormalBuffer
  6. (
  7. posInput
  8. .
  9. positionSS
  10. ,
  11. normalData
  12. )
  13. ;
  14. float3
  15. normal
  16. =
  17. normalData
  18. .
  19. normalWS
  20. ;
要注意的是,需要include一个额外的hlsl。
  1. #include
  2. "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/NormalBuffer.hlsl"
  • 方法2,这里使用的是映射后的[0,1)的NDC坐标来采样,也是需要include那个额外的hlsl的。
  1. SAMPLER
  2. (
  3. sampler_NormalBufferTexture
  4. )
  5. ;
  6. void
  7. DecodeFromNormalBufferNDC
  8. (
  9. float2
  10. positionNDC
  11. ,
  12. out
  13. NormalData
  14. normalData
  15. )
  16. {
  17. float4
  18. normalBuffer
  19. =
  20. SAMPLE_TEXTURE2D_X
  21. (
  22. _NormalBufferTexture
  23. ,
  24. sampler_NormalBufferTexture
  25. ,
  26. positionNDC
  27. )
  28. ;
  29. DecodeFromNormalBuffer
  30. (
  31. normalBuffer
  32. ,
  33. positionNDC
  34. ,
  35. normalData
  36. )
  37. ;
  38. }
  39. NormalData
  40. normalData
  41. ;
  42. // get world normal method 2
  43. DecodeFromNormalBufferNDC
  44. (
  45. posInput
  46. .
  47. positionNDC
  48. ,
  49. normalData
  50. )
  51. ;
  52. float3
  53. normal
  54. =
  55. normalData
  56. .
  57. normalWS
  58. ;

获取视口中的单位化屏幕坐标数据,值域[0,1)

  1. // get positionNDC[0, 1)
  2. float2
  3. positionNDC
  4. =
  5. posInput
  6. .
  7. positionNDC
  8. ;
  9. finalColor
  10. =
  11. float4
  12. (
  13. positionNDC
  14. ,
  15. 0
  16. ,
  17. 1
  18. )
  19. ;

获取裁剪空间位置数据

虽然叫varyings.positionCS,但是那个并不是真正的裁剪空间位置。
  1. float4
  2. positionCS
  3. =
  4. ComputeClipSpacePosition
  5. (
  6. posInput
  7. .
  8. positionNDC
  9. ,
  10. posInput
  11. .
  12. deviceDepth
  13. )
  14. ;

调试小技巧,输出矩阵数据

输出Matrix数据,有助于排查矩阵是否正确。
  1. // get UNITY_MATRIX_I_VP
  2. if
  3. (
  4. posInput
  5. .
  6. positionNDC
  7. .
  8. x
  9. <
  10. 0.5
  11. &&
  12. posInput
  13. .
  14. positionNDC
  15. .
  16. y
  17. <
  18. 0.5
  19. )
  20. finalColor
  21. =
  22. UNITY_MATRIX_I_VP
  23. [
  24. 0
  25. ]
  26. ;
  27. if
  28. (
  29. posInput
  30. .
  31. positionNDC
  32. .
  33. x
  34. >
  35. 0.5
  36. &&
  37. posInput
  38. .
  39. positionNDC
  40. .
  41. y
  42. <
  43. 0.5
  44. )
  45. finalColor
  46. =
  47. UNITY_MATRIX_I_VP
  48. [
  49. 1
  50. ]
  51. ;
  52. if
  53. (
  54. posInput
  55. .
  56. positionNDC
  57. .
  58. x
  59. <
  60. 0.5
  61. &&
  62. posInput
  63. .
  64. positionNDC
  65. .
  66. y
  67. >
  68. 0.5
  69. )
  70. finalColor
  71. =
  72. UNITY_MATRIX_I_VP
  73. [
  74. 2
  75. ]
  76. ;
  77. if
  78. (
  79. posInput
  80. .
  81. positionNDC
  82. .
  83. x
  84. >
  85. 0.5
  86. &&
  87. posInput
  88. .
  89. positionNDC
  90. .
  91. y
  92. >
  93. 0.5
  94. )
  95. finalColor
  96. =
  97. UNITY_MATRIX_I_VP
  98. [
  99. 3
  100. ]
  101. ;

下期预告

下一期分享,将会真正的去尝试制作一个勾边特效,并且介绍其中的原理等。最后,对于本文,求关注、点赞。对于B站视频,求关注、一键三连(点赞、投币、收藏)。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/101654
推荐阅读
相关标签
  

闽ICP备14008679号