您说的着色器是什么意思?了解如何使用 HTML5 和 WebGL 创建它们





5.00/5 (3投票s)
今天,我将尝试向您解释 babylon.js 中的着色器是如何工作的。
您可能已经注意到,去年我们对 babylon.js 进行了大量讨论,最近我们发布了 babylon.js v2.0,其中包括 3D 声音定位(使用 WebAudio)和体积光散射。
如果您错过了 v1.0 的公告,您可以先在此观看第 2 天的重点介绍,然后直接跳转到 2:24-2:28:http://channel9.msdn.com/Events/Build/2014/KEY02。在其中,微软的传教士 Steven Guggenheimer 和 John Shewchuk 展示了如何将 Oculus Rift 支持添加到 Babylon.js。此演示的关键之一是我们为模拟透镜所做的特定着色器工作,您可以在下图看到
我还与 Frank Olivier 和 Ben Constable 进行了关于 IE 和 Babylon.js 图形的专题演讲:channel9.msdn.com/Events/Build/2014/3-558
这让我想到了我经常对 babylon.js 提出的一个问题:您说的着色器是什么意思??? 所以今天我将尝试向您解释着色器是如何工作的。
理论
在开始实验之前,我们必须先了解事物的内部工作原理。
在处理硬件加速 3D 时,我们讨论的是两个 CPU:主 CPU 和 GPU。GPU 是一种高度专业化的 CPU。
GPU 是一个状态机,您可以使用 CPU 进行设置。例如,CPU 会配置 GPU 以渲染线条而不是三角形。或者它会定义开启透明度等等。
一旦所有状态都设置完毕,CPU 将定义要渲染的内容(由一组点(称为顶点,存储在名为顶点缓冲区的数组中)和一组索引(面(或三角形),存储在名为索引缓冲区的数组中)组成的几何体)。
CPU 的最后一步是定义如何渲染几何体,对于这项特定任务,CPU 将为 GPU 定义着色器。着色器是一段代码,GPU 将为它需要渲染的每个顶点和像素执行。
首先是一些词汇:可以将顶点(当有多个时称为 vertices)视为 3D 环境中的“点”,与 2D 环境中的点相对。
有两种着色器:顶点着色器和像素(或片段)着色器。
图形管线
在深入研究着色器之前,让我们先回顾一下。要渲染像素,GPU 将采用 CPU 定义的几何体并执行以下操作
- 使用索引缓冲区,将三个顶点收集起来定义一个三角形:索引缓冲区包含一个顶点索引列表。这意味着索引缓冲区中的每个条目都是顶点缓冲区中顶点的编号。这对于避免重复顶点非常有用。例如,以下索引缓冲区是 2 个面的列表:[1 2 3 1 3 4]。第一个面包含顶点 1、顶点 2 和顶点 3。第二个面包含顶点 1、顶点 3 和顶点 4。所以这个几何体中有 4 个顶点。
- 顶点着色器应用于三角形的每个顶点。顶点着色器的主要目标是为每个顶点生成一个像素(3D 顶点在 2D 屏幕上的投影)。
- 使用这 3 个像素(它们在屏幕上定义了一个 2D 三角形),GPU 将插值像素的所有关联值(至少是其位置),然后将像素着色器应用于 2D 三角形中的每个像素,以生成每个像素的颜色。
- 这个过程对索引缓冲区定义的每个面都会执行。
显然,由于其并行性,GPU 能够同时处理大量面的此步骤,从而获得非常好的性能。
GLSL
我们刚刚看到,要渲染三角形,GPU 需要两个着色器:顶点着色器和像素着色器。这些着色器使用一种称为GLSL(图形库着色语言)的语言编写。它看起来像 C。
对于 Internet Explorer 11,我们开发了一个编译器,可将 GLSL 转换为 HLSL(高级着色语言),这是 DirectX 11 的着色器语言。这使得 IE11 能够确保着色器代码是安全的(您不希望在使用WebGL 时重置计算机)。
precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
顶点着色器结构
顶点着色器包含以下内容
- 属性(Attributes):属性定义了顶点的一部分。默认情况下,顶点至少应包含一个位置(一个 vector3:x,y,z)。但是作为开发人员,您可以决定添加更多信息。例如,在前面的着色器中,有一个名为 uv 的 vector2(纹理坐标,允许将 2D 纹理应用于 3D 对象)。
- Uniforms:Uniform 是着色器使用的变量,由 CPU 定义。这里唯一的 uniform 是一个用于将顶点的位置(x,y,z)投影到屏幕(x,y)的矩阵。
- Varying:Varying 变量是由顶点着色器创建并传输到像素着色器的值。这里顶点着色器将一个 vUV(uv 的简单副本)值传输到像素着色器。这意味着这里的像素由位置和纹理坐标定义。这些值将由 GPU 插值并由像素着色器使用。
- main:名为 main 的函数是 GPU 为每个顶点执行的代码,并且至少必须为 `gl_position`(当前顶点的屏幕位置)生成一个值。
在我们的示例中,我们可以看到顶点着色器非常简单。它生成一个名为 `gl_position` 的系统变量(以 `gl_` 开头),用于定义关联像素的位置,并设置一个名为 `vUV` 的 varying 变量。
矩阵背后的玄机
在我们的着色器中,有一个名为 `worldViewProjection` 的矩阵。我们使用此矩阵将顶点位置投影到 `gl_position` 变量。这很酷,但我们如何获得此矩阵的值?它是一个 uniform,所以我们必须在 CPU 端(使用 JavaScript)定义它。
这是进行 3D 开发的复杂部分之一。您必须理解复杂的数学(否则您将不得不使用像 babylon.js 这样的 3D 引擎,我们稍后将看到)。
`worldViewProjection` 矩阵是 3 个不同矩阵的组合
使用生成的矩阵,我们可以将 3D 顶点转换为 2D 像素,同时考虑视点以及与当前对象位置/比例/旋转相关的所有内容。
这是您作为 3D 开发者的责任:创建并更新此矩阵。
回到着色器
一旦顶点着色器在每个顶点上执行(总共 3 次),我们就得到 3 个具有正确 `gl_position` 和 `vUV` 值的像素。然后 GPU 将在这些像素产生的三角形中的每个像素上插值这些值。
然后,对于每个像素,它将执行像素着色器。
precision highp float;
varying vec2 vUV;
uniform sampler2D textureSampler;
void main(void) {
gl_FragColor = texture2D(textureSampler, vUV);
}
像素(或片段)着色器结构
像素着色器的结构与顶点着色器类似
- Varying:Varying 变量是由顶点着色器创建并传输到像素着色器的值。这里像素着色器将从顶点着色器接收一个 `vUV` 值。
- Uniforms:Uniform 是着色器使用的变量,由 CPU 定义。这里唯一的 uniform 是一个采样器,它是一个用于读取纹理颜色的工具。
- main:名为 `main` 的函数是 GPU 为每个像素执行的代码,并且至少必须为 `gl_FragColor`(当前像素的颜色)生成一个值。
这个像素着色器相当简单:它使用来自顶点着色器的纹理坐标从纹理中读取颜色(顶点着色器又从顶点获取了这些坐标)。
您想看看这种着色器的结果吗?这是
(您可以在我的博客上找到完整的可运行代码这里)
要实现这个结果,您将不得不处理大量的WebGL 代码。事实上,WebGL 是一个非常强大但非常底层的 API,您必须自己完成所有工作,从创建缓冲区到定义顶点结构。您还必须完成所有数学计算,设置所有状态,处理纹理加载等等……
太难了?BABYLON.ShaderMaterial 来拯救你
我知道您在想什么:着色器真的很酷,但我不希望烦恼于WebGL 的内部细节,甚至不希望烦恼于数学。
您说得对!这是一个完全合乎情理的要求,这正是我创建Babylon.js 的原因。
请允许我向您展示上面那个滚动的球体演示所使用的代码。首先,您需要一个简单的网页。
<!DOCTYPE html>
<html>
<head>
<title>Babylon.js</title>
<script src="Babylon.js"></script>
<script type="application/vertexShader" id="vertexShaderCode">
precision highp float;
// Attributes
attribute vec3 position;
attribute vec2 uv;
// Uniforms
uniform mat4 worldViewProjection;
// Normal
varying vec2 vUV;
void main(void) {
gl_Position = worldViewProjection * vec4(position, 1.0);
vUV = uv;
}
</script>
<script type="application/fragmentShader" id="fragmentShaderCode">
precision highp float;
varying vec2 vUV;
uniform sampler2D textureSampler;
void main(void) {
gl_FragColor = texture2D(textureSampler, vUV);
}
</script>
<script src="index.js"></script>
<style>
html, body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
margin: 0px;
overflow: hidden;
}
#renderCanvas {
width: 100%;
height: 100%;
touch-action: none;
-ms-touch-action: none;
}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
</body>
</html>
您会注意到着色器是由 `<script>` 标签定义的。使用Babylon.js,您也可以将它们定义在单独的文件(.fx 文件)中。
您可以在这里或我们的 GitHub 仓库下载 babylon.js。您必须使用 1.11 版本或更高版本才能访问 BABYLON.StandardMaterial。
最后,主要的 JavaScript 代码如下。
"use strict";
document.addEventListener("DOMContentLoaded", startGame, false);
function startGame() {
if (BABYLON.Engine.isSupported()) {
var canvas = document.getElementById("renderCanvas");
var engine = new BABYLON.Engine(canvas, false);
var scene = new BABYLON.Scene(engine);
var camera = new BABYLON.ArcRotateCamera("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas);
// Creating sphere
var sphere = BABYLON.Mesh.CreateSphere("Sphere", 16, 5, scene);
var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene, {
vertexElement: "vertexShaderCode",
fragmentElement: "fragmentShaderCode",
},
{
attributes: ["position", "uv"],
uniforms: ["worldViewProjection"]
});
amigaMaterial.setTexture("textureSampler", new BABYLON.Texture("amiga.jpg", scene));
sphere.material = amigaMaterial;
engine.runRenderLoop(function () {
sphere.rotation.y += 0.05;
scene.render();
});
}
};
您可以看到我使用了一个 BABYLON.ShaderMaterial 来摆脱编译、链接和处理着色器的所有负担。
当您创建 BABYLON.ShaderMaterial 时,您必须指定用于存储着色器的 DOM 元素或着色器文件的基本名称。如果您选择使用文件,您必须为每个着色器创建一个文件,并使用以下模式:basename.vertex.fx 和 basename.fragment,.fx。然后您需要像这样创建材质。
var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader", { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] });
您还必须指定您使用的属性和 uniform 的名称。
然后,您可以使用 `setTexture`、`setFloat`、`setFloats`、`setColor3`、`setColor4`、`setVector2`、`setVector3`、`setVector4`、`setMatrix` 函数直接设置 uniform 和采样器的值。
很简单,对吧?
您还记得之前的 `worldViewProjection` 矩阵吗?使用Babylon.js 和 `BABYLON.ShaderMaterial`,您无需担心任何事情!`BABYLON.ShaderMaterial` 将自动为您计算它,因为您在 uniform 列表中声明了它。
BABYLON.ShaderMaterial 还可以为您处理以下矩阵
- world
- 查看
- projection
- worldView
- worldViewProjection
不再需要数学了。 例如,每次您执行 `sphere.rotation.y += 0.05` 时,球体的 world 矩阵都会为您生成并传输到 GPU。
CYOS:创建您自己的着色器
所以,让我们做得更大一些,创建一个页面,您可以在其中动态创建自己的着色器并立即看到结果。此页面将使用我们之前讨论过的相同代码,并将使用 `BABYLON.ShaderMaterial` 对象来编译和执行您创建的着色器。
我为CYOS 使用了 ACE 代码编辑器。这是一个了不起的代码编辑器,带有语法高亮。请随意在此查看。您可以在这里找到CYOS。
使用第一个组合框,您将能够选择预定义的着色器。我们稍后将逐一介绍它们。
您还可以使用第二个组合框更改用于预览您的着色器的网格(3D 对象)。
编译按钮用于从您的着色器创建新的 `BABYLON.ShaderMaterial`。此按钮使用的代码如下。
// Compile shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "normal", "uv"], uniforms: ["world", "worldView", "worldViewProjection"] }); var refTexture = new BABYLON.Texture("ref.jpg", scene); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = new BABYLON.Texture("amiga.jpg", scene); shaderMaterial.setTexture("textureSampler", amigaTexture); shaderMaterial.setTexture("refSampler", refTexture); shaderMaterial.setFloat("time", 0); shaderMaterial.setVector3("cameraPosition", BABYLON.Vector3.Zero()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial;
非常简单,对吧?材质已准备好为您提供三个预计算的矩阵(`world`、`worldView` 和 `worldViewProjection`)。顶点将带有位置、法线和纹理坐标。另外两个纹理也已为您加载。
- amiga.jpg
- ref.jpg
最后,这是我更新两个方便的 uniform 的 `renderLoop`。
- 一个称为 `time`,用于获得一些有趣的动画。
- 一个称为 `cameraPosition`,用于将相机的位置获取到您的着色器中(这将对光照方程很有用)。
engine.runRenderLoop(function () { mesh.rotation.y += 0.001; if (shaderMaterial) { shaderMaterial.setFloat("time", time); time += 0.02; shaderMaterial.setVector3("cameraPosition", camera.position); } scene.render(); });
得益于我们在 Windows Phone 8.1 上所做的工作,我们也可以在您的 Windows Phone 上使用 CYOS(现在是创建着色器的好时机)。
基本着色器
那么,让我们从CYOS 中定义的第一个着色器开始:基本着色器。
我们已经知道这个着色器。它计算 `gl_position` 并使用纹理坐标为每个像素获取颜色。
要计算像素位置,我们只需要 `worldViewProjection` 矩阵和顶点的 `position`。
precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
纹理坐标(uv)会未经修改地传输到像素着色器。
请注意,我们需要在顶点和像素着色器的第一行添加“precision mediump float;”,因为 Chrome 需要它。它定义了,为了获得更好的性能,我们不使用全精度浮点值。
像素着色器甚至更简单,因为我们只需要使用纹理坐标并获取纹理颜色。
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }
我们之前说过 `textureSampler` uniform 被填充了“amiga”纹理,所以结果如下。
黑白着色器
现在,让我们继续一个新的着色器:黑白着色器。
此着色器的目标是使用上一个着色器,但采用黑白渲染模式。
为此,我们可以保留相同的顶点着色器。像素着色器将稍作修改。
我们的第一个选择是只取一个分量,例如绿色分量。
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0); }
如您所见,我们使用了 `.ggg` 而不是 `.rgb`(此操作称为swizzle)。
但是,如果我们想要一个真正准确的黑白效果,最好计算亮度(它考虑了所有分量)。
precision highp float;
varying vec2 vUV;
uniform sampler2D textureSampler;
void main(void) {
float luminance = dot(texture2D(textureSampler, vUV).rgb, vec3(0.3, 0.59, 0.11));
gl_FragColor = vec4(luminance, luminance, luminance, 1.0);
}
点运算(或点积)的计算方法如下:
result = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z
所以,在我们的例子中:
luminance = r * 0.3 + g * 0.59 + b * 0.11 (This values are based on the fact that human eye is more sensible to green)
听起来很棒,不是吗?
卡通着色器
现在,让我们转向一个更复杂的着色器:卡通着色器。
这个着色器将需要在像素着色器中获取顶点的法线和顶点的位置。所以顶点着色器将是这样的。
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 world; uniform mat4 worldViewProjection; // Varying varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vPositionW = vec3(world * vec4(position, 1.0)); vNormalW = normalize(vec3(world * vec4(normal, 0.0))); vUV = uv; }
请注意,我们也使用了 `world` 矩阵,因为位置和法线存储时没有经过任何变换,我们必须应用 world 矩阵来考虑对象的旋转。
像素着色器如下。
precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { float ToonThresholds[4]; ToonThresholds[0] = 0.95; ToonThresholds[1] = 0.5; ToonThresholds[2] = 0.2; ToonThresholds[3] = 0.03; float ToonBrightnessLevels[5]; ToonBrightnessLevels[0] = 1.0; ToonBrightnessLevels[1] = 0.8; ToonBrightnessLevels[2] = 0.6; ToonBrightnessLevels[3] = 0.35; ToonBrightnessLevels[4] = 0.2; vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); vec3 color = texture2D(textureSampler, vUV).rgb; if (ndl > ToonThresholds[0]) { color *= ToonBrightnessLevels[0]; } else if (ndl > ToonThresholds[1]) { color *= ToonBrightnessLevels[1]; } else if (ndl > ToonThresholds[2]) { color *= ToonBrightnessLevels[2]; } else if (ndl > ToonThresholds[3]) { color *= ToonBrightnessLevels[3]; } else { color *= ToonBrightnessLevels[4]; } gl_FragColor = vec4(color, 1.); }
此着色器的目标是模拟光源,而不是计算平滑着色,我们将假定光源根据特定的亮度阈值进行应用。例如,如果光强度介于 1(最大)和 0.95 之间,则直接应用对象的颜色(从纹理获取)。如果强度介于 0.95 和 0.5 之间,则颜色将衰减 0.8 倍,依此类推。
因此,此着色器中主要有 4 个步骤。
- 首先,我们声明阈值和级别常量。
- 然后,我们需要使用 Phong 方程计算光照(我们假定光源不移动)。
vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW));
每像素的光照强度取决于法线和光方向之间的角度。
- 然后,我们获取像素的纹理颜色。
- 最后,我们检查阈值并将级别应用于颜色。
结果看起来像一个卡通对象。
Phong 着色器
我们在上一个着色器中使用了 Phong 方程的一部分。所以,现在让我们尝试完全使用它。
这里的顶点着色器非常简单,因为所有操作都将在像素着色器中完成。
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vUV = uv; vPosition = position; vNormal = normal; }
根据方程,您必须计算漫反射和镜面反射部分,方法是使用光方向和顶点的法线。
precision highp float; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; // Uniforms uniform mat4 world; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 vLightPosition = vec3(0, 20, 10); // World values vec3 vPositionW = vec3(world * vec4(vPosition, 1.0)); vec3 vNormalW = normalize(vec3(world * vec4(vNormal, 0.0))); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); vec3 color = texture2D(textureSampler, vUV).rgb; // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); // Specular vec3 angleW = normalize(viewDirectionW + lightVectorW); float specComp = max(0., dot(vNormalW, angleW)); specComp = pow(specComp, max(1., 64.)) * 2.; gl_FragColor = vec4(color * ndl + vec3(specComp), 1.); }
我们已经在上一个着色器中使用了漫反射部分,所以这里我们只需要添加镜面反射部分。维基百科文章中的这张图片很好地解释了着色器的工作原理。
在我们的球体上的结果。
Discard 着色器
对于 Discard 着色器,我想介绍一个新概念:discard 关键字。
此着色器将丢弃所有非红色像素,从而产生一个镂空对象的错觉。
顶点着色器与基本着色器使用的相同。
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
像素着色器需要测试颜色,并在例如绿色分量过高时使用 discard。
precision highp float; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { vec3 color = texture2D(textureSampler, vUV).rgb; if (color.g > 0.5) { discard; } gl_FragColor = vec4(color, 1.); }
结果很有趣。
Wave 着色器
我们已经对像素着色器做了很多尝试,但我也想向您展示,我们可以用顶点着色器做很多事情。
对于 Wave 着色器,我们将重用 Phong 像素着色器。
顶点着色器将使用名为 `time` 的 uniform 来获取一些动画值。使用此 uniform,着色器将生成一个波浪效果,改变顶点的位置。
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; uniform float time; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec3 v = position; v.x += sin(2.0 * position.y + (time)) * 0.5; gl_Position = worldViewProjection * vec4(v, 1.0); vPosition = position; vNormal = normal; vUV = uv; }
对 `position.y` 应用正弦函数,结果如下。
球面环境映射
这个着色器很大程度上受到这个教程的启发。我让您阅读这篇优秀的文章并玩转相关的着色器。
菲涅尔着色器
最后,我想以我最喜欢的着色器结束这篇文章:菲涅尔着色器。
此着色器用于根据观察方向与顶点法线之间的角度应用不同的强度。
顶点着色器与卡通着色器使用的相同,我们可以轻松地在像素着色器中计算菲涅尔项(因为我们有法线和相机位置,可用于评估观察方向)。
precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 color = vec3(1., 1., 1.); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Fresnel float fresnelTerm = dot(viewDirectionW, vNormalW); fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.); gl_FragColor = vec4(color * fresnelTerm, 1.); }
您的着色器?
您现在更有能力创建自己的着色器了。请随时使用这里的评论或下面链接的babylon.js 论坛来分享您的实验!
如果您想进一步了解,这里有一些有用的链接。
- Babylon.js 仓库:https://github.com/BabylonJS/Babylon.js
- Babylon.js 论坛:http://www.html5gamedevs.com/forum/16-babylonjs/
- CYOS:http: //www.babylonjs.com/CYOS
- GLSL 在维基百科上:http://en.wikipedia.org/wiki/OpenGL_Shading_Language
- GLSL 文档:https://www.opengl.org/documentation/glsl/
还有一些我创建的关于该主题的学习资料。
或者,回顾一下,我们团队关于 JavaScript 的学习系列。
- 让您的 HTML/JavaScript 更快的实用性能技巧(一个 7 部分系列,涵盖响应式设计、休闲游戏到性能优化)。
- 现代 Web 平台 JumpStart(HTML、CSS 和 JS 的基础知识)。
- 使用 HTML 和 JavaScript 开发通用 Windows 应用 JumpStart(使用您已创建的 JS 构建应用)。
当然,您随时可以使用我们的一些免费工具来构建您的下一个 Web 体验:Visual Studio Community、Azure 试用版,以及适用于 Mac、Linux 或 Windows 的跨浏览器测试工具。
本文是 Microsoft Web 开发技术系列的一部分。我们很高兴与您分享Project Spartan 及其新渲染引擎。在modern.IE 上获取免费虚拟机或在您的 Mac、iOS、Android 或 Windows 设备上远程测试。