新建一个 xcode 项目,自定义 SLView 继承 UIView,在Main.storyboard 中把 View 的父类改为 SLView,然后在 SLView.m 实现代码。
SLView.m
#import "SLView.h"
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>
#import <GLKit/GLKit.h>
GLuint program;
EAGLContext * context;
@implementation SLView
- (void)layoutSubviews
{
[super layoutSubviews];
[self setUpGL];
}
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
-(void)setUpGL
{
//1.上下文初始化&设置当前上下文
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
BOOL isSetContextRight = [EAGLContext setCurrentContext:context];
if (!isSetContextRight)
{
NSLog(@"设置context失败");
}
//2.获取顶点着色器/图形片元着色器/光源片元着色
NSString * verStr = [[NSBundle mainBundle] pathForResource:@"Texture2D_Vert.glsl" ofType:nil];
NSString * fragStr = [[NSBundle mainBundle] pathForResource:@"Texture2D_Frag.glsl" ofType:nil];
//3.正方形箱子渲染program
program = createGLProgramFromFile(verStr.UTF8String, fragStr.UTF8String);
//4.使用program
glUseProgram(program);
//5.创建渲染缓存区(renderBuffer)
GLuint renderBuffer;
glGenRenderbuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
GLint wi,he;
//6.检索有关绑定缓冲区的对象的信息(width,height)
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &wi);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &he);
//7.创建深度缓冲区(depthBuffer)
GLuint depthBuffer;
glGenRenderbuffers(1, &depthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer);
/*
glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)
参数1:GL_RENDERBUFFER
参数2:渲染格式
参数3:width
参数4:height
*/
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, wi, he);
//8.将depthBuffer/renderBuffer ->frameBuffer
GLuint frameBuffer;
glGenFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
//一个立方体坐标信息(6个面,每个面2个三角形)
float vertices[] = {
//顶点位置 //法线 //纹理坐标
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
//设置VBO 将顶点坐标/法线坐标/纹理坐标
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void *)NULL);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void*)(3*sizeof(float)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void*)(6*sizeof(float)));
[self setUpTexture:@"tianqi"];
//添加到渲染循环
[self setUpLinker];
}
GLuint createGLProgramFromFile(const char * verPath,const char * fragPath)
{
char vBuffer[20480] = {0};
char fBuffer[20480] = {0};
if (getFileContent(vBuffer, sizeof(vBuffer), verPath) < 0)
{
return 0;
}
if (getFileContent(fBuffer, sizeof(fBuffer), fragPath) < 0)
{
return 0;
}
return createGLProgram(vBuffer, fBuffer);
}
long getFileContent(char * buffer, long len, const char * filePath)
{
FILE * file = fopen(filePath, "rb");
if (file == NULL)
{
return -1;
}
fseek(file, 0, SEEK_END);
long size = ftell(file);
rewind(file);
if (len < size)
{
NSLog(@"file is large than the size(%ld) you give\n", len);
return -1;
}
fread(buffer, 1, size, file);
buffer[size] = '\0';
fclose(file);
return size;
}
GLuint createGLProgram(const char * vertex, const char * frag)
{
/*
程序对象是一个容器对象,可以将着色器与之连接,并链接一个最终的可执行程序
*/
// 创建一个程序对象,返回程序对象的句柄
GLuint program = glCreateProgram();
//得到需要的着色器
GLuint vertShader = createGLShader(vertex, GL_VERTEX_SHADER);
GLuint fragShader = createGLShader(frag, GL_FRAGMENT_SHADER);
if (vertShader == 0 || fragShader == 0)
{
return 0;
}
//将程序对象和 着色器对象链接 //在ES 3.0中,每个程序对象 必须连接一个顶点着色器和片段着色器
//program程序对象句柄 shader着色器句柄
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
//链接程序对象 生成可执行程序(在着色器已完成编译 且程序对象连接了着色器)
//链接程序会检查各种对象的数量,和各种条件.
//在链接阶段就是生成最终硬件指令的时候
glLinkProgram(program);
//检查链接是否成功
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success)
{
GLint infoLen;
//使用 GL_INFO_LOG_LENGTH 表示获取信息日志
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1)
{
GLchar * infoLog = (GLchar *)malloc(sizeof(char) * infoLen);
if (infoLog)
{
//从信息日志中获取信息
glGetProgramInfoLog(program, infoLen, NULL, infoLog);
NSLog(@"%s",infoLog);
free(infoLog);
}
}
glDeleteShader(vertShader);
glDeleteShader(fragShader);
//删除程序对象
glDeleteProgram(program);
return 0;
}
/*
* 链接完着色器,生成可执行程序. 将着色器断开删除
*/
//断开指定程序对象和片段着色器
glDetachShader(program, vertShader);
glDetachShader(program, fragShader);
//将着色器标记为删除
glDeleteShader(vertShader);
glDeleteShader(fragShader);
return program;
}
GLuint createGLShader(const char * shaderText, GLenum shaderType)
{
//创建着色器,将根据传入的type参数 创建一个新的 顶点或片段着色器,返回值为新的着色器对象句柄
//GL_VERTEX_SHADER(顶点着色器) GL_FRAGMENT_SHADER(片段着色器)
GLuint shader = glCreateShader(shaderType);
//为着色器对象 提供着色器源代码.
//参数: shader --> 着色器对象句柄
// count --> 着色器源字符串数量
// string --> 字符串的数组指针
// length ---> 指向保存美工着色器字符串大小且元素数量为count的整数数组指针.如果length为NULL 着色器字符串将被认定为空.
glShaderSource(shader, 1, &shaderText, NULL);
//调用该方法,将指定的着色器源代码 进行编译
//参数shader 为着色器句柄
glCompileShader(shader);
//调用该方法获取 着色器源代码编译是否成功,并获取其他相关信息
//第二个参数 pname 表示要查询什么信息
/*
GL_COMPILE_STATUS ---> 是否编译成功 成功返回 GL_TRUE
GL_INFO_LOG_LENGTH ---> 查询源码编译后长度
GL_SHADER_SOURCE_LENGTH ---> 查询源码长度
GL_SHADER_TYPE ---> 查询着色器类型()
GL_DELETE_STATUS ---> 着色器是否被标记删除
*/
int compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled)
{
GLuint infoLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1)
{
char * infoLog = (char *)malloc(sizeof(char) * infoLen);
if (infoLog)
{
//检索信息日志
//参数: shader 着色器对象句柄
// maxLength 保存信息日志的缓冲区大小
// length 写入信息日志长度 ,不需要知道可传NULL
// infoLog 保存日志信息的指针
glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
NSLog(@"Error compiling shader: %s\n", infoLog);
free(infoLog);
}
}
//删除着色器对象, 参数shader为要删除的着色器对象的句柄
//若一个着色器链接到一个程序对象,那么该方法不会立刻删除着色器,而是将着色器标记为删除,当着色器不在连接到任何程序对象时,它的内存将被释放.
glDeleteShader(shader);
return 0;
}
return shader;
}
#pragma mark - 从图片中加载纹理
-(void)setUpTexture:(NSString *)fileName
{
//1、将 UIImage 转换为 CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
//判断图片是否获取成功
if (!spriteImage)
{
NSLog(@"fail load image %@",fileName);
return;
}
//2、读取图片的大小,宽和高
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
//3.获取图片字节数 宽*高*4(RGBA)
GLubyte * spriteData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
//4.创建上下文
/*
参数1:data,指向要渲染的绘制图像的内存地址
参数2:width,bitmap的宽度,单位为像素
参数3:height,bitmap的高度,单位为像素
参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
参数6:colorSpace,bitmap上使用的颜色空间 kCGImageAlphaPremultipliedLast:RGBA
*/
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
//5、在CGContextRef上--> 将图片绘制出来
/*
CGContextDrawImage 使用的是Core Graphics框架,坐标系与UIKit 不一样。UIKit框架的原点在屏幕的左上角,Core Graphics框架的原点在屏幕的左下角。
CGContextDrawImage
参数1:绘图上下文
参数2:rect坐标
参数3:绘制的图片
*/
CGRect rect = CGRectMake(0, 0, width, height);
//6.使用默认方式绘制
CGContextDrawImage(spriteContext, rect, spriteImage);
CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
CGContextDrawImage(spriteContext, rect, spriteImage);
//7、画图完毕就释放上下文
CGContextRelease(spriteContext);
//绑定纹理并设置纹理参数
GLuint texture;
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
free(spriteData);
glUniform1i(glGetUniformLocation(program, "Texture"), 0);
}
-(void)setUpLinker
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CADisplayLink * link = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)];
link.preferredFramesPerSecond = 24;
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
});
}
-(void)render
{
glEnable(GL_DEPTH_TEST);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
//光照颜色->片元/顶点着色器
glUniform3f(glGetUniformLocation(program, "lightColor"), 1.0f, 1.0f, 1.0f);
//投影矩阵->顶点着色器
float aspect = (float)self.bounds.size.width / (float)self.bounds.size.height;
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathRadiansToDegrees(45.0f), aspect, 0.1f, 800.0f);
glUniformMatrix4fv(glGetUniformLocation(program, "projection"), 1, GL_FALSE, (GLfloat *)projectionMatrix.m);
//模型视图矩阵-->顶点着色器
float radius = 10.0f;
float camX = sin(CACurrentMediaTime()) * radius;
float camZ = cos(CACurrentMediaTime()) * radius;
GLKVector3 viewPo = {camX, camX, camZ};
//获取世界坐标系去模型矩阵中.
/*
LKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ)
等价于 OpenGL 中
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz);
目的:根据你的设置返回一个4x4矩阵变换的世界坐标系坐标。
参数1:眼睛位置的x坐标
参数2:眼睛位置的y坐标
参数3:眼睛位置的z坐标
第一组:就是脑袋的位置
参数4:正在观察的点的X坐标
参数5:正在观察的点的Y坐标
参数6:正在观察的点的Z坐标
第二组:就是眼睛所看物体的位置
参数7:摄像机上向量的x坐标
参数8:摄像机上向量的y坐标
参数9:摄像机上向量的z坐标
第三组:就是头顶朝向的方向(因为你可以头歪着的状态看物体)
*/
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeLookAt(camX, camX, camZ, 0, 0, 0, 0, 1, 0);
modelViewMatrix = GLKMatrix4Scale(modelViewMatrix, 5.0f, 5.0f, 5.0f);
glUniformMatrix4fv(glGetUniformLocation(program, "view"), 1, GL_FALSE, (GLfloat *)modelViewMatrix.m);
//光源位置
glUniform3f(glGetUniformLocation(program, "lightPo"),1.2f, 1.0f, 2.0f);
//观察者位置
glUniform3f(glGetUniformLocation(program, "viewPo"), viewPo.x, viewPo.y, viewPo.z);
glDrawArrays(GL_TRIANGLES, 0, 36);
[context presentRenderbuffer:GL_RENDERBUFFER];
}
@end
创建两个文件 Texture2D_Vert.glsl 和 Texture2D_Frag.glsl,Texture2D_Vert.glsl 为顶点着色器,Texture2D_Frag.glsl 为片元着色器。然后分别在其中编写 GLSL 代码。
Texture2D_Vert.glsl
#version 300 es
layout(location = 0) in vec3 position; //顶点
layout(location = 1) in vec3 normal; //法向量
layout(location = 2) in vec2 textureCoord; //纹理坐标
uniform mat4 view;
uniform mat4 projection;
out vec3 fragPo; //顶点在世界坐标位置
out vec3 outNormal; //法向量
out vec2 outTextureCoord; //纹理坐标
void main()
{
fragPo = position;
outNormal = normal;
outTextureCoord = textureCoord;
gl_Position = projection * view * vec4(position,1.0f);
}
Texture2D_Frag.glsl
#version 300 es
precision mediump float;
out vec4 FragColor;
uniform vec3 lightColor; //光源颜色
uniform vec3 lightPo; //光源位置
uniform vec3 viewPo; //视角位置
uniform sampler2D Texture; //物体纹理
in vec3 fragPo; //顶点坐标
in vec3 outNormal; //顶点法向量
in vec2 outTextureCoord; //纹理坐标
//点光源版本
void pointLight()
{
float ambientStrength = 0.3; //环境因子
float specularStrength = 2.0; //镜面强度
float reflectance = 256.0; //反射率
float constantPara = 1.0f; //距离衰减常量
float linearPara = 0.09f; //线性衰减常量
float quadraticPara = 0.032f; //二次衰减常量
//环境光 = 环境因子 * 物体的材质颜色
vec3 ambient = ambientStrength * texture(Texture,outTextureCoord).rgb;
//漫反射
vec3 norm = normalize(outNormal);
//当前顶点 至 光源的的单位向量
vec3 lightDir = normalize(lightPo - fragPo);
//DiffuseFactor=光源与法线夹角 max(0,dot(N,L))
float diff = max(dot(norm,lightDir),0.0);
//漫反射光颜色计算 = 光源的漫反射颜色 * 物体的漫发射材质颜色 * DiffuseFactor
vec3 diffuse = diff * lightColor*texture(Texture,outTextureCoord).rgb;
//镜面反射
vec3 viewDir = normalize(viewPo - fragPo);
// reflect (genType I, genType N),返回反射向量
vec3 reflectDir = reflect(-lightDir,outNormal);
//SpecularFactor = power(max(0,dot(N,H)),shininess)
float spec = pow(max(dot(viewDir, reflectDir),0.0),reflectance);
//镜面反射颜色 = 光源的镜面光的颜色 * 物体的镜面材质颜色 * SpecularFactor
vec3 specular = specularStrength * spec * texture(Texture,outTextureCoord).rgb;
//衰减因子计算
float LFDistance = length(lightPo - fragPo);
//衰减因子 = 1.0/(距离衰减常量 + 线性衰减常量 * 距离 + 二次衰减常量 * 距离的平方)
float lightWeakPara = 1.0/(constantPara + linearPara * LFDistance + quadraticPara * (LFDistance*LFDistance));
//光照颜色 =(环境颜色 + 漫反射颜色 + 镜面反射颜色)* 衰减因子
vec3 res = (ambient + diffuse + specular)*lightWeakPara;
//最终输出的颜色
FragColor = vec4(res,1.0);
}
// 平行光版本
void parallelLight(){
float ambientStrength = 0.3; //环境因子
float specularStrength = 2.0; //镜面强度
float reflectance = 256.0; //反射率
//平行光方向
vec3 paraLightDir = normalize(vec3(-0.2,-1.0,-0.3));
//环境光 = 环境因子 * 物体的材质颜色
vec3 ambient = ambientStrength * texture(Texture,outTextureCoord).rgb;
//漫反射
vec3 norm = normalize(outNormal);
//当前顶点至光源的的单位向量
vec3 lightDir = normalize(lightPo - fragPo);
//DiffuseFactor=光源与paraLightDir 平行光夹角 max(0,dot(N,L))
float diff = max(dot(norm,paraLightDir),0.0);
//漫反射光颜色计算 = 光源的漫反射颜色 * 物体的漫发射材质颜色 * DiffuseFactor
vec3 diffuse = diff * lightColor * texture(Texture,outTextureCoord).rgb;
//镜面反射
vec3 viewDir = normalize(viewPo - fragPo);
// reflect (genType I, genType N),返回反射向量 -paraLightDir平行光
vec3 reflectDir = reflect(-paraLightDir,outNormal);
//SpecularFactor = power(max(0,dot(N,H)),shininess)
float spec = pow(max(dot(viewDir, reflectDir),0.0),reflectance);
//镜面反射颜色 = 光源的镜面光的颜色 * 物体的镜面材质颜色 * SpecularFactor
vec3 specular = specularStrength * spec * texture(Texture,outTextureCoord).rgb;
//距离衰减常量
float constantPara = 1.0f;
//线性衰减常量
float linearPara = 0.09f;
//二次衰减常量
float quadraticPara = 0.032f;
//衰减因子计算
float LFDistance = length(lightPo - fragPo);
//衰减因子 = 1.0/(距离衰减常量 + 线性衰减常量 * 距离 + 二次衰减常量 * 距离的平方)
float lightWeakPara = 1.0/(constantPara + linearPara * LFDistance + quadraticPara * (LFDistance*LFDistance));
//光照颜色 =(环境颜色 + 漫反射颜色 + 镜面反射颜色)* 衰减因子
vec3 res = (ambient + diffuse + specular)*lightWeakPara;
//最终输出的颜色
FragColor = vec4(res,1.0);
}
//聚光版本
void Spotlight(){
float ambientStrength = 0.3; //环境因子
float specularStrength = 2.0; //镜面强度
float reflectance = 256.0; //反射率
//环境光 = 环境因子 * 物体的材质颜色
vec3 ambient = ambientStrength * texture(Texture,outTextureCoord).rgb;
//漫反射
vec3 norm = normalize(outNormal);
vec3 lightDir = normalize(lightPo - fragPo); //当前顶点 至 光源的的单位向量
//DiffuseFactor=光源与paraLightDir lightDir夹角 max(0,dot(N,L))
float diff = max(dot(norm,lightDir),0.0); //光源与法线夹角
//漫反射光颜色计算 = 光源的漫反射颜色 * 物体的漫发射材质颜色 * DiffuseFactor
vec3 diffuse = diff * lightColor*texture(Texture,outTextureCoord).rgb;
//镜面反射
vec3 viewDir = normalize(viewPo - fragPo);
// reflect (genType I, genType N),返回反射向量
vec3 reflectDir = reflect(-lightDir,outNormal);
//SpecularFactor = power(max(0,dot(N,H)),shininess)
float spec = pow(max(dot(viewDir, reflectDir),0.0),reflectance);
//镜面反射颜色 = 光源的镜面光的颜色 * 物体的镜面材质颜色 * SpecularFactor
vec3 specular = specularStrength * spec * texture(Texture,outTextureCoord).rgb;
float constantPara = 1.0f; //距离衰减常量
float linearPara = 0.09f; //线性衰减常量
float quadraticPara = 0.032f; //二次衰减常量
//衰减因子计算
float LFDistance = length(lightPo - fragPo);
//衰减因子 = 1.0/(距离衰减常量 + 线性衰减常量 * 距离 + 二次衰减常量 * 距离的平方)
float lightWeakPara = 1.0/(constantPara + linearPara * LFDistance + quadraticPara * (LFDistance*LFDistance));
//聚光灯切角 (一些复杂的计算操作 应该让CPU做,提高效率,不变的量也建议外部传输,避免重复计算)
float inCutOff = cos(radians(10.0f));
float outCutOff = cos(radians(15.0f));
vec3 spotDir = vec3(-1.2f,-1.0f,-2.0f);
//聚光灯因子 = clamp((外环的聚光灯角度cos值 - 当前顶点的聚光灯角度cos值)/(外环的聚光灯角度cos值- 内环聚光灯的角度的cos值),0,1);
float theta = dot(lightDir,normalize(-spotDir));
//(外环的聚光灯角度cos值- 内环聚光灯的角度的cos值)
float epsilon = inCutOff - outCutOff;
//(外环的聚光灯角度cos值 - 当前顶点的聚光灯角度cos值) / (外环的聚光灯角度cos值- 内环聚光灯的角度的cos值)
float intensity = clamp((theta - outCutOff)/epsilon,0.0,1.0);
vec3 res = (ambient + diffuse + specular)*intensity*lightWeakPara;
FragColor = vec4(res,1.0);
}
void main()
{
//点光源版本
pointLight();
//平行光版本
parallelLight();
//聚光版本
Spotlight();
}
实现了点光源、平行光、聚光三种不同的光照效果,如下: