背景
本篇收录于《数据可视化和图形学》专栏
首先Canvas 2D 和 WebGL上篇文章做了简单的对比,可以了解到WebGL的API相对来说比较难懂。所以我觉得后续的coding更多是采取WebGL来实现我们的效果(欸,就是玩~) 当然知识点还是从简单到复杂。。。如果一上来就介绍视觉物理,光照/全局光照,抗锯齿,延迟渲染,实时渲染....或许我创建专栏的初衷就丢掉了;当然如果有想深入讨论的可以私下交流。
上文实现了简单的2d图形 有需要可以参考上文
本篇大纲
- 什么是纹理?2d与3d纹理贴图区别?
- 纹理管线 The Texturing Pipeline
- coding (WebGL中简单使用纹理)
1. 什么是纹理?2d与3d纹理贴图区别?
在计算机图形学中,纹理贴图(Texturing)是使用图像、函数或其他数据源来改变物体表面外观的技术。
2d纹理
二维纹理(2D texture)是一张简单的位图图片,用于为三维模型提供表面点的颜色值
3d纹理
三维纹理(3D texture),可以被认为由很多张 2D 纹理组成,用于描述三维空间数据的图片。
2. 通用纹理管线 The Texturing Pipeline
渲染管线对于日常使用可能是个黑盒 但是理解这个对你的编程是有莫大的提高...(不懂也没关系本来这块介绍的也很浅 大部分从别的地方copy的. 哈哈)
简单来说,纹理(Texturing)是一种针对物体表面属性进行“建模”的高效技术。图像纹理中的像素通常被称为纹素(Texels),区别于屏幕上的像素。
请看下图---单个纹理应用纹理贴图的详细过程
- 投射函数(projector function) 就是将空间中的三维点转化为纹理坐标,也就是获取表面的位置并将其投影到参数空间中。例如有球形、圆柱、以及平面投影相关的函数
- 映射函数(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有点卡 大概示意...
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);
}
webgl-utils.js
webgl相关函数封装工具库m4.js
矩阵相关数学函数库
完整代码示例 请点击git仓库查看代码示例
2D渲染方面你可能需要了解的有
- 纹理缓存
- 纹理压缩
- 2D/2D纹理优化
- 渲染优化...
最后
最后强烈希望大家学习相关理论知识;理论可能日常用到的地方很少,但是它能决定你走多远。(有的人问难怎么办,勤于练习吧),写作速度呢 该专栏我加速(一周1-2篇) 其他专栏(1篇) 计算机图形学相关的基础知识都会带一遍。然后后续主要写数据可视化方向。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。