Shader开发(十八)实现纹理滚动效果

在图形渲染中,通过操纵 UV 坐标实现动态效果是常见技术。本章节探讨如何调整顶点着色器中的 UV 坐标,以创建纹理滚动的视觉效果,类似于跑马灯。通过设置纹理的 wrap mode 和引入时间统一变量,实现纹理的连续滚动。


操纵 UV 坐标实现滚动

在传统的纹理映射中,四边形的UV坐标与纹理的标准坐标完全对应,均限制在[0,1]的标准化范围内。然而,着色器系统支持对超出此范围的坐标进行采样操作。

当UV坐标超出标准范围时,最终的视觉效果取决于纹理的Wrap Mode配置。

为了演示UV坐标偏移的基本原理,首先创建一个基础的顶点着色器实现。

#version 410
layout(location = 0) in vec3 pos;
layout(location = 3) in vec2 uv;

out vec2 fragUV;

void main()
{
    gl_Position = vec4(pos, 1.0);
    fragUV = vec2(uv.x, 1.0 - uv.y) + vec2(0.25, 0.0);  // UV坐标偏移
}

通过向UV坐标添加固定偏移向量vec2(0.25, 0.0),四边形的纹理坐标范围从原始的[0,1]扩展至[0.25,1.25]。这种坐标扩展使得屏幕左侧顶点的X分量UV坐标达到0.25以上,而右侧顶点的X分量则达到1.25,超出了标准纹理坐标范围。

图1 加上偏移UV坐标的鹦鹉纹理


设置纹理 Wrap Mode

在默认配置下,GPU采用截取(Clamp)模式处理超出[0,1]范围的UV坐标。具体而言,任何大于1.0的坐标值都被强制截取为1.0,小于0.0的值则被截取为0.0。这种处理方式导致纹理边缘像素在超出范围的区域内重复显示,产生图1所示的视觉效果。
对于动态滚动效果的实现需求,Clamp模式显然无法满足要求,因为它无法实现纹理的连续循环显示。

为实现理想的滚动效果,需要将纹理环绕模式配置为Repeat(重复)模式。在此模式下,坐标值1.25会被自动转换为0.25,从而实现纹理的无缝循环。

setup() 函数中设置:

void ofApp::setup()
{
    // 其他初始化代码省略
    img.load("parrot.png");
    img.getTexture().setTextureWrap(GL_REPEAT, GL_REPEAT);  // 设置重复模式
}

setTextureWrap()函数接受两个参数,分别用于配置水平和垂直方向的环绕模式。在本实现中,两个方向均设置为GL_REPEAT模式。需要注意的是,此处首次需要通过getTexture()方法获取底层的ofTexture对象,以便直接操作纹理参数。

图2 全屏四边形上使用"REPEAT"wrap mode的鹦鹉纹理


引入时间统一变量实现动画

着色器本身不具备时间感知能力,因此需要通过统一变量机制从CPU端传递时间信息。由于时间值在单次渲染过程中保持恒定,使用统一变量是最适合的数据传输方式。

为使滚动随时间变化,在顶点着色器中添加时间统一变量:

#version 410
layout(location = 0) in vec3 pos;
layout(location = 3) in vec2 uv;

uniform float time;      // 时间统一变量
out vec2 fragUV;

void main()
{
    gl_Position = vec4(pos, 1.0);
    fragUV = vec2(uv.x, 1.0 - uv.y) + vec2(1.0, 0.0) * time;  // 时间驱动的偏移
}

在时间驱动的实现中,偏移向量被设计为vec2(1.0, 0.0) * time。这里使用单位向量(1.0, 0.0)作为基础方向向量,通过与时间值相乘来控制偏移幅度。

当应用程序运行t秒时,UV坐标的偏移量为(t, 0),实现了线性的水平滚动效果。这种设计确保了动画的连续性和可预测性。

完整的时间驱动系统还需要CPU端的配合,通过统一变量将当前时间传递给着色器。

draw() 函数中传递时间值:

void ofApp::draw() {
    shader.begin();
    shader.setUniformTexture("parrot", img, 0);
    shader.setUniform1f("time", ofGetElapsedTimef());  // 传递运行时间
    quad.draw();
    shader.end();
}

ofGetElapsedTimef()函数返回自程序启动以来经过的秒数,为着色器提供了连续的时间基准。通过setUniform1f()方法,这个时间值被传递给着色器的time统一变量。

完成上述技术实现后,程序将展现出纹理沿水平方向连续滚动的动态效果。由于Repeat环绕模式的配置,纹理在滚动过程中实现了无缝循环,创造出类似跑马灯的视觉效果。


小结

本文演示了 UV 坐标滚动的技术,包括偏移、wrap mode 配置及时间动画。UV坐标的动态操控技术为着色器开发提供了强大的创意表达工具。通过对纹理坐标的数学变换,开发者可以在保持几何复杂度不变的前提下,创造出丰富多样的视觉效果。


项目代码参考

ofApp.h

#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp {

public:
    void setup();
    void update();
    void draw();

    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y);
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void mouseEntered(int x, int y);
    void mouseExited(int x, int y);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);

    ofMesh quad;
    ofShader shader;
    ofImage img;
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup()
{
    quad.addVertex(glm::vec3(-1, -1, 0));
    quad.addVertex(glm::vec3(-1, 1, 0));
    quad.addVertex(glm::vec3(1, 1, 0));
    quad.addVertex(glm::vec3(1, -1, 0));

    quad.addColor(ofDefaultColorType(1, 0, 0, 1)); //red
    quad.addColor(ofDefaultColorType(0, 1, 0, 1)); //green
    quad.addColor(ofDefaultColorType(0, 0, 1, 1)); //blue
    quad.addColor(ofDefaultColorType(1, 1, 1, 1)); //white

    quad.addTexCoord(glm::vec2(0, 1));
    quad.addTexCoord(glm::vec2(0, 0));
    quad.addTexCoord(glm::vec2(1, 0));
    quad.addTexCoord(glm::vec2(1, 1));

    ofIndexType indices[6] = { 0,1,2,2,3,0 };
    quad.addIndices(indices, 6);

    shader.load("scrolling_uv.vert", "texture.frag");

    ofDisableArbTex();
    img.load("parrot.png");
    img.getTexture().setTextureWrap(GL_REPEAT, GL_REPEAT);  // 设置重复模式
}


//--------------------------------------------------------------
void ofApp::update() {

}

//--------------------------------------------------------------
void ofApp::draw() {
    shader.begin();
    shader.setUniformTexture("parrot", img, 0);
    shader.setUniform1f("time", ofGetElapsedTimef());  // 传递运行时间
    quad.draw();
    shader.end();
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key) {

}

//--------------------------------------------------------------
void ofApp::keyReleased(int key) {

}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h) {

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg) {

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo) {

}

main.cpp

#include "ofMain.h"
#include "ofApp.h"

//========================================================================
int main() {
    ofGLWindowSettings glSettings;
    glSettings.setSize(1024, 768); //was 748 vertical
    glSettings.windowMode = OF_WINDOW;
    glSettings.setGLVersion(4, 1);
    ofCreateWindow(glSettings);

    printf("%s\n", glGetString(GL_VERSION));
    ofRunApp(new ofApp());

}

scrolling_uv.vert

#version 410

layout (location = 0) in vec3 pos; 
layout (location = 3) in vec2 uv;

uniform float time;
out vec2 fragUV;

void main()
{
	gl_Position = vec4(pos, 1.0);
//	fragUV = uv + vec2(0.25, 0.0);
    // 执行Y坐标翻转:将[0,1]范围内的Y坐标反转
    // fragUV = vec2(uv.x, 1.0 - uv.y);
    
    fragUV = uv + vec2(1.0,0.0)*time;
}

texture.frag

#version 410

uniform sampler2D parrot;

in vec2 fragUV;
out vec4 outCol;

void main()
{
	outCol = texture(parrot, fragUV);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值