finish 6 chapters

pull/26/head
tornote 9 years ago
parent fe2c7cf1bc
commit ce98c2f2d7

@ -0,0 +1,51 @@
# 关于这本书
## 引言
<canvas id="custom" class="canvas" data-fragment-url="cmyk-halftone.frag" data-textures="vangogh.jpg" width="700px" height="320px"></canvas>
上面两幅图由不同的方式制成。第一张是梵高用手一层一层画出来的,需要花费些时间。第二张则是用一些【?像素矩阵的组合】生成的:一个青色,一个品红,一个黄色,和一个黑色。关键的区别在于第二张图是用非序列化方式实现的(即不是一步一步实现,而是多个同时进行)。
这本书是关于这个革命性的计算机技术片段着色器fragment shaders它将数字生成的图像带到了新的层面。类似于当年古腾堡的印刷术。
![Gutenberg's press](gutenpress.jpg)
Fragment shaders片段着色器可以让你控制像素在屏幕上的快速渲染。这就是为什么 shader 在各种场合被广泛使用的原因不管是在手机的视频过滤器还是酷炫的的3D视频游戏中。
![Journey by That Game Company](journey.jpg)
在接下来的章节你会发现 shader 是多么难以置信地快速和强大,还有如何将它应用到专业的和个人的作品中。
## 这本书是为谁而写的?
这本书是写给有线性代数和三角学的基本知识的创意编程者、游戏开发者和工程师的,还有那些想要提升他们的作品的图形质量到一个令人激动的新层次的人。(如果你想要学习编程,我强烈推荐你先学习[Processing](https://processing.org/)等你玩起来processing再回来看这个
这本书会教你如何使用shaders并把它整合进你的项目里以提升作品的表现力和图形质量。因为GLSLOpenGL的绘制语言的shaders 在很多平台都可以编译和运行你将可以把在这里学的运用到任何使用OpenGL, OpenGL ES 和 WebGL 的环境中。也就是说,你将可以把学到的知识应用到[Processing](https://processing.org/)[openFrameworks](http://openframeworks.cc/)[Cinder](http://libcinder.org/)[Three.js](http://threejs.org/)和iOS/Android游戏中。
## 这本书包含哪些内容?
这本书将会专注于GLSL像素着色。首先我们会给出shaders的定义然后我们会学习如何制作可被程序执行的形状图案材质和与之相关的动画。你将会学到基础的着色语言并把它们应用到有用的情景中比如图像加工图像运算矩阵卷积模糊颜色过滤查找表及其他效果和模拟Conway的生命游戏Gray-Scott反应扩散水波水彩效果Voronoi细胞等等。到书的最后我们讲看到一系列基于光线行进的进阶技术。
**每章都会有可以玩的交互的例子。**当你改动代码的时候,你会立刻看到这些变化。一些概念可能会晦涩难懂,而这些可交互的例子会对你学习这些材料非常有益。你越快把这些代码付诸实践,你学习的过程就会越容易。
这本书里不包括的内容有:
* 这**不是**一本openGL或webGL的书。OpenGL/webGL是一个比GLSL或fragment shaders更大的主题。如果你想要学习openGL/webGL推荐看 [OpenGL Introduction](https://open.gl/introduction), [the 8th edition of the OpenGL Programming Guide](http://www.amazon.com/OpenGL-Programming-Guide-Official-Learning/dp/0321773039/ref=sr_1_1?s=books&ie=UTF8&qid=1424007417&sr=1-1&keywords=open+gl+programming+guide) (也被叫做红宝书) 或 [WebGL: Up and Running](http://www.amazon.com/WebGL-Up-Running-Tony-Parisi/dp/144932357X/ref=sr_1_4?s=books&ie=UTF8&qid=1425147254&sr=1-4&keywords=webgl)
* 这**不是**一本数学书。虽然我们会涉及到很多关于线代和三角学的算法和技术,但我们不会详细解释它。关于数学的问题我推荐手边备一本:[3rd Edition of Mathematics for 3D Game Programming and computer Graphics](http://www.amazon.com/Mathematics-Programming-Computer-Graphics-Third/dp/1435458869/ref=sr_1_1?ie=UTF8&qid=1424007839&sr=8-1&keywords=mathematics+for+games) 或 [2nd Edition of Essential Mathematics for Games and Interactive Applications](http://www.amazon.com/Essential-Mathematics-Games-Interactive-Applications/dp/0123742978/ref=sr_1_1?ie=UTF8&qid=1424007889&sr=8-1&keywords=essentials+mathematics+for+developers)。
## 开始学习需要什么准备?
没什么。如果你有可以运行WebGL的浏览器像ChromeFirefox或Safari和网络点击页面底端的“下一章”按钮就可以开始了。
此外,基于你有的条件或需求你可以:
* [制作一个离线版的本书](http://thebookofshaders.com/appendix/)
* [用树莓派而不是浏览器来运行书中示例](http://thebookofshaders.com/appendix/)
* [做一个PDF版的书用于打印](http://thebookofshaders.com/appendix/)
* 用[github仓库](https://github.com/patriciogonzalezvivo/thebookofshaders)来帮助解决问题和分享代码

@ -0,0 +1,48 @@
# 开始
## 什么是 Fragment Shader(片段着色器)
在之前的章节我们把 shaders 和古腾堡印刷术相提并论。为什么这样类比呢?更重要的是,什么是 shader
![From Letter-by-Letter, Right: William Blades (1891). To Page-by-page, Left: Rolt-Wheeler (1920).](print.png)
如果你曾经有用计算机绘图的经验,你就知道在这个过程中你需要画一个圆,然后一个长方形,一条线,一些三角形……直到画出你想要的图像。这个过程很像用手写一封信或一本书 —— 都是一系列的指令,需要你一件一件完成。
Shaders 也是一系列的指令,但是这些指令会对屏幕上的每个像素同时下达。也就是说,你的代码必须根据像素在屏幕上的不同位置,表现出不同的样貌。就像活字印刷,你的程序就像一个 function函数输入位置信息输出颜色信息当它编译完之后会以相当快的速度运行。
![Chinese movable type](typepress.jpg)
## 为什么 shaders 运行特别快?
为了回答这个问题,不得不给大家介绍**并行处理**parallel processing的神奇之处。
想象你的 CPU 是一个大的工业管道,然后每一个任务都是通过这个管道的某些东西 —— 就像一个生产流水线那样。有些任务要比别的大,也就是说要花费更多时间和精力去处理。我们就称它要求更强的处理能力。因为计算机自身架构的原因,这些任务需要串行;即一次一个地依序完成。现代计算机通常有一组四个处理器,就像这个管道一样运行,一个接一个地处理这些任务,从而使计算机流畅运行。每个管道通常被称为**线程**。
![CPU](00.jpeg)
视频游戏和其他图形应用比起别的程序来说,需要高得多的处理能力。因为它们的图形内容需要操作无数像素。想想看,屏幕上的每一个像素都需要计算,而在 3D 游戏中几何和透视也都需要计算。
让我们回到开始那个关于管道和任务的比喻。屏幕上的每个像素都代表一个最简单的任务。单独来看完成任何一个像素的任务对 CPU 来说都很容易,那么问题来了,屏幕上的每一个像素都需要解决这样的小任务!也就是说,哪怕是对于一个老式的屏幕(分辨率 800x600来说都需要每帧处理480000个像素即每秒进行14400000次计算是的这对于微处理器就是大问题了而对于一个现代的 2800x1800 视网膜屏每秒运行60帧就需要每秒进行311040000次计算。图形工程师是如何解决这个问题的
![](03.jpeg)
这个时候并行处理就是最好的解决方案。比起用三五个强大的微处理器或者说“管道”来处理这些信息用一大堆小的微处理器来并行计算就要好得多。这就是图形处理器GPU : Graphic Processor Unit)的来由。
![GPU](04.jpeg)
设想一堆小型微处理器排成一个平面的画面假设每个像素的数据是乒乓球。14400000个乒乓球可以在一秒内阻塞几乎任何管道。但是一面800x600的管道墙每秒接收30波480000个像素的信息就可以流畅完成。这在更高的分辨率下也是成立的 —— 并行的处理器越多,可以处理的数据流就越大。
另一个 GPU 的魔法是特殊数学函数可通过硬件加速。非常复杂的数学操作可以直接被微芯片解决,而无须通过软件。这就表示可以有更快的三角和矩阵运算 —— 和电流一样快。
## GLSL是什么
GLSL 代表 openGL Shading LanguageopenGL 着色语言这是你在接下来章节看到的程序所遵循的具体标准。根据硬件和操作系统的不同还有其他的着色器shaders)。这里我们将依照[Khronos Group](https://www.khronos.org/opengl/)的规则来执行。了解 OpenGL的历史将有助于你理解大多数奇怪的约定所以建议不妨阅读[openglbook.com/chapter-0-preface-what-is-opengl.html](http://openglbook.com/chapter-0-preface-what-is-opengl.html)。
## 为什么 Shaders 有名地不好学?
就像蜘蛛侠里的那句名言欲戴其冠必承其重并行计算也是如此GPU 的强大的架构设计也有其限制与不足。
为了能使许多管线并行运行,每一个线程必须与其他的相独立。我们称这些线程对于其他线程在进行的运算是“盲视”的。这个限制就会使得所有数据必须以相同的方向流动。所以就不可能检查其他线程的输出结果,修改输入的数据,或者把一个线程的输出结果输入给另一个线程。如果允许线程到线程的数据流动将使所有的数据面临威胁。
并且 GPU 会让所有并行的微处理器(管道们)一直处在忙碌状态;只要它们一有空闲就会接到新的信息。一个线程不可能知道它前一刻在做什么。它可能是在画操作系统界面上的一个按钮,然后渲染了游戏中的一部分天空,然后显示了一封 email 中的一些文字。每个线程不仅是“盲视”的,而且还是“无记忆”的。同时,它要求编写一个通用的规则,依据像素的不同位置依次输出不同的结果。这种抽象性,和盲视、无记忆的限制使得 shaders 在程序员新人中不是很受欢迎。
但是不要担心!在接下来的章节中,我们会一步一步地,由浅入深地学习着色语言。如果你是在用一个靠谱的浏览器阅读这个教程,你会喜欢边读边玩书中的示例的。好了,不要再浪费时间了,赶快去玩起来吧! 点击**Next >>**开启 shader 之旅!

@ -0,0 +1,53 @@
## Hello World
“Hello world!”通常都是学习一个新语言的第一个例子。这是一个非常简单,只有一行的程序。它既是一个热情的欢迎,也传达了编程所能带来的可能性。
然而在 GPU 的世界里,第一步就渲染一行文字太难了,所以我们改为选择一个鲜艳的欢迎色,躁起来!
<div class="codeAndCanvas" data="hello_world.frag"></div>
如果你是在线阅读这本书的话,上面的代码都是可以交互的。你可以点击或者改动代码中任何一部分,尽情探索。多亏 GPU 的架构shader 会**飞速**地编译和更新这使得你的改动都会立刻出现在你眼前。试试改动第6行的值看会发生什么。
尽管这几行简单的代码看起来不像有很多内容,我们还是可以据此推测出一些知识点:
1. shader 语言 有一个 ```main``` 函数会在最后返回颜色值。这点和C语言很像。
2. 最终的像素颜色取决于预设的全局变量 ```gl_FragColor```。
3. 这个C系语言有内建的**变量**(像```gl_FragColor```**函数**和**数据类型**。在本例中我们刚刚介绍了```vec4```(四分量浮点向量)。之后我们会见到更多的类型,像 ```vec3``` (三分量浮点向量)和 ```vec2``` (二分量浮点向量),还有非常著名的:```float```(单精度浮点型), ```int```(整型) 和 ```bool```(布尔型)。
4. 如果我们仔细观察 ```vec4``` 类型,可以推测这四个变元分别响应红,绿,蓝和透明度通道。同时我们也可以看到这些变量是**规范化**的意思是它们的值是从0到1的。之后我们会学习如何规范化变量使得在变量间**map**(映射)数值更加容易。
5. 另一个可以从本例看出来的很重要的 C 系语言特征是,预处理程序的宏指令。宏指令是预编译的一部分。有了宏才可以 ```#define``` (定义)全局变量和进行一些基础的条件运算(通过使用 ```#ifdef``` 和 ```#endif```)。所有的宏都以 ```#``` 开头。预编译会在编译前一刻发生,把所有的命令复制到 ```#defines``` 里,检查```#ifdef``` 条件句是否已被定义, ```#ifndef``` 条件句是否没有被定义。在我们刚刚的“hello world!”的例子中我们在第2行检查了 ```GL_ES``` 是否被定义,这个通常用在移动端或浏览器的编译中。
6. ```float```类型在 shaders 中非常重要,所以**精度**非常重要。更低的精度会有更快的渲染速度,但是会以质量为代价。你可以选择每一个浮点值的精度。在第一行(```precision mediump float;```)我们就是设定了所有的浮点值都是中等精度。但我们也可以选择把这个值设为“低”(```precision lowp float;```)或者“高”(```precision highp float;```)。
7. 最后可能也是最重要的细节是GLSL 语言规范并不保证变量会被自动转换类别。这句话是什么意思呢显卡的硬件制造商各有不同的显卡加速方式但是却被要求有最精简的语言规范。因而自动强制类型转换并没有包括在其中。在我们的“hello world!”例子中,```vec4``` 精确到单精度浮点,所以应被赋予 ```float``` 格式。但是如果你想要代码前后一致,不要之后花费大量时间 debug 的话,最好养成在 ```float``` 型数值里加一个 ```.``` 的好习惯。如下这种代码就可能不能正常运行:
```glsl
void main() {
gl_FragColor = vec4(1,0,0,1); // 出错
}
```
现在我们已经基本讨论完了“hello world!”例子中所有主要的内容,是时候点击代码,检验一下我们所学的知识了。你会发现出错时程序会编译失败,只留一个寂寞的白屏。你可以试试一些好玩的小点子,比如说:
* 把单精度浮点值换成整型数值,猜猜你的显卡能不能忍这个行为。
* 试试把第六行 comment 掉,不给函数赋任何像素的值。
* 尝试另外写个函数,返回某个颜色,然后把 ```main()``` 放到这个函数里面。给个提示,这个函数应该长这样:
```glsl
vec4 red(){
return vec4(1.0,0.0,0.0,1.0);
}
```
* 有很多种构造 ```vec4``` 类型的方式,试试看其他方式。下面就是其中一种方式:
```glsl
vec4 color = vec4(vec3(1.0,0.0,1.0),1.0);
```
尽管这个例子看起来不那么刺激,它却是最最基础的 —— 我们把画布上的每一个像素都改成了一个确切的颜色。在接下来的章节中我们将会看到如何用两种输入源来改变像素的颜色:空间(依据像素在屏幕上的位置)和时间(依据页面加载了多少秒)。

@ -0,0 +1,148 @@
## 颜色
![Paul Klee - Color Chart (1931)](klee.jpg)
我们目前为止还未涉及到GLSL的向量类型。在我们深入向量之前学习更多关于变量和色彩主题是一个了解向量类型的好方法。
若你熟悉面向对象的编程范式或者说编程思维模式你一定注意到我们以一种类C的 ```struct```的方式访问向量数据的内部分量。
```glsl
vec3 red = vec3(1.0,0.0,0.0);
red.x = 1.0;
red.y = 0.0;
red.z = 0.0;
```
以x,y,z定义颜色是不是有些奇怪正因如此我们有其他方法访问这些变量——以不同的名字。```.x```, ```.y```, ```.z```也可以被写作```.r```, ```.g```, ```.b``` 和 ```.s```, ```.t```, ```.p```。(```.s```, ```.t```, ```.p```通常被用做后面章节提到的贴图空间坐标)你也可以通过使用索引位置```[0]```, ```[1]``` 和 ```[2]```来访问向量.
下面的代码展示了所有访问相同数据的方式:
```glsl
vec4 vector;
vector[0] = vector.r = vector.x = vector.s;
vector[1] = vector.g = vector.y = vector.t;
vector[2] = vector.b = vector.z = vector.p;
vector[3] = vector.a = vector.w = vector.q;
```
这些指向向量内部变量的不同方式仅仅是设计用来帮助你写出干净代码的术语。着色语言所包含的灵活性为你互换地思考颜色和坐标位置。
GLSL中向量类型的另一大特点是可以用你需要的任意顺序简单地投射和混合变量值。这种能力被形象地称为*鸡尾酒*。
```glsl
vec3 yellow, magenta, green;
// Making Yellow
yellow.rg = vec2(1.0); // Assigning 1. to red and green channels
yellow[2] = 0.0; // Assigning 0. to blue channel
// Making Magenta
magenta = yellow.rbg; // Assign the channels with green and blue swapped
// Making Green
green.rgb = yellow.bgb; // Assign the blue channel of Yellow (0) to red and blue channels
```
#### 个人工具箱
你可能不习惯用数字拾取颜色--这样非常反直觉。幸运的是app store上有许多可以轻松完成这项任务的程序。寻找一个合适自己的并练习将颜色转化为 ```vec3``` 或 ```vec4``` 格式。例如,这是我在[Spectrum](http://www.eigenlogik.com/spectrum/mac)中使用的的模板。
```
vec3({{rn}},{{gn}},{{bn}})
vec4({{rn}},{{gn}},{{bn}},1.0)
```
### 混合颜色
现在你了解到如何定义颜色是时候将先前所学的整合一下了在GLSL中有个十分有用的函数[```mix()```](../glossary/?search=mix)这个函数让你以百分比混合两个值。猜下百分比的取值范围没错0到1完美在你学习了这些基于栅格运动功夫后是时候练习一下了
![](mix-f.jpg)
看下下列代码中的第18行这里展示了我们如果是用随时间变化的sin绝对值来混合 ```colorA``` 和 ```colorB```。
<div class="codeAndCanvas" data="mix.frag"></div>
试着来show一下你所学到的
* 给颜色赋予一个有趣的过渡。想想某种特定的感情。哪种颜色更具代表性他如何产生又如何褪去再想想另外的一种感情以及对应的颜色。然后改变上诉代码中的代表这种情感的开始颜色和结束颜色。Robert Penner 开发了一些列流行的计算机动画塑形函数,被称之为平滑函数。你可以研究这些例子并得到启发,但最好你还是自己写一个自己的过度函数。
### 玩玩渐变
[```mix()```](../glossary/?search=mix) 函数有更多的用处。我们可以输入两个互相匹配的变量类型而不仅仅是单独的 ```float``` 变量,在我们这个例子中用的是 ```vec3```。这样我们便获得了混合颜色单独通道 ```.r``````.g``` 和 ```.b```的能力。
![](mix-vec.jpg)
试试下面的例子。正如前面一个例子我们用一条线来可视化根据单位化x坐标的过渡。现在所有通道都按照同样的线性变换过渡。
现在试试取消25行的注释看看会发生什么。然后再试试取消26行和27行。记住直线代表了```colorA``` 和 ```colorB```每个通道的混合比例。
<div class="codeAndCanvas" data="gradient.frag"></div>
你可能认出了我们用在25行到27行的造型函数。试着改写他们是时候把前几张的内容结合起来探索一些新的渐变。试试下列挑战
![William Turner - The Fighting Temeraire (1838)](turner.jpg)
* 创作一个渐变来代表 William Turner的落日。
* 用 ```u_time``` 做个一日出和日落的动画。
* 能用我们所学的做一道彩虹吗?
* 用 ```step``` 函数在做一个五彩的旗子。
### HSB
我们不能脱离色彩空间来谈论颜色。正如你所知除了rgb值有其他不同的方法去描述定义颜色。
[HSB](http://en.wikipedia.org/wiki/HSL_and_HSV) 代表色相,饱和度和亮度(或称为值)。这更符合直觉也更有利于组织颜色。稍微花些时间阅读下面的 ```rgb2hsv()``` 和 ```hsv2rgb()``` 函数。
将x坐标位置映射到Hue值并将y坐标映射到明度我们就得到了五彩的可见光光谱。这样的色彩空间分布实现起来非常方便比起RGB用HSB来拾取颜色更直观。
<div class="codeAndCanvas" data="hsb.frag"></div>
### 极坐标下的HSB
HSB原本是在极坐标下产生的以半径和角度定义而并非在笛卡尔坐标系基于xy定义下。将HSB映射到极坐标我们需要取得角度和到像素屏中点的距离。由此我们运用 [```length()```](../glossary/?search=length) 函数和 [```atan(y,x)```](../glossary/?search=atan) 函数在GLSL中通常用atany,x
当用到矢量和三角学函数时,```vec2```, ```vec3``` 和 ```vec4```被当做向量对待,即使有时候他们代表颜色。我们开始把颜色和向量同等的对待,事实上你会慢慢发现这种理念的灵活性有着相当强大的用途。
**注意**如果你想了解除length以外的诸多几何函数例如[```distance()```](../glossary/?search=distance), [```dot()```](../glossary/?search=dot), [```cross```](../glossary/?search=cross), [```normalize()```](../glossary/?search=normalize), [```faceforward()```](../glossary/?search=fraceforward), [```reflect()```](../glossary/?search=reflect) 和 [```refract()```](../glossary/?search=refract)。 GLSL也有与向量相关的函数[```lessThan()```](../glossary/?search=lessThan), [```lessThanEqual()```](../glossary/?search=lessThanEqual), [```greaterThan()```](../glossary/?search=greaterThan), [```greaterThanEqual()```](../glossary/?search=greaterThanEqual), [```equal()```](../glossary/?search=equal) and [```notEqual()```](../glossary/?search=notEqual)。
一旦我们得到角度和长度我们需要单位化这些值0.0到1.0。在27行[```atan(y,x)```](../glossary/?search=atan) 会返回一个介于-PI到PI的弧度值-3.14 to 3.14),所以我们要将这个返回值除以 ```TWO_PI```在code顶部定义了来得到一个-0.5到0.5的值。这样一来用简单的加法就可以把这个返回值最终映射到0.0到1.0。半径会返回一个最大值0.5因为我们计算的是到视口中心的距离而视口中心的范围已经被映射到0.0到1.0所以我们需要把这个值乘以二来得到一个0到1.0的映射。
正如你所见这里我们的游戏都是关于变换和映射到一个0到1这样我们乐于处理的值。
<div class="codeAndCanvas" data="hsb-colorwheel.frag"></div>
来挑战下下面的练习吧:
* 把极坐标映射的例子改成选择色轮,就像“正忙”的鼠标图标。
* 把造型函数整合进来来让HSB和RGB的转换中强调某些特定值并且弱化其他的。
![William Home Lizars - Red, blue and yellow spectra, with the solar spectrum (1834)](spectrums.jpg)
* 如果你仔细观察用来拾色的色轮见下图你会发现它用一种根据RYB色彩空间的色谱。例如红色的对面应该是绿色但在我们的例子里是青色。你能找到一种修复的方式来让它看起来和下图一样么[提示:这是用塑形函数的好机会!]
![](colorwheel.png)
#### 注意函数和变量
在进入下一章之前让我们停下脚步回顾下。复习下之前例子的函数。你会注意到变量类型之前有个限定符 ```in```,在这个 [*qualifier*](http://www.shaderific.com/glsl-qualifiers/#inputqualifier) (限定符)例子中它特指这个变量是只读的。在之后的例子中我们会看到可以定义一个 ```out``` 或者 ```inout```变量。最后这个 ```inout```,再概念上类似于参照输入一个变量,这意味着我们有可能修改一个传入的变量。
```glsl
int newFunction(in vec4 aVec4, // read-only
out vec3 aVec3, // write-only
inout int aInt); // read-write
```
或许你还不相信我们可以用所有这些元素来画一些炫酷的东西。下一章我们会学习如何结合所有这些技巧通过融合 (*blending*) 空间来创造几何形状。没错。。。融合(*blending*) 空间。

@ -0,0 +1,238 @@
## 图形/形状
##### 图形和形状在这里会是有一些习惯上的区别。第二版在校正吧!!!
![Alice Hubbard, Providence, United States, ca. 1892. Photo: Zindman/Freemont.](froebel.jpg)
终于我们一直学习的技能就等着这一刻你已经学习过GLSL的大部分基础类型和函数。你一遍又一遍的联系你的造型方程。是时候把他们整合起来了。你就是为了这个挑战而来的在这一章里你会学习到如何以一种并行处理方式来画简单的图形。
### 长方形
想象我们有张数学课上使用的方格纸而我们的作业是画一个正方形。纸的大小是10 * 10而正方形应该是8 * 8. 你会怎么做?
![](grid_paper.jpg)
你是不是会涂满除了第一行第一列和最后一行和最后一列的所有格点?
这和着色器有什么关系方格纸上的每个小方形格点就是一个线程一个像素。每个格点有它的位置就想棋盘上的坐标一样。在之前的章节我们将x和y映射到rgb通道并且我们学习了如何将二维边界限制在0和1之间。我们如何用这些来画一个中心点位于示像屏的中心正方形
我们从空间角度来判别的if语句伪代码开始。这个原理和我们思考方格纸的策略异曲同工。
```glsl
if ( (X GREATER THAN 1) AND (Y GREATER THAN 1) )
paint white
else
paint black
```
现在我们有个更好的主意让这个想法实现来试试把if语句换成step并用0到1代替10 * 10的范围。
```glsl
uniform vec2 u_resolution;
void main(){
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(0.0);
// Each result will return 1.0 (white) or 0.0 (black).
float left = step(0.1,st.x); // Similar to ( X greater than 0.1 )
float bottom = step(0.1,st.y); // Similar to ( Y greater than 0.1 )
// The multiplication of left*bottom will be similar to the logical AND.
color = vec3( left * bottom );
gl_FragColor = vec4(color,1.0);
}
```
step函数会让没每一个小于0.1的像素变成黑色vec30.0并将其与的变成白色vec31.0。左边和底边的“并行”由逻辑运算符AND完成——当x y都为1.0时返回1.0.这就画了两条黑线,一个在画布的底面另一个在左边。
![](rect-01.jpg)
在前一例代码中我们重复每个像素的结构左边和底边。我们可以把原来的一个值换成两个值直接给step来精减代码。就像这样
```glsl
vec2 borders = step(vec2(0.1),st);
float pct = borders.x * borders.y;
```
目前为止,我们只画了长方形的两条边(左边和底面)。看下下面的例子:
<div class="codeAndCanvas" data="rect-making.frag"></div>
取消21~22行的注释来看看如何转置坐标的同时重复使用step函数。这样二维向量vec2(0.0,0.0)会被变换到右上角。这就是转置页面和重复过程的数字等价。
![](rect-02.jpg)
注意在18行和22行所有的边都被放大了。等价于这样写
```glsl
vec2 bl = step(vec2(0.1),st); // bottom-left
vec2 tr = step(vec2(0.1),1.0-st); // top-right
color = vec3(bl.x * bl.y * tr.x * tr.y);
```
是不是很有趣这种都是关于运用step函数、逻辑运算和转置坐标的结合。
再进行下一个环节之前,挑战下下面的练习:
* 改变长方形的比例和大小。
* 用smoothstep函数代替step函数试试在相同的代码下会有什么不同。注意通过改变取值你可以不仅可以得到模糊边界也可以由漂亮的顺滑边界。
* 应用floor做个另外的案例。
* 挑个你最喜欢的应用做成函数,这样未来你可以调用它。并且让它灵活高效。
* 写一个只画长方形边界的函数。
* 想一下如何在一个画板上移动并放置不同的长方形?如果你做出来了,试着像[Piet Mondrian](http://en.wikipedia.org/wiki/Piet_Mondrian)一样创作以长方形和色彩的图画。
![Piet Mondria - Tableau (1921)](mondrian.jpg)
### 圆
在笛卡尔坐标系下,用方格纸来画正方形和长方形是很容易的。但是画圆就需要另一种方式了,尤其我们需要一个对“每个像素”的算法。一种解决办法是用[```step()```](../glossary/?search=step)函数将重新映射的空间坐标来画圆。
如何实现?让我们重新回顾一下数学课上的方格纸:我们把圆规展开到半径的长度,把一个针脚戳在圆圆心上,旋转着把圆的边界留下来。
![](compass.jpg)
将这个过程翻译给shader意味着纸上的每个方形格点都会隐含着问每个像素线程是否在圆的区域以内。我们通过计算像素到中心的距离来实现这个判断
![](circle.jpg)
There are several ways to calculate that distance. The easiest one uses the [```distance()```](../glossary/?search=distance) function, which internally computes the [```length()```](../glossary/?search=length) of the difference between two points (in our case the pixel coordinate and the center of the canvas). The ```length()``` function is nothing but a shortcut of the [hypotenuse equation](http://en.wikipedia.org/wiki/Hypotenuse) that uses square root ([```sqrt()```](../glossary/?search=sqrt)) internally.
有几种方法来计算距离。最简单的是用[```distance()```](../glossary/?search=distance)函数,这个函数其实内部调用 [```length()```](../glossary/?search=length)函数计算不同两点的距离在此例中是像素坐标和画布中心的距离。length函数内部只不过是用平方根([```sqrt()```](../glossary/?search=sqrt))计算斜边的方程。
![](hypotenuse.png)
你可以使用[```distance()```](../glossary/?search=distance), [```length()```](../glossary/?search=length) 或 [```sqrt()```](../glossary/?search=sqrt)到计算显示屏的中心的距离。下面的代码包含着三个函数,毫无悬念的他们返回相同的结果。
* 注释和取消注释某行来试试看用不同方式得到相同的结果。
<div class="codeAndCanvas" data="circle-making.frag"></div>
上回我们把到中心的距离映射为颜色亮度。离中心越近的越暗。注意到映射值不宜过高因为从中心vec2(0.5, 0.5)到最远距离才刚刚超过0.5一点。仔细考察这个映射:
* 你能从中推断出什么?
* 我们怎么用这个方法来画圆?
* 试试有没有其他方法来实现这样画布内圆形渐变的效果。
### 距离场
我们可也可以从另外的角度思考上面的例子把它当做海拔地图等高线图——越黑的地方意味着海拔越高。想象下你就在圆锥的顶端那么这里的渐变就和圆锥的等高线图有些相似。到圆锥的水平距离是一个常数0.5。这个距离值在每个方向上都是相等的。通过选择从那里截取这个圆锥,你就会得到或大或小的圆纹面。
![](distance-field.jpg)
其实我们是通过“空间距离”来重新解释什么是图形。这种技巧被称之为“距离场”从字体轮廓到3D图形被广泛应用。
来小试下牛刀:
* 用[```step()```](../glossary/?search=step)函数把所有大于0.5的像素点变成白色并把小于的变成黑色0.0【原文似乎有问题等于0.5呢这就要看step函数的定义了哈哈哈】
* 反转前景色和背景色。
* 调戏下[```smoothstep()```](../glossary/?search=smoothstep)函数,用不同的值来试验着得到一个顺滑的边界的圆。
* 一旦遇到令你满意的应用,把他写成一个函数,这样将来就可以调用了。
* 给这个圆来些缤纷的颜色吧!(圣诞红?不错的主意)
* 再加点动画?一闪一闪亮晶晶?或者是怦然的心跳?(或许你可以从上一章汲取一些灵感)
* 让它动起来?能不能移动它并且在同一个市像屏上放置多个圆?
* 如果你结合函数来混合不同的距离场,会发生什么呢?
```glsl
pct = distance(st,vec2(0.4)) + distance(st,vec2(0.6));
pct = distance(st,vec2(0.4)) * distance(st,vec2(0.6));
pct = min(distance(st,vec2(0.4)),distance(st,vec2(0.6)));
pct = max(distance(st,vec2(0.4)),distance(st,vec2(0.6)));
pct = pow(distance(st,vec2(0.4)),distance(st,vec2(0.6)));
```
* 用这种技巧制作三个元素,如果它们是运动的,那就再好不过啦!
#### 添加自己的工具箱
就计算效率而言,[```sqrt()```](../glossary/?search=sqrt)函数,以及所有依赖它的运算,都耗时耗力。[```dot()```](../glossary/?search=dot)点乘是另外一种用来高效计算圆形距离场的方式。
<div class="codeAndCanvas" data="circle.frag"></div>
### 距离场的特点
![Zen garden](zen-garden.jpg)
距离场几乎可以用来画任何东西。显然,图形越复杂,方程也越复杂。但是一旦你找到某个特定图形的公式,就很容易添加图形或应用像过渡边界的效果。正因如此,距离场经常用于字体渲染,例如[Mapbox GL Labels](https://www.mapbox.com/blog/text-signed-distance-fields/), [Matt DesLauriers](https://twitter.com/mattdesl) [Material Design Fonts](http://mattdesl.svbtle.com/material-design-on-the-gpu) 和 [as is describe on Chapter 7 of iPhone 3D Programming, OReilly](http://chimera.labs.oreilly.com/books/1234000001814/ch07.html#ch07_id36000921).
看看下面的代码:
<div class="codeAndCanvas" data="rect-df.frag"></div>
我们一开始把坐标系移到中心并把它映射到-1到1之间。在 *24行* 这儿,我们用一个[```fract()```](../glossary/?search=fract) 函数来呈现这个距离场产生的图案。这个距离场不断重复,就像在禅花园看到的环一样。
现在我们来看下 *19行* 的距离场方程。这里我们在计算点 ```(.3,.3)``` 或 ```vec3(.3)```到所有四象限的距离(这就是 [```abs()```](../glossary/?search=abs) 在起作用)。
如果你取消第 *20行* 的注释,你会发现我们把到四个点的距离用[```min()```](../glossary/?search=min) 函数合并到0并产生了一个有趣的图案。
现在再试着取消第 *21行* 的注释,我们做的和之前一样,只不过这次用的是 [```max()```](../glossary/?search=max) 函数。这次的记过是圆角矩形。注意距离场的环形是如何离中心越远越光滑的。
最后从*27 行到 29 行*一行行地取消注释,思考距离场的不同用途。
### 极坐标下的图形
![Robert Mangold - Untitled (2008)](mangold.jpg)
在关于颜色的章节我们通过如下的方程把每个像素的 *半径**角度* 笛卡尔坐标映射到极坐标。
```glsl
vec2 pos = vec2(0.5)-st;
float r = length(pos)*2.0;
float a = atan(pos.y,pos.x);
```
我们用了部分方程在这章的开头来画圆,即用 [```length()```](../glossary/?search=length) 计算到中心的距离。现在我们可以用极坐标来画圆。
极坐标这种方式虽然有所限制但却十分简单。
Below you will find the same functions in the cartesian graph and in a polar coordinates shader example (between *lines 21 and 25*). Uncomment the functions one-by-one, paying attention the relationship between one coordinate system and the other.
<div class="simpleFunction" data="y = cos(x*3.);
//y = abs(cos(x*3.));
//y = abs(cos(x*2.5))*0.5+0.3;
//y = abs(cos(x*12.)*sin(x*3.))*.8+.1;
//y = smoothstep(-.5,1., cos(x*10.))*0.2+0.5;"></div>
<div class="codeAndCanvas" data="polar.frag"></div>
试着:
* 让这些图形动起来。
* 结合不同的造型函数来 *雕刻* 图形,制作诸如花,雪花和齿轮。
* 用我们在 *造型函数* 章节的 ```plot()``` 函数画等高线。
### 整合的魅力
到目前为止,我们知道如何用[```atan()```](../glossary/?search=atan)函数来根据角度调整半径以获得不同的图形,以及如何用```atan()```结合所以和距离场有关的技巧得到可能的效果。
看下下面来自[Andrew Baldwin](https://twitter.com/baldand)的例子。这里的技巧是用极坐标的方式通过定义多边形的边数来构建一个距离场。
<div class="codeAndCanvas" data="shapes.frag"></div>
* 用这个例子,改造一个输入位置,指定图形(形状)的顶点数来返回一个距离场(的值)。
* 结合使用 [```min()```](../glossary/?search=min) 和 [```max()```](../glossary/?search=max) 函数混合距离场。
* 选一个几何logo永距离场来衍生生成出一个。用距离场画个自己感兴趣的logo白话
恭喜崎岖一路走来不容易啊休息下让这些概念沉淀一下吧当然不是用简单地用Processing来画些什么。在shader的世界里画图形变得有些别扭而且适应这种新的编程范式编程思维模式会有些精疲力竭。
既然现在你知道了如何画图形,我十分肯定你脑袋里已经充满了新的点子。在接下来的章节里你会学习到怎么移动,旋转以及缩放图形。这将使你的创作如虎添翼!

@ -0,0 +1,105 @@
## 2D Matrices 二维矩阵
<canvas id="custom" class="canvas" data-fragment-url="matrix.frag" width="700px" height="200px"></canvas>
### 平移
之前的章节我们学习了如何制作一些图形 - 而如何移动它们的技巧则是借助移动它们自身的参考坐标系。我们只需要给 ```st``` 变量加上一个包含每个片段的位置的向量。这样就移动了整个坐标系。
![](translate.jpg)
还是画着比较更容易解释,如上图所示:
* 取消下面代码中第35行的注释看下坐标空间是如何平移的。
<div class="codeAndCanvas" data="cross-translate.frag"></div>
现在尝试下下面的练习:
* 结合 ```u_time``` 和造型函数来移动十字,并试着让它有趣一点。找一个你觉得你感兴趣的某种运动形式,让这个十字也这样运动。记录“真实世界”的一些现象或许对你有所启发 — 可以是波的运动,摆动,弹球,汽车的加速运动,一辆自行车的刹车。
### 旋转
要移动物体,我们同样需要移动整个空间(坐标)系统。为此我们将使用一个[矩阵](http://en.wikipedia.org/wiki/Matrix_%28mathematics%29)。矩阵是一个通过行和列定义的一组数。用矩阵乘以一个向量是用一组精确的规则定义的,这样做是为了以一组特定的方式来改变向量的值。
[![Wikipedia entry for Matrix (mathematics) ](matrixes.png)](https://en.wikipedia.org/wiki/Matrix)
GLSL本身支持2维3维和4维方阵m*m矩阵[```mat2```](../glossary/?search=mat2) (2x2), [```mat3```](../glossary/?search=mat3) (3x3) 和 [```mat4```](../glossary/?search=mat4) (4x4)。GLSL同样支持矩阵相乘 (```*```)和特殊矩阵函数([```matrixCompMult()```](../glossary/?search=matrixCompMult))。
基于矩阵的特性,我们便有可能构造一个矩阵来产生特定的作用。比如我们可以用一个矩阵来平移一个向量:
![](3dtransmat.png)
更有趣的是,我们可以用矩阵来旋转坐标系统:
![](rotmat.png)
看下下面构成2维旋转的矩阵的代码。这个函数根据上面的[公式](http://en.wikipedia.org/wiki/Rotation_matrix),将二维向量绕 ```vec2(0.0)``` 点旋转。
```glsl
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
```
根据以往我们画形状的方式,这并不是我们想要的。我们的十字是画在画布中心的,对应于点 ```vec2(0.5)``` 。所以,再旋转坐标空间之前,我们需要先把图形移到中心点,坐标 ```vec2(0.0)``` ,再旋转坐标空间,最后在移动回原点。
![](rotate.jpg)
就像下面的代码:
<div class="codeAndCanvas" data="cross-rotate.frag"></div>
试试下面的练习:
* 取消第45行的代码看看会发生什么。
* 在37行和39行将旋转之前的平移注释掉观察结果。
* 用旋转改进在平移练习中模拟的动画。
### 缩放
我们看到了如何用矩阵平移和旋转物体。或者更准确的说如何通过变换坐标系统来旋转和移动物体。如果你用过3D建模软件或者 Processing中的 pushmatrix 和 popmatrix 函数,你会知道矩阵也可以被用来缩放物体的大小。
![](scale.png)
根据上面的公式我们知道如何构造一个2D缩放矩阵
```glsl
mat2 scale(vec2 _scale){
return mat2(_scale.x,0.0,
0.0,_scale.y);
}
```
<div class="codeAndCanvas" data="cross-scale.frag"></div>
试试下面的练习,尝试深入理解矩阵的工作机制:
* 取消上面代码中的第42行来观察空间坐标是如何被缩放的。
* 看看注释掉37和39行变换之前和之后的缩放会发生什么。
* 试着结合旋转矩阵和缩放矩阵。注意他们的先后顺序。先乘以一个矩阵,再乘以向量。
* 现在你知道如何画不同的图形,知道如何移动,旋转和缩放它们,是时候用这些来创作了。设计一个[fake UI or HUD (heads up display)](https://www.pinterest.com/patriciogonzv/huds/)。参考[Ndel](https://www.shadertoy.com/user/ndel)在ShaderToy上的例子。
<iframe width="800" height="450" frameborder="0" src="https://www.shadertoy.com/embed/4s2SRt?gui=true&t=10&paused=true" allowfullscreen></iframe>
### Other uses for matrices: YUV color 矩阵的其他应用YUV 颜色
[YUV](http://en.wikipedia.org/wiki/YUV) 是个用来模拟照片和视频的编码的色彩空间。这个色彩空间考虑人类的感知,减少色度的带宽。
下面的代码展现一种利用GLSL中的矩阵操作来切换颜色模式的有趣可能。
<div class="codeAndCanvas" data="yuv.frag"></div>
正如你所见,我们用对向量乘以矩阵的方式对待色彩。用这种方式,我们“移动”这些值。
这章我们学习如何运用矩阵变换来移动,旋转和缩放向量。除了之前章节学的图形,这些变换是创作的基础。在接下来的章节我们会应用我们所学的制作漂亮的程序纹理。你会发现编程的重复性和多样性是种令人兴奋的实践。

@ -18,24 +18,24 @@ This is a gentle step-by-step guide through the abstract and complex universe of
## 目录
* [关于这本书](00/)
* [关于这本书](00/?lan=ch)
* 开始
* [什么是着色器shader](01/)
* [“Hello world!”](02/)
* [Uniforms值](03/)
* [运行你的shader](04/)
* [什么是片段着色器Fragment Shader](01/?lan=ch)
* [“Hello world!”](02/?lan=ch)
* [Uniforms值](03/?lan=ch)
* [运行你的shader](04/?lan=ch)
* 用算法绘画
* [造型函数](05/)
* [颜色](06/)
* [形状](07/)
* [矩阵](08/)
* [图案](09/)
* [造型函数](05/?lan=ch)
* [颜色](06/?lan=ch)
* [形状](07/?lan=ch)
* [矩阵](08/?lan=ch)
* [图案](09/?lan=ch)
* 生成设计
* [Random函数](10/)
* [noise函数](11/)
* [Random函数](10/?lan=ch)
* [noise函数](11/?lan=ch)
* 布朗运动
* 分形
@ -62,13 +62,13 @@ This is a gentle step-by-step guide through the abstract and complex universe of
* 折射和反射
* [附录:](appendix/) Other ways to use this book
* [如何离线阅读此书?](appendix/)
* [如何在RaspberryPi上运行示例?](appendix/)
* [如何打印这本书](appendix/)
* [如何离线阅读此书?](appendix/?lan=ch)
* [如何在RaspberryPi上运行示例?](appendix/?lan=ch)
* [如何打印这本书](appendix/?lan=ch)
* [示例墙](examples/)
* [示例墙](examples/?lan=ch)
* [词汇表](glossary/)
* [词汇表](glossary/?lan=ch)
## 关于作者

Loading…
Cancel
Save