这里所说的标准 GLSL
例程指的是 OpenGL ES 2.0/3.0
中使用 GLSL
编写, 由 顶点着色器
和 片断着色器
组成的 shader
. 在 <OpegGL ES 3.0 编程指南>
中有不少优秀的 shader
例程, 它们通常经过 C 程序
来调用, 咱们如今但愿把它们改写为 Codea shader
, 也就是准备用 Lua
来调用, 以一个 法线贴图
的例程为例说明改写过程细节.php
下面是顶点着色器原始代码:算法
uniform mat4 u_matViewInverse; uniform mat4 u_matViewProjection; uniform vec3 u_lightPosition; uniform vec3 u_eyePosition; varying vec2 v_texcoord varying vec3 v_viewDirection varying vec3 v_lightDirection attribute vec4 a_vertex; attribute vec2 a_texcoord0; attribute vec3 a_normal; attribute vec3 a_binormal; attribute vec3 a_tangent; void main(void) { // Transform eye vector into world space vec3 eyePositionWorld = (u_matViewInverse * vec4(u_eyePosition, 1.0)).xyz; // Compute world space direction vector vec3 viewDirectionWorld = eyePositionWorld - a_vertex.xyz; // Transform light position into world space vec3 lightPositionWorld = (u_matViewInverse * vec4(u_lightPosition, 1.0)).xyz; // Compute world space light direction vector vec3 lightDirectionWorld = lightPositionWorld - a_vertex.xyz; // Create the tangent matrix mat3 tangentMat = mat3(a_tangent, a_binormal, a_normal); // Transform the view and light vectors into tangent space v_viewDirection = viewDirectionWorld * tangentMat; v_lightDirection = lightDirectionWorld * tangentMat; // Transform output position gl_Position = u_matViewProjection * a_vertex; // Pass through texture coordinate v_texcoord = a_texcoord0.xy; }
咱们按照代码顺序来一行行地进行转换, 首先变量声明, 有 4
个统一变量 uniform
, 有 3
传给片断着色器的变量 varying
, 有 5
个来自主程序的顶点属性 attribute
.编程
咱们须要处理的就是找到 uniform
和 attribute
在 Codea
中的对应设置, 依次进行:函数
4个统一变量以下:spa
uniform mat4 u_matViewInverse; uniform mat4 u_matViewProjection; uniform vec3 u_lightPosition; uniform vec3 u_eyePosition;
按照变量名的含义:.net
u_matViewInverse
应该是 viewMatrix
的逆矩阵;u_matViewProjection
应该是 viewMatrix
和 projectionMatrix
的相乘;u_lightPosition
应该是光源位置, 由咱们自行设置;u_eyePosition
应该是摄像机位置, 能够经过 camera
来设置.视图矩阵的逆矩阵, 在书中描述是为了把 u_lightPosition
和 u_eyePosition
从视图空间转换到世界空间, 那么视图矩阵就是从世界空间转换到视图空间了?3d
在 Codea
中, 有一些约定俗成的设置, 好比顶点的属性 attribute
, 就有一些预先定义好的:调试
position
顶点坐标texCoord
顶点的纹理坐标color
顶点的颜色normal
顶点的法线在上面的顶点着色器代码中, 咱们看到用了这么几个属性:code
attribute vec4 a_vertex; attribute vec2 a_texcoord0; attribute vec3 a_normal; attribute vec3 a_binormal; attribute vec3 a_tangent;
很明显, 对应关系以下:orm
a_vertex
跟 position
同样;a_texcoord0
跟 texCoord
同样;a_normal
跟 normal
同样.还有两个属性 a_binormal 次法线
和 a_tangent 切线
, 在 Codea
中没有对应的属性, 就须要咱们本身计算了, 这部分能够在 Lua
主程序中计算, 也能够在 shader
中计算. 若是在 Lua
中计算, 那么它们的声明能够保持不变, 若是在 shader
中计算, 就不能声明为 attribute
了.
本来计划为减小调试难度, 咱们决定先在 Lua
中计算, 确认调试经过了, 再改写为 shader
计算.
后来看到这篇教程 Mesh Deformers with the GLSL, 给出了在 shader
中根据 normal
属性计算 binormal
和 tangent
的算法, 因此咱们就直接引用一下:
vec3 tangent; vec3 binormal; vec3 c1 = cross(gl_Normal, vec3(0.0, 0.0, 1.0)); vec3 c2 = cross(gl_Normal, vec3(0.0, 1.0, 0.0)); if(length(c1)>length(c2)) { tangent = c1; } else { tangent = c2; } // 归一化切线 tangent = normalize(tangent); binormal = cross(gl_Normal, tangent); binormal = normalize(binormal);
因此, 最终获得的顶点着色器代码为:
uniform mat4 u_matViewInverse; uniform mat4 u_matViewProjection; uniform vec3 u_lightPosition; uniform vec3 u_eyePosition; varying vec2 v_texcoord varying vec3 v_viewDirection varying vec3 v_lightDirection attribute vec4 a_vertex; attribute vec2 a_texcoord0; attribute vec3 a_normal; // attribute vec3 a_binormal; // attribute vec3 a_tangent; void main(void) { // 先根据 normal 计算 binormal 和 tangent vec3 tangent; vec3 binormal; vec3 c1 = cross(normal, vec3(0.0, 0.0, 1.0)); vec3 c2 = cross(normal, vec3(0.0, 1.0, 0.0)); if(length(c1)>length(c2)) { tangent = c1; } else { tangent = c2; } // 归一化切线和次法线 tangent = normalize(tangent); binormal = cross(normal, tangent); binormal = normalize(binormal); // 坐标空间转换: 摄像机坐标 Transform eye vector into world space vec3 eyePositionWorld = (u_matViewInverse * vec4(u_eyePosition, 1.0)).xyz; // Compute world space direction vector vec3 viewDirectionWorld = eyePositionWorld - a_vertex.xyz; // Transform light position into world space vec3 lightPositionWorld = (u_matViewInverse * vec4(u_lightPosition, 1.0)).xyz; // Compute world space light direction vector vec3 lightDirectionWorld = lightPositionWorld - a_vertex.xyz; // Create the tangent matrix mat3 tangentMat = mat3(a_tangent, a_binormal, a_normal); // Transform the view and light vectors into tangent space v_viewDirection = viewDirectionWorld * tangentMat; v_lightDirection = lightDirectionWorld * tangentMat; // Transform output position gl_Position = u_matViewProjection * a_vertex; // Pass through texture coordinate v_texcoord = a_texcoord0.xy; }
下面是片断着色器原始代码:
precision mediump float; uniform vec4 u_ambient; uniform vec4 u_specular; uniform vec4 u_diffuse; uniform float u_specularPower; uniform sampler2D s_baseMap; uniform sampler2D s_bumpMap; varying vec2 v_texcoord; varying vec3 v_viewDirection; varying vec3 v_lightDirection; void main(void) { // Fetch basemap color vec4 baseColor = texture2D(s_baseMap, v_texcoord); // Fetch the tangent-space normal from normal map vec3 normal = texture2D(s_bumpMap, v_texcoord).xyz; // Scale and bias from [0, 1] to [-1, 1] and normalize normal = normalize(normal * 2.0 - 1.0); // Normalize the light direction and view direction vec3 lightDirection = normalize(v_lightDirection); vec3 viewDirection = normalize(v_viewDirection); // Compute N.L float nDotL = dot(normal, lightDirection); // Compute reflection vector vec3 reflection = (2.0 * normal * nDotL) - lightDirection; // Compute R.V float rDotV = max(0.0, dot(reflection, viewDirection)); // Compute Ambient term vec4 ambient = u_ambient * baseColor; // Compute Diffuse term vec4 diffuse = u_diffuse * nDotL * baseColor; // Compute Specular term vec4 specular = u_specular * pow(rDotV, u_specularPower); // Output final color gl_FragColor = ambient + diffuse + specular; }
这段代码不须要任何修改, 直接使用就行
咱们用来加载 shader
的 Lua
主程序以下:
function setup() print("normal 3D") tchx=0 tchy=0 createMesh() cam = vec3(0, 0, 1000) obj = vec3(0, 0, 0) light = vec3(tchx, tchy, 0.075) end function draw() perspective(50, WIDTH/HEIGHT) light = vec3(tchx, tchy, 0.0075) setShaderParam() camera(cam.x, cam.y, cam.z, obj.x, obj.y, obj.z) m:draw() end function createMesh() m = mesh() local w,h = WIDTH,HEIGHT local img1, img2 = readImage("Dropbox:n1"), readImage("Dropbox:n2") m:addRect(w/2,h/2,w,h) -- m:addRect(0,0,w,h) ---[[ m.shader = shader(shader2.vs,shader2.fs) m.shader.u_matViewInverse = viewMatrix():inverse() m.shader.u_matViewProjection = viewMatrix() * projectionMatrix() m.shader.s_baseMap = img1 m.shader.s_bumpMap = img2 --]] --[[ m.shader.u_lightPosition = light or vec3(100, 100, 100) m.shader.u_eyePosition = cam or vec3(100, 100, 100) -- ambient/specular/diffuse 环境光,反射光,散射 m.shader.u_ambient = vec4(0.15, 0.15, 0.15, 0.8) m.shader.u_specular = vec4(0.05, 0.05, 0.05, 0.8) m.shader.u_diffuse = vec4(0.105, 0.005, 0.005, 0.8) m.shader.u_specularPower = 0.09 --]] -- m.texture = img1 end function setShaderParam() m.shader.u_lightPosition = light or vec3(100, 100, 100) m.shader.u_eyePosition = cam or vec3(100, 100, 100) -- ambient/specular/diffuse 环境光,反射光,散射 m.shader.u_ambient = vec4(0.37,0.37,0.37,1.0) m.shader.u_specular = vec4(0.5,0.5,0.5,1.0) m.shader.u_diffuse = vec4(0.88,0.88,0.88,1.0) m.shader.u_specularPower = .05 end function touched(touch) if touch.state == BEGAN or touch.state == MOVING then tchx=touch.x+50 tchy=touch.y+50 end end
发现没法经过设置 camera
函数的参数来改变视角, 找了半天, 发现是顶点着色器中这条语句的缘由;
gl_Position = u_matViewProjection * a_vertex;
它把视图投影矩阵应用于顶点, 变换后获得视图投影, 而不是经常使用的模型视图投影, 能够将其修改成:
uniform mat4 modelViewProjection; gl_Position = modelViewProjection * a_vertex;
这样就能够调节摄像机和物体之间的距离了.
具体来讲就是点击上方, 原本应该把光源放在上方, 结果下方出现高光, 说明上下坐标错位, 在咱们的屏幕上也就是 y
轴坐标错位, 这一点多是由于 Codea
使用了不一样手系的坐标致使, 能够在顶点着色器中经过乘一个以下的矩阵来调整:
mat4 mm = mat4( 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0. 0.0, 0.0, 0.0, 1.0); gl_Position = mm * modelViewProjection * a_vertex;
不设置衰减的光线会致使光源越远, 物体越亮的错误状况, 增长一个衰减系数就能够了