1. 程式人生 > >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的時候感覺很麻煩,現在我覺得只要把一些東西封裝好能複用也沒想象的那麼繁瑣。