为啥着色器必须在 webgl 程序的 html 文件中?

Posted

技术标签:

【中文标题】为啥着色器必须在 webgl 程序的 html 文件中?【英文标题】:Why do shaders have to be in html file for webgl program?为什么着色器必须在 webgl 程序的 html 文件中? 【发布时间】:2012-12-22 14:22:18 【问题描述】:

我看到以下问题,有人询问如何从 html 中删除着色器: WebGL - is there an alternative to embedding shaders in HTML?

有详细的解决方法可以加载到包含问题答案中建议的着色器的文件中。

在我看到的教程中,着色器代码是直接嵌入到 html 中的。 javascript 代码使用 getElementById 引用它。但是由于许多原因,将着色器直接嵌入到 html 中是很难看的。为什么我不能只使用 src= 属性在外部引用它?

<script type="x-shader/x-fragment" id="shader-fs" src="util/fs"></script>

上面的不行,我只是想知道为什么不行。这显然与脚本本身的限制有关,但我不明白。

【问题讨论】:

它们不必是 HTML,它只是许多示例使用的东西,但您也可以使用 ajax 获取着色器源或使用 require.js 或任何其他您能想到的方式加载为模块of 获取文本。 WebGL - is there an alternative to embedding shaders in HTML?的可能重复 【参考方案1】:

着色器语言脚本只是文本。 可以从任何地方抓取或生成文本(您可以阅读或生成文本)。许多教程只是跳过了魔术发生的部分,并且 WebGL 着色器实例是从获得的字符串中创建的。您没有理由不能像您建议的那样在外部引用脚本,但是您需要额外的 JavaScript 来加载内容,而不是浏览器。脚本标签很可能在教程中使用,主要是因为如果你给脚本标签一个浏览器不理解的类型,浏览器会跳过标签内容的执行或脚本源的检索 ,所以标签的内容和属性可以随心所欲地使用。

编辑:好吧,我必须收回一些东西。我决定通过四种浏览器(Chrome、Firefox、IE9、Opera),看看当你有一行时会发生什么

<script type="x-shader/x-fragment" id="shader-fs" src="util/fs"></script>

在您的 html 中。事实证明,浏览器确实在我尝试过的每个浏览器中加载文件,所以我错了。但是,这并不意味着浏览器除了缓存文件之外还知道如何处理文件。我不知道“为什么 src="util/fs" 不起作用???”是什么意思。在我尝试过的所有浏览器中,

alert(document.getElementById('shader-fs').src);

在给定部分路径时提醒文件的完整路径。 (也许这是您的问题?当浏览器为您提供完整路径时,您期望的是部分路径?)除此之外,我不确定如何解释您的问题。

【讨论】:

这不能回答问题。我试图重写脚本以从外部提取脚本文本。忘记没有支持的文本类型“x-shader/x-fragment”,这家伙在他的加载器代码中使用它。为什么 src="util/fs" 不起作用??? 更新了答案。如果这个答案没有帮助,它可能会帮助您在问题中添加您如何提取外部资源。【参考方案2】:

2018 年更新

在 2018 年,我建议使用多行模板文字,如用反引号围绕着色器,它可以跨多行

const someShaderSource = `
attribute vec4 position;
uniform mat4 matrix;
void main() 
  gl_Position = matrix * position;

`;

如果您想将着色器放在单独的文件中,您可以在 2018 年使用 JavaScript 模块轻松做到这一点。着色器文件可能如下所示

// someshader.glsl.js
export default `
attribute vec4 position;
uniform mat4 matrix;
void main() 
  gl_Position = matrix * position;

`;

要使用 JavaScript 模块,您的主要 JavaScript 必须位于单独的文件中。您可以通过导入来访问着色器源

// main.js

import someShaderSource from './someshader.glsl.js';

// use someShadeSource

然后你将它包含在你的 HTML 中

<script src="main.js" type="module"></script>

或者你可以像这样从页面本身的脚本标签中使用它

<script type="module">
import someShaderSource from './someshader.glsl.js';

// use someShadeSource
</script>

如果您想支持旧版浏览器,您可以使用rollup 之类的工具,它会读取所有import 语句并生成一个大的JavaScript 文件。这就是three.js 所做的。

如果您需要支持 IE11,您可以使用babel 转换多行模板。多年来,所有其他浏览器都支持多行模板。

原答案

为什么着色器必须在 webgl 程序的 html 文件中?

他们没有

您可以将着色器放在外部 javascript 中。例如

// --myshader.js--
var myFragmentShader = 
  "void main() \n" +
  "  gl_FragColor = vec4(1,0,0,1);\n" +
  "n\";

或者其他常见的格式

// --myshader.js--
var myFragmentShader = [
  "void main() ",
  "  gl_FragColor = vec4(1,0,0,1);", 
  "",
].join("\n");

在所有支持 WebGL 的浏览器中,您都可以使用 template literals

// --myshader.js--
var myFragmentShader = `
  void main() 
    gl_FragColor = vec4(1,0,0,1); 
  
`;

否则,您可以将它们放在文本文件中并使用 XMLHTTPRequest 加载它们

// --myshader.txt
  void main() 
    gl_FragColor = vec4(1,0,0,1); 
  

然后在 JavaScript 中执行以下操作

function loadTextFile(url, callback) 
  var request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.addEventListener('load', function() 
     callback(request.responseText);
  );
  request.send();


loadTextFile("myshader.txt", function(text) 
  // use text...
);

人们将它们放在 HTML 中的原因是因为它简单、高效且同步。

easy:与 JS 文件版本不同,您不必在每一行都用引号和其他标点符号括起来。尽管现在使用 es6 不再是问题。每个支持 WebGL 的浏览器都支持 es6 模板字符串。

高效:与 text 和 js 文件不同,服务器只有一个请求。当然,有些人可能会在他们的 js 文件上运行连接器来修复其中的一些问题。

同步:与文本文件不同,它们的使用是同步的。无需回调或承诺或以其他方式处理下载文件的异步问题。

至于为什么您的示例不起作用,我很确定原因是它允许跨源资源访问。 &lt;script&gt; 标签是在人们发现跨源访问是一个问题之前设计的,因此他们无法在不破坏一堆站点的情况下关闭跨源脚本。他们可以让其他一切变得更加严格。

例如,XMLHttpRequest 不允许跨源访问,除非您联系的服务器给予许可。如果脚本标签允许您访问该内容,您可以使用脚本标签来解决该限制。换句话说,不是创建 XMLHttpRequest 并读取 request.responseText 以获取结果,而是以编程方式创建脚本标记,将其 src 设置为您想要的 URL,然后在完成时读取其 text 字段。为确保您不能这样做,您不能读取具有 src 属性的脚本标签的 text 字段

【讨论】:

投票赞成,因为尽管已经提出了一种解决方法,但了解为什么原始示例不起作用是很有用的。 它被否决(我认为)是因为它说明了脚本标签不做什么,而不是解释它做了什么。 但是如果我使用 ES6 格式,我可以让 Babel 将其转编译为字符串版本之一吗? 对不起,我忽略了你所说的每个支持 WebGL 的浏览器也支持模板字符串的部分。如果是这种情况,我想知道为什么将 GLSL 存储为实际字符串似乎仍然更常见?老实说,使用这种格式实在是太糟糕了。 @zwcloud:我有两种方法,在我的编辑器中有一个行号。如果着色器从第 517 行开始并且错误消息显示为 235,则 517+235 = 错误着色器行位于 752。否则,在我的库 twgl 中,如果出现错误,我将打印整个着色器,并将行号添加到控制台。 【参考方案3】:

您根本不必使用&lt;script&gt; 标签来加载着色器程序。大多数教程和示例只是将它们用作在网页的 DOM 中存储字符串的容器。脚本类型"x-shader/x-fragment" 对网络浏览器没有意义,所以它们不执行脚本。但是,它们确实将该标记的内容作为字符串存储在 DOM 中,以后可以通过“真实”脚本访问。这仅在脚本内容在 HTML 文件中时有效。 通过 src 属性加载脚本时,内容不会成为脚本标签的文本子节点,因此无法通过 DOM 树访问

您也可以将着色器的源代码作为字符串存储在 Javascript 文件中:

// myVertextShader.glsl.js
var myVertexShaderSrc =         
        "attribute vec3 pos;"+      
        "void main() "+        
        "   gl_Position = vec4(pos, 1.0);"+     
        ""
    ;

然后您将像这样编译着色器:

var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, myVertexShaderSrc);
gl.compileShader(vertexShader);

gl.attachShader(program, vertexShader);

【讨论】:

以上是关于为啥着色器必须在 webgl 程序的 html 文件中?的主要内容,如果未能解决你的问题,请参考以下文章

WebGL -- 传参给着色器

WebGL着色器绘制一个点

第二篇: WebGL 着色器和GLSL

为啥我必须切换纹理单元才能让我的片段着色器识别要使用的纹理?

03 传递贴图给着色器

WebGL 创建和初始化着色器过程