当前位置: 首页 > news >正文

OpenGL ES 设置光效效果

文章目录

    • 阴影模型
    • 光效三要素
    • 开启光效
    • 设置环境光
    • 设置漫反射光
    • 设置高光
    • 设置光源位置
    • 光源的方向
    • 聚光截止角 (Spot Cutoff Angle):
    • 顶点法线
    • 完整代码

上一篇文章记录了如何绘制3D图形以及对应视口,这篇文章在此基础上,为绘制3D效果图增加光效效果

阴影模型

GL_FLAT(恒定)、GL_SMOOTH(光滑)

在 OpenGL ES 中,GL_FLAT 和 GL_SMOOTH 是两种基本的着色模式,它们定义了如何在多边形表面上插值颜色。这两种模式在不同的渲染管线阶段工作,会产生截然不同的视觉效果。

  • GL_FLAT(平面着色):

每个多边形只使用一个颜色值
颜色值取自多边形的最后一个顶点
不进行颜色插值
产生块状、不连续的视觉效果
计算开销较低

  • GL_SMOOTH(平滑着色):

在多边形表面进行颜色插值
基于每个顶点的颜色计算
产生连续、平滑的视觉效果
计算开销较高

  • 视觉效果对比
    使用 GL_FLAT 时,每个多边形显示为单一颜色,边界清晰可见,常用于风格化渲染或需要明显区分不同多边形的场景。而 GL_SMOOTH 会在多边形表面平滑过渡颜色,适合表现圆润的物体和自然光照效果。

OpenGL ES默认采用了平滑着色的方式 ,对比上一篇绘制正方形的样式

gl.glShadeModel(GL10.GL_SMOOTH)

在这里插入图片描述

下面采用了平面着色绘制后的效果。

gl.glShadeModel(GL10.GL_FLAT)

在这里插入图片描述
因为每个面采用两个正方形绘制的,所以采用平滑绘制后,每个三角形颜色互不相同。

光效三要素

环境元素(ambient component)
散射元素(diffuse component)
高光元素(specular component)

开启光效

gl.glEnable(GL10.GL_LIGHTING)

如果仅仅是开启光效,效果如下,颜色全部没有了,变成了一个黑色的正方体。当未开启光效时OpenGL有默认的光效效果。开启以后需要自己设置光源等设置。
在这里插入图片描述

设置环境光

 		val ambientLight = floatArrayOf(0.2f, 0.2f, 0.2f, 1.0f)gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, ambientLight)
  • 设置光源氏通过上述方法设置的,其第一个参数如下:

GL10.GL_LIGHT0
这是第一个参数,表示要配置的光源编号。OpenGL ES 最多支持 8 个光源(GL_LIGHT0 到 GL_LIGHT7)。这里选择的是 0 号光源。

  • 第二个参数如下:
    指定要设置的光源属性类型。GL_AMBIENT 表示环境光属性。环境光是一种全局光照,均匀照亮场景中的所有物体,没有特定方向。除了GL_AMBIENT,还可以设置下面的参数。
参数含义
GL_AMBIENT表示环境光属性
GL_DIFFUSE漫反射光属性,产生定向照明效果
GL_SPECULAR镜面反射光属性,产生高光效果
GL_POSITION光源位置,用于定位点光源、聚光灯等
GL_SPOT_DIRECTION聚光灯方向
GL_SPOT_EXPONENT聚光指数,控制聚光的集中程度
GL_SPOT_CUTOFF聚光截止角度
GL_CONSTANT_ATTENUATION常量衰减因子
GL_LINEAR_ATTENUATION线性衰减因子
GL_QUADRATIC_ATTENUATION二次方衰减因子

环境光模拟的是光线在环境中多次反射后形成的均匀光照,例如室内墙壁反射的光线、室外大气散射的自然光。这种光没有明确的光源方向,会均匀地从所有方向照射到物体上。通常环境光设置的比较小

设置漫反射光

		 // 设置漫反射光val diffuseLight = floatArrayOf(0.8f, 0.8f, 0.8f, 1.0f)gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, diffuseLight, 0)

当光线照射到粗糙表面(如纸张、岩石、布料)时,会因表面微观结构的不规则性向各个方向散射,这种反射称为漫反射。
特点:无论观察者从哪个角度看,漫反射光的强度基本一致,表面呈现均匀的明暗效果(如磨砂玻璃、粉笔字的反光)。

  • diffuseLight:漫反射光的颜色和强度,格式为[R, G, B, A]
    若将diffuseLight设为(0, 0, 0, 1),物体将失去主要光照,仅依赖环境光和镜面反射光,可能显得过暗;
    若设为(1, 1, 1, 1),漫反射光过强,可能导致物体过亮,失去明暗层次。

      漫反射光通常是光照模型中的 “主力”,强度应高于环境光(如环境光设为 0.2,漫反射设为 0.8),以突出光源方向的影响。强光场景(如室外正午):漫反射光可设为 0.9-1.0,模拟太阳直射;弱光场景(如室内灯光):设为 0.5-0.7,避免物体过亮。金属材质:漫反射系数较低(如 0.3-0.5),镜面反射更强;布料材质:漫反射系数较高(0.6-0.8),镜面反射较弱。
    

漫反射光是计算机图形学中构建真实感的关键要素,它通过模拟光线在粗糙表面的散射现象,为物体赋予立体形态和固有色表现。在代码中合理设置漫反射光的强度和颜色,能显著提升场景的视觉真实度,而其与环境光、镜面反射光的配合,则构成了经典光照模型的基础框架。

设置高光

	// 启用光照和高光gl.glEnable(GL10.GL_LIGHTING);gl.glEnable(GL10.GL_LIGHT0);gl.glEnable(GL10.GL_SPECULAR); // 启用高光反射// 设置高光val specularLight = floatArrayOf(1.0f, 1.0f, 1.0f, 1.0f)gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, specularLight, 0)

在 OpenGL ES 中,高光(Specular Highlights) 是模拟光滑物体表面反射强光的视觉效果,用于表现物体的光泽度和材质特性。以下从物理原理、计算模型、代码实现及应用技巧四个方面详细解析:

  • 物理原理
    当光线照射到光滑表面(如金属、玻璃、水面)时,会遵循反射定律(入射角等于反射角),在特定方向形成明亮的反射光斑,这就是高光。
    对比漫反射:漫反射向各个方向均匀散射光线,而高光仅在反射方向附近可见,且强度随观察角度变化。
  • 高光与材质的关系
    金属材质:高光强烈且集中(如不锈钢的高光几乎为白色);
    非金属材质(如塑料、陶瓷):高光较柔和,颜色接近光源色;
    粗糙表面(如木材、布料):几乎无明显高光。

高光是实现真实感渲染的关键要素,它通过模拟光线在光滑表面的镜面反射,为物体赋予材质特性(如金属光泽、玻璃反光)和动态视觉效果。合理调整高光参数(颜色、强度、指数)能显著提升模型的真实度,使其在不同光照条件下呈现出符合物理规律的视觉表现。

设置光源位置

        // 设置光源位置val lightPosition = floatArrayOf(0.0f, 0.0f, -1.0f, 0.0f) // 方向光gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosition, 0)
/*** 设置光源的位置或方向* @param light 光源编号(如 GL10.GL_LIGHT0 表示第一个光源)* @param pname 参数名称(GL10.GL_POSITION 表示设置位置)* @param params 包含位置或方向的浮点数数组(长度为4)* @param offset 数组起始偏移量*/
public abstract void glLightfv(int light, int pname, float[] params, int offset);

第三个参数:
格式为 [x, y, z, w],其中:
x, y, z:表示三维空间中的坐标或方向向量;
w 的值决定光源类型:
w = 0:表示方向光(平行光),此时 [x,y,z] 表示光线方向(如太阳);
w = 1:表示位置光(点光源 / 聚光灯),此时 [x,y,z] 表示光源在世界坐标系中的位置。

  • 平行光
    所有光线平行,无具体位置,仅需指定方向;
    光照强度不随距离衰减,适用于模拟太阳等远距离光源。

  • 点光源(Point Light)
    光线从 [x,y,z] 位置向四面八方发散;
    光照强度随距离衰减(默认按 1/d² 衰减,可通过 GL_ATTENUATION 调整)。

  • 聚光灯(Spotlight)(需要额外设置光源的角度和方向)
    光线从位置 [x,y,z] 出发,沿 SPOT_DIRECTION 指定方向呈锥形发射;
    仅当物体位于锥角内时才会被照亮。
    效果:光源随相机移动,类似手电筒效果。

光源的方向

        // 光源的方向val lightDirection = floatArrayOf(0f, 0f, 1f)gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPOT_DIRECTION, lightDirection, 0)

这个函数用于设置 OpenGL 中指定光源(这里是GL_LIGHT0)的聚光灯方向向量。聚光灯是一种有方向的锥形光源,只有位于锥形区域内的物体才会被照亮,方向向量决定了这个锥形的朝向。

  • GL10.GL_LIGHT0:指定要配置的光源编号。OpenGL ES 1.0 最多支持 8 个光源(GL_LIGHT0到GL_LIGHT7)。
  • GL10.GL_SPOT_DIRECTION:指定要设置的光源属性为聚光灯方向。
  • lightDirection:一个包含三个浮点数的数组[x, y, z],表示聚光灯的方向向量。这个向量从光源位置出发,指向聚光灯照射的方向。
  • 0:数组lightDirection的起始偏移量,表示从数组的第 0 个元素开始读取方向向量数据。

使用场景
聚光灯常用于模拟手电筒、舞台灯光等效果。例如,在游戏中模拟角色手持电筒的照明效果,或者在 3D 模型展示中突出显示特定区域。

聚光截止角 (Spot Cutoff Angle):

        gl.glLightf(GL10.GL_LIGHT0, GL10.GL_SPOT_CUTOFF, 90F)

设置聚光灯特效的光线角度。第三个参数代表角度,中心线到边缘的角度,所以最大可以设置180度(代表360度可以照射)。角度外的物体无法照射光源。一般配合光源位置角度一起使用。

顶点法线

在 Android OpenGL ES 中,顶点法线(Vertex Normal)是实现光照效果的关键概念,它定义了顶点表面的方向。下面从基础概念到代码实现进行详细说明:

  1. 顶点法线的作用
  • 定义表面方向:法线是垂直于顶点所在表面的单位向量(长度为 1)。
  • 计算光照:OpenGL 使用法线来确定光线与表面的夹角,从而计算反射光强度。
  • 平滑着色:通过顶点法线插值,可实现平滑的光照过渡(如 Phong 着色)
  1. 法线的数学特性
  • 单位向量:法线必须归一化(长度为 1),否则光照计算会失真。
  • 方向敏感性:法线方向决定表面朝向(正面 / 背面),影响光照和背面剔除。
  • 多边形面法线:对三角形等平面,所有顶点共享同一个法线(如立方体的面)。
  • 平滑表面法线:对曲面(如球体),每个顶点有不同的法线以实现平滑过渡。
  1. 常见问题
    表面全黑:可能是法线未归一化,或方向错误。
    光照不均匀:检查法线插值是否正确,或是否需要平滑着色。
    背面显示异常:确认背面剔除设置和法线方向是否一致。

完整代码

下面是包括上面各种光源设置的代码,包括顶点法线:

class CubeRen2 : GLSurfaceView.Renderer {private var normals: FloatBufferprivate val vertexBuffer: FloatBufferprivate val indexBuffer: ShortBufferprivate val colorBuffer: FloatBuffer// 正方体的8个顶点坐标private val vertices = floatArrayOf(-0.5f, -0.5f, -0.5f,  // 左下后 V00.5f, -0.5f, -0.5f,  // 右下后 V10.5f, 0.5f, -0.5f,  // 右上后 V2-0.5f, 0.5f, -0.5f,  // 左上后 V3-0.5f, -0.5f, 0.5f,  // 左下前 V40.5f, -0.5f, 0.5f,  // 右下前 V50.5f, 0.5f, 0.5f,  // 右上前 V6-0.5f, 0.5f, 0.5f // 左上前 V7)// 正方体12个三角形的顶点索引(两个三角形组成一个面)private val indices = shortArrayOf(0, 1, 2, 0, 2, 3,  // 后面1, 5, 6, 1, 6, 2,  // 右面5, 4, 7, 5, 7, 6,  // 前面4, 0, 3, 4, 3, 7,  // 左面3, 2, 6, 3, 6, 7,  // 上面4, 5, 1, 4, 1, 0 // 下面)// 每个顶点的颜色(RGBA)private val colors = floatArrayOf(0.0f, 0.0f, 0.0f, 1.0f,  // V0黑色1.0f, 0.0f, 0.0f, 1.0f,  // V1红色1.0f, 1.0f, 0.0f, 1.0f,  // V2黄色0.0f, 1.0f, 0.0f, 1.0f,  // V3绿色0.0f, 0.0f, 1.0f, 1.0f,  // V4蓝色1.0f, 0.0f, 1.0f, 1.0f,  // V5紫色1.0f, 1.0f, 1.0f, 1.0f,  // V6白色0.0f, 1.0f, 1.0f, 1.0f // V7青色)private var angleX = 0fprivate var angleY = 0finit {// 初始化顶点缓冲区val vbb = ByteBuffer.allocateDirect(vertices.size * 4)vbb.order(ByteOrder.nativeOrder())vertexBuffer = vbb.asFloatBuffer()vertexBuffer.put(vertices)vertexBuffer.position(0)// 初始化索引缓冲区val ibb = ByteBuffer.allocateDirect(indices.size * 2)ibb.order(ByteOrder.nativeOrder())indexBuffer = ibb.asShortBuffer()indexBuffer.put(indices)indexBuffer.position(0)// 初始化颜色缓冲区val cbb = ByteBuffer.allocateDirect(colors.size * 4)cbb.order(ByteOrder.nativeOrder())colorBuffer = cbb.asFloatBuffer()colorBuffer.put(colors)colorBuffer.position(0)// 计算法线数组val normalsFloat = calculateNormals(vertices, indices)val normalBuffer = ByteBuffer.allocateDirect(normalsFloat.size * 4)normalBuffer.order(ByteOrder.nativeOrder())normals = normalBuffer.asFloatBuffer()normals.put(normalsFloat)normals.position(0)}// 根据顶点和索引计算法线private fun calculateNormals(vertices: FloatArray, indices: ShortArray): FloatArray {// 初始化法线数组(每个顶点对应一个法线向量)val normals = FloatArray(vertices.size)// 临时存储每个顶点的法线累加值val tempNormals = Array(vertices.size / 3) { FloatArray(3) { 0.0f } }// 遍历每个三角形for (i in indices.indices step 3) {val i0 = indices[i].toInt()val i1 = indices[i + 1].toInt()val i2 = indices[i + 2].toInt()// 获取三角形的三个顶点坐标val v0 = floatArrayOf(vertices[i0 * 3],vertices[i0 * 3 + 1],vertices[i0 * 3 + 2])val v1 = floatArrayOf(vertices[i1 * 3],vertices[i1 * 3 + 1],vertices[i1 * 3 + 2])val v2 = floatArrayOf(vertices[i2 * 3],vertices[i2 * 3 + 1],vertices[i2 * 3 + 2])// 计算边向量val edge1 = floatArrayOf(v1[0] - v0[0],v1[1] - v0[1],v1[2] - v0[2])val edge2 = floatArrayOf(v2[0] - v0[0],v2[1] - v0[1],v2[2] - v0[2])// 计算面法线(叉乘)val faceNormal = floatArrayOf(edge1[1] * edge2[2] - edge1[2] * edge2[1],edge1[2] * edge2[0] - edge1[0] * edge2[2],edge1[0] * edge2[1] - edge1[1] * edge2[0])// 累加面法线到每个顶点for (j in 0..2) {tempNormals[i0][j] += faceNormal[j]tempNormals[i1][j] += faceNormal[j]tempNormals[i2][j] += faceNormal[j]}}// 归一化每个顶点的法线for (i in tempNormals.indices) {val normal = tempNormals[i]val length = Math.sqrt((normal[0] * normal[0] +normal[1] * normal[1] +normal[2] * normal[2]).toDouble()).toFloat()if (length > 0) {normal[0] /= lengthnormal[1] /= lengthnormal[2] /= length}// 将归一化后的法线存入结果数组normals[i * 3] = normal[0]normals[i * 3 + 1] = normal[1]normals[i * 3 + 2] = normal[2]}return normals}override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {// 设置清屏颜色为灰色gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f)// 启用深度测试gl.glEnable(GL10.GL_DEPTH_TEST)// 启用顶点和颜色数组gl.glEnableClientState(GL10.GL_VERTEX_ARRAY)gl.glEnableClientState(GL10.GL_COLOR_ARRAY)setupLight(gl)}/*** 设置光效*/private fun setupLight(gl: GL10) {// 启用光照和材质颜色追踪gl.glEnable(GL10.GL_LIGHTING)gl.glEnable(GL10.GL_LIGHT0)gl.glEnable(GL10.GL_COLOR_MATERIAL)gl.glEnable(GL10.GL_SPECULAR)gl.glEnable(GL10.GL_SPOT_CUTOFF)// 设置环境光val ambientLight = floatArrayOf(0.2f, 0.2f, 0.2f, 1.0f)gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, ambientLight, 0)// 设置漫反射光val diffuseLight = floatArrayOf(0.8f, 0.8f, 0.8f, 1.0f)gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, diffuseLight, 0)// 设置高光val specularLight = floatArrayOf(1.0f, 1.0f, 1.0f, 1.0f)gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, specularLight, 0)// 设置光源位置val lightPosition = floatArrayOf(0.0f, 0.0f, -1.0f, 0.0f) // 方向光gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosition, 0)// 光源的方向val lightDirection = floatArrayOf(0f, 0f, 1f)gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPOT_DIRECTION, lightDirection, 0)gl.glLightf(GL10.GL_LIGHT0, GL10.GL_SPOT_CUTOFF, 10F)}override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {// 设置视口大小gl.glViewport(0, 0, width, height)// 设置投影矩阵gl.glMatrixMode(GL10.GL_PROJECTION)gl.glLoadIdentity()// 设置透视投影val aspectRatio = width.toFloat() / heightGLU.gluPerspective(gl, 45.0f, aspectRatio, 0.1f, 1000.0f)// 设置模型视图矩阵gl.glMatrixMode(GL10.GL_MODELVIEW)gl.glLoadIdentity()}override fun onDrawFrame(gl: GL10) {// 清除颜色和深度缓冲区gl.glClear(GL10.GL_COLOR_BUFFER_BIT or GL10.GL_DEPTH_BUFFER_BIT)gl.glLoadIdentity()// 设置着色模式gl.glShadeModel(GL10.GL_SMOOTH)// 设置观察位置gl.glTranslatef(0.0f, 0f, -5.0f)gl.glRotatef(angleX, 1.0f, 0.0f, 0.0f)gl.glRotatef(angleY, 0.0f, 1.0f, 0.0f)// 旋转正方体angleX += 1.0fangleY += 0.5f// 启用法线数组gl.glEnableClientState(GL10.GL_NORMAL_ARRAY)gl.glNormalPointer(GL10.GL_FLOAT, 0, normals)// 设置顶点和颜色指针gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer)gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer)// 绘制正方体gl.glDrawElements(GL10.GL_TRIANGLES, indices.size,GL10.GL_UNSIGNED_SHORT, indexBuffer)// 禁用法线数组gl.glDisableClientState(GL10.GL_NORMAL_ARRAY)}
}

效果和前一篇文章差不多,区别就是根据各种光源设置,显示的光亮等程度有变化。

http://www.lryc.cn/news/572359.html

相关文章:

  • 输入url之后发生了什么
  • c++ STL---vector使用
  • 为什么 C++ 11 引入了 `nullptr`
  • day037-openssh服务与http协议
  • 2025实时数据同步:多平台商品信息接口的高效更新技术解析
  • jquery 赋值时不触发change事件解决——仙盟创梦IDE
  • Python——PyQt5初体验
  • LVS 负载均衡群集
  • LeetCode | 二分法题型详解+图解
  • bos_token; eos_token; pad_token是什么
  • QSqlDatabase: QSQLITE driver not loaded
  • infinisynapse 使用清华源有问题的暂时解决方法:换回阿里云源并安装配置PPA
  • LoRA 浅析
  • Python Beautiful Soup 4【HTML/XML解析库】 简介
  • StableDiffusion实战-手机壁纸制作 第一篇:从零基础到生成艺术品的第一步!
  • Hexo 个人博客配置记录(GitHub Pages + Butterfly 主题 + Waline 评论 + 自动部署)
  • Kernel K-means:让K-means在非线性空间“大显身手”
  • 职坐标IT培训:嵌入式AI物联网开源项目精选
  • 基于大模型的急性结石性胆囊炎全流程预测与诊疗方案研究
  • 【图像处理入门】11. 深度学习初探:从CNN到GAN的视觉智能之旅
  • 跟着AI学习C# Day22
  • 实时输出subprocess.Popen运行程序的日志
  • 永磁同步电机无速度算法--基于正切函数锁相环的滑模观测器
  • 【鸿蒙HarmonyOS Next App实战开发】​​​​ArkUI纯色图生成器
  • VACM 详解:SNMPv3 的访问控制核心
  • 回溯----8.N皇后
  • C++ std::set的用法
  • 根据图片理解maven
  • FocalAD论文阅读
  • SpringBoot 应用开发核心分层架构与实战详解