影に身をかがめるか、次の世界を探す



アセンブラーは私の好きな言語です...しかし、人生はとても短いです。


私はいくつかのベーグルに適した影の問題に関する研究のサイクルを続けています。 出版後、 私はこのトピックまで 冷やしましが、不完全なアクションの影響により、ピクセルの破片に戻り、 ゲシュタルトを完了するように促されます。



自分自身を知っていれば、ゲームはその具体化を得ることがほとんどないだろうと確信していますが、一部の大衆はこの厄介な道での私の成果に興味を持つかもしれません。 それでは始めましょう。



すでに最後のサイクルの終わりに、CPU上のグラフィックスの計算はすでに前世紀であるという理解に至りましたが、自然な頑固さが主張されました:すべての可能性が使用されたわけではなく、興味深い解決策の選択肢がまだありました。



レイトレーシングは未実装のままです。 より正確には、画像の各ピクセル(ピクセルのブロック)に対して光線が投げられ、現在のポイントの照明レベルが決定される種類です。 アルゴリズム自体は前の記事で説明されており、それに戻る意味はありません。 逆光線追跡では、コードがさらに簡素化され、すべての三角法が完全に削除され、将来的には許容できる結果が得られる可能性があります。



パスカル
const tile_size = 32; //   tile_size1 : single = 0.03125; // 1/32 -    block_size = 4; // /    Size_X:Byte = 32; //    X Size_Y:Byte = 24; //    Y //--------------------------------- function is_no_empty(x,y:Integer):Integer; begin if (x>=0) AND (x<Size_X) AND (y>=0) AND (y<Size_Y) then begin if map[x,y]=1 then begin is_no_empty:=1; end else if map[x,y]=2 then begin is_no_empty:=2; end else is_no_empty:=0; end else is_no_empty:=-1; end; //--------------------------------- function crossing(r_view, x,y:Single; xi,yj, i,j:Integer):Byte; var di,dj,ddi,ddj :Shortint; //   k,i2,j2 :integer; //    key:Boolean; last_k, transp_key :Byte; sum_lenX,sum_lenY, Dx,Dy,Dx1,DY1, l :Single; //  sec1,cosec1, temp_x,temp_y, dx0,dy0 :Single; //   i0,j0 :Integer; //       begin temp_x := i*block_size; temp_y := j*block_size; i0 := trunc(temp_x * tile_size1); j0 := trunc(temp_y * tile_size1); l := sqrt(sqr(temp_y-y) + sqr(temp_x-x)) + 0.0000001; transp_key := 0; //     if is_no_empty(xi,yj)>0 then inc(transp_key); if (xi=i0) and (yj=j0) then begin crossing := min(255,transp_key*64+ l * r_view); exit; end; dx0 := (temp_x-x)/l+0.0000001; dy0 := (temp_y-y)/l+0.0000001; key := False; last_k :=0; //   if dx0<0 then begin di :=-1; ddi:= 0; end else begin di := 1; ddi:= 1; end; if dy0<0 then begin dj :=-1; ddj:= 0; end else begin dj := 1; ddj:= 1; end; sum_lenX := 0; sum_lenY := 0; sec1 := 1/dx0; cosec1 := 1/dy0; //       Y temp_x := x-(xi+ddi) * tile_size ; temp_y := y-(yj+ddj) * tile_size ; Dx := sqrt(sqr(temp_x) + sqr(temp_x * sec1 * dy0)); DY := sqrt(sqr(temp_y) + sqr(temp_y * cosec1 * dx0)); //      Y Dx1 := abs(tile_size * sec1); Dy1 := abs(tile_size * cosec1); repeat if sum_lenX+DX < sum_lenY+DY then begin xi += di; k := is_no_empty(xi,yj); sum_lenX += DX; if DX<>Dx1 then DX := Dx1; end else begin yj += dj; k := is_no_empty(xi,yj); sum_lenY += DY; if DY<>Dy1 then DY := Dy1; end; if key Then begin if (xi<>i2) Or (yj<>j2) then begin //  (  ) if last_k=1 then begin crossing := 255; exit; end; //   (  ) if transp_key>2 then begin crossing := 255; exit; end; inc(transp_key); key:= false; end; end; if k>0 then begin i2:=xi; j2:=yj; key:=true; last_k:=k; end; //    if (xi=i0) and (yj=j0) then begin crossing := min(255, transp_key*64+ l * r_view); exit; end; until k=-1; //     end; //--------------------------------- .................. x0:= mouse_x; y0:= mouse_y; //       x1 := x0 div tile_size; y1 := y0 div tile_size; koef := tile_size div block_size; //      (     ) for j:=0 to Size_Y * koef do for i:=0 to Size_X * koef do picture_mask.SetPixel(i, j, BGRA(0,0,0,crossing(x0, y0, x1, y1, i, j))); ..................
      
      







残念なことに、結果が予想よりもはるかに悪かったのです。いったん画像がフルスクリーンに展開されると、FPSはユニットを目指していました。







ピクセルをマクロブロックにグループ化して計算を減らし、その後のスムージングを適用しても、パフォーマンスはそれほど向上しませんでした。 その効果は率直に言ってこの言葉を好まなかった。







アルゴリズムは完全に並列でしたが、多くのスレッドを使用することは意味がありませんでした。効果は前の記事よりもはるかに悪く、画質はさらに優れていました。

それは行き止まりであることが判明しました。 私の目のグラフィックスの計算でCPUが使い果たされたことを認めざるを得ませんでした。 カーテン。



余談1
過去10年間、汎用プロセッサの開発はほとんど進展していません。 ユーザーがアプローチした場合、最大のパフォーマンスの向上はコアあたり30%以下です。 控えめに言っても、進歩は取るに足りないものです。 ベクトル命令の長さの延長、およびコンベアブロックの加速を省略した場合、これは動作中のコアの数の増加です。 スレッドを使用した安全な作業は依然として喜びであり、すべてのタスクを正常に並列化することはできません。 1つではありますが、動作中のコアを使用したいのですが、そうであれば、5〜10倍速くなりますが、悲しいかな、そうですね。

Habréには、 「時代の生活」「暗い「シリコン」という素晴らしいシリーズの記事がありますこれは、現在の状況の前提条件の一部を説明するだけでなく、天から地へと戻ります。 次の10年では、コアあたりの計算が大幅に増加することは期待できません。 しかし、GPUコアの数とそれらの全体的な加速のさらなる発展が期待できます。 私の古いラップトップでも、推定のGPUの合計パフォーマンスは、単一のCPUスレッドの20倍です。 4つのプロセッサコアすべてを効果的にロードしたとしても、予想よりはるかに少なくなります。

ハードウェアアクセラレータを使わずに傑作を作った過去のグラフィックスの開発者、本物のマスターに敬意を表します。



そこで、GPUを扱います。 実際には、ポリゴンの形状を単純にばらばらにする人はほとんどいないというのは、私にとっては予想外のことでした。 すべての興味深いものはシェーダーを使用して作成されます 。 完成した3Dエンジンを捨てて、テクノロジーが深いレベルにあるため、内臓の研究を試みました。 同じプロセッサは同じアセンブラであり、少数の切り捨てられた命令セットと独自の作業仕様のみです。 テストのために、 GLSLに立ち寄りました。これは、Cに似た構文、シンプルさ、Habrを含む多くのトレーニングレッスンと例です。

私はPascalで書くことに慣れていたので、タスクはOpenGLを接続する方法でした

プロジェクトに。 GLFWライブラリとdglOpenGLヘッダーファイルの2つの接続方法を見つけました。 最初の唯一のものはシェーダーを接続できませんでしたが、明らかにこれは私の手の湾曲からです。



余談2
多くの友人から、なぜPascalで書くのかと聞かれます。 明らかに、これは絶滅の危機にある言語であり、そのコミュニティは着実に衰退しており、開発はほとんどありません。 低レベルのシステムエンジニアは、C、およびJava、Python、Ruby、または現在のピーク時のものを好みます。

私にとって、パスカルは初恋に似ています。 20年前、 Turbo Pascal 5.5の時代に、それは私の魂に沈み、それ以来、Delphiであろうと最近のLazarusであろうと、私と一緒に歩き続けています。 私は言語の予測可能性、比較的低レベル(アセンブラーの挿入とプロセッサー命令の表示)、Cとの互換性が好きです。主なことは、コードが問題なくアセンブルされ実行されることですが、ファッショナブルではないという事実は時代遅れであり、いくつかの機能はありません、これはナンセンスです。 噂によると、まだLISPについて書いている人がいますが、彼にとっては一般に半世紀の間です。



それでは、開発に飛び込みましょう。 テストステップでは、シェーディングの正確で現実的なモデルを使用せず、GPUのパフォーマンスを使用して、既に試したものを実装しようとします。



最初は、オブジェクトに三角形を使用して、ほぼこの形状の影を取得することを考えました。







滑らかな円効果を作成するには、多くのポリゴンが必要です。 しかし、ピクセルシェーダーを使用してシェイプに穴を作成し、三角形を最小限に使用するとどうなるでしょう。 このアイデアは尊敬されるマスターによる記事を読んだ後に思いつきました。シェーダーで球体を作成する機会が開かれました。







画面の境界を越えて三角形を延長すると、結果は次のようになります。







影の境界は非常に硬く、また段付きであることが判明しました。 しかし、 スーパーサンプリングを使用せずに許容可能な結果を​​得る方法があります。これは、滑らかな境界線を使用しています。 これを行うには、スキームをわずかに変更します。 円の接線の交点にあるポリゴンの角は透明になります。







結果は良くなりますが、それでも不自然に見えます。







円を少し滑らかにして柔らかさを与え、グラデーションの形を線形から累乗に変更します。







許容できる結果です。

最後に、障害物を模倣したオブジェクトをフォームに追加します。







シェーダーコード
//



#version 330 core

layout (location = 0) in vec2 aVertexPosition;

void main(void) {

gl_Position = vec4(aVertexPosition.xy, 0, 1.0);

}



//



#version 330 core

layout (points) in;

layout (triangle_strip, max_vertices = 5) out;

uniform mat4 uModelViewMatrix;

uniform float uRadius;

uniform vec2 uHeroPoint;

out float fTransparency;

out vec2 vCenter;

void main(){

vCenter = gl_in[0].gl_Position.xy;

vec2 d = uHeroPoint - vCenter;

float l = length(d);

float i = uRadius / l;

float ii = i*i;

float ij = i * sqrt(1 - ii);

vec2 p1 = vec2(vCenter.x + dx*ii - dy*ij , vCenter.y + dx*ij + dy*ii);

vec2 p2 = vec2(vCenter.x + dx*ii + dy*ij , vCenter.y - dx*ij + dy*ii);

d = uHeroPoint - p1;

vec2 p3 = vec2(p1 - d/length(d)*1000000);

d = uHeroPoint - p2;

vec2 p4 = vec2(p2 - d/length(d)*1000000);

fTransparency = 0;

gl_Position = uModelViewMatrix * vec4(p1, 0, 1);

EmitVertex();

fTransparency = 1;

gl_Position = uModelViewMatrix * vec4(p3, 0, 1);

EmitVertex();

gl_Position = uModelViewMatrix * vec4(vCenter, 0, 1);

EmitVertex();

gl_Position = uModelViewMatrix * vec4(p4, 0, 1);

EmitVertex();

fTransparency = 0;

gl_Position = uModelViewMatrix * vec4(p2, 0, 1);

EmitVertex();

EndPrimitive();

}



//



#version 330 core

precision mediump float;

varying float fTransparency;

varying vec2 vCenter;

uniform float uRadius;

uniform vec2 uScreenHalfSize;

uniform float uShadowTransparency;

uniform float uShadowSmoothness;

out vec4 FragColor;

void main(){

float l = distance(vec2((gl_FragCoord.xy - uScreenHalfSize.xy)/uScreenHalfSize.y), vCenter.xy);

if (l<uRadius) {discard;}

else {FragColor = vec4(0, 0, 0, min(pow(fTransparency, uShadowSmoothness), (l-uRadius)/uRadius*10)*uShadowTransparency);}

}









有益だったことを願っています



あなたの謙虚な使用人、ピクセルトルメンター、リビルダー。



私は小さなデモを同封しています。 (EXE Windows)



PS記事のタイトルには、 Siala Chronicle三部作への言及であるイースターエッグが含まれています。 アレクセイ・ペホフによる、ホーンの不幸についてのファンタジーのスタイルの優れた作品。



All Articles