当前位置: 首页 > ai >正文

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)); //redquad.addColor(ofDefaultColorType(0, 1, 0, 1)); //greenquad.addColor(ofDefaultColorType(0, 0, 1, 1)); //bluequad.addColor(ofDefaultColorType(1, 1, 1, 1)); //whitequad.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 verticalglSettings.windowMode = OF_WINDOW;glSettings.setGLVersion(4, 1);ofCreateWindow(glSettings);printf("%s\n", glGetString(GL_VERSION));ofRunApp(new ofApp());}

scrolling_uv.vert

#version 410layout (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 410uniform sampler2D parrot;in vec2 fragUV;
out vec4 outCol;void main()
{outCol = texture(parrot, fragUV);
}
http://www.xdnf.cn/news/19095.html

相关文章:

  • 【基础知识】互斥锁、读写锁、自旋锁的区别
  • 控制系统仿真之PID校正-PID校正(八)
  • 动手实现多元线性回归
  • 医疗 AI 的 “破圈” 时刻:辅助诊断、药物研发、慢病管理,哪些场景已落地见效?
  • 鸿蒙FA/PA架构:打破设备孤岛的技术密钥
  • Mysql基本语句(二)
  • 解决 jsdelivr CDN不可用问题
  • GTSAM中gtsam::LinearContainerFactor因子详解
  • Acrobat Pro DC 2025安装包下载及详细安装教程,PDF编辑器永久免费中文版(稳定版安装包)
  • Android 短信验证码输入框实现
  • 嵌入式Linux驱动开发:定时器驱动
  • 北斗传输采集数据的自定义通信协议
  • 香港电讯创新解决方案,开启企业数字化转型新篇章
  • CollageIt:简单易用的照片拼贴工具
  • Spring boot 启用第二数据源
  • 【Day 40】Shell脚本-条件判断
  • linux中.tar 解压命令
  • 【系列05】端侧AI:构建与部署高效的本地化AI模型 第4章:模型量化(Quantization)
  • 嵌入式Linux驱动开发 - DTS LED驱动
  • 管家婆辉煌ERP中如何查询畅销商品
  • java8浮点型算平均值
  • 37 HTB Remote 机器 - 容易
  • 字典解密助手ArchiveHelperWpfv1.0.12详细使用说明书
  • Apisix工作流程
  • 界面钝化新策略:华南理工实现泡沫铜/Bi-In相变材料热循环性能显著增强
  • 直流电机驱动与TB6612
  • Excel数组学习笔记
  • 【开题答辩全过程】以 基于JSP的养生网站系统为例,包含答辩的问题和答案
  • 本地部署商业服务器 Glassfish 并实现外部访问
  • Rust 安装与运行指南