Siv3Dでシェーダ入門する
2019/04/06
Siv3Dを使うといろいろ楽にできる。 シェーダも楽なんじゃないかと期待して触り始めた。
ソース
今のところ、Siv3Dでシェーダを触る記事自体があまりないため、結局MicrosoftのリファレンスなりUnityのシェーダ使用例を参照することになる。
したがって、調べるという点ではあまり楽だとは思わない。関数群のやっていることや使い方も良くわからないし。
ただこれはある程度触って基本的な流れがわかってくると、なんとなくでもどういうことを中でしているか想像できるようになってくるんじゃなかろうか。
使うという点ではすごく楽でいい。
ほかの環境でシェーダ使ったことはないが、シェーダファイルとシェーダ利用部分だけに注力しやすい。
公式のsampleで一番楽なのはおそらくこれ。
ただコピペだけでは動かないかもしれない。
自分が動かすためにやったメモ。
とりあえずMSVCの新規作成でHLSLのテンプレートは7つ出てくる設定にする。
設定を書き換えれば1つのテンプレートからどのシェーダファイルも作れるのだろうが面倒だ。
VisualStudio の HLSL テンプレートがおかしい
VisualStudio の HLSL テンプレートがおかしい その2
あとはファイルを作ってコピペする。
シェーダファイル(.hlsl)のプロパティからエントリポイントとシェーダの種類がそれぞれ、PSとピクセルシェーダになっていることを確認する。 デフォルトではエントリポイントがmainらしいので書き換える。
その後、コンパイルしていつも通りにプロジェクトを実行する。
シェーダが画面に反映されていないなら、PixelShader::isEmpty()でファイルが読み込めているか確認。
Siv3DプロジェクトのExampleフォルダには、いくつかシェーダが入っているので、そこのシェーダで試すのも良いかもしれない。
実際に触る。
Siv3Dのサンプルのwhite.hlslはピクセルシェーダだ。
ピクセルシェーダはレンダリングパイプラインの中で、比較的下流に位置するプログラマブルシェーダだ。
画面に表示される直前にピクセルの中身を制御する。らしい。
(したがって画面上の広い範囲に適用したいならなるべく重い処理を書かないほうが良い。その大きさ分、ピクセルシェーダで計算することになる。)
このページがさっさと読めてわかりやすい。
【シェーダ】レンダリングパイプライン
サンプルでは線形補完(?)というものをしている。
高校の数学の教科書で見たような下の式が関係している。
三角形OABを考える。ここで
とすると、は三角形の辺AB上の任意の点まで、点Oから伸びるベクトルになるはず。
もっと簡単に言うと点Aと点Bの間を滑らかに変化する点Xを表現するのに使える。のだろう。
このがsample中のstrength変数にあたる。
コンスタントバッファはシェーダに渡す変数をまとめた構造体。
これをもっとうまく使えば、任意の色のみの画像と元の画像とを行き来するようなシェーダを一つのシェーダファイルで実現できるのだろう。
21.コンスタントバッファの作成 | gameproject
今回reversed1等、一切使っていないのだが必要なのだろうか。
EndPS関数等、まだ良くわからない。
cmakeを使った雑記
何度か心が折れているけど、それでもテスト自動化したい。
GitLabにpushすると自動でCIが回って云々してくれるのは分かった。 一方で色々わからない。定石がわからない。
- GitLabでテストを呼ぶ
- GitLabに呼べるようにテストを置く
- そもそもローカルで自動化すべきでは
どうせ1人の開発だろうし、ローカルでも良いと思う。 仮にpublicにするとしても、GitLabでのみテストするってのは効率が良いとは思えない。 (無料かつほぼデフォルトの設定だと高速ではないような) お金を払ったり、複数人で開発するなら全然良いんだろうけど。
大抵調べるとCIしてるプロジェクトはUnix系?かMacOSでそのまま参考にはできない。 dockerも入れてコンテナ建てるpullするくらいは分かったけど、 ここに例えばubuntu入れてcmake+clang or gcc 入れて、 そこでコード書くかホストのWindowsから渡すようなことをするのか? 面倒さは増えてるような。
良くわからないけどシンプルなのは手元で自動化したものをそのままGitLabに上げることじゃなかろうか。 色々あるんだろうけどGitLab/GithubのCIでcmakeを使っているらしいプロジェクトがあるのは知っている。 cmakeを使ってローカルでもある程度回せないのか?
そんなわけでcmakeを触り始めた。適当にやってわかるものではないとわかったのでtutorialをする。 現在step2/7が終わった。ソースをⅮLするとtutorialのstepごとの結果も入っているので参考にできる。 でもstepが微妙にずれているように思う。(ソースだとstepが11まである)
とりあえずビルドの産物をbuildディレクトリに隔離できるのは楽でいい。 ディレクトリ指定の誤りや誤字で簡単にコケるし、どこが間違っていると教えてくれるとも限らないのは厳しい。 それを踏まえるとなるべく短くわかりやすく書くのがいいんだろうかこれ。
プロジェクトのディレクトリにCMakeLists.txt と buildディレクトリ作ったら下の感じで良いっぽい。
$ ...\build>cmake .. $ ...\build>cmake --build .
--buildにたどり着くまで結構かかった。 .slnをVisualStudioで開いてビルドとかでも良い。 調べた先ではmakeしてるから Windows MSVCだとmakeは何にあたるのか調べてnmakeだとかを新しく入れるとか.dat使うとか。 色々調べてcmake --help眺めてたら見つけた。
最終的には以下になるのか?
- 未実施だったらテスト
- テストが通った時だけmasterにcommit
Windowsで開発するのは、今まで思っていたよりもずっと面倒で大変なことなのかもしれない。 cmake終わったら折角入れたんだからdockerも使ってみようか。 ubuntu等手軽に触れるのはいい。
参考
CMAKE によるビルド自動化とテスト実行 - zonomasaの日記
上記以外にもいくつか参考にしたけど、特にディレクトリ関係がわかりやすいのをいくつか。
ABC120
ABC120
UnionFindがわからないなら以下を見ると良いかもしれない。
UnionFindの解説 : B: Union Find - AtCoder Typical Contest 001 | AtCoder
A問題
方針
python
#python3
a, b, c = map( int, input().split() )
print( min(b//a,c) )
C++
//C++
#include<iostream>
#include<algorithm>
int main() {
int a, b, c;
std::cin >> a >> b >> c;
std::cout << std::min(b/a ,c) << "\n";
return 0;
}
B問題:
python
#python 3
a, b, k = map(int,input().split())
ans = []
for i in range(1,min(a,b)+1):
if a%i ==0 and b%i == 0:
ans.append(i)
print(ans[-1*k])
C++
//cpp14
#include<iostream>
#include<vector>
#include<algorithm>
int main() {
int a, b, k;
std::cin >> a >> b >> k;
std::vector<int> v;
auto mv = std::min(a, b);
for (int i{1}; i <= mv; ++i) {
if (a%i == 0 &&; b%i == 0)
v.emplace_back(i);
}
std::cout << *(v.rbegin() + k-1) << "\n";
return 0;
}
C問題:
蛇足
python
#python 3
s = input()
n = len(s)
stack = [ s[0] ]
rem = 0
ss = len(stack)
for i in range(1,n):
if ss==0 or stack[-1] == s[i]:
stack.append(s[i])
ss += 1
continue
stack.pop()
ss -= 1
i+=1
rem +=2
print(rem)
C++
//cpp14
#include<iostream>
#include<vector>
#include<string>
int main() {
std::string s;
std::cin >> s;
std::vector<char> stack_;
stack_.reserve(s.size());
auto count = 0;
for (const auto&e : s) {
if (stack_.empty()) {
stack_.push_back(e);
continue;
}
if (stack_.back() != e) {
stack_.pop_back();
count += 2;
}
else {
stack_.push_back(e);
}
}
std::cout << count << "\n";
return 0;
}
C++ 別解
//cpp14 別解
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
int main() {
std::string s;
std::cin >> s;
decltype(s.size()) zeros = std::count(s.begin(), s.end(), '0');
std::cout << 2*std::min(zeros, s.size() - zeros) << "\n";
return 0;
}
D問題:
方針
size: root: __link__:実装概要、多分だけど
再帰難しい
python
# python 3
class dset:
def __init__(self,n):
self.par = [-1]*n
def size(self,n):
return -self.par[self.root(n)]
def root(self,n):
if self.par[n]<0:
return n
else:
self.par[n] = self.root(self.par[n])
return self.par[n]
def same(self, a, b):
return self.root(a) == self.root(b)
def __link__(self,a,b):
if a == b:
return
if self.size(a) < self.size(b):
a, b = b, a
self.par[a] += self.par[b]
self.par[b] = a
def unite(self,a,b):
self.__link__(
self.root(a)
,self.root(b)
)
def main():
n, m = map(int, input().split() )
ins = [ list( map(int,input().split()) ) for i in range(m) ]
ds = dset(n)
ans = [0]*m
ans[-1] = n*(n-1)//2
for i in range(m-1,0,-1):
ans[i-1] = ans[i]
a,b = ins[i]
a -= 1
b -= 1
# print(ins[i])
# print( (ds.root(a),ds.root(b)) )
if not ds.same(a,b):
x = ds.size(a)
y = ds.size(b)
#print((x,y))
ans[i-1] -= x*y
ds.unite(a,b)
for e in ans:
print(e)
if __name__ == '__main__':
main()
C++
//cpp14
#include<iostream>
#include<vector>
#include<algorithm>
using sll = signed long long;
using ui = long int;
class disjoint_set {
std::vector<int> p;
auto link(int x, int y) {
//x = find(x);
//y = find(y);
if(x==y)
return false;
if ( size(x) < size(y) )
std::swap(x, y);
p[x] += p[y];
p[y] = x;
return true;
}
public:
disjoint_set():p{}{}
disjoint_set(int size_) :
p{}{
p.resize(size_, int{-1});
}
auto find(int x)
->decltype(p)::value_type {
return p[x] < 0 ?
x : p[x] = find(p[x]);
}
auto same(int x, int y) {
return
find(x) == find(y);
}
auto unite(int x, int y) {
this->link(find(x), find(y));
}
auto size(int x)
->decltype(p)::value_type{
return -p[ this->find(x) ];
}
};
int main() {
int n, m;
std::cin >> n >> m;
std::vector< std::pair<int, int> > vp;
disjoint_set ds{ n };
vp.resize(m);
for (int i{}; i < m; ++i) {
int a, b;
std::cin >> a >> b;
vp[i] = std::make_pair(a-1, b-1);
}
auto it = std::rbegin(vp);
auto end_ = std::rend(vp);
auto ansi = sll{ n } *(n - 1) / 2;
std::vector<sll> ans{};
ans.resize(m, 0);
ans[m - 1] = ansi;
for (int i{m-1}; i > 0; --i) {
auto a = it->first;
auto b = it->second;
ans[i-1] = ans[i];
// std::cout << " ab " << a+1 << " " << b+1 << "\n";
if (!ds.same(a, b)) {
auto x = ds.size(a);
auto y = ds.size(b);
ans[i-1] -= x * y;
ds.unite(a, b);
// std::cout <<" xy "<< x << " " << y << "\n";
if (ans[i-1] < 0 )
ans[i-1] = 0;
}
++it;
}
for (auto&e : ans)
std::cout << e << "\n";
return 0;
}
とりあえず現在の限界は、今回のD問題が解けないくらい これ以上レートを上げていくということが、 このUnionFindを平然と実装できる人達と競争するということなら、現状の能力ではかなり厳しいと感じる UnionFindくらい難しいと何も考えずに書けるものでもない。 参考にしたUnionFindを書いた人は賢いやり方をしていてなかなかわからなかった。感想
残念な失敗が多くて厳しい
経路圧縮やランクによる効率化で処理が増えても複雑になっていくのを理解できるか、再帰を考えられるかが大事なんだろう。
今回はコンテスト後に復習をかねてもう一度、コンテスト時に用いた言語ではない言語で書きなおしたので、A-Dまでpython3とC++のコードがある。
今更だけど載せるのってC以上の問題だけで良い気がする。
書き方も色々試してみる。今回はmarkdown。
コード載せる時に気づいたけどこれは正規の挙動なのか。
コード内でマークダウン効いたり改行でspan出たり何とかならないのかね。
<details>tagの中にdivを入れるのは、展開後の文章にmarkdownを適用するとき。
ABC118-ABCを解いた
最近はうまくいかないこととか頑張り切れないことも多い中、
レートを維持どころか上昇させられたのは大変うれしい。
Dはやっぱまだわからん。AからCまで自分なりの解き方を以下に示す。
今回はすべてC++で解いた。
A : 約数ってなんだっけ?から確認しなきゃならなかったけど、
サンプル見たらわかった。
ある数を割り切る数のことらしいな。
A問題は短く書いてしまいたいので条件演算子が有用。
解き方:問題文の通りに実装すれば良い。
#include<iostream> int main() { int a, b; std::cin >> a >> b; std::cout << (b%a == 0 ? a + b : b - a) << "\n"; return 0; }
B - Foods Loved by Everyone
B : あんまり似たものはBで出ない気がする。
Kの意味が分からなくて何度か文章とサンプルを行き来した。
(入力のルールを見る限り、i番目の人間の回答の数、つまりi番目の入力の数らしい。)
解き方:食べ物ごとに何回好きと言われたかカウントする。
N人全員が好きだと答えたのならば、ある食べ物が好きと言われた数はN回であるはずだ。
したがって、食べ物のカウントがちょうどNであるものの数を出力すれば良い。
(他の実装では、boolかstd::bitsetあたりを使って一度でも選ばれなかったらfalseにして、 trueの数を数えるのもありかもしれない。)
#include<iostream> #include<algorithm> int main() { int n, m; std::cin >> n >> m; int food[30]{}; //[1,30] = [0,29] = [0,30) for (int i{}; i < n; ++i) { int k{}; std::cin >> k; for (int ki{}; ki < k; ++ki) { int t; std::cin >> t; food[t-1]++; } } /* for (const auto&e : food) { std::cout << " " << e; }std::cout << "\n"; */ auto ans = std::count(std::begin(food), std::end(food), n); std::cout << ans << "\n"; return 0; }
C - Monsters Battle Royale
C : 難しかった 。
ACはできたけれど証明して提出したわけではないので、
あまりいい回答ではないのかもしれない。
公約数だったかを求めるやつとか使えそうって思ったけど直接は使わない実装にした。
結構ぐだぐだなので読み飛ばしてコードを見るほうがいいかもしれない。
ちなみに今回載せる回答は提出後により良いコードに書き直したものだ。
解き方:
まず入力される数列 Ai ( 1 <= i <= N ) の順序は
結果に影響を与えないっぽいのでソートしても良いだろう。
次に Ax と Ay ( x != y かつ 0 < Ax < Ay とする ) の2体のモンスターが戦うことを考える。
愚直に問題文のとおりに書いてしまうとwhileで減算(a)とかになるだろうけど、
そこで得られる結果は %(b) の結果と同じだろう。(一応疑似コード載せるけどいらないかも。)
//疑似コード //(a) while( Ay > Ax ) Ay -= Ax //(b) Ay %= Ax // 右のように書いてもいい==> Ay = Ay % Ax
他にも解くのに色々考えたのだけれど上手に説明できない。
ので列挙する。
- 数列 Ai に 1 が(戦闘が進んで一度でも)現れたら答えは1
- そうでないならAi %= Ax ( i != x ) を繰り返した0ではない最小値が答え
- ではどのようにAxを選ぶのが良いのか?
(選び方によっては
<i>結果が同じでも計算コストが増える
<ii>結果が変わる
ことはないのか?)
- <i >A%Bした結果は[0, B-1]になるので、Bが小さい方が解も早く収束しそう?
- <ii>については分からなかった。反例も思いつかなかった。
上記2点が弱い部分で、可能であれば証明か反例どちらかを成し遂げて
実装を決められたら良かったのだろう。
効率を考えて、除算する数は最小値 Aminにした。
以下コード。
大事な部分?は余りが0の時は要素 *itk を 余りではなく最小値に書き換えているところ。
最小値に書き換えれば重複するので、次のuniqueで間違いなく消えてくれる。
余りが0の場合でも余りで書き換えてしまうと次のループで0除算が起きるようになって面倒。
(コンテスト中の提出はこっちで通した)
#include<iostream> #include<vector> #include<algorithm> int main() { int n; std::cin >> n; std::vector<int> v; v.resize(n); for (auto&e : v) std::cin >> e; std::sort(std::begin(v), std::end(v)); auto it = std::unique(std::begin(v), std::end(v)); while (std::distance(std::begin(v), it) != 1) { auto beg = std::begin(v); for (auto itk = ++std::begin(v); itk != it; ++itk) { *itk = *itk % *beg == 0 ?*beg : *itk % *beg; } std::sort(std::begin(v), it); it = std::unique(std::begin(v), it); } std::cout << v[0] << "\n"; return 0; }
( 何度もソートとuniqueするのは計算コスト的に如何なものか、
と実装しながら考えたが、まぁ一応ACする程度には早いらしい。
「まぁ最初の数回は大きなコストを支払うかもしれないけれど、
最小値が小さくなるほど重複する要素が増えて、
この次に計算の対象になる要素数が大きく減るんじゃなかろうか」
みたいな感覚はあったけれど、実際どうなのだろう。)
みんなのプロコン2019-Cを解いた
難しかったけど解けた。難しいのは300点ではなく400点問題だからだろうか。
普通C問って何点なんだろうか。
点数をあまり意識したことはなかったけど、点数から自分のACできるかもしれない問題かどうかが判断つくなら良いことだ。
Dもいけるかなって思ったけど解説見ても書けないので今はまだ無理なのだろう。
続きを読む
Unityのtutorial
Survival Shooter Tutorial - 2 of 10 : Player Character - Unity Official Tutorials (new) - YouTube
今やっているのはこの途中まで。
Unity version 2018 .3 .0f2 Personal
動画と環境が違うのが足を引っ張っている。今からでも変えたほうがいいかもしれない。
とりあえず「MinⅮrawer.csのMinAttributeがambiguousです」というエラーはこれで直る。同じ名前のクラスか何かがいろんなところに所属するようになったということだろう。
https://github.com/Unity-Technologies/PostProcessing/issues/725
別のエラーも出た。
「Can't add ~ The script needs to derive from MonoBehaviour! 」
このエラーは
1.スクリプトファイルの名前とその中で定義されているクラスの名前が異なる。
2.定義されているクラスがMonoBehaviouから派生していない。
(【Unity】Assets「DOTween」Can’t add script エラーが出る場合の対処法 - Qiitaとか)
あたりは検索すれば出る。けどこれらの方法では直らなかった。Assetのre-importもダメ。
結局 playerに Add Component から new scriptでまっさらなPlayerMovement.csを作成して中身のほとんどをコピーしたらcompile Errorは無くなった(スクリプトの最初にあるusing等はnew scriptのものを優先したので”ほとんど”)。たしかこの解決方法は動画のコメントからctrl + f で見つけた気がする。Unity tutorialの動画のコメントは新しいユーザ向けにエラーの対処法が載っていたりする。
Assetのupdateはしたつもりなのだけど、うまくいっているかはわからないのでそのあたりも原因の一つかもしれない。Assetって何から何まで含むのだろう。
ちなみに動作を確認したところ、Animationの遷移がうまくいっていないように思う。Animator Controllerの細かいUIにもversion間で差異があるらしく、動画のものと見た目が違う。動画にはなかったEntryとExitに対して何かしなきゃいけないんだろう。今は時間がないので置いておくが、もう一度コメントや動画を見て確認する必要がある。コメントにいくつかAnimation遷移に関連したものがいくつかあったはずだ。
*追記
youtubeのコメントより:Animationの遷移はIDLEとMOVEの間の2つの遷移矢印それぞれを選択してから’Has Exit Time’パラメータのチェックを外すと直るようだ。ついでにカメラ設定も少し違うらしいのでMain CameraのtagをdefaultからMainCameraへ変更した。
修正にどちらも必要だったのか、どちらかだけでよかったかはよくわからない。