问题描述
我最近发现了使用 SNES 模式 7 的伪 3d 效果,并想尝试在 Godot 引擎中复制它.我尝试在网上环顾四周,但所有内容要么以我无法理解的方式解释,要么以我不知道的编程语言进行解释.我还需要学习如何旋转区域,并将精灵作为角色或敌人放入,但我没有找到任何关于这些的东西.有人可以解释这个公式,以及我如何实现它吗?
好的,我想通了.我将解释两种设置.
在我们开始之前,让我解释一下我们将使用的着色器代码:
shader_type canvas_item;均匀的 mat3 矩阵;无效片段(){vec3 uv = 矩阵 * vec3(UV, 1.0);颜色 = 纹理(纹理,uv.xy/uv.z);}
这是一个 canvas_item
着色器,因此它旨在在 2D 中工作.我们正在做的是将变换矩阵(作为uniform
传递)应用于纹理坐标(UV
).我们将结果存储在 uv
变量中.我们将使用它来对使用此着色器的任何节点的纹理进行采样……但是我们需要使用 uv
的 z
来实现透视效果.为此,我们将 uv.xy
除以 uv.z
.
但是,我想将它应用到纹理的中心.所以,让我在开始时减去 0.5
,并在最后加上 0.5
:
shader_type canvas_item;均匀的 mat3 矩阵;无效片段(){vec3 uv = 矩阵 * vec3(UV - 0.5, 1.0);颜色 = 纹理(纹理,(uv.xy/uv.z)+ 0.5);}
还有一件事.我不喜欢在极端值上我们看到反转的图像.因此,我会这样处理:
shader_type canvas_item;均匀的 mat3 矩阵;无效片段(){vec3 uv = 矩阵 * vec3(UV - 0.5, 1.0);如果 (uv.z
这里有一个给无分支爱好者的替代方案(不知道好不好):
shader_type canvas_item;均匀的 mat3 矩阵;无效片段(){vec3 uv = 矩阵 * vec3(UV - 0.5, 1.0);颜色 = 纹理(纹理,(uv.xy/uv.z)+ 0.5);COLOR.a *= sign(sign(uv.z) + 1.0);}
这里的 sign(uv.z)
将是 -1.0
、0.0
或 1.0
.>
然后 sign(uv.z) + 1.0
它将是 0.0
、1.0
或 2.0
.
最后 sign(sign(uv.z) + 1.0)
将是 0.0
或 1.0
(你可以使用 clamp(sign(uv.z), 0.0, 1.0)
代替,如果你愿意).因此 COLOR.a *= sign(sign(uv.z) + 1.0)
是将 alpha 乘以 0.0
在任何 uv.z
为负的地方.
注意:我在片段着色器中操作 UV 坐标而不是在顶点着色器中操作的原因是因为 Godot 正在为 2D 做仿射纹理映射.这会导致失真.这是一种解决方法.
第一个设置只是一个精灵.用你想要的任何纹理设置一个 Sprite
,并将材质设置为新的着色器材质,并在着色器中使用我在开始时显示的代码.
Godot 将为您提供编辑材质资源中 Shader Param
下的 uniform mat3 matrix
的选项.默认情况下,它将是单位矩阵,在编辑器中看起来像这样:
x 1 y 0 z 0x 0 y 1 z 0x 0 y 0 z 1
您可以使用它来应用旋转、缩放、剪切或透视变换.*我建议从改变z列(最右边的)的零开始,控制透视:
x 1 y 0 z 3d_rotate_horizontalx 0 y 1 z 3d_rotate_verticalx 0 y 0 z 比例
示例结果:
建议:不要使用一直延伸到边缘的纹理.当您应用透视时,着色器将读取边缘之外的内容,但默认它被钳制,这会导致拉伸纹理边缘的任何像素.
顺便说一句,如果您将图像导入为Image
(而不是默认的Texture
),您可以将Sprite 纹理设置为ImageTexture代码>,它将为您提供对纹理显示方式的一些额外控制,包括启用 mipmap、抗锯齿过滤器以及在其边缘之外重复纹理(镜像和非镜像).
第二个更复杂的设置是针对多个对象.这也是适用于 TileMap
的设置.您将需要这种树结构:
- Sprite2D+- 视口+- Camera2D+- 目标
将 Sprite2D
放在我们想要看到的位置,使用我在开始时显示的着色器代码为其提供着色器材质.顺便说一句,这也应该与 TextureRect
一起使用,以防您在 UI 中需要它.
不要为Sprite2D
(或TextureRect
)设置纹理.您将附加一个如下所示的脚本:
扩展精灵功能_就绪():var 视口 = $视口产量(get_tree(),空闲帧")产量(get_tree(),空闲帧")纹理 = viewport.get_texture()
如果需要,将 Sprite
更改为 TextureRect
.
此代码引用了 Viewport
节点,等待两帧(以确保 Viewport
纹理可用),然后获取纹理并将其分配给自己.
您需要为视口设置您想要的大小.另外我建议设置Transparent Bg
和V Flip
.Camera2D
可以保持其默认值.
最后目标"是你想展示的任何东西.它可以是一个或多个二维节点.我建议将其设为另一个场景,这样就可以轻松地独立于此设置对其进行编辑(Viewport
的任何子项在编辑器中都将不可见).
示例结果:
是的,我们可以用 Godot 中的实际 3D 存档相同的效果,没问题.但我们没有.我们选择用 2D 工具实现这种效果并做其他事情,不是因为它们容易,而是因为它们很难.
此答案中使用的纹理是公共域 (CC0),来自 Kenney.
I recently found out about the pseudo-3d effect that utilized SNES mode 7, and want to try to replicate it in the Godot Engine. I tried looking around online, but everything was either explained in a way i couldn't understand, or in a programming language I didn't know. I also need to learn how to rotate the area, and put sprites in as characters or enemies, but I didn't find anything on those. Can someone explain the formula, as well as how I could implement it?
Ok, I figured this out. There are two kinds of setup I'll explain.
Before we get to that, let me explain the shader code we will be using:
shader_type canvas_item;
uniform mat3 matrix;
void fragment()
{
vec3 uv = matrix * vec3(UV, 1.0);
COLOR = texture(TEXTURE, uv.xy / uv.z);
}
This is a canvas_item
shader, as such it is intended to work in 2D. What we are doing is applying a transformation matrix (passed as uniform
) to the texture coordinates (UV
). The result we are storing in the uv
variable. We are going to use it to sample the texture of whatever node is using this shader… However we need to use the z
of uv
to do a perspective effect. To do that we divide uv.xy
by uv.z
.
However, I want to apply it centered to the texture. So, let me subtract 0.5
at the start, and add the 0.5
back at the end:
shader_type canvas_item;
uniform mat3 matrix;
void fragment()
{
vec3 uv = matrix * vec3(UV - 0.5, 1.0);
COLOR = texture(TEXTURE, (uv.xy / uv.z) + 0.5);
}
One more thing. I don't like that on extreme values we see a reversed image. Thus, I'll handle that like this:
shader_type canvas_item;
uniform mat3 matrix;
void fragment()
{
vec3 uv = matrix * vec3(UV - 0.5, 1.0);
if (uv.z < 0.0) discard;
COLOR = texture(TEXTURE, (uv.xy / uv.z) + 0.5);
}
Here is an alternative for the branchless enthusiasts (I don't know if it is better):
shader_type canvas_item;
uniform mat3 matrix;
void fragment()
{
vec3 uv = matrix * vec3(UV - 0.5, 1.0);
COLOR = texture(TEXTURE, (uv.xy / uv.z) + 0.5);
COLOR.a *= sign(sign(uv.z) + 1.0);
}
Here sign(uv.z)
will be either -1.0
, 0.0
or 1.0
.
Then sign(uv.z) + 1.0
it will be either 0.0
, 1.0
or 2.0
.
Finally sign(sign(uv.z) + 1.0)
will be either 0.0
or 1.0
(you could use clamp(sign(uv.z), 0.0, 1.0)
instead if you prefer). And thus COLOR.a *= sign(sign(uv.z) + 1.0)
is multiplying alpha by 0.0
anywhere uv.z
is negative.
Note: The reason why I manipulate the UV coordinates in the fragment shader instead of doing it in the vertex shader is because Godot is doing affine texture mapping for 2D. Which would result in a distortion. This is a workaround.
The first setup is simply a sprite. Set a Sprite
with whatever texture you want, and set the material to a new shader material, and in the shader use the code I shown at the start.
Godot will give you the option to edit the uniform mat3 matrix
under Shader Param
in the material resource. By default it will be the identity matrix, which looks like this in the editor:
x 1 y 0 z 0
x 0 y 1 z 0
x 0 y 0 z 1
You can use it to apply a rotation, scaling, shearing or perspective transformations. *I suggest to start by changing the zeros of the z column (the rightmost one), the control the perspective:
x 1 y 0 z 3d_rotate_horizontal
x 0 y 1 z 3d_rotate_vertical
x 0 y 0 z scale
Example result:
A recommendation: Do not use a texture that goes all the way to the edge. When you apply perspective, the shader will read beyond the edge, but by default it is clamped, which result in stretching any pixels at the edge of the texture.
By the way, if you import images as Image
(instead of Texture
which is the default), you can set the Sprite texture as ImageTexture
which will give you some additional control on how the texture shows, including enabling mipmap, antialias filter, and repeating the texture beyond its edge (both mirrored and not mirrored).
The second, more complex setup, is for multiple objects. This is also the setup that works for a TileMap
. You are going to need this tree structure:
- Sprite2D
+- Viewport
+- Camera2D
+- target
Position the Sprite2D
where we want to see this, give it a shader material with the shader code I shown at the start. By the way, this should also work with a TextureRect
in case you need it in the UI.
Do not set a texture for the Sprite2D
(or TextureRect
). You are going to attach an script that looks like this:
extends Sprite
func _ready():
var viewport = $Viewport
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
texture = viewport.get_texture()
Change Sprite
to TextureRect
if needed.
This code is taking a reference to the Viewport
node, waiting two frames (to make sure the Viewport
texture is available) and then taking the texture and assigning it to itself.
You need to give the Viewport the size you want. Also I suggest to set Transparent Bg
and V Flip
. The Camera2D
can keep its defaults.
Finally "target" is whatever you want to show. It can be one or multiple 2D nodes. I suggest to make it another scene, that way it will be easy to edit it independently of this setup (whatever is child of the Viewport
will not be visible in the editor).
Example result:
Yes, we could have archived this same effect with actual 3D in Godot, no problem. But we didn't. We choose to implement this effect with 2D tools and do the other things, not because they are easy, but because they are hard.
The textures used in this answer are public domain (CC0), from Kenney.
这篇关于简单模式 7 公式/示例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!