Hi!
We are working on adding TypeScript Definitions support in order to be able to call WebGL from C# in a strongly-typed manner.
In the meantime,
please find attached a very cool WebGL example by
learningwebgl.com ported to CSHTML5.
It was ported to CSHTML5 using the "
HtmlPresenter" control and the "Interop.
ExecuteJavaScript" method (
documentation).
The demo shows an animation where the camera zooms into the Mandelbrot fractal set using a WebGL fragment shader (tested on desktop browsers Chrome and Firefox).
For those who would like to see the source code without downloading the project, here it is:
MAINPAGE.XAML:
Code: Select all
<Page
x:Class="Cshtml5WebGLSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<Border x:Name="LayoutRoot" Background="#FFCCCCFF">
</Border>
</ScrollViewer>
</Page>
MAINPAGE.XAML.CS:
Code: Select all
using CSHTML5;
using CSHTML5.Native.Html.Controls;
using System.Windows;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Cshtml5WebGLSample
{
public partial class MainPage : Page
{
//------------------------------------------------------
// CSHTML5 WebGL sample - Zooming into the Mandelbrot set in a WebGL fragment shader!
// Credits: http://learningwebgl.com/blog/?p=205
// Original source code: https://github.com/gpjt/webgl-lessons/blob/master/example01/index.html
// The main changes that were done to adapt the code are:
// - Explicitely declared all the variables because CSHTML5 runs JavaScript in "strict mode" (see the lines that have the comment "//strictmode").
// - Made the functions globally accessible by adding them to the "window" object.
// - Loaded the shaders from string instead of loading them from <script> tags (just changed the "getShader" method).
//------------------------------------------------------
public MainPage()
{
this.InitializeComponent();
//---------------------------------------------------------------------
// Load the HTML code of the demo by using an "HtmlPresenter" control:
// This section was copy/pasted from the <body> tag of the original example.
//---------------------------------------------------------------------
var htmlPresenter = new HtmlPresenter();
LayoutRoot.Child = htmlPresenter;
htmlPresenter.Html = @"
<canvas id=""example01-canvas"" style=""border: none;"" width=""425"" height=""330""></canvas>
<h2>Inputs</h2>
<form name=""inputs"">
<p>
Zoom target: <br />
X <input type=""text"" id=""zoomCenterXInput"" value=""0.28693186889504513"" />
Y <input type=""text"" id=""zoomCenterYInput"" value=""0.014286693904085048"" />
<p>
<input type=""button"" value=""Reset Zoom"" onClick=""resetZoom()"" />
</form>
<h2>Current state (read-only)</h2>
<form name=""outputs"">
<p>
Current center: <br />
X <input type=""text"" id=""centerOffsetXOutput"" /> Y <input type=""text"" id=""centerOffsetYOutput"" />
<p>
Zoom: <input type=""text"" id=""zoomOutput"" />
</form>
<br/>
<a href=""http://learningwebgl.com/blog/?p=205""><< Back to the blog</a><br />
";
this.Loaded += MainPage_Loaded;
}
//-----------------------
// Declare the shaders.
// This section was copy/pasted from the first two <script> tags of the original example.
//-----------------------
const string ShaderFragment = @"
precision mediump float;
varying vec2 vPosition;
void main(void) {
float cx = vPosition.x;
float cy = vPosition.y;
float hue;
float saturation;
float value;
float hueRound;
int hueIndex;
float f;
float p;
float q;
float t;
float x = 0.0;
float y = 0.0;
float tempX = 0.0;
int i = 0;
int runaway = 0;
for (int i=0; i < 100; i++) {
tempX = x * x - y * y + float(cx);
y = 2.0 * x * y + float(cy);
x = tempX;
if (runaway == 0 && x * x + y * y > 100.0) {
runaway = i;
}
}
if (runaway != 0) {
hue = float(runaway) / 200.0;
saturation = 0.6;
value = 1.0;
hueRound = hue * 6.0;
hueIndex = int(mod(float(int(hueRound)), 6.0));
f = fract(hueRound);
p = value * (1.0 - saturation);
q = value * (1.0 - f * saturation);
t = value * (1.0 - (1.0 - f) * saturation);
if (hueIndex == 0)
gl_FragColor = vec4(value, t, p, 1.0);
else if (hueIndex == 1)
gl_FragColor = vec4(q, value, p, 1.0);
else if (hueIndex == 2)
gl_FragColor = vec4(p, value, t, 1.0);
else if (hueIndex == 3)
gl_FragColor = vec4(p, q, value, 1.0);
else if (hueIndex == 4)
gl_FragColor = vec4(t, p, value, 1.0);
else if (hueIndex == 5)
gl_FragColor = vec4(value, p, q, 1.0);
} else {
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
}
";
const string ShaderVertex = @"
attribute vec2 aVertexPosition;
attribute vec2 aPlotPosition;
varying vec2 vPosition;
void main(void) {
gl_Position = vec4(aVertexPosition, 1.0, 1.0);
vPosition = aPlotPosition;
}
";
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (Interop.IsRunningInTheSimulator)
{
MessageBox.Show("WebGL is currently not supported in the Simulator. Please launch the application in the browser to test it.");
}
else
{
//-----------------------
// Load the main script (this was orignally done in the last <script> tag, in the original example):
//-----------------------
RunWebGLInitialisationScript();
//-----------------------
// Start the animation (this was originally done in the "onload" event of the html Body, in the original example):
//-----------------------
Interop.ExecuteJavaScript("webGLStart()");
}
}
void RunWebGLInitialisationScript()
{
//-----------------------
// This section was copy/pasted from the last <script> tag of the original example.
// Some changes were made to the code: read the comments at the beginning of this class for more information.
// Please note that the two shaders are passed as $0 and $1 parameters to the "ExecuteJavaScript" method.
// More info about the "ExecuteJavaScript" method can be found at: http://cshtml5.com/links/how-to-call-javascript.aspx]Interop.ExecuteJavaScript
//-----------------------
Interop.ExecuteJavaScript(@"
var gl;
window.initGL = function(canvas) {
try {
gl = canvas.getContext(""webgl"");
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch(e) {
}
if (!gl) {
alert(""Could not initialise WebGL, it is probably not supported on this browser."");
}
}
window.getShader = function(gl, id)
{
var str;
var shader;
if (id == ""shader-fs"")
{
str = $0;
shader = gl.createShader(gl.FRAGMENT_SHADER);
}
else if (id == ""shader-vs"")
{
str = $1;
shader = gl.createShader(gl.VERTEX_SHADER);
}
else
{
alert(""invalid shader id: "" + id);
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
{
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
var shaderProgram;
var aPlotPosition; //strictmode
var aVertexPosition;
window.initShaders = function()
{
var fragmentShader = getShader(gl, ""shader-fs"");
var vertexShader = getShader(gl, ""shader-vs"");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
{
alert(""Could not initialise shaders"");
}
gl.useProgram(shaderProgram);
aVertexPosition = gl.getAttribLocation(shaderProgram, ""aVertexPosition"");
gl.enableVertexAttribArray(aVertexPosition);
aPlotPosition = gl.getAttribLocation(shaderProgram, ""aPlotPosition"");
gl.enableVertexAttribArray(aPlotPosition);
}
var centerOffsetX = 0;
var centerOffsetY = 0;
var zoom;
var zoomCenterX;
var zoomCenterY;
var vertexPositionBuffer;
var corners; //strictmode
var x; //strictmode
var y; //strictmode
window.initBuffers = function()
{
vertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
var vertices = [
1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
-1.0, -1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
vertexPositionBuffer.itemSize = 2;
vertexPositionBuffer.numItems = 4;
}
var baseCorners = [
[0.7, 1.2],
[-2.2, 1.2],
[0.7, -1.2],
[-2.2, -1.2],
];
window.drawScene = function()
{
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
gl.vertexAttribPointer(aVertexPosition, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
var plotPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, plotPositionBuffer);
var cornerIx;
corners = [];
for (cornerIx in baseCorners)
{
x = baseCorners[cornerIx][0];
y = baseCorners[cornerIx][1];
corners.push(x / zoom + centerOffsetX);
corners.push(y / zoom + centerOffsetY);
}
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(corners), gl.STATIC_DRAW);
gl.vertexAttribPointer(aPlotPosition, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.deleteBuffer(plotPositionBuffer)
zoom *= 1.02;
document.getElementById(""zoomOutput"").value = zoom;
if (centerOffsetX != zoomCenterX)
{
centerOffsetX += (zoomCenterX - centerOffsetX) / 20;
}
document.getElementById(""centerOffsetXOutput"").value = centerOffsetX;
if (centerOffsetY != zoomCenterY)
{
centerOffsetY += (zoomCenterY - centerOffsetY) / 20;
}
document.getElementById(""centerOffsetYOutput"").value = centerOffsetY;
}
window.resetZoom = function() {
zoom = 1.0;
zoomCenterX = parseFloat(document.getElementById(""zoomCenterXInput"").value);
zoomCenterY = parseFloat(document.getElementById(""zoomCenterYInput"").value);
}
window.webGLStart = function()
{
resetZoom();
var canvas = document.getElementById(""example01-canvas"");
initGL(canvas);
initShaders()
initBuffers();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
setInterval(drawScene, 15);
}
webGLStart();
",
ShaderFragment,
ShaderVertex);
}
}
}
Regards,
JS-Support