點精靈(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 <GLTools.h> // OpenGL toolkit
#include <GLFrustum.h>
#include <StopWatch.h>
#include <math.h>
#include <stdlib.h>
#include <GL/glut.h>
//定義星星的數量
#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));
}
如上面的實例所示,把大量的計算轉移到頂點着色器當中。