1
0
mirror of https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git synced 2025-08-22 20:25:28 +08:00
Files
LearnOpenGL-CN/02 Lighting/04 Lighting maps.md
2015-07-03 16:45:45 +08:00

129 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

本文作者JoeyDeVries由[Django](http://bullteacher.com/15-lighting-maps.html)翻译自[http://learnopengl.com](http://www.learnopengl.com/#!Lighting/Lighting-maps)
# 光照贴图
前面的教程,我们讨论了每个物体都拥有各自不同的材质,因而会对光做出不同的反应。在一个光照场景中,给每个物体和其他物体不同的外观很棒,但是这仍然不会对一个物体的图像输出提供很多的伸缩性。
前面的教程我们为一个物体的作为一个整体定义了一个材质但是现实世界的物体通常不会只有这么一种材质而是由多种材质组成。想象一辆车它的外表质地光亮车窗会部分反射环境它的轮胎没有specular高光轮彀却非常闪亮在洗过之后。汽车同样有diffuse和ambient颜色它们在整个车上都不相同一辆车显示了多种不同的ambient/diffuse颜色。总之这样一个物体每个部分都有多种材质属性。
所以前面的材质系统对于除了最简单的模型以外都是不够的所以我们需要扩展前面的系统我们要介绍diffuse和specular贴图。它们允许你对一个物体的diffuse对于简洁的ambient成分来说它们几乎总是是一样的和specular成分能够有更精确的影响。
## 漫反射贴图
我们希望通过某种方式对每个原始像素独立设置diffuse颜色。有可以让我们基于物体原始像素的位置来获取颜色值的系统吗
这可能听起来极其相似坦白来讲我们我们使用这样的系统已经有一会儿了。听起来很像在一个较早的教程中谈论的纹理它基本就是一个纹理。我们其实是使用同一个潜在原则下的不同名称使用一张图片包裹住物体我们为每个原始像素索引独立颜色值。在有光的场景里通常叫做diffuse贴图这通常是3D艺术家的叫法因为这个纹理图像表现了所有物体的diffuse颜色。
为了强调diffuse贴图我们将会使用下面的图片它是一个有一圈钢边的木箱
![](http://www.learnopengl.com/img/textures/container2.png)
在着色器中使用diffuse贴图和纹理教程介绍的一样。这次我们把纹理储存为sampler2D它在Material结构体中。我们使用diffuse贴图替代早期定义的vec3类型的diffuse颜色。
要记住的是sampler2D也叫做模糊类型这意味着我们不能以某种类型对它实例化只能用uniform定义它们。如果我们用结构体而不是uniform实例化就像函数的参数那样GLSL会抛出奇怪的错误这同样也适用于其他模糊类型。
我们也要移除amibient材质颜色向量因为ambient颜色绝大多数情况等于diffuse颜色所以不需要分别去储存它
```c++
struct Material
{
sampler2D diffuse;
vec3 specular;
float shininess;
};
...
in vec2 TexCoords;
```
如果你非把ambient颜色设置为不同的值不可不同于diffuse值你可以继续保持ambient的vec3但是整个物体的ambient颜色会继续保持不变。为了使每个原始像素得到不同ambient值你需要对ambient值单独使用另一个纹理。
注意在像素着色器中我们将会再次需要纹理坐标所以我们声明一个额外输入变量。然后我们简单地从纹理采样来获得原始像素的diffuse颜色值
```c++
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
```
同样不要忘记把ambient材质的颜色设置为diffuse材质的颜色
```c++
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
```
这就是diffuse贴图的全部内容了。就像你看到的这不是什么新的东西但是它却极大提升了视觉品质。为了让它工作我们需要用到纹理坐标更新顶点数据把它们作为顶点属性传递到像素着色器把纹理加载把纹理绑定到合适的纹理单元。
更新的顶点数据可以从这里找到。顶点数据现在包括了顶点位置,法线向量和纹理坐标,每个立方体的顶点都有这些属性。让我们更新顶点着色器来接受纹理坐标作为顶点属性,然后发送到像素着色器:
```c++
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
...
out vec2 TexCoords;
void main()
{
...
TexCoords = texCoords;
}
```
要保证更新的顶点属性指针不仅是VAO匹配新的顶点数据也要把箱子图片加载为纹理。在绘制箱子之前我们希望首选纹理单元被赋为material.diffuse这个uniform采样器并绑定箱子的纹理到这个纹理单元
```c++
glUniform1i(glGetUniformLocation(lightingShader.Program, "material.diffuse"), 0);
...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
```
现在使用一个diffuse贴图我们在细节上再次获得惊人的提升这次添加到箱子上的光照开始闪光了名符其实。你的箱子现在可能看起来像这样
![](http://www.learnopengl.com/img/lighting/materials_diffuse_map.png)
你可以在这里得到应用的全部代码。
## 镜面贴图
你可能注意到specular高光看起来不怎么样由于我们的物体是个箱子大部分是木头我们知道木头是不会有镜面高光的。我们通过把物体设置specular材质设置为vec3(0.0f)来修正它。但是这样意味着铁边会不再显示镜面高光我们知道钢铁是会显示一些镜面高光的。我们会想要控制物体部分地显示镜面高光它带有修改了的亮度。这个问题看起来和diffuse贴图的讨论一样。是巧合吗我想不是。
我们同样适用一个纹理贴图来获得镜面高光。这意味着我们需要生成一个黑白或者你喜欢的颜色纹理来定义specular亮度把它应用到物体的每个部分。下面是一个specular贴图的例子
![](http://www.learnopengl.com/img/textures/container2_specular.png)
一个specular高光的亮度可以通过图片中每个纹理的亮度来获得。specular贴图的每个像素可以显示为一个颜色向量比如在那里黑色代表颜色向量vec3(0.0f)灰色是vec3(0.5f)。在像素着色器中我们采样相应的颜色值把它乘以光的specular亮度。像素越“白”乘积的结果越大物体的specualr部分越亮。
由于箱子几乎是由木头组成木头作为一个材质不会有镜面高光整个不透部分的diffuse纹理被用黑色覆盖黑色部分不会包含任何specular高光。箱子的铁边有一个修改的specular亮度它自身更容易受到镜面高光影响木纹部分则不会。
从技术上来讲木头也有镜面高光尽管这个闪亮值很小更多的光被散射影响很小但是为了学习目的我们可以假装木头不会有任何specular光反射。
使用Photoshop或Gimp之类的工具剪切一些部分非常容易变换一个diffuse纹理为specular图片以增加亮度/对比度的方式,可以把这个部分变换为黑色或白色。
###15.2.1 specular贴图采样
一个specular贴图和其他纹理一样所以代码和diffuse贴图的代码也相似。确保合理的加载了图片生成一个纹理对象。由于我们在同样的像素着色器中使用另一个纹理采样器我们必须为specular贴图使用一个不同的纹理单元查看纹理所以在渲染前让我们把它绑定到合适的纹理单元
```c++
glUniform1i(glGetUniformLocation(lightingShader.Program, "material.specular"), 1);
...
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);
```
然后更新像素着色器材质属性接受一个sampler2D作为这个specular部分的类型而不是vec3
```c++
struct Material
{
sampler2D diffuse;
sampler2D specular;
float shininess;
};
```
最后我们希望采样这个specular贴图来获取原始像素相应的specular亮度
```c++
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
color = vec4(ambient + diffuse + specular, 1.0f);
```
通过使用一个specular贴图我们可以定义极为精细的细节物体的这个部分会获得闪亮的属性我们可以设置它们相应的亮度。specular贴图给我们一个附加的高于diffuse贴图的控制权限。
如果你不想成为主流你可以在specular贴图里使用颜色不单单为每个原始像素设置specular亮度同时也设置specular高光的颜色。从真实角度来说specular的颜色基本是由光源自身决定的所以它不会生成真实的图像这就是为什么图片通常是黑色和白色的我们只关心亮度
如果你现在运行应用,你可以清晰地看到箱子的材质现在非常类似真实的铁边的木头箱子了:
![](http://www.learnopengl.com/img/lighting/materials_specular_map.png)
你可以在这里找到全部源码。也对比一下你的顶点着色器和像素着色器。
使用diffuse和specular贴图我们可以给相关但简单物体添加一个极为明显的细节。我们可以使用其他纹理贴图比如法线/bump贴图或者反射贴图给物体添加更多的细节。但是这些在后面教程才会涉及。把你的箱子给你所有的朋友和家人看有一天你会很满足我们的箱子会比现在更漂亮