溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

使用OpenGL ES怎么實現(xiàn)光照效果

發(fā)布時間:2021-05-31 17:17:11 來源:億速云 閱讀:145 作者:Leah 欄目:移動開發(fā)

使用OpenGL ES怎么實現(xiàn)光照效果?相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

一個立方體

立方體類Rectangle.java

public class Rectangle {
  private FloatBuffer mVertexBuffer;
  private int mProgram;
  private int mPositionHandle;
  private int muMVPMatrixHandle;
  private int mColorHandle;

  public Rectangle(float r) {
    initVetexData(r);
  }
  public void initVetexData(float i) {
    float vertices[] = new float[] {
        // 頂點   顏色
        //前面
         0, 0, 1, 1,1,1,0,
         1, 1, 1, 1,0,0,0,
        -1, 1, 1, 1,0,0,0,
         0, 0, 1, 1,1,1,0,
        -1, 1, 1, 1,0,0,0,
        -1,-1, 1, 1,0,0,0,
         0, 0, 1, 1,1,1,0,
        -1,-1, 1, 1,0,0,0,
         1,-1, 1, 1,0,0,0,
         0, 0, 1, 1,1,1,0,
         1,-1, 1, 1,0,0,0,
         1, 1, 1, 1,0,0,0,
         //后面
         0, 0,-1, 1,1,1,0,
         1, 1,-1, 1,0,0,0,
         1,-1,-1, 1,0,0,0,
         0, 0,-1, 1,1,1,0,
         1,-1,-1, 1,0,0,0,
        -1,-1,-1, 1,0,0,0,
         0, 0,-1, 1,1,1,0,
        -1,-1,-1, 1,0,0,0,
        -1, 1,-1, 1,0,0,0,
         0, 0,-1, 1,1,1,0,
        -1, 1,-1, 1,0,0,0,
         1, 1,-1, 1,0,0,0,
        //左面
        -1, 0, 0, 1,1,1,0,
        -1, 1, 1, 1,0,0,0,
        -1, 1,-1, 1,0,0,0,
        -1, 0, 0, 1,1,1,0,
        -1, 1,-1, 1,0,0,0,
        -1,-1,-1, 1,0,0,0,
        -1, 0, 0, 1,1,1,0,
        -1,-1,-1, 1,0,0,0,
        -1,-1, 1, 1,0,0,0,
        -1, 0, 0, 1,1,1,0,
        -1,-1, 1, 1,0,0,0,
        -1, 1, 1, 1,0,0,0,
        //右面
         1, 0, 0, 1,1,1,0,
         1, 1, 1, 1,0,0,0,
         1,-1, 1, 1,0,0,0,
         1, 0, 0, 1,1,1,0,
         1,-1, 1, 1,0,0,0,
         1,-1,-1, 1,0,0,0,
         1, 0, 0, 1,1,1,0,
         1,-1,-1, 1,0,0,0,
         1, 1,-1, 1,0,0,0,
         1, 0, 0, 1,1,1,0,
         1, 1,-1, 1,0,0,0,
         1, 1, 1, 1,0,0,0,
         //上面
         0, 1, 0, 1,1,1,0,
         1, 1, 1, 1,0,0,0,
         1, 1,-1, 1,0,0,0,
         0, 1, 0, 1,1,1,0,   
         1, 1,-1, 1,0,0,0,
        -1, 1,-1, 1,0,0,0,
         0, 1, 0, 1,1,1,0,
        -1, 1,-1, 1,0,0,0,
        -1, 1, 1, 1,0,0,0,
         0, 1, 0, 1,1,1,0,
        -1, 1, 1, 1,0,0,0,
         1, 1, 1, 1,0,0,0,
        //下面
         0,-1, 0, 1,1,1,0,
         1,-1, 1, 1,0,0,0,
        -1,-1, 1, 1,0,0,0,
         0,-1, 0, 1,1,1,0,
        -1,-1, 1, 1,0,0,0,
        -1,-1,-1, 1,0,0,0,
         0,-1, 0, 1,1,1,0,
        -1,-1,-1, 1,0,0,0,
         1,-1,-1, 1,0,0,0,
         0,-1, 0, 1,1,1,0,
         1,-1,-1, 1,0,0,0,
         1,-1, 1, 1,0,0,0,
      };

    ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
    vbb.order(ByteOrder.nativeOrder());
    mVertexBuffer = vbb.asFloatBuffer();
    mVertexBuffer.put(vertices);
    mVertexBuffer.position(0);

    int vertexShader = loaderShader(GLES20.GL_VERTEX_SHADER,
        vertexShaderCode);
    int fragmentShader = loaderShader(GLES20.GL_FRAGMENT_SHADER,
        fragmentShaderCode);

    mProgram = GLES20.glCreateProgram();
    GLES20.glAttachShader(mProgram, vertexShader);
    GLES20.glAttachShader(mProgram, fragmentShader);
    GLES20.glLinkProgram(mProgram);

    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
    mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
    muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
  }

  public void draw(float[] mvpMatrix) {
    GLES20.glUseProgram(mProgram);
    // 將頂點數(shù)據(jù)傳遞到管線,頂點著色器
    // 定位到位置0,讀取頂點
    mVertexBuffer.position(0);
    // stride 跨距,讀取下一個值跳過的字節(jié)數(shù)
    GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, (4+3) * 4, mVertexBuffer);
    // 將頂點顏色傳遞到管線,頂點著色器
    // 定位到位置3,讀取顏色
    mVertexBuffer.position(3);
    GLES20.glVertexAttribPointer(mColorHandle, 4, GLES20.GL_FLOAT, false, (4+3) * 4, mVertexBuffer);
    GLES20.glEnableVertexAttribArray(mPositionHandle);
    GLES20.glEnableVertexAttribArray(mColorHandle);
    GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mvpMatrix, 0);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 12*6);
  }

  private int loaderShader(int type, String shaderCode) {
    int shader = GLES20.glCreateShader(type);
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);
    return shader;
  }

  private String vertexShaderCode = "uniform mat4 uMVPMatrix;"
      + "attribute vec4 aColor;"
      + "varying vec4 aaColor;"
      + "attribute vec3 aPosition;"
      + "void main(){"
      + "gl_Position = uMVPMatrix * vec4(aPosition,1);"
      + "aaColor = aColor;"
      + "}";

  private String fragmentShaderCode = "precision mediump float;"
      + "varying vec4 aaColor;"
      + "void main(){"
      + "gl_FragColor = aaColor;"
      + "}";
}

initVetexData類和前面的例子中基本一樣,但這里和前面有一些不一樣的地方,在定義頂點時,發(fā)現(xiàn)里面不僅定義了定點的坐標(biāo),還定義了頂點的顏色,也就是坐標(biāo)和頂點放在了一個數(shù)據(jù)緩沖中,因此在讀取的時候,調(diào)用glVertexAttribPointer函數(shù)要注意stride參數(shù)傳入正確的值,并且在度去玩頂點坐標(biāo)值后,要將ByteBuffer的position重新置位到第一個顏色值開始的地方。

RectangleView.java

public class RectangleView extends GLSurfaceView{

  private float mPreviousY;
  private float mPreviousX;
  MyRender mMyRender;
  public RectangleView(Context context) {
    super(context);
    setEGLContextClientVersion(2);
    mMyRender = new MyRender();
    setRenderer(mMyRender);
  }

  public boolean onTouchEvent(MotionEvent e) {
    float y = e.getY();
    float x = e.getX();
    switch (e.getAction()) {
    case MotionEvent.ACTION_MOVE:
      float dy = y - mPreviousY;
      float dx = x - mPreviousX;
      mMyRender.yAngle += dx;
      mMyRender.xAngle+= dy;
      requestRender();
    }
    mPreviousY = y;
    mPreviousX = x;
    return true;
  }

  class MyRender implements GLSurfaceView.Renderer {
    private Rectangle mRectangle;
    float yAngle;
    float xAngle;
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
      GLES20.glClearColor(1, 1, 1, 1);
      mRectangle = new Rectangle();
      GLES20.glEnable(GLES20.GL_DEPTH_TEST);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
      GLES20.glViewport(0, 0, width, height);
      Matrix.perspectiveM(mProjectionMatrix, 0, 45, (float)width/height, 5, 15);
      Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 10, 0, 0, 0, 0, 1, 0);
    }

    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];
    private final float[] mModuleMatrix = new float[16];
    private final float[] mViewProjectionMatrix = new float[16];
    private final float[] mMVPMatrix = new float[16];
    @Override
    public void onDrawFrame(GL10 gl) {
      GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
      Matrix.setIdentityM(mModuleMatrix, 0);
      Matrix.rotateM(mModuleMatrix, 0, xAngle, 1, 0, 0);
      Matrix.rotateM(mModuleMatrix, 0, yAngle, 0, 1, 0);
      Matrix.multiplyMM(mViewProjectionMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
      Matrix.multiplyMM(mMVPMatrix, 0, mViewProjectionMatrix, 0, mModuleMatrix, 0);
      mRectangle.draw(mMVPMatrix, mModuleMatrix);
    }
  }
}

產(chǎn)生的效果

使用OpenGL ES怎么實現(xiàn)光照效果

現(xiàn)在看起來感覺真實感還不是很強,因為自然界中還存在光照的影響。本篇文章就針對上面的立方體加入光照

光照模型

光照模型有三種,包括環(huán)境光、散射光和鏡面光。

環(huán)境光

環(huán)境光:從四面八方照射到物體上,全方位都均勻的光,代表的是現(xiàn)實世界中從廣元射出經(jīng)過多次反射后各個方向基本均勻的光,環(huán)境光不依賴光源位置,而且沒有方向性。環(huán)境光入射均勻,反射也是均勻的。

環(huán)境光最終強度 = 環(huán)境光強度

修改片元著色器如下即可實現(xiàn)環(huán)境光的效果。

gl_FragColor = aaColor*vec4(0.5,0.5,0.5,1);

加入環(huán)境光后的效果如下,可以看到效果很不好,畢竟每個地方的光照是一樣的,沒差別

使用OpenGL ES怎么實現(xiàn)光照效果

散射光

散射光:從物體表面向全方位360度均勻反射的光,代表了現(xiàn)實世界中粗糙物體表面被光照射時,反射到各個方向基本均勻,也被稱為漫反射。散射光強度和入射角關(guān)系很大,入射角度越小,越亮。

散射光最終強度=散射光強度?max{0,(cosθ)}

其中θ表示入射角

散射光的示意圖

使用OpenGL ES怎么實現(xiàn)光照效果

接下來主要修改頂點設(shè)色器的代碼即可。

private String vertexShaderCode = "uniform mat4 uMVPMatrix;"
      + "uniform mat4 uMMatrix;" // 模型變換的矩陣
      + "uniform vec3 uLightLocation;" // 光源位置
      + "attribute vec4 aColor;"
      + "varying vec4 vColor;" 
      + "varying vec4 vDiffuse;" // 傳遞給片元著色器的散射光強度,需要插值計算
      + "attribute vec3 aPosition;" // 頂點位置
      + "void main(){" 
      + "vec3 normalVectorOrigin = aPosition;" // 原始采用點法向量
      + "vec3 normalVector = normalize((uMMatrix*vec4(normalVectorOrigin,1)).xyz);" // 歸一化的變換后的法向量
      + "vec3 vectorLight = normalize(uLightLocation - (uMMatrix * vec4(aPosition,1)).xyz);" // 歸一化的光源到點的向量
      + "float factor = max(0.0, dot(normalVector, vectorLight));"
      + "vDiffuse = factor*vec4(1,1,1,1.0);" // 散射光強度,需要插值計算
      + "gl_Position = uMVPMatrix * vec4(aPosition,1);"
      + "vColor = aColor;"
      + "}";

片元著色器

private String fragmentShaderCode = "precision mediump float;"
      + "varying vec4 vColor;"
      + "varying vec4 vDiffuse;" // 從頂點著色器傳過來的插值散射光的值,散射光的值依賴頂點。
      + "void main(){"
      + "gl_FragColor = vColor*vDiffuse;" // 原本的顏色乘上散射光強度
      + "}";

上面主要的代碼含義已經(jīng)添加在注釋里面了。還有以下幾個地方需要注意

  • 頂點著色器中除了MVP矩陣還傳入了M矩陣,原因是顯然的,當(dāng)光照照在物體上,計算法線和該頂點和廣元的位置肯定要用進(jìn)行過基本變換(平移縮放和旋轉(zhuǎn))操作后的位置,上面?zhèn)魅隡矩陣目的就在于此。

  • 向流量的點積:ab=|a||b|cosa,因此想要計算夾角的余弦只需要將向量歸一化在計算點積即可。

  • 某一個點的法向量,點的法向量定義為該點的切面垂直向外的向量。對于不規(guī)則的形狀找其法線的方法是找其臨界點組成的平面的法向量,也可以求其相鄰的面向量的平均法向量。

接著修改頂點和片元著色器后,再在代碼中增加獲取uMMatrix、uLightLocation的引用以及往著色器傳遞數(shù)據(jù)的代碼

muMMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMMatrix");
muLightLocationHandle = GLES20.glGetUniformLocation(mProgram, "uLightLocation");
...
GLES20.glUniformMatrix4fv(muMMatrixHandle, 1, false, mMatrix, 0);
GLES20.glUniform3f(muLightLocationHandle, 0, 0, 20); 

 // 注意和攝像機位置的設(shè)置,否則設(shè)置到背面就只能看見一點點內(nèi)容了

增加了散射光的效果,可以看到效果明顯好了很多,有些地方比較暗,有些地方就是黑的,因為光照沒有照上。因為散射光根據(jù)和光源的角度有關(guān),角度越小越亮,這就是自然界的真實現(xiàn)象。

使用OpenGL ES怎么實現(xiàn)光照效果

代碼下載

鏡面光

鏡面光:現(xiàn)實世界中,當(dāng)光滑表面被照射后會有方向很集中的反射光,這種反射光就是鏡面光,鏡面光除了依賴入射角外,還依賴觀察者(攝像機)的位置,如果攝像機到被照射點的向量不在反射光集中的范圍內(nèi),就看不到鏡面光。

鏡面光最終強度=鏡面光強度?max{0,(cosθ)α}

其中θθ指的是半向量和法向量的夾角,αα表示粗糙度。

鏡面光示意圖

使用OpenGL ES怎么實現(xiàn)光照效果

使用鏡面光時,需要將攝像機矩陣傳入頂點著色器中,計算方法只需要按照定義來就可以。
綜合環(huán)境光、散射光和鏡面光的模型

gl_FragColor = vColor*vec4(0.5,0.5,0.5,1) + vColor*vDiffuse + vColor*vSpecular

看完上述內(nèi)容,你們掌握使用OpenGL ES怎么實現(xiàn)光照效果的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI