# 第十四课: 渲染到纹理
# 第十四课:渲染到纹理
“渲染到纹理”是一系列特效方法之一。基本思想是:像通常那样渲染一个场景——只是这次是渲染到可以重用的纹理中。
应用包括:游戏(in-game)相机、后期处理(post-processing)以及你能想象到一切.
## 渲染到纹理
我们有三个任务:创建要渲染的纹理对象;将纹理渲染到对象上;使用生成的纹理。
## 创建渲染目标(Render Target)
我们要渲染的对象叫做帧缓存。它像一个容器,用来存纹理和一个可选的深度缓冲区(depth buffer)。在OpenGL中我们可以像创建其他对象一样创建它:
```
<pre class="calibre16">```
<span class="token2">// The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.</span>
GLuint FramebufferName <span class="token">=</span> <span class="token6">0</span><span class="token1">;</span>
<span class="token3">glGenFramebuffers</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>amp<span class="token1">;</span>FramebufferName<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glBindFramebuffer</span><span class="token1">(</span>GL_FRAMEBUFFER<span class="token1">,</span> FramebufferName<span class="token1">)</span><span class="token1">;</span>
```
```
现在需要创建纹理,纹理中包含着色器的RGB输出。这段代码非常的经典:
```
<pre class="calibre16">```
<span class="token2">// The texture we're going to render to</span>
GLuint renderedTexture<span class="token1">;</span>
<span class="token3">glGenTextures</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>amp<span class="token1">;</span>renderedTexture<span class="token1">)</span><span class="token1">;</span>
<span class="token2">// "Bind" the newly created texture : all future texture functions will modify this texture</span>
<span class="token3">glBindTexture</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> renderedTexture<span class="token1">)</span><span class="token1">;</span>
<span class="token2">// Give an empty image to OpenGL ( the last "0" )</span>
<span class="token3">glTexImage2D</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span>GL_RGB<span class="token1">,</span> <span class="token6">1024</span><span class="token1">,</span> <span class="token6">768</span><span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span>GL_RGB<span class="token1">,</span> GL_UNSIGNED_BYTE<span class="token1">,</span> <span class="token6">0</span><span class="token1">)</span><span class="token1">;</span>
<span class="token2">// Poor filtering. Needed !</span>
<span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MAG_FILTER<span class="token1">,</span> GL_NEAREST<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MIN_FILTER<span class="token1">,</span> GL_NEAREST<span class="token1">)</span><span class="token1">;</span>
```
```
同时还需要一个深度缓冲区(depth buffer)。这是可选的,取决于纹理中实际需要画的东西;由于我们渲染的是小猴Suzanne,所以需要深度测试。
```
<pre class="calibre16">```
<span class="token2">// The depth buffer</span>
GLuint depthrenderbuffer<span class="token1">;</span>
<span class="token3">glGenRenderbuffers</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>amp<span class="token1">;</span>depthrenderbuffer<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glBindRenderbuffer</span><span class="token1">(</span>GL_RENDERBUFFER<span class="token1">,</span> depthrenderbuffer<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glRenderbufferStorage</span><span class="token1">(</span>GL_RENDERBUFFER<span class="token1">,</span> GL_DEPTH_COMPONENT<span class="token1">,</span> <span class="token6">1024</span><span class="token1">,</span> <span class="token6">768</span><span class="token1">)</span><span class="token1">;</span>
<span class="token3">glFramebufferRenderbuffer</span><span class="token1">(</span>GL_FRAMEBUFFER<span class="token1">,</span> GL_DEPTH_ATTACHMENT<span class="token1">,</span> GL_RENDERBUFFER<span class="token1">,</span> depthrenderbuffer<span class="token1">)</span><span class="token1">;</span>
```
```
最后,配置frameBuffer。
```
<pre class="calibre16">```
<span class="token2">// Set "renderedTexture" as our colour attachement #0</span>
<span class="token3">glFramebufferTexture</span><span class="token1">(</span>GL_FRAMEBUFFER<span class="token1">,</span> GL_COLOR_ATTACHMENT0<span class="token1">,</span> renderedTexture<span class="token1">,</span> <span class="token6">0</span><span class="token1">)</span><span class="token1">;</span>
<span class="token2">// Set the list of draw buffers.</span>
GLenum DrawBuffers<span class="token1">[</span><span class="token6">2</span><span class="token1">]</span> <span class="token">=</span> <span class="token1">{</span>GL_COLOR_ATTACHMENT0<span class="token1">}</span><span class="token1">;</span>
<span class="token3">glDrawBuffers</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> DrawBuffers<span class="token1">)</span><span class="token1">;</span> <span class="token2">// "1" is the size of DrawBuffers</span>
```
```
这个过程中可能出现一些错误,取决于GPU的性能;下面是检查的方法:
```
<pre class="calibre16">```
<span class="token2">// Always check that our framebuffer is ok</span>
<span class="token4">if</span><span class="token1">(</span><span class="token3">glCheckFramebufferStatus</span><span class="token1">(</span>GL_FRAMEBUFFER<span class="token1">)</span> <span class="token">!=</span> GL_FRAMEBUFFER_COMPLETE<span class="token1">)</span>
<span class="token4">return</span> <span class="token6">false</span><span class="token1">;</span>
```
```
## 渲染到纹理
渲染到纹理很直观。简单地绑定帧缓存,然后像往常一样画场景。轻松搞定!
```
<pre class="calibre16">```
<span class="token2">// Render to our framebuffer</span>
<span class="token3">glBindFramebuffer</span><span class="token1">(</span>GL_FRAMEBUFFER<span class="token1">,</span> FramebufferName<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glViewport</span><span class="token1">(</span><span class="token6">0</span><span class="token1">,</span><span class="token6">0</span><span class="token1">,</span><span class="token6">1024</span><span class="token1">,</span><span class="token6">768</span><span class="token1">)</span><span class="token1">;</span> <span class="token2">// Render on the whole framebuffer, complete from the lower left corner to the upper right</span>
```
```
fragment shader只需稍作调整:
```
<pre class="calibre16">```
<span class="token3">layout</span><span class="token1">(</span>location <span class="token">=</span> <span class="token6">0</span><span class="token1">)</span> out vec3 color<span class="token1">;</span>
```
```
这意味着每当修改变量“color”时,实际修改了0号渲染目标;这是因为之前调用了`glFramebufferTexture(GL\_FRAMEBUFFER, GL\_COLOR\_ATTACHMENT0, renderedTexture, 0);
注意:最后一个参数表示mipmap的级别,这个0和GL\_COLOR\_ATTACHMENT0没有任何关系。
## 使用渲染出的纹理
我们将画一个简单的铺满屏幕的四边形。需要buffer、shader、ID……
```
<pre class="calibre16">```
<span class="token2">// The fullscreen quad's FBO</span>
GLuint quad_VertexArrayID<span class="token1">;</span>
<span class="token3">glGenVertexArrays</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>quad_VertexArrayID<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glBindVertexArray</span><span class="token1">(</span>quad_VertexArrayID<span class="token1">)</span><span class="token1">;</span>
static const GLfloat g_quad_vertex_buffer_data<span class="token1">[</span><span class="token1">]</span> <span class="token">=</span> <span class="token1">{</span>
<span class="token">-</span><span class="token6">1.0</span>f<span class="token1">,</span> <span class="token">-</span><span class="token6">1.0</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span>
<span class="token6">1.0</span>f<span class="token1">,</span> <span class="token">-</span><span class="token6">1.0</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span>
<span class="token">-</span><span class="token6">1.0</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span>
<span class="token">-</span><span class="token6">1.0</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span>
<span class="token6">1.0</span>f<span class="token1">,</span> <span class="token">-</span><span class="token6">1.0</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span>
<span class="token6">1.0</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token1">,</span> <span class="token6">0.0</span>f<span class="token1">,</span>
<span class="token1">}</span><span class="token1">;</span>
GLuint quad_vertexbuffer<span class="token1">;</span>
<span class="token3">glGenBuffers</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>quad_vertexbuffer<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> quad_vertexbuffer<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glBufferData</span><span class="token1">(</span>GL_ARRAY_BUFFER<span class="token1">,</span> <span class="token3">sizeof</span><span class="token1">(</span>g_quad_vertex_buffer_data<span class="token1">)</span><span class="token1">,</span> g_quad_vertex_buffer_data<span class="token1">,</span> GL_STATIC_DRAW<span class="token1">)</span><span class="token1">;</span>
<span class="token2">// Create and compile our GLSL program from the shaders</span>
GLuint quad_programID <span class="token">=</span> <span class="token3">LoadShaders</span><span class="token1">(</span> <span class="token5">"Passthrough.vertexshader"</span><span class="token1">,</span> <span class="token5">"SimpleTexture.fragmentshader"</span> <span class="token1">)</span><span class="token1">;</span>
GLuint texID <span class="token">=</span> <span class="token3">glGetUniformLocation</span><span class="token1">(</span>quad_programID<span class="token1">,</span> <span class="token5">"renderedTexture"</span><span class="token1">)</span><span class="token1">;</span>
GLuint timeID <span class="token">=</span> <span class="token3">glGetUniformLocation</span><span class="token1">(</span>quad_programID<span class="token1">,</span> <span class="token5">"time"</span><span class="token1">)</span><span class="token1">;</span>
```
```
现在想渲染到屏幕上的话,必须把glBindFramebuffer的第二个参数设为0。
```
<pre class="calibre16">```
<span class="token2">// Render to the screen</span>
<span class="token3">glBindFramebuffer</span><span class="token1">(</span>GL_FRAMEBUFFER<span class="token1">,</span> <span class="token6">0</span><span class="token1">)</span><span class="token1">;</span>
<span class="token3">glViewport</span><span class="token1">(</span><span class="token6">0</span><span class="token1">,</span><span class="token6">0</span><span class="token1">,</span><span class="token6">1024</span><span class="token1">,</span><span class="token6">768</span><span class="token1">)</span><span class="token1">;</span> <span class="token2">// Render on the whole framebuffer, complete from the lower left corner to the upper right</span>
```
```
我们用下面这个shader来画全屏的四边形:
```
<pre class="calibre16">```
#version <span class="token6">330</span> core
<span class="token4">in</span> vec2 UV<span class="token1">;</span>
out vec3 color<span class="token1">;</span>
uniform sampler2D renderedTexture<span class="token1">;</span>
uniform float time<span class="token1">;</span>
void <span class="token3">main</span><span class="token1">(</span><span class="token1">)</span><span class="token1">{</span>
color <span class="token">=</span> <span class="token3">texture</span><span class="token1">(</span> renderedTexture<span class="token1">,</span> UV <span class="token">+</span> <span class="token6">0.005</span><span class="token">*</span><span class="token3">vec2</span><span class="token1">(</span> <span class="token3">sin</span><span class="token1">(</span>time<span class="token">+</span><span class="token6">1024.0</span><span class="token">*</span>UV<span class="token1">.</span>x<span class="token1">)</span><span class="token1">,</span><span class="token3">cos</span><span class="token1">(</span>time<span class="token">+</span><span class="token6">768.0</span><span class="token">*</span>UV<span class="token1">.</span>y<span class="token1">)</span><span class="token1">)</span> <span class="token1">)</span><span class="token1">.</span>xyz<span class="token1">;</span>
<span class="token1">}</span>
```
```
这段代码只是简单地采样纹理,加上一个随时间变化的微小偏移。
## 结果
![](https://box.kancloud.cn/2015-11-02_5636f308caa98.png)
## 进一步探索
## 使用深度
在一些情况下,使用已渲染的纹理可能需要深度。本例中,像下面这样,简单地渲染到纹理中:
```
<pre class="calibre16">```
<span class="token3">glTexImage2D</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span>GL_DEPTH_COMPONENT24<span class="token1">,</span> <span class="token6">1024</span><span class="token1">,</span> <span class="token6">768</span><span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span>GL_DEPTH_COMPONENT<span class="token1">,</span> GL_FLOAT<span class="token1">,</span> <span class="token6">0</span><span class="token1">)</span><span class="token1">;</span>
```
```
(“24”是精度。你可以按需从16,24,32中选。通常24刚好)
上面这些已经足够您起步了。课程源码中有完整的实现。
运行可能有点慢,因为驱动无法使用[Hi-Z](http://developer.amd.com/media/gpu_assets/Depth_in-depth.pdf)这类优化。
下图的深度层次已经经过手动“优化”。通常,深度纹理不会这么清晰。深度纹理中,近 = Z接近0 = 颜色深; 远 = Z接近1 = 颜色浅。
![](https://box.kancloud.cn/2015-11-02_5636f30901503.png)
## 多重采样
能够用多重采样纹理来替代基础纹理:只需要在C++代码中将glTexImage2D替换为[glTexImage2DMultisample](http://www.opengl.org/sdk/docs/man3/xhtml/glTexImage2DMultisample.xml),在fragment shader中将`sampler2D/texture`替换为`sampler2DMS/texelFetch`。
但要注意:`texelFetch`多出了一个参数,表示采样的数量。换句话说,就是没有自动“滤波”(在多重采样中,正确的术语是“分辨率(resolution)”)功能。
所以需要你自己解决多重采样的纹理,另外,非多重采样纹理,是多亏另一个着色器。
没有什么难点,只是体积庞大。
## 多重渲染目标
你可能需要同时写多个纹理。
简单地创建若干纹理(都要有正确、一致的大小!),调用glFramebufferTexture,为每一个纹理设置一个不同的color attachement,用更新的参数(如`(2,{GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1,GL_DEPTH_ATTACHMENT})`一样)调用glDrawBuffers,然后在片断着色器中多添加一个输出变量:
```
<pre class="calibre16">```
<span class="token3">layout</span><span class="token1">(</span>location <span class="token">=</span> <span class="token6">1</span><span class="token1">)</span> out vec3 normal_tangentspace<span class="token1">;</span> <span class="token2">// or whatever</span>
```
```
提示1:如果真需要在纹理中输出向量,浮点纹理也是有的,可以用16或32位精度代替8位……看看[glTexImage2D](http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml)的参考手册(搜GL\_FLOAT)。提示2:对于以前版本的OpenGL,请使用glFragData\[1\] = myvalue。
## 练习
- 试使用`glViewport(0,0,512,768)`代替`glViewport(0,0,1024,768)`;(帧缓存、屏幕两种情况都试试)
- 在最后一个fragment shader中尝试一下用其他UV坐标
- 试用一个真正的变换矩阵变换四边形。首先用硬编码方式。然后尝试使用`controls.hpp`里面的函数,观察到了什么现象?
> © <http://www.opengl-tutorial.org/>
> Written with [StackEdit](https://stackedit.io/).