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(11) views8738 comments0

发送私信

最新评论

请先 登录 再评论.
相关文章
  • CentOS6 Apache2.2多站点HTTPS配置

    可以使用letsencrypt(certbot)免费证书服务。支持多系统、多站点和多目录,支持wildcard(通配符域名),90天生效,可用定时任务自动更新。需要注意一点的是apache2.4以下版本需要在默认的ssl配置中添加如下的指令:NameVirtualHost

  • Monaco Editor 编辑器拷贝粘贴功能调用和获取选中文本

    有时候需要在monaco editor外部调用编辑器的内置功能比如希望在页面主工具栏实现一些快捷操作。button

  • html5跨平台实战-第一周-水平测验-新闻列表页面

    这是一个DIV+CSS布局页面的一个实例,主要介绍POSITION定位、导航UL LI的制作、利用浮动原理对页面分栏、分列的页面布局。新闻页面的效果图

  • CSS3属性选择器特性使用详解

    CSS3除了引入动画、滤镜(用于特效)以及新的布局技术外,在选择器(selector)方面也有增强。属性选择器根据元素的属性(attributes)来匹配。这可以是一个单独...

  • 前端开发框架技术选型:Angular2 VS React VS jQuery

    Angular和React是主流的2个前端开发框架,但是严格来说两者并非对等的概念。Angular是一个基于MVC(或者MVVM)的框架,包含model(模型)/view(视图)/controll...

  • 深入理解CSS3滤镜(filter)功能和实例详解

    CSS3滤镜功能源自SVG滤镜规范,SVG滤镜最早用来给矢量图添加类似PS中像素图的一些特效。
    把这个滤镜功能引入到普通HTML元素中可以带来很有趣的效果(模糊、...

  • 使用HTML5 FileReader和Canvas压缩用户上传的图片

    手机用户拍的照片通常会有2M以上,这对服务器带宽产生较大压力。因此在某些应用下(对图片要求不那么高)我们可以在客户端来压缩图片,然后再提交给服务器。总体...

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

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

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

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

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

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

  • WebVR简介和常用资源链接

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

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

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

  • WebGL入门教程4 - 使用纹理贴图(Texture Map)

    3D建模和纹理贴图的关系就好比人体和皮肤(或着装)的关系,3D建模用来处理空间属性,而贴图适合用来处理细腻的表面属性。如果不使用贴图,而想在表面达到足够的...

  • 更多...