ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

【笔记】WebGL编程指南学习(4)

2022-03-20 22:35:28  阅读:283  来源: 互联网

标签:WebGL 编程 笔记 纹理 片元 坐标 顶点 gl 着色器


WebGL编程指南学习(4)

4. 最后一块拼图

在学会处理顶点,包括处理顶点的坐标、Javascript和WebGL管线的数据通信、坐标变换之后,还需要处理顶点的其他数据——如颜色等。此外,还需要处理将图像(或纹理)映射到图形或三维对象表面上。这就是WebGL的最后一块拼图。

  • 将顶点的其他(非坐标)数据(如颜色)传入顶点着色器
  • 发生在顶点着色器和片元着色器之间的从图形到片元的转化,又称为图元光栅化
  • 将图像(或称纹理)映射到图形或三维对象的表面上

4.1 将非坐标数据传入顶点着色器

4.1.1 第一个例程:绘制不同尺寸的点

回顾:顶点坐标传入着色器的仪式感

  1. 创建缓冲区对象;
  2. 将缓冲区对象绑定到target上;
  3. 将顶点坐标数据写入缓冲区对象;
  4. 将缓冲区对象分配给对应的attribute变量
  5. 开启attribute变量

顶点相关数据送入顶点着色器的步骤是一模一样的

注意:WebGL系统创建的缓冲区对象是不同的,然后分配给不同的attribute变量

新加代码如下:

    // 将顶点size写入顶点着色器
    gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW);
    var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
    gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(a_PointSize);
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=45c69ad2915a493ea0d92b43f705b617.png?,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55WM5piO5Z-O,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)

问题:使用多个缓冲区对象传递多种数据,适合数据量不大的情况。当顶点数据很多时,有没有高效的办法?

答:可以把顶点的所有相关数据打包到同一个缓冲区对象中

再问:那打包到一起以后,怎么将它们分配给不同的attribute啊?

答:有办法的,而且不止一种办法

交错组织(iterleaving)

  • 首先,顶点坐标和点的尺寸统一写进结构化数组verticesSizes
  • 然后,通过BYTES_PER_ELEMENT获得数组每个元素的大小,方面后续按地址查找
  • 再次,活用vertexAttribPointer里的stride(步长)和offset(步长内的偏移),正确找到不同类型的数据(坐标、点的尺寸)
  • 其他与正常写法相同
// 顶点坐标和点的尺寸
var verticesSizes = new Floay32Array([
  0.0, 0.5, 10.0 // 第一个点
  -0.5, -0.5, 20.0 // 第二个点
  0.5, -0.5, 30.0 // 第三个点
])
// 获取结构化数组元素的大小
var FSIZE = verticesSizes.BYTES_PER_ELEMENT;
...
// 获取a_Position的存储位置,分配缓冲区并开启
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
...
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0);
gl.enableVertexAttribArray(a_Position);
// 步长为3(3个float,其中坐标是第一个,所以offset是0
// 获取a_PointSize的存储位置,分配缓冲区并开启
var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
...
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2)
gl.enableVertexAttribArray(a_PointSize);
// 步长为3, offset是当前步长内偏移两个元素

gl.vertexAttribPointer(location, size, type, normalized, stride, offset)

  • size: 指定缓冲区中每个顶点的分量个数(1到4)
  • normalized:是否将非浮点数归一化
  • stride:指定相邻两个顶点间的字节数,默认为0
  • offset:指定缓冲区对象中的偏移量(以字节为单位)

4.1.2 第二个例程:修改颜色

  • 方法和前面一样,只不过将顶点尺寸数据改为顶点颜色

  • 顶点的颜色数据从JavaScript传给顶点着色器中的attribute变量,但真正影响绘制颜色的gl_FragColor在片元着色器中

顶点着色器使用varying变量向片元着色器传输数据(uniform变量也可以)

在这里插入图片描述

要点

  • 顶点着色器只能接受attribute,所以需要转运一下,把attribute转给varying
  • 只要顶点着色器和片元着色器里的varying变量名一样,就算是对上了
  • varying变量只能是float的(以及相关的veci,mati)

4.2 顶点着色器和片元着色器之间的数据传输细节

4.2.1 几何形状的装配和光栅化

提问:就给了3个点的坐标,怎么就变成了三角形呢?三角形的内部像素是怎么填充的?

答:很多细节被掩盖了

在这里插入图片描述

在顶点着色器和片元着色器中间,还有两个步骤:图形装备光栅化过程

  • 图形装配:将孤立的顶点坐标装配成几何图形,几何图形的类别由gl.drawArray()的第一个参数决定;
  • 光栅化:将装配好的几何图形转化为片元

在这里插入图片描述

  • gl_Position实际上是几何图形装配(geometric shape assembly,或图元装配,primitive assembly process)的输入,装配输出数据叫图元(primitives)
  • 片元数目就是图元最终在屏幕上所覆盖的像素数,是光栅化过程决定的

4.2.2 调用片元着色器

  • 光栅化结束后,程序就开始逐片元地调用片元着色器。假设光栅化最终决定图片覆盖了屏幕上的10个像素,那么片元着色器就要被调用10次,每调用一次,处理一个片元
  • 对每个片元,片元着色器计算该片元的颜色,并写入颜色缓冲区。所有片元被处理完成后,颜色缓冲区输出到浏览器

在这里插入图片描述

  • 光栅化过程生成的片元都是带有坐标信息的,调用片元着色器时,这些坐标信息也随着片元传了进去。可以通过片元着色器中的内置变量来访问片元的坐标
vec4 gl_FragCoord // 该内置变量的第1个和第2个分量表示片元在<canvas>坐标系统(窗口)中的坐标值
// 使用gl_FragCoord参与颜色计算
// Fragment Shader
var FSHADER_SOURCE = 
'precision mediump float;\n' +
'uniform float u_Width;\n' +
'uniform float u_Height;\n' +
'void main(){\n' +
'   gl_FragColor = vec4(gl_FragCoord/u_Width, 0.0, gl_FragCoord.y/u_Height, 1.0);\n' +
'}\n';
// 从JavaScript传数据
    var u_Width = gl.getUniformLocation(gl.program, 'u_Width');
    ...
    var u_Height = gl.getUniformLocation(gl.program, 'u_Height');
    ...
    // 传递u_Width和u_Height
    gl.uniform1f(u_Width, gl.drawingBufferWidth);
    gl.uniform1f(u_Height, gl.drawingBufferHeight);

在这里插入图片描述

4.2.3 varying变量的作用和内插过程

提问:为什么把v_Color赋给每个顶点,最后得到的三角形是渐变的呢?

回答:我们把顶点的颜色付给了顶点着色器中的varying变量,v_Color,它的值被传给片元着色器中的同名、同类型的变量

但是,更准确地说,顶点着色器中的v_Color在传入片元着色器前,经过了内插过程。因此,片元着色器中的v_Color变量和顶点着色器的v_Color实际上并不是一回事——它是变化的(varying)

4.3 为图形贴上图像

为每个小图元设置不同的颜色和位置,可以模拟真实的场景——但是太繁琐了!

  • 使用纹理映射,可以简单地将一张图像映射到一个几何图形的表面上去。此时,这张图片又可称为纹理图像纹理

  • 纹理映射,就是根据纹理图像,为光栅化后的每个片元涂上合适的颜色

  • 组成纹理图像的像素又可称为纹素(texels)

4.3.1 WebGL中纹理映射的步骤

  1. 准备好映射到几何图形上的纹理图像
  2. 为几何图形配置纹理映射的方式
  3. 加载纹理图像,对其进行一些配置,以在WebGL中使用它
  4. 在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元

说明

  • 第2步指定映射方式,就是确定“几何图形的某个片元”的颜色如何取决于“纹理图像中哪个(或哪几个)像素”的问题。利用图形的顶点坐标来确定屏幕上哪部分被纹理图像覆盖;利用纹理坐标确定纹理图像的哪部分覆盖到几何图形上

纹理坐标

  • WebGL的纹理坐标是二维的(st坐标系统),s轴水平向右,t轴垂直向上。左下角(0.0,0.0);右下角(1.0,0.0);右上角(1.0,1.0)
  • 通过纹理图像的纹理坐标与几何形体顶点坐标间的映射关系,确定怎样将纹理图像贴上去

4.3.2 例程:纹理映射

  • 顶点着色器:varying变量,TexCoord,传递纹理坐标
var VSHADER_SOURCE = 
'attribute vec4 a_Position;\n' +
'attribute vec2 a_TexCoord;\n' +
'varying vec2 v_TexCoord;\n' +
'void main(){\n' +
'   gl_Position = a_Position;\n' +
'   v_TexCoord = a_TexCoord;\n' +
'}\n';
  • 片元着色器:接收TexCoord,还有纹理采样器u_Sampler,调用texture2D从纹理图像抽取纹素颜色,赋给当前片元
var FSHADER_SOURCE = 
'precision mediump float;\n' +
'uniform sampler2D u_Sampler;\n' +
'varying vec2 v_TexCoord;\n' +
'void main(){\n' +
'   gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' +
'}\n';
  • 设置纹理坐标、创建顶点缓冲区并分配属性
function main(){
...
    var n = initVertexBuffers(gl);
...
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    // 设置纹理
    if (!initTextures(gl, n)){
        console.log('Failed to intialize the texture.');
        return;
    }
}

function initVertexBuffers(gl){
    var verticesTexCoords = new Float32Array([
        // 顶点坐标,纹理坐标
        -0.5, 0.5, 0.0, 1.0,
        -0.5, -0.5, 0.0, 0.0,
        0.5, 0.5, 1.0, 1.0,
        0.5, -0.5, 1.0, 0.0,
    ]);
    var n = 4;

    // 创建VBO
...
    // 传入顶点着色器
    var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
...
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
    gl.enableVertexAttribArray(a_Position);
    var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
...
    gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
    gl.enableVertexAttribArray(a_TexCoord);

    return n;
}
  • 准备待加载的纹理图像,并命令浏览器读取它
function initTextures(gl, n){
    var texture = gl.createTexture(); // 创建纹理对象
...
    // 找到u_Sampler
    var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
...
    // 创建图像对象
    var image = new Image();
...
    // 注册事件处理器,响应图像加载调用
    image.onload = function(){loadTexture(gl, n, texture, u_Sampler, image);};
    // 告诉浏览器加载图像
    image.src = '../resources/sky.jpg';

    return true;
}
  • 监听纹理图像的加载时间,加载纹理图像
function loadTexture(gl, n, texture, u_Sampler, image){
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 沿着y轴翻转图像
    // 启用纹理单元0
    gl.activeTexture(gl.TEXTURE0);
    // 绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);
    
    // 设置纹理参数
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    // 设置纹理图像
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);

    // 设置纹理单元0的采样器
    gl.uniform1i(u_Sampler, 0);

...
}

设置纹理坐标(initVertexBuffers)

没啥好说的

配置和加载纹理(initTextures)

  • gl.createTexture()创建纹理对象;纹理对象管理WebGL系统中的纹理
  • u_Sampler(取样器,采样器),用来接收纹理图像
  • 与OpenGL不同,WebGL加载纹理图像是异步的。浏览器发起加载图请求,与图像加载这两个步骤是异步的

为WebGL配置纹理(loadTexture)

  • 使用纹理前,需要给它沿着Y轴翻转一下,因为WebGL纹理坐标系统的t轴的方向和传统图片的坐标系统的Y轴是反的->gl.pixelStorei(pname, param)
  • 激活纹理单元gl.activeTexture()。WebGL通过纹理单元机制同时使用多个纹理
    • 系统支持的纹理单元个数取决于硬件和浏览器的WebGL实现。默认至少支持8个纹理单元,gl.TEXTURE0...gl.TEXTURE7
  • 绑定纹理对象,WebGL支持两种类型的纹理:二维纹理gl.TEXTURE_2D和立方体纹理gl.TEXTURE_CUBE_MAP ; 注意gl.bindTexture 做了两件事,开启纹理对象,以及将纹理对象绑定到纹理单元上。所以纹理缓冲这里没有enable函数
  • 配置纹理对象的参数gl.texParameteri(target, pname, param)
    • 通过pname指定4中纹理参数:
      • gl.TEXTURE_MAG_FILTER:放大方法,当纹理绘制范围比纹理大时,如何填充空隙
      • gl.TEXTURE_MIN_FILTER:缩小方法,当纹理绘制范围比纹理小时,如何提出像素
      • gl.TEXTURE_WRAP_S|gl.TEXTURE_WRAP_T:水平|垂直填充方法,如何对纹理图像左右侧|上下方区域进行填充
    • 通过param指定具体方式
      • 可以赋给放大和缩小方法的参数:gl.LINEAR 线性加权;gl.NEAREST 最近邻像素的颜色值
      • 可以赋给水平|垂直填充方法的参数:gl.REPEAT 平铺重复; gl.MIRRORED_REPEAR 镜像对称式的重复;gl.CLAMP_TO_EDGE 使用纹理图像边缘值拉伸
      • 缩小方法有一种特殊的参数:gl.NEAREST_MINMAP_LINEAR,构造金字塔纹理
  • 将纹理图像分配给纹理对象gl.texImage2D(target, level, internalformat, format, type, image)
    • target TEXTURE_2D还是TEXTURE_CUBE_MAP
    • level 金字塔纹理的层级
    • internalformat 图像的内部格式
      • RGB,对jpg格式;RGBA,对PNG格式,LUMINANCE,灰度图像;还有其他
    • format 纹理数据的格式,必须使用与internalformat相同的值
    • type 纹理数据的类型
  • 将纹理单元传递给片元着色器gl.uniform1i()
    • 必须将着色器中表示纹理对象的uniform变量声明为一种特殊的、专为纹理对象的数据类型(sampler2D|samplerCube)
    • initTexture()u_Sampler传给了loadTexture()
    • 必须通过指定纹理单元编号将纹理对象传给u_Sampler,第二个参数指定了纹理单元编号
  • 从顶点着色器向片元着色器传输纹理坐标
    • 通过使用varying变量,传递的纹理坐标是内插后的
    • 片元着色器根据纹理坐标,从纹理图像(u_Sampler)抽取出纹素的颜色,然后涂到当前的片元上
  • 在片元着色器中获取纹理像素颜色(vec4 texture2D(sampler2D u_Sampler, vec 2 v_TexCoord))
    • u_Sampler, 指定纹理单元编号
    • v_TexCoord, 指定纹理坐标
    • 返回值是纹理坐标处像素的颜色,格式由internalformat参数决定

在这里插入图片描述

使用WebGL加载图像

  • 使用WebGL加载本地图像有问题,所以尝试加载在线图像
  • 经过研究,发现在线图像存在跨域请求(CrossDomain)问题,需要图片网站授权访问
  • 参考文献:WebGLFundamentals<-- 宝藏网站!

标签:WebGL,编程,笔记,纹理,片元,坐标,顶点,gl,着色器
来源: https://blog.csdn.net/wangxiaochaun/article/details/123623119

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有