可以把视口理解成接收游戏投影的一个屏幕。为了能够看到游戏内容,我们需要一个表面去绘制它;那个表面就是根视口(root viewport)。
视口也可以被添加到场景(节点)上,这样就会有多个可绘制投影的表面了。当我们要绘制一个非根的视口时,我们管它叫渲染目标(render target)。我们可以通过访问渲染目标对应的纹理来访问渲染目标的内容。把视口作为渲染目标使用,我们可以要么同时渲染多个场景要么渲染到一个场景中应用到某对象上的纹理上,比如动态天空盒(dynamic skybox)。
视口有很多种应用场景,包括:
将3D渲染到2D游戏
将2D渲染到3D游戏
渲染动态纹理
在运行时生成程序纹理
同一个场景中渲染多个摄像机
所有这些应用场景的共同特征是你可以把对象画到一个纹理上,就好象它是另一个场景,并且可以选择如何使用结果纹理。
视口也负责向其全部子节点传递输入事件。一般来讲,输入事件会被**节点树(tree)**上最近的视口接收,但是你可以通过勾选Disable Input
为On
将视口设置为不接受输入事件。这样将允许节点树上剩下节点中最近的那个获取输入事件。
更多关于Godot对输入事件处理方式的内容,请参阅输入事件教程。
Godot支持3D音频(2D和3D节点都支持);更多关于3D音频的内容请参阅音频流教程。如果想让3D音频被接收到,需要将视口设置为一个监听者(对2D或3D)。如果你使用自定义视口来显示的你的**世界(World)**,不要忘了将它开启。
当使用Camera
或Camera2D
时,摄像机将永远显示在父节点中最近的那个视口(即像root方向寻找Viewport
)。例如如下层级图:
CameraA
会显示在根视口(Root Viewport)并且会绘制MeshA
.CameraB
会显示在名为Viewport
的视口并且会绘制MeshB
。尽管MeshB
在场景的层级图中,它也不会被绘制到根视口,同理MeshA
也不会被绘制到Viewport
视口,因为Viewport
只会显示层级图中在它下面的节点。
每一个Viewport
只有一个起作用的摄像机,所以如果有多个摄像机,请确保想要起作用的那个勾选current
属性。或者运行时调用下面的代码将其设置为当前摄像机:
camera.make_current()
视口的size
属性表示它的以像素为单位的大小。如果一个视口是ViewportContainer
节点的子节点,它的size
值将被重置,但是对于其它情况,这个size
决定它们的分辨率(resolution)。
我们也可以通过如下代码缩放2D内容以及改变Viewport
的分辨率大小:
viewport.set_size_override(true, Vector2(width, height)) # Custom size for 2D
viewport.set_size_override_stretch(true) # Enable stretch for custom size.
根视口的缩放设置选项在“project settings -> stretch options”中设置,获取更多关于缩放和拉伸的内容,请参阅Multiple Resolutions Tutorial 。
对于3D,一个视口会包含一个世界。这是将物理和渲染连接在一起的宇宙。3D节点(Spatial-base node)会注册到所属视口的世界中。默认情况下,新创建的视口并不包含世界,但会共享其父视口的世界(根视口永远会包含一个世界,即对象默认渲染到的那个世界)。可以通过视口的world
属性来设置世界,(拥有了自己的世界以后)此视口的所有子节点将从父视口的世界中分离出来,不再与父视口的世界有交互。这种机制在某些情景下非常有用,例如:3D中,你想独立显示一个角色于整个游戏之上(好像星际争霸中那样)。
有时候,你只像创建一个显示独立物对象的视口,但不想创建一个世界,这时可以用到一个简便方法,视口有一个选项允许你使用它自己的世界。这非常游泳,尤其是当你想实例化一个3D角色或对象在2D世界。
对于2D,每个视口都包含自己的World2D
。这对大多数情况都适用,有时如果想共享世界,可以手动设置视口的World2D
。
想获取实例,请查阅3D in 2D和2D in 3D。
可以获取一个视口内容的快照。对于根视口,这实际上就是屏幕快照。代码如下:
# Retrieve the captured Image using get_data().
var img = get_viewport().get_texture().get_data()
# Flip on the y axis.
# You can also set "V Flip" to true if not on the Root Viewport.
img.flip_y()
# Convert Image to ImageTexture.
var tex = ImageTexture.new()
tex.create_from_image(img)
# Set Sprite Texture.
$sprite.texture = tex
但是如果你在_ready()
中或者视口初始化的第一帧中使用的话,将得到一个空纹理,因为获取不到纹理。你可以用以下方式解决这个问题:
# Let two frames pass to make sure the screen can be captured.
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
# You can get the image after this.
如果返回的图像是空,快照没有发生,则稍等一会儿,因为Godot的渲染API是异步(asynchronous)的,请参看Demo Screen Capture example。
如果视口是一个视口容器的子节点,它将是起作用的(active)并且会显示其内的任何内容。节点结构如下:
如果视口容器的拉伸属性被设置为true
,此时视口会覆盖其父节点视口容器的全部内部区域。注意:视口容器的尺寸不能小于视口的尺寸。
实际上视口就是通往另一个渲染表面(rendering surface)的入口,因此它暴露了一些渲染属性。首先是MSAA
;你可以对每一个视口选择不同级别的MSAA
;默认情况下是DISABLED
。你也可以让视口使用HDR
。当你存储在纹理的值域超出0.0-1.0
时HDR
会非常有用。
Godot也提供了一种自定义方式,使用Debug Draw
来决定如何绘制视口中的内容。默认情况下Debug Draw
是关闭的。
Debug Draw 关闭
另外三个选项是:Unshaded
,Overdraw
和Wireframe
Unshaded
模式将不使用光照信息来绘制,此时所有物体看起来都是平的,且只有其漫反射颜色。
使用Unshaded
绘制同样的场景
Overdraw
以半透明模式来绘制网格模型,你可以观察到网格模型之间的重叠情况。
使用Overdraw
绘制同样的场景
最后,Wireframe
仅仅使用网格模型的三角形边线来绘制。
当像视口绘制的时候,无论视口中是什么,都不会显示在场景编辑器中。如果要显示内容,你要把视口的视口纹理(ViewportTexture)绘制到某个地方。例如:
# This gives us the ViewportTexture.
var rtt = viewport.get_texture()
sprite.texture = rtt
或者,在编辑器中通过选择“New ViewportTexture”来指定。
然后选择你要的视口。
每一帧,视口的纹理都会被默认“清理色”(或者如果Transparent BG
被设置为true
,该颜色是透明的)清理。这个可以通过Clear Mode
来改变,比如设置为Never
或Next Frame
。Never
意味着纹理永远不会被清理,Next Frame
意味着会在下一帧被清除,然后将自己设为Never
。
默认地,对视口的重绘会发生在视口为例已经在一帧中被绘制。如果是可见的,它将被绘制;否则不会。这个行为可以通过改为手动渲染(一次)或者永远渲染,无论它是否可见。这种灵活的机制允许用户把一个图片渲染一次,然后在不用每帧都渲染的情况下使用其纹理。
请一定看一下视口的范例。