OpenGL ES著色語言-光照效果之散射光
OpenGL光照模型,在固定管線中,主要是呼叫OpenGL函式實現,如果使用著色器,該怎麼實現。本文的例子是移植OpenGL 4.0 Shading Language Cookbook中第二章的例子。程式碼已經移植到Android上。
散射光計算主要涉及到兩個向量,第一個是頂點到光源的向量S,以及頂點處的法向量N。光照計算在眼睛座標中進行。具體見下圖所示:
有這兩個向量之後,還要考慮頂點處的漫反射係數以及光源強度,最終頂點處的光照強度的結果可以通過下列公式計算:
Ld為光源強度,Kd為漫反射係數。關於該公式的推導什麼的,這裡不做過多的描述。
有了上面的基本原理之後,下面我們就可以來一步步構建我們的demo了。
第一步:編寫頂點和片段著色器
1、頂點shader
2、片段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的時候感覺很麻煩,現在我覺得只要把一些東西封裝好能複用也沒想象的那麼繁瑣。