OpenGL学习: 环境纹理映射(environment mapping)

上一节初步学习了使用cubeMap创建天空包围盒,本节继续深入Cubemap这个主题,学习环境纹理贴图。

本节内容整理自: 1.www.learnopengl.com cubemaps

环境纹理贴图

同上一节的Cubemap创建天空包围盒有些类似,创建环境纹理贴图也是对当前待渲染物体,从包围的环境纹理上采样作为这个物体的纹理而渲染出的逼真效果。本节介绍环境纹理贴图主要的方式包括:reflection(反射贴图)和refraction(折射贴图)。

Reflection 反射贴图

上一节cubemaps中,我们提到对立方体纹理进行采样,需要使用3维向量(s,t,r),而当立方体中心处于原点时,立方体的顶点位置就可以作为这个采样的坐标。对于反射贴图,我们也同样需要一个纹理坐标,不过这个向量的计算过程如下图所示(来自 www.learnopengl.com):

reflection
图中向量 I 表示观察向量,注意它从观察者位置指出, N 表示顶点对应的法向量,而计算出来的反射向量 R 则作为从Cubemap采样的向量。在光照基础,一节我们已经见过使用reflect函数计算反射向量了,这里再次说明下。我们计算向量的过程都可以在世界坐标系或者相机坐标系,只要统一一个坐标系即可。这里我们使用世界坐标系,在顶点着色器中,实现为:

   #version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
out vec3 FragNormal;
out vec3 FragPos;
void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0);
    FragPos = vec3(model * vec4(position, 1.0));    // 在世界坐标系中指定
    mat3 normalMatrix = mat3(transpose(inverse(model)));
    FragNormal = normalMatrix * normal; // 计算法向量经过模型变换后值
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

上面在世界坐标系中计算转换后的法向量时使用了公式:
FragPos=vec3(modelvec4(position,1.0));()
FragNormal=mat3(transpose(inverse(model)))normal()

对于公式不熟悉的,可以回过头去参考光照基础一节。
在片元着色器中实现为:

   #version 330 core
in vec3 FragNormal;
in vec3 FragPos;
uniform samplerCube envText; // 环境纹理
uniform vec3 cameraPos;
out vec4 color;
void main()
{
    vec3 viewDir = normalize(FragPos - cameraPos); // 注意这里向量从观察者位置指出
    vec3 reflectDir = reflect(viewDir, normalize(FragNormal));
    color = texture(envText, reflectDir);   // 使用反射向量采样环境纹理
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

注意上面计算过程中向量的方向和单位化。这里输入的环境纹理依然是我们的天空包围盒cubemap纹理。绘制上一节的立方体,使用反射贴图得到的效果如下图所示:

这里写图片描述

这个贴图得到的效果是,立方体的表面反射包围盒的纹理。 
如果使用球体模型的话,则能得到更逼真的效果:

球体反射贴图

这个球体模型,可以从我的github下载

Refraction 折射贴图

与反射相对应,当光从一种材质进入另一种材质时将会发生折射,满足折射定律。这里我们采用折射后得到的向量,作为采样纹理的向量,计算如下图所示(来自 www.learnopengl.com):
refraction

这里的 I 为入射向量,从观察者位置指出, N 仍然是法向量,而得到的折射向量 R 作为采样纹理的向量。可以看出当光从空气进入水中时,发生了折射现象,折射向量 R 与原始的入射光线 I 发生了偏离。 
在计算折射向量时,需要使用到折射率,这个参数,从一种材质进入另一种材质,实际计算时使用两种材质的折射率的比例。实现refraction效果是,顶点着色器部分与上面相同,片元着色器需要修改,计算折射向量,如下:

   #version 330 core
in vec3 FragNormal;
in vec3 FragPos;
uniform samplerCube envText; // 环境纹理
uniform vec3 cameraPos;
out vec4 color;
void main()
{
    float indexRation = 1.00 / 1.52;
    vec3 viewDir = normalize(FragPos - cameraPos); // 注意这里向量从观察者位置指出
    vec3 refractDir = refract(viewDir, normalize(FragNormal), indexRation);
    color = texture(envText, refractDir);   // 使用 折射向量 采样环境纹理
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

上面着色器中,refractive的第三个参数是折射率的比例,这里我们模拟的是从空气进入玻璃。绘制上面的立方体,得到的效果像是透明玻璃:

refraction

环境纹理贴图和模型加载

在前面AssImp纹理加载一节,我们使用AssImp库加载了一个纳米战斗服模型,这里对模型使用反射环境贴图加以改进。之前使用的是diffuse map 和specular map,这里将使用的方法称之为reflection map。通过加载模型中的reflection map,决定渲染的物体中哪部分需要做反射环境贴图以及环境贴图的强度系数,而不是像上面球体那样,整个执行反射环境贴图。

利用AssImp加载Reflection map,遇到的麻烦在于,AssImp对Reflection map默认支持不好,因此这里使用的技巧是修改obj资源和代码,做出调整。你可以从我的github下载,修改后的模型。

在资源中使用aiTextureType_AMBIENT作为reflection map,在model.h代码中添加处理:

// 获取Reflection 注意: AssImp对Reflection支持不好 所以这里采用ambient_map
// 除了这里的代码 还需要修改对应的obj文件
std::vector<Texture> reflectionTexture;
this->processMaterial(materialPtr, sceneObjPtr, aiTextureType_AMBIENT, reflectionTexture);
textures.insert(textures.end(), reflectionTexture.begin(), reflectionTexture.end());
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

同时在mesh.h中也要添加相应的处理,在片元着色器中修改为:

#version 330 core
in vec3 FragNormal;
in vec3 FragPos;
in vec2 TextCoord;
uniform samplerCube envText;    // 环境纹理
uniform sampler2D texture_diffuse0;
uniform sampler2D specular_diffuse0;
uniform sampler2D texture_reflection0; // 反射map
uniform vec3 cameraPos;
out vec4 color;
void main()
{
    vec4    diffuseColor = texture(texture_diffuse0, TextCoord);
    float   relefctIntensity = texture(texture_reflection0, TextCoord).r; 
    vec4    reflectColor = vec4(0.0, 0.0, 0.0, 0.0);
    if(relefctIntensity > 0.1) // 决定是否开启
    {
        vec3 viewDir = normalize(FragPos - cameraPos); 
        vec3 reflectDir = reflect(viewDir, normalize(FragNormal));
        reflectColor = texture(envText, reflectDir) * relefctIntensity; // 使用反射向量采样环境纹理 使用强度系数控制
    }
    color = diffuseColor + reflectColor;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

这里通过读取reflection map,采样纹理后,获取一个强度系数,根据强度系数来决定是否开启refleciton map,如果开启则输出颜色为diffuse map和reflection map的混合结果。

渲染时只输出diffuse map或者reflection map,以及最终得到的效果,对比如下图所示:

reflection map对头部进行放大,我们看到了反射贴图的效果:
reflection part

反射贴图为模型的表面增加了环境成分,显得更加逼真,而且模型在移动过程中,贴图能够动态变换,反映位置的改变。

最后的说明

在实现本节内容过程中,需要注意,使用反射或者折射时需要提供物体表面法向量,不仅需要修改顶点属性数据,还要修改对应的顶点着色器,如果修改错误,可能产生错误效果如下图:

这里写图片描述

从图中结果,可以看到渲染基本的图形出错,可以立马联想到顶点属性数据配置出错。

同时在加载模型,使用reflection map时,天空包围盒使用的纹理单元,要更新为3, 因为前面加载了其他的specular map、diffuse map、reflection map:

glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_CUBE_MAP, skybox.getTextId());
glUniform1i(glGetUniformLocation(shader.programId, "envText"), 3); 
 
 
  • 1
  • 2
  • 3

如果没有更新这个配置,导致天空包围盒的纹理单元被其他纹理占用,将发生错误,例如可能的错误结果如下:

refletion map error

关于环境纹理贴图,还有一项技术称为dynamic environment mapping。通过借助framebuffer,为每个物体渲染6个包含场景中其他物体的纹理,从而构建一个环境纹理,然后实行贴图。这种动态贴图方式,由于在framebuffer中要为每个物体执行6次场景渲染,在保持较好性能开销下使用它需要很多技巧,没有这里介绍的天空包围盒这么容易使用。在后面时间充足时,可以学习下这个技术。

<think>好的,用户问的是如何区分PC和H5获取录音权限。首先,我需要明确用户的需求。他们可能是在开发一个需要录音功能的Web应用,想要了解在桌面浏览器(PC)和移动浏览器(H5)上获取麦克风权限的不同方法。需要从技术实现、API使用、用户交互、兼容性等方面进行分析。 首先,回忆一下Web Audio API的相关知识。在Web开发中,获取用户麦克风权限通常使用navigator.mediaDevices.getUserMedia方法。这个方法在PC和移动端的基本调用方式是一样的,但实际应用中可能会有差异。需要分步骤说明两者的不同点。 接下来,权限请求的触发方式。在PC端,大多数浏览器要求用户必须在用户交互(比如点击事件)中触发权限请求,否则会被阻止。而在移动端,尤其是H5页面,可能同样需要用户交互,但不同移动浏览器的行为可能不同。需要确认这一点,是否有例外情况。 然后,用户提示的差异。PC端浏览器通常会在地址栏显示权限请求的图标,而移动端可能会以弹窗或底部提示的方式出现。这里可能需要具体举例,比如Chrome在PC和手机上的不同表现。 安全上下文的要求。无论是PC还是移动端,使用getUserMedia都需要在HTTPS环境下,本地开发环境(localhost)除外。这一点是否一致?需要确认。 设备检测方面,移动端可能需要处理更多设备适配问题,比如前后麦克风的选择,而PC端可能通常只有一个麦克风。但这里可能需要更准确的描述,比如移动设备可能有多个麦克风,但通常由系统管理,而开发者可能不需要特别处理。 兼容性问题。需要列举不同浏览器在PC和移动端对录音权限的支持情况。比如Safari在iOS上的限制,或者某些安卓浏览器的不同行为。 接下来,代码示例部分。需要提供一个基本的请求麦克风权限的代码示例,并指出在PC和H5中的注意事项。比如在移动端可能需要处理自动播放策略,或者用户拒绝后的不同处理方式。 另外,权限持久化方面,PC端浏览器可能会记住用户的选择,而移动端可能每次都需要重新请求,或者有不同的会话管理方式。需要确认不同浏览器的具体行为。 最后,总结关键点,帮助用户区分两者的主要差异,比如触发方式、用户提示、兼容性处理等。确保回答结构清晰,步骤明确,用户能够逐步理解和应用。 需要验证一些不确定的点,比如移动端是否真的每次都需要用户交互触发,或者不同浏览器是否有不同的策略。例如,iOS上的Safari对getUserMedia的调用是否有严格的用户手势要求,而Chrome on Android是否允许在某些情况下自动触发。此外,权限请求的UI展示在不同平台上的具体表现,可能需要进一步查证。 另外,设备检测方面,是否需要使用mediaDevices.enumerateDevices来列出麦克风设备,这在移动端是否有必要,或者是否通常只有一个可用麦克风。这部分可能需要简化,避免复杂化问题。 最后,确保回答符合用户可见层的要求,结构清晰,逐步解决问题,并且内容真实可靠。避免技术错误,比如错误的API名称或不兼容的浏览器示例。</think>区分PC端和移动端H5获取录音权限的实现差异,主要涉及浏览器API调用方式、用户交互流程及兼容性处理。以下是关键点分析: --- ### **一、核心API调用** 无论PC或H5均使用Web API `navigator.mediaDevices.getUserMedia()`,但实现细节不同: ```javascript // 通用代码框架 async function requestMicrophone() { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // 成功获取音频流 } catch (error) { // 处理权限拒绝或错误 } } ``` --- ### **二、用户交互差异** | **场景** | **PC端** | **H5(移动端)** | |----------------|----------------------------------------|---------------------------------------| | **触发条件** | 需用户主动交互(如点击按钮) | 同样需用户手势触发,部分浏览器更严格 | | **权限弹窗** | 浏览器顶部地址栏提示 | 系统级弹窗或底部提示栏 | | **持久化策略** | 浏览器可记住用户选择 | 部分移动浏览器每次刷新需重新授权 | --- ### **三、兼容性注意事项** 1. **PC端重点问题** - 旧版Edge/Firefox 需处理 `getUserMedia` 前缀 - Safari 要求 HTTPS 环境 2. **H5移动端特殊处理** - **iOS Safari**:首次拒绝后需手动开启(设置 > Safari > 麦克风) - **微信浏览器**:需配置JS接口安全域名 - **安卓WebView**:需应用层声明录音权限 --- ### **四、关键代码适配技巧** 1. **检测设备类型** ```javascript const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); ``` 2. **错误处理优化** ```javascript catch (error) { if (error.name === 'NotAllowedError') { // 提示用户手动开启权限 if (isMobile) alert('请前往系统设置启用麦克风权限'); } } ``` 3. **自动播放策略(H5重点)** ```javascript // 移动端需在用户交互后立即播放音频,避免静音阻塞 audioElement.play().catch(() => { /* 处理自动播放失败 */ }); ``` --- ### **五、调试建议** 1. **PC端**:使用Chrome `chrome://settings/content/microphone` 管理权限 2. **H5移动端**: - Android Chrome:长按页面 > 站点设置 - iOS Safari:需完全关闭页面后重新进入 --- ### **总结** - **本质相同**:均依赖 `getUserMedia` API,但交互流程和系统限制不同 - **核心差异**:移动端涉及更多系统级权限管控和浏览器兼容问题 - **适配关键**:结合设备检测优化提示逻辑,优先在HTTPS环境部署
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值