图形学之纹理简单介绍/WebGL进行coding

1,561 阅读5分钟

背景

本篇收录于《数据可视化和图形学》专栏

首先Canvas 2D 和 WebGL上篇文章做了简单的对比,可以了解到WebGL的API相对来说比较难懂。所以我觉得后续的coding更多是采取WebGL来实现我们的效果(欸,就是玩~) 当然知识点还是从简单到复杂。。。如果一上来就介绍视觉物理,光照/全局光照,抗锯齿,延迟渲染,实时渲染....或许我创建专栏的初衷就丢掉了;当然如果有想深入讨论的可以私下交流。

上文实现了简单的2d图形 有需要可以参考上文

本篇大纲

  1. 什么是纹理?2d与3d纹理贴图区别?
  2. 纹理管线 The Texturing Pipeline
  3. coding (WebGL中简单使用纹理)

1. 什么是纹理?2d与3d纹理贴图区别?

在计算机图形学中,纹理贴图(Texturing)是使用图像、函数或其他数据源来改变物体表面外观的技术。

2d纹理

二维纹理(2D texture)是一张简单的位图图片,用于为三维模型提供表面点的颜色值

3d纹理

三维纹理(3D texture),可以被认为由很多张 2D 纹理组成,用于描述三维空间数据的图片。

2. 通用纹理管线 The Texturing Pipeline

渲染管线对于日常使用可能是个黑盒 但是理解这个对你的编程是有莫大的提高...(不懂也没关系本来这块介绍的也很浅 大部分从别的地方copy的. 哈哈) 简单来说,纹理(Texturing)是一种针对物体表面属性进行“建模”的高效技术。图像纹理中的像素通常被称为纹素(Texels),区别于屏幕上的像素。

请看下图---单个纹理应用纹理贴图的详细过程

image.png

  1. 投射函数(projector function) 就是将空间中的三维点转化为纹理坐标,也就是获取表面的位置并将其投影到参数空间中。例如有球形、圆柱、以及平面投影相关的函数
  2. 映射函数(The Corresponder Function)的作用是将参数空间坐标(parameter-space coordinates)转换为纹理空间位置(texture space locations)。
  • 第一步。通过将 投影方程(projector function) 运用于空间中的点 ,从而得到一组称为参数空间值(parameter-space values) 的关于纹理的数值。
  • 第二步。在使用这些新值访问纹理之前,可以使用一个或者多个映射函数(corresponder function)参数空间值(parameter-space values) 转换到纹理空间。
  • 第三步。使用这些纹理空间值(texture-space locations) 从纹理中获取相应的值(obtain value) 。例如,可以使用图像纹理的数组索引来检索像素值。
  • 第四步。再使用值变换函数(value transform function) 对检索结果进行值变换,最后使用得到的新来改变表面属性,如材质或者着色法线等等。

法线/法向量相关知识需要自行补充 在图形学中非常重要(经常会见到)

coding (WebGL中简单使用纹理贴图2d)

简单用WebGL实现一个纹理(贴图),并通过一个矩阵进行动画(旋转) 插入的gif有点卡 大概示意...

webgl2d.gif

1. shader编写 增加了纹理坐标 修改了顶点坐标(采用矩阵/旋转)

//vertex shader
attribute vec4 a_position;
attribute vec2 a_texcoord;  

uniform mat4 u_matrix;

varying vec2 v_texcoord;

void main() {
   gl_Position = u_matrix * a_position;
   // 纹理坐标传递
   v_texcoord = a_texcoord;
}
// fragment shader
precision mediump float;

varying vec2 v_texcoord;

uniform sampler2D u_texture;

void main() {
   gl_FragColor = texture2D(u_texture, v_texcoord);
}

2. 初始化context(渲染上下文) 以及 着色器程序

var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
    return;
}

3. 初始设置GLSL程序并新增缓冲区(纹理)

// 设置GLSL程序
  var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader", "fragment-shader"]);
  // 获取顶点坐标需要绑定的地方
  var positionLocation = gl.getAttribLocation(program, "a_position");
  var texcoordLocation = gl.getAttribLocation(program, "a_texcoord");
  // 获取 uniforms
  var matrixLocation = gl.getUniformLocation(program, "u_matrix");
  var textureLocation = gl.getUniformLocation(program, "u_texture");
 
 
  // 创建缓冲区
  var positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  var positions = [
    -1, -1,
    -1,  1,
     1, -1,
     1, -1,
    -1,  1,
     1,  1,
  ];
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  // 为纹理坐标创建缓冲区
  var texcoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
  var texcoords = [
    0, 0,
    0, 1,
    1, 0,
    1, 0,
    0, 1,
    1, 1,
  ];
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW);

4. 创建并加载纹理

 //解决图片跨域问题
 function requestCORSIfNotSameOrigin(img, url) {
    if ((new URL(url, window.location.href)).origin !== window.location.origin) {
      img.crossOrigin = "";
    }
  }
 // 创建一个纹理 { width: w, height: h, texture: tex }
 // 初始化1x1 px像素 图片加载完更新
 function loadImageAndCreateTextureInfo(url) {
    var tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    // Fill the texture with a 1x1 blue pixel.
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
                  new Uint8Array([0, 0, 255, 255]));

    // let's assume all images are not a power of 2
    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);

    var textureInfo = {
      width: 1,   
      height: 1,
      texture: tex,
    };
    var img = new Image();
    img.addEventListener('load', function() {
      textureInfo.width = img.width;
      textureInfo.height = img.height;

      gl.bindTexture(gl.TEXTURE_2D, textureInfo.texture);
      // 调用 texImage2D() 把已经加载的图片图形数据写到纹理
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
      render()
    });
    requestCORSIfNotSameOrigin(img, url);
    img.src = url;

    return textureInfo;
}
// 加载纹理
var texInfo = loadImageAndCreateTextureInfo('https://webglfundamentals.org/webgl/resources/leaves.jpg');

5. 绘制相关设置 纹理坐标并赋值矩阵坐标。 使用着色程序 绘制。

function render(time) {
    time *= 0.001; // time 加速度 旋转图片
    // 重新设置canvas大小
    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // 裁剪像素区
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.bindTexture(gl.TEXTURE_2D, texInfo.texture);

    // 使用着色器程序
    gl.useProgram(program);

    // 设置参数,让我们可以绘制任何尺寸的图像
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
    gl.enableVertexAttribArray(texcoordLocation);
    gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0);

    var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    var matrix = m4.scaling(1, aspect, 1);  //可是试试将aspect 改为0.1看看效果 - -
    matrix = m4.zRotate(matrix, time);
    matrix = m4.scale(matrix, 0.5, 0.5, 1);
    // 设置矩阵
    gl.uniformMatrix4fv(matrixLocation, false, matrix);

    // 从第0个unit开始获取纹理
    gl.uniform1i(textureLocation, 0);

    // 绘制 (2 triangles, 6 vertices)
    gl.drawArrays(gl.TRIANGLES, 0, 6);

    requestAnimationFrame(render);
}
  1. webgl-utils.js webgl相关函数封装工具库
  2. m4.js 矩阵相关数学函数库 完整代码示例 请点击git仓库查看代码示例

2D渲染方面你可能需要了解的有

  1. 纹理缓存
  2. 纹理压缩
  3. 2D/2D纹理优化
  4. 渲染优化...

最后

最后强烈希望大家学习相关理论知识;理论可能日常用到的地方很少,但是它能决定你走多远。(有的人问难怎么办,勤于练习吧),写作速度呢 该专栏我加速(一周1-2篇) 其他专栏(1篇) 计算机图形学相关的基础知识都会带一遍。然后后续主要写数据可视化方向。