Shader开发(十五)创建四边形

现代计算机图形学中,纹理映射技术是实现高质量视觉效果的核心技术之一。与仅依赖顶点颜色的传统渲染方式相比,纹理技术能够以更低的几何复杂度实现更丰富的视觉细节。本章将详细探讨纹理技术的基础——四边形网格的构建方法及其底层的索引缓冲区技术。


纹理技术的必要性

顶点颜色方案的局限性

使用纯顶点颜色渲染复杂图像存在以下技术限制:

  • 几何复杂度:渲染高细节图像(如图3-1所示的鹦鹉照片)需要数百万个顶点

  • 存储效率:每个顶点都需要独立的颜色数据,造成显著的内存开销

  • 维护成本:图像内容的修改需要重新构建整个网格结构

  • 性能影响:大量顶点数据的传输和处理会降低渲染性能

图3-1 鹦鹉照片

纹理映射技术优势

纹理映射通过将二维图像数据映射到三维网格表面,有效解决了上述问题:

  • 图像数据以纹理形式独立存储,网格仅需基本几何信息

  • 纹理内容可独立修改,无需重构网格

  • 单个片元可访问纹理的任意精度细节

  • 现代GPU对纹理采样操作进行了专门优化


纹理映射的技术原理

纹理坐标系统

纹理映射的核心机制包括以下组件:

  1. 纹理坐标(UV 坐标):为网格顶点指定 2D 坐标(范围通常 [0,1])。

  2. 插值:渲染管线在面间插值 UV 坐标,每个片元获得唯一 UV 值。

  3. 纹理采样:片元着色器根据 UV 坐标从纹理中提取颜色。

纹理采样流程

顶点UV坐标 → GPU插值计算 → 片元UV坐标 → 纹理采样 → 最终颜色

这一流程确保了纹理能够平滑地映射到任意复杂的三维表面。


四边形网格构建

从三角形到四边形的技术转换

在基于三角形的渲染系统中,四边形必须通过两个三角形的组合来实现。图3-2展示了这种基本的几何分解:

图3-2 由两个三角形拼成的四边形

构建四边形网格时,存在两种主要的数据组织方案:

  1. 独立顶点方案:为每个三角形创建独立的顶点数据(6个顶点)

  2. 索引缓冲区方案:创建共享顶点池,通过索引引用(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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值