OpenGL(2)-初探

了解了OpenGL的相关概念,我们再通过两个小示例来看看OpenGL的相关API以及图形的绘制流程。

在实现示例之前,我们需要配置相关的环境:

  • 添加OpenGl.frameworkGLUT.framework系统库,添加libGLTools.a静态库,
  • 引入CLToolsglew,并且在Build SettingsHeader Search Paths中添加CLTools的路径,
  • 移除原来的mainAppDelegateViewController文件,创建一个main.cpp文件。

这样,我们就可以在main.cpp实现相关功能了。

三角形

引入工具类

#include "GLTools.h"
#include <GLUT/GLUT.h>
#include "GLShaderManager.h"
  • GLTools.h:包含了⼤部分GLTools中类似C语言的独立函数
  • GLUT/GLUT.h:使用glut
  • GLShaderManager.h:着⾊器管理器,允许我们使用并管理着色器,另外还提供了一组存储着色器,能够进行一些基本的渲染操作

创建全局变量

着色管理器和GL批处理类:

GLShaderManager shaderManager;
GLBatch glBatch;

代码实现

    1. 初始化、基础设置操作
    1. 注册窗口调整和渲染的回调函数
    1. 配置三角形的顶点数据
int main(int argc,char* argv[]) {
    
    // 设置当前工作目录,针对MAC OS X
    gltSetWorkingDirectory(argv[0]);
    
    // 初始化GLUT库
    glutInit(&argc, argv);
    
    // 初始化双缓冲窗口
    // GLUT_DOUBLE表示双缓冲窗口、GLUT_RGBA表示RGBA颜色模式、GLUT_DEPTH表示深度测试、GLUT_STENCIL表示模板缓冲区
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    
    // 设置GLUT窗口大小、标题
    glutInitWindowSize(800, 800);
    glutCreateWindow("Triangle");
    
    // 注册回调函数
    // 窗口调整
    glutReshapeFunc(reshapeAction);
    // 渲染操作
    glutDisplayFunc(renderAction);
    
    // 驱动程序的初始化中容错判断
    GLenum error = glewInit();
    if(GLEW_OK != error) {
        fprintf(stderr,"glew error:%s\n",glewGetErrorString(error));
        return 1;
    }
    
    // 初始化设置
    setupTriangle();
    
    glutMainLoop();
    
    return 0;
}

void setupTriangle() {
    // 设置窗口背景颜色
    glClearColor(1, 1, 1, 1);
    
    // 初始化着色管理器
    shaderManager.InitializeStockShaders();
    
    // 设置三角形的顶点坐标 x y z
    GLfloat vertCoordinates[] = {
        -0.5f, 0.0f, 0.0f,
         0.5f, 0.0f, 0.0f,
         0.0f, 0.5f, 0.0f,
    };
    
    // 批次处理
    glBatch.Begin(GL_TRIANGLES, 3);
    glBatch.CopyVertexData3f(vertCoordinates);
    glBatch.End();
}

// 窗口调整
void reshapeAction(int width, int height) {
    // glViewport (GLint x, GLint y, GLsizei width, GLsizei height)
    // x,y 以像素为单位,指定了窗口的左下角位置。width,height表示视口矩形的宽度和高度,根据窗口的实时变化重绘窗口。
    glViewport(0, 0, width, height);
}

// 渲染
void renderAction() {
    
    // 清除一个或一组特定的缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // 设置颜色
    GLfloat vRed[] = {1.0, 0.0, 0.0, 1.0};

    // 着色管理器使用存储着色器进行渲染
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
    
    glBatch.Draw();
    
    // 双缓冲处理,交换缓冲区指针
    glutSwapBuffers();
}

固定管线存储着色器

  1. GLT_SHADER_IDENTITY:单位着色器/单元着色器

使用默认笛卡尔坐标系,坐标范围为(-1.0,1.0),所有片段都应用同一种颜色。使用方法:

shaderManager.UserStockShader(GLT_SHADER_IDENTITY, GLfloat vColor[4]);
  • 参数1:着色器类型
  • 参数2:颜色参数
  1. GLT_SHADER_FLAT:平面着色器

用以模型/投影变化。可以为几何图形变化指定一个4*4变换矩阵,该矩阵被称为“模型视图投影矩阵”。使用方法:

shaderManager.UserStockShader(GLT_SHADER_FLAT,GLfloat mvp[16],GLfloat vColor[4]);
  • 参数1:着色器类型
  • 参数2:4*4变换矩阵
  • 参数3:颜色参数
  1. GLT_SHADER_SHADED:上色着色器

用以将颜色平滑的插入到顶点之间,进行平滑着色。使用方法:

shaderManager.UserStockShader(GLT_SHADER_SHADED,GLfloat vColor[4]);
  • 参数1:着色器类型
  • 参数2:颜色参数
  1. GLT_SHADER_DEFAULT_LIGHT:默认光源着色器

用来为图形产生阴影和光照效果。使用方法:

shaderManager.UserStockShader(GLT_SHADER_DEFAULT_LIGHT,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vColor[4]);
  • 参数1:着色器类型
  • 参数2:模型视图矩阵
  • 参数3:投影矩阵
  • 参数4:颜色参数
  1. GLT_SHADER_POINT_LIGHT_DIFF:点光源着色器

用来为图形产生阴影和光照效果。与默认光源着色器非常类似,区别在与点光源着色器可以指定光源位置。使用方法:

shaderManager.UserStockShader(GLT_SHADER_DEFAULT_LIGHT,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vColor[4]);
  • 参数1:着色器类型
  • 参数2:模型视图矩阵
  • 参数3:投影矩阵
  • 参数4:视点坐标光源位置
  • 参数5:颜色参数
  1. GLT_SHADER_TEXTURE_REPLACE:纹理替换矩阵着色器

通过给定的模型视图投影矩阵,使用纹理单元来进行填充,其每个像素点的颜色是从纹理中获取。使用方法:

shaderManager.UserStockShader(GLT_SHADER_TEXTURE_REPLACE,GLfloat mvMatrix[16],GLint nTextureUnit);
  • 参数1:着色器类型
  • 参数2:模型视图矩阵
  • 参数3:纹理单元
  1. GLT_SHADER_TEXTURE_MODULATE:纹理调整着色器

通过给定的模型视图投影矩阵,将一个基本色乘以一个取自纹理单元nTextureUnit的纹理,将颜色与纹理进行混合后填充到片段中。使用方法:

shaderManager.UserStockShader(GLT_SHADER_TEXTURE_MODULATE,GLfloat mvMatrix[16],GLfloat vColor[4],GLint nTextureUnit);
  • 参数1:着色器类型
  • 参数2:模型视图矩阵
  • 参数3:颜色参数
  • 参数4:纹理单元
  1. GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF:纹理光源着色器

通过给定的模型视图投影矩阵,将一个纹理通过漫反射照明计算进行调整(相乘)。使用方法:

shaderManager.UserStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIEF,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLightPos[3],GLfloat vBaseColor[4],GLint nTextureUnit);
  • 参数1:着色器类型
  • 参数2:模型视图矩阵
  • 参数3:视觉空间中的光源位置
  • 参数4:几何图形的基本色
  • 参数5:需要处理的纹理单元

设置初始显示模式

对应宏定义 含义
GLUT_RGB 0x0000 指定 RGB 颜色模式的窗口
GLUT_RGBA 0x0000 指定 RGBA 颜色模式的窗口
GLUT_INDEX 0x0001 指定颜色索引模式的窗口
GLUT_SINGLE 0x0000 指定单缓存窗口
GLUT_DOUBLE 0x0002 指定双缓存窗口
GLUT_ACCUM 0x0004 窗口使用累加缓存
GLUT_ALPHA 0x0008 窗口的颜色分量包含 alpha 值
GLUT_DEPTH 0x0010 窗口使用深度缓存
GLUT_STENCIL 0x0020 窗口使用模板缓存
GLUT_MULTISAMPLE 0x0080 指定支持多样本功能的窗口
GLUT_STEREO 0x0100 指定立体窗口
GLUT_LUMINANCE 0x0200 窗口使用亮度颜色模型

几何图元

  • GL_POINTS:点
  • GL_LINES:线段,二个点确定线段
  • GL_LINE_STRIP:第一个点依次连接的线段
  • GL_LINE_LOOP:和GL_LINE_STRIP相同,但首尾连接,形成环状
  • GL_POLYGON:多边形
  • GL_QUADS:由四个点组成一个四边形
  • GL_QUADS_STRIP:四边形带
  • GL_TRIANGLES:三角形,三个点确定
  • GL_TRIANGLE_STRIP:共用一个条带上的顶点的一组三角形
  • GL_TRIANGLE_FAN:以一个原点为中心呈扇形排列,公共相邻顶点的一组三角形
几何图元.png

可移动的正方形

渲染矩形和渲染三角形在初始化设置上基本一样。实现矩形可以通过修改几何图元的类型即可,下面我们提供两种思路,一种是使用批处理类渲染三角形的方式(GL_TRIANGLES),将两个三角形拼接成矩形,这样需要6个顶点;另一种是直接使用批处理类渲染矩形的方式(GL_QUADS或者GL_TRIANGLE_FAN),需要4个顶点。

GLfloat squareEdge = 0.1;
// 给定初始坐标
GLfloat vVerts[] = {
        -squareEdge, -squareEdge, 0.0,
        squareEdge,  -squareEdge, 0.0,
        squareEdge,  squareEdge,  0.0,
        -squareEdge, squareEdge,  0.0
};

// 偏移量
GLfloat xPos = 0.0;
GLfloat yPos = 0.0;

void setupSquare() {
    glClearColor(1, 1, 1, 1);
    
    // 初始化着色管理器
    shaderManager.InitializeStockShaders();
    
    // 批次类处理设置为矩形
    glBatch.Begin(GL_TRIANGLE_FAN, 4);
    glBatch.CopyVertexData3f(vVerts);
    glBatch.End();
}

由于我们需要通过键盘控制所绘制矩形的位置,所以我们需要监听键盘的输入,并且修改矩形的位置重新渲染。

// 注册键盘的回调
glutSpecialFunc(squareSpecialKey);

此处,我们使用键盘的上下左右键来移动矩形。这里需要做个特殊处理,当矩形移动到窗口的边界时,就需要停止继续移动。注意,这里每次只响应一个键。

void squareSpecialKey(int key, int x, int y) {
    // 每次移动的步长(距离)
    GLfloat stepSize = 0.025;

    // 只响应上下左右键
    if (key == GLUT_KEY_UP || key == GLUT_KEY_DOWN || key == GLUT_KEY_LEFT || key == GLUT_KEY_RIGHT) {
        switch (key) {
            case GLUT_KEY_UP:
                yPos += stepSize;
                break;
            case GLUT_KEY_DOWN:
                yPos -= stepSize;
                break;
            case GLUT_KEY_LEFT:
                xPos -= stepSize;
                break;
            case GLUT_KEY_RIGHT:
                xPos += stepSize;
                break;
            default:
                break;
        }
        
        // 边界检测
        // -x
        if (xPos < -1.0 + squareEdge) {
            xPos = -1.0 + squareEdge;
        }
        // x
        if (xPos > 1.0 - squareEdge) {
            xPos = 1.0 - squareEdge;
        }
        // -y
        if (yPos < -1.0 + squareEdge) {
            yPos = -1.0 + squareEdge;
        }
        // y
        if (yPos > 1.0 - squareEdge) {
            yPos = 1.0 - squareEdge;
        }
        
        glutPostRedisplay();
    }
}

下面就是渲染过程了,考虑到每次移动都需要修改矩阵的坐标,我们可以使用平面着色器,将新的坐标矩阵通过变换之后,直接传给着色器管理器即可。

void squareRender() {
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // 设置颜色
    GLfloat vRed[] = {1.0, 0.0, 0.0, 1.0};
    
    // 矩阵变化 对原来矩形的顶点数组进行变换 将变换之后的结果存入mTransfromMatrix
    M3DMatrix44f mTransfromMatrix;
    m3dTranslationMatrix44(mTransfromMatrix, xPos, yPos, 0.0);
    
    shaderManager.UseStockShader(GLT_SHADER_FLAT, mTransfromMatrix, vRed);
    glBatch.Draw();
    glutSwapBuffers();
}

渲染一个圆形,我们可以使用几何图元GL_POLYGON,也就是从圆周上选定无限多个点作为顶点,把这些顶点连接起来,即可组成一个圆。下面示例中,我们选定了10000个顶点。

int n = 10000;
GLfloat PI = 3.1415926;
// 半径
GLfloat r = 0.5;

void setupCircle() {
    // 设置窗口背景颜色
    glClearColor(1, 1, 1, 1);
    
    // 初始化着色管理器
    shaderManager.InitializeStockShaders();
    
    GLfloat vVerts[30000] = {0};
    
    // 计算顶点
    for (int i = 0; i < n; i++) {
        vVerts[i*3] = r * cos(2 * PI * i / n);
        vVerts[i*3+1] = r * sin(2 * PI * i / n);
        vVerts[i*3+2] = 0;
    }
    
    // 批次处理
    glBatch.Begin(GL_POLYGON, n);
    glBatch.CopyVertexData3f(vVerts);
    glBatch.End();
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345