破線について
地図では点線・破線・鎖線を使った表現をよく行います。例えば、市区町村の境界を示す場合や地下鉄の表現、最近ではリニア鉄道の計画線(図6)などにも利用しています。
このようにいろいろな線種を用いて地図を表現していきますが、OpenGL ES 2.0では、実線以外の表現を基本機能としては扱うことができません。そこでシェーダを使って表現していく必要があります。破線を描くための流れは以下になります。
処理の概要を説明させていただくと、北海道を構成する線分を12000分割して、1区分あたり2:1の割合で有色と透明を配置することで破線を実現しています。
省略
/**
* コンストラクタ
* @param context
* @param parent
* @param mapView
*/
public GL20MyRenderer(Context context, GL20SurfaceView parent , MapView mapView){
mMapView = mapView;
mShader = new Shader();
mSPShader = new SPShader();
mCoordList = mMapView.getCoordinateList();
mSpLine = new SPLine(mCoordList,2,12000); //破線用の準備データの作成
mFrontProjectionMatrix = new GMatrix();
mFrontModelViewMatrix = new GMatrix();
mBackProjectionMatrix = new GMatrix();
mBackModelViewMatrix = new GMatrix();
}
省略
@Override
public void onDrawFrame(GL10 arg0) {
synchronized (mFrontModelViewMatrix) {
mFrontModelViewMatrix.copy(mBackModelViewMatrix);
mFrontProjectionMatrix.copy(mBackProjectionMatrix);
}
//行列をセット
GLES20.glUniformMatrix4fv(mSPShader.getShaderParameterId(SPShader.ID_VuViewMatrix), 1, false, mBackModelViewMatrix.matrix, 0);
GLES20.glUniformMatrix4fv(mSPShader.getShaderParameterId(SPShader.ID_VuProjMatrix), 1, false, mBackProjectionMatrix.matrix, 0);
//背景色を設定します。
GLES20.glClearColor(0.6f, 0.8f, 0.9f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//線幅を設定します
GLES20.glLineWidth(2.0f);
GLES20.glEnable(GL10.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
GLES20.glUniform4f(mSPShader.getShaderParameterId(SPShader.ID_VuColor), 0.0f, 0.0f, 0.0f, 1.0f );
GLES20.glUniform4f(mSPShader.getShaderParameterId(SPShader.ID_VuColor2), 7.0f, 2.0f, 2.0f, 1.0f );
//頂点バッファを設定します
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVertexId);
GLES20.glVertexAttribPointer(mSPShader.getShaderParameterId(SPShader.ID_VaPos), 2, GLES20.GL_FLOAT, false, FSIZE*5, 0);
GLES20.glVertexAttribPointer(mSPShader.getShaderParameterId(SPShader.ID_VaTex), 2, GLES20.GL_FLOAT, false, FSIZE*5, FSIZE*3);
//描画
GLES20.glDrawArrays(GLES20.GL_LINE_STRIP, 0, mNumVertices);
}
省略
@Override
public void onSurfaceCreated(GL10 arg0, EGLConfig arg1) {
//破線用シェーダの初期化
int program = mSPShader.initShaders();
if (mAPosition == -1) {
throw new RuntimeException("a_Positionの格納場所の取得に失敗");
}
GLES20.glEnableVertexAttribArray(mSPShader.getShaderParameterId(SPShader.ID_VaPos));
GLES20.glEnableVertexAttribArray(mSPShader.getShaderParameterId(SPShader.ID_VaTex));
mNumVertices = initVertexBuffers();
}
/**
* 描画用の頂点データを作成します。
* @return
*/
public int initVertexBuffers() {
FloatBuffer vertices = makeFloatBuffer(mSpLine.vertex);
int vertex_num = mSpLine.vertex.length / 5; //頂点数
//VRAMにデータを設定します。
int[] vertexId = new int[1];
GLES20.glGenBuffers(1, vertexId, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexId[0]);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, FSIZE * vertices.limit(), vertices, GLES20.GL_STATIC_DRAW);
mVertexId = vertexId[0];
return vertex_num;
}
省略
※追加・変更箇所は太字にしています。
後述のSPShaderを使った破線のレンダリングのためのデータの準備と描画処理を追加しています。データに準備については、SPLineをご参照ください。
package jp.co.yahoo.map.codezine_sample;
public class SPLine {
public float[] vertex = null;
public SPLine(float[] coord,int w,int h) {
this.vertex = ffconvert(coord,w,h);
}
/**
* 破線
* **/
public static float[] ffconvert(float[] line, int w, int h) {
int pLen = line.length / 3; //直線数
float buf[] = new float[(pLen)*5];
ffmakeLine(line, buf, w, h, line.length);
return buf;
}
private static void ffmakeLine(float[] line, float[] buf, int w, int h, int lLen) {
float nextTexHeight = 0.0f;
try {
float x1,y1,z1,x2,y2,z2;
buf[0] = line[0];
buf[1] = line[1];
buf[2] = line[2];
buf[3] = 0.5f;
buf[4] = nextTexHeight;
for(int i=3,j=5;i<lLen;i+=3,j+=5) {
x1 = line[i-3];
y1 = line[i-2];
x2 = line[i];
y2 = line[i+1];
z2 = line[i+2];
double dx = x2 - x1;
double dy = y2 - y1;
float texh = (float) (Math.abs(Math.sqrt((dx*dx) + (dy*dy)))/h);
nextTexHeight = nextTexHeight+texh;
buf[j] = x2;
buf[j+1] = y2;
buf[j+2] = z2;
buf[j+3] = 0.5f;
buf[j+4] = nextTexHeight;
}
} catch (Exception e) {
}
}
}
SPLineでは、頂点情報をもとに破線を描くためのUV値を計算しています。視点からの距離を求めて、12000分割しています。
package jp.co.yahoo.map.codezine_sample;
import android.opengl.GLES20;
public class SPShader {
public static byte ID_MAX = 0;
public static final byte ID_VaPos= ID_MAX++;
public static final byte ID_VaTex= ID_MAX++;
public static final byte ID_VuProjMatrix= ID_MAX++;
public static final byte ID_VuViewMatrix= ID_MAX++;
public static final byte ID_VuColor= ID_MAX++;
public static final byte ID_VuColor2= ID_MAX++;
protected int[] mShaderParameter = null;
/**
* 頂点シェーダプログラム
*/
private final String VSHADER_SOURCE =
"precision highp float;" +
"uniform mat4 u_ViewMatrix;\n" +
"uniform mat4 u_ProjMatrix;\n" +
"attribute vec4 a_Position;\n" +
"attribute vec2 a_TexCoord;\n" +
"uniform vec4 u_Color;\n" +
"varying vec4 v_Color;\n" +
"varying vec2 v_TexCoord;\n" +
// "uniform float u_vscale;"+
"void main() {\n" +
" gl_Position = u_ProjMatrix * u_ViewMatrix * a_Position;\n" +
" v_Color = u_Color;\n"+
// " v_color.a = u_outlinecolor.a;\n"+
" v_TexCoord = a_TexCoord;\n"+
// " v_TexCoord.y = a_TexCoord.y / u_vscale;\n" +
"}\n";
/**
* フラグメントシェーダプログラム
*/
private final String FSHADER_SOURCE =
"precision mediump float;" +
"varying vec4 v_Color;" +
"varying vec2 v_TexCoord;" +
"uniform vec4 u_Color2;" +
"void main() {\n" +
" vec4 pt = u_Color2;" +
" float texfH = fract(v_TexCoord.y);"+
" gl_FragColor = v_Color;" +
" float x = 0.0;"+
" float y = 0.0;"+
" float z = 0.0;"+
" float ptlen = pt.g + pt.b;" +
" z = pt.g / ptlen;"+
" x = clamp(texfH, y, z);"+
" if(x == texfH) return;"+
" gl_FragColor.a = 0.0;"+
"}\n";
/**
* コンストラクタ
*/
public SPShader() {
mShaderParameter = new int[ID_MAX];
}
/**
* シェーダープログラムを読み込みコンパイルします。
* @return
*/
public int initShaders() {
int program = createProgram();
GLES20.glUseProgram(program);
return program;
}
/**
* プログラムオブジェクトを作成します。
* @return
*/
public int createProgram() {
// シェーダオブジェクトを作成する
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, this.VSHADER_SOURCE);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, this.FSHADER_SOURCE);
// プログラムオブジェクトを作成する
int program = GLES20.glCreateProgram();
if (program == 0) {
throw new RuntimeException("failed to create program");
}
// シェーダオブジェクトを設定する
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
// プログラムオブジェクトをリンクする
GLES20.glLinkProgram(program);
// リンク結果をチェックする
int[] linked = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linked, 0);
if (linked[0] != GLES20.GL_TRUE) {
String error = GLES20.glGetProgramInfoLog(program);
throw new RuntimeException("failed to link program: " + error);
}
// 値のパイプを設定
setShaderParamaterId(ID_VaPos, GLES20.glGetAttribLocation(program, "a_Position"));
setShaderParamaterId(ID_VaTex, GLES20.glGetAttribLocation(program, "a_TexCoord"));
setShaderParamaterId(ID_VuProjMatrix, GLES20.glGetUniformLocation(program, "u_ProjMatrix"));
setShaderParamaterId(ID_VuViewMatrix, GLES20.glGetUniformLocation(program, "u_ViewMatrix"));
setShaderParamaterId(ID_VuColor, GLES20.glGetUniformLocation(program, "u_Color"));
setShaderParamaterId(ID_VuColor2, GLES20.glGetUniformLocation(program, "u_Color2"));
return program;
}
/**
* シェーダーを読み込みます。
* @param type
* @param source
* @return
*/
public int loadShader(int type, String source) {
// シェーダオブジェクトを作成する
int shader = GLES20.glCreateShader(type);
if (shader == 0) {
throw new RuntimeException("unable to create shader");
}
// シェーダのプログラムを設定する
GLES20.glShaderSource(shader, source);
// シェーダをコンパイルする
GLES20.glCompileShader(shader);
// コンパイル結果を検査する
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] != GLES20.GL_TRUE) {
String error = GLES20.glGetShaderInfoLog(shader);
throw new RuntimeException("failed to compile shader: " + error);
}
return shader;
}
/**
* パイプ配列を設定する
* @param id
* @param value
*/
public void setShaderParamaterId(byte id, int value) {
mShaderParameter[id] = value;
}
/**
* 指定のパイプを返す
* @param id
* @return
*/
public int getShaderParameterId(byte id) {
return mShaderParameter[id];
}
}
