一. 环境搭建
需要准备资源 GLTools 工具类和libGLTools.a文件 如下:
包含了一个可移动正方块的demo:
https://github.com/TeeMoYan/OpenGLTest.git
1.打开XCode,新建一个项目! Xcode -> macOS -> Cocoa Application
2.添加 OpenGl.framework GLUT.framework两个系统库
3.添加准备的资源include和libGLTools.a资源
4.在Bulid Settings 输入Header Search path 中拖入include,生成路径
5.删除不同文件
5.创建main.cpp文件 ( c++
6.复制如下代码到main.cpp
#include "GLTools.h"
#include
//程序入口
int main(int argc,char* argv[]){
}
到此环境搭建完成!此时编译将会通过!(注意将include资源和.a放在同一个目录下)
二 . 一些术语
顶点(vertexs)
OpenGL中的图形是有大量的顶点和顶点之间的连线构成的,可以说,顶点是OpenGL图形的基础单元
着色
沿着顶点之间改变颜色值,能够轻松创建关照照射子啊一个立方体上的效果。
纹理贴图
不过是一个用来贴到三角形或多边形上的图片。在GPU上,纹理是快捷有效的
混合
将不同的颜色混在一起
通常着色器分两种:
1顶点着色器(vertex shader)这个是告诉电脑如何打线稿的——如何处理顶点、法线等的数据的小程序。
2片面着色器(fragment shader)这个是告诉电脑如何上色的——如何处理光、阴影、遮挡、环境等等对物体表面的影响,最终生成一副图像的小程序。
什么叫管线?
管线(pipeline),可以理解为渲染流水线。管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
什么叫固定管线?
可以简单理解为渲染图像的这个过程,我们只能通过调用GLShaderManager类的固定管线效果实现我们一系列的着色器处理。
什么可编程管线?
可以简单理解, 在我们处理图形的过程,我们必须使用顶点着色器和片元着色过程。我们可以才有GLSL自行编写着色器程序,来执行这个过程的事情。
举例: 假设是一家制造肥皂的工厂,如果是固定管线生产,我们投入肥皂水(顶点数据等)通过管线生产出来的可能是比较固化的肥皂,方的、圆的等等。自由性差了许多。 而如果是可编程管线造肥皂,那我们可以在肥皂水倒入磨具过程中,通过程序修改它的造型。比如修改成桃心等。
客户机、服务
管线分为上下2部分,上部分是客户端, 下半部分则是服务端。
客户端是存储在CPU存储 中的,并且在应 程序中执行 ,或者在主系统内存的驱动程序中执行。驱动程序会将渲染命令和数组组合起来,发送给服务 执 !(在 台典型的个 计算机上,服务 就是实际上就是图形加速卡上的硬件和内存)
服务 和 客户机在功能上也是异步的。 它们是各 独 的软件块或硬件块。我们是希望它们2个端都尽 在 停的 作。客户端 断的把数据块和命令块组合在 起输送到缓冲区,然后缓冲区就会发送到服务 执行。
如果服务器停止工 作等待客户机,或者客户机停止工 作来等待服务器 做好接受更多的命令和准备,我们把这种情况成为管线停滞
图上(primitive Assembly说明的是:3个顶点已经组合在 起, 三 形已经逐个 段的进 光栅化。每个 段通过执 元着 进 填充。 元着 会输出我们将屏幕上看到的最终颜 值
我们必须在这之前为着色器 提供数据,否则什么都无法实现!
有3种向OpenGL着 传递渲染数据的 法可供我们选择:
shader的三种变量类型
uniform变量一般用来表示:变换矩阵,材质,光照参数和颜色等信息;如果在vertex和fragment两者之间声明方式完全一样,则它可以在vertex和fragment共享使用;只读常量数据;
attribute变量是只能在vertex shader中使用的变量;一般用attribute变量来表示一些顶点的数据,如:顶点坐标,法线,纹理坐标,顶点颜色等;
属性总是以四维向 的形式进 内部存储的,即使我们 会使 所有的4个分 。 个顶点位置可能存储(x,y,z),将占有4个分 中的3个。实际上如果是在平 情况下:只要在xy平 上就能绘制,那么Z分 就会 动设置为0;
这 些属性只提供给顶点着 使 ,对于 片元着 木 有太 大意义
纹理 varying变量是vertex和fragment shader之间做数据传递用。一般vertex shader修改varying变量的值,然后fragment shader使用该varying变量的值。因此varying变量在vertex和fragment shader二者之间的声明必须是一致的。application不能使用此变量。
矩阵变换
模型-视图-投影(MVP)矩阵的概念:
模型矩阵--将物体坐标变化成世界坐标
视图矩阵--将世界坐标变换成眼睛坐标
投影矩阵--将眼睛坐标变换成裁剪坐标
在固定管线OpenGL中,模型-视图矩阵可以用glRotatef,glTranslatef,glScalef等函数创建;可编程管线已不可用这些函数,可在应用程序中处理
图元(primitives)
图元可以理解为组成图形的基本单元。比如点、线、三角形。 我们可以通过一系列函数或顶点数据帮助我们实现多种多样的图形。OpenGL中绘制几何图元,必须使用glBegin(GLenum mode)和glEnd()这一对函数!
图元支持类型
阶段1. 顶点 ->图元
几何顶点被组合为图元(点,线段或多边形),然后图元被合成片元,最后片元被转换为帧缓存中的象素数据。
阶段2. 图元 ->片元
图元被分几步转换为片元:图元被适当的裁剪,颜色和纹理数据也相应作出必要的调整,相关的坐标被转换为窗口坐标。最后,光栅化将裁剪好的图元转换为片元。
1) 裁剪
在裁剪时点,线段和多边形处理略微不同。对于点,要么保留原始状态(在裁剪体内部),要么被裁掉(在裁剪体外部)。对于线段和多边形来说,如果部分在裁剪体外部,则需要在裁剪点生成新的几何顶点。对于多边形,还需要在新增的顶点间增加完整的边。不论裁剪了线段还是多边形,都需要给新增几何点赋予边界标志、法线、颜色和纹理坐标信息。
裁剪过程时两步:
a 应用程序指定裁剪(Application-specific clipping),一旦组合图元完成后,如果在程序中用glClipPlane()函数定义了任意的裁剪面,就进行裁剪。
b 视景体裁剪(View volume clipping),随后,图元被投影矩阵投影(进入裁剪坐标系),被相应的视景体裁剪。投影矩阵可以由glFrustum() 或者glOrtho()定义,投影矩阵的操作和上面其他矩阵变换的操作相同。
2) 转换到窗口坐标
裁剪坐标在转换为窗口坐标之前,要除以规格化设备坐标(normalized device coordinates)的w值进行规范化。然后对这些规范化数据进行视口变换(viewport)计算生成窗口坐标。可以用glDepthRange()和glViewport()控制视口大小,决定屏幕上显示图象的区域。
光栅化
光栅化是将一个图元转变为一个二维图象(其实只是布满平面,没有真正的替换帧缓存区)的过程。二维图象上每个点都包含了颜色、深度和纹理数据。将该点和相关信息叫做一个片元(fragment)。(yuyu注:这就是片元和像素之间的关键区别,虽然两者的直观印象都是的像素,但是片元比像素多了许多信息,在光栅化中纹理映射之后图元信息转化为了像素)在这个阶段,对象素绘制和位图进行操作需要用到当前栅格位置(用glRasterPos*()定义)。正如上面讨论的,三种图元的光栅化方法是不同的,另外,象素块和位图也需要光栅化。
官方翻译成栅格化或者像素化。没错,就是把矢量图形转化成像素点儿的过程。我们屏幕上显示的画面都是由像素组成,而三维物体都是点线面构成的。要让点线面,变成能在屏幕上显示的像素,就需要Rasterize这个过程。就是从矢量的点线面的描述,变成像素的描述。
如下图,这是一个放大了1200%的屏幕,前面是告诉计算机我有一个圆形,后面就是计算机把圆形转换成可以显示的像素点。这个过程就是光栅化。
存储着色器的使用
//定义着色器
GLShaderMananger shaderManager;
//初始化着色器
shaderManager.InitalizeStockShaders()
//使用
shaderManager userStockManager(参数列表)
• 单位着色器 UserStockShader(GLT_ATTRIBUTE_VERTEX,GLfloat vColor[4]);
只是简单地使 默认笛卡尔坐标系(坐标范围(-1.0, 1.0))。所有的 段都应 同 种颜 , 何图形为 实 和未渲染 的。 需要设置存储着 个属性: GLT_ATTRIBUTE_VERTEX(顶点分 ) 参数2:vColor[4],你需要的颜
• 平面着色器
UserStockShader(GLT_SHADER_FLAT,GLfloat mvp[16],GLfloat vColor[4]);
• 上色着色器
UserStockShader(GLT_SHADER_SHADED,GLfloat mvp[16]);
• 默认光源着色器
UserStockShader(GLT_SHADER_DEFAULT_LIGHT,GLfloat mvMatrix[16],GLfloat
• 点光源着色器
UserStockShader(GLT_SHADER_DEFAULT_LIGHT_DIEF,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLightPos[3],GLfloat vColor[4]);
• 纹理替换矩阵
UserStockShader(GLT_SHADER_TEXTURE_REPLACE,GLfloat mvMatrix[16],GLint nTextureUnit);
• 纹理调整着色器
UserStockShader(GLT_SHADER_TEXTURE_MODULATE,GLfloat mvMatrix[16],GLfloat vColor[4],GLint nTextureUnit);
• 纹理光源着色器
UserStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIEF,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLightPos[3],GLfloat vBaseColor[4],GLint nTextureUnit);
常用函数
glutInit() 负责初始化GLUT库。它会处理向程序输入的命令行参数,并且移除其中与控制GLUT如何操作相关的部分。它必须是应用程序第一个GLUT函数,负责设置其他GLUT例程必需的数据结构。
glutInitDisplayMode() 设置了程序所使用的窗口类型。窗口设置更多的OpenGL 特性,例如RAGA颜色空间,使用深度缓存或动画效果。
glutInitWindowsSize() 设置所需的窗口大小,如果不想在这个设置一个固定值,也可以先查询显示设备的尺寸,然后根据计算机的屏幕动态设置窗口的大小。
glutCreateWindow(),它的功能和它的名字一样,如果当前的系统环境可以满足glutInitDisplayMode()的显示模式要求,这里就会创建一个窗口(此时会调用计算机窗口系统的接口)。只有GLUT创建了一个窗口之后(其中包含创建创建OpenGL环境的过程),我们才可以使用OpenGL相关的函数
glewInit()函数,属于另一个辅助库GLEW(OpenGL Extention Wrangler)。GLEW可以简化获取函数地址的过程,并且包含了可以跨平台使用的其他一些OpenGL编程方法。
glutDisplayFunc(),它设置了一个显示回调(diplay callback),即GLUT在每次更新窗口内容的时候回自动调用该例程
glutMainLoop(),这是一个无限执行的循环,它会负责一直处理窗口和操作系统的用户输入等操作。(注意:不会执行在glutMainLoop()之后的所有命令。)