10.纹理

本文详细介绍WebGL纹理映射的原理与实现,包括纹理坐标系统、纹理单元配置、纹理图像加载及片元着色器处理流程。通过实例演示如何使用单一及多个纹理,为3D图形添加逼真细节。

1.纹理坐标:st坐标系统,纹理左边的原点位于图像左下角,图像右上角的坐标始终是(1.0,1.0)

2.纹理坐标与WebGL坐标的映射:

3.纹理映射:根据纹理图像,为之前光栅化后的每个图元图上合适的颜色,组成纹理图像的元素称为纹素。
4.纹理映射的过程:
    (1)准备好映射到几何图形上的纹理图像
    (2)为几何图像配置纹理映射方式(几何图形的某个片元的颜色取决于纹理图像中哪个像素)
    (3)加载纹理图像,对其进行一些配置,以在WebGL中使用
    (4)在片元着色器中将相应的纹理从纹理中抽取出来,并将纹素颜色赋给片元
5.纹理单元:
    WebGL通过一种称作纹理单元的机制来同时使用多个纹理,每个纹理单元有一个编号来管理一张纹理图像,纹理单元的个数却决于硬件和浏览器的WebGL实现,但是默认情况下WebGL至少支持8个纹理单元
6.为WebGL配置纹理
    (1)将图像y轴进行反转,图像坐标系与WebL坐标系Y轴方向相反
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);
    API:gl.pixelStorei(pname,param)
            pname:可以是gl.UNPACK_FLIP_WEBGL(对图像进行y轴反转,默认是false),也可以是gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL(将图像的RGB值的每一个分量乘以A,默认为false)
            param:指定非0(true)或0(false)
    (2)激活纹理单元:
gl.activeTexture(gl.TEXTURE0);
    API:gl.activeTexture( texUnit )
            texUnit:指定激活的纹理单元:gl.TEXTURE0.....gl.TEXTURE7,最后的数字表示纹理单元编号
    (3)绑定纹理对象:WebGL支持两种类型纹理:gl.TEXTURE_2D二维纹理,gl.TEXTURE_CUBE_MAP立方体纹理
gl.bindTexture(gl.TEXTURE_2D,texture)
    API:gl.bindTexture(target,texture)
            target:纹理对象类型
            texture:绑定的纹理单元
    注意:该方法完成了两个任务:开启纹理对象和绑定到纹理单元(之前已经激活了纹理单元)。在WebGL中无法直接操作纹理对象,必须将纹理对象绑定到纹理单元,然后通过操作纹理单元来操作对象
    (3)配置纹理对象参数:设置纹理图像映射到图形上的具体方式(如何根据纹理坐标获取纹素颜色、和按哪种方式重复填充纹理)
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);
//注意:如果图片的分辨率不是2的次幂,那么需要配置以下两个参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    API:gl.textParameteri( target , pname, param)
            target:纹理类型,gl.TEXTURE_2D二维纹理,gl.TEXTURE_CUBE_MAP立方体纹理
            pname:纹理参数
            param:纹理参数的值
            纹理参数及其默认值;
                    gl.TEXTURE_MAG_FILTER        纹理放大        gl.LINEAR
                    gl.TEXTURE_MIN_FILTER         纹理缩小        gl.NEAREST_MIPMAP_LINEAR
                    gl.TEXTURE_WRAP_S               纹理水平填充    gl.REPEAT
                    gl.TEXTURE_WRAP_T               纹理垂直填充    gl.REPEAT

        可以赋值给gl.TEXTURE_MAG_FILTER和gl.TEXTURE_MIN_FILTER的非金字塔纹理类型常量:

            gl.NEAREST:使用原纹理上距离映射后像素(新像素)中心最近的哪个像素的颜色值,作为新像素的值(使用曼哈顿距离,即x1,x2到y1,y2的值是|x1-x2|+|y1-y2|)

            gl.LINEAR:使用距离新像素中心最近的四个像素的加权平均作为新像素的值(该方法开销较大,图像质量更好)

        可以赋值给gl.TEXTURE_WRAP_S和gl.TEXTURE_WRAP_T的常量:

            gl.REPEAT:平铺式重复纹理

            gl.MIRRORED_PEREAT:镜像对称式重复纹理

            gl.CLAMP_TO_EDGE:使用纹理图像边缘值

    (4)将纹理图像分配给纹理对象:

gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image)

API:gl.textImage2D(target,level,internalformat,format,type,image)

        target:纹理类型,gl.TEXTURE_2D二维纹理,gl.TEXTURE_CUBE_MAP立方体纹理

        level:传入0(该参数为金字塔准备)

        internalformat:图像内部格式,和图片格式有关(jpg,png,jpeg,bmp等)。流明表示我们感知到的物体表面亮度,使用RGB分量值的加权平均值来计算

        format:纹理数据格式,必须和internalformat一致

        type:纹理数据类型,后面几种数据格式通常用来压缩数据,减少浏览器加载时间

        image:包含纹理图像的Image图像

    (5)将纹理单元传递给片元着色器:通过指定纹理单元编号将纹理对象传给u_Sampler()取样器使用多个纹理

gl.uniform1i(u_Sampler,0);

7.通过上述步骤,纹理图像已经全部设置完成,顶点着色器接收到顶点的纹理坐标之后会光栅化内插出所有片元的纹理坐标,接下来由片元着色器进行处理,texture2D是GLSL ES的内置方法,只需传入纹理单元编号和纹理坐标,就可以得到纹理上的像素颜色。通过varying变量可以将片元的纹理坐标由顶点着色器传入片元着色器。

简单将一幅纹理图映射到一个矩形上:

var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec2 a_TexCoord;\n' +
'varying vec2 v_TexCoord;\n' +
'void main(){' +
'gl_Position = a_Position;\n' +
'v_TexCoord = a_TexCoord;\n' +
'}\n';
var FSHADER_SOURCE =
'precision mediump float;\n'+
'uniform sampler2D u_Sampler;\n' +
'varying vec2 v_TexCoord;\n' +
'void main(){\n' +
'gl_FragColor = texture2D(u_Sampler,v_TexCoord);\n' +
'}';

function main()
{
var canvas = document.getElementById('webgl');
var gl = canvas.getContext('webgl');
initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE);

var n = initVertexBuffers(gl);
initTextures(gl,n);
}

function initVertexBuffers(gl)
{
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 FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
var vertexTexCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,vertexTexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER,verticesTexCoords,gl.STATIC_DRAW);
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,FSIZE*4,0);
gl.enableVertexAttribArray(a_Position);

//将纹理坐标分配给a_TexCoord
var a_TexCoord = gl.getAttribLocation(gl.program,'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord,2,gl.FLOAT,false,FSIZE*4,FSIZE*2);
gl.enableVertexAttribArray(a_TexCoord);

return n;
}
function initTextures(gl,n)
{
//创建纹理对象
var texture = gl.createTexture();
//获取u_Sampler的存储位置
var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
var image = new Image();
//注册图像加载事件的响应函数
image.onload = function (){
//为WebGL配置纹理
loadTexture(gl,n,texture,u_Sampler,image);
};
//浏览器开始加载图形
image.src = 'res/3.png';
return true;
}
function loadTexture(gl,n,texture,u_Sampler,image)
{
//alert("图片加载事件已触发");
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);//对纹理图形进行y轴反转
//开启0号纹理单元8
gl.activeTexture(gl.TEXTURE0);
//向target绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D,texture);
//配置纹理参数
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);
//注意:如果图片的分辨率不是2的次幂,那么需要配置以下两个参数
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
//配置纹理图像
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,image);
//将0号纹理传给着色器
gl.uniform1i(u_Sampler,0);
gl.drawArrays(gl.TRIANGLE_STRIP,0,n);
}

使用多个纹理:

var VSHADER_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';
var FSHADER_SOURCE =
'precision mediump float;\n'+
'uniform sampler2D u_Sampler0;\n' +
'uniform sampler2D u_Sampler1;\n' +
'varying vec2 v_TexCoord;\n' +
'void main(){\n' +
'vec4 color0 = texture2D(u_Sampler0,v_TexCoord);\n' +
'vec4 color1 = texture2D(u_Sampler1,v_TexCoord);\n' +
'gl_FragColor = color0 * color1;\n' +
'}\n';

function main()
{
var canvas = document.getElementById('webgl');
var gl = canvas.getContext('webgl');
initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE);
var n = initVertex(gl);
initTextures(gl,n);
}

function initVertex(gl)
{
var vertices = 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 FSIZE = vertices.BYTES_PER_ELEMENT;
console.log(FSIZE);
var n = 4;
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
var a_TexCoord = gl.getAttribLocation(gl.program,'a_TexCoord');

var vertexCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,vertexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
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)
{
var texture0 = gl.createTexture();
var texture1 = gl.createTexture();
var u_Sampler0 = gl.getUniformLocation(gl.program,'u_Sampler0');
var u_Sampler1 = gl.getUniformLocation(gl.program,'u_Sampler1');
var image0 = new Image();
var image1 = new Image();
image0.onload = function(){
loadTexture(gl,n,texture0,u_Sampler0,image0,0);
};
image1.onload = function(){
loadTexture(gl,n,texture1,u_Sampler1,image1,1);
};
image0.src = "res/1.png";
image1.src = "res/3.png";
return true;
}
//标记纹理单元是否就绪
var g_texUnit0 = false,g_texUnit1 = false;

function loadTexture(gl,n,texture,u_Sampler,image,texUnit)
{
//反转纹理图像y轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);
//激活纹理
if(texUnit == 0)
{
gl.activeTexture(gl.TEXTURE0);
g_texUnit0 = true;
console.log("激活单元0");
}else {
gl.activeTexture(gl.TEXTURE1);
g_texUnit1 = true;
console.log("激活单元1");
}
//绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D,texture);
//配置纹理参数
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);
//注意:如果图片的分辨率不是2的次幂,那么需要配置以下两个参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,image)
gl.uniform1i(u_Sampler,texUnit);
if(g_texUnit0 && g_texUnit1)
{
console.log(n);
gl.drawArrays(gl.TRIANGLE_STRIP,0,n);
}
}

 

重点:(1)因为矩形顶点在两幅纹理上的纹理坐标是完全相同的,所以只用处理一次

          (2)使用两个纹素来计算最终的片元颜色。

          (3)由于图像的加载过程是异步的,我们无法知道哪副纹理图像先被加载完成,所以需要添加判断当两幅图像都加载完成时再绘制图像。

<think>我们正在讨论的是在three.js中实现纹理贴图自适应不同分辨率的处理方法。自适应分辨率通常意味着根据显示需求动态调整纹理分辨率,以平衡性能和画质。 在three.js中,纹理的分辨率通常由加载的图片决定。但我们可以通过以下方法实现自适应: 1. 使用多级渐远纹理(Mipmapping)技术:这是硬件支持的特性,three.js默认启用。它会根据物体在屏幕上的大小自动选择合适分辨率的纹理。 2. 动态加载不同分辨率的纹理:根据设备性能或屏幕尺寸,加载不同分辨率的纹理图片。 3. 使用压缩纹理格式:如ASTC(Adaptive Scalable Texture Compression),它可以在不同分辨率下保持较好的视觉质量[^2]。 4. 在运行时根据相机距离动态替换纹理:当物体距离相机较远时,使用低分辨率纹理;靠近时,使用高分辨率纹理。 具体实现思路: - 多级渐远纹理:在three.js中,可以通过设置`texture.minFilter`和`texture.magFilter`来控制纹理过滤方式,通常使用`THREE.LinearMipmapLinearFilter`(用于minFilter)可以获得较好的效果。但注意,这需要纹理是2的幂次尺寸,并且需要生成mipmap(默认会自动生成)。 - 动态加载纹理:我们可以根据设备的像素比(devicePixelRatio)或屏幕宽度,选择加载不同分辨率的纹理。例如: ```javascript const textureLoader = new THREE.TextureLoader(); let textureUrl; if (window.devicePixelRatio >= 2) { textureUrl = 'textures/high-res.png'; } else if (window.innerWidth > 768) { textureUrl = 'textures/medium-res.png'; } else { textureUrl = 'textures/low-res.png'; } const texture = textureLoader.load(textureUrl); ``` - 使用压缩纹理:three.js支持加载压缩纹理格式(如ASTC、DXT、PVRTC等),但需要设备支持。我们可以使用`THREE.CompressedTextureLoader`加载压缩纹理文件。例如: ```javascript const loader = new THREE.CompressedTextureLoader(); loader.setPath('textures/compressed/'); loader.load('texture.astc.ktx', function (texture) { material.map = texture; }); ``` - 运行时动态替换:我们可以监听相机位置的变化,当物体距离相机超过一定阈值时,切换纹理。例如: ```javascript function updateTextureBasedOnDistance() { const distance = camera.position.distanceTo(object.position); if (distance > 50 && currentTexture !== lowResTexture) { material.map = lowResTexture; currentTexture = lowResTexture; material.needsUpdate = true; } else if (distance <= 50 && currentTexture !== highResTexture) { material.map = highResTexture; currentTexture = highResTexture; material.needsUpdate = true; } } // 在渲染循环中调用 function animate() { updateTextureBasedOnDistance(); renderer.render(scene, camera); requestAnimationFrame(animate); } ``` 注意:动态切换纹理可能会造成卡顿,因为加载纹理是异步的,所以需要预加载纹理。 另外,还可以考虑使用渐进式加载,先加载低分辨率纹理,再加载高分辨率纹理,然后进行替换。 总结:在three.js中实现纹理自适应分辨率,我们可以结合使用多级渐远纹理、动态加载不同分辨率的纹理、压缩纹理以及根据相机距离动态替换纹理等方法。具体选择哪种方法取决于应用场景和性能要求。 §§相关问题§§ 1. 在three.js中如何实现纹理的渐进式加载? 2. 如何检测设备支持的压缩纹理格式? 3. three.js中多级渐远纹理是如何工作的?</think>在 three.js 中实现纹理贴图自适应不同分辨率的处理方法,可以通过以下技术方案实现: ### 1. 使用多级渐远纹理 (Mipmapping) 这是硬件加速的自适应纹理技术,three.js 默认支持: ```javascript const texture = new THREE.TextureLoader().load('texture.jpg'); texture.minFilter = THREE.LinearMipmapLinearFilter; // 启用mipmap texture.generateMipmaps = true; // 自动生成多级纹理 ``` - **原理**:系统根据物体在屏幕上的大小自动选择合适分辨率的纹理层级 - **优势**:减少远处物体的锯齿,提高渲染性能[^1] ### 2. 动态纹理加载 (LOD策略) 根据相机距离动态加载不同分辨率纹理: ```javascript const textureLODs = { high: 'texture_4k.jpg', medium: 'texture_2k.jpg', low: 'texture_1k.jpg' }; function updateTexture() { const distance = camera.position.distanceTo(mesh.position); let texUrl; if (distance < 10) texUrl = textureLODs.high; else if (distance < 30) texUrl = textureLODs.medium; else texUrl = textureLODs.low; new THREE.TextureLoader().load(texUrl, tex => { mesh.material.map = tex; mesh.material.needsUpdate = true; }); } // 在渲染循环中调用 function animate() { updateTexture(); requestAnimationFrame(animate); } ``` ### 3. 使用压缩纹理格式 采用自适应可扩展纹理压缩 (ASTC) 技术[^2]: ```javascript const loader = new THREE.CompressedTextureLoader(); loader.setPath('textures/compressed/'); loader.load('texture.astc.ktx', texture => { material.map = texture; }); ``` - 支持格式:ASTC, DXT, PVRTC 等 - 优势:内存占用减少 50-70%,保持视觉质量 ### 4. 响应式纹理方案 根据设备像素比动态调整: ```javascript function getOptimalTexture() { const dpr = window.devicePixelRatio; const width = window.innerWidth * dpr; if (width > 3000) return '4k_texture.jpg'; if (width > 1500) return '2k_texture.jpg'; return '1k_texture.jpg'; } // 窗口大小变化时更新 window.addEventListener('resize', () => { const newTexture = getOptimalTexture(); if (currentTexture !== newTexture) { loadTexture(newTexture); } }); ``` ### 5. 渐进式加载技术 结合 THREE.LazyLoad 实现无缝切换: ```javascript import { LazyLoad } from 'three/addons/utils/LazyLoad.js'; const lazyLoader = new LazyLoad(renderer); const textureQueue = [ { url: 'texture_low.jpg', priority: 0 }, { url: 'texture_high.jpg', priority: 1 } ]; textureQueue.forEach(item => { lazyLoader.add(item.url, { load: () => new THREE.TextureLoader().load(item.url), priority: item.priority }); }); // 当高分辨率纹理加载完成后替换 lazyLoader.onItemLoad = item => { if (item.priority === 1) { mesh.material.map = item.texture; } }; ``` ### 性能优化建议 1. 使用 `texture.dispose()` 及时释放不用的纹理内存 2. 对于静态场景,预加载所有分辨率纹理 3. 使用 `WEBGL_compressed_texture_astc` 扩展检测: ```javascript const astcSupported = renderer.extensions.has('WEBGL_compressed_texture_astc'); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值