OpenGL ES 中的材质

文章目录

上一期讲了OpenGL ES的光效效果,材质的属性会影响我们看到物体的颜色效果,下面整理了关于OpenGL材质的基础知识点

在 OpenGL ES 中,材质(Material)用于定义物体表面对光照的反应方式,它决定了物体的外观特性(如颜色、光泽度、反射率等)。材质与光照模型结合,能够模拟出金属、塑料、玻璃等不同质感的物体。

我们通常通过定义材质的反射光来定义0penGLES中的材质,正如现实世界中一样。如果一个材质定义为反射红光, 那么在正常的白光下,它将显示红色。在0penGL中(至少在使用光滑着色处理和光效时),材质是没有颜色的。OpenGL具有分别定义材质是怎样反射0penGL光效三要素(环境,散射和高光)的能力。另外,它还具有指定材质自发光(emissive)属性的能力。

下面是不开启默认材质的效果(注释掉开启材质代码 gl.glEnable(GL10.GL_COLOR_MATERIAL),只能看到一个简单的颜色。

指定材质

通常使用 glMaterialfv 和 glMaterialf 指定颜色材质,第一个通常和颜色有关,第二个一般设置光泽度。下面代码是增加了glMaterialfv 颜色是 0.1f, 0.9f, 1.0f ,偏向蓝色。

bash 复制代码
    private fun setMaterial(gl: GL10) {
        // 设置环境元素和散射元素的颜色  RGBA 这里设置的比较便蓝色
        val ambientLight = floatArrayOf(0f, 0.1f, 0.9f, 1.0f)
        // 设置材质环境元素和反射元素
        // 第一个参数:GL10.GL_FRONT_AND_BACK 设置面,都会设置成前后
        // 第二个参数:GL10.GL_AMBIENT_AND_DIFFUSE 设置环境元素和反射元素 还可以设置  GL10.GL_AMBIENT\GL10.GL_DIFFUSE
        // 被感知的颜色一般同时经过环境元素和反射元素所影响。
        // 第三个参数:设置颜色
        // 第四个参数:偏移量,如果使用重载的buffer传入则就前面三个参数
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT_AND_DIFFUSE, ambientLight, 0)
    }

下面是增加了这段代码后,我们看到的正方体的颜色。因为我们制定反射的颜色是蓝色,所以光源照射到以后看起来就是蓝色的。看到蓝色有深有浅就是因为我们环境光和反射光共同作用的效果。

单独设置环境元素和反射元素

bash 复制代码
    private fun setMaterial(gl: GL10) {
        // 环境元素  蓝色
        val ambientLight = floatArrayOf(0f, 0.1f, 0.9f, 1.0f)
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, ambientLight, 0)
        // 散射元素  红色
        val diffuseLight = floatArrayOf(0.9f, 0f, 0.1f, 1.0f)
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, diffuseLight, 0)
    }

看一下效果如下,红色是散射元素作用,蓝色是环境元素作用。

高光元素

还可以单独设置场景中高光元素的反射方式,从而控制高光"热点"的亮度。一个叫GL_SHININESS的参数与材质的高光元素一起定义了高光热点的大小。如果你设定了材质的GL_SPECULAR值,你还应该定义其反光度。反光度越高,高光反射越小,所以默认值0.0几乎完全淹没了散射光。高光使三角形边缘突出,高光通常在我们的游戏中经常使用的低面片的物体上表现不佳。在常规0penGL中,有一个称为 着色器(shader)的机制可以用来为低面片物体产生较为理想的结果,但是只有OpenGL ES 2.0,有shader功能。

bash 复制代码
 		// 设置高光元素 黄色
        val specularLight = floatArrayOf(0.9f, 0.9f, 0f, 1.0f)
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, specularLight, 0)
        // 设置反光度 反射度越高 高光反射越小
        gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 25F )

高光看起来效果并不是很明显。

自发光

通过设定自发光元素,使得材质看上去会发射我们指定的颜色。它并不是真正在发光。例如,其周边物体并不会被发射的光线影响。如果你希望一个物体像灯泡一样发光照亮其他物体,你需要将自发光元素和与物体同一位置处的实际光源结合起来。但是自发光元素可以使物体漂亮地发光。

自发光元素影响整个材质,因此 GL_EMISSION的值将与落入物体指定区域的任何类型的光相叠加。

bash 复制代码
        // 自发光  绿色
        val emissionLight = floatArrayOf(0.0f, 0.4f, 0f, 1.0f)
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_EMISSION, emissionLight, 0)

效果如下:颜色叠加后的效果。

总结

材质设置方法 gl.glMaterialfv()和 gl.glMaterialf() ,由环境元素、散射元素、高光元素、自发光共同影响显示效果。

  1. face

    指定材质应用于哪些面,可选值:

    GL_FRONT:仅应用于正面(默认逆时针方向为正面)。

    GL_BACK:仅应用于背面。

    GL_FRONT_AND_BACK:同时应用于正面和背面。

  2. pname

    指定要设置的材质属性类型,常用值:

pname 含义 常用值
GL_AMBIENT 环境光颜色 RGB/RGBA 数组
GL_DIFFUSE 漫反射颜色 RGB/RGBA 数组
GL_SPECULAR 镜面反射颜色 RGB/RGBA 数组
GL_EMISSION 自发光颜色(物体自身发光) RGB/RGBA 数组
GL_AMBIENT_AND_DIFFUSE 同时设置环境光和漫反射颜色 RGB/RGBA 数组
GL_SHININESS 光泽度(镜面高光大小) 单个浮点数(需用glMaterialf)
  1. params

    包含属性值的浮点数组,数组长度和含义取决于pname:

    RGB 格式:长度为 3 的数组[R, G, B],每个值范围[0.0, 1.0]。

    RGBA 格式:长度为 4 的数组[R, G, B, A],A 表示透明度。

  2. offset

    数组的起始偏移量(从第几个元素开始读取),通常为 0。

总体代码

bash 复制代码
package com.e.opengl

import android.opengl.GLSurfaceView
import android.opengl.GLU
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import java.nio.ShortBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10

class CubeRen2 : GLSurfaceView.Renderer {
    private var normals: FloatBuffer
    private val vertexBuffer: FloatBuffer
    private val indexBuffer: ShortBuffer
    private val colorBuffer: FloatBuffer

    // 正方体的8个顶点坐标
    private val vertices = floatArrayOf(
        -0.5f, -0.5f, -0.5f,  // 左下后 V0
        0.5f, -0.5f, -0.5f,  // 右下后 V1
        0.5f, 0.5f, -0.5f,  // 右上后 V2
        -0.5f, 0.5f, -0.5f,  // 左上后 V3
        -0.5f, -0.5f, 0.5f,  // 左下前 V4
        0.5f, -0.5f, 0.5f,  // 右下前 V5
        0.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 = 0f
    private var angleY = 0f

    init {
        // 初始化顶点缓冲区

        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] /= length
                normal[1] /= length
                normal[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)
        setMaterial(gl)
    }
	/**
	* 材质属性
	*/
    private fun setMaterial(gl: GL10) {
//        // 设置环境元素和散射元素的颜色  RGBA 这里设置的比较便蓝色
//        val ambientLight = floatArrayOf(0f, 0.1f, 0.9f, 1.0f)
//        // 设置材质环境元素和反射元素
//        // 第一个参数:GL10.GL_FRONT_AND_BACK 设置面,都会设置成前后
//        // 第二个参数:GL10.GL_AMBIENT_AND_DIFFUSE 设置环境元素和反射元素 还可以设置  GL10.GL_AMBIENT\GL10.GL_DIFFUSE
//        // 被感知的颜色一般同时经过环境元素和反射元素所影响。
//        // 第三个参数:设置颜色
//        // 第四个参数:偏移量,如果使用重载的buffer传入则就前面三个参数
//        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT_AND_DIFFUSE, ambientLight, 0)

        // 环境元素  蓝色
        val ambientLight = floatArrayOf(0f, 0.1f, 0.9f, 1.0f)
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, ambientLight, 0)
        // 散射元素  红色
        val diffuseLight = floatArrayOf(0.9f, 0f, 0.1f, 1.0f)
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, diffuseLight, 0)
        // 设置高光元素 黄色
        val specularLight = floatArrayOf(0.9f, 0.9f, 0f, 1.0f)
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, specularLight, 0)
        // 设置反光度 反射度越高 高光反射越小
        gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 25F )

        // 自发光  绿色
        val emissionLight = floatArrayOf(0.0f, 0.4f, 0f, 1.0f)
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_EMISSION, emissionLight, 0)

    }

    /**
     * 设置光效
     */
    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() / height
        GLU.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.0f
        angleY += 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)
    }
}
相关推荐
安东尼肉店4 小时前
Android compose屏幕适配终极解决方案
android
2501_916007474 小时前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun5 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户2018792831679 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子9 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822710 小时前
安卓接入Max广告源
android
齊家治國平天下10 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO10 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel10 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢10 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱