点精灵其实是OpenGL 1.5及之后的版本所支持的一个特性,通过点精灵这个特性,我们可以通过绘制1个3D点,去将一个2D纹理图像显示在屏幕的任意位置。所以我们可以用点去表示屏幕上移动的大量的粒子,来产生一些视觉效果。把这些点表示为重叠2D图像是可以做成动画细丝的效果的。其实OpenGL一直都是支持对点进行纹理贴图的,但是在1.5版本之前,这意味着只是将单个纹理坐标应用于整个点。较大的经过纹理贴图的点就是经过过滤的单个纹理单元的放大版本,所以肯定是没有点精灵效果高的。
如果在点精灵效果出来之前,要实现这样的效果,需要在屏幕上绘制大量的纹理四边形或者是三角形带(额,这块地方可以理解,但是毕竟没经历过那种时代),然后这个屏幕保护效果是会旋转的,所以我们还需要对每个多边形执行开销巨大的旋转来实现,因为我们要确保它能面对着照相机,或者说在2D正交投影下绘制所有的粒子,因为如果超出正交投影所设置的边界是不会进行绘制的,点精灵效果是允许我们发送单个3D顶点,以此来渲染一个完美对齐的纹理2D多边形,点精灵所需要的带宽只有四边形发送4个顶点所需带宽的四分之一
关于点精灵的使用,我们需要做的就是去简单地绑定一个2D纹理,需要注意的是我们不能同时的去使用点精灵和抗锯齿点。因为点精灵已经是默认的点光栅化模式了,如果开启了点平滑就不是处于这个模式了(目前也在摸索)。
我们在着色器程序中有一个内置只读变量就是gl_PointCoord,它的值是当前片元着所在点图元的二维坐标点,如果当前的图元不是一个点,那么这个值是读出来是未被定义的。
所以关于片元着色器的内容,我们可以这么写
#version 120
//传入的颜色值
varying vec4 vStarColor;
//采样器
uniform sampler2D starImage;
void main(void)
{
//表示当前所在点图元的二维坐标点
gl_FragColor = texture2D(starImage, gl_PointCoord) * vStarColor;
}
我们要想在顶点着色器中去确定点最终光栅化的大小,我们可以去设置一个内置变量gl_PointSize,但是在此之前,我们需要去做的就是去启动点大小的模式
glEnable(GL_PROGRAM_POINT_SIZE);
接下来我们来看下顶点着色器的内容,这里其实做的就是将点的颜色传递给片元着色器,以及去设置粒子的z位置根据时间间隔,以及去设置粒子的大小根据点的z值
//传入顶点
attribute vec4 vVertex;
//传入颜色
attribute vec4 vColor;
//传入模型视图投影矩阵
uniform mat4 mvpMatrix;
//传入时间值
uniform float timeStamp;
//传入颜色
varying vec4 vStarColor;
void main(void)
{
//传递顶点
vec4 vNewVertex = vVertex;
//设置颜色
vStarColor = vColor;
// Offset by running time, makes it move closer
//设置新的位置的z值要加上timeStamp
vNewVertex.z += timeStamp;
// If out of range, adjust
//如果z值到达临近裁剪面的时候,只要将它们的位置进行循环回到远裁剪面就可以了
if(vNewVertex.z > -1.0)
vNewVertex.z -= 999.0;
//设置点的大小,使用平方根也就是说z值越大,粒子越大,因为vNewVertex都为负数
gl_PointSize = 30.0 + (vNewVertex.z / sqrt(-vNewVertex.z));
// If they are very small, fade them up
//判断如果粒子太小了,就会发生闪烁,所以我们要去使这个点的颜色逐渐变暗,从而使它们在视野中消失。
if(gl_PointSize < 4.0)
vStarColor = smoothstep(0.0, 4.0, gl_PointSize) * vStarColor;
// Don't forget to transform the geometry! 设置点的位置
gl_Position = mvpMatrix * vNewVertex;
}
其他的代码如下所示
#include "GLShaderManager.h"
#include
#include
#include
#include
#include
#include
#ifdef __APPLE__
#include
#else
#define FREEGLUT_STATIC
#include
#endif
#define NUM_STARS 10000
//传入透视投影矩阵 Frustum:截头锥体;平截头体
GLFrustum viewFrustum;
//批次类容器 Batch:批处理
GLBatch starsBatch;
//粒子效果着色器程序 StarField 飞舞的星星 星空 粒子效果
GLuint starFieldShader; // The point sprite shader
//MVP矩阵
GLint locMVP; // The location of the ModelViewProjection matrix uniform
//时间戳
GLint locTimeStamp; // The location of the time stamp
//纹理的uniform值
GLint locTexture; // The location of the texture uniform
//星星纹理
GLuint starTexture; // The star texture texture object
//加载TGA文件为纹理的方法
// Load a TGA as a 2D Texture. Completely initialize the state
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// Read the texture bits
//读取TGA文件来获得指针的的内存指向
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
//如果pBits==NULL就返回false
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);
//指定OpenGL 如何从数据缓冲区中解包图像数据
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//加载纹理,将纹理载入内存,一旦被载入之后这些纹理就会变成当前纹理状态
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
//释放指针所指向的那块数据缓冲区
free(pBits);
//指定过滤方式
if(minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST)
{
//生成map贴图
glGenerateMipmap(GL_TEXTURE_2D);
}
return true;
}
void SetupRC(void)
{
//设置清屏颜色
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}, // White
{ 0.67f, 0.68f, 0.82f, 1.0f}, // Blue Stars
{ 1.0f, 0.5f, 0.5f, 1.0f}, // Reddish
{ 1.0f, 0.82f, 0.65f, 1.0f}}; // Orange
// Randomly place the stars in their initial positions, and pick a random color 随机地将星星放置在它们的初始位置,然后选择一个随机颜色
starsBatch.Begin(GL_POINTS, NUM_STARS);
for(int i = 0; i < NUM_STARS; i++)
{
//随机产生颜色
int iColor = 0; // All stars start as white
// One in five will be blue
if(rand() % 5 == 1)
iColor = 1;
// One in 50 red
if(rand() % 50 == 1)
iColor = 2;
// One in 100 is amber
if(rand() % 100 == 1)
iColor = 3;
starsBatch.Color4fv(fColors[iColor]);
//随机产生位置
M3DVector3f vPosition;
vPosition[0] = float(3000 - (rand() % 6000)) * 0.1f;
vPosition[1] = float(3000 - (rand() % 6000)) * 0.1f;
vPosition[2] = -float(rand() % 1000)-1.0f; // -1 to -1000.0f
//三角形批次类拷贝顶点
starsBatch.Vertex3fv(vPosition);
}
//结束存放数据
starsBatch.End();
//加载着色器程序
starFieldShader = gltLoadShaderPairWithAttributes("SpaceFlight.vsh", "SpaceFlight.fsh", 2, GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_COLOR, "vColor");
//获取模型视图矩阵的id
locMVP = glGetUniformLocation(starFieldShader, "mvpMatrix");
//设置纹理采样器
locTexture = glGetUniformLocation(starFieldShader, "starImage");
//timeStamp为时间传入
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);
}
// Cleanup
void ShutdownRC(void)
{
//删除纹理对象
glDeleteTextures(1, &starTexture);
}
// Called to draw scene
void RenderScene(void)
{
static CStopWatch timer;
// Clear the window and the depth buffer
//清除屏幕缓冲区
glClear(GL_COLOR_BUFFER_BIT);
//开启混合
// Turn on additive blending
glEnable(GL_BLEND);
//由于纹理的背景颜色也为黑色,所以直接颜色进行相加
glBlendFunc(GL_ONE, GL_ONE);
// Let the vertex program determine the point size 让顶点程序确定点大小
glEnable(GL_PROGRAM_POINT_SIZE);
// Bind to our shader, set uniforms 使用着色器程序
glUseProgram(starFieldShader);
//直接用投影矩阵传入模型视图投影矩阵
glUniformMatrix4fv(locMVP, 1, GL_FALSE, viewFrustum.GetProjectionMatrix());
//绑定纹理单元
glUniform1i(locTexture, 0);
//获取时间间隔
// fTime goes from 0.0 to 999.0 and recycles
float fTime = timer.GetElapsedSeconds() * 10.0f;
//fTime进行取余
fTime = fmod(fTime, 999.0f);
//传入fTime
glUniform1f(locTimeStamp, fTime);
// Draw the stars 开始绘制
starsBatch.Draw();
//交换双缓冲区
glutSwapBuffers();
//标记为重新绘制
glutPostRedisplay();
}
void ChangeSize(int w, int h)
{
// Prevent a divide by zero
if(h == 0)
h = 1;
//设置视口
// Set Viewport to window dimensions
glViewport(0, 0, w, h);
//设置投影视图
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 1000.0f);
}
///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(800, 600);
glutCreateWindow("Spaced Out");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}
star.tga文件是这样的,因为它的背景是黑色的,所以我们只需要去做颜色相加来混合就可以了
我们可以利用gl_PointCoord和discard来完成丢弃位于我们想要的点形状范围之外的片段,我们先看看先去修改清屏颜色和关闭混合效果得到的效果是什么
我们修改片段着色器
#version 120
//传入的颜色值
varying vec4 vStarColor;
//采样器
uniform sampler2D starImage;
void main(void)
{
vec2 p = gl_PointCoord *2.0 -vec2(1.0);
if(dot(p,p)>1.0)
discard;
//表示当前所在点图元的二维坐标点
gl_FragColor = texture2D(starImage, gl_PointCoord) * vStarColor;
}
生成花朵图形,可以将着色器程序改为下面的代码
#version 120
//传入的颜色值
varying vec4 vStarColor;
//采样器
uniform sampler2D starImage;
void main(void)
{
vec2 temp = gl_PointCoord *2.0 - vec2(1.0);
//atan可返回数字的反正切值
if(dot(temp,temp)>sin(atan(temp.y,temp.x)*0.5))
discard;
//表示当前所在点图元的二维坐标点
gl_FragColor = texture2D(starImage, gl_PointCoord) * vStarColor;
}