开个新坑,之前囫囵吞枣地过了一遍 202,现在忘得差不多了,该捡起来。正好给枯燥的生活增添一点色彩(?),最近比较忙但还是争取一周做一个作业


🚩 0x00 To begin with

这篇文章将会包含以下内容:

  • GAMES202 作业 0
  • 不包含作业框架分析

For reference👇:

master 分支上是 202 全部作业汇总,working 分支用于提交代码。需要原始作业可下载 master 分支的包


⚙️ 0x01 Configuration 环境配置

选择使用 VSCode 插件搭建本地服务器,在扩展中搜索 Live Server,安装此插件。

在编辑 Html 代码窗口按 F1(或者右键)选择 Live Server: Open with Live Server,随后会自动打开浏览器,导航至本地服务器端口,一切正常的话是下面这个效果:

可以使用鼠标和右上角的控制面板进行一些简单的交互。

如果遇到模型贴图加载不出来(只有 202 字样),可以选择多刷新几次。或者直接在加载脚本前预加载贴图,可以根治这个问题。

1<link rel="preload" href="assets/mary/MC003_Kozakura_Mari.png" as="image" type="image/png" crossorigin/>

🤔 0x02 Blinn-Phong Shading Model

没什么好说的,CV 工程师

使用 GLSL 编写 Shader

如果使用字符串的方式引入 Shader,就在 InternalShader.js 中加上:

 1// Phong Shader
 2
 3const PhongVertexShader = `
 4// attribute is vertex input data
 5attribute vec3 aVertexPosition;
 6attribute vec3 aNormalPosition;
 7attribute vec2 aTextureCoord;
 8
 9// uniform is consistent/global data between VS/PS in a drawcall
10uniform mat4 uModelViewMatrix;
11uniform mat4 uProjectionMatrix;
12
13// varying is data need to be interpolated from VS to PS
14varying highp vec2 vTextureCoord;
15varying highp vec3 vFragPos;
16varying highp vec3 vNormal;
17
18void main(void) {
19
20  vFragPos = aVertexPosition;
21  vNormal = aNormalPosition;
22  // MVP
23  gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
24
25  vTextureCoord = aTextureCoord;
26}
27`;
28
29const PhongFragmentShader = `
30#ifdef GL_ES
31precision mediump float;
32#endif
33uniform sampler2D uSampler;
34//binn
35uniform vec3 uKd;
36uniform vec3 uKs;
37uniform vec3 uLightPos;
38uniform vec3 uCameraPos;
39uniform float uLightIntensity;
40uniform int uTextureSample;
41  
42varying highp vec2 vTextureCoord;
43varying highp vec3 vFragPos;
44varying highp vec3 vNormal;
45
46void main(void) {
47  vec3 color;
48  if (uTextureSample == 1) {
49	  // Gamma correction
50	  color = pow(texture2D(uSampler, vTextureCoord).rgb, vec3(2.2));
51  } else {
52  // default color
53  color = uKd;
54  }
55
56  vec3 ambient = 0.05 * color;
57
58  // lambertian diffuse
59  vec3 lightDir = normalize(uLightPos- vFragPos);
60  vec3 normal = normalize(vNormal);
61  float diff = max(dot(lightDir, normal), 0.0);
62  float light_atten_coff = uLightIntensity / length(uLightPos- vFragPos);
63  vec3 diffuse = diff * light_atten_coff * color;
64
65  // Phong specular
66  vec3 viewDir = normalize(uCameraPos- vFragPos);
67  float spec = 0.0;
68  vec3 reflectDir = reflect(-lightDir, normal);
69  spec = pow (max(dot(viewDir, reflectDir), 0.0), 35.0);
70  vec3 specular = uKs * light_atten_coff * spec;
71
72  // Compose all components and do gamma reverse
73  gl_FragColor = vec4(pow((ambient + diffuse + specular), vec3(1.0/2.2)), 1.0);
74
75}
76`;

实现 PhongMaterial 类

在 src/materials/ 下新建 PhongMaterial.js,定义继承自 Material 的 PhongMaterial 类,传参,绑定 Shader。

super 调用父类构造

 1class PhongMaterial extends Material {
 2/**
 3 * Creates an instance of PhongMaterial.
 4 * @param {vec3f} color The material color
 5 * @param {Texture} colorMap The texture object of the material
 6 * @param {vec3f} specular The material specular coefficient
 7 * @param {float} intensity The light intensity
 8 * @memberof PhongMaterial
 9 */
10    constructor(color, colorMap, specular, intensity) {
11        let textureSample = 0;
12  
13        if (colorMap != null) {
14            textureSample = 1;
15        super({
16            'uTextureSample': { type: '1i', value: textureSample },
17            'uSampler': { type: 'texture', value: colorMap },
18            'uKd': { type: '3fv', value: color },
19            'uKs': { type: '3fv', value: specular },
20            'uLightIntensity': { type: '1f', value: intensity }
21        }, [], PhongVertexShader, PhongFragmentShader);
22        } else {
23        //console.log(color);
24        super({
25        'uTextureSample': { type: '1i', value: textureSample },
26
27        'uKd': { type: '3fv', value: color },
28        'uKs': { type: '3fv', value: specular },
29        'uLightIntensity': { type: '1f', value: intensity }
30        }, [], PhongVertexShader, PhongFragmentShader);
31        }
32
33    }
34}

导入 PhongMaterial.js

在 index.html 中,在导入 Material.js 之后导入 PhongMaterial.js。

1<script src="src/materials/Material.js" defer></script>
2<!-- Load phong material **after** Material -->
3<script src="src/materials/PhongMaterial.js" defer></script>

使用 PhongMaterial

src/loads/loadOBJ.js 中,删除原本创建默认材质的部分,改为使用 PhongMaterial 创建实例。

 1// // MARK: You can change the myMaterial object to your own Material instance
 2
 3// let textureSample = 0;
 4// let myMaterial;
 5// if (colorMap != null) {
 6// 	textureSample = 1;
 7// 	myMaterial = new Material({
 8// 		'uSampler': { type: 'texture', value: colorMap },
 9// 		'uTextureSample': { type: '1i', value: textureSample },
10// 		'uKd': { type: '3fv', value: mat.color.toArray() }
11// 	},[],VertexShader, FragmentShader);
12// }else{
13// 	myMaterial = new Material({
14// 		'uTextureSample': { type: '1i', value: textureSample },
15// 		'uKd': { type: '3fv', value: mat.color.toArray() }
16// 	},[],VertexShader, FragmentShader);
17// }
18							
19let myMaterial = new PhongMaterial( mat.color.toArray(), 
20									colorMap, 
21									mat.specular.toArray(),
22								renderer.lights[0].entity.mat.intensity)

🍪 Extra:从文件中加载 Shader

为了让这篇显得不那么水,补一下另一个从外部文件加载 Shader 的方法,在 src/shaders/phongShader 中已经给出了这两个着色器文件

更改 PhongMaterial 类

因为构造函数不支持异步,就把创建 Material 的逻辑放到一个函数中,在这个函数里调用 loadShader.js 中定义的 loadShaderFile 函数加载 shader 文件,等待加载完后再调用构造函数并返回新创建的实例:

 1// Load the shader files from file
 2class PhongMaterial extends Material {
 3    constructor(uniforms, vertexShader, fragmentShader) {
 4        super(uniforms, [], vertexShader, fragmentShader);
 5    }
 6
 7    /**
 8     * Creates an instance of PhongMaterial async.
 9     * @param {vec3f} color
10     * @param {Texture} colorMap
11     * @param {vec3f} specular
12     * @param {float} intensity
13     * @returns {Promise<PhongMaterial>}
14     */
15    static async create(color, colorMap, specular, intensity) {
16        const textureSample = colorMap ? 1 : 0;
17
18        const [vertexShader, fragmentShader] = await Promise.all([
19            loadShaderFile('src/shaders/phongShader/vertex.glsl'),
20            loadShaderFile('src/shaders/phongShader/fragment.glsl')
21        ]);
22        // console.log('Vertex Shader:', vertexShader.slice(0, 100));
23        // console.log('Fragment Shader:', fragmentShader.slice(0, 100));  
24        const uniforms = {
25            'uTextureSample': { type: '1i', value: textureSample },
26            'uKd': { type: '3fv', value: color },
27            'uKs': { type: '3fv', value: specular },
28            'uLightIntensity': { type: '1f', value: intensity }
29        };
30
31        if (colorMap) {
32            uniforms['uSampler'] = { type: 'texture', value: colorMap };
33        }
34
35        return new PhongMaterial(uniforms, vertexShader, fragmentShader);
36    }
37}

更改 loadOBJ.js

用 create 方法创建一个新材质实例,完成后进行渲染。

1PhongMaterial.create( mat.color.toArray(),
2					  colorMap,
3					  mat.specular.toArray(),
4					  renderer.lights[0].entity.mat.intensity)
5			 .then((myMaterial) => {
6				let meshRender = new MeshRender(renderer.gl, mesh, myMaterial);
7				renderer.addMesh(meshRender);
8			 })

📝 Afterwords

一切正常就是下面的效果。