1. 程式人生 > >OpenGL陰影,Shadow Volumes(附源程式,使用 VCGlib )

OpenGL陰影,Shadow Volumes(附源程式,使用 VCGlib )

轉載自:http://www.cnblogs.com/liangliangh/p/4165228.html

實驗平臺:Win7,VS2010

先上結果截圖:

OpenGL陰影 Shadow Volumes 
  

本文是我前一篇部落格:OpenGL陰影,Shadow Mapping(附源程式)的下篇,描述兩個最常用的陰影技術中的第二個,Shadow Volumes 方法。將從基本原理出發,首先講解 Zpass 方法,然後是 Zfail 方法(比較實際的方法),最後對 Shadow Mapping 和 Shadow Volumes 方法做簡要分析對比。

Shadow Volumes 需要網格的連線資訊,本文使用 構造拓撲資訊及讀寫網格檔案,為了清晰,將 VCGlib 使用的簡單總結作為附錄,附於文章的最後。

1. 數學原理

關於陰影的定義,請見我的前一篇部落格(文獻[1])。Shadow Mapping 將空間各個方向上離光源最近點的距離編碼成深度紋理。Shadow Volumes 採用一種不同的方法,它直接構造光源被物體(投射陰影的物體,Shadow caster)遮擋的空間的邊界,即落在這個邊界內的任何點都處於陰影中,反之被光源照亮,如下圖所示(使用Blender製作,另見文獻[3]PPT第10頁):

 

遮擋空間邊界所包圍的空間即為 Shadow Volume (陰影體積),構造 Shadow Volume 並不困難,對上圖中的三角形(設頂點為 A,B,C)只需要從光源點到三角形頂點做連線並延伸出去到足夠遠(設 A,B,C 延伸到點 D,E,F),並用這些多邊形構成封閉體積:面ABC、面ADEB、面BEFC、面CFDA、面EDF,共5個面,注意頂點字母的順序已經考慮了頂點環繞方向向外(右手法則)。

那如何判斷一個點是否位於 Shadow Volume 內部呢? Shadow Volumes 採用一種間接方法:從一個位於所有 Shadow Volume 外的點出發作射線,從 0 開始計數,每穿入一個 Shadow Volume +1,每穿出一個 Shadow Volume -1,這樣到達點 P 時,如果計數為 0 說明位於陰影體積外,大於 0 說明在一層或多層 Shadow Volume 內部。原理是,每個 Shadow Volume 都是封閉的,如果點 P 位於所有 Shadow Volume 外,則穿入和穿出必成對出現,有一種極端情況:射線與一個 Shadow Volume 相切於稜邊上,這時射線與 Shadow Volume 表面只有 1 個交點而不是通常的 2 個交點(Shadow Volume 為凸時),好在,這裡說的幾何原理的實際實現使用光柵化進行離散化,在離散化空間中,這種極端情況並不存在(這和光柵化特性有關,如 "watertight" rasterization 見文獻[3])。這個原理如下圖所示(摘自文獻[3]PPT第18頁,二維示意):

這個計數的起點其實就是攝像機所在點,計數的任務可以由圖形硬體的 Stencil Buffer (模板緩衝)機制提供,可以看到,這裡要求攝像機位於陰影之外。

2. Zpass 方法

直接實現第1節的數學原理的方法即為 Zpass 方法。實現 Zpass 需要完成兩方面工作:構造 Shadow Volume 、利用 Stencil Buffer 的功能實現計數。我們先來看最簡單的情況,場景中只有兩個三角形和一個地板,如下圖(看到陰影對判斷空間位置的重要性):

 

場景程式碼如下:

複製程式碼
// 世界,四邊形地板
void draw_world()
{
    glStaff::xyz_frame(2, 2, 2, false);
    glBegin(GL_POLYGON);
        glNormal3f(0, 1, 0);
        glVertex3f(-5, 0,-5); glVertex3f(-5, 0, 5);
        glVertex3f(5, 0, 5); glVertex3f(5, 0,-5);
    glEnd();
}

glm::vec3 tri1[3] = { glm::vec3(0, 3, 0), glm::vec3( 0, 3, 2), glm::vec3(2, 3, 0) };
glm::vec3 tri2[3] = { glm::vec3(1, 2,-1), glm::vec3(-1, 2,-1), glm::vec3(1, 2, 1) };
// 模型,兩個三角形
void draw_model()
{
    GLfloat _ca[4], _cd[4];
    glGetMaterialfv(GL_FRONT, GL_AMBIENT, _ca);
    glGetMaterialfv(GL_FRONT, GL_DIFFUSE, _cd);
    GLfloat c[4];
    glBegin(GL_TRIANGLES);
        c[0]=1; c[1]=0; c[2]=0; c[3]=1; glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c);
        glNormal3fv(&glm::normalize(glm::cross(tri1[1]-tri1[0], tri1[2]-tri1[0]))[0]);
        for(int i=0; i<3; ++i) glVertex3fv(&tri1[i][0]); // tri1,紅色
        c[0]=0; c[1]=1; c[2]=0; c[3]=1; glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c);
        glNormal3fv(&glm::normalize(glm::cross(tri2[1]-tri2[0], tri2[2]-tri2[0]))[0]);
        for(int i=0; i<3; ++i) glVertex3fv(&tri2[i][0]); // tri2,綠色
    glEnd();
    glMaterialfv(GL_FRONT, GL_AMBIENT, _ca);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, _cd);
}
複製程式碼

構造 Shadow Volume 程式碼如下(light_pos 為光源位置,位置式光源): 

複製程式碼
static float d_far = 10;
// 構造、繪製 Shadow Volume,僅考慮位置光源
void draw_model_volumes()
{for(int t=0; t<2; ++t){
        glm::vec3* tri = t==0 ? tri1 : tri2; // tri1 or tri2
        glm::vec3 tri_far[3];
        for(int i=0; i<3; ++i){
            tri_far[i] = tri[i] + glm::normalize(tri[i]-glm::vec3(light_pos))*d_far;
        }
        for(int i=0; i<3; ++i){
            glBegin(GL_POLYGON); // 三個邊擠出(extrude)的四邊形
                glVertex3fv(&tri[i][0]);
                glVertex3fv(&tri_far[i][0]);
                glVertex3fv(&tri_far[(i+1)%3][0]);
                glVertex3fv(&tri[(i+1)%3][0]);
            glEnd();
        }
        glBegin(GL_TRIANGLES); // 頂部(near cap),原三角形,對 Zpass 來說可選
            for(int i=0; i<3; ++i) glVertex3fv(&tri[i][0]);
        glEnd();
        glBegin(GL_TRIANGLES); // 底部(far cap),擠出三角形,對 Zpass 來說可選
            for(int i=0; i<3; ++i) glVertex3fv(&tri_far[2-i][0]);
        glEnd();
    }
}
複製程式碼

構造的 Shadow Volume 如下圖所示:

 

Stencil Buffer 實現計數程式碼:

複製程式碼
// ------------------------------------------ 清除緩衝區,包括模板緩衝
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

// ------------------------------------------ 第1遍,渲染環境光,深度值
// 關閉光源,開啟環境光
GLboolean _li0 = glIsEnabled(GL_LIGHT0); if(_li0) glDisable(GL_LIGHT0);
glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&mat_view[0][0]);
    draw_world();
glMultMatrixf(&mat_model[0][0]);
    draw_model();
if(_li0) glEnable(GL_LIGHT0);

// ------------------------------------------ 第2遍,渲染模板值
// 不需要光照,不更新顏色和深度緩衝
GLboolean _li = glIsEnabled(GL_LIGHTING); if(_li) glDisable(GL_LIGHTING);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE); glStencilMask(~0);
glEnable(GL_CULL_FACE);
glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 0, ~0);
// 剔除背面留下正面,穿入,模板值 加 1
glCullFace(GL_BACK); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glMatrixMode(GL_MODELVIEW);glLoadMatrixf(&mat_view[0][0]);glMultMatrixf(&mat_model[0][0]);
    draw_model_volumes();
// 剔除正面留下背面,穿出,模板值 減 1
glCullFace(GL_FRONT); glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
glMatrixMode(GL_MODELVIEW);glLoadMatrixf(&mat_view[0][0]);glMultMatrixf(&mat_model[0][0]);
    draw_model_volumes();
// 恢復狀態
if(_li) glEnable(GL_LIGHTING);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE); glStencilMask(~0);
glDisable(GL_CULL_FACE); glDisable(GL_STENCIL_TEST); glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);

// ------------------------------------------ 第3遍,渲染光源光照,依據模板值判斷陰影
// 關閉環境光,開啟光源
GLfloat _lia[4]; glGetFloatv(GL_LIGHT_MODEL_AMBIENT, _lia);
GLfloat ca[4]={0}; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ca);
// 模板測試為,等於0通過, 深度測試為,相等通過,顏色混合為直接累加
glEnable(GL_STENCIL_TEST); glStencilFunc(GL_EQUAL, 0, ~0);
glDepthFunc(GL_EQUAL); glBlendFunc(GL_ONE, GL_ONE);

glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&mat_view[0][0]);
glLightfv(GL_LIGHT0, GL_POSITION, &light_pos[0]); // 位置式光源
    draw_world();
glMultMatrixf(&mat_model[0][0]);
    draw_model();
// 恢復狀態
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, _lia);
glDisable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 0, ~0);
glDepthFunc(GL_LESS); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

// 在光源處繪製一個黃色的球
glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&mat_view[0][0]);
    dlight(0.05f);
複製程式碼

這裡要用到 Stencil Buffer,要在建立視窗時(即建立 OpenGL Context)啟用 Stencil Buffer,GLFW 預設就啟用了(8-bit)。第1遍渲染時,僅開啟環境光,渲染場景後,顏色緩衝是環境光貢獻,深度緩衝是離攝像機最近的片斷的深度。第2遍渲染,只更新 Stencil Buffer,因為深度緩衝已經儲存了最近片斷深度,深度測試 GL_LESS 通過的片斷都是未經遮擋的 Shadow Volume 部分,如果看到了正面,模板值+1,背面-1,注意正背面是依據頂點環繞方向確定的(光柵化的任務),因為是深度測試通過後計數故稱作 Zpass 。第3遍渲染,因為模板值為0的點為光照,否則為陰影,設定模板測試為和0比較相等時通過,並設定混合函式為直接累加(和 Shadow Mapping 類似)。

模板緩衝區的值(全黑為模板值為0,每個顏色梯度模板值變化1),以及最終渲染結果如下圖所示:

 

讀取模板緩衝區使用 glReadPixels(ox,oy, width,height, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, data),上面所有程式碼見所附程式中的 volumes_basic0.cpp。

在講輪廓邊之前,先看下上面程式碼幾個需要改進的地方:

  1. 在渲染模板值時,不需要渲染兩遍(一遍正面,一遍背面),OpenGL 支援在一遍渲染中對正背面使用不同的模板更新操作,使用 glStencilOpSeparate() 函式;
  2. 為防止模板緩衝區溢位或減小為負數(預設模板緩衝為8-bit),可以使用繞回模式(wrap,255加1變成0,0減1變成255);
  3. 可以利用齊次座標特性將 Shadow Volume 延伸到無窮遠,對於 Zpass 來說,不需要對 Shadow Volume 進行封口(cap),底部不需要封口因為 Zpass 只關心未被遮擋(Zpass)部分,頂部不需要封口因為它正好被原三角形遮擋(不能通過 Z 測試)。
  4. 上面程式碼沒有考慮光源為平行光源的情況(光源位置座標w分量為0),也沒有考慮三角形背對光源的情況,背對時 Shadow Volume 的頂點環繞方向將向內部(如果所有 Shadow Volume 都向內部也沒關係,問題是向內向外不一致將導致計數錯誤),是面對還是背對光源可以用光源到三角形上任意一點的連線向量和三角形法向量的內積的正負號判斷;
  5. 上面程式碼未考慮模型變換矩陣的變換(滑鼠左鍵拖動物體,陰影將不再正確),因為模型變換同樣施加到 Shadow Volume 上,只需對光源位置進行反變換。

上面程式碼的 “第2遍,渲染模板值” 的繪製部分等價程式碼如下:

複製程式碼
// 不需要光照,不更新顏色和深度緩衝
// ...
// 正面加1,背面減1
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); // 改進後
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&mat_view[0][0]); glMultMatrixf(&mat_model[0][0]);
    draw_model_volumes(glm::affineInverse(mat_model)*light_pos);
// 恢復狀態
// ...
複製程式碼

將三角形邊擠出到無窮遠的程式碼如下(考慮三角形是否背對光源):

複製程式碼
// 構造、繪製 Shadow Volume,擠出(extrude)到無窮遠
void draw_model_volumes(glm::vec4& lpos)
{
    for(int t=0; t<2; ++t){
        glm::vec3* tri = t==0 ? tri1 : tri2; // tri1 or tri2
        glm::vec4 tri_far[3];
        for(int i=0; i<3; ++i){
            tri_far[i] = glm::vec4(
                tri[i].x*lpos.w-lpos.x, tri[i].y*lpos.w-lpos.y, tri[i].z*lpos.w-lpos.z, 0);
        }
        glm::vec3 n = glm::cross(tri[1]-tri[0], tri[2]-tri[0]);
        glm::vec3 l0 = lpos.w==0 ? glm::vec3(lpos) : glm::vec3(lpos)/lpos.w-tri[0];
        int m = glm::dot(n,l0)>=0 ? 1 : -1; // 是否反轉四邊形環繞方向
        for(int i=0; i<3; ++i){
            glBegin(GL_POLYGON); // 三個邊擠出(extrude)的四邊形
                glVertex3fv(&tri[i][0]);
                glVertex4fv(&tri_far[i][0]);
                glVertex4fv(&tri_far[(i+m+3)%3][0]);
                glVertex3fv(&tri[(i+m+3)%3][0]);
            glEnd();
        }
    }
}
複製程式碼

位置光源和平行光源的對比如下:

 

這部分程式碼見所附程式中的 volumes_basic1.cpp。

到目前為止,我們的場景過於簡單,現在考慮複雜的網格,這裡僅考慮質量好的三角網格(封閉,任意點為二維流形,manifold,即每個邊接兩個面,面之間無交叉)。我們使用 VCGlib,關於用 VCGlib 讀寫網格檔案、構造頂點邊面連線資訊、法向量計算、平滑等處理請見本文最後的附錄。最簡單的將上述方法擴充套件到複雜網格的方法是:對每個三角形都構造 Shadow Volume ,對一個 mesh 的每個三角形構造 Shadow Volume 的程式碼如下(讀入的 PLY 網格檔案已經預先用 Blender 和 MeshLab 處理為 manifold 三角網格,關於 VCGlib 的使用見最後的附錄):

複製程式碼
// 構造、繪製 Shadow Volume
void draw_model_volumes(GLMesh& mesh, glm::vec4& lpos)
{
    assert(mesh.FN()==mesh.face.size()); // vcg::tri::Allocator<>::CompactFace/Edge/VertexVector()
    for(int i=0; i<mesh.FN(); ++i){ // for each face (i.e. triangle)
        GLMesh::FaceType& f = mesh.face[i];
        glm::vec4 tri_far[3]; // 擠出的3個點,到無窮遠
        for(int i=0; i<3; ++i){
            tri_far[i] = glm::vec4(
                f.V(i)->P().X()*lpos.w-lpos.x,
                f.V(i)->P().Y()*lpos.w-lpos.y,
                f.V(i)->P().Z()*lpos.w-lpos.z, 0 );
        }
        glm::vec3 n( vcg_to_glm(f.N()) );
        glm::vec3 l0 = lpos.w==0 ? glm::vec3(lpos) :
            glm::vec3(lpos)/lpos.w - vcg_to_glm(f.V(0)->P());
        int m = glm::dot(n,l0)>=0 ? 1 : -1; // 是否反轉四邊形環繞方向
        for(int i=0; i<3; ++i){
            glBegin(GL_POLYGON); // 三個邊擠出(extrude)的四邊形
                glVertex3fv(&f.V(i)->P()[0]);
                glVertex4fv(&tri_far[i][0]);
                glVertex4fv(&tri_far[(i+m+3)%3][0]);
                glVertex3fv(&f.V((i+m+3)%3)->P()[0]);
            glEnd();
        }
    }
}
複製程式碼

程式結果如下:左上為最終結果;右上為對應 Stencil 值(顏色梯度表示變化 1,可以想見 Stencil 的更新非常頻繁,但因為都是+1和-1操作,所以累積值並不一定很大);下面是 Shadow Volume 的顯示,可以看到,因為每個三角形都構造 Shadow Volume,Shadow Volume 的線條非常密。渲染時間約 180ms:

 
 

並不需要對所有邊都進行擠出(extrude),只需要對某些被稱作 “輪廓邊” 的邊(準確的說是 “可能輪廓邊”)進行擠出就可以構造出合格的 Shadow Volume,“可能輪廓邊” 是指其所連線的兩個面(對 manifold 網格每個邊必連線兩個面)一個面對光源另一個背對光源。面對還是背對光源可以用三角形面法向量和光源到三角形上任一點連線向量的內積的正負號判斷,優化後的,只對 “可能輪廓邊” 進行擠出的程式碼如下,注意和上面不同,此時對邊進行遍歷,而不再是三角形,注意要保證四邊形環繞方向為向外:

複製程式碼
// 構造、繪製 Shadow Volume
void draw_model_volumes(GLMesh& mesh, glm::vec4& lpos)
{
    assert(mesh.EN()==mesh.edge.size());
    for(int i=0; i<mesh.EN(); ++i){
        GLMesh::EdgeType& e = mesh.edge[i];
        GLMesh::FaceType* fa = e.EFp(); // fa,fb 為邊 e 鄰接的兩個面
        GLMesh::FaceType* fb = fa->FFp(e.EFi());
        glm::vec3 l0 = lpos.w==0 ? glm::vec3(lpos) :
            glm::vec3(lpos)/lpos.w-vcg_to_glm(e.V(0)->P());
        int sa = glm::dot(l0, vcg_to_glm(fa->N()))>=0 ? 1 : -1; // 面對還是背對光源
        int sb = glm::dot(l0, vcg_to_glm(fb->N()))>=0 ? 1 : -1;
        if( sa*sb < 0 ){ // 一個面面對,一個面背對光源,“可能輪廓邊”
            GLMesh::VertexType* va = fa->V(e.EFi());
            GLMesh::VertexType* vb = fa->V((e.EFi()+1)%3);
            if(sa<0) std::swap(va, vb); // 確定頂點順序,是最終四邊形環繞方向向外
            glm::vec4 e_far[2]; // 擠出的2個點,到無窮遠
            e_far[0] = glm::vec4(
                va->P().X()*lpos.w-lpos.x,
                va->P().Y()*lpos.w-lpos.y,
                va->P().Z()*lpos.w-lpos.z, 0 );
            e_far[1] = glm::vec4(
                vb->P().X()*lpos.w-lpos.x,
                vb->P().Y()*lpos.w-lpos.y,
                vb->P().Z()*lpos.w-lpos.z, 0 );
            glBegin(GL_POLYGON); // 邊擠出(extrude)的四邊形
                glVertex3fv(&va->P()[0]);
                glVertex4fv(&e_far[0][0]);
                glVertex4fv(&e_far[1][0]);
                glVertex3fv(&vb->P()[0]);
            glEnd();
        }
    }
}
複製程式碼

再看結果,對比上面的圖,現在 Shadow Volume 的邊稀疏多了,且渲染時間減少到了 45ms:

 
 

Zpass 方法的第一個問題是:當攝像機位於陰影中時,光照處 Stencil 值將不再為0,見下面的例子:

 
 
 

這個問題可以通過檢測攝像機是否位於陰影中,並在攝像機位於陰影中時對 Stencil 值進行偏移進行解決,但這需要額外開銷,後面用 Zfail 方法避免這一問題。和想象中的不同,攝像機並不是 “要麼在陰影中,要麼在陰影外” ,它有可能 “一半位於陰影中,一半位於陰影外”,這其實是近裁剪面的作用:

 

近裁剪面問題是 Zpass 方法的第二個問題,詳見文獻[3]。這小節程式碼見所附程式中的 volumes_zpass.cpp。

相關推薦

OpenGL陰影Shadow Volumes源程式使用 VCGlib

轉載自:http://www.cnblogs.com/liangliangh/p/4165228.html 實驗平臺:Win7,VS2010 先上結果截圖:     本文是我前一篇部落格:OpenGL陰影,Shadow Mapping(附源程式)的下篇,描

OpenGL陰影Shadow Mapping源程式

OpenGL陰影,Shadow Mapping(附源程式)   實驗平臺:Win7,VS2010   先上結果截圖(文章最後下載程式,解壓後直接執行BIN資料夾下的EXE程式):   本文描述圖形學的兩個最常用的陰影技術之一,Shad

Android 音視頻深入 十六 FFmpeg 推流手機攝像頭實現直播 源碼下載

音視頻 FFmpeg RTMP 直播 Android 源碼地址https://github.com/979451341/RtmpCamera/tree/master 配置RMTP服務器,雖然之前說了,這裏就直接粘貼過來吧 1.配置RTMP服務器 這個我不多說貼兩個博客分別是在mac和win

Android 音視頻深入 十八 FFmpeg播放視頻有聲音源碼下載

音視頻 Android FFmpeg 項目地址https://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpegv%E6%92%AD%E6%94%BE%E8%A7%86%E9%A2%91%E6%9C%89%E5%A3%B0%

Android 音視頻深入 十三 OpenSL ES 制作音樂播放器能暫停和調整音量源碼下載

音視頻 OpenSL ES 項目地址https://github.com/979451341/OpenSLAudio OpenSL ES 是基於NDK也就是c語言的底層開發音頻的公開API,通過使用它能夠做到標準化, 高性能,低響應時間的音頻功能實現方法。 這次是使用OpenSL ES來做一個音樂播

Android 音視頻深入 十四 FFmpeg與OpenSL ES 播放mp3音樂能暫停源碼

FFmpeg OpenSL ES 項目地址https://github.com/979451341/FFmpegOpenslES 這次說的是FFmpeg解碼mp3,數據給OpenSL ES播放,並且能夠暫停。1.創建引擎 slCreateEngine(&engineObject,0,NULL,

如何自學人工智能路徑規劃資源百分百親身經驗

而且 文章 綜述 獨立性 -i 關於 ade sdn 其中 下面的每個資源都是我親身學過的,且是網上公開公認最優質的資源。 下面的每個學習步驟也是我一步步走過來的。希望大家以我為參考,少走彎路。 請大家不要浪費時間找非常多的資料,只看最精華的! 綜述,機器學習的自學簡

Ubuntu上的C/C++編譯基於cmake例項連結

1. apt-get安裝cmake,版本應該到3.5以上 2. 建立工程資料夾,命名為專案名稱,ProjectName 3. 分別在ProjectName下建立src、bin、build三個資料夾,存放原始檔、執行程式、編譯檔案 4. ProjectName下建立頂層C

【PythonWindows】numpy matploylib 及相關庫的安裝教程資源賊良心好嘛!CSDN上各種庫下載都要氪金的!客官滿意的話關注一下點個贊

我自己的經驗,搞這個只有兩步:一、下載安裝包;二、安裝安裝包。 各軟體及庫的下載連結附在後面,自取不謝。 除了python是.exe檔案,直接很簡單的安裝軟體一般流程之外,其他的都需要一點點操作。 安裝安裝包(.whl)檔案過程: 開啟Cmd的命令視窗(Win+

Visual Studio 2013正式版下載直接連結彙總本人親測好使

微軟已經向MSDN訂閱使用者提供了Visual Studio 2013正式版映象下載,不過非MSDN使用者可以在微軟的Visual Studio 2013官方網站上下載到正式版映象(通過下載專業版本,已驗證與MSDN版本一致)。下面為大家整理一下個版本下載地址。 Micro

教你分分鐘爬取百度貼吧新手可操作原始碼及解析

不要以為這個教程很難,其實非常容易上手。並且講解非常詳細。 原理:通過檢視原始碼扣出關鍵資料,然後將其儲存到本地txt檔案下。(一通百通 ,原理大多一樣。) 【新建一個BugBaidu.py檔案,然後將程式碼複製到裡面後,雙擊執行。將貼吧中樓主釋出的內容打包txt儲存到本地。】 學

看完48秒動畫讓你不敢再登入HTTP網站完整示例程式碼

在我的 單點登入SSO示例程式碼 一文中,強烈不建議部署HTTP的SSO服務站點。 在此寫個基於網路包嗅探的HTTP會話劫持程式,給大家一個直觀的危害性展示。 示例中,我在一臺Mac上登入58同城,被另一臺Windows上的程式劫持。“黑客”檢視我的資訊暢行無阻,還順手改了我的頭像。 先直接上演示動

openGL三維網格座標旋轉縮放燈光設定紋理讀取模型讀取MFC單文件

1.三維網格座標建立 2.基本3維圖形建立 3.滑鼠相應旋轉縮放 4.鍵盤相應旋轉縮放 5.燈光設定 6.紋理載入對映 7.讀取模型 關於MFC配置編寫openGL網上有很多教程 需要的函式建立一般是: OnCreat()

editplusnotepad++ultraEdit使用技巧實用快捷鍵多年總結待完善

Editplus 1.模版配置。模版就是當你快捷鍵Shift+Ctrl+N新建一個html(或jsp,js等)時自動生成你需要的元素JQuery.js、script標籤、utf-8設定等等。 設定方法:Tools-Preferences-(左側框中)-Files-Templ

40張技術圖譜架構師階梯 高清下載

本文所有圖譜、思維導圖90%都是高清無碼,包含K8S、容器相關的4張,架構型別的5張,也含有運維

01揹包的理解二維陣列化一維陣列的理解hdu2602 Bone Collector

01揹包問題: 有n個物品和一個容量為v的揹包,用val[i]表示第i個物品的價值,用vol[i]表示第i個物品的體積,那麼,如何使揹包裡裝的物品的總價值最大呢? 貪心是不行的,舉個反例: n=3, v=100 val[i] vol[i]

如何自學人工智慧路徑規劃資源百分百親身經驗

下面的每個資源都是我親身學過的,且是網上公開公認最優質的資源。 下面的每個學習步驟也是我一步步走過來的。希望大家以我為參考,少走彎路。 請大家不要浪費時間找非常多的資料,只看最精華的! 綜述,機器學習的自學簡單來說分為三個步驟 前期:知識儲備包括數學知識,機器學習經典

程式猿在面試中怎麼回答SpringIOCAOP教程和書籍

你對spring的理解是什麼? 答: spring: 開源框架 IoC(控制反轉),將類的建立和依賴關係寫在配置檔案裡,由配置檔案注入,實現了鬆耦合 AOP 將安全,事務等於程式邏輯相對獨立的功能抽取出來,利用spring的配置檔案將這些功能插進去,實現了

Faster RCNN演算法解析原始碼可以直接執行

一、前言知識 1、基於Region Proposal(候選區域)的深度學習目標檢測演算法 Region Proposal(候選區域),就是預先找出圖中目標可能出現的位置,通過利用影象中的紋理、邊緣、顏色等資訊,保證在選取較少視窗(幾千個甚至幾百個)的情況下保

Hibernate入門原始碼jar包

1. 安裝eclipse hibernate 外掛 http://download.jboss.org/jbosstools/updates/stable/ 選擇   JBoss Application Development    的     Hibernate T