坐标定义: 纹理坐标系范围是(0,1),左下角为(0,0),右上角为(1,1); webGL几何坐标系:x(-1, 1) y(-1, 1)
gl.createTexture() —— 创建并初始化一个WebGLTexture对象,即创建一个纹理;
gl.bindTexture(target, texture) 将给定的texture绑定到目标,其中texture参数为创建的纹理对象,target为绑定点,类型为GLenum,支持的值包括gl.TEXTURE_2D(二维纹理)、gl.TEXTURE_CUBE_MAP(立方体映射纹理),使用webgl 2时可以支持gl.TEXTURE_3D(三维纹理)、gl.TEXTURE_2D_ARRAY(二维纹理数组);
WebGLRenderingContext.texImage2D() 用于指定二维纹理图像,把已加载的图形数据写到纹理;
// WebGL1: void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView? pixels); void gl.texImage2D(target, level, internalformat, format, type, ImageData? pixels); void gl.texImage2D(target, level, internalformat, format, type, HTMLImageElement? pixels); void gl.texImage2D(target, level, internalformat, format, type, HTMLCanvasElement? pixels); void gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels); void gl.texImage2D(target, level, internalformat, format, type, ImageBitmap? pixels);其中target为指定纹理绑定对象,支持gl.TEXTURE_2D、gl.TEXTURE_CUBE_MAP_POSITIVE_X、gl.TEXTURE_CUBE_MAP_NEGATIVE_X、gl.TEXTURE_CUBE_MAP_POSITIVE_Y、gl.TEXTURE_CUBE_MAP_NEGATIVE_Y、gl.TEXTURE_CUBE_MAP_POSITIVE_Z、gl.TEXTURE_CUBE_MAP_NEGATIVE_Z;
level:0为基本图像等级,n为第n即金字塔简化级;
internalformat:指定纹理图像中的颜色组成,支持RGBA、RGB、ALPHA等;
format:webgl1中与internalformat保持一致,webgl2参考文献3
type:指定纹理像素的数据类型 —— gl.UNSIGNED_BYTE、gl.UNSIGNED_SHORT_5_6_5(5bits红、6bits绿、5bits蓝)等
pixels:纹理像素源,取以下对象之一:ArrayBufferView(type为gl.UNSIGNED_BYTE时必须使用Uint8Array)、ImageData、HTMLImageElement、HTMLCanvasElement、HTMLVideoElement、ImageBitmap;
WebGLRenderingContext.pixelStorei(pname, params) 图像预处理函数,参数pname指定预处理函数名,params指定预处理方式的参数。pname可选值包括gl.PACK_ALIGNMENT、gl.UNPACK_ALIGNMENT、gl.UNPACK_FLIP_Y_WEBGL(为true图像对称轴上下翻转)、 gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL(其他颜色通道乘以alpha通道);
由于webgl纹理中坐标原点位于左下方,而图片信息依赖的坐标原点时左上角;因此通常需要沿着Y轴进行翻转处理
gl.activeTexture(texture) 激活指定的纹理,texture取值gl.TEXTUREI,其中的 I 在 0 到 gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1 范围内,gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS是一个常量
WebGLRenderingContext.texParameter[fi]() 用于设置纹理参数, gl.TEXTURE_MAG_FILTER纹理放大滤波器(可选值gl.LINEAR(默认值)、gl.NEAREST);
void gl.texParameterf(GLenum target, GLenum pname, GLfloat param); void gl.texParameteri(GLenum target, GLenum pname, GLint param); gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);WebGLRenderingContext.uniform[1234][fi][v]() 指定uniform变量的值;location指定要修改的uniform的位置;
void gl.uniform1f(location, v0); void gl.uniform1fv(location, value); void gl.uniform1i(location, v0); void gl.uniform1iv(location, value); void gl.uniform2f(location, v0, v1); void gl.uniform2fv(location, value); void gl.uniform2i(location, v0, v1); void gl.uniform2iv(location, value);创建WebGLRenderingContext对象:
var canvas = document.getElementById("canvas"); var gl = canvas.getContext('webgl'); gl.clearColor(0.0, 0.0, 0.0, 1.0);初始化shaders及program 首先创建vertexShader和fragmentShader,然后创建一个WebGLProgram,最后将program与着色器关联;
function initShaders(gl, vertexShaderSource, fragmentShaderSource) { var program = createProgram(gl, vertexShaderSource, fragmentShaderSource); if (!program) { console.log('Failed to create program'); return false; } gl.useProgram(program); gl.program = program; return true; } function createProgram(gl, vertexShaderSource, fragmentShaderSource) { var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vertexShaderSource); var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource); if (!vertexShader || !fragmentShader) { return null; } // create a program object var program = gl.createProgram(); if (!program) { console.log('gl.createProgram failed'); return null; } // attach the shader objects gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); // link the program object gl.linkProgram(program); // check link status var linked = gl.getProgramParameter(program, gl.LINK_STATUS); if (!linked) { var error = gl.getProgramInfoLog(program); console.log('Failed to link program: ' + error); gl.deleteProgram(program); gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); return null; } return program; } function loadShader(gl, type, source) { // create shader object var shader = gl.createShader(type); if (shader == null) { console.log('unable to create shader'); return null; } // set shader source code gl.shaderSource(shader, source); // compile the shader gl.compileShader(shader); // check compile status var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (!compiled) { var error = gl.getShaderInfoLog(shader); console.log('Failed to compile shader: ' + error); return null; } return shader; }编写glsl定义两个着色器并定义顶点信息 顶点信息使用Float32Array的强类型数组定义,其中前两位表示几何坐标系位置坐标,范围x(-1, 1)、y(-1, 1);后两位表示纹理坐标系,范围x(0, 1)、y(0, 1);具体的坐标值需要根据具体的渲染区域做相应的调整;
在vertexShader中多定义了两个变量——a_TextCoord和v_TextCoord;a_Position和a_TextCoord分别从帧缓存器中读取顶点位置坐标和纹理坐标,v_TextCoord则用于将纹理坐标信息传递给片元着色器,用于提取纹理颜色;
在片元着色器中使用方法texture2D(u_Sampler, v_TextCoord)用取样器采集对应纹理坐标位置(实际上就是取原图形中的对应位置的颜色信息)的颜色信息;
var vertices = new Float32Array([ -1, 1, 0.0, 1.0, // 前 2 位是位置坐标, 后 2 位是纹理坐标 -1, -1, 0.0, 0.0, 1, 1, 1.0, 1.0, 1, -1, 1.0, 0.0 ]); // 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';加载并绘制纹理 相对于基本的几何图形绘制,纹理绘制需要在声明着色器时需要额外的变量;
initVertexBuffers(gl, vertices); var image = new Image(); image.src = './assets/drawn.jpg'; image.onload = function () { loadTexture(image); draw(); }; 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; console.log('fsize:', FSIZE) 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(); // 创建纹理 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 翻转 Y 轴 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); }非2的幂的处理 需要注意的是,在多数情况下,纹理的宽和高都必须是2的幂;上述代码中的图片是256*256的;
方案一:通过调整画布大小将图片纹理进行合理映射,存在明显拉伸时可以适当修正纹理坐标值;
方案二:必须使用非 2的幂的纹理——webGL原生支持,但是有一定的限制条件;
这种非2的幂纹理不能用来生成多级渐进纹理,而且不能使用纹理重复(重复纹理寻址等)。使用重复纹理寻址的一个例子就是使用一张砖块的纹理来平铺满一面墙壁。
多级渐进纹理和纹理坐标重复可以通过调用 texParameteri() 来禁用,当然首先你已经通过调用 bindTexture() 绑定过纹理了。这样虽然已经可以使用非2的幂纹理了,但是你将无法使用多级渐进纹理,纹理坐标包装,纹理坐标重复,而且无法控制设备如何处理你的纹理。
使用以下参数可以使用非2的幂的图片
// gl.NEAREST is also allowed, instead of gl.LINEAR, as neither mipmap. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); // Prevents s-coordinate wrapping (repeating). gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); // Prevents t-coordinate wrapping (repeating). gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);