WebGL基础知识 - GLSL和着色器(Shader)
在本站的WebGL入门教程中,提到绘制管道中有两个着色器,一个是vertex shader(顶点着色器)和一个fragment shader(片段着色器)。本章简介这两个着色器的具体使用。
每个着色器本质上就是一个函数,有特定的输入和输出。着色器函数被串联到同一个着色器程序中。
Vertex Shader
顶点着色器的功能是把原始顶点数据变换到裁减空间坐标。每个顶点都会调用该着色器函数。
顶点着色器的输入数据有如下2种方式:
Attributes (从缓存中获取的数据)
Uniforms (单次绘制中对所有顶点保持不变的值)
Attributes
最常见的方式是通过缓存和属性。首先创建缓存:
var buf = gl.createBuffer();
写入数据:
gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, someData, gl.STATIC_DRAW);
然后, 给定一个着色程序,您可以在初始化时查找其属性的位置:
var positionLoc = gl.getAttribLocation(someShaderProgram, "a_position");
在渲染时告诉WebGL如何从缓存中读出数据并写入属性中:
// 启用此属性从缓冲区中获取数据的功能 gl.enableVertexAttribArray(positionLoc); var numComponents = 3; // (x, y, z) var type = gl.FLOAT; // 32bit浮点数 var normalize = false; // 保持数据原样,不做规范化 var offset = 0; // 从缓存的起点处开始 var stride = 0; // 移动到下一个顶点的步幅 // 0 = 使用符合当前type和numComponents取值的步幅 gl.vertexAttribPointer(positionLoc, numComponents, type, false, stride, offset);
在着色器函数中使用属性数据如下(这里尚未作任何数学处理):
attribute vec4 a_position; void main() { gl_Position = a_position; }
属性(Attributes)可以使用的数据类型有:float, vec2, vec3, vec4, mat2, mat3 和 mat4。
Uniforms
Uniforms是传递给着色器的值,这些值对于绘制调用中的所有顶点保持不变。
比如我们可以向上面的顶点着色器添加一个偏移量:
attribute vec4 a_position; uniform vec4 u_offset; void main() { gl_Position = a_position + u_offset; }
这样我们就可以将每个顶点偏移一定的量。
为此,首先我们需要在初始化时得到该uniform的位置:
var offsetLoc = gl.getUniformLocation(someProgram, "u_offset");
然后在渲染前给uniform设置值:
gl.uniform4fv(offsetLoc, [1, 0, 0, 0]); //将其偏移到屏幕的右半部分
注意,这里uniforms是属于单个着色器程序的,如果你的WebGL应用程序包含多个着色器程序,那么在不同的shader中的同名uniform将拥有他们各自的存储位置和取值。当调用gl.uniformXXX方法时我们只是设置当前着色器程序(shader program)的uniform,当前shader program就是你最近一次调用gl.useProgram所应用的那个。
Uniforms有很多类型,对于每个类型都有对应的方法来设置它的值:
gl.uniform1f (floatUniformLoc, v); // for float gl.uniform1fv(floatUniformLoc, [v]); // for float or float array gl.uniform2f (vec2UniformLoc, v0, v1); // for vec2 gl.uniform2fv(vec2UniformLoc, [v0, v1]); // for vec2 or vec2 array gl.uniform3f (vec3UniformLoc, v0, v1, v2); // for vec3 gl.uniform3fv(vec3UniformLoc, [v0, v1, v2]); // for vec3 or vec3 array gl.uniform4f (vec4UniformLoc, v0, v1, v2, v4); // for vec4 gl.uniform4fv(vec4UniformLoc, [v0, v1, v2, v4]); // for vec4 or vec4 array gl.uniformMatrix2fv(mat2UniformLoc, false, [ 4x element array ]) // for mat2 or mat2 array gl.uniformMatrix3fv(mat3UniformLoc, false, [ 9x element array ]) // for mat3 or mat3 array gl.uniformMatrix4fv(mat4UniformLoc, false, [ 16x element array ]) // for mat4 or mat4 array gl.uniform1i (intUniformLoc, v); // for int gl.uniform1iv(intUniformLoc, [v]); // for int or int array gl.uniform2i (ivec2UniformLoc, v0, v1); // for ivec2 gl.uniform2iv(ivec2UniformLoc, [v0, v1]); // for ivec2 or ivec2 array gl.uniform3i (ivec3UniformLoc, v0, v1, v2); // for ivec3 gl.uniform3iv(ivec3UniformLoc, [v0, v1, v2]); // for ivec3 or ivec3 array gl.uniform4i (ivec4UniformLoc, v0, v1, v2, v4); // for ivec4 gl.uniform4iv(ivec4UniformLoc, [v0, v1, v2, v4]); // for ivec4 or ivec4 array gl.uniform1i (sampler2DUniformLoc, v); // for sampler2D (textures) gl.uniform1iv(sampler2DUniformLoc, [v]); // for sampler2D or sampler2D array
比如设置一个二维矢量的数组:
// in shader uniform vec2 u_someVec2[3]; // in JavaScript at init time var someVec2Loc = gl.getUniformLocation(someProgram, "u_someVec2"); // at render time gl.uniform2fv(someVec2Loc, [1, 2, 3, 4, 5, 6]);
Fragment Shader
顶点着色器的输出数据经过光栅化处理后,输入给片段着色器,而片段着色器的功能就是为正在光栅化的当前像素提供颜色。
每个像素都会调用片段着色器。片段着色器的输入数据有如下3种方式:
Uniforms (对于单个绘图调用的每个像素保持相同的值,同上)
Textures (从像素pixels和纹元texels中读取的数据)
Varyings (从顶点着色器传递并插值的数据)
Textures
首先在shader中创建一个sampler2d uniform,我们可以使用glsl函数texture2d从中提取值(需要提供纹理坐标):
precision mediump float; uniform sampler2D u_texture; void main() { vec2 texcoord = vec2(0.5, 0.5) // 从texture的中间位置获取数据 gl_FragColor = texture2D(u_texture, texcoord); }
在WebGL程序初始化时得到shader中u_texture的位置:
var someSamplerLoc = gl.getUniformLocation(someProgram, "u_texture");
然后我们需要设置texture的数据(数据格式有很多种),下面是一个简单的示例数据:
var tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); var level = 0; var width = 2; var height = 1; var data = new Uint8Array([ 255, 0, 0, 255, // 1个红色像素 0, 255, 0, 255, // 1个绿色像素 ]); gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
在渲染时告诉WebGL激活某texture单元,并绑定tex到该单元(比如gl.TEXTURE0),然后告诉着色器把someSampler关联到该单元:
var unit = 0; gl.activeTexture(gl.TEXTURE + unit); gl.bindTexture(gl.TEXTURE_2D, tex); gl.uniform1i(someSamplerLoc, unit);
通过上面一连串的绑定关联动作,我们就通过WebGL程序接口把2个像素数据设置到shader中的u_texture中,并最终给到gl_FragColor。
Varyings
变量(Varyings)是一种将值从顶点着色器传递到片段着色器的方法。和常量(Uniforms)不同的是,该值可以被顶点着色器修改,然后在片段着色器中读取(只读)。变量的值是一个插值数据,每个像素都不同。
要使用变量,我们需要在顶点和片段着色器中声明匹配的变量。我们用每个顶点的值来设置顶点着色中的变化。当WebGL绘制像素时,它将在这些值之间进行插值,并将它们传递给片段明暗器中相应的变量。
Vertex shader
attribute vec4 a_position; uniform vec4 u_offset; varying vec4 v_positionWithOffset; void main() { gl_Position = a_position + u_offset; v_positionWithOffset = a_position + u_offset; }
Fragment shader
precision mediump float; varying vec4 v_positionWithOffset; void main() { // convert from clipsapce (-1 <-> +1) to color space (0 -> 1). vec4 color = v_positionWithOffset * 0.5 + 0.5 gl_FragColor = color; }
最新评论
- 相关文章
微信公众号在线生成二维码带参数怎么搞?
带参数二维码是微信公众号渠道二维码的一种实现
微信的带参数二维码有两种,一种是临时二维码,一种是永久二维码,但是永久二维码的生成是有个数限制的,微...A-Frame WebVR(网页虚拟现实)快速开发入门教程
WebVR和WebGL应用程序接口使得我们已经可以在浏览器上创建虚拟现实(VR)体验,但从工程化的角度而言,开发社区还需要更多方便强大的开发库来简化编程,Mozilla的
React JSX语法简介
JSX是一种类似XML的标签语法,用来简化代码,我们可以不使用JSX,但了解并使用也没什么坏处:)在React中,JSX是一个使用 React.createElement() API的快捷方式...
CSS3人行走动作图解和动画实现
对于人类而言,行走是一种很自然的想要前进并防止跌倒的一组动作重复。大部分人1岁就学会了走路,但至此以后的几十年间,或许我们从来没留意过自己行走姿势。当...
常见面试题JavaScript闭包(ES5语法)
JavaScript闭包(Closure)是常见的JS面试题,是否理解闭包是一个简单的区分JS初级和高级程序员的判例。几乎每个JS程序员都在使用闭包,有意或无意间。比如编写一个jQuery鼠标点击处理函数:$(function()
纹理基础知识和过滤模式详解
1、 为什么在纹理采样时需要texture filter(纹理过滤)。
我们的纹理是要贴到三维图形表面的,而三维图形上的pixel中心和纹理上的texel中心并不一至(pixe...使用top/left/margin和CSS3 translate两种方法实现标题居中的性能差异详解
要实现标题全屏居中(同时在垂直和水平方向居中),有若干种方法,包括使用弹性布局、表格单元、绝对定位、自动外边距和CSS3平移变换等。你可能已经使用了这些方...
jQuery Ribbles - 基于WebGL的水面涟漪动效插件
使用jQuery
如何使用纯CSS3实现一个3D泡沫
要实现一个逼真的泡沫,涉及到比较复杂的光学/物理学知识。我们这里先简化下问题,实现一个相对简单而足够实用的泡沫元素。我们可以把基础的泡沫元素应用在很多场景中,比如水景、泡咖啡、啤酒甚至火焰特效中。泡沫首先是一个圆形元素.bubble
在PHP网页程序中执行Sass/Compass命令
我们需要在wow云开发平台支持sass/compass等预编译样式语言,为此我们首先尝试了scssphp扩展,但是在支持最新语法上,经常会出现异常。所以我们采用了代理的方式...
更多...