您的位置:首页 > 运维架构

OpenGLES入门笔记:Rajawali学习(2)场景绘制基本流程

2016-08-15 23:38 627 查看

背景

上一篇文章中我们简单体验了一下Rajawali的基本功能,现在我们来具体看一下这些物体是如何封装,最终调用GLES 绘制的。上一篇中通过阅读代码我们发现Rajawali的绘制也是类似于Surface和Renderer的机制,Surface用于最终的呈现,Renderer用于渲染图形。

实现

Surface

ISurface

这个接口定义了Surface中的基本功能,实现了它才算是一个基本的Surface。我们看看Surface需要哪些基本方法。

/**
* 抗锯齿选项
*/
public static enum ANTI_ALIASING_CONFIG {
NONE, MULTISAMPLING, COVERAGE;

public static ANTI_ALIASING_CONFIG fromInteger(int i) {
switch (i) {
case 0:
return NONE;
case 1:
return MULTISAMPLING;
case 2:
return COVERAGE;
}
return NONE;
}
}

/**
* The renderer only renders
* when the surface is created, or when {@link #requestRenderUpdate()} is called.
*/
public final static int RENDERMODE_WHEN_DIRTY = 0;
/**
* The renderer is called
* continuously to re-render the scene.
*/
public final static int RENDERMODE_CONTINUOUSLY = 1;

/**
* 设置帧率
*/
public void setFrameRate(double rate);

/**
* 获取渲染模式
*/
public int getRenderMode();

/**
* 设置渲染模式
*/
public void setRenderMode(int mode);

/**
* 使能多重采样
* Must be called before {@link #setSurfaceRenderer(ISurfaceRenderer)}.
*/
public void setAntiAliasingMode(ANTI_ALIASING_CONFIG config);

/**
* 设置采样次数,多重采样开启时生效
*/
public void setSampleCount(int count);

/**
* 设置render
*/
public void setSurfaceRenderer(ISurfaceRenderer renderer) throws IllegalStateException ;

/**
* render请求生效时调用
*/
public void requestRenderUpdate();


TextureView

这是我们真正的Sureface,它并不是Android原生的TextureView,而是继承了原生TextureView并实现了ISurface。这个类的代码比较长,不过我们可以发现其中包含了很多内部类,现在我们简单梳理一下这些内部类的功能。

GLThreadManager 通过判断标志及使用notifyAll进行线程调度

GLThread GL线程,用于委托render进行绘制,同步相关在GLThreadManager中完成

在run方法中执行guardedRun()方法,guardedRun()循环判断同步条件后执行如下代码

...
try {
mEglHelper.start();
} catch (RuntimeException t) {
sGLThreadManager.releaseEglContextLocked(this);
throw t;
}
mHaveEglContext = true;
createEglContext = true;

sGLThreadManager.notifyAll();
...


其中mEglHelper.start()的作用是初始化了如下OpenGL变量

EGL10 mEgl;
EGLDisplay mEglDisplay;
EGLSurface mEglSurface;
EGLConfig mEglConfig;
EGLContext mEglContext;


EglHelper

ComponentSizeChooser 设置使用的rgba 深度,模板的大小

BaseConfigChooser

DefaultWindowSurfaceFactory EGLSurfaces的工厂类,提供了生成和销毁的方法

DefaultContextFactory EGLContext的工厂类,提供了生成和销毁的方法

RendererDelegate render委托类,将帧率,抗锯齿,以及关联的TextureView设置给render

我们自定义的TextureView类就是靠上面这些类来实现上述具体的功能。

了解了内部类的基本作用,我们开始看TextureView的主要功能。

public TextureView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
applyAttributes(context, attrs);
}


构造函数中,我们将帧率等属性进行赋值。注意获取属性列表的方式:

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextureView);


下面看一下onAttachedToWindow

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
}
if (mDetached && (mRendererDelegate != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (mGLThread != null) {
renderMode = mGLThread.getRenderMode();
}
mGLThread = new GLThread(mThisWeakRef);
if (renderMode != RENDERMODE_CONTINUOUSLY) {
mGLThread.setRenderMode(renderMode);
}
mGLThread.start();
}
mDetached = false;
}


此处执行我们上文提到过的GLThread进行GL绘制,可见Surface中更新UI并不是在主线程,而是在GL线程中进行。

Renderer

ISurfaceRenderer

同理,renderer也有个接口,作为renderer必须实现这个接口。

/**
* Fetch the current target frame rate in frames per second.
*
* @return {@code double} The target frame rate.
*/
public double getFrameRate();

/**
* Sets the target frame rate in frames per second.
*
* @param rate {@code int} The target rate.
*/
public void setFrameRate(int rate);

/**
* Sets the target frame rate in frames per second.
*
* @param rate {@code double} The target rate.
*/
public void setFrameRate(double rate);

/**
* Called to inform the renderer of the multisampling configuration on this surface.
*
* @param config {@link ISurface.ANTI_ALIASING_CONFIG} The desired anti aliasing configuration.
*/
public void setAntiAliasingMode(ISurface.ANTI_ALIASING_CONFIG config);

/**
* Sets the {@link ISurface} which this implementation will be rendering on.
*
* @param surface {@link ISurface} The rendering surface.
*/
public void setRenderSurface(ISurface surface);

/**
* Called when the renderer should pause all of its rendering activities, such as frame draw requests.
*/
public void onPause();

/**
* Called when the renderer should continue all of its rendering activities, such as frame draw requests.
*/
public void onResume();

/**
* This corresponds to {@link TextureView.SurfaceTextureListener#onSurfaceTextureAvailable(SurfaceTexture, int, int)}
* and {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}. Unused parameters are passed as null or -1.
*
* @param config {@link EGLConfig config}. This is used if the surface is {@link GL10} type (SurfaceView).
* @param gl {@link GL10} for rendering.
* @param width {@code width} The surface width in pixels.
* @param height {@code height} The surface height in pixels.
*/
public void onRenderSurfaceCreated(EGLConfig config, GL10 gl, int width, int height);

/**
* Called when the rendering surface has been destroyed, such as the view being detached from the window.
*
* @param surface {@link SurfaceTexture} The texture which was being rendered to.
*/
public void onRenderSurfaceDestroyed(SurfaceTexture surface);

/**
* This corresponds to {@link TextureView.SurfaceTextureListener#onSurfaceTextureSizeChanged(SurfaceTexture, int, int)}
* and {@link GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int)}.
*
* @param gl {@link GL10} for rendering.
* @param width {@code width} The surface width in pixels.
* @param height {@code height} The surface height in pixels.
*/
public void onRenderSurfaceSizeChanged(GL10 gl, int width, int height);

/**
* Called when the renderer should draw its next frame.
*
* @param gl {@link GL10} for rendering.
*/
public void onRenderFrame(GL10 gl);

/**
* NOTE: Only relevant when rendering a live wallpaper.
*
* Called to inform you of the wallpaper's offsets changing within its contain, corresponding to the container's
* call to WallpaperManager.setWallpaperOffsets().
*
* @param xOffset
* @param yOffset
* @param xOffsetStep
* @param yOffsetStep
* @param xPixelOffset
* @param yPixelOffset
*/
public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep,
float yOffsetStep, int xPixelOffset, int yPixelOffset);

/**
* Called as the user performs touch-screen interaction with the window that is currently showing this wallpaper.
* Note that the events you receive here are driven by the actual application the user is interacting with,
* so if it is slow you will get fewer move events.
*
* @param event {@link MotionEvent} The touch event.
*/
public void onTouchEvent(MotionEvent event);


上面一堆主要做了以下几件事

1.采样率设置

2.抗锯齿设置

3.surface相关回调方法

*surface创建

*surface销毁

*surface大小发生改变

4.绘制下一帧回掉

5.外部容器位置变化回掉

6.Touch事件回调

Renderer

Renderer是一个实现了ISurfaceRenderer的抽象类,其中void initScene()方法需要我们自己实现,功能是初始化摆放我们的各种模型。

private Scene mCurrentScene;
//场景列表
protected final List<Scene>                     mScenes; //List of all scenes this renderer is aware of.
//目标列表
protected final List<RenderTarget>              mRenderTargets; //List of all render targets this renderer is aware of.
//添加场景内容任务
private final Queue<AFrameTask>                 mFrameTaskQueue;
private final SparseArray<ModelRunnable>        mLoaderThreads;
private final SparseArray<IAsyncLoaderCallback> mLoaderCallbacks;


构造方法

public Renderer(Context context, boolean registerForResources) {
RajLog.i("Rajawali | Bombshell | v1.1 Development ");
RajLog.i("THIS IS A DEV BRANCH CONTAINING SIGNIFICANT CHANGES. PLEASE REFER TO CHANGELOG.md FOR MORE INFORMATION.");
mHaveRegisteredForResources = registerForResources;
mContext = context;
RawShaderLoader.mContext = new WeakReference<>(context);
mFrameRate = getRefreshRate();
mScenes = Collections.synchronizedList(new CopyOnWriteArrayList<Scene>());
mRenderTargets = Collections.synchronizedList(new CopyOnWriteArrayList<RenderTarget>());
mFrameTaskQueue = new LinkedList<>();

mSceneCachingEnabled = true;
mSceneInitialized = false;

mLoaderThreads = new SparseArray<>();
mLoaderCallbacks = new SparseArray<>();

final Scene defaultScene = getNewDefaultScene();
mScenes.add(defaultScene);
mCurrentScene = defaultScene;

// Make sure we use the default viewport size initially
clearOverrideViewportDimensions();

// Make sure we have a texture manager
mTextureManager = TextureManager.getInstance();
mTextureManager.setContext(getContext());

// Make sure we have a material manager
mMaterialManager = MaterialManager.getInstance();
mMaterialManager.setContext(getContext());

// We are registering now
if (registerForResources) {
mTextureManager.registerRenderer(this);
mMaterialManager.registerRenderer(this);
}
}


开启渲染

此处开启RequestRenderTask来执行渲染操作,帧率就是RequestRenderTask执行的频率。

public void startRendering() {
RajLog.d("startRendering()");
if (!mSceneInitialized) {
return;
}
mRenderStartTime = System.nanoTime();
mLastRender = mRenderStartTime;
if (mTimer != null) return;
mTimer = Executors.newScheduledThreadPool(1);
mTimer.scheduleAtFixedRate(new RequestRenderTask(), 0, (long) (1000 / mFrameRate), TimeUnit.MILLISECONDS);
}


下面看一下RequestRenderTask里做了什么

private class RequestRenderTask implements Runnable {
public void run() {
if (mSurface != null) {
mSurface.requestRenderUpdate();
}
}
}


我们发现这里又回到了Isurface中的方法requestRenderUpdate()。那么,我们的场景是如何传入surface中的呢?好像并没有直接传入场景数据的方法,我们需要再往下看。

onRenderFrame

//轮一遍mFrameTaskQueue中的任务
performFrameTasks(); //Execute any pending frame tasks
...

onRender(elapsedRenderTime, deltaTime);

++mFrameCount;
if (mFrameCount % 50 == 0) {
...
if (mFPSUpdateListener != null)
mFPSUpdateListener.onFPSUpdate(mLastMeasuredFPS); //Update the FPS listener
}


onRender

protected void onRender(final long ellapsedRealtime, final double deltaTime) {
render(ellapsedRealtime, deltaTime);
}


找到render方法

protected void render(final long ellapsedRealtime, final double deltaTime) {
mCurrentScene.render(ellapsedRealtime, deltaTime, mCurrentRenderTarget);
}


可见,最终的渲染其实是调用了mCurrentScene的render方法,参数中mCurrentRenderTarget正是我们场景中的模型。

Scene

我们发现render中真实调用的其实是Scene中的render方法,现在我们来看看这里做了什么。此处开始真正调用Opengl的API来对我们传入数据进行渲染。这里先检查有没有新的模型或者其他物体添加,如果有的话在render线程中同步添加,之后,分别处理每个列表,调用物体本身的render方法进行绘制,这些方法就直接调用Opengl API进行绘制。

public void render(long ellapsedTime, double deltaTime, RenderTarget renderTarget) {
render(ellapsedTime, deltaTime, renderTarget, null);
}

public void render(long ellapsedTime, double deltaTime, RenderTarget renderTarget, Material sceneMaterial) {

if (mPickerInfo != null) {
最终调用ObjectColorPicker的静态方法pickObject(pickerInfo),查看是否有模型被选中
}

performFrameTasks(); //如果有新的物体,同步添加

synchronized (mFrameTaskQueue) {
为表面材料设置光照
}

synchronized (mNextSkyboxLock) {
设置天空盒
}
synchronized (mNextCameraLock) {
设置Camera切换
}

int clearMask = mAlwaysClearColorBuffer? GLES20.GL_COLOR_BUFFER_BIT : 0;

...

if (mEnableDepthBuffer) {
设置Opengl深度缓存
}
if (mAntiAliasingConfig.equals(ISurface.ANTI_ALIASING_CONFIG.COVERAGE)) {
clearMask |= GL_COVERAGE_BUFFER_BIT_NV;
}
GLES20.glClear(clearMask);

final int preCount = mPreCallbacks.size();
if (preCount > 0) {
执行onPreFrame回调
}

// Update all registered animations
synchronized (mAnimations) {
执行动画
}

//更新Camera的MVP矩阵
// We are beginning the render process so we need to update the camera matrix before fetching its values
mCamera.onRecalculateModelMatrix(null);
// Get the view and projection matrices in advance
mVMatrix = mCamera.getViewMatrix();
mPMatrix = mCamera.getProjectionMatrix();
// Pre-multiply View and Projection matrices once for speed
mVPMatrix.setAll(mPMatrix).multiply(mVMatrix);
mInvVPMatrix.setAll(mVPMatrix).inverse();
mCamera.updateFrustum(mInvVPMatrix); // Update frustum plane

// 更新光源的模型矩阵
synchronized (mLights) {
final int numLights = mLights.size();
for (int i = 0; i < numLights; ++i) {
mLights.get(i).onRecalculateModelMatrix(null);
}
}

// Execute onPreDraw callbacks
// We explicitly break out the steps here to help the compiler optimize
final int preDrawCount = mPreDrawCallbacks.size();
if (preDrawCount > 0) {
执行onPreFrame回调
}

if (mSkybox != null) {
绘制天空盒
}

if(sceneMaterial != null) {
绑定场景的材质
}

synchronized (mChildren) {
为每个模型传入Camera以及模型矩阵,视图矩阵,投影矩阵,场景的材质
绘制每个模型,此处调用Object3D类中的render方法现实
}

if (mDisplaySceneGraph) {
//绘制场景图,mDisplaySceneGraph一般为false
mSceneGraph.displayGraph(mCamera, mVPMatrix, mPMatrix, mVMatrix);
}

if(sceneMaterial != null) {
sceneMaterial.unbindTextures();
}

synchronized (mPlugins) {
执行Plugins的render方法
}

if(renderTarget != null) {
renderTarget.unbind();
}

// Execute onPostFrame callbacks
// We explicitly break out the steps here to help the compiler optimize
final int postCount = mPostCallbacks.size();
if (postCount > 0) {
执行onPostFrame回调
}
}


回头来看Scane,每个renderer中都包含一个Scene mCurrentScene变量,用来标识当前的场景。下面我们就来看一下Scene中做了些什么。下面我们先大概看看其中的变量。

首先,有一个renderer的引用,用来记录对应的renderer。

protected Renderer mRenderer;


接下来时四个矩阵,物体绘制时需要用到这些矩阵

protected Matrix4 mVMatrix = new Matrix4();
protected Matrix4 mPMatrix = new Matrix4();
protected Matrix4 mVPMatrix = new Matrix4();
protected Matrix4 mInvVPMatrix = new Matrix4();


之后定义了天空盒和雾霾参数

protected volatile ColorPickerInfo      mPickerInfo;


再之后就是我们在前文中向场景中添加的各种对象的列表

private final List<Object3D> mChildren;
private final List<ASceneFrameCallback> mPreCallbacks;
private final List<ASceneFrameCallback> mPreDrawCallbacks;
private final List<ASceneFrameCallback> mPostCallbacks;
private final List<Animation> mAnimations;
private final List<IRendererPlugin> mPlugins;
private final List<ALight> mLights;


最后当然还少不了摄像机

protected Camera mCamera;


以上是目前能大概知道意思的成员变量,可以推测Scene的主要作用是用于管理和绘制各种模型,结合上文分析我们可以印证renderer中真正执行Opengl绘制的功能,其实是在Scene中。

AFrameTask

AFrameTask是Scane频繁出现的一个类,下面我们看看它的用途。

我们发现在Scene中操作模型,光等对象时,都先New一个AFrameTask,之后返回internalOfferTask(task);

看来要搞清楚Scene的工作,必须先搞明白这个的原理。

AFrameTask的代码很简单,就是一个实现了Runnable的类。

/**
* Adds a task to the frame task queue.
*
* @param task AFrameTask to be added.
* @return boolean True on successful addition to queue.
*/
private boolean internalOfferTask(AFrameTask task) {
synchronized (mFrameTaskQueue) {
return mFrameTaskQueue.offer(task);
}
}


可见这里是用来同步添加物体的。

private void performFrameTasks() {
synchronized (mFrameTaskQueue) {
//Fetch the first task
AFrameTask task = mFrameTaskQueue.poll();
while (task != null) {
task.run();
//Retrieve the next task
task = mFrameTaskQueue.poll();
}
}
}


在这里依次执行表中的每个任务,并删除相应节点。这个方法在render方法的开始调用,也就是说在执行render方法时,先检查有没有新添加的物体或者回到接口等,如果有的话,先同步将它们添加,再做下一步操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: