在现代计算机图形学中,纹理映射技术的核心基础是纹理坐标系统,即UV坐标系统。本节将深入探讨UV坐标的理论原理、命名规范以及在着色器中的具体实现方法,为后续的纹理采样技术奠定理论基础。
UV 坐标的定义与应用
命名规范
纹理坐标系统采用UV命名规范而非传统的XY坐标命名,这一设计源于计算机图形学中对不同坐标系统的严格区分需求。在三维图形渲染中,XY轴通常用于表示空间位置坐标,为避免概念混淆和提高代码可读性,纹理坐标系统采用了独立的命名约定:
-
U轴:对应纹理的水平方向(X轴)
-
V轴:对应纹理的垂直方向(Y轴)
这种命名规范已成为图形学领域的标准惯例,确保了不同坐标系统之间的清晰区分。
坐标定义
UV坐标系统采用标准化的坐标范围[0,1],具有以下特征:
-
原点位置:纹理左下角为坐标原点(0,0)
-
坐标范围:U轴和V轴均在[0,1]区间内
-
增长方向:从左下角向右上角方向递增

标注了UV坐标的纹理
坐标映射
以图所示的纹理为例,片元着色器通过UV坐标(0.8,0.3)可精确访问纹理右下区域的羽毛颜色信息。这种映射机制使得着色器能够根据几何表面的位置动态获取对应的纹理颜色数据。
向网格添加 UV 坐标
在 openFrameworks 中,向网格添加 UV 坐标类似于添加颜色属性,使用 addTexCoord() 函数。需确保坐标顺序与顶点位置一致,且 V 轴向上递增以匹配窗口顶部:
// 按照逆时针顺序为四个顶点分配UV坐标
quad.addTexCoord(glm::vec2(0, 0)); // 左下角
quad.addTexCoord(glm::vec2(0, 1)); // 左上角
quad.addTexCoord(glm::vec2(1, 1)); // 右上角
quad.addTexCoord(glm::vec2(1, 0)); // 右下角
着色器实现UV坐标传递
顶点着色器
UV坐标在渲染管线中的传递机制遵循标准的顶点属性处理流程。顶点着色器负责接收CPU传递的UV坐标数据,并将其传递给片元着色器进行插值处理。
#version 410
layout(location = 0) in vec3 pos; // 顶点位置属性
layout(location = 3) in vec2 uv; // UV坐标属性
out vec2 fragUV; // 输出到片元着色器的UV坐标
void main()
{
gl_Position = vec4(pos, 1.0); // 标准位置变换
fragUV = uv; // UV坐标直接传递
}
顶点属性布局规范
OpenFrameworks框架对顶点属性采用了标准化的布局分配机制:
-
位置属性 (Position) - location = 0
-
颜色属性 (Color) - location = 1
-
法线属性 (Normal) - location = 2
-
UV坐标属性 - location = 3
这种布局规范确保了不同类型顶点数据的有序组织,提高了着色器程序的可维护性和跨平台兼容性。
片元着色器的UV可视化
片元着色器接收插值后的 UV 坐标,并将其映射为颜色输出(U → 红色通道,V → 绿色通道)。预期结果:角部颜色分别为红色、绿色、黄色和黑色,中间区域渐变混合:
#version 410
in vec2 fragUV; // 从顶点着色器接收的插值UV坐标
out vec4 outCol; // 最终输出颜色
void main()
{
// 将UV坐标映射为RGB颜色分量
outCol = vec4(fragUV, 0.0f, 1.0f);
}
渲染结果
颜色分布
执行上述着色器代码后,屏幕将呈现特定的颜色分布模式:
-
左下角:黑色 (UV: 0,0 → RGB: 0,0,0)
-
右下角:红色 (UV: 1,0 → RGB: 1,0,0)
-
左上角:绿色 (UV: 0,1 → RGB: 0,1,0)
-
右上角:黄色 (UV: 1,1 → RGB: 1,1,0)
这种颜色分布验证了UV坐标插值机制的正确性,为后续的纹理采样操作提供了可靠的坐标基础。
显示结果如下:

结论
UV坐标系统是纹理映射技术的核心基础设施。通过严格的命名规范、标准化的坐标范围以及高效的着色器传递机制,UV坐标系统为复杂的纹理采样操作提供了可靠的技术支撑。掌握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;
};
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
// 按照逆时针顺序为四个顶点分配UV坐标
quad.addTexCoord(glm::vec2(0, 0)); // 左下角
quad.addTexCoord(glm::vec2(0, 1)); // 左上角
quad.addTexCoord(glm::vec2(1, 1)); // 右上角
quad.addTexCoord(glm::vec2(1, 0)); // 右下角
ofIndexType indices[6] = { 0,1,2,2,3,0 };
quad.addIndices(indices, 6);
shader.load("passthrough.vert", "uv_vis.frag");
}
//--------------------------------------------------------------
void ofApp::update(){
}
//--------------------------------------------------------------
void ofApp::draw(){
shader.begin();
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());
}
passthrough.vert
#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 = uv;
}
uv_vis.frag
#version 410
in vec2 fragUV;
out vec4 outCol;
void main()
{
outCol = vec4(fragUV, 0.0f, 1.0f);
}
9176

被折叠的 条评论
为什么被折叠?



