一、OpenGL点精灵
点精灵是OpenGL 1.5及更新版本支持的一个特性,使用点精灵,可以通过绘制一个3D点将一个2D纹理图像显示在屏幕的任意位置上。
点精灵的使用需要绑定一个2D纹理,点精灵默认是点光栅化模式,只有一种情况例外,就是在开启点平滑(point smoothing)的时候。
不能同时使用点精灵和抗锯齿点,在片段程序中,有一个内建的变量gl_PointCoord,这是一个二分量向量,在顶点上对纹理坐标进行插值用于采样应用在点精灵上的2D纹理。
设置点精灵点的大小
使用glPointSize函数。
void glPointSize(GLfloat size);
这个函数为锯齿点和抗锯齿点设置点的直径,以像素为单位,使用前需要在逻辑端启用设置点大小模式。
glEnable(GL_PROGRAM_POINT_SIZE);
之后在顶点程序中设置内建变量glPointSize,这个变量确定了点的最终光栅化大小。当我们使用glPointSize函数设置点的大小时,它们不受透视除法的影响,而是将所有的点设置为同样大小,无论多远。
设置点精灵参数
通过glPointParameter函数,可以对点精灵(实际还包括一般的点)的几个特性进行微调;如下图所示一个点精灵上的纹理的原点(0,0)的两个可能位置。
将GL_POINT_SPRITE_COORD_ORIGIN参数设置为GL_LOWER_LEFT,可以将纹理坐标系的原点放置在点的左下角,点精灵的默认方向为GL_UPPER_LEFT。
glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN,GL_LOWER_LEFT);
点精灵其他内建变量:
gl_FragCoord,在任何图元进行渲染时,gl_FragCoord会包含当前片段的屏幕空间坐标,这个坐标的X和Y分量在这个点区域的不同位置也不相同,对于点精灵来说,Z和W分量都是常量,因为点是作为一个平面进行渲染的,这个平面与近端面和远端面平行。
改变点精灵的形状:
利用gl_PointCoord配合discard(丢弃片段)关键字创建非正方形的点。
//圆形
vec2 temp = gl_PointCoord * 2.0 - vec2(1.0);
if (dot(temp, temp) > 1.0)
{
discard;
}
//花朵
vec2 temp = gl_PointCoord * 2.0 - vec2(1.0);
if (dot(temp, temp) > sin(atan(temp.y, temp.x) * 5.0))
{
discard;
}
点精灵的旋转:
Opengl中点是作为按轴对齐的正方形进行渲染的,对点精灵进行旋转必须通过修改用于读取点精灵的纹理坐标来完成,所以只需要在着色器中创建一个2D旋转矩阵,并用它乘以gl_PointCoord使它围绕Z轴进行旋转,旋转的角度可以从顶点着色器或者几何着色器做为变量插值到片段着色器中,也可以作为一个顶点属性提供;
//构建二维旋转矩阵
float sin_theta = sin(angle);
float cos_theta = cos(angle);
rotation_matrix = mat2(cos_theta, sin_theta,
-sin_theta, cos_theta);
二、纹理数组
通过纹理数组,可以将几个2D图像加载到一个单独的纹理对象中,然后在着色器中对它们进行检索,这样就大大增加了着色器可用的纹理数据数量;
纹理数组添加了两个新的纹理对象作为大多数纹理管理函数的有效参数,它们是GL_TEXTURE_1D_ARRAY和GL_TEXTURE_2D_ARRAY。
对于2D纹理数组来说,和其正常创建和绑定纹理相似只是改变了target参数;
GLuint Texture;
glGenTextures(1,&Texture);
glBindTexture(GL_TEXTURE_2D_ARRAY,Texture);
纹理参数,纹理模式和过滤器的情况也是这样:
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
加载纹理数据使用了glTexImage3D函数;
glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border,GLenum format, GLenum type, void*data);
对于target 使用GL_TEXTURE_2D_ARRAY 并且参数代表2D图像的“切片”或数组索引。
这个函数优点可以一次性加载整个2D图像数组,缺点它需要一次性加载一个2D图像数组,这并不总是那么方便,特别图像数量较多时;
还有另一种方法,是使用glTexImageXD函数都有的特性,如果将NULL作为最后一个参数(这样就没有任何纹理数据需要复制了)Opengl会保留纹理存储空间,但会将它们保持为未初始化状态。
这样我们之后就可以使用glTexSubImageXD函数来更新纹理;
访问纹理数组
有一个新类型的2D纹理数组采样器sampler2Darray 使用texture2DArray函数对这个纹理进行采样,并且传递一个3分量纹理坐标,这个纹理坐标的前两个分量是s和t 作为二维纹理坐标,第三个分量 p实际上是纹理数组的一个整型索引。
三、渲染逻辑代码示例:
Render.CPP
#include <glew/glew.h> //GLEW库引入(一定要在GLUT引入之前引入,否则会编译错误),如果要引入其他OpenGL头文件,必须要注意将这个头文件放在前面
#include <gltools/GLTools.h>
#include <gltools/GLShaderManager.h> //着色器管理类
#include <gltools/GLMatrixStack.h> //矩阵堆栈
#include <gltools/GLFrame.h> //矩阵
#include <gltools/GLFrustum.h> //投影矩阵
#include <gltools/GLBatch.h> //三角形批次类
#include <gltools/GLGeometryTransform.h> //几何变换管道
#include <gltools/StopWatch.h> //时间管理
#include <freeglut/freeglut.h>
#include <math.h> //数学库
#include <iostream>
using namespace std;
#define NUM_STARS 10000
//矩阵存储在堆区,而地址是存储在栈区,在大量进行变换的应用的场景中,就需要顶点与大量的变换矩阵进行相乘,这时候就需要大量的构造矩阵,这时候有一个便利的矩阵构造函数可以进行构造矩阵操作矩阵乘法会方便很多,在math3d的这个类被称为GLMatrixsStack。
//使用矩阵堆栈进行矩阵的创建和操作矩阵乘法很方便,但是我们还要方便的管理这些堆栈,就是说我们可以随时方便取到堆栈矩阵的地址,GLGeometryTransform可以设置指针指向我们创建好的堆栈矩阵。
//GTools封装的类
GLFrustum viewFrustum; //创建一个透视投影的矩阵
GLBatch starsBatch; //点精灵模型
GLuint starFieldShader; // 点精灵Shader
GLint locMVP; // 点精灵 MVP 矩阵
GLint locTimeStamp; // 点精灵 时间
GLint locTexture; // 点精灵 贴图ID
GLuint starTexture; // 贴图ID
//月亮
GLBatch moonBatch;
GLuint moonTexture;
GLuint moonShader;
GLint locMoonMVP;
GLint locMoonTexture;
GLint locMoonTime;
// 导入TGA图片 并初始化相关设置
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// 读取贴图数据
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if (pBits == NULL)
return false;
//设置贴图的过滤和环绕模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
//释放数据
free(pBits);
//判断是否生成MinMap贴图
if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST)
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
void ChangeSize(int w, int h)
{
// 高度不为0
if (h == 0)
h = 1;
// 设置渲染窗口的大小为窗口大小
glViewport(0, 0, w, h);
//设置投影矩阵 FOV 35 近截面1.0 远截面距离100
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 100.0f);
}
//设置渲染环境 执行一次
void SetupRC()
{
// 设置背景颜色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//开启渲染点精灵功能
glEnable(GL_POINT_SPRITE);
//点精灵颜色数组
GLfloat fColors[4][4] = { { 1.0f, 1.0f, 1.0f, 1.0f}, // 白色
{ 0.67f, 0.68f, 0.82f, 1.0f}, // 蓝色
{ 1.0f, 0.5f, 0.5f, 1.0f}, // 红色
{ 1.0f, 0.82f, 0.65f, 1.0f} }; // 橘黄
//开始渲染点
starsBatch.Begin(GL_POINTS, NUM_STARS);
for (int i = 0; i < NUM_STARS; i++)
{
int iColor = 0;
//随机颜色点
if (rand() % 5 == 1)
iColor = 1;
if (rand() % 50 == 1)
iColor = 2;
if (rand() % 100 == 1)
iColor = 3;
//设置点随机颜色
starsBatch.Color4fv(fColors[iColor]);
//点的位置
M3DVector3f vPosition;
vPosition[0] = float(5000 - (rand() % 6000)) * 0.1f;
vPosition[1] = float(5000 - (rand() % 6000)) * 0.1f;
vPosition[2] = -float(rand() % 1000) - 1.0f;
starsBatch.Vertex3fv(vPosition);
}
starsBatch.End();
//点精灵着色器
starFieldShader = gltLoadShaderPairWithAttributes("ShaderPointSpritesTestVS.vs", "ShaderPointSpritesTestFS.fs", 2, GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_COLOR, "vColor");
//获取点精灵着色器 变量地址ID
locMVP = glGetUniformLocation(starFieldShader, "mvpMatrix");
locTexture = glGetUniformLocation(starFieldShader, "starImage");
locTimeStamp = glGetUniformLocation(starFieldShader, "timeStamp");
//绑定贴图
glGenTextures(1, &starTexture);
glBindTexture(GL_TEXTURE_2D, starTexture);
LoadTGATexture("Star.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
// 月亮
GLfloat x = 924; //月亮的位置与半径
GLfloat y = 620;
GLfloat r = 50.0f;
GLfloat angle = 0.0f;
moonBatch.Begin(GL_TRIANGLE_FAN, 4, 1);
moonBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
moonBatch.Vertex3f(x - r, y - r, 0.0f);
moonBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
moonBatch.Vertex3f(x + r, y - r, 0.0f);
moonBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
moonBatch.Vertex3f(x + r, y + r, 0.0f);
moonBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
moonBatch.Vertex3f(x - r, y + r, 0.0f);
moonBatch.End();
//月亮着色器
moonShader = gltLoadShaderPairWithAttributes("ShaderMoonTextureArrayVS.vs", "ShaderMoonTextureArrayFS.fs", 2, GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_TEXTURE0, "vTexCoords");
locMoonMVP = glGetUniformLocation(moonShader, "mvpMatrix");
locMoonTexture = glGetUniformLocation(moonShader, "moonImage");
locMoonTime = glGetUniformLocation(moonShader, "fTime");
glGenTextures(1, &moonTexture);
glBindTexture(GL_TEXTURE_2D_ARRAY, moonTexture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 64, 64, 30, 0,
GL_BGRA, GL_UNSIGNED_BYTE, NULL);
for (int i = 0; i < 29; i++) {
char cFile[32];
sprintf_s(cFile, "moon%02d.tga", i);
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// 读取贴图数据
pBits = gltReadTGABits(cFile, &nWidth, &nHeight, &nComponents, &eFormat);
//更新纹理
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, nWidth, nHeight, 1, GL_BGRA, GL_UNSIGNED_BYTE, pBits);
free(pBits);
}
}
//清除材质信息
void ShutdownRC()
{
glDeleteTextures(1, &starTexture);
}
//渲染场景 循环执行
void RenderScene(void)
{
static CStopWatch timer;
//设置背景颜色 缓冲区
glClear(GL_COLOR_BUFFER_BIT );
// 开启混合模式 叠加
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
// 启用可以设置点大小模式
glEnable(GL_PROGRAM_POINT_SIZE);
// 绑定着色器 设置参数
glUseProgram(starFieldShader);
glUniformMatrix4fv(locMVP, 1, GL_FALSE, viewFrustum.GetProjectionMatrix());
glUniform1i(locTexture, 0);
// 设置时间0-999
float fTime = timer.GetElapsedSeconds() * 10.0f;
fTime = fmod(fTime, 999.0f);
glUniform1f(locTimeStamp, fTime);
//绘制
starsBatch.Draw();
//月亮
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//立方体投影矩阵
M3DMatrix44f mScreenSpace;
m3dMakeOrthographicMatrix(mScreenSpace, 0.0f, 1024, 0.0f, 720, -1.0f, 1.0f);
glBindTexture(GL_TEXTURE_2D_ARRAY, moonTexture);
glUseProgram(moonShader);
glUniformMatrix4fv(locMoonMVP, 1, GL_FALSE, mScreenSpace);
glUniform1i(locMoonTexture, 0);
// 切换贴图数组索引
float fTime01 = timer.GetElapsedSeconds();
fTime01 = fmod(fTime01, 28.0f);
glUniform1f(locMoonTime, fTime01);
moonBatch.Draw();
glutSwapBuffers();
glutPostRedisplay();
}
int main(int argc, char*argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(1024, 720); //窗口大小
glutCreateWindow("Render"); //渲染窗口名称
glutReshapeFunc(ChangeSize); //改变窗口尺寸时执行适配窗口函数
glutDisplayFunc(RenderScene); //渲染
//检测初始化过程是否有问题
GLenum err = glewInit();
if (GLEW_OK != err)
{
cout << "Error: '%s'\n" << glewGetErrorString(err) << endl;
return 1;
}
//初始化渲染环境 预加载纹理 建立几何图形 渲染器等
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}
ShaderPointSpritesTestVS.vs
#version 330
// 输入顶点 与颜色
in vec4 vVertex;
in vec4 vColor;
//变量
uniform mat4 mvpMatrix;
uniform float timeStamp;
//输出坐标
out vec4 vStarColor;
out mat2 rotation_matrix;
void main(void)
{
vec4 vNewVertex = vVertex;
vStarColor = vColor;
// 偏移 根据运行时间
vNewVertex.z += timeStamp;
// 如果超过 范围 进行调整
if (vNewVertex.z > -1.0)
vNewVertex.z -= 999.0;
//设置点大小
gl_PointSize = 100.0 + (vNewVertex.z / sqrt(-vNewVertex.z));
// 如果太小 让他们逐渐消失
if (gl_PointSize < 4.0)
{
vStarColor = smoothstep(0.0, 4.0, gl_PointSize) * vStarColor;
}
//构建二维旋转矩阵
float sin_theta = sin(timeStamp);
float cos_theta = cos(timeStamp);
rotation_matrix = mat2(cos_theta, sin_theta,
-sin_theta, cos_theta);
//顶点转换到 裁剪坐标系
gl_Position = mvpMatrix * vNewVertex;
}
ShaderPointSpritesTestFS.fs
#version 330
in vec4 vStarColor;
in mat2 rotation_matrix;
uniform sampler2D starImage;
out vec4 vFragColor;
void main(void)
{
//圆形
//vec2 temp = gl_PointCoord * 2.0 - vec2(1.0);
//if (dot(temp, temp) > 1.0)
//{
// discard;
//}
//花朵
//if (dot(temp, temp) > sin(atan(temp.y, temp.x) * 5.0))
//{
// discard;
//}
//移动坐标点到中心
vec2 pt = gl_PointCoord - vec2(0.5);
//计算旋转过后的坐标点 剔除一些不需要的片段
vec2 temp01 = (rotation_matrix * pt + vec2(0.5)) * 2.0 - vec2(1.0);
//花朵形状
if (dot(temp01, temp01) > sin(atan(temp01.y, temp01.x) * 5.0))
{
discard;
}
//旋转
vFragColor = texture(starImage, rotation_matrix * pt + vec2(0.5)) * vStarColor;
}
ShaderMoonTextureArrayVS.vs
#version 330
in vec4 vVertex;
in vec4 vTexCoords;
uniform mat4 mvpMatrix;
uniform float fTime;
smooth out vec3 vMoonCoords;
void main(void)
{
vMoonCoords.st = vTexCoords.st;
vMoonCoords.p = fTime;
gl_Position = mvpMatrix * vVertex;
}
ShaderMoonTextureArrayFS.fs
#version 330
#extension GL_EXT_gpu_shader4: enable
out vec4 vFragColor;
uniform sampler2DArray moonImage;
smooth in vec3 vMoonCoords;
void main(void)
{
vFragColor = texture2DArray(moonImage, vMoonCoords.stp);
}
点精灵效果预览:
点精灵