title: 纹理滚动着色器
致谢:本教程由论坛用户MasterMind贡献(原论坛帖子)。
通过着色器滚动纹理是许多着色器效果中的基本技术。让我们来制作一个!使用示例项目来跟随并亲自尝试。将要使用的方法是在着色器中使用常量进行UV偏移。
对于那些想要查看这个简短教程/指南结果的人,还可以在itch.io上查看示例项目的演示:
项目设置如下:
water_bg.png
和water_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_texcoord0
和var_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_textcoord0
和var_texcoord1
,然后我们为我们在模型和材质中设置的采样器纹理提供uniform,它们被命名为tex0
和tex1
。然后在void main()
中,我们创建向量4来使用texture2d()
分配给我们的纹理,图像是RGBA(红、绿、蓝、alpha)通道格式。我们分配采样器名称,然后是我们希望它们使用的纹理坐标。正如你在上图中看到的,"water_waves"分配了var_texcoord1
。这是我们正在动画化/滚动的纹理,而分配给water_bg
的var_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