OpenGL ES iOS 入门第一个实例: 绘制三角形
一, 将ViewController
设置为继承自GLKViewController
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController
@end
二, 声明GLKBaseEffect和GLuint属性和设置顶点坐标
typedef struct {
GLKVector3 positionCoords;
}sceneVertex;
// 两个三角形:(在OpenGL中所有的图形都是由点, 线, 三角形组成)
static const sceneVertex vertices[] = {
// 三角形
{{0.5, -0.5, 0.0f}
{{0.5, 0.5, 0.0f}
{{-0.5, 0.5, 0.0f}
};
@interface ViewController ()
// 声明GLKBaseEffect属性
@property (nonatomic, strong) GLKBaseEffect *baseEffect;
// 声明缓存ID属性
@property (nonatomic, assign) GLuint vertextBufferID;
@end
三, 设置上下文,并创建缓存区
- (void)viewDidLoad {
[super viewDidLoad];
// 获取到渲染的图层
GLKView *kView = (GLKView *)self.view;
// 创建OpenGL ES 3.0 的上下文
kView.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
// 设置当前上下文
[EAGLContext setCurrentContext:kView.context];
// 初始化GLKBaseEffect
self.baseEffect = [[GLKBaseEffect alloc] init];
// 使用静态颜色绘制
self.baseEffect.useConstantColor = GL_TRUE;
// 设置图形默认颜色
self.baseEffect.constantColor = GLKVector4Make(1.0f, 0, 0, 1.0);
// 设置背景颜色
glClearColor(0, 0, 0, 1);
/*
GLKBaseEffect使得我们不在需要编写着色器语言就可以绘制一些简单的图形. 如: 三角形,正方形等
// 绘制三角形,需要描述三角形在卡迪尔坐标系中的空间坐标值
全局声明一个顶点三角形的坐标
*/
// 生成缓存区
glGenBuffers(1, &_vertextBufferID);
// 绑定指定标识的缓存区为当前缓存
glBindBuffer(GL_ARRAY_BUFFER, _vertextBufferID);
// 从CPU复制数据到GPU
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}
四, 绘制
// 绘制
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
[self.baseEffect prepareToDraw];
// 1, 清除buffer中的数据
glClear(GL_COLOR_BUFFER_BIT);
// 2, 开启缓存
glEnableVertexAttribArray(GLKVertexAttribPosition);
// 设置缓存数据指针
glVertexAttribPointer(GLKVertexAttribPosition,
3,
GL_FLOAT,
GL_FALSE, // 小数点的固定数据是否被改变
sizeof(sceneVertex),
NULL); // 从开始位置
glDrawArrays(GL_TRIANGLES, 0, 3);
}
五, 删除缓存数据
// 删除缓存中的数据
- (void)dealloc {
GLKView *kView = (GLKView *)self.view;
[EAGLContext setCurrentContext:kView.context];
if (_vertextBufferID != 0) {
glDeleteBuffers(1, &_vertextBufferID);
_vertextBufferID = 0;
}
}
总结
总结:
OpenGL ES iOS 入门第一个实例: 绘制三角形
创建缓存数据,并完成最终渲染到显示的几个步骤:
1, 生成缓存区: glGenBuffers()
2, 绑定缓存数据: glBindBuffer()
3, 从CPU复制缓存数据到GPU
4, 启用缓存: glEnableVertexAttribArray()
5, 设置指针: glVertexAttribPointer()
6, 绘图: glDrawArrays()
7, 重写glView:drawRect方法
8, 删除缓存: glDeleteBuffers()
OpenGL ES iOS 入门第二个实例: 生成纹理图片
一, 将ViewController
设置为继承自GLKViewController
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController
@end
二, 声明GLKBaseEffect和GLuint属性
@interface ViewController ()
// 声明GLKBaseEffect属性
@property (nonatomic, strong) GLKBaseEffect *baseEffect;
// 声明缓存ID属性
@property (nonatomic, assign) GLuint vertextBufferID;
@end
三, 设置矩形坐标(由两个三角形组成)和对应的纹理坐标
typedef struct {
GLKVector3 positionCoords;
GLKVector2 textureCoords;// 纹理
}sceneVertex;
// 两个三角形:(在OpenGL中所有的图形都是由点, 线, 三角形组成)
static const sceneVertex vertices[] = {
// 前面部分是三角形的顶点坐标 后面部分是纹理坐标, 两者需要一一对应
// 第一个三角形
{{0.5, -0.5, 0.0f},{0.5,0.0f}}, //右下
{{0.5, 0.5, 0.0f},{0.5,0.5}}, //右上
{{-0.5, 0.5, 0.0f},{0.0f,0.5}}, //左上
// 第二个三角形
{{0.5, -0.5, 0.0f},{0.5,0.0f}}, //右下
{{-0.5, 0.5, 0.0f},{0.0f,0.5}}, //左上
{{-0.5, -0.5, 0.0f},{0.0f,0.0f}}, //左下
};
四, 创建图层上下文
- (void)viewDidLoad {
[super viewDidLoad];
// 设置图层上下文
GLKView *kView = (GLKView *)self.view;
kView.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
[EAGLContext setCurrentContext:kView.context];
// 初始化GLKBaseEffect: 有了GLKBaseEffect就可以不用写着色器语言
self.baseEffect = [[GLKBaseEffect alloc] init];
self.baseEffect.useConstantColor = GL_TRUE;
self.baseEffect.constantColor = GLKVector4Make(1.0, 1.0, 1.0, 1);
// 生成缓存区
[self fillVertexArray];
}
五, 创建缓存区并设置纹理和缓存的指针, 最后生成纹理
// 顶点和需要设置的纹理是位置是一一对应的. 所以在生成和绑定顶点和纹理的buffer时,也需要设置对应指针的偏移量
- (void)fillVertexArray {
glGenBuffers(1, &_vertextBufferID);
glBindBuffer(GL_ARRAY_BUFFER, _vertextBufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 启用并设置指针
glEnableVertexAttribArray(GLKVertexAttribPosition); // 顶点数据
glVertexAttribPointer(GLKVertexAttribPosition,
3,
GL_FLOAT,
GL_FALSE,
sizeof(sceneVertex),
NULL + offsetof(sceneVertex, positionCoords));
// 纹理
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0,
2,
GL_FLOAT,
GL_FALSE,
sizeof(sceneVertex), NULL + offsetof(sceneVertex, textureCoords));
// 生成纹理
CGImageRef imageRef = [[UIImage imageNamed:@"AAAA"] CGImage];
// 通过图片数据生成纹理缓存
GLKTextureInfo *tetureInfo = [GLKTextureLoader textureWithCGImage:imageRef options:nil error:nil];
self.baseEffect.texture2d0.name = tetureInfo.name;
self.baseEffect.texture2d0.target = tetureInfo.target;
}
六, 绘制图形, 这个方法会一直被调用
// 绘制
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
// 清除背景颜色
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
[self.baseEffect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 6); // 6: 表示这里是两个三角形, 所以有6个点
}
七, 删除缓存
// 删除缓存数据
- (void)dealloc {
GLKView *kView = (GLKView *)self.view;
[EAGLContext setCurrentContext:kView.context];
if (_vertextBufferID != 0) {
glDeleteBuffers(1, &_vertextBufferID);
_vertextBufferID = 0;
}
[EAGLContext setCurrentContext:nil];
}
当前设置的是一张静态图,所以是可以在
viewDidLoad()
函数中直接生成和绑定数据缓存的,如果是要做一些动态的效果,则需要在drawRect()
函数中生成和绑定缓存.
运行效果: 图片变成了倒立,这是因为CoreGraphics的坐标的问题,解决方法则是在option
中添加坐标变换就可以了
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@(1), GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:imageRef options:options error:NULL];
总结
遗留问题: 如何纹理图片并没有按照比例显示, 如何使得图片等比例填充?
OpenGL ES iOS 入门第三个实例: 纹理取样, 混合,多重纹理
一, 纹理取样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GL_TEXTURE_MIN_FILTER: 表示的是多个纹素对应一个顶点
GL_TEXTURE_MAG_FILTER: 表示没有足够的纹素唯一性来映射一个或者多个纹素到片元着色器时的配置取样.
GL_NEAREST: 取最近的纹素
GL_LINEAR: 取附近多个纹素混合的结果
顶点坐标系中的u,v坐标和纹理坐标的s,t是一一映射的关系
需要注意的是:当我们更新完数据顶点的坐标需要重新glBufferData刷新GPU的缓存.
纹理混合
纹理混合其实类似OC中的加载UIImage
, 填充纹理后直接使用glEnable(GL_BLEND)
和glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
函数就可以直接进行纹理混合操作
GL_SRC_ALPHA: 该模式用于让源片元的透明度和其他的片元的透明度进行相乘;
GL_ONE_MINUS_SRC_ALPHA: 该模式是让源片元的透明度和正在更新的像素颜色相乘;
多重纹理
多重纹理: 从至少两个纹理缓存中取纹素; 目的是替代纹理混合,提高性能, 原因是纹理混合是通过多次读写像素颜色渲染缓存,所以每次更新界面图形都需要渲染多次,需要从帧缓存读取颜色数据和片元数据混合,再次回帧缓存,所以这样的方式多次内存读写的操作就决定了混合在性能上的不佳.所以采用多重纹理方式替代.
参考代码
一, 将ViewController
设置为继承自GLKViewController
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController
@end
二, 设置顶点数据
// 顶点数据
typedef struct {
GLKVector3 positionCoords; // 空间坐标x,y,z
GLKVector2 textureCoords; // 纹理坐标 s,t
}SceneVertex;
static const SceneVertex vertices[] = {
{{顶点坐标}, {纹理坐标}}
{{1, -1, 0.0f,},{1.0f,0.0f}}, //右下
{{1, 1, 0.0f},{1.0f,1.0f}}, //右上
{{-1, 1, 0.0f},{0.0f,1.0f}}, //左上
{{1, -1, 0.0f},{1.0f,0.0f}}, //右下
{{-1, 1, 0.0f},{0.0f,1.0f}}, //左上
{{-1, -1, 0.0f},{0.0f,0.0f}}, //左下
};
三, 声明属性
@interface ViewController ()
// 绑定缓存的标识ID
@property (nonatomic, assign) GLuint vertextBufferID;
@property (nonatomic, strong) GLKBaseEffect *baseEffect;
// 多重纹理
@property (nonatomic, strong) GLKTextureInfo *textureInfo1;
@property (nonatomic, strong) GLKTextureInfo *textureInfo2;
@end
四, 初始化上下文和GLKBaseEffect
- (void)viewDidLoad {
[super viewDidLoad];
[self initContextAndGLKBaseEffect];
}
// 初始化context上下文和bassEffect
- (void)initContextAndGLKBaseEffect {
// 初始化上下文
GLKView *kview = (GLKView *)self.view;
NSAssert([kview isKindOfClass:[GLKView class]], @"kview is not viewController`s GLKView");
kview.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:kview.context];
// 初始化GLKBaseEffect
self.baseEffect = [[GLKBaseEffect alloc] init];
self.baseEffect.useConstantColor = GL_TRUE;
self.baseEffect.constantColor = GLKVector4Make(1.0f, 1.0f, 1.0f, 1.0f);
// self.baseEffect.texture2d0.envMode = GLKTextureEnvModeDecal;
// 填充顶点缓存
[self fillVertexBuffer];
// 填充纹理
[self fillTexture];
}
五, 填充顶点数据, 设置顶点偏移量指针和纹理偏移量指针
// 填充顶点缓存, 设置偏移指针
- (void)fillVertexBuffer {
// 创建顶点缓存
glGenBuffers(1, &_vertextBufferID);
glBindBuffer(GL_ARRAY_BUFFER, _vertextBufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 设置顶点指针和偏移量
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL + offsetof(SceneVertex, positionCoords));
// 设置纹理的指针和偏移量
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL + offsetof(SceneVertex, textureCoords));
}
七, 加载纹理
// 加载纹理
- (void)fillTexture {
// 填充纹理主要使用的是GLKTextureInfo函数的GLKTextureLoader, 纹理图片的层级顺序是取决于纹理时的顺序,
CGImageRef imageRef1 = [UIImage imageNamed:@"leaves.gif"].CGImage;
self.textureInfo1 = [GLKTextureLoader textureWithCGImage:imageRef1 options:nil error:nil];
// 图片2
CGImageRef imageRef2 = [UIImage imageNamed:@"beetle"].CGImage;
self.textureInfo2 = [GLKTextureLoader textureWithCGImage:imageRef2 options:nil error:nil];
// 多纹理混合需要使用到下面两个函数
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
八, 重写glkView:drawRect:方法
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0, 0, 0, 1.0f);
// 依次绘制纹理1和纹理2
self.baseEffect.texture2d0.name = self.textureInfo1.name;
self.baseEffect.texture2d0.target = self.textureInfo1.target;
[self.baseEffect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 6);
self.baseEffect.texture2d0.name = self.textureInfo2.name;
self.baseEffect.texture2d0.target = self.textureInfo2.target;
[self.baseEffect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 6);
}
删除缓存
// 删除
- (void)dealloc {
GLKView *view = (GLKView *)self.view;
[EAGLContext setCurrentContext:view.context];
if (_vertextBufferID != 0) {
glDeleteBuffers(1, &_vertextBufferID);
_vertextBufferID = 0;
}
[EAGLContext setCurrentContext:nil];
}
总结
疑问: 纹理的绘制的标准位置在哪?网上有的实例在viewDidLoad()
中,有的在glkView:drawRect:
中.他们的区别在哪里? 如何更好的使用绘制的相关函数.
OpenGL ES iOS 入门第四个实例: 灯光
在开发中我们常常会看到一些图像失真,模糊的情况,造成这现象的原因很有可能就是缺少灯光照射,灯光照射可以让图形更加立体清晰.其原理是计算机在模拟光照时, GPU会为每个三角形的顶点进行光线计算,再把结果进行插值,从而得出每个片元的最终颜色.
OpenGL ES 的灯光模拟包含: 环境光
, 漫反射光
, 镜面反射
.
一个渲染三角形中的每个光线组成部分取决于三个互相关联的因素
- 光线的位置
- 三角形相对光线的方向
- 三角形的材质
光线的计算依赖于表面法向量,法向量可以通过矢量进行计算.
表面法向量决定了平面的方向,通过光线和法向量的角度可以计算出漫反射光,环境光,镜面反射光的模拟,可以使用GLKit,系统会内置模拟计算出灯光效果.
平面法线: 假设一个三角形的三个顶点都被赋予了相同的法向量.
灯光烘焙到纹理
同样可以把灯光烘焙到纹理中,GPU模拟灯光需要做出很大的运行量,所以烘焙到纹理可以避开模拟灯光的矢量运算,但是相应的光烘焙到纹理仅仅适用于静态的场景,不适于动态场景.
#import "ViewController.h"
typedef struct {
GLKVector3 positionCoords; // 顶点法线
GLKVector3 normalCoords; // 法线
}SceneVertex;
typedef struct {
SceneVertex vertices[3];
}SceneTriangle;
// 顶点数据点
static const SceneVertex vertexA = {
// {{顶点坐标},{法线坐标}}
{-0.5, 0.5, -0.5}, {0.0, 0.0, 1.0}
};
static const SceneVertex vertexB = {
{-0.5, 0.0, -0.5}, {0.0, 0.0, 1.0}
};
static const SceneVertex vertexC = {
{-0.5, -0.5, -0.5}, {0.0, 0.0, 1.0}
};
static const SceneVertex vertexD = {
{ 0.0, 0.5, -0.5}, {0.0, 0.0, 1.0}
};
static const SceneVertex vertexE = {
{ 0.0, 0.0, -0.5}, {0.0, 0.0, 1.0}
};
static const SceneVertex vertexF = {
{ 0.0, -0.5, -0.5}, {0.0, 0.0, 1.0}
};
static const SceneVertex vertexG = {
{ 0.5, 0.5, -0.5}, {0.0, 0.0, 1.0}
};
static const SceneVertex vertexH = {
{ 0.5, 0.0, -0.5}, {0.0, 0.0, 1.0}
};
static const SceneVertex vertexI = {
{ 0.5, -0.5, -0.5}, {0.0, 0.0, 1.0}
};
// 多少个三角形面
#define NUM_FACES (8)
// 法线顶点
#define NUM_NORMAL_LINE_VERTS (48)
// 法线顶点+两个灯光方向顶点
#define NUM_LINE_VERTS (NUM_NORMAL_LINE_VERTS + 2)
// 函数的前置声明
static SceneTriangle SceneTriangleMake(
const SceneVertex vertexA,
const SceneVertex vertexB,
const SceneVertex vertexC);
static GLKVector3 SceneTriangleFaceNormal(
const SceneTriangle triangle);
static void SceneTrianglesUpdateFaceNormals(
SceneTriangle someTriangles[NUM_FACES]);
static void SceneTrianglesUpdateVertexNormals(
SceneTriangle someTriangles[NUM_FACES]);
static void SceneTrianglesNormalLinesUpdate(
const SceneTriangle someTriangles[NUM_FACES],
GLKVector3 lightPosition,
GLKVector3 someNormalLineVertices[NUM_LINE_VERTS]);
static GLKVector3 SceneVector3UnitNormal(
const GLKVector3 vectorA,
const GLKVector3 vectorB);
@interface ViewController ()
{
// 8个三角形
SceneTriangle triangles[NUM_FACES];
}
@property (nonatomic, strong) GLKBaseEffect *baseEffect; // 顶点的baseEffect
@property (nonatomic, strong) GLKBaseEffect *extralEffect; // 绘制法线方向的baseEffect
@property (nonatomic, assign) GLuint vertexBufferID; // 顶点的buffer
@property (nonatomic, assign) GLuint extralBufferID; // 绘制法线方向的buffer
@property (nonatomic, assign) GLfloat centerVertexHeight;
@property (nonatomic, assign) BOOL shouldUseFaceNormals;
@property (nonatomic, assign) BOOL shouldDrawNormals;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化上下文,灯光位置, 灯光颜色, 以及GLKBaseEffect;
[self initContextAndGLKBaseEffect];
}
- (void)initContextAndGLKBaseEffect {
GLKView *view = (GLKView *)self.view;
NSAssert([view isKindOfClass:[GLKView class]], @"viewController`s view is not a GLKView");
view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:view.context];
// 顶点的GLKBaseEEffect
self.baseEffect = [[GLKBaseEffect alloc] init];
self.baseEffect.light0.enabled = GL_TRUE;
// 设置灯光的漫反射颜色
self.baseEffect.light0.diffuseColor = GLKVector4Make(
0.7f, // r
0.7f, // g
0.7f, // b
1.0f); // a
// 设置灯光的位置
self.baseEffect.light0.position = GLKVector4Make(1.0f,
1.0f,
0.5f,
0.0f);
// 绘制法线方向的GLKBaseEffect
self.extralEffect = [[GLKBaseEffect alloc] init];
self.extralEffect.useConstantColor = GL_TRUE;
self.extralEffect.constantColor = GLKVector4Make(
0.0f,
1.0f,
0.0f,
1.0f);
{
// 视点变换
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(
GLKMathDegreesToRadians(-60), 1.0f, 0.0f, 0.0f);
modelViewMatrix = GLKMatrix4Rotate(
modelViewMatrix,
GLKMathDegreesToRadians(-3), 0.0f, 0.0f, 1.0f);
modelViewMatrix = GLKMatrix4Translate(
modelViewMatrix,
0.0f, 0.0f, 0.25f);
self.extralEffect.transform.modelviewMatrix = modelViewMatrix;
self.baseEffect.transform.modelviewMatrix = modelViewMatrix;
}
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 使用顶点初始化这8个三角形的数据
triangles[0] = SceneTriangleMake(vertexA, vertexB, vertexD);
triangles[1] = SceneTriangleMake(vertexB, vertexC, vertexF);
triangles[2] = SceneTriangleMake(vertexD, vertexB, vertexE);
triangles[3] = SceneTriangleMake(vertexE, vertexB, vertexF);
triangles[4] = SceneTriangleMake(vertexD, vertexE, vertexH);
triangles[5] = SceneTriangleMake(vertexE, vertexF, vertexH);
triangles[6] = SceneTriangleMake(vertexG, vertexD, vertexH);
triangles[7] = SceneTriangleMake(vertexH, vertexF, vertexI);
// 绑定顶点buffer
glGenBuffers(1, &_vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangles), triangles, GL_DYNAMIC_DRAW);
// 绑定法线绘制的buffer, 默认是不会绘制
glGenBuffers(1, &_extralBufferID);
glBindBuffer(GL_ARRAY_BUFFER, _extralBufferID);
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_DYNAMIC_DRAW);
// 设置默认的效果
self.centerVertexHeight = 0.0f;
self.shouldUseFaceNormals = YES;
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
[self.baseEffect prepareToDraw];
glClear(GL_COLOR_BUFFER_BIT);
// 顶点缓存的偏移量指针
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferID);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL + offsetof(SceneVertex, positionCoords));
// 法线缓存的偏移量指针
glBindBuffer(GL_ARRAY_BUFFER, _extralBufferID);
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL + offsetof(SceneVertex, normalCoords));
// 绘制
glDrawArrays(GL_TRIANGLES, 0, sizeof(triangles) / sizeof(SceneVertex));
if (self.shouldDrawNormals) {
[self drawNormals];
}
}
// 设置中心点的高度
- (void)setCenterVertexHeight:(GLfloat)centerVertexHeight {
_centerVertexHeight = centerVertexHeight;
SceneVertex newVertexE = vertexE;
newVertexE.positionCoords.z = self.centerVertexHeight;
// 涉及到E点
triangles[2] = SceneTriangleMake(vertexD, vertexB, newVertexE);
triangles[3] = SceneTriangleMake(newVertexE, vertexB, vertexF);
triangles[4] = SceneTriangleMake(vertexD, newVertexE, vertexH);
triangles[5] = SceneTriangleMake(newVertexE, vertexF, vertexH);
[self updateNormals];
}
- (void)setShouldUseFaceNormals:(BOOL)shouldUseFaceNormals {
if (shouldUseFaceNormals != _shouldUseFaceNormals) {
_shouldUseFaceNormals = shouldUseFaceNormals;
[self updateNormals];
}
}
- (IBAction)shouldUseFaceNormal:(UISwitch *)sender {
self.shouldUseFaceNormals = sender.isOn;
}
- (IBAction)shouldDrawNormals:(UISwitch *)sender {
self.shouldDrawNormals = sender.isOn;
}
- (IBAction)takeCenterVertexHeightFrom:(UISlider *)sender {
self.centerVertexHeight = sender.value;
}
// 绘制法线
- (void)drawNormals {
GLKVector3 normalLineVertices[NUM_LINE_VERTS];
// 更新48个法向量顶点和两个灯光的方向顶点
SceneTrianglesNormalLinesUpdate(
triangles,
GLKVector3MakeWithArray(self.baseEffect.light0.position.v),
normalLineVertices);
glBindBuffer(GL_ARRAY_BUFFER, _extralBufferID);
glBufferData(GL_ARRAY_BUFFER, NUM_LINE_VERTS*sizeof(GLKVector3), normalLineVertices, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLKVector3), NULL);
// 绘制每条顶点的法线
self.extralEffect.useConstantColor = GL_TRUE;
self.extralEffect.constantColor = GLKVector4Make(0.0, 1.0, 0.0, 1.0);
[self.extralEffect prepareToDraw];
glDrawArrays(GL_LINES, 0, NUM_NORMAL_LINE_VERTS);
// 绘制灯光方向
self.extralEffect.constantColor = GLKVector4Make(1.0, 1.0, 0.0, 1.0);
[self.extralEffect prepareToDraw];
glDrawArrays(GL_LINES, NUM_NORMAL_LINE_VERTS, NUM_LINE_VERTS);
}
// 更新法向量
- (void)updateNormals {
if (self.shouldUseFaceNormals) {
// 使用平均法线
SceneTrianglesUpdateFaceNormals(triangles);
} else {
// 使用顶点法线
SceneTrianglesUpdateVertexNormals(triangles);
}
//重新绑定缓存
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangles), triangles, GL_DYNAMIC_DRAW);
}
// 计算8个三角形的法向量, 并赋值新值
static void SceneTrianglesUpdateFaceNormals(SceneTriangle someTriangles[NUM_FACES]) {
for (int i = 0; i < NUM_FACES; i++) {
GLKVector3 faceNormal = SceneTriangleFaceNormal(someTriangles[i]);
someTriangles[i].vertices[0].normalCoords = faceNormal;
someTriangles[i].vertices[1].normalCoords = faceNormal;
someTriangles[i].vertices[2].normalCoords = faceNormal;
}
}
// 更新三角形法向量, 顶点采用的是平均法向量
static void SceneTrianglesUpdateVertexNormals(SceneTriangle someTriangles[NUM_FACES]) {
SceneVertex newVertexA = vertexA;
SceneVertex newVertexB = vertexB;
SceneVertex newVertexC = vertexC;
SceneVertex newVertexD = vertexD;
SceneVertex newVertexE = someTriangles[3].vertices[0];
SceneVertex newVertexF = vertexF;
SceneVertex newVertexG = vertexG;
SceneVertex newVertexH = vertexH;
SceneVertex newVertexI = vertexI;
GLKVector3 faceNormals[NUM_FACES];
for (int i = 0; i < NUM_FACES; i++) {
faceNormals[i] = SceneTriangleFaceNormal(someTriangles[i]);
}
//每个顶点的平均法向量
newVertexA.normalCoords = faceNormals[0];
newVertexB.normalCoords = GLKVector3MultiplyScalar(
GLKVector3Add(
GLKVector3Add(
GLKVector3Add(
faceNormals[0],
faceNormals[1]),
faceNormals[2]),
faceNormals[3]), 0.25);
newVertexC.normalCoords = faceNormals[1];
newVertexD.normalCoords = GLKVector3MultiplyScalar(
GLKVector3Add(
GLKVector3Add(
GLKVector3Add(
faceNormals[0],
faceNormals[2]),
faceNormals[4]),
faceNormals[6]), 0.25);
newVertexE.normalCoords = GLKVector3MultiplyScalar(
GLKVector3Add(
GLKVector3Add(
GLKVector3Add(
faceNormals[2],
faceNormals[3]),
faceNormals[4]),
faceNormals[5]), 0.25);
newVertexF.normalCoords = GLKVector3MultiplyScalar(
GLKVector3Add(
GLKVector3Add(
GLKVector3Add(
faceNormals[1],
faceNormals[3]),
faceNormals[5]),
faceNormals[7]), 0.25);
newVertexG.normalCoords = faceNormals[6];
newVertexH.normalCoords = GLKVector3MultiplyScalar(
GLKVector3Add(
GLKVector3Add(
GLKVector3Add(
faceNormals[4],
faceNormals[5]),
faceNormals[6]),
faceNormals[7]), 0.25);
newVertexI.normalCoords = faceNormals[7];
//更新triangles
someTriangles[0] = SceneTriangleMake(
newVertexA,
newVertexB,
newVertexD);
someTriangles[1] = SceneTriangleMake(
newVertexB,
newVertexC,
newVertexF);
someTriangles[2] = SceneTriangleMake(
newVertexD,
newVertexB,
newVertexE);
someTriangles[3] = SceneTriangleMake(
newVertexE,
newVertexB,
newVertexF);
someTriangles[4] = SceneTriangleMake(
newVertexD,
newVertexE,
newVertexH);
someTriangles[5] = SceneTriangleMake(
newVertexE,
newVertexF,
newVertexH);
someTriangles[6] = SceneTriangleMake(
newVertexG,
newVertexD,
newVertexH);
someTriangles[7] = SceneTriangleMake(
newVertexH,
newVertexF,
newVertexI);
}
// 更新三角形法线, 以及灯光方向线
static void SceneTrianglesNormalLinesUpdate(
const SceneTriangle someTriangles[NUM_FACES],
GLKVector3 lightPosition,
GLKVector3 someNormalLineVertices[NUM_LINE_VERTS]) {
int trianglesIndex = 0;
int lineVetexIndex = 0;
// 确定每个法向量的顶点, 用于绘制法线
for (trianglesIndex = 0; trianglesIndex < NUM_FACES; trianglesIndex++) {
someNormalLineVertices[lineVetexIndex++] =
someTriangles[trianglesIndex].vertices[0].positionCoords;
someNormalLineVertices[lineVetexIndex++] =
GLKVector3Add(
someTriangles[trianglesIndex].vertices[0].positionCoords,
GLKVector3MultiplyScalar(
someTriangles[trianglesIndex].vertices[0].normalCoords,
0.5));
someNormalLineVertices[lineVetexIndex++] =
someTriangles[trianglesIndex].vertices[1].positionCoords;
someNormalLineVertices[lineVetexIndex++] =
GLKVector3Add(
someTriangles[trianglesIndex].vertices[1].positionCoords,
GLKVector3MultiplyScalar(
someTriangles[trianglesIndex].vertices[1].normalCoords,
0.5));
someNormalLineVertices[lineVetexIndex++] =
someTriangles[trianglesIndex].vertices[2].positionCoords;
someNormalLineVertices[lineVetexIndex++] =
GLKVector3Add(
someTriangles[trianglesIndex].vertices[2].positionCoords,
GLKVector3MultiplyScalar(
someTriangles[trianglesIndex].vertices[2].normalCoords,
0.5));
}
// 添加法线顶点
someNormalLineVertices[lineVetexIndex++] = lightPosition;
someNormalLineVertices[lineVetexIndex] = GLKVector3Make(0.0, 0.0, -0.5);
}
//生成triangle
static SceneTriangle SceneTriangleMake(
const SceneVertex vertexA,
const SceneVertex vertexB,
const SceneVertex vertexC)
{
SceneTriangle result;
result.vertices[0] = vertexA;
result.vertices[1] = vertexB;
result.vertices[2] = vertexC;
return result;
}
// 生成triangle法向量
static GLKVector3 SceneTriangleFaceNormal(const SceneTriangle triangle) {
GLKVector3 vectorA = GLKVector3Subtract(
triangle.vertices[1].positionCoords,
triangle.vertices[0].positionCoords);
GLKVector3 vectorB = GLKVector3Subtract(
triangle.vertices[2].positionCoords,
triangle.vertices[0].positionCoords);
return SceneVector3UnitNormal(
vectorA,
vectorB);
}
//法向量
GLKVector3 SceneVector3UnitNormal(
const GLKVector3 vectorA,
const GLKVector3 vectorB)
{
return GLKVector3Normalize(
GLKVector3CrossProduct(vectorA, vectorB));
}
@end
网友评论