這裏所說的標準 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;
不設置衰減的光線會致使光源越遠, 物體越亮的錯誤狀況, 增長一個衰減係數就能夠了