高级后期处理
前言
自定义后期处理教程中介绍的方法。 Viewport , 然后将Viewport在一个 ViewportContainer 中渲染到主场景. 这个方法的一个限制是, 我们无法访问深度缓冲区, 因为深度缓冲区只在空间着色器中可用, 而视窗并不维护深度信息.
全屏四边形
custom post-processing tutorial 中, 我们介绍了如何使用视窗来制作自定义的后期处理效果. 使用视窗有两个主要缺点:
- 无法访问深度缓冲区
- 在编辑器中看不到后期处理着色器的效果
MeshInstance 并使用 QuadMesh 原语. 这允许我们使用空间着色器并访问场景的深度纹理. 接下来, 使用顶点着色器使四边形始终覆盖屏幕, 以便始终应用后期处理效果, 包括在编辑器中.
(0, 0, 0)为中心的四边形, 宽度和高度为1. 将宽度和高度设置为2. 现在, 这个四边形在世界空间中占据了原点的位置;但是, 我们希望它能随着摄像机的移动而移动, 这样它就能始终覆盖整个屏幕. 为此, 我们将绕过坐标转换, 该转换通过不同的坐标空间转换顶点位置, 并将顶点视为已位于裁剪空间中.-1到屏幕顶部和右侧的1的坐标. 这就是为什么QuadMesh需要有2的高度和宽度.Godot在幕后期处理从模型到视图空间到剪辑空间的转换, 所以我们需要使Godot的转换效果无效. 我们通过设置内置POSITION到我们想要的坐标来做到这一点.POSITION绕过内置变换, 直接设置顶点坐标.
即使有了这个顶点着色器, 四边形仍会消失. 这是由于视锥剔除, 它是在CPU上完成的.Frustum culling使用摄像机矩阵和Mesh的AABB来确定Mesh是否可见, 然后再传递给GPU.CPU不知道我们对顶点做了什么, 所以它认为指定的坐标指的是世界坐标, 而不是裁剪空间的坐标, 这导致Godot在我们旋转, 离开场景中心时对四边形进行剔除. 为了防止四边形被剔除, 有几个选项:shader_type spatial;void vertex() { POSITION = vec4(VERTEX, 1.0);}
- 将QuadMesh作为子节点添加到相机, 因此相机始终指向它
extra_cull_margin设置得尽可能大 第二个选项确保四边形在编辑器中可见, 而第一个选项保证即使摄像机移出剔除边缘, 它仍可见. 您也可以使用这两个选项.深度纹理
texture()和uniform变量DEPTH_TEXTURE进行纹理查询.
注解 与访问屏幕纹理类似, 访问深度纹理只有在从当前视窗读取时才能进行. 深度纹理不能从你已经渲染的另一个视窗中访问.float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
DEPTH_TEXTURE返回的值介于0和1之间, 并且是非线性的. 当直接从 “DEPTH_TEXTURE” 显示深度时, 除非它非常接近, 否则一切都会看起来几乎是白色的. 这是因为深度缓冲区使用比进一步更多的位来存储更靠近相机的对象, 因此深度缓冲区中的大部分细节都靠近相机. 为了使深度值与世界或模型坐标对齐, 我们需要将值线性化. 当我们将投影矩阵应用于顶点位置时,z值是非线性的, 所以为了线性化它我们将它乘以投影矩阵的倒数, 在Godot中可以用变量INV_PROJECTION_MATRIX访问它.-1到1, 类似于裁剪空间坐标. 使用SCREEN_UV来重建NDC的x和y轴, 以及z的深度值.void fragment() { float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x; vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;}
INV_PROJECTION_MATRIX, 将NDC转换成视图空间. 回顾一下, 视图空间给出了相对于相机的位置, 所以z值将给我们提供到该点的距离.void fragment() { ... vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0); view.xyz /= view.w; float linear_depth = -view.z;}
z方向的, 所以坐标会有一个负的z值. 为了得到一个可用的深度值, 我们必须否定view.z.CAMERA_MATRIX需要将坐标从视图空间转换到世界空间, 所以它需要以varying的方式传递给片段着色器.varying mat4 CAMERA;void vertex() { CAMERA = CAMERA_MATRIX;}void fragment() { ... vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0); vec3 world_position = world.xyz / world.w;}
优化
这里 . 但是, 这种好处非常小, 只有在运行特别复杂的片段着色器时才有用. ArrayMesh. ArrayMesh是一个工具, 允许您从顶点, 法线, 颜色等方便地从数组构造网格. 现在, 将脚本附加到MeshInstance并使用以下代码:
注解extends MeshInstancefunc _ready(): # Create a single triangle out of vertices: var verts = PoolVector3Array() verts.append(Vector3(-1.0, -1.0, 0.0)) verts.append(Vector3(-1.0, 3.0, 0.0)) verts.append(Vector3(3.0, -1.0, 0.0)) # Create an array of arrays. # This could contain normals, colors, UVs, etc. var mesh_array = [] mesh_array.resize(Mesh.ARRAY_MAX) #required size for ArrayMesh Array mesh_array[Mesh.ARRAY_VERTEX] = verts #position of vertex array in ArrayMesh Array # Create mesh from mesh_array: mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_array)
x和y方向都从-1到1运行. 这使得屏幕2单位宽,2单位高. 为了用一个三角形覆盖整个屏幕, 使用一个4单位宽和4单位高的三角形, 高度和宽度加倍. 从上面分配相同的顶点着色器, 所有内容应该看起来完全相同. 使用ArrayMesh而不是使用QuadMesh的一个缺点是ArrayMesh在编辑器中不可见, 因为在运行场景之前不会构造三角形. 为了解决这个问题, 在建模程序中构建一个三角形Mesh, 然后在MeshInstance中使用它.
