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 相关文章推荐
jquery插件开发注意事项小结
Jun 04 Javascript
jquery中event对象属性与方法小结
Dec 18 Javascript
button没写type=button会导致点击时提交
Mar 06 Javascript
js实现简单的左右两边固定广告效果实例
Apr 10 Javascript
《JavaScript高级编程》学习笔记之object和array引用类型
Nov 01 Javascript
js实现(全选)多选按钮的方法【附实例】
Mar 30 Javascript
微信小程序 页面跳转如何实现传值
Apr 05 Javascript
vue组件中点击按钮后修改输入框的状态实例代码
Apr 14 Javascript
js使用i18n实现页面国际化的方法
May 09 Javascript
Vue2.0学习系列之项目上线的方法步骤(图文)
Sep 25 Javascript
详解Js里的for…in和for…of的用法
Mar 28 Javascript
javascript中的offsetWidth、clientWidth、innerWidth及相关属性方法
May 14 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对象类型判断
2008/08/27 PHP
PHP CURL CURLOPT参数说明(curl_setopt)
2013/09/30 PHP
destoon切换城市后实现logo旁边显示地区名称的方法
2014/08/21 PHP
PHP CURL 内存泄露问题解决方法
2015/02/12 PHP
Thinkphp5 微信公众号token验证不成功的原因及解决方法
2017/11/12 PHP
网页的标准,IMG不支持onload标签怎么办
2006/06/29 Javascript
js动态添加onload、onresize、onscroll事件(另类方法)
2012/12/26 Javascript
jquery 表格的增行删行实现思路
2013/03/21 Javascript
页面图片浮动左右滑动效果的简单实现案例
2014/02/10 Javascript
JQuery异步获取返回值中文乱码的解决方法
2015/01/29 Javascript
正则表达式优化JSON字符串的技巧
2015/12/24 Javascript
Vue.js组件tabs实现选项卡切换效果
2016/12/01 Javascript
js 中文汉字转Unicode、Unicode转中文汉字、ASCII转换Unicode、Unicode转换ASCII、中文转换
2016/12/06 Javascript
详解AngularJS ng-class样式切换
2017/06/27 Javascript
Angular在模板驱动表单中自定义校验器的方法
2017/08/09 Javascript
Vue-router 类似Vuex实现组件化开发的示例
2017/09/15 Javascript
vue使用 better-scroll的参数和方法详解
2018/01/25 Javascript
Vue.use源码学习小结
2018/06/20 Javascript
详释JavaScript执行环境与执行栈
2019/04/02 Javascript
vue实现随机验证码功能(完整代码)
2019/12/10 Javascript
微信小程序点击按钮动态切换input的disabled禁用/启用状态功能
2020/03/07 Javascript
Python如何快速实现分布式任务
2017/07/06 Python
Tornado 多进程实现分析详解
2018/01/12 Python
HTML5等待加载动画效果
2017/07/27 HTML / CSS
html5唤起app的方法
2017/11/30 HTML / CSS
英国最大线上综合鞋类商城:Office
2017/12/08 全球购物
英国天然宝石首饰购买网站:Gemondo Jewellery
2018/10/23 全球购物
俄罗斯品牌服装和鞋子的在线商店:KUPIVIP
2019/10/27 全球购物
目标责任书范本
2014/04/16 职场文书
怎样写离婚协议书
2014/09/10 职场文书
司法局群众路线教育实践活动整改措施思想汇报
2014/10/13 职场文书
优秀党务工作者先进事迹材料
2014/12/25 职场文书
公司费用报销管理制度
2015/08/04 职场文书
python引入其他文件夹下的py文件具体方法
2021/05/23 Python
MySQL into_Mysql中replace与replace into用法案例详解
2021/09/14 MySQL
Python中的 Set 与 dict
2022/03/13 Python