高級OpenGL-05.幀緩沖(Framebuffers)

到目前為止臼予,我們使用了幾種不同類型的屏幕緩沖:用于寫入顏色值的顏色緩沖,用于寫入深度信息的深度緩沖啃沪,以及允許我們基于一些條件丟棄指定片段的模板緩沖粘拾。把這幾種緩沖結合起來叫做幀緩沖(Framebuffer),它被儲存于內存中创千。OpenGL給了我們自己定義幀緩沖的自由缰雇,我們可以選擇性的定義自己的顏色緩沖入偷、深度和模板緩沖

我們目前所做的渲染操作都是是在默認的幀緩沖之上進行的。當你創(chuàng)建了你的窗口的時候默認幀緩沖就被創(chuàng)建和配置好了(GLFW為我們做了這件事)械哟。通過創(chuàng)建我們自己的幀緩沖我們能夠獲得一種額外的渲染方式盯串。

你也許不能立刻理解應用程序的幀緩沖的含義,通過幀緩沖可以將你的場景渲染到一個不同的幀緩沖中戒良,可以使我們能夠在場景中創(chuàng)建鏡子這樣的效果,或者做出一些炫酷的特效冠摄。首先我們會討論它們是如何工作的糯崎,然后我們將利用幀緩沖來實現(xiàn)一些炫酷的效果。

創(chuàng)建一個幀緩沖

就像OpenGL中其他對象一樣河泳,我們可以使用一個叫做glGenFramebuffers的函數(shù)來創(chuàng)建一個幀緩沖對象(簡稱FBO):

GLuint fbo;
glGenFramebuffers(1, &fbo);

我們使用glBindFramebuffer來將fbo綁定到當前幀緩沖:

glBindFramebuffer(GL_FRAMEBUFFER, fbo);

綁定到GL_FRAMEBUFFER目標后沃呢,接下來所有的讀、寫幀緩沖的操作都會影響到當前綁定的幀緩沖拆挥。也可以把幀緩沖分開綁定到讀或寫目標上薄霜,分別使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER來做這件事。如果綁定到了GL_READ_FRAMEBUFFER纸兔,就能執(zhí)行所有讀取操作惰瓜,像glReadPixels這樣的函數(shù)使用了;綁定到GL_DRAW_FRAMEBUFFER上汉矿,就允許進行渲染崎坊、清空和其他的寫入操作。大多數(shù)時候你不必分開用洲拇,通常把兩個都綁定到GL_FRAMEBUFFER上就行奈揍。

現(xiàn)在我們還不能使用自己的幀緩沖,因為還沒做完呢赋续。建構一個完整的幀緩沖必須滿足以下條件:

  • 我們必須往里面加入至少一個附件(attachments)(顏色男翰、深度、模板緩沖)纽乱。
  • 其中至少有一個是顏色附件蛾绎。
  • 所有的附件都應該是已經(jīng)完全做好的(已經(jīng)存儲在內存之中)。
  • 每個緩沖都應該有同樣數(shù)目的樣本迫淹。

從上面的需求中你可以看到秘通,我們需要為幀緩沖創(chuàng)建一些附件,還需要把這些附件附加到幀緩沖上敛熬。當我們做完所有上面提到的條件的時候我們就可以用 glCheckFramebufferStatus 帶上 GL_FRAMEBUFFER這個參數(shù)來檢查是否真的成功做到了肺稀。然后檢查當前綁定的幀緩沖,返回了這些規(guī)范中的哪個值应民。如果返回的是 GL_FRAMEBUFFER_COMPLETE就對了:

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) 
    // Execute victory dance

后續(xù)所有渲染操作將渲染到當前綁定的幀緩沖的附加緩沖中话原,由于我們的幀緩沖不是默認的幀緩沖夕吻,渲染命令對窗口的視頻輸出不會產(chǎn)生任何影響。出于這個原因繁仁,它被稱為離屏渲染(off-screen rendering)涉馅,就是渲染到一個另外的緩沖中。為了讓所有的渲染操作對主窗口產(chǎn)生影響我們必須通過綁定為0來使默認幀緩沖被激活:

glBindFramebuffer(GL_FRAMEBUFFER, 0);

當我們做完所有幀緩沖操作黄虱,不要忘記刪除幀緩沖對象:

glDeleteFramebuffers(1, &fbo);

現(xiàn)在在執(zhí)行完成檢測前稚矿,我們需要把一個或更多的附件附加到幀緩沖上。一個附件就是一個內存地址捻浦,這個內存地址里面包含一個為幀緩沖準備的緩沖晤揣,它可以是個圖像。當創(chuàng)建一個附件的時候我們有兩種方式可以采用:紋理或渲染緩沖(renderbuffer)對象朱灿。

紋理附件(Texture attachments)

當把一個紋理附加到幀緩沖上的時候昧识,所有渲染命令會寫入到紋理上,就像它是一個普通的顏色/深度或者模板緩沖一樣盗扒。使用紋理的好處是跪楞,所有渲染操作的結果都會被儲存為一個紋理圖像,這樣我們就可以簡單的在著色器中使用了侣灶。

創(chuàng)建一個幀緩沖的紋理和創(chuàng)建普通紋理差不多:

GLuint texture;
glGenTextures (1, &texture);
glBindTexture (GL_TEXTURE_2D, texture);

glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);

glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

這里主要的區(qū)別是我們把紋理的維度設置為屏幕大械榧馈(盡管不是必須的),我們還傳遞NULL作為紋理的data參數(shù)炫隶。對于這個紋理淋叶,我們只分配內存,而不去填充它伪阶。紋理填充會在渲染到幀緩沖的時候去做煞檩。同樣,要注意栅贴,我們不用關心環(huán)繞方式或者Mipmap斟湃,因為在大多數(shù)時候都不會需要它們的。

如果你打算把整個屏幕渲染到一個或大或小的紋理上檐薯,你需要用新的紋理的尺寸作為參數(shù)再次調用glViewport(要在渲染到你的幀緩沖之前做好)凝赛,否則只有一小部分紋理或屏幕能夠繪制到紋理上。

現(xiàn)在我們已經(jīng)創(chuàng)建了一個紋理坛缕,最后一件要做的事情是把它附加到幀緩沖上:

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D, texture, 0);

glFramebufferTexture2D函數(shù)需要傳入下列參數(shù):

  • target:我們所創(chuàng)建的幀緩沖類型的目標(繪制墓猎、讀取或兩者都有)。
  • attachment:我們所附加的附件的類型∽現(xiàn)在我們附加的是一個顏色附件毙沾。需要注意,最后的那個0是暗示我們可以附加1個以上顏色的附件宠页。我們會在后面的教程中談到左胞。
  • textarget:你希望附加的紋理類型寇仓。
  • texture:附加的實際紋理。
  • level:Mipmap level烤宙。我們設置為0遍烦。

除顏色附件以外,我們還可以附加一個深度和一個模板紋理到幀緩沖對象上躺枕。為了附加一個深度緩沖服猪,我們可以知道那個GL_DEPTH_ATTACHMENT作為附件類型。記住拐云,這時紋理格式和內部格式類型(internalformat)就成了 GL_DEPTH_COMPONENT去反應深度緩沖的存儲格式蔓姚。附加一個模板緩沖,你要使用 GL_STENCIL_ATTACHMENT
作為第二個參數(shù)慨丐,把紋理格式指定為 GL_STENCIL_INDEX。

也可以同時附加一個深度緩沖和一個模板緩沖為一個單獨的紋理泄私。這樣紋理的每32位數(shù)值就包含了24位的深度信息和8位的模板信息房揭。為了把一個深度和模板緩沖附加到一個單獨紋理上,我們使用GL_DEPTH_STENCIL_ATTACHMENT類型配置紋理格式以包含深度值和模板值的結合物晌端。下面是一個附加了深度和模板緩沖為單一紋理的例子:

glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL );
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);

渲染緩沖對象附件(Renderbuffer object attachments)

在介紹了幀緩沖的可行附件類型——紋理后捅暴,OpenGL引進了渲染緩沖對象(Renderbuffer objects),所以在過去那些美好時光里紋理是附件的唯一可用的類型咧纠。和紋理圖像一樣蓬痒,渲染緩沖對象也是一個緩沖,它可以是一堆字節(jié)漆羔、整數(shù)梧奢、像素或者其他東西。渲染緩沖對象的一大優(yōu)點是演痒,它以OpenGL原生渲染格式儲存它的數(shù)據(jù)亲轨,因此在離屏渲染到幀緩沖的時候,這些數(shù)據(jù)就相當于被優(yōu)化過的了鸟顺。

渲染緩沖對象將所有渲染數(shù)據(jù)直接儲存到它們的緩沖里惦蚊,而不會進行針對特定紋理格式的任何轉換,這樣它們就成了一種快速可寫的存儲介質了讯嫂。然而蹦锋,渲染緩沖對象通常是只寫的,不能修改它們(就像獲取紋理欧芽,不能寫入紋理一樣)莉掂。可以用glReadPixels函數(shù)去讀取,函數(shù)返回一個當前綁定的幀緩沖的特定像素區(qū)域渐裸,而不是直接返回附件本身巫湘。

因為它們的數(shù)據(jù)已經(jīng)是原生格式了装悲,在寫入或把它們的數(shù)據(jù)簡單地到其他緩沖的時候非常快尚氛。當使用渲染緩沖對象時诀诊,像切換緩沖這種操作變得異常高速。我們在每個渲染迭代末尾使用的那個glfwSwapBuffers函數(shù)阅嘶,同樣以渲染緩沖對象實現(xiàn):我們簡單地寫入到一個渲染緩沖圖像属瓣,最后交換到另一個里。渲染緩沖對象對于這種操作來說很完美讯柔。

創(chuàng)建一個渲染緩沖對象和創(chuàng)建幀緩沖代碼差不多:

GLuint rbo;
glGenRenderbuffers(1, &rbo);

相似地抡蛙,我們打算把渲染緩沖對象綁定,這樣所有后續(xù)渲染緩沖操作都會影響到當前的渲染緩沖對象:

glBindRenderbuffer(GL_RENDERBUFFER, rbo);

由于渲染緩沖對象通常是只寫的魂迄,它們經(jīng)常作為深度和模板附件來使用粗截,由于大多數(shù)時候,我們不需要從深度和模板緩沖中讀取數(shù)據(jù)捣炬,但仍關心深度和模板測試熊昌。我們就需要有深度和模板值提供給測試,但不需要對這些值進行采樣(sample)湿酸,所以深度緩沖對象是完全符合的婿屹。當我們不去從這些緩沖中采樣的時候,渲染緩沖對象通常很合適推溃,因為它們等于是被優(yōu)化過的昂利。

調用glRenderbufferStorage函數(shù)可以創(chuàng)建一個深度和模板渲染緩沖對象:

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);

創(chuàng)建一個渲染緩沖對象與創(chuàng)建紋理對象相似航徙,不同之處在于這個對象是專門被設計用于圖像的血巍,而不是通用目的的數(shù)據(jù)緩沖狸棍,比如紋理灸眼。這里我們選擇GL_DEPTH24_STENCIL8作為內部格式纹蝴,它同時代表24位的深度和8位的模板緩沖从诲。

最后一件還要做的事情是把幀緩沖對象附加上:

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

在幀緩沖項目中俺夕,渲染緩沖對象可以提供一些優(yōu)化丹喻,但更重要的是知道何時使用渲染緩沖對象襟铭,何時使用紋理碌奉。通常的規(guī)則是,如果你永遠都不需要從特定的緩沖中進行采樣寒砖,渲染緩沖對象對特定緩沖是更明智的選擇赐劣。如果哪天需要從比如顏色或深度值這樣的特定緩沖采樣數(shù)據(jù)的話,你最好還是使用紋理附件哩都。從執(zhí)行效率角度考慮魁兼,它不會對效率有太大影響。

渲染到紋理

現(xiàn)在我們知道了(一些)幀緩沖如何工作的漠嵌,是時候把它們用起來了我們會把場景渲染到一個顏色紋理上咐汞,這個紋理附加到一個我們創(chuàng)建的幀緩沖上盖呼,然后把紋理繪制到一個簡單的四邊形上,這個四邊形鋪滿整個屏幕化撕。輸出的圖像看似和沒用幀緩沖一樣几晤,但是這次,它其實是直接打印到了一個單獨的四邊形上面植阴。為什么這很有用呢蟹瘾?下一部分我們會看到原因。

第一件要做的事情是創(chuàng)建一個幀緩沖對象掠手,并綁定它憾朴,這比較明了:

GLuint framebuffer;
glGenFramebuffers (1, &framebuffer);
glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);

下一步我們創(chuàng)建一個紋理圖像,這是我們將要附加到幀緩沖的顏色附件喷鸽。我們把紋理的尺寸設置為窗口的寬度和高度众雷,并保持數(shù)據(jù)未初始化:

// Generate texture
GLuint texColorBuffer;
glGenTextures(1, &texColorBuffer);
glBindTexture(GL_TEXTURE_2D, texColorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);

// Attach it to currently bound framebuffer object
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);

我們同樣打算要讓OpenGL確定可以進行深度測試(模板測試,如果你用的話)所以我們必須還要確保向幀緩沖中添加一個深度(和模板)附件做祝。由于我們只采樣顏色緩沖报腔,并不采樣其他緩沖,我們可以創(chuàng)建一個渲染緩沖對象來達到這個目的剖淀。記住,當你不打算從指定緩沖采樣的的時候纤房,它們是一個不錯的選擇纵隔。

創(chuàng)建一個渲染緩沖對象不太難。唯一一件要記住的事情是炮姨,我們正在創(chuàng)建的是一個渲染緩沖對象的深度和模板附件捌刮。我們把它的內部給事設置為GL_DEPTH24_STENCIL8
,對于我們的目的來說這個精確度已經(jīng)足夠了舒岸。

GLuint rbo; // render buffer object
glGenRenderbuffers (1, &rbo);
glBindRenderbuffer (GL_RENDERBUFFER, rbo);
glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenWidth, screenHeight);
glBindRenderbuffer (GL_RENDERBUFFER, 0);

我們?yōu)殇秩揪彌_對象分配了足夠的內存空間以后绅作,我們可以解綁渲染緩沖。

接著蛾派,在做好幀緩沖之前俄认,還有最后一步,我們把渲染緩沖對象附加到幀緩沖的深度和模板附件上:

glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

然后我們要檢查幀緩沖是否真的做好了洪乍,如果沒有眯杏,我們就打印一個錯誤消息。

if (glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    cout << "ERROR:FRAMEBUFFER:: Framebuffer is not complete!" << endl;
glBindFramebuffer (GL_FRAMEBUFFER, 0);

還要保證解綁幀緩沖壳澳,這樣我們才不會意外渲染到錯誤的幀緩沖上岂贩。

現(xiàn)在幀緩沖做好了,我們要做的全部就是渲染到幀緩沖上巷波,而不是綁定到幀緩沖對象的默認緩沖萎津。余下所有命令會影響到當前綁定的幀緩沖上卸伞。所有深度和模板操作同樣會從當前綁定的幀緩沖的深度和模板附件中讀取,當然锉屈,得是在它們可用的情況下荤傲。如果你遺漏了比如深度緩沖,所有深度測試就不會工作部念,因為當前綁定的幀緩沖里沒有深度緩沖弃酌。

所以,為把場景繪制到一個單獨的紋理儡炼,我們必須以下面步驟來做:

  1. 使用新的綁定為激活幀緩沖的幀緩沖妓湘,像往常那樣渲染場景。
  2. 綁定到默認幀緩沖乌询。
  3. 繪制一個四邊形榜贴,讓它平鋪到整個屏幕上,用新的幀緩沖的顏色緩沖texColorBuffer作為他的紋理妹田。

我們使用在深度測試教程中同一個場景進行繪制唬党,但是這次使用老氣橫秋的箱子紋理

為了繪制四邊形我們將會創(chuàng)建新的著色器鬼佣。我們不打算引入任何花哨的變換矩陣驶拱,因為我們只提供已經(jīng)是標準化設備坐標的頂點坐標,所以我們可以直接把它們作為頂點著色器的輸出晶衷。頂點著色器看起來像這樣:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoords;

out vec2 TexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0f);
    TexCoords = texCoords;
}

沒有花哨的地方蓝纲。片段著色器更簡潔,因為我們做的唯一一件事是從紋理采樣:

#version 330 core
in vec2 TexCoords;
out vec4 color;

uniform sampler2D screenTexture;

void main()
{
    color = texture(screenTexture, TexCoords);
}

接著需要你為屏幕上的四邊形創(chuàng)建和配置一個VAO晌纫。

    // Setup quad VAO
    GLuint quadVAO, quadVBO;
    glGenVertexArrays (1, &quadVAO);
    glGenBuffers (1, &quadVBO);
    glBindVertexArray (quadVAO);
    glBindBuffer (GL_ARRAY_BUFFER, quadVBO);
    glBufferData (GL_ARRAY_BUFFER, sizeof (quadVertices), &quadVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray (0);
    glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof (GLfloat), (GLvoid*) 0);
    glEnableVertexAttribArray (1);
    glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof (GLfloat), (GLvoid *) (2 * sizeof (GLfloat)));

渲染迭代中幀緩沖處理會有下面的結構:

// First pass
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We're not using stencil buffer now
glEnable(GL_DEPTH_TEST);
DrawScene();

// Second pass
glBindFramebuffer(GL_FRAMEBUFFER, 0); // back to default
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

screenShader.Use();  
glBindVertexArray(quadVAO);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);

只有很少的事情要說明税迷。第一,由于我們用的每個幀緩沖都有自己的一系列緩沖锹漱,我們打算使用glClear設置的合適的位(bits)來清空這些緩沖箭养。第二,當渲染四邊形的時候哥牍,我們關閉深度測試毕泌,因為我們不關系深度測試,我們繪制的是一個簡單的四邊形嗅辣;當我們繪制普通場景時我們必須再次開啟深度測試懈词。

如果每件事都做對了就一定能成功,你將會得到這樣的輸出:

左側展示了和深度測試教程中一樣的輸出結果辩诞,但是這次卻是渲染到一個簡單的四邊形上的坎弯。如果我們以線框方式顯示的話,那么顯然,我們只是繪制了一個默認幀緩沖中單調的四邊形抠忘。

你可以從這里得到應用的源碼撩炊。
項目完整代碼在這里

然而這有什么好處呢崎脉?好處就是我們現(xiàn)在可以自由的獲取已經(jīng)渲染場景中的任何像素拧咳,然后把它當作一個紋理圖像了,我們可以在片段著色器中創(chuàng)建一些有意思的效果囚灼。所有這些有意思的效果統(tǒng)稱為后處理特效骆膝。

后處理

現(xiàn)在,整個場景渲染到了一個單獨的紋理上灶体,我們可以創(chuàng)建一些有趣的效果阅签,只要簡單操縱紋理數(shù)據(jù)就能做到。這部分蝎抽,我們會向你展示一些流行的后處理特效政钟,以及怎樣添加一些創(chuàng)造性去創(chuàng)建出你自己的特效。樟结、

反相

我們已經(jīng)取得了渲染輸出的每個顏色养交,所以在片段著色器里返回這些顏色的反色并不難。我們得到屏幕紋理的顏色瓢宦,然后用1.0減去它:

void main()
{
    color = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}

灰度

另一個有意思的效果是移除所有除了黑白灰以外的顏色作用碎连,是整個圖像成為黑白的。實現(xiàn)它的簡單的方式是獲得所有顏色元素驮履,然后將它們平均化

void main()
{
    color = texture(screenTexture, TexCoords);
    float average = (color.r + color.g + color.b) / 3.0;
    color = vec4(average, average, average, 1.0);
}

這已經(jīng)創(chuàng)造出很贊的效果了鱼辙,但是人眼趨向于對綠色更敏感,對藍色感知比較弱疲吸,所以為了獲得更精確的符合人體物理的結果,我們需要使用加權通道:

void main()
{
    color = texture(screenTexture, TexCoords);
    float average = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
    color = vec4(average, average, average, 1.0);
}

Kernel effects

在單獨紋理圖像上進行后處理的另一個好處是我們可以從紋理的其他部分進行采樣前鹅。比如我們可以從當前紋理值的周圍采樣多個紋理值摘悴。創(chuàng)造性地把它們結合起來就能創(chuàng)造出有趣的效果了。

kernel是一個長得有點像一個小矩陣的數(shù)值數(shù)組舰绘,它中間的值中心可以映射到一個像素上蹂喻,這個像素和這個像素周圍的值再乘以kernel,最后再把結果相加就能得到一個值捂寿。所以口四,我們基本上就是給當前紋理坐標加上一個它四周的偏移量,然后基于kernel把它們結合起來秦陋。下面是一個kernel的例子:

這個kernel表示一個像素周圍八個像素乘以2蔓彩,它自己乘以-15。這個例子基本上就是把周圍像素乘上2,中間像素去乘以一個比較大的負數(shù)來進行平衡赤嚼。

你在網(wǎng)上能找到的kernel的例子大多數(shù)都是所有值加起來等于1旷赖,如果加起來不等于1就意味著這個紋理值比原來更大或者更小了。

kernel對于后處理來說非常管用更卒,因為用起來簡單等孵。網(wǎng)上能找到有很多實例,為了能用上kernel我們還得改改片段著色器蹂空。這里假設每個kernel都是3×3(實際上大多數(shù)都是3×3):

const float offset = 1.0 / 300;  

void main()
{
    vec2 offsets[9] = vec2[](
        vec2(-offset, offset),  // top-left
        vec2(0.0f,    offset),  // top-center
        vec2(offset,  offset),  // top-right
        vec2(-offset, 0.0f),    // center-left
        vec2(0.0f,    0.0f),    // center-center
        vec2(offset,  0.0f),    // center-right
        vec2(-offset, -offset), // bottom-left
        vec2(0.0f,    -offset), // bottom-center
        vec2(offset,  -offset)  // bottom-right
    );

    float kernel[9] = float[](
        -1, -1, -1,
        -1,  9, -1,
        -1, -1, -1
    );

    vec3 sampleTex[9];
    for(int i = 0; i < 9; i++)
    {
        sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
    }
    vec3 col;
    for(int i = 0; i < 9; i++)
        col += sampleTex[i] * kernel[i];

    color = vec4(col, 1.0);
}

在片段著色器中我們先為每個四周的紋理坐標創(chuàng)建一個9個vec2偏移量的數(shù)組俯萌。偏移量是一個簡單的常數(shù),你可以設置為自己喜歡的上枕。接著我們定義kernel咐熙,這里應該是一個銳化kernel,它通過一種有趣的方式從所有周邊的像素采樣姿骏,對每個顏色值進行銳化糖声。最后,在采樣的時候我們把每個偏移量加到當前紋理坐標上分瘦,然后用加在一起的kernel的值乘以這些紋理值蘸泻。

這個銳化的kernel看起來像這樣:

這里創(chuàng)建的有趣的效果就好像你的玩家吞了某種麻醉劑產(chǎn)生的幻覺一樣。

Blur

創(chuàng)建模糊效果的kernel定義如下:

由于所有數(shù)值加起來的總和為16,簡單返回結合起來的采樣顏色是非常亮的,所以我們必須將kernel的每個值除以16.最終的kernel數(shù)組會是這樣的:

float kernel[9] = float[](
    1.0 / 16, 2.0 / 16, 1.0 / 16,
    2.0 / 16, 4.0 / 16, 2.0 / 16,
    1.0 / 16, 2.0 / 16, 1.0 / 16  
);

通過在像素著色器中改變kernel的float數(shù)組,我們就完全改變了之后的后處理效果.現(xiàn)在看起來會像是這樣:

這樣的模糊效果具有創(chuàng)建許多有趣效果的潛力.例如,我們可以隨著時間的變化改變模糊量,創(chuàng)建出類似于某人喝醉酒的效果,或者,當我們的主角摘掉眼鏡的時候增加模糊.模糊也能為我們在后面的教程中提供都顏色值進行平滑處理的能力.

你可以看到我們一旦擁有了這個kernel的實現(xiàn)以后,創(chuàng)建一個后處理特效就不再是一件難事.最后,我們再來討論一個流行的特效,以結束本節(jié)內容.

邊檢測

下面的邊檢測kernel與銳化kernel類似:

這個kernel將所有的邊提高亮度,而對其他部分進行暗化處理,當我們值關心一副圖像的邊緣的時候,它非常有用.

練習

  1. 你可以使用幀緩沖來創(chuàng)建一個后視鏡嗎?做到它,你必須繪制場景兩次:一次正常繪制,另一次攝像機旋轉180度后繪制.嘗試在你的顯示器頂端創(chuàng)建一個小四邊形,在上面應用后視鏡的鏡面紋理:解決方案

完整項目代碼在這

  1. 自己隨意調整一下kernel值,創(chuàng)建出你自己后處理特效.嘗試在網(wǎng)上搜索其他有趣的kernel.
left sobel
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末嘲玫,一起剝皮案震驚了整個濱河市悦施,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌去团,老刑警劉巖抡诞,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異土陪,居然都是意外死亡昼汗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門鬼雀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顷窒,“玉大人,你說我怎么就攤上這事源哩⌒” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵励烦,是天一觀的道長谓着。 經(jīng)常有香客問我,道長坛掠,這世上最難降的妖魔是什么赊锚? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任治筒,我火速辦了婚禮,結果婚禮上改抡,老公的妹妹穿的比我還像新娘矢炼。我一直安慰自己,他們只是感情好阿纤,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布句灌。 她就那樣靜靜地躺著,像睡著了一般欠拾。 火紅的嫁衣襯著肌膚如雪胰锌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天藐窄,我揣著相機與錄音资昧,去河邊找鬼。 笑死荆忍,一個胖子當著我的面吹牛格带,可吹牛的內容都是我干的。 我是一名探鬼主播刹枉,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼叽唱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了微宝?” 一聲冷哼從身側響起棺亭,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蟋软,沒想到半個月后镶摘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡岳守,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年凄敢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湿痢。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡涝缝,死狀恐怖,靈堂內的尸體忽然破棺而出蒙袍,到底是詐尸還是另有隱情俊卤,我是刑警寧澤嫩挤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布害幅,位于F島的核電站,受9級特大地震影響岂昭,放射性物質發(fā)生泄漏以现。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望邑遏。 院中可真熱鬧佣赖,春花似錦、人聲如沸记盒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纪吮。三九已至俩檬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碾盟,已是汗流浹背棚辽。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冰肴,地道東北人屈藐。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像熙尉,于是被迫代替她去往敵國和親联逻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容