如何把一个标准 GLSL 例程改写为 Codea shader

原创
2016/07/17 11:38
阅读数 82

如何把一个标准 GLSL 例程改写为 Codea shader

概述

这里所说的标准 GLSL 例程指的是 OpenGL ES 2.0/3.0 中使用 GLSL 编写, 由 顶点着色器片段着色器 组成的 shader. 在 <OpegGL ES 3.0 编程指南> 中有很多优秀的 shader 例程, 它们一般通过 C 程序 来调用, 我们现在希望把它们改写为 Codea shader, 也就是准备用 Lua 来调用, 以一个 法线贴图 的例程为例说明改写过程细节.

法线贴图例程

顶点着色器代码

下面是顶点着色器原始代码:

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个统一变量如下:

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

按照变量名的含义:

  • u_matViewInverse 应该是 viewMatrix 的逆矩阵;
  • u_matViewProjection 应该是 viewMatrixprojectionMatrix 的相乘;
  • u_lightPosition 应该是光源位置, 由我们自行设置;
  • u_eyePosition 应该是摄像机位置, 可以通过 camera 来设置.

视图矩阵的逆矩阵, 在书中描述是为了把 u_lightPosition u_eyePosition 从视图空间转换到世界空间, 那么视图矩阵就是从世界空间转换到视图空间了?

属性的对应设置

Codea 中, 有一些约定俗成的设置, 比如顶点的属性 attribute, 就有一些预先定义好的:

  • position 顶点坐标
  • texCoord 顶点的纹理坐标
  • color 顶点的颜色
  • normal 顶点的法线

在上面的顶点着色器代码中, 我们看到用了这么几个属性:

attribute vec4 a_vertex; 
attribute vec2 a_texcoord0; 
attribute vec3 a_normal; 
attribute vec3 a_binormal; 
attribute vec3 a_tangent;

很明显, 对应关系如下:

  • 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

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部