OPPO新机R11s的虚拟华为p10导航键失灵怎么用?

6903人阅读
WebGL(26)
注:文章译自,原作者杉本雅広(doxas),文章中如果有我的额外说明,我会加上[lufy:],另外,鄙人webgl研究还不够深入,一些专业词语,如果翻译有误,欢迎大家指正。WebGL与纹理上次介绍了用点光源的光来进行补色着色的方法。在片段着色器中对光进行计算,阴影,亮点等效果都非常的漂亮,3D场景的真实度大幅度提升。并且能和顶点颜色一起使用,理解了前面讲解的内容之后,就应该能进行比较高质量的3D渲染了。这一次,来看高级一点的纹理的使用。所谓纹理,简单一点说,就是可以放到多边形上的图片数据,在WebGL中当然也可以使用。WebGL和HTML不同,一般的图片类型(gif,jpg,png等)是不可以直接使用的,另外,也可以把canvas转换成纹理,总之,要变换一下方法来进行渲染。这一次,先来看一下利用纹理的最基本的绘制方法。WebGL中纹理的限制上面说了,在HTML中使用的一般类型的图片变换成纹理之后,就可以在WebGL中使用了。意思就是说要把网页中使用的图片数据变成WebGL中可以使用的形式。但是,WebGL中的纹理需要注意一点,所使用的图片数据的大小必须是2的阶乘,横竖的像素长度大小必须是32x32,128x128等2的阶乘的形式。当然,做一些处理的话,不是2的阶乘的图片数据也是可以用的,但是基本上作为纹理使用的图像数据的大小必须是2的阶乘。另外,看一下普通的网页就能感觉到,网页上的图片数据的读取是要花一点时间的,在进行纹理转换的话,必须是在图片读取完之后才行,这里需要做一些特殊的处理,如果对javascript不太熟悉的话可能会无从下手,这个后面会说。纹理的生成和使用那么,下面就开始说一说纹理使用的步骤吧。纹理在WebGL中要使用纹理对象来处理,生成纹理对象需要使用createTexture函数。&createTexture的使用例子var tex = gl.createTexture();这个函数没有参数,只是单纯的返回一个纹理对象,经过上面的代码,变量tex就是一个空的纹理对象了。生成纹理对象之后,接着要把纹理对象和WebGL进行绑定。大家想一想,之前在WebGL中使用缓存对象的时候也是需要进行和WebGL的绑定处理,比如使用VBO的时候,这次也和缓存一样,要进行绑定处理。要对纹理数据进行操作的时候,首先必须先进行绑定,然后使用操作纹理的一些函数,被绑定的纹理对象才能适用这些处理。把纹理数据和WebGL进行绑定的函数是bindTexture。&bindTexture的使用例子gl.bindTexture(gl.TEXTURE_2D, tex);这个函数需要两个参数。第一个参数是纹理的种类,绘制2D的图像类型的话,通常使用gl.TEXTURE_2D作为参数。第二个参数就是要绑定的纹理对象,这里还是挺简单的吧。虽然将纹理对象和WebGL进行了绑定,但是还没有把最核心的图像数据加进来,将图像数据和纹理进行连接的是texImage2D函数。&texImage2D的使用例子gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);看了上面的代码,一定会有”这是什么东东?“的感觉吧,这个函数一共接收六个参数,看起来复杂,其实挺简单的。第一个参数是bindTexture的时候也使用过的纹理的类型,这里也使用gl.TEXTURE_2D就行了。第二个参数纹理映射的等级,暂时不用考虑,设定成0就行了。接着,第三个参数和第四个参数中都指定了gl.RGBA,现阶段先不用考虑,直接这么用就行了。同样,第五个参数也是没什么特别的利用的话,先指定gl.UNSIGNED_BYTE就可以了。主要是,现阶段第一个到第五个参数先像上面这样设置就没问题了,最重要的是第六个参数,这里需要指定图像数据,这个第六个参数中的图像数据在这个时候分配给绑定的纹理。图片读取时间的考虑刚才也说了,使用texImage2D函数可以把图片数据接分配给纹理,但是网页中的图片的读取是要花一点时间的。这里需要注意的是,texImage2D函数被调用的时候,必须是图片已经读取完了之后,如果在图片读取完之前调用texImage2D,则无法正确的将图片数据分配给纹理。。这里需要在图片读取完后的事件中来调用。具体流程,就是先用javascript来生成图片对象,图片对象有onload事件可以监测到图片读取完成。在这里把纹理相关的处理做完,最后给图片对象指定图片地址开始读取图片。这里比较重要的一点是,在图片开始读取之前,要先添加onload事件,在事件中添加纹理相关的处理,这样,图片读取完成之后就可以自动进行纹理相关的这些处理了。上面说的这些处理,写成函数的话,就是下面这样。&纹理的生成函数function create_texture(source){
// イメージオブジェクトの生成
var img = new Image();
// データのオンロードをトリガーにする
img.onload = function(){
// テクスチャオブジェクトの生成
var tex = gl.createTexture();
// テクスチャをバインドする
gl.bindTexture(gl.TEXTURE_2D, tex);
// テクスチャへイメージを適用
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
// ミップマップを生成
gl.generateMipmap(gl.TEXTURE_2D);
// テクスチャのバインドを無効化
gl.bindTexture(gl.TEXTURE_2D, null);
// 生成したテクスチャをグローバル変数に代入
// イメージオブジェクトのソースを指定
}这个自定义函数create_texture接收一个图片对象的图片地址作为参数。函数中,首先生成图片对象,在图片数据读取之前,先添加了onload事件,在事件中,有纹理的生成,绑定,以及分配图片数据等处理。在函数中,texImage2D函数执行之后,应该会感觉有点奇怪吧,为了生成纹理映射,使用了generateMipmap函数。纹理映射是一个提前准备一些不同大小的图片数据的组织,不光是在WebGL中,在所有的3D编程中都有这个概念。纹理映射,准备一个纹理使用的场景,在纹理图像需要缩小显示的时候能够发挥很大的作用,因为缩小后的图像数据已经在内部提前准备好,然后进行适当的切换渲染,所以即使把图像缩的再小,也能渲染的很漂亮。执行了generateMipmap函数之后,就能生成纹理映射了,这个函数的参数和bindTexture一样,也是gl.TEXTURE_2D。和VBO一样,WebGL中同一时间只能绑定一个纹理,所以最后要解除绑定。在最终生成纹理对象的时候,使用了全局变量,因为onload没有办法返回自己本身。上面的例子,变量texture必须是create_texture函数能够参考到的空间内。onload的对应结束之后,最后给图片对象指定图片地址,因为已经加了onload事件,图片读取完成之后会自动调用onload事件,执行生成纹理的代码。纹理坐标和顶点的属性好了,已经知道了纹理对象的生成的方法了,那么重要的就是如何把纹理放到多边形中了。要把纹理放到多边形中,在生成多边形的时候,就需要包含将纹理如何放到多边形中这样的信息。所以需要给顶点添加新的顶点属性。在前面的文章()中已经详细介绍了,向顶点中添加信息需要使用新的VBO。那么这一次新添加的VBO中需要保存顶点的纹理坐标。纹理坐标就是为了表示使用纹理的哪个坐标。纹理坐标使用的范围是0 ~ 1,而且有横竖两个方向。所以,表示纹理坐标的时候类似于(0.0, 0.0)这样,需要两个元素。(lufy:翻译的有点绕嘴,看后面的使用部分就明白了。)而且,这里有些奇怪的地方,一般,图片数据的坐标系是以左上为原点来考虑的,如下图所示。从左上角的原点,向右以及向下来对应X和Y的值的大小。而WebGL中的纹理坐标系则是像下面这样。坐标系上下颠倒了一下。就是说纹理坐标系中,左下是原点,纵方向上的数值越向上表示越大。但是,对照两个图看一下就知道了,指定纹理坐标的时候也不需要考虑太多。为什么呢?因为图片也是上下翻转的,所以和以左上角为原点的图片一样考虑就行,结果是一致的。现在的阶段,只需要知道纹理空间上坐标系是上下翻转的就可以了。*以后使用纹理进行一些特殊的处理的时候才需要详细了解相关的知识。javascript 的修正接着,为了在程序中使用纹理,来修改一下代码吧。这次没有任何光照效果,渲染的只是带有图像的多边形而已。虽然用圆环体也可以,但是还需要处理一些细节部分,所以还从多边形模型来开始介绍。首先,准备模型的顶点数据,刚才也说了,为了保存纹理坐标,要添加新的顶点属性,而因为不需要光照效果,所以法线等情报这次也不使用。&顶点数据的准备// attributeLocationを配列に取得
var attLocation = new Array();
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'color');
attLocation[2] = gl.getAttribLocation(prg, 'textureCoord');
// attributeの要素数を配列に格納
var attStride = new Array();
attStride[0] = 3;
attStride[1] = 4;
attStride[2] = 2;
// 頂点の位置
var position = [
-1.0, -1.0,
1.0, -1.0,
var color = [
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0
// テクスチャ座標
var textureCoord = [
// 頂点インデックス
var index = [
// VBOとIBOの生成
var vPosition
= create_vbo(position);
var vColor
= create_vbo(color);
var vTextureCoord = create_vbo(textureCoord);
var VBOList
= [vPosition, vColor, vTextureCoord];
var iIndex
= create_ibo(index);
// VBOとIBOの登録
set_attribute(VBOList, attLocation, attStride);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iIndex);定义了一个四个顶点的四边形。详细看一下保存顶点的位置的部分的话就明白了,是以四边形的中心为原点,顶点顺序和写字母Z的顺序一样,顶点颜色定义成了不透明的白色。纹理坐标和之前说的一样,定义了包含横竖的两个元素。顶点数据是用一个数组来表示的,和之前一样生成VBO和IBO。这和之前所作的是完全一样的。这样,顶点相关的处理,也就是说着色器内用attribute变量处理的数据的准备都已经ok了。这次因为不进行光照处理,所以准备好坐标变换矩阵就可以处理顶点了。逆矩阵和光源的位置等都是不需要的。但是考虑一下uniform修饰符的变量的话,需要增加一个,uniform修饰符定义的变量是指全部顶点都进行一致处理的数据,所以,作为所有顶点都同样被使用的纹理数据,就必须使用uniform变量来传递数据了。&uniform相关处理// uniformLocationを配列に取得
var uniLocation = new Array();
uniLocation[0]
= gl.getUniformLocation(prg, 'mvpMatrix');
uniLocation[1]
= gl.getUniformLocation(prg, 'texture');这次使用的uniform变量有两个,一个是为了处理坐标变换矩阵,另一个是为了提交纹理数据。设置纹理有效纹理中有单位这个概念,是为了给纹理设置编号从而管理纹理的东西,默认是将0号的纹理单位设置为有效。纹理单位在处理多个纹理的时候可以发挥作用,这次只是使用一个纹理,所以就使用默认的0号的单位就可以了。要将特定的纹理单位设置为有效使用的是activeTexture函数。&纹理单位有效化// 有効にするテクスチャユニットを指定
gl.activeTexture(gl.TEXTURE0);这里作为参数使用的gl.TEXTURE0常量,后面的0就是纹理单位的编号,如果将编号1的纹理设置为有效的话,就需要是gl.TEXTURE1。但是,没有什么特殊的理由的话,使用纹理单位应该按照从小到大的顺序来使用。&&纹理单位的最大值(上限值)多个纹理同时使用的时候,纹理单位是必须用到的,这个最大的单位数是由运行环境决定的。因为运行WebGL的除了电脑,还有手机等,所以纹理单位能使用到多少个判断起来是非常费劲的。因为是受到硬件的性能的制约,所以使用之前先判断一下,然后进行分别处理是可行的。查询执行环境的可使用最大纹理单位数使用getParameter函数。下面是例子gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);向getParameter函数中传入这个非常长的常量gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS就可以得到一个整数值,表示可以使用的最大文理单位数,如果返回值是10的话,那么可以使用的纹理单位就是gl.TEXTURE0 ~ gl.TEXTURE9。向着色器中传入纹理数据将相应的纹理单位设置为有效之后,接着就需要将纹理和WebGL进行绑定处理了,这个在将图片数据转换成纹理数据的时候也做过了,所以简单如下。&纹理和WebGL绑定// テクスチャをバインドする
gl.bindTexture(gl.TEXTURE_2D, texture);绑定了纹理情报要传送给着色器,所以需要用uniform变量来处理,这里还使用之前的uniformLocation,处理如下。&将纹理数据传给着色器// uniform変数にテクスチャを登録
gl.uniform1i(uniLocation[1], 0);这里需要注意的是,和向着色器中传入矩阵和向量不同,因为需要传入纹理单位的编号。uniform1i函数是向着色器中传入一个整数的时候使用的。第二个参数就是要向着色器中传入的整数0。也就是说,这里传入的整数是和之前有效化的纹理单位相一致的。着色器修改接着,是着色器的修改了,首先是顶点着色器。&顶点着色器代码attribute vec3
attribute vec4
attribute vec2 textureC
vec2 vTextureC
void main(void){
vTextureCoord = textureC
gl_Position
= mvpMatrix * vec4(position, 1.0);
}顶点着色器中,顶点的位置,顶点的颜色,还有顶点的纹理坐标都是用attribute修饰符定义的。顶点的颜色和纹理坐标没有做任何处理,都是直接传给片段着色器的。顶点着色器相关的处理没什么难的地方,接着看片段着色器。&片段着色器代码pre
uniform sampler2D
varying vec4
varying vec2
void main(void){
vec4 smpColor = texture2D(texture, vTextureCoord);
gl_FragColor
= vColor * smpC
}片段着色器使用uniform修饰符来接收纹理数据。注意这个sampler2D的变量类型,就是采样的意思,先把它当作纹理数据考虑就行了。另外,还使用了一个texture2D函数,这个函数有两个参数,第一个参数是采样型的纹理数据,第二个参数是表示纹理坐标的vec型的数据。这次的例子,顶点着色器中用attribute定义的顶点的纹理坐标,在片段着色器中用varying变量来接收,然后在片段着色器一侧将这个纹理坐标,传入到texture2D函数中。这样,用texture2D函数获取到纹理的颜色信息之后,再和顶点颜色(varying变量vColor)相乘,就得到最终的颜色了。总结纹理的使用方法,用了双倍的时间来讲解了,应该理解了吧。纹理周边的处理是非常冗长的,但是主要的就是纹理坐标,纹理对象,以及为了处理这些数据的着色器的对应,纹理中单位这个概念,使用合理的操作可以处理多个纹理等。虽然这次只是最基本的部分的封装,但是变更点非常的多,所以最后会贴出全部代码(lufy:我就不贴了,大家直接用浏览器查看就行了。),另外,最后给出了运行demo。下次,介绍一下多个纹理的使用。四边形中使用纹理的demo转载请注明:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1376317次
积分:15396
积分:15396
排名:第577名
原创:87篇
译文:27篇
评论:2265条
系统讲解HTML 5 Canvas的基础知识和高级技巧,深入剖析开源库件lufylegend的原理与使用以实例为向导,详细讲解射击游戏、物理游戏、网络游戏等各类游戏的开发思路和技巧
qq交流群:
qq交流群:
文章:27篇
阅读:187465
文章:20篇
阅读:237753
文章:48篇
阅读:693325
三国记是一款以三国时期为背景的战略类游戏,玩家可以任选一名君主,以统一全国为目标。
本游戏以三国为背景,本次更新为第一部黄巾之乱的后续剧情,续作了群雄讨伐董卓,界桥之战以及救援徐州等剧情。
本游戏以三国为背景,玩家在游戏中可以体验三国中的四个经典战役,虎牢关之战,官渡之战,赤壁之战,夷陵之战。
(5)(2)(16)(5)(1)(1)(4)(1)(2)(2)(1)(2)(1)(3)(4)(4)(5)(4)(2)(1)(1)(2)(3)(4)(5)(2)(2)(4)(1)(5)(9)(1)(1)(1)(1)(2)(1)(3)最好的HTML5 WebGL中文资讯站!
You are here:
& 用WebGL进行局部水体环境渲染
大家好,今天我们要介绍的这个Demo名字很简单,叫做WebGL Water;但是效果可不一般!它自从上线以来,就被在Twitter和其他科技博客上广为转载,目前在Twitter上搜索WebGL,几乎十条有八条会提及这个Demo——俨然已经成为WebGL技术对大众展示的代言人了!“Cool!”、“Amazing!”、“Excelent!”无数的赞美之词加于一身!那么它的作者是谁呢?对了,正是我们之前提及的那个Evan Wallace!他的上一个Demo一样也是获得了业界和一般大众的一致好评!还真是WebGL界最近的Pop Star呢!
好了,废话少说,还是让我们赶紧来看一下这个用WebGL进行局部水体环境渲染的Demo吧!
Demo名称:WebGL Water
作者:Evan Wallace
作者网站:
Demo地址:
浏览器要求:Chrome 13.0+, Firefox 5.0+, Safari 5.1+
Demo信息:
这个Demo需要一个相当好的显卡和最新的显卡驱动。
操作说明:
拖拽水体来生成水纹涟漪
拖动背景来转换相机视角
按空格键来暂停/开始
拖拽圆球移动它
按L键设置光线方向
按G键来设置重力
使用到的技术:
光线追踪的反射和折射
基于统计的漫反射遮挡
基于高度的水体模拟(需要OES_texture_float扩展)
(需要OES_texture_standard_derivatives扩展,目前只有Google Chrome浏览器支持这一扩展)
赶紧打开Demo地址来看一下吧!如果你的显卡不够好或者没有Chrome浏览器,那也不要担心。HiWebGL为你准备了如下视频。
HiWebGL点评
Evan Wallace的Water Demo结合了基于高度的水面渲染、反射与折射(reflections & refractions)、基于统计的漫反射遮挡(Analytic AO)及平滑的实时软阴影生成技术、向我们呈现了一个非常逼真的局部水体环境的渲染解决方案。这个Demo的亮点在于将多项常用图形渲染技巧精彩完美得结合在一起,并使用WebGL技术在网页前端展示出来,同时也验证了基于浏览器前端硬件加速能力的WebGL应用,已经可以达到非常高质量的实时环境渲染水平了。
Posted in ,
and tagged , , . Bookmark the .
关注我们的新浪微博![哆啦A梦吃惊] : 因写了抢猫粮插件被抓的小喵。 来自 转发微博 : 在看颜的时代,如何让一款游戏画面更具高bigger?从8位像素画风到1080P/4K,再到ARM的Enlighten全局光照技术,能够分分钟变化出数十种光照效果,再挑的玩家都给它点赞了,不信你就来看看[围观][围观] 来自 转发微博 : 你知道使用 target='_blank' 也会有安全漏洞么?尤其是在各种 UGC 网站中。不过小编早就玩过了,其实一般倒也不会用在钓鱼上,而是用在各种各样的广告点击中。推荐文章《target='_blank' 安全漏洞示例》地址
$w 来自 不做游戏,帮转 : 有做WebVR游戏的公司么,求合作。求圈内大神帮忙转发。@寒冬winter @大城小胖 @w3cplus 来自 阿里云OSS是否支持websockets?有人知道吗? 来自 分类目录
Designed by . In collaboration with , , .使用 WebGL 实现素描效果的渲染 - 推酷
使用 WebGL 实现素描效果的渲染
这次来介绍一下我最近刚完成的一个小玩意儿:通过 WebGL 在网页上显示一个素描风格的场景。
欢迎先使用支持 WebGL 的浏览器浏览一下本文对应的
。 本文的代码以及场景文件也以 BSD 的协议在 Github 上发布了 (
开始之前先说点题外话,在过去的两个月里,我还开发了一个用来管理矢量图标并生成图标字体的工具:MyIcons。这是一个 Web-based 的工具,很方便部署到 Heroku,有需要的朋友欢迎围观和体验! 我也会在之后的博文中逐步给出这个工具有关问题的解说。
一点点综述
这次的素描渲染 Demo 是基于 Three.js 的,跟之前的文章Let Rocket Fly 不同,这次要涉及到 Shader 的编写,因此复杂度也要比原来高很多。
实现一个这样的渲染效果,主要的步骤包括:
准备模型和场景
通过 WebGL (Three.js) 导入场景
实现 Shader 以表现接近素描的效果
在最重要的第 3 步中,我们要实现的主要有两个效果:
模型边缘的描边 (不同于单纯的线框)
模型表面类似于素描的线条效果
为了实现这样的效果,我们实际并不能直接在单一的 3D 的空间上完成的,而需要另外准备一个二维场景用于合成。 总体的渲染与合成流程如下:
其中的 3D 场景,就是我们想要处理成素描效果的场景。这里使用了一个小技巧, 那就是我们并非直接将 3D 场景中的渲染效果输出到屏幕,而是先将三种不同类型的渲染结果输出到位于显存中的 Buffer (Three.js 中的 WebGLRenderTarget ) 里。再在 2D 场景中合成这些输出结果。
这个 2D 场景非常简单,里面只有一个恰好和视口大小一样的矩形平面和一个非透视类型的 Camera, 将我们从 3D 场景得到的不同类型的渲染图作为矩形平面的贴图,这样我们就可以编写 Shader 来高效地处理合成效果了。最终输出的结果其实是 2D 场景的渲染结果,但是观看的人不会感觉到任何差异。
使用这样一个简单的 2D 场景进行后期合成可以说是一个非常常用的技巧,因为这样可以通过 OpenGL 充分利用显卡的渲染性能。
首先要做的工作是准备用来渲染的场景,选用的建模软件当然是我最喜欢的
。我参考 BlenderNation 上刊登的一副
进行了仿制。 我仿制的场景渲染结果如下:
选用这个场景的主要原因是场景的主体结构都非常简单,大多数物体都可以通过简单的立方体变换和修改而成。 大量的平面也方便表现素描的效果。
建模的细节不再赘述。在这一阶段还有一个主要的工序需要完成,那就是 UV 展开和阴影明暗的烘焙 (Bake)。
模型的 UV 展开实质上就是确定模型的贴图坐标与模型坐标的映射关系。一个好的 UV 映射决定了模型渲染时贴图的显示效果。 因为模型表面的素描效果实际是通过贴图实现的,因此如果没有一个好的 UV 映射,显示出来的笔触可能会出现扭曲、变形、 粗细不一等各种问题。UV 展开可以说是一个非常繁琐耗时的工序。最后为了减少工作量,我不得不删除了一些比较复杂的模型。
我将场景中的所有模型合并为一个物体,并完成 UV 展开后的结果如下:
完成 UV 展开之后将会进行烘焙。所谓的烘焙 (Bake) 就是将模型在场景环境下的明暗变化、阴影等事先渲染并映射到模型的贴图上。 这个技术常用于静态场景中。在这种静态场景里,灯光的位置和角度不会变化,只有摄像机的方向会改变。 因此实际上物体的明暗阴影都是固定的,将其固定在贴图中之后,使用 OpenGL 渲染时不再进行明暗处理和阴影生成。 这样可以节约大量的计算时间。而且使用 CPU 渲染的阴影往往可以使用更为复杂的算法以获得真实的效果。
Blender 的烘焙选项在 Render 选项卡的最下方,这里选择 Full Render 来将一切光源产生的明暗阴影都固定下来。
对照之前的 UV 展开,我烘焙出来的光影贴图如下:
最后,使用 Three.js 提供的
,将我们的场景输出成 Three.js 可以识别的 .json 文件。 我输出的模型文件和相关贴图都已经上传到 GitHub 的
这里再为有兴趣的同学推荐一个来自台湾同胞的 Blender 基础教程 (
)。 个人感觉是 Blender 的中文视频教程中比较好的一个,虽然时间录制早了些,但是讲解很清晰。 而且本文制作时使用的建模、UV 展开、贴图和烘焙技巧都有介绍。
编写 Shader
终于到了这篇文章的重中之重了,Shader 是通过 GPU 实现图形渲染的核心,通过 OpenGL 实现的任何 2D 或 3D 效果都离不开它。
一点点基础知识
众所周知, WebGL 使用的 Shader 语言其实是 OpenGL 的一个嵌入式版本 OpenGL ES 所定义的,这一 Shader 语言使用了类似 C 语言的语法,但是有下面几个区别:
Shader 语言没有动态分配内存的机制,所有内存 (变量) 的空间都是静态分配的
Shader 语言是强类型的,不同类型的数不能隐式转换 (比如整形不能隐式转换为浮点型)
Shader 语言提供的一些数据结构,如向量类型 vec2 、 vec3 、 vec4 和矩阵类型 mat2 、 mat2 、 mat4 是直接可以使用加减乘除运算符进行操作的。
在 WebGL 中,我们可以自己编写的 Shader 有两种类型
Vertex Shader: 模型的每个顶点上调用
Fragment Shader: 模型三个顶点组成的面上显示出来的每个像素上执行
在渲染时,GPU 会先在每个顶点上执行 Vertex Shader,再在每个像素上执行 Fragment Shader。 Vertex Shader 主要用来计算每个定点投影在视平面上的位置,但是也可以用来进行一些颜色的计算并将结果传送给 Fragment Shader。 Fragment Shader 则决定了最终显示出来的每个像素的颜色。
接下来介绍 Shader 的变量修饰词。Shader 的变量修饰词可以分为 5 种:
(无): 默认的变量修饰符,作用域只限本地
const : 只读常量
attribute : 用来将每个节点的数据和 Vertex Shader 联系起来的变量,简单来说就是在某一个顶点上执行 Vertex Shader 时,变量的值就是这个顶点对应的值。这种对应关系是在初始化 WebGL 的程序时手动指定的。 不过幸好 Three.js 已经为我们完成这一任务了。
uniform : 这种类型的变量也是运行在 CPU 的主程序向 Shader 传递数据的一个途径,主要用于与所处理的 Vertex 和 Fragment 无关的值,比如摄像机的位置、灯光光源的位置方向等,这些参数在每一帧的渲染时都不变,因此使用 uniform 传递进来。
varying : 用来从 Vertex Shader 向 Fragment Shader 传递数据的变量。在 Vertex Shader 和 Fragment Shader 上定义相同变量名的 varying 变量,在运行时 Fragment Shader 中变量的值将会是组成这个面的三个顶点所提供的值的线性插值。
Three.js 已经为我们预设了必要的 attribute 和 uniform , 预设变量列表可以参见
两种 Shader 都有一个 main 函数,不过执行的参数并非通过 main 函数的参数传入程序, 输出结果也不是通过 main 函数的返回值返回的。实际上,OpenGL 已经固定了每种 Shader 的默认输入变量和输出变量的名称与类型, 程序可以直接访问和设置这些变量。当然,外部程序也可以通过 attribute 和 uniform 机制来指定额外的输入。
一个典型的 Vertex Shader 如下面的代码所示:
void main(void) {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
其中, position 、 projectionMatrix 、 modelViewMatrix 这些变量都是 Three.js 默认设置好并传递进 Shader 的。 position 是 attribute 类型,它代表了每个 Vertex 在 3D 空间中的坐标,另外两个变量是 uniform ,是 Three.js 根据场景的属性而设定的。 gl_Position 就是 OpenGL 指定的 Vertex Shader 的输出值。
一个典型的 Vertex Shader 是通过给出的顶点 position ,以及相关的一些变换投影矩阵, 计算出这个顶点做透视投影后显示在屏幕中的 2D 坐标。因此在这里也可以实现各种透视效果, 如常见的投影透视 (近大远小)、平视透视 (远近一样大),甚至超现实的反投影透视 (近小远大) 等。
Fragment Shader 的主要用处是确定某个像素的颜色,其已经指定的输出值为 gl_FragColor ,这是一个 vec4 类型的变量, 代表了 RGBA 类型的颜色表示,为每一个表面输出白色的 Fragment Shader 如下:
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
除了直接计算颜色,还可以通过贴图 (texture) 来确定某个 Fragment 的颜色。在 WebGL 中,贴图是通过 uniform 的方式传递进 Shader 里的,其类型是 sample2D 。随后,我们可以使用 texture2D(texture, uv) 函数获得某一个像素的颜色,这里的 uv 是一个二维向量,可以通过 Vertex Shader 获得。
在 Three.js 实现访问贴图的一个简单的例子是:
// Vertex Shader
varying vUv;
void main(void) {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
// Fragment Shader
uniform sample2D aT
varying vUv;
void main(void) {
gl_FragColor = texture2D(aTexture, vUv);
在 Vertex Shader 中使用的 uv 变量,也是 Three.js 中已经提供好的 attribute 。接下来就是在 Three.js 中使用 Shader 的方法了。
在 Three.js 中使用 Shader
Three.js 提供了 ShaderMaterial 用于实现自定义 Shader 的 Material。下面是一个来自其
var material = new THREE.ShaderMaterial( {
uniforms: {
time: { type: &f&, value: 1.0 },
resolution: { type: &v2&, value: new THREE.Vector2() }
attributes: {
vertexOpacity: { type: 'f', value: [] }
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
你可以通过设置 uniforms 和 attributes 等参数向 Shader 传递数据,传递的格式文档中都有介绍。 我们也是在这里将 Shader 需要用到的 Texture 通过 uniforms 传递进去的。Texture 写在 unifroms 里的 type 是 t , value 可以是一个 Three.js 的 Texture 对象,也可以是 WebGLRenderTarget 。
这里只是将值传递了进去,你还是要在 Shader 源码里自己声明这些变量才能访问他们, 在 Shader 里定义的名称应该与你在 JavaScript 中给出的键名相同。
显示模型的 Outline
模型的 Outline 就是在卡通风格的图画中围绕在物体边缘的线,因为卡通风格中物体的总体色调都比较平面化, 所以需要这样的线来强调物体与物体之间的区分。
实现这种 Outline 有两种简单直观的方法:
使用深度作为特征,将深度变化大的地方标记出来
使用表面法线的方向作为特征,将发现变化大的地方标记出来
这两种方法都各自有自己的缺点。比如深度特征时,很容易将一个与观察方向夹角比较小的面全部标记为黑色; 而法线特征时,又无法将前后两个法线相近但是距离较远的表面区分开。这里参考另一篇相关内容的英文博客
的方法来实现。
这种方法结合了深度和法线,假设有两个点 A 和 B,通过计算 A 的空间位置到 B 的法线所构成的平面的距离作为衡量, 判断是否应该标记为 Outline。A 和 B 的空间位置则需要通过 A 和 B 的深度来计算出来。 因此,我们需要先将我们的 3D 场景的深度和法线渲染图输出出来。
Three.js 已经提供了 MeshDepthMaterial 和 MeshNormalMaterial 分别用来输出深度和法线渲染图。 我们直接使用这两个类就好了。假设我们已经初始化了一个 depthMaterial 和一个 normalMaterial , 那么将整个场景里的物体都用某一个 Material 进行渲染的话,我们可以使用
objectScene.overrideMaterial = depthM // 或 normalMaterial
这样的方法实现。
此外,我们不希望渲染结果直接输出到屏幕,因此我们需要先新建一个 WebGLRenderTarget 作为一个 FrameBuffer 来存放结果。 此后这个 WebGLRenderTarget 可以直接作为贴图传入用于合成的 2D 场景。
var pars = {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBFormat,
stencilBuffer: false
var depthTexture = new THREE.WebGLRenderTarget(width, height, pars)
var normalTexture = new THREE.WebGLRenderTarget(width, height, pars)
使用下面的代码,将渲染结果输出到 FrameBuffer 里:
// render depth
objectScene.overrideMaterial = depthM
renderer.setClearColor('#000000');
renderer.clearTarget(depthTexture, true, true);
renderer.render(objectScene, objectCamera, depthTexture);
// render normal
objectScene.overrideMaterial = normalM
renderer.setClearColor('#000000');
renderer.clearTarget(normalTexture, true, true);
renderer.render(objectScene, objectCamera, normalTexture);
在输出之前,别忘记使用 renderer 的 clearTarget 函数将 Buffer 清空。 如果将我们在这一步生成的贴图显示出来的话,大概是下面的样子:
生成素描笔触
接下来就是在物体的表面生成绘制的素描线条效果了。这个方面其实比想象中更简单一点, 我们的素描效果是使用的是如下一系列贴图组成的:
接下来的问题就是找一种方法将这种不同密度的贴图融合在一起,这种问题被称为 Hatching。 这里使用的 Hatching 方法是 MicroSoft Research 在 2001 年发表的一篇
中给出的。
不同于原文中使用 6 张贴图合成的方法,这里采用了使用 3 张贴图合成,然后将贴图旋转 90 度再合成一次, 从而获得交叉的笔划。
void main() {
vec2 uv = vUv * 15.0;
vec2 uv2 = vUv.yx * 10.0;
float shading = texture2D(bakedshadow, vUv).r + 0.1;
float crossedShading = shade(shading, uv) * shade(shading, uv2) * 0.6 + 0.4;
gl_FragColor = vec4(vec3(crossedShading), 1.0);
shade 函数就是用合成多个贴图的函数,具体代码可以参见 GitHub 上的
。 可以注意到,我其实使用了之前 bake 出来的明暗来作为素描线条深浅的参考因素, 这样就可以表现出明暗和阴影了。
最后的合成
最后就是要在我们的二维场景里进行最后的合成了。构造这样一个二维场景的代码很简单:
var composeCamera = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, -10, 10);
var composePlaneGeometry = new THREE.PlaneBufferGeometry(width, height);
composePlaneMesh = new THREE.Mesh(composePlaneGeometry, composeMaterial);
composeScene.add(composePlaneMesh);
场景的主要构造就是一个和视口一样大小的矩形几何体,摄像机则是一个 OrthographicCamera ,这种摄像机没有透视效果, 正合适用于我们这种合成的需求。
将前几步输出到 FrameBuffer (也就是 WebGLRenderTarget ) 的结果作为这个矩形表面的贴图, 然后我们编写一个 Shader 来进行合成。
这一次,我们不再需要输出到 Buffer 上,而是直接输出到屏幕。而 Outline 的生成也是在这一步完成的。 用来计算 Outline 的函数是:
float planeDistance(const in vec3 positionA, const in vec3 normalA,
const in vec3 positionB, const in vec3 normalB) {
vec3 positionDelta = positionB-positionA;
float planeDistanceDelta = max(abs(dot(positionDelta, normalA)), abs(dot(positionDelta, normalB)));
return planeDistanceD
在当前坐标周围取一个十字形的采样,对于上下和左右取出的点分别执行上面的函数, 最后使用 smoothstep 来获得 Outline 的颜色:
vec2 planeDist = vec2(
planeDistance(leftpos, leftnor, rightpos, rightnor),
planeDistance(uppos, upnor, downpos, downnor));
float planeEdge = 2.5 * length(planeDist);
planeEdge = 1.0 - 0.5 * smoothstep(0.0, depthCenter, planeEdge);
在最后实现的版本里,我还尝试了再混入法线方式生成的边缘线的效果。最终生成的 Outline 效果如下:
最后,将 Hatching 过程输出的结果混合进来:
vec4 hatch = texture2D(hatchtexture, vUv);
gl_FragColor = vec4(vec3(hatch * edge), 1.0);
完整的实现可以参见我放在 GitHub 上的
大功告成!最后的合成效果如图:
各位可以访问我使用简单添加了一点交互之后得到的
(请使用支持 WebGL 的现代浏览器进行访问,加载模型和全部贴图可能需要一小会,请耐心等待)。
我实现的所有代码以及模型都已经以 BSD 协议发布到 GitHub 上了 (
虽然是作为我在学校一门课程的 Final Project 的一部分完成的项目, 但是在这个过程中我总算是对于 Shader 的编写方面有所入门。此外,这次进行 Blender 进行建模也感觉比以前顺利了许多。
虽然对 Blender 和 WebGL 的爱好现在看起来还没有什么现实价值,但是能够自己完成一个有趣的 Project 还是很有成就感的!
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致

我要回帖

更多关于 小米mix虚拟导航键 的文章

 

随机推荐