lsys是一个用CoffeeScript编写的超快的 L-System渲染器。
下面是C#和WPF中的简单渲染器。它是硬编码的,以呈现this example。运行时的结果如下:
在窗口中单击鼠标将调整angleGrowth
变量。重新计算GeometryGroup
以及构建Canvas
通常需要不到十分之一秒。但是,实际的屏幕更新似乎需要更长的时间。
有关如何更快或更高效的建议?它目前比CoffeeScript / JavaScript版本慢......: - )
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Diagnostics;
namespace WpfLsysRender
{
class DrawingVisualElement : FrameworkElement
{
public DrawingVisual visual;
public DrawingVisualElement() { visual = new DrawingVisual(); }
protected override int VisualChildrenCount { get { return 1; } }
protected override Visual GetVisualChild(int index) { return visual; }
}
class State
{
public double size;
public double angle;
public double x;
public double y;
public double dir;
public State Clone() { return (State) this.MemberwiseClone(); }
}
public partial class MainWindow : Window
{
static string Rewrite(Dictionary<char, string> tbl, string str)
{
var sb = new StringBuilder();
foreach (var elt in str)
{
if (tbl.ContainsKey(elt))
sb.Append(tbl[elt]);
else
sb.Append(elt);
}
return sb.ToString();
}
public MainWindow()
{
InitializeComponent();
Width = 800;
Height = 800;
var states = new Stack<State>();
var str = "L";
{
var tbl = new Dictionary<char, string>();
tbl.Add('L', "|-S!L!Y");
tbl.Add('S', "[F[FF-YS]F)G]+");
tbl.Add('Y', "--[F-)<F-FG]-");
tbl.Add('G', "FGF[Y+>F]+Y");
for (var i = 0; i < 12; i++) str = Rewrite(tbl, str);
}
var canvas = new Canvas();
Content = canvas;
var sizeGrowth = -1.359672;
var angleGrowth = -0.138235;
State state;
var pen = new Pen(new SolidColorBrush(Colors.Black), 0.25);
var geometryGroup = new GeometryGroup();
Action buildGeometry = () =>
{
state = new State()
{
x = 0,
y = 0,
dir = 0,
size = 14.11,
angle = -3963.7485
};
geometryGroup = new GeometryGroup();
foreach (var elt in str)
{
if (elt == 'F')
{
var new_x = state.x + state.size * Math.Cos(state.dir * Math.PI / 180.0);
var new_y = state.y + state.size * Math.Sin(state.dir * Math.PI / 180.0);
geometryGroup.Children.Add(
new LineGeometry(
new Point(state.x, state.y),
new Point(new_x, new_y)));
state.x = new_x;
state.y = new_y;
}
else if (elt == '+') state.dir += state.angle;
else if (elt == '-') state.dir -= state.angle;
else if (elt == '>') state.size *= (1.0 - sizeGrowth);
else if (elt == '<') state.size *= (1.0 + sizeGrowth);
else if (elt == ')') state.angle *= (1 + angleGrowth);
else if (elt == '(') state.angle *= (1 - angleGrowth);
else if (elt == '[') states.Push(state.Clone());
else if (elt == ']') state = states.Pop();
else if (elt == '!') state.angle *= -1.0;
else if (elt == '|') state.dir += 180.0;
}
};
Action populateCanvas = () =>
{
var drawingVisualElement = new DrawingVisualElement();
Console.WriteLine(".");
canvas.Children.Clear();
canvas.RenderTransform = new TranslateTransform(400.0, 400.0);
using (var dc = drawingVisualElement.visual.RenderOpen())
dc.DrawGeometry(null, pen, geometryGroup);
canvas.Children.Add(drawingVisualElement);
};
MouseDown += (s, e) =>
{
angleGrowth += 0.001;
Console.WriteLine("angleGrowth: {0}", angleGrowth);
var sw = Stopwatch.StartNew();
buildGeometry();
populateCanvas();
sw.Stop();
Console.WriteLine(sw.Elapsed);
};
buildGeometry();
populateCanvas();
}
}
}
答案 0 :(得分:3)
WPF的几何渲染is just slow。如果您想要快速,请使用其他技术进行渲染,并将结果托管在WPF中。例如,您可以使用Direct3D进行渲染,并在D3DImage内托管渲染目标。 Here's an example using Direct2D instead。或者您可以通过在RGB缓冲区中手动设置字节值进行绘制,并将其复制到WriteableBitmap内。
编辑:正如OP发现的那样,还有一个免费的库来帮助在一个名为WriteableBitmapEx的WriteableBitmap中绘图。答案 1 :(得分:2)
以下是使用WritableBitmap
作为Asik建议的版本。我为DrawLine
方法使用了WriteableBitmapEx扩展方法库。
现在快得离谱了。谢谢阿西克!
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Diagnostics;
namespace WpfLsysRender
{
class DrawingVisualElement : FrameworkElement
{
public DrawingVisual visual;
public DrawingVisualElement() { visual = new DrawingVisual(); }
protected override int VisualChildrenCount { get { return 1; } }
protected override Visual GetVisualChild(int index) { return visual; }
}
class State
{
public double size;
public double angle;
public double x;
public double y;
public double dir;
public State Clone() { return (State) this.MemberwiseClone(); }
}
public partial class MainWindow : Window
{
static string Rewrite(Dictionary<char, string> tbl, string str)
{
var sb = new StringBuilder();
foreach (var elt in str)
{
if (tbl.ContainsKey(elt))
sb.Append(tbl[elt]);
else
sb.Append(elt);
}
return sb.ToString();
}
public MainWindow()
{
InitializeComponent();
Width = 800;
Height = 800;
var bitmap = BitmapFactory.New(800, 800);
Content = new Image() { Source = bitmap };
var states = new Stack<State>();
var str = "L";
{
var tbl = new Dictionary<char, string>();
tbl.Add('L', "|-S!L!Y");
tbl.Add('S', "[F[FF-YS]F)G]+");
tbl.Add('Y', "--[F-)<F-FG]-");
tbl.Add('G', "FGF[Y+>F]+Y");
for (var i = 0; i < 12; i++) str = Rewrite(tbl, str);
}
var sizeGrowth = -1.359672;
var angleGrowth = -0.138235;
State state;
var lines = new List<Point>();
var pen = new Pen(new SolidColorBrush(Colors.Black), 0.25);
var geometryGroup = new GeometryGroup();
Action buildLines = () =>
{
lines.Clear();
state = new State()
{
x = 400,
y = 400,
dir = 0,
size = 14.11,
angle = -3963.7485
};
foreach (var elt in str)
{
if (elt == 'F')
{
var new_x = state.x + state.size * Math.Cos(state.dir * Math.PI / 180.0);
var new_y = state.y + state.size * Math.Sin(state.dir * Math.PI / 180.0);
lines.Add(new Point(state.x, state.y));
lines.Add(new Point(new_x, new_y));
state.x = new_x;
state.y = new_y;
}
else if (elt == '+') state.dir += state.angle;
else if (elt == '-') state.dir -= state.angle;
else if (elt == '>') state.size *= (1.0 - sizeGrowth);
else if (elt == '<') state.size *= (1.0 + sizeGrowth);
else if (elt == ')') state.angle *= (1 + angleGrowth);
else if (elt == '(') state.angle *= (1 - angleGrowth);
else if (elt == '[') states.Push(state.Clone());
else if (elt == ']') state = states.Pop();
else if (elt == '!') state.angle *= -1.0;
else if (elt == '|') state.dir += 180.0;
}
};
Action updateBitmap = () =>
{
using (bitmap.GetBitmapContext())
{
bitmap.Clear();
for (var i = 0; i < lines.Count; i += 2)
{
var a = lines[i];
var b = lines[i+1];
bitmap.DrawLine(
(int) a.X, (int) a.Y, (int) b.X, (int) b.Y,
Colors.Black);
}
}
};
MouseDown += (s, e) =>
{
angleGrowth += 0.001;
Console.WriteLine("angleGrowth: {0}", angleGrowth);
var sw = Stopwatch.StartNew();
buildLines();
updateBitmap();
sw.Stop();
Console.WriteLine(sw.Elapsed);
};
buildLines();
updateBitmap();
}
}
}
答案 2 :(得分:1)
我尚未测试WriteableBitmapEx
版本,因此我不知道这是如何比较的,但我能够使用StreamGeometry
和Freeze()
大幅加快WPF原生版本的速度,这是一种在没有动画时进行优化的方法。 (虽然它仍然感觉不如javascript版本快)
我认为计时器不包括实际的渲染时间,只是填充渲染命令的时间。但是,它也感觉更快。这个WPF performance test演示了获得实际渲染时间的方法。
我还删除了Canvas
和FrameworkElement
,但它正在切换到实现加速的StreamGeometry。
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Diagnostics;
using System.Windows.Media.Imaging;
// https://stackoverflow.com/q/22599806/519568
namespace WpfLsysRender
{
class UpdatableUIElement : UIElement {
DrawingGroup backingStore = new DrawingGroup();
public UpdatableUIElement() {
}
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
drawingContext.DrawDrawing(backingStore);
}
public void Redraw(Action<DrawingContext> fn) {
var vis = backingStore.Open();
fn(vis);
vis.Close();
}
}
class State
{
public double size;
public double angle;
public double x;
public double y;
public double dir;
public State Clone() { return (State)this.MemberwiseClone(); }
}
public partial class MainWindow : Window
{
static string Rewrite(Dictionary<char, string> tbl, string str) {
var sb = new StringBuilder();
foreach (var elt in str) {
if (tbl.ContainsKey(elt))
sb.Append(tbl[elt]);
else
sb.Append(elt);
}
return sb.ToString();
}
public MainWindow() {
// InitializeComponent();
Width = 800;
Height = 800;
var states = new Stack<State>();
var str = "L";
{
var tbl = new Dictionary<char, string>();
tbl.Add('L', "|-S!L!Y");
tbl.Add('S', "[F[FF-YS]F)G]+");
tbl.Add('Y', "--[F-)<F-FG]-");
tbl.Add('G', "FGF[Y+>F]+Y");
for (var i = 0; i < 12; i++) str = Rewrite(tbl, str);
}
var lsystem_view = new UpdatableUIElement();
Content = lsystem_view;
var sizeGrowth = -1.359672;
var angleGrowth = -0.138235;
State state;
var pen = new Pen(new SolidColorBrush(Colors.Black), 0.25);
var geometry = new StreamGeometry();
Action buildGeometry = () => {
state = new State() {
x = 0,
y = 0,
dir = 0,
size = 14.11,
angle = -3963.7485
};
geometry = new StreamGeometry();
var gc = geometry.Open();
foreach (var elt in str) {
if (elt == 'F') {
var new_x = state.x + state.size * Math.Cos(state.dir * Math.PI / 180.0);
var new_y = state.y + state.size * Math.Sin(state.dir * Math.PI / 180.0);
var p1 = new Point(state.x, state.y);
var p2 = new Point(new_x, new_y);
gc.BeginFigure(p1,false,false);
gc.LineTo(p2,true,true);
state.x = new_x;
state.y = new_y;
}
else if (elt == '+') state.dir += state.angle;
else if (elt == '-') state.dir -= state.angle;
else if (elt == '>') state.size *= (1.0 - sizeGrowth);
else if (elt == '<') state.size *= (1.0 + sizeGrowth);
else if (elt == ')') state.angle *= (1 + angleGrowth);
else if (elt == '(') state.angle *= (1 - angleGrowth);
else if (elt == '[') states.Push(state.Clone());
else if (elt == ']') state = states.Pop();
else if (elt == '!') state.angle *= -1.0;
else if (elt == '|') state.dir += 180.0;
}
gc.Close();
geometry.Freeze();
};
Action populateCanvas = () => {
Console.WriteLine(".");
lsystem_view.RenderTransform = new TranslateTransform(400,400);
lsystem_view.Redraw((dc) => {
dc.DrawGeometry(null, pen, geometry);
});
};
MouseDown += (s, e) => {
angleGrowth += 0.001;
Console.WriteLine("angleGrowth: {0}", angleGrowth);
var sw = Stopwatch.StartNew();
buildGeometry();
populateCanvas();
sw.Stop();
Console.WriteLine(sw.Elapsed);
};
buildGeometry();
populateCanvas();
}
}
}
答案 3 :(得分:0)
Here is a DirectX version使用SlimDX。