在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ GPU 加速下的圖像處理
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

GPU 加速下的圖像處理

Instagram,Snapchat,Photoshop。

所有這些應(yīng)用都是用來做圖像處理的。圖像處理可以簡(jiǎn)單到把一張照片轉(zhuǎn)換為灰度圖,也可以復(fù)雜到是分析一個(gè)視頻,并在人群中找到某個(gè)特定的人。盡管這些應(yīng)用非常的不同,但這些例子遵從同樣的流程,都是從創(chuàng)造到渲染。

在電腦或者手機(jī)上做圖像處理有很多方式,但是目前為止最高效的方法是有效地使用圖形處理單元,或者叫 GPU。你的手機(jī)包含兩個(gè)不同的處理單元,CPU 和 GPU。CPU 是個(gè)多面手,并且不得不處理所有的事情,而 GPU 則可以集中來處理好一件事情,就是并行地做浮點(diǎn)運(yùn)算。事實(shí)上,圖像處理和渲染就是在將要渲染到窗口上的像素上做許許多多的浮點(diǎn)運(yùn)算。

通過有效的利用 GPU,可以成百倍甚至上千倍地提高手機(jī)上的圖像渲染能力。如果不是基于 GPU 的處理,手機(jī)上實(shí)時(shí)高清視頻濾鏡是不現(xiàn)實(shí),甚至不可能的。

著色器 (shader) 是我們利用這種能力的工具。著色器是用著色語言寫的小的,基于 C 語言的程序。現(xiàn)在有很許多種著色語言,但你如果做 OS X 或者 iOS 開發(fā)的話,你應(yīng)該專注于 OpenGL 著色語言,或者叫 GLSL。你可以將 GLSL 的理念應(yīng)用到其他的更專用的語言 (比如 Metal) 上去。這里我們即將介紹的概念與和 Core Image 中的自定義核矩陣有著很好的對(duì)應(yīng),盡管它們?cè)谡Z法上有一些不同。

這個(gè)過程可能會(huì)很讓人恐懼,尤其是對(duì)新手。這篇文章的目的是讓你接觸一些寫圖像處理著色器的必要的基礎(chǔ)信息,并將你帶上書寫你自己的圖像處理著色器的道路。

什么是著色器

我們將乘坐時(shí)光機(jī)回顧一下過去,來了解什么是著色器,以及它是怎樣被集成到我們的工作流當(dāng)中的。

如果你在 iOS 5 或者之前就開始做 iOS 開發(fā),你或許會(huì)知道在 iPhone 上 OpenGL 編程有一個(gè)轉(zhuǎn)變,從 OpenGL ES 1.1 變成了 OpenGL ES 2.0。

OpenGL ES 1.1 沒有使用著色器。作為替代,OpenGL ES 1.1 使用被稱為固定功能管線 (fixed-function pipeline) 的方式。有一系列固定的函數(shù)用來在屏幕上渲染對(duì)象,而不是創(chuàng)建一個(gè)單獨(dú)的程序來指導(dǎo) GPU 的行為。這樣有很大的局限性,你不能做出任何特殊的效果。如果你想知道著色器在工程中可以造成怎樣的不同,看看這篇 Brad Larson 寫的他用著色器替代固定函數(shù)重構(gòu) Molecules 應(yīng)用的博客

OpenGL ES 2.0 引入了可編程管線??删幊坦芫€允許你創(chuàng)建自己的著色器,給了你更強(qiáng)大的能力和靈活性。

在 OpenGL ES 中你必須創(chuàng)建兩種著色器:頂點(diǎn)著色器 (vertex shaders) 和片段著色器 (fragment shaders)。這兩種著色器是一個(gè)完整程序的兩半,你不能僅僅創(chuàng)建其中任何一個(gè);想創(chuàng)建一個(gè)完整的著色程序,兩個(gè)都是必須存在。

頂點(diǎn)著色器定義了在 2D 或者 3D 場(chǎng)景中幾何圖形是如何處理的。一個(gè)頂點(diǎn)指的是 2D 或者 3D 空間中的一個(gè)點(diǎn)。在圖像處理中,有 4 個(gè)頂點(diǎn):每一個(gè)頂點(diǎn)代表圖像的一個(gè)角。頂點(diǎn)著色器設(shè)置頂點(diǎn)的位置,并且把位置和紋理坐標(biāo)這樣的參數(shù)發(fā)送到片段著色器。

然后 GPU 使用片段著色器在對(duì)象或者圖片的每一個(gè)像素上進(jìn)行計(jì)算,最終計(jì)算出每個(gè)像素的最終顏色。圖片,歸根結(jié)底,實(shí)際上僅僅是數(shù)據(jù)的集合。圖片的文檔包含每一個(gè)像素的各個(gè)顏色分量和像素透明度的值。因?yàn)閷?duì)每一個(gè)像素,算式是相同的,GPU 可以流水線作業(yè)這個(gè)過程,從而更加有效的進(jìn)行處理。使用正確優(yōu)化過的著色器,在 GPU 上進(jìn)行處理,將使你獲得百倍于在 CPU 上用同樣的過程進(jìn)行圖像處理的效率。

把東西渲染到屏幕上從一開始就是一個(gè)困擾 OpenGL 開發(fā)者的問題。僅僅讓屏幕呈現(xiàn)出非黑色就要寫很多樣板代碼和設(shè)置。開發(fā)者必須跳過很多坑 ,而這些坑所帶來的沮喪感以及著色器測(cè)試方法的匱乏,讓很多人放棄了哪怕是嘗試著寫著色器。

幸運(yùn)的是,過去幾年,一些工具和框架減少了開發(fā)者在嘗試著色器方面的焦慮。

下面我將要寫的每一個(gè)著色器的例子都是從開源框架 GPUImage 中來的。如果你對(duì) OpenGL/OpenGL ES 場(chǎng)景如何配置,從而使其可以使用著色器渲染感到好奇的話,可以 clone 這個(gè)倉儲(chǔ)。我們不會(huì)深入到怎樣設(shè)置 OpenGL/OpenGL ES 來使用著色器渲染,這超出了這篇文章的范圍。

我們的第一個(gè)著色器的例子

頂點(diǎn)著色器

好吧,關(guān)于著色器我們說的足夠多了。我們來看一個(gè)實(shí)踐中真實(shí)的著色器程序。這里是一個(gè) GPUImage 中一個(gè)基礎(chǔ)的頂點(diǎn)著色器:

attribute vec4 position;
attribute vec4 inputTextureCoordinate;

varying vec2 textureCoordinate;

void main()
{
    gl_position = position;
    textureCoordinate = inputTextureCoordinate.xy;
}

我們一句一句的來看:

attribute vec4 position;

像所有的語言一樣,著色器語言的設(shè)計(jì)者也為常用的類型創(chuàng)造了特殊的數(shù)據(jù)類型,例如 2D 和 3D 坐標(biāo)。這些類型是向量,稍后我們會(huì)深入更多?;氐轿覀兊膽?yīng)用程序的代碼,我們創(chuàng)建了一系列頂點(diǎn),我們?yōu)槊總€(gè)頂點(diǎn)提供的參數(shù)里的其中一個(gè)是頂點(diǎn)在畫布中的位置。然后我們必須告訴我們的頂點(diǎn)著色器它需要接收這個(gè)參數(shù),我們稍后會(huì)將它用在某些事情上。因?yàn)檫@是一個(gè) C 程序,我們需要記住要在每一行代碼的結(jié)束使用一個(gè)分號(hào),所以如果你正使用 Swift 的話,你需要把在末尾加分號(hào)的習(xí)慣撿回來。

attribute vec4 inputTextureCoordinate;

現(xiàn)在你或許很奇怪,為什么我們需要一個(gè)紋理坐標(biāo)。我們不是剛剛得到了我們的頂點(diǎn)位置了嗎?難道它們不是同樣的東西嗎?

其實(shí)它們并非一定是同樣的東西。紋理坐標(biāo)是紋理映射的一部分。這意味著你想要對(duì)你的紋理進(jìn)行某種濾鏡操作的時(shí)候會(huì)用到它。左上角坐標(biāo)是 (0,0)。右上角的坐標(biāo)是 (1,0)。如果我們需要在圖片內(nèi)部而不是邊緣選擇一個(gè)紋理坐標(biāo),我們需要在我們的應(yīng)用中設(shè)定的紋理坐標(biāo)就會(huì)與此不同,像是 (.25, .25) 是在圖片左上角向右向下各圖片高寬 1/4 的位置。在我們當(dāng)前的圖像處理應(yīng)用里,我們希望紋理坐標(biāo)和頂點(diǎn)位置一致,因?yàn)槲覀兿敫采w到圖片的整個(gè)長度和寬度。有時(shí)候你或許會(huì)希望這些坐標(biāo)是不同的,所以需要記住它們未必是相同的坐標(biāo)。在這個(gè)例子中,頂點(diǎn)坐標(biāo)空間從 -1.0 延展到 1.0,而紋理坐標(biāo)是從 0.0 到 1.0。

varying vec2 textureCoordinate;

因?yàn)轫旤c(diǎn)著色器負(fù)責(zé)和片段著色器交流,所以我們需要?jiǎng)?chuàng)建一個(gè)變量和它共享相關(guān)的信息。在圖像處理中,片段著色器需要的唯一相關(guān)信息就是頂點(diǎn)著色器現(xiàn)在正在處理哪個(gè)像素。

gl_Position = position;

gl_Position 是一個(gè)內(nèi)建的變量。GLSL 有一些內(nèi)建的變量,在片段著色器的例子中我們將看到其中的一個(gè)。這些特殊的變量是可編程管道的一部分,API 會(huì)去尋找它們,并且知道如何和它們關(guān)聯(lián)上。在這個(gè)例子中,我們指定了頂點(diǎn)的位置,并且把它從我們的程序中反饋給渲染管線。

textureCoordinate = inputTextureCoordinate.xy;

最后,我們?nèi)〕鲞@個(gè)頂點(diǎn)中紋理坐標(biāo)的 X 和 Y 的位置。我們只關(guān)心 inputTextureCoordinate 中的前兩個(gè)參數(shù),X 和 Y。這個(gè)坐標(biāo)最開始是通過 4 個(gè)屬性存在頂點(diǎn)著色器里的,但我們只需要其中的兩個(gè)。我們拿出需要的屬性,然后賦值給一個(gè)將要和片段著色器通信的變量,而不是把更多的屬性反饋給片段著色器。

在大多數(shù)圖像處理程序中,頂點(diǎn)著色器都差不多,所以,這篇文章接下來的部分,我們將集中討論片段著色器。

片段著色器

看過了我們簡(jiǎn)單的頂點(diǎn)著色器后,我們?cè)賮砜匆粋€(gè)可以實(shí)現(xiàn)的最簡(jiǎn)單的片段著色器:一個(gè)直通濾鏡:

varying highp vec2 textureCoordinate;

uniform sampler2D inputImageTexture;

void main()
{
    gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}

這個(gè)著色器實(shí)際上不會(huì)改變圖像中的任何東西。它是一個(gè)直通著色器,意味著我們輸入每一個(gè)像素,然后輸出完全相同的像素。我們來一句句的看:

varying highp vec2 textureCoordinate;

因?yàn)槠沃髯饔迷诿恳粋€(gè)像素上,我們需要一個(gè)方法來確定我們當(dāng)前在分析哪一個(gè)像素/片段。它需要存儲(chǔ)像素的 X 和 Y 坐標(biāo)。我們接收到的是當(dāng)前在頂點(diǎn)著色器被設(shè)置好的紋理坐標(biāo)。

uniform sampler2D inputImageTexture;

為了處理圖像,我們從應(yīng)用中接收一個(gè)圖片的引用,我們把它當(dāng)做一個(gè) 2D 的紋理。這個(gè)數(shù)據(jù)類型被叫做 sampler2D ,這是因?yàn)槲覀円獜倪@個(gè) 2D 紋理中采樣出一個(gè)點(diǎn)來進(jìn)行處理。

gl_FragColor = texture2D(inputImageTexture, textureCoordinate);

這是我們碰到的第一個(gè) GLSL 特有的方法:texture2D,顧名思義,創(chuàng)建一個(gè) 2D 的紋理。它采用我們之前聲明過的屬性作為參數(shù)來決定被處理的像素的顏色。這個(gè)顏色然后被設(shè)置給另外一個(gè)內(nèi)建變量,gl_FragColor。因?yàn)槠沃鞯奈ㄒ荒康木褪谴_定一個(gè)像素的顏色,gl_FragColor 本質(zhì)上就是我們片段著色器的返回語句。一旦這個(gè)片段的顏色被設(shè)置,接下來片段著色器就不需要再做其他任何事情了,所以你在這之后寫任何的語句,都不會(huì)被執(zhí)行。

就像你看到的那樣,寫著色器很大一部分就是了解著色語言。即使著色語言是基于 C 語言的,依然有很多怪異和細(xì)微的差別讓它和普通的 C 語言有不同。

GLSL 數(shù)據(jù)類型和運(yùn)算

各式著色器都是用 OpenGL 著色語言 (GLSL) 寫的。GLSL 是一種從 C 語言導(dǎo)出的簡(jiǎn)單語言。它缺少 C 語言的高級(jí)功能,比如動(dòng)態(tài)內(nèi)存管理。但是,它也包含一些在著色過程中常用的數(shù)學(xué)運(yùn)算函數(shù)。

在負(fù)責(zé) OpenGL 和 OpenGL ES 實(shí)現(xiàn)的 Khronos 小組的網(wǎng)站上有一些有用的參考資料。在你開始之前,一件你可以做的最有價(jià)值的事情就是獲取 OpenGL 和 OpenGL ES 的快速入門指導(dǎo):

通過查看這些參考卡片,你可以快速簡(jiǎn)單地了解在寫 OpenGL 應(yīng)用時(shí)需要的著色語言函數(shù)和數(shù)據(jù)類型。

盡早用,經(jīng)常用。

即使在這么簡(jiǎn)單的著色器的例子里,也有一些地方看起來很怪異,不是嗎?看過了基礎(chǔ)的著色器之后,是時(shí)候開始解釋其中一些內(nèi)容,以及它們?yōu)槭裁创嬖谟?GLSL 中。

輸入,輸出,以及精度修飾 (Precision Qualifiers)

看一看我們的直通著色器,你會(huì)注意到有一個(gè)屬性被標(biāo)記為 “varying”,另一個(gè)屬性被標(biāo)記為 “uniform”。

這些變量是 GLSL 中的輸入和輸出。它允許從我們應(yīng)用的輸入,以及在頂點(diǎn)著色器和片段著色器之間進(jìn)行交流。

在 GLSL 中,實(shí)際有三種標(biāo)簽可以賦值給我們的變量:

  • Uniforms
  • Attributes
  • Varyings

Uniforms 是一種外界和你的著色器交流的方式。Uniforms 是為在一個(gè)渲染循環(huán)里不變的輸入值設(shè)計(jì)的。如果你正在應(yīng)用茶色濾鏡,并且你已經(jīng)指定了濾鏡的強(qiáng)度,那么這些就是在渲染過程中不需要改變的事情,你可以把它作為 Uniform 輸入。 Uniform 在頂點(diǎn)著色器和片段著色器里都可以被訪問到。

Attributes 僅僅可以在頂點(diǎn)著色器中被訪問。Attribute 是在隨著每一個(gè)頂點(diǎn)不同而會(huì)發(fā)生變動(dòng)的輸入值,例如頂點(diǎn)的位置和紋理坐標(biāo)等。頂點(diǎn)著色器利用這些變量來計(jì)算位置,以它們?yōu)榛A(chǔ)計(jì)算一些值,然后把這些值以 varyings 的方式傳到片段著色器。

最后,但同樣重要的,是 varyings 標(biāo)簽。Varying 在頂點(diǎn)著色器和片段著色器都會(huì)出現(xiàn)。Varying 是用來在頂點(diǎn)著色器和片段著色器傳遞信息的,并且在頂點(diǎn)著色器和片段著色器中必須有匹配的名字。數(shù)值在頂點(diǎn)著色器被寫入到 varying ,然后在片段著色器被讀出。被寫入 varying 中的值,在片段著色器中會(huì)被以插值的形式插入到兩個(gè)頂點(diǎn)直接的各個(gè)像素中去。

回看我們之前寫的簡(jiǎn)單的著色器的例子,在頂點(diǎn)著色器和片段著色器中都用 varying 聲明了 textureCoordinate。我們?cè)陧旤c(diǎn)著色器中寫入 varying 的值。然后我們把它傳入片段著色器,并在片段著色器中讀取和處理。

在我們繼續(xù)之前,最后一件要注意的事??纯磩?chuàng)建的這些變量。你會(huì)注意到紋理坐標(biāo)有一個(gè)叫做 highp 的屬性。這個(gè)屬性負(fù)責(zé)設(shè)置你需要的變量精度。因?yàn)?OpenGL ES 被設(shè)計(jì)為在處理能力有限的系統(tǒng)中使用,精度限制被加入進(jìn)來可以提高效率。

如果不需要非常高的精度,你可以進(jìn)行設(shè)定,這或許會(huì)允許在一個(gè)時(shí)鐘循環(huán)內(nèi)處理更多的值。相反的,在紋理坐標(biāo)中,我們需要盡可能的確保精確,所以我們具體說明確實(shí)需要額外的精度。

精度修飾存在于 OpenGL ES 中,因?yàn)樗潜辉O(shè)計(jì)用在移動(dòng)設(shè)備中的。但是,在老版本的桌面版的 OpenGL 中則沒有。因?yàn)?OpenGL ES 實(shí)際上是 OpenGL 的子集,你幾乎總是可以直接把 OpenGL ES 的項(xiàng)目移植到 OpenGL。如果你這樣做,記住一定要在你的桌面版著色器中去掉精度修飾。這是很重要的一件事,尤其是當(dāng)你計(jì)劃在 iOS 和 OS X 之間移植項(xiàng)目時(shí)。

向量

在 GLSL 中,你會(huì)用到很多向量和向量類型。向量是一個(gè)很棘手的話題,它們表面上看起來很直觀,但是因?yàn)樗鼈冇泻芏嘤猛?,這使我們?cè)谑褂盟鼈儠r(shí)常常會(huì)感到迷惑。

在 GLSL 環(huán)境中,向量是一個(gè)類似數(shù)組的特殊的數(shù)據(jù)類型。每一種類型都有固定的可以保存的元素。深入研究一下,你甚至可以獲得數(shù)組可以存儲(chǔ)的數(shù)值的精確的類型。但是在大多數(shù)情況下,只要使用通用的向量類型就足夠了。

有三種向量類型你會(huì)經(jīng)常看到:

  • vec2
  • vec3
  • vec4

這些向量類型包含特定數(shù)量的浮點(diǎn)數(shù):vec2 包含兩個(gè)浮點(diǎn)數(shù),vec3 包含三個(gè)浮點(diǎn)數(shù),vec4 包含四個(gè)浮點(diǎn)數(shù)。

這些類型可以被用在著色器中可能被改變或者持有的多種數(shù)據(jù)類型中。在片段著色器中,很明顯 X 和 Y 坐標(biāo)是的你想保存的信息。 (X,Y) 存儲(chǔ)在 vec2 中就很合適。

在圖像處理過程中,另一個(gè)你可能想持續(xù)追蹤的事情就是每個(gè)像素的 R,G,B,A 值。這些可以被存儲(chǔ)在 vec4 中。

矩陣

現(xiàn)在我們已經(jīng)了解了向量,接下來繼續(xù)了解矩陣。矩陣和向量很相似,但是它們添加了額外一層的復(fù)雜度。矩陣是一個(gè)浮點(diǎn)數(shù)數(shù)組的數(shù)組,而不是單個(gè)的簡(jiǎn)單浮點(diǎn)數(shù)數(shù)組。

類似于向量,你將會(huì)經(jīng)常處理的矩陣對(duì)象是:

  • mat2
  • mat3
  • mat4

vec2 保存兩個(gè)浮點(diǎn)數(shù),mat 保存相當(dāng)于兩個(gè) vec2 對(duì)象的值。將向量對(duì)象傳遞到矩陣對(duì)象并不是必須的,只需要有足夠填充矩陣的浮點(diǎn)數(shù)即可。在 mat2 中,你需要傳入兩個(gè) vec2 或者四個(gè)浮點(diǎn)數(shù)。因?yàn)槟憧梢越o向量命名,而且相比于直接傳浮點(diǎn)數(shù),你只需要負(fù)責(zé)兩個(gè)對(duì)象,而不是四個(gè),所以非常推薦使用封裝好的值來存儲(chǔ)你的數(shù)字,這樣更利于追蹤。對(duì)于 mat4 會(huì)更復(fù)雜一些,因?yàn)槟阋?fù)責(zé) 16 個(gè)數(shù)字,而不是 4 個(gè)。

在我們 mat2 的例子中,我們有兩個(gè) vec2 對(duì)象。每個(gè) vec2 對(duì)象代表一行。每個(gè) vec2 對(duì)象的第一個(gè)元素代表一列。構(gòu)建你的矩陣對(duì)象的時(shí)候,確保每個(gè)值都放在了正確的行和列上是很重要的,否則使用它們進(jìn)行運(yùn)算肯定得不到正確的結(jié)果。

既然我們有了矩陣也有了填充矩陣的向量,問題來了:“我們要用它們做什么呢?“ 我們可以存儲(chǔ)點(diǎn)和顏色或者其他的一些的信息,但是要如果通過修改它們來做一些很酷的事情呢?

向量和矩陣運(yùn)算,也就是初等線性代數(shù)

我找到的最好的關(guān)于線性代數(shù)和矩陣是如何工作的資源是這個(gè)網(wǎng)站的更好的解釋。我從這個(gè)網(wǎng)站偷來借鑒的一句引述就是:

線性代數(shù)課程的幸存者都成為了物理學(xué)家,圖形程序員或者其他的受虐狂。

矩陣操作總體來說并不“難”;只不過它們沒有被任何上下文解釋,所以很難概念化地理解究竟為什么會(huì)有人想要和它們打交道。我希望能在給出一些它們?cè)趫D形編程中的應(yīng)用背景后,我們可以了解它們?cè)鯓訋椭覀儗?shí)現(xiàn)不可思議的東西。

線性代數(shù)允許你一次在很多值上進(jìn)行操作。假想你有一組數(shù),你想要每一個(gè)數(shù)乘以 2。你一般會(huì)一個(gè)個(gè)地順次計(jì)算數(shù)值。但是因?yàn)閷?duì)每一個(gè)數(shù)都進(jìn)行的是同樣的操作,所以你完全可以并行地實(shí)現(xiàn)這個(gè)操作。

我們舉一個(gè)看起來可怕的例子,CGAffineTransforms。仿射轉(zhuǎn)化是很簡(jiǎn)單的操作,它可以改變具有平行邊的形狀 (比如正方形或者矩形) 的大小,位置,或者旋轉(zhuǎn)角度。

在這種時(shí)候你當(dāng)然可以坐下來拿出筆和紙,自己去計(jì)算這些轉(zhuǎn)化,但這么做其實(shí)沒什么意義。GLSL 有很多內(nèi)建的函數(shù)來進(jìn)行這些龐雜的用來計(jì)算轉(zhuǎn)換的函數(shù)。了解這些函數(shù)背后的思想才是最重要的。

GLSL 特有函數(shù)

這篇文章中,我們不會(huì)把所有的 GLSL 內(nèi)建的函數(shù)都過一遍,不過你可以在 Shaderific 上找到很好的相關(guān)資源。很多 GLSL 函數(shù)都是從 C 語言數(shù)學(xué)庫中的基本的數(shù)學(xué)運(yùn)算導(dǎo)出的,所以解釋 sin 函數(shù)是做什么的真的是浪費(fèi)時(shí)間。我們將集中闡釋一些更深?yuàn)W的函數(shù),從而達(dá)到這篇文章的目的,解釋怎樣才能充分利用 GPU 的性能的一些細(xì)節(jié)。

step(): GPU 有一個(gè)局限性,它并不能很好的處理?xiàng)l件邏輯。GPU 喜歡做的事情是接受一系列的操作,并將它們作用在所有的東西上。分支會(huì)在片段著色器上導(dǎo)致明顯的性能下降,在移動(dòng)設(shè)備上尤其明顯。step() 通過允許在不產(chǎn)生分支的前提下實(shí)現(xiàn)條件邏輯,從而在某種程度上可以緩解這種局限性。如果傳進(jìn) step() 函數(shù)的值小于閾值,step() 會(huì)返回 0.0。如果大于或等于閾值,則會(huì)返回 1.0。通過把這個(gè)結(jié)果和你的著色器的值相乘,著色器的值就可以被使用或者忽略,而不用使用 if() 語句。

mix(): mix 函數(shù)將兩個(gè)值 (例如顏色值) 混合為一個(gè)變量。如果我們有紅和綠兩個(gè)顏色,我們可以用 mix() 函數(shù)線性插值。這在圖像處理中很常用,比如在應(yīng)用程序中通過一組獨(dú)特的設(shè)定來控制效果的強(qiáng)度等。

*clamp(): GLSL 中一個(gè)比較一致的方面就是它喜歡使用歸一化的坐標(biāo)。它希望收到的顏色分量或者紋理坐標(biāo)的值在 0.0 和 1.0 之間。為了保證我們的值不會(huì)超出這個(gè)非常窄的區(qū)域,我們可以使用 clamp() 函數(shù)。 clamp() 會(huì)檢查并確保你的值在 0.0 和 1.0 之間。如果你的值小于 0.0,它會(huì)把值設(shè)為 0.0。這樣做是為了防止一些常見的錯(cuò)誤,例如當(dāng)你進(jìn)行計(jì)算時(shí)意外的傳入了一個(gè)負(fù)數(shù),或者其他的完全超出了算式范圍的值。

更復(fù)雜的著色器的例子

我知道數(shù)學(xué)的洪水一定讓你快被淹沒了。如果你還能跟上我,我想舉幾個(gè)優(yōu)美的著色器的例子,這會(huì)更有意義,這樣你又有機(jī)會(huì)淹沒在 GLSL 的潮水中。

飽和度調(diào)整

http://wiki.jikexueyuan.com/project/objc/images/21-25.png" alt="" />

這是一個(gè)做飽和度調(diào)節(jié)的片段著色器。這個(gè)著色器出自 《圖形著色器:理論和實(shí)踐》一書,我強(qiáng)烈推薦整本書給所有對(duì)著色器感興趣的人。

飽和度是用來表示顏色的亮度和強(qiáng)度的術(shù)語。一件亮紅色的毛衣的飽和度要遠(yuǎn)比北京霧霾時(shí)灰色的天空的飽和度高得多。

在這個(gè)著色器上,參照人類對(duì)顏色和亮度的感知過程,我們有一些優(yōu)化可以使用。一般而言,人類對(duì)亮度要比對(duì)顏色敏感的多。這么多年來,壓縮軟件體積的一個(gè)優(yōu)化方式就是減少存儲(chǔ)顏色所用的內(nèi)存。

人類不僅對(duì)亮度比顏色要敏感,同樣亮度下,我們對(duì)某些特定的顏色反應(yīng)也更加靈敏,尤其是綠色。這意味著,當(dāng)你尋找壓縮圖片的方式,或者以某種方式改變它們的亮度和顏色的時(shí)候,多放一些注意力在綠色光譜上是很重要的,因?yàn)槲覀儗?duì)它最為敏感。

varying highp vec2 textureCoordinate;

uniform sampler2D inputImageTexture;
uniform lowp float saturation;

const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);

void main()
{
   lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
   lowp float luminance = dot(textureColor.rgb, luminanceWeighting);
   lowp vec3 greyScaleColor = vec3(luminance);

    gl_FragColor = vec4(mix(greyScaleColor, textureColor.rgb, saturation), textureColor.w);

}

我們一行行的看這個(gè)片段著色器的代碼:

varying highp vec2 textureCoordinate;

uniform sampler2D inputImageTexture;
uniform lowp float saturation;

再一次,因?yàn)檫@是一個(gè)要和基礎(chǔ)的頂點(diǎn)著色器通信的片段著色器,我們需要為輸入紋理坐標(biāo)和輸入圖片紋理聲明一個(gè) varyings 變量,這樣才能接收到我們需要的信息,并進(jìn)行過濾處理。這個(gè)例子中我們有一個(gè)新的 uniform 的變量需要處理,那就是飽和度。飽和度的數(shù)值是一個(gè)我們從用戶界面設(shè)置的參數(shù)。我們需要知道用戶需要多少飽和度,從而展示正確的顏色數(shù)量。

const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);

這就是我們?cè)O(shè)置三個(gè)元素的向量,為我們的亮度來保存顏色比重的地方。這三個(gè)值加起來要為 1,這樣我們才能把亮度計(jì)算為 0.0 - 1.0 之間的值。注意中間的值,就是表示綠色的值,用了 70% 的顏色比重,而藍(lán)色只用了它的 10%。藍(lán)色對(duì)我們的展示不是很好,把更多權(quán)重放在綠色上是很有意義的。

lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);

我們需要取樣特定像素在我們圖片/紋理中的具體坐標(biāo)來獲取顏色信息。我們將會(huì)改變它一點(diǎn)點(diǎn),而不是想直通濾鏡那樣直接返回。

lowp float luminance = dot(textureColor.rgb, luminanceWeighting);

這行代碼會(huì)讓那些沒有學(xué)過線性代數(shù)或者很早以前在學(xué)校學(xué)過但是很少用過的人看起來不那么熟悉。我們是在使用 GLSL 中的點(diǎn)乘運(yùn)算。如果你記得在學(xué)校里曾用過點(diǎn)運(yùn)算符來相乘兩個(gè)數(shù)字的話,那么你就能明白是什么回事兒了。點(diǎn)乘計(jì)算以包含紋理顏色信息的 vec4 為參數(shù),舍棄 vec4 的最后一個(gè)不需要的元素,將它和相對(duì)應(yīng)的亮度權(quán)重相乘。然后取出所有的三個(gè)值把它們加在一起,計(jì)算出這個(gè)像素綜合的亮度值。

lowp vec3 greyScaleColor = vec3(luminance);

我們創(chuàng)建一個(gè)三個(gè)值都是亮度信息的 vec3。如果你只指定一個(gè)值,編譯器會(huì)幫你把該將向量中的每個(gè)分量都設(shè)成這個(gè)值。

gl_FragColor = vec4(mix(greyScaleColor, textureColor.rgb, saturation), textureColor.w);

最后,我們把所有的片段組合起來。為了確定每個(gè)新的顏色是什么,我們使用剛剛學(xué)過的很好用的 mix 函數(shù)。mix 函數(shù)會(huì)把我們剛剛計(jì)算的灰度值和初始的紋理顏色以及我們得到的飽和度的信息相結(jié)合。

這就是一個(gè)很棒的,好用的著色器,它讓你用主函數(shù)里的四行代碼就可以把圖片從彩色變到灰色,或者從灰色變到彩色。還不錯(cuò),不是嗎?

球形折射

最后,我們來看一個(gè)很漂亮的濾鏡,你可以用來向你的朋友炫耀,或者嚇唬你的敵人。這個(gè)濾鏡看起來像是有一個(gè)玻璃球在你的圖片上。這會(huì)比之前的看起來更復(fù)雜。但我相信我們可以完成它。

http://wiki.jikexueyuan.com/project/objc/images/21-26.png" alt="" />

varying highp vec2 textureCoordinate;

uniform sampler2D inputImageTexture;

uniform highp vec2 center;
uniform highp float radius;
uniform highp float aspectRatio;
uniform highp float refractiveIndex;

void main()
{
    highp vec2 textureCoordinateToUse = vec2(textureCoordinate.x, (textureCoordinate.y * aspectRatio + 0.5 - 0.5 * aspectRatio));
    highp float distanceFromCenter = distance(center, textureCoordinateToUse);
    lowp float checkForPresenceWithinSphere = step(distanceFromCenter, radius);

    distanceFromCenter = distanceFromCenter / radius;

    highp float normalizedDepth = radius * sqrt(1.0 - distanceFromCenter * distanceFromCenter);
    highp vec3 sphereNormal = normalize(vec3(textureCoordinateToUse - center, normalizedDepth));

    highp vec3 refractedVector = refract(vec3(0.0, 0.0, -1.0), sphereNormal, refractiveIndex);

    gl_FragColor = texture2D(inputImageTexture, (refractedVector.xy + 1.0) * 0.5) * checkForPresenceWithinSphere;
}

再一次,看起來很熟悉...

uniform highp vec2 center;
uniform highp float radius;
uniform highp float aspectRatio;
uniform highp float refractiveIndex;

我們引入了一些參數(shù),用來計(jì)算出圖片中多大的區(qū)域要通過濾鏡。因?yàn)檫@是一個(gè)球形,我們需要一個(gè)中心點(diǎn)和半徑來計(jì)算球形的邊界。寬高比是由你使用的設(shè)備的屏幕尺寸決定的,所以不能被硬編碼,因?yàn)?iPhone 和 iPad 的比例是不相同的。我們的用戶或者程序員會(huì)決定折射率,從而確定折射看起來是什么樣子的。GPUImage 中折射率被設(shè)置為 0.71.

highp vec2 textureCoordinateToUse = vec2(textureCoordinate.x, (textureCoordinate.y * aspectRatio + 0.5 - 0.5 * aspectRatio));

圖像的紋理坐標(biāo)是在歸一化的 0.0-1.0 的坐標(biāo)空間內(nèi)。歸一化的坐標(biāo)空間意味著考慮屏幕是一個(gè)單位寬和一個(gè)單位長,而不是 320 像素寬,480 像素高。因?yàn)槭謾C(jī)的高度比寬度要長,我們需要為球形計(jì)算一個(gè)偏移率,這樣球就是圓的而不是橢圓的。

http://wiki.jikexueyuan.com/project/objc/images/21-27.png" alt="" />

highp float distanceFromCenter = distance(center, textureCoordinateToUse);

我們需要計(jì)算特定的像素點(diǎn)距離球形的中心有多遠(yuǎn)。我們使用 GLSL 內(nèi)建的 distance() 函數(shù),它會(huì)使用勾股定律計(jì)算出中心坐標(biāo)和長寬比矯正過的紋理坐標(biāo)的距離。

lowp float checkForPresenceWithinSphere = step(distanceFromCenter, radius);

這里我們計(jì)算了片段是否在球體內(nèi)。我們計(jì)算當(dāng)前點(diǎn)距離球形中心有多遠(yuǎn)以及球的半徑是多少。如果當(dāng)前距離小于半徑,這個(gè)片段就在球體內(nèi),這個(gè)變量被設(shè)置為 1.0。否則,如果距離大于半徑,這個(gè)片段就不在球內(nèi),這個(gè)變量被設(shè)置為 0.0 。

http://wiki.jikexueyuan.com/project/objc/images/21-28.png" alt="" />

distanceFromCenter = distanceFromCenter / radius;

By dividing it by the radius, we are making our math calculations easier in the next few lines of code.

既然我們已經(jīng)計(jì)算出哪些像素是在球內(nèi)的,我們接著要對(duì)這些球內(nèi)的像素進(jìn)行計(jì)算并做些事情。再一次,我們需要標(biāo)準(zhǔn)化到球心的距離。我們直接重新設(shè)置 distanceFromCenter 的值,而不是新建一個(gè)變量,因?yàn)槟菚?huì)增加我們的開銷。 通過將它與半徑相除,我們可以讓之后幾行計(jì)算代碼變得簡(jiǎn)單一些。

highp float normalizedDepth = radius * sqrt(1.0 - distanceFromCenter * distanceFromCenter);

因?yàn)槲覀冊(cè)噲D模擬一個(gè)玻璃球,我們需要計(jì)算球的“深度”是多少。這個(gè)虛擬的球,不論怎樣,在 Z 軸上,將會(huì)延伸圖片表面到觀察者的距離。這將幫助計(jì)算機(jī)確定如何表示球內(nèi)的像素。還有,因?yàn)榍蚴菆A的,距離球心不同的距離,會(huì)有不同的深度。由于球表面方向的不同,球心處和邊緣處對(duì)光的折射會(huì)不相同:

http://wiki.jikexueyuan.com/project/objc/images/21-29.png" alt="" />

highp vec3 sphereNormal = normalize(vec3(textureCoordinateToUse - center, normalizedDepth));

這里我們又進(jìn)行了一次歸一化。為了計(jì)算球面某個(gè)點(diǎn)的方向,我們用 X ,Y 坐標(biāo)的方式,表示當(dāng)前像素到球心的距離,然后把這些和計(jì)算出的球的深度結(jié)合。然后把結(jié)果向量進(jìn)行歸一化。

想想當(dāng)你正在使用 Adobe Illustrator 這樣的軟件時(shí),你在 Illustrator 中創(chuàng)建一個(gè)三角形,但是它太小了。你按住 option 鍵,放大三角形,但是它現(xiàn)在太大了。你然后把它縮小到你想要的尺寸:

http://wiki.jikexueyuan.com/project/objc/images/21-30.png" alt="" />

highp vec3 refractedVector = refract(vec3(0.0, 0.0, -1.0), sphereNormal, refractiveIndex);

refract() 是一個(gè)很有趣的 GLSL 函數(shù)。refract() 以我們剛才創(chuàng)建的球法線和折射率來計(jì)算當(dāng)光線通過這樣的球時(shí),從任意一個(gè)點(diǎn)看起來是怎樣的。

gl_FragColor = texture2D(inputImageTexture, (refractedVector.xy + 1.0) * 0.5) * checkForPresenceWithinSphere;

最后,通過所有這些障礙后,我們終于湊齊了計(jì)算片段使用的顏色所需要的所有信息。折射光向量用來查找讀取的輸入位于圖片哪個(gè)位置的,但是因?yàn)樵谀莻€(gè)向量中,坐標(biāo)是從 -1.0 到 1.0 的,我們需要把它調(diào)整到 0.0-1.0 的紋理坐標(biāo)空間內(nèi)。

我們?nèi)缓蟀盐覀兊慕Y(jié)果和球邊界檢查的值相乘。如果我們的片段沒有在球內(nèi),一個(gè)透明的像素 (0.0, 0.0, 0.0, 0.0) 將被寫入。如果片段在球形內(nèi),這個(gè)結(jié)果被使用,然后返回計(jì)算好的顏色值。這樣我們?cè)谥髦锌梢跃捅苊獍嘿F的條件邏輯。

調(diào)試著色器

著色器調(diào)試不是一件直觀的工作。普通的程序中,如果程序崩潰了,你可以設(shè)置一個(gè)斷點(diǎn)。這在每秒會(huì)被并行調(diào)用幾百萬次的運(yùn)算中是不可能的。在著色器中使用 printf() 語句來調(diào)試哪里出錯(cuò)了也是不可能的,因?yàn)檩敵龅侥睦锬兀靠紤]你的著色器運(yùn)行在黑盒中,你怎么才能打開它然后看看為什么它們不工作呢?

你有一個(gè)可以使用的輸出:我們的老朋友 gl_FragColor。gl_FragColor 會(huì)給你一個(gè)輸出,換一種思路想一想,你可以用它來調(diào)試你的代碼。

所有你在屏幕上看到的顏色都是由一系列的數(shù)字表示的,這些數(shù)字是每一個(gè)像素的紅綠藍(lán)和透明度的百分比。你可以用這些知識(shí)來測(cè)試著色器的每一部分是不是像你構(gòu)建的那樣工作,從而確定它是不是按照你想的那樣在運(yùn)行。和一般調(diào)試不同,你不會(huì)得到一個(gè)可以打印的值,而是拿到一個(gè)顏色以及和它相關(guān)的某個(gè)指定值,依靠這些你可以進(jìn)行逆向反推。

如果想知道你的一個(gè)在 0 和 1 之間的值,你可以把它設(shè)置給一個(gè)將要傳入 gl_FragColorvec4 中。假設(shè)你把它設(shè)置進(jìn)第一部分,就是紅色值。這個(gè)值會(huì)被轉(zhuǎn)換然后渲染到屏幕上,這時(shí)候你就可以檢查它來確定原始的傳進(jìn)去的值是什么。

你會(huì)有幾種方法來捕捉到這些值。從著色器輸出的圖片可以被捕獲到然后作為圖片寫進(jìn)磁盤里 (最好用戶沒有壓縮過的格式)。這張圖片之后就可以放進(jìn)像 Photoshop 這樣的應(yīng)用,然后檢查像素的顏色。

為了更快一些,你可以將圖片用 OS X 的程序或者 iOS 的模擬器顯示到屏幕上。在你的應(yīng)用程序文件夾下的實(shí)用工具里有一個(gè)“數(shù)碼測(cè)色計(jì)”的工具可以用來分析這些渲染過的視圖。把鼠標(biāo)放在桌面的任何一個(gè)像素點(diǎn)上,它都會(huì)精確的展示這個(gè)像素點(diǎn) RGB 的值。因?yàn)?RGB 值在數(shù)碼測(cè)色計(jì)和 Photoshop 中是從 0 到 255 而不是 從 0 到 1,你需要把你想要的值除以 255 來獲得一個(gè)近似的輸入值。

回顧下我們的球形折射著色器。簡(jiǎn)直無法想象沒有任何測(cè)試就可以寫下整個(gè)程序。我們有很大一塊代碼來確定當(dāng)前處理的像素是不是在這個(gè)圓形當(dāng)中。那段代碼的結(jié)尾用 step() 函數(shù)來設(shè)置像素的這個(gè)值為 0.0 或者 1.0 。

把一個(gè) vec4 的紅色分量設(shè)為 step() 的輸出,其他兩個(gè)顏色值設(shè)為 0,然后傳入gl_FragColor 中去。如果你的程序正確的運(yùn)行,你將看到在黑色的屏幕上一個(gè)紅色的圈。如果整個(gè)屏幕都是黑色,或者都是紅色,那么肯定是有什么東西出錯(cuò)了。

性能調(diào)優(yōu)

性能測(cè)試和調(diào)優(yōu)是非常重要的事情。尤其是你想讓你的應(yīng)用在舊的 iOS 設(shè)備上也能流暢運(yùn)行時(shí)。

測(cè)試著色器性能很重要,因?yàn)槟憧偸遣荒艽_定一個(gè)東西的性能會(huì)怎樣。著色器性能變化的很不直觀。你會(huì)發(fā)現(xiàn) Stack Overflow 上一個(gè)非常好的優(yōu)化方案并不會(huì)加速你的著色器,因?yàn)槟銢]有優(yōu)化代碼的真正瓶頸。即使僅只是調(diào)換你工程里的幾行代碼都有可能非常大的減少或增加渲染的時(shí)間。

分析的時(shí)候,我建議測(cè)算幀渲染的時(shí)間,而不是每秒鐘渲染多少幀。幀渲染時(shí)間隨著著色器的性能線性的增加或減少,這會(huì)讓你觀察你的影響更簡(jiǎn)單。FPS 是幀時(shí)間的倒數(shù),在調(diào)優(yōu)的時(shí)候可能會(huì)難于理解。最后,如果你使用 iPhone 的相機(jī)捕捉圖像,它會(huì)根據(jù)場(chǎng)景的光亮來調(diào)整 FPS ,如果你依賴于此,會(huì)導(dǎo)致不準(zhǔn)確的測(cè)量。

幀渲染時(shí)間是幀從開始處理到完全結(jié)束并且渲染到屏幕或者一張圖片所花費(fèi)的時(shí)間。許多移動(dòng) GPU 用一種叫做 “延遲渲染” 的技術(shù),它會(huì)把渲染指令批量處理,并且只會(huì)在需要的時(shí)候才會(huì)處理。所以,需要計(jì)算整個(gè)渲染過程,而不是中間的操作過程,因?yàn)樗鼈兓蛟S會(huì)以一種與你想象不同的順序運(yùn)行。

不同的設(shè)備上,桌面設(shè)備和移動(dòng)設(shè)備上,優(yōu)化也會(huì)很不相同。你或許需要在不同類型的設(shè)備上進(jìn)行分析。例如,GPU 的性能在移動(dòng) iOS 設(shè)備上有了很大的提升。iPhone 5S 的 CPU 比 iPhone 4 快了接近十倍,而 GPU 則快上了好幾百倍。

如果你在有著 A7 芯片或者更高的設(shè)備上測(cè)試你的應(yīng)用,相比于 iPhone 5 或者更低版本的設(shè)備,你會(huì)獲得非常不同的結(jié)果。Brad Larson 測(cè)試了高斯模糊在不同的設(shè)備上花費(fèi)的時(shí)間,并且非常清晰的展示了在新設(shè)備上性能有著令人驚奇的提升:

iPhone 版本 幀渲染時(shí)間 (毫秒)
iPhone 4 873
iPhone 4S 145
iPhone 5 55
iPhone 5S 3

你可以下載一個(gè)工具,Imagination Technologies PowerVR SDK,它會(huì)幫助你分析你的著色器,并且讓你知道著色器渲染性能的最好的和最壞的情況 。為了保持高幀速率,使渲染著色器所需的周期數(shù)盡可能的低是很重要的。如果你想達(dá)成 60 幀每秒,你只有 16.67 毫秒來完成所有的處理。

這里有一些簡(jiǎn)單的方式來幫助你達(dá)成目標(biāo):

  • 消除條件邏輯: 有時(shí)候條件邏輯是必須得,但盡量最小化它。在著色器中使用像 step() 函數(shù)這樣的變通方法可以幫助你避免一些昂貴的條件邏輯。

  • 減少依賴紋理的讀取: 在片段著色器中取樣時(shí),如果紋理坐標(biāo)不是直接以 varying 的方式傳遞進(jìn)來,而是在片段著色器中進(jìn)行計(jì)算時(shí),就會(huì)發(fā)生依賴紋理的讀取。依賴紋理的讀取不能使用普通的紋理讀取的緩存優(yōu)化,會(huì)導(dǎo)致讀取更慢。例如,如果你想從附近的像素取樣,而不是計(jì)算和片段著色器中相鄰像素的偏差,最好在頂點(diǎn)著色器中進(jìn)行計(jì)算,然后把結(jié)果以 varying 的方式傳入片段著色器。在 Brad Larson的文章中關(guān)于索貝爾邊緣檢測(cè)的部分有一個(gè)這方面的例子。

  • 讓你的計(jì)算盡量簡(jiǎn)單: 如果你在避免一個(gè)昂貴的操作情況下可以獲得一個(gè)近似的足夠精度的值,你應(yīng)該這樣做。昂貴的計(jì)算包括調(diào)用三角函數(shù) (像sin(), cos(), 和 tan())。

  • 如果可以的話,把工作轉(zhuǎn)移到頂點(diǎn)著色器: 之前講的關(guān)于依賴紋理的讀取就是把紋理坐標(biāo)計(jì)算轉(zhuǎn)移到頂點(diǎn)著色器的很有意義的一種情況。如果一個(gè)計(jì)算在圖片上會(huì)有相同的結(jié)果,或者線性的變化,看看能不能把計(jì)算移到頂點(diǎn)著色器進(jìn)行。頂點(diǎn)著色器對(duì)每個(gè)頂點(diǎn)運(yùn)行一次,片段著色器在每個(gè)像素上運(yùn)行一次,所以在前者上的計(jì)算會(huì)比后者少很多。

  • 在移動(dòng)設(shè)備上使用合適的精度 在特定的移動(dòng)設(shè)備上,在向量上使用低精度的值會(huì)變得更快。在這些設(shè)備上,兩個(gè) lowp vec4 相加的操作可以在一個(gè)時(shí)鐘周期內(nèi)完成,而兩個(gè) highp vec4 相加則需要四個(gè)時(shí)鐘周期。但是在桌面 GPU 和最近的移動(dòng) GPU 上,這變得不再那么重要,因?yàn)樗鼈儗?duì)低精度值的優(yōu)化不同。

結(jié)論和資源

著色器剛開始看起來很嚇人,但它們也僅僅是改裝的 C 程序而已。創(chuàng)建著色器相關(guān)的所有事情,我們大多數(shù)都在某些情況下處理過,只不過在不同的上下文中罷了。

對(duì)于想深入了解著色器的人,我非常推薦的一件事就是回顧下三角學(xué)和線性代數(shù)。做相關(guān)工作的時(shí)候,我遇到的最大的阻力就是忘了很多大學(xué)學(xué)過的數(shù)學(xué),因?yàn)槲乙呀?jīng)很長時(shí)間沒有實(shí)際使用過它們了。

如果你的數(shù)學(xué)有些生疏了,我有一些書可以推薦給你:

也有數(shù)不清的關(guān)于GLSL書和特殊著色器被我們行業(yè)突出的人士創(chuàng)造出來:

還有,再一次強(qiáng)調(diào),GPUImage是一個(gè)開源的資源,里面有一些非常酷的著色器。一個(gè)非常好的學(xué)習(xí)著色器的方式,就是拿一個(gè)你覺得很有意思的著色器,然后一行一行看下去,搜尋任何你不理解的部分。GPUImage 還有一個(gè)著色器設(shè)計(jì)的 Mac 端應(yīng)用,可以讓你測(cè)試著色器而不用準(zhǔn)備 OpenGL 的代碼。

學(xué)習(xí)有效的在代碼中實(shí)現(xiàn)著色器可以給你帶來很大的性能提升。不僅如此,著色器也使你可以做以前不可能做出來的東西。

學(xué)習(xí)著色器需要一些堅(jiān)持和好奇心,但是并不是不可能的。如果一個(gè) 33 歲的還在康復(fù)中的新聞專業(yè)的人都能夠克服她對(duì)數(shù)學(xué)的恐懼來處理著色器的話,那么你肯定也可以。

上一篇:安全下一篇:Foundation