GLSL 構文
GLSL は厳密に型指定された言語であり、すべての文にはセミコロンが必要です。その構文は typescript に非常に似ています。
GLSL のコメント構文は JS と同じで、変数名の規則も JS と同じで、キーワード、予約語、および gl_、webgl_、またはwebglで始めることはできません。演算子は基本的に JS と同じで、++ – += && || と三項演算子がすべてサポートされています。
GLSL には、浮動小数点、整数、およびブール値の 3 つの主要なデータ値タイプがあります。浮動小数点数には小数点が必要であることに注意してください。型変換は、float、int、bool 関数を使用して直接実行できます。たとえば、整数 1 は次のように浮動小数点数 1.0 に変換されます。
float f = float(1);
さらに、GLSL で変数を定義するための構文は <type> <variable name> であり、変数に割り当てられる値は、定義された変数の型と一致している必要があります。上記のコードで float f = 1; と記述すると、エラーが発生します。 1 は整数であるため、直接報告されますが、小数点を追加すると浮動小数点数 (float f = 1) に変わります。; このように記述した場合、エラーは報告されません。
JavaScriptやCなどの言語はCPU上で実行されることが多いのに対し、シェーダ言語はGPU上で実行されることが多く、実行環境が異なるため、当然構文設計も多少異なります。
基本タイプ:
タイプ | 説明する |
---|---|
空所 | 空の型、つまり値を返さない |
ブール | ブール型 true、false |
整数 | 符号付き整数 符号付き整数 |
浮く | 符号付き浮動小数点数浮動スカラー |
物2、物3、物4 | n 成分の浮動小数点ベクトル |
bvec2、bvec3、bvec4 | n 次元のブール ベクトル ブール ベクトル |
ivec2、ivec3、ivec4 | n 次元の整数ベクトル 符号付き整数ベクトル |
マット2、マット3、マット4 | 2x2、3x3、4x4 浮動小数点行列 浮動小数点行列 |
サンプラー2D | 2D テクスチャa 2D テクスチャ |
サンプラーキューブ | キューブマップされたテクスチャ |
基本的な構造と配列:
タイプ | 説明する |
---|---|
構造 | struct type-name{} は C 言語の構造に似ています |
配列 | float foo[3] glsl は 1 次元配列のみをサポートし、配列は構造体のメンバーになることができます |
ベクトルのコンポーネントアクセス:
glsl のベクトル (vec2、vec3、vec4) は特別な意味を持つことがよくあります。たとえば、空間座標 (x、y、z、w) または色 (r、g、b、a) を表す場合があり、その後、Orはテクスチャ座標 (s、t、p、q) を表すため、glsl はよりユーザーフレンドリーなコンポーネント アクセス メソッドを提供します。
Vector.xyzw xyzw は任意の方法で組み合わせることができます
Vector.rgba ここでは rgba を任意の方法で組み合わせることができます
rgbaを任意の方法で組み合わせることができるvector.stpq
vec4 v = vec4(1.0,2.0,3.0,1.0);
float x = v.x; //1.0
float x1 = v.r; //1.0
float x2 = v[0]; //1.0
vec3 xyz = v.xyz; // vec3(1.0,2.0,3.0)
vec3 xyz1 = vec(v[0],v[1],v[2]); // vec3(1.0,2.0,3.0)
vec3 rgb = v.rgb; // vec3(1.0,2.0,3.0)
vec2 xyzw = v.xyzw; // vec4(1.0,2.0,3.0,1.0);
vec2 rgba = v.rgba; // vec4(1.0,2.0,3.0,1.0);
オペレーター:
優先度(小さいほど高い) | オペレーター | 説明する |
---|---|---|
1 | () | グループ化:a*(b+c) |
2 | [] () 。++ - - | 配列添字 []、メソッド パラメータ fun(arg1、arg2、arg3)、属性アクセス ab、自動インクリメント/デクリメント サフィックス a++ a- - |
3 | ++ – + – ! | 自動インクリメント/デクリメント接頭語 ++a --a、正符号と負符号 (通常、正符号は書きません) a、-a、否定! false |
4 | * / | 乗算と除算の数学的演算 |
5 | + - | 加算と減算の演算 |
6 | < > <= >= | 関係演算子 |
7 | == != | 等価演算子 |
8 | && | 論理積 |
9 | ^^ | 排他的論理和 (その有用性は基本的に != と同等です) |
10 | 論理和。入力できません。記号は二重の縦線です | |
11 | ? : | 三項演算子 |
12 | = += -= *= /= | 代入と複合代入 |
13 | 、 | 順次割り当て操作 |
基本タイプ間の操作:
glsl では、暗黙的な型変換はありません。原則として、glsl では、式の左辺と右辺 (l 値) と (r 値) の型が一貫している必要があります。つまり、次の式は間違っています。 :
int a = 2.0; // 错误,右侧 value为float 而 左侧 value 为int.
int a = 1.0 + 2;
float a = 2;
float a = 2.0 + 1;
bool a = 0;
vec3 a = vec3(1.0, 2.0, 3.0) * 2;
その他の考えられる状況
1. float と int:
Float と float、int と int は直接操作できますが、float と int は直接操作できません。明示的な変換を実行する必要があります。つまり、float を int に変換する: int(1.0)、または int を float に変換します: float ( 1
) 、次の式は正しいです。
int a = int(2.0);
float a = float(2);
int a = int(2.0)*2 + 1;
float a = float(2)*6.0+2.3;
2. float および vec (ベクトル) mat (行列):
これらのタイプの vec と mat は実際には float の複合体です. float で操作すると、実際には各コンポーネントで float で操作されます. これはいわゆるコンポーネントごとの操作です. glsl のほとんどは vec と mat を必要とします
.の演算はすべてコンポーネントごとの演算ですが、すべてではありません。特殊な場合については以下で説明します。コンポーネント
ごとの演算は線形です。つまり、vec と float の演算の結果は依然として vec です。 int と vec, matの関係は
vec と mat の各コンポーネントが float 型であるため操作できません int ではコンポーネントごとに計算できません 以下に float と vec, mat の演算のいくつかのケースを列挙し
ます。
vec3 a = vec3(1.0, 2.0, 3.0);
mat3 m = mat3(1.0);
float s = 10.0;
vec3 b = s * a; // vec3(10.0, 20.0, 30.0)
vec3 c = a * s; // vec3(10.0, 20.0, 30.0)
mat3 m2 = s * m; // = mat3(10.0)
mat3 m3 = m * s; // = mat3(10.0)
3. vec (ベクター) と vec (ベクター):
2 つのベクトル間の演算では、まずオペランドの順序が同じであることを確認する必要があります。そうでない場合、計算はできません。例: vec3*vec2 vec4+vec3 などは不可能です。その計算方法は、同じ位置にある 2 つのオペランドは
それぞれ 演算の本質はコンポーネントごとであり、
前述の float 型のコンポーネントごとの演算とは若干異なる場合があります。 vec と vec の間の演算は引き続き vec であり、順序は変わりません。
vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(0.1, 0.2, 0.3);
vec3 c = a + b; // vec3(1.1, 2.2, 3.3)
vec3 d = a * b; // vec3(0.1, 0.4, 0.9)
4. vec (ベクトル) と mat (行列):
オペランドの順序を同じにする必要があり、vec と mat の間の演算は乗算のみであり、
計算方法は線形代数の行列乗算と同じであり、成分ごとの演算ではありません。
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2., 3., 4.);
vec2 w = m * v; // vec2(1. * 10. + 3. * 20., 2. * 10. + 4. * 20.)
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2., 3., 4.);
vec2 w = v * m; // vec2(1. * 10. + 2. * 20., 3. * 10. + 4. * 20.)
ベクトルと行列の乗算規則は次のとおりです。
5. mat(行列) と mat(行列):
mat と mat の演算では、線形代数における行列の乗算である乗算を除き、残りの演算は成分ごとの演算です。つまり、乗算のみが特殊で、他は vec と同じです。 vec 演算と同様です。
mat2 a = mat2(1., 2., 3., 4.);
mat2 b = mat2(10., 20., 30., 40.);
mat2 c = a * b; // mat2(1.*10.+3.*20.,2.*10.+4.*20.,1.* 30.+3.*40.,2.* 30.+4.*40.);
mat2 d = a+b; // mat2(1.+10.,2.+20.,3.+30.,4.+40);
行列の乗算の規則は次のとおりです。
変数修飾子:
修飾子 | 説明する |
---|---|
なし | (デフォルトは省略可能) 読み書き可能なローカル変数、関数の入力パラメータはこのタイプです |
定数 | 変数または関数のパラメータを読み取り専用型として宣言します。 |
属性 | 頂点シェーダ内にのみ存在できます。通常、頂点または法線データを保存するために使用されます。データ バッファ内のデータを読み取ることができます。 |
ユニフォーム | シェーダは実行時にユニフォーム変数を変更できませんが、通常はプログラムによってシェーダに渡される変換マトリックス、マテリアル、照明パラメータなどを配置するために使用されます。 |
変化する | 主に頂点とフラグメントの間で変数を渡す役割を果たします。 |
定数:
C 言語と同様に、const 修飾子によって変更された変数は初期化後は不変です。ローカル変数に加えて、関数パラメータも const 修飾子を使用できます。ただし、構造体変数は const で変更できますが、フィールドは const で変更できることに注意してください。構造的にはできません。
const 変数は宣言時に初期化する必要があります const vec3 v3 = vec3(0.,0.,0.)
ローカル変数は const 修飾子のみ使用でき、
関数パラメータは const 修飾子のみ使用できます。
struct light {
vec4 color;
vec3 pos;
// const vec3 pos1; // 结构中的字段不可用const修饰会报错.
};
const light lgt = light(vec4(1.0), vec3(0.0)); // 结构变量可以用const修饰
属性:
属性変数はグローバルで読み取り専用です。頂点シェーダー内でのみ使用でき、浮動小数点数、ベクトル、または行列変数とのみ組み合わせることができます。一般に、属性変数はモデルの頂点、法線、色、テクスチャを配置するために使用され
ますなど、プログラムによって渡されます。データはデータ バッファーにアクセスできます
(関数 __gl.vertexAttribPointer__ を思い出してください)。
attribute vec4 a_Position;
ユニフォーム:
uniform变量是全局且只读的,在整个shader执行完毕前其值不会改变,他可以和任意基本类型变量组合,
一般我们使用uniform变量来放置外部程序传递来的环境数据(如点光源位置,模型的变换矩阵等等)
这些数据在运行中显然是不需要被改变的.
uniform vec4 lightPosition;
varying:
varying类型变量是 vertex shader 与 fragment shader 之间的信使,一般我们在 vertex shader 中修改它然后在fragment shader使用它,但不能在fragment shader中修改它.
// 顶点着色器
varying vec4 v_Color;
void main(){
v_Color = vec4(1.,1.,1.,1);
}
// 片元着色器
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
glsl的函数:
glsl允许在程序的最外部声明函数.函数不能嵌套,不能递归调用,且必须声明返回值类型(无返回值时声明为void) 在其他方面glsl函数与c函数非常类似.
vec4 getPosition(){
vec4 v4 = vec4(0.,0.,0.,1.);
return v4;
}
void doubleSize(inout float size){
size= size*2.0 ;
}
void main() {
float psize= 10.0;
doubleSize(psize);
gl_Position = getPosition();
gl_PointSize = psize;
}
构造函数:
glsl中变量可以在声明的时候初始化,float pSize = 10.0 也可以先声明然后等需要的时候在进行赋值.
聚合类型对象如(向量,矩阵,数组,结构) 需要使用其构造函数来进行初始化. vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
// 一般类型
float pSize = 10.0;
float pSize1;
pSize1 = 10.0;
// 复合类型
vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
vec4 color1;
color1 = vec4(0.0, 1.0, 0.0, 1.0);
// 结构
struct light {
float intensity;
vec3 position;
};
light lightVar = light(3.0, vec3(1.0, 2.0, 3.0)); // 数组
const float c[3] = float[3](5.0, 7.2, 1.1);
类型转换:
glsl可以使用构造函数进行显式类型转换,各值如下:
bool t= true;
bool f = false;
int a = int(t); // true转换为1或1.0
int a1 = int(f); // false转换为0或0.0
float b = float(t);
float b1 = float(f);
bool c = bool(0); // 0或0.0转换为false
bool c1 = bool(1); // 非0转换为true
bool d = bool(0.0);
bool d1 = bool(1.0);
精度限定:
glsl在进行光栅化着色的时候,会产生大量的浮点数运算,这些运算可能是当前设备所不能承受的,所以glsl提供了3种浮点数精度,我们可以根据不同的设备来使用合适的精度.
在变量前面加上 highp mediump lowp 即可完成对该变量的精度声明.
lowp float color;
varying mediump vec2 Coord;
lowp ivec2 foo(lowp mat3);
highp mat4 m;
我们一般在片元着色器(fragment shader)最开始的地方加上 precision mediump float; 便设定了默认的精度.这样所有没有显式表明精度的变量都会按照设定好的默认精度来处理.
如何确定精度:
变量的精度首先是由精度限定符决定的,如果没有精度限定符,则要寻找其右侧表达式中,已经确定精度的变量,一旦找到,那么整个表达式都将在该精度下运行.如果找到多个,
则选择精度较高的那种,如果一个都找不到,则使用默认或更大的精度类型.
uniform highp float h1;
highp float h2 = 2.3 * 4.7; //运算过程和结果都 是高精度
mediump float m;
m = 3.7 * h1 * h2; //运算过程 是高精度
h2 = m * h1; //运算过程 是高精度
m = h2 – h1; //运算过程 是高精度
h2 = m + m; //运算过程和结果都 是中等精度
void f(highp float p); // 形参 p 是高精度
f(3.3); //传入的 3.3是高精度
invariant关键字:
由于shader在编译时会进行一些内部优化,可能会导致同样的运算在不同shader里结果不一定精确相等.这会引起一些问题,尤其是vertx shader向fragmeng shader传值的时候.
所以我们需要使用invariant 关键字来显式要求计算结果必须精确一致. 当然我们也可使用 #pragma STDGL invariant(all)来命令所有输出变量必须精确一致,
但这样会限制编译器优化程度,降低性能.
#pragma STDGL invariant(all) //所有输出变量为 invariant
invariant varying texCoord; //varying在传递数据的时候声明为invariant
限定符的顺序:
当需要用到多个限定符的时候要遵循以下顺序:
1.在一般变量中: invariant > storage > precision
2.在参数中: storage > parameter > precision
我们来举例说明:
invariant varying lowp float color; // invariant > storage > precision
void doubleSize(const in lowp float s){ //storage > parameter > precision
float s1=s;
}
预编译指令:
以 # 开头的是预编译指令,常用的有:
#define #undef #if #ifdef #ifndef #else
#elif #endif #error #pragma #extension #version #line
比如 #version 100 他的意思是规定当前shader使用 GLSL ES 1.00标准进行编译,如果使用这条预编译指令,则他必须出现在程序的最开始位置.
内置的宏:
LINE : 当前源码中的行号.
VERSION : 一个整数,指示当前的glsl版本 比如 100 ps: 100 = v1.00
GL_ES : 如果当前是在 OPGL ES 环境中运行则 GL_ES 被设置成1,一般用来检查当前环境是不是 OPENGL ES.
GL_FRAGMENT_PRECISION_HIGH : 如果当前系统glsl的片元着色器支持高浮点精度,则设置为1.一般用于检查着色器精度.
实例:
1.如何通过判断系统环境,来选择合适的精度:
#ifdef GL_ES //
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
2.自定义宏:
#define NUM 100
#if NUM==100
#endif
内置的特殊变量
glsl程序使用一些特殊的内置变量与硬件进行沟通.他们大致分成两种
一种是 input类型,他负责向硬件(渲染管线)发送数据.
另一种是output类型,负责向程序回传数据,以便编程时需要.
在 vertex Shader 中:
output 类型的内置变量:
变量 | 说明 | 单位 |
---|---|---|
highp vec4 gl_Position; | gl_Position 放置顶点坐标信息 | vec4 |
mediump float gl_PointSize; | gl_PointSize 需要绘制点的大小,(只在gl.POINTS模式下有效) | float |
在 fragment Shader 中:
input 类型的内置变量:
变量 | 说明 | 单位 |
---|---|---|
mediump vec4 gl_FragCoord; | 片元在framebuffer画面的相对位置 | vec4 |
bool gl_FrontFacing; | 标志当前图元是不是正面图元的一部分 | bool |
mediump vec2 gl_PointCoord; | 经过插值计算后的纹理坐标,点的范围是0.0到1.0 | vec2 |
output 类型的内置变量:
变量 | 说明 | 单位 |
---|---|---|
mediump vec4 gl_FragColor; | 设置当前片点的颜色 | vec4 RGBA color |
mediump vec4 gl_FragData[n] | 设置当前片点的颜色,使用glDrawBuffers数据数组 | vec4 RGBA color |
流控制
glsl的流控制和c语言非常相似,这里不必再做过多说明,唯一不同的是片段着色器中有一种特殊的控制流discard.使用discard会退出片段着色器,不执行后面的片段着色操作。片段也不会写入帧缓冲区。
void main() {
vec4 diffuseColor = texture2D( map, vUv );
gl_FragColor = vec4( diffuseColor.xyz * HSLtoRGB(vec3(vScale/5.0, 1.0, 0.5)), diffuseColor.w );
if ( diffuseColor.w < 0.5 ) discard;
}
内置函数库
glsl提供了非常丰富的函数库,供我们使用,这些功能都是非常有用且会经常用到的. 这些函数按功能区分大改可以分成7类:
1、通用函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
方法 | 说明 |
---|---|
T abs(T x) | 返回x的绝对值 |
T sign(T x) | 比较x与0的值,大于,等于,小于 分别返回 1.0 ,0.0,-1.0 |
T floor(T x) | 返回<=x的最大整数 |
T ceil(T x) | 返回>=等于x的最小整数 |
T fract(T x) | 获取x的小数部分 |
T mod(T x, T y),T mod(T x, float y) | 取x,y的余数 |
T min(T x, T y),T min(T x, float y) | 取x,y的最小值 |
T max(T x, T y),T max(T x, float y) | 取x,y的最大值 |
T clamp(T x, T minVal, T maxVal),T clamp(T x, float minVal,float maxVal) | min(max(x, minVal), maxVal),返回值被限定在 minVal,maxVal之间 |
T mix(T x, T y, T a),T mix(T x, T y, float a) | 取x,y的线性混合,x*(1-a)+y*a |
T step(T edge, T x),T step(float edge, T x) | 如果 x<edge 返回 0.0 否则返回1.0 |
T smoothstep(T edge0, T edge1, T x),T smoothstep(float edge0,float edge1, T x) | 如果x<edge0 返回 0.0 如果x>edge1返回1.0, 否则返回Hermite插值 |
2、角度&三角函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
方法 | 说明 |
---|---|
T radians(T degrees) | 角度转弧度 |
T radians(T degrees) | 角度转弧度 |
T degrees(T radians) | 弧度转角度 |
T sin(T angle) | 正弦函数,角度是弧度 |
T cos(T angle) | 余弦函数,角度是弧度 |
T tan(T angle) | 正切函数,角度是弧度 |
T asin(T x) | 反正弦函数,返回值是弧度 |
T acos(T x) | 反余弦函数,返回值是弧度 |
T atan(T y, T x),T atan(T y_over_x) | 反正切函数,返回值是弧度 |
3、指数函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
方法 | 说明 |
---|---|
T pow(T x, T y) | 返回x的y次幂 xy |
T exp(T x) | 返回x的自然指数幂 ex |
T log(T x) | 返回x的自然对数 ln |
T exp2(T x) | 返回2的x次幂 2x |
T log2(T x) | 返回2为底的对数 log2 |
T sqrt(T x) | 开根号 √x |
T inversesqrt(T x) | 先开根号,在取倒数,就是 1/√x |
4、几何函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.
方法 | 说明 |
---|---|
float length(T x) | 返回矢量x的长度 |
float distance(T p0, T p1) | 返回p0 p1两点的距离 |
float dot(T x, T y) | 返回x y的点积 |
vec3 cross(vec3 x, vec3 y) | 返回x y的叉积 |
T normalize(T x) | 对x进行归一化,保持向量方向不变但长度变为1 |
T faceforward(T N, T I, T Nref) | 根据 矢量 N 与Nref 调整法向量 |
T reflect(T I, T N) | 返回 I - 2 * dot(N,I) * N, 结果是入射矢量 I 关于法向量N的 镜面反射矢量 |
T refract(T I, T N, float eta) | 返回入射矢量I关于法向量N的折射矢量,折射率为eta |
5、矩阵函数:
mat可以为任意类型矩阵.
方法 | 说明 |
---|---|
mat matrixCompMult(mat x, mat y) | 将矩阵 x 和 y的元素逐分量相乘 |
6、向量函数:
下文中的 类型 T可以是 vec2, vec3, vec4, 且可以逐分量操作.
bvec指的是由bool类型组成的一个向量:
vec3 v3= vec3(0.,0.,0.);
vec3 v3_1= vec3(1.,1.,1.);
bvec3 aa= lessThan(v3,v3_1); //bvec3(true,true,true)
方法 | 说明 |
---|---|
bvec lessThan(T x, T y) | 逐分量比较x < y,将结果写入bvec对应位置 |
bvec lessThanEqual(T x, T y) | 逐分量比较 x <= y,将结果写入bvec对应位置 |
bvec greaterThan(T x, T y) | 逐分量比较 x > y,将结果写入bvec对应位置 |
bvec greaterThanEqual(T x, T y) | 逐分量比较 x >= y,将结果写入bvec对应位置 |
bvec equal(T x, T y),bvec equal(bvec x, bvec y) | 逐分量比较 x == y,将结果写入bvec对应位置 |
bvec notEqual(T x, T y),bvec notEqual(bvec x, bvec y) | 逐分量比较 x!= y,将结果写入bvec对应位置 |
bool any(bvec x) | 如果x的任意一个分量是true,则结果为true |
bool all(bvec x) | 如果x的所有分量是true,则结果为true |
bvec not(bvec x) | bool矢量的逐分量取反 |
7、纹理查询函数:
图像纹理有两种 一种是平面2d纹理,另一种是盒纹理,针对不同的纹理类型有不同访问方法.
纹理查询的最终目的是从sampler中提取指定坐标的颜色信息. 函数中带有Cube字样的是指 需要传入盒状纹理. 带有Proj字样的是指带投影的版本.
以下函数只在vertex shader中可用:
vec4 texture2DLod(sampler2D sampler, vec2 coord, float lod);
vec4 texture2DProjLod(sampler2D sampler, vec3 coord, float lod);
vec4 texture2DProjLod(sampler2D sampler, vec4 coord, float lod);
vec4 textureCubeLod(samplerCube sampler, vec3 coord, float lod);
以下函数只在fragment shader中可用:
vec4 texture2D(sampler2D サンプラー、vec2 coord、float バイアス);
vec4 texture2DProj(sampler2D サンプラー、vec3 coord、float バイアス);
vec4 texture2DProj(sampler2D サンプラー、vec4 coord、float バイアス);
vec4 textureCube(samplerCube サンプラー、vec3 coord、float バイアス);
頂点シェーダーとフラグメント シェーダーの両方で使用できます。
vec4 texture2D(sampler2D サンプラー、vec2 coord);
vec4 texture2DProj(sampler2D サンプラー、vec3 coord);
vec4 texture2DProj(sampler2D サンプラー、vec4 coord);
vec4 textureCube(samplerCube サンプラー、vec3 coord);
公式シェーダーの例:
次のシェーダが一目で理解できれば、glsl 言語を基本的にマスターしたことになります。
頂点シェーダー:
uniform mat4 mvp_matrix; //透视矩阵 * 视图矩阵 * 模型变换矩阵
uniform mat3 normal_matrix; //法线变换矩阵(用于物体变换后法线跟着变换)
uniform vec3 ec_light_dir; //光照方向
attribute vec4 a_vertex; // 顶点坐标
attribute vec3 a_normal; //顶点法线
attribute vec2 a_texcoord; //纹理坐标
varying float v_diffuse; //法线与入射光的夹角
varying vec2 v_texcoord; //2d纹理坐标
void main(void) {
// 归一化法线
vec3 ec_normal = normalize(normal_matrix * a_normal);
// v_diffuse 是法线与光照的夹角.根据向量点乘法则,当两向量长度为1是 乘积即cosθ值
v_diffuse = max(dot(ec_light_dir, ec_normal), 0.0);
v_texcoord = a_texcoord;
gl_Position = mvp_matrix * a_vertex;
}
フラグメントシェーダ:
precision mediump float;
uniform sampler2D t_reflectance;
uniform vec4 i_ambient;
varying float v_diffuse;
varying vec2 v_texcoord;
void main (void) {
vec4 color = texture2D(t_reflectance, v_texcoord);
// 这里分解开来是 color*vec3(1,1,1)*v_diffuse + color*i_ambient
// 色*光*夹角cos + 色*环境光
gl_FragColor = color*(vec4(v_diffuse) + i_ambient);
}