WebGL基础知识 - GLSL和着色器(Shader)

iefreer 发表于 2019-04-29 18:26:12

标签: webgl, shader, glsl

- +

在本站的WebGL入门教程中,提到绘制管道中有两个着色器,一个是vertex shader(顶点着色器)和一个fragment shader(片段着色器)。本章简介这两个着色器的具体使用。

每个着色器本质上就是一个函数,有特定的输入和输出。着色器函数被串联到同一个着色器程序中。

Vertex Shader

顶点着色器的功能是把原始顶点数据变换到裁减空间坐标。每个顶点都会调用该着色器函数。

顶点着色器的输入数据有如下2种方式:

  1. Attributes (从缓存中获取的数据)

  2. 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种方式:

  1. Uniforms (对于单个绘图调用的每个像素保持相同的值,同上)

  2. Textures (从像素pixels和纹元texels中读取的数据)

  3. 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;
}


possitive(5) negative(0) views1640 comments0
私信 收藏 分享
分享到

发送私信

最新评论

请先 登录 再评论.
相关文章
  • 3D感知和建模关键硬件技术:双目、3D结构光和TOF

    无论VR、AR和3D打印,其核心技术包含3D成像和建模。而3D建模属于劳动密集型的工作,耗时耗力,凡这类工作都会是被新技术革命的地方,自动3D建模技术就是为了解决...

  • 踏得网精选2016年度10大最佳HTML5动画

    踏得网精选2016年度最酷最新的HTML5动画集,评选标准为:创意新颖度+实现技术难度+趣味程度。使用一些在线H5生成工具的作品,因其主要使用图片和CSS3套路动画,...

  • 创建非矩形网页页面元素的常用技术和实例代码

    非矩形设计正在变成一种时尚,比如波浪形、菱形、三角形等:而随着技术发展,这种设计在技术实现上也变得更容易。本文以最简单的三角形为例,演示使用5种方法来...

  • WebGL Roadmap

    Unity 5.0 shipped with a working preview of our WebGL technology in March this year. Since then, Google has disabled (by default) NPAPI support in the...

  • React JSX语法简介

    JSX是一种类似XML的标签语法,用来简化代码,我们可以不使用JSX,但了解并使用也没什么坏处:)在React中,JSX是一个使用 React.createElement() API的快捷方式...

  • 常见面试题JavaScript闭包(ES5语法)

    JavaScript闭包(Closure)是常见的JS面试题,是否理解闭包是一个简单的区分JS初级和高级程序员的判例。几乎每个JS程序员都在使用闭包,有意或无意间。比如编写一个jQuery鼠标点击处理函数:$(function()

  • HTML5动画背后的数学 - 粒子群仿生算法简介

    本站收录了多个算法可视化动画,如模拟鸟群运动:http://wow.techbrood.com/fiddle/30529等等。这里面除...

  • WebVR简介和常用资源链接

    什么是WebVR这是一个实验性的JavaScript API,提供了在用户网页浏览器中访问虚拟现实设备的统一接口。当前主流VR设备如Oculus Rift DK2、谷歌的CardBoard、三星...

  • Three.js入门教程2 - 着色器(下)

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

  • IE各版本CSS Hack(兼容性处理)语法速查表

    为了兼容IE各个版本,需要在CSS中添加额外的代码,比如以前常用的_width。之所以工作,是因为浏览器会忽略不能解析的样式规则,因此举个例子来说,把_width写在w...

  • WebGL入门教程1 - 3D绘图基础知识

    现代浏览器努力使得Web用户体验更为丰富,而WebGL正处于这样的技术生态系统的中心位置。其应用范围覆盖在线游戏、大数据可视化、计算机辅助设计、虚拟现实以及数...

  • 使用Canvas绘制完美的不完美圆形

    真实世界是不完美的,当我们需要模拟真实世界时,经常需要引入不完美/不规则的形状。比如陨石、雨滴、行星、树叶、绵延的海岸线、云朵等。本文介绍如何基于Canva...

  • Three.js 开发基础知识 - 绘制3D对象

    Three.js是一个用来简化WebGL开发的JavaScript库,比如绘制一个三维立方体,使用WebGL需要100多行,那Three.js只要10几行就能够完成。本文通过创建一个立方体来...

  • 使用requestAnimationFrame和Canvas给按钮添加绕边动画

    要给按钮添加酷炫的绕边动画,可以使用Canvas来实现。基本的思路是创建一个和按钮大小相同的Canvas元素,内置在按钮元素中。然后在Canvas上实现边线环绕的动画。...

  • 使用纯CSS3实现一个3D旋转的书本

    有一些前沿的电商网站已经开始使用3D模型来展示商品并支持在线定制,而其中图书的展示是最为简单的一种,无需复杂的建模过程,使用图片和CSS3的一些变换即可实现...

  • 更多...