https://blog.csdn.net/junzia/article/details/73717506
2017年06月25日 14:59:39 湖广午王 阅读数:10582 标签: android opengl es 更多
个人分类: OpenGLES Android 午王
所属专栏: Android OpenGLES
版权声明:欢迎转载,转载请保留文章出处。 https://blog.csdn.net/junzia/article/details/73717506
学习在Android中使用OpenGLES,就不得不提到一个控件:GLSurfaceView。本篇博客将介绍GLSurfaceView的基本使用、GLSurfaceView的源码的简单分析,以及使用GLSurfaceView渲染图像到SurfaceView/TextureView或者PBuffer上等。
GLSurfaceView继承了SurfaceView,实现了SurfaceHolder.Callback2接口。
SurfaceView在View的基础上是创建了独立的Surface,拥有SurfaceHolder来管理它的Surface,渲染的工作可以不再在主线程中做了。可以通过SurfaceHolder得到Canvas,在单独的线程中,利用Canvas绘制需要显示的内容,然后更新到Surface上。
而GLSurfaceView,它主要是在SurfaceView的基础上实现了一个GLThread(EGLContext创建GL环境所在线程即为GL线程),绘制的工作直接通过OpenGL来进行,绘制的内容默认情况下依旧是绘制到SurfaceView所提供的Surface上。
参照GLSurfaceView的实现,我们可以自行创建GL环境,来进行GL渲染。实现自行制定指定载体、后台渲染等功能。
GLSurfaceView作为一个View,实例化上基本和其他View相当。可以选择在xml布局文件中增加,然后在Java代码中取得它的控制权。也可以在Java代码中直接new,然后呈现出来。GLSurfaceView必须加入到一个布局中,才能正确的使用,否则有可能会造成崩溃
,这和GLSurfaceView的attachToWindow和detachFromWindow中相关操作。
GLSurfaceView具有onResume和onPause两个同Activity及Fragment中的生命周期同名的方法。一般来说,在Activity或者Fragment中的onResume和onPause方法中,需要主动调用GLSurfaceView的实例的这两个方法。
GLSurfaceView的基本使用代码如下:
mGLView= (GLSurfaceView) findViewById(R.id.mGLView);
//GLContext设置为OpenGLES2.0
mGLView.setEGLContextClientVersion(2);
//在setRenderer之前,可以调用以下方法来进行EGL设置
//mGLView.setEGLConfigChooser(); //颜色、深度、模板等等设置
//mGLView.setEGLWindowSurfaceFactory(); //窗口设置
//mGLView.setEGLContextFactory(); //EGLContext设置
//设置渲染器,渲染主要就是由渲染器来决定
mGLView.setRenderer(new GLSurfaceView.Renderer(){
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//todo surface被创建后需要做的处理
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//todo 渲染窗口大小发生改变的处理
}
@Override
public void onDrawFrame(GL10 gl) {
//todo 执行渲染工作
}
});
/*渲染方式,RENDERMODE_WHEN_DIRTY表示被动渲染,只有在调用requestRender或者onResume等方法时才会进行渲染。RENDERMODE_CONTINUOUSLY表示持续渲染*/
mGLView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
GLSurfaceView中,对于GL环境的操作,出queueEvent是将事件放入队列中,到GL线程中执行外,其他方法基本都是在主线程(也可以是其他线程,非当前GLSurfaceView实例的GL线程)中修改某个状态值,然后取消GL线程的等待,在GL线程中根据状态值作相应的操作,并在操作后反馈给调用方法的那个线程,当然有的方法也不需要反馈。
GLSurfaceView主要方法及中文注释如下:
public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 {
public final static int RENDERMODE_WHEN_DIRTY = 0;
public final static int RENDERMODE_CONTINUOUSLY = 1;
public GLSurfaceView(Context context) {
super(context);
init();
}
public GLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
//调试用的
public void setDebugFlags(int debugFlags);
public int getDebugFlags();
//设置暂停的时候是否保持EglContext
public void setPreserveEGLContextOnPause(boolean preserveOnPause);
public boolean getPreserveEGLContextOnPause();
//设置渲染器,这个非常重要,渲染工作就依靠渲染器了
//调用此方法会开启一个新的线程,即GL线程
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
//设置EGLContext工厂,不设置就用默认的
public void setEGLContextFactory(EGLContextFactory factory);
//设置EGLSurface工厂,不设置就用默认的
public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory);
//设置EglConfig,一般颜色深度等等,利用此方法设置。不设置就用默认的
public void setEGLConfigChooser(EGLConfigChooser configChooser);
//内部调用setEGLConfigChooser
public void setEGLConfigChooser(boolean needDepth);
//内部调用setEGLConfigChooser
public void setEGLConfigChooser(int redSize, int greenSize, int blueSize,
int alphaSize, int depthSize, int stencilSize);
//设置EGLContextVersion,比如2,即OpenGLES2.0
public void setEGLContextClientVersion(int version);
//设置渲染方式,有RENDERMODE_CONTINUOUSLY表示不断渲染
//以及RENDERMODE_WHEN_DIRTY表示在需要的时候才会渲染
//渲染的时候要求调用requestRender,必须在setRenderer后调用
public void setRenderMode(int renderMode);
public int getRenderMode();
//主动请求渲染
public void requestRender();
public void surfaceCreated(SurfaceHolder holder);
public void surfaceDestroyed(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h);
@Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
if (mGLThread != null) {
mGLThread.requestRenderAndWait();
}
}
//生命周期,一般在Activity、Fragment的onPause中调用
public void onPause();
//生命周期,一般在Activity、Fragment的onResume中调用
public void onResume();
//向GL线程发送一个任务
public void queueEvent(Runnable r);
//附加到Window上时被调用,外部不可调用
protected void onAttachedToWindow();
//从Window上被移除时调用,外部不可调用
protected void onDetachedFromWindow();
//渲染器接口
public interface Renderer {
//Surface被创建时被调用,通常在此进行渲染的初始化
void onSurfaceCreated(GL10 gl, EGLConfig config);
//Surface大小被改变时被调用
void onSurfaceChanged(GL10 gl, int width, int height);
//执行渲染时被调用,以完成用户渲染工作
void onDrawFrame(GL10 gl);
}
//非常重要的一个EGL帮助类,GL环境的建立依靠此类
private static class EglHelper {
public EglHelper(WeakReference<GLSurfaceView> glSurfaceViewWeakRef) {
mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
}
//EGL的初始化,可以参考此方法
public void start() {
if (LOG_EGL) {
Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
}
/*
* Get an EGL instance
*/
mEgl = (EGL10) EGLContext.getEGL();
/*
* Get to the default display.
*/
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
/*
* We can now initialize EGL for that display
*/
int[] version = new int[2];
if(!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view == null) {
mEglConfig = null;
mEglContext = null;
} else {
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
/*
* Create an EGL context. We want to do this as rarely as we can, because an
* EGL context is a somewhat heavy object.
*/
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
}
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
mEglContext = null;
throwEglException("createContext");
}
if (LOG_EGL) {
Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
}
mEglSurface = null;
}
//创建EGLSurface,使GL的渲染,能够渲染到用户指定的Surface
//默认的Surface就是SurfaceHolder的Surface
public boolean createSurface() {
if (LOG_EGL) {
Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId());
}
/*
* Check preconditions.
*/
if (mEgl == null) {
throw new RuntimeException("egl not initialized");
}
if (mEglDisplay == null) {
throw new RuntimeException("eglDisplay not initialized");
}
if (mEglConfig == null) {
throw new RuntimeException("mEglConfig not initialized");
}
/*
* The window size has changed, so we need to create a new
* surface.
*/
destroySurfaceImp();
/*
* Create an EGL surface we can render into.
*/
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
mEglDisplay, mEglConfig, view.getHolder());
} else {
mEglSurface = null;
}
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
int error = mEgl.eglGetError();
if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
}
return false;
}
/*
* Before we can issue GL commands, we need to make sure
* the context is current and bound to a surface.
*/
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
/*
* Could not make the context current, probably because the underlying
* SurfaceView surface has been destroyed.
*/
logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
return false;
}
return true;
}
//通过EGL得到GL,然后用户设置了Wrapper的话会给得到的GL做个包装
//同时也会解析一下用户的Debug意图,看看要不要debug
GL createGL();
//绘制完成之后,调用此方法,将绘制的内容输出到前台,让用户可以看到
public int swap();
//销毁Surface的方法,具体实现在destroySurfaceImp方法中
public void destroySurface();
//销毁GL环境
public void finish();
}
//GL线程,此类中存在的方法,GLSurfaceView中有同名的,
//基本都是提供给GLSurfaceView作为真正的实现调用
static class GLThread extends Thread {
//销毁EglSurface
private void stopEglSurfaceLocked();
//销毁EglContext
private void stopEglContextLocked();
//GL线程的主要逻辑都在这个方法里面,这个方法比较复杂
//GLSurfaceView的核心就在这个里面了,最后在单独分析这个里面的逻辑
private void guardedRun() throws InterruptedException;
public boolean ableToDraw() {
return mHaveEglContext && mHaveEglSurface && readyToDraw();
}
private boolean readyToDraw() {
return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
&& (mWidth > 0) && (mHeight > 0)
&& (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
}
//设置渲染方法,见GLSurfaceView的setRenderMode
public void setRenderMode(int renderMode);
public int getRenderMode();
//请求一次渲染
public void requestRender();
//请求一次渲染,并等待渲染完成
public void requestRenderAndWait();
//创建Surface
public void surfaceCreated();
//销毁Surface
public void surfaceDestroyed();
public void onPause();
public void onResume();
//Surface的大小被改变时调用
public void onWindowResize(int w, int h);
//请求退出渲染线程,并等待退出
public void requestExitAndWait();
//请求是否EglContext
public void requestReleaseEglContextLocked();
//向GL线程发送一个任务
public void queueEvent(Runnable r);
}
//debug使用的
static class LogWriter extends Writer {
}
//很多方法都会调用此方法,会检查mGLThread不为null
//即保证调用此方法的方法,必须在setRenderer之前调用
private void checkRenderThreadState();
//主要就是用来做同步用的,利用Object的wait和notifyAll
private static class GLThreadManager {
}
}
渲染的主要逻辑在GLThread的guardedRun()方法中,对guardedRun()方法梳理下。
private void guardedRun() throws InterruptedException {
mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
mHaveEglContext = false;
mHaveEglSurface = false;
mWantRenderNotification = false;
try {
GL10 gl = null;
boolean createEglContext = false;
boolean createEglSurface = false;
boolean createGlInterface = false;
boolean lostEglContext = false;
boolean sizeChanged = false;
boolean wantRenderNotification = false;
boolean doRenderNotification = false;
boolean askedToReleaseEglContext = false;
int w = 0;
int h = 0;
Runnable event = null;
while (true) {
synchronized (sGLThreadManager) {
while (true) {
//外部请求退出GL线程
if (mShouldExit) {
return;
}
/*外部请求在GL线程中处理的事件没有处理完时,就优先处理这些事件*/
if (! mEventQueue.isEmpty()) {
event = mEventQueue.remove(0);
break;
}
//暂停和恢复状态变化时,onResume和onPause状态变化
boolean pausing = false;
if (mPaused != mRequestPaused) {
pausing = mRequestPaused;
mPaused = mRequestPaused;
/*GLSurfaceView的onPause和onResume都会用wait方法等待GL线程的响应,这时候主线程阻塞。此处调用notifyAll通知onPause和onResume,放弃主线程的阻塞。GLSurfaceView中其他很多方法也存在wait方法,基本与此类似
*/
sGLThreadManager.notifyAll();
if (LOG_PAUSE_RESUME) {
Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId());
}
}
// 需要释放EglContext时候执行的工作
if (mShouldReleaseEglContext) {
if (LOG_SURFACE) {
Log.i("GLThread", "releasing EGL context because asked to tid=" + getId());
}
stopEglSurfaceLocked();
stopEglContextLocked();
mShouldReleaseEglContext = false;
askedToReleaseEglContext = true;
}
// EglContext丢失时,销毁EglSurface和EglContext
if (lostEglContext) {
stopEglSurfaceLocked();
stopEglContextLocked();
lostEglContext = false;
}
//接收了暂停信号,而且当前EglSurface存在时,销毁EglSurface
if (pausing && mHaveEglSurface) {
if (LOG_SURFACE) {
Log.i("GLThread", "releasing EGL surface because paused tid=" + getId());
}
stopEglSurfaceLocked();
}
/*接收了暂停信号,而且当前EglContext存在时,根据用户设置,来决定是否销毁EglContext*/
if (pausing && mHaveEglContext) {
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
boolean preserveEglContextOnPause = view == null ?
false : view.mPreserveEGLContextOnPause;
if (!preserveEglContextOnPause) {
stopEglContextLocked();
if (LOG_SURFACE) {
Log.i("GLThread", "releasing EGL context because paused tid=" + getId());
}
}
}
/*Surface不存在(不是EglSurface),而且当前并没有在等待Surface*/
if ((! mHasSurface) && (! mWaitingForSurface)) {
if (LOG_SURFACE) {
Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId());
}
if (mHaveEglSurface) {
stopEglSurfaceLocked();
}
mWaitingForSurface = true;
mSurfaceIsBad = false;
sGLThreadManager.notifyAll();
}
// Surface存在,而且在等待Surface
if (mHasSurface && mWaitingForSurface) {
if (LOG_SURFACE) {
Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId());
}
mWaitingForSurface = false;
sGLThreadManager.notifyAll();
}
if (doRenderNotification) {
if (LOG_SURFACE) {
Log.i("GLThread", "sending render notification tid=" + getId());
}
mWantRenderNotification = false;
doRenderNotification = false;
mRenderComplete = true;
sGLThreadManager.notifyAll();
}
// 当前环境准备好了渲染执行,否则进入下一轮等待及判断
if (readyToDraw()) {
// 没有EglContext就需要借助EglHelper来创建EglContext
if (! mHaveEglContext) {
if (askedToReleaseEglContext) {
askedToReleaseEglContext = false;
} else {
try {
mEglHelper.start();
} catch (RuntimeException t) {
sGLThreadManager.releaseEglContextLocked(this);
throw t;
}
mHaveEglContext = true;
createEglContext = true;
sGLThreadManager.notifyAll();
}
}
/*有了EglContext,但是没有EglSurface,就需要设置一些状态,以便后续操作*/
if (mHaveEglContext && !mHaveEglSurface) {
mHaveEglSurface = true;
createEglSurface = true;
createGlInterface = true;
sizeChanged = true;
}
/*有eglSurface时,需要判断是否需要执行surface sizechange*/
if (mHaveEglSurface) {
if (mSizeChanged) {
sizeChanged = true;
w = mWidth;
h = mHeight;
mWantRenderNotification = true;
if (LOG_SURFACE) {
Log.i("GLThread",
"noticing that we want render notification tid="
+ getId());
}
// Destroy and recreate the EGL surface.
createEglSurface = true;
mSizeChanged = false;
}
mRequestRender = false;
sGLThreadManager.notifyAll();
if (mWantRenderNotification) {
wantRenderNotification = true;
}
//注意此处break,跳出等待的循环
break;
}
}
// By design, this is the only place in a GLThread thread where we wait().
if (LOG_THREADS) {
Log.i("GLThread", "waiting tid=" + getId()
+ " mHaveEglContext: " + mHaveEglContext
+ " mHaveEglSurface: " + mHaveEglSurface
+ " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface
+ " mPaused: " + mPaused
+ " mHasSurface: " + mHasSurface
+ " mSurfaceIsBad: " + mSurfaceIsBad
+ " mWaitingForSurface: " + mWaitingForSurface
+ " mWidth: " + mWidth
+ " mHeight: " + mHeight
+ " mRequestRender: " + mRequestRender
+ " mRenderMode: " + mRenderMode);
}
sGLThreadManager.wait();
}
}
/*外部请求在GL线程中处理的事件没有处理完时,就优先处理这些事件*/
if (event != null) {
event.run();
event = null;
continue;
}
//后续就是根据上面的判断设置,来执行相应的操作
if (createEglSurface) {
if (LOG_SURFACE) {
Log.w("GLThread", "egl createSurface");
}
//创建EglSurface
if (mEglHelper.createSurface()) {
synchronized(sGLThreadManager) {
mFinishedCreatingEglSurface = true;
sGLThreadManager.notifyAll();
}
} else {
synchronized(sGLThreadManager) {
mFinishedCreatingEglSurface = true;
mSurfaceIsBad = true;
sGLThreadManager.notifyAll();
}
continue;
}
createEglSurface = false;
}
if (createGlInterface) {
gl = (GL10) mEglHelper.createGL();
createGlInterface = false;
}
if (createEglContext) {
if (LOG_RENDERER) {
Log.w("GLThread", "onSurfaceCreated");
}
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSurfaceCreated");
//调用GLSurfaceView设置的renderer的onSurfceCreated方法
view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
createEglContext = false;
}
//surface大小被改变
if (sizeChanged) {
if (LOG_RENDERER) {
Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")");
}
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSurfaceChanged");
view.mRenderer.onSurfaceChanged(gl, w, h);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
sizeChanged = false;
}
if (LOG_RENDERER_DRAW_FRAME) {
Log.w("GLThread", "onDrawFrame tid=" + getId());
}
//每帧绘制
{
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onDrawFrame");
view.mRenderer.onDrawFrame(gl);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
int swapError = mEglHelper.swap();
switch (swapError) {
case EGL10.EGL_SUCCESS:
break;
case EGL11.EGL_CONTEXT_LOST:
if (LOG_SURFACE) {
Log.i("GLThread", "egl context lost tid=" + getId());
}
lostEglContext = true;
break;
default:
EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError);
synchronized(sGLThreadManager) {
mSurfaceIsBad = true;
sGLThreadManager.notifyAll();
}
break;
}
if (wantRenderNotification) {
doRenderNotification = true;
wantRenderNotification = false;
}
}
} finally {
/*
* clean-up everything...
*/
synchronized (sGLThreadManager) {
stopEglSurfaceLocked();
stopEglContextLocked();
}
}
}
根据上面的分析,我们知道,GLSurfaceView有setEGLWindowSurfaceFactory
借助此方法,我们可以将图像渲染到其他的地方,比如我们创建一个如下的自定义GLSurfaceView,就可以将图像渲染到外部指定surface上。但是遗憾的是,在某些手机上,这种方式会失效。
private class GLView extends GLSurfaceView{
public GLView(Context context) {
super(context);
init();
}
private void init(){
getHolder().addCallback(null);
setEGLWindowSurfaceFactory(new GLSurfaceView.EGLWindowSurfaceFactory() {
@Override
public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig
config, Object window) {
return egl.eglCreateWindowSurface(display,config,surface,null);
}
@Override
public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
egl.eglDestroySurface(display, surface);
}
});
setEGLContextClientVersion(2);
setRenderer(TextureController.this);
setRenderMode(RENDERMODE_WHEN_DIRTY);
setPreserveEGLContextOnPause(true);
}
public void attachedToWindow(){
super.onAttachedToWindow();
}
public void detachedFromWindow(){
super.onDetachedFromWindow();
}
}
那么如何办呢?我们可以新建一个类GLEnvironment,将GLSurfaceView内容全部复制出来,然后取消其继承和接口实现,将所有报错的代码删除掉,这样就相当于剔除了GLSurfaceView的SurfaceView而保留了它的GL环境,我们可以使用GLEnvironment来进行渲染,并自由的指定渲染载体,可以是SurfaceView/TextureView或Pbuffer,也可以是Pixmap。
同样是利用setEGLWindowSurfaceFactory
方法来设置,当然可以改个名字更为贴切,比如setEGLSurfaceFactory,如下:
mEnv.setEGLSurfaceFactory(new GLEnvironment.EGLSurfaceFactory() {
@Override
public EGLSurface createSurface(EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) {
/*使用SurfaceView或者TextureView,customWindow可以为SurfaceTexture\SurfaceHolder或者Surface等*/
egl.eglCreateWindowSurface(display,config,customWindow,null);
//使用pbuffer
//reture egl.eglCreatePbufferSurface();
//使用pixmap
//return egl.eglCreatePixmapSurface();
}
@Override
public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
egl.eglDestroySurface(display,surface);
}
});
文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大
文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码
文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版
文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗
文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程
文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0
文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader
文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型
文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写
文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录
文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点
文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文