LoginSignup
6
5

More than 5 years have passed since last update.

Cocos Creator でカスタムシェーダーを使う

Last updated at Posted at 2016-09-01

Cocos Creator で cc.Sprite に独自の cc.GLProgram を適用する方法について情報がなかったのでメモ。

Cocoa Creator の紹介


(出典: cocos2d-x 公式ブログ)

Cocos Creator は cocos2d-html5 をベースとした Unity 似の開発環境で、ウェブ・モバイル向けの2Dゲームを効率良く開発することができます。対応言語は英語と中国語 (簡体字) のみで、ドキュメントも非常に少ないですが、Unity よりもずっとコンパクトで、用途を限れば (たぶん) 有用だと思います。

Cocos Creator で使用しているのは cocos2d-html5 や cocos2d-x そのものではなく、cocos2d-html5 に Unity 風エンティティ-コンポーネントアーキテクチャを追加した Cocos Creator Engine Framework と呼ばれるもので、使い方は cocos2d 系列のライブラリとは大きく異なります (ドキュメンテーション)。

Cocos Creator ではノード (cc.Node) をシーン上に配置します。ノードにはコンポーネントを追加することで機能を持たせることができ、例えばスプライトコンポーネント (cc.Sprite, cocos2d-html5に同名のクラスがあるので注意!) を追加することで、スプライトを画面上に表示させることができます。さらに、ユーザ定義のコンポーネントを JavaScript/CoffeeScript で記述し、ノードに追加することで、独自の動作をさせることができます。

cc.Sprite でシェーダを使う

cocos2d-html5 では、SpritesetShaderProgram メソッドを呼出すことで、WebGL GLSL シェーダを設定し、高度な視覚効果を実現することができました。しかし、Cocos Creator の cc.Sprite ではこのメソッドは提供されていないようです。

Cocos Creator Engine Framework は内部的には cocos2d-html5 をベースとして動作しています。では cocos2d-html5 の Sprite がどこに行ったのかと言うと、ccsg.Sprite という非公開 API として残っています。cc.Sprite は内部的には ccsg.Sprite を利用しており、_sgNode というフィールドでこのインスタンスを持っている為、これにアクセスすれば同様のことが Cocos Creator でも実現できるということになります。

実際に動作する例を示します。次のスクリプトをプロジェクトに追加し、スプライトノードにコンポーネントとして追加してみて下さい。ブラウザで表示すると、当該スプライトがモノクロになって表示されるはずです。

MakeSpriteMonochromeController.coffee
# シェーダの生成
shader = new cc.GLProgram()
shader.initWithString """
    attribute vec4 a_position;
    attribute vec2 a_texCoord;
    attribute vec4 a_color;

    varying lowp vec4 v_fragmentColor;
    varying mediump vec2 v_texCoord;

    void main() 
    {                           
        gl_Position = CC_PMatrix * a_position;
        v_fragmentColor = a_color;
        v_texCoord = a_texCoord;
    }""", """
    precision lowp float;
    varying vec4 v_fragmentColor;   
    varying vec2 v_texCoord;    

    void main()         
    {
        vec4 v_orColor = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
        float gray = dot(v_orColor.rgb, vec3(0.299, 0.587, 0.114));
        gl_FragColor = vec4(gray, gray, gray, v_orColor.a);
    }"""
shader.addAttribute cc.macro.ATTRIBUTE_NAME_POSITION, cc.macro.VERTEX_ATTRIB_POSITION
shader.addAttribute cc.macro.ATTRIBUTE_NAME_COLOR, cc.macro.VERTEX_ATTRIB_COLOR
shader.addAttribute cc.macro.ATTRIBUTE_NAME_TEX_COORD, cc.macro.VERTEX_ATTRIB_TEX_COORDS
shader.link()
shader.updateUniforms()

# コンポーネント定義
cc.Class {
    extends: cc.Component

    properties: {
        # foo:
        #   default: null
        #   type: cc
        #   serializable: true # [optional], default is true
        #   visible: true      # [optional], default is true
        #   displayName: 'Foo' # [optional], default is property name
        #   readonly: false    # [optional], default is false
    }

    onLoad: () ->

        sprite = @getComponent "cc.Sprite"
        sprite._sgNode.setShaderProgram shader

        return

    update: (dt) ->
        # do your update here
        return
}

パラメータの渡し方

本来想定されていない、いわゆる「ハック」的な使用法ですので、パラメータを渡すにも「公式」な方法は存在せず、Cocos Engine の WebGL レンダラーの内部動作を考慮しながらうまく実装する必要があります。

例えば、Sprite が複数あり、それぞれ異なるパラメータを渡したい場合を考えてみましょう。シェーダに渡している頂点属性は「位置」「色」「テクスチャ座標」があり、「色」は自由に設定できることから、これをパラメータを渡すのに利用することができます。しかし、「色」は 4 個の浮動小数点値なので、渡せる情報がかなり限られます。これに対するアプローチは複数考えられますが、uniform を使用した場合について考えてみましょう。

シェーダへパラメータを渡す場合、uniform を用いるのが一般的ですが、ここで注意が必要です。Cocos Engine ではパフォーマンスを最適化するために、複数の Sprite を一つのバッチでまとめて描画する機能があります。このため、Sprite 毎に異なる uniform を与えたい場合、まずバッチが発生しないようにする必要があります。レンダラーのコードから分かるように、複数の Sprite がバッチされる必要条件の一つは「shader が共通であること」です。つまり Sprite 毎に異なる GLProgram のインスタンスを指定すればバッチは回避できます。Sprite の個数分 GLProgram を作りなおすのはオーバーヘッドが大き過ぎるので、例えば次のようなコードならどうでしょうか?

batch-hack.coffee
shader = new cc.GLProgram()
# (中略)
shader.updateUniforms()

# JSのプロトタイプチェーンを利用して低オーバーヘッドでshaderを複製する
# 注: ここではclassは使えません!
MyShader = () ->
MyShader.prototype = shader

# (中略)

    onLoad: () ->

        sprite = @getComponent "cc.Sprite"
        sprite._sgNode.setShaderProgram new MyShader() # 複製

        return

これで、レンダラーから見ると、Sprite 毎に違う GLProgram が設定されているように見えるので、バッチ化は行われなくなるはずです。では、次に uniform を設定する方法について考えてみましょう。

レンダラーでは、各ドローコールの前に、その Sprite に設定された shaderuse メソッドを呼び出しています。Sprite 毎に uniform の値を設定したい場合、ここで uniform を設定するのが一番確実な方法と思われます。例えばこんな感じでどうでしょうか?Uniform の設定方法については cocos2d-html5 のドキュメントをご覧下さい

MyShader = () ->
MyShader.prototype = shader
shader.use = () ->
    cc.GLProgram::use.apply @, arguments

    # Uniformの値を設定
    @setUniformLocationWith1i "u_myparameter", 114514
    return

まとめ

Cocoa Creator の cc.Sprite でカスタムシェーダーを使用し、パラメータを設定する方法について説明しました。非公開 API を利用し、エンジン内部の動作に依存した相当ダーティーな方法でしたが、如何でしたでしょうか?

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5