11. GLSL索引绘图 & 绘制纹理颜色混合立方体
GLSL索引绘图
#import "RenderView.h"
#import <OpenGLES/ES3/gl.h>
#import "GLESMath.h"
@interface RenderView (){
CAEAGLLayer *glLayer;
EAGLContext *context;
GLuint renderBufferID;
GLuint frameBufferID;
GLuint vertexID;
GLuint programID;
}
@end
@implementation RenderView
+ (Class)layerClass {
return [CAEAGLLayer class];
}
- (void)layoutSubviews {
[self configAttribute];
[self render];
}
/// 配置属性变量
- (void)configAttribute {
/// layer
glLayer = (CAEAGLLayer *)self.layer;
glLayer.opaque = YES;
[self setContentScaleFactor:[UIScreen mainScreen].scale];//让缩放比例同屏幕显示一致
glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @false,
kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8,
};
/// context
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
NSAssert(context, @"context 创建失败");
BOOL configContext = [EAGLContext setCurrentContext:context];
if (!configContext) {
NSLog(@"配置上下文失败");
return;
}
/// buffer
[self deleteBuffer];
glGenBuffers(1, &renderBufferID);
glBindRenderbuffer(GL_RENDERBUFFER, renderBufferID);
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:glLayer];
glGenFramebuffers(1, &frameBufferID);
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBufferID);
}
- (void)render {
glClearColor(0.3, 0.5, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
CGFloat scale = [[UIScreen mainScreen] scale];
//2.设置视口
glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
/// vertexArray x,y,z, r,g,b,
GLfloat vertexArray[] = {
-0.5, -0.5, -0.5, 1.0, 1.0, 0.0,
0.5, -0.5, -0.5, 1.0, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 1.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0, 1.0,
0.0, 0.0, 0.5, 0.0, 1.0, 1.0,
};
/// indexArray
GLuint indexArray[] = {
0, 3, 1,
3, 2, 1,
0, 4, 3,
1, 4, 0,
2, 4, 1,
3, 4, 2,
};
/// vertex buffer
if (vertexID == 0) {
glGenBuffers(1, &vertexID);
}
glBindBuffer(GL_ARRAY_BUFFER, vertexID);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexArray), vertexArray, GL_DYNAMIC_DRAW);
/// vertex shader
GLuint vShaderID = glCreateShader(GL_VERTEX_SHADER);
NSString *vShaderFilePath = [[NSBundle mainBundle] pathForResource:@"shaderV" ofType:@"glsl"];
const GLchar *vShaderSource = (GLchar *)[NSString stringWithContentsOfFile:vShaderFilePath encoding:NSUTF8StringEncoding error:nil].UTF8String;
glShaderSource(vShaderID, 1, &vShaderSource, NULL);//着色器源码附加到着色器对象上
glCompileShader(vShaderID);//编译
/// fragment shader
GLuint fShaderID = glCreateShader(GL_FRAGMENT_SHADER);
NSString *fShaderFilePath = [[NSBundle mainBundle] pathForResource:@"shaderF" ofType:@"glsl"];
const GLchar *fShaderSource = (GLchar *)[NSString stringWithContentsOfFile:fShaderFilePath encoding:NSUTF8StringEncoding error:nil].UTF8String;
glShaderSource(fShaderID, 1, &fShaderSource, NULL);
glCompileShader(fShaderID);
/// program
if (programID) {
glDeleteProgram(programID);
programID = 0;
}
programID = glCreateProgram();
glAttachShader(programID, vShaderID);// 附加着色器
glAttachShader(programID, fShaderID);
glDeleteShader(vShaderID); // 释放已经附加到program上去,不再需要的着色器
glDeleteShader(fShaderID);
glLinkProgram(programID);
GLint linkSuccess;
glGetProgramiv(programID, GL_LINK_STATUS, &linkSuccess);
if (linkSuccess == GL_FALSE) {
GLchar messages[256];
glGetProgramInfoLog(programID, sizeof(messages), 0, &messages[0]);
NSLog(@"program link error: %@", [NSString stringWithUTF8String:messages]);
return;
}
glUseProgram(programID);
/// shader attribute
GLuint position = glGetAttribLocation(programID, "position");
glEnableVertexAttribArray(position);
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (GLfloat *)NULL);
GLuint positionColor = glGetAttribLocation(programID, "positionColor");
glEnableVertexAttribArray(positionColor);
glVertexAttribPointer(positionColor, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (GLfloat *)NULL + 3);
/// shader uniform
GLuint projectionMatrixID = glGetUniformLocation(programID, "projectionMatrix");
KSMatrix4 projectionMatrix;
ksMatrixLoadIdentity(&projectionMatrix);//获取单元矩阵
float width = self.frame.size.width;
float height = self.frame.size.height;
float aspect = width / height;
ksPerspective(&projectionMatrix, 30.0, aspect, 5.0f, 20.0f); //获取透视矩阵
/*
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
参数列表:
location:指要更改的uniform变量的位置
count:更改矩阵的个数
transpose:是否要转置矩阵,并将它作为uniform变量的值。必须为GL_FALSE
value:执行count个元素的指针,用来更新指定uniform变量
*/
glUniformMatrix4fv(projectionMatrixID, 1, GL_FALSE, (GLfloat*)&projectionMatrix.m[0][0]);
GLuint modelViewMatrixID = glGetUniformLocation(programID, "modelViewMatrix");
KSMatrix4 modelViewMatrix;
ksMatrixLoadIdentity(&modelViewMatrix);
ksTranslate(&modelViewMatrix, 0.0, 0.0, -10.0);//平移
KSMatrix4 rotationMatrix;
ksMatrixLoadIdentity(&rotationMatrix);
ksRotate(&rotationMatrix, self.xDegree, 1.0, 0.0, 0.0); //绕X轴
ksRotate(&rotationMatrix, self.yDegree, 0.0, 1.0, 0.0); //绕Y轴
ksRotate(&rotationMatrix, self.zDegree, 0.0, 0.0, 1.0); //绕Z轴
ksMatrixMultiply(&modelViewMatrix, &rotationMatrix, &modelViewMatrix);
glUniformMatrix4fv(modelViewMatrixID, 1, GL_FALSE, (GLfloat *)&modelViewMatrix.m[0][0]);
/*
void glDrawElements(GLenum mode,GLsizei count,GLenum type,const GLvoid * indices);
参数列表:
mode:要呈现的画图的模型
GL_POINTS
GL_LINES
GL_LINE_LOOP
GL_LINE_STRIP
GL_TRIANGLES
GL_TRIANGLE_STRIP
GL_TRIANGLE_FAN
count:绘图个数
type:类型
GL_BYTE
GL_UNSIGNED_BYTE
GL_SHORT
GL_UNSIGNED_SHORT
GL_INT
GL_UNSIGNED_INT
indices:绘制索引数组
*/
glDrawElements(GL_TRIANGLES, sizeof(indexArray)/sizeof(indexArray[0]), GL_UNSIGNED_INT, indexArray);
[context presentRenderbuffer:GL_RENDERBUFFER];
}
- (void)deleteBuffer {
glDeleteBuffers(1, &renderBufferID);
renderBufferID = 0;
glDeleteBuffers(1, &frameBufferID);
frameBufferID = 0;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
@end
索引绘图可以简化构建图元的顶点数组的创建。通用顶点不用重复添加了。但是这样顶点颜色和顶点纹理坐标都是固定的,如果在不同图元里同一个顶点的颜色和纹理坐标不一致,那么需要在顶点数组里再添加一个同顶点不同颜色和纹理坐标的数据。
纹理颜色混合
#import "RenderView.h"
#import <OpenGLES/ES3/gl.h>
#import "GLESMath.h"
@interface RenderView ()
@property (nonatomic, strong) EAGLContext *context;
@property (nonatomic, strong) CAEAGLLayer *glLayer;
@property (nonatomic, assign) GLuint renderBuffer;
@property (nonatomic, assign) GLuint frameBuffer;
@property (nonatomic, assign) GLuint program;
@property (nonatomic, assign) GLuint vertices;
@end
@implementation RenderView
+ (Class)layerClass {
return [CAEAGLLayer class];
}
- (void)layoutSubviews {
/* Context */
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!self.context) {
NSLog(@"context create error");
return;
}
[EAGLContext setCurrentContext:self.context];
/* Layer */
self.glLayer = (CAEAGLLayer *)self.layer;
[self setContentScaleFactor:[UIScreen mainScreen].scale];
self.glLayer.opaque = YES;
self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @(NO),
kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8
};
/* render buffer & frame buffer */
[self deleteBuffer];
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, self.renderBuffer);
[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.renderBuffer);
[self render];
}
- (void)deleteBuffer {
glDeleteRenderbuffers(1, &_renderBuffer);
self.renderBuffer = 0;
glDeleteFramebuffers(1, &_frameBuffer);
self.frameBuffer = 0;
}
- (void)render {
glClearColor(0.5, 0.5, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glViewport(self.frame.origin.x*[UIScreen mainScreen].scale, self.frame.origin.y*[UIScreen mainScreen].scale, self.frame.size.width*[UIScreen mainScreen].scale, self.frame.size.height*[UIScreen mainScreen].scale);
/* vertice shader*/
GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
NSString *vFile = [[NSBundle mainBundle] pathForResource:@"shaderV" ofType:@"glsl"];
NSString *vContent = [NSString stringWithContentsOfFile:vFile encoding:NSUTF8StringEncoding error:nil];
const GLchar *vSource = (GLchar *)vContent.UTF8String;
glShaderSource(vShader, 1, &vSource, NULL);
glCompileShader(vShader);
GLint vLink;
glGetShaderiv(vShader, GL_COMPILE_STATUS, &vLink);
if (vLink == GL_FALSE) {
GLchar vMessages[256];
glGetShaderInfoLog(vShader, sizeof(vMessages), 0, &vMessages[0]);
NSString *error = [NSString stringWithUTF8String:vMessages];
NSLog(@"vertice shader Compile error: %@", error);
return;
}
/* fragment shader*/
GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
NSString *fFile = [[NSBundle mainBundle] pathForResource:@"shaderF" ofType:@"glsl"];
NSString *fContent = [NSString stringWithContentsOfFile:fFile encoding:NSUTF8StringEncoding error:nil];
const GLchar *fSource = (GLchar *)fContent.UTF8String;
glShaderSource(fShader, 1, &fSource, NULL);
glCompileShader(fShader);
GLint fLink;
glGetShaderiv(fShader, GL_COMPILE_STATUS, &fLink);
if (fLink == GL_FALSE) {
GLchar fMessages[256];
glGetShaderInfoLog(fShader, sizeof(fMessages), 0, &fMessages[0]);
NSString *error = [NSString stringWithUTF8String:fMessages];
NSLog(@"fragment shader Compile error: %@", error);
return;
}
/* program */
self.program = glCreateProgram();
glAttachShader(self.program, vShader);
glAttachShader(self.program, fShader);
glDeleteShader(vShader);
glDeleteShader(fShader);
glLinkProgram(self.program);
GLint pLink;
glGetProgramiv(self.program, GL_LINK_STATUS, &pLink);
if (pLink == GL_FALSE) {
GLchar pMessages[256];
glGetProgramInfoLog(self.program, sizeof(pMessages), 0, &pMessages[0]);
NSString *error = [NSString stringWithUTF8String:pMessages];
NSLog(@"program link error: %@", error);
return;
}
glUseProgram(self.program);
/* 顶点 x,y,z, r,g,b, s,t*/
GLfloat vertices[] = {
-0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0,
0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0,
-0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.5, -0.5, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0,
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.5, 0.5,
};
/* 索引 */
GLint index[] = {
0, 3, 2,
0, 1, 3,
0, 2, 4,
0, 4, 1,
2, 3, 4,
1, 4, 3,
};
if (self.vertices == 0) {
glGenBuffers(1, &_vertices);
}
glBindBuffer(GL_ARRAY_BUFFER, self.vertices);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);
/* texture */
CGImageRef image = [UIImage imageNamed:@"kkk.png"].CGImage;
if (!image) {
NSLog(@"纹理图片不存在");
return;
}
size_t width = CGImageGetWidth(image);
size_t height = CGImageGetHeight(image);
GLubyte *imageData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
CGContextRef imageContext = CGBitmapContextCreate(imageData, width, height, 8, width * 4, CGImageGetColorSpace(image), kCGImageAlphaPremultipliedLast);
CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(imageContext, rect, image);
CGContextRelease(imageContext);
glBindTexture(GL_TEXTURE_2D, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (float)width, (float)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
free(imageData);
/* shader data input */
GLuint position = glGetAttribLocation(self.program, "position");
glEnableVertexAttribArray(position);
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8, (GLfloat *)NULL);
GLuint positionColor = glGetAttribLocation(self.program, "positionColor");
glEnableVertexAttribArray(positionColor);
glVertexAttribPointer(positionColor, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8, (GLfloat *)NULL + 3);
GLuint textureCoor = glGetAttribLocation(self.program, "textureCoor");
glEnableVertexAttribArray(textureCoor);
glVertexAttribPointer(textureCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8, (GLfloat *)NULL + 6);
GLuint colorMap = glGetUniformLocation(self.program, "colorMap");
glUniform1i(colorMap, 0);
GLuint projectionMatrix = glGetUniformLocation(self.program, "projectionMatrix");
KSMatrix4 _projectionMatrix;
ksMatrixLoadIdentity(&_projectionMatrix);
ksPerspective(&_projectionMatrix, 30.0, self.frame.size.width/self.frame.size.height, 5.0f, 20.0f);
glUniformMatrix4fv(projectionMatrix, 1, GL_FALSE, (GLfloat*)&_projectionMatrix.m[0][0]);
GLuint modelViewMatrix = glGetUniformLocation(self.program, "modelViewMatrix");
KSMatrix4 _modelViewMatrix;
ksMatrixLoadIdentity(&_modelViewMatrix);
ksTranslate(&_modelViewMatrix, 0.0, 0.0, -7.0);
KSMatrix4 _rotationMatrix;
ksMatrixLoadIdentity(&_rotationMatrix);
ksRotate(&_rotationMatrix, self.xDegree, 1.0, 0.0, 0.0); //绕X轴
ksRotate(&_rotationMatrix, self.yDegree, 0.0, 1.0, 0.0); //绕Y轴
ksRotate(&_rotationMatrix, self.zDegree, 0.0, 0.0, 1.0); //绕Z轴
ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
glUniformMatrix4fv(modelViewMatrix, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);
/* render */
glDrawElements(GL_TRIANGLES, sizeof(index)/sizeof(index[0]), GL_UNSIGNED_INT, index);
[self.context presentRenderbuffer:GL_RENDERBUFFER];
}
@end
GLKit
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) EAGLContext *context;
@property (nonatomic, strong) GLKBaseEffect *effect;
@property(nonatomic,assign)int count;
//旋转的度数
@property(nonatomic,assign)float XDegree;
@property(nonatomic,assign)float YDegree;
@property(nonatomic,assign)float ZDegree;
//是否旋转X,Y,Z
@property(nonatomic,assign) BOOL XB;
@property(nonatomic,assign) BOOL YB;
@property(nonatomic,assign) BOOL ZB;
@end
@implementation ViewController
{
dispatch_source_t timer;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
GLKView *view = (GLKView *)self.view;
view.context = self.context;
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
[EAGLContext setCurrentContext:self.context];
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
GLfloat verticeArray[] = {
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 1.0f,
0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f,
};
GLuint indexArray[] = {
0, 3, 2,
0, 1, 3,
0, 2, 4,
0, 4, 1,
2, 3, 4,
1, 4, 3,
};
self.count = sizeof(indexArray) /sizeof(GLuint);
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(verticeArray), verticeArray, GL_STATIC_DRAW);
GLuint index;
glGenBuffers(1, &index);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexArray), indexArray, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8, NULL);
glEnableVertexAttribArray(GLKVertexAttribColor);
glVertexAttribPointer(GLKVertexAttribColor, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8, (GLfloat *)NULL + 3);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 8, (GLfloat *)NULL + 6);
NSString *file = [[NSBundle mainBundle] pathForResource:@"kkk" ofType:@"png"];
NSDictionary *options = @{GLKTextureLoaderOriginBottomLeft : @(YES),
};
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:file options:options error:nil];
self.effect = [[GLKBaseEffect alloc]init];
self.effect.texture2d0.enabled = GL_TRUE;
self.effect.texture2d0.name = textureInfo.name;
CGSize size = self.view.bounds.size;
float aspect = fabs(size.width / size.height);
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(90.0), aspect, 0.1f, 10.f);
projectionMatrix = GLKMatrix4Scale(projectionMatrix, 1.0f, 1.0f, 1.0f);
self.effect.transform.projectionMatrix = projectionMatrix;
GLKMatrix4 modelViewMatrix = GLKMatrix4Translate(GLKMatrix4Identity, 0.0f, 0.0f, -2.0f);
self.effect.transform.modelviewMatrix = modelViewMatrix;
double seconds = 0.1;
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC, 0.0);
dispatch_source_set_event_handler(timer, ^{
self.XDegree += 0.02f * self.XB;
self.YDegree += 0.02f * self.YB;
self.ZDegree += 0.02f * self.ZB ;
});
dispatch_resume(timer);
}
-(void)update
{
GLKMatrix4 modelViewMatrix = GLKMatrix4Translate(GLKMatrix4Identity, 0.0f, 0.0f, -2.0f);
modelViewMatrix = GLKMatrix4RotateX(modelViewMatrix, self.XDegree);
modelViewMatrix = GLKMatrix4RotateY(modelViewMatrix, self.YDegree);
modelViewMatrix = GLKMatrix4RotateZ(modelViewMatrix, self.ZDegree);
self.effect.transform.modelviewMatrix = modelViewMatrix;
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
[self.effect prepareToDraw];
glDrawElements(GL_TRIANGLES, self.count, GL_UNSIGNED_INT, 0);
}
- (IBAction)XClick:(id)sender {
_XB = !_XB;
}
- (IBAction)YClick:(id)sender {
_YB = !_YB;
}
- (IBAction)ZClick:(id)sender {
_ZB = !_ZB;
}
@end
代码示例见:Github