如何把一个标准 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

相关文章
相关标签/搜索