分享
这系列收集OpenGL ES的应用。
- iOS开发-OpenGLES画图应用
这篇介绍的3D魔方(原文地址),重点是魔方的旋转与点击的判断。
效果展示
概念准备
拾取
把地形的位置坐标编码到片元的颜色分量中,用户触摸时,检查特定的像素的颜色分量以确定触摸到的地形的位置。用户看不到用于拾取的渲染,因为用于拾取的像素颜色渲染缓存不会显示到屏幕上,而是渲染到一个OpenGL ES的帧缓存对象(FBO)中。
- 1、基于颜色拾取 把位置信息编码进颜色分量,使用 glReadPixels() 读取。
把渲染值从FBO读取到CPU控制的内存需要花费时间执行耗时的同步操作。 拾取在每秒中可能发生多次,会影响渲染。
- 2、几何拾取 设想一个光线从平截体近平面上一个触摸位置头投射向这个位置对应的远平面的点。被这个光线穿过的离视点最近的对象就是要拾取的对象。
不需要读取FBO的渲染值,通过触摸的视口坐标和平截体,可形成光线。
核心思路
魔方直接渲染到屏幕,拾取的时候再渲染一次到FBO,通过拾取结果决定是旋转某一列还是旋转整个魔方。
具体解析
1、坐标系与旋转
代码语言:javascript复制#define ROTATE_NONE -1
#define ROTATE_ALL 0
#define ROTATE_X_CLOCKWISE 1
#define ROTATE_X_ANTICLOCKWISE 2
#define ROTATE_Y_CLOCKWISE 3
#define ROTATE_Y_ANTICLOCKWISE 4
#define ROTATE_Z_CLOCKWISE 5
#define ROTATE_Z_ANTICLOCKWISE 6
ROTATE_NONE 为未旋转 ROTATE_ALL 为旋转整个魔方 ROTATE_X_CLOCKWISE 为绕X轴顺时针 ROTATE_X_ANTICLOCKWISE 为绕X轴逆时针 ROTATE_Y_CLOCKWISE 为绕Y轴顺时针 ROTATE_Y_ANTICLOCKWISE 为Y轴逆时针 ROTATE_Z_CLOCKWISE 为绕Z轴顺时针 ROTATE_Z_ANTICLOCKWISE 为绕Z轴逆时针
魔方的坐标系如下:
2、attribute属性、uniform变量的统一管理
YHCOpenGLProgram是对GLProgram的封装,可以设置顶点、片元着色器,设置attribute属性、uniform变量。
流程大致分三步
代码语言:javascript复制 //1、初始化,并设置属性
_program = [[YHCOpenGLProgram alloc] init];
[_program setVertexShader:@"Shader"];
[_program setFragmentShader:@"Shader"];
[_program addAttributeLocation:@"ATTRIBUTE_VERTEX" forAttribute:@"position"];
[_program compileAndLink];
attributes[ATTRIBUTE_VERTEX] = [_program getAttributeIDForIndex:@"ATTRIBUTE_VERTEX"];
//2、在链接之前 绑定 attribute (attribute location 绑定 必须在链接之前)
for (NSString *key in _attributes) {
YHCShaderAttribute *thisAttribute = [_attributes objectForKey:key];
glBindAttribLocation(_programId, thisAttribute.attributeId, [thisAttribute.attributeName UTF8String]);
}
//3、链接成功后,从 OpenGL 获取uniform locations (该操作应该在link之后进行)
for (NSString *key in _uniforms) {
YHCShaderUniform *thisUniform = [_uniforms objectForKey:key];
[thisUniform setUniformLocation:glGetUniformLocation(_programId, [thisUniform.uniformName UTF8String])];
}
3、逻辑设计
没有深入了解,参考原文,还有代码里的GameLogic
类。
4、文字显示
加载一张含有多个文字的图片,通过在上面选定区域来显示文字(无法显示中文)。
思考1:是否存在替代的做法?
5、旋转部分魔方的动画实现
不断增大_sliceRotateAngle,当_sliceRotateAngle>=90°之后,设置为_rotationState为ROTATE_NONE,并设置_currentSlice数组为-1,完成一次旋转。
代码语言:javascript复制 if(_rotationState == ROTATE_X_CLOCKWISE || _rotationState == ROTATE_Y_CLOCKWISE || _rotationState == ROTATE_Z_CLOCKWISE){
_sliceRotateAngle = rotationAngle;
}else if(_rotationState == ROTATE_X_ANTICLOCKWISE || _rotationState == ROTATE_Y_ANTICLOCKWISE || _rotationState == ROTATE_Z_ANTICLOCKWISE){
_sliceRotateAngle = -rotationAngle;
}else{
_sliceRotateAngle = 0;
}
6、旋转整个魔方
监听touchesMove:withEvent:
方法,通过locationWithUITouch:View
得出点击位置的Point,和touchesBegan
开始记录的_lastTouchPosition相比,得出绕X、Y轴旋转的角度大小,直接对整个魔方的旋转矩阵进行操作。
当初始点击处不在魔方时,旋转整个魔方。根据点击初始点的x、y移动的距离,来决定饶Y、X轴的角度,注意是相反的。
代码语言:javascript复制- (void)rotateCubeAroundX:(float)x andY:(float)y
{
GLfloat totalXRotation = x * M_PI / 180.0f;
GLfloat totalYRotation = y * M_PI / 180.0f;
if (_rotationState == ROTATE_ALL) {
[MatrixTools applyRotation:_rotationMatrix x:totalXRotation y:totalYRotation z:0.0];
}
}
7、旋转部分魔方判断
获取点击的位置,重绘魔方到FBO,获取点击位置对应的颜色,确定rotationState。
代码语言:javascript复制 if (_isSelectMode) {
glVertexAttribPointer(ATTRIBUTE_COLOR, 4, GL_UNSIGNED_BYTE, 1, 0, cubes[i]._colors);
glEnableVertexAttribArray(ATTRIBUTE_COLOR); // 如果是选择模式,用颜色
}else{
glVertexAttribPointer(ATTRIBUTE_TEXTURE_COORD, 2, GL_FLOAT, 0, 0, cubes[i]._textureCoords);
glEnableVertexAttribArray(ATTRIBUTE_TEXTURE_COORD); // 如果不上选择模式,使用纹理坐标
}
glReadPixels(point1.x,viewport[3]-point1.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixelColor);
思考2:为何不直接读取屏幕的颜色?
总结
魔方的逻辑较复杂,着重了解魔方的显示、旋转,点击的拾取与判断。
代码地址在这里。
思考
1、替代的做法:文字直接添加到UILabel,UILabel绘制成纹理,再加载到OpenGL ES。 2、如果添加的是纹理,颜色变量无法携带位置信息。