SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

スマホアプリ開発の最新トレンド! ぜったい外せない位置情報活用術

OpenGLを使った地図描画エンジンの作り方(3)

スマホアプリ開発の最新トレンド! ぜったい外せない位置情報活用術 番外編

破線について

 地図では点線・破線・鎖線を使った表現をよく行います。例えば、市区町村の境界を示す場合や地下鉄の表現、最近ではリニア鉄道の計画線(図6)などにも利用しています。

図6(リニア鉄道の計画線)
図6(リニア鉄道の計画線)

 このようにいろいろな線種を用いて地図を表現していきますが、OpenGL ES 2.0では、実線以外の表現を基本機能としては扱うことができません。そこでシェーダを使って表現していく必要があります。破線を描くための流れは以下になります。

 処理の概要を説明させていただくと、北海道を構成する線分を12000分割して、1区分あたり2:1の割合で有色と透明を配置することで破線を実現しています。

GL20MyRender(変更)
省略

    /**
     * コンストラクタ
     * @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をご参照ください。

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分割しています。

SPShader(追加)
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];
    }
}

次のページ
まとめ

この記事は参考になりましたか?

スマホアプリ開発の最新トレンド! ぜったい外せない位置情報活用術連載記事一覧

もっと読む

この記事の著者

冨川 修広(ヤフー株式会社)(トミカワ ノブヒロ(ヤフーカブシキカイシャ))

2008年ヤフー入社。地図に関するシステム全般の設計・開発を担当。フルリニューアルしたAndroid版Yahoo!地図アプリでは、プロダクトマネージャとしてプロジェクト全体を統括。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/7686 2014/03/26 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー