マテリアルの設定変更

目次

はじめに

 前回(パラメータ)こんなことを書きました。

オブジェクトを作成する順番を工夫してもダメな場合は、あきらめましょう。(´・ω・`)

 スマンありゃ ウソだった。
マテリアルの設定を変えれば、透過したときの色の混ぜ方(ブレンディング)を変更できたり、Zテスト関連の設定を変えることで描画順に依存しない表現も可能なようです。(と、先日メールでご指摘いただきました。知らない情報だったのでありがたかったです。よくわからんと思ってマニュアルは読み飛ばしてました。)

 マテリアルと言うと、テクスチャの設定などがよく使うところだと思いますが、他にもレンダリングに関する設定を変更できました。

materialファイルを書き換えてみる

 materialファイルでいろいろできるようなので、ちょっと確認してみます。

 使うサンプルは、HSP3付属の「\sample\hgimg4\tamane2.hsp」です。歩く珠音が表示されるものです。不透明だと効果がわからないものもあるので、適当な行に次の一行を書き足して半透明表示にしておきます。


  gpsetprm id_model, PRMSET_ALPHA, 127

 実行すると珠音(たまね)が半透明表示されます。腕が見えたりと少し透けすぎな部分もありますが、それなりにいい感じです。 確認したらEscキーを押して終了しておきます。

 「\res\tamane2.material」をテキストエディターで開いて書き換えます。戻せなくなると困るので、バックアップを取っておきましょう。 materialファイルは、GLSLという言語で書かれています。HSP3とは文法がまったく違うのでご注意ください。

 書き換えるのは、以下の範囲。上の方なので、すぐに見つかりました。


  renderState
  {
      cullFace = true
      depthTest = true
      blend = true
      blendSrc = SRC_ALPHA
      blendDst = ONE_MINUS_SRC_ALPHA
  }

今の設定は、次のようになっています。

変数 設定内容 現在の設定値
cullFace カリング 有効
depthTestZテスト 有効
blend ブレンド 有効
blendSrc ブレンド元書き込み元α
blendDst ブレンド先書き込み元α(反転値)

 試しにcullFaceを変えてみます。 この変数はカリング(隠面消去/隠面除去)の有効無効を設定できます。 有効にすると裏面を描画しません。 通常ポリゴンの裏面は、内側に配置するため隠れて見えません。 見えない部分を描画しなければ、その分処理が軽くなります。 また見えないほうが、演出上都合がいい場合もあります。

 ここではカリングを無効にして、裏面も表示するようにしてみます。


  cullFace = false

に書き換えて、materialファイルを上書き保存。tamane2.hspを実行すると、裏面が見えるようになります。 半透明表示じゃなくても、スカートが裏面から見えるようになるといった効果があります。

 同様にdepthTestも変更してみます。 この変数は、Zテスト(深度テスト)を実施する/実施しないを設定できます。 Zテストをざっくり説明すると、重なっている面どうしの深度(カメラからの距離)を比較テストして、合格したものだけ描画するという機能です。(通常は、カメラに近い方だけを合格とします。判定基準は変更可能。) depthTestfalseに設定すると、Zテストを実施しません。すべての面がテストに合格したものとして描画されます。 スカートで隠れている足も表示されるようになります。


  cullFace = true
  depthTest = false

 cullFacedepthTesttrueに戻して、depthWrite = falseを追加するとどうでしょうか。Zバッファ書き込みを無効にします。


  cullFace = true
  depthTest = true
  depthWrite = false

 depthTestfalseにしたときと同じような描画結果になりました。 深度書き込みをしない=テストするための深度情報がないため、本来隠れるはずの面はテストされずに描画されます。

ZテストとZバッファ書き込み

 この結果だけだと、depthTest(Zテスト)とdepthWrite(Zバッファ書き込み)の違いがよくわかりませんね。

 珠音(たまね)とカメラの間に板を置くと違いが出ます。


  gpplate id_plate, 10, 60, $303050
  setpos  id_plate, 5, 0, 10

を適当な場所に追加すると板が出現します。

 Zテストだけを無効(depthTest = false)にすると、珠音の全身が表示されます。板を無視して描画されました。ノードオブジェクト内だけでなく、板(他のオブジェクト)ともZテストをしていないようです。

 Zバッファ書き込みだけを無効(depthWrite = false)にすると、珠音は板で隠れました。板とZテストしているようです。 こちらは1つのモデル内で完結するものみたいですね。 Unityなどでも、入り組んだモデルを半透明表示する場合は、Zバッファ書き込みを有効にするようです。

 深い理屈はよくわかりませんが、ZテストZバッファ書き込みには、こういう違いがあるようです。

gpmatstate

 materialファイルを書き換える以外にも、マテリアルの設定を変更する方法があります。 gpmatstategpmatprmt命令を使う方法です。

 マテリアルの設定を変更するタイミングは、オブジェクト生成前と生成後の2パターンがあります。 実装方法は、次のような手順です。

オブジェクト生成にマテリアルの設定を変える場合

  • gptexmatまたはgpcolormatでマテリアルを作成。
  • gpmatstateでマテリアルの設定を変更。
  • gpboxgpplateの引数としてマテリアルIDを渡してノードオブジェクトを作る。

オブジェクト生成にマテリアルの設定を変える場合

  • ノードオブジェクトを作成しておく。(gpboxやgploadなど)
  • ノードオブジェクトが使用しているマテリアルIDをgpnodeinfoで取得する。
  • gpmatstateでマテリアルの設定を変更。

 オブジェクト生成前なら、共通のマテリアルを1つ準備するだけですみます。しかし、後から設定を変更することはできません。 一方、オブジェクト生成後の場合は、オブジェクト1つ1つのマテリアルを書き換える必要がありますが、設定を動的に変更できます。つまり、メインループ内の任意のタイミングでマテリアルの設定を変更できます。 この辺の動きを正しく理解するには、マテリアルの取り扱いについて理解する必要があります。

 gptexmatgpcolormatでマテリアルオブジェクトを作った場合、マテリアルの雛形として使用されます。直接ノードオブジェクトで使用されることはありません。 gpboxgpplateなどノードオブジェクト生成時に、先に作成しておいたマテリアルオブジェクトを指定すると、作成したオブジェクト内に指定したマテリアルと同じ内容のマテリアルオブジェクトが新たに生成されます。

 オブジェクト生成前に作成したマテリアルは、ノードオブジェクトに紐づいていません。 このため、オブジェクト生成後に設定を変えてもノードオブジェクトには反映されません。 一方でgpnodeinfoでは、ノードオブジェクトに紐づいたマテリアルIDを取得するため、設定を変更するとノードオブジェクトに結果が反映されます。

 ということでUnityのMaterial Variantみたいな一括変更は、HGIMG4ではできません。出来たら出来たで余計わかりにくい気がするので、今の仕様はHSP3的でわかりやすくていいですね。 また、オブジェクト生成前にマテリアルを作成する方法だと、マテリアルの設定変更が反映されない場合があるのでこの点にも注意です。(HSP3.7β4現在)

 言葉だけではわかりにくいのでサンプルです。


  #include "hgimg4.as"

	;-----------------------------
	;	環境
	;-----------------------------
	gpreset
	setcls CLSMODE_SOLID, $808080		; 画面クリア設定
	setpos   GPOBJ_CAMERA, 0, 2, 4		; カメラ位置を設定
	gplookat GPOBJ_CAMERA, 0,0.5,0


	;-----------------------------
	;	マテリアルを生成
	;-----------------------------
	gptexmat   id_mat0, "res/qbox.png"
	; 裏面のみ表示(表面をカリング)
	gpmatstate id_mat0, "cullFaceSide", "FRONT"
	
	;-----------------------------
	;	箱ノード作成
	;-----------------------------
	; 同じ箱を2つ作成
	gpbox    id_box1, 1, -1, id_mat0
	gpbox    id_box2, 1, -1, id_mat0
	setpos   id_box1, -1.0, 0.5, 0.0
	setpos   id_box2,  1.0, 0.5, 0.0
	gpsetprm id_box1, PRMSET_ALPHA, 127
	gpsetprm id_box2, PRMSET_ALPHA, 127

	;-----------------------------
	;	マテリアルの設定を変更
	;-----------------------------
	; 右の箱(id_box1)のマテリアルを変更します。
	; 箱が実際に使用しているマテリアルIDを取得して変更します。

	; 生成されたマテリアルIDを取得
	; IDを確認するために取得しています。設定変更には使用していません。
	gpnodeinfo id_mat1, id_box1, GPNODEINFO_MATERIAL

	; 生成されたマテリアルIDを取得
	gpnodeinfo id_mat2, id_box2, GPNODEINFO_MATERIAL
	; Zバッファ書き込み無効
	gpmatstate id_mat2, "cullFace", "false"
	gpmatstate id_mat2, "depthWrite", "false"
	;gpmatprmt  id_mat2, , "res/body_SD.png"
	
	; id_mat0はノードオブジェクトには使用されていないので、
	; 変更してもオブジェクトには反映されません。
	;gpmatstate id_mat0, "cullFace", "false"
	;gpmatstate id_mat0, "depthWrite", "false"

	; gpcloneだとマテリアルが取得できない
	;gpclone  id_box3, id_box1
	;setpos   id_box3, 0.0, 0.5, 0.0
	;; gpsetprm id_box3, PRMSET_USEGPMAT, id_mat0	; 設定しても反映されない。
	;gpnodeinfo id_mat3, id_box3, GPNODEINFO_MATERIAL
	;logmes "" + id_mat3	; -1(マテリアルなし)になっている。
	;gpmatstate id_mat3, "cullFace", "false"
	;gpmatstate id_mat3, "depthWrite", "false"
	
	;-----------------------------
	;	床ノード作成
	;-----------------------------
	gpfloor id_floor, 30,30, $404040


*main
	stick key,15
	if key&128 : end

	redraw 0
		gpdraw
		pos 10, 10

		;	マテリアルIDの違い
		; オブジェクト生成に使用したマテリアル
		mes "id_mat0 = " + id_mat0
		; 生成されたマテリアル
		mes "id_mat1 = " + id_mat1
		mes "id_mat2 = " + id_mat2
		
	redraw 1
	await 1000/60

	goto *main

左の箱は裏面、右の箱は表面がレンダリングされる。

 左右どちらの箱も半透明表示です。左側の箱は、表面を非表示にして裏面だけ表示するようにしています。 右側の箱は、裏表どちらの面の表示しつつ深度での比較を行わないようにして、重なっている部分もすべて描画するようにしています。 ウィンドウ左上にマテリアルIDを表示しています。

 オブジェクトが作成されると、マテリアルIDが生成されたことを確認できると思います。 また、gpmatstateをコメントにすると共通のマテリアルから生成されたことが確認できます。 そういえばgpmatprmtに触れてませんでしたが、テクスチャ画像を変更する場合に使用します。

 コメントにしている行を使うと、オブジェクト生成前に作成したマテリアルid_mat0の設定を変更しても後から作った箱には反映されないことが確認できます。

 また、gpcloneでオブジェクトを作成するとgpnodeinfoでマテリアルが取得できないようです。これは不具合なんじゃないだろうか。(HSP3.7β4)

depthFunc

 あまり変更する機会はないと思いますが、depthFunc(Z値比較方法)についてちょっと書いてみます。

 通常は不透明なノードオブジェクトを描画する際、これから描くものが手前のもので隠れている位置の場合は、見えないはずなので描画しません。 これは「すでに描画されているもの(DST = DESTINATION)」と「これから描画するもの(SRC = SOURCE)」それぞれのカメラからの距離(Zバッファ、深度)を比較することで判定を行っています。

 この判定方法だと半透明なオブジェクトの場合、隠れているオブジェクトは見えないはずという前提は成り立たたないため、正しく描画されません。 また、カメラから遠い方のオブジェクトを描画したいといった特殊な表現にも対応できません。

 そこでdepthFunc(Z値比較方法)を使うと、この深度テストの評価方法を変更できます。 評価方法を逆にしてカメラから遠い方を描画したり、無効にして距離に関係なく描画したりといった事が可能になります。

 実際に動きを見たほうがわかりやすいので、サンプルを書いてみました。

サンプルを実行すると赤い箱が表示される。


  ;depthFuncのサンプル
  ;↑↓スペースで壁を動かせます。
  ;←→でdepthFuncを変更できます。
  ;
  #include "hgimg4.as"
  
    ;-----------------------------
    ;	環境
    ;-----------------------------
    gpreset
    setcls CLSMODE_SOLID, $808080		; 画面クリア設定
    setpos   GPOBJ_CAMERA, 0, 2, 6		; カメラ位置を設定
    gplookat GPOBJ_CAMERA, 0,0.5,0
  
  
    ;-----------------------------
    ;	マテリアルを生成
    ;-----------------------------
    ;gptexmat id_matbase, "res/qbox.png"	; テクスチャあり
    gpcolormat id_matbase, 0xff00ff	; テクスチャなし
  
    ; 深度テストの評価方法
    ; 深度(Z値、カメラからの距離)の比較方法を指定します。
    ; SRC = SOURCE      これから描こうとしているもの
    ; DST = DESTINATION すでに描かれているもの
    sdim depthFunc, 64, 8
    depthFunc(0) = "NEVER"		; FALSE      →描画しない
    depthFunc(1) = "LESS"		; SRC <  DST →近い方を描画(デフォルト値)
    depthFunc(2) = "EQUL"		; SRC =  DST →重なっている部分だけを描画
    depthFunc(3) = "LEQUAL"		; SRC <= DST →近い方を描画
    depthFunc(4) = "GREATER"	; SRC >  DST →遠くにある方を描画
    depthFunc(5) = "NOTEQUAL"	; SRC != DST →重なっていない部分を描画
    depthFunc(6) = "GEQUAL"		; SRC >= DST →遠くにある方を描画
    depthFunc(7) = "ALWAYS"		; TRUE       →全て描画
    mid = 1
    mid0 = mid
    gpmatstate id_matbase, "depthFunc", depthFunc(mid)
  
  
    ;-----------------------------
    ;	箱ノード量産
    ;-----------------------------
    dim id_boxc, 6
    dim id_mat, 6
    repeat 6
      ;gpbox    id_boxc(cnt), 1, -1
      ;gpsetprm id_boxc(cnt), PRMSET_USEGPMAT, id_matbase
  
      gpbox id_boxc(cnt), 1, -1, id_matbase
      gpnodeinfo id_mat(cnt), id_boxc(cnt), GPNODEINFO_MATERIAL
    loop
  
    ;	配置
    ; 左の3つ
    setpos  id_boxc(0), -2.0, 0.5,  0.0
    setpos  id_boxc(1), -1.5, 0.5, -1.0
    setpos  id_boxc(2), -1.0, 0.5, -2.0
    ; 右の3つ
    setpos  id_boxc(3), 1.0, 0.5, -2.0
    setpos  id_boxc(4), 1.5, 0.5, -1.0
    setpos  id_boxc(5), 2.0, 0.5,  0.0
    ;	半透明
    repeat 6
      gpsetprm id_boxc(cnt), PRMSET_ALPHA, 127
      ; gpsetprm id_boxc(cnt), PRMSET_MODE, OBJ_LATE	; 後から描画
    loop
  
  
    ;-----------------------------
    ;	床ノード
    ;-----------------------------
    gpfloor id_floor, 30,30, $404040
  
  
    ;-----------------------------
    ;	壁
    ;-----------------------------
    gpplate id_plate, 6, 3, $303050
    setpos  id_plate, 0, 0, -0.5
  
  *main
    stick key, 2+8
    if key&128 : end
  
    redraw 0
      ;	壁を移動
      ;↑↓スペース
      getpos id_plate, x,y,z
      if key &  2 : z -= 0.1
      if key &  8 : z += 0.1
      if key & 16 : z = -0.5
      setpos id_plate, x,y,z
  
      ;	depthFuncを変更
      ;←→
      if key & 1 : mid--
      if key & 4 : mid++
      if mid > 7 : mid = 0
      if mid < 0 : mid = 7
      if mid ! mid0 {
        repeat 6
          gpmatstate id_mat(cnt), "depthFunc", depthFunc(mid)
        loop
      }
      mid0 = mid
  
      gpdraw
      pos 10,10
      mes "板を移動 : ↑、↓、スペース"
      mes "マテリアルの設定を変更 : ←→\n"
      
      mes "depthFunc = " + depthFunc(mid)
      if depthFunc(mid) = "NEVER"    : s = "FALSE      描画しない"
      if depthFunc(mid) = "LESS"     : s = "SRC <  DST 近い方を描画(デフォルト値)"
      if depthFunc(mid) = "EQUL"     : s = "SRC =  DST 重なっている部分だけを描画"
      if depthFunc(mid) = "LEQUAL"   : s = "SRC <= DST 近い方を描画"
      if depthFunc(mid) = "GREATER"  : s = "SRC >  DST 遠くにある方を描画"
      if depthFunc(mid) = "NOTEQUAL" : s = "SRC != DST 重なっていない部分を描画"
      if depthFunc(mid) = "GEQUAL"   : s = "SRC >= DST 遠くにある方を描画"
      if depthFunc(mid) = "ALWAYS"   : s = "TRUE       全て描画"
      mes s
  
    redraw 1
    await 1000/60
  
    goto *main

 「↑、↓、スペース」キーで板を動かすことができます。 「←、→」でdepthFuncを変更できます。 箱は、左の箱から右への順で作成しました。 箱が重なっている部分や板と箱の関係に注視してください。

LESSLESS

EQULEQUL

LEQUALLEQUAL

GREATERGREATER

NOTEQUALNOTEQUAL

GEQUALGEQUAL

ALWAYSALWAYS

NEVERNEVER

 HGIMG4の場合、LESSが初期値のようです。 GREATERにすると板よりも遠い箱だけが表示されるので、板の中にしか箱が見えない状態になっていておもしろい表現です。 ALWAYSにするとどの箱も、箱どうしが重なっている部分がかける事なく描画されています。 EQULLESSと同じになっているのは…いいのかこれ?

gpsetprm

 あまりオススメはしませんが、gpboxでオブジェクトを作成した後にマテリアルを適用することもできます。gpsetprmを使用します。

 さきほどのサンプルのgpboxで箱を作成している部分を次のように書き換えます。


	repeat 6
		gpbox    id_boxc(cnt), 1, -1
		gpsetprm id_boxc(cnt), PRMSET_USEGPMAT, id_matbase
	loop

 これだけなのですが、HSP3.7β4現在では不具合があるのか半透明描画ができません。 箱の色が変わったりはするので、この書き方でマテリアルは適用されているはずなんですけどね。 HSP3.7β5で半透明表示されるように修正されました。 また、複数のマテリアルを内包している3Dモデル(gpbファイルから生成された3Dモデル)は、この方法で書き換えを行うことができないので注意してください。

3Dモデルのマテリアル設定変更

 複数のマテリアルを内包している3Dモデル(gpbファイルから生成された3Dモデル)のマテリアルの設定変更をやってみます。 HSP3に付属のtamane2.gpbがちょうど良さそうなので、これを使ってやってみます。

 しかしtamane2.gpbのような3Dモデルの場合、ここまでの説明の方法だけでは設定変更できません。 ここまでは、ノードオブジェクト1つに付き1つのマテリアルがある前提でしたが、tamane2.gpbの場合は4つのマテリアルを内包しています。 tamane2.gpbは、構造がatamaudebodykosiに分かれており、それぞれ別々のマテリアルオブジェクトを持っています。 マテリアルの設定変更をするには、内包しているマテリアルIDを取得して1個ずつ変更する必要があります。

 マテリアルIDを取得するには、gpnodeinfoに「階層ノード名」を追加するだけです。 この後の操作は、これまで取り扱っていた方法と同じです。前置きの割に簡単でしたね。

 サンプルを書いてみました。


  ;
  ; 3Dモデルのマテリアル設定変更
  ;
  #include "hgimg4.as"
  
  ;------------------------------
  ;	環境
  ;------------------------------
  gpreset
  setcls CLSMODE_SOLID, $404040
  setcolor GPOBJ_LIGHT, 1,1,1	
  setdir GPOBJ_LIGHT, 0.5,0.5,0.5
  
  ;------------------------------
  ;	オブジェクトノード
  ;------------------------------
  
  ;	床
  gpfloor id_floor, 30,30, $4040F0
  
  ;	壁
  gpplate id_plate, 10, 60, $303050
  setpos  id_plate, 5, 0, 10
  isPlateHide = 0	; 壁表示
  
  ;	3Dモデル
  gpload   id_model,"res/tamane2"		; モデル読み込み
  setscale id_model, 0.1,0.1,0.1
  
  ; 半透明
  gpsetprm id_model, PRMSET_ALPHA, 127
  
  ; アニメーションクリップ
  gpact id_model
  
  
  ;------------------------------
  ;	マテリアルを変更
  ;------------------------------
  
  ;	マテリアルIDを取得
  ; 3Dモデルが複数の階層構造を持つ場合は、各ノード毎にマテリアルIDを
  ; 取得する必要があります。
  gpnodeinfo mat_id(0), id_model, GPNODEINFO_MATERIAL, "atama"
  gpnodeinfo mat_id(1), id_model, GPNODEINFO_MATERIAL, "ude"
  gpnodeinfo mat_id(2), id_model, GPNODEINFO_MATERIAL, "body"
  gpnodeinfo mat_id(3), id_model, GPNODEINFO_MATERIAL, "kosi"
  repeat 4
    logmes "" + mat_id(cnt)
  loop
  ; 階層構造を考えずに取得すると-1が返されます。
  gpnodeinfo mid, id_model, GPNODEINFO_MATERIAL
  logmes "-> "+mid
  
  
  ;------------------------------
  ;	カメラ
  ;------------------------------
  setpos GPOBJ_CAMERA, 0,20,30
  
  ;------------------------------
  ;	メインループ
  ;------------------------------
  *main
    stick key, 256
    if key&128 : end
  
    redraw 0			; 描画開始
  
      ;------------------------------
      ;	マテリアルの設定を変更
      ;------------------------------
      ;←→
      if key & 1 : mode--
      if key & 4 : mode++
      if mode > 5 : mode = 0
      if mode < 0 : mode = 5
      if mode ! mode0 {
        ; 複数の階層構造を持つ3Dモデル全体に対してのマテリアル設定変更はできません。
        ; 全ての階層構造の一つ一つ全てに、マテリアルを設定する必要があります。
        ; blend      ブレンド無効     → 不透明な描画になるはず
        ; cullFace   カリング無効     → 裏面も見えるはず
        ; depthTest  深度テスト無効   → 奥側にある面も全て描画されるはず
        ; depthWrite 深度書き込み無効 → 奥側にある面も全て描画されるはず
        blend      = "true"
        cullFace   = "true"
        depthTest  = "true"
        depthWrite = "true"
        if mode = 0 : blend      = "false"
        if mode = 1 : cullFace   = "false"
        if mode = 2 : depthTest  = "false"
        if mode = 3 : depthWrite = "false"
        if mode = 4 : cullFace   = "false" : depthWrite = "false"
        repeat 4
          gpmatstate mat_id(cnt), "blend",      blend
          gpmatstate mat_id(cnt), "cullFace",   cullFace
          gpmatstate mat_id(cnt), "depthTest",  depthTest
          gpmatstate mat_id(cnt), "depthWrite", depthWrite
        loop
      }
      mode0 = mode
  
      ;------------------------------
      ;	壁の表示・非表示
      ;------------------------------
      if key & 16 {
        isPlateHide ^= 1
        if isPlateHide {
          setobjmode id_plate, OBJ_HIDE, 0
        } else {
          setobjmode id_plate, OBJ_HIDE, 1
        }
      }
  
      ;------------------------------
      ;	ドラッグ上下でカメラ操作
      ;------------------------------
      if key & 256 {
        dy=0.05*(mousey-dragy)+cy
        setpos GPOBJ_CAMERA, dx,dy,cz
      } else {
        dragy=mousey
        getpos GPOBJ_CAMERA, cx,cy,cz
      }
      gplookat GPOBJ_CAMERA, 0,14,0
    
      addang id_model,0,0.02		; ノード回転
      gpdraw				; シーンの描画
  
      ;------------------------------
      ;	状態を表示
      ;------------------------------
      color 255,255,255
      pos 8,8
      mes "マテリアルID"
      mes "atama:" + mat_id(0)
      mes "ude  :" + mat_id(1)
      mes "body :" + mat_id(2)
      mes "kosi :" + mat_id(3)
      mes "指定なし:"+mid
      mes ""
  
      ; マテリアルの設定
      mes "マテリアルの設定(←→で変更)"
      s = ""
      s +=   "blend      : " : if blend      = "true" : s+="○" : else : s+="×"
      s += "\ncullFace   : " : if cullFace   = "true" : s+="○" : else : s+="×"
      s += "\ndepthTest  : " : if depthTest  = "true" : s+="○" : else : s+="×"
      s += "\ndepthWrite : " : if depthWrite = "true" : s+="○" : else : s+="×"
      mes s
  
      mes "\nスペースキー:壁の表示/非表示"
      mes "マウス上下ドラッグ:カメラ移動"
  
    redraw 1			; 描画終了
    await 1000/60			; 待ち時間
  
    goto *main

 tamane2.gpbを半透明表示しています。 「←、→、スペース、マウスドラッグ」で操作できます。右半分の板は「スペースキー」で表示非表示の切り替えができます。

 構造毎にマテリアルIDが別々になっていることがわかります。また、階層ノード名を指定しない場合は、-1(マテリアルなし)になっています。

 マテリアルの設定を「←、→」キーで切り替えると、その効果の違いがわかります。 やはりdepthWriteは複雑な3Dモデルを半透明表示する際にこそ意味のある設定項目ですね。trueにしておかないと、見えないはずのものが見えてしまって見栄えがよくありません。 tamane2.materialは、マテリアルの設定が適切に行われていることもわかりましたね。

○○○○blend / cullFace / depthTest / depthWrite

×○○○- / cullFace / depthTest / depthWrite

○×○○blend / - / depthTest / depthWrite

○○×○blend / cullFace / - / depthWrite

○×○×blend / - / depthTest / -

アルファブレンディング

 オブジェクトが重なった部分を半透明描画をする際、重なった部分は元の色と重ねた色を混ぜた色になります。 この色を混ぜることをブレンディングと言うそうです。また特に、透明色情報(アルファ値)を使って色を混ぜることをアルファブレンディングというようです。 重なった色の混ぜ合わせ方(α値を掛けたり、地の色を掛けたりなど)も計算方法が色々あるのですが、これはブレンドファクターと呼ばれています。 ってここを書くにあたって参考にしたサイトに書いてあったので、ここでもこの呼び名を使用します。

参考資料:
wgld.org アルファブレンディング
https://wgld.org/d/webgl/w029.html

 hgimg4でもこの設定を変更できます。materialファイルを書き換えるかgpmatstate命令を使うことで変更が可能です。 ブレンディングを有効にするには、blendtrueに設定します。 ブレンドファクターの設定は、blendSrcblendDstで設定を行います。 設定は単純なんですが、知識がないと意味がわからない部分です。 総当たりでいい感じの設定を探すのは大変なので、少し意味を理解したいと思います。

 ブレンディングは、すでに描かれている色(描画先、DST = DESTINATION)の上に これから描こうとしている色(描画元、SRC = SOURCE)を乗せて混ぜ合わせる処理です。取り扱う色は、この2つだけです。 絵を描く方の場合は、ペイントソフトでレイヤーを重ねる操作をイメージするとわかりやすいかもしれません。 また、ブレンドファクターは、ペイントソフトのレイヤーのブレンドモード(乗算とか覆い焼きとか)をイメージするといいと思います。

 雰囲気をつかんだところで、ブレンドファクターの一覧を見てみます。

定数名 内容 値・式(R, G, B, A)
ZERO 即値(0) (0, 0, 0, 0)
ONE 即値(1) (1, 1, 1, 1)
SRC_COLOR 書き込み元カラー (Rs, Gs, Bs, As)
DST_COLOR 書き込み先カラー (Rd, Gd, Bd, Ad)
ONE_MINUS_SRC_COLOR 書き込み元カラー(反転値)(1, 1, 1, 1) - (Rs, Gs, Bs, As)
ONE_MINUS_DST_COLOR 書き込み先カラー(反転値)(1, 1, 1, 1) - (Rd, Gd, Bd, Ad)
SRC_ALPHA 書き込み元α (As, As, As, As)
DST_ALPHA 書き込み先α (Ad, Ad, Ad, Ad)
ONE_MINUS_SRC_ALPHA 書き込み元α(反転値) (1, 1, 1, 1) - (As, As, As, As)
ONE_MINUS_DST_ALPHA 書き込み先α(反転値) (1, 1, 1, 1) - (Ad, Ad, Ad, Ad)
CONSTANT_COLOR (hgimg4未実装) (Rc, Gc, Bc, Ac)
ONE_MINUS_CONSTANT_COLOR(hgimg4未実装) (1, 1, 1, 1) - (Rc, Gc, Bc, Ac)
CONSTANT_ALPHA α固定値 (Ac, Ac, Ac, Ac)
ONE_MINUS_CONSTANT_ALPHAα固定値(反転値) (1, 1, 1, 1) - (Ac, Ac, Ac, Ac)
SRC_ALPHA_SATURATE 書き込み元α反転値 (f, f, f, 1) f = min(As, 1 - Ad)

 どこがブレンドモードなんだか。意味わからないですね。まずは、表の見方から。定数名の意味は以下の表のようになっています。 例えば、ONE_MINUS_SRC_ALPHAなら「1 - 描画元のアルファ値」という感じです。そのままですね。

定数名 概要
ZERO 0
ONE 1
SRC SOURCE
描画元、これから描こうとしている色
DST DESTINATION
描画先、すでに描かれている色
_ALPHA アルファ値
_COLOR 色(R,G,B,A
ONE_MINUS_○○1 - ○○
CONSTANT 定数

 値・式のカッコ内は、(R,G,B,A)の4要素です。RGBAの右下の接尾辞は、以下の表の通り。 Rsなら描画元の赤ですね。

接尾辞名前意味
ssource 描画元
ddestination描画先
cconstant 定数

 選択した定数名値・式を使って描画元と描画先の2色を計算して、描画色が決定されます。 例を用いて計算方法を説明してみます。

 ブレンドファクターは、blendSrc、blendDstに定数名を指定することで設定します。 例えば、次のような設定を行った場合の描画色を考えてみます。単純な重ね合わせの場合です。

blendSrc = ONE
blendDst = ZERO


  描画色 = 描画元の色 * blendSrc + 描画先の色 * blendDst
  = (Rs,Gs,Bs,As) * (1,1,1,1) + (Rd,Gd,Bd,Ad) * (0,0,0,0)
  = (Rs,Gs,Bs,As)

 この設定の場合は、描画元の色で描画されます。もう1ケースやってみます。アルファブレンドの場合です。

blendSrc = SRC_ALPHA
blendDst = ONE_MINUS_SRC_ALPHA


  描画色 = 描画元の色 * blendSrc + 描画先の色 * blendDst
  = (Rs,Gs,Bs,As) * (As,As,As,As) + (Rd,Gd,Bd,Ad) * ((1, 1, 1, 1) - (As, As, As, As))
  = (Rs*As, Gs*As, Bs*As, As*As) + (Rd*(1-As), Gd*(1-As), Bd*(1-As), Ad*(1-As))
  = (Rs*As+Rd*(1-As), Gs*As+Gd*(1-As), Bs*As+Bd*(1-As), As*As+Ad*(1-As) )

数値を用いた具体例
描画元の色 (1 , 0, 0 , 0.7)
描画先の色 (0 , 0, 1 , 1)
描画色 (0.7, 0, 0.3, 0.79)

 このように、値・式を掛け算して、その結果を足し合わせたものが描画色です。 なお、合計が1を超えたら1、0を下回ったら0になるようです。 値・式を足したとき、1になるような組み合わせであれば、黒っぽくなったり明るくなりすぎたりは無いと思います。 基本的にHOGEHOGEONE_MINUS_HOGEHOGEみたいな組み合わせで使うと良さそうですね。

 さて、以上のようにブレンドファクターは計算式を与えるのですが、計算式には計算式以上の意味がないので、このままでは使いにくい。 そこで、ペイントソフト等で見慣れたレイヤーのブレンドモードに相当する組み合わせを調べてみました。

参考資料:
パーティクルで学ぶ OpenGL ブレンディング ( Android OpenGL フレームワーク “Rajawali” と戯れる #06 ) https://dev.classmethod.jp/articles/android-rajawali-tutorials-06/
melpon日記 - HaskellもC++もまともに扱えないへたれのページ https://melpon.hatenadiary.org/entry/20070824/p1

blendSrc blendDst 名称 アルファ値
ONE ZERO 上書き なし
SRC_ALPHA ONE_MINUS_SRC_ALPHAアルファ
ONE ONE 加算(覆い焼きリニア)なし
SRC_ALPHA ONE 加算(覆い焼きリニア)
ZERO SRC_COLOR 乗算 なし
DST_COLOR ZERO 乗算 なし
ONE_MINUS_DST_COLORONE スクリーン なし
ONE ONE_MINUS_SRC_COLORスクリーン なし
ONE_MINUS_DST_COLORONE_MINUS_SRC_COLORXOR(排他的論理和) なし
ONE_MINUS_DST_COLORZERO 反転 なし

 多少は見慣れた名前が付くので、使いやすくなった気がします。 使用していないブレンドファクターもいくつかありますが、調べてもどういうときに使うのかわかりませんでした。 使ってみて、描画結果がいい感じならそれでいいんだと思います。

blendSrc = SRC_ALPHA_SATURATE
blendDst = ONE
OpenGL Programming Guide
アンチエイリアシングが何だとか…やっぱりわかりません。

まとめ

 マテリアルの設定変更には、materialファイルを書き換える方法とgpmatstateを使用する方法があります。 プログラム側でマテリアルの設定を変更する場合は、いくつかのポイントを押さえないと設定が反映されないので注意が必要です。 基本的には、作成済みノードからgpnodeinfoでマテリアルIDを取得して、gpmatstateで変更という流れがいいようです。

 細かい調整方法については、UnityとかWebGLあたりで調べると情報が出てくると思います。多分。

関連記事

  1. UVマッピング はじめに サンプル 解説1 解説2 解説3 gpmeshpolygonの不具合?仕様? 法線ベクトル...
  2. シェーダー はじめに 参考になりそうな資料 バーテックスシェーダーとフラグメントシェーダー 変数 付属のシェーダ...
  3. パラメータ はじめに PRMSET_FLAG PRMSET_MODE PRMSET_MODE サンプル 正面属性...
  4. Zバッファ値 はじめに gpcnvaxis モード0の戻り値 問題点 サンプル レンダリングされる範囲 Zバッファ...
  5. ライト 陰と影 ライトの種類 アンビエントカラー ディレクショナルライトのサンプル ライト作成手順 ライト作...
  6. UVマッピング はじめに サンプル 解説1 解説2 解説3 gpmeshpolygonの不具合?仕様? 法線ベクトル...