Shader开发(十六)UV 坐标介绍

在现代计算机图形学中,纹理映射技术的核心基础是纹理坐标系统,即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框架对顶点属性采用了标准化的布局分配机制:

  1. 位置属性 (Position) - location = 0

  2. 颜色属性 (Color) - location = 1

  3. 法线属性 (Normal) - location = 2

  4. 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);
}
### 实现UV世界坐标平铺效果 在Unity中,为了基于世界坐标实现纹理的平铺效果,可以利用顶点的世界位置来计算新的UV坐标。这允许创建随物体移动而保持一致性的纹理映射。 对于材质中的纹理属性,通常会设置`Tiling`和平移参数`Offset`,这些信息通过`float4 {TextureName}_ST`属性传入着色器,在此结构中: - `x`包含X轴上的重复次数, - `y`包含Y轴上的重复次数, - `z`表示沿X方向的偏移量, - `w`则代表沿Y方向的偏移量[^4]。 当希望依据对象的世界空间位置调整纹理时,则需自定义Shader代码片段如下所示: ```hlsl // 计算基于世界坐标UVs half2 CalculateWorldSpaceUV(float3 worldPos, half tiling) { // 获取当前像素所在面法线向量并标准化 float3 normal = normalize(v.normal); // 创建一个临时变量用于存储最终结果 half2 uv; // 如果表面朝上(即Z分量接近于1) if (abs(normal.z) > abs(normal.x) && abs(normal.z) > abs(normal.y)) uv = worldPos.xy * tiling; // 使用XY作为UV // 若表面主要面向侧面(X正负),采用YZ平面作为UV else if(abs(normal.x) >= abs(normal.y) || abs(normal.x) >= abs(normal.z)) uv = worldPos.yz * tiling; // 对于其他情况,默认使用XZ平面作为UV else uv = worldPos.xz * tiling; return uv; } ``` 上述函数接受两个输入参数:一个是网格模型某一点的世界坐标;另一个是指定纹理应该被拉伸多少次(`tiling`)。根据不同的面朝向选择合适的二维子集(xy,yz,xz)乘以指定倍数得到最终使用的UV值[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值