Java 3DGame LWJGL Retry Lv7 遊んでみる3〜全部テクスチャにする〜

イントロダクション

ここのところ負け越しの1引分けでしたが、ついにやっつけました。

<戦闘履歴>

  1. Eclipse アプリ作成 Lv5〜惨敗:CubeにTextureを貼る〜「惨敗」
  2. Java 3DGame LWJGL Retry Lv3 Texture〜動かして理解する3〜「負け越し」
  3. Java 3DGame LWJGL Retry Lv4 デバック〜動かして理解する4〜「黒星」
  4. Java 3DGame LWJGL Retry Lv5 遊んでみる〜動かして理解する5〜「引分け」
  5. Java 3DGame LWJGL Retry Lv6 遊んでみる2〜動かして理解する6〜「白星」

最終的に以下の様なコードができました。主にいじったのは「DummyGame」クラスです。ポイントは以下になります。

  1. メッシュを作成するときはテクスチャを使用する様に修正
  2. メッシュを作成するときの各頂点(Vertex)の定義を見直す
  3. 同様に頂点を結びつける順番(indices)を見直す

<DummyGame#init()>

    @Override
    public void init(Window window) throws Exception {
        renderer.init(window);
        // Cubeの高さ
        ArrayList<float[]> floats = new ArrayList<>();
        floats.add(new float[] {0.0001f, 0.12f, 0.3f, 0.001f, 0.1f, 0.25f, 0.1f});
        floats.add(new float[] {0.15f, 0.19f, 0.23f, 0.2f, 0.08f, 0.13f, 0.12f});
        floats.add(new float[] {0.1f, 0.2f, 0.4f, 0.001f, 0.2f, 0.05f, 0.15f});
        floats.add(new float[] {0.11f, 0.12f, 0.3f, 0.001f, 0.1f, 0.25f, 0.1f});
        floats.add(new float[] {0.12f, 0.13f, 0.14f, 0.015f, 0.16f, 0.17f, 0.18f});
        // Cubeの底面のサイズ(正方形)
        final float cubeSize = 0.1f;
        // x軸の初期値
        final float xInit = -0.5f;
        // y軸の初期値
        final float yInit = -0.8f;
        // z軸の初期値
        final float zInit = -2;
        // x軸の増減幅
        final float xWidth = 0.185f;
        // y軸の増減幅
        final float yWidth = 0.033f;
        // z軸の増減幅
        final float zWidth = 0.1f;
        ArrayList arr = new ArrayList();
        
        // 初めの1回目だけ曜日のテキストプレートを作成する
        boolean isCreateTexture = false;
        // 1ヶ月分(5週間分のマスを作る)
        for(int j = 1; j <= 5; j++) {
        	// 開始点より一列文ずらす
        	// X軸の開始点
        	float xStart = xInit - (0.1f * j);
        	// Y軸の開始点
        	float yStart = yInit + (0.06f * j) ;
        	// Z軸の開始点
        	float zStart = zInit - (0.16f * j);
            // 1週間分
        	float[] weekArr = floats.get(j-1);
        	
        	// 1回目だけテクスチャを作成する
        	isCreateTexture = j == 1 ? true: false;
            for(int i = 1; i <= 7; i++) {
            	float xAdd = xWidth * i;
            	float yAdd = yWidth * i;
            	float zAdd = zWidth * i;
            	if (isCreateTexture) {
            		arr.add(putOnTexturePlate(xStart + xAdd + 0.01f , yStart + yAdd - 0.03f, zStart - zAdd + 0.03f, i-1));
            	}
            	float val = weekArr[i - 1];
        		arr.add(createCube(val,
        				cubeSize, xStart + xAdd, yStart + yAdd, zStart - zAdd));
            }
        }
        // 配列の要素数を指定する
        System.out.println("GmaeItems: " + arr.size());
        GameItem[] items = new GameItem[arr.size()];
        // 配列の取り出し
        gameItems = arr.toArray(items);
        
        // DEBUG
        //debug();
    }

<DummyGame#putOnTexturePlate()>

    /**
     * Texture作成メソッド
     * @param xPos
     * @param yPos
     * @param zPos
     * @param num 曜日の番号0:月〜7:日
     * @return GameItem
     */
    private GameItem putOnTexturePlate(float xPos, float yPos, float zPos, int num) {
    	float size = 0.08f;
    	float[] positions = new float[] {
    			-1 * size, size, size, // V0
    			-1 * size, -1 * size, size, //V1 
    			size, -1 * size, size, // V2
    			size, size, size, // V3
    			
    			-1 * size, size, -1 * size, // V4
    			size, -1 * size, size, //V5 
    			-1 * size, -1 * size, -1 * size, // V6
    			size, -1 * size, -1 * size, // V7
    			};
    	float[] textCoords = new float[]{
                0.0f, 0.0f, 
                0.0f, 1.0f,
                1.0f, 1.0f,
                1.0f, 0.0f,
            };
    	int[] indices = new int[] {
    			0, 1, 3, 3, 1, 2
			};
    	Texture texture = null;

    	try {
        	texture = new Texture("/textures/" + weekTexture[num] +  ".png");
    	} catch(Exception e) {
    		e.printStackTrace();
    	}
        TexturedMesh mesh = new TexturedMesh(positions, textCoords, indices, texture);
        GameItem gameItem = new GameItem(mesh);
        gameItem.setPosition(xPos, yPos, zPos);
        gameItem.setRotation(20, 30, 0);
        return gameItem;
    }

<DummyGame#createCube()>

    private GameItem createCube(float height, float cubeSize, float posX, float posY, float posZ) {
        // Create the Mesh
        float[] positions = new float[]{
            // VO
            -1 * cubeSize,  height,  cubeSize,
            // V1
            -1 * cubeSize, -1 * cubeSize,  cubeSize,
            // V2
            cubeSize, -1 * cubeSize,  cubeSize,
            // V3
            cubeSize,  height,  cubeSize,
            // V4
            -1 * cubeSize,  height, -1 * cubeSize,
            // V5
            cubeSize,  height, -1 * cubeSize,
            // V6
            -1 * cubeSize, -1 * cubeSize, -1 * cubeSize,
            // V7
            cubeSize, -1 * cubeSize, -1 * cubeSize,
        };
        float[] colours = new float[]{
            0.5f, 0.0f, 0.0f,
            0.0f, 0.5f, 0.0f,
            0.0f, 0.0f, 0.5f,
            0.0f, 0.5f, 0.5f,
            0.5f, 0.0f, 0.0f,
            0.0f, 0.5f, 0.0f,
            0.0f, 0.0f, 0.5f,
            0.0f, 0.5f, 0.5f,
        };
        int[] indices = new int[]{
            // Front face
            0, 1, 3, 3, 1, 2,
            // Top Face
            4, 0, 3, 5, 4, 3,
            // Right face
            3, 2, 7, 5, 3, 7,
            // Left face
            6, 1, 0, 6, 0, 4,
            // Bottom face
            2, 1, 6, 2, 6, 7,
            // Back face
            7, 6, 4, 7, 4, 5,
        };
    	Texture texture = null;
    	try {
        	texture = new Texture("/textures/wood1.png");
    	} catch(Exception e) {
    		e.printStackTrace();
    	}
        TexturedMesh mesh = new TexturedMesh(positions, colours, indices, texture);
        GameItem gameItem = new GameItem(mesh);
        gameItem.setPosition(posX, posY, posZ);
        gameItem.setRotation(20, 30, 0);
        return gameItem;
    }

上記のポイントを抑えサイドコードを直したところうまくいきました。


関連ページ一覧

<Java Basic>

  1. Java Basic for文 〜Step1_3_1〜
  2. Java Basic Level8 〜How to use for statement〜
  3. Java Basic Level 9〜Training of for statement〜
  4. Java 3DGame LWJGL GitBook chapter7-1〜Cube作成〜「動画あり」

Java 3DGame LWJGL Chapter4〜シェーダについて〜

イントロダクション

LWJGL GitBookChapterを写経しながら理解していきます。Git(ソース)はこちら。。。

前回は、Chapter3をやりました。

今回は、前回やったプログラムの続きです。前回はプログラムの全体的な流れを見ていきましたので、今回は詳細部分描画処理を見ていきます、前回示した目玉の部分です。

Introduction

LWJGL We will understand GitBook ‘s chapter while shooting. Git (source) is here. . . Last time I did Chapter 3 so We will do Chapter 4 this time.

Last time. we have larned rough processing of this program.This time we will learn sight of this program.

https://ahbejarano.gitbook.io/lwjglgamedev/chapter4

シェーダについて

ShaderProgramクラスについて学びます。このクラスはRendererクラスから呼ばれていますので、そこから追いかけます。

Renderer#init()

        shaderProgram = new ShaderProgram();
        shaderProgram.createVertexShader(Utils.loadResource("/vertex.vs"));
        shaderProgram.createFragmentShader(Utils.loadResource("/fragment.fs"));
        shaderProgram.link();

LWJGLフレームワークの部品GL20のメソッド「glCreateProgram」を呼び出しています。

ShaderProgram

    public ShaderProgram() throws Exception {
        programId = glCreateProgram();
        if (programId == 0) {
            throw new Exception("Could not create Shader");
        }
    }

ここでの注意点は、赤字のメソッドがメソッド内のコードだけでは「どのクラスのメソッドかわからない」と言うことです。

結論から言うと「import static」しているので呼び出すことが可能です。

import static org.lwjgl.opengl.GL20.*;

そして、参照しているドキュメントには以下の様な記述があります。

    1. OpenGLプログラム(空のオブジェクト)を作成する:glCreateProgram
    2. 頂点とフラグメントのシェーダコードファイルをロードします。
        • シェーダを定義します:glCreateShaderSource
        • シェーダをコンパイル(実行できる様にします。上で定義した時のIDでコンパイルする様です。:glCompileShader
    3. 各シェーダーについて、新しいシェーダープログラムを作成し、そのタイプ(頂点、フラグメント)を指定します:glCreateShaderSource
    4. シェーダをコンパイルします。:glCompileShader
    5. シェーダをプログラムに接続します。:glAttachShader
    6. プログラムをリンクします。:glLinkProgram
public class ShaderProgram {

    private final int programId;

    private int vertexShaderId;

    private int fragmentShaderId;

    public ShaderProgram() throws Exception {
        /* 1.OpenGLプログラム(空のオブジェクト)を作成する */
        programId = glCreateProgram();
        if (programId == 0) {
            throw new Exception("Could not create Shader");
        }
    }
    /*新しいシェーダープログラムを作成 */
    public void createVertexShader(String shaderCode) throws Exception {
        /*空のシェーダを作成(Vertex(点用)) */
        vertexShaderId = createShader(shaderCode, GL_VERTEX_SHADER);
    }
    /*新しいシェーダープログラムを作成 */
    public void createFragmentShader(String shaderCode) throws Exception {
        /*空のシェーダを作成(フラグメント用))*/
        fragmentShaderId = createShader(shaderCode, GL_FRAGMENT_SHADER);
    }

    protected int createShader(String shaderCode, int shaderType) throws Exception {
        /* 空のシェーダを作成 */
        int shaderId = glCreateShader(shaderType);
        if (shaderId == 0) {
            throw new Exception("Error creating shader. Type: " + shaderType);
        }
        /*頂点とフラグメントのシェーダコードファイルをロード */
        glShaderSource(shaderId, shaderCode);
        /*シェーダをコンパイル */
        glCompileShader(shaderId);
        /* コンパイルができたか確認する */
        if (glGetShaderi(shaderId, GL_COMPILE_STATUS) == 0) {
            throw new Exception("Error compiling Shader code: " + glGetShaderInfoLog(shaderId, 1024));
        }
        /* シェーダとプログラムとを関連付ける(アタッチする) */
        glAttachShader(programId, shaderId);

        return shaderId;
    }

    public void link() throws Exception {
        /* */
        glLinkProgram(programId);
        if (glGetProgrami(programId, GL_LINK_STATUS) == 0) {
            throw new Exception("Error linking Shader code: " + glGetProgramInfoLog(programId, 1024));
        }

        if (vertexShaderId != 0) {
            /* アタッチの反対を行います*/
            glDetachShader(programId, vertexShaderId);
        }
        if (fragmentShaderId != 0) {
            glDetachShader(programId, fragmentShaderId);
        }
     /* シェーダプログラムが使用可能かチェックする */
        glValidateProgram(programId);
        if (glGetProgrami(programId, GL_VALIDATE_STATUS) == 0) {
            System.err.println("Warning validating Shader code: " + glGetProgramInfoLog(programId, 1024));
        }

    }

    public void bind() {
        /* シェーダプログラムをインストール(実行じゅんびOK状態にする) */
        glUseProgram(programId);
    }

    public void unbind() {
        /* シェーダプログラムを解放する */
        glUseProgram(0);
    }

    public void cleanup() {
        unbind();
        if (programId != 0) {
            /* シェーダプログラムの破棄 */
            glDeleteProgram(programId);
        }
    }
}

<補足>
フラグメントシェーダプログラム

#version 330

out vec4 fragColor;

void main()
{
	fragColor = vec4(0.0, 0.5, 0.5, 1.0);
}

Vertexシェーダプログラム

#version 330

layout (location =0) in vec3 position;

void main()
{
	gl_Position = vec4(position, 1.0);
}

シェーダプログラムに関しては触れていなかったので、ちょいと保留します。

今回は、ここまでにしておきます。

 

Java 3DGame LWJGL GitBook chapter3〜プロジェクトの起動エラー〜

トラブルシューティング

この記事は、chapter3を始めるにあたり画面の起動でエラーが出てしまった時のメモとして記載します。

エラーになった原因(自分のケース) Cause getting Error in Chapter3

出力したエラーログ

java.lang.NullPointerException: source
	at java.util.Objects.requireNonNull(Objects.java:228)
	at java.util.Scanner.(Scanner.java:578)
	at zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter4.engine.Utils.loadResource(Utils.java:11)
	at zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter4.game.Renderer.init(Renderer.java:26)
	at zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter4.game.DummyGame.init(DummyGame.java:22)
	at zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter4.engine.GameEngine.init(GameEngine.java:50)
	at zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter4.engine.GameEngine.run(GameEngine.java:38)
	at java.lang.Thread.run(Thread.java:748)
	at zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter4.engine.GameEngine.start(GameEngine.java:29)
	at zenryokuservice.gui.lwjgl.tutoriral.gitbook.chapter4.game.Main.main(Main.java:13)

上の様なエラーログが出力された場合です。これは上の赤字の部分から追いかけると原因がわかります。

対象のコード(赤い字)

    public static String loadResource(String fileName) throws Exception {
        String result;
        try (InputStream in = Class.forName(Utils.class.getName()).getResourceAsStream(fileName);
                Scanner scanner = new Scanner(in, "UTF-8")) {
            result = scanner.useDelimiter("\\A").next();
        }
        return result;
    }

上記の赤い部分がエラーの出ている行になります。コードを追いかけてみると「in」をScannerで読み取るための準備(Scannerをインスタンス化)をしているところでエラーが起きていますので、「in」「"UTF-8"」が怪しいと睨みます。「"UTF-8」は固定値(定数)なので犯人ではなさそうです。

次に、「in」を追跡します。「in」はInputStream=>ClassクラスのメソッドgetResourceAsStreamを呼び出しているのでそいつを調べます。

引数にある「リソース(ファイル)」を取得するメソッドですので返却する値はInputStreamです。

※ここら辺について記載した記事はこちらです。

 

設計を始める〜3.機能概要〜

イントロダクション

前回は、機能の実装イメージを絵にしました。

今回は、機能概要と称してどんな処理が必要かリストアップします。

成果物

※自分用なので一般的に使えません…

上部右側にあるMainはメインメソッドを示していて四角で囲ってる処理を行います。

同様にRead, schedule, Checkはボタン押下時の処理を示しています。

次は

実現する為の手法を考えます。