|
|
|
2021/11/30(Tue) 13:02:09|NO.94574
●HGIMG4でblender 2.83のWorkbenchと同じアルゴリズムのMatcapを実装したい
通常のMatcapではなく、精度の高いMatcapを実装したいと思って、blender 2.83のWorkbenchのソースを参考にして、
同アルゴリズムを分析してHGIMG4に実装しました。
github blender 2.83 matcap関連のソース
https://github.com/blender/blender/blob/594f47ecd2d5367ca936cf6fc6ec8168c2b360d0/source/blender/draw/engines/workbench/shaders/workbench_matcap_lib.glsl
ですが、実際に動かしてみると、数日前に質問を投稿させて頂いた「常にテクスチャが正面を向くシェーダー」の問題と同じく、
広角透視モードでは正面を正しく向いてくれません。
自作したシェーダーを検証してみると、概念がよく分からない部分があって、
その一部が原因でシェーダーが上手く動作していないようなのです。
以下は、そのシェーダーです。
シェーダーのサンプルファイル
http://youdaizone.webcrow.jp/matcap_shader_test.zip
matcap_dev.vert
//------------------------------------------------------------------------------
// attribute
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec3 a_tangent;
attribute vec3 a_binormal;
//------------------------------------------------------------------------------
// uniform
// world
uniform mat4 u_worldMatrix;
uniform mat4 u_worldViewMatrix;
uniform mat4 u_worldViewProjectionMatrix;
// inverse
uniform mat4 u_inverseTransposeWorldMatrix;
uniform mat4 u_inverseTransposeWorldViewMatrix;
// projection
uniform mat4 u_projectionMatrix;
// view
uniform mat4 u_viewMatrix;
uniform mat4 u_viewProjectionMatrix;
// camera
uniform vec3 u_cameraWorldPosition;
uniform vec3 u_cameraViewPosition;
//------------------------------------------------------------------------------
// encode
/*
Compact Normal Storage for small G-Buffers のエンコーディングを GLSL に移植
*/
// GeForce 8800
// パフォーマンス enc 12.00, dec 15.00
// 品質 48.071
vec4 sphereMap_encode(vec3 n)
{
float p = sqrt(n.z * 8+8);
return vec4(n.xy / p + 0.5, 0, 0);
}
//------------------------------------------------------------------------------
// varying
varying vec4 v_normal;
varying mat4 v_projectionMatrix;
varying vec3 v_viewvec0;
varying vec3 v_viewvec1;
//------------------------------------------------------------------------------
// main
void main()
{
vec4 positionWorldViewSpace = u_worldViewMatrix * a_position;
mat3 inverseTransposeWorldViewMatrix = mat3(u_inverseTransposeWorldViewMatrix[0].xyz, u_inverseTransposeWorldViewMatrix[1].xyz, u_inverseTransposeWorldViewMatrix[2].xyz);
vec3 normalVector = normalize(inverseTransposeWorldViewMatrix * a_normal);
vec3 tangentVector = normalize(inverseTransposeWorldViewMatrix * a_tangent);
vec3 binormalVector = normalize(inverseTransposeWorldViewMatrix * a_binormal);
mat3 tangentSpaceTransformMatrix = mat3(tangentVector.x, binormalVector.x, normalVector.x, tangentVector.y, binormalVector.y, normalVector.y, tangentVector.z, binormalVector.z, normalVector.z);
vec3 cameraDirection = tangentSpaceTransformMatrix * (u_cameraWorldPosition - positionWorldViewSpace.xyz);
// 短い版?
//cameraDirection = positionWorldViewSpace.xyz - u_cameraWorldPosition;
/*
viewvec について
viewvecについては、そもそもカメラのベクトルなのかどうかもよく分からない。
しかし何らかの視点系ベクトルであると思われる。
*/
v_viewvec0 = u_cameraWorldPosition;
v_viewvec1 = cameraDirection;
vec3 N = inverseTransposeWorldViewMatrix * a_normal;
v_normal = sphereMap_encode(N); // 法線をエンコーディング
gl_Position = u_worldViewProjectionMatrix * a_position;
}
matcap_dev.frag
//------------------------------------------------------------------------------
// quality
#if defined(OPENGL_ES) || defined(GL_ES)
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
//------------------------------------------------------------------------------
// uniform
uniform sampler2D u_matcapTexture;
uniform float u_aspect;
//------------------------------------------------------------------------------
// decode
/*
Compact Normal Storage for small G-Buffers のデコーディングを GLSL に移植
*/
vec3 sphereMap_decode(vec2 enc)
{
vec2 fenc = enc * 4 - 2;
float f = dot(fenc, fenc);
float g = sqrt(1 - f / 4);
vec3 n;
n.xy = fenc * g;
n.z = 1 - f / 2;
return n;
}
//------------------------------------------------------------------------------
// matcap
// blender 2.83.18 workbench matcap
// I = 不明。恐らく何らかの視点系ベクトル
// N = 法線。恐らく (mat3(u_inverseTransposeWorldViewMatrix) * a_normal で処理した後に指定するもの?
// flipped = 反転スイッチ
vec2 matcap_uv_compute(vec3 I, vec3 N, bool flipped)
{
// Quick creation of an orthonormal basis
// Iに関係する処理 平行投影のベクトルは 0.0, 0.0, 1.0
float a = 1.0 / (1.0 + I.z); // 平行投影の場合、a = 0.5
float b = -I.x * I.y * a; // 平行投影の場合、b = 0.0
vec3 b1 = vec3(1.0 - I.x * I.x * a, b, -I.x); // 平行投影の場合、b1 = 1.0, 0.0, 0.0
vec3 b2 = vec3(b, 1.0 - I.y * I.y * a, -I.y); // 平行投影の場合、b2 = 0.0, 1.0, 0.0
// Iを処理したb1、b2とNをdot積する
vec2 matcap_uv = vec2(dot(b1, N), dot(b2, N));
// 平行投影の場合、matcap_uv.x は 法線.x の方向にだけ向き、
// matcap_uv.y は 法線.y の方向にだけ向くことになる
// 左右反転処理、及び、そのスイッチ
if (flipped) {
matcap_uv.x = -matcap_uv.x;
}
// センタリグ処理
return matcap_uv * 0.496 + 0.5;
}
// uv = 不明
// viewvec0 = 不明
// viewvec1 = 不明
// proj_mat = u_projectionMatrix
/*
本来の関数は
vec3 view_vector_from_screen_uv(vec2 uv, vec4 viewvec[3], mat4 proj_mat)
{
if (proj_mat[3][3] == 0.0) {
// 広角透視モード
return normalize(viewvec[0].xyz + vec3(uv, 0.0) * viewvec[1].xyz);
}
else {
// 平行投影モード
return vec3(0.0, 0.0, 1.0);
}
}
というアルゴリズムだったが、以下のものと結果的には同じもの。
分かりやすくするために、以下のようにした。
*/
// このuv、viewvec0、viewvec1に何を指定するべきかがよく分からない
vec3 view_vector_from_screen_uv(vec2 uv, vec3 viewvec0, vec3 viewvec1, mat4 proj_mat)
{
if (proj_mat[3][3] == 0.0) {
// 広角透視モード
return normalize(viewvec0 + vec3(uv, 0.0) * viewvec1);
}
else {
// 平行投影モード
return vec3(0.0, 0.0, 1.0);
}
}
//------------------------------------------------------------------------------
// varying
varying vec4 v_normal;
varying mat4 v_projectionMatrix;
varying vec3 v_viewvec0;
varying vec3 v_viewvec1;
//------------------------------------------------------------------------------
// main
void main()
{
// uv の値は、これで正しいのかよく分からない
vec2 uv = gl_FragCoord.xy * vec2(u_aspect, 1.0);
/* Normal and Incident vector are in viewspace. Lighting is evaluated in viewspace. */
vec3 I = view_vector_from_screen_uv(uv, v_viewvec0, v_viewvec1, v_projectionMatrix);
vec3 N = sphereMap_decode(v_normal.xy); // 法線をデコーディング
vec2 matcap_uv = matcap_uv_compute(I, N, 0);
gl_FragColor = texture2D(u_matcapTexture, matcap_uv);
}
cop.material
material matcap_shader
{
u_worldMatrix = WORLD_MATRIX
u_worldViewMatrix = WORLD_VIEW_MATRIX
u_worldViewProjectionMatrix = WORLD_VIEW_PROJECTION_MATRIX
u_inverseTransposeWorldViewMatrix = INVERSE_TRANSPOSE_WORLD_VIEW_MATRIX
u_inverseTransposeWorldMatrix = INVERSE_TRANSPOSE_WORLD_MATRIX
u_projectionMatrix = PROJECTION_MATRIX
u_cameraWorldPosition = CAMERA_WORLD_POSITION
u_cameraViewPosition = CAMERA_VIEW_POSITION
u_viewMatrix = VIEW_MATRIX
u_viewProjectionMatrix = VIEW_PROJECTION_MATRIX
u_matrixPalette = MATRIX_PALETTE
u_aspect = 1.333333333333333
sampler u_matcapTexture
{
mipmap = false
wrapS = REPEAT
wrapT = REPEAT
minFilter = LINER
magFilter = LINER
}
renderState
{
cullFace = true
depthTest = true
}
technique
{
pass
{
vertexShader = matcap_dev.vert
fragmentShader = matcap_dev.frag
}
}
}
material mc : matcap_shader
{
sampler u_matcapTexture
{
path = res/m000.png
}
}
これは平行投影モードでは正確にMatcapなので、matcap_uv_compute自体が動作していないわけではないようです。
おそらく、view_vector_from_screen_uv関数に渡す値が間違っているのだと思います。
このuv、viewvec0、viewvec1に渡す正しい値さえ分かれば、おそらく広角透視モードでも正しく正面を向いてくれるはずだと思います。
(matcapは本来は常に正面を向くシェーダーです)
view_vector_from_screen_uv関数の関数名から想像すると、ビューベクトルからスクリーンUVに変換する関数なのだと思います。
view_vector_from_screen_uv関数のアルゴリズムは一見単純で
return normalize(viewvec0 + vec3(uv, 0.0) * viewvec1);
これだけが本体なようなもので、すぐに分かるだろうと思っていたのですが、
これがどうしてビューからスクリーンUVへの変換になるのか想像できません。
ちなみにsphereMap_encode関数、sphereMap_decode関数については、法線のクオリティを調整する関数なので、
今回の問題とはあまり関係ないと思われます。
もしこれがシェーダーの動作に問題あるようであれば、外して貰っても構いません。
仕組みが違うから同じ問題は発生しないかもと思ったのですが、これも「常にテクスチャが正面を向くシェーダー」の時と同じ問題が発生しているのでしょうか?
これが広角透視モードでも正しく正面を向くよう、アドバイスお願い致します。
| |
|
2021/12/2(Thu) 00:43:46|NO.94586
viewvecの詳細らしきものを見つけました。
https://www.mail-archive.com/bf-blender-cvs@blender.org/msg129323.html
google日本語翻訳で簡易的に訳したものは以下のものです。
(viewvecs[0]は本シェーダーではviewvecs0、viewvecs[1]は本シェーダーではviewvec1です)
翻訳文--------------------------------------------------------------------------------------------------------------
view_vecs [1]は、左下隅から右上隅に向かうベクトルです。
Perspの場合:view_vecs [0] .xyとview_vecs [1] .xyはそれぞれ左下隅です
Z = 1の場合、左上隅はZ = 1の場合
view_vecs [0] .zニアクリップ距離およびview_vecs [1] .zは、ニアプレーンからファークリッププレーンまでの(符号付き)+ *距離です。
copy_v3_v3(storage-> viewvecs [0]、view_vecs [0]);
/ *差異を保存する必要があります* /
storage-> viewvecs [1] [0] = view_vecs [1] [0] --view_vecs [0] [0];
storage-> viewvecs [1] [1] = view_vecs [2] [1] --view_vecs [0] [1];
storage-> viewvecs [1] [2] = view_vecs [3] [2] --view_vecs [0] [2];
翻訳文終了--------------------------------------------------------------------------------------------------------
|
|
2021/12/2(Thu) 01:02:12|NO.94587
原文を読み直して、翻訳文を一部修正しました。
原文の一部
/**
+ * If ortho : view_vecs[0] is the near-bottom-left corner of the frustum and
+ * view_vecs[1] is the vector going from the near-bottom-left corner to
+ * the far-top-right corner.
+ * If Persp : view_vecs[0].xy and view_vecs[1].xy are respectively the bottom-left corner
+ * when Z = 1, and top-left corner if Z = 1.
+ * view_vecs[0].z the near clip distance and view_vecs[1].z is the (signed)
+ * distance from the near plane to the far clip plane.
+ */
平行投影の場合:
view_vecs [0]は錐台の左下隅にあり、view_vecs [1]は、左下隅から右上隅に向かうベクトルです。
広角透視の場合:
view_vecs [0] .xyとview_vecs [1] .xyはそれぞれ左下隅です。
Z = 1の場合、左上隅はZ = 1の場合
view_vecs [0] .zニアクリップ距離およびview_vecs [1] .zは、ニアプレーンからファークリッププレーンまでの(符号付き)+ *距離です。
copy_v3_v3(storage-> viewvecs [0]、view_vecs [0]);
/ *差異を保存する必要があります* /
storage-> viewvecs [1] [0] = view_vecs [1] [0] --view_vecs [0] [0];
storage-> viewvecs [1] [1] = view_vecs [2] [1] --view_vecs [0] [1];
storage-> viewvecs [1] [2] = view_vecs [3] [2] --view_vecs [0] [2];
|
|
2021/12/3(Fri) 20:19:00|NO.94595
私も全体が理解できていないので外している可能性はありますが
それっぽい動作ができましたのでご参考まで.
matcap_dev.vert varying 以下のみ
// 略
//------------------------------------------------------------------------------
// varying
varying vec4 v_normal;
varying mat4 v_projectionMatrix;
//varying vec3 v_viewvec0;
//varying vec3 v_viewvec1;
//------------------------------------------------------------------------------
// main
void main()
{
mat3 inverseTransposeWorldViewMatrix = mat3(u_inverseTransposeWorldViewMatrix[0].xyz, u_inverseTransposeWorldViewMatrix[1].xyz, u_inverseTransposeWorldViewMatrix[2].xyz);
v_projectionMatrix = u_projectionMatrix;
vec3 N = inverseTransposeWorldViewMatrix * a_normal;
v_normal = sphereMap_encode(N); // 法線をエンコーディング
gl_Position = u_worldViewProjectionMatrix * a_position;
}
matcap_dev.frag varying 以下のみ
// 略
//------------------------------------------------------------------------------
// varying
varying vec4 v_normal;
varying mat4 v_projectionMatrix;
//varying vec3 v_viewvec0;
//varying vec3 v_viewvec1;
//------------------------------------------------------------------------------
// main
void main()
{
vec3 v_viewvec0 = vec3(0.0, 0.0, 0.0);
vec3 v_viewvec1 = vec3(0.0, 0.0, 0.0);
vec2 reso = vec2(800.0, 600.0);
float aspectw = reso.x / reso.y;
if (v_projectionMatrix[3][3] == 0.0) {
// perspective
float ang = 51.48 * 3.141592 / 180.0;
float t = tan(ang * 0.5);
float near = 0.1;
float far = 50.0;
v_viewvec1 = vec3( t * aspectw * 2.0, t * 2.0, far - near);
// v_viewvec0 = vec3(-t * aspectw, -t, near);
v_viewvec0 = vec3(-t * aspectw, -t, 1.0);
} else {
// ortho
float near = 0.1;
float far = 100.0;
float zoom = 8.0;
float h = zoom * 0.5;
float w = h * aspectw;
v_viewvec1 = vec3(w * 2.0, h * 2.0, far - near);
v_viewvec0 = vec3(-w, -h, near);
}
// uv の値は、これで正しいのかよく分からない
// vec2 uv = gl_FragCoord.xy * vec2(u_aspect, 1.0);
vec2 uv = vec2(gl_FragCoord.x / reso.x, gl_FragCoord.y / reso.y);
/* Normal and Incident vector are in viewspace. Lighting is evaluated in viewspace. */
vec3 I = view_vector_from_screen_uv(uv, v_viewvec0, v_viewvec1, v_projectionMatrix);
vec3 N = sphereMap_decode(v_normal.xy); // 法線をデコーディング
vec2 matcap_uv = matcap_uv_compute(I, N, false);
gl_FragColor = texture2D(u_matcapTexture, matcap_uv);
}
なお view_vector_from_screen_uv は、スクリーンUVからビューベクトルの計算です。
| |
|
2021/12/5(Sun) 10:10:22|NO.94611
アドバイスありがとうございます。
砂時計さんのシェーダーを検証してみましたが、広角透視時にはmatcapが正面を向いていませんでした。
質問した後に自分でも様々な方法で試行錯誤してきましたが、
やはりview_vector_from_screen_uv関数の部分でとん挫してしまいました。
そこでシェーダーの趣旨を変えてblenderのworkbenchのmatcapを再現するのではなく、
「広角透視時でもmatcapに正面を向かせる」ことだけ重視したシンプルなmatcapシェーダーを作りました。
シェーダーサンプルファイル
http://youdaizone.webcrow.jp/simple_matcap.zip
シェーダーサンプルファイルに同梱した matcap_sample.blend に、
workbenchではなく、eeveeのシェーダーノードエディターで作成したmatcapのサンプルを作ってあります。
これは常に正面を向くmatcapです。これの肝はblenderでいう所の「ベクトル変換」ノードです。
これをそのままGLSLで再現したいです。
matcap_simple.vert
// 常に正面を向くmatcapシェーダー
// モデルの法線や位置を調整するのではなく、空間そのものをコンバートして、それを法線へ処理したい
//------------------------------------------------------------------------------
// attribute
attribute vec4 a_position;
attribute vec3 a_normal;
//------------------------------------------------------------------------------
// uniform
// world
uniform mat4 u_worldMatrix;
uniform mat4 u_worldViewMatrix;
uniform mat4 u_worldViewProjectionMatrix;
// inverse
uniform mat4 u_inverseTransposeWorldMatrix;
uniform mat4 u_inverseTransposeWorldViewMatrix;
// view
uniform mat4 u_viewMatrix;
uniform mat4 u_viewProjectionMatrix;
// projection
uniform mat4 u_projectionMatrix;
// camera
uniform vec3 u_cameraWorldPosition;
uniform vec3 u_cameraViewPosition;
//------------------------------------------------------------------------------
// matcap
// blenderでいう所の「ベクトル変換」をしたい
// space0 = 変換元空間
// space1 = 変換先空間
// normal = 法線
vec3 cnv_space_normal(mat4 space0, mat4 space1, vec3 normal)
{
// u_worldViewProjectionMatrixなどの行列が空間ということなのかも?
// 行列そのものを何らかの処理でトランスフォームしたものを
// normalと乗算するのだと思われる。
// オブジェクト空間というのはu_worldMatrixのこと?
// カメラ空間というのはu_viewMatrixのこと?
// どうすれば空間を変換できたことになるのか分からない
mat4 cnvSpace = space1 * space0;
return mat3(cnvSpace) * normal;
}
// シンプル版
vec2 matcap_uv_compute(mat4 objectSpace, mat4 cameraSpace, vec3 normal)
{
// オブジェクト空間から、カメラ空間へnormalを変換
vec3 uv = cnv_space_normal(objectSpace, cameraSpace, normal);
// mapping
uv = uv * vec3(0.496, 0.496, 1.0);
uv = uv + vec3(0.5, 0.5, 0.0);
return uv.xy;
}
//------------------------------------------------------------------------------
// varying
varying vec2 v_uv;
//------------------------------------------------------------------------------
// main
void main()
{
v_uv = matcap_uv_compute(u_worldMatrix, u_viewMatrix, a_normal);
gl_Position = u_worldViewProjectionMatrix * a_position;
}
matcap_simple.frag
//------------------------------------------------------------------------------
// quality
#if defined(OPENGL_ES) || defined(GL_ES)
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
//------------------------------------------------------------------------------
// uniform
uniform sampler2D u_matcapTexture;
//------------------------------------------------------------------------------
// varying
varying vec2 v_uv;
//------------------------------------------------------------------------------
// main
void main()
{
gl_FragColor = texture2D(u_matcapTexture, v_uv);
}
それでよく分からないところは、blenderの「ベクトル変換」ノードをどうやってGLSLで書くかというところです。
おそらく、空間とはu_worldViewProjectionMatrix等の行列のことで、その行列に関する部分だと思うのですが、よく分かりません。
matcap_sample.blend をGLSLで再現できれば、おそらく「広角透視時でもmatcapに正面を向かせる」シェーダーができるはずです。
アドバイスお願い致します。
| |
|
2021/12/5(Sun) 10:33:25|NO.94613
自分は普段HLSLを使ってるからGLSLは良く分からないけど
attribute vec3 a_normal;
これが恐らく頂点法線ですよね?
であれば
この頂点法線をカメラから見た向きに変換するだけで良いから
uniform mat4 u_worldViewMatrix;
この行列で変換した法線ベクトルを
スフィアマップの座標に変換すれば良いのでは?
|
|
2021/12/5(Sun) 10:39:30|NO.94614
>uniform mat4 u_worldViewMatrix;
>この行列で変換した法線ベクトルを
>スフィアマップの座標に変換すれば良いのでは?
スフィアマップの座標とは何ですか?
|
|
2021/12/5(Sun) 10:51:50|NO.94615
もしかしてスフィアマップの座標とは、エンコード・デコード用に使っている関数のことでしょうか?
これは文字通りエンコード・デコード用の関数なので座標は持ちません。
この関数の中のNの法線について指摘されているのでしょうか?
>attribute vec3 a_normal;
>これが恐らく頂点法線ですよね?
>であれば
>この頂点法線をカメラから見た向きに変換するだけで良いから
>uniform mat4 u_worldViewMatrix;
>この行列で変換した法線ベクトルを
>スフィアマップの座標に変換すれば良いのでは?
u_worldViewMatrixを法線に乗算すると平行投影時にはmatcapの向きが画面を向きますが、
広角透視時では正面のオブジェクトしか画面を向きません。
u_inverseTransposeWorldViewMatrixと同じです。
|
|
2021/12/5(Sun) 14:40:45|NO.94622
目的を達成するまでに課題が2つあるように見えますね。
まず1つめから。
"matcap_sample.blend をGLSLで再現できれば、おそらく「広角透視時でもmatcapに正面を向かせる」シェーダーができるはずです。"について
ほんと? という点です。
matcap_sample.blend で HSP3 側と似た状況を作るために
以下のように球体を追加しカメラも設定を合わせてみてください。
追加→メッシュ→UV球 を1つ作る。
マテリアルプロパティから user_matcap を指定する。
オブジェクトデータプロパティ→ノーマル
→自動スムーズにチェックを入れる。
モディファイアプロパティで
重み付き法線を追加する。
モディファイアプロパティで
配列を追加する。数を5にする。
ビュー→視点→カメラを選択する。(または F4 を押す。)
Camera のオブジェクトプロパティで
位置を (4,-8,0),回転を (90°,0°,0°) にする。
Camera のオブジェクトプロパティで
焦点距離を28mmにする。
このようにすると
5つの球体がおおよそ描画フレームにちょうど入った状態になっています。
これがこの質問の最初の HSP3 側の状態に近いものだと思います。
このとき球面に映っているものは
私の blender の環境では正面を向いていませんが
youdai さんの環境でも正面を向かないのではないでしょうか?
※私の認識では透視投影での user_matcap の処理では
正面を向かなくて正しいと思っています。
|
|
2021/12/5(Sun) 17:37:18|NO.94625
前回の NO.94556 と同じ方針で実装すると以下のようになります。
例によって端にいくほど歪みますがなんとなく正面を向きます。
matcap_simple.vert varying 以下
// 略
//------------------------------------------------------------------------------
// varying
varying vec2 v_uv;
//------------------------------------------------------------------------------
// main
void main()
{
vec3 uv = (u_inverseTransposeWorldViewMatrix * vec4(a_normal, 0.0)).xyz;
if (u_projectionMatrix[3][3] == 0.0) {
vec3 r = normalize((u_worldViewMatrix * a_position).xyz);
// Y軸回り
vec3 s = vec3(-r.z * uv.x + r.x * uv.z, uv.y, r.x * uv.x - r.z * uv.z);
// X軸回り
uv = vec3(s.x, - r.z * s.y + r.y * s.z, - r.y * s.y - r.z * s.z);
}
v_uv = uv.xy * 0.496 + 0.5;
gl_Position = u_worldViewProjectionMatrix * a_position;
}
|
|
2021/12/5(Sun) 18:14:09|NO.94626
砂時計さんへ
blenderのuser_matcapを確認してみた所、ご指摘の通りに確かに完全にはカメラに正面を向いていませんでした。
画面の淵に近づくに従って、外側を僅かに向いていることを確認しました。
user_matcap方式のmatcapのアドバイスありがとうございます。
実際にゲームシーンで使ってみましたが、自分にとっては十分な精度がありました。
非常に助かりました。
実はもう半ば諦めかけていて、今度の方式がダメならもう諦めようと思っていました。
質問して良かったです。
大変参考になりました。
ありがとうございました!
|
|
2021/12/6(Mon) 20:03:54|NO.94627
すでに解決となっていますが
NO.94625 はX軸Y軸上以外ではちょっと変換が怪しい感じだったので
元々のコードに登場する、Iベクトルに対する直交基底を使用する関数
matcap_uv_compute() から算出するコードを改めてまとめました。
ご参考までに。
matcap_dev.vert
//------------------------------------------------------------------------------
// attribute
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec3 a_tangent;
attribute vec3 a_binormal;
//------------------------------------------------------------------------------
// uniform
// world
uniform mat4 u_worldMatrix;
uniform mat4 u_worldViewMatrix;
uniform mat4 u_worldViewProjectionMatrix;
// inverse
uniform mat4 u_inverseTransposeWorldMatrix;
uniform mat4 u_inverseTransposeWorldViewMatrix;
// projection
uniform mat4 u_projectionMatrix;
// view
uniform mat4 u_viewMatrix;
uniform mat4 u_viewProjectionMatrix;
// camera
uniform vec3 u_cameraWorldPosition;
uniform vec3 u_cameraViewPosition;
//------------------------------------------------------------------------------
// encode
/*
Compact Normal Storage for small G-Buffers のエンコーディングを GLSL に移植
*/
// GeForce 8800
// パフォーマンス enc 12.00, dec 15.00
// 品質 48.071
vec4 sphereMap_encode(vec3 n)
{
float p = sqrt(n.z * 8+8);
return vec4(n.xy / p + 0.5, 0, 0);
}
//------------------------------------------------------------------------------
// varying
varying vec4 v_normal;
//varying mat4 v_projectionMatrix;
varying float v_proj33;
//------------------------------------------------------------------------------
// main
void main()
{
mat3 inverseTransposeWorldViewMatrix = mat3(u_inverseTransposeWorldViewMatrix[0].xyz, u_inverseTransposeWorldViewMatrix[1].xyz, u_inverseTransposeWorldViewMatrix[2].xyz);
// v_projectionMatrix = u_projectionMatrix;
v_proj33 = u_projectionMatrix[3][3];
vec3 N = inverseTransposeWorldViewMatrix * a_normal;
v_normal = sphereMap_encode(N); // 法線をエンコーディング
gl_Position = u_worldViewProjectionMatrix * a_position;
}
matcap_dev.frag
//------------------------------------------------------------------------------
// quality
#if defined(OPENGL_ES) || defined(GL_ES)
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
//------------------------------------------------------------------------------
// uniform
uniform sampler2D u_matcapTexture;
uniform float u_aspect;
//------------------------------------------------------------------------------
// decode
/*
Compact Normal Storage for small G-Buffers のデコーディングを GLSL に移植
*/
vec3 sphereMap_decode(vec2 enc)
{
vec2 fenc = enc * 4 - 2;
float f = dot(fenc, fenc);
float g = sqrt(1 - f / 4);
vec3 n;
n.xy = fenc * g;
n.z = 1 - f / 2;
return n;
}
//------------------------------------------------------------------------------
// matcap
// blender 2.83.18 workbench matcap
// I = 不明。恐らく何らかの視点系ベクトル
// N = 法線。恐らく (mat3(u_inverseTransposeWorldViewMatrix) * a_normal で処理した後に指定するもの?
// flipped = 反転スイッチ
vec2 matcap_uv_compute(vec3 I, vec3 N, bool flipped)
{
// Quick creation of an orthonormal basis
// Iに関係する処理 平行投影のベクトルは 0.0, 0.0, 1.0
float a = 1.0 / (1.0 + I.z); // 平行投影の場合、a = 0.5
float b = -I.x * I.y * a; // 平行投影の場合、b = 0.0
vec3 b1 = vec3(1.0 - I.x * I.x * a, b, -I.x); // 平行投影の場合、b1 = 1.0, 0.0, 0.0
vec3 b2 = vec3(b, 1.0 - I.y * I.y * a, -I.y); // 平行投影の場合、b2 = 0.0, 1.0, 0.0
// Iを処理したb1、b2とNをdot積する
vec2 matcap_uv = vec2(dot(b1, N), dot(b2, N));
// 平行投影の場合、matcap_uv.x は 法線.x の方向にだけ向き、
// matcap_uv.y は 法線.y の方向にだけ向くことになる
// 左右反転処理、及び、そのスイッチ
if (flipped) {
matcap_uv.x = -matcap_uv.x;
}
// センタリグ処理
return matcap_uv * 0.496 + 0.5;
}
//------------------------------------------------------------------------------
// varying
varying vec4 v_normal;
//varying mat4 v_projectionMatrix;
varying float v_proj33;
//------------------------------------------------------------------------------
// main
void main()
{
// view 座標系で、カメラから点へ
vec3 to_point = vec3(0.0, 0.0, -1.0);
if (v_proj33 == 0.0) {
// perspective
vec2 reso = vec2(800.0, 600.0);
float aspectw = reso.x / reso.y;
vec2 uv = vec2(gl_FragCoord.x / reso.x, gl_FragCoord.y / reso.y);
float ang = 51.48 * 3.141592 / 180.0;
float t = tan(ang * 0.5);
float near = 0.1;
float far = 50.0;
vec3 v_viewvec1 = vec3( t * aspectw * 2.0, t * 2.0, far - near);
// vec3 v_viewvec0 = vec3(-t * aspectw, -t, near);
vec3 v_viewvec0 = vec3(-t * aspectw, -t, -1.0);
to_point = normalize(v_viewvec0 + vec3(uv, 0.0) * v_viewvec1);
}
vec3 N = sphereMap_decode(v_normal.xy); // 法線をデコーディング
vec2 matcap_uv = matcap_uv_compute(-to_point, N, false);
gl_FragColor = texture2D(u_matcapTexture, matcap_uv);
}
| |
|
2021/12/14(Tue) 19:42:51|NO.94698
砂時計さん新しいアドバイスありがとうございます。
あれから自分もblender 2.83のworkbenchのmatcapの再現を、自分なりに試行錯誤して改良を重ねました。
このシェーダーは広角透視時でも、workbenchのmatcapにかなり近いのではないかと思います。
これは自分なりのmatcapシェーダー完成版です。
matcap.vert
//------------------------------------------------------------------------------
// attribute
attribute vec4 a_position;
attribute vec3 a_normal;
//------------------------------------------------------------------------------
// uniform
// world
uniform mat4 u_worldMatrix;
uniform mat4 u_worldViewMatrix;
uniform mat4 u_worldViewProjectionMatrix;
// inverse
uniform mat4 u_inverseTransposeWorldMatrix;
uniform mat4 u_inverseTransposeWorldViewMatrix;
// projection
uniform mat4 u_projectionMatrix;
// view
uniform mat4 u_viewMatrix;
uniform mat4 u_viewProjectionMatrix;
// camera
uniform vec3 u_cameraWorldPosition;
uniform vec3 u_cameraViewPosition;
//------------------------------------------------------------------------------
// varying
varying vec2 v_uv;
//------------------------------------------------------------------------------
// blender 2.83.18 workbench matcap
vec2 matcap_uv_compute(vec3 I, vec3 N, bool flipped)
{
// Quick creation of an orthonormal basis
// Iに関係する処理 平行投影のベクトルは 0.0, 0.0, 1.0
float a = 1.0 / (1.0 + I.z); // 平行投影の場合、a = 0.5
float b = -I.x * I.y * a; // 平行投影の場合、b = 0.0
vec3 b1 = vec3(1.0 - I.x * I.x * a, b, -I.x); // 平行投影の場合、b1 = 1.0, 0.0, 0.0
vec3 b2 = vec3(b, 1.0 - I.y * I.y * a, -I.y); // 平行投影の場合、b2 = 0.0, 1.0, 0.0
// Iを処理したb1、b2とNをdot積する
vec2 matcap_uv = vec2(dot(b1, N), dot(b2, N));
// 平行投影の場合、matcap_uv.x は 法線.x の方向にだけ向き、
// matcap_uv.y は 法線.y の方向にだけ向くことになる
// 左右反転処理、及び、そのスイッチ
if (flipped) {
matcap_uv.x = -matcap_uv.x;
}
// センタリグ処理
return matcap_uv * 0.496 + 0.5;
}
//------------------------------------------------------------------------------
// main
void main()
{
vec3 viewVec;
if(u_projectionMatrix[3][3] == 0.0)
{
vec4 positionWorldViewSpace = u_worldViewMatrix * a_position;
vec3 cameraDirection = u_cameraViewPosition - positionWorldViewSpace.xyz;
viewVec = normalize(cameraDirection);
} else {
viewVec = vec3(0.0, 0.0, 1.0);
}
vec3 normal = (u_inverseTransposeWorldViewMatrix * vec4(a_normal, 0.0)).xyz;
v_uv = matcap_uv_compute(viewVec, normal, false);
gl_Position = u_worldViewProjectionMatrix * a_position;
}
matcap.frag
//------------------------------------------------------------------------------
// quality
#if defined(OPENGL_ES) || defined(GL_ES)
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
//------------------------------------------------------------------------------
// uniform
uniform sampler2D u_matcapTexture;
//------------------------------------------------------------------------------
// varying
varying vec2 v_uv;
//------------------------------------------------------------------------------
// main
void main()
{
gl_FragColor = texture2D(u_matcapTexture, v_uv);
}
| |
|