“着色器”是什么意思? 如何使用HTML5和WebGL创建它们

本文是Microsoft的Web开发技术系列的一部分。 感谢您支持使SitePoint成为可能的合作伙伴。

您可能已经注意到,去年我们第一次谈论了babylon.js ,最近发布了带有3D声音定位(使用WebAudio)和体积光散射的babylon.js v2.0

如果您错过了v1.0公告,那么首先您可以在此处进行第二天的主题演讲,直接进入2:24-2:28。 在其中,Microsoft宣传员Steven Guggenheimer和John Shewchuk演示了如何在Babylon.js中添加Oculus Rift支持。 这个演示的关键内容之一就是我们在特定的着色器上所做的工作,以模拟镜头,如您在此图中看到的:

Babylon.js的Oculus Rift支持演示

我还与Frank Olivier和Ben Constable举行了有关IE和Babylon.js上图形的会议。

这使我想到了有关babylon.js的一个常见问题:着色器是什么意思? 因此,今天我将尝试向您解释着色器的工作原理。

理论

在开始实验之前,我们必须首先了解事物在内部如何工作。

在处理硬件加速的3D时,我们讨论了两个CPU:主CPU和GPU。 GPU是一种非常专业的CPU。

GPU是您使用CPU设置的状态机。 例如,CPU将配置GPU渲染线条而不是三角形。 否则它将定义透明性等等。

设置完所有状态后,CPU将定义要渲染的内容(几何图形,该几何图形由点列表(称为顶点,并存储在称为顶点缓冲区的数组中)和索引列表(面或三角形)组成—存储到称为索引缓冲区的数组中)。

CPU的最后一步是定义如何渲染几何图形,对于此特定任务,CPU将为GPU定义着色器。 着色器是GPU将针对其必须渲染的每个顶点和像素执行的一段代码。

首先介绍一些词汇:将一个顶点(有多个顶点时的顶点)视为3D环境中的“点”,而不是2D环境中的点。

着色器有两种:顶点着色器和像素(或片段)着色器。

图形管线

在深入了解着色器之前,让我们退后一步。 为了渲染像素,GPU将采用CPU定义的几何形状并执行以下操作:

  • 使用索引缓冲区,可以收集三个顶点以定义一个三角形:索引缓冲区包含一个顶点索引列表。 这意味着索引缓冲区中的每个条目都是顶点缓冲区中的顶点数。 这对于避免重复顶点非常有用。 例如,以下索引缓冲区是2个面的列表:[1 2 3 1 3 4]。 第一个面包含顶点1,顶点2和顶点3。第二个面包含顶点1,顶点3和顶点4。因此,此几何图形中有4个顶点:

  • 顶点缓冲区图

  • 顶点着色器将应用于三角形的每个顶点。 顶点着色器的主要目标是为每个顶点(3D顶点在2D屏幕上的投影)生成一个像素:

  • 座标图

  • 使用这3个像素(在屏幕上定义2d三角形),GPU将对附加到该像素的所有值(至少是其位置)进行插值,并将像素着色器应用于2d三角形中包含的每个像素,以便生成每个像素的颜色:

  • 带有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;
}

顶点着色器结构

顶点着色器包含以下内容:

  • 属性 :属性定义了顶点的一部分。 默认情况下,顶点至少应包含一个位置( vector3:x, y, z )。 但是作为开发人员,您可以决定添加更多信息。 例如,在以前的着色器中,有一个名为uvvector2 (纹理坐标,允许在3D对象上应用2D纹理)
  • 制服 :制服是着色器使用的变量,由CPU定义。 我们这里唯一的统一是一个矩阵,用于将顶点(x,y,z)的位置投影到屏幕(x,y)
  • 变化 :变化变量是由顶点着色器创建并传输到像素着色器的值。 此处,顶点着色器会将vUVuv的简单副本)值传输到像素着色器。 这意味着此处定义了具有位置和纹理坐标的像素。 这些值将由GPU内插,并由像素着色器使用。
  • main :名为main的函数是GPU为每个顶点执行的代码,并且必须至少产生gl_position的值(当前顶点在屏幕上的位置)。

我们可以在示例中看到顶点着色器非常简单。 它产生一个系统变量(开始用gl_命名) gl_position来定义相关联的像素的位置,并将其设置称为变可变vUV

矩阵背后的伏都教

在我们的着色器中,我们有一个名为worldViewProjection的矩阵。 我们使用此矩阵将顶点位置gl_positiongl_position变量。 那很酷,但是我们如何得到这个矩阵的值呢? 它是统一的,因此我们必须在CPU端定义它(使用JavaScript)。

这是执行3D的复杂部分之一。 您必须了解复杂的数学运算(否则,您将不得不使用3D引擎(如babylon.js),我们稍后将介绍)。

worldViewProjection矩阵是3种不同矩阵的组合:

worldViewProjection矩阵

使用结果矩阵,我们可以将3d顶点转换为2d像素,同时考虑到视点以及与当前对象的位置/比例/旋转相关的所有内容。

作为3D开发人员,这是您的责任:创建并保持此矩阵为最新。

返回着色器

在每个顶点上执行顶点着色器后(然后执行三次),我们将获得三个像素,它们具有正确的gl_position和_vUV _value。 然后,GPU将在这些像素产生的三角形中包含的每个像素上插值这些值

然后,对于每个像素,它将执行像素着色器:

precision highp float;
varying vec2 vUV;
uniform sampler2D textureSampler;

void main(void) {
    gl_FragColor = texture2D(textureSampler, vUV);
}

像素(或片段)着色器结构

像素着色器的结构类似于顶点着色器:

  • 变化 :变化变量是由顶点着色器创建并传输到像素着色器的值。 此处,像素着色器将从顶点着色器接收vUV值。
  • 制服 :制服是着色器使用的变量,由CPU定义。 我们这里唯一的制服是采样器,它是一种用于读取纹理颜色的工具。
  • main :名为main的函数是GPU为每个像素执行的代码,并且至少必须产生gl_FragColor的值(当前像素的颜色)。

这个像素着色器非常简单:它使用来自顶点着色器的纹理坐标从纹理中读取颜色(后者又从顶点获取)。

您是否要查看这种着色器的结果? 这里是:

像素着色器结果

(你可以看到完整的工作代码在我的博客点击这里

为了获得这个结果,您将不得不处理大量的WebGL代码。 的确,WebGL是一个功能强大但水平很低的API,从创建缓冲区到定义顶点结构,您必须自己做所有事情。 您还必须进行所有数学运算并设置所有状态并处理纹理加载等等。

太难? BABYLON.Shader救援材料

我知道您在想什么:着色器确实很棒,但是我不想打扰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.fxbasename.fragment,.fx 然后,您将必须创建如下材料:

var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader",{
            attributes: ["position", "uv"],
            uniforms: ["worldViewProjection"]
        });

您还必须指定使用的属性和制服的名称。

然后,您可以使用setTexturesetFloatsetFloatssetColor3setColor4setVector2setVector3setVector4setMatrix functions直接设置制服和采样器的值。

您还记得以前的worldViewProjection矩阵吗? 使用Babylon.js和BABYLON.ShaderMaterial ,您无需担心! BABYLON.ShaderMaterial会自动为您计算它,因为您在制服列表中声明了它。 BABYLON.ShaderMaterial还可以为您处理以下矩阵:

  • 世界
  • 视图
  • 投影
  • 世界观
  • worldViewProjection

不再需要数学。 例如,每次执行sphere.rotation.y += 0.05 ,都会为您生成球体的世界矩阵并将其传输到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;

资料准备好向您发送三个预先计算的矩阵( worldworldViewworldViewProjection )。 顶点将带有位置,法线和纹理坐标。 还已经为您加载了两个纹理:

棋盘格纹理用作起点

球形形状用作起点

最后是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仪表板

基本着色器

因此,让我们从CYOS上定义的第一个着色器开始:基本着色器。

我们已经知道此着色器。 它计算gl_position并使用纹理坐标为每个像素获取颜色。

要计算像素位置,我们只需要worldViewProjection矩阵和顶点位置:

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制服填充了“ amiga”纹理,因此结果如下:

基本着色器结果

黑白着色器

现在让我们继续一个新的着色器:黑白着色器。

该着色器的目标是使用前一个着色器,但仅具有黑白渲染模式。

为此,我们可以保留相同的顶点着色器。 像素着色器将稍作修改。

我们的第一个选择是仅采用一个组件,例如绿色组件:

precision highp float;

varying vec2 vUV;

uniform sampler2D textureSampler;

void main(void) {
    gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0);
}

正如你所看到的,而不是使用.rgb(该操作被称为调酒 ),我们使用.ggg

但是,如果我们想要真正准确的黑白效果,那么计算亮度(考虑所有分量)应该是一个更好的主意:

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矩阵,因为位置和法线存储时没有任何变换,我们必须应用世界矩阵来考虑对象的旋转。

像素着色器如下:

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倍,依此类推。

因此,此着色器有四个步骤:

  • 首先我们声明阈值和水平常量
  • 然后,我们需要使用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;

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.);
}

我们已经在上一个着色器中使用了扩散部分,因此在这里我们只需要添加镜面反射部分。 这张来自Wikipedia的图片很好地说明了着色器的工作原理:

冲反射组件

我们这个领域的结果:

Phong着色器结果

舍弃着色器

对于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着色器,我们将重用Phong像素着色器。

顶点着色器将使用统一的time来获取一些动画值。 使用此制服,着色器将生成一个具有顶点位置的波:

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社区Azure试用版以及用于Mac,Linux或Windows的跨浏览器测试工具

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

From: https://www.sitepoint.com/mean-shaders-create-html5-webgl/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值