使用视口

前言

将视口 Viewport 想象为展示游戏画面的屏幕。为了看到游戏,我们需要首先在一块表面上绘制游戏。这块表面就是“根视口”。

../../_images/subviewportnode.webp

SubViewport 是子视口节点,这也是一种 Viewport,能够在场景中添加,这样我们就有了多个可以用来绘图的表面。如果我们在 SubViewport 上绘图,那这个子视口就叫做“渲染目标”。我们可以通过对应的 texture 来访问渲染目标的内容。使用 SubViewport 作为渲染目标,我们就可以同时渲染多个场景,也可以将画面渲染到视口纹理 ViewportTexture 上,从而在场景中的物体上显示,实现动态天空盒之类的效果。

SubViewport 的用处有很多,包括:

  • 在2D游戏中渲染3D物体

  • 在3D游戏中渲染2D元素

  • 渲染动态纹理

  • 在运行时生成程序式纹理

  • 在同一场景中渲染多个摄像机

所有这些用例的共同点是, 你被赋予了在纹理上绘制物体的能力, 就好像它是另一个屏幕一样, 然后可以选择如何处理产生的纹理.

Another kind of Viewports in Godot are Windows. They allow their content to be projected onto a window. While the Root Viewport is a Window, they are less flexible. If you want to use the texture of a Viewport, you'll be working with SubViewports most of the time.

输入

Viewport 还负责向其子节点传递经过适当调整和缩放的输入事件。默认情况下,SubViewport 不会自动接收输入,除非它们从其直接 SubViewportContainer 父节点接收输入。在这种情况下,可以使用 Disable Input 属性来禁用输入。

../../_images/input.webp

关于 Godot 如何处理输入的更多信息,请阅读《输入事件教程》。

Listener

Godot supports 3D sound (in both 2D and 3D nodes). More on this can be found in the Audio Streams Tutorial. For this type of sound to be audible, the Viewport needs to be enabled as a listener (for 2D or 3D). If you are using a SubViewport to display your World3D or World2D, don't forget to enable this!

摄像机(2D 和 3D)

当使用 Camera3DCamera2D 时,它将始终显示在最近的父级 Viewport 上(朝向根节点)。例如,在下面的层次结构中:

../../_images/cameras.webp

CameraA 将显示在根 Viewport 上,并且它将绘制 MeshACameraB 将与 MeshB 一起被 SubViewport 捕获。即使 MeshB 位于场景层次结构中,它仍然不会被绘制到根视口。同样,MeshA 在子视口中不可见,因为子视口仅捕获层次结构中位于其下方的节点。

一个 Viewport 只能有一个活动相机,因此存在多个相机时,请确保所需的那个已经设置了 current 属性,或通过调用以下命令使其成为当前相机:

camera.make_current()

默认情况下,相机将渲染其世界中的所有对象。在 3D 中,相机可以使用其 cull_mask 属性与 VisualInstance3Dlayer 属性结合来限制哪些对象被渲染。

缩放和拉伸

SubViewports 有一个 size 属性,该属性表示 SubViewport 的像素大小。对于 SubViewportContainers 的子级 SubViewport,这些值会被覆盖,但对于所有其他 SubViewport,这会设置它们的分辨率。

还可以缩放 2D 内容并使 SubViewport 分辨率与大小指定的分辨率不同,可通过调用:

sub_viewport.set_size_2d_override(Vector2i(width, height)) # Custom size for 2D.
sub_viewport.set_size_2d_override_stretch(true) # Enable stretch for custom size.

有关使用根视口进行缩放和拉伸的信息,请访问《多分辨率教程

世界

For 3D, a Viewport will contain a World3D. This is basically the universe that links physics and rendering together. Node3D-based nodes will register using the World3D of the closest Viewport. By default, newly created Viewports do not contain a World3D but use the same as their parent Viewport. The Root Viewport always contains a World3D, which is the one objects are rendered to by default.

可以使用 World 3D 属性在 Viewport 中设置 World3D,这将分离该 Viewport 的所有子节点,并阻止它们与父 Viewport 的 World3D 交互。这在以下场景中特别有用,例如,你可能希望在游戏中以 3D 形式显示单独的角色(像在星际争霸中一样)。

当你想要创建显示单个对象的 Viewport 而不想创建 World3D 时,作为帮助工具,Viewport 可以选择使用它自己的 World3D。当你想要在 World2D 中实例化 3D 角色或对象时,这很有用。

对于 2D,每个 Viewport 始终包含它自己的 World2D。这在大多数情况下就足够了,但如果需要共享它们,可以通过代码在 Viewport 上设置 world_2d 来实现。

关于如何工作的例子, 请分别参阅演示项目 3D in 2D2D in 3D .

捕获

可以查询 Viewport 内容的捕获。对于根 Viewport,这实际上是一个屏幕截图。这可以通过以下代码完成:

# Retrieve the captured Image using get_image().
var img = get_viewport().get_texture().get_image()
# Convert Image to ImageTexture.
var tex = ImageTexture.create_from_image(img)
# Set sprite texture.
sprite.texture = tex

但是如果你在 _ready() 中使用, 或者从 Viewport 的 初始化的第一帧开始使用, 你会得到一个空的纹理, 因为没有什么可以作为纹理获得. 你可以用来处理它, 例如:

# Wait until the frame has finished before getting the texture.
await RenderingServer.frame_post_draw
# You can get the image after this.

视口容器

如果 SubViewportSubViewportContainer 的子项,它将变为活动状态并显示其内部的所有内容。布局看起来像这样:

../../_images/container.webp

如果在 SubViewportContainer 中将 Stretch 设置为 true,则 SubViewport 将完全覆盖其父级 SubViewportContainer 的区域。

备注

SubViewportContainer 的大小不能小于 SubViewport 的大小。

渲染

由于 Viewport 是进入另一个渲染表面的入口,因此它公开了一些可能与项目设置不同的渲染属性。你可以选择为每个 Viewport 使用不同级别的 MSAA。该默认行为是 Disabled

If you know that the Viewport is only going to be used for 2D, you can Disable 3D. Godot will then restrict how the Viewport is drawn. Disabling 3D is slightly faster and uses less memory compared to enabled 3D. It's a good idea to disable 3D if your viewport doesn't render anything in 3D.

备注

如果你需要在视口中渲染 3D 阴影,请确保将视口的 positional_shadow_atlas_size 属性设置为大于 0 的值。否则,阴影将不会被渲染。默认情况下,等效项目设置在桌面平台上设置为 4096,在移动平台上设置为 2048

Godot 还提供了一种使用 Debug Draw 自定义 Viewport 中所有内容绘制方式的方法。Debug Draw 允许你指定一种模式,该模式决定了 Viewport 将如何显示其中绘制的内容。默认情况下,Debug Draw 处于 Disabled 状态。其他一些选项包括 UnshadedOverdrawWireframe。有关完整列表,请参阅《Viewport 文档》。

  • Debug Draw = Disabled(默认):场景正常绘制。

../../_images/default_scene.webp
  • Debug Draw = Unshaded:无阴影绘制场景时不使用照明信息,因此所有物体都以其反照颜色扁平地显示。

../../_images/unshaded.webp
  • Debug Draw = Overdraw:过度绘制使用加法混合将网格绘制成半透明的,以便你可以看到网格是如何重叠的。

../../_images/overdraw.webp
  • Debug Draw = Wireframe:线框仅使用网格中三角形的边来绘制场景。

../../_images/wireframe.webp

备注

使用兼容性渲染方法时,目前支持调试绘制模式。它们将显示为常规绘制模式。

渲染目标

When rendering to a SubViewport, whatever is inside will not be visible in the scene editor. To display the contents, you have to draw the SubViewport's ViewportTexture somewhere. This can be requested via code using (for example):

# This gives us the ViewportTexture.
var tex = viewport.get_texture()
sprite.texture = tex

或者可以通过选择"New ViewportTexture"在编辑器中指定它

../../_images/texturemenu.webp

然后选择你想要使用的 Viewport.

../../_images/texturepath.webp

每一帧,Viewport 的纹理都会使用默认清除颜色(如果 Transparent BG 设置为 true,则使用透明颜色)清除。 这可以通过将 Clear Mode 设置为 NeverNext Frame 来更改。顾名思义,Never 表示纹理永远不会被清除,而 next frame 将在下一帧清除纹理并将自身设置为 Never。

默认情况下,SubViewport 的重新渲染发生在其 ViewportTexture 被绘制的那一帧之时。可见则会渲染,否则不会。可以通过将 Update Mode 设置为 NeverOnceAlwaysWhen Parent Visible 来更改此行为。Never 和 Always 分别表示永不或始终重新渲染。Once 将重新渲染下一帧,之后更改为 Never。这可用于手动更新视口。这种灵活性允许用户渲染一次图像,然后使用该纹理,而无需承担渲染每一帧的成本。

备注

请务必查看 Viewport 演示项目。它们位于演示存档的 viewport 文件夹中,或位于 https://github.com/godotengine/godot-demo-projects/tree/master/viewport