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 实现AJAX加载XML并解析的脚本
Jul 25 Javascript
为jQuery.Treeview添加右键菜单的实现代码
Oct 22 Javascript
Javascript面象对象成员、共享成员变量实验
Nov 19 Javascript
javascript 弹出窗口中是否显示地址栏的实现代码
Apr 14 Javascript
基于jQuery实现的水平和垂直居中的div窗口
Aug 08 Javascript
微信小程序-拍照或选择图片并上传文件
Jan 06 Javascript
浅谈JS获取元素的N种方法及其动静态讨论
Aug 25 Javascript
vue 权限认证token的实现方法
Jul 17 Javascript
JavaScript 替换所有匹配内容及正则替换方法
Feb 12 Javascript
vue-cli3.0实现一个多页面应用的历奇经历记录总结
Mar 16 Javascript
详解vue父子组件状态同步的最佳方式
Sep 10 Javascript
解决VUE项目使用Element-ui 下拉组件的验证失效问题
Nov 07 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实现获取客户端IP并获取IP信息
2015/03/17 PHP
PHP5.2下preg_replace函数的问题
2015/05/08 PHP
分享3个php获取日历的函数
2015/09/25 PHP
Laravel开启跨域请求的方法
2019/10/13 PHP
JavaScript之编码规范 推荐
2012/05/23 Javascript
JavaScript实现的Tween算法及缓冲特效实例代码
2015/11/03 Javascript
每天一篇javascript学习小结(基础知识)
2015/11/10 Javascript
JavaScript原型及原型链终极详解
2016/01/04 Javascript
jQuery中ajax的load()与post()方法实例详解
2016/01/05 Javascript
分享一个插件实现水珠自动下落效果
2016/06/01 Javascript
jQuery代码性能优化的10种方法
2016/06/21 Javascript
Bootstrap零基础学习第一课之模板
2016/07/18 Javascript
bootstrapfileinput实现文件自动上传
2016/11/08 Javascript
微信小程序 template模板详解及实例
2017/02/21 Javascript
JS请求servlet功能示例
2017/06/01 Javascript
如何将 jQuery 从你的 Bootstrap 项目中移除(取而代之使用Vue.js)
2017/07/17 jQuery
react-navigation 如何判断用户是否登录跳转到登录页的方法
2017/12/01 Javascript
javascript使用正则实现去掉字符串前面的所有0
2018/07/23 Javascript
vue工程全局设置ajax的等待动效的方法
2019/02/22 Javascript
python实现数据库跨服务器迁移
2018/04/12 Python
Python爬虫之pandas基本安装与使用方法示例
2018/08/08 Python
flask框架json数据的拿取和返回操作示例
2019/11/28 Python
python输出第n个默尼森数的实现示例
2020/03/08 Python
在python中使用nohup命令说明
2020/04/16 Python
Python读取excel文件中带公式的值的实现
2020/04/17 Python
用HTML5制作一个简单的桌球游戏的教程
2015/05/12 HTML / CSS
Clarins娇韵诗美国官网:法国天然护肤品牌
2016/09/26 全球购物
ECOSUSI官网:女式皮革背包
2019/09/27 全球购物
JAVA程序设计笔试题面试题一套
2015/07/28 面试题
计算机应用专业推荐信
2013/11/13 职场文书
质量标语大全
2014/06/12 职场文书
小班下学期幼儿评语
2014/12/30 职场文书
综合办公室岗位职责
2015/04/11 职场文书
2016年度基层党建工作公开承诺书
2016/03/25 职场文书
nginx简单配置多个server的方法
2021/03/31 Servers
SpringBoot2 参数管理实践之入参出参与校验的方式
2021/06/16 Java/Android