首页 > 代码库 > OpenGL ES着色语言-光照效果之散射光
OpenGL ES着色语言-光照效果之散射光
OpenGL光照模型,在固定管线中,主要是调用OpenGL函数实现,如果使用着色器,该怎么实现。本文的例子是移植OpenGL 4.0 Shading Language Cookbook中第二章的例子。代码已经移植到Android上。
散射光计算主要涉及到两个向量,第一个是顶点到光源的向量S,以及顶点处的法向量N。光照计算在眼睛坐标中进行。具体见下图所示:
有这两个向量之后,还要考虑顶点处的漫反射系数以及光源强度,最终顶点处的光照强度的结果可以通过下列公式计算:
Ld为光源强度,Kd为漫反射系数。关于该公式的推导什么的,这里不做过多的描述。
有了上面的基本原理之后,下面我们就可以来一步步构建我们的demo了。
第一步:编写顶点和片段着色器
1、顶点shader
#version 310 es precision mediump float; layout (location = 0) in vec3 VertexPosition; layout (location = 1) in vec3 VertexNormal; out vec3 LightIntensity; uniform vec4 LightPosition; // 光源位置(眼睛坐标) uniform vec3 Kd; // 漫反射系数 uniform vec3 Ld; // 漫反射光强度 uniform mat4 ModelViewMatrix; uniform mat3 NormalMatrix; uniform mat4 ProjectionMatrix; uniform mat4 MVP; //直接传入MVP矩阵是为了减少每个顶点的计算量 void main() { vec3 tnorm = normalize( NormalMatrix * VertexNormal); //法线转换到眼睛坐标 vec4 eyeCoords = ModelViewMatrix * vec4(VertexPosition,1.0); vec3 s = normalize(vec3(LightPosition - eyeCoords)); LightIntensity = Ld * Kd * max( dot( s, tnorm ), 0.0 ); //漫反射光计算 gl_Position = MVP * vec4(VertexPosition,1.0); }
#version 310 es precision mediump float; in vec3 LightIntensity; layout( location = 0 ) out vec4 FragColor; void main() { FragColor = vec4(LightIntensity, 1.0); }
第二步:渲染矿建搭建
渲染框架也就是一个架子,只有先把这个弄好了之后才能做最后的渲染工作。
1、在Java层用GLSurfaceView结合Render进行渲染,关于这方面的资料网上有很多。
这个弄好了之后,还需要通过NDK调用到C++层执行实际的渲染。这里可以弄一个接口类,这个类专门负责native函数实现。
import javax.microedition.khronos.egl.EGLConfig; import android.content.res.AssetManager; public class GLinterface { public native static void onDrawFrame(); public native static void onSurfaceChanged(int width, int height); public native static void onSurfaceCreated(EGLConfig config); public native static void initializeAssetManager(AssetManager assetManager); }
然后Render类中调用它即可。
public class GlRenderer implements GLSurfaceView.Renderer{ @Override public void onDrawFrame(GL10 gl) { // TODO Auto-generated method stub GLinterface.onDrawFrame(); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { // TODO Auto-generated method stub GLinterface.onSurfaceChanged(width, height); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { // TODO Auto-generated method stub GLinterface.onSurfaceCreated(config); } }
initializeAssetManager主要负责向C层传递AssetManager的对象,然后C层读取assets中的文件,这里我主要是读取shader文件。
initializeAssetManager在Activity的oncreate中调用。
2、C层框架的搭建:
java层的native函数声明完之后,然后可以生成对应的C函数声明,这里主要是初始化,窗口变化以及渲染这三个函数,这些都弄好之后,然后可以构建这个例子需要用到的场景了,场景中就包括了对渲染数据的准备,shader的加载编译,向shader传数据。
场景是由一个类来管理,类的声明如下:
class VBOTorus; class DiffuseShader { public: DiffuseShader(); virtual ~DiffuseShader(); void Init(); void Resize(int width,int height); void Draw(); private: GLuint vaoHandle; GLSLProgram mProgram; Matrix4x4 model; Matrix4x4 view; Matrix4x4 projection; VBOTorus *torus; void setMatrices(); };
(1)构造函数以及shader加载
shader加载用Assetsmanager来读取数据,具体为,
extern std::string strVert; extern std::string strFrag; void GetShaderFile(AAssetManager* mgr,const char* vertFile,const char* fragFile) { //打开顶点shader AAsset* asset = AAssetManager_open(mgr,vertFile,AASSET_MODE_UNKNOWN); off_t lenght = AAsset_getLength(asset); __android_log_print(ANDROID_LOG_INFO,"GLES2","length = %ld",lenght); strVert.resize(lenght+1,0); memcpy((char*)strVert.data(),AAsset_getBuffer(asset),lenght); __android_log_print(ANDROID_LOG_INFO,"GLES2","content = %s",strVert.c_str()); AAsset_close(asset); asset = AAssetManager_open(mgr,fragFile,AASSET_MODE_UNKNOWN); lenght = AAsset_getLength(asset); __android_log_print(ANDROID_LOG_INFO,"GLES2","length = %ld",lenght); strFrag.resize(lenght+1,0); memcpy((char*)strFrag.data(),AAsset_getBuffer(asset),lenght); __android_log_print(ANDROID_LOG_INFO,"GLES2","content = %s",strVert.c_str()); AAsset_close(asset); }
JNIEXPORT void JNICALL Java_com_example_ndkgles_GLinterface_initializeAssetManager(JNIEnv *env, jclass clsObj, jobject assetManager) { AAssetManager* mgr = AAssetManager_fromJava(env, assetManager); //GetShaderFile(mgr,"uniformBlock.vsh","uniformBlock.fsh"); GetShaderFile(mgr,"diffuse.vert","diffuse.frag"); }
Java_com_example_ndkgles_GLinterface_initializeAssetManager对应的函数就是Java层的initializeAssetManager函数,这样shader的文本已经加载进来了,然后剩下shader创建。程序对象编译了。
extern std::string strVert; extern std::string strFrag; DiffuseShader::DiffuseShader() { // TODO Auto-generated constructor stub mProgram.InitWithShader(strVert.c_str(), strFrag.c_str()); mProgram.LinkProgram(); }
mProgram的类型是GLSLProgram,这个可以参考我的前一篇博客,GLSL程序对象封装。strVert和strFrag是全局变量,用来存放shader的文本内容。
2、init函数
init函数主要是向shader传递数据以及创建渲染的对象
glClearColor(0.0,0.0,0.0,1.0); glEnable(GL_DEPTH_TEST); torus = new VBOTorus(0.7f, 0.3f, 30, 30); Matrix4x4::CreateRotationX(-35.0f,model); Matrix4x4 modelTemp; Matrix4x4::CreateRotationY(35.0f,modelTemp); model *= modelTemp; Matrix4x4::CreateScale(1.0,1.0,1.0,modelTemp); model *= modelTemp; Matrix4x4::CreateLookAt(Vector3(0.0f,0.0f,2.6f),Vector3(0.0f,0.0f,0.0f), Vector3(0.0f,1.0f,0.0f),view); projection = Matrix4x4::IDENTITY; mProgram.SetUniformVariable("Kd", 0.9f, 0.5f, 0.3f); mProgram.SetUniformVariable("Ld", 1.0f, 1.0f, 1.0f); //设置灯光位置 Vector4 vec4(0,0,0,0); vec4 = view * Vector4(5.0f,5.0f,2.0f,1.0f); mProgram.SetUniformVariable("LightPosition", vec4.x,vec4.y,vec4.z,vec4.w );
关于上面的矩阵操作,可以用NDK自带的,或者你自己写也可以,这个是我的一个个人项目,现在还不适合公开。
3、resize函数
glViewport(0,0,width,height); Matrix4x4::CreatePerspective(70.0f,(Real)width/height,0.3f,100.f,projection);
4、draw函数
这个函数就是最后的渲染函数了,主要工作就是设置MVP矩阵、法线矩阵等操作。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); Matrix4x4 mv = view * model; mProgram.SetUniformMatrix4f("ModelViewMatrix", 1, true, &mv[0][0]); Matrix3x3 matNormal = mv.GetMatrix3().Inverse().Transpose(); mProgram.SetUniformMatrix3f("NormalMatrix",1, true, &matNormal[0][0]); mProgram.SetUniformMatrix4f("MVP", 1, true, &(projection * mv)[0][0]); torus->render();
VBOTorus这个类是该demo显示的对象,代码我已经上传,下载地址,在这里就不啰嗦了。
第三步、连接C和Java之间的桥梁
经过前两部,Java和C之间的工作都做完,并且接口已经定义好,这时只需要在native的实现函数里面调用相应的功能即可。
DiffuseShader* pBasicShder = NULL; /* * Class: com_example_ndkgles_GLinterface * Method: onDrawFrame * Signature: ()V */ JNIEXPORT void JNICALL Java_com_example_ndkgles_GLinterface_onDrawFrame(JNIEnv *env, jclass clsObj) { //OnDrawGlFrame(); pBasicShder->Draw(); } /* * Class: com_example_ndkgles_GLinterface * Method: onSurfaceChanged * Signature: (II)V */ JNIEXPORT void JNICALL Java_com_example_ndkgles_GLinterface_onSurfaceChanged(JNIEnv *env, jclass clsObj, jint width, jint height) { //OnResize(width,height); pBasicShder->Resize(width, height); } /* * Class: com_example_ndkgles_GLinterface * Method: onSurfaceCreated * Signature: (Ljavax/microedition/khronos/egl/EGLConfig;)V */ JNIEXPORT void JNICALL Java_com_example_ndkgles_GLinterface_onSurfaceCreated(JNIEnv *env, jclass clsObj, jobject obj) { //OnInited(); pBasicShder = new DiffuseShader(); pBasicShder->Init(); }
上面这几个函数就对应着Java层的GLinterface,至此,所有的工作都做完了,连接手机,不出意外,图像显示出来了。
以前刚开始弄shader的时候感觉很麻烦,现在我觉得只要把一些东西封装好能复用也没想象的那么繁琐。
OpenGL ES着色语言-光照效果之散射光