首页 > 代码库 > 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);
}


2、片段shader

#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着色语言-光照效果之散射光