上一篇文章分析了Cardboard SDK的生命周期设计。
这里我们看下畸变部分的实现。
private static final float[] DEFAULT_COEFFICIENTS = { 250.0F, 50000.0F };
private float[] mCoefficients;
public float distortionFactor(float radius)
{
float rSq = radius * radius;
return 1.0F + mCoefficients[0] * rSq + mCoefficients[1] * rSq * rSq;
}
public float distort(float radius)
{
return radius * distortionFactor(radius);
}
public float distortInverse(float radius)
{
float r0 = radius / 0.9F;
float r1 = radius * 0.9F;
float dr0 = radius - distort(r0);
while (Math.abs(r1 - r0) > 0.0001D) {
float dr1 = radius - distort(r1);
float r2 = r1 - dr1 * ((r1 - r0) / (dr1 - dr0));
r0 = r1;
r1 = r2;
dr0 = dr1;
}
return r1;
}
在CardboardDeviceParams的构造函数中会构造这个Distortion类,并提供了getDistortion()来获取这个Distortion对象。
WindowManager windowManager = (WindowManager)context.getSystemService("window");
DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(metrics);
mXMetersPerPixel = (0.0254F / metrics.xdpi);
mYMetersPerPixel = (0.0254F / metrics.ydpi);
public float getWidthMeters()
{
return mWidth * mXMetersPerPixel;
}
public float getHeightMeters()
{
return mHeight * mYMetersPerPixel;
}
private EyeViewport initViewportForEye(EyeParams eye, float xOffsetM)
{
//获取屏幕属性
ScreenParams screen = mHmd.getScreen();
//获取Cardboard设备属性
CardboardDeviceParams cdp = mHmd.getCardboard();
//计算出眼睛到屏幕的距离0.011+0.037
float eyeToScreenDistanceM = cdp.getEyeToLensDistance() + cdp.getScreenToLensDistance();
//根据视场角算出人眼可见的屏幕区域
float leftM = (float)Math.tan(Math.toRadians(eye.getFov().getLeft())) * eyeToScreenDistanceM;
float rightM = (float)Math.tan(Math.toRadians(eye.getFov().getRight())) * eyeToScreenDistanceM;
float bottomM = (float)Math.tan(Math.toRadians(eye.getFov().getBottom())) * eyeToScreenDistanceM;
float topM = (float)Math.tan(Math.toRadians(eye.getFov().getTop())) * eyeToScreenDistanceM;
EyeViewport vp = new EyeViewport();
//视场偏移量
vp.x = xOffsetM;
vp.y = 0.0F;
//视场的宽
vp.width = (leftM + rightM);
//视场高
vp.height = (bottomM + topM);
//视场左上角坐标
vp.eyeX = (leftM + xOffsetM);
vp.eyeY = bottomM;
//屏幕横向像素数2560/以米为单位的屏幕宽度0.13155563,得到每米像素数19459.447
float xPxPerM = screen.getWidth() / screen.getWidthMeters();
//屏幕纵向像素数1440/以米为单位的屏幕高度0.07425001,得到每米像素数19393.936
float yPxPerM = screen.getHeight() / screen.getHeightMeters();
//最终算出视场左上角的像素坐标和像素宽高
eye.getViewport().x = Math.round(vp.x * xPxPerM);
eye.getViewport().y = Math.round(vp.y * xPxPerM);
eye.getViewport().width = Math.round(vp.width * xPxPerM);
eye.getViewport().height = Math.round(vp.height * xPxPerM);
return vp;
}
public DistortionMesh(EyeParams eye, Distortion distortion, float screenWidthM, float screenHeightM, float xEyeOffsetMScreen, float yEyeOffsetMScreen, float textureWidthM, float textureHeightM, float xEyeOffsetMTexture, float yEyeOffsetMTexture, float viewportXMTexture, float viewportYMTexture, float viewportWidthMTexture, float viewportHeightMTexture)
{
float mPerUScreen = screenWidthM;
float mPerVScreen = screenHeightM;
float mPerUTexture = textureWidthM;
float mPerVTexture = textureHeightM;
float[] vertexData = new float[8000];
int vertexOffset = 0;
Log.d(TAG,"screenWidthM="+screenWidthM+"\nscreenHeightM="+screenHeightM+"\nxEyeOffsetMScreen="+xEyeOffsetMScreen+"\nyEyeOffsetMScreen="+yEyeOffsetMScreen+"\ntextureWidthM="+textureWidthM+"\ntextureHeightM="+textureHeightM+"\nxEyeOffsetMTexture="+xEyeOffsetMTexture+"\nyEyeOffsetMTexture="+yEyeOffsetMTexture+"\nviewportXMTexture="+viewportXMTexture+"\nviewportYMTexture="+viewportYMTexture+"\nviewportWidthMTexture="+viewportWidthMTexture+"\nviewportHeightMTexture="+viewportHeightMTexture);
for (int row = 0; row < 40; row++) {
for (int col = 0; col < 40; col++)
{
float uTexture = col / 39.0F * (viewportWidthMTexture / textureWidthM) + viewportXMTexture / textureWidthM;
float vTexture = row / 39.0F * (viewportHeightMTexture / textureHeightM) + viewportYMTexture / textureHeightM;
float xTexture = uTexture * mPerUTexture;
float yTexture = vTexture * mPerVTexture;
float xTextureEye = xTexture - xEyeOffsetMTexture;
float yTextureEye = yTexture - yEyeOffsetMTexture;
float rTexture = (float)Math.sqrt(xTextureEye * xTextureEye + yTextureEye * yTextureEye);
float textureToScreen = rTexture > 0.0F ? distortion.distortInverse(rTexture) / rTexture : 1.0F;
float xScreen = xTextureEye * textureToScreen + xEyeOffsetMScreen;
float yScreen = yTextureEye * textureToScreen + yEyeOffsetMScreen;
float uScreen = xScreen / mPerUScreen;
float vScreen = yScreen / mPerVScreen;
float vignetteSizeMTexture = 0.002F / textureToScreen;
float dxTexture = xTexture - DistortionRenderer.clamp(xTexture, viewportXMTexture + vignetteSizeMTexture, viewportXMTexture + viewportWidthMTexture - vignetteSizeMTexture);
float dyTexture = yTexture - DistortionRenderer.clamp(yTexture, viewportYMTexture + vignetteSizeMTexture, viewportYMTexture + viewportHeightMTexture - vignetteSizeMTexture);
float drTexture = (float)Math.sqrt(dxTexture * dxTexture + dyTexture * dyTexture);
float vignette = 1.0F - DistortionRenderer.clamp(drTexture / vignetteSizeMTexture, 0.0F, 1.0F);
vertexData[(vertexOffset + 0)] = (2.0F * uScreen - 1.0F);
vertexData[(vertexOffset + 1)] = (2.0F * vScreen - 1.0F);
vertexData[(vertexOffset + 2)] = vignette;
vertexData[(vertexOffset + 3)] = uTexture;
vertexData[(vertexOffset + 4)] = vTexture;
vertexOffset += 5;
}
}
nIndices = 3158;
int[] indexData = new int[nIndices];
int indexOffset = 0;
vertexOffset = 0;
for (int row = 0; row < 39; row++) {
if (row > 0) {
indexData[indexOffset] = indexData[(indexOffset - 1)];
indexOffset++;
}
for (int col = 0; col < 40; col++) {
if (col > 0) {
if (row % 2 == 0)
{
vertexOffset++;
}
else {
vertexOffset--;
}
}
indexData[(indexOffset++)] = vertexOffset;
indexData[(indexOffset++)] = (vertexOffset + 40);
}
vertexOffset += 40;
}
FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuffer.put(vertexData).position(0);
IntBuffer indexBuffer = ByteBuffer.allocateDirect(indexData.length * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
indexBuffer.put(indexData).position(0);
int[] bufferIds = new int[2];
GLES20.glGenBuffers(2, bufferIds, 0);
mArrayBufferId = bufferIds[0];
mElementBufferId = bufferIds[1];
GLES20.glBindBuffer(34962, mArrayBufferId);
GLES20.glBufferData(34962, vertexData.length * 4, vertexBuffer, 35044);
GLES20.glBindBuffer(34963, mElementBufferId);
GLES20.glBufferData(34963, indexData.length * 4, indexBuffer, 35044);
GLES20.glBindBuffer(34962, 0);
GLES20.glBindBuffer(34963, 0);
}
private int createTexture(int width, int height) {
int[] textureIds = new int[1];
GLES20.glGenTextures(1, textureIds, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, width, height, 0,GLES20.GL_RGB, GLES20.GL_UNSIGNED_SHORT_5_6_5, null);
return textureIds[0];
}
private int setupRenderTextureAndRenderbuffer(int width, int height) {
if (mTextureId != -1) {
GLES20.glDeleteTextures(1, new int[] { mTextureId }, 0);
}
if (mRenderbufferId != -1) {
GLES20.glDeleteRenderbuffers(1, new int[] { mRenderbufferId }, 0);
}
if (mFramebufferId != -1) {
GLES20.glDeleteFramebuffers(1, new int[] { mFramebufferId }, 0);
}
mTextureId = createTexture(width, height);
checkGlError("setupRenderTextureAndRenderbuffer: create texture");
int[] renderbufferIds = new int[1];
GLES20.glGenRenderbuffers(1, renderbufferIds, 0);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderbufferIds[0]);
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height);
mRenderbufferId = renderbufferIds[0];
checkGlError("setupRenderTextureAndRenderbuffer: create renderbuffer");
int[] framebufferIds = new int[1];
GLES20.glGenFramebuffers(1, framebufferIds, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferIds[0]);
mFramebufferId = framebufferIds[0];
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, mTextureId, 0);
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER,
renderbufferIds[0]);
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
throw new RuntimeException("Framebuffer is not complete: "
+ Integer.toHexString(status));
}
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
return framebufferIds[0];
}
public void beforeDrawFrame()
{
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, mOriginalFramebufferId);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebufferId);
}
public void afterDrawFrame()
{
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mOriginalFramebufferId.array()[0]);
GLES20.glViewport(0, 0, mHmd.getScreen().getWidth(), mHmd.getScreen().getHeight());
GLES20.glGetIntegerv(GLES20.GL_VIEWPORT, mViewport);
GLES20.glGetIntegerv(GLES20.GL_CULL_FACE, mCullFaceEnabled);
GLES20.glGetIntegerv(GLES20.GL_SCISSOR_TEST, mScissorTestEnabled);
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
GLES20.glDisable(GLES20.GL_CULL_FACE);
GLES20.glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(mProgramHolder.program);
GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
GLES20.glScissor(0, 0, mHmd.getScreen().getWidth() / 2, mHmd.getScreen().getHeight());
renderDistortionMesh(mLeftEyeDistortionMesh);
GLES20.glScissor(mHmd.getScreen().getWidth() / 2, 0, mHmd.getScreen().getWidth() / 2, mHmd.getScreen().getHeight());
renderDistortionMesh(mRightEyeDistortionMesh);
GLES20.glDisableVertexAttribArray(mProgramHolder.aPosition);
GLES20.glDisableVertexAttribArray(mProgramHolder.aVignette);
GLES20.glDisableVertexAttribArray(mProgramHolder.aTextureCoord);
GLES20.glUseProgram(0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
if (mCullFaceEnabled.array()[0] == 1) {
GLES20.glEnable(GLES20.GL_CULL_FACE);
}
if (mScissorTestEnabled.array()[0] == 1) {
GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
}
GLES20.glViewport(mViewport.array()[0], mViewport.array()[1], mViewport.array()[2], mViewport.array()[3]);
}
public void onProjectionChanged(HeadMountedDisplay hmd, EyeParams leftEye, EyeParams rightEye, float zNear, float zFar)
{
mHmd = new HeadMountedDisplay(hmd);
mLeftEyeFov = new FieldOfView(leftEye.getFov());
mRightEyeFov = new FieldOfView(rightEye.getFov());
ScreenParams screen = mHmd.getScreen();
CardboardDeviceParams cdp = mHmd.getCardboard();
if (mProgramHolder == null) {
mProgramHolder = createProgramHolder();
}
EyeViewport leftEyeViewport = initViewportForEye(leftEye, 0.0F);
EyeViewport rightEyeViewport = initViewportForEye(rightEye, leftEyeViewport.width);
leftEye.getFov().toPerspectiveMatrix(zNear, zFar, leftEye.getTransform().getPerspective(), 0);
rightEye.getFov().toPerspectiveMatrix(zNear, zFar, rightEye.getTransform().getPerspective(), 0);
float textureWidthM = leftEyeViewport.width + rightEyeViewport.width;
float textureHeightM = Math.max(leftEyeViewport.height, rightEyeViewport.height);
float xPxPerM = screen.getWidth() / screen.getWidthMeters();
float yPxPerM = screen.getHeight() / screen.getHeightMeters();
int textureWidthPx = Math.round(textureWidthM * xPxPerM);
int textureHeightPx = Math.round(textureHeightM * yPxPerM);
float xEyeOffsetMScreen = screen.getWidthMeters() / 2.0F - cdp.getInterpupillaryDistance() / 2.0F;
float yEyeOffsetMScreen = cdp.getVerticalDistanceToLensCenter() - screen.getBorderSizeMeters();
mLeftEyeDistortionMesh = createDistortionMesh(leftEye, leftEyeViewport, textureWidthM, textureHeightM, xEyeOffsetMScreen, yEyeOffsetMScreen);
xEyeOffsetMScreen = screen.getWidthMeters() - xEyeOffsetMScreen;
mRightEyeDistortionMesh = createDistortionMesh(rightEye, rightEyeViewport, textureWidthM, textureHeightM, xEyeOffsetMScreen, yEyeOffsetMScreen);
setupRenderTextureAndRenderbuffer(textureWidthPx, textureHeightPx);
}