现代计算机图形学中,纹理映射技术是实现高质量视觉效果的核心技术之一。与仅依赖顶点颜色的传统渲染方式相比,纹理技术能够以更低的几何复杂度实现更丰富的视觉细节。本章将详细探讨纹理技术的基础——四边形网格的构建方法及其底层的索引缓冲区技术。
纹理技术的必要性
顶点颜色方案的局限性
使用纯顶点颜色渲染复杂图像存在以下技术限制:
-
几何复杂度:渲染高细节图像(如图3-1所示的鹦鹉照片)需要数百万个顶点
-
存储效率:每个顶点都需要独立的颜色数据,造成显著的内存开销
-
维护成本:图像内容的修改需要重新构建整个网格结构
-
性能影响:大量顶点数据的传输和处理会降低渲染性能

图3-1 鹦鹉照片
纹理映射技术优势
纹理映射通过将二维图像数据映射到三维网格表面,有效解决了上述问题:
-
图像数据以纹理形式独立存储,网格仅需基本几何信息
-
纹理内容可独立修改,无需重构网格
-
单个片元可访问纹理的任意精度细节
-
现代GPU对纹理采样操作进行了专门优化
纹理映射的技术原理
纹理坐标系统
纹理映射的核心机制包括以下组件:
-
纹理坐标(UV 坐标):为网格顶点指定 2D 坐标(范围通常 [0,1])。
-
插值:渲染管线在面间插值 UV 坐标,每个片元获得唯一 UV 值。
-
纹理采样:片元着色器根据 UV 坐标从纹理中提取颜色。
纹理采样流程
顶点UV坐标 → GPU插值计算 → 片元UV坐标 → 纹理采样 → 最终颜色
这一流程确保了纹理能够平滑地映射到任意复杂的三维表面。
四边形网格构建
从三角形到四边形的技术转换
在基于三角形的渲染系统中,四边形必须通过两个三角形的组合来实现。图3-2展示了这种基本的几何分解:

图3-2 由两个三角形拼成的四边形
构建四边形网格时,存在两种主要的数据组织方案:
-
独立顶点方案:为每个三角形创建独立的顶点数据(6个顶点)
-
索引缓冲区方案:创建共享顶点池,通过索引引用(4个顶点)
索引缓冲区方案在内存效率和渲染性能方面均优于独立顶点方案,是现代图形系统的标准实践。
索引缓冲区技术
顶点定义
首先定义四边形的四个顶点坐标:
void ofApp::setup() {
// 定义四边形的四个顶点(逆时针顺序)
quad.addVertex(glm::vec3(-1, -1, 0)); // 左下角 - 索引0
quad.addVertex(glm::vec3(-1, 1, 0)); // 左上角 - 索引1
quad.addVertex(glm::vec3( 1, 1, 0)); // 右上角 - 索引2
quad.addVertex(glm::vec3( 1, -1, 0)); // 右下角 - 索引3
顶点属性
为每个顶点分配颜色属性(用于调试和验证):
// 为调试目的分配顶点颜色
quad.addColor(ofDefaultColorType(1, 0, 0, 1)); // 红色
quad.addColor(ofDefaultColorType(0, 1, 0, 1)); // 绿色
quad.addColor(ofDefaultColorType(0, 0, 1, 1)); // 蓝色
quad.addColor(ofDefaultColorType(1, 1, 1, 1)); // 白色
索引缓冲区构建
通过索引数组定义两个三角形的顶点连接关系:
// 定义索引缓冲区:两个三角形共享顶点
ofIndexType indices[6] = { 0, 1, 2, 2, 3, 0 };
quad.addIndices(indices, 6);
索引序列解析:
-
第一个三角形:顶点0 → 顶点1 → 顶点2
-
第二个三角形:顶点2 → 顶点3 → 顶点0

图3-3 标记了每个顶点索引的四边形网格
-
索引缓冲区:数组指定顶点连接顺序(0-1-2 为第一个三角形,2-3-0 为第二个)。重用顶点 0 和 2,节省内存。
-
优势:适用于复杂网格,如游戏角色,避免冗余数据。
-
渲染效果:使用之前的着色器,将显示渐变色填充窗口。
四边形效果如下图:

索引缓冲区的特性
顶点属性的原子性
索引缓冲区中的每个索引值引用的是完整的顶点数据,包括:
-
顶点位置坐标
-
顶点颜色属性
-
纹理坐标(后续添加)
-
其他自定义属性
这种原子性设计确保了顶点数据的一致性和完整性。
内存优化效果
相比独立顶点方案,索引缓冲区技术实现了:
-
内存节省:四边形仅需4个顶点而非6个
-
缓存友好:共享顶点数据提高了GPU缓存命中率
-
带宽优化:减少了顶点数据的传输量
项目代码参考
项目结构
项目根目录/
├── src/
│ ├── ofApp.h
│ ├── ofApp.cpp
│ └── main.cpp
└── bin/data/
├── first_vertex.vert
└── first_fragment.frag
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)); // 左下角 - 索引0
quad.addVertex(glm::vec3(-1, 1, 0)); // 左上角 - 索引1
quad.addVertex(glm::vec3( 1, 1, 0)); // 右上角 - 索引2
quad.addVertex(glm::vec3( 1, -1, 0)); // 右下角 - 索引3
// 为调试目的分配顶点颜色
quad.addColor(ofDefaultColorType(1, 0, 0, 1)); // 红色
quad.addColor(ofDefaultColorType(0, 1, 0, 1)); // 绿色
quad.addColor(ofDefaultColorType(0, 0, 1, 1)); // 蓝色
quad.addColor(ofDefaultColorType(1, 1, 1, 1)); // 白色
// 定义索引缓冲区:两个三角形共享顶点
ofIndexType indices[6] = { 0, 1, 2, 2, 3, 0 };
quad.addIndices(indices, 6);
shader.load("vertex_color.vert", "vertex_color.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());
}
vertex_color.vert
#version 410
// 布局限定符:指定顶点属性的输入位置
layout(location = 0) in vec3 position; // 位置属性
layout(location = 1) in vec4 color; // 颜色属性
// 输出变量:传递给片元着色器
out vec4 vertex_color; // 将颜色传递给下一阶段
void main()
{
// 变换顶点位置
gl_Position = vec4(position, 1.0);
// 传递颜色数据
vertex_color = color;
}
vertex_color.frag
#version 410
// 输入变量:接收来自顶点着色器的数据
in vec4 vertex_color; // 插值后的颜色
// 输出变量:最终像素颜色
out vec4 output_color;
void main()
{
// 直接使用插值后的颜色
output_color = vertex_color;
}
创建四边形&spm=1001.2101.3001.5002&articleId=150525511&d=1&t=3&u=8881dc6b3dab4aeab5c0ca8e3d1b4642)
162万+

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



