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

techbrood 发表于 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(22) views12967 comments0

发送私信

最新评论

请先 登录 再评论.
相关文章
  • CSS3原生变量(Native Variables)新特性简介

    对Web开发者来说,一个盼望已久的特性是CSS终于支持原生变量了!
    变量是程序语言中用来解决代码重复和进行表达式计算的关键概念(想想数学方程式中的x)。...

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

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

  • 使用SVG和CSS3创建圆形进度条动画

    圆形进度条是一个经典的控制面板元素,常用于显示任务进度,比如用户档案的完整程度,或者升级状态。有很多方法来实现圆形进度条,比如用JS, CSS3, Canvas, SVG...

  • Three.js 对象局部坐标转换为世界坐标

    在Three.js中进行顶点几何计算时,一个需要注意的地方是,需要统一坐标系。比如你通过Three.js提供的API创建了一个球体网孔对象,那么默认情况下,各网孔顶点的...

  • 三维向量的简单运算和实用意义

    在WebGL的实际应用中我们广泛使用向量的几何运算来计算角度、距离,判断点线、点面之间的关系,比如物体之间的碰撞检测。本文简要介绍三维计算机图形学中常用的...

  • 粒子运动模拟 - Verlet积分算法简介

    Verlet算法是经典力学(牛顿力学)中的一种最为普遍的积分方法,被广泛运用在分子运动模拟(Molecular Dynamics Simulation),行星运动以及织物变形模拟等领域...

  • 如何使用Three.js加载obj和mtl文件

    OBJ和MTL是3D模型的几何模型文件和材料文件。在最新的three.js版本(r78)中,以前的OBJMTLLoader类已废弃。现在要加载OBJ和MTL文件,需要结合OBJLoader和MTLLoade...

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

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

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

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

  • 如何使用CSS3实现书页(书本)卷角效果

    我们有时候想在页面显示一个公告或用户提示信息。一个常用设计是使用书签形状。我们可以给书签添加卷角效果,以使其更为逼真。所谓的“卷角”实际上可以用小角度...

  • SVG过滤器feColorMatrix矩阵变换效果用法详解

    在计算机图形学(数学)中,矩阵乘法可用于把空间向量进行几何变换。我们可以把颜色的值(RGBA)表示成一个四维空间向量:color = (r, g, b, a);那么就可以应用...

  • 更多...