OpenGL ES2.0 實(shí)時(shí)陰影

實(shí)時(shí)陰影在3D游戲中基本上已經(jīng)得到了廣泛的應(yīng)用。實(shí)時(shí)的陰影效果能給游戲場(chǎng)景的真實(shí)度加分不少铡原。

陰影和光照的關(guān)系基本上是相輔相成的纱新。對(duì)陰影的渲染主要用到幀緩沖對(duì)象(Framebuffer Object),及其相關(guān)的繪制到紋理的技術(shù)捆愁。本來(lái)打算先寫一篇OpenGL ES繪制到紋理的相關(guān)文章的割去,后來(lái)發(fā)現(xiàn)通用的繪制到紋理的技術(shù)和實(shí)時(shí)陰影有很多共通點(diǎn),因此就放在一篇文章里描述要來(lái)的方便一些昼丑。

首先講一下實(shí)時(shí)陰影算法的原理吧呻逆。主要利用到OpenGL ES在坐標(biāo)變換之后的坐標(biāo)點(diǎn)的深度值。通俗的來(lái)講菩帝,OpenGL ES在經(jīng)過(guò)世界坐標(biāo)變換之后咖城,會(huì)生成每個(gè)坐標(biāo)點(diǎn)轉(zhuǎn)換后的深度值,然后這些深度值經(jīng)過(guò)深度檢測(cè)(DEPTH_TEST)和坐標(biāo)歸一化(轉(zhuǎn)換為-1呼奢,1之間的坐標(biāo))宜雀,最終得到的點(diǎn)的Z值既是我們?cè)谄聊簧纤芸吹降狞c(diǎn)。

那么握础,思路就出來(lái)了——

1.變換觀察點(diǎn)

我們將觀察點(diǎn)放在光源處辐董,也就是從光源點(diǎn)作為觀察點(diǎn),那么最終歸一化之后禀综,將會(huì)得到一個(gè)Za值简烘,然后將這個(gè)深度值寫入已經(jīng)綁定紋理的幀緩沖(Framebuffer Object)他匪,渲染到紋理。

2.重置觀察點(diǎn)并比較深度值

從真正的觀察點(diǎn)進(jìn)行坐標(biāo)變換夸研,也會(huì)得到一個(gè)Zb值邦蜜,從剛才幀緩沖中綁定的紋理中,采樣獲得第1步計(jì)算出的深度值Z亥至。我們比較這兩個(gè)Z值悼沈,如果Zb > Za則表示該坐標(biāo)點(diǎn)不在陰影內(nèi)(正常光照顯示),否則該點(diǎn)在陰影內(nèi)(顯示環(huán)境光+(漫反射+鏡面反射)×陰影系數(shù))(注:這里攝像頭朝Z-方向)姐扮。

看下代碼的關(guān)鍵步驟吧絮供。

創(chuàng)建深度幀緩沖并綁定紋理
    // 創(chuàng)建并綁定一個(gè)紋理對(duì)象
    glGenTextures(1, &shadowmap_tex);
    glBindTexture(GL_TEXTURE_2D, shadowmap_tex);

    // 設(shè)置紋理對(duì)象的數(shù)據(jù),因?yàn)槭亲鳛殇秩灸繕?biāo)茶敏,因此這時(shí)沒(méi)有數(shù)據(jù)
    // 使用GL_DEPTH_COMPONENT16和GL_DEPTH_COMPONENT選項(xiàng)表明作為深度紋理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, g_glstate.swidth, g_glstate.sheight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);

    // 設(shè)置過(guò)濾選項(xiàng)壤靶,沒(méi)什么特別的
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    // 創(chuàng)建并綁定幀緩沖對(duì)象
    glGenFramebuffers(1, &shadowmap_fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, shadowmap_fbo);
    // 將創(chuàng)建的紋理對(duì)象綁定到幀緩沖區(qū)
    // GL_DEPTH_ATTACHMENT參數(shù)表明作為深度緩沖
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowmap_tex, 0);
設(shè)置觀察點(diǎn)為點(diǎn)光源坐標(biāo)
void QGLSL_SetUniformMat4v(const char *name, const char *uniform, const mat4 *in) {
    GLint loc = QGLSL_GetUniformLoc(shader, uniform);
    if (loc >= 0) {
        glUniformMatrix4fv(loc, 1, GL_FALSE, in->m);
    }
}
...
    QGLSL_SetUniform1v(shadow_shader, "isPassLight", 1);
    QGLSL_SetUniformMat4v(shadow_shader, "model", &model);
    QGLSL_SetUniformMat4v(shadow_shader, "view", &lit_view);
    QGLSL_SetUniformMat4v(shadow_shader, "projection", &lit_projection);
    QGLSL_SetUniformMat4v(shadow_shader, "light_view", &lit_view);
    QGLSL_SetUniformMat3v(shadow_shader, "norm_mat", &norm_mat);

上面函數(shù)就不一一介紹了,大致就是激活陰影shader程序惊搏,然后逐一設(shè)置常量贮乳。

綁定幀緩沖區(qū)繪制到紋理
    // 激活并清空幀緩沖
    glBindFramebuffer(GL_FRAMEBUFFER, shadowmap_fbo);
    glViewport(0, 0, g_glstate.swidth, g_glstate.sheight);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_DEPTH_BUFFER_BIT);

    // 設(shè)置偏移,減少陰影計(jì)算時(shí)的失真
    glEnable(GL_POLYGON_OFFSET_FILL);
    glPolygonOffset(2.5f, 20.0f);

    // 渲染場(chǎng)景
    QUnit_shadow_mapping_DrawScene();
綁定默認(rèn)緩沖區(qū)恬惯,重置觀察點(diǎn)并繪制到系統(tǒng)緩沖
    // 重置觀察點(diǎn)到相機(jī)位置
    QGLSL_SetUniform1v(shadow_shader, "isPassLight", 0);
    QGLSL_SetUniformMat3v(shadow_shader, "norm_mat", &norm_mat);
    QGLSL_SetUniformMat4v(shadow_shader, "model", &model);
    QGLSL_SetUniformMat4v(shadow_shader, "view", &camera_view);
    QGLSL_SetUniformMat4v(shadow_shader, "light_view", &lit_view);
    QGLSL_SetUniformMat4v(shadow_shader, "projection", &camera_projection);
    // 解除剛才綁定的緩沖區(qū)對(duì)象向拆,默認(rèn)繪制到系統(tǒng)離屏緩沖
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    // 關(guān)閉偏移
    // glCullFace(GL_BACK);
    glDisable(GL_POLYGON_OFFSET_FILL);

    // 使用shader
    QGLSL_Active(shadow_shader);

    // 設(shè)置紋理對(duì)象(做采樣對(duì)比Z值繪制陰影時(shí)使用)
    QGLSL_SetUniform1v(shadow_shader, "s_shadowmap", 0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, shadowmap_tex);

最終繪制結(jié)果如下:


OpenGL ES 實(shí)時(shí)陰影
Shader代碼以及重要的幾步工作
// vertex shader
precision highp float;

attribute vec3 vert_pos;
attribute vec3 vert_norm;

varying vec3 v_pos;
varying vec3 v_norm;
varying vec4 v_shadowcoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 light_view;
uniform mat3 norm_mat;

void main(void) {
    const mat4 bias = mat4(0.5, 0.0, 0.0, 0.0,
                          0.0, 0.5, 0.0, 0.0,
                          0.0, 0.0, 0.5, 0.0,
                          0.5, 0.5, 0.5, 1.0);
    v_norm = normalize(norm_mat * vert_norm);
    v_pos = vec3(view * model * vec4(vert_pos, 1.0));
    v_shadowcoord = bias * projection * light_view * model * vec4(vert_pos, 1.0);
    
    gl_Position = projection * view * model * vec4(vert_pos, 1.0);
}

在vertex shader source中很重要的一個(gè)東西就是這個(gè)bias矩陣,經(jīng)過(guò)MLP(區(qū)別于MVP酪耳,L指的是Light)變換后的xyz值是0-1之間的浓恳,需要將其歸一化為-1~1之間的值,通過(guò)矩陣運(yùn)算很容易實(shí)現(xiàn)碗暗。

// fragment shader source
precision highp float;

struct Light {
    vec3    pos;
    vec3    la;
    vec3    ld;
    vec3    ls;
};
uniform Light light;

struct Material {
    vec3    ka;
    vec3    kd;
    vec3    ks;
    float   shininess;
};
uniform Material material;

// uniform sampler2DShadow s_shadowmap;
uniform sampler2D s_shadowmap;

varying vec3 v_pos;
varying vec3 v_norm;
varying vec4 v_shadowcoord;

uniform int isPassLight;

void main(void) {
    if (1 == isPassLight) {
        return;
    }
    vec3 s = normalize(light.pos - v_pos);
    vec3 r = reflect(-s, v_norm);
    vec3 v = normalize(-v_pos.xyz);
    float sDotN = max(dot(s, v_norm), 0.0);
    vec3 ambient = light.la * material.ka;
    vec3 diffuse = light.ld * material.kd * sDotN;
    vec3 specular = vec3(0.0);
    if (sDotN > 0.0) {
        specular = light.ls * material.ks * pow(max(dot(r, v), 0.0), material.shininess);
    }
    // 這里用作矯正偏移的
    vec2 poissonDisk[4] = vec2[](
        vec2(-0.94201624, -0.39906216),
        vec2(0.94558609, -0.76890725),
        vec2(-0.0941848101, -0.92938870),
        vec2(0.34495938, 0.29387760)
    );
    vec4 shadow_coord = v_shadowcoord.xyzw / v_shadowcoord.w;
    float shadow = 1.0;
    for (int i = 0; i < 4; ++i) {
        // 采樣深度紋理颈将,然后比較z值
        vec4 tex_col = texture2D(s_shadowmap, shadow_coord.xy + poissonDisk[i]/700.0);
        if (tex_col.z < shadow_coord.z) {
            shadow -= 0.2;
        }
    }
    gl_FragColor = vec4(ambient + (diffuse + specular) * shadow, 1.0);
}

至此,全部完成言疗。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晴圾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子洲守,更是在濱河造成了極大的恐慌疑务,老刑警劉巖沾凄,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梗醇,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡撒蟀,警方通過(guò)查閱死者的電腦和手機(jī)叙谨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)保屯,“玉大人手负,你說(shuō)我怎么就攤上這事涤垫。” “怎么了竟终?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蝠猬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我统捶,道長(zhǎng)榆芦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任喘鸟,我火速辦了婚禮匆绣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘什黑。我一直安慰自己崎淳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布愕把。 她就那樣靜靜地躺著拣凹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恨豁。 梳的紋絲不亂的頭發(fā)上咐鹤,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音圣絮,去河邊找鬼祈惶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扮匠,可吹牛的內(nèi)容都是我干的捧请。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼棒搜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼疹蛉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起力麸,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤可款,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后克蚂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闺鲸,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年埃叭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了摸恍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖立镶,靈堂內(nèi)的尸體忽然破棺而出壁袄,到底是詐尸還是另有隱情,我是刑警寧澤媚媒,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布嗜逻,位于F島的核電站,受9級(jí)特大地震影響缭召,放射性物質(zhì)發(fā)生泄漏变泄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一恼琼、第九天 我趴在偏房一處隱蔽的房頂上張望妨蛹。 院中可真熱鬧,春花似錦晴竞、人聲如沸蛙卤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)颤难。三九已至,卻和暖如春已维,著一層夾襖步出監(jiān)牢的瞬間行嗤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工垛耳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留栅屏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓堂鲜,卻偏偏與公主長(zhǎng)得像栈雳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缔莲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容