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);
}
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");
}
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();
}
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 );
3、resize函数
glViewport(0,0,width,height);
Matrix4x4::CreatePerspective(70.0f,(Real)width/height,0.3f,100.f,projection);
这个函数就是最后的渲染函数了,主要工作就是设置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();
第三步、连接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();
}
以前刚开始弄shader的时候感觉很麻烦,现在我觉得只要把一些东西封装好能复用也没想象的那么繁琐。