CPU上で頂点・フラグメントシェーダ < 点を描くところから始めるRust製ソフトウェアラスタライザ
WJ.iconまず現行の処理をデフォルトシェーダーを落とし込む
今の一連の描画処理はCamera#snapshotメソッドで行われている
code:rs
pub fn snapshot(&mut self, canvas:&mut Canvas, actors:&Vec<Actor>) {
let perspective = self.perspective_conversion();
let view = self.view_conversion();
let pv = perspective*view;
for actor in actors {
let model = actor.model_conversion();
let pvm = &pv * &model;
for polygon in &actor.polygons {
let projected = [
Vec4Project::new(&pvm * &polygon.vertices0).into_screen(&canvas.size()), Vec4Project::new(&pvm * &polygon.vertices1).into_screen(&canvas.size()), Vec4Project::new(&pvm * &polygon.vertices2).into_screen(&canvas.size()), ];
canvas.draw_triangle(projected, &polygon.color);
}
}
}
そしてdraw_triangleの中身
今思うと、x_begin, x_endとわざわざ走査すべきところを限定する必要はなかったような気もするが....appbird.icon
コードを簡単化するためにもうちょい簡単なものにするか
code:rs
pub fn draw_triangle(
&mut self,
) {
let points = points.map(|p| p.0);
let bound_x = ClosedInterval::between(0,(self.width-1) as i32);
let bound_y = ClosedInterval::between(0,(self.height-1) as i32);
let bound_z = ClosedInterval::between(-1.,1.);
let x_segment = ClosedInterval::range(points.iter().map(|p| p.x() as i32));
let y_segment = ClosedInterval::range(points.iter().map(|p| p.y() as i32));
let z_segment = ClosedInterval::range(points.iter().map(|p| p.z()));
let x_segment = x_segment.and(&bound_x);
let y_segment = y_segment.and(&bound_y);
let z_segment = z_segment.and(&bound_z);
if z_segment.is_empty() { return; }
// Barycentric座標
let area_abc = area(&points0, &points1, &points2); // culling
if self.culling && area_abc < 0. { return; }
if area_abc.abs() < 1e-6 { return; }
let inv_abc = 1./area_abc;
for y in &y_segment.and(&bound_y) {
for x in &x_segment.and(&bound_x) {
let p = Vec4::newpixel(x, y);
let w = [
area(&points1, &points2, &p) * inv_abc, area(&points2, &points0, &p) * inv_abc, area(&points0, &points1, &p) * inv_abc, ];
if !(0. < w0 && w0 < 1. && { continue; }
let z = [
points0.z(), points1.z(), points2.z(), ];
let p = p.to_point2();
let depth = w0*z0 + w1*z1 + w2*z2; let color = &color0*w0 + &color1*w1 + &color2*w2; self.draw_pixel_with_depth(&p, &depth, &color);
}
}
}
頂点シェーダーとバーテックスシェーダーはそれぞれどの部分に対応するか?
--(appdata)--> 頂点シェーダ --(v2f)--> バーテックスシェーダ --(SV_Target)--> 画面出力
code:hlsl
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
SV_TargetはRGB値と思われるappbird.icon
上記を踏まえれば頂点シェーダーはこのうち次の部分に対応する
code:rs
Vec4Project::new(&pvm * &polygon.vertices0)//.into_screen(&canvas.size()) フラグメントシェーダーはこの部分の中の...
code:rs
canvas.draw_triangle(projected, &polygon.color);
code:rs
self.draw_pixel_with_depth(&p, &depth, &color);
depth値もcolor値もフラグメントシェーダには渡されるからなぁというappbird.icon
まぁ最も単純なシェーダーの実装はこうよな
code:shader/vertex.rs
pub fn default_vshader(
pvm:&Mat4x4,
point:&Vec4
) -> Vec4Project {
Vec4Project::new(pvm * point)
}
code:world/camera.rs
let projected = [
vertex::default_vshader(&pvm, &polygon.vertices0).into_screen(&canvas.size()), vertex::default_vshader(&pvm, &polygon.vertices1).into_screen(&canvas.size()), vertex::default_vshader(&pvm, &polygon.vertices2).into_screen(&canvas.size()), ];
code:shader/fragment.rs
pub fn default_fshader(
_point:Point2,
color:Vec4
) -> Vec4 {
return color;
}
code:canvas/triangle.rs
let color = &color0*w0 + &color1*w1 + &color2*w2; let color = fragment::default_fshader(p, color);
self.draw_pixel_with_depth(&p, &depth, &color);
まぁ、これでもいい、いいんだけど...。
シグネチャがいちいち変わるのはなんかちょっと扱いづらいなぁという気持ち。
draw_trianglesのシグネチャはできるだけ変えないようにしたい。
main()のほうから変えたら、あとは自動的に計算されるようにしたい...どうすればいい?
どこで情報を入力させるべきだろう?
頂点以外の属性情報が出てきた場合、どうやってそれらの情報をシェーダーに渡せばよいか
複数のVBOを関連付けられるようにするには
今のままだと頂点位置しか対応付けられない...。
処理を実行するのに必要なものはなにか
先行事例を参考にしてみる
Vertex Shaderへの入力を表す構造体(VBOに対応)
code:rs
struct Uniform {
PVM: Mat4x4;
}
struct Attribute {
position: Vec4,
color: Vec4,
normal: Vec4,
}
struct Varying {
color: Vec4;
}
Vertex Shader, Fragment Shaderに対応する部分
code:rs
impl VertexShader {
fn main(unif:&Uniform, attr: &Attribute, vary:&mut Varying) -> Vec4Project {
vary.color = attr.color;
Vec4Project::new({unif.PVM * attr.point})
}
}
impl FragmentShader {
fn main(unif:&Uniform, vary:&Varying) -> Vec4 {
vary.color
}
}
これらをどう駆動させるかが問題になるappbird.icon まずシェーダーはどこで設定できるべきだ...?
actorごとにShaderは設定できるべきだよな...appbird.icon 本当か?
階層構造を作ることができればメッシュごとにActorを作れば問題解決な気はする
もっと言えば、materialとして設定できると良い
じゃあAttribute変数、Varying変数はどこから設定できるべきか...
これActorが持ってる情報をうまくAttribute変数に直してくれる機構が必要なんじゃないのという気になる
code:rs
actor1.shader.vertex = default_vertex_shader;
actor1.shader.fragment = default_fragment_shader;
// ....
camera.snapshot(canvas, &world);
うーんこうしよう
これでうまくいくかはわからないけど
1. ActorクラスがMeshRendererを内部に持つ
2. MeshRendererが内部にShaderを設定できる
3. MeshRendererがRenderingの責任を持つ
最悪MeshRendererの仕組みが破綻しても回避できる
4. 渡されるShaderに対してMeshRendererはどう対応するか?
DIする?
基本的に関数にまとめられるということは、処理を一箇所にまとめられるということなので、その前提が成り立つかが怪しい
WJ.iconVertexShader基底クラスを作る。
何が必要?
Vec4World ---> Vec4Screen
WJ.iconそれの一実装として現行のパイプラインを組み込んでおく。
WJ.iconFlagmentShader基底クラスを作る。
(Point2, double uni, double vari) ---> Vec4(Color)
それの一実装として現行のパイプラインを組み込んでおく。