如何把一個標準 GLSL 例程改寫爲 Codea shader

如何把一個標準 GLSL 例程改寫爲 Codea shader

概述

這裏所說的標準 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.編程

咱們須要處理的就是找到 uniformattributeCodea 中的對應設置, 依次進行:函數

統一變量的對應設置

4個統一變量以下:spa

uniform mat4 u_matViewInverse;
uniform mat4 u_matViewProjection;
uniform vec3 u_lightPosition;
uniform vec3 u_eyePosition;

按照變量名的含義:.net

  • u_matViewInverse 應該是 viewMatrix 的逆矩陣;
  • u_matViewProjection 應該是 viewMatrixprojectionMatrix 的相乘;
  • 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_vertexposition 同樣;
  • a_texcoord0texCoord 同樣;
  • a_normalnormal 同樣.

還有兩個屬性 a_binormal 次法線a_tangent 切線, 在 Codea 中沒有對應的屬性, 就須要咱們本身計算了, 這部分能夠在 Lua 主程序中計算, 也能夠在 shader 中計算. 若是在 Lua 中計算, 那麼它們的聲明能夠保持不變, 若是在 shader 中計算, 就不能聲明爲 attribute 了.

本來計劃爲減小調試難度, 咱們決定先在 Lua 中計算, 確認調試經過了, 再改寫爲 shader 計算.

後來看到這篇教程 Mesh Deformers with the GLSL, 給出了在 shader 中根據 normal 屬性計算 binormaltangent 的算法, 因此咱們就直接引用一下:

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; 
}

這段代碼不須要任何修改, 直接使用就行

Lua 主程序

咱們用來加載 shaderLua 主程序以下:

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

問題

1 沒法改變距離

發現沒法經過設置 camera 函數的參數來改變視角, 找了半天, 發現是頂點着色器中這條語句的緣由;

gl_Position = u_matViewProjection * a_vertex;

它把視圖投影矩陣應用於頂點, 變換後獲得視圖投影, 而不是經常使用的模型視圖投影, 能夠將其修改成:

uniform mat4 modelViewProjection;

gl_Position = modelViewProjection * a_vertex;

這樣就能夠調節攝像機和物體之間的距離了.

2 上下座標錯位

具體來講就是點擊上方, 原本應該把光源放在上方, 結果下方出現高光, 說明上下座標錯位, 在咱們的屏幕上也就是 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;

3 光線沒有設置衰減

不設置衰減的光線會致使光源越遠, 物體越亮的錯誤狀況, 增長一個衰減係數就能夠了

參考

Mesh Deformers with the GLSL

相關文章
相關標籤/搜索