WebGL 纹理的使用

一、引言

在 WebGL 中,纹理是创建逼真 3D 场景不可或缺的一部分。它可以为物体表面添加细节,使其看起来更加真实和生动。纹理可以是图像、图案或者其他形式的数据,通过将其映射到 3D 模型的表面,能够极大地增强场景的视觉效果。本文将从基础的纹理概念入手,逐步深入探讨 WebGL 中纹理的使用方法,包含大量的代码示例,帮助你更好地理解和掌握这一重要技术。

二、纹理基础概念

2.1 什么是纹理

纹理可以理解为一张图像,它被 “贴” 在 3D 模型的表面。例如,在创建一个虚拟的木质箱子时,我们可以使用一张木纹的图像作为纹理,这样箱子看起来就像由真实的木材制成。在 WebGL 中,纹理可以是 JPEG、PNG 等常见的图像格式。

2.2 纹理坐标

纹理坐标是用来指定纹理图像上的点如何映射到 3D 模型表面的坐标系统。它的范围是从 0 到 1,其中 (0, 0) 表示纹理图像的左下角,(1, 1) 表示右上角。例如,纹理坐标 (0.5, 0.5) 表示纹理图像的中心点。

2.3 纹理单元

WebGL 支持多个纹理单元,每个纹理单元可以存储一个纹理。在使用多个纹理时,需要指定使用哪个纹理单元。纹理单元通过编号来标识,从 0 开始,例如 gl.TEXTURE0gl.TEXTURE1 等。

三、基础纹理加载与使用

3.1 初始化 WebGL 上下文

首先,我们需要创建一个 WebGL 上下文,这是使用 WebGL 的基础。以下是创建 WebGL 上下文的代码:

function initWebGL(canvas) {
    var gl = null;
    try {
        gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    } catch (e) {}
    if (!gl) {
        alert("无法初始化 WebGL,你的浏览器可能不支持。");
        return null;
    }
    return gl;
}

3.2 创建和绑定纹理

接下来,我们要创建一个纹理对象,并将其绑定到当前的纹理单元。代码如下:

function createAndBindTexture(gl) {
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    return texture;
}

3.3 加载纹理图像

我们使用 JavaScript 的 Image 对象来加载纹理图像。在图像加载完成后,将其上传到 WebGL 纹理对象中。示例代码如下:

function loadTexture(gl, url) {
    var texture = createAndBindTexture(gl);
    var image = new Image();
    image.onload = function () {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        gl.generateMipmap(gl.TEXTURE_2D);
    };
    image.src = url;
    return texture;
}

3.4 顶点着色器和片元着色器

顶点着色器用于处理顶点的位置和纹理坐标,片元着色器用于对每个像素进行颜色计算。以下是一个简单的顶点着色器和片元着色器的代码示例:

// 顶点着色器代码
var vertexShaderSource = `
    attribute vec4 a_position;
    attribute vec2 a_texCoord;
    varying vec2 v_texCoord;
    void main() {
        gl_Position = a_position;
        v_texCoord = a_texCoord;
    }
`;

// 片元着色器代码
var fragmentShaderSource = `
    precision mediump float;
    uniform sampler2D u_texture;
    varying vec2 v_texCoord;
    void main() {
        gl_FragColor = texture2D(u_texture, v_texCoord);
    }
`;

3.5 完整示例代码

将上述步骤整合起来,形成一个完整的示例代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>WebGL 纹理示例</title>
</head>

<body>
    <canvas id="glCanvas" width="640" height="480"></canvas>
    <script>
        function initWebGL(canvas) {
            var gl = null;
            try {
                gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
            } catch (e) {}
            if (!gl) {
                alert("无法初始化 WebGL,你的浏览器可能不支持。");
                return null;
            }
            return gl;
        }

        function createAndBindTexture(gl) {
            var texture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, texture);
            return texture;
        }

        function loadTexture(gl, url) {
            var texture = createAndBindTexture(gl);
            var image = new Image();
            image.onload = function () {
                gl.bindTexture(gl.TEXTURE_2D, texture);
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
                gl.generateMipmap(gl.TEXTURE_2D);
            };
            image.src = url;
            return texture;
        }

        function createShader(gl, type, source) {
            var shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);
            var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
            if (success) {
                return shader;
            }
            console.log(gl.getShaderInfoLog(shader));
            gl.deleteShader(shader);
        }

        function createProgram(gl, vertexShader, fragmentShader) {
            var program = gl.createProgram();
            gl.attachShader(program, vertexShader);
            gl.attachShader(program, fragmentShader);
            gl.linkProgram(program);
            var success = gl.getProgramParameter(program, gl.LINK_STATUS);
            if (success) {
                return program;
            }
            console.log(gl.getProgramInfoLog(program));
            gl.deleteProgram(program);
        }

        function main() {
            var canvas = document.getElementById("glCanvas");
            var gl = initWebGL(canvas);
            if (!gl) {
                return;
            }

            var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
            var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
            var program = createProgram(gl, vertexShader, fragmentShader);

            var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
            var texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");
            var textureUniformLocation = 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,
                1, 0,
                0, 1,
                0, 1,
                1, 0,
                1, 1
            ];
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);

            var texture = loadTexture(gl, "texture.jpg");

            gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
            gl.clearColor(0, 0, 0, 1);
            gl.clear(gl.COLOR_BUFFER_BIT);

            gl.useProgram(program);

            gl.enableVertexAttribArray(positionAttributeLocation);
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

            gl.enableVertexAttribArray(texCoordAttributeLocation);
            gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
            gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);

            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.uniform1i(textureUniformLocation, 0);

            gl.drawArrays(gl.TRIANGLES, 0, 6);
        }

        var vertexShaderSource = `
            attribute vec4 a_position;
            attribute vec2 a_texCoord;
            varying vec2 v_texCoord;
            void main() {
                gl_Position = a_position;
                v_texCoord = a_texCoord;
            }
        `;

        var fragmentShaderSource = `
            precision mediump float;
            uniform sampler2D u_texture;
            varying vec2 v_texCoord;
            void main() {
                gl_FragColor = texture2D(u_texture, v_texCoord);
            }
        `;

        main();
    </script>
</body>

</html>

在这个示例中,我们创建了一个简单的 WebGL 场景,将一张纹理图像应用到一个矩形上。通过上述代码,我们可以看到纹理加载和使用的基本流程:初始化 WebGL 上下文、创建和绑定纹理、加载纹理图像、编写着色器代码、设置顶点和纹理坐标,最后进行渲染。

四、纹理参数设置

4.1 纹理过滤

纹理过滤决定了当纹理被放大或缩小时如何处理。WebGL 提供了几种不同的过滤模式,包括最近邻过滤(gl.NEAREST)和线性过滤(gl.LINEAR)。以下是设置纹理过滤参数的代码示例:

function loadTexture(gl, url) {
    var texture = createAndBindTexture(gl);
    var image = new Image();
    image.onload = function () {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        // 设置纹理过滤参数
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.generateMipmap(gl.TEXTURE_2D);
    };
    image.src = url;
    return texture;
}

在上述代码中,gl.TEXTURE_MIN_FILTER 用于设置纹理缩小过滤模式,gl.TEXTURE_MAG_FILTER 用于设置纹理放大过滤模式。gl.LINEAR 表示使用线性过滤,它会在多个纹素之间进行插值,使纹理看起来更加平滑。

4.2 纹理环绕

纹理环绕决定了当纹理坐标超出 [0, 1] 范围时如何处理。WebGL 提供了几种环绕模式,如 gl.REPEAT(重复纹理)、gl.CLAMP_TO_EDGE(边缘拉伸)和 gl.MIRRORED_REPEAT(镜像重复)。以下是设置纹理环绕参数的代码示例:

function loadTexture(gl, url) {
    var texture = createAndBindTexture(gl);
    var image = new Image();
    image.onload = function () {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        // 设置纹理环绕参数
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
        gl.generateMipmap(gl.TEXTURE_2D);
    };
    image.src = url;
    return texture;
}

在上述代码中,gl.TEXTURE_WRAP_S 用于设置纹理在水平方向的环绕模式,gl.TEXTURE_WRAP_T 用于设置纹理在垂直方向的环绕模式。gl.REPEAT 表示纹理会重复显示。

五、多纹理使用

在某些情况下,我们可能需要同时使用多个纹理来创建更复杂的效果。以下是一个使用两个纹理的示例代码:

// 顶点着色器代码
var vertexShaderSource = `
    attribute vec4 a_position;
    attribute vec2 a_texCoord;
    varying vec2 v_texCoord;
    void main() {
        gl_Position = a_position;
        v_texCoord = a_texCoord;
    }
`;

// 片元着色器代码
var fragmentShaderSource = `
    precision mediump float;
    uniform sampler2D u_texture1;
    uniform sampler2D u_texture2;
    varying vec2 v_texCoord;
    void main() {
        vec4 color1 = texture2D(u_texture1, v_texCoord);
        vec4 color2 = texture2D(u_texture2, v_texCoord);
        gl_FragColor = mix(color1, color2, 0.5);
    }
`;

function main() {
    var canvas = document.getElementById("glCanvas");
    var gl = initWebGL(canvas);
    if (!gl) {
        return;
    }

    var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
    var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    var program = createProgram(gl, vertexShader, fragmentShader);

    var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
    var texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");
    var texture1UniformLocation = gl.getUniformLocation(program, "u_texture1");
    var texture2UniformLocation = gl.getUniformLocation(program, "u_texture2");

    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,
        1, 0,
        0, 1,
        0, 1,
        1, 0,
        1, 1
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);

    var texture1 = loadTexture(gl, "texture1.jpg");
    var texture2 = loadTexture(gl, "texture2.jpg");

    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.useProgram(program);

    gl.enableVertexAttribArray(positionAttributeLocation);
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

    gl.enableVertexAttribArray(texCoordAttributeLocation);
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture1);
    gl.uniform1i(texture1UniformLocation, 0);

    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, texture2);
    gl.uniform1i(texture2UniformLocation, 1);

    gl.drawArrays(gl.TRIANGLES, 0, 6);
}

在这个示例中,我们在片元着色器中定义了两个 sampler2D 类型的统一变量 u_texture1 和 u_texture2,分别对应两个纹理。在 JavaScript 代码中,我们使用 gl.activeTexture 激活不同的纹理单元,并将纹理绑定到相应的单元上,最后通过 gl.uniform1i 将纹理单元编号传递给着色器。

六、纹理压缩

纹理压缩可以减少纹理数据的存储空间,提高加载速度和渲染性能。WebGL 支持多种纹理压缩格式,如 DXT、ETC 等。以下是一个使用纹理压缩的示例代码:

function loadCompressedTexture(gl, url, format) {
    var texture = createAndBindTexture(gl);
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.responseType = "arraybuffer";
    xhr.onload = function () {
        var arrayBuffer = xhr.response;
        if (arrayBuffer) {
            var data = new Uint8Array(arrayBuffer);
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, gl.UNSIGNED_BYTE, data);
            gl.generateMipmap(gl.TEXTURE_2D);
        }
    };
    xhr.send(null);
    return texture;
}

在上述代码中,我们使用 XMLHttpRequest 加载压缩纹理数据,并将其传递给 gl.texImage2D 函数。需要注意的是,不同的纹理压缩格式需要使用不同的 format 参数。

七、结论

通过本文的介绍,我们了解了 WebGL 中纹理的基本概念、加载和使用方法,以及纹理参数设置、多纹理使用和纹理压缩等高级技术。纹理是 WebGL 中非常重要的一部分,它可以为 3D 场景带来更加真实和生动的效果。希望通过本文的代码示例和讲解,你能够更好地掌握 WebGL 纹理的使用,创造出更加精彩的 WebGL 应用。在实际开发中,还可以进一步探索纹理的更多应用,如纹理动画、纹理映射变形等,不断提升 WebGL 应用的质量和性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亿只小灿灿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值