点精灵(Point Sprite)使用点精灵我们可以通过绘制一个3D点将一个2D纹理图像显示在任意位置上。
点精灵最常见的应用就是微粒系统。我们可以用点来表示在屏幕上移动的大量微粒,来产生一些视觉效果。但是把这些点表示为很小的重叠2D图像就是戏剧性流动着的细丝。
点精灵允许我们通过发送单个3D顶点,渲染一个完美对齐的纹理2D多边形。点精灵所需要的带宽只有为四边形发送四个顶点所用的带宽的四分之一,并且不需要客户端的矩阵逻辑来保持3D四边形和照相机的对齐。
(1)使用点
在客户端我们要做的只是简单绑定一个2D纹理,并且为纹理单元设置恰当的统一值。因为现在点精灵已经是默认的点光栅化模式。一种情况下例外:开启点平滑的时候。
我们不能使用点精灵和抗锯齿点。在片段程序中,有一个内建变量gl_PointCoord,这是一个分量向量,在顶点上对纹理坐标进行插值。
#version 130
out vec4 vFragColor;
in vec4 vStarColor;
uniform sampler2D starImage;
void main(void)
{
vFragColor = texture(starImage, gl_PointCoord) * vStarColor;
}
如上面的片段着色器代码,对于点精灵来说,不需要将纹理坐标作为属性进行传递。
一个点就是一个单独的顶点,那么我们就不能以任何其他方式在点表面上进行插值了。当然如果我们无论如何都要提供一个纹理坐标,或者以自定义的方法进行插值,那么也没什么。
(2)点的大小
两种方式可以设置点的大小。
//第一种方式:
void glPointSize(FLfloat size);
这个函数为锯齿点以及抗锯齿点设置点的直径,以像素为单位。我们也可以在顶点着色器中用程序设置点大小:
//第二种方式
//首先启用点大小模式:
glEnable(GL_PROGRAM_POINT_SIZE);
然后在我们的顶点程序中,可以设置一个内建变量gl_PointSize,这个变量确定了点的最终光栅化大小。这种方式的一种常见用途就是根据点的距离来确定点的大小。当我们使用glPointSize函数设置点的代销的时候,他们不受到透视除法的影响,而是将所有点设置为相同大小。无论他们有多远。
关于点距离变换如下公式:
d为从这个点到观察点的距离,而a,b,c是二次方程的参数。我们可以将他们存储为统一值,并且应用程序来对他们进行更新,或者我们已经想好了一组特定的参数,也可以在顶点着色器中将他们设置为常量。
比如:我们想设置一个常量大小值,那么就将a设置为非0值,而将b,c设置为0。
如果a,c为0,b非0,那么点大小将随着距离变化而线性变化。
如果a,b设置为0,而c设置为非0。那么点大小将随着距离变化而呈平方关系。
(3)飞跃星空代码分析
#pragma comment(lib,"GLTools.lib")
#include // OpenGL toolkit
#include
#include
#include
#include
#include
//定义星星的数量
#define NUM_STARS 10000
GLFrustum viewFrustum;
GLBatch starsBatch;
GLuint starFieldShader; // The point sprite shader
GLint locMVP; // The location of the ModelViewProjection matrix uniform
GLint locTimeStamp; // The location of the time stamp
GLint locTexture; // The location of the texture uniform
GLuint starTexture; // The star texture texture object
// 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
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);
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 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
// 随机位置,随机颜色
starsBatch.Begin(GL_POINTS, NUM_STARS);//直接添加10000个点
for (int i = 0; i < NUM_STARS; i++)
{
int iColor = 0; // 初始化星星颜色为0
// 五分之一为蓝色
if (rand() % 5 == 1)
iColor = 1;
// 五十分之一为红色
if (rand() % 50 == 1)
iColor = 2;
// 一百分之一为橙色
if (rand() % 100 == 1)
iColor = 3;
//将颜色添加到batch当中
starsBatch.Color4fv(fColors[iColor]);
M3DVector3f vPosition;
//上下600像素随机值
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.vp", "SpaceFlight.fp", 2, GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_COLOR, "vColor");
//设置其中的统一值
locMVP = glGetUniformLocation(starFieldShader, "mvpMatrix");
locTexture = glGetUniformLocation(starFieldShader, "starImage");
locTimeStamp = glGetUniformLocation(starFieldShader, "timeStamp");
//绑定纹理注意是2D
glGenTextures(1, &starTexture);
glBindTexture(GL_TEXTURE_2D, starTexture);
//并且加载tga图像
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;
// 清除视窗以及深度缓冲区。
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);
// 使用定时器来调节点的距离
float fTime = timer.GetElapsedSeconds() * 10.0f;
//得到对应的余数
fTime = fmod(fTime, 999.0f);
glUniform1f(locTimeStamp, fTime);
//画出星星
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;
}
关于程序实现的点渲染器代码:
#version 130
// 输入每个点的坐标以及颜色
in vec4 vVertex;
in vec4 vColor;
uniform mat4 mvpMatrix;
uniform float timeStamp;
out vec4 vStarColor;
void main(void)
{
vec4 vNewVertex = vVertex;
vStarColor = vColor;
// 跟随时间而不断移动
vNewVertex.z += timeStamp;
//顶点的z位置通过时间戳统一值进行偏移,星星向我们移动的动画效果就是这么实现的。我们需要检查位置
//当到达临近剪切面时,只要将他们的位置进行循环回到远端剪切面即可。我们使用一个平方根倒数,
//函数来使得星星在靠近我们的过程中越来越大,并且用变量设置最终的大小。
// 如果越界那么更新其位置
if(vNewVertex.z > -1.0)
vNewVertex.z -= 999.0;
gl_PointSize = 30.0 + (vNewVertex.z / sqrt(-vNewVertex.z));
//如果点大小太小,那么需要淡入,由无到有进行淡入
if(gl_PointSize < 4.0)
vStarColor = smoothstep(0.0, 4.0, gl_PointSize) * vStarColor;
// 更新几何变换
gl_Position = mvpMatrix * vNewVertex;
}
(4)点参数
通过glPointParameter函数,我们可以对点精灵的几个特征进行微调。应用到一个点精灵上的纹理的原点(0,0)的两个可能位置。
将GL_POINT_SPRITE_COORD_ORIGIN参数设置为GL_LOWER_LEFT, 可以将纹理坐标系的原点放置在点的左下角。
glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_LOWER_LEFT);注意点精灵的默认方向为GL_UPPER_LEFT。
另外一个和纹理无关的点参数可以用来设置alpha值,使得点可以通过将alpha与到观察点的距离进行混合而逐渐消失。
(5)异形点
除了gl_PointCoord为纹理坐标应用纹理之外,我们还可以使用点精灵完成一些其他工作。gl_FragCoord就是一个内建变量。
在任何其他图元进行渲染时, gl_FragCoord会包含当前片段的屏幕空间坐标。这样,这个坐标的x,y分量在这个点区域的不同位置也不相同。z和w分量都是常量,因为这个点是作为一个平面进行渲染的。这个平面和近端面和远端面平行。
同时我们可以使用gl_PointCoord来完成很多工作,而不仅仅是纹理坐标。
我们可以在片段着色器当中使用discard关键字来丢弃位于我们想要点形状范围之外的片段,从而创建出非正方形的点。
下面给出两个示例程序:
//绘制一个圆形
vec2 p=gl_PointCoord*2.0-vec2(1.0);
if(dot(p,p)>1.0)
discard;
//生成花朵形状
vec2 temp=gl_PointCoord*2-vec2(1);
if(dot(temp,temp)>sin(atan(temp.y,temp.x)*5))
discard;
(6)点的旋转
OPENGL当中的点是作为按轴对其的正方形而进行渲染的,对点精灵进行旋转必须通过修改用于读取点精灵纹理的纹理坐标来完成的。要完成这项工作,我们只需要在片段着色器当中创建一个2D旋转矩阵,并且用它乘以一个gl_PointCoord使他绕着z轴进行旋转。旋转的角度可以从顶点着色器或者几何着色器中作为一个插值变量传递到片段着色器。变量的值可以在顶点着色器或几何着色器当中一次计算,也可以通过一个顶点属性提供。
#version 330
uniform sampler2D sprite_texture;
in float angle;
out vec4 color;
void main(void)
{
const float sin_theta=sin(angle);
const floay cos_theta=cos(angle);
const mat2 rotation_matrix=mat2(cos_theta, sin_theta,
-sin_theta, cos_theta);
const vec2 pt=gl_PointCoord-vec2(0.5);
color=texture(sprite_texture, rotation_matrix*pt + vec(0.5));
}
注意,这个实例允许我们创建旋转的点精灵。不过,angle的值当然不能在点精灵中的片段之间进行改变。这就是说,对于点中的每个片段来说,旋转矩阵也是一个常量。这样,比起为每个片段分别计算旋转矩阵,在顶点着色器当中进行旋转矩阵运算,然后将它作为一个mat2变量传递给片段着色器的效率要高得多。
这里有一个更新过的顶点着色器和片段着色器,允许我们绘制旋转的点精灵。
#version 330
uniform matrix mvp;
in vec4 position;
in float angle;
out mat2 rotation_matrix;
void main(void)
{
const float sin_theta = sin(angle);
const float cos_theta=cos(angle);
const mat2 rotation_matrix=mat2(cos_theta, sin_theta,
-sin_theta, cos_theta);
gl_Position= mvp* position;
}
#version 330
uniform sampler2D sprite_texture;
in mat2 rotation_matrix;
out vec4 color;
void main(void)
{
const vec2 pt=gl_PointCoord-vec2(0.5);
color=texture(sprite_texture, rotation_matrix*pt + vec(0.5));
}
如上面的实例所示,把大量的计算转移到顶点着色器当中。