OpenGL ES实现光照效果(六)

为了演示光照效果,在前面学习过的内容基础上我们首先创建一个立方体,同时为了看起来直观一些,这个立方体每个面采用中心为白色,周围红色的渐变方案,不然看上去同样的颜色混在一起,看不出来是否是立方体。并且添加上转动旋转功能,这样转动起来立体感更强一些。

一个立方体

立方体类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);
    // 将顶点数据传递到管线,顶点着色器
    // 定位到位置0,读取顶点
    mVertexBuffer.position(0);
    // stride 跨距,读取下一个值跳过的字节数
    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类和前面的例子中基本一样,但这里和前面有一些不一样的地方,在定义顶点时,发现里面不仅定义了定点的坐标,还定义了顶点的颜色,也就是坐标和顶点放在了一个数据缓冲中,因此在读取的时候,调用glVertexAttribPointer函数要注意stride参数传入正确的值,并且在度去玩顶点坐标值后,要将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);
    }
  }
}

产生的效果

现在看起来感觉真实感还不是很强,因为自然界中还存在光照的影响。本篇文章就针对上面的立方体加入光照

光照模型

光照模型有三种,包括环境光、散射光和镜面光。

环境光

环境光:从四面八方照射到物体上,全方位都均匀的光,代表的是现实世界中从广元射出经过多次反射后各个方向基本均匀的光,环境光不依赖光源位置,而且没有方向性。环境光入射均匀,反射也是均匀的。

环境光最终强度 = 环境光强度

修改片元着色器如下即可实现环境光的效果。

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

加入环境光后的效果如下,可以看到效果很不好,毕竟每个地方的光照是一样的,没差别

散射光

散射光:从物体表面向全方位360度均匀反射的光,代表了现实世界中粗糙物体表面被光照射时,反射到各个方向基本均匀,也被称为漫反射。散射光强度和入射角关系很大,入射角度越小,越亮。

散射光最终强度=散射光强度∗max{0,(cosθ)}

其中θ表示入射角

散射光的示意图

接下来主要修改顶点设色器的代码即可。

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;" // 原本的颜色乘上散射光强度
      + "}";

上面主要的代码含义已经添加在注释里面了。还有以下几个地方需要注意

  • 顶点着色器中除了MVP矩阵还传入了M矩阵,原因是显然的,当光照照在物体上,计算法线和该顶点和广元的位置肯定要用进行过基本变换(平移缩放和旋转)操作后的位置,上面传入M矩阵目的就在于此。
  • 向流量的点积:ab=|a||b|cosa,因此想要计算夹角的余弦只需要将向量归一化在计算点积即可。
  • 某一个点的法向量,点的法向量定义为该点的切面垂直向外的向量。对于不规则的形状找其法线的方法是找其临界点组成的平面的法向量,也可以求其相邻的面向量的平均法向量。

接着修改顶点和片元着色器后,再在代码中增加获取uMMatrix、uLightLocation的引用以及往着色器传递数据的代码

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

 // 注意和摄像机位置的设置,否则设置到背面就只能看见一点点内容了

增加了散射光的效果,可以看到效果明显好了很多,有些地方比较暗,有些地方就是黑的,因为光照没有照上。因为散射光根据和光源的角度有关,角度越小越亮,这就是自然界的真实现象。

代码下载

镜面光

镜面光:现实世界中,当光滑表面被照射后会有方向很集中的反射光,这种反射光就是镜面光,镜面光除了依赖入射角外,还依赖观察者(摄像机)的位置,如果摄像机到被照射点的向量不在反射光集中的范围内,就看不到镜面光。

镜面光最终强度=镜面光强度∗max{0,(cosθ)α}

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

镜面光示意图

使用镜面光时,需要将摄像机矩阵传入顶点着色器中,计算方法只需要按照定义来就可以。
综合环境光、散射光和镜面光的模型

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • OpenGL ES 矩阵变换及其数学原理详解(五)

    引子 向量刻画的是线性空间中的对象. 矩阵刻画的是向量在线性空间中的运动(变换,跃迁),相似矩阵本质上就是同一个线性变换的不同的描述. 在一个线性空间中,选定了一组基,对于任何一个线性变化都可以用一个确定的矩阵来描述 矩阵不仅可以作为线性变换的描述,而且可以作为一组基的描述,作为变换的矩阵,不但可以把线性空间中的一个点给变换到另一个点去,而且也能够把线性空间中的一个坐标系(基)表换到另一个坐标系(基)去. 当我们谈到向量时,一定要指定它所在的坐标系才有意义,比如向量b=(1,2,3)实际上指的是

  • OpenGL ES着色器使用详解(二)

    本文介绍了OpenGL ES着色器使用的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 1.着色器语言 着色器语言是一种高级图形编程语言,和C/C++语言很类似,但存在很大差别,比如,不支持double,byte ,short,不支持unin,enum,unsigned以及位运算等,但其加入了很多原生的数据类型,如向量,矩阵等. 数据类型可分为标量.向量.矩阵.采样器.结构体.数组等 向量 向量传递参数,如果只提供一个标量,这个值用于设置所有向量的值:如果输入是多个标量或者是矢量,从左到

  • java实现OpenGL ES纹理映射的方法

    本文实例讲述了java实现OpenGL ES纹理映射的方法.分享给大家供大家参考.具体如下: 1. GlRenderer.java文件: package net.obviam.opengl; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.opengl.GL

  • OpenGL ES正交投影实现方法(三)

    本文实例为大家分享了OpenGL ES正交投影展示的具体代码,供大家参考,具体内容如下 绘制正方形 在最开始绘制的六边形里面好像看起来挺容易的,也没有出现什么问题,接下来不妨忘记前面绘制六边形的代码,让我们按照自己的理解来绘制一个简单的正方形. 按照我的理解,要想在屏幕中间显示一个正方形,效果如下图所示 应该创建的数据如下图所示 即传给渲染管线的顶点数据如下图: float[] vertexArray = new float[] { (float) -0.5, (float) -0.5, 0,

  • OpenGL ES渲染管线概述(一)

    渲染管线一般是由显示芯片GPU内部处理图形信号的并行处理单元组成,这些并行处理单元之间是独立的,从另一个角度看,渲染管线实际上也是一系列绘制过程,这一系列过程的输入是待绘制物体的相关描述信息,输出的是要显示的图像帧数据. OpenGL ES管线主要包括: 读取顶点数据->顶点着色器->组装图元->光栅化图元->片元着色器->写入帧缓冲区->显示到屏幕上 读取顶点数据指的是将待绘制的图形的顶点数据传递给渲染管线中. 顶点着色器最终生成每个定点的最终位置,执行顶点的各种变换

  • OpenGL ES透视投影实现方法(四)

    在之前的学习中,我们知道了一个顶点要想显示到屏幕上,它的x.y.z分量都要在[-1,1]之间,我们回顾一下渲染管线的图元装配阶段,它实际上做了以下几件事:剪裁坐标.透视分割.视口变换.图元装配的输入是顶点着色器的输出,抓哟是物体坐标gl_Position,之后到光栅化阶段. 图元装配 剪裁坐标 当顶点着色器写入一个值到gl_Position时,这个点要求必须在剪裁空间中,即它的x.y.z坐标必须在[-w,w]之间,任何这个范围之外的点都是不可见的. 这里需要注意以下,对于attribute类型的

  • OpenGL ES纹理详解

    使用前面学过的技术已经可以利用OpenGL ES构建立体图形,并通过顶点着色器和片元着色器对其进行各种变化呢和光照等效果使得三维效果更加真实,实际上我看看到很多的3D游戏漂亮多了,那是因为有各种各样的漂亮的图像带给人很多视觉盛宴,这篇文章在前面的基础上,增加物体的表面贴图,使得物体更加好看. 纹理概念 纹理用来表示图像照片或者说一系列的数据,使用纹理可以使物体用用更多的细节.OpenGL ES 2.0 中有两种贴图:二维纹理和立方体纹理. 每个二维纹理都由许多小的纹理元素组成,类似与片元和像素,

  • OpenGL ES实现光照效果(六)

    为了演示光照效果,在前面学习过的内容基础上我们首先创建一个立方体,同时为了看起来直观一些,这个立方体每个面采用中心为白色,周围红色的渐变方案,不然看上去同样的颜色混在一起,看不出来是否是立方体.并且添加上转动旋转功能,这样转动起来立体感更强一些. 一个立方体 立方体类Rectangle.java public class Rectangle { private FloatBuffer mVertexBuffer; private int mProgram; private int mPositi

  • android使用OPENGL ES绘制圆柱体

    本文实例为大家分享了android使用OPENGL ES绘制圆柱体的具体代码,供大家参考,具体内容如下 效果图: 编写jiem.java *指定屏幕所要显示的假面,并对见.界面进行相关设置     *为Activity设置恢复处理,当Acitvity恢复设置时显示界面同样应该恢复     *当Activity暂停设置时,显示界面同样应该暂停 package com.scout.eeeeeee; import android.app.Activity; import android.os.Bund

  • java基于OpenGL ES实现渲染实例

    本文实例讲述了java基于OpenGL ES实现渲染的方法.分享给大家供大家参考.具体如下: 1. Run.java文件: package net.obviam.opengl; import android.app.Activity; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; public

  • 通过OpenGL ES混合模式缩放视频缓冲区来适应显示尺寸

    当开发基于软件模式的游戏时,通过缩放视频缓冲区来适应显示尺寸是最棘手的问题之一.当面对众多不同的分辨率时(比如开放环境下的Android),该问题会变得更加麻烦,作为开发人员,我们必须尝试在性能与显示质量之间找到最佳平衡点.正如我们在第2章中看到的,缩放视频缓冲区从最慢到最快共有3种类型. 软件模拟:3中类型中最慢,但最容易实现,是没有GPU的老款设备上的最佳选择.但是现在大部分智能手机都支持硬件加速. 混合模式:这种方式混合使用软件模拟(创建图像缓冲区)和硬件渲染(向显示屏绘制)两种模式.这种

  • Android开发 OpenGL ES绘制3D 图形实例详解

    OpenGL ES是 OpenGL三维图形API 的子集,针对手机.PDA和游戏主机等嵌入式设备而设计. Ophone目前支持OpenGL ES 1.0 ,OpenGL ES 1.0 是以 OpenGL 1.3 规范为基础的,OpenGL ES 1.1 是以 OpenGL 1.5 规范为基础的.本文主要介绍利用OpenGL ES绘制图形方面的基本步骤. 本文内容由三部分构成.首先通过EGL获得OpenGL ES的编程接口;其次介绍构建3D程序的基本概念;最后是一个应用程序示例. OpenGL E

随机推荐