mirror of
https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git
synced 2025-08-22 20:25:28 +08:00
Prepare for the convert
This commit is contained in:
36
old/01/04 Hello Triangle.md
Normal file
36
old/01/04 Hello Triangle.md
Normal file
@@ -0,0 +1,36 @@
|
||||
本文作者JoeyDeVries,由Cocooshu翻译自[http://learnopengl.com](http://learnopengl.com/#!Getting-started/Hello-Triangle)
|
||||
|
||||
##你好,三角形
|
||||
|
||||
未翻译完成...
|
||||
---
|
||||
|
||||
由于OpenGL使用的是三维坐标系而我们的屏幕和窗口都是由二维像素组成的,所以OpenGL的一项重要工作就是将三维坐标系转换到二维像素使得我们的屏幕能够显示显示这些数据。将三维坐标转换为二维坐标的过程使用OpenGL的图形渲染管线来管理。图形渲染管线可以被分为两大部分:
|
||||
|
||||
|
||||
* 将三维坐标系转换为二维的屏幕坐标
|
||||
* 将转换得到的二维坐标中的每一个点转换为具体的像素颜色
|
||||
|
||||
|
||||
在本教程中我们将简单的讨论OpenGL的渲染管线并使用它来创建一些给力的像素图。
|
||||
|
||||
小提示
|
||||
|
||||
二维坐标系与像素坐标系有一点不同,一个二维坐标描述的是在二维空间中一个确切点的位置,而一个二维像素是一个由二维坐标和屏幕的分辨率所决定的近似值。
|
||||
|
||||
图形渲染管线将一系列的三维坐标转化为你屏幕中的像素颜色。图形渲染管线可以被分为若干个部分,每一个部分都需要上一部分的输出数据作为其输入数据。所有部分都是高效运行并且可以是同时进行的,由于这一并行的机制,现代的图形处理器(GPU)拥有成千上万个核心通过快速地执行一些小程序来处理渲染管线中每一部分的数据。这些小程序被称为着色器(Shader)。
|
||||
|
||||
许多着色器都允许开发者进行配置,OpenGL允许我们写自己的着色器来替换已经存在的默认着色器。由于着色器程序是使用GPU执行的,这样我们就可以通过书写自己的着色器程序来对渲染进行更加细微的控制,也能减轻CPU的运算压力。OpenGL的着色器程序使用GLSL(OpenGL Shading Language)语言编写,我们将在接下来的教程中探索如何使用它。
|
||||
下图是对渲染管线流程简介,其中蓝色的部分允许我们编写自己的着色器程序。
|
||||
|
||||

|
||||
|
||||
正如你所看到的,图形渲染管线把顶点数据最终转化为了像素图形。图形渲染管线中包含了许多个部分,每一个部分处理这个转化过程中的一部分特定的工作。我们将简要地为您解释图形渲染管线中重要部分是如何运作的。
|
||||
|
||||
我们将一组能构成一个三角形的三个三维坐标的数组作为图形渲染管线的输入数据,这组输入数据被称之为顶点数据(Vertex Data),它是许多顶点的集合,每一个顶点都表示三维空间中都一个点。这个顶点数据不止用于表示空间坐标,它还能代表你想输入渲染管线的一切信息。简单起见,我们让每个顶点包含一个顶点的三维坐标和一些颜色值。
|
||||
|
||||
小提示:
|
||||
|
||||
为了让OpenGL知道你的顶点坐标和颜色数据是用来做什么的,OpenGL要求你告诉他你需要用这些顶点数据构成什么图形。我们是想要将传入的顶点数据呈现为一些点还是一些三角形或者只是一组线段?这些给出的渲染类型提示被称之为原型(Primitive),让OpenGL执行任何绘制操作时都需要告诉它我们想要的原型。例如其中一些原型:GL_POINTS, GL_TRIANGLES and GL_LINE_STRIP。
|
||||
|
||||
渲染管线的第一部分是顶点着色器(Vertex Shader),它每次将一个顶点数据作为输入源。使用顶点着色器的主要目的是将输入的三维空间坐标转换为另一个三维的空间坐标(看似很傻逼的,我们后面再详细讨论这个问题),同时顶点着色器也允许我们对顶点数据数据做一些其他的处理操作。
|
331
old/01/05 Shaders.md
Normal file
331
old/01/05 Shaders.md
Normal file
@@ -0,0 +1,331 @@
|
||||
本文作者JoeyDeVries,由[codeman001](https://github.com/codeman001)翻译自[http://learnopengl.com](http://learnopengl.com/#!Getting-started/Shaders)
|
||||
|
||||
##着色器
|
||||
|
||||
在[Hello Triangle](https://github.com/Geequlim/LearnOpenGL-CN/blob/master/01%20Getting%20started/04%20Hello%20Triangle.md) 中已经提到,着色器是在GPU上运行的小程序,这些程序负责处理图形渲染管线的特定阶段,简单来说,着色器也是一个处理输入输出的程序。着色器是非常孤立执行的程序,彼此之间没有太多的交互,
|
||||
|
||||
在前面的教程中,我们大致介绍了表面着色器和如何正确使用它们。接下来我们讲接触更加流行的OpenGL着色语言
|
||||
|
||||
###GLSL
|
||||
GLSL是写法类似C语言,GLSL是专门针对图形以及向量和矩阵变化设计的。着色器通常开始生命一个版本信息,然后是输入、输出列表以及常量和入口函数(main函数),每个着色器都是在入口函数处来处理输入参数。然后输出结果。
|
||||
|
||||
|
||||
着色器通常具有以下结构:
|
||||
```c++
|
||||
#version version_number
|
||||
|
||||
in type in_variable_name;
|
||||
in type in_variable_name;
|
||||
|
||||
out type out_variable_name;
|
||||
|
||||
uniform type uniform_name;
|
||||
|
||||
int main()
|
||||
{
|
||||
// Process input(s) and do some weird graphics stuff
|
||||
...
|
||||
// Output processed stuff to output variable
|
||||
out_variable_name = weird_stuff_we_processed;
|
||||
}
|
||||
```
|
||||
|
||||
所谓的顶点着色器就讲顶点属性作为输入的着色器,顶点属性的个数主要受限于硬件实现,OpenGL的保证总有至少16个四分量的顶点可以使用,但是硬件可能个多些,可以通过查询:**GL_MAX_VERTEX_ATTRIBS**
|
||||
```c++
|
||||
GLint nrAttributes;
|
||||
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
|
||||
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
|
||||
```
|
||||
|
||||
这个结果最小返回16,一般情况下使用。
|
||||
|
||||
###类型
|
||||
|
||||
|
||||
GLSL跟一般的编程语言一样,定义了一些常用的数据类型,基础类型比如:int,float, double, uint and bool等,还用两种我们在教程中经常用到的容器类型:vectors(向量) 和 matrices(矩阵),稍后会提到。
|
||||
|
||||
|
||||
####向量
|
||||
一个向量可以包含1-4个分量,分量的类型可以是上面我们提到的基础类型。向量个类型名字规则如下:
|
||||
|
||||
- vecn: 默认向量,有个n浮点型分量.
|
||||
- bvecn: 有n个bool分量.
|
||||
- ivecn: 有n个整型分量.
|
||||
- uvecn: 有n个无符号整型分量.
|
||||
- dvecn: 有n个双浮点型分量.
|
||||
|
||||
大多数情况下我们使用vecn,因为浮点型足够满足我们大多数需求。
|
||||
向量的分量可以通过vec.x来访问第一个分量,同时可以使用.x,.y,.z,.w来访问一个向量的四个成员,同样可以使用rgba来访问颜色值对应的向量,纹理坐标则可以使用stpq来访问分量的值。
|
||||
向量的访问方式支持趣味性和扩展性,被称为交叉混合性,实例如下:
|
||||
```c++
|
||||
vec2 someVec;
|
||||
vec4 differentVec = someVec.xyxx;
|
||||
vec3 anotherVec = differentVec.zyw;
|
||||
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
|
||||
```
|
||||
可以使用任意组合来组成新向量,同时也可以把维度小的向量放在高纬度向量的构造函数里来构成新的向量。如下代码:
|
||||
```c++
|
||||
vec2 vect = vec2(0.5f, 0.7f);
|
||||
vec4 result = vec4(vect, 0.0f, 0.0f);
|
||||
vec4 otherResult = vec4(result.xyz, 1.0f);
|
||||
```
|
||||
###输入和输出
|
||||
着色器是运行在GPU上的小程序,但是麻雀虽小五脏俱全,也会有输入和输出来构成完整的程序,GLSL使用in和out关键字来定义输入和输出。每个着色器可以是这些关键字来指定输入和输出,输入变量经过处理以后会得到适合下个处理阶段可以使用的输出变量,顶点着色器和片段着色器有点小却别。
|
||||
|
||||
顶点着色器接受特定格式的输入,否则不能正确使用。顶点着色器直接接受输入的顶点数据,但是需要在CPU一边指定数据的对应的位置,前面的教程可以到对位置0的输入(location=0),所以顶点着色器需要一个特定的声明来确定CPU和GPU数据对应关联关系,
|
||||
|
||||
<div style="border:solid #AFDFAF;border-radius:5px;background-color:#D8F5D8;margin:20px 20px 20px 0px;padding:15px">
|
||||
也可以使用*glGetAttribLocation*来查询对应的位置,这样可以省略layout的声明,但是我觉得可以是用layout声明比较好,这也可以减少GPU的一些工作
|
||||
</div>
|
||||
|
||||
|
||||
对于片段着色器有个vec4的颜色作为特定的输出,因为片段处理后最终是要生产一个颜色来显示的。否则将输出黑色或白色颜色作为输出。
|
||||
|
||||
如果我们想从一个着色器向另外一个发送数据,我们需要在发送方定义一个输出,然后再接收方顶一个输入,同时保证这两个变量类型和名字是相同的。
|
||||
下面是一个实例来展示如何从顶点着色器传递一个颜色值跟片段着色器使用:
|
||||
|
||||
***顶点着色器***
|
||||
```c++
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 position; // The position variable has attribute position 0
|
||||
|
||||
out vec4 vertexColor; // Specify a color output to the fragment shader
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(position, 1.0); // See how we directly give a vec3 to vec4's constructor
|
||||
vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f); // Set the output variable to a dark-red color
|
||||
}
|
||||
```
|
||||
***片段着色器***
|
||||
```c++
|
||||
#version 330 core
|
||||
in vec4 vertexColor; // The input variable from the vertex shader (same name and same type)
|
||||
|
||||
out vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = vertexColor;
|
||||
}
|
||||
```
|
||||
可以看到在顶点着色器生命了一个向量:*vertexColor* 有out修饰,同时在片段着色器声明了一个*vertexColor* 使用in来修饰,这样片段着色器就可以获取顶点着色器处理的*vertexColor*的结果了。
|
||||
根据上面shader,可以得出下图的效果:
|
||||
|
||||

|
||||
|
||||
|
||||
**常量**
|
||||
常量是另外一个中从CPU端向GPU传输数据的方式,常量方式跟顶点数据有非常明显的不同。常量有两个特性:
|
||||
|
||||
1.具有全局性,可以在着色器不同阶段来获取同一个常量
|
||||
|
||||
2.不变性,一旦设置了值,在渲染过程中就不能被改变,只有从新设置才能改变。
|
||||
|
||||
声明常量非常简单使用uniform 放在类型和变量名前面即可。下面看一个例子:
|
||||
```c++
|
||||
#version 330 core
|
||||
|
||||
out vec4 color;
|
||||
|
||||
uniform vec4 ourColor; // We set this variable in the OpenGL code.
|
||||
|
||||
void main()
|
||||
{
|
||||
color = ourColor;
|
||||
}
|
||||
```
|
||||
声明了一个ourColor为常量类型,然后把它的值付给了输出变量color。
|
||||
<div style="border:solid #E1B3B3;border-radius:10px;background-color:#FFD2D2;margin:10px 10px 10px 0px;padding:10px">
|
||||
注意:如果声明了一个从来没用到常量,GLSL的编译器会默认删除这个常量,由此可能导致一些莫名的问题。
|
||||
</div>
|
||||
现在这个常量还是个空值,接下来给ourColor在CPU端传递数据给它。思路:获取ourColor在索引位置,然后传递数据给这个位置。另外做一些小动作,不传递固定的这个,传递一个随时间变化的值,如下:
|
||||
```c++
|
||||
GLfloat timeValue = glfwGetTime();
|
||||
GLfloat greenValue = (sin(timeValue) / 2) + 0.5;
|
||||
GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
|
||||
glUseProgram(shaderProgram);
|
||||
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
|
||||
```
|
||||
首先通过glfwGetTime函数获取程序运行时间(秒数)。然后使用sin函数将greenValue的值控制在0-1。
|
||||
|
||||
然后使用glGetUniformLocation函数查询ourColor的索引位置。是一个参数是要查询的着色器程序,第二个参数是常量在着色器中声明的变量名。如果glGetUniformLocation函数返回-1,表明没找到对应的常量的索引位置。
|
||||
|
||||
最合使用glUniform4f来完成赋值。
|
||||
|
||||
注意:使用glGetUniformLocation 不需要在glUseProgram之后,但是glUniform4f一定要在lUseProgram之后,因为我们也只能对当前激活的着色器程序传递数据。
|
||||
到目前为止已经学会了这么给常量传递数据和渲染使用这些数据,如果我们想每帧改变常量的值,我们需要在主循环的不停的计算和更新常量的值。
|
||||
```c++
|
||||
while(!glfwWindowShouldClose(window))
|
||||
{
|
||||
// Check and call events
|
||||
glfwPollEvents();
|
||||
|
||||
// Render
|
||||
// Clear the colorbuffer
|
||||
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Be sure to activate the shader
|
||||
glUseProgram(shaderProgram);
|
||||
|
||||
// Update the uniform color
|
||||
GLfloat timeValue = glfwGetTime();
|
||||
GLfloat greenValue = (sin(timeValue) / 2) + 0.5;
|
||||
GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
|
||||
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
|
||||
|
||||
// Now draw the triangle
|
||||
glBindVertexArray(VAO);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
```
|
||||
如果运行正常的话我们能看到一个绿色到黑色,黑色到绿色变化的三角形,
|
||||
可以查看完整的代码[实例](http://learnopengl.com/code_viewer.php?code=getting-started/shaders-interpolated)
|
||||
|
||||
|
||||
**着色器程序管理程序**
|
||||
|
||||
编写、编译和管理着色器程序是非常繁重的工作,为了减轻这个工作量我们自己定义一个着色器程序管理器,负责读取着色器程序文件,然后编译他们,链接并检查着色器程序有无错误发生。这也可以让我们把已经学到的知识封装到抽象的对象里。
|
||||
|
||||
我们首先顶一个着色器程序的头文件,如下:
|
||||
```c++
|
||||
#ifndef SHADER_H
|
||||
#define SHADER_H
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
#include <GL/glew.h>; // Include glew to get all the required OpenGL headers
|
||||
|
||||
class Shader
|
||||
{
|
||||
public:
|
||||
// The program ID
|
||||
GLuint Program;
|
||||
// Constructor reads and builds the shader
|
||||
Shader(const GLchar* vertexSourcePath, const GLchar* fragmentSourcePath);
|
||||
// Use the program
|
||||
void Use();
|
||||
};
|
||||
|
||||
#endif
|
||||
```
|
||||
着色器程序类包含一个着色器程序ID,有一个接受顶点和片段程序的接口,这个两个路径就是普通的文本文件就可以了。
|
||||
Use函数是一个工具属性的函数,主要是控制当前着色器程序是否激活。
|
||||
|
||||
读取着色器程序文件
|
||||
```c++
|
||||
Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
|
||||
{
|
||||
// 1. Retrieve the vertex/fragment source code from filePath
|
||||
std::string vertexCode;
|
||||
std::string fragmentCode;
|
||||
std::ifstream vShaderFile;
|
||||
std::ifstream fShaderFile;
|
||||
// ensures ifstream objects can throw exceptions:
|
||||
vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
|
||||
fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
|
||||
try
|
||||
{
|
||||
// Open files
|
||||
vShaderFile.open(vertexPath);
|
||||
fShaderFile.open(fragmentPath);
|
||||
std::stringstream vShaderStream, fShaderStream;
|
||||
// Read file's buffer contents into streams
|
||||
vShaderStream << vShaderFile.rdbuf();
|
||||
fShaderStream << fShaderFile.rdbuf();
|
||||
// close file handlers
|
||||
vShaderFile.close();
|
||||
fShaderFile.close();
|
||||
// Convert stream into GLchar array
|
||||
vertexCode = vShaderStream.str();
|
||||
fragmentCode = fShaderStream.str();
|
||||
}
|
||||
catch(std::ifstream::failure e)
|
||||
{
|
||||
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
|
||||
}
|
||||
const GLchar* vShaderCode = vertexCode.c_str();
|
||||
const GLchar* fShaderCode = fragmentCode.c_str();
|
||||
[...]
|
||||
```
|
||||
|
||||
接下来编译和链接这些程序,同时收集一些编译和链接的错误,来帮助我们调试。
|
||||
```c++
|
||||
// 2. Compile shaders
|
||||
GLuint vertex, fragment;
|
||||
GLint success;
|
||||
GLchar infoLog[512];
|
||||
|
||||
// Vertex Shader
|
||||
vertex = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vertex, 1, &vShaderCode, NULL);
|
||||
glCompileShader(vertex);
|
||||
// Print compile errors if any
|
||||
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
|
||||
if(!success)
|
||||
{
|
||||
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
|
||||
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
|
||||
};
|
||||
|
||||
// Similiar for Fragment Shader
|
||||
[...]
|
||||
|
||||
// Shader Program
|
||||
this->Program = glCreateProgram();
|
||||
glAttachShader(this->Program, vertex);
|
||||
glAttachShader(this->Program, fragment);
|
||||
glLinkProgram(this->Program);
|
||||
// Print linking errors if any
|
||||
glGetProgramiv(this->Program, GL_LINK_STATUS, &success);
|
||||
if(!success)
|
||||
{
|
||||
glGetProgramInfoLog(this->Program, 512, NULL, infoLog);
|
||||
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
|
||||
}
|
||||
|
||||
// Delete the shaders as they're linked into our program now and no longer necessery
|
||||
glDeleteShader(vertex);
|
||||
glDeleteShader(fragment);
|
||||
```
|
||||
最后来实现一个use函数
|
||||
|
||||
void Use() { glUseProgram(this->Program); }
|
||||
接下来是一个使用这个简单实例:
|
||||
```c++
|
||||
Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.frag");
|
||||
...
|
||||
while(...)
|
||||
{
|
||||
ourShader.Use();
|
||||
glUniform1f(glGetUniformLocation(ourShader.Program, "someUniform"), 1.0f);
|
||||
DrawStuff();
|
||||
}
|
||||
```
|
||||
上面我们已经把顶点着色器和片段着色器代码分别放在shader.vs和shader.frag里面了,这些文件的名字和后缀名都可以随意命名的,只要符合文件名规范就好。
|
||||
完整的代码实例:
|
||||
|
||||
[使用的实例](http://learnopengl.com/code_viewer.php?code=getting-started/shaders-using-object)
|
||||
|
||||
[着色器类](http://learnopengl.com/code_viewer.php?type=header&code=shader)
|
||||
|
||||
[顶点着色器代码](http://learnopengl.com/code_viewer.php?type=vertex&code=getting-started/basic)
|
||||
|
||||
[片段着色器代码](http://learnopengl.com/code_viewer.php?type=fragment&code=getting-started/basic)
|
||||
|
||||
|
||||
**练习题**
|
||||
|
||||
1.通过调整的顶点着色器,以使三角形是倒置 [答案](http://learnopengl.com/code_viewer.php?code=getting-started/shaders-exercise1)
|
||||
|
||||
2.通过一个常量,使得三角在x方向偏移 [答案](http://learnopengl.com/code_viewer.php?code=getting-started/shaders-exercise2)
|
||||
|
||||
3.使用in 和out 关键字,把顶点着色器的位置数据作为片段着色器的颜色,然后看看得出的三角形颜色,进一步理解差值的问题,同时可以尝试回答下面的问题:为什么我们三角形左下侧有黑边?:[答案](http://learnopengl.com/code_viewer.php?code=getting-started/shaders-exercise3)
|
||||
|
||||
|
6
old/01/06 Textures.md
Normal file
6
old/01/06 Textures.md
Normal file
@@ -0,0 +1,6 @@
|
||||
本文作者JoeyDeVries,由gjy_1992翻译自[http://learnopengl.com](http://learnopengl.com/#!Getting-started/Textures)
|
||||
|
||||
##纹理
|
||||
|
||||
未翻译完成
|
||||
---
|
70
old/01/07 Transformations.md
Normal file
70
old/01/07 Transformations.md
Normal file
@@ -0,0 +1,70 @@
|
||||
本文作者JoeyDeVries,由Meow J翻译自[http://learnopengl.com](http://learnopengl.com/#!Getting-started/Shaders)
|
||||
|
||||
##变换(Transformations)
|
||||
|
||||
尽管我们现在已经知道了如何创建物体,着色,与加入纹理,他们仍然是静态的,还不是那么有趣. 我们可以尝试着在每一帧改变物体的顶点并且重设缓冲区从而使他们移动,但这太繁琐了,而且会消耗很多的处理时间. 然而,我们现在有一个更好的解决方案,那就是使用(多个)矩阵来对这个物体进行**变换**(Transform).
|
||||
|
||||
**矩阵**(Matrix)是一种非常有用的数学工具,尽管听起来可能有些吓人. 不过一旦你适应了使用它们,你会发现它们非常的有用. 在讨论矩阵的过程中,我们必须使用到一些数学知识,如果你对这块的数学非常感兴趣,我会提供给你提供额外的拓展资源.
|
||||
|
||||
为了深入了解变换,我们需要在讨论矩阵之前先来深入探讨**矢量**(Vector). 这一节的目标是让你拥有将来需要的最基础的数学背景知识. 如果你发现这节十分困难,尽量去理解吧. 你以后遇见问题也可以回来复习这节的概念.
|
||||
|
||||
##矢量(Vectors)
|
||||
|
||||
最简单的来定义,矢量表示一个方向. 更正式的说,矢量同时拥有**方向** (Direction)和**大小** (Magnitude). 你可以想象一下,把矢量当做藏宝图的指示:左走10步,之后向北走3步,再向右走5步. 在这个例子当中,“左”就是方向,“10步”就是这个矢量的大小. 你可以发现,这个藏宝图的指示一共有三个矢量. 矢量可以在任意**维度**(Dimension)上,但我们最常使用的是从二维到四维. 如果一个矢量有两个维度,它就表示在平面上的一个方向;如果一个矢量有三个维度,他就能表示在3D世界中的一个方向.
|
||||
|
||||
下图中你可以看到三个矢量,每一个矢量都由一个箭头表示,并且记为(x,y). 我们在2D图片中展示这些矢量,因为这样子会更直观. 你仍然可以把这些2D矢量当做z坐标为0的3D矢量. 因为矢量代表着一个方向,矢量原点的不同**并不会**改变它的值. 在图像中,矢量和是**相等**的,尽管他们的原点不一样.
|
||||
|
||||

|
||||
|
||||
当表示一个矢量的时候,通常我们使用在字符上面加一道杠的方法来表示,比如. 在公式中是这样表示的:
|
||||
|
||||

|
||||
|
||||
因为矢量是方向,所以有些时候会很难形象地用位置(Position)表示出来. 我们通常设定这个方向的原点为(0,0,0),然后指向对应坐标的点,使其变为位置矢量(Position Vector)来表示(我们也可以定义一个不同的原点并认为其从该原点的空间指向那个点). 位置矢量(3,5)将在图像中从原点(0,0)指向点(3,5). 因此,我们可以使用矢量在2D或3D空间中表示方向**与**位置.
|
||||
|
||||
|
||||
就像普通的数字一样,我们也可以给矢量定义一些运算(你可能已经知道一些了).
|
||||
|
||||
### 矢量与标量运算(Scalar Vector Operations)
|
||||
|
||||
**标量**(Scalar)仅仅是一个数字(或者说是仅有一个分量的矢量). 当使用标量对矢量进行加/减/乘/除运算时,我们可以简单地对该矢量的每一个元素进行该运算. 对于加法来说会像这样:
|
||||
|
||||

|
||||
|
||||
其中的+可以是+/-/×/÷,注意-和÷运算时不能颠倒,颠倒的运算是没有定义的(标量-/÷矢量)
|
||||
|
||||
### 矢量的取反(Vector Negation)
|
||||
|
||||
对一个矢量取反会将其方向逆转,比如说一个指向东北方向的矢量取反后将会指向西南方向. 我们在一个矢量的每个分量前加负号从而实现取反(或者说用-1数乘该矢量):
|
||||
|
||||

|
||||
|
||||
### 矢量的加与减(Addition and Subtraction)
|
||||
|
||||
矢量的加法可以被定义为是分量的相加,即将一个矢量中的每一个分量加上另一个矢量的对应分量:
|
||||
|
||||

|
||||
|
||||
在图像上v=(4,2)与k=(1,2)相加是这样的:
|
||||
|
||||

|
||||
|
||||
就像数字的加减一样,矢量的减法等同于一个矢量加上取反的另一个矢量.
|
||||
|
||||

|
||||
|
||||
两个矢量的相减会得到这两个矢量指向位置的差. 这在我们想要获取两点的差会非常有用.
|
||||
|
||||
### 矢量的长度(Length)
|
||||
|
||||
我们使用勾股定理(Pythagoras theorem)来获取矢量的长度(大小). 如果你把矢量的x与y分量画出来,该矢量会形成一个以x与y分量为边的三角形:
|
||||
|
||||

|
||||
|
||||
因为x与y已知,我们可以用勾股定理求出斜边:
|
||||
|
||||

|
||||
|
||||
其中代表矢量的大小. 我们也可以很容易加上把这个公式拓展到三维空间
|
||||
|
||||
#WIP
|
11
old/05/07 Bloom.md
Normal file
11
old/05/07 Bloom.md
Normal file
@@ -0,0 +1,11 @@
|
||||
本文作者JoeyDeVries,由Meow J翻译自[http://learnopengl.com](http://learnopengl.com/#!Advanced-Lighting/Bloom)
|
||||
|
||||
## 泛光(Bloom)
|
||||
|
||||
亮光源与被光照区域常常很难传达给观众,因为显示器的光强度范围是受限制的. 其中一个解决方案是让这些光源泛光(Glow)从而区分亮光源: 让光漏(bleed)出光源. 这有效让观众感觉到光源和光照区域非常的亮.
|
||||
|
||||
光的是通过一个后期处理效果叫做泛光(Bloom)来完成的. 泛光让所有光照地区一种在发光的效果.下面两个场景就是有泛光(右)与无泛光(左)的区别(图像来自于虚幻引擎(Unreal)):
|
||||
|
||||

|
||||
|
||||
泛光提供了对于物体亮度显眼的视觉暗示,因为泛光能给我们物体真的是很亮的视觉效果. 如果我们能够很好的完成它(有一些游戏实现的很糟糕),泛光将能很大程度的加强我们场景的光照效果,并且也能给我们很多特效.
|
Reference in New Issue
Block a user