# OpenGL十二讲代码—by yjq

OpenGL入门教程

【侵权立删】

## 第一讲

### 画一个矩形

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>

void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);
//glBegin(GL_POINTS);
glBegin(GL_POLYGON);//单个简单填充多边形
glVertex2f(0.0f, 0.0f);
glVertex2f(0.5f, 0.0f);
glVertex2f(0.5f, 0.5f);
glVertex2f(0.0f, 0.5f);
glEnd();
//glRectf(-0.5f, -0.5f, 0.5f, 0.5f);
glFlush();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(400, 400);
glutCreateWindow("几何图形的绘制");
glutDisplayFunc(&myDisplay);
glutMainLoop();
return 0;
}

``````

## 第二讲

### 画一个五角星

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>

const int n = 1000;
//修改const int n的值，观察当n=3,4,5,8,10,15,20,30,50等不同数值时输出的变化情况
const GLfloat R = 0.5f;
const GLfloat Pi = 3.1415926536;

void myDisplay(void)
{
//画一个五角星
GLfloat a = 1 / (2 - 2 * cos(72 * Pi / 180));
GLfloat bx = a * cos(18 * Pi / 180);
GLfloat by = a * sin(18 * Pi / 180);
GLfloat cy = -a * cos(18 * Pi / 180);
GLfloat
PointA[2] = { 0, a },
PointB[2] = { bx, by },
PointC[2] = { 0.5, cy },
PointD[2] = { -0.5, cy },
PointE[2] = { -bx, by };

glClear(GL_COLOR_BUFFER_BIT);
// 按照A->C->E->B->D->A的顺序，可以一笔将五角星画出
glBegin(GL_LINE_LOOP);
glVertex2fv(PointA);
glVertex2fv(PointC);
glVertex2fv(PointE);
glVertex2fv(PointB);
glVertex2fv(PointD);
glEnd();
glFlush();
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(400, 400);
glutCreateWindow("第二讲——画一个五角星");
glutDisplayFunc(&myDisplay);
glutMainLoop();
return 0;
}
``````

### 画一个圆

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>

const int n = 1000;
//修改const int n的值，观察当n=3,4,5,8,10,15,20,30,50等不同数值时输出的变化情况
const GLfloat R = 0.5f;
const GLfloat Pi = 3.1415926536;

void myDisplay(void)
{
//画一个圆
int i;
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_POLYGON);//单个简单填充多边形
for (i = 0; i < n; i++) {
glVertex2f(R * cos(2 * Pi / n * i), R * sin(2 * Pi / n * i));
}
glEnd();
glFlush();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(400, 400);
glutCreateWindow("第二讲——画一个圆");
glutDisplayFunc(&myDisplay);
glutMainLoop();
return 0;
}
``````

### 画一个正弦函数

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>

const GLfloat factor = 0.1f;
void myDisplay(void)
{
GLfloat x;
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_LINES);
glVertex2f(-1.0f, 0.0f);
glVertex2f(1.0f, 0.0f);        // 以上两个点可以画x轴
glVertex2f(0.0f, -1.0f);
glVertex2f(0.0f, 1.0f);        // 以上两个点可以画y轴
glEnd();
glBegin(GL_LINE_STRIP);
for (x = -1.0f / factor; x < 1.0f / factor; x += 0.01f)
{
glVertex2f(x * factor, sin(x) * factor);
}
glEnd();
glFlush();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(400, 400);
glutCreateWindow("第二讲——画一个正弦函数");
glutDisplayFunc(&myDisplay);
glutMainLoop();
return 0;
}

``````

## 第三讲

### 画点

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>

void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glPointSize(5.0f);
glBegin(GL_POINTS);
glVertex2f(0.0f, 0.0f);
glVertex2f(0.5f, 0.5f);
glEnd();
glFlush();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(400, 400);
glutCreateWindow("第三讲——画两个点");
glutDisplayFunc(&myDisplay);
glutMainLoop();
return 0;
}
``````

### 画虚线

pattern是由1和0组成的长度为16的序列，从最低位开始看，如果为1，则直线上接下来应该画的factor个点将被画为实的；如果为0，则直线上接下来应该画的factor个点将被画为虚的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F4bjJ6WD-1641975777776)(C:/Users/26969/AppData/Local/Temp/msohtmlclip1/01/clip_image001.gif)]

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>

void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_LINE_STIPPLE);
glLineStipple(2, 0x0F0F);
glLineWidth(10.0f);
glBegin(GL_LINES);
glVertex2f(0.0f, 0.0f);
glVertex2f(0.5f, 0.5f);
glEnd();
glFlush();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(400, 400);
glutCreateWindow("第三讲——画虚线");
glutDisplayFunc(&myDisplay);
glutMainLoop();
return 0;
}
``````

### 多边形

#### 多边形的两面

``````glPolygonMode(GL_FRONT, GL_FILL);      // 设置正面为填充方式
glPolygonMode(GL_BACK, GL_LINE);      // 设置反面为边缘绘制方式
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); // 设置两面均为顶点绘制方式
``````

#### 前后反转

`````` glFrontFace(GL_CCW); // 设置CCW方向为“正面”，CCW即CounterClockWise，逆时针
glFrontFace(GL_CW);  // 设置CW方向为“正面”，CW即ClockWise，顺时针
``````
``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glPolygonMode(GL_FRONT, GL_FILL); // 设置正面为填充模式
glPolygonMode(GL_BACK, GL_LINE);  // 设置反面为线形模式
glFrontFace(GL_CCW);              // 设置逆时针方向为正面
glBegin(GL_POLYGON);              // 按逆时针绘制一个正方形，在左下方
glVertex2f(-0.5f, -0.5f);
glVertex2f(0.0f, -0.5f);
glVertex2f(0.0f, 0.0f);
glVertex2f(-0.5f, 0.0f);
glEnd();
glBegin(GL_POLYGON);              // 按顺时针绘制一个正方形，在右上方
glVertex2f(0.0f, 0.0f);
glVertex2f(0.0f, 0.5f);
glVertex2f(0.5f, 0.5f);
glVertex2f(0.5f, 0.0f);
glEnd();
glFlush();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(400, 400);
glutCreateWindow("第三讲——多边形正反面");
glutDisplayFunc(&myDisplay);
glutMainLoop();
return 0;
}

``````

#### 剔除多边形表面

glCullFace的参数可以是GL_FRONT，GL_BACK或者GL_FRONT_AND_BACK，分别表示剔除正面、剔除反面、剔除正反两面的多边形。

#### 镂空多边形

`````` void glPolygonStipple(const GLubyte*mask);
``````

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
void myDisplay(void)
{
//镂空效果
glClear(GL_COLOR_BUFFER_BIT);
FILE* fp;
if (!fp)
exit(0);
exit(0);
exit(0);
fclose(fp);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_POLYGON_STIPPLE);//启动剔除功能
glRectf(-0.5f, -0.5f, 0.5f, 0.5f);   // 绘制一个有镂空效果的正方形
glFlush();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(400, 400);
glutCreateWindow("第三讲——多边形镂空");
glutDisplayFunc(&myDisplay);
glutMainLoop();
return 0;
}
``````

## 第四讲

### RGBA颜色

``````void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3ub(255,192,203);//粉色
glRectf(-0.5f, -0.5f, 0.5f, 0.5f);
glFlush();
}
``````

glColor系列函数，在参数类型不同时，表示“最大”颜色的值也不同。

### 颜色索引

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#include <time.h>
#include "tex.h"

#pragma comment (lib, "opengl32.lib")
#pragma comment (lib, "glaux.lib")
#pragma comment(lib, "legacy_stdio_definitions.lib")

const GLdouble Pi = 3.1415926536;
void myDisplay(void)
{
int i;
for (i = 0; i < 8; ++i)
auxSetOneColor(i, (float)(i & 0x04), (float)(i & 0x02), (float)(i & 0x01));
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLE_FAN);
glVertex2f(0.0f, 0.0f);
for (i = 0; i <= 8; ++i)
{
glIndexi(i);
glVertex2f(cos(i * Pi / 4), sin(i * Pi / 4));
}
glEnd();
glFlush();
}

int main(void)
{
auxInitDisplayMode(AUX_SINGLE | AUX_INDEX);
auxInitPosition(0, 0, 400, 400);
auxInitWindow(L"");
myDisplay();
Sleep(10 * 1000);
return 0;
}

``````

### 颜色表

`````` glShadeModel的使用方法：
``````
``````void myDisplay(void)
{
int i;
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLE_FAN);
glColor3f(1.0f, 1.0f, 1.0f);
glVertex2f(0.0f, 0.0f);
for (i = 0; i <= 8; ++i)
{
glColor3f(i & 0x04, i & 0x02, i & 0x01);
glVertex2f(cos(i * Pi / 4), sin(i * Pi / 4));
}
glEnd();
glFlush();
}
``````

## 第五讲

glMatrixMode(GL_MODELVIEW);

glTranslate*，把当前矩阵和一个表示移动物体的矩阵相乘。三个参数分别表示了在三个坐标上的位移值。
glRotate*，把当前矩阵和一个表示旋转物体的矩阵相乘。物体将绕着(0,0,0)到(x,y,z)的直线以逆时针旋转，参数angle表示旋转的角度。
glScale*，把当前矩阵和一个表示缩放物体的矩阵相乘。x,y,z分别表示在该方向上的缩放比例。

“先移动后旋转”和“先旋转后移动”得到的结果很可能不同，初学的时候需要特别注意这一点。

### 太阳月亮地球

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>

// 太阳、地球和月亮
// 假设每个月都是30天
// 一年12个月，共是360天
static int day = 200; // day的变化：从0到359
void myDisplay(void)
{
glDepthFunc(GL_ALWAYS);//总是绘制
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glMatrixMode(GL_PROJECTION);
gluPerspective(75, 1, 1, 400000000);
glMatrixMode(GL_MODELVIEW);
gluLookAt(0, -200000000, 200000000, 0, 0, 0, 0, 0, 1);

// 绘制红色的“太阳”
glColor3f(1.0f, 0.0f, 0.0f);
glutSolidSphere(69600000, 20, 20);
// 绘制蓝色的“地球”
glColor3f(0.0f, 0.0f, 1.0f);
glRotatef(day / 360.0 * 360.0, 0.0f, 0.0f, -1.0f);
glTranslatef(150000000, 0.0f, 0.0f);
glutSolidSphere(15945000, 20, 20);
// 绘制黄色的“月亮”
glColor3f(1.0f, 1.0f, 0.0f);
glRotatef(day / 30.0 * 360.0 - day / 360.0 * 360.0, 0.0f, 0.0f, -1.0f);
glTranslatef(38000000, 0.0f, 0.0f);
glutSolidSphere(4345000, 20, 20);
glFlush();
}

int  main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(400, 400);
glutCreateWindow("第五讲——太阳月亮地球");
glutDisplayFunc(myDisplay);
glutMainLoop();
return 0;
}
``````

## 第六讲

### 太阳月亮地球加旋转

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#include<time.h>
static int day = 200; // day的变化：从0到359
double CalFrequency()
{
static int count;
static double save;
static clock_t last, current;
double timegap;

++count;
if (count <= 50)
return save;
count = 0;
last = current;
current = clock();
timegap = (current - last) / (double)CLK_TCK;
save = 50.0 / timegap;
return save;
}//统计该函数自身的调用频率

void myDisplay(void)
{
double FPS = CalFrequency();
printf("FPS = %f\\n", FPS);
glDepthFunc(GL_ALWAYS);//总是绘制
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glMatrixMode(GL_PROJECTION);
gluPerspective(75, 1, 1, 400000000);
glMatrixMode(GL_MODELVIEW);
gluLookAt(0, -200000000, 200000000, 0, 0, 0, 0, 0, 1);

// 绘制红色的“太阳”
glColor3f(1.0f, 0.0f, 0.0f);
glutSolidSphere(69600000, 20, 20);
// 绘制蓝色的“地球”
glColor3f(0.0f, 0.0f, 1.0f);
glRotatef(day / 360.0 * 360.0, 0.0f, 0.0f, -1.0f);
glTranslatef(150000000, 0.0f, 0.0f);
glutSolidSphere(15945000, 20, 20);
// 绘制黄色的“月亮”
glColor3f(1.0f, 1.0f, 0.0f);
glRotatef(day / 30.0 * 360.0 - day / 360.0 * 360.0, 0.0f, 0.0f, -1.0f);
glTranslatef(38000000, 0.0f, 0.0f);
glutSolidSphere(4345000, 20, 20);

glFlush();
glutSwapBuffers();
}

void myIdle(void)
{
/* 新的函数，在空闲时调用，作用是把日期往后移动一天并重新绘制，达到动画效果 */
++day;
if (day >= 360)
day = 0;
myDisplay();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); // 修改了参数为GLUT_DOUBLE
glutInitWindowPosition(100, 100);
glutInitWindowSize(400, 400);
glutCreateWindow("第六讲——太阳，地球和月亮");   // 改了窗口标题
glutDisplayFunc(&myDisplay);
glutIdleFunc(&myIdle);               // CPU空闲的时间调用某一函数
glutMainLoop();
return 0;
}

``````

## 第七讲

### 太阳月亮加光照

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#include<time.h>
#define WIDTH 400
#define HEIGHT 400

static GLfloat angle = 0.0f;

void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1.0, 1.0, 1.0,1.0);
// 创建透视效果视图
glMatrixMode(GL_PROJECTION);
gluPerspective(90.0f, 1.0f, 1.0f, 20.0f);
glMatrixMode(GL_MODELVIEW);
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);
glLightfv(GL_LIGHT0, GL_AMBIENT, sun_light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, sun_light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, sun_light_specular);

glEnable(GL_LIGHT0);
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(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_RGB | GLUT_DOUBLE); // 修改了参数为GLUT_DOUBLE
glutInitWindowPosition(100, 100);
glutInitWindowSize(WIDTH,HEIGHT);
glutCreateWindow("第七讲——太阳，地球和月亮");   // 改了窗口标题
glutDisplayFunc(&myDisplay);
glutIdleFunc(&myIdle);               // CPU空闲的时间调用某一函数
glutMainLoop();
return 0;
}

``````

## 第八讲

#### 一、分配显示列表编号

OpenGL允许多个显示列表同时存在，就好象C语言允许程序中有多个函数同时存在。C语言中，不同的函数用不同的名字来区分，而在OpenGL中，不同的显示列表用不同的正整数来区分。

glGenLists函数有一个参数i，表示要分配i个连续的未使用的显示列表编号。返回的是分配的若干连续编号中最小的一个。例如，glGenLists(3);如果返回20，则表示分配了20、21、22这三个连续的编号。如果函数返回零，表示分配失败。

#### 二、创建显示列表

`````` glNewList(list, GL_COMPILE);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 0.0f);
glEnd();
``````

`````` int i = 3;
glNewList(list, GL_COMPILE);
if( i > 20 )
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 0.0f);
glEnd();
``````

#### 三、调用显示列表

GL_BYTE（每个编号用一个GLbyte表示），GL_UNSIGNED_BYTE（每个编号

``````用一个GLubyte表示），GL_SHORT，GL_UNSIGNED_SHORT，GL_INT，GL_UNSIGNED_INT，GL_FLOAT。
``````

`````` GLuint lists[] = {1, 3, 4, 8};
glListBase(10);
glCallLists(4, GL_UNSIGNED_INT, lists);
``````

#### 四、销毁显示列表

1、明显的减少OpenGL函数的调用次数。如果函数调用是通过网络进行的（Linux等操作系统支持这样的方式，即由应用程序在客户端发出OpenGL请求，由网络上的另一台服务器进行实际的绘图操作），将显示列表保存在服务器端，可以大大减少网络负担。
2、保存中间结果，避免一些不必要的计算。例如前面的样例程序中，cos、sin函数的计算结果被直接保存到显示列表中，以后使用时就不必重复计算。
3、便于优化。我们已经知道，使用glTranslate*、glRotate*、glScale*等函数时，实际上是执行矩阵乘法操作，由于这些函数经常被组合在一起使用，通常会出现矩阵的连乘。这时，如果把这些操作保存到显示列表中，则一些复杂的OpenGL版本会尝试先计算出连乘的一部分结果，从而提高程序的运行速度。在其它方面也可能存在类似的例子。

#### 举例

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#define WIDTH 400
#define HEIGHT 400

#define ColoredVertex(c, v) do{ glColor3fv(c); glVertex3fv(v); }while(0)

GLfloat angle = 0.0f;

void myDisplay(void)
{
static int list = 0;
if (list == 0)
{
// 如果显示列表不存在，则创建
GLfloat
PointA[] = { 0.5f, (GLfloat) (-sqrt(6.0f) / 12) , (GLfloat)(-sqrt(3.0f) / 6) },
PointB[] = { -0.5f, (GLfloat)(-sqrt(6.0f) / 12) , (GLfloat)(-sqrt(3.0f) / 6) },
PointC[] = { 0.0f, (GLfloat)(-sqrt(6.0f) / 12) ,   (GLfloat)(sqrt(3.0f) / 3) },
PointD[] = { 0.0f,   (GLfloat)(sqrt(6.0f) / 4),             0 };
GLfloat
ColorR[] = { 1, 0, 0 },
ColorG[] = { 0, 1, 0 },
ColorB[] = { 0, 0, 1 },
ColorY[] = { 1, 1, 0 };

list = glGenLists(1);
glNewList(list, GL_COMPILE);
glBegin(GL_TRIANGLES);
// 平面ABC
ColoredVertex(ColorR, PointA);
ColoredVertex(ColorG, PointB);
ColoredVertex(ColorB, PointC);
// 平面ACD
ColoredVertex(ColorR, PointA);
ColoredVertex(ColorB, PointC);
ColoredVertex(ColorY, PointD);
// 平面CBD
ColoredVertex(ColorB, PointC);
ColoredVertex(ColorG, PointB);
ColoredVertex(ColorY, PointD);
ColoredVertex(ColorG, PointB);
ColoredVertex(ColorR, PointA);
ColoredVertex(ColorY, PointD);
glEnd();
glEndList();

glEnable(GL_DEPTH_TEST);
}
// 已经创建了显示列表，在每次绘制正四面体时将调用它
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(angle, 1, 0.5, 0);
glCallList(list);
glPopMatrix();
glutSwapBuffers();
}

void myIdle(void)
{
++angle;
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("第八讲——显示列表");
glutDisplayFunc(&myDisplay);
glutIdleFunc(&myIdle);
glutMainLoop();
return 0;
}

``````

## 第九讲

### 源因子与目标因子

OpenGL会把源颜色和目标颜色各自取出，并乘以一个系数（源颜色乘以的系数称为“源因子”，目标颜色乘以的系数称为“目标因子”），然后相加，这样就得到了新的颜色。

``````Rs*Sr+Rd*Dr, Gs*Sg+Gd*Dg, Bs*Sb+Bd*Db, As*Sa+Ad*Da
``````

`````` GL_ZERO：   表示使用0.0作为因子，实际上相当于不使用这种颜色参与混合运算。
GL_ONE：   表示使用1.0作为因子，实际上相当于完全的使用了这种颜色参与混合运算。
GL_SRC_ALPHA：表示使用源颜色的alpha值来作为因子。
GL_DST_ALPHA：表示使用目标颜色的alpha值来作为因子。
GL_ONE_MINUS_SRC_ALPHA：表示用1.0减去源颜色的alpha值来作为因子。
GL_ONE_MINUS_DST_ALPHA：表示用1.0减去目标颜色的alpha值来作为因子。
``````

🏷 举例来说：
1️⃣ 如果设置了`glBlendFunc(GL_ONE, GL_ZERO);，`则表示完全使用源颜色，完全不使用目标颜色，因此画面效果和不使用混合的时候一致（当然效率可能会低一点点）。如果没有设置源因子和目标因子，则默认情况就是这样的设置。

2️⃣ 如果设置了`glBlendFunc(GL_ZERO, GL_ONE);，`表示完全不使用源颜色，因此无论你想画什么，最后都不会被画上去了。（但这并不是说这样设置就没有用，有些时候可能有特殊用途）

3️⃣ 如果设置了`glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);，`表示源颜色乘以自身的alpha值，目标颜色乘以1.0减去源颜色的alpha值，这样一来，源颜色的alpha值越大，则产生的新颜色中源颜色所占比例就越大，而目标颜色所占比例则减小。这种情况下，我们可以简单的将源颜色的alpha值理解为“ 不透明度”。这也是混合时最常用的方式。

4️⃣ 如果设置了glBlendFunc(GL_ONE, GL_ONE);，则表示完全使用源颜色和目标颜色，最终的颜色实际上就是两种颜色的简单相加。例如红色(1, 0, 0)和绿色(0, 1, 0)相加得到(1, 1, 0)，结果为黄色。

⚠️ 注意：

### 二维图形混合举例

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#define WIDTH 400
#define HEIGHT 400

void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);

glEnable(GL_BLEND);
//glBlendFunc(GL_ONE, GL_ZERO);//与不混和没有区别
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//表示使用源颜色的alpha值来作为因子，表示用1.0减去源颜色的alpha值来作为因子。
glBlendFunc(GL_ONE, GL_ONE);//两种颜色混合
glColor4f(1, 0, 0, 0.5);
glRectf(-1, -1, 0.5, 0.5);
glColor4f(0, 1, 0, 0.5);
glRectf(-0.5, -0.5, 1, 1);

glutSwapBuffers();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(200, 200);
glutInitWindowSize(WIDTH, HEIGHT);
glutCreateWindow("第九讲——颜色混合");
glutDisplayFunc(&myDisplay);
glutMainLoop();
return 0;
}

``````

### 三维混合

``````glDepthMask(GL_FALSE);可将深度缓冲区设置为只读形式
``````

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#define WIDTH 400
#define HEIGHT 400

void myDisplay1(void)
{
glClear(GL_COLOR_BUFFER_BIT);

glEnable(GL_BLEND);
//glBlendFunc(GL_ONE, GL_ZERO);//与不混和没有区别
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//表示使用源颜色的alpha值来作为因子，表示用1.0减去源颜色的alpha值来作为因子。
glBlendFunc(GL_ONE, GL_ONE);//两种颜色混合
glColor4f(1, 0, 0, 0.5);
glRectf(-1, -1, 0.5, 0.5);
glColor4f(0, 1, 0, 0.5);
glRectf(-0.5, -0.5, 1, 1);

glutSwapBuffers();
}

void setLight(void)
{
static const GLfloat light_position[] = { 1.0f, 1.0f, -1.0f, 1.0f };
static const GLfloat light_ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
static const GLfloat light_diffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };
static const GLfloat light_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };

glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);

glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
}
//每一个球体颜色不同。所以它们的材质也都不同。这里用一个函数来设置材质。
void setMatirial(const GLfloat mat_diffuse[4], GLfloat mat_shininess)
{
static const GLfloat mat_specular[] = { 0.0f, 0.0f, 0.0f, 1.0f };
static const GLfloat mat_emission[] = { 0.0f, 0.0f, 0.0f, 1.0f };

glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
glMaterialf(GL_FRONT, GL_SHININESS, mat_shininess);
}

void myDisplay2(void)
{
// 定义一些材质颜色
const static GLfloat red_color[] = { 1.0f, 0.0f, 0.0f, 1.0f };
const static GLfloat green_color[] = { 0.0f, 1.0f, 0.0f, 0.3333f };
const static GLfloat blue_color[] = { 0.0f, 0.0f, 1.0f, 0.5f };

// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 启动混合并设置混合因子
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

// 设置光源
setLight();

// 以(0, 0, 0.5)为中心，绘制一个半径为.3的不透明红色球体（离观察者最远）
setMatirial(red_color, 30.0);
glPushMatrix();
glTranslatef(0.0f, 0.0f, 0.5f);
glutSolidSphere(0.3, 30, 30);
glPopMatrix();

// 下面将绘制半透明物体了，因此将深度缓冲设置为只读

// 以(0.2, 0, -0.5)为中心，绘制一个半径为.2的半透明蓝色球体（离观察者最近）
setMatirial(blue_color, 30.0);
glPushMatrix();
glTranslatef(0.2f, 0.0f, -0.5f);
glutSolidSphere(0.2, 30, 30);//第一个参数表示球体的半径，后两个参数代表了“面”的数目，简单点说就是球体的精确程度，数值越大越精确，当然代价就是速度越缓慢
glPopMatrix();

// 以(0.1, 0, 0)为中心，绘制一个半径为.15的半透明绿色球体（在前两个球体之间）
setMatirial(green_color, 30.0);
glPushMatrix();
glTranslatef(0.1, 0, 0);
glutSolidSphere(0.15, 30, 30);
glPopMatrix();

// 完成半透明物体的绘制，将深度缓冲区恢复为可读可写的形式

glutSwapBuffers();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(200, 200);
glutInitWindowSize(WIDTH, HEIGHT);
glutCreateWindow("第九讲——颜色混合");
glutDisplayFunc(&myDisplay2);
glutMainLoop();
return 0;
}
``````

## 第十讲

glDrawPixels：绘制一些像素。当前可以简单理解为“把内存中一些数据作为像素数据，进行绘制”。
glCopyPixels：复制一些像素。当前可以简单理解为“把已经绘制好的像素从一个位置复制到另一个位置”。虽然从功能上看，好象等价于先读取像素再绘制像素，但实际上它不需要把已经绘制的像素（它可能已经被保存到显卡的显存中）转换为内存数据，然后再由内存数据进行重新的绘制，所以要比先读取后绘制快很多。

Windows所使用的BMP文件，在开始处有一个文件头，大小为54字节。保存了包括文件格式标识、颜色数、图象大小、压缩方式等信息，因为我们仅讨论24位色不压缩的BMP，所以文件头中的信息基本不需要注意，只有“大小”这一项对我们比较有用。

### 像素读取

``````#define WindowWidth   500
#define WindowHeight 500

/* 函数grab
* 抓取窗口中的像素
* 假设窗口宽度为WindowWidth，高度为WindowHeight
*/
void grab(void)
{
FILE* pDummyFile;
FILE* pWritingFile;
GLubyte* pPixelData;
GLint     i, j;
GLint     PixelDataLength;

// 计算像素数据的实际长度
i = WindowWidth * 3;    // 得到每一行的像素数据长度
//24位色的BMP文件中，每三个字节表示一个像素的颜色。
while (i % 4 != 0)       // 补充数据，直到i是4的倍数
++i;                // 本来还有更快的算法，
// 但这里仅追求直观，对速度没有太高要求
PixelDataLength = i * WindowHeight;

// 分配内存和打开文件
pPixelData = (GLubyte*)malloc(PixelDataLength);
if (pPixelData == 0)
exit(0);

fopen_s(&pDummyFile,"dummy.bmp", "rb");
if (pDummyFile == 0)
exit(0);

fopen_s(&pWritingFile,"grab.bmp", "wb");
if (pWritingFile == 0)
exit(0);

// 读取像素
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);//4对齐
GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData);//最后一个参数，是像素读取后存放在内存中的位置

// 把dummy.bmp的文件头复制为新文件的文件头
fseek(pWritingFile, 0x0012, SEEK_SET); //移动到0x0012位置,读取宽度
i = WindowWidth;
//读完以后自动到下一个高度
j = WindowHeight;
fwrite(&i, sizeof(i), 1, pWritingFile);
fwrite(&j, sizeof(j), 1, pWritingFile);

// 写入像素数据
fseek(pWritingFile, 0, SEEK_END);//当前文件尾部，即头信息后
//偏移起始位置：文件头0(SEEK_SET)，当前位置1(SEEK_CUR)，文件尾2(SEEK_END)）
fwrite(pPixelData, PixelDataLength, 1, pWritingFile);

// 释放内存和关闭文件
fclose(pDummyFile);
fclose(pWritingFile);
free(pPixelData);
}
void init()
{
{

/*设置光源*/
GLfloat light_position[] = { 0.5,0.0,1.0,0.0 };//光源位置
GLfloat light_ambient[] = { 1.0,1.0,1.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_POSITION, light_position);//设置光源的位置
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
}

{
/*设置球的材质*/
GLfloat sphere_specular[] = { 1.0,1.0,1.0,1.0 };
GLfloat sphere_diffuse[] = { 1.0,1.0,1.0,1.0 };
GLfloat sphere_ambient[] = { 0.0,0.0,0.0,1.0 };
GLfloat sphere_emission[] = { 0.0,0.0,0.0,1.0 };//向外散光
GLfloat sphere_shininess[] = { 100.0 }; //镜面反射指数

glMaterialfv(GL_FRONT, GL_AMBIENT, sphere_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, sphere_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, sphere_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, sphere_emission);
glMaterialfv(GL_FRONT, GL_SPECULAR, sphere_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, sphere_shininess);
}

GLfloat gl_model_ambient[] = { 0.2,0.2,0.2,1.0 };//微弱环境光，使物体可见

glClearColor(1.0, 0.75, 0.78, 1.0);//背景颜色

glLightModelfv(GL_LIGHT_MODEL_AMBIENT, gl_model_ambient);
//指定全局的环境光，物体才能可见
glEnable(GL_LIGHTING);//启动光照
glEnable(GL_LIGHT0);	//启动光源
glEnable(GL_DEPTH_TEST);//启动深度测试
}
void myDisplay()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glutSolidSphere(1.0, 3000, 1000);//半径，南北经线，纬线
grab();
glFlush();
}
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);//使像素矩阵占据整个新窗口
glMatrixMode(GL_PROJECTION);//设置当前操作的矩阵为“投影视图矩阵”

float nowRange = 2;
GLfloat nRange = (GLfloat)nowRange;

if (w <= h) //改变窗口大小，图形形状不变
glOrtho(-nRange, nRange, -nRange * h / w, nRange * h / w, -nRange, nRange);
else
glOrtho(-nRange * w / h, nRange * w / h, -nRange, nRange, -nRange, nRange);
//将当前的可视空间设置为正投影空间
glMatrixMode(GL_MODELVIEW);//设置当前操作的矩阵为“模型视图矩阵”

}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(WindowWidth, WindowHeight);
glutCreateWindow("OpenGL3D编程基础");
init();//初始化视图设置
glutDisplayFunc(&myDisplay);
glutReshapeFunc(reshape);//改变窗口
glutMainLoop();
return 0;
}
``````

glPixelStorei(GL_UNPACK_ALIGNMENT,4)控制的是所读取数据的对齐方式，默认4字节对齐，即一行的图像数据字节数必须是4的整数倍，即读取数据时，读取4个字节用来渲染一行，之后读取4字节数据用来渲染第二行。对RGB 3字节像素而言，若一行10个像素，即30个字节，在4字节对齐模式下，OpenGL会读取32个字节的数据，若不加注意，会导致glTextImage中致函数的读取越界，从而全面崩溃。

glDrawPixels

### 像素绘制

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#include <time.h>

static GLint    ImageWidth;
static GLint    ImageHeight;
static GLint    PixelLength;
static GLubyte* PixelData;

void display(void)
{
// 清除屏幕并不必要
// 每次绘制时，画面都覆盖整个屏幕
// 因此无论是否清除屏幕，结果都一样
// glClear(GL_COLOR_BUFFER_BIT);

// 绘制像素
glDrawPixels(ImageWidth, ImageHeight,
GL_BGR_EXT, GL_UNSIGNED_BYTE, PixelData);

// 完成绘制
glutSwapBuffers();
}

int main(int argc, char* argv[])
{
// 打开文件
FILE* pFile;
fopen_s(&pFile, "grab.bmp", "rb");
if (pFile == 0)
exit(0);

// 读取图象的大小信息
fseek(pFile, 0x0012, SEEK_SET);

// 计算像素数据长度
PixelLength = ImageWidth * 3;
while (PixelLength % 4 != 0)
++PixelLength;
PixelLength *= ImageHeight;

// 读取像素数据
PixelData = (GLubyte*)malloc(PixelLength);
if (PixelData == 0)
exit(0);

fseek(pFile, 54, SEEK_SET);

// 关闭文件
fclose(pFile);

// 初始化GLUT并运行
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(ImageWidth, ImageHeight);
glutCreateWindow("读取像素");
glutDisplayFunc(&display);
glutMainLoop();

return 0;
}

``````

glCopyPixels函数也通过glRasterPos*系列函数来设置绘制的位置，因为不需要涉及到主内存，所以不需要指定数据在内存中的格式，也不需要使用任何指针。

glCopyPixels函数有五个参数，第一、二个参数表示复制像素来源的矩形的左下角坐标，第三、四个参数表示复制像素来源的举行的宽度和高度，第五个参数通常使用GL_COLOR，表示复制像素的颜色，但也可以是GL_DEPTH或GL_STENCIL，分别表示复制深度缓冲数据或模板缓冲数据。

### 像素复制

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#include <time.h>

#define WindowWidth   500
#define WindowHeight 500

/* 函数grab
* 抓取窗口中的像素
* 假设窗口宽度为WindowWidth，高度为WindowHeight
*/
void grab(void)
{
FILE* pDummyFile;
FILE* pWritingFile;
GLubyte* pPixelData;
GLint     i, j;
GLint     PixelDataLength;

// 计算像素数据的实际长度
i = WindowWidth * 3;    // 得到每一行的像素数据长度
//24位色的BMP文件中，每三个字节表示一个像素的颜色。
while (i % 4 != 0)       // 补充数据，直到i是4的倍数
++i;                // 本来还有更快的算法，
// 但这里仅追求直观，对速度没有太高要求
PixelDataLength = i * WindowHeight;

// 分配内存和打开文件
pPixelData = (GLubyte*)malloc(PixelDataLength);
if (pPixelData == 0)
exit(0);

fopen_s(&pDummyFile,"dummy.bmp", "rb");
if (pDummyFile == 0)
exit(0);

fopen_s(&pWritingFile,"grab.bmp", "wb");
if (pWritingFile == 0)
exit(0);

// 读取像素
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);//4对齐
GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData);//最后一个参数，是像素读取后存放在内存中的位置

// 把dummy.bmp的文件头复制为新文件的文件头
fseek(pWritingFile, 0x0012, SEEK_SET); //移动到0x0012位置,读取宽度
i = WindowWidth;
//读完以后自动到下一个高度
j = WindowHeight;
fwrite(&i, sizeof(i), 1, pWritingFile);
fwrite(&j, sizeof(j), 1, pWritingFile);

// 写入像素数据
fseek(pWritingFile, 0, SEEK_END);//当前文件尾部，即头信息后
//偏移起始位置：文件头0(SEEK_SET)，当前位置1(SEEK_CUR)，文件尾2(SEEK_END)）
fwrite(pPixelData, PixelDataLength, 1, pWritingFile);

// 释放内存和关闭文件
fclose(pDummyFile);
fclose(pWritingFile);
free(pPixelData);
}

void display(void)
{
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT);

// 绘制
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);    glVertex2f(0.0f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f);    glVertex2f(1.0f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f);    glVertex2f(0.5f, 1.0f);
glEnd();
glPixelZoom(-0.5f, -0.5f);
glRasterPos2i(1, 1);
glCopyPixels(WindowWidth / 2, WindowHeight / 2,
WindowWidth / 2, WindowHeight / 2, GL_COLOR);

// 完成绘制，并抓取图象保存为BMP文件
glutSwapBuffers();
grab();
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
int cx = glutGet(GLUT_SCREEN_WIDTH);
int cy = glutGet(GLUT_SCREEN_HEIGHT);//为了使窗口居中
glutInitWindowPosition((cx - 400) / 2, (cy - 400) / 2);
glutInitWindowSize(WindowWidth, WindowHeight);
glutCreateWindow("OpenGL3D编程基础");
glutDisplayFunc(&display);
glutMainLoop();
return 0;
}
``````

## 第十一讲

### 1、启用纹理和载入纹理

``````    glEnable(GL_TEXTURE_2D);  // 启用二维纹理
glDisable(GL_TEXTURE_2D); // 禁用二维纹理
``````

1️⃣ 第一个参数为指定的目标，在我们的入门教材中，这个参数将始终使用GL_TEXTURE_2D。

2️⃣ 第二个参数为“多重细节层次”，现在我们并不考虑多重纹理细节，因此这个参数设置为零。

3️⃣ 第三个参数有两种用法。在OpenGL 1.0，即最初的版本中，使用整数来表示颜色分量数目，例如：像素数据用RGB颜色表示，总共有红、绿、蓝三个值，因此参数设置为3，而如果像素数据是用RGBA颜色表示，总共有红、绿、蓝、alpha四个值，因此参数设置为4。而在后来的版本中，可以直接使用GL_RGB或GL_RGBA来表示以上情况，显得更直观（并带来其它一些好处，这里暂时不提）。注意：虽然我们使用Windows的BMP文件作为纹理时，一般是蓝色的像素在最前，其真实的格式为GL_BGR而不是GL_RGB，在数据的顺序上有所不同，但因为同样是红、绿、蓝三种颜色，因此这里仍然使用GL_RGB。（如果使用GL_BGR，OpenGL将无法识别这个参数，造成错误）

4️⃣ 第四、五个参数是二维纹理像素的宽度和高度。这里有一个很需要注意的地方：OpenGL在以前的很多版本中，限制纹理的大小必须是2的整数次方，即纹理的宽度和高度只能是16, 32, 64, 128, 256等值，直到最近的新版本才取消了这个限制。而且，一些OpenGL实现（例如，某些PC机上板载显卡的驱动程序附带的OpenGL）并没有支持到如此高的OpenGL版本。因此在使用纹理时要特别注意其大小。尽量使用大小为2的整数次方的纹理，当这个要求无法满足时，使用gluScaleImage函数把图象缩放至所指定的大小（在后面的例子中有用到）。另外，无论旧版本还是新版本，都限制了纹理大小的最大值，例如，某OpenGL实现可能要求纹理最大不能超过1024*1024。可以使用如下的代码来获得OpenGL所支持的最大纹理：

``````GLint max;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
``````

5️⃣ 第六个参数是纹理边框的大小，我们没有使用纹理边框，因此这里设置为零

``````glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
``````

### 2、纹理坐标

``````glBegin( /* ... */ );
glTexCoord2f( /* ... */ );  glVertex3f( /* ... */ );
glTexCoord2f( /* ... */ );  glVertex3f( /* ... */ );
/* ... */
glEnd();
``````

glRotate*，glScale*，glTranslate*等操作矩阵的函数可以用来处理“对纹理坐标进行转换”的工作

#### 3、纹理参数

1️⃣ GL_TEXTURE_MAG_FILTER指当纹理图象被使用到一个大于它的形状上时（即：有可能纹理图象中的一个像素会被应用到实际绘制时的多个像素。例如将一幅256*256的纹理图象应用到一个512*512的正方形），应该如何处理。可选择的设置有GL_NEAREST和GL_LINEAR，前者表示“使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色”，后者表示“使用纹理中坐标最接近的若干个颜色，通过加权平均算法得到需要绘制的像素颜色”。前者只经过简单比较，需要运算较少，可能速度较快，后者需要经过加权平均计算，其中涉及除法运算，可能速度较慢（但如果有专门的处理硬件，也可能两者速度相同）。从视觉效果上看，前者效果较差，在一些情况下锯齿现象明显，后者效果会较好（但如果纹理图象本身比较大，则两者在视觉效果上就会比较接近）。

2️⃣ GL_TEXTURE_MIN_FILTER：指当纹理图象被使用到一个小于（或等于）它的形状上时（即有可能纹理图象中的多个像素被应用到实际绘制时的一个像素。例如将一幅256*256的纹理图象应用到一个128*128的正方形），应该如何处理。可选择的设置有

``````GL_NEAREST，GL_LINEAR，GL_NEAREST_MIPMAP_NEAREST，GL_NEAREST_MIPMAP_LINEAR，GL_LINEAR_MIPMAP_NEAREST和GL_LINEAR_MIPMAP_LINEAR。
``````

3️⃣ GL_TEXTURE_WRAP_S：指当纹理坐标的第一维坐标值大于1.0或小于0.0时，应该如何处理。基本的选项有GL_CLAMP和GL_REPEAT，前者表示“截断”，即超过1.0的按1.0处理，不足0.0的按0.0处理。后者表示“重复”，即对坐标值加上一个合适的整数（可以是正数或负数），得到一个在[0.0, 1.0]范围内的值，然后用这个值作为新的纹理坐标。例如：某二维纹理，在绘制某形状时，一像素需要得到纹理中坐标为(3.5, 0.5)的像素的颜色，其中第一维的坐标值3.5超过了1.0，则在GL_CLAMP方式中将被转化为(1.0, 0.5)，在GL_REPEAT方式中将被转化为(0.5, 0.5)。在后来的OpenGL版本中，又增加了新的处理方式，这里不做介绍。如果不指定这个参数，则默认为GL_REPEAT

4️⃣ GL_TEXTURE_WRAP_T：指当纹理坐标的第二维坐标值大于1.0或小于0.0时，应该如何处理。选项与GL_TEXTURE_WRAP_S类似，不再重复。如果不指定这个参数，则默认为GL_REPEAT。

``````glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
``````

### 4、纹理对象

``````GLuint texture_ID;
glGenTextures(1, &texture_ID); // 分配一个纹理对象的编号

：
GLuint texture_ID_list[5];
glGenTextures(5, texture_ID_list); // 分配5个纹理对象的编号

``````

`glBindTexture`函数有两个参数，第一个参数是需要使用纹理的目标，因为我们现在只学习二维纹理，所以指定为GL_TEXTURE_2D，第二个参数是所使用的纹理的编号。

``````// 在程序开始时：分配好纹理编号，并载入纹理
glGenTextures( /* ... */ );
glBindTexture(GL_TEXTURE_2D, texture_ID_1);
// 载入第一幅纹理
glBindTexture(GL_TEXTURE_2D, texture_ID_2);
// 载入第二幅纹理

// 在绘制时，切换并使用纹理，不需要再进行载入
glBindTexture(GL_TEXTURE_2D, texture_ID_1); // 指定第一幅纹理
// 使用第一幅纹理
glBindTexture(GL_TEXTURE_2D, texture_ID_2); // 指定第二幅纹理
// 使用第二幅纹理
``````

### 5、示例

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#include <time.h>

#define WindowWidth   500
#define WindowHeight 500

/* 函数grab
* 抓取窗口中的像素
* 假设窗口宽度为WindowWidth，高度为WindowHeight
*/
void grab(void)
{
FILE* pDummyFile;
FILE* pWritingFile;
GLubyte* pPixelData;
GLint    i, j;
GLint    PixelDataLength;

// 计算像素数据的实际长度
i = WindowWidth * 3;   // 得到每一行的像素数据长度
while (i % 4 != 0)      // 补充数据，直到i是的倍数
++i;               // 本来还有更快的算法，
// 但这里仅追求直观，对速度没有太高要求
PixelDataLength = i * WindowHeight;

// 分配内存和打开文件
pPixelData = (GLubyte*)malloc(PixelDataLength);
if (pPixelData == 0)
exit(0);

fopen_s(&pDummyFile, "dummy.bmp", "rb");
if (pDummyFile == 0)
exit(0);

fopen_s(&pWritingFile, "grab.bmp", "wb");
if (pWritingFile == 0)
exit(0);

// 读取像素
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData);

// 把dummy.bmp的文件头复制为新文件的文件头
fseek(pWritingFile, 0x0012, SEEK_SET);
i = WindowWidth;
j = WindowHeight;
fwrite(&i, sizeof(i), 1, pWritingFile);
fwrite(&j, sizeof(j), 1, pWritingFile);

// 写入像素数据
fseek(pWritingFile, 0, SEEK_END);
fwrite(pPixelData, PixelDataLength, 1, pWritingFile);

// 释放内存和关闭文件
fclose(pDummyFile);
fclose(pWritingFile);
free(pPixelData);
}

/* 函数power_of_two
* 检查一个整数是否为2的整数次方，如果是，返回1，否则返回0
* 实际上只要查看其二进制位中有多少个，如果正好有1个，返回1，否则返回0
* 在“查看其二进制位中有多少个”时使用了一个小技巧
* 使用n &= (n-1)可以使得n中的减少一个（具体原理大家可以自己思考）
*/
int power_of_two(int n)
{
if (n <= 0)
return 0;
return (n & (n - 1)) == 0;
}

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

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

// 读取文件中图象的宽度和高度
fseek(pFile, 0x0012, 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中
// 如果图象的宽度和高度不是的整数次方，则需要进行缩放
// 这里并没有检查OpenGL版本，出于对版本兼容性的考虑，按旧版本处理
// 另外，无论是旧版本还是新版本，
// 当图象的宽度和高度超过当前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;
}

// 绑定新的纹理，载入纹理并设置纹理参数
// 在绑定前，先获得原来绑定的纹理编号，以便在最后进行恢复
glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture_ID);
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);//利用glTexImage2D函数可以载入一个二维的纹理
glBindTexture(GL_TEXTURE_2D, last_texture_ID);//指定“当前所使用的纹理对象”

// 之前为pixels分配的内存可在使用glTexImage2D以后释放
// 因为此时像素数据已经被OpenGL另行保存了一份（可能被保存到专门的图形硬件中）
free(pixels);
return texture_ID;
}
/* 两个纹理对象的编号
*/
GLuint texGround;
GLuint texWall;

void display(void)
{
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 设置视角
glMatrixMode(GL_PROJECTION);
gluPerspective(75, 1, 1, 21);
glMatrixMode(GL_MODELVIEW);
gluLookAt(1, 5, 5, 0, 0, 0, 0, 0, 1);

// 使用“地”纹理绘制土地
glBindTexture(GL_TEXTURE_2D, texGround);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-8.0f, -8.0f, 0.0f);
glTexCoord2f(0.0f, 5.0f); glVertex3f(-8.0f, 8.0f, 0.0f);
glTexCoord2f(5.0f, 5.0f); glVertex3f(8.0f, 8.0f, 0.0f);
glTexCoord2f(5.0f, 0.0f); glVertex3f(8.0f, -8.0f, 0.0f);
glEnd();
// 使用“墙”纹理绘制栅栏
glBindTexture(GL_TEXTURE_2D, texWall);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-6.0f, -3.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-6.0f, -3.0f, 1.5f);
glTexCoord2f(5.0f, 1.0f); glVertex3f(6.0f, -3.0f, 1.5f);
glTexCoord2f(5.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);
glEnd();

// 旋转后再绘制一个
glRotatef(-90, 0, 0, 1);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-6.0f, -3.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-6.0f, -3.0f, 1.5f);
glTexCoord2f(5.0f, 1.0f); glVertex3f(6.0f, -3.0f, 1.5f);
glTexCoord2f(5.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);
glEnd();

// 交换缓冲区，并保存像素数据到文件
grab();//顺序
glutSwapBuffers();

}

int main(int argc, char* argv[])
{
// GLUT初始化
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(WindowWidth, WindowHeight);
glutCreateWindow("第十一讲——纹理映射测试");
glutDisplayFunc(&display);

// 在这里做一些初始化
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
printf("%d", (int)texGround);
printf("%d", (int)texWall);
// 开始显示
glutMainLoop();

return 0;
}

``````

## 第十二讲

### 裁剪测试

``````glEnable(GL_SCISSOR_TEST); // 启用剪裁测试
glDisable(GL_SCISSOR_TEST); // 禁用剪裁测试
``````

``````glScissor(x, y, width, height);
``````

### Alpha测试

``````glEnable(GL_ALPHA_TEST); // 启用Alpha测试
glDisable(GL_ALPHA_TEST); // 禁用Alpha测试
``````

``````glAlphaFunc(GL_GREATER, 0.5f);
``````

`````` GL_ALWAYS（始终通过），
GL_NEVER（始终不通过），
GL_LESS（小于则通过），
GL_LEQUAL（小于等于则通过），
GL_EQUAL（等于则通过），
GL_GEQUAL（大于等于则通过），
GL_NOTEQUAL（不等于则通过）。
``````

Alpha测试可以实现的效果几乎都可以通过OpenGL混合功能来实现。那么为什么还需要一个Alpha测试呢？答案就是，这与性能相关。Alpha测试只要简单的比较大小就可以得到最终结果，而混合操作一般需要进行乘法运算，性能有所下降。另外，OpenGL测试的顺序是：剪裁测试、Alpha测试、模板测试、深度测试。如果某项测试不通过，则不会进行下一步，而只有所有测试都通过的情况下才会执行混合操作。因此，在使用Alpha测试的情况下，透明的像素就不需要经过模板测试和深度测试了；而如果使用混合操作，即使透明的像素也需要进行模板测试和深度测试，性能会有所下降。还有一点：对于那些“透明”的像素来说，如果使用Alpha测试，则“透明”的像素不会通过测试，因此像素的深度值不会被修改；而使用混合操作时，虽然像素的颜色没有被修改，但它的深度值则有可能被修改掉了。

#### 举例

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#include <time.h>
#include "tex.h"
/* 将当前纹理BGR格式转换为BGRA格式
* 纹理中像素的RGB值如果与指定rgb相差不超过absolute，则将Alpha设置为0.0，否则设置为1.0
*/
void texture_colorkey(GLubyte r, GLubyte g, GLubyte b, GLubyte absolute)
{
GLint width, height;
GLubyte* pixels = 0;

// 获得纹理的大小信息
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);

// 分配空间并获得纹理像素
pixels = (GLubyte*)malloc(width * height * 4);
if (pixels == 0)
return;
glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);

// 修改像素中的Alpha值
// 其中pixels[i*4], pixels[i*4+1], pixels[i*4+2], pixels[i*4+3]
//   分别表示第i个像素的蓝、绿、红、Alpha四种分量，0表示最小，255表示最大
{
GLint i;
GLint count = width * height;
for (i = 0; i < count; ++i)
{
if (abs(pixels[i * 4] - b) <= absolute
&& abs(pixels[i * 4 + 1] - g) <= absolute
&& abs(pixels[i * 4 + 2] - r) <= absolute)
pixels[i * 4 + 3] = 0;
else
pixels[i * 4 + 3] = 255;
}
}

// 将修改后的像素重新设置到纹理中，释放内存
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
free(pixels);
}
void display(void)
{
static int initialized = 0;
static GLuint texWindow = 0;
static GLuint texPicture = 0;

// 执行初始化操作，包括：读取相片，读取相框，将相框由BGR颜色转换为BGRA，启用二维纹理
if (!initialized)
{
glBindTexture(GL_TEXTURE_2D, texWindow);
texture_colorkey(255, 255, 255, 10);

glEnable(GL_TEXTURE_2D);

initialized = 1;
}

// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT);

// 绘制相片，此时不需要进行Alpha测试，所有的像素都进行绘制
glBindTexture(GL_TEXTURE_2D, texPicture);
glDisable(GL_ALPHA_TEST);
glTexCoord2f(0, 0);     glVertex2f(-1.0f, -1.0f);
glTexCoord2f(0, 1);     glVertex2f(-1.0f, 1.0f);
glTexCoord2f(1, 1);     glVertex2f(1.0f, 1.0f);
glTexCoord2f(1, 0);     glVertex2f(1.0f, -1.0f);
glEnd();

// 绘制相框，此时进行Alpha测试，只绘制不透明部分的像素
glBindTexture(GL_TEXTURE_2D, texWindow);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
glTexCoord2f(0, 0);     glVertex2f(-1.0f, -1.0f);
glTexCoord2f(0, 1);     glVertex2f(-1.0f, 1.0f);
glTexCoord2f(1, 1);     glVertex2f(1.0f, 1.0f);
glTexCoord2f(1, 0);     glVertex2f(1.0f, -1.0f);
glEnd();

// 交换缓冲
glutSwapBuffers();
}

int main(int argc, char* argv[])
{
// GLUT初始化
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(500,500);
glutCreateWindow("第十二讲——alpha测试");
glutDisplayFunc(&display);

glutMainLoop();

return 0;
}

``````

### 模板测试

``````glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_STENCIL);
``````
``````glEnable(GL_STENCIL_TEST); // 启用模板测试
glDisable(GL_STENCIL_TEST); // 禁用模板测试
``````

OpenGL在模板缓冲区中为每个像素保存了一个“模板值”，当像素需要进行模板测试时，将设定的模板参考值与该像素的“模板值”进行比较，符合条件的通过测试，不符合条件的则被丢弃，不进行绘制。

OpenGL在模板缓冲区中为每个像素保存了一个“模板值”，当像素需要进行模板测试时，将设定的模板参考值与该像素的“模板值”进行比较，符合条件的通过测试，不符合条件的则被丢弃，不进行绘制。

``````glClear(GL_STENCIL_BUFFER_BIT);
可以同时复位颜色值和模板值：
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
正如可以使用glClearColor函数来指定清空屏幕后的颜色那样，也可以使用glClearStencil函数来指定复位后的“模板值”。
每个像素的“模板值”会根据模板测试的结果和深度测试的结果而进行改变。
``````
``````glStencilOp(fail, zfail, zpass);
``````

`````` GL_KEEP（不改变，这也是默认值），
GL_ZERO（回零），
GL_REPLACE（使用测试条件中的设定值来代替当前模板值），
GL_INCR（增加1，但如果已经是最大值，则保持不变），
GL_INCR_WRAP（增加1，但如果已经是最大值，则从零重新开始），
GL_DECR（减少1，但如果已经是零，则保持不变），
GL_DECR_WRAP（减少1，但如果已经是零，则重新设置为最大值），
GL_INVERT（按位取反）。
``````

(1) 关闭模板测试，绘制地面和树木。
(2) 开启模板测试，使用glClear设置所有像素的模板值为0。
(3) 设置glStencilFunc(GL_ALWAYS, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);绘制湖泊水面。这样一来，湖泊水面的像素的“模板值”为1，而其它地方像素的“模板值”为0。
(4) 设置glStencilFunc(GL_EQUAL, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);绘制倒影。这样一来，只有“模板值”为1的像素才会被绘制，因此只有“水面”的像素才有可能被倒影的像素替换，而其它像素则保持不变。

#### 举例

``````#include<stdio.h>
#include<stdlib.h>
#include <math.h>
#include<glut.h>
#include<glaux.h>
#include <time.h>
#include "tex.h"
void draw_sphere()
{
// 设置光源
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
{
GLfloat
pos[] = { 5.0f, 5.0f, 0.0f, 1.0f },
ambient[] = { 0.0f, 1.0f, 1.0f, 1.0f };
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
}

// 绘制一个球体
glColor3f(1, 0, 0);
glPushMatrix();
glTranslatef(0, 0, 2);
glutSolidSphere(0.5, 20, 20);
glPopMatrix();
}

void display(void)
{
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 设置观察点
glMatrixMode(GL_PROJECTION);
gluPerspective(60, 1, 5, 25);
glMatrixMode(GL_MODELVIEW);
gluLookAt(5, 0, 6.5, 0, 0, 0, 0, 1, 0);

glEnable(GL_DEPTH_TEST);

// 绘制球体
glDisable(GL_STENCIL_TEST);
draw_sphere();

// 绘制一个平面镜。在绘制的同时注意设置模板缓冲。
// 另外，为了保证平面镜之后的镜像能够正确绘制，在绘制平面镜时需要将深度缓冲区设置为只读的。
// 在绘制时暂时关闭光照效果
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glEnable(GL_STENCIL_TEST);

glDisable(GL_LIGHTING);
glColor3f(0.5f, 0.5f, 0.5f);
glRectf(-1.5f, -1.5f, 1.5f, 1.5f);

// 绘制一个与先前球体关于平面镜对称的球体，注意光源的位置也要发生对称改变
// 因为平面镜是在X轴和Y轴所确定的平面，所以只要Z坐标取反即可实现对称
// 为了保证球体的绘制范围被限制在平面镜内部，使用模板测试
glStencilFunc(GL_EQUAL, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glScalef(1.0f, 1.0f, -1.0f);
draw_sphere();

// 交换缓冲
glutSwapBuffers();
}
int main(int argc, char* argv[])
{
// GLUT初始化
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(500, 500);
glutCreateWindow("第十二讲——模板测试");
glutDisplayFunc(&display);

glutMainLoop();

return 0;
}
``````

### 深度测试

`````` glEnable(GL_DEPTH_TEST); // 启用深度测试 glDisable(GL_DEPTH_TEST); // 禁用深度测试
``````

glDepthFunc(GL_LESS);