// 位置
attribute vec3 a_emissionPosition;
// 速度
attribute vec3 a_emissionVelocity;
// 受力
attribute vec3 a_emissionForce;
// 大小和Fade持續時間 size = GLKVector2Make(aSize, aDuration);
attribute vec2 a_size;
// 發射時間和消失時間
attribute vec2 a_emissionAndDeathTimes;
// UNIFORMS
uniform highp mat4 u_mvpMatrix; // 變換矩陣
uniform sampler2D u_samplers2D[1]; // 紋理
uniform highp vec3 u_gravity; // 重力
uniform highp float u_elapsedSeconds; // 當前時間
// Varyings:粒子透明度
varying lowp float v_particleOpacity;
void main() {
// 流逝時間
highp float elapsedTime = u_elapsedSeconds - a_emissionAndDeathTimes.x;
// 品質假設是1.0 加速度 = 力 (a = f/m)
// v = v0 + at : v 是當前速度; v0 是初速度;
// a 是加速度; t 是時間
// a_emissionForce 受力,u_gravity 重力
// 求速度velocity
highp vec3 velocity = a_emissionVelocity +
((a_emissionForce + u_gravity) * elapsedTime);
// s = s0 + 0.5 * (v0 + v) * t
// s 當前位置
// s0 初始位置
// v0 初始速度
// v 當前速度
// t 是時間
// 運算是對向量運算,相當於分別求出x、y、z的位置,再綜合
// 求粒子的受力後的位置 = a_emissionPosition(原始位置) + 0.5 * (速度+加速度) * 流逝時間
highp vec3 untransformedPosition = a_emissionPosition +
0.5 * (a_emissionVelocity + velocity) * elapsedTime;
//得出點的位置
gl_Position = u_mvpMatrix * vec4(untransformedPosition, 1.0);
gl_PointSize = a_size.x / gl_Position.w;
// 消失時間減去當前時間,得到當前的壽命; 除以Fade持續時間,當剩餘時間小於Fade時間後,得到一個從1到0變化的值
// 如果這個值小於0,則取0
float remainTime = a_emissionAndDeathTimes.y - u_elapsedSeconds;
float keepTime = max(a_size.y, 0.00001);
v_particleOpacity = max(0.0, min(1.0,remainTime /keepTime));
}
// UNIFORMS
uniform highp mat4 u_mvpMatrix;
uniform sampler2D u_samplers2D[1];
uniform highp vec3 u_gravity;
uniform highp float u_elapsedSeconds;
// Varyings
varying lowp float v_particleOpacity;
void main() {
// 通過texture2D函數我們可以得到一個紋素(texel),這是一個紋理圖片中的畫素。函數參數分別爲simpler2D以及紋理座標:
// gl_PointCoord是片元着色器的內建只讀變數,它的值是當前片元所在點圖元的二維座標。點的範圍是0.0到1.0
lowp vec4 textureColor = texture2D(u_samplers2D[0], gl_PointCoord);
// 粒子透明度 與 v_particleOpacity 值相關
textureColor.a = textureColor.a * v_particleOpacity;
// 設定片元顏色值
gl_FragColor = textureColor;
}
該工具主要是將OpenGL ES 的部分操作封裝成了自定義的方法,增加了程式碼的複用性:
// 此方法在當前的OpenGL ES上下文中建立一個頂點屬性陣列緩衝區
- (id)initWithAttribStride:(GLsizeiptr)aStride
numberOfVertices:(GLsizei)count
bytes:(const GLvoid *)dataPtr
usage:(GLenum)usage {
self = [super init];
if(self != nil) {
_stride = aStride;
_bufferSizeBytes = _stride * count;
// 初始化快取區
// 建立VBO的3個步驟
// 1.生成新快取物件glGenBuffers
// 2.系結快取物件glBindBuffer
// 3.將頂點數據拷貝到快取物件中glBufferData
// STEP 1 建立快取物件並返回快取物件的識別符號
glGenBuffers(1,&_name);
// STEP 2 將快取物件對應到相應的快取上
/*
glBindBuffer (GLenum target, GLuint buffer);
target:告訴VBO快取物件時儲存頂點陣列數據還是索引陣列數據 :GL_ARRAY_BUFFER\GL_ELEMENT_ARRAY_BUFFER
任何頂點屬性,如頂點座標、紋理座標、法線與顏色分量陣列都使用GL_ARRAY_BUFFER。用於glDraw[Range]Elements()的索引數據需要使用GL_ELEMENT_ARRAY系結。注意,target標誌幫助VBO確定快取物件最有效的位置,如有些系統將索引儲存AGP或系統記憶體中,將頂點儲存在顯示卡記憶體中。
buffer: 快取區物件
*/
glBindBuffer(GL_ARRAY_BUFFER,self.name);
/*
數據拷貝到快取物件
void glBufferData(GLenum target,GLsizeiptr size, const GLvoid* data, GLenum usage);
target:可以爲GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY
size:待傳遞數據位元組數量
data:源數據陣列指針
usage:
GL_STATIC_DRAW
GL_STATIC_READ
GL_STATIC_COPY
GL_DYNAMIC_DRAW
GL_DYNAMIC_READ
GL_DYNAMIC_COPY
GL_STREAM_DRAW
GL_STREAM_READ
GL_STREAM_COPY
」static「表示VBO中的數據將不會被改動(一次指定多次使用)
」dynamic「表示數據將會被頻繁改動(反覆 反復指定與使用)
」stream「表示每幀數據都要改變(一次指定一次使用)
」draw「表示數據將被髮送到GPU以待繪製(應用程式到GL)
」read「表示數據將被用戶端程式讀取(GL到應用程式)
*/
// STEP 3 數據拷貝到快取物件
glBufferData(
GL_ARRAY_BUFFER, // Initialize buffer contents
_bufferSizeBytes, // Number of bytes to copy
dataPtr, // Address of bytes to copy
usage); // Hint: cache in GPU memory
}
return self;
}
// 此方法載入由接收儲存的數據
- (void)reinitWithAttribStride:(GLsizeiptr)aStride
numberOfVertices:(GLsizei)count
bytes:(const GLvoid *)dataPtr {
_stride = aStride;
_bufferSizeBytes = aStride * count;
// STEP 1 將快取物件對應到相應的快取上
glBindBuffer(GL_ARRAY_BUFFER, self.name);
// STEP 2 數據拷貝到快取物件
glBufferData(
GL_ARRAY_BUFFER,
_bufferSizeBytes,
dataPtr,
GL_DYNAMIC_DRAW);
}
// 當應用程式希望使用緩衝區呈現任何幾何圖形時,必須準備一個頂點屬性陣列緩衝區。當你的應用程式準備一個緩衝區時,一些OpenGL ES狀態被改變,允許系結緩衝區和設定指針。
- (void)prepareToDrawWithAttrib:(GLuint)index
numberOfCoordinates:(GLint)count
attribOffset:(GLsizeiptr)offset
shouldEnable:(BOOL)shouldEnable {
if (count < 0 || count > 4) {
NSLog(@"Error:Count Error");
return ;
}
if (_stride < offset) {
NSLog(@"Error:_stride < Offset");
return;
}
if (_name == 0) {
NSLog(@"Error:name == Null");
}
// STEP 1 將快取物件對應到相應的快取上
glBindBuffer(GL_ARRAY_BUFFER,self.name);
// 判斷是否使用
if(shouldEnable) {
// Step 2
//出於效能考慮,所有頂點着色器的屬性(Attribute)變數都是關閉的,意味着數據在着色器端是不可見的,哪怕數據已經上傳到GPU,由glEnableVertexAttribArray啓用指定屬性,纔可在頂點着色器中存取逐頂點的屬性數據.
//VBO只是建立CPU和GPU之間的邏輯連線,從而實現了CPU數據上傳至GPU。但是,數據在GPU端是否可見,即,着色器能否讀取到數據,由是否啓用了對應的屬性決定,這就是glEnableVertexAttribArray的功能,允許頂點着色器讀取GPU(伺服器端)數據。
//頂點數據傳入GPU之後,還需要通知OpenGL如何解釋這些頂點數據,這個工作由函數glVertexAttribPointer完成
glEnableVertexAttribArray(index);
}
// Step 3
//頂點數據傳入GPU之後,還需要通知OpenGL如何解釋這些頂點數據,這個工作由函數glVertexAttribPointer完成
/*
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
indx:參數指定頂點屬性位置
size:指定頂點屬性大小
type:指定數據型別
normalized:數據被標準化
stride:步長
ptr:偏移量 NULL+offset
*/
glVertexAttribPointer(
index,
count,
GL_FLOAT,
GL_FALSE,
(int)self.stride,
NULL + offset);
}
// 將繪圖命令模式和instructsopengl ES確定使用緩衝區從頂點索引的第一個數的頂點,頂點索引從0開始
- (void)drawArrayWithMode:(GLenum)mode
startVertexIndex:(GLint)first
numberOfVertices:(GLsizei)count {
if (self.bufferSizeBytes < (first + count) * self.stride) {
NSLog(@"Vertex Error!");
}
//繪製
/*
glDrawArrays (GLenum mode, GLint first, GLsizei count);提供繪製功能。當採用頂點陣列方式繪製圖形時,使用該函數。該函數根據頂點陣列中的座標數據和指定的模式,進行繪製。
參數列表:
mode,繪製方式,OpenGL2.0以後提供以下參數:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。
first,從陣列快取中的哪一位開始繪製,一般爲0。
count,陣列中頂點的數量。
*/
glDrawArrays(mode, first, count);
}
主要是管理並繪製所有的粒子,相當GLKit中的GLKBaseEffect類
// 用於定義粒子屬性的型別
typedef struct {
GLKVector3 emissionPosition; // 發射位置
GLKVector3 emissionVelocity; // 發射速度
GLKVector3 emissionForce; // 發射重力
GLKVector2 size; // 發射大小
GLKVector2 emissionTimeAndLife;// 發射時間和壽命[出生時間,死亡時間]
} CCParticleAttributes;
// GLSL程式Uniform參數
enum {
CCMVPMatrix, // MVP矩陣
CCSamplers2D, // Samplers2D紋理
CCElapsedSeconds,// 耗時
CCGravity, // 重力
CCNumUniforms // Uniforms個數
};
// 屬性識別符號
typedef enum {
CCParticleEmissionPosition = 0,// 粒子發射位置
CCParticleEmissionVelocity, // 粒子發射速度
CCParticleEmissionForce, // 粒子發射重力
CCParticleSize, // 粒子發射大小
CCParticleEmissionTimeAndLife, // 粒子發射時間和壽命
} CCParticleAttrib;
// 重力
@property(nonatomic,assign)GLKVector3 gravity;
// 耗時
@property(nonatomic,assign)GLfloat elapsedSeconds;
// 紋理
@property (strong, nonatomic, readonly)GLKEffectPropertyTexture *texture2d0;
// 變換
@property (strong, nonatomic, readonly) GLKEffectPropertyTransform *transform;
@interface YDWPointParticleEffect() {
GLfloat elapsedSeconds; // 耗時
GLuint program; // 程式
GLint uniforms[CCNumUniforms];// Uniforms陣列
}
// 頂點屬性陣列緩衝區
@property (strong, nonatomic, readwrite) YDWVertexAttribArrayBuffer *particleAttributeBuffer;
// 粒子個數
@property (nonatomic, assign, readonly) NSUInteger numberOfParticles;
// 粒子屬性數據
@property (nonatomic, strong, readonly) NSMutableData *particleAttributesData;
// 是否更新粒子數據
@property (nonatomic, assign, readwrite) BOOL particleDataWasUpdated;
// 載入shaders
- (BOOL)loadShaders;
// 編譯shaders
- (BOOL)compileShader:(GLuint *)shader
type:(GLenum)type
file:(NSString *)file;
// 鏈接Program
- (BOOL)linkProgram:(GLuint)prog;
// 驗證Program
- (BOOL)validateProgram:(GLuint)prog;
// 初始化
- (id)init {
self = [super init];
if (self != nil) {
// 初始化紋理屬性
texture2d0 = [[GLKEffectPropertyTexture alloc] init];
// 是否可用
texture2d0.enabled = YES;
/* 命名紋理物件
* 等價於:void glGenTextures (GLsizei n, GLuint *textures);
* 在陣列textures中返回n個當期未使用的值,表示紋理物件的名稱
* 零作爲一個保留的紋理物件名,它不會被此函數當做紋理物件名稱而返回
*/
texture2d0.name = 0;
// 紋理型別 預設值是glktexturetarget2d
texture2d0.target = GLKTextureTarget2D;
/* 紋理用於計算其輸出片段顏色的模式。看到GLKTextureEnvMode
* GLKTextureEnvModeReplace,輸出顏色設定爲從紋理獲取的顏色,忽略輸入顏色
* GLKTextureEnvModeModulate, 預設!輸出顏色是通過將紋理的顏色乘以輸入顏色來計算的
* GLKTextureEnvModeDecal,輸出顏色是通過使用紋理的alpha元件來混合紋理顏色和輸入顏色來計算的
*/
texture2d0.envMode = GLKTextureEnvModeReplace;
// 座標變換的資訊用於GLKit渲染效果。GLKEffectPropertyTransform類定義的屬性進行渲染時的效果提供的座標變換
transform = [[GLKEffectPropertyTransform alloc] init];
// 重力.預設地球重力
gravity = CCDefaultGravity;
// 耗時
elapsedSeconds = 0.0f;
// 粒子屬性數據
particleAttributesData = [NSMutableData data];
}
return self;
}
// 新增一個粒子
- (void)addParticleAtPosition:(GLKVector3)aPosition
velocity:(GLKVector3)aVelocity
force:(GLKVector3)aForce
size:(float)aSize
lifeSpanSeconds:(NSTimeInterval)aSpan
fadeDurationSeconds:(NSTimeInterval)aDuration {
// 建立新的粒子
CCParticleAttributes newParticle;
// 設定相關參數(位置\速度\拋物線\大小\耗時)
newParticle.emissionPosition = aPosition;
newParticle.emissionVelocity = aVelocity;
newParticle.emissionForce = aForce;
newParticle.size = GLKVector2Make(aSize, aDuration);
// 向量(耗時,發射時長)
newParticle.emissionTimeAndLife = GLKVector2Make(elapsedSeconds, elapsedSeconds + aSpan);
BOOL foundSlot = NO;
// 粒子個數
const long count = self.numberOfParticles;
// 回圈設定粒子到陣列中
for(int i = 0; i < count && !foundSlot; i++) {
// 獲取當前舊粒子
CCParticleAttributes oldParticle = [self particleAtIndex:i];
// 如果舊的粒子的死亡時間小於當前時間:emissionTimeAndLife.y = elapsedSeconds + aspan
if(oldParticle.emissionTimeAndLife.y < self.elapsedSeconds) {
// 更新粒子的屬性
[self setParticle:newParticle atIndex:i];
// 是否替換
foundSlot = YES;
}
}
// 如果不替換
if(!foundSlot) {
// 在particleAttributesData 拼接新的數據
[self.particleAttributesData appendBytes:&newParticle
length:sizeof(newParticle)];
// 粒子數據是否更新
self.particleDataWasUpdated = YES;
}
}
// 設定粒子的屬性
- (void)setParticle:(CCParticleAttributes)aParticle
atIndex:(NSUInteger)anIndex {
// mutableBytes:指向可變數據物件所包含數據的指針
// 獲取粒子屬性結構體內容
CCParticleAttributes *particlesPtr = (CCParticleAttributes *)[self.particleAttributesData mutableBytes];
// 將粒子結構體對應的屬性修改爲新值
particlesPtr[anIndex] = aParticle;
// 更改粒子狀態! 是否更新
self.particleDataWasUpdated = YES;
}
// 獲取粒子個數
- (NSUInteger)numberOfParticles {
static long last;
// 總數據/粒子結構體大小
long ret = [self.particleAttributesData length] / sizeof(CCParticleAttributes);
// 如果last != ret 表示粒子個數更新
if (last != ret) {
// 則修改last數量
last = ret;
NSLog(@"count %ld", ret);
}
return ret;
}
- (BOOL)loadShaders {
GLuint vertShader, fragShader;
NSString *vertShaderPathname, *fragShaderPathname;
// 建立program
program = glCreateProgram();
// 建立並編譯 vertex shader.
vertShaderPathname = [[NSBundle mainBundle] pathForResource:
@"CCPointParticleShader" ofType:@"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER
file:vertShaderPathname]) {
NSLog(@"Failed to compile vertex shader");
return NO;
}
// 建立並編譯 fragment shader.
fragShaderPathname = [[NSBundle mainBundle] pathForResource:
@"CCPointParticleShader" ofType:@"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER
file:fragShaderPathname]) {
NSLog(@"Failed to compile fragment shader");
return NO;
}
// 將vertex shader 附加到程式.
glAttachShader(program, vertShader);
// 將fragment shader 附加到程式.
glAttachShader(program, fragShader);
// 系結屬性位置
// 這需要在鏈接之前完成.
/*
應用程式通過glBindAttribLocation把「頂點屬性索引」系結到「頂點屬性名」,glBindAttribLocation在program被link之前執行。
void glBindAttribLocation(GLuint program, GLuint index,const GLchar *name)
program:對應的程式
index:頂點屬性索引
name:屬性名稱
*/
//位置
glBindAttribLocation(program, CCParticleEmissionPosition,
"a_emissionPosition");
//速度
glBindAttribLocation(program, CCParticleEmissionVelocity,
"a_emissionVelocity");
//重力
glBindAttribLocation(program, CCParticleEmissionForce,
"a_emissionForce");
//大小
glBindAttribLocation(program, CCParticleSize,
"a_size");
//持續時間、漸隱時間
glBindAttribLocation(program, CCParticleEmissionTimeAndLife,
"a_emissionAndDeathTimes");
// Link program 失敗
if (![self linkProgram:program]) {
NSLog(@"Failed to link program: %d", program);
//link識別,刪除vertex shader\fragment shader\program
if (vertShader) {
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader) {
glDeleteShader(fragShader);
fragShader = 0;
}
if (program) {
glDeleteProgram(program);
program = 0;
}
return NO;
}
// 獲取uniform變數的位置.
// MVP變換矩陣
uniforms[CCMVPMatrix] = glGetUniformLocation(program,"u_mvpMatrix");
// 紋理
uniforms[CCSamplers2D] = glGetUniformLocation(program,"u_samplers2D");
// 重力
uniforms[CCGravity] = glGetUniformLocation(program,"u_gravity");
// 持續時間、漸隱時間
uniforms[CCElapsedSeconds] = glGetUniformLocation(program,"u_elapsedSeconds");
// 使用完
// 刪除 vertex and fragment shaders.
if (vertShader) {
glDetachShader(program, vertShader);
glDeleteShader(vertShader);
}
if (fragShader) {
glDetachShader(program, fragShader);
glDeleteShader(fragShader);
}
return YES;
}
// 編譯shader
- (BOOL)compileShader:(GLuint *)shader
type:(GLenum)type
file:(NSString *)file {
//狀態
//GLint status;
//路徑-C語言
const GLchar *source;
//從OC字串中獲取C語言字串
//獲取路徑
source = (GLchar *)[[NSString stringWithContentsOfFile:file
encoding:NSUTF8StringEncoding error:nil] UTF8String];
//判斷路徑
if (!source)
{
NSLog(@"Failed to load vertex shader");
return NO;
}
//建立shader-頂點\片元
*shader = glCreateShader(type);
//系結shader
glShaderSource(*shader, 1, &source, NULL);
//編譯Shader
glCompileShader(*shader);
//獲取載入Shader的日誌資訊
//日誌資訊長度
GLint logLength;
/*
在OpenGL中有方法能夠獲取到 shader錯誤
參數1:物件,從哪個Shader
參數2:獲取資訊類別,
GL_COMPILE_STATUS //編譯狀態
GL_INFO_LOG_LENGTH //日誌長度
GL_SHADER_SOURCE_LENGTH //着色器原始檔長度
GL_SHADER_COMPILER //着色器編譯器
參數3:獲取長度
*/
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
// 判斷日誌長度 > 0
if (logLength > 0) {
// 建立日誌字串
GLchar *log = (GLchar *)malloc(logLength);
/*
獲取日誌資訊
參數1:着色器
參數2:日誌資訊長度
參數3:日誌資訊長度地址
參數4:日誌儲存的位置
*/
glGetShaderInfoLog(*shader, logLength, &logLength, log);
//列印日誌資訊
NSLog(@"Shader compile log:\n%s", log);
//釋放日誌字串
free(log);
return NO;
}
return YES;
}
// 使用program
glUseProgram(program);
// 計算MVP矩陣變化
// 投影矩陣 與 模式檢視矩陣 相乘結果
GLKMatrix4 modelViewProjectionMatrix = GLKMatrix4Multiply(self.transform.projectionMatrix,self.transform.modelviewMatrix);
/* 將結果矩陣,通過unifrom傳遞
* glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
* 參數1:location,要更改的uniforms變數的位置
* 參數2:cout ,更改矩陣的個數
* 參數3:transpose,指是否要轉置矩陣,並將它作爲uniform變數的值,必須爲GL_FALSE
* 參數4:value ,指向count個數的元素指針.用來更新uniform變數的值.
*/
glUniformMatrix4fv(uniforms[CCMVPMatrix], 1, 0,modelViewProjectionMatrix.m);
/* 一個紋理採樣均勻變數
* glUniform1f(GLint location, GLfloat v0);
* location:指明要更改的uniform變數的位置
* v0:指明在指定的uniform變數中要使用的新值
*/
glUniform1i(uniforms[CCSamplers2D], 0);
/* 粒子物理值:重力
* void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
* 參數列表:
* location:指明要更改的uniform變數的位置
* count:指明要更改的向量個數
* value:指明一個指向count個元素的指針,用來更新指定的uniform變數
*/
glUniform3fv(uniforms[CCGravity], 1, self.gravity.v);
// 耗時
glUniform1fv(uniforms[CCElapsedSeconds], 1, &elapsedSeconds);
// 粒子數據更新
if(self.particleDataWasUpdated) {
// 快取區爲空,且粒子數據大小>0
if(self.particleAttributeBuffer == nil && [self.particleAttributesData length] > 0) {
/* 頂點屬性沒有送到GPU:初始化快取區
* 1.數據大小 sizeof(CCParticleAttributes)
* 2.數據個數 (int)[self.particleAttributesData length] / sizeof(CCParticleAttributes)
* 3.數據源 [self.particleAttributesData bytes]
* 4.用途 GL_DYNAMIC_DRAW
*/
// 數據大小
GLsizeiptr size = sizeof(CCParticleAttributes);
// 個數
int count = (int)[self.particleAttributesData length] /
sizeof(CCParticleAttributes);
self.particleAttributeBuffer = [[YDWVertexAttribArrayBuffer alloc] initWithAttribStride:size numberOfVertices:count bytes:[self.particleAttributesData bytes] usage:GL_DYNAMIC_DRAW];
} else {
/* 如果已經開闢空間,則接收新的數據
* 1.數據大小 sizeof(CCParticleAttributes)
* 2.數據個數 (int)[self.particleAttributesData length] / sizeof(CCParticleAttributes)
* 3.數據源 [self.particleAttributesData bytes]
*/
// 數據大小
GLsizeiptr size = sizeof(CCParticleAttributes);
// 個數
int count = (int)[self.particleAttributesData length] /
sizeof(CCParticleAttributes);
[self.particleAttributeBuffer
reinitWithAttribStride:size
numberOfVertices:count
bytes:[self.particleAttributesData bytes]];
}
// 恢復更新狀態爲NO
self.particleDataWasUpdated = NO;
}
// 準備頂點數據
[self.particleAttributeBuffer
prepareToDrawWithAttrib:CCParticleEmissionPosition
numberOfCoordinates:3
attribOffset:
offsetof(CCParticleAttributes, emissionPosition)
shouldEnable:YES];
// 準備粒子發射速度數據
[self.particleAttributeBuffer
prepareToDrawWithAttrib:CCParticleEmissionVelocity
numberOfCoordinates:3
attribOffset:
offsetof(CCParticleAttributes, emissionVelocity)
shouldEnable:YES];
// 準備重力數據
[self.particleAttributeBuffer
prepareToDrawWithAttrib:CCParticleEmissionForce
numberOfCoordinates:3
attribOffset:
offsetof(CCParticleAttributes, emissionForce)
shouldEnable:YES];
// 準備粒子size數據
[self.particleAttributeBuffer
prepareToDrawWithAttrib:CCParticleSize
numberOfCoordinates:2
attribOffset:
offsetof(CCParticleAttributes, size)
shouldEnable:YES];
// 準備粒子的持續時間和漸隱時間數據
[self.particleAttributeBuffer
prepareToDrawWithAttrib:CCParticleEmissionTimeAndLife
numberOfCoordinates:2
attribOffset:
offsetof(CCParticleAttributes, emissionTimeAndLife)
shouldEnable:YES];
// 將所有紋理系結到各自的單位
/*
void glActiveTexture(GLenum texUnit);
該函數選擇一個紋理單元,線面的紋理函數將作用於該紋理單元上,參數爲符號常數GL_TEXTUREi ,i的取值範圍爲0~K-1,K是OpenGL實現支援的最大紋理單元數,可以使用GL_MAX_TEXTURE_UNITS來呼叫函數glGetIntegerv()獲取該值
可以這樣簡單的理解爲:顯示卡中有N個紋理單元(具體數目依賴你的顯示卡能力),每個紋理單元(GL_TEXTURE0、GL_TEXTURE1等)都有GL_TEXTURE_1D、GL_TEXTURE_2D等
*/
glActiveTexture(GL_TEXTURE0);
//判斷紋理標記是否爲空,以及紋理是否可用
if(0 != self.texture2d0.name && self.texture2d0.enabled) {
//系結紋理到紋理標記上
//參數1:紋理型別
//參數2:紋理名稱
glBindTexture(GL_TEXTURE_2D, self.texture2d0.name);
} else {
//系結一個空的
glBindTexture(GL_TEXTURE_2D, 0);
}
- (void)draw {
// 禁用深度緩衝區寫入
glDepthMask(GL_FALSE);
//繪製
/*
1.模式
2.開始的位置
3.粒子個數
*/
[self.particleAttributeBuffer drawArrayWithMode:GL_POINTS startVertexIndex:0 numberOfVertices:(int)self.numberOfParticles];
// 啓用深度緩衝區寫入
glDepthMask(GL_TRUE);
}
通過粒子類YDWPointParticleEffect建立粒子物件,並實現4種粒子效果,然後系統呼叫GLKView & GLKViewDelegate代理方法進行繪製和更新
- (void)viewDidLoad {
[super viewDidLoad];
// 新建OpenGLES 上下文
self.mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
GLKView* view = (GLKView *)self.view;
view.context = self.mContext;
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
[EAGLContext setCurrentContext:self.mContext];
// 紋理路徑
NSString *path = [[NSBundle bundleForClass:[self class]]
pathForResource:@"ball" ofType:@"png"];
if (path == nil) {
NSLog(@"ball texture image not found");
return;
}
// 載入紋理物件
NSError *error = nil;
self.ballParticleTexture = [GLKTextureLoader textureWithContentsOfFile:path options:nil error:&error];
// 粒子物件
self.particleEffect = [[YDWPointParticleEffect alloc]init];
self.particleEffect.texture2d0.name = self.ballParticleTexture.name;
self.particleEffect.texture2d0.target = self.ballParticleTexture.target;
// 開啓深度測試
glEnable(GL_DEPTH_TEST);
// 開啓混合
glEnable(GL_BLEND);
// 設定混合因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 執行程式碼塊.4種不同效果
void(^blockA)(void) = ^{
self.autoSpawnDelta = 0.5f;
// 重力
self.particleEffect.gravity = CCDefaultGravity;
// X軸上隨機速度
float randomXVelocity = -0.5f + 1.0f * (float)random() / (float)RAND_MAX;
/*
Position:出發位置
velocity:速度
force:拋物線
size:大小
lifeSpanSeconds:耗時
fadeDurationSeconds:漸逝時間
*/
[self.particleEffect
addParticleAtPosition:GLKVector3Make(0.0f, 0.0f, 0.9f)
velocity:GLKVector3Make(randomXVelocity, 1.0f, -1.0f)
force:GLKVector3Make(0.0f, 9.0f, 0.0f)
size:8.0f
lifeSpanSeconds:3.2f
fadeDurationSeconds:0.5f];
};
void(^blockB)(void) = ^{
self.autoSpawnDelta = 0.05f;
// 重力
self.particleEffect.gravity = GLKVector3Make(0.0f,0.5f, 0.0f);
// 一次建立多少個粒子
int n = 50;
for(int i = 0; i < n; i++) {
// X軸速度
float randomXVelocity = -0.1f + 0.2f *(float)random() / (float)RAND_MAX;
// Y軸速度
float randomZVelocity = 0.1f + 0.2f * (float)random() / (float)RAND_MAX;
[self.particleEffect
addParticleAtPosition:GLKVector3Make(0.0f, -0.5f, 0.0f)
velocity:GLKVector3Make(
randomXVelocity,
0.0,
randomZVelocity)
force:GLKVector3Make(0.0f, 0.0f, 0.0f)
size:16.0f
lifeSpanSeconds:2.2f
fadeDurationSeconds:3.0f];
}
};
void(^blockC)(void) = ^{
self.autoSpawnDelta = 0.5f;
//重力
self.particleEffect.gravity = GLKVector3Make(0.0f, 0.0f, 0.0f);
int n = 100;
for(int i = 0; i < n; i++) {
//X,Y,Z速度
float randomXVelocity = -0.5f + 1.0f * (float)random() / (float)RAND_MAX;
float randomYVelocity = -0.5f + 1.0f * (float)random() / (float)RAND_MAX;
float randomZVelocity = -0.5f + 1.0f * (float)random() / (float)RAND_MAX;
//建立粒子
[self.particleEffect
addParticleAtPosition:GLKVector3Make(0.0f, 0.0f, 0.0f)
velocity:GLKVector3Make(
randomXVelocity,
randomYVelocity,
randomZVelocity)
force:GLKVector3Make(0.0f, 0.0f, 0.0f)
size:4.0f
lifeSpanSeconds:3.2f
fadeDurationSeconds:0.5f];
}
};
void(^blockD)(void) = ^{
self.autoSpawnDelta = 3.2f;
// 重力
self.particleEffect.gravity = GLKVector3Make(0.0f, 0.0f, 0.0f);
int n = 100;
for(int i = 0; i < n; i++) {
// X,Y速度
float randomXVelocity = -0.5f + 1.0f * (float)random() / (float)RAND_MAX;
float randomYVelocity = -0.5f + 1.0f * (float)random() / (float)RAND_MAX;
// GLKVector3Normalize 計演算法向量
// 計算速度與方向
GLKVector3 velocity = GLKVector3Normalize( GLKVector3Make(
randomXVelocity,
randomYVelocity,
0.0f));
[self.particleEffect
addParticleAtPosition:GLKVector3Make(0.0f, 0.0f, 0.0f)
velocity:velocity
force:GLKVector3MultiplyScalar(velocity, -1.5f)
size:4.0f
lifeSpanSeconds:3.2f
fadeDurationSeconds:0.1f];
}
};
// 將4種不同效果的BLOCK塊儲存到陣列中
self.emitterBlocks = @[[blockA copy],[blockB copy],[blockC copy],[blockD copy]];
// 縱橫比
float aspect = CGRectGetWidth(self.view.bounds) / CGRectGetHeight(self.view.bounds);
// 設定投影方式\模型檢視變換矩陣
[self preparePointOfViewWithAspectRatio:aspect];
}
// MVP矩陣
- (void)preparePointOfViewWithAspectRatio:(GLfloat)aspectRatio {
// 設定透視投影方式
self.particleEffect.transform.projectionMatrix =
GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(85.0f),
aspectRatio,
0.1f,
20.0f);
// 模型檢視變換矩陣
// 獲取世界座標系去模型矩陣中.
/*
LKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ)
等價於 OpenGL 中
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz);
目的:根據你的設定返回一個4x4矩陣變換的世界座標系座標。
參數1:眼睛位置的x座標
參數2:眼睛位置的y座標
參數3:眼睛位置的z座標
第一組:就是腦袋的位置
參數4:正在觀察的點的X座標
參數5:正在觀察的點的Y座標
參數6:正在觀察的點的Z座標
第二組:就是眼睛所看物體的位置
參數7:攝像機上向量的x座標
參數8:攝像機上向量的y座標
參數9:攝像機上向量的z座標
第三組:就是頭頂朝向的方向(因爲你可以頭歪着的狀態看物體)
*/
self.particleEffect.transform.modelviewMatrix =
GLKMatrix4MakeLookAt(
0.0, 0.0, 1.0, // Eye position
0.0, 0.0, 0.0, // Look-at position
0.0, 1.0, 0.0); // Up direction
}
// 更新
- (void)update {
// 時間間隔
NSTimeInterval timeElapsed = self.timeSinceFirstResume;
/*
// 上一次更新時間
NSLog(@"timeSinceLastUpdate: %f", self.timeSinceLastUpdate);
// 上一次繪製的時間
NSLog(@"timeSinceLastDraw: %f", self.timeSinceLastDraw);
// 第一次恢復時間
NSLog(@"timeSinceFirstResume: %f", self.timeSinceFirstResume);
// 上一次恢復時間
NSLog(@"timeSinceLastResume: %f", self.timeSinceLastResume);
*/
// 消耗時間
self.particleEffect.elapsedSeconds = timeElapsed;
// 動畫時間 < 當前時間與上一次更新時間
if(self.autoSpawnDelta < (timeElapsed - self.lastSpawnTime)) {
// 更新上一次更新時間
self.lastSpawnTime = timeElapsed;
// 獲取當前選擇的block
void(^emitterBlock)() = [self.emitterBlocks objectAtIndex: self.currentEmitterIndex];
// 執行block
emitterBlock();
}
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClearColor(0.3, 0.3, 0.3, 1);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
// 準備繪製
[self.particleEffect prepareToDraw];
// 繪製
[self.particleEffect draw];
}