美文网首页
webgl 12.Texture (纹理)

webgl 12.Texture (纹理)

作者: lesliefang | 来源:发表于2017-08-29 20:01 被阅读323次

    在 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);
    }
    

    查看源码

    相关文章

      网友评论

          本文标题:webgl 12.Texture (纹理)

          本文链接:https://www.haomeiwen.com/subject/lhradxtx.html