介绍

AsyncDisplayKit的核心概念就是node。ASDisplayNode 是一个基于UIView或CALayer的抽象概念,通过看对应的头文件也能基本看出来,它把View与layer相关属性都整合到一起了。

  • layerBacked可以设置成直接用layer构成node,默认是一个_ASDisplayView的一个内部类对应Node

  • 拥有自己的layout引擎,类似CSS,不支持Auto Layout

ASDisplayNode

  • init

    • 任何线程调用
    • touch事件或者手势绑定不要在这里执行,放到didLoad里面
  • didLoad

    • 主线程调用
    • 可以进行任何UI相关操作和初始化
  • layoutSpecThatFits

    • 任何线程调用
    • 计算布局的主要方法
    • 不可进行任何UI相关操作和其他Node的创建,也不需要进行Super调用
    • 另外官方建议不要Cache layout,尽量每次都重算。
  • layout

    • 主线程调用
    • 比较类似viewWillLayoutSubviews,需要调用super方法,以致进行自身和subnodes的布局运算
    • 适合进行hidden或者背景色等UI基本信息设置,但不包含布局设置
    • 如果要设置UIView相关对象,可以在layout中设置frame,尽量使用initWithViewBlock方法,放到后台进程
    • 官方举了个使用例子,colletionNode设置全屏适合在layout方法中调用

        subnode.frame = self.bounds;
      

ASViewController

UIViewController的子类,都是主线程操作

  • init

    • 不要访问self.view或者self.node.view,这样会强迫视图被过早初始化,以免引发问题。
    • 自带initWithNode方法初始化,其自身管理node的方法和view类似
  • loadView

    • 官方建议不使用此方法,因为实在没用。
    • 在这里不要自行改变self.view的对应视图对象,因为调用super方法会设置到node.view上
  • viewDidLoad

* 不要写任何布局逻辑
  • viewWillLayoutSubviews

    • 与node的layout方法一起调用
    • 会在node的bounds行为(包括旋转、分屏、键盘弹出)改变时调用,也包括子node被改变时
    • 官方建议把布局逻辑写在这里(不强依赖于size的代码)
  • viewWillAppear / viewDidDisappear

    • 适合统计用户行为log
    • 适合进行Controller的动画操作

用法

  • Layout Specs

  • Layout Element Properties

    • ASStackLayoutElement Properties
    • ASAbsoluteLayoutElement Properties
    • ASLayoutElement Properties
  • 支持多种赋值


// dimension returned is relative (%)
ASDimensionMake(@"50%");  
ASDimensionMakeWithFraction(0.5);

// dimension returned in points
ASDimensionMake(@"70pt")
ASDimensionMake(70);      
ASDimensionMakeWithPoints(70);

  • Batch Fetching API

    官方提供方法用做预加载,实现无限滚动功能

--tableNode:willBeginBatchFetchWithContext:
--collectionNode:willBeginBatchFetchWithContext:
  • ASDisplayNode是一切Node的始祖类。

  • 更新布局,记得调用setNeedsLayout

  • automaticallyManagesSubnodes方法可以自动管理子node,不用再手动addSubNode

  • inverted可以翻转整个tableNode、CollectionNode的NSIndexPath,最下方的cellNode变成NSIndexPath(0, 0), 比如聊天类的app会方便很多

  • imageModificationBlock是ImageNode的block,可以做一个后期处理,比如圆角裁剪等

  • shouldRasterizeDescendants 子树光栅化,意味着类似VVebo的效果,一个点内的所有node层级都会绘制到一个layer上

  • neverShowPlaceholders同步绘制cellNode,主线程会锁死,知道界面完成绘制

  • 自定义ASViewController或者ASDisplayNode的子类时要细心,因为它们的区别很微妙。

  • ASImageNode、ASButtonNode、ASTextNode 同为 ASControlNode 子类,可以直接使用 addTarget(self, action: "handleXXX", forControlEvents: .TouchUpInside) 为它们添加点击响应事件,而避免使用addGesture等方法。

  • ASImageNode 是 UIImageView 的极佳替代品,ASImageNode 会在后台线程渲染好 UIImage ,然后再在主线程中呈现图像。 因此,ASImageNode可以极大地提升FPS数值。

          如果是本地图像,你可以直接将一个 UIImage 实例赋值到 ASImageNode.image 中。
    		
          如果是网络图像,你应该使用 ASNetworkImageNode, 同时你需要为 ASNetworkImageNode 指定一个 ImageManager 用于管理网络请求、图像缓存等操作。
    
  • hitTestSlop属性可以扩大点击响应区域

  • enableHitTestDebug

    ADK提供很多debug功能,这个是测试相应区域的,可响应区域会变成绿色

FAQ

  • 常见错误

    • 禁止在-init:方法访问node.view
    • 确保在node block外部访问data source
    • viewBlocks避免循环引用
  • 常见概念误区

    • ASCellNode不会复用
    • LayoutSpecs会在每次layout被调用时执行
    • AsyncDisplayKit有自己的强大布局API,用法上和系统的API又很大区别
  • 常见性能问题场景
    • 避免使用cornerRadius属性(以及shadowPathborder, mask
    • ASDK不支持Auto Layout
    • ASDisplayNode的指针会保持alive(据说系统中当layer被添加到UIView的super layer上后,也会一直保留,即使layer的delegate被置为weak)
  • Corner Rounding

    官网有一篇圆角的分析和优化文章,具体可以看文档

依赖

  • JavascriptCore.framework

  • <objc/runtime.h>

原理

利用OC运行时特性,动态对指定类的方法进行增添或替换,以及注册新类等。

  • 消息传递

通过JSContext以Block形式进行方法定义,然后在OC下执行,将返回值传给JS。参数类型都会通过JavascriptCore进行自动转换。

关键词

  • require

    在JS全局作用域上创建一个同名变量,变量指向一个对象,对象属性 __clsName 保存类名,同时表明这个对象是一个 OC Class。


var _require = function(clsName) {
  if (!global[clsName]) {
    global[clsName] = {
      __clsName: clsName
    }
  }
  return global[clsName]
}

  • defineClass

    通过在JPEngine中向脚本全局注入_OC_defineClass方法,然后在JSPatch.js中调用defineClass方法,实质即调用OC的_OC_defineClass方法。


context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
        return defineClass(classDeclaration, instanceMethods, classMethods);
    };

然后在js中的defineClass实现如下。
参数列表:
1. 类名字符串
2. 属性列表,已经被转换为JS的数据类型
3. 类的实例方法和类方法列表 ```js

global.defineClass = function(declaration, properties, instMethods, clsMethods) { var newInstMethods = {}, newClsMethods = {} if (!(properties instanceof Array)) { clsMethods = instMethods instMethods = properties properties = null }

var realClsName = declaration.split(':')[0].trim()

_formatDefineMethods(instMethods, newInstMethods, realClsName)
_formatDefineMethods(clsMethods, newClsMethods, realClsName)

var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
var className = ret['cls']
var superCls = ret['superCls']

_ocCls[className] = {
  instMethods: {},
  clsMethods: {},
  props: {}
}

if (superCls.length && _ocCls[superCls]) {
  for (var funcName in _ocCls[superCls]['instMethods']) {
    _ocCls[className]['instMethods'][funcName] = _ocCls[superCls]['instMethods'][funcName]
  }
  for (var funcName in _ocCls[superCls]['clsMethods']) {
    _ocCls[className]['clsMethods'][funcName] = _ocCls[superCls]['clsMethods'][funcName]
  }
  if (_ocCls[superCls]['props']) {
    _ocCls[className]['props'] = JSON.parse(JSON.stringify(_ocCls[superCls]['props']));
  }
}

_setupJSMethod(className, instMethods, 1, realClsName)
_setupJSMethod(className, clsMethods, 0, realClsName)

if (properties) {
  properties.forEach(function(o){
    _ocCls[className]['props'][o] = 1
    _ocCls[className]['props']['set' + o.substr(0,1).toUpperCase() + o.substr(1)] = 1
  })
}
return require(className)   }
* **_formatDefineMethods**


	规范化JS方法
```js
 var _formatDefineMethods = function(methods, newMethods, realClsName) {
    for (var methodName in methods) {
      if (!(methods[methodName] instanceof Function)) return;
      (function(){
        var originMethod = methods[methodName]
        newMethods[methodName] = [originMethod.length, function() {
          try {
            var args = _formatOCToJS(Array.prototype.slice.call(arguments))
            var lastSelf = global.self
            global.self = args[0]
            if (global.self) global.self.__realClsName = realClsName
            args.splice(0,1)
            var ret = originMethod.apply(originMethod, args)
            global.self = lastSelf
            return ret
          } catch(e) {
            _OC_catch(e.message, e.stack)
          }
        }]
      })()
    }
  }

将传进来的JS对象进行修改

原来的形式是:
    {
        handleBtn:function(){...}
    }
修改之后是:
    {
        handleBtn: [argCount,function (){...新的实现}]
    }
因为无法直接解析原始的js实现函数,那么就不知道参数的个数,特别是在创建新的方法的时候,需要根据参数个数生成方法签名,所以只能在js端拿到js函数的参数个数,传递到OC端
  • _c()函数

    实际并不存在,为自定义,用来将不存在的方法转发到特定函数中。


UIView.__c('alloc')().__c('init')()
UIView.alloc().init()


Object.defineProperty(Object.prototype, '__c', {value: function(methodName) {
  if (!this.__obj && !this.__clsName) return this[methodName].bind(this);
  var self = this
  return function(){
    var args = Array.prototype.slice.call(arguments)
    return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
  }
}})


使用


[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];

  • 所有基本数据类型无需转换成NSNumber/NSValue, 会自动转换

  • CGRect要按照规定格式书写{x:1, y:2, width:4.2, height:1}

  • 类别方法无法调用

  • weakSelf无法使用typedef, 直接定义新变量即可

  • 不要使用GCD

  • 不支持枚举变量,使用Int

  • 方法内不要出现_,因为方法会被转化成xxx_xxx_xxx

  • NSLog无效,使用MBProgressHUD弹出信息进行调试

var weakSelf = self;

扩展

  • Struct

  • Extension

安全性

  • 完整文件检验值,服务端与本地加密结果对比

  • 使用URL,非JS明文传递,并且使用https,避免中间人攻击

  • 防止本地沙盒文件篡改

  • JSLoader

    官方提供的下发Class

Convertor

  • 项目主页

  • 试用页面

    • 不支持的内容:
      1. Macro / constant variable / Enum
      2. C function calling
      3. GCD functions
      4. Pointer / Struct
      5. Getting / Setting private variable

Plugin

JSPatchX

UITableView提供了一个自带技能,若设置headerView,会在当前Section存在子Cell出现在屏幕上方时,停留在屏幕顶端以提示Section信息,类似黏性的效果。

但有时在应用设计时不需要如上的特性,而又不想使用UICollectionView来模拟tableView的行为。因此,我们需要可使headerView不再停留的解决方案。

方案一

继承scrollViewDidScroll,通过计算屏幕竖直方向的contentOffset来控制headerView的移动,最终可以达到预期目标。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {  
    CGFloat sectionHeaderHeight = 40;  
    if (scrollView.contentOffset.y<=sectionHeaderHeight&&scrollView.contentOffset.y>=0) {  
        scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);  
    } else if (scrollView.contentOffset.y>=sectionHeaderHeight) {  
        scrollView.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight, 0, 0, 0);  
    }  
}  

但这种方式存在比较大的缺陷,在section数量为1时,方案一可以较好的模拟headerView的移动,但在section数量更多时,会使情况变的更加复杂,方案一并不能很好的达成目标。

方案二

经过测试发现,headView只会在当前section仍然有cell处于屏幕上方时才会发生停留。 所以,可以利用这一点,在原始布局中插入空内容的section,可以巧妙的避免headView的停留。

当然,footerView也同样可以通过上述方式达到禁止停留的效果,

原文链接:http://blog.db-in.com/all-about-opengl-es-2-x-part-1/

大家好!

欢迎再次来到新一期的系列教程。这次让我们进入充满魔力的3D世界。下面就来谈谈OpenGL。最近五个月的时间,我全身心的投入到3D世界中,即将完成我的新3D引擎(我觉得这是我做过的最棒的事情了)。现在是时候把我所了解的内容分享给大家了,其中包含所有的引用来源,所有的书籍、教程等等,当然,我会从大家的回复中学到更多。

本系列分为3大部分:

所有,如果你对于如何书写代码更感兴趣的话,可以直接跳到第二部分,因为这部分我将只涉及基本概念,不会有其他的内容。

好的,下面就让我们进入正题。

引言

是否有人没有听说过OpenGL?OpenGL全称为Open Grahics Library(开源图形库),在今天的计算机语言中被广泛使用。OpenGL作用在CPU(开发者运行应用程序的场所)与GPU(每个显卡都存在的图形处理器)之间。所以OpenGL需要被显卡(比如NVidia)和操作系统供应商(比如苹果的MacOS和iOS)所支持,而最终,OpenGL给予开发者一个统一的API标准。这样的API是跨平台的,即“Language Free”,不受编程语言的限制。这是非常令人兴奋的事情,因为无论你使用C、C++、Objective-C、Perl、C#或者JavaScript,API总是等价的,并且呈现相同的行为,拥有相同的函数,相同的命令,而本系列教程也会说明这一点!开发者可以尽情使用OpenGL的API。

在开始谈论OpenGL的API之前,我们需要了解3D世界。计算机语言的3D历史包含了OpenGL的历史,所以,就让我们简单看看这段历史。

一个简短的故事

大约在20年前,硅谷图像公司(SGI)研发了一种技术,能够呈现现实世界的视觉体验。在当时到处都是2D图像的世界中,它竟然可以通过模拟人眼的透视和深度来显示3D图像。这种技术被称为IrisGL(可能因为它在试图模仿人眼的虹膜)。

严格来讲,这种技术是第一个优秀图像库。但它很快就被淘汰了,因为需要开发者同时控制计算机的多种器件,如显卡、窗口系统、Basic语言,甚至是前端。这都让仅仅一个公司来管理显然是不可能的。所以SGI开始代为处理一些事情,比如“创建显卡”、“管理窗口”、“生成前端”,而其他公司专注于图形库更重要的部分。在1992年启动了第一版OpenGL的开发。

在1995年微软发布Direct3D,成为了OpenGL的主要竞争者。而仅仅在1997年,OpenGL就发布了1.1版本。但是我真正开始关注OpenGL是在2004年,此时OpenGL发布了2.0并做了大幅度的改进。Shaders,可编程管线,我真是爱死它了!

最终在2007年我们见到了OpenGL ES 2.0,将Shader和可编程管线技术带到了嵌入式系统中。

今天我们能在很多游戏、3D、2D应用和很多图形软件中看到OpenGL(或者OpenGL ES)的商标。OpenGL ES现在被PlayStation,Android,任天堂的3DS,诺基亚,三星,塞班,当然还有苹果的MacOS和iOS所使用。

OpenGL最大的挑战

下面我们谈谈微软的Windows系统(真是不爽!!)

你还记得我说过OpenGL在1992第一次启动研发吗?在那个时候,微软拥有了他们闪亮的Windows3.1。好吧,就像微软所坚信的那样“好东西不是被创造的,而且被复制来的”,微软开始在他们所谓的DirectX技术中尝试抄袭OpenGL,并在1995年Windows95发布时首次介绍DirectX。

一年后的1996年,微软介绍了Direct3D,一个OpenGL的完整复制版。而微软支配了主要市场长达数年,DirectX(或者叫Direct3D)像瘟疫一样在很多电脑上传播渗透,当微软开始将他们的系统应用于手机和视频游戏上,DirectX也跟着一起被使用。

现在的DirectX在结构上与OpenGL非常相似:比如Shader Language,拥有可编程管线,也有固定管线,甚至在API的命名上都很相似。不同之处在于OpenGL是开源的,而DirectX是闭源的。OpenGL使用于iOS,MacOS以及Linux系统上,而DirectX是应用于微软自己的系统上。

好的,现在让我们来开始真正的3D旅程!

3D world

眼睛

从我记事开始,我就对3D世界和3D游戏充满了兴趣。 我们人类都很清楚能在3D视觉中模拟真实世界的仅仅来自于一个地方:我们的眼睛。

眼睛是3D世界一切事物的基础。我们所做的一切努力都是在模拟人眼神奇而美丽的能力。我不是医生,也不想在教程中过多讨论眼睛的事情,但是如果你了解一切基本概念:如视野,双眼和单眼视觉,眼睛的透镜,凹凸镜,和很多这类的东西,这对于了解后面的概念很有帮助。

我们在3D世界中所做的就是通过人眼重建视觉:透视,盲点,扭曲,景深,焦点,视野,所有内容都是在模拟相应的视觉。

三维

也许这看上去很蠢,但是仍然有必要谈论一下,3D世界之所以是3D的,因为有3个维度。“WTF,这很明显啊!”,冷静下来,我之所以说这些有着很重要的原因,比起2D世界多出的那一维会给我们带来非常多的麻烦。

在2D世界中当我们需要旋转一个正方形非常简单,45º总可以让正方形做出一个特定的旋转,但是在3D世界,旋转一个简单的正方形需要X,Y,Z三种旋转。根据我们首先旋转的维度,最终的结果会完全不同。当我们做连续旋转时会变得更糟糕,举个例子,旋转x=25、y=20是一个结果,但是旋转x=10、y=20然后再旋转x=10是完全不同的新结果。

好吧,这里要说明的重点是增加的那一维度使我们要做的工作非常恼人的翻了几番。

不是3D…通常是4D

WTF!又一个维度?

是的,这是我要说的最后一点。我们通常不仅仅只在3D世界中操作,我们拥有第四个维度:时间。3D世界的物体是相互作用的,需要移动、加速、碰撞、改变惯性。就像我之前说过的,在3D世界中做连续改变会产生成倍的结果。

到现在为止,我们可以为3D虚拟世界做一个定义:“对人眼的模拟并且所有的物体都在移动”。

3D世界中的OpenGL

现在本教程开始变得有趣了。让我们开始讨论伟大的引擎OpenGL。首先我们需要感谢伟大的数学家,如Leonhard Euler,William Rowan,Hamilton,Pythagoras和很多人。感谢他们,在3D空间我们有了如此多的公式与技术。OpenGL运用了所有这些知识并在我们面前构架出了一个3D世界。每秒钟会执行上千甚至百万次操作才能模拟人眼中看到的美丽世界。

OpenGL是一个庞大的状态机(这意味着,整个OpenGL都是在状态模式下运行的)。为了解释清OpenGL是什么,让我们想象一台港口里有很多的集装箱,每一个里面都装满了木箱。而OpenGL就像整个港口:

  • 所有的集装箱就是OpenGL的操作对象(材质,着色器,网格和很多类似的属性)
  • 集装箱里面的木箱就是我们用OpenGL在应用中创建的实例。
  • 起重机对应的就是我们可以访问的OpenGL的API。

所以当我们执行一个OpenGL函数时,就好比给了起重机一条命令。起重机将港口中的集装箱提起并保持一段时间,在集装箱内部进行处理,最终将集装箱放回原先的位置。你不能直接进入港口,也无法看到或者改变集装箱内部的事物, 当然也无法重组它,总之你无法直接对集装箱做任何事。你所能做的就是给予起重机指令。记住这点!到现在为止要开始的是OpenGL最重要的部分。起重机是港口唯一可以管理集装箱的器材。

好吧,从这种方式上看OpenGL会觉得API是受限的,但实际并非如此。OpenGL的起重机非常强大,它能够每秒重复提起、放下集装箱几千甚至几倍玩次。而OpenGL的另一个优势是,使用状态机模式意味着我们不需要保持任何实例,也不需要直接创建任何对象,我们仅仅需要持有id或者插入的语句,以及集装箱的唯一句柄。

OpenGL是如何工作的

深入OpenGL的核心就会发现所有计算都直接的使用硬件加速,由GPU进行浮点运算。CPU是电脑或电子装置的处理器。GPU是电脑或电子装置的图像显示卡。之所以显卡可以有效缓解处理器的压力,是因为在将内容显示于屏幕前显卡会对图像进行大量的计算。所以深入来讲,就是由GPU来代替CPU进行大量的计算。 GPU比CPU在处理浮点运算时要快得多。这就是一款游戏在一台拥有强大显卡的电脑上可以运行更快的真正原因,也是专业3D软件提供“软件渲染”(CPU处理)和“图形卡渲染”(GPU处理)选项的原因。还有另外一些软件也能为你提供“OpenGL”的选项,现在你该知道它的含义吧,就是用GPU进行处理!所以,有人会猜测OpenGL直接工作在GPU吗?其实并不是这样,GPU所做的只是一些图像处理和其余一些事情。OpenGL给予我们很多方便的接口,可以用一种优化的格式来储存图像、数据或者信息。这些优化过的数据将会直接由GPU进行处理。所以,又会有人猜测OpenGL是依赖于硬件吗? 很不幸,是这样的!如果硬件(显卡)不支持OpenGL,我们就无法使用。新的OpenGL版本经常需要新的GPU特性支持。以上这些是我们应该知道的,但是没有必要为此担心。因为OpenGL需要供应商来集成,我们开发者只需要等到设备准备就绪就可以在新的OpenGL版本下工作了。实际上,今天的所有显卡芯片都集成了OpenGL。所以,你们可以在各种设备和各种编程语言下使用OpenGL,甚至是微软的Windows系统下。

OpenGL的逻辑实现

OpenGL图形库是非常简洁和专注的。你在专业3D软件中看到的是使用OpenGL进行的非常复杂的操作。因为在底层,OpenGL的内部逻辑包含如下的内容:

  • 基本体
  • 缓冲区
  • 光栅化

只有这三样吗?请相信,OpenGL是围绕这三种概念来运转的。下面就让我们分别认识下每一种概念和它们在高级3D图形库中是如何参与运作的。(你也可以使用OpenGL进行2D图像处理,只是工作在Z轴为0的3D图像处理的特例而已,有关这一点我们以后再说。)

基本体

OpenGL的基本体只有三种:

  • 3D空间内的点(x,y,z)
  • 3D空间内的直线(有两个点组成)
  • 3D空间内的三角形(有三个点组成)

3D空间内的点能被用来做空间粒子。 3D空间内的直线通常都是一条单独的线,并且被用来做为3D空间向量。 3D空间内的三角形能够做为网格成千上万个的表面。 有一些OpenGL版本也支持四边形,仅仅是三角形的旁支。 但是OpenGL ES需要实现最高的性能,因此四边形是不支持的。

缓冲区

现在让我们谈谈缓冲区。简单来说,缓冲区就是一个优化过的临时存储器。存什么?存很多的东西。 OpenGL主要包含三种类型:

  • 帧缓冲区
  • 渲染缓冲区
  • 对象缓存

帧缓冲区是这三类中最抽象的。但你使用OpenGL进行渲染,你会把最终的图像直接传给屏幕或者帧缓冲区。所以,帧缓冲区就是临时的图像数据,是这样吗?并不准确。你可以将OpenGL渲染的输出结果图像化,这就意味着是一组图像,而不是一个。但究竟是哪类图像呢?这种图像包含了3D物体,物体在空间中深度、相交,和物体可见的部分。所以帧缓冲区就好比是图像的收集器。所有这些像素的信息都以二进制数组的形式进行存储。

渲染缓冲区是一个单独图像的临时存储器。现在你能清楚的明白实际上帧缓冲区是渲染缓冲区的集合。渲染缓冲区存在几种类型:颜色、深度和模板。

颜色渲染缓冲区存储的是由OpenGL最终生成的彩色图像。颜色渲染缓冲区实际上是一个彩色图像(包含RGB)。深度渲染缓冲区存储的是物体的Z轴深度信息。如果你熟悉3D软件,你就会知道Z轴深度图像。

它是3D空间里对象在Z轴位置的一个灰度图像,近处可见对象为纯白色,而大部分远端物体呈现纯黑色(纯黑色是不可见的)。

模板渲染缓冲注意着对象的可见部分。它就像一个覆盖在可见部分上的遮罩,其本身是一个黑白色图像。 缓冲区对象是一个存储器,在OpenGL中被称为“服务器端”(或者服务器的地址空间),它也是一个临时存储器,但是其临时性并非像其他对象一样。一个缓冲区对象可以在应用执行中持续存在。它能够持有你的3D物体的格式优化过的相关信息。这些信息存在两种形式:结构和索引。

结构就是描述你的3D物体的数组,比如顶点数组,纹理坐标数组或者任何你想要的数组。而索引更加具体,索引数组通常被用来标识你的网格面是如何被一个结构数组所构建。

是不是感觉很困惑?

好吧,就让我们来看个例子。

想象一个3D立方体。这个立方体由8个顶点构建出六个面,对吧?

六个面都是四边形,但是你是否还记得OpenGL的基本体是三角形?所以我们需要利用OpenGL将三角形转化成四边形。当我们这样处理的时候,实际上六个面变成了12个面。上面的这张图是用Modo制作的,注意看右下角,是Modo给出的关于网格的信息。就像我们可以直观看到的,一共8个顶点和12个面(GL:12)。

现在让我们思考一下。

OpenGL的三角形是由三个顶点组成。所以在构建立方体的正面时,我们需要创建{vertex 1, vertex2, vertex 3}, {vertex 1, vertex 3, vertex 4},对吧?

换句话说,我们需要在每个立方体的面上重复两条向量。更糟糕的是,如果我们的网格是五角形,则我们会有四条向量重复,同样的,六角形的话,就会重复六条向量等等。

这样结果会急剧扩张。

所以OpenGL给了我们一种更简便的处理方式,被称为索引数组。在上面的立方体例子中,我们最后能得到8个顶点的数组:{vertex 1, vertex 2, vertex 3, vertex 4, …}。为了避免重写这些信息,我们创建一个索引数组:{0,1,2,0,2,3,2,6,3,2,5,6…}。数组中每3个元素的组合(0,1,2 - 0,2,3 - 6,3,2)代表了一个三角形面。利用这种特性,我们可以一次写入顶点信息,并多次复用索引数组中的信息。

现在,我们回到缓冲区对象的内容,结构数组中的第一种类型是顶点,类似{vertex 1, vertex 2, vertex 3, vertex 4, …},第二种是索引数组,类似{0,1,2,0,2,3,2,6,3,2,5,6…}。

缓冲区对象最大的优势在于它们自身已被优化,并且会直接送入GPU进行处理,而我们不需要在应用中创建缓冲区对象后继续持有该数组。

光栅化

光栅化是OpenGL利用所有关于3D物体(坐标,顶点等等)的信息来创建2D图像,并经过多次改变并最终显示到屏幕上(通常情况下)。

但是最后一步,搭建像素信息和显示设备屏幕的桥梁,是供应商的职责。Khronos组织提供另一种API,被称为EGL,但这需要供应商来集成。而我们开发者不会直接操作Khronos EGL,但会操作供应商的修改版本。

所以,当你使用OpenGL渲染时,你可以选择直接使用供应商的EGL实现来渲染到屏幕上,或者渲染到一个帧缓冲区上。当渲染到帧缓冲区,你也同样在使用OpenGL API,但是渲染的内容并不会立刻显示到设备屏幕上。当渲染到屏幕上,你将不再使用OpenGL API,而转而使用EGL API。所以在渲染的时候,你可以选择这两种输出的任意一种。

但是,现在不需要担心这些,就像我说过的,每个渲染器都集成了各自的EGL API。例如,苹果公司并不会让你的渲染器直接渲染到设备屏幕上,你总需要渲染到一个帧缓冲区,然后再使用苹果集成的EGL实现来向屏幕呈现内容。

OpenGL的管线

我之前提到过“可编程管线”与“固定管线”。但是简单来说,可编程管线到底是什么东西?

可编程管线是图形库委托给我们开发者的,可以用来处理摄像,灯光,材料和特效相关的内容。我们能做的都要和这著名的着色器联系在一起。所以每次当你听到“可编程管线”的时候,你就要联想到着色器!

但是,什么优势着色器呢?

着色器就像一段代码片段,只是一段程序,直接在GPU运行来处理一些复杂运算。比如:使用聚光透镜T在位置P使用相机C看到的,在拥有光功率SL及Z轴入射角LA和衰减F的光照L下,一个纹理T的表面点的最终颜色,被隆起的纹理TB所修改,并使用了高光级别SL的单个高光颜色SC。

无论这意味着什么,如果让CPU来进行处理会相当复杂。所以可编程管线能够代替我们管理这类事情。

而固定管线又是什么呢?

它与可编程管线完全相反!使用固定管线,即图形库要关心所有要进行处理,并且给予我们处理摄像,灯光,材料和特效相关的API。

为了创建着色器,我们使用一种类似C的编程语言,名叫OpenGL Shader Language(GLSL)。OpenGL ES使用了更加严格的版本,被称为OpenGL ES Shader Language(也被称为GLSL ES或者ESSL)。两者的不同之处在于,你在GLSL中可以使用更多的固定函数,并且可以创建更多的变量,但是两者的语法是相同的。

但是这些着色器是如何工作的呢?

你可以在单独的滤镜或者直接在代码中创建它们,但无论怎样,最重要的事情是最终创建的包含SL的字符串会发送给OpenGL的核心,并由核心来编译再返回给你(你甚至能用预编译的二进制着色器,但这是本系列另一部分的内容)。

着色器是成对运行的:包括顶点寄存器和片段着色器。这一点需要大家多多关注,所以让我们进一步地来了解顶点和片段着色器,并理解每个着色器的工作原理。现在让我们回到刚才的立方体实例。

顶点着色器

顶点着色器,也被称为VS或者VSH,是一段执行在网格顶点的程序。回顾之前看的立方体,就像我所说的,一共需要8个顶点(现在图片中有5个顶点是可见的,很快你就会了解为什么会变少)。所以现在立方体的VSH将由GPU处理这8个顶点。

顶点着色器所做的事情就是确定顶点的最终位置。你是否还记得可编程管线可以让开发者来负责摄像?现在正是时候!

位置和照相机的镜头会影像顶点的最终位置。顶点着色器也负责处理些准备工作,并向片段着色器输出一些变量。在OpenGL中我们可以在顶底着色器中定义变量,但不能直接在片段着色器进行变量的定义,因为我们的片段着色器必须穿过顶点着色器。

但是为什么我们不能直接访问片段着色器? 让我们来看看FSH,相信你马上就会明白。

片段着色器

再次观察立方体图像。

你是否意识到只有5个顶点可见?这是因为这种特定的位置和特定的旋转角度下,我们刚刚能看到由7个顶点组成的3个面。

这就是片段着色器要做的事情!FSH会对最终图像的每个可见片段进行处理。这里你可以将每个片段理解为一个像素。但是一般情况下这种比喻并不准确,因为在OpenGL渲染和呈现最终显示在屏幕上的图像时会有拉伸。所以一个片段会小于或大于一个像素,这取决于设备和渲染器的设置。在上面的立方体中,片段着色器将处理由7个顶点构成的3个面中的每个像素。

在片段着色器内部,我们将处理网格表面相关的任何属性,比如材质,碰撞,阴影和光照,反射,折射,纹理和其他任何我们想要的效果。最终片段着色器输出的是一个像素颜色的格式化RGBA。

现在,你需要了解的最后一件事就是VSH和FSH是怎么协同工作的。一个顶点着色器对应只一个片段着色器,这是强制性规定。为了确保我们不会犯错,OpenGL拥有程序的属性。在OpenGL中的一段程序就是一对VSH和FSH编译后的结果。

总结

非常好!

以上这些就是关于OpenGL的基本概念。希望大家可以牢记。

  1. OpenGL的逻辑由3个简单的概念组成:基本体,缓冲区和光栅化。
    • 基本体包括点、线和三角形。
    • 缓冲区可以是帧缓冲区,渲染缓冲区或者对象缓存。
    • 光栅化是在像素数据上进行OpenGL数学转换的处理过程。
  2. OpenGL是在固定或是可编程管线中运行。
    • 固定管线很老旧,而且庞大而缓慢。有很多固定的函数来处理摄像,灯光,材料和特效。
    • 可编程管线的使用更加容易,而且比固定管线更加快速和简洁,因为OpenGL可以让开发者使用编程的方式来处理摄像,灯光,材料和特效。
  3. 可编程管线是着色器的同义词:顶点着色器存在于每个网格的顶点,片段着色器存在于每个网格可见的片段。每一对顶点着色器和片段着色器都会在程序内部进行编译。

只看这3点,看上去OpenGL很容易理解和学习。是的!它确实很容易理解…但是学起来…额… 这些内容会衍生出更多的知识,了解所有的东西可能要花费数月之久。

我在本系列后面的两个部分,会把我这六个月以来深入学习OpenGL所了解到的内容展示给大家。下一章节,我会给大家展示一些用OpenGL编写的应用内部的一些基本函数和结构,与你所使用的编程语言和设备无关。

但是再次之前,我想向大家再介绍一点OpenGL相关的概念。

OpenGL异常API

OpenGL是一个庞大的状态机就像港口的起重机一样工作,而且你无法了解内部真正发生了什么。所以如果内部发生了错误,你的应用不会发生任何事,因为OpenGL是完全的外部核心。

但是,如何才能知道你的着色器出了问题?如何才能了解渲染缓冲区有没有配置正确?

为了处理这类错误,OpenGL提供了异常API。此API非常简单,拥有固定的组合函数。一个是简单的检测,返回YES或者NO,仅仅知道命令是否正确执行。另外一个用来检测异常信息。所以非常简单。首先你应进行检测,过程发生的非常迅速,如果有错误发生你会很快收到信息。

通常我们会在一些关键部位进行检测,比如着色器的编译以及缓冲区的配置,并会留意一些常见的错误。

后续

好了,现在大家应该准备好进行实际操作了。 在下一章节里,我会展示一些代码范例,大家做好准备开始编写自己的程序。

感谢大家的阅读,我们下次再会!

下一部分深入了解OpenGL ES 2.0(进阶)

本文由博主Bayonetta独立完成译制,请尊重他人劳动成果,转载请注明译者信息和本文网址。

几个月前因为工作的关系,将主力开发方向由Android转到iOS,因为体会过Eclipse和0.x时代的Android Studio是如何的笨拙不稳定,如何的臃肿不便,以致初次打开Xcode,竟似牧羊少年初识大都会,随之泛起的是浓浓的幸福和新鲜感。足够优雅的ui,华丽的交互,与触摸板深度结合的便利操作,这一切的一切都直令眼角湿润。

感慨了不少,不过是想说明Xcode在Mac下的体验称的上是出类拔萃,能够将它作为最常使用的生产平台可以说是幸运的。但是原生的Xcode尚有很多不如人意的地方,所以将它打造的更加完美便是第一步。

Theme

github上有非常多的可选主题,xcode-themes是其中比较全面的主题集合。

我使用的是Solarized,一款多平台下的通用主题,它提供Dark和Light两种不同样式。

第一次见到,就已经无可救药的爱上她了

Font

系统字体Monaco,我的唯一选择。之前在Linux/Windows下一直使用YaHei Consolas Hybrid,Consolas + 微软雅黑的搭配曾经一度征服我的感官,但是Mac下的显示效果并不尽如人意。

顺滑的圆角,已然怦然心动

Snippets

Xcode从Version4开始引入Code Snippets,在整个界面的右下角,可以通过快捷键:cmd + ctrl + opt + 2 调出来。Code Snippets是一些代码的模版,对于一些常见的编程模式,Xcode都将这些代码抽象成模版放到Code Snippet中,使用的时候,只需要键入快捷键,就可以把模版的内容填到代码中。

输入<#xxx#>可以使圈中区域’xxx’变为待填充状态,在Snippets的填写中十分有用,这种用法在Xcode中到处可见。

推荐mattt大神的Xcode-Snippets项目, 包含了常用开发中绝大部分代码片段。

这比用Github的Gist或者Dash自带的代码片段收集工具要方便很大,因为其与Xcode的深度绑定,可以设置快捷键进行快速补全,无比畅快。

Plugins

  • 猫神onevcat的VVDocumenter-Xcode,star数直逼2000+,可以自动生成方法头部文档说明的格式范本,触发方式为///

  • ColorSense-for-Xcode,将颜色配置可视化

  • HOStringSense-for-Xcode,常规模式下辅助显示字符串长度,并可以自动对特殊字符进行自动转义

  • SCXodeMiniMap,可以加入类似SublimeText的minimap,非常Cool

  • Lin-Xcode5,可以直接索引和编辑本地化操作

  • KSImageNamed-Xcode,可以自动索引并预览归档在Images.xcassets下地图片文件,非常实用

  • AutoresizeMask-for-Xcode,将autoresizeMask配置可视化

  • XVim,将vim mode完美移植,并加入:build, :run来满足命令控的需求,但个人更喜欢使用mac下完全通用的emacs文本编辑模式,再加上键位合理的快捷键,足以让开发变得异常便捷。

插件安装路径位于~/Library/Application Support/Developer/Shared/Xcode/Plug-ins,各位看官可以根据个人喜欢自由增删。

Debugger

chisel是Facebook的开源工具,对Xcode的调试器LLDB进行增强。

安装

$ ~/.lldbinit
...
command script import /path/to/fblldb.py

建议把所有配置文件都放在云盘里,在用户根目录下建立软连接,这样就算重新装机也不会丢失配置文件,省去麻烦。

添加的自定义命令

命令 描述
pviews 递归打印keyWindow包含的View的描述信息
pvc 递归打印keyWindow包含的ViewControoler的描述信息
visualize 使用Mac上的Preview.app查看UIImage,UIView或者CALayer
fv 在View层级表中查找类名符合正则表达式的视图
fvc 在ViewController层级表中查找类d名符合正则表达式的视图
show/hide 显示后隐藏给出的View或Layer。甚至不需要继续处理就能查看变化
mask/unmask 使用透明矩形标示出View或者Layer
border/unborder 使用可见矩形标示出View或者Layer
caflush 刷新渲染器(如果没有动画实际效果等于“repaint”)
bmessage 在类方法或实例方法上放置象征断点,不用担心类的继承
wivar 在实例对象上设置观察点
presponder 打印出给出对象的responder链
… 还有更多

More:

  • NSHipter最新一期的文章Xcode Plugins介绍了更多更炫的插件(NSHipter越来越无聊了,估计Mattt大神真的是没东西可写了)
  • 唐巧在他的开源项目Xcode-tool中提供了Snippets的自动化安装脚本