当前位置: 首页 > news >正文

OpenGL入门教程

OpenGL入门教程

参考

  • OpenGL入门教程
  • [Opengl]图形学final_project作业记录

文章目录

    • OpenGL入门教程
      • 一、概述
        • 1、OpenGL
        • 2、OpenGL ES与WebGL
        • 3、OpenGL发展史
        • 4、OpenGL窗口库
      • 二、OpenGL基础知识
        • 1、数据类型和函数名
        • 2、2D图形绘制(example)
        • 3、3D图形绘制(example)
          • 1)gluCylinder
          • 2)gluSphere
          • 3)gluDisk
          • 4)gluPartialDisk
          • 5)glutSolidCube
          • 6)glutSolidSphere
        • 4、纹理映射(example)
      • 三、几何图形绘制(★★★★★)
        • 1、几何图形
          • 1)点
          • 2)线
          • 3)多边形
        • 2、绘制图元
          • 1)定义顶点
          • 2)几何图元
          • 3)图元标志(example)
      • 四、坐标系及坐标变换(★★★★★)
        • 1、右手坐标系
        • 2、坐标空间
          • 1)局部空间
          • 2)世界空间
          • 3)观测空间
          • 4)裁剪空间
          • 5)屏幕空间
        • 3、空间变换
        • 4、相关API
          • 1)模型矩阵变换(局部坐标系 -> 世界坐标系)
          • 2)视图矩阵变换(世界坐标系 -> 观测坐标系)
          • 3)投影变换(观测坐标系 -> 裁剪坐标系)
          • 4)视口变换(裁剪坐标系 -> 屏幕坐标系)
      • 五、OpenGL纹理
        • 1、纹理分类
        • 2、纹理定义
          • 1)一维纹理
          • 2)二维纹理
        • 3、纹理控制函数
        • 纹理坐标
      • 六、OpenGL光照和材质
        • 1、光组成
        • 2、创建光源
        • 3、启动光照
        • 4、材质颜色
        • 5、材质RGB值 与 光源RGB
        • 6、示例
          • 1)example1
          • 2)example2
      • 七、常见问题
        • 1、gluLookAt的使用
        • 2、GL_PROJECTION & GL_MODELVIEW的区别
        • 2、什么是视口变换
        • 3、抗锯齿
        • 4、二次曲面的纹理映射
        • 5、绘制图像的显示区域
        • 6、光照和材质
          • 1)建立光照模型
          • 2)法线向量
          • 3)控制光源
          • 4)控制材质
          • 5)选择光照模型

一、概述

1、OpenGL

OpenGL是渲染2D、3D矢量图形硬件的一种软件接口。本质上说,它是一个3D图形和模型库,具有高度的可移植性,并且具有非常快的渲染速度。OpenGL并不是一种语言,而是更像一个C运行时函数库。它提供了一些预包装的功能,帮助开发人员编写功能强大的三维应用程序。 OpenGL可以再多种操作系统平台上运行,例如各种版本的Windows、UNIX/Linux、Mac OS 和 OS/2等。如今,OpenGL广泛流行于游戏、医学影像、地理信息、气象模拟等领域,是高性能图像和交互性场景处理的工业标准。
​ OpenGL的高效实现(利用了图形加速硬件)存在于Windows,部分UNIX平台和Mac OS。这些实现一般由显示设备厂商提供,而且非常依赖于该厂商提供的硬件。

2、OpenGL ES与WebGL

​ OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计

​ WebGL(全写Web Graphics Library)是一种3D绘图协议,这种绘图技术标准允许把JavaScript和OpenGL ES 结合在一起,通过增加OpenGL ES 的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染,这样Web开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型了,还能创建复杂的导航和数据视觉化。

3、OpenGL发展史

​ OpenGL是个开放的标准,虽然它由SGI(美国硅图公司)首创,但它的标准并不是控制在SGI的手中,而是由OpenGL体系结构审核委员会(ARB)所掌管。 ARB由SGC、DEC、IBM、Intel和Microsoft等著名公司1992年创立,后来又陆续添加了Nvidia、ATI等图形芯片领域的巨擎。 ARB每隔4年开一次会,对OpenGL规范进行维护和改善,并出台计划对OpenGL标准进行升级,使OpenGL一直保持与时代的同步。

​ 2006年,SGIG公司把OpenGL标准的控制从ARB移交给一个新的工作组:Khronos小组(www.khronos.org)。 Khronos是一个由成员提供资金的行业协会,专注于开放媒体标准的创建和维护。

4、OpenGL窗口库

OpenGL是一个图形库,而要画图,就需要先创建一个窗口。不幸的是,OpenGL并没有提供创建窗口的功能,必须自己创建窗口。而创建窗口在每一个操作系统上都不同的(在Windows上代码量也不少),为了方便,我们会使用一个窗口库来简化这一过程。常用的OpenGL窗口库有GLUT、GLFW和SDL,此处为我们选择使用得比较多的GLFW

​ Visual Studio对于OpenGL(gl.h)只支持到1.1,而我们使用的是OpenGL 3.3。但是,OpenGL是由显卡支持的,显卡已经提供了我们需要的OpenGL函数。因此就需要在运行程序时动态地获取函数地址。

二、OpenGL基础知识

1、数据类型和函数名

​ OpenGL的数据类型定义可以与其它语言一致,但建议在ANSI C下最好使用以下定义的数据类型,例如GLint、GLfloat等。

前缀数据类型相应C语言类型OpenGL类型
b8-bit integersigned charGLbyte
s16-bit integershortGLshort
i32-bit integerlongGLint,GLsizei
f32-bit floating-pointfloatGLfloat,GLclampf
d64-bit floating-pointdoubleGLdouble,GLclampd
ub8-bit unsigned integerunsigned charGLubyte,GLboolean
us16-bit unsigned integerunsigned shortGLushort
ui32-bit unsigned integerunsigned longGLuint,GLenum,GLbitfield

​ 从上表可以看出,OpenGL的库函数命名方式很有规律,了解这种规律后阅读和编写程序都比较容易方便。
首先,每个库函数有前缀gl、glu、glx或aux,表示此函数分属于基本库、实用库、X窗口扩充库或辅助库,其后的函数名头字母大写,后缀是参数类型的简写,取i、f。例如:

glVertex2i(2,4);
glVertex3f(2.0,4.0,5.0);

​ 如上,有的函数参数类型后缀前带有数字2、3、4。其中,2代表二维,3代表三维,4代表alpha值。

​ 除此之外,有些OpenGL函数最后带一个字母v,表示函数参数可用一个指针指向一个向量(或数组)替代一系列单个参数值。下面两种格式都表示设置当前颜色为红色,二者等价。

glColor3f(1.0,0.0,0.0);
float color_array[]={1.0,0.0,0.0};
glColor3fv(color_array);

​ 除了以上基本命名方式外,还有一种带“”星号的表示方法,例如glColor(),它表示可以用函数的各种方式来设置当前颜色。同理,glVertex*v()表示用一个指针指向所有类型的向量来定义一系列顶点坐标值。

2、2D图形绘制(example)

例如有下面一个示例程序,也是一个初学者学习的第一个示例程序。源码如下:

# include <iostream>
#include <gl\glut.h>	//Mostafiz,Md.Tasrul(12-20782-1)
using namespace std;    //Zaidi,Abir Mohammad Mahbub (12-20640-1)

void tree() {

	glBegin(GL_TRIANGLES);
	glColor3f((1.0f / 255) * 0, (1.0f / 255) * 128, (1.0f / 255) * 0);
	glVertex2i(150, 220);
	glVertex2i(60, 220);
	glVertex2i(110, 290);
	glEnd();

	glBegin(GL_QUADS);
	glColor3f((1.0f / 255) * 165, (1.0f / 255) * 42, (1.0f / 255) * 42);
	glVertex2i(100, 180);
	glVertex2i(100, 220);
	glVertex2i(110, 220);
	glVertex2i(110, 180);
	glEnd();
}

void draw()
{
	tree();
	glutSwapBuffers();  //glutswapBuffers swaps the buffers of the current window if double buffered.
}

void myDisplay(void)
{
	//glViewport(0, 0, 1024, 1024);   //视口变换
	//setLight1(true);
	glPushMatrix();  //push the current matrix stack
	draw();
	glPopMatrix();  //pop the current matrix stack
}

void myInit(void)
{
	glClearColor(1.0, 1.0, 1.0, 0);  //specify clear values for the color buffers
	glColor3f(0.0f, 0.0f, 0.0f); //set the current color
	glPointSize(4.0);   //specify the diameter of rasterized points
	glMatrixMode(GL_PROJECTION_MATRIX);   //specify which matrix is the current matrix
	glLoadIdentity();  //replace the current matrix with the identity matrix
	gluOrtho2D(0.0, 1024.0, 0.0, 768.0);  //define a 2D orthographic projection matrix
}

void main(int argc, char** argv)
{
	glutInit(&argc, argv);    // glutInit is used to initialize the GLUT library.
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);  //glutInitDisplayMode sets the initial display mode.

	//glutInitWindowPosition  and  glutInitWindowSize set the initial window position and size respectively.
	glutInitWindowSize(800, 600);
	glutInitWindowPosition(100, 150);

	glutCreateWindow("Tree");  // glutCreateWindow creates a top-level window.
	glutDisplayFunc(myDisplay);  //glutDisplayFunc sets the display callback for the current window.
	myInit();
	glutMainLoop();  //glutMainLoop enters the GLUT event processing loop.
}
//https://www.cnblogs.com/zhengwin7/p/5294161.html
#include <GL/glut.h>

void draw(void) {
	
	//gl开头的函数为openGL的标准函数

	//(使用当前缓冲区的值来)清除指定的缓冲区
	glClear(GL_COLOR_BUFFER_BIT);

	//画矩形
	//glRectf(-0.5f, -0.5f, 0.5f, 0.5f);
    
    //圆形
    GLfloat PI = 3.141592653f;
    int pre = 30;
    glBegin(GL_POLYGON);//画多边形
        for (int i = 0; i < pre; i++) {
            glVertex2f(r * cos(2 * PI*i / pre), r * sin(2 * PI*i / pre));
        }
	glEnd();

		//画直线
	glBegin(GL_LINES);
		glVertex2f(0.5f, 0.5f);
		glVertex2f(-0.5f, -0.5f);
	glEnd();

	//刷新缓冲,保证绘图命令能被执行
	glFlush();
}

int main(int argc, char *argv[]) {

	//初始化GLUT library
	glutInit(&argc, argv);
	//对窗口的大小进行初始化
	glutInitWindowSize(300, 300);
	// 设置窗口出现的位置
	//glutInitWindowPosition(int x, int y);
    
	//初始化程序展示模式
	glutInitDisplayMode(GLUT_RGBA);
    
	glutCreateWindow("project of openGL");
	//win: 指定子窗口的父窗口
	//glutCreateSubWindow(int win, int x, int y, int width, int height);
    
	//为当前窗口设置函数回调,用于画图
	glutDisplayFunc(&draw);
	//进行glut事件循环,否则看不到图形效果(一闪而过)
	glutMainLoop();

	return 0;
}

3、3D图形绘制(example)

模型视图变换GL_MODELVIEW):从“相对移动”的观点来看,改变观察点的位置与方向和改变物体本身的位置与方向具有等效性。

透视投影变换GL_PROJECTION):定义一个可视空间,可视空间以外的物体不会被绘制到屏幕上。

视口变换glViewPort):通常情况下,程序默认像素填充整个窗口,通过视口变换设置像素在窗口上的填充情况。

每次对物体进行变换的时候,都需要先设置当前操作的矩阵为某种变换的矩阵,在进行变换之前还需要将矩阵转换为单位矩阵才能进行操作。

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

绘制Cube:

//https://www.cnblogs.com/zhengwin7/p/5294161.html
#include <GL/glut.h>
#include <math.h>
 
void setCube(void) {
    glClearColor(1.0, 1.0, 1.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(0, 0, 0);//设置黑色
    glLoadIdentity();//加载单位矩阵
    gluLookAt(6, 0, 2.5, 0, 0, 0, 1, 1, 0);
    //前三个参数设置观察者的观察位置,中三个参数设置观察点的位置,后三个参数设置观察者的观察方向
    glLineWidth(2.0f);//设置边的像素宽,默认为1.0f
    glutWireCube(2.0);
    glFlush();
}
 
void drawCube(void) {
    glClearColor(1.0, 1.0, 1.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();
    //视图矩阵变换
    gluLookAt(4, 0, 1.5, 0, 0, 0, 1, 1, 0);
 
    //绘制正方体的面
    glColor3f(0, 1, 0);
    glBegin(GL_QUADS);
        //---1---
        glNormal3f(-1, 0, 0);//设置点的法向量
        glVertex3f(0.5, 0.5, 0.5);
        glVertex3f(0.5, -0.5, 0.5);
        glVertex3f(0.5, -0.5, -0.5);
        glVertex3f(0.5, 0.5, -0.5);
        //---2---
        glNormal3f(-1, 0, 0);
        glVertex3f(-0.5, 0.5, 0.5);
        glVertex3f(-0.5, -0.5, 0.5);
        glVertex3f(-0.5, -0.5, -0.5);
        glVertex3f(-0.5, 0.5, -0.5);
        //---3---
        glNormal3f(0, 1, 0);
        glVertex3f(0.5, 0.5, 0.5);
        glVertex3f(-0.5, 0.5, 0.5);
        glVertex3f(-0.5, 0.5, -0.5);
        glVertex3f(0.5, 0.5, -0.5);
        //---4---
        glNormal3f(0, -1, 0);
        glVertex3f(0.5, -0.5, 0.5);
        glVertex3f(-0.5, -0.5, 0.5);
        glVertex3f(-0.5, -0.5, -0.5);
        glVertex3f(0.5, -0.5, -0.5);
        //---5---
        glNormal3f(0, 0, 1);
        glVertex3f(0.5, 0.5, 0.5);
        glVertex3f(-0.5, 0.5, 0.5);
        glVertex3f(-0.5, -0.5, 0.5);
        glVertex3f(0.5, -0.5, 0.5);
        //---6---
        glNormal3f(0, 0, -1);
        glVertex3f(0.5, 0.5, 0.5);
        glVertex3f(-0.5, 0.5, 0.5);
        glVertex3f(-0.5, -0.5, 0.5);
        glVertex3f(0.5, -0.5, 0.5);
    glEnd();
 
    //draw
    glColor3f(0, 0, 0);
    glLineWidth(2.0f);
    //绘制正方体的边
    glBegin(GL_LINES);
        //---1---
        glVertex3f(0.5, 0.5, 0.5);
        glVertex3f(-0.5, 0.5, 0.5);
        glVertex3f(-0.5, 0.5, 0.5);
        glVertex3f(-0.5, -0.5, 0.5);
        glVertex3f(-0.5, -0.5, 0.5);
        glVertex3f(0.5, -0.5, 0.5);
        glVertex3f(0.5, -0.5, 0.5);
        glVertex3f(0.5, 0.5, 0.5);
        //---2---
        glVertex3f(0.5, 0.5, -0.5);
        glVertex3f(-0.5, 0.5, -0.5);
        glVertex3f(-0.5, 0.5, -0.5);
        glVertex3f(-0.5, -0.5, -0.5);
        glVertex3f(-0.5, -0.5, -0.5);
        glVertex3f(0.5, -0.5, -0.5);
        glVertex3f(0.5, -0.5, -0.5);
        glVertex3f(0.5, 0.5, -0.5);
        //---3---
        glVertex3f(0.5, 0.5, 0.5);
        glVertex3f(0.5, -0.5, 0.5);
        glVertex3f(0.5, -0.5, 0.5);
        glVertex3f(0.5, -0.5, -0.5);
        glVertex3f(0.5, -0.5, -0.5);
        glVertex3f(0.5, 0.5, -0.5);
        glVertex3f(0.5, 0.5, -0.5);
        glVertex3f(0.5, 0.5, 0.5);
        //---4---
        glVertex3f(-0.5, 0.5, 0.5);
        glVertex3f(-0.5, -0.5, 0.5);
        glVertex3f(-0.5, -0.5, 0.5);
        glVertex3f(-0.5, -0.5, -0.5);
        glVertex3f(-0.5, -0.5, -0.5);
        glVertex3f(-0.5, 0.5, -0.5);
        glVertex3f(-0.5, 0.5, -0.5);
        glVertex3f(-0.5, 0.5, 0.5);
    glEnd();
    glFlush();
}
 
void threeD(int w, int h) {
    //视口变换(最终的坐标将会被映射到屏幕空间)
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    
    //投影
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    
    //平截头体:对坐标进行标准化和裁剪,变换到裁剪空间上
    glFrustum(-1.0, 1.0, -1.0, 1.0, 2.0, 10.0);
    //模型视图(相机坐标系转世界坐标系)
    glMatrixMode(GL_MODELVIEW);
}
 
int main(int argc, char *argv[]) {
 
    //初始化GLUT library
    glutInit(&argc, argv);
    //对窗口的大小进行初始化
    glutInitWindowSize(500, 500);
    // 设置窗口出现的位置
    //glutInitWindowPosition(int x, int y);
    
    //初始化程序展示模式
    glutInitDisplayMode(GLUT_RGBA);
    glutCreateWindow("project of openGL");
    //为当前窗口设置函数回调,用于画图
    glutDisplayFunc(drawCube);
    
    //相机坐标系转世界坐标系
    glutReshapeFunc(threeD);
    glutMainLoop();
 
    return 0;
}
void drawCube(void) {
	// 清除之前帧数据
	glClear(GL_COLOR_BUFFER_BIT);

	// 绘制三角形
	glBegin(GL_TRIANGLES);
	glColor3f(1, 0, 0);
	glVertex3f(-1, -1, -5);
	glColor3f(0, 1, 0);
	glVertex3f(1, -1, -5);
	glColor3f(0, 0, 1);
	glVertex3f(0, 1, -5);
	glEnd();
	// 执行绘图命令
	glFlush();
}

参考https://blog.csdn.net/biggbang/article/details/20552915

1)gluCylinder
 gluCylinder(qobj, 0.5, 0.3, 1.0, 15, 5);
	  void gluCylinder (GLUquadric *qobj, GLdouble , GLdouble topRadius, GLdouble height, GLint slices, GLint stacks);
	  //绘制一个沿z轴的圆柱体,底面位于z=0平面,顶面位于z=height的平面。baseRadius是z=0半径,topRadius是z=height半径
2)gluSphere
gluSphere(qobj, 0.75, 15, 10);
	  void gluSphere (GLUquadric *qobj, GLdouble radius, GLint slices, GLint stacks);
	  //绘制一个半径为radius的球体,圆心(0,0,0),经线slices,纬线stacks
3)gluDisk
gluDisk(qobj, 0.25, 1.0, 20, 4);
	  void gluDisk (GLUquadric *qobj, GLdouble innerRadius, GLdouble , GLint slices, GLint loops);
	  //绘制一个位于z=0处的圆盘,innerRadius是内半径,outerRadius是外半径,loops是几个同心圆
4)gluPartialDisk
gluPartialDisk(qobj, 0.0, 1.0, 20, 4, 0.0, 225.0);
	  void gluPartialDisk (GLUquadric *qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops, GLdouble startAngle, GLdouble sweepAngle);
	  //绘制一个位于z=0处的不完整的圆盘,从startAngle开始,共绘制startAngle+sweepAngle度的圆盘
5)glutSolidCube
glutSolidCube(1.f);
6)glutSolidSphere
glutSolidSphere(1.0, 40, 50);

4、纹理映射(example)

#<https://blog.csdn.net/dcrmg/article/details/53180369>
#define _CRT_SECURE_NO_WARNINGS
#define WindowWidth  400
#define WindowHeight 400
#define WindowTitle  "OpenGL纹理测试"

#include <glut.h>
#include <stdio.h>
#include <stdlib.h>

//定义两个纹理对象编号
GLuint texGround;
GLuint texWall;

#define BMP_Header_Length 54  //图像数据在内存块中的偏移量
static GLfloat angle = 0.0f;   //旋转角度

// 函数power_of_two用于判断一个整数是不是2的整数次幂
int power_of_two(int n)
{
	if (n <= 0)
		return 0;
	return (n & (n - 1)) == 0;
}

/* 函数load_texture
* 读取一个BMP文件作为纹理
* 如果失败,返回0,如果成功,返回纹理编号
*/
GLuint load_texture(const char* file_name)
{
	GLint width, height, total_bytes;
	GLubyte* pixels = 0;
	GLuint last_texture_ID = 0, texture_ID = 0;

	// 打开文件,如果失败,返回
	FILE* pFile = fopen(file_name, "rb");
	if (pFile == 0)
		return 0;

	// 读取文件中图象的宽度和高度
	fseek(pFile, 0x0012, SEEK_SET);
	fread(&width, 4, 1, pFile);
	fread(&height, 4, 1, pFile);
	fseek(pFile, BMP_Header_Length, SEEK_SET);

	// 计算每行像素所占字节数,并根据此数据计算总像素字节数
	{
		GLint line_bytes = width * 3;
		while (line_bytes % 4 != 0)
			++line_bytes;
		total_bytes = line_bytes * height;
	}

	// 根据总像素字节数分配内存
	pixels = (GLubyte*)malloc(total_bytes);
	if (pixels == 0)
	{
		fclose(pFile);
		return 0;
	}

	// 读取像素数据
	if (fread(pixels, total_bytes, 1, pFile) <= 0)
	{
		free(pixels);
		fclose(pFile);
		return 0;
	}

	// 对就旧版本的兼容,如果图象的宽度和高度不是的整数次方,则需要进行缩放
	// 若图像宽高超过了OpenGL规定的最大值,也缩放
	{
		GLint max;
		glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
		if (!power_of_two(width)
			|| !power_of_two(height)
			|| width > max
			|| height > max)
		{
			const GLint new_width = 256;
			const GLint new_height = 256; // 规定缩放后新的大小为边长的正方形
			GLint new_line_bytes, new_total_bytes;
			GLubyte* new_pixels = 0;

			// 计算每行需要的字节数和总字节数
			new_line_bytes = new_width * 3;
			while (new_line_bytes % 4 != 0)
				++new_line_bytes;
			new_total_bytes = new_line_bytes * new_height;

			// 分配内存
			new_pixels = (GLubyte*)malloc(new_total_bytes);
			if (new_pixels == 0)
			{
				free(pixels);
				fclose(pFile);
				return 0;
			}

			// 进行像素缩放
			gluScaleImage(GL_RGB,
				width, height, GL_UNSIGNED_BYTE, pixels,
				new_width, new_height, GL_UNSIGNED_BYTE, new_pixels);

			// 释放原来的像素数据,把pixels指向新的像素数据,并重新设置width和height
			free(pixels);
			pixels = new_pixels;
			width = new_width;
			height = new_height;
		}
	}

	// 分配一个新的纹理编号
	glGenTextures(1, &texture_ID);
	if (texture_ID == 0)
	{
		free(pixels);
		fclose(pFile);
		return 0;
	}

	// 绑定新的纹理,载入纹理并设置纹理参数
	// 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复
	GLint lastTextureID = last_texture_ID;
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID);
	glBindTexture(GL_TEXTURE_2D, texture_ID);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
		GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
	glBindTexture(GL_TEXTURE_2D, lastTextureID);  //恢复之前的纹理绑定
	free(pixels);
	return texture_ID;
}


void display(void)
{
	// 清除屏幕
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// 设置视角
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(75, 1, 1, 21);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(-4, 7, 7, 0, 0, 0, 0, 0, 1);

	//glRotatef(angle, 0.0f, 0.0f, 1.0f); //旋转

	// 绘制底面以及纹理
	glBindTexture(GL_TEXTURE_2D, texGround);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-8.0f, -8.0f, 0.0f);
	glTexCoord2f(0.0f, 3.0f); glVertex3f(-8.0f, 8.0f, 0.0f);
	glTexCoord2f(3.0f, 3.0f); glVertex3f(8.0f, 8.0f, 0.0f);
	glTexCoord2f(3.0f, 0.0f); glVertex3f(8.0f, -8.0f, 0.0f);
	glEnd();
	// 绘制立面
	glBindTexture(GL_TEXTURE_2D, texWall);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-6.0f, -3.0f, 0.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-6.0f, -3.0f, 5.0f);
	glTexCoord2f(2.0f, 1.0f); glVertex3f(6.0f, -3.0f, 5.0f);
	glTexCoord2f(2.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);
	glEnd();

	//绘制另外一个立面
	glBegin(GL_QUADS);
	glTexCoord2f(2.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);
	glTexCoord2f(0.0f, 0.0f); glVertex3f(6.0f, 9.0f, 0.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(6.0f, 9.0f, 5.0f);
	glTexCoord2f(2.0f, 1.0f); glVertex3f(6.0f, -3.0f, 5.0f);
	glEnd();

	glutSwapBuffers();
}

void myIdle(void)
{
	angle += 1.8f;
	if (angle >= 360.0f)
		angle = 0.0f;
	display();
}

int main(int argc, char* argv[])
{
	// GLUT初始化
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(WindowWidth, WindowHeight);
	glutCreateWindow(WindowTitle);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);    // 启用纹理
	texGround = load_texture("lamu.bmp");  //加载纹理
	texWall = load_texture("leimu.bmp");
	glutDisplayFunc(&display);   //注册函数 
	glutIdleFunc(&myIdle);
	glutMainLoop(); //循环调用
	return 0;
}

三、几何图形绘制(★★★★★)

​ 在空间直角坐标系中,任意一点可用一个三维坐标矩阵[x y z]表示。如果将该点用一个四维坐标的矩阵[Hx Hy Hz H]表示时,则称为齐次坐标表示方法。在齐次坐标中,最后一维坐标H称为比例因子
  在OpenGL中,二维坐标点全看作三维坐标点,所有点都用齐次坐标来描述,统一作为三维齐次点来处理。每个齐次点用一个向量(x, y, z, w)表示,其中四个元素全不为零。齐次点具有下列几个性质:
  1)如果实数a非零,则(x, y, x, w)和(ax, ay, az, aw)表示同一个点,类似于x/y = (ax)/( ay)。
  2)三维空间点(x, y, z)的齐次点坐标为(x, y, z, 1.0),二维平面点(x,y)的齐次坐标为(x, y, 0.0, 1.0)。
  3)当w不为零时,齐次点坐标(x, y, z, w)即三维空间点坐标(x/w, y/w, z/w);当w为零时,齐次点(x, y, z, 0.0)表示此点位于某方向的无穷远处。
  注意:OpenGL中指定w大于或等于0.0

1、几何图形

在集合图形中,会涉及到几个概念:

1)点

​ 用浮点值表示的点称为顶点(Vertex)。所有顶点在OpenGL内部计算时都作为三维点处理,用二维坐标(x, y)定义的点在OpenGL中默认z值为0。所有顶点坐标用齐次坐标(x, y, z, w) 表示,如果w不为0.0,这些齐次坐标表示的顶点即为三维空间点(x/w, y/w, z/w)。编程者可以自己指定w值,但很少这样做。一般来说,w缺省为1.0

2)线

​ 在OpenGL中,线代表线段(Line Segment),不是数学意义上的那种沿轴两个方向无限延伸的线。这里的线由一系列顶点顺次连结而成,有闭合和不闭合两种

3)多边形

​ OpenGL中定义的多边形是由一系列线段依次连结而成的封闭区域。这些线段不能交叉,区域内不能有空洞,多边形必须在凸多边形,否则不能被OpenGL函数接受

2、绘制图元

1)定义顶点

在OpenGL中,所有几何物体最终都由有一定顺序的顶点集来描述的。函数glVertex{234}{sifd}[v\](TYPE coords)可以用二维、三维或齐次坐标定义顶点。例如:

glVertex2s(2,3);
glVertex3d(0.0,1.0,3.1414926535);
glVertex4f(2.4,1.0,-2.2,2.0);
GLfloat pp[3]={5.0,2.0,10.2};
glVertex3fv(pp);

第一例子表示一个空间顶点(2, 3, 0),第二个例子表示用双精度浮点数定义一个顶点,第三个例子表示用齐次坐标定义一个顶点,其真实坐标为(1.2, 0.5, -1.1),最后一个例子表示用一个指针(或数组)定义顶点。

2)几何图元

在实际应用中,通常用一组相关的顶点序列以一定的方式组织起来定义某个几何图元,而不采用单独定义多个顶点来构造几何图元。在OpenGL中,所有被定义的顶点必须放在glBegain()glEnd()两个函数之间才能正确表达一个几何图元或物体,否则,glVertex*()不完成任何操作。例如:

glBegin(GL_POLYGON);
    glVertex2f(0.0,0.0);
    glVertex2f(0.0,3.0);
    glVertex2f(3.0,3.0);
    glVertex2f(4.0,1.5);
    glVertex2f(3.0,0.0);
glEnd();

以上这段程序定义了一个多边形,如果将glBegin()中的参数GL_POLYGON改为GL_POINTS,则图形变为一组顶点(5个)

3)图元标志(example)

点函数glBegin(GLenum mode)标志描述一个几何图元的顶点列表的开始,其参数mode表示几何图元的描述类型。所有类型及说明见下表:

类型说明
GL_POINTS单个顶点集
GL_LINES多组双顶点线段
GL_POLYGON单个简单填充凸多边形
GL_TRAINGLES多组独立填充三角形
GL_QUADS多组独立填充四边形
GL_LINE_STRIP不闭合折线
GL_LINE_LOOP闭合折线
GL_TRAINGLE_STRIP线型连续填充三角形串
GL_TRAINGLE_FAN扇形连续填充三角形串
GL_QUAD_STRIP连续填充四边形串

glBegin()glEnd()之间最重要的信息就是由函数glVertex*()定义的顶点,必要时也可为每个顶点指定颜色、法向、纹理坐标或其他,即调用相关的函数,如下表。

函数说明
glVertex*()设置顶点坐标
glColor*()设置当前颜色
glIndex*()设置当前颜色表
glNormal*()设置法向坐标
glCallList(),glCallLists()执行显示列表
glTexCoord*()设置纹理坐标
glEdgeFlag*()控制边界绘制
glMaterial*()设置材质
glBegin(GL_POINTS);
    glColor3f(1.0,0.0,0.0); /* red color */
    glVertex(...);
    glColor3f(0.0,1.0,0.0); /* green color */
    glColor3f(0.0,0.0,1.0); /* blue color */
    glVertex(...);
    glVertex(...);
glEnd();

为了更好的理解OpenGL几何图形的绘制,下面看一个综合的示例:

#include<glut.h>

void DrawMyObjects(void) {
	/* draw some points */
	glBegin(GL_POINTS);
	glColor3f(1.0, 0.0, 0.0);
	glVertex2f(-10.0, 11.0);
	glColor3f(1.0, 1.0, 0.0);
	glVertex2f(-9.0, 10.0);
	glColor3f(0.0, 1.0, 1.0);
	glVertex2f(-8.0, 12.0);
	glEnd();


	/* draw some line_segments */
	glBegin(GL_LINES);
	glColor3f(1.0, 1.0, 0.0);
	glVertex2f(-11.0, 8.0);
	glVertex2f(-7.0, 7.0);
	glColor3f(1.0, 0.0, 1.0);
	glVertex2f(-11.0, 9.0);
	glVertex2f(-8.0, 6.0);
	glEnd();


	/* draw one opened_line */
	glBegin(GL_LINE_STRIP);
	glColor3f(0.0, 1.0, 0.0);
	glVertex2f(-3.0, 9.0);
	glVertex2f(2.0, 6.0);
	glVertex2f(3.0, 8.0);
	glVertex2f(-2.5, 6.5);
	glEnd();


	/* draw one closed_line */
	glBegin(GL_LINE_LOOP);
	glColor3f(0.0, 1.0, 1.0);
	glVertex2f(7.0, 7.0);
	glVertex2f(8.0, 8.0);
	glVertex2f(9.0, 6.5);
	glVertex2f(10.3, 7.5);
	glVertex2f(11.5, 6.0);
	glVertex2f(7.5, 6.0);
	glEnd();


	/* draw one filled_polygon */
	glBegin(GL_POLYGON);
	glColor3f(0.5, 0.3, 0.7);
	glVertex2f(-7.0, 2.0);
	glVertex2f(-8.0, 3.0);
	glVertex2f(-10.3, 0.5);
	glVertex2f(-7.5, -2.0);
	glVertex2f(-6.0, -1.0);
	glEnd();


	/* draw some filled_quandrangles */
	glBegin(GL_QUADS);
	glColor3f(0.7, 0.5, 0.2);
	glVertex2f(0.0, 2.0);
	glVertex2f(-1.0, 3.0);
	glVertex2f(-3.3, 0.5);
	glVertex2f(-0.5, -1.0);
	glColor3f(0.5, 0.7, 0.2);
	glVertex2f(3.0, 2.0);
	glVertex2f(2.0, 3.0);
	glVertex2f(0.0, 0.5);
	glVertex2f(2.5, -1.0);
	glEnd();

	/* draw some filled_strip_quandrangles */
	glBegin(GL_QUAD_STRIP);
	glVertex2f(6.0, -2.0);
	glVertex2f(5.5, 1.0);
	glVertex2f(8.0, -1.0);
	glColor3f(0.8, 0.0, 0.0);
	glVertex2f(9.0, 2.0);
	glVertex2f(11.0, -2.0);
	glColor3f(0.0, 0.0, 0.8);
	glVertex2f(11.0, 2.0);
	glVertex2f(13.0, -1.0);
	glColor3f(0.0, 0.8, 0.0);
	glVertex2f(14.0, 1.0);
	glEnd();


	/* draw some filled_triangles */

	glBegin(GL_TRIANGLES);
	glColor3f(0.2, 0.5, 0.7);
	glVertex2f(-10.0, -5.0);
	glVertex2f(-12.3, -7.5);
	glVertex2f(-8.5, -6.0);
	glColor3f(0.2, 0.7, 0.5);
	glVertex2f(-8.0, -7.0);
	glVertex2f(-7.0, -4.5);
	glVertex2f(-5.5, -9.0);
	glEnd();

	/* draw some filled_strip_triangles */
	glBegin(GL_TRIANGLE_STRIP);
	glVertex2f(-1.0, -8.0);
	glVertex2f(-2.5, -5.0);
	glColor3f(0.8, 0.8, 0.0);
	glVertex2f(1.0, -7.0);
	glColor3f(0.0, 0.8, 0.8);
	glVertex2f(2.0, -4.0);
	glColor3f(0.8, 0.0, 0.8);
	glVertex2f(4.0, -6.0);
	glEnd();


	/* draw some filled_fan_triangles */
	glBegin(GL_TRIANGLE_FAN);
	glVertex2f(8.0, -6.0);
	glVertex2f(10.0, -3.0);
	glColor3f(0.8, 0.2, 0.5);
	glVertex2f(12.5, -4.5);
	glColor3f(0.2, 0.5, 0.8);
	glVertex2f(13.0, -7.5);
	glColor3f(0.8, 0.5, 0.2);
	glVertex2f(10.5, -9.0);
	glEnd();
}

void draw() {
	glBegin(GL_POLYGON);
		glColor3f(1.0, 0.0, 0.0); /* red color */
		glVertex3f(0.0,0.0,0.0);

		glColor3f(0.0, 1.0, 0.0); /* green color */
		glVertex3f(100.0,100.0,0.0);

		glColor3f(0.0, 0.0, 1.0); /* blue color */
		glVertex3f(0.0,100.0,0.0);
	glEnd();
}

//绘图回调函数
void myDisplay() {
	//清除之前帧数据
	glClear(GL_COLOR_BUFFER_BIT);
	DrawMyObjects();

	//执行绘图命令
	glFlush();
}

//窗口大小变化回调函数
void reshape(int w, int h) {

	//视口变换
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);

	//投影(可以设置透视投影/平行投影,用来定义相机)
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 0.1, 100000.0);
	
	//GL_MODELVIEW is about having different objects being pushed into a "world space". 
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0, 0, 25, 0, 0, -1, 0, 1, 0);
}

int main(int argc, char* argv[]){
	//初始化窗口
	glutInit(&argc, argv);
	glutInitWindowSize(500, 500);
	//glutInitWindowPosition(100, 100);
	glutInitDisplayMode(GLUT_RGBA);

	//创建窗口
	glutCreateWindow("test Window");

	//投影 & 视口变换,将相机坐标系转到世界坐标系中
	glutReshapeFunc(reshape);

	//为当前窗口设置函数回调,用于画图
	glutDisplayFunc(myDisplay);
	glutMainLoop();
	
	return 0;
}

四、坐标系及坐标变换(★★★★★)

1、右手坐标系

openGL采用右手坐标系,关于左右手坐标系区别可参考下图。

2、坐标空间

openGL 空间分为:

  • 局部空间(Local Space,或者称为物体空间(Object Space))
  • 世界空间(World Space)
  • 观察空间(View Space,或者称为视觉空间(Eye Space))
  • 裁剪空间(Clip Space)
  • 屏幕空间(Screen Space)
1)局部空间

局部空间是指物体所在的坐标空间,即对象最开始所在的地方。想象你在一个建模软件中创建了一个立方体。你创建的立方体的原点有可能位于(0, 0, 0),即便它有可能最后在程序中处于完全不同的位置。甚至有可能你创建的所有模型都以(0, 0, 0)为初始位置。所以,你的模型的所有顶点都是在局部空间中,它们相对于你的物体来说都是局部的。

2)世界空间

​ 如果我们将我们所有的物体导入到程序当中,它们有可能会全挤在世界的原点(0, 0, 0)上,这并不是我们想要的结果。我们想为每一个物体定义一个位置,从而能在更大的世界当中放置它们。世界空间中的坐标正如其名:是指顶点相对于世界的坐标。如果你希望将物体分散在世界上摆放(特别是非常真实的那样),这就是你希望物体变换到的空间。物体的坐标将会从局部变换到世界空间;该变换是由模型矩阵(Model Matrix)实现的。
​ 模型矩阵是一种
变换矩阵
,它能通过对物体进行位移、缩放、旋转来将它置于它本应该在的位置或朝向。你可以将它想像为变换一个房子,你需要先将它缩小(它在局部空间中太大了),并将其位移至郊区的一个小镇,然后在y轴上往左旋转一点以搭配附近的房子。你也可以把上一节将箱子到处摆放在场景中用的那个矩阵大致看作一个模型矩阵;我们将箱子的局部坐标变换到场景/世界中的不同位置

3)观测空间

观察空间经常被人们称之OpenGL的摄像机(Camera)(所以有时也称为摄像机空间(Camera Space)或视觉空间(Eye Space))。观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。而这通常是由一系列的位移和旋转的组合来完成,平移/旋转场景从而使得特定的对象被变换到摄像机的前方。这些组合在一起的变换通常存储在一个**观察矩阵(View Matrix)**里,它被用来将世界坐标变换到观察空间。

4)裁剪空间

​ 在一个顶点着色器运行的最后,OpenGL期望所有的坐标都能落在一个特定的范围内,且任何在这个范围之外的点都应该被裁剪掉(Clipped)。被裁剪掉的坐标就会被忽略,所以剩下的坐标就将变为屏幕上可见的片段。这也就是裁剪空间(Clip Space)名字的由来。
​ 因为将所有可见的坐标都指定在−1.0 到1.0 的范围内不是很直观,所以我们会指定自己的坐标集(Coordinate Set)并将它变换回标准化设备坐标系,就像OpenGL期望的那样。
​ 为了将顶点坐标从观察变换到裁剪空间,我们需要定义一个投影矩阵(Projection Matrix),它指定了一个范围的坐标,比如在每个维度上的−1000 −1000到1000 1000。投影矩阵接着会将在这个指定的范围内的坐标变换为标准化设备坐标的范围(−1.0,1.0) 。所有在范围外的坐标不会被映射到在−1.0 到1.0 的范围之间,所以会被裁剪掉。在上面这个投影矩阵所指定的范围内,坐标(1250,500,750)将是不可见的,这是由于它的x 坐标超出了范围,它被转化为一个大于1.0的标准化设备坐标,所以被裁剪掉了。
​ 如果只是图元(Primitive),例如三角形,其中的一部分超出了裁剪体积(Clipping Volume),则OpenGL会重新构建这个三角形为一个或多个三角形让其能够适合这个裁剪范围。
​ 由投影矩阵创建的观察箱(Viewing Box)被称为平截头体(Frustum),每个出现在平截头体范围内的坐标都会最终出现在用户的屏幕上。将特定范围内的坐标转化到标准化设备坐标系的过程(而且它很容易被映射到2D观察空间坐标)被称之为投影(Projection),因为使用投影矩阵能将3D坐标投影(Project)到很容易映射到2D的标准化设备坐标系中。

5)屏幕空间

最终的坐标将会被映射到屏幕空间中(使用glViewport中的设定),并被变换成片段。

3、空间变换

​ 为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。物体顶点的起始坐标在局部空间(Local Space),这里称它为局部坐标(Local Coordinate),它在之后会变成世界坐标(world Coordinate),观测坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Corrdinate)的形式结束。

下面这张图阐释了 空间变换过程中的具体过程和结果。

4、相关API

空间变化相关的API有:

1)模型矩阵变换(局部坐标系 -> 世界坐标系)
void glTranslate{fd}(TYPE x,TYPE y,TYPE z)
void glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z)
void glScale{fd}(TYPE x,TYPE y,TYPE z)
2)视图矩阵变换(世界坐标系 -> 观测坐标系)
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz);
3)投影变换(观测坐标系 -> 裁剪坐标系)
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far)
void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)
void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far);
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);
4)视口变换(裁剪坐标系 -> 屏幕坐标系)
glViewport(GLint x,GLint y,GLsizei width, GLsizei height);

五、OpenGL纹理

​ 在三维图形中,纹理映射(Texture Mapping)的方法运用得很广,尤其描述具有真实感的物体。比如绘制一面砖墙,就可以用一幅真实的砖墙图像或照片作为纹理贴到一个矩形上,这样,一面逼真的砖墙就画好了。如果不用纹理映射的方法,则墙上的每一块砖都必须作为一个独立的多边形来画。另外,纹理映射能够保证在变换多边形时,多边形上的纹理图案也随之变化。例如,以透视投影方式观察墙面时,离视点远的砖块的尺寸就会缩小,而离视点较近的就会大些。此外,纹理映射也常常运用在其他一些领域,如飞行仿真中常把一大片植被的图像映射到一些大多边形上用以表示地面,或用大理石、木材、布匹等自然物质的图像作为纹理映射到多边形上表示相应的物体。

1、纹理分类

按照纹理的使用场景和表现形式来分,纹理主要分为以下几类:

  • 一维纹理,例如,程序所绘制的带纹理的镶条的所有变化可能发生在同一个方向,一维纹理就像一个高度为1的二维纹理
  • 二维纹理,其实是最容易理解的,也是最常用的,具有横向和纵向纹理坐标的,通常一个图片可以用作一个二维纹理
  • 三维纹理,最常见的应用是医学和地球科学领域的渲染。在医学应用程序中,三维纹理可以用于表示一系列的断层计算成像系统(CT)或者核磁共振(MRI)图像。对于石油和天然气研究人员,三维纹理可以用来对岩石底层进行建模。三维纹理可以看成一层层二维子图像矩形构成的
  • 球体纹理, 也就是环境纹理,目标是渲染具有完美反射能力的物体,它的表面颜色就是反射到人眼周围环境的颜色。
  • 立方体纹理,是一种特殊的纹理技术,它用6幅二维纹理图像构成一个以原点为中心的纹理立方体。立方体纹理非常适用于实现环境、反射和光照效果。
  • 多重纹理,多重纹理允许应用几个纹理,在纹理操作管线中把它们逐个应用到同一个多边形上。
    。。。

2、纹理定义

1)一维纹理
void glTexImage1D(GLenum target,GLint level,GLint components,GLsizei width,
 GLint border,GLenum format,GLenum type,const GLvoid *pixels);

定义一个一维纹理映射,除了第一个参数target应设置为GL_TEXTURE_1D外,其余所有的参数与函数TexImage2D()的一致,不过纹理图像是一维纹素数组,其宽度值必须是2的幂,若有边界则为2m+2。

2)二维纹理
void glTexImage2D(GLenum target,GLint level,GLint components,
           GLsizei width, glsizei height,GLint border,
           GLenum format,GLenum type, const GLvoid *pixels);

定义一个二维纹理映射。其中参数target是常数GL_TEXTURE_2D。参数level表示多级分辨率的纹理图像的级数,若只有一种分辨率,则level设为0。

  • 参数components是一个从1到4的整数,指出选择了R、G、B、A中的哪些分量用于调整和混合,1表示选择了R分量,2表示选择了R和A两个分量,3表示选择了R、G、B三个分量,4表示选择了R、G、B、A四个分量。
  • 参数width和height给出了纹理图像的长度和宽度,参数border为纹理边界宽度,它通常为0,width和height必须是2m+2b,这里m是整数,长和宽可以有不同的值,b是border的值。纹理映射的最大尺寸依赖于OpenGL,但它至少必须是使用64x64(若带边界为66x66),若width和height设置为0,则纹理映射有效地关闭。
  • 参数format和type描述了纹理映射的格式和数据类型,它们在这里的意义与在函数glDrawPixels()中的意义相同,事实上,纹理数据与**glDrawPixels()**所用的数据有同样的格式。参数format可以是GL_COLOR_INDEX、GL_RGB、GL_RGBA、GL_RED、GL_GREEN、GL_BLUE、GL_ALPHA、GL_LUMINANCE或GL_LUMINANCE_ALPHA(注意:不能用GL_STENCIL_INDEX和GL_DEPTH_COMPONENT)。类似地,参数type是GL_BYPE、GL_UNSIGNED_BYTE、GL_SHORT、 GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FLOAT或GL_BITMAP。
  • 参数pixels包含了纹理图像数据,这个数据描述了纹理图像本身和它的边界。

3、纹理控制函数

OpenGL中的纹理控制函数如下:

void glTexParameter{if}[v](GLenum target,GLenum pname,TYPE param);

第一个参数target可以是GL_TEXTURE_1D或GL_TEXTURE_2D,它指出是为一维或二维纹理说明参数;后两个参数的可能值见下表。

参数对应的值
GL_TEXTURE_WRAP_SGL_CLAMP ,GL_REPEAT
GL_TEXTURE_WRAP_TGL_CLAMP,GL_REPEAT
GL_TEXTURE_MAG_FILTERGL_NEAREST,GL_LINEAR
GL_TEXTURE_MIN_FILTERGL_NEAREST,GL_LINEAR,GL_NEAREST_MIPMAP_NEAREST ,GL_NEAREST_MIPMAP_LINEAR ,GL_LINEAR_MIPMAP_NEAREST ,GL_LINEAR_MIPMAP_LINEAR

一般来说,纹理图像为正方形或长方形。但当它映射到一个多边形或曲面上并变换到屏幕坐标时纹理的单个纹素很少对应于屏幕图像上的象素。根据所用变换和所用纹理映射,屏幕上单个象素可以对应于一个纹素的一小部分(即放大)或一大批纹素(即缩小)。下面用函数glTexParameter*()说明放大和缩小的方法:

glTexParameter*(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
  glTexParameter*(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
  • 实际上,第一个参数可以是GL_TEXTURE_1D或GL_TEXTURE_2D,即表明所用的纹理是一维的还是二维的;第二个参数指定滤波方法,其中参数值GL_TEXTURE_MAG_FILTER指定为放大滤波方法,GL_TEXTURE_MIN_FILTER指定为缩小滤波方法;第三个参数说明滤波方式。
  • 若选择GL_NEAREST则采用坐标最靠近象素中心的纹素,这有可能使图像走样;若选择GL_LINEAR则采用最靠近象素中心的四个象素的加权平均值。GL_NEAREST所需计算比GL_LINEAR要少,因而执行得更快,但GL_LINEAR提供了比较光滑的效果。
  • 同时,纹理坐标可以超出(0, 1)范围,并且在纹理映射过程中可以重复映射或约简映射。在重复映射的情况下,纹理可以在s,t方向上重复。例如:
glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);

纹理坐标

在绘制纹理映射场景时,不仅要给每个顶点定义几何坐标,而且也要定义纹理坐标。经过多种变换后,几何坐标决定顶点在屏幕上绘制的位置,而纹理坐标决定纹理图像中的哪一个纹素赋予该顶点。并且顶点之间的纹理坐标插值与基础篇中所讲的平滑着色插值方法相同。
  纹理图像是方形数组,纹理坐标通常可定义成一、二、三或四维形式,称为s,t,r和q坐标,以区别于物体坐标(x, y, z, w)和其他坐标。一维纹理常用s坐标表示,二维纹理常用(s, t)坐标表示,目前忽略r坐标,q坐标象w一样,一半值为1,主要用于建立齐次坐标。OpenGL坐标定义的函数是:

void gltexCoord{1234}{sifd}\[v](TYPE coords);

设置当前纹理坐标,此后调用glVertex*()所产生的顶点都赋予当前的纹理坐标。对于gltexCoord1*(),s坐标被设置成给定值,t和r设置为0,q设置为1;用gltexCoord2*()可以设置s和t坐标值,r设置为0,q设置为1;对于gltexCoord3*(),q设置为1,其它坐标按给定值设置;用gltexCoord4*()可以给定所有的坐标。使用适当的后缀(s,i,f或d)和TYPE的相应值(GLshort、GLint、glfloat或GLdouble)来说明坐标的类型。注意:整型纹理坐标可以直接应用,而不是象普通坐标那样被映射到[-1, 1]之间。

Note:代码缺BMPLoader.h

#define _CRT_SECURE_NO_DEPRECATE

#include <gl/glut.h>
#define WindowWidth  400
#define WindowHeight 400
#define WindowTitle  "OpenGL纹理测试"

#include <stdio.h>
#include <stdlib.h>

//定义两个纹理对象编号
GLuint texGround;
GLuint texWall;
GLuint tex2D;

#define BMP_Header_Length 54  //图像数据在内存块中的偏移量
static GLfloat angle = 10.0f;   //旋转角度

// 函数power_of_two用于判断一个整数是不是2的整数次幂
int power_of_two(int n)
{
	if (n <= 0)
		return 0;
	return (n & (n - 1)) == 0;
}

// 初始化参数
GLuint load_texture(const char* file_name)
{
	GLint width, height, total_bytes;
	GLubyte* pixels = 0;
	GLuint last_texture_ID = 0, texture_ID = 0;

	// 打开文件,如果失败,返回
	FILE* pFile = fopen(file_name, "rb");
	if (pFile == 0)
		return 0;

	// 读取文件中图象的宽度和高度
	fseek(pFile, 0x0012, SEEK_SET);
	fread(&width, 4, 1, pFile);
	fread(&height, 4, 1, pFile);
	fseek(pFile, BMP_Header_Length, SEEK_SET);

	// 计算每行像素所占字节数,并根据此数据计算总像素字节数
	{
		GLint line_bytes = width * 3;
		while (line_bytes % 4 != 0)
			++line_bytes;
		total_bytes = line_bytes * height;
	}

	// 根据总像素字节数分配内存
	pixels = (GLubyte*)malloc(total_bytes);
	if (pixels == 0)
	{
		fclose(pFile);
		return 0;
	}

	// 读取像素数据
	if (fread(pixels, total_bytes, 1, pFile) <= 0)
	{
		free(pixels);
		fclose(pFile);
		return 0;
	}

	// 对就旧版本的兼容,如果图象的宽度和高度不是的整数次方,则需要进行缩放
	// 若图像宽高超过了OpenGL规定的最大值,也缩放
	{
		GLint max;
		glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
		if (!power_of_two(width)
			|| !power_of_two(height)
			|| width > max
			|| height > max)
		{
			const GLint new_width = 256;
			const GLint new_height = 256; // 规定缩放后新的大小为边长的正方形
			GLint new_line_bytes, new_total_bytes;
			GLubyte* new_pixels = 0;

			// 计算每行需要的字节数和总字节数
			new_line_bytes = new_width * 3;
			while (new_line_bytes % 4 != 0)
				++new_line_bytes;
			new_total_bytes = new_line_bytes * new_height;

			// 分配内存
			new_pixels = (GLubyte*)malloc(new_total_bytes);
			if (new_pixels == 0)
			{
				free(pixels);
				fclose(pFile);
				return 0;
			}

			// 进行像素缩放
			gluScaleImage(GL_RGB,
				width, height, GL_UNSIGNED_BYTE, pixels,
				new_width, new_height, GL_UNSIGNED_BYTE, new_pixels);

			// 释放原来的像素数据,把pixels指向新的像素数据,并重新设置width和height
			free(pixels);
			pixels = new_pixels;
			width = new_width;
			height = new_height;
		}
	}

	// 分配一个新的纹理编号
	glGenTextures(1, &texture_ID);
	if (texture_ID == 0)
	{
		free(pixels);
		fclose(pFile);
		return 0;
	}

	// 绑定新的纹理,载入纹理并设置纹理参数
	// 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复
	GLint lastTextureID = last_texture_ID;
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID);
	glBindTexture(GL_TEXTURE_2D, texture_ID);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
		GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
	glBindTexture(GL_TEXTURE_2D, lastTextureID);  //恢复之前的纹理绑定
	free(pixels);
	return texture_ID;
}

//void init() {
//	glEnable(GL_DEPTH_TEST);
//	glDepthFunc(GL_LESS);
//	glClearColor(0.1, 0.1, 0.4, 0.0);
//	glShadeModel(GL_SMOOTH);
//	/*CBMPLoader bmpLoader;
//	bmpLoader.LoadBmp("leimu.bmp");*/
//
//
//	// 创建纹理
//	glGenTextures(1, &tex2D);
//	glBindTexture(GL_TEXTURE_2D, tex2D);
//
//	// 纹理滤波参数设置
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
//
//
//	// 设置纹理数据
//	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, bmpLoader.imageWidth, bmpLoader.imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, bmpLoader.image);
//	angle = 0;
//}


/** 绘制木箱 */
void DrawBox() {
	glEnable(GL_TEXTURE_2D);

	/** 选择纹理 */
	glBindTexture(GL_TEXTURE_2D, texWall);

	/** 开始绘制四边形 */
	glBegin(GL_QUADS);

	/// 前侧面
	glNormal3f(0.0f, 0.0f, 1.0f);                               /**指定法线指向观察者 */
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
	glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f);
	glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);

	/// 后侧面
	glNormal3f(0.0f, 0.0f, -1.0f);                              /** 指定法线背向观察者 */
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
	glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f);

	/// 顶面
	glNormal3f(0.0f, 1.0f, 0.0f);                               /**指定法线向上 */
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
	glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 1.0f);
	glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);

	/// 底面
	glNormal3f(0.0f, -1.0f, 0.0f);                              /** 指定法线朝下 */
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
	glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f);
	glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);

	/// 右侧面
	glNormal3f(1.0f, 0.0f, 0.0f);                               /**指定法线朝右 */
	glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f);
	glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, 1.0f, -1.0f);
	glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, -1.0f, 1.0f);

	/// 左侧面
	glNormal3f(-1.0f, 0.0f, 0.0f);                              /**指定法线朝左 */
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
	glEnd();
	glDisable(GL_TEXTURE_2D);
}

// 绘图回调函数
void display() {
	// 清除之前帧数据
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glPushMatrix();
	glTranslatef(0.0f, 0.0f, -5.0f);
	glRotated(angle, 1, 1, 0);
	DrawBox();
	glPopMatrix();

	// 执行绘图命令
	glFlush();
	angle = angle + 0.01;
	glutPostRedisplay();
}

// 窗口大小变化回调函数
void reshape(int w, int h) {
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 0.1, 100000.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

int main(int argc, const char * argv[]) {
	// 初始化显示模式
	glutInit(&argc, const_cast<char **>(argv));
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);

	// 初始化窗口
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	glutCreateWindow(argv[0]);

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);    // 启用纹理
	texGround = load_texture("lamu.bmp");  //加载纹理
	texWall = load_texture("leimu.bmp");
	
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);

	//glutIdleFunc(&myIdle);

	// 开始主循环绘制
	glutMainLoop();
	return 0;
}

六、OpenGL光照和材质

​ 当光照射到一个物体表面上时,会出现三种情形。首先,光可以通过物体表面向空间反射,产生反射光。其次,对于透明体,光可以穿透该物体并从另一端射出,产生透射光。最后,部分光将被物体表面吸收而转换成热。在上述三部分光中,仅仅是透射光和反射光能够进入人眼产生视觉效果。这里介绍的简单光照模型只考虑被照明物体表面的反射光影响,假定物体表面光滑不透明且由理想材料构成,环境假设为由白光照明。
  一般来说,反射光可以分成三个分量,即环境反射、漫反射和镜面反射

  • 环境反射分量假定入射光均匀地从周围环境入射至景物表面并等量地向各个方向反射出去,通常物体表面还会受到从周围环境来的反射光(如来自地面、天空、墙壁等的反射光)的照射,这些光常统称为环境光(Ambient Light);
  • 漫反射分量表示特定光源在景物表面的反射光中那些向空间各方向均匀反射出去的光,这些光常称为漫射光(Diffuse Light);
  • 镜面反射光为朝一定方向的反射光,如一个点光源照射一个金属球时会在球面上形成一块特别亮的区域,呈现所谓“高光(Highlight)”,它是光源在金属球面上产生的镜面反射光(Specular Light)。对于较光滑物体,其镜面反射光的高光区域小而亮;相反,粗糙表面的镜面反射光呈发散状态,其高光区域大而不亮。

1、光组成

​ 在OpenGL简单光照模型中的几种光分为:辐射光(Emitted Light)、环境光(Ambient Light)、漫射光(Diffuse Light)、镜面光(Specular Light)。

  • 辐射光是最简单的一种光,它直接从物体发出并且不受任何光源影响
  • 环境光是由光源发出经环境多次散射而无法确定其方向的光,即似乎来自所有方向。一般说来,房间里的环境光成分要多些,户外的相反要少得多,因为大部分光按相同方向照射,而且在户外很少有其他物体反射的光。当环境光照到曲面上时,它在各个方向上均等地发散(类似于无影灯光)。
  • 漫射光来自一个方向,它垂直于物体时比倾斜时更明亮。一旦它照射到物体上,则在各个方向上均匀地发散出去。于是,无论视点在哪里它都一样亮。来自特定位置和特定方向的任何光,都可能有散射成分。
  • 镜面光来自特定方向并沿另一方向反射出去,一个平行激光束在高质量的镜面上产生100%的镜面反射。光亮的金属和塑料具有很高非反射成分,而象粉笔和地毯等几乎没有反射成分。因此,三某种意义上讲,物体的反射程度等同于其上的光强(或光亮度)。

2、创建光源

光源有许多特性,如颜色、位置、方向等。选择不同的特性值,则对应的光源作用在物体上的效果也不一样,这在以后的章节中会逐步介绍的。下面详细讲述定义光源特性的函数glLight*()

void glLight{if}[v](GLenum light , GLenum pname, TYPE param)

创建具有某种特性的光源。其中第一个参数light指定所创建的光源号,如GL_LIGHT0GL_LIGHT1、…、GL_LIGHT7。第二个参数pname指定光源特性,这个参数的辅助信息见表1-3所示。最后一个参数设置相应的光源特性值。

pname 参数名默认值说明
GL_AMBIENT(0.0, 0.0, 0.0, 1.0)RGBA模式下环境光
GL_DIFFUSE(1.0, 1.0, 1.0, 1.0)RGBA模式下漫反射光
GL_SPECULAR(1.0,1.0,1.0,1.0)RGBA模式下镜面光
GL_POSITION(0.0,0.0,1.0,0.0)光源位置齐次坐标(x,y,z,w)
GL_SPOT_DIRECTION(0.0,0.0,-1.0)点光源聚光方向矢量(x,y,z)
GL_SPOT_EXPONENT0.0点光源聚光指数
GL_SPOT_CUTOFF180.0点光源聚光截止角
GL_CONSTANT_ATTENUATION1.0常数衰减因子
GL_LINER_ATTENUATION0.0线性衰减因子
GL_QUADRATIC_ATTENUATION0.0平方衰减因子

以上列出的GL_DIFFUSEGL_SPECULAR的缺省值只能用于GL_LIGHT0,其他几个光源的GL_DIFFUSEGL_SPECULAR缺省值为(0.0,0.0,0.0,1.0)。在上面例程中,光源的创建为:

GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);

其中light_position是一个指针,指向定义的光源位置齐次坐标数组。其它几个光源特性都为缺省值。同样,我们也可用类似的方式定义光源的其他几个特性值。例如:

GLfloat light_ambient [] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat light_diffuse [] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
glLightfv(GL_LIGHT0, GL_AMBIENT , light_ambient );
glLightfv(GL_LIGHT0, GL_DIFFUSE , light_diffuse );
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);

3、启动光照

在OpenGL中,必须明确指出光照是否有效或无效。如果光照无效,则只是简单地将当前颜色映射到当前顶点上去,不进行法向、光源、材质等复杂计算,那么显示的图形就没有真实感,如前几章例程运行结果显示。要使光照有效,首先得启动光照,启动光照需要用到如下函数:

glEnable(GL_LIGHTING);

若使光照无效,则调用glDisable(GL_LIGHTING)可关闭当前光照。然后,必须使所定义的每个光源有效,如果只用了一个光源。

glEnable(GL_LIGHT0);

其它光源类似,只是光源号不同而已。

4、材质颜色

​ OpenGL用材料对光的红、绿、蓝三原色的反射率来近似定义材料的颜色。像光源一样,材料颜色也分成环境、漫反射和镜面反射成分,它们决定了材料对环境光、漫反射光和镜面反射光的反射程度。在进行光照计算时,

  • 材料对环境光的反射率与每个进入光源的环境光结合;
  • 漫反射光的反射率与每个进入光源的漫反射光结合;
  • 镜面光的反射率与每个进入光源的镜面反射光结合。

​ 对环境光与漫反射光的反射程度决定了材料的颜色,并且它们很相似。

镜面反射光的反射率通常是白色或灰色(即对镜面反射光中红、绿、蓝的反射率相同)。镜面反射高光最亮的地方将变成具有光源镜面光强度的颜色。例如一个光亮的红色塑料球,球的大部分表现为红色,光亮的高光将是白色的。材质的定义与光源的定义类似

void glMaterial{if}[v](GLenum face,GLenum pname,TYPE param);

定义光照计算中用到的当前材质。face可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明当前材质应该应用到物体的哪一个面上;pname说明一个特定的材质;param是材质的具体数值,若函数为向量形式,则param是一组值的指针,反之为参数值本身。非向量形式仅用于设置GL_SHINESS。另外,参数GL_AMBIENT_AND_DIFFUSE表示可以用相同的RGB值设置环境光颜色和漫反射光颜色。

参数名默认值说明
GL_AMBIENT(0.2, 0.2, 0.2, 1.0)材料的环境光颜色
GL_DIFFUSE(0.8, 0.8, 0.8, 1.0)材料的漫反射光颜色
GL_AMBIENT_AND_DIFFUSE材料的环境光和漫反射光颜色
GL_SPECULAR(0.0, 0.0, 0.0, 1.0)材料的镜面反射光颜色
GL_SHINESS0.0镜面指数(光亮度)
GL_EMISSION(0.0, 0.0, 0.0, 1.0)材料的辐射光颜色
GL_COLOR_INDEXES(0, 1, 1)材料的环境光、漫反射光和镜面光颜色

5、材质RGB值 与 光源RGB

​ 材质的颜色与光源的颜色有些不同。

  • 对于光源,R、G、B值等于R、G、B对其最大强度的百分比。若光源颜色的R、G、B值都是1.0,则是最强的白光;若值变为0.5,颜色仍为白色,但强度为原来的一半,于是表现为灰色;若R=G=1.0,B=0.0,则光源为黄色。
  • 对于材质,R、G、B值为材质对光的R、G、B成分的反射率。比如,一种材质的R=1.0、G=0.5、B=0.0,则材质反射全部的红色成分,一半的绿色成分,不反射蓝色成分。也就是说,若OpenGL的光源颜色为(LR、LG、LB),材质颜色为(MR、MG、MB),那么,在忽略所有其他反射效果的情况下,最终到达眼睛的光的颜色为(LRMR、LGMG、LB*MB)。
    同样,如果有两束光,相应的值分别为(R1、G1、B1)和(R2、G2、B2),则OpenGL将各个颜色成分相加,得到(R1+R2、G1+G2、B1+B2),若任一成分的和值大于1(超出了设备所能显示的亮度)则约简到1.0。

6、示例

1)example1

下面的示例将演示光照和材质在OpenGL上的应用。

参考Opengl真实感球绘制

# include < GL/glut.h > 
/* 初始化材料属性、光源属性、光照模型,打开深度缓冲区等 */
void init(void)
{
	GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
	GLfloat mat_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
	glClearColor(0.0, 0.0, 1.0, 0.0); //设置背景色为蓝色
	glShadeModel(GL_SMOOTH);
	//glMaterialfv(GL_FRONT,GL_AMBIENT,mat_specular);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);
}
/*调用 GLUT 函数,绘制一个球*/
void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//glutSolidSphere(1.0, 40, 50);    //半径为 1,40 条纬线,50 条经线
	/*bed后背*/

	glutSolidCube(1.f);
	glRotatef(-45, 1.f, 0.f, 0.f);
	glRotatef(-45, 0.f, 1.f, 0.f);

	glFlush();
}
/* 定义 GLUT 的 reshape 函数,w、h 分别是输出图形的窗口的宽和高*/
void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	if (w <= h)
		glOrtho(-1.5, 1.5, -1.5 * (GLfloat)h / (GLfloat)w,
			1.5* (GLfloat)h / (GLfloat)w, -10.0, 10.0); //建立平行视景体
	else
		glOrtho(-1.5 * (GLfloat)w / (GLfloat)h, 1.5 * (GLfloat)
			w / (GLfloat)h, -1.5, 1.5, -10.0, 10.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}
int main(int argc, char** argv)
{
	glutInit(&argc, argv);     // GLUT 环境初始化
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH); // 显示模式初始化
	glutInitWindowSize(300, 300);       // 定义窗口大小
	glutInitWindowPosition(100, 100);   // 定义窗口位置  
	glutCreateWindow(argv[0]);   // 显示窗口,窗口标题为执行函数名
	init();
	glutDisplayFunc(display); 	// 注册 OpenGL 绘图函数(一种特殊的调用方式,下同) 
	glutReshapeFunc(reshape);   // 注册窗口大小改变时的响应函数
	glutMainLoop();      // 进入 GLUT 消息循环,开始执行程序
	return 0;
}

如果绘制的是球体

glutSolidSphere(1.0, 40, 50);    //半径为 1,40 条纬线,50 条经线
2)example2

光照模型详细介绍请跳转至光照和材质

#include <gl/glut.h>
 
#define WIDTH 400
#define HEIGHT 400
 
static GLfloat angle = 0.0f;
 
void myDisplay(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清空颜色和深度缓冲
 
	// 创建透视效果视图
	glMatrixMode(GL_PROJECTION);//对投影矩阵操作
	glLoadIdentity();//将坐标原点移到中心
	gluPerspective(90.0f, 1.0f, 1.0f, 20.0f);//设置透视投影矩阵
	glMatrixMode(GL_MODELVIEW);//对模型视景矩阵操作
	glLoadIdentity();
	gluLookAt(0.0, 5.0, -10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);//视点转换
 
	// 定义太阳光源,它是一种白色的光源
	{
		GLfloat sun_light_position[] = {0.0f, 0.0f, 0.0f, 1.0f};
		GLfloat sun_light_ambient[]  = {0.0f, 0.0f, 0.0f, 1.0f};
		GLfloat sun_light_diffuse[]  = {1.0f, 1.0f, 1.0f, 1.0f};
		GLfloat sun_light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
 
		glLightfv(GL_LIGHT0, GL_POSITION, sun_light_position); //指定第0号光源的位置 
		glLightfv(GL_LIGHT0, GL_AMBIENT,  sun_light_ambient); //GL_AMBIENT表示各种光线照射到该材质上,
			                                                  //经过很多次反射后最终遗留在环境中的光线强度(颜色)
		glLightfv(GL_LIGHT0, GL_DIFFUSE,  sun_light_diffuse); //漫反射后~~
		glLightfv(GL_LIGHT0, GL_SPECULAR, sun_light_specular);//镜面反射后~~~
 
		glEnable(GL_LIGHT0); //使用第0号光照
		glEnable(GL_LIGHTING); //在后面的渲染中使用光照
		glEnable(GL_DEPTH_TEST); //这句是启用深度测试,这样,在后面的物体会被挡着,例如房子后面有棵树,如果不启用深度测试,
		                 //你先画了房子再画树,树会覆盖房子的;但启用深度测试后无论你怎么画,树一定在房子后面(被房子挡着) 
	}
 
	// 定义太阳的材质并绘制太阳
	{
		GLfloat sun_mat_ambient[]  = {0.0f, 0.0f, 0.0f, 1.0f};
		GLfloat sun_mat_diffuse[]  = {0.0f, 0.0f, 0.0f, 1.0f};
		GLfloat sun_mat_specular[] = {0.0f, 0.0f, 0.0f, 1.0f};
		GLfloat sun_mat_emission[] = {0.5f, 0.0f, 0.0f, 1.0f};
		GLfloat sun_mat_shininess  = 0.0f;
 
		glMaterialfv(GL_FRONT, GL_AMBIENT,   sun_mat_ambient); //定义材料的前面采用 "多次反射"
		glMaterialfv(GL_FRONT, GL_DIFFUSE,   sun_mat_diffuse); //材料的前面为 漫反射
		glMaterialfv(GL_FRONT, GL_SPECULAR,  sun_mat_specular); //定义材料的前面为 镜面反射
		glMaterialfv(GL_FRONT, GL_EMISSION,  sun_mat_emission); //定义材料的前面为 镜面指数
		glMaterialf (GL_FRONT, GL_SHININESS, sun_mat_shininess); //材料的前面 采用 的颜色
 
		glutSolidSphere(2.0, 40, 32);
	}
 
	// 定义地球的材质并绘制地球
	{
		GLfloat earth_mat_ambient[]  = {0.0f, 0.0f, 0.5f, 1.0f};
		GLfloat earth_mat_diffuse[]  = {0.0f, 0.0f, 0.5f, 1.0f};
		GLfloat earth_mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};
		GLfloat earth_mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f};
		GLfloat earth_mat_shininess  = 30.0f;
 
		glMaterialfv(GL_FRONT, GL_AMBIENT,   earth_mat_ambient);
		glMaterialfv(GL_FRONT, GL_DIFFUSE,   earth_mat_diffuse);
		glMaterialfv(GL_FRONT, GL_SPECULAR,  earth_mat_specular);
		glMaterialfv(GL_FRONT, GL_EMISSION,  earth_mat_emission);
		glMaterialf (GL_FRONT, GL_SHININESS, earth_mat_shininess);
 
		glRotatef(angle, 0.0f, -1.0f, 0.0f);
		glTranslatef(5.0f, 0.0f, 0.0f);
 
		//如果使用glutSolidSphere函数来绘制球体,则该函数会自动的指定这些法线向量,
		//不必再手工指出。如果是自己指定若干的顶点来绘制一个球体,则需要自己指定法线向量。
		glutSolidSphere(2.0, 40, 32);
	}
 
	glutSwapBuffers(); //交换缓冲区
}
void myIdle(void)
{
	angle += 1.0f;
	if( angle >= 360.0f )
		angle = 0.0f;
	myDisplay();
}
 
int main(int argc, char* argv[])
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowPosition(200, 200);
	glutInitWindowSize(WIDTH, HEIGHT);
	glutCreateWindow("OpenGL光照演示");
	glutDisplayFunc(&myDisplay);
	glutIdleFunc(&myIdle);
	glutMainLoop();
	return 0;
}

七、常见问题

1、gluLookAt的使用

参考OpenGL之gluLookAt 函数详解

void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz);
该函数定义一个视图矩阵,并与当前矩阵相乘。

  • 第一组eyex, eyey,eyez 相机在世界坐标的位置
  • 第二组centerx,centery,centerz 相机镜头对准的物体在世界坐标的位置
  • 第三组upx,upy,upz 相机向上的方向在世界坐标中的方向

你把相机想象成为你自己的脑袋:

  • 第一组数据就是脑袋的位置
  • 第二组数据就是眼睛看的物体的位置
  • 第三组就是头顶朝向的方向(因为你可以歪着头看同一个物体)。

代码参考Opengl—gluLookAt函数详解(转)

#include <GL/glut.h>
#include <stdlib.h>

void init(void)
{
	glClearColor(0.0, 0.0, 0.0, 0.0); //背景黑色
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT);
	glColor3f(1.0, 1.0, 1.0); //画笔白色

	glLoadIdentity();  //加载单位矩阵

	gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
	glutWireTeapot(2);
	glutSwapBuffers();
}

void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 1.0, 20.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}
int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	glutCreateWindow(argv[0]);
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutMainLoop();
	return 0;
}

如果修改display中的gluLookAt(不是reshape中的),其中gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

gluLookAt(0.0, 5.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
gluLookAt(0.0, 5.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

2、GL_PROJECTION & GL_MODELVIEW的区别

https://blog.csdn.net/Jolin_daikun/article/details/3950983

  • GL_PROJECTION is a matrix transformation that is applied to every point that comes after it,always. GL_PROJECTION transformations are always applied, so, we can use it to define a camera, set the perspective, among other things.
glMatrixMode(GL_PROJECTION);

glLoadIdentity();

glOrtho(-1, 1, -1, 1, -1.0, 1.0);
glTranslate( 100, 100, 100 );

glRotateF( 45, 1, 0, 0 );

Really means: GL_PROJECTION_MATRIX = IDENTITY * ORTHOGRAPHIC_MATRIX * TRANSLATION_MATRIX * ROTATION_MATRIX.

  • GL_MODELVIEW is a matrix transformation that is applied to every point in a particular model. GL_MODELVIEW is about having different objects being pushed into a “world space”(相机坐标系转世界坐标系).
glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

glTranslate( modelx, modely, modelz );

2、什么是视口变换

参考https://www.jianshu.com/p/2e109f335149

glViewPort(0,0,width,height)

  • openGL 中的glViewPort 官方定义为,视口变换。
    意思是将已经通过透视除法转换为归一化的坐标的相应数据,转化到具体的屏幕(窗口)坐标。
    该接口,有4个参数,x,y,width,height。具体区别通过下图展示。

glViewPort()作用总结

  • 归一化坐标转换为屏幕坐标的接口。换言之将整张纹理上的数据,转换到屏幕上具体的像素点的接口。

  • x,y为以控件左下角为起始坐标(VS中右上角为起始坐标),对应渲染纹理的左下角。

  • 右为x轴的正方向(VS中左为x正方向)。

  • 上为y轴的正方向(VS中下为y正方向)。

  • width,height是以x,y为起始位置的宽和高,用来确定渲染出的数据到屏幕的位置。
    可以在屏幕上正常渲染出来的像素范围为x轴:0–width,y轴:0–height。超出部分将不显示。

  • 用户可以通过该接口,控制数据渲染到屏幕的具体位置和范围
    如果超过该空间的屏幕像素,将不显示。但并不意味着opengGL没有绘制超出显示部分的纹理数据。

3、抗锯齿

1)方法1:启用混合功能

参考

  • OpenGL中的抗锯齿绘线(上)
  • OpenGL抗锯齿、提高显示效果的设置
	//OpenGl设定
	glEnable(GL_BLEND);             //启用混合功能,将图形颜色同周围颜色相混合  
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);   
 
	glEnable(GL_POLYGON_SMOOTH);     //多边形抗锯齿  
	glHint(GL_POLYGON_SMOOTH,GL_NICEST);
 
	glEnable(GL_LINE_SMOOTH);        //线抗锯齿  
	glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
 
	glEnable(GL_POINT_SMOOTH);       //点抗锯齿  
	glHint(GL_POINT_SMOOTH,GL_NICEST);

但是会生成很多虚线

2)方法2:GL_MULTISAMPLE

参考OpenGL核心技术之抗锯齿,需要导入glew.h,但是不一定起效果。

4、二次曲面的纹理映射

quadratic=gluNewQuadric();// 创建二次几何体
gluQuadricNormals(quadratic, GLU_SMOOTH);/// 使用平滑法线
gluQuadricTexture(quadratic, GL_TRUE);// 使用纹理

5、绘制图像的显示区域

// 1 绘制外围同心圆
	int cx = Width / 9; //中心点
	int cy = Height / 9;
	int R = 50; // 半径长
	int n = 100;
	int i;

	glBegin(GL_LINE_LOOP); // 绘制多边形,n足够大就会变成圆形
	for (i = 0; i < n; i++)
	{
		glVertex2f(cx + (R + 2)*cos(2 * PI / n * i), cy + (R + 2)*sin(2 * PI / n * i));
		/*printf("%f", cx + (R + 2)*cos(2 * PI / n * i));*/
	}
	glEnd();
...

如果圆的参数修改了,此时还是有绘制图像的,但有可能不在该窗口上显示

// 1 绘制外围同心圆
	int cx = Width / 2; //中心点
	int cy = Height / 2;
	int R = 200; // 半径长
	int n = 100;
	int i;

6、光照和材质

参考openGL学习笔记5(光照)

1)建立光照模型

OpenGL在处理光照时采用这样一种近似:把光照系统分为三部分,分别是光源、材质和光照环境

光源就是光的来源,可以是前面所说的太阳或者电灯等。

材质是指接受光照的各种物体的表面,由于物体如何反射光线只由物体表面决定(OpenGL中没有考虑光的折射),材质特点就决定了物体反射光线的特点。

光照环境是指一些额外的参数,它们将影响最终的光照画面,比如一些光线经过多次反射后,已经无法分清它究竟是由哪个光源发出,这时,指定一个“环境亮度”参数,可以使最后形成的画面更接近于真实情况。

在物理学中,光线如果射入理想的光滑平面,则反射后的光线是很规则的(这样的反射称为镜面反射)。

光线如果射入粗糙的、不光滑的平面,则反射后的光线是杂乱的(这样的反射称为漫反射)。

现实生活中的物体在反射光线时,并不是绝对的镜面反射或漫反射,但可以看成是这两种反射的叠加

对于光源发出的光线,可以分别设置其经过镜面反射和漫反射后的光线强度。对于被光线照射的材质,也可以分别设置光线经过镜面反射和漫反射后的光线强度。这些因素综合起来,就形成了最终的光照效果

2)法线向量

根据光的反射定律,由光的入射方向和入射点的法线就可以得到光的出射方向

因此,对于指定的物体,在指定了光源后,即可计算出光的反射方向,进而计算出光照效果的画面。

在OpenGL中,法线的方向是用一个向量来表示。
不幸的是,OpenGL并不会根据你所指定的多边形各个顶点来计算出这些多边形所构成的物体的表面的每个点的法线(这话听着有些迷糊),通常,为了实现光照效果,需要在代码中为每一个顶点指定其法线向量
指定法线向量的方式与指定颜色的方式有雷同之处。在指定颜色时,只需要指定每一个顶点的颜色,OpenGL就可以自行计算顶点之间的其它点的颜色。

并且,颜色一旦被指定,除非再指定新的颜色,否则以后指定的所有顶点都将以这一向量作为自己的颜色。

在指定法线向量时,只需要指定每一个顶点的法线向量,OpenGL会自行计算顶点之间的其它点的法线向量

并且,法线向量一旦被指定,除非再指定新的法线向量,否则以后指定的所有顶点都将以这一向量作为自己的法线向量。

使用glColor*函数可以指定颜色,而使用glNormal*函数则可以指定法线向量

注意:使用glTranslate*函数或者glRotate*函数可以改变物体的外观,但法线向量并不会随之改变。

然而,使用glScale*函数,对每一坐标轴进行不同程度的缩放,很有可能导致法线向量的不正确,虽然OpenGL提供了一些措施来修正这一问题,但由此也带来了各种开销。

因此,在使用了法线向量的场合,应尽量避免使用glScale*函数。即使使用,也最好保证各坐标轴进行等比例缩放

3)控制光源

在OpenGL中,仅仅支持有限数量的光源。

使用GL_LIGHT0表示第0号光源,GL_LIGHT1表示第1号光源,依次类推.

OpenGL至少会支持8个光源,即GL_LIGHT0到GL_LIGHT7

使用glEnable函数可以开启它们。例如,glEnable(GL_LIGHT0);可以开启第0号光源。

使用glDisable函数则可以关闭光源。

一些OpenGL实现可能支持更多数量的光源,但总的来说,开启过多的光源将会导致程序运行速度的严重下降,玩过3D Mark的朋友可能多少也有些体会。一些场景中可能有成百上千的电灯,这时可能需要采取一些近似的手段来进行编程,否则以目前的计算机而言,是无法运行这样的程序的。

每一个光源都可以设置其属性,这一动作是通过glLight*函数完成的。

glLight*函数具有三个参数,第一个参数指明是设置哪一个光源的属性,第二个参数指明是设置该光源的哪一个属性,第三个参数则是指明把该属性值设置成多少。光源的属性众多,下面将分别介绍

(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。

这三个属性表示了光源所发出的光的反射特性(以及颜色)。

每个属性由四个值表示,分别代表了颜色的R, G, B, A值

GL_AMBIENT表示该光源所发出的光,经过非常多次的反射后,最终遗留在整个光照环境中的强度(颜色)。

GL_DIFFUSE表示该光源所发出的光,照射到粗糙表面时经过漫反射,所得到的光的强度(颜色)

GL_SPECULAR表示该光源所发出的光,照射到光滑表面时经过镜面反射,所得到的光的强度(颜色)。

(2)GL_POSITION属性

表示光源所在的位置。

由四个值(X, Y, Z, W)表示。

如果第四个值W为零,则表示该光源位于无限远处,前三个值表示了它所在的方向。

这种光源称为方向性光源,通常,太阳可以近似的被认为是方向性光源。

如果第四个值W不为零,则X/W, Y/W, Z/W表示了光源的位置。

这种光源称为位置性光源。对于位置性光源,设置其位置与设置多边形顶点的方式相似,各种矩阵变换函数例如:glTranslate*、glRotate*等在这里也同样有效。

方向性光源在计算时比位置性光源快了不少,因此,在视觉效果允许的情况下,应该尽可能的使用方向性光源。

(3)GL_SPOT_DIRECTION、GL_SPOT_EXPONENT、GL_SPOT_CUTOFF属性

表示将光源作为聚光灯使用(这些属性只对位置性光源有效)。很多光源都是向四面八方发射光线,但有时候一些光源则是只向某个方向发射,比如手电筒,只向一个较小的角度发射光线。

GL_SPOT_DIRECTION属性有三个值,表示一个向量,即光源发射的方向。

GL_SPOT_EXPONENT属性只有一个值,表示聚光的程度,为零时表示光照范围内向各方向发射的光线强度相同,为正数时表示光照向中央集中,正对发射方向的位置受到更多光照,其它位置受到较少光照。数值越大,聚光效果就越明显。

GL_SPOT_CUTOFF属性也只有一个值,表示一个角度,它是光源发射光线所覆盖角度的一半(见下图),其取值范围在0到90之间,也可以取180这个特殊值。取值为180时表示光源发射光线覆盖360度,即不使用聚光灯,向全周围发射。

(4)GL_CONSTANT_ATTENUATION、GL_LINEAR_ATTENUATION、GL_QUADRATIC_ATTENUATION属性。

这三个属性表示了光源所发出的光线的直线传播特性(这些属性只对位置性光源有效)。

现实生活中,光线的强度随着距离的增加而减弱,OpenGL把这个减弱的趋势抽象成函数:

衰减因子 = 1 / (k1 + k2 * d + k3 * k3 * d)

其中d表示距离,光线的初始强度乘以衰减因子,就得到对应距离的光线强度。

k1, k2, k3分别就是GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, GL_QUADRATIC_ATTENUATION。

通过设置这三个常数,就可以控制光线在传播过程中的减弱趋势。

属性还真是不少。当然了,如果是使用方向性光源,(3)(4)这两类属性就不会用到了,问题就变得简单明了。

4)控制材质

材质与光源相似,也需要设置众多的属性。不同的是,光源是通过glLight*函数来设置的,而材质则是通过glMaterial*函数来设置的。

glMaterial*函数有三个参数。第一个参数表示指定哪一面的属性。可以是GL_FRONTGL_BACK或者GL_FRONT_AND_BACK。分别表示设置“正面”“背面”的材质,或者两面同时设置。

第二、第三个参数与glLight*函数的第二、三个参数作用类似。

下面分别说明glMaterial*函数可以指定的材质属性。

  • GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。

这三个属性与光源的三个对应属性类似,每一属性都由四个值组成。

GL_AMBIENT表示各种光线照射到该材质上,经过很多次反射后最终遗留在环境中的光线强度(颜色)。

GL_DIFFUSE表示光线照射到该材质上,经过漫反射后形成的光线强度(颜色)。

GL_SPECULAR表示光线照射到该材质上,经过镜面反射后形成的光线强度(颜色)。

通常,GL_AMBIENT和GL_DIFFUSE都取相同的值,可以达到比较真实的效果。

使用GL_AMBIENT_AND_DIFFUSE可以同时设置GL_AMBIENT和GL_DIFFUSE属性。

  • GL_SHININESS属性

该属性只有一个值,称为“镜面指数”,取值范围是0到128。

该值越小,表示材质越粗糙,点光源发射的光线照射到上面,也可以产生较大的亮点。该值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点。

  • GL_EMISSION属性。该属性由四个值组成,表示一种颜色。OpenGL认为该材质本身就微微的向外发射光线,以至于眼睛感觉到它有这样的颜色,但这光线又比较微弱,以至于不会影响到其它物体的颜色。

  • GL_COLOR_INDEXES属性

该属性仅在颜色索引模式下使用,由于颜色索引模式下的光照比RGBA模式要复杂,并且使用范围较小,这里不做讨论。

5)选择光照模型

这里所说的“光照模型”是OpenGL的术语,它相当于我们在前面提到的“光照环境”。

在OpenGL中,光照模型包括四个部分的内容:

  • 全局环境光线(即那些充分散射,无法分清究竟来自哪个光源的光线)的强度

  • 观察点位置是在较近位置还是在无限远处

  • 物体正面与背面是否分别计算光照

  • 镜面颜色(即GL_SPECULAR属性所指定的颜色)的计算是否从其它光照计算中分离出来,并在纹理操作以后在进行应用。

以上四方面的内容都通过同一个函数glLightModel*来进行设置。

该函数有两个参数,第一个表示要设置的项目,第二个参数表示要设置成的值。

  • GL_LIGHT_MODEL_AMBIENT表示全局环境光线强度,由四个值组成。

  • GL_LIGHT_MODEL_LOCAL_VIEWER表示是否在近处观看,若是则设置为GL_TRUE,否则(即在无限远处观看)设置为GL_FALSE。

  • GL_LIGHT_MODEL_TWO_SIDE表示是否执行双面光照计算。如果设置为GL_TRUE,则OpenGL不仅将根据法线向量计算正面的光照,也会将法线向量反转并计算背面的光照。

  • GL_LIGHT_MODEL_COLOR_CONTROL表示颜色计算方式。

如果设置为GL_SINGLE_COLOR,表示按通常顺序操作,先计算光照,再计算纹理

如果设置为GL_SEPARATE_SPECULAR_COLOR,表示将GL_SPECULAR属性分离出来,先计算光照的其它部分**,待纹理操作完成后再计算GL_SPECULAR**。

后者通常可以使画面效果更为逼真(当然,如果本身就没有执行任何纹理操作,这样的分离就没有任何意义)。

OpenGL默认是关闭光照处理的。要打开光照处理功能,使用下面的语句:glEnable(GL_LIGHTING);
要关闭光照处理功能,使用glDisable(GL_LIGHTING);即可。

code:跳转至example2

相关文章:

  • 嵌入式系统开发笔记91:认识ARM微控制器架构
  • gnn explainer笔记
  • Cadence Allegro 在Gerber光绘中生成板卡层叠结构文件
  • 达梦数据物理逻辑备份还原
  • ssm项目布置流程
  • JavaScript简介与快速体验
  • ExecutorService、Callable、Future实现有返回结果的多线程原理解析
  • java 字节流写入文件内容实现换行
  • Greenplum数据库数据分片策略Hash分布——执行器行为
  • java题3
  • 初探Prometheus+grafana
  • Axios入门
  • 数据库(mysql)主从复制与读写分离
  • Windbg 命令 (四)
  • 【华为机试真题JavaScript】分班
  • Angular4 模板式表单用法以及验证
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • git 常用命令
  • iOS小技巧之UIImagePickerController实现头像选择
  • JavaScript异步流程控制的前世今生
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • TypeScript实现数据结构(一)栈,队列,链表
  • Webpack 4 学习01(基础配置)
  • 从0实现一个tiny react(三)生命周期
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 两列自适应布局方案整理
  • 主流的CSS水平和垂直居中技术大全
  • 阿里云服务器如何修改远程端口?
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • #100天计划# 2013年9月29日
  • #pragam once 和 #ifndef 预编译头
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (Java数据结构)ArrayList
  • (附源码)php投票系统 毕业设计 121500
  • (一)u-boot-nand.bin的下载
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (转)shell中括号的特殊用法 linux if多条件判断
  • (转)树状数组
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • *++p:p先自+,然后*p,最终为3 ++*p:先*p,即arr[0]=1,然后再++,最终为2 *p++:值为arr[0],即1,该语句执行完毕后,p指向arr[1]
  • .babyk勒索病毒解析:恶意更新如何威胁您的数据安全
  • .Mobi域名介绍
  • .NET 4.0中的泛型协变和反变
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • /etc/shadow字段详解
  • /proc/stat文件详解(翻译)
  • @Transactional 竟也能解决分布式事务?
  • [Android]创建TabBar
  • [CC-FNCS]Chef and Churu
  • [C进阶] 数据在内存中的存储——浮点型篇
  • [datastore@cyberfear.com].Elbie、[thekeyishere@cock.li].Elbie勒索病毒数据怎么处理|数据解密恢复
  • [HarmonyOS]第一课:从简单的页面开始