texture-scrolling.md 6.9 KB


title: 纹理滚动着色器

brief: 在本教程中,你将学习如何使用着色器来滚动重复纹理。

致谢:本教程由论坛用户MasterMind贡献(原论坛帖子)。

纹理滚动着色器教程

通过着色器滚动纹理是许多着色器效果中的基本技术。让我们来制作一个!使用示例项目来跟随并亲自尝试。将要使用的方法是在着色器中使用常量进行UV偏移。

对于那些想要查看这个简短教程/指南结果的人,还可以在itch.io上查看示例项目的演示

设置

项目设置如下:

  • 一个细分的3D平面(.dae),将用于显示滚动的纹理。
  • 一个分配了3D平面(.dae文件)的模型组件。
  • 两个64x64的纹理图像(water_bg.pngwater_wave.png),创建为无缝平铺,在.model属性中分配。
  • 一个分配给平面模型的着色器(材质+顶点程序+片段程序)。
  • 一个在材质和着色器中设置的常量。
  • 一个附加到模型游戏对象的脚本组件,用于启动动画循环。

注意:water_bg分配给tex0插槽,water_wave设置到tex1插槽。在下面显示的材质采样器属性中分配了两个采样器插槽。

当前tex1将是滚动的纹理,因此Wrap U & V设置为Repeat。

这就是基本设置,现在我们可以继续处理water_scroll.vp(顶点)和water_scroll.fp(片段)程序。你可以在上图中看到这些被设置到材质中。

着色器代码

如果可以避免,最好在片段程序中避免进行过多的计算,所以我们在顶点程序中计算UV偏移,然后再将坐标发送到片段程序。我们还在材质中创建了一个类型为user的常量"animation_time"(如上所示)。该常量是一个向量4,但我们只使用第一个值。如果我们把这个向量4表示为vector4(x,y,z,w),我们将在着色器中只使用x值,如下所示。

// water_scroll.vp

// UV / 纹理滚动
attribute highp vec4 position;
attribute mediump vec2 texcoord0;

uniform mediump mat4 mtx_worldview;
uniform mediump mat4 mtx_proj;
uniform mediump vec4 animation_time; // 在材质中设置为类型user的顶点常量。

varying mediump vec2 var_texcoord0; // 设置变量纹理坐标0
varying mediump vec2 var_texcoord1; // 设置变量纹理坐标1

void main()
{
    vec4 p = mtx_worldview * vec4(position.xyz, 1.0);
    var_texcoord0 = texcoord0;
    var_texcoord1 = vec2(texcoord0.x - animation_time.x, texcoord0.y); // 计算变量纹理坐标1在U(x)轴上的UV偏移,发送到片段程序
    gl_Position = mtx_proj * p;
}

模型提供了属性texcoord0,这是我们的纹理UV坐标。我们声明了名为animation_time的vec4 uniform,还有两个vec2 varying变量var_texcoord0var_texcoord1,我们在void main()中为它们分配属性UV坐标texcoord0后将它们传递给片段程序。正如你所见,var_texcoord1是不同的,因为我们在发送到片段程序之前对其进行了偏移。分配一个vec2,这样我们可以根据需要将animation_time分别分配给x和y。在这种情况下,我们只取texcoord0.x并减去我们的常量animation_time.x,当动画化时,这将在负U轴(水平向左)上偏移,我们设置texcoord0.y以保持其属性位置。

// water_scroll.fp

varying mediump vec2 var_texcoord0; // 变量纹理坐标0与water_bg采样器一起使用
varying mediump vec2 var_texcoord1; // 变量纹理坐标1与water_waves采样器一起使用,UV动画计算在顶点程序中完成

uniform lowp sampler2D tex0; // 材质采样器插槽0 = 水背景 / 在plane.model中设置
uniform lowp sampler2D tex1; // 材质采样器插槽1 = 水波纹 / 在plane.model中设置

void main()
{
    vec4 water_bg = texture2D(tex0, var_texcoord0.xy);
    vec4 water_waves = texture2D(tex1, var_texcoord1.xy);
    
    gl_FragColor = vec4(water_bg.rgb + water_waves.rgb ,1.0); // 使用加法(+)将纹理波纹添加到背景,alpha设置为1.0,因为没有使用透明度
}

现在到片段程序,我们有一个简单的设置。两个"in" varying vec2变量,我们从顶点程序发送的var_textcoord0var_texcoord1,然后我们为我们在模型和材质中设置的采样器纹理提供uniform,它们被命名为tex0tex1。然后在void main()中,我们创建向量4来使用texture2d()分配给我们的纹理,图像是RGBA(红、绿、蓝、alpha)通道格式。我们分配采样器名称,然后是我们希望它们使用的纹理坐标。正如你在上图中看到的,"water_waves"分配了var_texcoord1。这是我们正在动画化/滚动的纹理,而分配给water_bgvar_texcoord0我们保持原样。对于全局保留变量gl_FragColor,这是像素颜色被分配的地方,使用相同的vec4(r,g,b,a)格式。我们想要将两个纹理组合在一起,所以我们使用加法将每个纹理的rgb通道混合在一起,此外我们没有使用纹理的alpha通道,所以我们分配一个1.0的浮点值,这等于完全不透明。

着色器动画脚本

-- animate_shader.script
local animate = 1.0
-- local float将用于设置滚动材质中的animation_time常量,着色器中只使用x常量值
-- 所以没有必要创建向量4

function init(self)
    go.animate("/scroll#plane", "animation_time.x", go.PLAYBACK_LOOP_FORWARD, animate, go.EASING_LINEAR, 4.0)
end

动画化常量值的方法不止一种,如果你愿意,可以在渲染脚本或普通脚本中计算时间步长并使用这些值更新常量。在这种情况下,我们只动画化一个着色器,如果你想要在几个着色器中有一个时间步长,在update()中计算可能更理想。然而我们将使用go.animate(),因为我们可以使用很多功能。使用go.animate(),我们可以只使用"animation_time.x"来动画化我们常量的x值。如果我们选择,我们还可以使用持续时间、延迟和缓动。我们还可以设置播放为循环或播放一次,并在需要时取消动画。这些在动画化我们的着色器时都非常方便。

local animate浮点数1.0是我们在"animation_time.x"中动画化的目标值。在着色器中,我们大多处理标准化的浮点值0.0到1.0。注意材质中我们的animation_time的默认常量值是(0,0,0,0),我们正在将第一个零从0.0动画化到1.0。这意味着我们的偏移UV坐标将动画化到边缘,然后一次又一次地循环,这正是我们想要的!

下一步

作为练习,你可以尝试在相反方向动画化water_bg纹理坐标,就像示例演示中一样!

希望这对你有帮助。如果你制作了滚动的效果,请分享!

/ MasterMind