THREE.JS入门教程(3)着色器-下


Posted in Javascript onJanuary 24, 2013

译序
Three.js是一个伟大的开源WebGL库,WebGL允许JavaScript操作GPU,在浏览器端实现真正意义的3D。但是目前这项技术还处在发展阶段,资料极为匮乏,爱好者学习基本要通过Demo源码和Three.js本身的源码来学习。

.简介
这是WebGL着色器教程的后半部分,如果你没看过前一篇,阅读这一篇教程可能会使你感到困惑,建议你翻阅前面的教程。

上一篇结束的时候,我们在屏幕中央画了一个好看的粉红色的球体。现在我要开始创建一些更加有意思的东西了。

在这一篇教程中,我们会先花点时间来加入一个动画循环,然后是顶点attributes变量和一个uniform变量。我们还要加一些varying变量,这样顶点着色器就可以向片元着色器传递信息了。最终的结果是哪个粉红色的球体会从顶部开始向两侧“点燃”,然后作有规律的运动。这有一点迷幻,但是会帮助你对着色器中的三种变量有更好的了解:他们互相联系,实现了整个集合体。当然我们会在Three.js的框架中做这些。
1.模拟光照
让我们更新颜色吧,这样球体看起来就不会是个扁平晦暗的圆了。如果我们想看看Three.js是怎样处理光照的,我敢肯定你会发现这比我们需要的要复杂得多,所以我们先模拟光照吧。你应该浏览一下Three.js中那些奇妙的着色器,还有一些来自最近的一个 Chris Milk 和 Google, Rome 的WebGL项目。
回到着色器,我们要更新顶点着色器来向片元着色器传递顶点的法向量。利用一个varying变量:

// 创建一个varying变量vNormal,顶点着色器和片元着色器都包含了该变量 
varying vec3 vNormal; 
void main() { 
// 将vNormal设置为normal,后者是Three.js创建并传递给着色器的attribute变量 
vNormal = normal; 
gl_Position = projectionMatrix * 
modelViewMatrix * 
vec4(position, 1.0); 
}

在片元着色器中,我们将会创建一个相同变量名的变量,然后将法线向量和另一个表示来自右上方光线的向量点乘,并将结果作用于颜色。最后结果的效果有点像平行光。
// 和顶点着色器中一样的变量vNormal 
varying vec3 vNormal; 
void main() { 
// 定义光线向量 
vec3 light = vec3(0.5,0.2,1.0); 
// 确保其归一化 
light = normalize(light); 
// 计算光线向量和法线向量的点积,如果点积小于0(即光线无法照到),就设为0 
float dProd = max(0.0, dot(vNormal, light)); 
// 填充片元颜色 
gl_FragColor = vec4(dProd, // R 
dProd, // G 
dProd, // B 
1.0); // A 
}

使用点积的原因是:两个向量的点积表明他们有多么“相似”。如果两个向量都是归一化的,而且他们的方向一模一样,点积的值就是1;如果两个向量的方向恰巧完全相反,点积的值就是-1。我们所做的就是把点积的值拿来作用到光纤上,所以如果这个点在球体的右上方,点积的值就是1,也就是完全照亮了;而在另一边的点,获得的点积值接近0,甚至到了-1。我们将获得的任何负值都设置为0。当你将数据传入之后,你就会看到最基本的光照效果了。

下面是什么?我们会将顶点的坐标掺和进来。
2.Attribut变量
接下来我要通过Attribute变量为每一个顶点传递一个随机数,这个随机数被用来将顶点沿着法线向量推出去一段距离。新的结果有点像一个怪异的不规则物体,每次刷新页面物体都会随机变化。现在,他还不会动(后面我会让他动起来),但是几次刷新就可以很好地观察到,他的形状是随机的。
让我们开始为顶点着色器加入attribute变量吧:

attribute float displacement; 
varying vec3 vNormal; 
void main() { 
vNormal = normal; 
// 将随机数displacement转化为三维向量,这样就可以和法线相乘了 
vec3 newPosition = position + 
normal * vec3(displacement); 
gl_Position = projectionMatrix * 
modelViewMatrix * 
vec4(newPosition, 1.0); 
}

你看到什么都没变,因为attribute变量displacement还没有被设定你,所以着色器就使用了0作为默认值。这时displacement还没起作用,但我们马上就要在着色器材质中加上attribute变量了,然后Three.js就会自动地把它们绑在一起运行了。

同时也要注意这样一个事实,我将更新后的位置指定给了一个新的三维向量变量,因为原来的位置变量position,就像所有的attribute变量一样,都是只读的。
3.更新着色器材质
现在我们来更新着色器材质,传入一些东西给attribute对象displacement。记住,attribute对象是和顶点一一对应的,所以我们对球体的每一个顶点都有一个值,就像这样:

var attributes = { 
displacement: { 
type: 'f', // 浮点数 
value: [] // 空数组 
} 
}; 
var vShader = $('#vertexshader'); 
var fShader = $('#fragmentshader'); 
// 创建一个包含attribute属性的着色器材质 
var shaderMaterial = 
new THREE.MeshShaderMaterial({ 
attributes: attributes, 
vertexShader: vShader.text(), 
fragmentShader: fShader.text() 
}); 
// 向displacement中填充随机数 
var verts = sphere.geometry.vertices; 
var values = attributes.displacement.value; 
for(var v = 0; v < verts.length; v++) { 
values.push(Math.random() * 30); 
}

这样,就可以看到一个变形的球体了。最Cool的是:所有这些变形都是在GPU中完成的。
4.动起来
要使这东西动起来,应该怎么做?好吧,应该做这两件事情。
一个uniform变量amplitude,在每一帧控制displacement实际造成了多少位移。我们可以使用正弦或余弦函数来在每一帧中生成它,因为这两个函数的取值范围从-1到1。
一个帧循环。

我们需要将这个uniform变量加入到着色器材质中,同时也需要加入到顶点着色器中。先来看顶点着色器:

uniform float amplitude; 
attribute float displacement; 
varying vec3 vNormal; 
void main() { 
vNormal = normal; 
// 将displacement乘以amplitude,当我们在每一帧中平滑改变amplitude时,画面就动起来了 
vec3 newPosition = 
position + 
normal * 
vec3(displacement * 
amplitude); 
gl_Position = projectionMatrix * 
modelViewMatrix * 
vec4(newPosition, 1.0); 
}

然后更新着色器材质:
var uniforms = { 
amplitude: { 
type: 'f', // a float 
value: 0 
} 
}; 
var vShader = $('#vertexshader'); 
var fShader = $('#fragmentshader'); 
// 创建最终的着色器材质 
var shaderMaterial = 
new THREE.MeshShaderMaterial({ 
uniforms: uniforms, 
attributes: attributes, 
vertexShader: vShader.text(), 
fragmentShader: fShader.text() 
});

我们的着色器也已经就绪了。但我们好像又倒退了一步,屏幕中又只剩下光滑的球了。别担心,这是因为amplitude值设置为0,因为我们将amplitude乘上了displacement,所以现在看不到任何变化。我们还没设置循环呢,所以amplitude只可能是0.

在我们的JavaScript中,需要将渲染过程打包成一个函数,然后用requestAnimationFrame去调用该函数。在这个函数里,我们更新uniform(译者注:即amplitude)的值。

var frame = 0; 
function update() { 
// amplitude来自于frame的正弦值 
uniforms.amplitude.value = 
Math.sin(frame); 
// 更新全局变量frame 
frame += 0.1; 
renderer.render(scene, camera); 
// 指定下一次屏幕刷新时,调用update 
requestAnimFrame(update); 
} 
requestAnimFrame(update);

5.小结
就是它了!你看到球体正在奇怪地脉动着。关于着色器,还有太多的内容没有讲到呢,但是我希望这篇教程能够对你有一些帮助。现在,当你看到一些其他的着色器时,我希望你能够理解它们,而且你应该有信心去创建自己的着色器了!

和往常一样,我将这一课的源码打包了

Javascript 相关文章推荐
自己的js工具 Cookie 封装
Aug 21 Javascript
瀑布流布局代码一例
Apr 11 Javascript
JavaScript实现打开链接页面的方式汇总
Jun 02 Javascript
多种jQuery绑定事件的实现方式
Jun 13 Javascript
原生js实现addclass,removeclass,toggleclasss实例
Nov 24 Javascript
JS作用域深度解析
Dec 29 Javascript
原生js实现手风琴功能(支持横纵向调用)
Jan 13 Javascript
react实现pure render时bind(this)隐患需注意!
Mar 09 Javascript
BootStrap Table前台和后台分页对JSON格式的要求
Jun 28 Javascript
Bootstrap 模态框自定义点击和关闭事件详解
Aug 10 Javascript
详解如何在webpack中做预渲染降低首屏空白时间
Aug 22 Javascript
JavaScript 中for/of,for/in 的详细介绍
Nov 17 Javascript
THREE.JS入门教程(2)着色器-上
Jan 24 #Javascript
THREE.JS入门教程(1)THREE.JS使用前了解
Jan 24 #Javascript
(跨浏览器基础事件/浏览器检测/判断浏览器)经验代码分享
Jan 24 #Javascript
jQuery ajax(复习)—Baidu ajax request分离版
Jan 24 #Javascript
javascript游戏开发之《三国志曹操传》零部件开发(五)可移动地图的实现
Jan 23 #Javascript
javascript游戏开发之《三国志曹操传》零部件开发(四)用地图块拼成大地图
Jan 23 #Javascript
javascript游戏开发之《三国志曹操传》零部件开发(三)情景对话中仿打字机输出文字
Jan 23 #Javascript
You might like
关于PHP实现异步操作的研究
2013/02/03 PHP
调试PHP程序的多种方法介绍
2014/11/06 PHP
PHP实现下载断点续传的方法
2014/11/12 PHP
php+javascript实现的动态显示服务器运行程序进度条功能示例
2017/08/07 PHP
PHP简单实现二维数组的矩阵转置操作示例
2017/11/24 PHP
不用ajax实现点击文字即可编辑的方法
2007/12/16 Javascript
javascript EXCEL 操作类代码
2009/07/30 Javascript
Javascript结合css实现网页换肤功能
2009/11/02 Javascript
ASP.NET jQuery 实例15 通过控件CustomValidator验证CheckBoxList
2012/02/03 Javascript
详解JavaScript中的4种类型识别方法
2015/09/14 Javascript
举例说明如何为JavaScript的方法参数设置默认值
2015/11/17 Javascript
基于bootstrap插件实现autocomplete自动完成表单
2016/05/07 Javascript
JavaScript基于对象去除数组重复项的方法
2016/10/09 Javascript
JavaScript定义全局对象的方法示例
2017/01/12 Javascript
jQuery插件HighCharts实现气泡图效果示例【附demo源码】
2017/03/13 Javascript
Nodejs进阶:express+session实现简易登录身份认证
2017/04/24 NodeJs
详解A标签中href=&quot;&quot;的几种用法
2017/08/20 Javascript
webpack手动配置React开发环境的步骤
2018/07/02 Javascript
element-ui中的select下拉列表设置默认值方法
2018/08/24 Javascript
在Vant的基础上实现添加表单验证框架的方法示例
2018/12/05 Javascript
微信小程序实现3D轮播图效果(非swiper组件)
2019/09/21 Javascript
[01:04:05]Mineski vs TNC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
用Python的urllib库提交WEB表单
2009/02/24 Python
使用urllib库的urlretrieve()方法下载网络文件到本地的方法
2018/12/19 Python
对Python3 解析html的几种操作方式小结
2019/02/16 Python
python爬虫爬取笔趣网小说网站过程图解
2019/11/18 Python
Python 静态方法和类方法实例分析
2019/11/21 Python
HMV日本官网:全球知名的音乐、DVD和电脑游戏零售巨头
2016/08/13 全球购物
世界上获奖最多的手机镜头:Olloclip
2018/03/03 全球购物
Glamest意大利:女性在线奢侈品零售店
2019/04/28 全球购物
写给医生的感谢信
2015/01/22 职场文书
检讨书格式
2015/05/07 职场文书
手术室消毒隔离制度
2015/08/05 职场文书
课题研究阶段性总结
2015/08/13 职场文书
2016保送生自荐信范文
2016/01/29 职场文书
win10电脑右下角输入法图标不见了?Win10右下角不显示输入法的解决方法
2022/07/23 数码科技