webgl 纹理
2021-07-13 本文已影响0人
Viewwei
- 前言
前面了解了彩色图形的绘制,虽然这种功能很强大,但是对于一些更加复杂的情况下任然不能胜任.比如绘制一只动画,或者一个很逼真的图像,如果使用前面的绘制三角形的方式绘制图形,会陷入繁琐和无意思的工作中
纹理的映射
纹理映射就是讲一张图形映射到一个几何矩形的表面上去.这样在矩形表面就能看到这样图片,这张图形又可以称为纹理图像或者纹理.纹理映射的作用就是根据纹理图像,为之前光栅化的每个片元涂上合适的颜色,组成纹理图像的像素又称为纹素.每个纹素有可以都是使用 RGB 或者 RGBA 格式编码
纹理映射的步骤
- 准备好映射的几何图形上的纹理图像
- 为几何图形配置纹理映射方式
- 加载纹理图像,对其进行一些配置
- 在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋值给片元
纹理坐标
纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹素颜色,webgl系统上的纹理坐标是二维的.webgl 中使用 s 和 t 命名纹理坐标
纹理坐标图像的四个角的坐标为左下角(0.0),右下角(1.0,0.0)右上角(1.0,1.0)和左上角(0.0,1.0).纹理坐标很通用,因为坐标值和图像自身的尺寸无关.
纹理坐标和 webgl 坐标系
- 注意
纹理坐标的 y 轴和 webgl 坐标系的 y 轴方向是相反的,如果在 webgl 中像正常显示纹理,需要设置纹理图片的y轴翻转
示例程序把纹理图片添加到几个图形上
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400"></canvas>
</body>
<script id="vertextShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_texCoord;
void main () {
gl_Position = a_Position;
v_texCoord = a_TexCoord;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
// 全局设置浮点数的精确度,其他类型都有默认的精度类型,浮点数需要单独的设置
precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_texCoord;
void main () {
// texture2D(u_Sampler,v_texCoord); 得到的是一个 vec4的向量,如果有多个纹理,也可以进行纹理的合并
gl_FragColor = texture2D(u_Sampler,v_texCoord);
}
</script>
<script src="./jsm/util.js"></script>
<!-- <script src="tool/cuon-matrix.js"></script> -->
<script>
function main () {
// function main() {
const canvas = document.getElementById('webgl')
const gl = canvas.getContext('webgl')
const vertextShader = document.getElementById('vertextShader').innerText
const fragmentShader = document.getElementById('fragmentShader').innerText
if (!initShaders(gl, vertextShader, fragmentShader)) return
if (!gl) return
const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
const a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord')
const u_Sampler = gl.getUniformLocation(gl.program,'u_Sampler')
let n = initVertexBuffers(gl,a_Position,a_TexCoord)
initTextures(gl,n,u_Sampler)
// if (a_Position < 0) return
gl.clearColor(0.0, 0.0, 0.0, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
// }
}
function initVertexBuffers (gl,a_Position,a_TexCoord) {
var verticesTexCoords = new Float32Array([
-0.5, 0.5, 0.0, 1.0,
-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 n = 4
var vertexTexCoordBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER,vertexTexCoordBuffer)
gl.bufferData(gl.ARRAY_BUFFER,verticesTexCoords,gl.STATIC_DRAW)
var fsize = verticesTexCoords.BYTES_PER_ELEMENT
gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,fsize*4,0)
gl.enableVertexAttribArray(a_Position)
gl.vertexAttribPointer(a_TexCoord,2,gl.FLOAT,false,fsize*4,fsize*2)
gl.enableVertexAttribArray(a_TexCoord)
return n
}
function initTextures(gl,n,u_Sampler) {
var texture = gl.createTexture() //创建纹理对象
var image = new Image()
image.onload = function () {
// 图片加载完成之后开始加载纹理
loadTexture(gl,n,texture,u_Sampler,image)
}
image.src = './img/erha.jpg'
}
function loadTexture(gl,n,texture,u_Sampler,image) {
// 因为 st 坐标和 webgl y坐标相反, 需要进行 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.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR)
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image)
gl.uniform1i(u_Sampler,0)
gl.drawArrays(gl.TRIANGLE_STRIP,0,n)
}
</script>
</html>
- 注意:
上述例子中使用的图片是标准的 2 进制图片(即图片的宽度和高度的值都是 2 的 n 次方),如果不是这种 2 进制的纹理图片,那么需要在单独设置纹理图图片的参数.后续会提到
添加纹理步骤
设置纹理坐标
将纹理坐标传入顶点着色器,与将其他顶点数据传入顶点着色器的方式相同.我们可以将纹理坐标和顶点坐标同一个缓冲器
var verticesTexCoords = new Float32Array([
-0.5, 0.5, 0.0, 1.0,
-0.5,-0.5, 0.0,0.0,
0.5,0.5,1.0,1.0,
0.5,-0.5,1.0,0.0
])
配置和加载纹理步骤
- 首先创建一个纹理对象
createTexture()
纹理对象是用来存储纹理图像的工具.创建一个纹理单元,使用 deleteTexture 可以删除指定的纹理对象
- 开启纹理单元
在创建纹理对象之后需要开启纹理单元,webgl 中最少有 8 个纹理单元,纹理单元为gl. TEXTURE0~gl.TEXTURE7 是管理的 8 个纹理单元.开启的方法为
gl.activeTexture(a)
// a 代表纹理单元
- 纹理对象和纹理单元的绑定
使用gl.bindTexture方法把创建的纹理对象和开启的纹理单元进行绑定.
gl.bindTexture(gl.TTEXTURE_2D,a)
//a 代表纹理对象
- 纹理参数设置
- gl.pixelStorei参数配置方法
gl.pixelStorei(pname,param)
pname: 可选值为 gl.UNPACK_FLIP_Y_WEBGL 对图像记性 Y 轴的翻转
gl.UNPACK_PREMULTIPLY_ALPHA:将图像 RGB 颜色的每个分量x A
param: 指定非 0(true) 或 0(false).必须是整数
- gl.texParameteri方法配置
gl.textParameteri可以配置对象的参数,以此老设置纹理图像映射到图形上的具体方法.比如如何根据纹理坐标获取纹素的颜色,按照哪种方式重复填充纹理
参数名称 | 参数值 | 参数说明 |
---|---|---|
target | gl.TEXTURE_2D或者 gl.TEXTURE_CUBE_MAP | 绑定类型 |
pname | 纹理参数 | 见表 2 |
param | 纹理参数 值 | 见表 3 |
pname 可以指定 4 个纹理参数
- 方法方法(gl.TEXTURE_MAG_FILTER):这个参数表示:当纹理的绘制范围比纹理本身更大的时,如何获取纹素的颜色
- 缩小方法(gl.TEXTURE_MIN_FILTER) ;这个参数表示当纹理绘制范围比纹理本身小时,如何获取纹素的颜色
- 水平填充(gl.TEXTURE_WRAP_S):这个参数表示如何对纹理左侧或者右侧区域进行填充(设置这个参数可以解决放图片不是 2 进制图片的时候,让他正常显示的问题)
-
垂直填充(gl.TEXTURE_WRAP_T):这个参数表示如何对纹理上或者下侧区域进行填充(设置这个参数可以解决放图片不是 2 进制图片的时候,让他正常显示的问题)
image.png
纹理参数以及默认值表 2 如下
参数名称 | 描述 | 默认值 |
---|---|---|
gl.TEXTURE_MAG_FILTER | 纹理放大 | gl.LINEAR |
gl.TEXTURE_MIN_FILTER | 纹理缩小,分子料理 | gl.NEAREST_MIP_LINEAR |
gl.TEXTURE_WRAP_S | 纹理水平填充 | gl.REPEAT |
gl.TEXTURE_WRAP_T | 纹理垂直填充 | gl.REPEAT |
纹理参数 gl.TEXTURE_MAG_FILTER gl.TEXTURE_MIN_FILTER 非金字塔纹理类常量
值 | 描述 |
---|---|
gl.NEAREST | 使用原纹理上距离映射后像素 |
gl.LINEAR | 使用距离像素点中心最近的四个像素的颜色值的加权平均 |
gl.TEXTURE_WRAP_S 和 gl.TEXTURE_WRAP_T
值 | 描述 |
---|---|
gl.REPEAT | 平铺式的重复纹理 |
gl.MIRRORED_REPEAT | 镜像对称的重复纹理 |
gl.CLAMP_TO_EDGE | 使用纹理图像边缘值 |
纹理图像分配给纹理对象
gl.texImage2D(target,level, internalformat,format,type,image)
参数值 | 参数说明 |
---|---|
target | gl.TEXTURE_2D或者 gl.TEXTURE_CUBE_MAP |
level | 传入 0 |
internalformat | 图像的内部格式 |
format | 纹理数据的格式 |
type | 纹理数据的类型 |
image | 包含纹理图像的image对象 |
将纹理单元传递给片元着色器
一旦将纹理图像传入给 webgl 系统,就必须将其他片元着色器传入片元着色器并映射到图形的表面上去
<script id="fragmentShader" type="x-shader/x-fragment">
// 全局设置浮点数的精确度,其他类型都有默认的精度类型,浮点数需要单独的设置
precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_texCoord;
void main () {
// texture2D(u_Sampler,v_texCoord); 得到的是一个 vec4的向量,如果有多个纹理,也可以进行纹理的合并
gl_FragColor = texture2D(u_Sampler,v_texCoord);
}
</script>
专用于纹理的数据类型
类型 | 描述 |
---|---|
sampler2D | 绑定gl. TEXTURE_2D 上的纹理数据类型 |
samperCube | 绑定gl. TEXTURE_CUBE_MAP上的纹理数据类型 |
必须通过指定纹理单元编号,将纹理对象传递给u_ Sampler
gl.unifirmli(a,b)
//a表示片元着色器 sampler2D对象,b 表示纹理单元
从顶点着色器想片元着色器传输纹理坐标
顶点着色器向片元着色器传递数据通过 varying 进行传递
- 顶点着色器
<script id="vertextShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_texCoord;
void main () {
gl_Position = a_Position;
v_texCoord = a_TexCoord;
}
</script>
- 片元着色器
<script id="fragmentShader" type="x-shader/x-fragment">
// 全局设置浮点数的精确度,其他类型都有默认的精度类型,浮点数需要单独的设置
precision mediump float;
uniform sampler2D u_Sampler;
//传递顶点着色器
varying vec2 v_texCoord;
void main () {
// texture2D(u_Sampler,v_texCoord); 得到的是一个 vec4的向量,如果有多个纹理,也可以进行纹理的合并
gl_FragColor = texture2D(u_Sampler,v_texCoord);
}
</script>
在片元着色器中获取纹理像素颜色
<script id="fragmentShader" type="x-shader/x-fragment">
// 全局设置浮点数的精确度,其他类型都有默认的精度类型,浮点数需要单独的设置
precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_texCoord;
void main () {
// texture2D(u_Sampler,v_texCoord); 得到的是一个 vec4的向量,如果有多个纹理,也可以进行纹理的合并
gl_FragColor = texture2D(u_Sampler,v_texCoord);
}
</script>
- 从sampler 指定的纹理获取 coord 指定的纹理坐标处的像素颜色
vec4 texture2D(sampler2D sampler,vec2 coord)
sampler:指定纹理单元
coord:指定纹理坐标