在本文中,将探讨如何将着色器代码内联到C++项目中,并讨论一些在WebAssembly和Blazor环境中优化性能和安全性的技巧。请注意,作者对Blazor的了解有限,因此这些技巧可能并不完全适用于Blazor。现在,让开始吧。
在典型的OpenGL项目中,着色器代码通常是与C++源代码分开存储的。对于初学者来说,WebGL 1.0标准是基于OpenGL 2.0 ES的,这两个标准非常相似,以至于可以将OpenGL 2.0 ES调用一对一地转换为WebGL 1.0调用。对于OpenGL 2.0 ES及其对应的WebGL 1.0,有两种类型的着色器:顶点着色器和片段着色器。在Direct3D术语中,片段着色器的对应物是像素着色器,但这个名字并不完全正确,因为这个着色器不是在像素上操作,而是在可见的纹理像素(texture pixels的缩写)上操作。但大多数人更喜欢像素着色器这个名字,而不是片段着色器。让给展示一个简单的顶点着色器,后面跟着片段着色器。
// 顶点着色器
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = WorldViewProjection * vec4(a_position, 1.0);
v_texCoord = a_texCoord;
}
// 片段着色器
varying vec2 v_texCoord;
uniform sampler2D s_texture;
uniform float s_alpha;
void main() {
vec4 color = texture2D(s_texture, v_texCoord);
color.a = color.a * s_alpha;
gl_FragColor = color;
}
这些是存储在vert_shader
和frag_shader
变量中的相同着色器,这次使用的是传统的C++字符串字面量。注意每一行都被引号包围,并以换行符结束!
const char* vert_shader =
"vert(uniform mat4 WorldViewProjection; \n"
"attribute vec3 a_position; \n"
"attribute vec2 a_texCoord; \n"
"varying vec2 v_texCoord; \n"
"void main() \n"
"{ \n"
"gl_Position = WorldViewProjection * vec4(a_position, 1.0); \n"
"v_texCoord = a_texCoord; \n"
"} \n"
;
const char* frag_shader =
"frag(varying vec2 v_texCoord; \n"
"uniform sampler2D s_texture; \n"
"uniform float s_alpha; \n"
"void main() \n"
"{ \n"
"vec4 color = texture2D( s_texture, v_texCoord ); \n"
"color.a = color.a * s_alpha; \n"
"gl_FragColor = color; \n"
"} \n"
;
这些是存储在vert_shader
和frag_shader
变量中的相同两个着色器,这次使用的是现代C++11字符串字面量。可以看到代码更干净了。通过内联着色器代码,应用程序不需要单独下载和处理着色器代码,为每个OpenGL对象节省了两个下载连接,这加起来可能很多。对于应用程序,节省了50次下载。为什么要将着色器内联到C++代码中?大多数时候,当更改着色器代码时,通常也需要修改与之交互的C++代码。内联的唯一缺点是,当有很多行着色器代码并且发生编译错误时,开发者可能很难确定哪行是有问题的行号。
const char* vert_shader =
R"vert(uniform mat4 WorldViewProjection;
attribute vec3 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main()
{
gl_Position = WorldViewProjection * vec4(a_position, 1.0);
v_texCoord = a_texCoord;
}
)vert";
const char* frag_shader =
R"frag(varying vec2 v_texCoord;
uniform sampler2D s_texture;
uniform float s_alpha;
void main()
{
vec4 color = texture2D( s_texture, v_texCoord );
color.a = color.a * s_alpha;
gl_FragColor = color;
}
)frag";
只要可能,就编写尽可能多的代码在GPU上运行,而不是在CPU上运行。动画正弦波就是在GPU上计算的。在撰写本文时,Asm.js严格来说只支持单线程,对于GPU来说,即使是低端的GPU也有很多简单的线程来分散浮点计算。看看下面每秒生成的字节数:当正弦波运动计算在CPU上完成时,每秒必须向GPU发送总共1,536,000字节。
1 float = 4 bytes
1 vertex = 3 floats = 12 bytes
1 quad = 4 vertex = 12 floats = 64 bytes
1 quad = 64 bytes / 2 = 32 bytes
800 quad = 800 * 32 = 25600 bytes
60 frames per second = 60 * 25600 = 1536000
视频链接:正弦波
使用gzip压缩资产/WASM文件。不要压缩已经压缩的文件,比如JPEG和PNG。如果图像库支持从内存中加载并具有正确的偏移量,可以将图像附加到一个大文件中并加载到内存中。
为了保护资产不被窃取,使用可以快速原地解密的加密算法,而不是解密到新的目的地文件。请注意,简单的加密虽然快速,但只能阻止偶然的最终用户,它不能完全防止有决心的黑客逆向工程源代码以找出加密密钥来窃取资产。