博客> OpenGLES系列12-激光特效
OpenGLES系列12-激光特效
2小时前 评论:0 阅读:704 handyTOOL
ios 开发 3D OPenGL ES

本系列所有文章目录

获取示例代码


本文主要介绍如何使用2个四边形实现一个简单的激光效果。下面是最终效果图。 在了解激光实现原理之前,先介绍一下我对上一篇文章的代码进行的简单重构。我把OpenGL关键性的代码都集成到了GLContext类中。

#import <GLKit>

@interface GLContext : NSObject
@property (assign, nonatomic) GLuint program;
+ (id)contextWithVertexShaderPath:(NSString *)vertexShaderPath fragmentShaderPath:(NSString *)fragmentShaderPath;
- (id)initWithVertexShader:(NSString *)vertexShader fragmentShader:(NSString *)fragmentShader;
- (void)active;

/// draw functions
- (void)drawTriangles:(GLfloat *)triangleData vertexCount:(GLint)vertexCount;

/// uniform setters
- (void)setUniform1i:(NSString *)uniformName value:(GLint)value;
- (void)setUniform1f:(NSString *)uniformName value:(GLfloat)value;
- (void)setUniform3fv:(NSString *)uniformName value:(GLKVector3)value;
- (void)setUniformMatrix4fv:(NSString *)uniformName value:(GLKMatrix4)value;

/// texture
- (void)bindTexture:(GLKTextureInfo *)textureInfo to:(GLenum)textureChannel uniformName:(NSString *)uniformName;

@end

这个类可以做以下事情:

  1. 编译链接Shader,生成program
  2. 调用active激活GLContext中的program
  3. 使用drawTriangles绘制三角形,后面会增加绘制三角带,线和点等等。
  4. setUniformXXX系列方法用来设置各种uniform的值。当然还可以增加更多。
  5. bindTexture用来绑定纹理到指定通道。

有了这个类之后,我们可以为不同的Shader建立不同的GLContext,这就意味着我们可以方便的在同一场景使用不同的Shader渲染不同的物体。GLContext的实现代码都是之前已有的代码,比较简单就不详述了。 回到激光特效实现的原理, 开头说到它是由两个四边形组成的,具体的形状如下。

两个四边形垂直相交,然后分别为他们加上一张纹理图。 渲染这两个四边形时,不能够使用前面介绍的光照模型,在Fragment Shader中直接返回纹理色即可,所以我们可以为激光单独创建一个Fragment Shader。

precision highp float;

varying vec2 fragUV;

uniform sampler2D diffuseMap;
uniform float life; // max: 1, min: 0
uniform float hue;

#define Max(a, b) (a > b ? a : b)
#define Min(a, b) (a < b xss=removed> 1.0) t -= 1.0;
    if(t < 1 xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed> 0.5 ? d / (2.0 - max - min) : d / (max + min);
        if (max == r) h = (g - b) / d + (g < b xss=removed xss=removed xss=removed xss=removed xss=removed> 0.05 && fragUV.y < 0 xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed link?url=sjNIht_UbG-Tz1BgtjVASBtQ-SdD7nrtCzt55d2dyN4xrzCuiufR0OXPlntkCo3ZE5mucLgbpts0e7RFlGoGsq)。相关代码如下。> b ? a : b)
#define Min(a, b) (a < b xss=removed> 1.0) t -= 1.0;
    if(t < 1 xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed> 0.5 ? d / (2.0 - max - min) : d / (max + min);
        if (max == r) h = (g - b) / d + (g < b xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed> HSL代表色相(H)、饱和度(S)、明度(L),其中色相是控制颜色的主要组件。

#### 防止过度拉伸
这行代码主要就是防止贴图在y方向上过度拉伸,对于大于0.05小于0.95的值一律都按照0.5处理,当然也可以为x方向做同样的处理。这个和日常App开发中九宫格的原理是类似的。

float v = (fragUV.y > 0.05 && fragUV.y < 0 xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed>

@class GLContext;

@interface Laser : NSObject @property (assign, nonatomic) GLfloat life; @property (assign, nonatomic) GLKVector3 position; @property (assign, nonatomic) GLKVector3 direction; @property (assign, nonatomic) float length; @property (assign, nonatomic) float radius;

  • (id)initWithLaserImage:(UIImage *)image;
  • (void)update:(NSTimeInterval)timeSinceLastUpdate;
  • (void)draw:(GLContext *)glContext; @end
    
    `life`就是之前提到的控制渐隐渐现的参数,`position`表示激光发射的位置,`direction`表示激光发射的方向,`length`是长度,`radius`是直径。初始化的时候将纹理图片传入即可。

update中计算对应的modelMatrix

- (void)update:(NSTimeInterval)timeSinceLastUpdate {
    self.life -= timeSinceLastUpdate;
    if (self.life <= 0) {
        self.life = 1;
        float x = rand() / (float)RAND_MAX * 0.1 - 0.05;
        float y = rand() / (float)RAND_MAX * 0.1 - 0.05;
        self.direction = GLKVector3Normalize(GLKVector3Make(x, y, 1));
        self.hue = rand() / (float)RAND_MAX * 1.0;
    }

    GLKVector3 defaultForward = GLKVector3Make(0, 0, 1);
    GLKVector3 rotateAxis = GLKVector3CrossProduct(defaultForward, self.direction);
    float cosAngle = GLKVector3DotProduct(defaultForward, self.direction);
    float angle = acos(cosAngle);

    GLKMatrix4 scaleMatrix = GLKMatrix4MakeScale(self.length, self.radius, self.radius);
    GLKMatrix4 rotateToZMatrix = GLKMatrix4MakeRotation(M_PI / 2, 0, 1, 0);
    GLKMatrix4 translateMatrix = GLKMatrix4MakeTranslation(0, 0, self.length / 2);
    GLKMatrix4 rotateMatrix = GLKMatrix4MakeRotation(angle, rotateAxis.x, rotateAxis.y, rotateAxis.z);
    GLKMatrix4 positionTranslateMatrix = GLKMatrix4MakeTranslation(self.position.x, self.position.y, self.position.z);
    self.modelMatrix = GLKMatrix4Multiply(rotateToZMatrix, scaleMatrix);
    self.modelMatrix = GLKMatrix4Multiply(translateMatrix, self.modelMatrix);
    self.modelMatrix = GLKMatrix4Multiply(rotateMatrix, self.modelMatrix);
    self.modelMatrix = GLKMatrix4Multiply(positionTranslateMatrix, self.modelMatrix);
}

上面的代码在每一次life小于等于0时,重置激光方向和色相Hue。计算modelMatrix的步骤如下:

  1. 根据方向计算旋转轴和旋转角。
  2. 计算缩放矩阵scaleMatrix将激光缩放到长self.length,直径self.radius
  3. 因为默认较长的方向是x轴,所以再计算旋转到z轴的旋转矩阵rotateToZMatrix
  4. 旋转到z轴后,将一端移至(0,0,0)点,计算出translateMatrix
  5. 在根据刚开始计算的旋转角和旋转轴计算旋转矩阵rotateMatrix
  6. 最后计算将激光移至self.position的矩阵positionTranslateMatrix

将上述矩阵相乘即可得到modelMatrix,注意是从下往上乘。

具体的绘制代码如下。lifehue都在- (void)draw:(GLContext *)glContext里传递给了Shader。- (void)drawLaser:(GLContext *)glContext中绘制了两个垂直平面,并且在绘制过程中禁用了DepthMask

- (void)draw:(GLContext *)glContext {
    [glContext setUniformMatrix4fv:@"modelMatrix" value:self.modelMatrix];
    bool canInvert;
    GLKMatrix4 normalMatrix = GLKMatrix4InvertAndTranspose(self.modelMatrix, &canInvert);
    [glContext setUniformMatrix4fv:@"normalMatrix" value:canInvert ? normalMatrix : GLKMatrix4Identity];

    [glContext setUniform1f:@"life" value:self.life];
    [glContext bindTexture:self.diffuseTexture to:GL_TEXTURE0 uniformName:@"diffuseMap"];
    [glContext setUniform1f:@"hue" value:self.hue];

    [self drawLaser: glContext];
}

- (void)drawLaser:(GLContext *)glContext{
    glDepthMask(GL_FALSE);

    static GLfloat plane1[] = {
        -0.5, 0.5f, 0, 1, 0, 0,     1, 0, // x, y, z, r, g, b,每一行存储一个点的信息,位置和颜色
        -0.5f, -0.5f, 0, 0, 1, 0,   0, 0,
        0.5f, -0.5f, 0, 0, 0, 1,    0, 1,
        0.5, -0.5f, 0, 0, 0, 1,     0, 1,
        0.5f, 0.5f, 0, 0, 1, 0,     1, 1,
        -0.5f, 0.5f, 0, 1, 0, 0,    1, 0,
    };
    [glContext drawTriangles:plane1 vertexCount:6];

    static GLfloat plane2[] = {
        -0.5,0, 0.5f,  1, 0, 0,     1, 0, // x, y, z, r, g, b,每一行存储一个点的信息,位置和颜色
        -0.5f,0, -0.5f,  0, 1, 0,   0, 0,
        0.5f, 0, -0.5f, 0, 0, 1,    0, 1,
        0.5,0, -0.5f,  0, 0, 1,     0, 1,
        0.5f, 0, 0.5f, 0, 1, 0,     1, 1,
        -0.5f, 0,0.5f,  1, 0, 0,    1, 0,
    };
    [glContext drawTriangles:plane2 vertexCount:6];

    glDepthMask(GL_TRUE);
}

本文的例子中使用的BlendFunc是glBlendFunc (GL_SRC_ALPHA, GL_DST_ALPHA);,这种混合方式可以让激光显得更明亮一些。

到此,激光的效果就介绍完了,这个效果涉及到了之前大部分的知识,算是一个阶段性总结了吧。

收藏
0
sina weixin mail 回到顶部