webgl 12.Texture (纹理)

在 fragment shader 中计算颜色虽然灵活但不好实现复杂的效果。更常用的方法从一张图片采样得到颜色,把一张图片贴到几何体的表面,通常叫做纹理贴图,而采样的图片就叫做 texture image (纹理图片) 或 texture (纹理) 。

我们来实现把一张图片贴到正方形上面

texture.png

先来看看纹理坐标

texture coordinate system.png

这里用 (s, t) 表示, 有些书籍用的是 (u, v)
纹理坐标跟图片的大小无关,无论图片是长方形还是正方形,左下角都是 (0,0) 右上角是 (1,1)

纹理贴图要做的就是把图片的坐标映射到几何体坐标。这里我们采用下图的映射方式。

texture map.png

shader 中要加入纹理坐标

// vertex shader
var VERTEX_SHADER_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';

// fragment shader
var FRAGMENT_SHADER_SOURCE =
    'precision mediump float;\n' +
    'varying vec2 v_TexCoord;\n' +
    'uniform sampler2D u_Sampler;\n' +

    'void main() {\n' +
    '   gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' +
    '}\n';
var vertices = new Float32Array([
    -0.5, 0.5, 0.0, 1.0, // 前 2 位是位置坐标, 后 2 位是纹理坐标
    -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 image = new Image();
image.onload = function () {
    loadTexture(image);
    draw();
};
image.src = 'images/sky.jpg';

加载图片是一个异步的过程,对图片的处理要放在回调中
如果浏览器不允许加载本地图片,就自己用 node.js 等起个本地静态文件服务器

function loadTexture(image) {
    var texture = gl.createTexture();
    // 翻转 Y 轴
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    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);

    var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
    gl.uniform1i(u_Sampler, 0);
}

function draw() {
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}

下面我们重点分析一下 loadTexture 方法

  • gl.createTexture()

新建一个 texture object

  • gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)

翻转 Y 轴,因为图片的 Y 轴和纹理的 Y 轴正好是反的

flip y.png
  • gl.activeTexture(texUnit)

激活一个 texUnit

texture unit.png

webgl 通过 Texture Unit 来支持多个纹理图片,意思是你可以传多个纹理图片到 webgl 中使用。

  • gl.bindTexture(target, texture)

把 texture 绑定到 target 上。
target 可取 gl.TEXTURE_2D 和 gl.TEXTURE_CUBE_MAP ,用来告诉 webgl 纹理图片的类型。
gl.TEXTURE_2D 表示一个二维的纹理图片

  • gl.texParameteri(target, pname, param)

设置纹理参数

有 4 种纹理参数可以设置:
gl.TEXTURE_MAG_FILTER
gl.TEXTURE_MIN_FILTER
gl.TEXTURE_WRAP_S
gl.TEXTURE_WRAP_T

因为纹理图片的大小跟几何图形的大小不一定是相等的,如果大小相等很简单每个像素一一对应就行了。如果不相等就得通过参数指定映射的方法。

gl.TEXTURE_MAG_FILTER : 用于当纹理图片大小小于集合体大小的时候,这时纹理图片需要被放大,通过这个参数指定纹理图片放大后怎么映射。
gl.TEXTURE_MIN_FILTER : 用于当纹理图片大小大于几何体大小的时候,这时纹理图片需要被缩小,通过这个参数指定纹理图片缩小后怎么映射。
gl.TEXTURE_WRAP_S: 用于指定当把图片映射到几何体的部分子区域时,左右两边剩余的区域怎么填充。
gl.TEXTURE_WRAP_T : 用于指定当把图片映射到几何体的部分子区域时,上下两边剩余的区域怎么填充。

看看下图会清晰一点

texture parameter.png

参数可以设置的值看下表

texture parameter value.png

参数默认值看下表

texture parameter default value.png

我对这几个参数的理解也不是特别清晰,读者可参考其它资料,有懂的可以留言分享一下

  • gl.texImage2D(target, level, internalformat, format, type, image)

把图片拷贝到 GPU 中并和 texture object 绑定在一起
level:0 (设置为 0 ,本书不讲解这个参数)
internalformat: 指定图片的格式
format : 图片格式,跟 internalformat 值相同
type : 指定纹理的数据类型

图片格式可以取以下值:
gl.RGB
gl.RGBA
gl.ALPHA
gl.LUMINANCE
gl.LUMINANCE_ALPHA

图片数据类型 type 可以取以下值:
gl.UNSIGNED_BYTE (Each color component has 1 byte)
gl.UNSIGNED_SHORT_5_6_5 (RGB: Each component has 5, 6, and 5 bits)
gl.UNSIGNED_SHORT_4_4_4_4 (RGBA: Each component has 4, 4, 4, and 4 bits)
gl.UNSIGNED_SHORT_5_5_5_1 (RGBA: Each RGB component has 5 bits, and A has 1 bit)

  • gl.uniform1i(u_Sampler, 0)

把 0 赋值给 u_Sampler, 因为我们用了 TEXTURE0
fragment shader 中 sampler2D 其实是一个整型

  • vec4 texture2D(sampler2D sampler, vec2 coord)

fragment shader 中的 texture2D 是一个内置函数, 用纹理坐标 coord 从图片 sampler 中采样,返回的是采样的颜色

完整代码

// vertex shader
var VERTEX_SHADER_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';

// fragment shader
var FRAGMENT_SHADER_SOURCE =
    'precision mediump float;\n' +
    'varying vec2 v_TexCoord;\n' +
    'uniform sampler2D u_Sampler;\n' +

    'void main() {\n' +
    '   gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' +
    '}\n';

var canvas = document.getElementById("canvas");
var gl = canvas.getContext('webgl');
gl.clearColor(0.0, 0.0, 0.0, 1.0);

if (!initShaders(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)) {
    alert('Failed to init shaders');
}

var vertices = new Float32Array([
    -0.5, 0.5, 0.0, 1.0, // 前 2 位是位置坐标, 后 2 位是纹理坐标
    -0.5, -0.5, 0.0, 0.0,
    0.5, 0.5, 1.0, 1.0,
    0.5, -0.5, 1.0, 0.0
]);

initVertexBuffers(gl, vertices);

var image = new Image();
image.onload = function () {
    loadTexture(image);
    draw();
};
image.src = 'images/sky.jpg';

function initVertexBuffers(gl, vertices) {
    var vertexBuffer = gl.createBuffer();
    if (!vertexBuffer) {
        console.log('Failed to create buffer object');
        return -1;
    }

    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    var FSIZE = vertices.BYTES_PER_ELEMENT;

    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 4 * FSIZE, 0);
    gl.enableVertexAttribArray(a_Position);

    var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
    gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 4 * FSIZE, 2 * FSIZE);
    gl.enableVertexAttribArray(a_TexCoord);
}

function loadTexture(image) {
    var texture = gl.createTexture();
    // 翻转 Y 轴
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    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);

    var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
    gl.uniform1i(u_Sampler, 0);
}

function draw() {
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}

查看源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容