65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2015 年 5 月 1 日

CPOL

15分钟阅读

viewsIcon

16470

今天,我将尝试向您解释 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 论坛来分享您的实验!

如果您想进一步了解,这里有一些有用的链接。

还有一些我创建的关于该主题的学习资料。

或者,回顾一下,我们团队关于 JavaScript 的学习系列。

当然,您随时可以使用我们的一些免费工具来构建您的下一个 Web 体验:Visual Studio CommunityAzure 试用版,以及适用于 Mac、Linux 或 Windows 的跨浏览器测试工具

本文是 Microsoft Web 开发技术系列的一部分。我们很高兴与您分享Project Spartan 及其新渲染引擎。在modern.IE 上获取免费虚拟机或在您的 Mac、iOS、Android 或 Windows 设备上远程测试。

© . All rights reserved.