一则apache+php或者mod_fcgid的编译错误


Configuring mod_fcgid for APXS in /usr/local/apache/bin/apxs
./configure.apxs: /usr/local/apache/bin/apxs: /replace/with/path/to/perl/interpreter: bad interpreter: No such file or directory
./configure.apxs: /usr/local/apache/bin/apxs: /replace/with/path/to/perl/interpreter: bad interpreter: No such file or directory
./configure.apxs: /usr/local/apache/bin/apxs: /replace/with/path/to/perl/interpreter: bad interpreter: No such file or directory
./configure.apxs: /usr/local/apache/bin/apxs: /replace/with/path/to/perl/interpreter: bad interpreter: No such file or directory
./configure.apxs: /usr/local/apache/bin/apxs: /replace/with/path/to/perl/interpreter: bad interpreter: No such file or directory
./configure.apxs: /usr/local/apache/bin/apxs: /replace/with/path/to/perl/interpreter: bad interpreter: No such file or directory
./configure.apxs: /usr/local/apache/bin/apxs: /replace/with/path/to/perl/interpreter: bad interpreter: No such file or directory
./configure.apxs: /usr/local/apache/bin/apxs: /replace/with/path/to/perl/interpreter: bad interpreter: No such file or directory
Detecting features

Finished, run 'make' to compile mod_fcgid

Run 'make install' to install mod_fcgid

奇怪的Apache APXS明明发生了错误,但是还是报告完成了configure,导致make的时候,Makefile找不到/rules.mk文件无法编译。.


Makefile:29: /rules.mk: No such file or directory
make: *** No rule to make target `/rules.mk'. Stop.

解决办法是用打开/usr/local/apache/bin/apxs,手动给APXS第一行指定perl的位置,一般是/usr/bin/perl或者是/bin/perl

这个错误应该不光是mod_fcgid编译时会出现,如果以dso方式编译php+apache的时候应该也会出现,因为都用到了apache的apxs脚本。

继续使用Eclipse的ADT Bundle开发Android程序

Android已经有了自己官方的IDE,不再是基于Eclipse的ADT Bundle,但是用了一段时间感觉还是很不习惯,于是换到以前的环境,下面是下载链接,android官方已经不推荐使用了,也找不到链接,还好Google没有删除ADT文件。

linux 64 bit: http://dl.google.com/android/adt/adt-bundle-linux-x86_64-20140702.zip
linux 32 bit: http://dl.google.com/android/adt/adt-bundle-linux-x86-20140702.zip
MacOS X: http://dl.google.com/android/adt/adt-bundle-mac-x86_64-20140702.zip
Win32: http://dl.google.com/android/adt/adt-bundle-windows-x86-20140702.zip
Win64: http://dl.google.com/android/adt/adt-bundle-windows-x86_64-20140702.zip

PS:搭载ADT的版本是23.0.2,下载后请自行升级到最新版本的23.0.7。

如果采用内部升级的话,可能会出现如下报错。解决办法是升级的时候不要选择“Contact all update sites during install to find required software”即可。


An error occurred while collecting items to be installed
session context was:(profile=epp.package.jee, phase=org.eclipse.equinox.internal.p2.engine.phases.Collect, operand=, action=).
No repository found containing: osgi.bundle,com.android.ide.eclipse.adt,16.0.1.v201112150204-238534
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.adt,16.0.1.v201112150204-238534
No repository found containing: osgi.bundle,com.android.ide.eclipse.ddms,16.0.1.v201112150204-238534
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.ddms,16.0.1.v201112150204-238534
No repository found containing: osgi.bundle,com.android.ide.eclipse.hierarchyviewer,16.0.1.v201112150204-238534
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.hierarchyviewer,16.0.1.v201112150204-238534
No repository found containing: osgi.bundle,com.android.ide.eclipse.traceview,16.0.1.v201112150204-238534
No repository found containing: org.eclipse.update.feature,com.android.ide.eclipse.traceview,16.0.1.v201112150204-238534
No repository found containing: osgi.bundle,overlay.com.android.ide.eclipse.adt.overlay,16.0.1.v201112150204-238534

便宜的.com域名购买教程,Godaddy和Namecheap哪个更好更便宜?

这几天在整理域名,以前都是在Godaddy上注册,去年在朋友推荐下,把Godaddy上的域名基本转移到了NameCheap,原因是Godaddy很多优惠码不支持国内信用卡,新注册域名很便宜,但是续费比较贵,也没什么优惠,基本就是$14.99刀,还有就是Godaddy的管理界面实在是难看。

去年开始陆陆续续把Godaddy上的域名转移到了NameCheap,NameCheap的界面虽然也不怎么样(现在已经改善了很多),但是价格比Godaddy有一定优势,新注册和续费都是在$10.69刀左右,最重要的是免费赠送WhoisGuard(域名信息隐私保护)功能,要知道在Godaddy,这一项服务一年要$7.99刀>_< 如今转移到NameCheap的域名渐渐又要续费了,这时候我才发现,NameCheap的所谓免费WhoisGuard只有一年时间,也就是说续费的时候如果想继续保护隐私,就要再多掏$2.89刀,虽然比Godaddy的$7.99刀厚道很多,但多少有点被骗了的感觉。 如果说价格的话,还是阿里云万网比较合适,但是域名放在国内毕竟还是不太放心,而且听说将来国内连域名都要实名认证,我去年买了块表……最近有一个叫NameSilo的域名注册商建议大家关注一下,新注册和续费的价格比较便宜,不到$9刀,比NameCheap还便宜一两刀,而且即使续费的情况下,WhoisGuard和Protection服务也是免费的,可谓是非常厚道,也支持支付宝支付,安全省心,目前唯一缺点就是界面不是怎么好看,期待后续改进。

拒绝流氓!百度安全卫士BaiduProtect.exe的彻底卸载删除方法

最近电脑三天两头被流氓软件骚扰,上周末从公司走的时候忘关机了,结果周一来到公司就发现电脑被安装了“百度安全卫士”,我记得我两个月前曾经卸载过它,没想到它还真顽固!

首先我到控制面板里选择了卸载删除,然后查看进程,居然“BaiduProtect.exe”还在,而且无法杀掉,哎,无奈重启。重启后发现“BaiduProtect.exe”还是依然存在,依然无法杀掉,而且还多了个小兄弟叫“bddownloader.exe”,我了个去,这不是要把“百度安全卫士”这只流氓再请回来的节奏嘛,哎。。。

查看“BaiduProtect.exe”的位置(C:\\Program Files\Common Files\Baidu\BaiduProtect1.*),居然和“那个百度安全卫士”不是一个软件,它叫“百度安全组件”,而且在控制面板未提供任何卸载程序!无奈在文件夹内把它卸载了。。。10年前杀毒软件收费,现在不光免费了,而且还强制登门“保护”,愿意只有一个,争夺用户,窃取隐私,搞他们的bigdata!

修复“briefly unavailable for scheduled maintenance. check back in a minute”报错

刚才更新wordpress的时候,不小心关了浏览器,再打开就出现了“briefly unavailable for scheduled maintenance. check back in a minute”这个错误,查了一下原来是wordpress在更新的时候会在程序根目录生成一个“.maintenance”文件,打开文件一看,原来是定义了一个时间戳变量,我觉得这个应该是wordpress用来锁住整个网站,为了防止更新中途中断,而用一个时间戳来判断是否更新超时,果然等了10分钟吧,再次刷新博客就自动可以运行了,进入后台提示继续更新,很不错的功能~

HTML4默认CSS样式标准

一下内容摘自w3官网,只是鼓励使用,当然不是强制标准,仅供参考

html, address,
blockquote,
body, dd, div,
dl, dt, fieldset, form,
frame, frameset,
h1, h2, h3, h4,
h5, h6, noframes,
ol, p, ul, center,
dir, hr, menu, pre   { display: block; unicode-bidi: embed }
li              { display: list-item }
head            { display: none }
table           { display: table }
tr              { display: table-row }
thead           { display: table-header-group }
tbody           { display: table-row-group }
tfoot           { display: table-footer-group }
col             { display: table-column }
colgroup        { display: table-column-group }
td, th          { display: table-cell }
caption         { display: table-caption }
th              { font-weight: bolder; text-align: center }
caption         { text-align: center }
body            { margin: 8px }
h1              { font-size: 2em; margin: .67em 0 }
h2              { font-size: 1.5em; margin: .75em 0 }
h3              { font-size: 1.17em; margin: .83em 0 }
h4, p,
blockquote, ul,
fieldset, form,
ol, dl, dir,
menu            { margin: 1.12em 0 }
h5              { font-size: .83em; margin: 1.5em 0 }
h6              { font-size: .75em; margin: 1.67em 0 }
h1, h2, h3, h4,
h5, h6, b,
strong          { font-weight: bolder }
blockquote      { margin-left: 40px; margin-right: 40px }
i, cite, em,
var, address    { font-style: italic }
pre, tt, code,
kbd, samp       { font-family: monospace }
pre             { white-space: pre }
button, textarea,
input, select   { display: inline-block }
big             { font-size: 1.17em }
small, sub, sup { font-size: .83em }
sub             { vertical-align: sub }
sup             { vertical-align: super }
table           { border-spacing: 2px; }
thead, tbody,
tfoot           { vertical-align: middle }
td, th, tr      { vertical-align: inherit }
s, strike, del  { text-decoration: line-through }
hr              { border: 1px inset }
ol, ul, dir,
menu, dd        { margin-left: 40px }
ol              { list-style-type: decimal }
ol ul, ul ol,
ul ul, ol ol    { margin-top: 0; margin-bottom: 0 }
u, ins          { text-decoration: underline }
br:before       { content: "\A"; white-space: pre-line }
center          { text-align: center }
:link, :visited { text-decoration: underline }
:focus          { outline: thin dotted invert }

/* Begin bidirectionality settings (do not change) */
BDO[DIR="ltr"]  { direction: ltr; unicode-bidi: bidi-override }
BDO[DIR="rtl"]  { direction: rtl; unicode-bidi: bidi-override }

*[DIR="ltr"]    { direction: ltr; unicode-bidi: embed }
*[DIR="rtl"]    { direction: rtl; unicode-bidi: embed }

@media print {
  h1            { page-break-before: always }
  h1, h2, h3,
  h4, h5, h6    { page-break-after: avoid }
  ul, ol, dl    { page-break-before: avoid }
}

WebGL教程:第16课,渲染到纹理

欢迎来到系列教程的第16课!在这一课中,我们将介绍一种非常有用的绘制技术: 将3D场景渲染到一张纹理中。通过这一技术,我们可以在绘制过程中利用上一次绘制的结果来创造一些特殊的效果。同时,这也是一种常用的绘制技巧,除了在一个场景中绘制另一个场景(本课将详细解释这种应用途径)外,渲染到纹理也是做选取(鼠标在3D场景中点选物体)、引用、反射等3D特效的基础技术。

下面的视频就是我们这节课将会完成的最终效果。

点击这里打开一个独立的WebGL页面,如果你的浏览器不支持WebGL,请点击这里

在本课的演示程序中,你会看到一个综合了多种光照效果(包含笔记本屏幕上的高光)的白色笔记本模型。除了笔记本模型,你会发现一些更加有趣的东西,在笔记本的屏幕上显示了另外一个3D场景,对,没错,那是13课中我们看过的月球和木箱。这个演示程序的思路很清楚,它将13课中的3D场景渲染到了一张2D纹理上,然后将这张2D纹理映射到了笔记本模型的屏幕上。

下面我们来看看它是怎么工作的……

惯例声明:本系列的教程是针对那些已经具备相应编程知识但没有实际3D图形经验的人的;目标是让学习者创建并运行代码,并且明白代码其中的含义,从而可以快速地创建自己的3D Web页面。如果你还没有阅读前一课,请先阅读前一课的内容吧。因为本课中我只会讲解那些与前一课中不同的新知识。

另外,我编写这套教程是因为我在独立学习WebGL,所以教程中可能(非常可能)会有错误,所以还请风险自担。尽管如此,我还是会不断的修正bug和改正其中的错误的,所以如果你发现了教程中的错误,请告诉我。

有两种方法可以获得上面实例的代码:在实例的独立页面中选择“查看源代码”;你也可以点击这里,下载我们为您打包好的压缩包。

你可以使用任何你所熟悉的文本编辑器来查看课程源码(index.html)。本课内容的文件组织与前几课有较大的不同,下面让我们按由下向上的顺序来查看代码内容。首先是webGLStart,发生变化的代码位于第789行。

  function webGLStart() {
    var canvas = document.getElementById("lesson16-canvas");
    initGL(canvas);
    initTextureFramebuffer();
    initShaders();
    initBuffers();
    initTextures();
    loadLaptop();

    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);

    tick();
  }

这段代码看起来并没有什么特别的地方,初始化WebGL,加载Shader,创建用于绘制的顶点Buffer,加载用于月亮和木箱的纹理,及像14课中加载茶壶模型一样,加载JSON格式的笔记本模型文件。在这中间,唯一所不同的,就是我们额外为纹理创建了一个framebuffer。在继续解释代码内容之前,让我们首先来了解一下什么是frame buffer。

当你使用WebGL API渲染3D内容时,显卡需要一块缓冲区来存储渲染的最终结果。通过WebGL 接口,你实际上可以控制这块存储区域的结构类型。首先,你至少需要一块区域来存储每个像素渲染后的颜色结果,同时,你往往还需要一个depth buffer(深度缓冲区)来处理视线遮挡,这样,近处的像素将会遮挡住较远处的像素,depth buffer同样需要一部分存储空间。除此之外,有时我们也会用到一些其它种类的buffer,比如stencil buffer(模板缓冲区),在接下来的系列教程中,我们会讲到它。

Framebuffer就是用来存储渲染结果的这一类缓冲区的一种集合。如果你没有指定,默认会存在一个”Default” frame buffer,也就是到本课之前,我们一直在使用的frame buffer,它代表最终会被显示在网页中的缓冲区域。除此之外,你可以创建你自己的frame buffer,并指定WebGL将渲染结果输送到你所创建的frame buffer中。在本课中,我们创建了一个自己的frame buffer,并指定它使用一张纹理作为存储像素颜色的缓冲区。同时,我们也需要分配一个深度缓冲区来做深度遮挡计算。

下面,让我们来看一下创建frame buffer的具体代码。在本课的实例中,initTextureFramebuffer函数完成了这个工作,如果你需要查找这个函数,它大概在文件中从上向下浏览的三分之一处。

  var rttFramebuffer;
  var rttTexture;

  function initTextureFramebuffer() {

在函数之前,我们定义了两个全局变量,rttFramebuffer用来存储将被渲染到笔记本屏幕的frame buffer,而rttTexture用来存储容纳笔记本屏幕渲染结果的纹理(rttTexture在我们绘制整个笔记本场景时会使用到)。下面,继续看这个函数接下来的部分。

    rttFramebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
    rttFramebuffer.width = 512;
    rttFramebuffer.height = 512;

第一步是创建一个frame buffer,然后的操作流程很标准,就像纹理和vertex attribute buffer一样,我们将frame buffer指定为WebGL的当前frame buffer。当前frame buffer的含义就是其后的对frame buffer的操作都将作用在当前frame buffer上。此外,我们将笔记本屏幕的绘制尺寸储存在了我们所创建的frame buffer中,注意,这两个尺寸属性并不是frame buffer的固有属性,我只是利用了javascript动态为对象添加属性的技巧,因为在后续对frame buffe的使用中,我们需要这个信息。我们选择绘制一个512×512 的frame buffer,WebGL的只能创建2的整数幂次大小的纹理,而对于我们的应用环境,256×256则会使得画面出现模糊,而更高的1024×1024则不会对提高画面精细度有太大的帮助。

接下来,我们创建一个纹理对象,并初始化相应的参数。

    rttTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, rttTexture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
    gl.generateMipmap(gl.TEXTURE_2D);

之后,我们调用gl.texImage2D来为纹理初始化数据,但在这里我们会发现一些与之前不同的地方。

      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, rttFramebuffer.width, rttFramebuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

通常,当我们想为texture加载一张图片时,我们会使用gl.texImage2D来为texture指定对应的Image,但在这里,我们并没有图片可用来加载,所以我们调用了gl.texImage2D 的另外一个版本,告诉WebGL我们并没有Image可供加载,我们仅仅需要创建一个指定大小的空数据的纹理对象。严格来说,这里的最后一个参数是用来指定传入纹理的像素列表,但在这里,我们不需要纹理对象加载任何数据,所以在这里,我们传入null。(早期的Minefield 需要使用者传入一个指定大小的空数组来初始化纹理,但这个问题目前似乎已经被修复掉了)

现在我们拥有了一张用来存储颜色结果的纹理,接下来,我们需要创建一张depth buffer用来记录深度信息。

    var renderbuffer = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rttFramebuffer.width, rttFramebuffer.height);

这里我们创建了一个render buffer对象,render buffer表示用来存储与frame buffer相关的广义用途的缓冲区,你可以用它来作为深度缓冲,或者模板缓冲,或者两者兼之。像其它缓冲区对象一样,我们绑定render buffer,将它作为WebGL的当前render buffer,并调用gl.renderbufferStorage来通知WebGL当前render buffer用作深度缓冲区,每个像素的深度数据大小16位,同时我们还指定了render buffer的尺寸。

接下来:

    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttTexture, 0);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);

我们将创建的纹理和render buffer都绑定到当前的frame buffer上(不要忘记,在frame buffer被创建后,我们就一直将它作为WebGL的当前frame buffer)。这里,我们告诉WebGL当前frame buffer使用rttTexture作为颜色缓冲区(gl.COLOR_ATTACHMENT0),使用我们创建的render buffer作为深度缓冲区(gl.DEPTH_ATTACHMENT)。

到此为止,frame buffer的内容初始化工作就完成了,WebGL已经知道我们所创建的frame buffer该绘制到什么地方去;所以,在这之后,我们将当前的texture、renderbuffer和framebuffer重置为默认状态。

    gl.bindTexture(gl.TEXTURE_2D, null);
    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  }

现在我们有了一个自定义的frame buffer,那如何去使用它呢? 让我们跳到drawScene函数来看一下,这个函数在文件的底部。在函数的最开始处,在正常的视口设置等初始化工作之前,你应该看到了一些新东西。

  var laptopAngle = 0;

  function drawScene() {
    gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
    drawSceneOnLaptopScreen();

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

    mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);

函数的第一行,非常容易理解,我们将当前frame buffer从默认frame buffer切换到之前在initTextureFramebuffer函数中所创建的frame buffer,这样,后续的绘制工作将被导向我们所设置的纹理和深度缓冲,而不再是最终的网页。之后,我们调用drawSceneOnLaptopScreen函数,将需要显示在笔记本屏幕上的3D场景绘制到指定的当前frame buffer中,绘制结束后,当前frame buffer被切换后默认状态。在继续接下来的学习之前,我认为读者有必要了解一下drawSceneOnLaptopScreen这个函数的内容,我不会在这里列出它们,因为所涉及的代码非常简单,基本是第13课中drawscene函数的简化版。这是因为我们之前的绘制代码并没有设置特定的frame buffer,而仅仅是将所有内容绘制到当前frame buffer上,这里与13课中唯一的不同就是我们去掉了可移动光源等在本课中并不需要的场景元素。

所以,当drawScene函数开头的三行代码执行后,我们就拥有了一张存储着第13课场景绘制结果的纹理。绘制代码的剩余部分就是正常绘制出笔记本模型,并将之前存储着场景绘制结果的纹理映射到笔记本模型的屏幕上。首先是初始化model-view矩阵,并将笔记本旋转一定的角度,角度由laptopAngle给出(像之前的课程一样,laptopAngle每帧都会在animate函数中更新来实现笔记本模型的持续旋转效果)。

    mat4.identity(mvMatrix);

    mvPushMatrix();

    mat4.translate(mvMatrix, [0, -0.4, -2.2]);
    mat4.rotate(mvMatrix, degToRad(laptopAngle), [0, 1, 0]);
    mat4.rotate(mvMatrix, degToRad(-90), [1, 0, 0]);

之后,在下面的代码中,我们将场景中光源的颜色和位置等信息传给显卡。

    gl.uniform1i(shaderProgram.showSpecularHighlightsUniform, true);
    gl.uniform3f(shaderProgram.pointLightingLocationUniform, -1, 2, -1);

    gl.uniform3f(shaderProgram.ambientLightingColorUniform, 0.2, 0.2, 0.2);
    gl.uniform3f(shaderProgram.pointLightingDiffuseColorUniform, 0.8, 0.8, 0.8);
    gl.uniform3f(shaderProgram.pointLightingSpecularColorUniform, 0.8, 0.8, 0.8);

接下来,我们需要将笔记本的光照材质信息传入显卡,这里有一些之前没有出现过的新内容,但它们与渲染到纹理技术本身并没有关系。你也许还记得,在第7课中,当我们描述Phong光照算法的时候,我提到了材质信息对应不同的光照类型有不同的颜色信息:ambient color(环境反射光),diffuse colour(漫反射光),specular colour(镜面反射光/高光)。在之前的课程中,我们始终假设所有的材质颜色都是纯白色,如果有纹理,我们则取纹理颜色作为所有材质属性的颜色,但在本课中,这种简化后的光照方式将不再试用,原因你将在稍后的文章中看到。在这里,我们需要为材质的每一种属性信息设置单独的数据,同时,我们还引入了一种新的材质属性:emissive colour(自发光)。但是不用担心,对于笔记本模型来说,这个设置过程非常简单,因为笔记本本身是白色的。

    // The laptop body is quite shiny and has no texture.  It reflects lots of specular light
    gl.uniform3f(shaderProgram.materialAmbientColorUniform, 1.0, 1.0, 1.0);
    gl.uniform3f(shaderProgram.materialDiffuseColorUniform, 1.0, 1.0, 1.0);
    gl.uniform3f(shaderProgram.materialSpecularColorUniform, 1.5, 1.5, 1.5);
    gl.uniform1f(shaderProgram.materialShininessUniform, 5);
    gl.uniform3f(shaderProgram.materialEmissiveColorUniform, 0.0, 0.0, 0.0);
    gl.uniform1i(shaderProgram.useTexturesUniform, false);

下一步,如果笔记本模型的顶点数据已经加载完毕,那就将它绘制出来,这部分代码读者应该已经非常熟悉了,特别是在阅读过14课之后(实际上,大部分是从第14课的内容中复制过来的)。

    if (laptopVertexPositionBuffer) {
      gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexPositionBuffer);
      gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, laptopVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

      gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexTextureCoordBuffer);
      gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, laptopVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

      gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexNormalBuffer);
      gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, laptopVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);

      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, laptopVertexIndexBuffer);
      setMatrixUniforms();
      gl.drawElements(gl.TRIANGLES, laptopVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
    }

上面的代码已经将笔记本模型绘制在了网页上,下面,我们需要将纹理映射到笔记本的屏幕上。首先依然是需要设置光照参数,在这里,我们设置了不同的emissive colour。

    gl.uniform3f(shaderProgram.materialAmbientColorUniform, 0.0, 0.0, 0.0);
    gl.uniform3f(shaderProgram.materialDiffuseColorUniform, 0.0, 0.0, 0.0);
    gl.uniform3f(shaderProgram.materialSpecularColorUniform, 0.5, 0.5, 0.5);
    gl.uniform1f(shaderProgram.materialShininessUniform, 20);
    gl.uniform3f(shaderProgram.materialEmissiveColorUniform, 1.5, 1.5, 1.5);
    gl.uniform1i(shaderProgram.useTexturesUniform, true);

下面,我们需要设置用于采样的纹理,也就是之前存储着第一次绘制结果的纹理对象。

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, rttTexture);
    gl.uniform1i(shaderProgram.samplerUniform, 0);

最后,绘制笔记本屏幕。

    setMatrixUniforms();
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, laptopScreenVertexPositionBuffer.numItems);

    mvPopMatrix();
  }

整个过程是不是有些由繁入简的感觉呢? :) 上面就是实现渲染到纹理,并将纹理用作其它绘制的全部代码。

在本文的最后,我们给出了一个用于新材质类型光照的fragment shader,在理解之前课程的基础上,新shader的内容应该非常容易理解。这里唯一的新内容就是增加了emissive colour,而shader对它的处理仅仅的将它简单的加在最终的像素颜色上:

  precision mediump float;

  varying vec2 vTextureCoord;
  varying vec3 vTransformedNormal;
  varying vec4 vPosition;

  uniform vec3 uMaterialAmbientColor;
  uniform vec3 uMaterialDiffuseColor;
  uniform vec3 uMaterialSpecularColor;
  uniform float uMaterialShininess;
  uniform vec3 uMaterialEmissiveColor;

  uniform bool uShowSpecularHighlights;
  uniform bool uUseTextures;

  uniform vec3 uAmbientLightingColor;

  uniform vec3 uPointLightingLocation;
  uniform vec3 uPointLightingDiffuseColor;
  uniform vec3 uPointLightingSpecularColor;

  uniform sampler2D uSampler;

  void main(void) {
    vec3 ambientLightWeighting = uAmbientLightingColor;

    vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
    vec3 normal = normalize(vTransformedNormal);

    vec3 specularLightWeighting = vec3(0.0, 0.0, 0.0);
    if (uShowSpecularHighlights) {
      vec3 eyeDirection = normalize(-vPosition.xyz);
      vec3 reflectionDirection = reflect(-lightDirection, normal);

      float specularLightBrightness = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess);
      specularLightWeighting = uPointLightingSpecularColor * specularLightBrightness;
    }

    float diffuseLightBrightness = max(dot(normal, lightDirection), 0.0);
    vec3 diffuseLightWeighting = uPointLightingDiffuseColor * diffuseLightBrightness;

    vec3 materialAmbientColor = uMaterialAmbientColor;
    vec3 materialDiffuseColor = uMaterialDiffuseColor;
    vec3 materialSpecularColor = uMaterialSpecularColor;
    vec3 materialEmissiveColor = uMaterialEmissiveColor;
    float alpha = 1.0;
    if (uUseTextures) {
      vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
      materialAmbientColor = materialAmbientColor * textureColor.rgb;
      materialDiffuseColor = materialDiffuseColor * textureColor.rgb;
      materialEmissiveColor = materialEmissiveColor * textureColor.rgb;
      alpha = textureColor.a;
    }
    gl_FragColor = vec4(
      materialAmbientColor * ambientLightWeighting
      + materialDiffuseColor * diffuseLightWeighting
      + materialSpecularColor * specularLightWeighting
      + materialEmissiveColor,
      alpha
    );
  }

到此为止,本课的所有内容就结束了。在这一课中,我们学习到了如何将一个场景渲染到一张纹理中,并在另一个场景中使用这张纹理,同时,我们也深入了解了材质属性的细节和它们的工作原理。在下一课中,我们将给出一个渲染到纹理的实际应用:3D场景中的鼠标点选,这个功能可以让用户通过鼠标点击与3D场景中物体进行直接交互。

WebGL教程:第15课,高光贴图

欢迎来到系列教程的第15课!在这一课中,我们将介绍高光贴图技术。就像普通纹理用来指定物体表面的颜色一样,高光贴图可以用来指定物体表面每一处细节的光照反射程度,因此可以大大增强物体的真实感。本课中并没有太多新增的代码, 只会在前几课基础上进行简单的改动,但从意义上来讲,本课将引入一种全新的概念。

下面的视频就是我们这节课将会完成的最终效果。

点击这里打开一个独立的WebGL页面,如果你的浏览器不支持WebGL,请点击这里

在本课的演示程序中,你应当会看到一个旋转的地球,地球表面有从太阳反射出的光斑。如果你仔细观察,你会发现高光反射只存在于地球表面的海洋部分,而对于陆地部分,正像你预想的那样,不会反射太阳的光照。

如果你尝试取消“开启高光贴图”选项(此选项位于canvas的下方)。你会发现高光反射就会出现在陆地上,这种效果非常不真实,看起来像是一个超大的聚光灯正在对着地球照射。这就是高光贴图的作用, 它可以允许你精确的控制物体表面的反光细节。

重新打开“开启高光贴图”选项,并降低光源的漫反射强度, 比如将漫反射强度设为(0.5,0.5,0.5), 之后, 再关掉高光贴图, 你会发现地球表面的颜色纹理已经几乎看不到了,但是高光却没有变化,依然同时出现在海洋和陆地区域。

下面我们来看看它是怎么工作的……

惯例声明:本系列的教程是针对那些已经具备相应编程知识但没有实际3D图形经验的人的;目标是让学习者创建并运行代码,并且明白代码其中的含义,从而可以快速地创建自己的3D Web页面。如果你还没有阅读前一课,请先阅读前一课的内容吧。因为本课中我只会讲解那些与前一课中不同的新知识。

另外,我编写这套教程是因为我在独立学习WebGL,所以教程中可能(非常可能)会有错误,所以还请风险自担。尽管如此,我还是会不断的修正bug和改正其中的错误的,所以如果你发现了教程中的错误,请告诉我。

有两种方法可以获得上面实例的代码:在实例的独立页面中选择“查看源代码”;你也可以点击这里,下载我们为您打包好的压缩包。

在解释代码细节之前,让我们先来了解一下背景知识。到目前为止,我们一直将纹理用作一种蒙在物体表面的图像。我们指定一张图像,并为每个顶点设定纹理坐标(纹理坐标标明顶点应该对应到纹理图像中的哪个位置),然后在fragment shader中, 从sampler中取出对应位置的纹理颜色, 并将它作为当前像素的颜色输出。

高光贴图则将我们上面对纹理的认识扩展了一下。一张纹理有R,G, B, A四个通道,在shader中, 每个通道都是一个float类型的标量值, 但并没有限定我们一定要将纹理数据当做颜色来对待。上一课中,我们了了解到,材质的反光度(shinness)也是一个float类型标量值,所以我们可以通过纹理来将物体表面的反光度属性赋给每一个对应的像素,就像我们使用纹理为像素赋颜色一样。

这就是我们在本课中使用的技巧,我们将两张纹理同时传给用于绘制地球的fragment shader,一张用来指定颜色,如下图:

另一张低分辨率的用来指定反光度:

上图只是一张普通的GIF图片, 是我在PAINT.NET中通过修改颜色纹理生成的。在本课中,我们假设RGB通道的反光度都是相同的, 同时, 我们需要选择一种颜色作为完全不反光的标志(按照我们的设计,如果需要得到反光度32,则纹理中会存储深灰色(32,32,32)),这里我们指定纯白色为完全不反光。

好了,需要预先解释的都已经说清楚了,下面我们来看代码。本课代码与14课内容只有非常微小的改动,而最主要的部分在fragment shader中,所以在这里我们主要看一下shader部分的内容:

第一个不同是shader多了两个新的uniform常量用来标明是否使用颜色和高光纹理 (第17、18行):

  precision mediump float;

  varying vec2 vTextureCoord;
  varying vec3 vTransformedNormal;
  varying vec4 vPosition;

  uniform bool uUseColorMap;
  uniform bool uUseSpecularMap;
  uniform bool uUseLighting;

接下来,是两个sampler,用于代表两张纹理。这里将颜色纹理重命名为uColorMapSampler,同时,增加了一个sampler用来表示高光贴图(第27、28行):

  uniform vec3 uAmbientColor;

  uniform vec3 uPointLightingLocation;
  uniform vec3 uPointLightingSpecularColor;
  uniform vec3 uPointLightingDiffuseColor;

  uniform sampler2D uColorMapSampler;
  uniform sampler2D uSpecularMapSampler;

下面,是常规的切换是否进行光照的代码,并计算法线和光照方向:

  void main(void) {
    vec3 lightWeighting;
    if (!uUseLighting) {
      lightWeighting = vec3(1.0, 1.0, 1.0);
    } else {
      vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
      vec3 normal = normalize(vTransformedNormal);

最后,就是实际处理高光贴图的代码。首先,我们定义了一个变量用来表示specular光照的权重;如果最终不应进行高光反射,则这个变量将被赋为0。

      float specularLightWeighting = 0.0;

接下来,开始处理材质的反光度部分。在这里,如果用户选择不使用高光贴图,我们就会默认将反光度初始化为32,否则,我们会从高光贴图中取出对应的反光数值,就像从颜色纹理中取出颜色一样。因为我们假设RGB通道的反光度相同(这就是为什么本课中的高光贴图看起来是一张灰度图),因此在这里我们只取高光贴图纹理的R通道数据,实际上,你从任何一个通道读取,结果都是一样的。

      float shininess = 32.0;
      if (uUseSpecularMap) {
        shininess = texture2D(uSpecularMapSampler, vec2(vTextureCoord.s, vTextureCoord.t)).r * 255.0;
      }

现在,你需要根据取出的反光度来判断是否需要进行反光,还记得吗?前文中我们提到将纯白色作为不进行反光的标志,因此在这里,我们将只对小于255的反光度进行实际的高光计算。

      if (shininess < 255.0) {
	

下面的代码和上一课中的高光计算基本相同,唯一的区别就是这里的反光度是从高光贴图中取出的,而不像之前那样从shader外部传入。

        vec3 eyeDirection = normalize(-vPosition.xyz);
        vec3 reflectionDirection = reflect(-lightDirection, normal);

        specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), shininess);
      }

最后,我们将所有类型的光照元素组合到一起,并使用这个结果作为物体颜色的权重。对于物体颜色,当用户选择使用颜色纹理,也就是uUseColorMap为true的时候,物体颜色是从颜色纹理中取出的,而当不使用颜色纹理时,我们默认认为物体颜色为纯白色。

      float diffuseLightWeighting = max(dot(normal, lightDirection), 0.0);
      lightWeighting = uAmbientColor
        + uPointLightingSpecularColor * specularLightWeighting
        + uPointLightingDiffuseColor * diffuseLightWeighting;
    }

    vec4 fragmentColor;
    if (uUseColorMap) {
      fragmentColor = texture2D(uColorMapSampler, vec2(vTextureCoord.s, vTextureCoord.t));
    } else {
      fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
    gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a);
  }

当你阅读到这里,你应该已经理解了实现高光贴图所需要的全部知识。除此之外,代码中还有一些其它的改动,但这些细小变化并不值得去细细研究。initShaders函数中添加了用于传输新uniform常量的代码,initTextures则需要加载两张新的纹理,而前一课中加载teapot的代码则被替换为11课中的initBuffers函数,drawScene的内容由绘制茶壶变为了绘制一个球形,同时用户交互UI和animate函数也有对应本课的改动。

That’s it!这就是本课的全部内容。在这一课中,你了解了怎样通过纹理来提供材质反光度所需要的数据,同时,其实你也可以通过纹理来提供其它的表面细节信息,比如,人们经常会用纹理来提供物体表面的法线信息,这样,可以在保持较低顶点数量的前提下,为模型增加一些表面细节,在未来课程中,对这一部分内容我们会特别提到。