本文由云+社区发表

作者:ivweb qcyhust

导语

WebGL绘制图像时,往着色器中传入颜色信息就可以给图形绘制出相应的颜色,现在已经知道顶点着色器和片段着色器一起决定着向颜色缓冲区写入颜色信息并最终呈现出来,那么这个过程是什么样,如果图形的颜色需要用现有图片来渲染那么又该如何操作?

颜色缓冲区

在绘制开始前,经常见到调用函数清空画布的代码gl.clear(gl.COLOR_BUFFER_BIT),清空画布的绘图区实际上就是用之前定义好的背景颜色将颜色缓冲的的颜色清除。颜色缓冲区中存放着需要显示到画布上的像素的颜色数据,它属于帧缓存的一部分,与深度缓存、模板缓存等一起决定着最终画布上图像的显示信息。

可以将颜色缓存区看成图像颜色存储器,在缓存区中以RGB或RGBA的格式存储着画布上每一个像素的颜色信息,各个像素点组合起来就构成了颜色缓存的矩形阵列。这个定义看起来与图片存储器是很相似的,颜色缓存为RGB或是RGBA每一个通道分配存放位数,其中RGB就是颜色数据,A表示alpha也就是该像素的透明度信息,颜色占用的位数值就是颜色深度,比如颜色深度为24位,表示每一个像素24位,一般24位的分配方案就是红色、蓝色、绿色各占8位,如果需要透明效果的话,可以采用32位颜色深度为alpha通道分配8位。

这里可以总结得出,画布上各个像素点呈现的颜色就是存放在颜色缓冲区的颜色信息所决定的,而绘制图形的颜色缓冲区的信息又是由顶点着色器决定。要知道颜色如何渲染就要深入分析着色器的工作过程。

图形装配

要绘制一个三角形,我们是这样定义着色器的:

// 顶点着色器
const VSHADER_SOURCE =
`attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}`; // 片段着色器
const FSHADER_SOURCE =
`void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}`;

之后通过gl.program将顶点position坐标传入顶点着色器,这就相当于在画布上确定了几个点的坐标信息,这些点需要用线条连接起来才能构成图形,这个由顶点坐标装配成几何图形的过程就叫做图形装配。

被装配的基本图形被称作图元,它包含点、线、面等基本几何图形。在调用WebGL的drawArrays或drawElements方法时作为参数传入,从而指定图元类型。

一个三角形的绘制过程拆分来看就是执行三次顶点着色器,将三个点坐标都传入装配区,根据绘制函数的图元参数gl.TRIANGLES将三个点装配成三角形,然后进入下一个过程——光栅化。

光栅化

简单来说,光栅化就是将图形转化成片元,可以理解成一个个像素。只有将图形转化成像素后才能交由片段着色器处理。

光栅化结束后,WebGL执行片段着色器。每执行一次片段着色器就处理一个片元,将该片元的颜色写入颜色缓冲区中,等到图形中所有的片元处理完毕画布上就得到了最后的图像。

如上面的例子,每一个片元都会被执行成红色,由这一个个红色像素组成的三角形也就是红色的。

如果要绘制一个多颜色三角图形又是一个什么过程呢?首先需要修改着色器的定义,也许可以这样:

// 顶点着色器
const VSHADER_SOURCE =
`attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}`; // 片段着色器
const FSHADER_SOURCE =
`varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}`;

向顶点着色器传入顶点坐标和颜色两个数据,执行三次后得到三角形三个顶点的坐标和颜色,接下来通过图元装配得到一个三角形的图元,到了关键的光栅化这一步,该如何定义片元的颜色呢?WebGL采用一个叫做内插的过程来计算颜色的值。

以一条线为例来解释内插,两个端点分别为(1.0,0.0,0.0)和(0.0,1.0,0.0),从一端到另一端,R的值从1.0降到0.0,G的值由0.0升到1.0,线上的所有点颜色值都这样计算出来,实现了平滑的颜色渐变,这就是内插。

经过内插,图形的每一个片元都指定了自己的颜色,写入颜色缓冲区后呈现出来。

纹理贴图

如果要为WebGL创建更加复杂更加自然的现实效果,就需要采用贴图来将现成的图片贴到图形上。

图片容器中存放的也是一个个RGB或RGBA的像素,将图片的信息读取后存放在纹理对象或者说纹理图像中,纹理图像有自己的坐标系,坐标中每一个单元格就存放的纹理图像的像素信息,也被称作纹素。

将纹理图像的坐标转换到画布上图形的坐标的映射过程就是纹理映射,这个过程中,为图形顶点指定了纹理坐标,剩下的颜色由内插计算得出,写入颜色缓冲区后,图形的表面就被贴上了图像的颜色。

用一个案例来实现纹理贴图,现在要做的是:

  • 加载好需要的纹理图像
  • 设置纹理坐标
  • 对纹理进行配置
  • 片段着色器抽出纹素并赋值给片元

在这个例子中我选择提前加载图片。在这里要注意有的浏览器不允许访问本地文件,可以考虑自己搭建server或是开启浏览器访问本地文件。

function main() {
const canvas = document.getElementById('webgl'); const webgl = getWebGLContext(canvas);
webgl.images = {}; // 初始化之前先加载图片
loadImage([
`src/images/0.jpeg`,
], webgl).then((gl) => {
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
} gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); const n = initVertexBuffers(gl);
initTextures(gl, n, 0);
});
}

loadImage的实现很简单,用一个promise来处理异步加载图片,传入数组为了之后支持多张图片。在initVertexBuffers中创建数据buffer,将图形顶点和纹理图像坐标一起传入着色器。

function initVertexBuffers(gl) {
// 顶点坐标和纹理图像坐标
const vertices = new Float32Array([
-0.3, 0.7, 0.0, 0.0, 1.0,
-0.3, -0.7, 0.0, 0.0, 0.0,
0.3, 0.7, 0.0, 1.0, 1.0,
0.3, -0.7, 0.0, 1.0, 0.0,
]); const FSIZE = vertices.BYTES_PER_ELEMENT; const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
const a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord'); gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 5, 0);
gl.enableVertexAttribArray(a_Position); gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 5, FSIZE * 3);
gl.enableVertexAttribArray(a_TexCoord); return 4;
}

然后看看最主要的initTextures,在这里配置纹理:

function initTextures(gl, n, index) {
// 创建纹理对象
const texture = gl.createTexture();
const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
const image = gl.images[index]; // WebGL纹理坐标中的纵轴方向和PNG,JPG等图片容器的Y轴方向是反的,所以先反转Y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 激活纹理单元,开启index号纹理单元
gl.activeTexture(gl[`TEXTURE${index}`]); // 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture); // 配置纹理对象的参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); // 将纹理图像分配给纹理对象
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); // 将纹理单元编号传给着色器
gl.uniform1i(u_Sampler, index); // 绘制
gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
}

这里又遇到两个概念:

纹理对象配置参数 texParameteri方法用来配置纹理对象参数,函数第二个参数传入配置参数名,第三个参数传入配置参数值,可以配置的参数有:

  • 伸展(gl.TEXTURE_MAX_FILTER): 绘制图形比纹理图像大的时候怎么取纹素,默认值gl.LINEAR
  • 收缩(gl.TEXTURE_MIN_FILTER): 绘制图形比纹理图像小的时候怎么取纹素, 默认值gl.NEAREST_MIP_LINEAR
  • 水平填充(gl.TEXTURE_WRAP_S): 定义绘制图形水平方向如何填充,默认值gl.REPEAT
  • 垂直填充(gl.TEXTURE_WRAP_T): 定义绘制图形垂直方向如何填充,默认值gl.REPEAT

详细参考texParameteri

纹理单元 如果需要使用多张图片就要管理多个纹理图片,WebGL为了使用多个纹理,用纹理单元来处理纹理图像。WebGL的实现至少支持8个纹理单元,分别用gl.TEXRTRUE0,gl.TEXRTRUE1,...,gl.TEXRTRUE7来表示。

最后是着色器代码,在调用gl.drawArrays传入图元类型TRIANGLE_STRIP后执行:

const VSHADER_SOURCE =
`attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}`; const FSHADER_SOURCE =
`precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}`;

顶点着色器中传入纹理图像的顶点坐标,将它传递给片段着色器,在片段着色器中声明了一个专用于纹理对象的数据类型sampler2D,指向一个纹理单元编号(接下来解释),着色器获取纹素由函数texture2D完成,传入参数纹理单元编号和纹理图像坐标。

多纹理实现

要使用多个纹理就要用到更多的纹理单元,多个纹理可以组合也可以单独渲染,利用前面的代码,可以很容易扩展成一起多纹理的案例,加上一些3D效果和动画,就可以组合成一个轮播图片。

此文已由腾讯云+社区在各渠道发布

获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

WebGL 纹理颜色原理的更多相关文章

  1. WebGL入门教程(五)-webgl纹理

    前面文章: WebGL入门教程(一)-初识webgl WebGL入门教程(二)-webgl绘制三角形 WebGL入门教程(三)-webgl动画 WebGL入门教程(四)-webgl颜色 这里就需要用到 ...

  2. TRIZ系列-创新原理-32-改变颜色原理

    改变颜色原理的详细描写叙述例如以下:1)改变物体或其环境的颜色:2)改变物体或其环境的透明度:3)对于难以看到的物体或过程.使用颜色加入剂来观測.4)假设已经使用了这样的加入剂,那么使用发光跟踪或原子 ...

  3. RGB颜色原理

    参考:http://www.cnblogs.com/harrytian/archive/2012/12/12/2814210.html 工作中经常和颜色打交道,但却从来没有从原理上了解一下,这篇文章希 ...

  4. WebGL的颜色渲染-渲染一张DEM(数字高程模型)

    目录 1. 具体实例 2. 解决方案 1) DEM数据.XYZ文件 2) showDEM.html 3) showDEM.js 4) 运行结果 3. 详细讲解 1) 读取文件 2) 绘制函数 3) 使 ...

  5. 拆解探索MagSafe电源接口结构和指示灯变颜色原理

    你有没有想过一个Mac的MagSafe接头里面有什么? 控制光线是什么? 在Mac如何知道它是什么样的充电器? 本文探讨的MagSafe连接器内,并回答这些问题. 2006年由苹果公司推出的MagSa ...

  6. web性能权威指南(High Performance Browser Networking)

    web性能权威指南(High Performance Browser Networking) https://www.cnblogs.com/qcloud1001/p/9663524.html HTT ...

  7. WebGL 颜色与纹理

    1.纹理坐标 纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹理颜色.WebGL系统中的纹理坐标系统是二维的,如图所示.为了将纹理坐标和广泛使用的x.y坐标区分开来,WebGL使用s和t ...

  8. 图解WebGL&Three.js工作原理

    “哥,你又来啦?”“是啊,我随便逛逛.”“别介啊……给我20分钟,成不?”“5分钟吧,我很忙的.”“不行,20分钟,不然我真很难跟你讲清楚.”“好吧……”“行,那进来吧,咱好好聊聊” 一.我们讲什么? ...

  9. WebGL学习之纹理贴图

    为了使图形能获得接近于真实物体的材质效果,一般会使用贴图,贴图类型主要包括两种:漫反射贴图和镜面高光贴图.其中漫反射贴图可以同时实现漫反射光和环境光的效果. 实际效果请看demo:纹理贴图 2D纹理 ...

随机推荐

  1. springboot项目打包

    使用IDEA或Eclipse的插件创建springboot项目的时候可以选择打包方式,一般情况下都是选择的jar包. 当想将原来的jar包格式的项目打成war包在本地tomcat下运行时可以通过以下几 ...

  2. PHP生成图表pChart

    pChart是一个开源的图表生成库,主要涉及3个Class:pChart.class, pData.class, pCache.class,可生成20多种简单或复杂的图表,支持PNG,JPG,GIF通 ...

  3. python学习笔记(5)

    .................................................................................................... ...

  4. RDD算子

    RDD算子 #常用Transformation(即转换,延迟加载) #通过并行化scala集合创建RDD val rdd1 = sc.parallelize(Array(1,2,3,4,5,6,7,8 ...

  5. Linux SVN服务器的搭建配置及分支的创建与合并

    第一步:通过yum命令安装svnserve,命令如下: >yum -y install subversion 若需查看svn安装位置,可以用以下命令: >rpm -ql subversio ...

  6. dedecms 后台可以上传mp4,但无法选择

    原文链接 找到 /include/dialog/select_media.php 找到rmvb,在其后面加 “|mp4” 即可. 1

  7. Jvm 内存模型 —— GC

    一.Jvm 原理 二.Jvm 运行时数据区( Run-Time Data Areas ) (主要是关于 non-stack 区域的详细划分) 从上图可以清楚地看到:程序计数器.Jvm 栈.本地方法栈 ...

  8. 无需sendmail:巧用LD_PRELOAD突破disable_functions

    *本文原创作者:yangyangwithgnu,本文属FreeBuf原创奖励计划,未经许可禁止转载 摘要:千辛万苦拿到的 webshell 居然无法执行系统命令,怀疑服务端 disable_funct ...

  9. 快速实现office文档在线预览展示(doc,docx,xls,xlsx,ppt,pptx)

    微软:https://view.officeapps.live.com/op/view.aspx?src=(输入你的文档在服务器中的地址):

  10. [Swift]LeetCode162. 寻找峰值 | Find Peak Element

    A peak element is an element that is greater than its neighbors. Given an input array nums, where nu ...