OpenGL ES实现光照效果(六)
作者:weiers 发布时间:2022-06-30 16:08:52
为了演示光照效果,在前面学习过的内容基础上我们首先创建一个立方体,同时为了看起来直观一些,这个立方体每个面采用中心为白色,周围红色的渐变方案,不然看上去同样的颜色混在一起,看不出来是否是立方体。并且添加上转动旋转功能,这样转动起来立体感更强一些。
一个立方体
立方体类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
来源:https://blog.csdn.net/cauchyweierstrass/article/details/52916686
猜你喜欢
- 概述在使用maven进行Java项目的开发过程中,难免会有些公共的私有库,这些库是不太方便放到中央仓库的,可以通过Nexus搭建一个私有仓库
- 登录接口实现public User queryUser(String UserName, String Password,HttpServl
- 第一步:后端简单建个SpringBoot项目,提供一个 helloWorld接口;版本选用 2.2.6.RELEASEpackage com
- 闭锁相当于一扇门,在闭锁到达结束状态之前,这扇门一直是关闭着的,没有任何线程可以通过,当到达结束状态时,这扇门才会打开并容许所有线程通过。它
- 本文实例讲述了Java实现SSL双向认证的方法。分享给大家供大家参考,具体如下:我们常见的SSL验证较多的只是验证我们的服务器是否是真实正确
- 项目比较大有时候会比较卡,虽然有GC自动清理机制,但是还是有不尽人意的地方。所以尝试在项目启动文件中,手动写了一个定时器,定时清理内存,加快
- 本文实例为大家分享了java实现三角形分形山脉的具体代码,供大家参考,具体内容如下三角形分形山脉原理原型图如图,这是三角形分形山脉的一个原型
- 序初涉江湖,还望海涵!写点东西,纯粹是因为个人的记忆能力较弱,写些笔记罢了,若有错误还望雅正!对Android中的时间获取做个记录,以下为结
- 本文实例为大家分享了Java实现简单抽牌游戏的具体代码,供大家参考,具体内容如下Main类package com.company;impor
- Glide 4.0由Google的各种团队内部使用,4.0被认为是内部稳定的。但外部用户可能会发现内部尚未发现的问题。因此,将此作为RC发布
- 前言本文主要给大家介绍了关于C#基础之Attribute和反射的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。A
- 数据传输在Android开发过程中,我们常常通过Intent在各个组件之间传递数据。例如在使用startActivity(android.c
- 前言现在市面上很多应用都会有当用户按返回键的时候提示用户:再按一次将退出应用的提示,也就是双击双击返回键退出应用,接下来我们就用几种办法来实
- 题目:使用struts2自定义 * ,完成用户登陆才能访问权限的实现在session中存放user变量表示用户登陆,若user为空则用户没有
- Kotlin基础教程之Run,标签Label,函数Function-Type在Java中可以使用{}建立一个匿名的代码块,代码块会被正常的执
- 具体代码如下所示:package zhangphil.test; import android.graphics.Bitmap; impor
- 架构:MVC架构基于JWT的身份认证Spring Data (JPA)应用用户密码加密数据库密码加密SQL ServerSlf4j基于Swa
- 目录前言继承Thread实现Runnale接口Callable线程池常见的4种线程池。总结前言在java中,如果每个请求到达就创建一个新线程
- 需求:用户和账户一对一关系,查询账户时实现用户的延迟加载思路:根据id查询,需要延迟加载的一方1、用户实体类package com.yl.b
- Android 调用发送短信的方 * 能:调用发送短信功能 1 、 权限 <uses-permission android:name=&