Android:OpenGL笔记

来源:互联网 发布:软件规格型号怎么写 编辑:程序博客网 时间:2024/04/28 17:07
一、流程
1.自定义类继承GLSurfaceView,在构造方法内:
this.setEGLContextClientVersion(2);                    //设置使用OPENGL ES2.0
mRenderer = new SceneRenderer();              //自定义场景渲染器
setRenderer(mRenderer);      //设置渲染器为自已实现的场景渲染器       
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);  //设置渲染模式为主动渲染   

2.自定场景渲染器,实现GLSurfaceView.Renderer中的onSurfaceCreated、onSurfaceChanged、onDrawFrame方法

onSurfaceCreated重写:

//设置屏幕背景色RGBA

GLES20.glClearColor(0.5f,0.5f,0.5f, 1.0f);

//创建3D物体对象

(1)初始化顶点数据:
// 创建顶点坐标数据缓冲
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());// 设置字节顺序
mVertexBuffer = vbb.asFloatBuffer();// 转换为Float型缓冲
mVertexBuffer.put(vertices);// 向缓冲区中放入顶点坐标数据
mVertexBuffer.position(0);// 设置缓冲区起始位置

// 创建顶点着色数据缓冲

ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
cbb.order(ByteOrder.nativeOrder());// 设置字节顺序
mColorBuffer = cbb.asFloatBuffer();// 转换为Float型缓冲
mColorBuffer.put(colors);// 向缓冲区中放入顶点着色数据
mColorBuffer.position(0);// 设置缓冲区起始位置

(2)加载顶点与片元着色脚本:

// 加载顶点着色器的脚本内容
mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
// 加载片元着色器的脚本内容
mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
// 基于顶点着色器与片元着色器创建程序
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
// 获取程序中顶点位置属性引用id
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
// 获取程序中顶点颜色属性引用id
maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
// 获取程序中总变换矩阵引用id
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

//打开深度检测

GLES20.glEnable(GLES20.GL_DEPTH_TEST);
//打开背面剪裁
GLES20.glEnable(GLES20.GL_CULL_FACE);

onSurfaceChanged重写:

//设置视窗大小及位置

GLES20.glViewport(0, 0, width, height);
//计算GLSurfaceView的宽高比
float ratio = (float) width / height;
//产生正交或透视投影矩阵
//正交投影
Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far);
//透视投影
Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
//产生摄像机9参数位置矩阵
Matrix.setLookAtm(mVMatrix, 0, cx, cy, cz, tx, ty, tz, upx, upy, upz);
//初始化变换矩阵
MatrixState.setInitStack();

onDrawFrame重写:
//清除深度缓冲与颜色缓冲
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
//绘制3D物体
// 制定使用某套shader程序
GLES20.glUseProgram(mProgram);
// 将最终变换矩阵传入shader程序
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
// 为画笔指定顶点位置数据
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer);
// 为画笔指定顶点着色数据
GLES20.glVertexAttribPointer(maColorHandle, 4, GLES20.GL_FLOAT, false, 4 * 4, mColorBuffer);
// 允许顶点位置数据数组
GLES20.glEnableVertexAttribArray(maPositionHandle);
GLES20.glEnableVertexAttribArray(maColorHandle);
// 绘制图形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);

GLES20.glDrawElements(GLES20.GL_TRIANGLE_FAN, iCount, GLES20.GL_UNSIGNED_BYTE, mIndexBuffer);


二、API说明
1.设置视窗大小及位置

GLES20.glViewport(x, y, width, height);
x、y:视口矩形左上侧点在屏幕坐标系内的坐标
width、height:视口的宽度与高度

2.正交投影,近面与远面物来一样大小:
Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far);
mProjMatrix:存储生成矩阵元素的float[]类型数组
0:填充起始偏移量
left、right、bottom、top:属于near面,中心点到左边距离为left,到右边距离为right,到上边距为top,到下边距为bottom
near:视点到near面的距离
far:视点到far面的距离

3.透视投影,近面比远面的物体体要小,成锥形:
Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
mProjMatrix:存储生成矩阵元素的float[]类型数组
0:填充起始偏移量
left、right、bottom、top:属于near面,中心点到左边距离为left,到右边距离为right,到上边距为top,到下边距为bottom
near:视点到near面的距离
far:视点到far面的距离

4.设置摄像机位置:
Matrix.setLookAtm(mVMatrix, 0, cx, cy, cz, tx, ty, tz, upx, upy, upz);
mVMatrix:存储生成矩阵元素的float[]类型数组
0:填充起始偏移量
cx、cy、cz:摄像机位置的X、Y、Z坐标
tx、ty、tz:观察目标点的X、Y、Z坐标
upx、upy、upz:up向量在X、Y、Z轴上的分量

5.让坐标轴平移
Matrix.translateM(currMatrix, 0, x, y, z)
currMatrix:存储生成矩阵元素的float[]类型数组
0:填充起始偏移量
x、y、z:让原坐标轴沿x、y、z轴平移一定的位置

6.让坐标轴旋转
Matrix.rotateM(currMatrix, 0, angle, x, y, z)
currMatrix:存储生成矩阵元素的float[]类型数组
0:填充起始偏移量
angle:要旋转的角度
x、y、z:旋转轴对应的X、Y、Z坐标值分量

7.让坐标轴缩放
Matrix.scaleM(currMatrix, 0, x, y, z)
currMatrix:存储生成矩阵元素的float[]类型数组
0:填充起始偏移量
x、y、z:float类型倍数值,让原坐标轴沿x、y、z轴分别按不同的倍数进行缩放

8.绘制方式
顶点法:
GLES20.glDrawArrays(绘制方式, 起始位置, 顶点数);
此方法按照传入的顶点本身顺序以及选用的绘制方式将顶点组成图元,会有重复的顶点

索引法:
GLES20.glDrawArrays(绘制方式, 顶点数,类别,索引数组缓冲);
此方法按照索引序列排序,需要传入顶点以及索引数组

GLES20.GL_TRIANGLES

9.是否打开背面剪裁
GLES20.glEnable(GLES20.GL_CULL_FACE);//打开背面剪裁,位于摄像机背面的将不会绘制,效率高。
GLES20.glDisable(GLES20.GL_CULL_FACE);//关闭背面剪裁,所有面都绘制,效率低。

10.设置哪个面为正面
GLES20.glFrontFace(GLES20.GL_CCW);//设置逆时针卷绕为正面,默认,不用设置

GLES20.glFrontFace(GLES20.GL_CW);//设置顺时针卷绕为正面


三、纹理

1.流程
(1)在onSurfaceCreated中初始化纹理:
//生成纹理ID
int[] textures = new int[1];
GLES20.glGenTextures(1,     //产生的纹理id的数量
textures,   //纹理id的数组
0           //偏移量
                );    
textureId=textures[0];
//绑定纹理ID   
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
//设置MIN采样方式
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
//设置MAG采样方式
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
//设置S轴拉伸方式
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
//设置T轴拉伸方式
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
//通过输入流加载图片
InputStream is = this.getResources().openRawResource(R.drawable.wall);
Bitmap bmp = BitmapFactory.decodeStream(is);
is.close();
//实际加载纹理
GLUtils.texImage2D(
        GLES20.GL_TEXTURE_2D,   //纹理类型,在OpenGL ES中必须为GL10.GL_TEXTURE_2D
        0,       //纹理的层次,0表示基本图像层,可以理解为直接贴图
        bmp,       //纹理图像的Bitmap
        0//纹理边框尺寸
        );
bmp.recycle();                //纹理加载成功后释放图片

(2)初始化顶点数据时,进行顶点纹理坐标数据的初始化:
float texCoor[]=new float[]//顶点颜色值数组,每个顶点4个色彩值RGBA{0.5f,0,   0,1,   1,1};        
//创建顶点纹理坐标数据缓冲
ByteBuffer cbb = ByteBuffer.allocateDirect(texCoor.length*4);
cbb.order(ByteOrder.nativeOrder());//设置字节顺序
FloatBuffer mTexCoorBuffer = cbb.asFloatBuffer();//转换为Float型缓冲
mTexCoorBuffer.put(texCoor);//向缓冲区中放入顶点着色数据
mTexCoorBuffer.position(0);//设置缓冲区起始位置

(3)初始化着色器:
//获取程序中顶点纹理坐标属性引用id  
int maTexCoorHandle= GLES20.glGetAttribLocation(mProgram, "aTexCoor");

(4)绑定纹理并绘制物体:
//将顶点纹理坐标数据传送进渲染管线
GLES20.glVertexAttribPointer (maTexCoorHandle,  2,   GLES20.GL_FLOAT,  false, 2*4,    mTexCoorBuffer);
//启用顶点纹理坐标数据
GLES20.glEnableVertexAttribArray(maTexCoorHandle);
//绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
//绘制物体
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount); 

(5)在着色文件中加入:
顶点着色文件vertex.sh:
attribute vec2 aTexCoor;     //顶点纹理坐标
varying vec2 vTextureCoord;  //用于传递给片元着色器的变量
void main()     
{
     vTextureCoord = aTexCoor;//将接收的纹理坐标传递给片元着色器
}

片元着色文件frag.sh:
varying vec2 vTextureCoord; //接收从顶点着色器过来的参数
uniform sampler2D sTexture;//纹理内容数据,就是GLES20.glGenTextures(1, textures,0)绑定的纹理内容
void main()                         
{           
   //使用texture2D内建方法从采样器中进行纹理的采样,得到颜色值赋给gl_FragColor,完成片元的着色
   gl_FragColor = texture2D(sTexture, vTextureCoord); 
}

2.纹理拉伸
(1)重复拉伸方式:
//设置S轴的拉伸方式为重复
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_REPEAT);
//设置T轴的拉伸方式为重复
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_REPEAT);
(2)截取拉伸方式:
//设置S轴的拉伸方式为截取
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
//设置T轴的拉伸方式为截取
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);

3.纹理采样
(1)最近点采样:
//设置MIN时为最近点采样
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
//设置MAG时为最近点采样
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

(2)线性采样:
//设置MIN时为线性采样
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
//设置MAG时为线性采样
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
(3)MIN与MAG采样含义:
MIN:大纹理图映射到小图元时采用
MAG:小纹理图映射到大图元时采用

4.mipmap纹理技术
//设置MIN情况为mipmap最近点采样
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_NEAREST);
//设置MAG情况为mipmap线性采样
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
//自动生成mipmap系列纹理
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D)

5.多重纹理与过程纹理
多重纹理:一个图元采用多张纹理图

过程纹理:多重纹理边界根据某种规则进行平滑过渡


四、混合与雾技术

1.混合
(1)
//onDrawFrame方法在进行开启混合
GLES20.glEnable(GLES20.GL_BLEND);
//设置混合因子
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); //实现半透明遮挡效果,先绘远物再开启绘近物

GLES20.glBlendFunc(GLES20.GL_SRC_COLOR, GLES20.GL_ONE_MINUS_SRC_COLOR); //实现滤光镜效果,先绘远物再开启绘近物
//绘制纹理

//关闭混合
GLES20.glDisable(GLES20.GL_BLEND);

(2)修改片元着色器frag.sh
//给此片元从纹理中采样出颜色值            
vec4 finalColor = texture2D(sTexture, vTextureCoord); 
//根据颜色值计算透明度
finalColor.a=(finalColor.r+finalColor.g+finalColor.b)/3.0;
//计算光照因素
finalColor=finalColor*ambient+finalColor*specular+finalColor*diffuse;
//给此片元颜色值 
gl_FragColor = finalColor;

2.雾
(1)雾化公式
f:雾化因子,取值为0.0-1.0,0表示只看见雾,1表示只看见物体
dist:片元到摄像机距离
end:片元到摄像机距离大于end时,f雾化因子为0
start:片元到摄像机距离小于start时,f雾化因子为1
smoothstep为连续函数,在0-1之间进行平滑过渡
//线性雾公式
f = max( min( (end-dist)/(end-start), 1.0 ), 0.0 );
//非线性雾公式
f = 1.0 - smoothstep(start, end, dist);

(2)顶点着色器vertex.sh
uniform mat4 uMMatrix;   //变换矩阵
uniform vec3 uCamera;  //摄像机位置
attribute vec3 aPosition;  //顶点位置
varying float vFogFactor;  //用于传递给片元着色器的雾化因子
//计算雾因子的方法
float computeFogFactor(){
   float tmpFactor;
   float fogDistance = length(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);//顶点到摄像机的距离
   const float end = 450.0;//雾结束位置
   const float start = 350.0;//雾开始位置
   tmpFactor = max(min((end- fogDistance)/(end-start),1.0),0.0);//用线性雾公式计算雾化因子
   或
   tmpFactor = 1.0 - smoothstep(start, end, fogDistance);       //用非线性雾公式计算雾化因子  
   return tmpFactor;
}
void main()     
{
   //计算雾因子
   vFogFactor = computeFogFactor();
}

(3)片元着色器frag.sh
precision mediump float;
varying vec4 ambient;     //环境光最终强度
varying vec4 diffuse;     //散射光最终强度
varying vec4 specular;    //镜面光最终强度
varying float vFogFactor; //雾化因子
void main()                         
{
vec4 objectColor=vec4(0.95,0.95,0.95,1.0);//物体颜色
vec4 fogColor = vec4(0.97,0.76,0.03,1.0);//雾的颜色
 if(vFogFactor != 0.0){//如果雾因子为0,不必计算光照
objectColor = objectColor*ambient+objectColor*specular+objectColor*diffuse;//计算光照之后物体颜色
gl_FragColor = objectColor*vFogFactor + fogColor*(1.0-vFogFactor);//物体颜色和雾颜色插值计算最终颜色
}else{
    gl_FragColor=fogColor;
 }

}


五、常用3D开发技巧

1.标志板
就是使用纹理矩形绘制,采用GL_TRIANGLES只需6个顶点,只绘制一个平面,朝向实时变更,正面永远对着摄像机

2.灰度图地形
用N*N的网络表示地形,同时提供一幅对应尺寸的灰度图,根据灰度图中的灰度来确定网格中顶点的海拔坐标值,黑色像素代表最低位置0,白色像素代表最高位置255,会产生高低不平的山地地形。
实际海拔 = 最低海拔 + 最大高差 *像素值 / 255.0
//从灰度图片中加载陆地上每个顶点的高度
public static final float LAND_HIGH_ADJUST=2f;//陆地的高度调整值
public static final float LAND_HIGHEST=40f;//陆地最大高差
public static float[][] loadLandforms(Resources resources,int index)
{
Bitmap bt=BitmapFactory.decodeResource(resources, index);
int colsPlusOne=bt.getWidth(); 
int rowsPlusOne=bt.getHeight(); 
float[][] result=new float[rowsPlusOne][colsPlusOne];
for(int i=0;i<rowsPlusOne;i++)
{
for(int j=0;j<colsPlusOne;j++)
{
int color=bt.getPixel(j,i);
int r=Color.red(color);
int g=Color.green(color); 
int b=Color.blue(color);
int h=(r+g+b)/3;
result[i][j]=h*LAND_HIGHEST/255-LAND_HIGH_ADJUST;  
}
}
return result;
}

3.过程纹理地形

将山地采用多个纹理贴图,采用多重纹理边界根据某种规则进行平滑过渡

(1)initShader方法中:
//获取草皮纹理引用
sTextureGrassHandle=GLES20.glGetUniformLocation(mProgram, "sTextureGrass");
//获取岩石地纹理引用
sTextureRockHandle=GLES20.glGetUniformLocation(mProgram, "sTextureRock");
//获取过程纹理起始Y坐标引用
landStartYYHandle=GLES20.glGetUniformLocation(mProgram, "landStartY");
//获取过程纹理跨度引用
landYSpanHandle=GLES20.glGetUniformLocation(mProgram, "landYSpan");

(2)drawSelf方法中:
/绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId); //绑定草皮纹理为0号
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rock_textId);  //绑定岩石纹理为1号
GLES20.glUniform1i(sTextureGrassHandle, 0);//使用0号草皮纹理
GLES20.glUniform1i(sTextureRockHandle, 1); //使用1号岩石纹理
//传送过程纹理起始Y坐标
GLES20.glUniform1f(landStartYYHandle, 0);
//传送过程纹理跨度
GLES20.glUniform1f(landYSpanHandle, 30);

(3)顶点着色器:
attribute vec2 aTexCoor;    //顶点纹理坐标
varying vec2 vTextureCoord;  //用于传递给片元着色器的纹理坐标
varying float currY;//用于传递给片元着色器的Y坐标
void main(){  
   vTextureCoord = aTexCoor;//将接收的纹理坐标传递给片元着色器
   currY=aPosition.y;//将顶点的Y坐标传递给片元着色器
}  

(4)片元着色器frag.sh:
precision mediump float;//给出默认的浮点精度
varying vec2 vTextureCoord; //接收从顶点着色器过来的纹理坐标
varying float currY;//接收从顶点着色器过来的Y坐标
uniform sampler2D sTextureGrass;//纹理内容数据(草皮)
uniform sampler2D sTextureRock;//纹理内容数据(岩石)
uniform float landStartY;//过程纹理起始Y坐标
uniform float landYSpan;//过程纹理跨度
void main(){          
   vec4 gColor=texture2D(sTextureGrass, vTextureCoord);//从草皮纹理中采样出颜色
   vec4 rColor=texture2D(sTextureRock, vTextureCoord);       //从岩石纹理中采样出颜色
   vec4 finalColor;//最终颜色
   if(currY<landStartY){
 finalColor=gColor;//当片元Y坐标小于过程纹理起始Y坐标时采用草皮纹理
   }else if(currY>landStartY+landYSpan){
 finalColor=rColor;//当片元Y坐标大于过程纹理起始Y坐标加跨度时采用岩石纹理
   }else{
       float currYRatio=(currY-landStartY)/landYSpan;//计算岩石纹理所占的百分比
       finalColor= currYRatio*rColor+(1.0- currYRatio)*gColor;//将岩石、草皮纹理颜色按比例混合
   } 
   gl_FragColor = finalColor; //给此片元最终颜色值    
}

4.MipMap地形
//使用MipMap线性纹理采样
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
//使用MipMap最近点纹理采样
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_NEAREST);
//自动生成Mipmap纹理
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);

5.天空盒与天空穹

实际开发中仅让天空盒或天空穹只遮住场景一部分,随着摄像机的移动天空盒或天空穹也跟着一起移动
(1)天空盒:

将场景放在立方体内部,立方体每一个面都是一个纹理正方形,绘制6个面并贴纹理图即可,重在设置摄像机参数

(2)天空穹:

将场景放在半球面内部,绘制半球面并进行纹理贴图即可,重在设置摄像机参数

6.镜像技术

(1)onSurfaceCreated中:
//关闭深度检测,因为开启深度检测之后,只有距离摄像机最近处才会被绘制
GLES20.glDisable(GLES20.GL_DEPTH_TEST);

(2)onDrawFrame中:
//绘制不透明反射面地板
//绘制镜像体
//绘制半透明地板
//开启混合
GLES20.glEnable(GLES20.GL_BLEND);
//设置混合因子
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
//绘制半透明地板
//关闭混合
GLES20.glDisable(GLES20.GL_BLEND);  
//绘制实际物体

7.动态文本输出
OpenGL ES20没有提供3D场景中绘制文字的方法,只能生成文本内容的纹理图Bitmap,然后通过纹理矩形呈现出来。
public static Bitmap generateWLT(String[] str,int width,int height)
{
Paint paint=new Paint();
paint.setARGB(255, R, G, B);
paint.setTextSize(textSize);
paint.setTypeface(null);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
Bitmap bmTemp=Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvasTemp = new Canvas(bmTemp);
for(int i=0;i<str.length;i++)
{
canvasTemp.drawText(str[i], 0, textSize*i+(i-1)*5, paint);
}
return bmTemp;
}

8.非真实感绘制

卡通着色主要使用不变的颜色与明显的轮廓对物体进行着色,计算光照,对散射光与镜面光采用特殊处理以得到最终片元的样色


六、几种剪裁与测试

1.剪裁测试
剪裁测试就是在指定矩形内进行副场景的绘制
在onDrawFrame中:
//绘制主视角场景         
//启用剪裁测试
GLES20.glEnable(GL10.GL_SCISSOR_TEST);    
//设置区域
GLES20.glScissor(int x, int y, int width, int height);  //x、y为剪裁区左下角X、Y坐标
//绘制副视角场景
//禁用剪裁测试
GLES20.glDisable(GL10.GL_SCISSOR_TEST);

2.Alpha测试
Alpha测试就是将副场景放在纹理图透明部分显示出来。
检测片元Alpha值,满足则绘制,否则丢弃,可在指定形状窗口内绘场景
(1)onSurfaceCreated中:
//加载一张中间为透明的图片为纹理

(2)onDrawFrame中:
//绘制场景
//绘制矩形并采用上面的纹理图,实现透明部分可看见场景的效果

(3)片元着色器frag.sh:
precision mediump float;
varying vec2 vTextureCoord; //接收从顶点着色器过来的纹理坐标
uniform sampler2D sTexture;//纹理内容数据
void main() { 
   vec4 bcolor = texture2D(sTexture, vTextureCoord);/根据纹理坐标从纹理图中采样出颜色值 
   //若Alpha值小于0.6,将片元丢弃,否则作为片元的颜色   
   if(bcolor.a<0.6) {
      discard; //丢弃当前片元
   } else {
      gl_FragColor=bcolor;
}}

3.模板测试
模板测试就将副场景放在不规则区域内显示,使用模板测试要绘制至少两次,第一次绘制区域,第二次绘制区域内的副场景
在onDrawFrame
//清除模板缓存
GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT);
//允许模板测试
GLES20.glEnable(GLES20.GL_STENCIL_TEST);
//设置模板测试参数,第一个参数为比较模式GL_ALWAYS表示总是通过模板测试,第2个参数1为参考值,第3个参数1为掩码(mask)
GLES20.glStencilFunc(GLES20.GL_ALWAYS, 1, 1);
//设置模板测试后的操作,第一个参数为模板测试未通过时模板值如何变化,第2个参数模板测试通过深度测试未通过该如何变化,第3个参数都通过时该如何变化,GL_REPLACE指替换
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_REPLACE);            
//绘制要显示副场景的不规则平面,比如水面
//设置模板测试参数,第一个参数为比较模式GL_EQUAL表示只有参考值=(模板缓冲区的值&mask)时才通过,第2个参数1为参考值,第3个参数1为掩码(mask)
GLES20.glStencilFunc(GLES20.GL_EQUAL,1, 1); 
//设置模板测试后的操作,第一个参数为模板测试未通过时模板值如何变化,第2个参数模板测试通过深度测试未通过该如何变化,第3个参数都通过时该如何变化。GL_KEEP指不改变
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_KEEP);
//绘制副场景,比如水中倒影
//禁用模板测试
GLES20.glDisable(GLES20.GL_STENCIL_TEST);

4.任意剪裁平面
将剪裁平面四个顶点代入平面方程Ax+By+Cz+D=0进行计算,小于0丢弃片元,大于0显示片元并赋颜色
(1)onSurfaceCreated中:
//获取程序中剪裁平面引用
int muClipHandle=GLES20.glGetUniformLocation(mProgram, "u_clipPlane");

(2)onDrawFrame中:
float clipPane[] = { 1, countE - 1, -countE + 1, 0 };//定义裁剪平面
//将剪裁平面传入shader程序   
GLES20.glUniform4fv(muClipHandle, 1, fromArrayToBuff(clipPane));
public FloatBuffer fromArrayToBuff(float[] a){
    ByteBuffer llbb = ByteBuffer.allocateDirect(a.length*4);
    llbb.order(ByteOrder.nativeOrder());//设置字节顺序
    FloatBuffer result=llbb.asFloatBuffer();
    result.put(a);
    result.position(0);  
    return result;
}

(3)顶点着色器vertex.sh:
attribute vec3 aPosition;  //顶点位置
uniform vec4 u_clipPlane;  //剪裁平面参数
varying float u_clipDist;  //传递给片元着色器的剪裁信息
void main()
{
   //将顶点位置(x0,y0,z0)代入平面方程Ax+By+Cz+D=0,
   //若Ax0+By0+Cz0+D>0,则顶点在平面的上侧,反之在平面的下侧
   u_clipDist = dot(aPosition.xyz, u_clipPlane.xyz) +u_clipPlane.w;
}

(4)片元着色器frag.sh:
varying float u_clipDist;  //接收来自顶点着色器的剪裁信息
void main()
{
   if(u_clipDist < 0.0) discard;  //如果剪裁信息小于0,则将片元丢弃
   //将计算出的颜色给此片元

}


七、物理学

1.碰撞检测基本技术
(1)AABB包围盒,又称轴对齐包围盒
将物体用长方体包围,进行碰撞时检测两个长方体的相交性,每个面都与坐标轴平面平行,物体旋转时得将包围盒也旋转,90度以外的角度旋转误差大,不规则物体包转盒碰撞误差大。
实际物体满足如下条件:
x(min) <= x <= x(max)   y(min) <= y <= y(max)  z(min) <= z <= z(max)
AABB包围盒可分为如下两组:
P(min) = [x(min), y(min), z(min)]   P(max) = [x(max), y(max), z(max)]
AABB包围盒几何中心c:
c = (P(min) + P(max)) / 2
/*
 * 包围盒类
 */
public class AABBBox 
{
float minX;//x轴最小位置
float maxX;//x轴最大位置
float minY;//y轴最小位置
float maxY;//y轴最大位置
float minZ;//z轴最小位置
float maxZ;//z轴最大位置
//初始化包围盒的最小以及最大顶点坐标
public void init()
{
minX=Float.POSITIVE_INFINITY;
maxX=Float.NEGATIVE_INFINITY;
minY=Float.POSITIVE_INFINITY;
maxY=Float.NEGATIVE_INFINITY;
minZ=Float.POSITIVE_INFINITY;
maxZ=Float.NEGATIVE_INFINITY;
}
//获取包围盒的实际最小以及最大顶点坐标
public void findMinAndMax(float[] vertices)
{
    for(int i=0;i<vertices.length/3;i++)
    {
        //判断X轴的最小和最大位置
        if(vertices[i*3]<minX)
        {
            minX=vertices[i*3];
        }
        if(vertices[i*3]>maxX)
        {
            maxX=vertices[i*3];
        }
        //判断Y轴的最小和最大位置
        if(vertices[i*3+1]<minY)
        {
            minY=vertices[i*3+1];
        }
        if(vertices[i*3+1]>maxY)
        {
            maxY=vertices[i*3+1];
        }
        //判断Z轴的最小和最大位置
        if(vertices[i*3+2]<minZ)
        {
            minZ=vertices[i*3+2];
        }
        if(vertices[i*3+2]>maxZ)
        {
            maxZ=vertices[i*3+2];
        }
    }
}
//获得物体平移旋转后的AABB包围盒
public AABBBox getCurrAABBBox(Vector3f currPosition,Orientation currOrientation)
{
    //先计算旋转后的包围盒
    Vector3f[] va=
    {
        new  Vector3f(minX,minY,minZ),
        new  Vector3f(minX,maxY,minZ),
        new  Vector3f(maxX,minY,minZ),
        new  Vector3f(maxX,maxY,minZ),
        new  Vector3f(minX,minY,maxZ),
        new  Vector3f(minX,maxY,maxZ),
        new  Vector3f(maxX,minY,maxZ),
        new  Vector3f(maxX,maxY,maxZ),  
    };
    float[] boxVertices=new float[24];
    int count=0;
    for(int i=0;i<va.length;i++)
    {
        float[] result=new float[4];
        float[] dot=new float[]{va[i].x,va[i].y,va[i].z,1};
        Matrix.multiplyMV(result, 0, currOrientation.orientationData,0,dot, 0);
        boxVertices[count++]=result[0];
        boxVertices[count++]=result[1];
        boxVertices[count++]=result[2];
    }

    float[] data=findMinAndMax(boxVertices);
    //再计算移动后的包围盒
    AABBBox result=new AABBBox
    (
        data[0]+currPosition.x,
        data[1]+currPosition.x,
        data[2]+currPosition.y,
        data[3]+currPosition.y,
        data[4]+currPosition.z,
        data[5]+currPosition.z
        );
        return result;
    }


AABB包围盒碰撞检测
碰撞检测类
public class RigidBody 
{
    LoadedObjectVertexNormal renderObject;//渲染者
    AABBBox collObject;//碰撞者
    boolean isStatic;//是否静止的标志位
    Vector3f currLocation;//位置三维变量
    Vector3f currV;//速度三维变量
    final float V_UNIT=0.02f;//阈值

    public void go(ArrayList<RigidBody> al)
    {
        if(isStatic) return;            //若为静止物体则不需要移动
        currLocation.add(currV);//物体根据速度值改变坐标
        for(int i=0;i<al.size();i++)    //遍历所有移动中的物体
        {
            RigidBody rb=al.get(i);
            if(rb!=this)           //不是本体
            {
                if(check(this,rb))//检验碰撞
                {
                    this.currV.x=-this.currV.x;//若碰撞了,该方向上的速度值置反,弹回效果
                }

            }
    }

}

public boolean check(RigidBody ra,RigidBody rb)//true为撞上
{
    float[] over=calOverTotal
    (
        ra.collObject.getCurrAABBBox(ra.currLocation),   //a物体AABB包围盒
        rb.collObject.getCurrAABBBox(rb.currLocation)    //b物体AABB包围盒
    );
    return over[0]>V_UNIT&&over[1]>V_UNIT&&over[2]>V_UNIT;
}

public float[] calOverTotal(AABBBox a,AABBBox b)                  //计算a、b两个包围盒x、y、z个轴的交叠值
{
    float xOver=calOverOne(a.maxX,a.minX,b.maxX,b.minX);
    float yOver=calOverOne(a.maxY,a.minY,b.maxY,b.minY);
    float zOver=calOverOne(a.maxZ,a.minZ,b.maxZ,b.minZ);
    return new float[]{xOver,yOver,zOver};
}

public float calOverOne(float amax,float amin,float bmax,float bmin)
{
    float minMax=0;                //两个最大值中较小的一个
    float maxMin=0;                //两个最小值中较大的一个
    if(amax<bmax)//a物体在b物体左侧
    {
        minMax=amax;
        maxMin=bmin;
    }
    else //a物体在b物体右侧
    {
        minMax=bmax;
        maxMin=amin;
    }

    if(minMax>maxMin)   //有交叠
    {
        return minMax-maxMin;
    }
    else
    {
        return 0;     //没有交叠
    }
}

(2)OBB包围盒,随物体旋转而旋转,适合物体旋转等碰撞,误差小,计算比较复杂。

代码暂无

2.粒子系统
1个粒子就是1点,粒子系统由许多粒子以不固定速度向各方向飞射组成,用来模拟火、喷泉、雪花等效果,GPU版粒子系统比CPU版粒子系统性能高。
S = Y(0) + V(y) * t- 0.5 * g * t(2)

CPU版粒子系统:

采用代码计算出各粒子坐标

GPU版粒子系统:
采用顶点着色器计算出各粒子坐标

//初始化顶点数据的initVertexData方法,计算粒子X、Y、Z方向上的速度
public void initVertexData(int vCount){
    float[] velocity=new float[vCount*3];
    for(int i=0;i<vCount;i++){    //生成各粒子顶点的速度数据,顶点坐标由顶点着色器计算
        double fwj=2*Math.PI*Math.random();              //随机生成方位角
        double yj=0.35*Math.PI*Math.random()+0.15*Math.PI;   //随机生成仰角
        final double vTotal=1.5+1.5*Math.random();//随机生成总速度
        double vy=vTotal*Math.sin(yj);//y方向上的速度
        double vx=vTotal*Math.cos(yj)*Math.sin(fwj);//x方向上的速度
        double vz=vTotal*Math.cos(yj)*Math.cos(fwj);//z方向上的速度
        velocity[i*3]=(float)vx;
        velocity[i*3+1]=(float)vy;
        velocity[i*3+2]=(float)vz;
     }
   
     //创建顶点速度数据缓冲
     ByteBuffer vbb = ByteBuffer.allocateDirect(velocity.length*4);
     vbb.order(ByteOrder.nativeOrder());//设置字节顺序
     mVelocityBuffer = vbb.asFloatBuffer();//转换为int型缓冲
     mVelocityBuffer.put(velocity);//向缓冲区中放入顶点坐标数据
     mVelocityBuffer.position(0);//设置缓冲区起始位置
}

顶点着色器vertex.sh,计算粒子X、Y、Z坐标:
uniform mat4 uMVPMatrix; //总变换矩阵
uniform float uPointSize;//点尺寸
uniform float uTime;     //粒子的累计运动时间
attribute vec3 aVelocity;  //粒子初速度
void main()     
{
   float currTime=mod(uTime,10.0);   //执行取模运算,相当于累计时间超过10则归0
   float px=aVelocity.x*currTime;    //计算粒子此时的X坐标
   float py=aVelocity.y*currTime-0.5*1.5*currTime*currTime+3.0;   //计算粒子此时的Y坐标
   float pz=aVelocity.z*currTime;         //计算粒子此时的Z坐标
   //根据总变换矩阵计算此次绘制此顶点位置                        
   gl_Position = uMVPMatrix * vec4(px,py,pz,1); 
   //设置粒子尺寸
   gl_PointSize=uPointSize;  
}

片元着色器frag.sh:
//焰火粒子着色器
precision mediump float;
uniform vec3 uColor;//粒子颜色
void main()                         
{
  //给此片元颜色值 
  gl_FragColor = vec4(uColor,1.0);
}

3.弹簧质点模型
就是指物体碰撞时的反弹效果
0 0
原创粉丝点击