Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> GraphicsLab Project之輝光(Glare,Glow)效果

GraphicsLab Project之輝光(Glare,Glow)效果

編輯:關於Android編程

引言

從GraphicsLab Project項目立項以來,一直都在忙著搭建Shader的實驗環境,現在基本的實驗環境已經搭建完畢,所以就試著使用它來編寫一些效果。本篇文章就將向大家講述,如何在OpenGL中,使用GLSL編寫出輝光效果。

輝光(Glare,Glow)

輝光效果,也就是Glare,Glow效果。它的主要作用是模擬一些場景中發光物體周邊的朦胧模糊的發光效果,有點類似於PS中的外發光。先來看一看這種效果的實際圖片,給大家一個感性的認識。

\

圖1

\

圖2

\

圖3

上面圖片給大家展示了輝光效果能夠帶來的視覺體驗。為場景中的發光物體添加輝光效果,能夠實現燈光的朦胧感覺。當然,為了整體畫面的和諧也不能亂用此效果,否則就給人一種很俗氣的感覺。

原理

下面給大家講述下這種效果的實現方式。其實說白了它的實現原理很簡單,我們只要對要添加輝光效果的物體進行一次模糊操作,然後將模糊過後的圖片與原先的圖片進行Alpha Blend,就能夠得到上面所示的效果。操作如圖所展示的那樣(這裡為了簡便,只在場景中繪制Glare物體,並且只添加了些許光照,沒有添加其他效果):

\

圖4 未進行模糊的原場景

\

圖5 對原場景進行模糊之後的圖像

\

圖6 將模糊之後的場景與原場景進行Alpha blend之後的最終效果

上面三張圖片展示了繪制一個簡單輝光效果的整體步驟。原理是不是十分的簡單???大家可以自己在PS等圖像處理軟件中,直接模擬看看是否能夠得出這種效果來。

OpenGL實現

在講述具體的實現之前,我們需要先了解一些基本的知識點。在了解了這些知識點的基礎之上,我們才能夠實現這種效果。

渲染到紋理(Render to Texture, RTT)

在圖形學處理中,我們經常需要對渲染好的場景進行一些處理。也就是說,我們需要一種方式,能夠讓我們通過圖形API(OpenGL, DirectX)繪制的場景保存到我們指定的內存(顯存)中去,以便於我們後期對他進行一些處理。而在OpenGL中,我們能夠很容易的實現這一點。這種技術在我的博客中也有講到。大家可以根據博客中的描述和代碼對此進行了解。

後處理(Post-processing)

後處理在圖形學中經常被用到。我們在游戲中繪制的場景,往往是沒有辦法僅僅依靠光照模型計算就能夠實現出來的。很多的特殊效果,都是通過對渲染好的場景進行圖像空間的後期出理來得到。比如本篇文章中實現的輝光效果,就使用了這種技術。還有很多其他在圖像空間進行後處理的特效,感興趣的同學可以閱讀Real-time Rendering中關於此技術的章節。

圖像模糊(Blur)

在前面講述輝光效果整體繪制流程的時候,講到了我們需要對原場景圖像進行模糊操作。那麼怎麼樣才能夠實現對圖像的模糊操作了?

在圖像處理中,我們經常使用的模糊是基於高斯分布函數的高斯模糊。這篇文章詳細的講述了圖像模糊的一些技術細節,大家可以據此了解如何進行高斯模糊。

高斯模糊在圖像處理的時候,是使用2維的高斯分布函數對一個像素的上下左右包圍的像素進行采樣來實現的。我們假設模糊的半徑為20個像素,按照高斯模糊的原始計算方式,每一個像素都需要計算(2 * 20) ^ 2次采樣。這在圖像處理上面可以接受,但是對於實時性的游戲來說,計算量就有點大了。

為此,人們想出了一種優化的方法來實現高斯模糊。對於一個像素,它的模糊半徑為20,那麼我們可以分兩次進行模糊,一次是橫向的模糊計算,一次是縱向的模糊計算。也就是將模糊操作從O(N^2)降低到了2 * O(N)。下圖展示了如何進行這樣的操作:

\

圖7

從圖中,我們可以看到,我們先對原場景圖進行一次橫向的模糊計算,此次計算我們只需要采用一維的高斯分布進行計算即可。然後對橫向模糊之後的圖像進行縱向模糊,與橫向模糊一樣,使用一維的高斯分布函數進行。這樣就能夠得到一張高斯模糊之後的圖像,它的模糊效果不比二維高斯模糊效果來的差。

OpenGL繪制流程

下面就以我的代碼中的繪制流程來向大家展示如何在OpenGL中繪制一個輝光效果:

void glb_display() {
    glb_draw_normal_scene();

    glb_draw_render_target_gauss_blur_h();

    glb_draw_render_target_gauss_blur_v();

    glb_draw_blend();

    glutSwapBuffers();
}

上面的代碼給出了我繪制的流程:

(1).繪制正常場景 -- 此處繪制的場景保存到一張紋理A中,並且復制A紋理到B紋理中

(2)繪制一個和屏幕一樣大小的矩形,將紋理A作為該矩形的貼圖,施加橫向模糊的Shader計算,最後的結果保存到另外一張貼圖C中

(3)繪制一個和屏幕一樣大小的矩形,將紋理C作為該矩形的貼圖,施加縱向模糊的Shader計算,最後的結果保存到紋理A中(A已經被復制到B中去,所以可以被覆蓋)

(4)經過前三步之後,A紋理保存了模糊之後的圖像,B紋理保存了原圖像,然後畫一個矩形,把這兩張貼圖都傳遞進入,進行Alpha混合計算。

Shader計算

對於(1)步驟,使用如下的兩個Shader:

light_pixel.vs
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Phong lights's vertex shader
//--------------------------------------------------------------------
#version 330

in vec3 vertex;
in vec3 normal;

uniform mat4 proj;
uniform mat4 view;
uniform mat4 world;
uniform mat4 trans_inv_world;

out vec3 vs_vertex;
out vec3 vs_normal;

void main() {
	gl_Position = proj * view * world * vec4(vertex, 1.0);
	vs_vertex = (world * vec4(vertex, 1.0)).xyz;
	vs_normal = (trans_inv_world * vec4(normal, 0.0)).xyz;
}

light_pixel.ps
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Phong lights's pixel shader
//-------------------------------------------------------------------
#version 330

in vec3 vs_vertex;
in vec3 vs_normal;

out vec4 color;

uniform vec3 parallel_light_dir;
uniform vec3 parallel_light_ambient;
uniform vec3 parallel_light_diffuse;
uniform vec3 parallel_light_specular;

uniform vec3 material_ambient;
uniform vec3 material_diffuse;
uniform vec3 material_specular;
uniform float material_specular_pow;

uniform vec3 eye_pos;

vec3 calc_diffuse(vec3 light_vec, vec3 normal, vec3 diffuse, vec3 light_color) {
	float ratio = dot(light_vec, normal);
	ratio = max(ratio, 0.0);
	return diffuse * light_color * ratio;
}

vec3 calc_specular(vec3 light_vec, vec3 normal, vec3 view_vec, vec3 specular, vec3 light_color, float pow_value) {
	vec3 ref_light_vec = reflect(light_vec, normal);
	float ratio = dot(ref_light_vec, view_vec);
	ratio = max(ratio, 0.0);
	ratio = pow(ratio, pow_value);

	return specular * light_color * ratio;
}

void main() {
	vec3 light_vec = -parallel_light_dir;
	vec3 normal = normalize(vs_normal);
	vec3 view_vec = normalize(eye_pos - vs_vertex);

	vec3 ambient = material_ambient * parallel_light_ambient;
	vec3 diffuse = calc_diffuse(light_vec, normal, material_diffuse, parallel_light_diffuse);
	vec3 specular = calc_specular(light_vec, normal, view_vec, material_specular, parallel_light_specular, material_specular_pow);

	color = vec4(ambient + diffuse + specular, 1.0);
}

這兩個Shader只是為了實現Phong式光照計算,給場景一點光亮,對於繪制輝光效果並不是必須的,但你總得有東西才能繪制輝光效果吧!

對於步驟(2)和步驟(3),使用如下的三個Shader:

gauss_blur.vs
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Gauss blur pass through vertex shader
//--------------------------------------------------------------------
#version 330

in vec3 vertex;
in vec2 texcoord;

out vec2 vs_texcoord;

void main() {
    gl_Position = vec4(vertex, 1.0);
    vs_texcoord = texcoord;
}

gauss_blurh.ps
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Gauss blur horizontal pass shader
//--------------------------------------------------------------------
#version 330

in vec2 vs_texcoord;
out vec4 color;

uniform sampler2D tex;
uniform float tex_width;

uniform float gauss_num[21];

void main() {
    color = texture2D(tex, vs_texcoord) * gauss_num[0];
    float step = 1.0 / tex_width;

    for (int i = 1; i < 21; i++) {
        if (vs_texcoord.x - i * step >= 0.0) {
            color += texture2D(tex, vec2(vs_texcoord.x - i * step, vs_texcoord.y)) * gauss_num[i];
        }

        if (vs_texcoord.x + i * step <= 1.0) {
            color += texture2D(tex, vec2(vs_texcoord.x + i * step, vs_texcoord.y)) * gauss_num[i];
        }
    }
}

gauss_blurv.ps
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Gauss blur vertical pass shader
//--------------------------------------------------------------------
#version 330

in vec2 vs_texcoord;
out vec4 color;

uniform sampler2D tex;
uniform float tex_height;

uniform float gauss_num[21];

void main() {
    color = texture2D(tex, vs_texcoord) * gauss_num[0];
    float step = 1.0 / tex_height;

    for (int i = 0; i <21; i++) {
        if (vs_texcoord.y - i * step >= 0.0) {
            color += texture2D(tex, vec2(vs_texcoord.x, vs_texcoord.y - i * step)) * gauss_num[i];
        }

        if (vs_texcoord.y + i * step <= 1.0) {
            color += texture2D(tex, vec2(vs_texcoord.x, vs_texcoord.y + i * step)) * gauss_num[i];
        }
    }
}

上面三個Shader組合完成了高斯模糊的計算。這裡有幾點需要注意。

(1)進行模糊計算的時候,邏輯上是以一個像素單位為步進值,但是由於紋理坐標是從[0,1],所以我們需要將一個像素的步進值計算為對應的紋理坐標步進。這個操作可以在Application階段計算完成,然後傳遞到Shader中,這裡博主偷懶了,直接在Shader裡面計算了。

(2)Shader中的gauss_num[21]保存了模糊半徑為20的高斯分布的各個數值。這個數值的計算是在Application階段完成,然後提交到Shader中去的,如下是計算Gauss_num的函數,使用的是一維的高斯分布函數完成:

float glb_gauss_num(int x) {
    float pi = 3.1415927f;
    float e = 2.71828f;
    float theta = 0.1f;
    float theta2 = theta * theta;
    float temp1 = 1.0f / (theta * sqrt(2 * pi));
    float temp2 = pow(e, -(x * x) / 2 * theta2);
    return temp1 * temp2;
}

void glb_calc_gauss_nums() {
    g_GaussNum[0] = 1.0f;

    for (int32_t i = 1; i < sizeof(g_GaussNum) / sizeof(g_GaussNum[0]); i++) {
        g_GaussNum[i] = glb_gauss_num(i);
    }

    float total = 0.0f;
    for (int32_t i = 0; i < sizeof(g_GaussNum) / sizeof(g_GaussNum[0]); i++) {
        total += g_GaussNum[i];
    }

    for (int32_t i = 0; i < sizeof(g_GaussNum) / sizeof(g_GaussNum[0]); i++) {
        g_GaussNum[i] = g_GaussNum[i] / total;
    }
}

對於步驟(4),使用了如下的兩個Shader:
blend.vs
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Blend pass through vertex shader
//--------------------------------------------------------------------
#version 330

in vec3 vertex;
in vec2 texcoord;

out vec2 vs_texcoord;

void main() {
    gl_Position = vec4(vertex, 1.0);
    vs_texcoord = texcoord;
}

blend.ps
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Alpha blend shader
//--------------------------------------------------------------------
#version 330

in vec2 vs_texcoord;
out vec4 color;

uniform sampler2D blur_tex;
uniform sampler2D scene_tex;

void main() {
	vec4 blur_color = texture2D(blur_tex, vs_texcoord);
	vec4 scene_color = texture2D(scene_tex, vs_texcoord);
	color = blur_color * 0.5 + scene_color * 0.5;
}

最後的混合計算,我選取了模糊貼圖的一半加上場景貼圖的一半來進行混合操作。最終的效果如下圖所示:

\

圖8

總結

上面文章為了簡化操作,做了很多在實際項目中不可以的操作。比如在Shader中計算紋理步進值。而在實際的情況下,實現輝光效果遠遠要比這裡展示的復雜的多,比如場景中有不發光的物體,和發光的物體,此時需要通過Alpha貼圖來標識出場景圖中哪些是需要進行模糊的,而哪些是不需要進行。又比如說,發光物體在某個不發光物體的後面,那麼進行模糊計算的時候,必然會讓發光物體滲透到不發光物體中,會產生比較粗糙的感覺

所以,本片文章僅僅是為了向大家講述輝光算法的基本原理,至於如何整合到你們自己的項目中去,需要大家自己來思考!希望本篇文章能夠幫助到你們!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved